feat: harden public OpenAI API contracts
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
"""Model discovery for assistants exposed through the public API."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from db.models import Assistant
|
||||
from db.session import SessionLocal
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from services.openai_api import (
|
||||
PUBLIC_API_BEARER,
|
||||
PUBLIC_ERROR_RESPONSES,
|
||||
REQUEST_ID_RESPONSE_HEADERS,
|
||||
openai_error_response,
|
||||
)
|
||||
from services.openai_realtime.auth import (
|
||||
RealtimeAuthError,
|
||||
authenticate_bearer,
|
||||
bearer_from_authorization,
|
||||
)
|
||||
from services.openai_responses.schemas import ModelList
|
||||
from sqlalchemy import select
|
||||
|
||||
|
||||
PUBLIC_ASSISTANT_TYPES = {"prompt", "workflow", "dify", "fastgpt"}
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/v1/models",
|
||||
tags=["openai-models"],
|
||||
dependencies=[Depends(PUBLIC_API_BEARER)],
|
||||
)
|
||||
|
||||
|
||||
async def _authenticate(request: Request) -> None:
|
||||
token = bearer_from_authorization(request.headers.get("authorization"))
|
||||
async with SessionLocal() as db:
|
||||
credential = await authenticate_bearer(db, token)
|
||||
if credential.ephemeral:
|
||||
raise RealtimeAuthError("A long-lived API key is required")
|
||||
|
||||
|
||||
def _capabilities(assistant: Assistant) -> list[str]:
|
||||
capabilities = ["realtime"]
|
||||
if assistant.runtime_mode == "pipeline":
|
||||
capabilities.insert(0, "responses")
|
||||
return capabilities
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=ModelList,
|
||||
responses={
|
||||
200: {"headers": REQUEST_ID_RESPONSE_HEADERS},
|
||||
**PUBLIC_ERROR_RESPONSES,
|
||||
},
|
||||
summary="List assistants available through the public API",
|
||||
)
|
||||
async def list_models(request: Request):
|
||||
try:
|
||||
await _authenticate(request)
|
||||
async with SessionLocal() as db:
|
||||
assistants = (
|
||||
await db.execute(
|
||||
select(Assistant)
|
||||
.where(
|
||||
Assistant.type.in_(PUBLIC_ASSISTANT_TYPES),
|
||||
Assistant.runtime_mode.in_(("pipeline", "realtime")),
|
||||
)
|
||||
.order_by(Assistant.created_at.asc(), Assistant.id.asc())
|
||||
)
|
||||
).scalars().all()
|
||||
return {
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"id": f"assistant:{assistant.id}",
|
||||
"object": "model",
|
||||
"created": int(assistant.created_at.timestamp()),
|
||||
"owned_by": "interactive-media",
|
||||
"name": assistant.name,
|
||||
"capabilities": _capabilities(assistant),
|
||||
"assistant_id": assistant.id,
|
||||
"assistant_type": assistant.type,
|
||||
"runtime_mode": assistant.runtime_mode,
|
||||
}
|
||||
for assistant in assistants
|
||||
],
|
||||
}
|
||||
except RealtimeAuthError as exc:
|
||||
return openai_error_response(
|
||||
str(exc),
|
||||
code="invalid_api_key",
|
||||
status_code=401,
|
||||
)
|
||||
Reference in New Issue
Block a user