import uuid from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException from fastapi.responses import StreamingResponse from pydantic import BaseModel from sqlalchemy.orm import Session as DBSession from app.db.database import get_db from app.db.models import Session, Message from app.core import memory as mem from app.core import llm from app.core.config import DEFAULT_MODEL router = APIRouter(prefix="/chat", tags=["chat"]) class ChatRequest(BaseModel): session_id: str | None = None message: str model: str | None = None class SessionResponse(BaseModel): id: str title: str model: str updated_at: datetime class Config: from_attributes = True @router.get("/sessions", response_model=list[SessionResponse]) def list_sessions(db: DBSession = Depends(get_db)): return db.query(Session).order_by(Session.updated_at.desc()).all() @router.delete("/sessions/{session_id}", status_code=204) def delete_session(session_id: str, db: DBSession = Depends(get_db)): session = db.get(Session, session_id) if not session: raise HTTPException(status_code=404, detail="Session not found") db.delete(session) db.commit() @router.post("/stream") async def stream_chat(req: ChatRequest, db: DBSession = Depends(get_db)): # セッション取得 or 新規作成 if req.session_id: session = db.get(Session, req.session_id) if not session: raise HTTPException(status_code=404, detail="Session not found") else: session = Session( id=str(uuid.uuid4()), title=req.message[:40], model=req.model or DEFAULT_MODEL, ) db.add(session) db.commit() # ユーザーメッセージを保存 user_msg = Message(session_id=session.id, role="user", content=req.message) db.add(user_msg) db.commit() context = mem.get_context_messages(db, session.id) async def generate(): # セッションIDをまず返す(新規セッション時にフロントへ通知) yield f"data: {{\"type\":\"session_id\",\"value\":\"{session.id}\"}}\n\n" full_response = [] async for chunk in llm.stream_chat(context, model=req.model or session.model): full_response.append(chunk) escaped = chunk.replace('"', '\\"').replace('\n', '\\n') yield f"data: {{\"type\":\"chunk\",\"value\":\"{escaped}\"}}\n\n" # アシスタントメッセージを保存 assistant_content = "".join(full_response) db.add(Message(session_id=session.id, role="assistant", content=assistant_content)) session.updated_at = datetime.now(timezone.utc) db.commit() # メモリ要約チェック await mem.maybe_summarize(db, session) yield "data: {\"type\":\"done\"}\n\n" return StreamingResponse(generate(), media_type="text/event-stream")