feat: transparent openai responses api integration
This commit is contained in:
parent
e7407b86b3
commit
b28f175b61
7 changed files with 1674 additions and 86 deletions
175
db.py
175
db.py
|
|
@ -1,4 +1,4 @@
|
|||
import aiosqlite, asyncio
|
||||
import aiosqlite, asyncio, orjson
|
||||
from typing import Optional
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timezone
|
||||
|
|
@ -75,6 +75,24 @@ class TokenDatabase:
|
|||
''')
|
||||
await db.execute('CREATE INDEX IF NOT EXISTS idx_token_time_series_timestamp ON token_time_series(timestamp)')
|
||||
await db.execute('CREATE INDEX IF NOT EXISTS idx_token_time_series_model_ts ON token_time_series(model, timestamp)')
|
||||
# Responses API state — the router owns conversation state for the
|
||||
# /v1/responses family (store / previous_response_id) and tracks
|
||||
# background-task status here so polling survives across workers.
|
||||
await db.execute('''
|
||||
CREATE TABLE IF NOT EXISTS stored_responses (
|
||||
response_id TEXT PRIMARY KEY,
|
||||
previous_response_id TEXT,
|
||||
model TEXT,
|
||||
status TEXT,
|
||||
created_at INTEGER,
|
||||
input_messages TEXT,
|
||||
output_items TEXT,
|
||||
usage TEXT,
|
||||
instructions TEXT,
|
||||
error TEXT
|
||||
)
|
||||
''')
|
||||
await db.execute('CREATE INDEX IF NOT EXISTS idx_stored_responses_prev ON stored_responses(previous_response_id)')
|
||||
await db.commit()
|
||||
|
||||
async def update_token_counts(self, endpoint: str, model: str, input_tokens: int, output_tokens: int):
|
||||
|
|
@ -319,3 +337,158 @@ class TokenDatabase:
|
|||
await db.commit()
|
||||
|
||||
return aggregated_count
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# Responses API state (store / previous_response_id / background)
|
||||
# -----------------------------------------------------------------
|
||||
@staticmethod
|
||||
def _row_to_response(row) -> dict:
|
||||
"""Map a stored_responses row to a plain dict, decoding JSON columns."""
|
||||
def _loads(val):
|
||||
if val is None:
|
||||
return None
|
||||
try:
|
||||
return orjson.loads(val)
|
||||
except (orjson.JSONDecodeError, TypeError):
|
||||
return None
|
||||
return {
|
||||
'response_id': row[0],
|
||||
'previous_response_id': row[1],
|
||||
'model': row[2],
|
||||
'status': row[3],
|
||||
'created_at': row[4],
|
||||
'input_messages': _loads(row[5]),
|
||||
'output_items': _loads(row[6]),
|
||||
'usage': _loads(row[7]),
|
||||
'instructions': row[8],
|
||||
'error': _loads(row[9]),
|
||||
}
|
||||
|
||||
async def store_response(
|
||||
self,
|
||||
response_id: str,
|
||||
*,
|
||||
previous_response_id: Optional[str],
|
||||
model: str,
|
||||
status: str,
|
||||
created_at: int,
|
||||
input_messages: list,
|
||||
output_items: Optional[list] = None,
|
||||
usage: Optional[dict] = None,
|
||||
instructions: Optional[str] = None,
|
||||
error: Optional[dict] = None,
|
||||
):
|
||||
"""Insert or replace a stored Responses-API response row."""
|
||||
db = await self._get_connection()
|
||||
async with self._operation_lock:
|
||||
await db.execute('''
|
||||
INSERT INTO stored_responses
|
||||
(response_id, previous_response_id, model, status, created_at,
|
||||
input_messages, output_items, usage, instructions, error)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (response_id) DO UPDATE SET
|
||||
previous_response_id = excluded.previous_response_id,
|
||||
model = excluded.model,
|
||||
status = excluded.status,
|
||||
created_at = excluded.created_at,
|
||||
input_messages = excluded.input_messages,
|
||||
output_items = excluded.output_items,
|
||||
usage = excluded.usage,
|
||||
instructions = excluded.instructions,
|
||||
error = excluded.error
|
||||
''', (
|
||||
response_id, previous_response_id, model, status, created_at,
|
||||
orjson.dumps(input_messages).decode("utf-8"),
|
||||
orjson.dumps(output_items).decode("utf-8") if output_items is not None else None,
|
||||
orjson.dumps(usage).decode("utf-8") if usage is not None else None,
|
||||
instructions,
|
||||
orjson.dumps(error).decode("utf-8") if error is not None else None,
|
||||
))
|
||||
await db.commit()
|
||||
|
||||
async def update_response_status(
|
||||
self,
|
||||
response_id: str,
|
||||
status: str,
|
||||
*,
|
||||
output_items: Optional[list] = None,
|
||||
usage: Optional[dict] = None,
|
||||
error: Optional[dict] = None,
|
||||
):
|
||||
"""Update the status (and optionally output/usage/error) of a stored response."""
|
||||
db = await self._get_connection()
|
||||
async with self._operation_lock:
|
||||
await db.execute('''
|
||||
UPDATE stored_responses
|
||||
SET status = ?,
|
||||
output_items = COALESCE(?, output_items),
|
||||
usage = COALESCE(?, usage),
|
||||
error = COALESCE(?, error)
|
||||
WHERE response_id = ?
|
||||
''', (
|
||||
status,
|
||||
orjson.dumps(output_items).decode("utf-8") if output_items is not None else None,
|
||||
orjson.dumps(usage).decode("utf-8") if usage is not None else None,
|
||||
orjson.dumps(error).decode("utf-8") if error is not None else None,
|
||||
response_id,
|
||||
))
|
||||
await db.commit()
|
||||
|
||||
async def get_response(self, response_id: str) -> Optional[dict]:
|
||||
"""Return a stored response as a dict, or None if not found."""
|
||||
db = await self._get_connection()
|
||||
async with self._operation_lock:
|
||||
async with db.execute('''
|
||||
SELECT response_id, previous_response_id, model, status, created_at,
|
||||
input_messages, output_items, usage, instructions, error
|
||||
FROM stored_responses WHERE response_id = ?
|
||||
''', (response_id,)) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return self._row_to_response(row) if row is not None else None
|
||||
|
||||
async def delete_response(self, response_id: str) -> bool:
|
||||
"""Delete a stored response. Returns True if a row was removed."""
|
||||
db = await self._get_connection()
|
||||
async with self._operation_lock:
|
||||
cursor = await db.execute(
|
||||
'DELETE FROM stored_responses WHERE response_id = ?', (response_id,)
|
||||
)
|
||||
await db.commit()
|
||||
return cursor.rowcount > 0
|
||||
|
||||
async def get_response_chain(self, response_id: str, max_turns: int = 50) -> list:
|
||||
"""Walk previous_response_id back to the root, returned oldest-first.
|
||||
|
||||
Bounded to ``max_turns`` so a pathological chain cannot stall a request.
|
||||
Missing links terminate the walk gracefully.
|
||||
"""
|
||||
chain: list = []
|
||||
seen: set = set()
|
||||
current = response_id
|
||||
while current and current not in seen and len(chain) < max_turns:
|
||||
seen.add(current)
|
||||
resp = await self.get_response(current)
|
||||
if resp is None:
|
||||
break
|
||||
chain.append(resp)
|
||||
current = resp.get('previous_response_id')
|
||||
chain.reverse()
|
||||
return chain
|
||||
|
||||
async def fail_orphaned_responses(self) -> int:
|
||||
"""Mark non-terminal responses as failed (called on startup).
|
||||
|
||||
A background task lives in a worker's event loop; a process restart loses
|
||||
it while the DB row stays ``queued``/``in_progress`` forever. Reconcile
|
||||
those to ``failed`` so polling clients get a terminal state.
|
||||
"""
|
||||
db = await self._get_connection()
|
||||
async with self._operation_lock:
|
||||
cursor = await db.execute('''
|
||||
UPDATE stored_responses
|
||||
SET status = 'failed',
|
||||
error = ?
|
||||
WHERE status IN ('queued', 'in_progress')
|
||||
''', (orjson.dumps({"message": "Response interrupted by server restart", "type": "server_error"}).decode("utf-8"),))
|
||||
await db.commit()
|
||||
return cursor.rowcount
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue