create_plan_agent.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. # agents/create_plan_agent.py
  2. """学习计划生成 Agent"""
  3. import re
  4. from hello_agents import ReActAgent, HelloAgentsLLM
  5. from core.file_manager import FileManager
  6. class CreatePlanAgent(ReActAgent):
  7. """
  8. 学习计划生成专家
  9. 支持三种输入:领域描述、GitHub URL、PDF 论文
  10. """
  11. def __init__(self, llm: HelloAgentsLLM, streaming: bool = None):
  12. """
  13. 初始化 CreatePlanAgent
  14. Args:
  15. llm: HelloAgentsLLM 实例
  16. streaming: 是否启用流式输出(None = 自动检测)
  17. """
  18. self.max_steps = 5
  19. self.file_manager = FileManager()
  20. # 添加流式输出支持
  21. from utils.streaming import should_stream
  22. self.streaming = should_stream(streaming)
  23. # 系统提示词
  24. system_prompt = """
  25. 你是学习规划专家。工作流程:
  26. 1. 识别输入类型:
  27. - 领域描述(如:"我想学习数学")
  28. - GitHub URL(如:"https://github.com/user/project")
  29. - PDF 论文路径(如:"/path/to/paper.pdf")
  30. 2. 如果是 URL/文件,调用相应工具深度分析
  31. 3. 询问用户的学习目标:
  32. - 使用自然语言描述(如:"想在工作中应用","想达到研究生水平")
  33. 4. 根据分析结果和学习目标,搜索该领域的最佳学习路径
  34. 5. 生成结构化的学习计划(Markdown格式),包括:
  35. - 领域概述
  36. - 前置知识要求
  37. - 学习路径(分阶段)
  38. - 推荐资源
  39. - 里程碑和检查点
  40. 使用 ReAct 格式:
  41. Thought: 你的思考过程
  42. Action: tool_name[input]
  43. Observation: 工具返回结果
  44. ...
  45. Finish: [最终生成的学习计划]
  46. """
  47. # 使用父类初始化
  48. super().__init__("CreatePlanAgent", llm, system_prompt)
  49. def _identify_input_type(self, input_data: str) -> str:
  50. """
  51. 识别输入类型
  52. Args:
  53. input_data: 用户输入
  54. Returns:
  55. 输入类型(github_url/pdf_paper/domain_description)
  56. """
  57. # 检查 GitHub URL
  58. if input_data.startswith("https://github.com/"):
  59. return "github_url"
  60. # 检查 PDF 文件路径
  61. if (
  62. input_data.endswith(".pdf")
  63. or input_data.startswith("~/")
  64. or input_data.startswith("/")
  65. ):
  66. return "pdf_paper"
  67. # 默认为领域描述
  68. return "domain_description"
  69. def _analyze_github_repo(self, url: str) -> dict:
  70. """
  71. 分析 GitHub 仓库
  72. Args:
  73. url: GitHub URL
  74. Returns:
  75. 分析结果字典
  76. """
  77. from specialist.repo_analyzer import RepoAnalyzerAgent
  78. import os
  79. # 获取 GitHub Token(如果配置了)
  80. github_token = os.getenv("GITHUB_TOKEN")
  81. # 创建 RepoAnalyzerAgent
  82. repo_analyzer = RepoAnalyzerAgent(self.llm, github_token)
  83. # 分析仓库
  84. try:
  85. analysis = repo_analyzer.analyze(url)
  86. return {
  87. "domain": analysis.get("domain", ""),
  88. "tech_stack": analysis.get("tech_stack", []),
  89. "prerequisites": analysis.get("prerequisites", []),
  90. "description": analysis.get("description", ""),
  91. "stars": analysis.get("stars", 0),
  92. }
  93. except Exception as e:
  94. # 降级:使用简化实现
  95. repo_name = url.rstrip(".git").split("/")[-1]
  96. return {
  97. "domain": repo_name.replace("-", " ").replace("_", " "),
  98. "tech_stack": [],
  99. "prerequisites": [],
  100. "description": f"GitHub 仓库分析失败:{e}",
  101. "stars": 0,
  102. }
  103. def _analyze_pdf_paper(self, file_path: str) -> dict:
  104. """
  105. 分析 PDF 论文
  106. Args:
  107. file_path: PDF 文件路径
  108. Returns:
  109. 分析结果字典
  110. """
  111. from specialist.paper_analyzer import PaperAnalyzerAgent
  112. # 创建 PaperAnalyzerAgent
  113. paper_analyzer = PaperAnalyzerAgent(self.llm)
  114. # 分析论文
  115. try:
  116. analysis = paper_analyzer.analyze(file_path)
  117. return {
  118. "domain": analysis.get("domain", ""),
  119. "title": analysis.get("title", ""),
  120. "prerequisites": analysis.get("prerequisites", []),
  121. "core_concepts": analysis.get("core_concepts", []),
  122. }
  123. except Exception as e:
  124. # 降级:使用简化实现
  125. import os
  126. filename = os.path.basename(file_path).replace(".pdf", "").replace("-", " ")
  127. return {
  128. "domain": filename,
  129. "title": filename,
  130. "prerequisites": [],
  131. "core_concepts": [],
  132. "error": f"PDF 分析失败:{e}",
  133. }
  134. def _ask_learning_goal(self, analysis: dict) -> str:
  135. """
  136. 询问学习目标
  137. Args:
  138. analysis: 分析结果
  139. Returns:
  140. 学习目标描述
  141. """
  142. print(f"\n📚 分析结果:{analysis.get('domain', '未知领域')}")
  143. if analysis.get("tech_stack"):
  144. print(f"技术栈:{', '.join(analysis['tech_stack'])}")
  145. if analysis.get("prerequisites"):
  146. print(f"前置知识:{', '.join(analysis['prerequisites'])}")
  147. if analysis.get("title"):
  148. print(f"论文标题:{analysis['title']}")
  149. if analysis.get("core_concepts"):
  150. print(
  151. f"核心概念:{', '.join(analysis['core_concepts'][:5])}"
  152. ) # 最多显示5个
  153. if analysis.get("description"):
  154. print(f"描述:{analysis['description']}")
  155. if analysis.get("stars", 0) > 0:
  156. print(f"⭐ Stars: {analysis['stars']}")
  157. return input("\n🎯 你想达到什么学习程度?(请用自然语言描述)\n> ")
  158. def _search_learning_resources(self, query: str) -> str:
  159. """
  160. 搜索学习资源
  161. Args:
  162. query: 搜索查询
  163. Returns:
  164. 搜索结果
  165. """
  166. # 简化实现,返回通用建议
  167. return f"为 '{query}' 找到的学习资源:在线课程、书籍、文档、实战项目"
  168. def _generate_plan(self, analysis: dict, goal: str, resources: str) -> str:
  169. """
  170. 生成学习计划
  171. Args:
  172. analysis: 分析结果
  173. goal: 学习目标
  174. resources: 学习资源
  175. Returns:
  176. 学习计划内容
  177. """
  178. user_prompt = f"""请为以下场景生成学习计划(Markdown格式):
  179. 【领域/主题】
  180. {analysis.get('domain', '未知')}
  181. 【技术栈】
  182. {', '.join(analysis.get('tech_stack', ['无']))}
  183. 【前置知识要求】
  184. {', '.join(analysis.get('prerequisites', ['无']))}
  185. 【学习目标】
  186. {goal}
  187. 【参考资源】
  188. {resources}
  189. 请生成结构化的学习计划,包括:
  190. 1. 领域概述(100字)
  191. 2. 前置知识检查清单
  192. 3. 分阶段学习路径(3-5个阶段)
  193. 4. 每个阶段的具体学习内容
  194. 5. 推荐资源(书籍、课程、文档)
  195. 6. 里程碑和自我评估标准
  196. """
  197. messages = [
  198. {
  199. "role": "system",
  200. "content": "你是一个专业的学习规划助手,擅长创建结构化的学习计划。",
  201. },
  202. {"role": "user", "content": user_prompt},
  203. ]
  204. if self.streaming:
  205. from utils.streaming import stream_response
  206. return stream_response(self.llm, messages)
  207. else:
  208. return self.llm.invoke(messages)
  209. def run(self, input_data: str) -> str:
  210. """
  211. 执行学习计划创建流程
  212. Args:
  213. input_data: 用户输入(领域描述/GitHub URL/PDF路径)
  214. Returns:
  215. 执行结果
  216. """
  217. # 步骤1:识别输入类型
  218. input_type = self._identify_input_type(input_data)
  219. # 步骤2:根据类型处理
  220. if input_type == "github_url":
  221. analysis = self._analyze_github_repo(input_data)
  222. elif input_type == "pdf_paper":
  223. analysis = self._analyze_pdf_paper(input_data)
  224. else: # domain_description
  225. analysis = {"domain": input_data, "tech_stack": [], "prerequisites": []}
  226. # 步骤3:确认学习目标
  227. learning_goal = self._ask_learning_goal(analysis)
  228. # 步骤4:搜索学习路径
  229. search_query = f"{analysis['domain']} 学习路径 {learning_goal}"
  230. learning_resources = self._search_learning_resources(search_query)
  231. # 步骤5:生成计划
  232. plan = self._generate_plan(analysis, learning_goal, learning_resources)
  233. # 步骤6:保存计划
  234. domain = analysis["domain"]
  235. self.file_manager.create_domain(domain)
  236. self.file_manager.save_plan(domain, plan)
  237. return f"✅ 学习计划已创建:{domain}\n\n{plan}"