qwen.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. """
  2. FastAPI SSE 接口 - Qwen 模型思维链展示
  3. 实时返回模型的思考过程(think process)
  4. """
  5. import asyncio
  6. import json
  7. from fastapi import FastAPI, Request
  8. from fastapi.responses import StreamingResponse
  9. from pydantic import BaseModel
  10. import httpx
  11. app = FastAPI(
  12. title="Qwen Think Process API",
  13. description="展示 Qwen 模型的思维链(Think Process),实时返回思考过程",
  14. version="1.0.0"
  15. )
  16. # Qwen API 配置(预留配置参数)
  17. QWEN_API_URL = "http://localhost:8000/v1/chat/completions" # Qwen 服务地址
  18. QWEN_API_KEY = "your-api-key-here" # API Key 预留
  19. QWEN_MODEL = "Qwen/Qwen2-7B-Instruct" # 模型名称
  20. class ThinkProcessRequest(BaseModel):
  21. """请求体模型"""
  22. question: str
  23. max_tokens: int = 500
  24. model: str = QWEN_MODEL
  25. api_key: str = None # 允许通过请求覆盖 API Key
  26. async def call_qwen_api(question: str, model: str = None, api_key: str = None):
  27. """
  28. 真实调用 Qwen API 获取思维链
  29. Args:
  30. question: 用户问题
  31. model: 模型名称(可选,默认使用配置值)
  32. api_key: API Key(可选,默认使用配置值)
  33. """
  34. # 使用传入值或默认配置
  35. target_model = model or QWEN_MODEL
  36. target_api_key = api_key or QWEN_API_KEY
  37. # 构建提示词,引导模型输出思维链
  38. think_prompt = f"""
  39. 请用中文详细展示你的思考过程来回答以下问题:
  40. 问题:{question}
  41. 思考过程格式:
  42. 1. 分析问题核心需求
  43. 2. 检索相关知识
  44. 3. 构建推理链条
  45. 4. 验证逻辑正确性
  46. 5. 生成最终回答
  47. 请按步骤详细说明你的思考过程。
  48. """
  49. payload = {
  50. "model": target_model,
  51. "messages": [
  52. {
  53. "role": "system",
  54. "content": "你是一个擅长展示思考过程的AI助手,请详细展示你的思考步骤。"
  55. },
  56. {
  57. "role": "user",
  58. "content": think_prompt
  59. }
  60. ],
  61. "stream": True,
  62. "max_tokens": 1000,
  63. "temperature": 0.7
  64. }
  65. headers = {
  66. "Content-Type": "application/json"
  67. }
  68. # 如果有 API Key,添加认证头
  69. if target_api_key and target_api_key != "your-api-key-here":
  70. headers["Authorization"] = f"Bearer {target_api_key}"
  71. try:
  72. async with httpx.AsyncClient(timeout=120.0) as client:
  73. async with client.stream(
  74. "POST",
  75. QWEN_API_URL,
  76. json=payload,
  77. headers=headers
  78. ) as response:
  79. if response.status_code != 200:
  80. try:
  81. error_data = await response.json()
  82. error_msg = error_data.get("error", {}).get("message", f"API返回错误: {response.status_code}")
  83. except Exception:
  84. error_msg = f"API请求失败: {response.status_code}"
  85. yield {"type": "error", "content": error_msg}
  86. yield {"type": "completed", "content": ""}
  87. return
  88. # 解析流式响应
  89. thinking_content = ""
  90. async for chunk in response.aiter_bytes(chunk_size=64):
  91. try:
  92. chunk_text = chunk.decode("utf-8", errors="ignore")
  93. # 解析 SSE 格式
  94. for line in chunk_text.split("\n"):
  95. if line.startswith("data: "):
  96. data_str = line[6:].strip()
  97. if data_str == "[DONE]":
  98. continue
  99. try:
  100. data = json.loads(data_str)
  101. choices = data.get("choices", [])
  102. if choices:
  103. delta = choices[0].get("delta", {})
  104. content = delta.get("content", "")
  105. if content:
  106. thinking_content += content
  107. yield {
  108. "type": "thinking",
  109. "content": content
  110. }
  111. except json.JSONDecodeError:
  112. continue
  113. except Exception as e:
  114. continue
  115. # 最终回答汇总
  116. if thinking_content:
  117. yield {"type": "answer", "content": thinking_content}
  118. yield {"type": "completed", "content": ""}
  119. except httpx.TimeoutException:
  120. yield {"type": "error", "content": "API请求超时"}
  121. yield {"type": "completed", "content": ""}
  122. except httpx.ConnectError:
  123. yield {"type": "error", "content": "无法连接到Qwen服务"}
  124. yield {"type": "completed", "content": ""}
  125. except Exception as e:
  126. yield {"type": "error", "content": f"API调用异常: {str(e)}"}
  127. yield {"type": "completed", "content": ""}
  128. @app.post("/api/v1/think")
  129. async def think_stream(request: Request):
  130. """
  131. SSE 接口:实时返回 Qwen 模型的思维链
  132. 请求体:
  133. {
  134. "question": "您的问题",
  135. "max_tokens": 500,
  136. "model": "Qwen/Qwen2-7B-Instruct", # 可选,覆盖默认模型
  137. "api_key": "your-key" # 可选,覆盖默认API Key
  138. }
  139. 响应格式(SSE):
  140. - thinking: 思考过程(流式返回)
  141. - answer: 最终回答汇总
  142. - error: 错误信息
  143. - completed: 完成标记
  144. """
  145. # 解析请求体
  146. body = await request.body()
  147. try:
  148. data = json.loads(body.decode("utf-8"))
  149. question = data.get("question", "")
  150. model = data.get("model", None)
  151. api_key = data.get("api_key", None)
  152. except Exception:
  153. question = "用户提问"
  154. model = None
  155. api_key = None
  156. async def stream_generator():
  157. async for step in call_qwen_api(question, model, api_key):
  158. yield f"data: {json.dumps(step, ensure_ascii=False)}\n\n"
  159. return StreamingResponse(
  160. stream_generator(),
  161. media_type="text/event-stream",
  162. headers={
  163. "Cache-Control": "no-cache",
  164. "Connection": "keep-alive",
  165. "Access-Control-Allow-Origin": "*",
  166. }
  167. )
  168. @app.get("/")
  169. async def root():
  170. return {
  171. "service": "Qwen Think Process API",
  172. "version": "1.0.0",
  173. "endpoints": {
  174. "think": "POST /api/v1/think (SSE)"
  175. },
  176. "description": "实时展示 Qwen 模型的思维链过程",
  177. "config": {
  178. "api_url": QWEN_API_URL,
  179. "default_model": QWEN_MODEL
  180. }
  181. }
  182. @app.get("/health")
  183. async def health():
  184. return {"status": "healthy"}
  185. if __name__ == "__main__":
  186. import uvicorn
  187. uvicorn.run("qwen:app", host="0.0.0.0", port=8000, reload=True)