添加 Copilot 聊天流式响应功能及相关配置
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import AsyncGenerator, Optional
|
||||
|
||||
import httpx
|
||||
from fastapi import APIRouter, Depends, Request, status
|
||||
from fastapi.responses import StreamingResponse
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.auth.dependencies import get_current_active_user
|
||||
from app.core.config import settings
|
||||
from app.domain.schemas.user import UserInDB
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class CopilotChatStreamRequest(BaseModel):
|
||||
message: str = Field(..., min_length=1, max_length=10000)
|
||||
conversation_id: Optional[str] = Field(default=None, max_length=128)
|
||||
|
||||
|
||||
def _sse_event(event: str, data: dict) -> str:
|
||||
return f"event: {event}\ndata: {json.dumps(data, ensure_ascii=False)}\n\n"
|
||||
|
||||
|
||||
@router.post(
|
||||
"/chat/stream",
|
||||
summary="Copilot 聊天流式响应",
|
||||
description="向 Python Copilot sidecar 转发请求并通过 SSE 返回增量内容",
|
||||
)
|
||||
async def copilot_chat_stream(
|
||||
payload: CopilotChatStreamRequest,
|
||||
request: Request,
|
||||
current_user: UserInDB = Depends(get_current_active_user),
|
||||
):
|
||||
timeout = httpx.Timeout(
|
||||
connect=10.0,
|
||||
read=float(settings.COPILOT_STREAM_TIMEOUT_SECONDS),
|
||||
write=10.0,
|
||||
pool=10.0,
|
||||
)
|
||||
sidecar_url = settings.COPILOT_SIDECAR_URL.rstrip("/")
|
||||
upstream_url = f"{sidecar_url}/chat/stream"
|
||||
|
||||
async def event_generator() -> AsyncGenerator[str, None]:
|
||||
headers: dict[str, str] = {}
|
||||
auth_header = request.headers.get("authorization")
|
||||
project_id = request.headers.get("x-project-id")
|
||||
if auth_header:
|
||||
headers["authorization"] = auth_header
|
||||
if project_id:
|
||||
headers["x-project-id"] = project_id
|
||||
|
||||
body = {
|
||||
"message": payload.message,
|
||||
"conversationId": payload.conversation_id,
|
||||
"userId": current_user.username,
|
||||
}
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=timeout) as client:
|
||||
async with client.stream(
|
||||
"POST",
|
||||
upstream_url,
|
||||
json=body,
|
||||
headers=headers,
|
||||
) as response:
|
||||
if response.status_code >= 400:
|
||||
detail_text = await response.aread()
|
||||
detail = detail_text.decode("utf-8", errors="replace")
|
||||
yield _sse_event(
|
||||
"error",
|
||||
{
|
||||
"message": "Copilot sidecar request failed",
|
||||
"status": response.status_code,
|
||||
"detail": detail,
|
||||
},
|
||||
)
|
||||
return
|
||||
|
||||
async for line in response.aiter_lines():
|
||||
if await request.is_disconnected():
|
||||
return
|
||||
yield f"{line}\n"
|
||||
except httpx.ReadTimeout:
|
||||
yield _sse_event(
|
||||
"error",
|
||||
{
|
||||
"message": "Copilot stream timeout",
|
||||
"status": status.HTTP_504_GATEWAY_TIMEOUT,
|
||||
},
|
||||
)
|
||||
except httpx.ConnectError as exc:
|
||||
yield _sse_event(
|
||||
"error",
|
||||
{
|
||||
"message": "Copilot sidecar unavailable",
|
||||
"status": status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
"detail": str(exc),
|
||||
},
|
||||
)
|
||||
except Exception as exc:
|
||||
yield _sse_event(
|
||||
"error",
|
||||
{
|
||||
"message": "Unexpected stream proxy error",
|
||||
"status": status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
"detail": str(exc),
|
||||
},
|
||||
)
|
||||
|
||||
return StreamingResponse(
|
||||
event_generator(),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"X-Accel-Buffering": "no",
|
||||
},
|
||||
)
|
||||
@@ -18,6 +18,7 @@ from app.api.v1.endpoints import (
|
||||
user_management, # 新增:用户管理
|
||||
audit, # 新增:审计日志
|
||||
meta,
|
||||
copilot_chat,
|
||||
)
|
||||
from app.api.v1.endpoints.network import (
|
||||
general,
|
||||
@@ -110,3 +111,6 @@ api_router.include_router(project_data.router, tags=["Project Data"])
|
||||
|
||||
# Extension
|
||||
api_router.include_router(extension.router, tags=["Extension"])
|
||||
|
||||
# Copilot Chat
|
||||
api_router.include_router(copilot_chat.router, prefix="/copilot", tags=["Copilot"])
|
||||
|
||||
Reference in New Issue
Block a user