import asyncio import json from fastapi import APIRouter, WebSocket, WebSocketDisconnect from app.core.database import async_db_session from app.core.dependencies import _authenticate from app.core.exceptions import CustomException from app.core.logger import logger from app.core.router_class import OperationLogRoute from .schema import ChatQuerySchema from .service import ChatService, get_user_model_config WS_AI = APIRouter( route_class=OperationLogRoute, prefix="/ai/chat", tags=["智能助手WebSocket"], ) async def _send_error_and_close(websocket: WebSocket, message: str) -> None: """发送错误消息并关闭连接""" try: await websocket.send_text(f"错误: {message}") except RuntimeError: pass finally: try: await websocket.close() except RuntimeError: pass @WS_AI.websocket("/ws", name="WebSocket聊天") async def websocket_chat_controller(websocket: WebSocket) -> None: """ WebSocket 聊天接口。 支持的消息格式(JSON): - 对话:{"message": "...", "session_id": "...", "files": [...]} - 停止:{"action": "stop", "session_id": "..."} ws://127.0.0.1:8001/api/v1/ai/chat/ws?token=xxx """ await websocket.accept() token = websocket.query_params.get("token") if not token: await _send_error_and_close(websocket, "未提供认证token,请重新登录") return # 跨消息循环共享的停止信号:客户端发送 stop 时 set,生成器检测到后退出 stop_event = asyncio.Event() # 标记当前是否在生成中,便于 stop 校验 is_generating = asyncio.Event() try: redis = websocket.app.state.redis async with async_db_session() as db: auth = await _authenticate(token, db, redis) user = auth.user logger.info("WebSocket连接已建立: {} - 用户: {}", websocket.client, user.username if user else "未认证") chat_service = ChatService(auth) # 消息循环 while True: try: data = await websocket.receive_text() try: message_data = json.loads(data) query = ChatQuerySchema(**message_data) except json.JSONDecodeError: logger.warning("收到非JSON消息: {}", data) await websocket.send_text("消息格式错误,请发送JSON格式的消息") continue except Exception as e: logger.warning("消息校验失败: {}", e) await websocket.send_text(f"消息格式错误: {e}") continue # 处理停止指令 if query.action == "stop": if is_generating.is_set(): stop_event.set() logger.info("收到停止指令: session={}", query.session_id) await websocket.send_text("[STOPPED]") else: await websocket.send_text("当前没有正在进行的生成任务") continue # 对话指令 logger.info("收到聊天查询: session_id={}", query.session_id) is_generating.set() stop_event.clear() # 读取用户的 AI 模型配置(每次可动态切换) model_config = await get_user_model_config(redis, user.id) try: async for chunk in chat_service.chat_query( query=query, stop_event=stop_event, model_config=model_config, ): if not chunk: continue try: await websocket.send_text(chunk) except RuntimeError: logger.warning("WebSocket连接已关闭,停止发送消息") return finally: is_generating.clear() stop_event.clear() # 告知前端生成结束 try: await websocket.send_text("[DONE]") except RuntimeError: return except WebSocketDisconnect: logger.info("WebSocket连接已断开: {}", websocket.client) return except CustomException as e: # 认证失败等业务异常 logger.warning("WebSocket认证失败: {}", e.msg) await _send_error_and_close(websocket, e.msg) except Exception as e: # 未知异常 logger.exception("WebSocket未知异常: {}", e) await _send_error_and_close(websocket, "服务器内部错误")