This commit is contained in:
liaibo
2025-12-06 16:47:17 +08:00
parent 15921bfeb7
commit 9ef8e06d67
75 changed files with 559237 additions and 93 deletions
+52 -11
View File
@@ -1,26 +1,67 @@
import asyncio
from fastapi import APIRouter
from __future__ import annotations
import json
from typing import AsyncIterator, Optional
from fastapi import APIRouter, HTTPException, status
from fastapi.responses import StreamingResponse
from app.deps import AuthDep
from app.services.lightrag_service import lightrag_service
from app.services.supabase_rest import supabase_rest
router = APIRouter(prefix="/chat")
@router.get("")
async def chat(query: str, document_id: str, auth: AuthDep) -> StreamingResponse: # noqa: ARG001
async def chat(
query: str,
auth: AuthDep,
document_id: Optional[str] = None,
) -> StreamingResponse:
"""
SSE 流式占位。后续会调用 LightRAG + OpenAI
当前直接返回 mock 文字,确保前端链路可用。
基于 LightRAG 的 SSE 流式回答
query: 必填问题
document_id: 可选,指定所属 workspace
"""
if not query or not query.strip():
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="query 不能为空")
async def event_stream() -> asyncio.AsyncGenerator[str, None]:
reply = await lightrag_service.query(query_text=query, user_id="placeholder-user")
chunks = [reply[: len(reply) // 2 or 1], reply[len(reply) // 2 or 1 :]]
for chunk in chunks:
yield f"data: {chunk}\n\n"
await asyncio.sleep(0.1)
workspace_id: Optional[str] = None
if document_id:
document = supabase_rest.select_one(
"documents", {"id": document_id, "user_id": auth.user_id}
)
if not document:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found")
workspace_id = (
str(document.get("workspace_id")) if document.get("workspace_id") else None
)
health = await lightrag_service.run_healthcheck()
if not health.get("ok"):
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail=health.get("error", "LightRAG 未就绪"),
)
lightrag_result = await lightrag_service.stream_answer(
query_text=query, user_id=auth.user_id, workspace_id=workspace_id
)
async def event_stream() -> AsyncIterator[str]:
if lightrag_result.get("is_streaming") and lightrag_result.get("iterator"):
iterator = lightrag_result["iterator"]
async for chunk in iterator:
payload = json.dumps({"type": "chunk", "content": chunk})
yield f"data: {payload}\n\n"
else:
payload = json.dumps(
{"type": "chunk", "content": lightrag_result.get("content", "")}
)
yield f"data: {payload}\n\n"
references = lightrag_result.get("references", [])
yield f"data: {json.dumps({'type': 'references', 'data': references})}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(event_stream(), media_type="text/event-stream")