194 lines
6.0 KiB
Python
194 lines
6.0 KiB
Python
"""Wire-format helpers for the public Responses API subset."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import Any
|
|
|
|
from db.models import OpenAIResponse
|
|
|
|
|
|
def encode_sse(event: dict[str, Any]) -> bytes:
|
|
"""Encode one named Responses event as an SSE record."""
|
|
|
|
return (
|
|
f"event: {event['type']}\n"
|
|
f"data: {json.dumps(event, ensure_ascii=False)}\n\n"
|
|
).encode("utf-8")
|
|
|
|
|
|
class SSEEventSequence:
|
|
"""Attach the monotonically increasing sequence required by SDK models."""
|
|
|
|
def __init__(self) -> None:
|
|
self._next = 0
|
|
|
|
def encode(self, event_type: str, **payload: Any) -> bytes:
|
|
event = {
|
|
"type": event_type,
|
|
"sequence_number": self._next,
|
|
**payload,
|
|
}
|
|
self._next += 1
|
|
return encode_sse(event)
|
|
|
|
|
|
def assistant_text_item(item_id: str, text: str) -> dict[str, Any]:
|
|
return {
|
|
"id": item_id,
|
|
"type": "message",
|
|
"status": "completed",
|
|
"role": "assistant",
|
|
"content": [
|
|
{
|
|
"type": "output_text",
|
|
"text": text,
|
|
"annotations": [],
|
|
}
|
|
],
|
|
}
|
|
|
|
|
|
def build_output_items(
|
|
*,
|
|
text: str,
|
|
text_item_id: str,
|
|
function_calls: list[dict[str, Any]],
|
|
) -> list[dict[str, Any]]:
|
|
items: list[dict[str, Any]] = []
|
|
if text:
|
|
items.append(assistant_text_item(text_item_id, text))
|
|
items.extend(function_calls)
|
|
return items
|
|
|
|
|
|
def output_lifecycle_events(
|
|
row: OpenAIResponse,
|
|
*,
|
|
started_text_items: set[str],
|
|
replay_text: bool,
|
|
) -> list[dict[str, Any]]:
|
|
"""Build terminal item events shared by live and idempotent streams."""
|
|
|
|
events: list[dict[str, Any]] = []
|
|
for output_index, item in enumerate(row.output_items or []):
|
|
if item.get("type") != "message":
|
|
pending_item = {**item, "status": "in_progress"}
|
|
if item.get("type") == "function_call":
|
|
pending_item["arguments"] = ""
|
|
events.append(
|
|
{
|
|
"type": "response.output_item.added",
|
|
"output_index": output_index,
|
|
"item": pending_item,
|
|
}
|
|
)
|
|
if item.get("type") == "function_call":
|
|
item_id = str(item.get("id") or "")
|
|
arguments = str(item.get("arguments") or "")
|
|
if arguments:
|
|
events.append(
|
|
{
|
|
"type": "response.function_call_arguments.delta",
|
|
"item_id": item_id,
|
|
"output_index": output_index,
|
|
"delta": arguments,
|
|
}
|
|
)
|
|
events.append(
|
|
{
|
|
"type": "response.function_call_arguments.done",
|
|
"item_id": item_id,
|
|
"output_index": output_index,
|
|
"name": str(item.get("name") or ""),
|
|
"arguments": arguments,
|
|
}
|
|
)
|
|
events.append(
|
|
{
|
|
"type": "response.output_item.done",
|
|
"output_index": output_index,
|
|
"item": item,
|
|
}
|
|
)
|
|
continue
|
|
|
|
item_id = str(item["id"])
|
|
content = item.get("content") or []
|
|
part = content[0] if content else {
|
|
"type": "output_text",
|
|
"text": "",
|
|
"annotations": [],
|
|
}
|
|
text = str(part.get("text") or "")
|
|
if item_id not in started_text_items:
|
|
events.extend(
|
|
[
|
|
{
|
|
"type": "response.output_item.added",
|
|
"output_index": output_index,
|
|
"item": {**item, "status": "in_progress", "content": []},
|
|
},
|
|
{
|
|
"type": "response.content_part.added",
|
|
"item_id": item_id,
|
|
"output_index": output_index,
|
|
"content_index": 0,
|
|
"part": {
|
|
"type": "output_text",
|
|
"text": "",
|
|
"annotations": [],
|
|
},
|
|
},
|
|
]
|
|
)
|
|
started_text_items.add(item_id)
|
|
if replay_text and text:
|
|
events.append(
|
|
{
|
|
"type": "response.output_text.delta",
|
|
"item_id": item_id,
|
|
"output_index": output_index,
|
|
"content_index": 0,
|
|
"delta": text,
|
|
"logprobs": [],
|
|
}
|
|
)
|
|
events.extend(
|
|
[
|
|
{
|
|
"type": "response.output_text.done",
|
|
"item_id": item_id,
|
|
"output_index": output_index,
|
|
"content_index": 0,
|
|
"text": text,
|
|
"logprobs": [],
|
|
},
|
|
{
|
|
"type": "response.content_part.done",
|
|
"item_id": item_id,
|
|
"output_index": output_index,
|
|
"content_index": 0,
|
|
"part": part,
|
|
},
|
|
{
|
|
"type": "response.output_item.done",
|
|
"output_index": output_index,
|
|
"item": item,
|
|
},
|
|
]
|
|
)
|
|
return events
|
|
|
|
|
|
def stored_input_items(row: OpenAIResponse) -> list[dict[str, Any]]:
|
|
"""Backfill stable wire IDs for records created before contract v1."""
|
|
|
|
result: list[dict[str, Any]] = []
|
|
for index, stored in enumerate(row.input_items or []):
|
|
item = dict(stored)
|
|
item.setdefault("id", f"item_{row.id}_{index}")
|
|
item.setdefault("status", "completed")
|
|
result.append(item)
|
|
return result
|