app.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. from fastapi import FastAPI, WebSocket, HTTPException, Query, Depends
  2. from fastapi.responses import StreamingResponse, FileResponse
  3. from fastapi.middleware.cors import CORSMiddleware
  4. from fastapi.staticfiles import StaticFiles
  5. from starlette.websockets import WebSocketDisconnect
  6. import os
  7. from pydantic import BaseModel
  8. import asyncio
  9. import json
  10. import uuid
  11. from typing import Optional, List, Dict, Any
  12. import time
  13. import re
  14. # 导入现有的系统
  15. from new_main import IntegratedQASystem
  16. # 创建应用实例
  17. app = FastAPI(title="问答系统API", description="集成MySQL和RAG的智能问答系统")
  18. # 配置CORS,允许前端访问
  19. app.add_middleware(
  20. CORSMiddleware,
  21. allow_origins=["*"], # 在生产环境中应该限制为特定域名
  22. allow_credentials=True,
  23. allow_methods=["*"],
  24. allow_headers=["*"],
  25. )
  26. # 创建静态文件目录
  27. os.makedirs("static", exist_ok=True)
  28. # 创建全局QA系统实例
  29. qa_system = IntegratedQASystem()
  30. # 定义日常问候用语模式和回复
  31. GREETING_PATTERNS = [
  32. {
  33. "pattern": r"^(你好|您好|hi|hello)",
  34. "response": "你好!我是企业知识库助手,专注于为员工答疑解惑,很高兴为你服务!"
  35. },
  36. {
  37. "pattern": r"^(你是谁|您是谁|你叫什么|你的名字|who are you)",
  38. "response": "我是企业知识库助手,致力于提供企业制度、IT服务、财务报销、合同流程和产品资料等内部知识解答!"
  39. },
  40. {
  41. "pattern": r"^(在吗|在不在|有人吗)",
  42. "response": "我在!我是企业知识库助手,随时为你解答问题!"
  43. },
  44. {
  45. "pattern": r"^(干嘛呢|你在干嘛|做什么)",
  46. "response": "我正在待命,随时为你检索和解答企业内部知识库问题!有什么我可以帮你的?"
  47. }
  48. ]
  49. # 定义请求模型
  50. class QueryRequest(BaseModel):
  51. query: str
  52. source_filter: Optional[str] = None
  53. session_id: Optional[str] = None
  54. # 定义响应模型
  55. class QueryResponse(BaseModel):
  56. answer: str
  57. is_streaming: bool
  58. session_id: str
  59. processing_time: float
  60. # 添加静态文件服务
  61. app.mount("/static", StaticFiles(directory="static"), name="static")
  62. # 根路径重定向到index.html
  63. @app.get("/")
  64. async def read_root():
  65. return FileResponse("static/index.html")
  66. # 创建新会话
  67. @app.post("/api/create_session")
  68. async def create_session():
  69. session_id = str(uuid.uuid4())
  70. return {"session_id": session_id}
  71. # 查询历史消息
  72. @app.get("/api/history/{session_id}")
  73. async def get_history(session_id: str):
  74. try:
  75. history = qa_system.get_session_history(session_id)
  76. return {"session_id": session_id, "history": history}
  77. except Exception as e:
  78. raise HTTPException(status_code=500, detail=f"获取历史记录失败: {str(e)}")
  79. # 清除历史消息
  80. @app.delete("/api/history/{session_id}")
  81. async def clear_history(session_id: str):
  82. success = qa_system.clear_session_history(session_id)
  83. if success:
  84. return {"status": "success", "message": "历史记录已清除"}
  85. else:
  86. raise HTTPException(status_code=500, detail="清除历史记录失败")
  87. # 检查是否为日常问候用语并返回模板回复
  88. def check_greeting(query: str) -> Optional[str]:
  89. query_text = query[1:].strip() # 去除 # 前缀
  90. for pattern_info in GREETING_PATTERNS:
  91. if re.match(pattern_info["pattern"], query_text, re.IGNORECASE):
  92. return pattern_info["response"]
  93. return None
  94. # 检查是否为日常问候用语并返回模板回复
  95. def check_greeting(query: str) -> Optional[str]:
  96. query_text = query.strip()
  97. for pattern_info in GREETING_PATTERNS:
  98. if re.match(pattern_info["pattern"], query_text, re.IGNORECASE):
  99. return pattern_info["response"]
  100. return None
  101. # 入参 出参
  102. # 非流式查询接口
  103. @app.post("/api/query")
  104. async def query(request: QueryRequest):
  105. start_time = time.time()
  106. session_id = request.session_id or str(uuid.uuid4())
  107. # 检查是否为日常问候
  108. greeting_response = check_greeting(request.query)
  109. if greeting_response:
  110. return {
  111. "answer": greeting_response,
  112. "is_streaming": False,
  113. "session_id": session_id,
  114. "processing_time": time.time() - start_time
  115. }
  116. # 判断是否需要流式处理(基于 need_rag)
  117. answer, need_rag = qa_system.bm25_search.search(request.query, threshold=0.85)
  118. if need_rag:
  119. # 需要 RAG 和 LLM 处理,返回流式响应提示
  120. return {
  121. "answer": "请使用WebSocket接口获取流式响应",
  122. "is_streaming": True,
  123. "session_id": session_id,
  124. "processing_time": time.time() - start_time
  125. }
  126. # 非流式查询,直接返回 BM25 检索的答案
  127. return {
  128. "answer": answer,
  129. "is_streaming": False,
  130. "session_id": session_id,
  131. "processing_time": time.time() - start_time
  132. }
  133. # 流式查询WebSocket接口
  134. @app.websocket("/api/stream")
  135. async def websocket_endpoint(websocket: WebSocket):
  136. await websocket.accept()
  137. try:
  138. while True:
  139. # 接收消息
  140. data = await websocket.receive_text()
  141. request_data = json.loads(data)
  142. query = request_data.get("query")
  143. source_filter = request_data.get("source_filter")
  144. session_id = request_data.get("session_id", str(uuid.uuid4()))
  145. start_time = time.time()
  146. # 发送开始标志
  147. if websocket.client_state == websocket.client_state.CONNECTED: # 确保客户端已连接
  148. await websocket.send_json({
  149. "type": "start",
  150. "session_id": session_id
  151. })
  152. # 检查是否为日常问候
  153. greeting_response = check_greeting(query)
  154. if greeting_response:
  155. if websocket.client_state == websocket.client_state.CONNECTED:
  156. await websocket.send_json({
  157. "type": "token",
  158. "token": greeting_response,
  159. "session_id": session_id
  160. })
  161. await websocket.send_json({
  162. "type": "end",
  163. "session_id": session_id,
  164. "is_complete": True,
  165. "processing_time": time.time() - start_time
  166. })
  167. break
  168. # 调用QA系统进行查询,流式返回结果
  169. collected_answer = ""
  170. for token, is_complete in qa_system.query(query, source_filter=source_filter, session_id=session_id):
  171. collected_answer += token
  172. if is_complete and not collected_answer:
  173. if websocket.client_state == websocket.client_state.CONNECTED:
  174. await websocket.send_json({
  175. "type": "end",
  176. "session_id": session_id,
  177. "is_complete": True,
  178. "processing_time": time.time() - start_time
  179. })
  180. break
  181. if token and websocket.client_state == websocket.client_state.CONNECTED:
  182. await websocket.send_json({
  183. "type": "token",
  184. "token": token,
  185. "session_id": session_id
  186. })
  187. if is_complete:
  188. if websocket.client_state == websocket.client_state.CONNECTED:
  189. await websocket.send_json({
  190. "type": "end",
  191. "session_id": session_id,
  192. "is_complete": True,
  193. "processing_time": time.time() - start_time
  194. })
  195. break
  196. await asyncio.sleep(0.01)
  197. except WebSocketDisconnect as e:
  198. print(f"WebSocket disconnected: code={e.code}, reason={e.reason}")
  199. except Exception as e:
  200. print(f"WebSocket error: {str(e)}")
  201. if websocket.client_state == websocket.client_state.CONNECTED:
  202. await websocket.send_json({
  203. "type": "error",
  204. "error": str(e)
  205. })
  206. finally:
  207. try:
  208. if websocket.client_state == websocket.client_state.CONNECTED:
  209. await websocket.close()
  210. except Exception as e:
  211. print(f"Error closing WebSocket: {str(e)}")
  212. # 健康检查端点
  213. @app.get("/health")
  214. async def health_check():
  215. return {"status": "healthy"}
  216. # 获取有效的知识域
  217. @app.get("/api/sources")
  218. async def get_sources():
  219. return {"sources": qa_system.config.VALID_SOURCES}
  220. if __name__ == "__main__":
  221. import uvicorn
  222. uvicorn.run("app:app", host="0.0.0.0", port=8003, reload=False)