- "backend/routers/chat.py" - "backend/chat_service.py" - "backend/tests/test_chat.py" GSD-Task: S02/T01
67 lines
2.1 KiB
Python
67 lines
2.1 KiB
Python
"""Chat endpoint — POST /api/v1/chat with SSE streaming response.
|
|
|
|
Accepts a query and optional creator filter, returns a Server-Sent Events
|
|
stream with sources, token, done, and error events.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, Depends
|
|
from fastapi.responses import StreamingResponse
|
|
from pydantic import BaseModel, Field
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from chat_service import ChatService
|
|
from config import Settings, get_settings
|
|
from database import get_session
|
|
from redis_client import get_redis
|
|
|
|
logger = logging.getLogger("chrysopedia.chat.router")
|
|
|
|
router = APIRouter(prefix="/chat", tags=["chat"])
|
|
|
|
|
|
class ChatRequest(BaseModel):
|
|
"""Request body for the chat endpoint."""
|
|
|
|
query: str = Field(..., min_length=1, max_length=1000)
|
|
creator: str | None = None
|
|
conversation_id: str | None = None
|
|
personality_weight: float = Field(default=0.0, ge=0.0, le=1.0)
|
|
|
|
|
|
@router.post("")
|
|
async def chat(
|
|
body: ChatRequest,
|
|
db: AsyncSession = Depends(get_session),
|
|
settings: Settings = Depends(get_settings),
|
|
) -> StreamingResponse:
|
|
"""Stream a chat response as Server-Sent Events.
|
|
|
|
SSE protocol:
|
|
- ``event: sources`` — citation metadata array (sent first)
|
|
- ``event: token`` — streamed text chunk (repeated)
|
|
- ``event: done`` — completion metadata with cascade_tier, conversation_id
|
|
- ``event: error`` — error message (on failure)
|
|
"""
|
|
logger.info("chat_request query=%r creator=%r cid=%r weight=%.2f", body.query, body.creator, body.conversation_id, body.personality_weight)
|
|
|
|
redis = await get_redis()
|
|
service = ChatService(settings, redis=redis)
|
|
|
|
return StreamingResponse(
|
|
service.stream_response(
|
|
query=body.query,
|
|
db=db,
|
|
creator=body.creator,
|
|
conversation_id=body.conversation_id,
|
|
personality_weight=body.personality_weight,
|
|
),
|
|
media_type="text/event-stream",
|
|
headers={
|
|
"Cache-Control": "no-cache",
|
|
"X-Accel-Buffering": "no",
|
|
},
|
|
)
|