main_agent.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. # core/main_agent.py
  2. """主 Agent - 协调层,负责意图识别和路由"""
  3. from hello_agents import SimpleAgent, HelloAgentsLLM
  4. from core.file_manager import FileManager
  5. class MainAgent(SimpleAgent):
  6. """
  7. 系统协调者,负责意图识别和路由
  8. 职责:
  9. - 接收用户输入
  10. - 识别用户意图(create/add/vibe/summary/help/exit)
  11. - 路由到相应的子 Agent 或处理器
  12. - 管理基本命令(help, list, exit)
  13. """
  14. # 意图关键词映射(按优先级排序,更具体的在前)
  15. INTENT_KEYWORDS = {
  16. "create": [
  17. "/create",
  18. "创建计划",
  19. "制定学习路径",
  20. "我想学",
  21. "我想学习",
  22. "学习计划", # 移除单独的"学习"以避免冲突
  23. ],
  24. "add": ["/add", "添加笔记", "记录知识", "添加知识"],
  25. "vibe": ["/vibe", "开始学习", "互动学习", "练习", "考察"],
  26. "summary": ["/summary", "学习进度", "总结", "评估"],
  27. "help": ["/help", "帮助", "help"],
  28. "list": ["/list", "列出所有", "所有领域", "列表"],
  29. "exit": ["/exit", "退出", "quit", "exit"],
  30. }
  31. def __init__(self, llm: HelloAgentsLLM, file_manager: FileManager, streaming: bool = None):
  32. """
  33. 初始化主 Agent
  34. Args:
  35. llm: HelloAgentsLLM 实例
  36. file_manager: FileManager 实例
  37. streaming: 是否启用流式输出(None = 自动检测)
  38. """
  39. system_prompt = """
  40. 你是 LearningAgent 学习助手的主界面。
  41. 支持的功能:
  42. 1. 创建学习计划 (/create, "我想学习")
  43. 2. 添加知识笔记 (/add, "添加笔记")
  44. 3. 开始互动学习 (/vibe, "开始学习")
  45. 4. 查看学习总结 (/summary, "总结")
  46. 5. 显示帮助 (/help, "帮助")
  47. 6. 列出所有领域 (/list)
  48. 7. 退出程序 (/exit, "退出")
  49. 识别用户意图后,调用相应的功能。
  50. 如果意图模糊,询问用户确认。
  51. """
  52. self.llm = llm
  53. self.file_manager = file_manager
  54. # 添加流式输出支持
  55. from utils.streaming import should_stream
  56. self.streaming = should_stream(streaming)
  57. # 会话状态管理
  58. self.active_session = None # {"domain": str, "mode": str, "round": int}
  59. # 使用父类初始化
  60. super().__init__("MainAgent", llm, system_prompt)
  61. def _identify_intent(self, user_input: str) -> str:
  62. """
  63. 识别用户意图
  64. Args:
  65. user_input: 用户输入
  66. Returns:
  67. 意图类型(create/add/vibe/summary/help/list/exit/unknown)
  68. """
  69. user_input_lower = user_input.lower().strip()
  70. # 检查每个意图的关键词
  71. for intent, keywords in self.INTENT_KEYWORDS.items():
  72. for keyword in keywords:
  73. if keyword.lower() in user_input_lower:
  74. return intent
  75. return "unknown"
  76. def process_command(self, user_input: str) -> str:
  77. """
  78. 处理用户命令
  79. Args:
  80. user_input: 用户输入
  81. Returns:
  82. 处理结果
  83. """
  84. # 检查是否有活跃的 vibe 会话
  85. if self.active_session is not None:
  86. # 检查是否要退出会话
  87. if self._is_exit_command(user_input):
  88. return self._end_vibe_session()
  89. # 否则继续对话
  90. return self._continue_vibe_session(user_input)
  91. # 正常命令处理
  92. intent = self._identify_intent(user_input)
  93. if intent == "create":
  94. return self._route_to_create_plan(user_input)
  95. elif intent == "add":
  96. return self._route_to_add_knowledge(user_input)
  97. elif intent == "vibe":
  98. return self._route_to_vibe_learning(user_input)
  99. elif intent == "summary":
  100. return self._route_to_summary(user_input)
  101. elif intent == "help":
  102. return self._show_help()
  103. elif intent == "list":
  104. return self._list_domains()
  105. elif intent == "exit":
  106. return "EXIT"
  107. elif intent == "unknown":
  108. return "❓ 未识别的命令。输入 /help 查看帮助。"
  109. def _route_to_create_plan(self, input_data: str) -> str:
  110. """
  111. 路由到 CreatePlanAgent
  112. Args:
  113. input_data: 用户输入
  114. Returns:
  115. 执行结果
  116. """
  117. from agents.create_plan_agent import CreatePlanAgent
  118. try:
  119. # 去掉命令前缀,只保留参数部分
  120. # 支持: "/create 数学", "创建计划 数学", "我想学习数学"
  121. clean_input = input_data
  122. # 去掉 /create 前缀
  123. for prefix in ["/create", "/CREATE"]:
  124. if clean_input.startswith(prefix):
  125. clean_input = clean_input[len(prefix) :].strip()
  126. break
  127. # 如果是自然语言形式,保留原样
  128. # 例如: "我想学习数学", "创建一个学习计划"
  129. if not clean_input or clean_input == input_data:
  130. clean_input = input_data
  131. agent = CreatePlanAgent(self.llm, streaming=self.streaming)
  132. return agent.run(clean_input)
  133. except Exception as e:
  134. return f"❌ 创建学习计划失败:{e}"
  135. def _route_to_add_knowledge(self, input_data: str) -> str:
  136. """
  137. 路由到 AddKnowledgeProcessor
  138. Args:
  139. input_data: 用户输入
  140. Returns:
  141. 执行结果
  142. """
  143. from processors.add_knowledge import AddKnowledgeProcessor
  144. try:
  145. # 去掉命令前缀,只保留参数部分
  146. # 支持: "/add 数学 算法", "添加笔记", "记录知识"
  147. clean_input = input_data
  148. # 去掉 /add 前缀
  149. for prefix in ["/add", "/ADD"]:
  150. if clean_input.startswith(prefix):
  151. clean_input = clean_input[len(prefix) :].strip()
  152. break
  153. # 如果是自然语言形式,需要询问用户输入内容和领域
  154. if not clean_input or clean_input == input_data or len(clean_input) < 10:
  155. return self._ask_for_knowledge_input()
  156. # 解析输入(格式:领域 内容)
  157. # 例如: "机器学习 决策树算法"
  158. parts = clean_input.split(maxsplit=1)
  159. if len(parts) == 2:
  160. domain, content = parts
  161. domain = domain.strip()
  162. content = content.strip()
  163. else:
  164. # 无法解析,询问用户
  165. return self._ask_for_knowledge_input()
  166. processor = AddKnowledgeProcessor(self.llm, self.file_manager)
  167. return processor.add(domain, content)
  168. except Exception as e:
  169. return f"❌ 添加知识失败:{e}"
  170. def _ask_for_knowledge_input(self) -> str:
  171. """
  172. 询问用户知识内容和领域
  173. Returns:
  174. 提示信息
  175. """
  176. return """📝 添加知识笔记
  177. 请按以下格式输入:
  178. > /add <领域> <知识内容>
  179. 例如:
  180. > /add 机器学习 # 决策树算法简介
  181. 或者直接输入内容(会询问领域):
  182. > /add 决策树是一种监督学习算法,用于分类和回归...
  183. 💡 提示:长内容可以先在编辑器中准备好,然后一次性粘贴。
  184. """
  185. def _route_to_vibe_learning(self, input_data: str) -> str:
  186. """
  187. 路由到 VibeLearningAgent
  188. Args:
  189. input_data: 用户输入
  190. Returns:
  191. 执行结果
  192. """
  193. from agents.vibe_learning_agent import VibeLearningAgent
  194. try:
  195. # 去掉命令前缀,只保留参数部分
  196. # 支持: "/vibe Python", "/vibe Python --mode quiz"
  197. clean_input = input_data
  198. # 去掉 /vibe 前缀
  199. for prefix in ["/vibe", "/VIBE", "/Vibe"]:
  200. if clean_input.startswith(prefix):
  201. clean_input = clean_input[len(prefix):].strip()
  202. break
  203. # 如果是自然语言形式,询问用户
  204. if not clean_input or len(clean_input.split()) < 1:
  205. return self._ask_for_vibe_input()
  206. # 解析输入
  207. # 格式: <领域> [--mode <mode>]
  208. parts = clean_input.split()
  209. domain = parts[0].strip()
  210. # 检查是否有模式选项
  211. mode = "free" # 默认模式
  212. if "--mode" in parts:
  213. mode_idx = parts.index("--mode")
  214. if mode_idx + 1 < len(parts):
  215. mode = parts[mode_idx + 1].strip().lower()
  216. if mode not in ["free", "quiz"]:
  217. return "❌ 无效的模式。请使用 --mode free 或 --mode quiz"
  218. # 启动学习会话
  219. agent = VibeLearningAgent(self.llm, self.file_manager,
  220. streaming=self.streaming)
  221. result = agent.start_session(domain, mode=mode)
  222. # 设置活跃会话
  223. self.active_session = {
  224. "domain": domain,
  225. "mode": mode,
  226. "round": 1,
  227. "agent": agent,
  228. "streaming": self.streaming # 保存 streaming 设置
  229. }
  230. return result
  231. except Exception as e:
  232. return f"❌ 启动互动学习失败:{e}"
  233. def _route_to_summary(self, input_data: str) -> str:
  234. """
  235. 路由到 SummaryAgent
  236. Args:
  237. input_data: 用户输入
  238. Returns:
  239. 执行结果
  240. """
  241. from agents.summary_agent import SummaryAgent
  242. try:
  243. # 去掉命令前缀,只保留参数部分
  244. # 支持: "/summary Python", "总结学习进度"
  245. clean_input = input_data
  246. # 去掉 /summary 前缀
  247. for prefix in ["/summary", "/SUMMARY", "/Summary"]:
  248. if clean_input.startswith(prefix):
  249. clean_input = clean_input[len(prefix):].strip()
  250. break
  251. # 如果是自然语言形式,询问用户
  252. if not clean_input or len(clean_input.split()) < 1:
  253. return self._ask_for_summary_input()
  254. # 解析输入
  255. # 格式: <领域>
  256. domain = clean_input.split()[0].strip()
  257. # 生成学习总结
  258. agent = SummaryAgent(self.llm, self.file_manager,
  259. streaming=self.streaming)
  260. return agent.run(domain)
  261. except Exception as e:
  262. return f"❌ 生成学习总结失败:{e}"
  263. def _is_exit_command(self, user_input: str) -> bool:
  264. """
  265. 检查是否是退出命令
  266. Args:
  267. user_input: 用户输入
  268. Returns:
  269. 是否是退出命令
  270. """
  271. exit_keywords = ["/exit", "exit", "退出", "quit", "/quit", "结束", "完成"]
  272. return user_input.strip().lower() in exit_keywords
  273. def _end_vibe_session(self) -> str:
  274. """
  275. 结束 vibe 会话
  276. Returns:
  277. 结束消息
  278. """
  279. domain = self.active_session["domain"]
  280. mode = self.active_session["mode"]
  281. rounds = self.active_session["round"]
  282. # 清除会话状态
  283. self.active_session = None
  284. return f"""✅ 会话已结束
  285. 📁 领域: {domain}
  286. 📝 模式: {mode}
  287. 💬 对话轮数: {rounds}
  288. 💡 上下文已保存。输入 /help 查看可用命令。
  289. """
  290. def _continue_vibe_session(self, user_input: str) -> str:
  291. """
  292. 继续 vibe 会话
  293. Args:
  294. user_input: 用户回答
  295. Returns:
  296. 反馈和下一个问题
  297. """
  298. try:
  299. agent = self.active_session["agent"]
  300. domain = self.active_session["domain"]
  301. mode = self.active_session["mode"]
  302. # 确保 streaming 设置一致
  303. agent.streaming = self.active_session.get("streaming", agent.streaming)
  304. # 继续对话(获取反馈和下一个问题)
  305. result = agent.continue_session(domain, user_input, mode)
  306. # 增加轮次计数
  307. self.active_session["round"] += 1
  308. return result
  309. except Exception as e:
  310. # 发生错误时清除会话状态
  311. self.active_session = None
  312. return f"❌ 对话过程中发生错误:{e}\n\n会话已结束。"
  313. def _ask_for_vibe_input(self) -> str:
  314. """
  315. 询问用户互动学习参数
  316. Returns:
  317. 提示信息
  318. """
  319. return """📝 互动学习
  320. 请按以下格式输入:
  321. > /vibe <领域> [--mode <模式>]
  322. 例如:
  323. > /vibe Python
  324. > /vibe Python --mode quiz
  325. 模式说明:
  326. - free: 自由对话模式(默认)
  327. - quiz: 测验模式
  328. 💡 提示:需要先使用 /create 创建学习计划。
  329. """
  330. def _ask_for_summary_input(self) -> str:
  331. """
  332. 询问用户学习总结参数
  333. Returns:
  334. 提示信息
  335. """
  336. return """📝 学习进度总结
  337. 请按以下格式输入:
  338. > /summary <领域>
  339. 例如:
  340. > /summary Python
  341. > /summary 机器学习
  342. 💡 提示:需要先使用 /create 创建学习计划。
  343. """
  344. def _show_help(self) -> str:
  345. """显示帮助信息"""
  346. return """
  347. # 🤖 LearningAgent 帮助
  348. ## 命令列表
  349. ### 创建学习计划
  350. - `/create <领域>` - 创建学习计划
  351. 例:`/create 数学`
  352. 例:`/create https://github.com/user/project`
  353. 例:`/create ~/paper.pdf`
  354. - 自然语言:`我想学习数学`
  355. ### 添加知识笔记 ✨ 新功能
  356. - `/add <领域> <内容>` - 添加知识笔记
  357. 例:`/add 机器学习 # 决策树算法简介`
  358. 例:`/add Python 这是一个列表推导式的例子...`
  359. - 文件输入:`/add ~/notes.md`
  360. - 自然语言:`添加笔记` `记录知识`
  361. ### 开始互动学习 ✨ 新功能
  362. - `/vibe <领域>` - 开始互动学习
  363. 例:`/vibe Python`
  364. 例:`/vibe Python --mode quiz`
  365. - 模式说明:
  366. - `free`: 自由对话模式(默认)
  367. - `quiz`: 测验模式
  368. - 退出会话:输入"退出"、"exit"或"/exit"即可随时结束
  369. - 自然语言:`开始学习数学` `练习一下`
  370. ### 查看学习总结 ✨ 新功能
  371. - `/summary <领域>` - 查看学习总结
  372. 例:`/summary Python`
  373. 例:`/summary 机器学习`
  374. - 自然语言:`总结学习进度` `评估我的水平`
  375. ### 其他命令
  376. - `/list` - 列出所有学习领域
  377. - `/help` - 显示帮助
  378. - `/exit` 或 `exit` - 退出程序
  379. ## 提示
  380. - 支持命令前缀(如 `/create`)和自然语言(如"我想学习")
  381. - 添加知识时,内容会自动分析、分类并打标签
  382. - 随时输入 `/help` 查看帮助
  383. """
  384. def _list_domains(self) -> str:
  385. """列出所有学习领域"""
  386. domains = self.file_manager.list_domains()
  387. if not domains:
  388. return "📭 还没有创建任何学习领域。\n使用 `/create` 创建第一个学习计划。"
  389. domain_list = "\n".join([f"- {domain}" for domain in domains])
  390. return f"# 📚 学习领域\n\n{domain_list}\n\n共 {len(domains)} 个领域"
  391. def list_domains(self) -> list:
  392. """
  393. 获取所有领域列表
  394. Returns:
  395. 领域名称列表
  396. """
  397. return self.file_manager.list_domains()