verify_qa_e2e.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. #!/usr/bin/env python3
  2. # scripts/manual_tests/test_qa_simple.py
  3. """
  4. 简易 QA 接口集成测试(真实服务调用)
  5. 前提:服务已启动(localhost:8000),且 .env 配置了必要的 LLM/数据库/Milvus。
  6. 运行:
  7. conda activate edu_agent
  8. python scripts/manual_tests/test_qa_simple.py
  9. """
  10. import sys
  11. import json
  12. import httpx
  13. sys.path.insert(0, ".")
  14. BASE_URL = "http://localhost:8000/api/v1"
  15. USERNAME = "bidder01"
  16. PASSWORD = "Bidder@123456"
  17. SESSION = "qa-simple-session" # 固定会话 ID
  18. def login() -> str:
  19. """登录获取 JWT token"""
  20. resp = httpx.post(
  21. f"{BASE_URL}/auth/login",
  22. json={"username": USERNAME, "password": PASSWORD},
  23. trust_env=False
  24. )
  25. resp.raise_for_status()
  26. token = resp.json()["access_token"]
  27. print(f"[登录] Token 获取成功(前20字符): {token[:20]}...")
  28. return token
  29. def chat(token: str, message: str, session_id: str = SESSION, enable_web_search: bool = False) -> dict:
  30. """非流式聊天"""
  31. resp = httpx.post(
  32. f"{BASE_URL}/qa/chat",
  33. headers={"Authorization": f"Bearer {token}"},
  34. json={
  35. "session_id": session_id,
  36. "message": message,
  37. "enable_web_search": enable_web_search,
  38. },
  39. timeout=60.0,
  40. )
  41. resp.raise_for_status()
  42. return resp.json()
  43. def stream_chat(token: str, message: str, session_id: str = SESSION) -> None:
  44. """流式聊天,打印所有 SSE 事件"""
  45. print(f"\n[流式] 问题: {message}")
  46. with httpx.stream(
  47. "POST",
  48. f"{BASE_URL}/qa/chat/stream",
  49. headers={
  50. "Authorization": f"Bearer {token}",
  51. "Accept": "text/event-stream",
  52. },
  53. json={"session_id": session_id, "message": message},
  54. timeout=60.0,
  55. ) as resp:
  56. print("[流式] 开始接收事件:")
  57. for line in resp.iter_lines():
  58. if line.startswith("data:"):
  59. data_str = line[5:].strip()
  60. try:
  61. event = json.loads(data_str)
  62. # 根据事件类型美化打印
  63. if event.get("type") == "token":
  64. print(event["content"], end="", flush=True) # 打字机效果
  65. elif event.get("type") == "progress":
  66. print(f"\n[进度] {event['stage']}")
  67. elif event.get("type") == "meta":
  68. print(f"\n[元数据] 模式={event['answer_mode']}, 置信度={event['confidence']}, 来源={event.get('sources', [])}")
  69. elif event.get("type") == "done":
  70. print("\n[流式] 完成")
  71. else:
  72. print(f"\n[其他事件] {event}")
  73. except json.JSONDecodeError:
  74. print(f"[原始数据] {data_str}")
  75. print("\n")
  76. def print_result(label: str, result: dict) -> None:
  77. """打印非流式结果摘要"""
  78. print(f"\n{'='*60}")
  79. print(f"[{label}]")
  80. print(f" 模式 : {result['answer_mode']}")
  81. print(f" 置信度: {result['confidence']:.4f}")
  82. print(f" 来源 : {result['sources']}")
  83. print(f" 回答前100字: {result['answer'][:100]}...")
  84. print(f"{'='*60}")
  85. if __name__ == "__main__":
  86. try:
  87. token = login()
  88. except Exception as e:
  89. print(f"❌ 登录失败,请检查服务是否启动及账号密码: {e}")
  90. sys.exit(1)
  91. # # ---------- 1. 测试 GENERAL 路径(闲聊) ----------
  92. # print("\n>>> 测试 GENERAL 路径")
  93. # r = chat(token, "你好,介绍一下你自己")
  94. # print_result("GENERAL", r)
  95. # if r["answer_mode"] == "general":
  96. # print("✅ GENERAL 路径正常")
  97. # else:
  98. # print(f"⚠️ 模式为 {r['answer_mode']},可能未触发 GENERAL(需检查分类器)")
  99. #
  100. # # ---------- 2. 测试 PRECISE 路径(具体问题,期望 RAG) ----------
  101. # print("\n>>> 测试 PRECISE 路径(RAG)")
  102. # r = chat(token, "介绍下商品聚合的项目?")
  103. # print_result("PRECISE", r)
  104. # if r["answer_mode"] == "rag" and len(r["sources"]) > 0:
  105. # print("✅ PRECISE RAG 正常")
  106. # else:
  107. # print(f"⚠️ 模式={r['answer_mode']}, 来源数={len(r['sources'])},可能知识库无相关内容或置信度不足")
  108. #
  109. # # ---------- 3. 测试低置信度(知识库无关内容) ----------
  110. # print("\n>>> 测试低置信度(知识库无关)")
  111. # r = chat(token, "如何制作披萨?")
  112. # print_result("低置信度", r)
  113. # if r["answer_mode"] == "llm_direct" and "⚠️" in r["answer"]:
  114. # print("✅ 低置信度路径正常(llm_direct + 提示)")
  115. # else:
  116. # print(f"⚠️ 模式={r['answer_mode']},可能知识库意外包含相关内容,或置信度判读有变")
  117. # ---------- 4. 测试流式接口 ----------
  118. print("\n>>> 测试流式接口")
  119. stream_chat(token, "介绍下商品聚合的项目")
  120. # ---------- 5. 测试历史记录接口 ----------
  121. # print("\n>>> 测试历史记录接口")
  122. # try:
  123. # hist_resp = httpx.get(
  124. # f"{BASE_URL}/qa/sessions/{SESSION}/history",
  125. # headers={"Authorization": f"Bearer {token}"},
  126. # timeout=10.0,
  127. # )
  128. # hist_resp.raise_for_status()
  129. # hist_data = hist_resp.json()
  130. # print(f'hist_data-->{hist_data}')
  131. # print(f" 会话ID: {hist_data['session_id']}")
  132. # print(f" 总轮数: {hist_data['total_turns']}")
  133. # summary_val = hist_data.get('summary')
  134. # if summary_val is None:
  135. # summary_display = "无摘要(对话轮数不足10轮)"
  136. # else:
  137. # summary_display = summary_val[:100]
  138. # if hist_data["total_turns"] > 0:
  139. # print("✅ 历史记录接口正常")
  140. # else:
  141. # print("⚠️ 历史记录为空,可能服务未保存")
  142. # except Exception as e:
  143. # print(f"❌ 历史记录接口异常: {e}")
  144. print("\n✅ 所有测试完成。")