File size: 5,088 Bytes
3493993
 
 
40e751d
3493993
 
 
 
b07a262
3493993
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
da0c6f1
3493993
 
 
 
 
 
 
 
40e751d
3493993
 
 
40e751d
 
 
 
 
3493993
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cf9dee4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b07a262
 
 
 
 
 
 
 
 
 
 
 
 
3493993
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
from __future__ import annotations

from typing import Annotated
from uuid import UUID

from fastapi import APIRouter, Header, HTTPException, Query, Request, status

from app.generation.schemas.requests import (
    GenerationJobAttemptView,
    GenerationJobView,
    GenerationProviderView,
    GenerationRequestCreate,
    GenerationRequestView,
)
from app.generation.model_registry import GenerationModelView

router = APIRouter(prefix="/v1/generation", tags=["generation"])


def _generation(request: Request):
    service = request.app.state.container.generation
    service.ensure_ready()
    return service


def _identity(request: Request) -> tuple[str, str]:
    context = request.state.auth
    if not context.workspace_id or not context.user_id:
        # The credential itself is never a workspace.  API-key middleware
        # resolves the authoritative membership before this route runs.
        raise HTTPException(status_code=403, detail="No active workspace membership.")
    return context.workspace_id, context.user_id


@router.get("/providers", response_model=list[GenerationProviderView])
async def list_providers(request: Request) -> list[GenerationProviderView]:
    return _generation(request).list_providers()


@router.get(
    "/providers/{provider}", response_model=GenerationProviderView
)
async def get_provider(request: Request, provider: str) -> GenerationProviderView:
    return _generation(request).get_provider(provider)


@router.get("/models", response_model=list[GenerationModelView])
async def list_models(
    request: Request,
    provider: str | None = Query(default=None, min_length=1, max_length=64),
) -> list[GenerationModelView]:
    return _generation(request).list_models(provider=provider)


@router.get("/providers/{provider}/models/{model_id}", response_model=GenerationModelView)
async def get_model(
    request: Request, provider: str, model_id: str
) -> GenerationModelView:
    return _generation(request).get_model(provider, model_id)


@router.post(
    "/requests",
    response_model=GenerationRequestView,
    status_code=status.HTTP_202_ACCEPTED,
)
async def create_request(
    request: Request,
    payload: GenerationRequestCreate,
    idempotency_key: Annotated[
        str, Header(alias="Idempotency-Key", min_length=8, max_length=255)
    ],
) -> GenerationRequestView:
    workspace_id, user_id = _identity(request)
    return await _generation(request).create(
        workspace_id=workspace_id,
        user_id=user_id,
        payload=payload,
        idempotency_key=idempotency_key,
        project_id=payload.project_id,
    )


@router.get("/requests", response_model=list[GenerationRequestView])
async def list_requests(
    request: Request,
    offset: int = Query(default=0, ge=0),
    limit: int = Query(default=100, ge=1, le=500),
    project_id: UUID | None = Query(default=None),
) -> list[GenerationRequestView]:
    workspace_id, user_id = _identity(request)
    return await _generation(request).list_requests(
        workspace_id,
        user_id,
        offset=offset,
        limit=limit,
        project_id=str(project_id) if project_id else None,
    )


@router.get("/requests/{generation_request_id}", response_model=GenerationRequestView)
async def get_request(
    request: Request, generation_request_id: str
) -> GenerationRequestView:
    workspace_id, user_id = _identity(request)
    return await _generation(request).get_request(
        workspace_id, user_id, generation_request_id
    )


@router.get("/jobs/{generation_job_id}", response_model=GenerationJobView)
async def get_job(request: Request, generation_job_id: str) -> GenerationJobView:
    workspace_id, user_id = _identity(request)
    return await _generation(request).get_job(
        workspace_id, user_id, generation_job_id
    )


@router.post(
    "/jobs/{generation_job_id}/retry",
    response_model=GenerationRequestView,
    status_code=status.HTTP_202_ACCEPTED,
)
async def retry_failed_job(
    request: Request,
    generation_job_id: str,
    idempotency_key: Annotated[
        str, Header(alias="Idempotency-Key", min_length=8, max_length=255)
    ],
) -> GenerationRequestView:
    workspace_id, user_id = _identity(request)
    return await _generation(request).retry_failed_job(
        workspace_id,
        user_id,
        generation_job_id,
        idempotency_key=idempotency_key,
    )


@router.get(
    "/jobs/{generation_job_id}/attempts",
    response_model=list[GenerationJobAttemptView],
)
async def list_job_attempts(
    request: Request, generation_job_id: str
) -> list[GenerationJobAttemptView]:
    workspace_id, user_id = _identity(request)
    return await _generation(request).list_job_attempts(
        workspace_id, user_id, generation_job_id
    )


@router.post(
    "/jobs/{generation_job_id}/cancel", response_model=GenerationJobView
)
async def cancel_job(request: Request, generation_job_id: str) -> GenerationJobView:
    workspace_id, user_id = _identity(request)
    return await _generation(request).cancel(
        workspace_id, user_id, generation_job_id
    )