82 lines
2.1 KiB
Python
82 lines
2.1 KiB
Python
"""Engine gateway HTTP and SSE contract.
|
|
|
|
Keep this module adapter-neutral. The Python API client, the current Python
|
|
gateway, and a future Node.js gateway must preserve these shapes.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import Any, Literal, Optional
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
AIRole = Literal["client", "counselor", "evaluator"]
|
|
EngineMessageRole = Literal["system", "user", "assistant"]
|
|
EngineGatewaySseEvent = Literal["token", "done", "error"]
|
|
|
|
ENGINE_GATEWAY_SSE_TOKEN: EngineGatewaySseEvent = "token"
|
|
ENGINE_GATEWAY_SSE_DONE: EngineGatewaySseEvent = "done"
|
|
ENGINE_GATEWAY_SSE_ERROR: EngineGatewaySseEvent = "error"
|
|
ENGINE_GATEWAY_SSE_EVENTS: tuple[EngineGatewaySseEvent, ...] = (
|
|
ENGINE_GATEWAY_SSE_TOKEN,
|
|
ENGINE_GATEWAY_SSE_DONE,
|
|
ENGINE_GATEWAY_SSE_ERROR,
|
|
)
|
|
|
|
|
|
class EngineMessage(BaseModel):
|
|
role: EngineMessageRole
|
|
content: str
|
|
cache: bool = False
|
|
|
|
|
|
class GenerateRequest(BaseModel):
|
|
ai_role: AIRole = "client"
|
|
messages: list[EngineMessage]
|
|
model: Optional[str] = None
|
|
max_tokens: int = 1024
|
|
temperature: float = 0.7
|
|
structured_schema: Optional[dict[str, Any]] = None
|
|
session_id: Optional[str] = None
|
|
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
class StreamRequest(GenerateRequest):
|
|
"""SSE stream request for live client AI turns."""
|
|
|
|
|
|
class GenerateResponse(BaseModel):
|
|
text: str
|
|
model: str
|
|
provider: str
|
|
tokens_in: int = 0
|
|
tokens_out: int = 0
|
|
cost_usd: float = 0.0
|
|
inference_geo: Optional[str] = None
|
|
structured: Optional[dict[str, Any]] = None
|
|
|
|
|
|
class StreamTokenEvent(BaseModel):
|
|
text: str
|
|
|
|
|
|
class StreamDoneEvent(BaseModel):
|
|
provider: str
|
|
model: str
|
|
tokens_in: int = 0
|
|
tokens_out: int = 0
|
|
cost_usd: float = 0.0
|
|
turns: int = 0
|
|
|
|
|
|
class StreamErrorEvent(BaseModel):
|
|
detail: str
|
|
|
|
|
|
def sse_frame(event: EngineGatewaySseEvent, payload: BaseModel | dict[str, Any]) -> str:
|
|
if isinstance(payload, BaseModel):
|
|
body = payload.model_dump()
|
|
else:
|
|
body = payload
|
|
return f"event: {event}\ndata: {json.dumps(body, ensure_ascii=False)}\n\n"
|