1
0

11_Q&A_Assistant.py 15 KB

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