qwen2.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import os
  2. import json
  3. import asyncio
  4. import httpx
  5. from typing import AsyncGenerator, Optional
  6. from fastapi import FastAPI, Request, HTTPException
  7. from fastapi.responses import StreamingResponse
  8. from fastapi.middleware.cors import CORSMiddleware
  9. from pydantic import BaseModel
  10. # 初始化 FastAPI 应用
  11. app = FastAPI(title="Qwen3.6 Thinking Stream Proxy")
  12. # 配置 CORS
  13. app.add_middleware(
  14. CORSMiddleware,
  15. allow_origins=["*"],
  16. allow_credentials=True,
  17. allow_methods=["*"],
  18. allow_headers=["*"],
  19. )
  20. # Qwen3.6 服务配置
  21. # 假设使用兼容 OpenAI 格式的接口,如本地部署或 DashScope
  22. QWEN_BASE_URL = os.getenv("QWEN_BASE_URL", "https://dashscope.aliyuncs.com/compatible-mode/v1")
  23. QWEN_API_KEY = os.getenv("QWEN_API_KEY", "your_api_key_here")
  24. QWEN_MODEL = os.getenv("QWEN_MODEL", "qwen-plus") # 或 qwen-max, qwen-turbo 等支持思考的模型
  25. class ChatRequest(BaseModel):
  26. message: str
  27. model: Optional[str] = QWEN_MODEL
  28. enable_thinking: bool = True # 默认开启思考模式
  29. async def event_generator(request_body: dict, client: httpx.AsyncClient) -> AsyncGenerator[str, None]:
  30. """
  31. 异步生成器:从 Qwen 接口获取 SSE 流并转发给前端
  32. """
  33. headers = {
  34. "Authorization": f"Bearer {QWEN_API_KEY}",
  35. "Content-Type": "application/json",
  36. "Accept": "text/event-stream"
  37. }
  38. # 构建符合 OpenAI 兼容格式的请求体
  39. # 注意:enable_thinking 通常放在 extra_body 或特定参数中,具体取决于服务商实现
  40. # 这里假设通过 extra_body 传递,或者如果是 DashScope 原生接口需调整结构
  41. # 以下以 OpenAI 兼容格式为例,若使用 DashScope 原生 SDK 需调整 payload 结构
  42. payload = {
  43. "model": request_body.get("model", QWEN_MODEL),
  44. "messages": [
  45. {"role": "user", "content": request_body["message"]}
  46. ],
  47. "stream": True,
  48. "stream_options": {"include_usage": False} # 可选
  49. }
  50. # 如果使用的是 DashScope 兼容模式且支持 extra_body 传递 thinking 参数
  51. if request_body.get("enable_thinking", True):
  52. payload["extra_body"] = {"enable_thinking": True}
  53. try:
  54. async with client.stream(
  55. "POST",
  56. f"{QWEN_BASE_URL}/chat/completions",
  57. json=payload,
  58. headers=headers,
  59. timeout=None
  60. ) as response:
  61. if response.status_code != 200:
  62. error_detail = await response.aread()
  63. raise HTTPException(status_code=response.status_code, detail=f"Upstream error: {error_detail.decode()}")
  64. async for line in response.aiter_lines():
  65. if not line:
  66. continue
  67. # 处理 SSE 结束标记
  68. if line.strip() == "[DONE]":
  69. yield "data: [DONE]\n\n"
  70. break
  71. # 解析上游返回的 JSON 数据
  72. if line.startswith("data: "):
  73. json_str = line[6:]
  74. try:
  75. data = json.loads(json_str)
  76. # 提取内容片段
  77. # Qwen 的思考过程通常在 delta.content 或 delta.reasoning_content 中
  78. # 具体字段名取决于模型版本和接口定义
  79. choices = data.get("choices", [])
  80. if choices:
  81. delta = choices.get("delta", {})
  82. # 尝试获取思考内容 (reasoning_content) 和普通内容 (content)
  83. reasoning = delta.get("reasoning_content", "")
  84. content = delta.get("content", "")
  85. # 构造前端易于区分的数据格式
  86. # 例如:{ "type": "thinking", "data": "..." } 或 { "type": "answer", "data": "..." }
  87. if reasoning:
  88. output_data = json.dumps({"type": "thinking", "content": reasoning}, ensure_ascii=False)
  89. yield f"data: {output_data}\n\n"
  90. if content:
  91. output_data = json.dumps({"type": "answer", "content": content}, ensure_ascii=False)
  92. yield f"data: {output_data}\n\n"
  93. except json.JSONDecodeError:
  94. # 如果不是 JSON,直接透传(以防万一)
  95. yield f"data: {json_str}\n\n"
  96. else:
  97. # 非 data: 开头的行,可能是注释或其他,忽略或按需处理
  98. pass
  99. except httpx.RequestError as exc:
  100. yield f"data: {{\"type\": \"error\", \"content\": \"Connection error: {str(exc)}\"}}\n\n"
  101. except Exception as exc:
  102. yield f"data: {{\"type\": \"error\", \"content\": \"Internal error: {str(exc)}\"}}\n\n"
  103. @app.post("/api/chat/stream")
  104. async def chat_stream(request: ChatRequest):
  105. """
  106. 代理接口:接收前端提问,转发给 Qwen3.6,流式返回思考和回答
  107. """
  108. upstream_payload = request.dict()
  109. async with httpx.AsyncClient() as client:
  110. return StreamingResponse(
  111. event_generator(upstream_payload, client),
  112. media_type="text/event-stream",
  113. headers={
  114. "Cache-Control": "no-cache",
  115. "Connection": "keep-alive",
  116. "X-Accel-Buffering": "no"
  117. }
  118. )
  119. if __name__ == "__main__":
  120. import uvicorn
  121. uvicorn.run(app, host="0.0.0.0", port=8000)