""" Responses router. OpenAI-compatible /v1/responses endpoint with streaming support. """ import logging from fastapi import APIRouter, HTTPException from sse_starlette.sse import EventSourceResponse from src.responses import service from src.responses.schemas import ResponseRequest, Response from src.core.exceptions import ModelNotFoundError, AppException logger = logging.getLogger(__name__) router = APIRouter(prefix="/responses", tags=["responses"]) @router.post("", response_model=Response) async def create_response( request: ResponseRequest, ) -> Response | EventSourceResponse: """ Create a response using Responses API format. Supports: - Reasoning summaries (thinking/reasoning display) - Function calling (tool usage) - Streaming responses - Multi-turn conversations - Error handling Args: request: Response request with model, input, optional reasoning/tools Returns: Response object or SSE stream Example non-streaming request: POST /v1/responses { "model": "lorem-tester", "input": [{"role": "user", "content": "Hello"}], "reasoning": {"effort": "medium", "summary": "auto"}, "stream": false } Example streaming request: POST /v1/responses { "model": "lorem-tester", "input": [{"role": "user", "content": "Hello"}], "stream": true } Response format (non-streaming): { "id": "resp_...", "object": "response", "created_at": 1733529600, "model": "lorem-tester", "status": "completed", "output": [ { "type": "reasoning", "id": "rs_...", "summary": ["Analyzing...", "Considering..."] }, { "type": "message", "id": "msg_...", "role": "assistant", "content": [{"type": "output_text", "text": "Lorem ipsum..."}] } ], "usage": { "input_tokens": 10, "output_tokens": 50, "reasoning_tokens": 20, "total_tokens": 80 } } Streaming format (SSE): event: response.reasoning_summary_text.delta data: {"delta": "Analyzing..."} event: response.output_text.delta data: {"delta": "Lorem"} event: response.done data: {"response": {...}} """ logger.info(f"Response request for model: {request.model}") try: # Check if this is a Tatlock request - use Steward preprocessing (Phase 2) model_id = request.model if "." in model_id: model_id = model_id.split(".", 1)[1] use_steward = model_id.lower() == "tatlock" if request.stream: logger.info("Streaming response requested") if use_steward: logger.info("Streaming with Steward preprocessing for Tatlock request") # Use Steward + Tatlock streaming (Milestone 3.5) from src.responses.streaming import StreamingCoordinator coordinator = StreamingCoordinator() return EventSourceResponse( coordinator.stream_response_with_steward(request) ) else: # Regular streaming for non-Tatlock models return EventSourceResponse( service.create_response_stream(request) ) # Use appropriate service method if use_steward: logger.info("Using Steward preprocessing for Tatlock request") return await service.create_response_with_steward(request) else: return await service.create_response(request) except ModelNotFoundError as e: logger.error(f"Model not found: {e}") raise HTTPException(status_code=404, detail=str(e)) except AppException as e: logger.error(f"Application error: {e}") raise HTTPException(status_code=e.status_code, detail=e.message) except Exception as e: logger.error(f"Unexpected error: {e}", exc_info=True) raise HTTPException(status_code=500, detail="Internal server error")