feat: harden public OpenAI API contracts
Quality gate / backend (push) Canceled after 0s
Quality gate / frontend (push) Canceled after 0s

This commit is contained in:
Xin Wang
2026-09-07 14:25:48 +08:00
parent 8025ed8aa0
commit 2649d64b55
36 changed files with 16643 additions and 236 deletions
+157 -10
View File
@@ -18,9 +18,11 @@ from services.openai_responses.protocol import (
)
from services.realtime.launcher import resolve_assistant_config
from sqlalchemy import func, select, update
from sqlalchemy.exc import IntegrityError
CONTINUABLE_STATUSES = {"completed"}
IDEMPOTENCY_CONSTRAINT = "uq_openai_response_idempotency"
@dataclass(frozen=True)
@@ -33,6 +35,26 @@ class PreparedResponse:
client_tool_results: list[dict[str, Any]]
def _integrity_constraint_name(error: IntegrityError) -> str | None:
current = getattr(error, "orig", None)
seen: set[int] = set()
while current is not None and id(current) not in seen:
seen.add(id(current))
direct = getattr(current, "constraint_name", None)
if direct:
return str(direct)
diagnostic = getattr(current, "diag", None)
value = getattr(diagnostic, "constraint_name", None)
if value:
return str(value)
current = getattr(current, "__cause__", None) or getattr(
current,
"__context__",
None,
)
return None
def _content_text(item: dict[str, Any]) -> str:
return "\n".join(
str(part.get("text") or "")
@@ -97,6 +119,7 @@ async def _load_chain(
seen: set[str] = set()
async with SessionLocal() as db:
current_id: str | None = response_id
is_requested_response = True
while current_id:
if current_id in seen or len(chain) >= 1000:
raise ResponsesAPIError(
@@ -104,12 +127,17 @@ async def _load_chain(
)
seen.add(current_id)
row = await db.get(OpenAIResponse, current_id)
if row is None or row.api_key_id != api_key_id:
if (
row is None
or row.api_key_id != api_key_id
or (is_requested_response and row.deleted_at is not None)
):
raise ResponsesAPIError(
"Response not found", code="response_not_found", status_code=404
)
chain.append(row)
current_id = row.previous_response_id
is_requested_response = False
chain.reverse()
return chain
@@ -158,17 +186,23 @@ def _client_tool_results(
chain: list[OpenAIResponse],
current_items: list[dict[str, Any]],
) -> list[dict[str, Any]]:
calls = {
str(item.get("call_id") or ""): item
calls = [
item
for row in chain
for item in row.output_items or []
if item.get("type") == "function_call"
]
outputs = {
str(item.get("call_id") or ""): item
for item in current_items
if item.get("type") == "function_call_output"
}
results: list[dict[str, Any]] = []
for item in current_items:
if item.get("type") != "function_call_output":
for call in calls:
call_id = str(call.get("call_id") or "")
item = outputs.get(call_id)
if item is None:
continue
call = calls.get(str(item.get("call_id") or "")) or {}
output = str(item.get("output") or "")
try:
value = json.loads(output)
@@ -178,8 +212,9 @@ def _client_tool_results(
value = {"status": "ok", "data": value}
results.append(
{
"call_id": item.get("call_id"),
"call_id": call_id,
"name": call.get("name"),
"arguments": call.get("arguments"),
"result": value,
}
)
@@ -190,6 +225,8 @@ async def prepare_response(
request: ResponseRequest,
*,
api_key_id: str,
idempotency_key: str | None = None,
request_hash: str | None = None,
) -> PreparedResponse:
chain: list[OpenAIResponse] = []
parent: OpenAIResponse | None = None
@@ -221,7 +258,14 @@ async def prepare_response(
)
assistant_id = parent.assistant_id
if request.model:
requested_id = assistant_id_from_model(request.model)
try:
requested_id = assistant_id_from_model(request.model)
except ValueError as exc:
raise ResponsesAPIError(
str(exc),
code="invalid_model",
param="model",
) from exc
if requested_id != assistant_id:
raise ResponsesAPIError(
"model must match the previous response",
@@ -307,6 +351,8 @@ async def prepare_response(
context_messages=[],
runtime_state={},
dynamic_variables=effective_variables,
idempotency_key=idempotency_key,
request_hash=request_hash,
)
async with SessionLocal() as db:
if parent is None:
@@ -334,7 +380,21 @@ async def prepare_response(
conversation.status = "active"
conversation.ended_at = None
db.add(row)
await db.commit()
try:
await db.commit()
except IntegrityError as exc:
await db.rollback()
if (
idempotency_key is not None
and _integrity_constraint_name(exc) == IDEMPOTENCY_CONSTRAINT
):
raise ResponsesAPIError(
"Idempotency-Key is already in use",
code="idempotency_key_in_use",
param="Idempotency-Key",
status_code=409,
) from exc
raise
await db.refresh(row)
return PreparedResponse(
@@ -351,16 +411,99 @@ async def get_response(
response_id: str,
*,
api_key_id: str,
include_deleted: bool = False,
) -> OpenAIResponse:
async with SessionLocal() as db:
row = await db.get(OpenAIResponse, response_id)
if row is None or row.api_key_id != api_key_id:
if (
row is None
or row.api_key_id != api_key_id
or (row.deleted_at is not None and not include_deleted)
):
raise ResponsesAPIError(
"Response not found", code="response_not_found", status_code=404
)
return row
async def get_idempotent_response(
*,
api_key_id: str,
idempotency_key: str | None,
request_hash: str,
) -> OpenAIResponse | None:
"""Return a terminal replay or reject conflicting reuse of a request key."""
if idempotency_key is None:
return None
async with SessionLocal() as db:
row = (
await db.execute(
select(OpenAIResponse).where(
OpenAIResponse.api_key_id == api_key_id,
OpenAIResponse.idempotency_key == idempotency_key,
)
)
).scalar_one_or_none()
if row is None:
return None
if row.request_hash != request_hash:
raise ResponsesAPIError(
"Idempotency-Key was already used with a different request",
code="idempotency_key_conflict",
param="Idempotency-Key",
status_code=409,
)
if row.deleted_at is not None:
raise ResponsesAPIError(
"The response created by this Idempotency-Key was deleted",
code="idempotency_response_deleted",
param="Idempotency-Key",
status_code=409,
)
if row.status == "in_progress":
raise ResponsesAPIError(
"A request with this Idempotency-Key is still in progress",
code="idempotency_key_in_use",
param="Idempotency-Key",
status_code=409,
)
return row
async def delete_response(
response_id: str,
*,
api_key_id: str,
) -> OpenAIResponse:
"""Create a tombstone while retaining state needed by existing branches."""
async with SessionLocal() as db:
row = await db.get(
OpenAIResponse,
response_id,
with_for_update=True,
)
if (
row is None
or row.api_key_id != api_key_id
or row.deleted_at is not None
):
raise ResponsesAPIError(
"Response not found", code="response_not_found", status_code=404
)
if row.status == "in_progress":
raise ResponsesAPIError(
"An in-progress response cannot be deleted",
code="response_not_ready",
status_code=409,
)
row.deleted_at = datetime.now(UTC)
await db.commit()
await db.refresh(row)
return row
async def finish_response(
response_id: str,
*,
@@ -370,6 +513,7 @@ async def finish_response(
dynamic_variables: dict[str, Any] | None = None,
runtime_state: dict[str, Any] | None = None,
error: dict[str, Any] | None = None,
usage: dict[str, Any] | None = None,
) -> OpenAIResponse:
now = datetime.now(UTC)
async with SessionLocal() as db:
@@ -384,6 +528,7 @@ async def finish_response(
if runtime_state is not None:
row.runtime_state = runtime_state
row.error = error
row.usage = usage
row.completed_at = now
conversation = (
@@ -445,6 +590,8 @@ async def finish_response(
await db.commit()
await db.refresh(row)
return row
async def recover_interrupted_responses() -> None:
"""Mark generations interrupted by a process restart as cancelled."""
now = datetime.now(UTC)