11_Q&A_Assistant.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 智能文档问答助手 - 基于HelloAgents的智能文档问答系统
  5. 这是一个完整的PDF学习助手应用,支持:
  6. - 加载PDF文档并构建知识库
  7. - 智能问答(基于RAG)
  8. - 学习历程记录(基于Memory)
  9. - 学习回顾和报告生成
  10. """
  11. import os
  12. import time
  13. import json
  14. from datetime import datetime
  15. from typing import Dict, List, Optional, Any, Tuple
  16. from hello_agents.tools import MemoryTool, RAGTool
  17. import gradio as gr
  18. class PDFLearningAssistant:
  19. """智能文档问答助手"""
  20. def __init__(self, user_id: str = "default_user"):
  21. """初始化学习助手
  22. Args:
  23. user_id: 用户ID,用于隔离不同用户的数据
  24. """
  25. self.user_id = user_id
  26. self.session_id = f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
  27. # 初始化工具
  28. self.memory_tool = MemoryTool(user_id=user_id)
  29. self.rag_tool = RAGTool(rag_namespace=f"pdf_{user_id}")
  30. # 学习统计
  31. self.stats = {
  32. "session_start": datetime.now(),
  33. "documents_loaded": 0,
  34. "questions_asked": 0,
  35. "concepts_learned": 0
  36. }
  37. # 当前加载的文档
  38. self.current_document = None
  39. def load_document(self, pdf_path: str) -> Dict[str, Any]:
  40. """加载PDF文档到知识库
  41. Args:
  42. pdf_path: PDF文件路径
  43. Returns:
  44. Dict: 包含success和message的结果
  45. """
  46. if not os.path.exists(pdf_path):
  47. return {"success": False, "message": f"文件不存在: {pdf_path}"}
  48. start_time = time.time()
  49. try:
  50. # 使用RAG工具处理PDF
  51. result = self.rag_tool.execute(
  52. "add_document",
  53. file_path=pdf_path,
  54. chunk_size=1000,
  55. chunk_overlap=200
  56. )
  57. process_time = time.time() - start_time
  58. # RAG工具返回的是字符串消息
  59. self.current_document = os.path.basename(pdf_path)
  60. self.stats["documents_loaded"] += 1
  61. # 记录到学习记忆
  62. self.memory_tool.execute(
  63. "add",
  64. content=f"加载了文档《{self.current_document}》",
  65. memory_type="episodic",
  66. importance=0.9,
  67. event_type="document_loaded",
  68. session_id=self.session_id
  69. )
  70. return {
  71. "success": True,
  72. "message": f"加载成功!(耗时: {process_time:.1f}秒)",
  73. "document": self.current_document
  74. }
  75. except Exception as e:
  76. return {
  77. "success": False,
  78. "message": f"加载失败: {str(e)}"
  79. }
  80. def ask(self, question: str, use_advanced_search: bool = True) -> str:
  81. """向文档提问
  82. Args:
  83. question: 用户问题
  84. use_advanced_search: 是否使用高级检索(MQE + HyDE)
  85. Returns:
  86. str: 答案
  87. """
  88. if not self.current_document:
  89. return "⚠️ 请先加载文档!使用 load_document() 方法加载PDF文档。"
  90. # 记录问题到工作记忆
  91. self.memory_tool.execute(
  92. "add",
  93. content=f"提问: {question}",
  94. memory_type="working",
  95. importance=0.6,
  96. session_id=self.session_id
  97. )
  98. # 使用RAG检索答案
  99. answer = self.rag_tool.execute(
  100. "ask",
  101. question=question,
  102. limit=5,
  103. enable_advanced_search=use_advanced_search,
  104. enable_mqe=use_advanced_search,
  105. enable_hyde=use_advanced_search
  106. )
  107. # 记录到情景记忆
  108. self.memory_tool.execute(
  109. "add",
  110. content=f"关于'{question}'的学习",
  111. memory_type="episodic",
  112. importance=0.7,
  113. event_type="qa_interaction",
  114. session_id=self.session_id
  115. )
  116. self.stats["questions_asked"] += 1
  117. return answer
  118. def add_note(self, content: str, concept: Optional[str] = None):
  119. """添加学习笔记
  120. Args:
  121. content: 笔记内容
  122. concept: 相关概念(可选)
  123. """
  124. self.memory_tool.execute(
  125. "add",
  126. content=content,
  127. memory_type="semantic",
  128. importance=0.8,
  129. concept=concept or "general",
  130. session_id=self.session_id
  131. )
  132. self.stats["concepts_learned"] += 1
  133. def recall(self, query: str, limit: int = 5) -> str:
  134. """回顾学习历程
  135. Args:
  136. query: 查询关键词
  137. limit: 返回结果数量
  138. Returns:
  139. str: 相关记忆
  140. """
  141. result = self.memory_tool.execute(
  142. "search",
  143. query=query,
  144. limit=limit
  145. )
  146. return result
  147. def get_stats(self) -> Dict[str, Any]:
  148. """获取学习统计
  149. Returns:
  150. Dict: 统计信息
  151. """
  152. duration = (datetime.now() - self.stats["session_start"]).total_seconds()
  153. return {
  154. "会话时长": f"{duration:.0f}秒",
  155. "加载文档": self.stats["documents_loaded"],
  156. "提问次数": self.stats["questions_asked"],
  157. "学习笔记": self.stats["concepts_learned"],
  158. "当前文档": self.current_document or "未加载"
  159. }
  160. def generate_report(self, save_to_file: bool = True) -> Dict[str, Any]:
  161. """生成学习报告
  162. Args:
  163. save_to_file: 是否保存到文件
  164. Returns:
  165. Dict: 学习报告
  166. """
  167. # 获取记忆摘要
  168. memory_summary = self.memory_tool.execute("summary", limit=10)
  169. # 获取RAG统计
  170. rag_stats = self.rag_tool.execute("stats")
  171. # 生成报告
  172. duration = (datetime.now() - self.stats["session_start"]).total_seconds()
  173. report = {
  174. "session_info": {
  175. "session_id": self.session_id,
  176. "user_id": self.user_id,
  177. "start_time": self.stats["session_start"].isoformat(),
  178. "duration_seconds": duration
  179. },
  180. "learning_metrics": {
  181. "documents_loaded": self.stats["documents_loaded"],
  182. "questions_asked": self.stats["questions_asked"],
  183. "concepts_learned": self.stats["concepts_learned"]
  184. },
  185. "memory_summary": memory_summary,
  186. "rag_status": rag_stats
  187. }
  188. # 保存到文件
  189. if save_to_file:
  190. report_file = f"learning_report_{self.session_id}.json"
  191. try:
  192. with open(report_file, 'w', encoding='utf-8') as f:
  193. json.dump(report, f, ensure_ascii=False, indent=2, default=str)
  194. report["report_file"] = report_file
  195. except Exception as e:
  196. report["save_error"] = str(e)
  197. return report
  198. def create_gradio_ui():
  199. """创建Gradio Web UI"""
  200. # 全局助手实例
  201. assistant_state = {"assistant": None}
  202. def init_assistant(user_id: str) -> str:
  203. """初始化助手"""
  204. if not user_id:
  205. user_id = "web_user"
  206. assistant_state["assistant"] = PDFLearningAssistant(user_id=user_id)
  207. return f"✅ 助手已初始化 (用户: {user_id})"
  208. def load_pdf(pdf_file) -> str:
  209. """加载PDF文件"""
  210. if assistant_state["assistant"] is None:
  211. return "❌ 请先初始化助手"
  212. if pdf_file is None:
  213. return "❌ 请上传PDF文件"
  214. # Gradio上传的文件是临时文件对象
  215. pdf_path = pdf_file.name
  216. result = assistant_state["assistant"].load_document(pdf_path)
  217. if result["success"]:
  218. return f"✅ {result['message']}\n📄 文档: {result['document']}"
  219. else:
  220. return f"❌ {result['message']}"
  221. def chat(message: str, history: List) -> Tuple[str, List]:
  222. """聊天功能"""
  223. if assistant_state["assistant"] is None:
  224. return "", history + [[message, "❌ 请先初始化助手并加载文档"]]
  225. if not message.strip():
  226. return "", history
  227. # 判断是技术问题还是回顾问题
  228. if any(keyword in message for keyword in ["之前", "学过", "回顾", "历史", "记得"]):
  229. # 回顾学习历程
  230. response = assistant_state["assistant"].recall(message)
  231. response = f"🧠 **学习回顾**\n\n{response}"
  232. else:
  233. # 技术问答
  234. response = assistant_state["assistant"].ask(message)
  235. response = f"💡 **回答**\n\n{response}"
  236. history.append([message, response])
  237. return "", history
  238. def add_note_ui(note_content: str, concept: str) -> str:
  239. """添加笔记"""
  240. if assistant_state["assistant"] is None:
  241. return "❌ 请先初始化助手"
  242. if not note_content.strip():
  243. return "❌ 笔记内容不能为空"
  244. assistant_state["assistant"].add_note(note_content, concept or None)
  245. return f"✅ 笔记已保存: {note_content[:50]}..."
  246. def get_stats_ui() -> str:
  247. """获取统计信息"""
  248. if assistant_state["assistant"] is None:
  249. return "❌ 请先初始化助手"
  250. stats = assistant_state["assistant"].get_stats()
  251. result = "📊 **学习统计**\n\n"
  252. for key, value in stats.items():
  253. result += f"- **{key}**: {value}\n"
  254. return result
  255. def generate_report_ui() -> str:
  256. """生成报告"""
  257. if assistant_state["assistant"] is None:
  258. return "❌ 请先初始化助手"
  259. report = assistant_state["assistant"].generate_report(save_to_file=True)
  260. result = f"✅ 学习报告已生成\n\n"
  261. result += f"**会话信息**\n"
  262. result += f"- 会话时长: {report['session_info']['duration_seconds']:.0f}秒\n"
  263. result += f"- 加载文档: {report['learning_metrics']['documents_loaded']}\n"
  264. result += f"- 提问次数: {report['learning_metrics']['questions_asked']}\n"
  265. result += f"- 学习笔记: {report['learning_metrics']['concepts_learned']}\n"
  266. if "report_file" in report:
  267. result += f"\n💾 报告已保存至: {report['report_file']}"
  268. return result
  269. # 创建Gradio界面
  270. with gr.Blocks(title="智能文档问答助手", theme=gr.themes.Soft()) as demo:
  271. gr.Markdown("""
  272. # 📚 智能文档问答助手
  273. 基于HelloAgents的智能文档问答系统,支持:
  274. - 📄 加载PDF文档并构建知识库
  275. - 💬 智能问答(基于RAG)
  276. - 📝 学习笔记记录
  277. - 🧠 学习历程回顾
  278. - 📊 学习报告生成
  279. """)
  280. with gr.Tab("🏠 开始使用"):
  281. with gr.Row():
  282. user_id_input = gr.Textbox(
  283. label="用户ID",
  284. placeholder="输入你的用户ID(可选,默认为web_user)",
  285. value="web_user"
  286. )
  287. init_btn = gr.Button("初始化助手", variant="primary")
  288. init_output = gr.Textbox(label="初始化状态", interactive=False)
  289. init_btn.click(init_assistant, inputs=[user_id_input], outputs=[init_output])
  290. gr.Markdown("### 📄 加载PDF文档")
  291. pdf_upload = gr.File(
  292. label="上传PDF文件",
  293. file_types=[".pdf"],
  294. type="filepath"
  295. )
  296. load_btn = gr.Button("加载文档", variant="primary")
  297. load_output = gr.Textbox(label="加载状态", interactive=False)
  298. load_btn.click(load_pdf, inputs=[pdf_upload], outputs=[load_output])
  299. with gr.Tab("💬 智能问答"):
  300. gr.Markdown("### 向文档提问或回顾学习历程")
  301. chatbot = gr.Chatbot(
  302. label="对话历史",
  303. height=400,
  304. bubble_full_width=False
  305. )
  306. with gr.Row():
  307. msg_input = gr.Textbox(
  308. label="输入问题",
  309. placeholder="例如:什么是Transformer? 或 我之前学过什么?",
  310. scale=4
  311. )
  312. send_btn = gr.Button("发送", variant="primary", scale=1)
  313. gr.Examples(
  314. examples=[
  315. "什么是大语言模型?",
  316. "Transformer架构有哪些核心组件?",
  317. "如何训练大语言模型?",
  318. "我之前学过什么内容?",
  319. "回顾一下关于注意力机制的学习"
  320. ],
  321. inputs=msg_input
  322. )
  323. msg_input.submit(chat, inputs=[msg_input, chatbot], outputs=[msg_input, chatbot])
  324. send_btn.click(chat, inputs=[msg_input, chatbot], outputs=[msg_input, chatbot])
  325. with gr.Tab("📝 学习笔记"):
  326. gr.Markdown("### 记录学习心得和重要概念")
  327. note_content = gr.Textbox(
  328. label="笔记内容",
  329. placeholder="输入你的学习笔记...",
  330. lines=3
  331. )
  332. concept_input = gr.Textbox(
  333. label="相关概念(可选)",
  334. placeholder="例如:transformer, attention"
  335. )
  336. note_btn = gr.Button("保存笔记", variant="primary")
  337. note_output = gr.Textbox(label="保存状态", interactive=False)
  338. note_btn.click(add_note_ui, inputs=[note_content, concept_input], outputs=[note_output])
  339. with gr.Tab("📊 学习统计"):
  340. gr.Markdown("### 查看学习进度和统计信息")
  341. stats_btn = gr.Button("刷新统计", variant="primary")
  342. stats_output = gr.Markdown()
  343. stats_btn.click(get_stats_ui, outputs=[stats_output])
  344. gr.Markdown("### 生成学习报告")
  345. report_btn = gr.Button("生成报告", variant="primary")
  346. report_output = gr.Textbox(label="报告状态", interactive=False)
  347. report_btn.click(generate_report_ui, outputs=[report_output])
  348. return demo
  349. def main():
  350. """主函数 - 启动Gradio Web UI"""
  351. print("\n" + "="*60)
  352. print("� 智能文档问答助手")
  353. print("="*60)
  354. print("正在启动Web界面...\n")
  355. demo = create_gradio_ui()
  356. demo.launch(
  357. server_name="0.0.0.0",
  358. server_port=7860,
  359. share=False,
  360. show_error=True
  361. )
  362. if __name__ == "__main__":
  363. main()