import logging import uuid import json from fastapi import APIRouter, HTTPException, status from fastapi.responses import StreamingResponse from config import QWEN_API_KEY, QWEN_UPSTREAM_URL from models import ChatRequest from utils import convert_prompt_to_messages, standard_sse_generator router = APIRouter() logger = logging.getLogger("proxy") @router.post("/qwen/chat") async def proxy_qwen_chat(request: ChatRequest): # 为每个请求生成一个唯一的 ID,方便在日志中追踪 request_id = f"qwen-{uuid.uuid4()}" # 检查 API Key 是否已在 config.py 中配置 if not QWEN_API_KEY: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Qwen API key is not configured.", ) # 记录从客户端收到的原始请求体 client_request_body = request.model_dump() logger.debug( f"[{request_id}] === Qwen New Request ===\n" f"[{request_id}] Client Request Body:\n" f"{json.dumps(client_request_body, indent=2, ensure_ascii=False)}" ) # 将 prompt 格式转换为 API 所需的 messages 格式 messages = convert_prompt_to_messages(request.prompt) logger.debug( f"[{request_id}] Agent Transformed Messages:\n" f"{json.dumps(messages, indent=2, ensure_ascii=False)}" ) # 构建发送到上游 API 的 payload payload = { "model": "qwen-plus", # 指定使用的模型 "messages": messages, "stream": True, "temperature": request.temperature, "top_p": request.top_p, "max_tokens": request.max_tokens, } # 如果客户端请求 web_search=True,则为 Qwen 启用网页搜索 if request.web_search is True: payload["enable_search"] = True logger.info(f"[{request_id}] Enabled web search for Qwen.") logger.debug( f"[{request_id}] Upstream Payload:\n" f"{json.dumps(payload, indent=2, ensure_ascii=False)}" ) # 请求头,包含认证信息 headers = {"Authorization": f"Bearer {QWEN_API_KEY}"} # 返回一个流式响应对象 # standard_sse_generator 会处理与上游的通信和流式数据的转发 return StreamingResponse( standard_sse_generator( QWEN_UPSTREAM_URL, payload, headers, "Qwen", request_id ), media_type="text/event-stream", # 指定 SSE 的 MIME 类型 )