react_agent.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. """ReAct Agent实现 - 推理与行动结合的智能体"""
  2. import re
  3. from typing import Optional, List, Tuple, Iterator
  4. from ..core.agent import Agent
  5. from ..core.llm import HelloAgentsLLM
  6. from ..core.config import Config
  7. from ..core.stream import StreamEvent
  8. from ..tools.registry import ToolRegistry
  9. # 默认ReAct提示词模板
  10. DEFAULT_REACT_PROMPT = """你是一个具备推理和行动能力的AI助手。你可以通过思考分析问题,然后调用合适的工具来获取信息,最终给出准确的答案。
  11. ## 可用工具
  12. {tools}
  13. ## 工作流程
  14. 请严格按照以下格式进行回应,每次只能执行一个步骤:
  15. Thought: 分析问题,确定需要什么信息,制定研究策略。
  16. Action: 选择合适的工具获取信息,格式为:
  17. - `{{tool_name}}[{{tool_input}}]`:调用工具获取信息。
  18. - `Finish[研究结论]`:当你有足够信息得出结论时。
  19. ## 重要提醒
  20. 1. 每次回应必须包含Thought和Action两部分
  21. 2. 工具调用的格式必须严格遵循:工具名[参数]
  22. 3. 只有当你确信有足够信息回答问题时,才使用Finish
  23. 4. 如果工具返回的信息不够,继续使用其他工具或相同工具的不同参数
  24. ## 当前任务
  25. **Question:** {question}
  26. ## 执行历史
  27. {history}
  28. 现在开始你的推理和行动:"""
  29. class ReActAgent(Agent):
  30. """
  31. ReAct (Reasoning and Acting) Agent
  32. 结合推理和行动的智能体,能够:
  33. 1. 分析问题并制定行动计划
  34. 2. 调用外部工具获取信息
  35. 3. 基于观察结果进行推理
  36. 4. 迭代执行直到得出最终答案
  37. 这是一个经典的Agent范式,特别适合需要外部信息的任务。
  38. """
  39. def __init__(
  40. self,
  41. name: str,
  42. llm: HelloAgentsLLM,
  43. tool_registry: Optional[ToolRegistry] = None,
  44. system_prompt: Optional[str] = None,
  45. config: Optional[Config] = None,
  46. max_steps: int = 5,
  47. custom_prompt: Optional[str] = None,
  48. ):
  49. """
  50. 初始化ReActAgent
  51. Args:
  52. name: Agent名称
  53. llm: LLM实例
  54. tool_registry: 工具注册表(可选,如果不提供则创建空的工具注册表)
  55. system_prompt: 系统提示词
  56. config: 配置对象
  57. max_steps: 最大执行步数
  58. custom_prompt: 自定义提示词模板
  59. """
  60. super().__init__(name, llm, system_prompt, config)
  61. # 如果没有提供tool_registry,创建一个空的
  62. if tool_registry is None:
  63. self.tool_registry = ToolRegistry()
  64. else:
  65. self.tool_registry = tool_registry
  66. self.max_steps = max_steps
  67. self.current_history: List[str] = []
  68. # 设置提示词模板:用户自定义优先,否则使用默认模板
  69. self.prompt_template = custom_prompt if custom_prompt else DEFAULT_REACT_PROMPT
  70. def add_tool(self, tool):
  71. """添加工具到工具注册表"""
  72. self.tool_registry.register_tool(tool)
  73. def run(self, input_text: str, **kwargs) -> str:
  74. """
  75. 运行ReAct Agent
  76. Args:
  77. input_text: 用户问题
  78. **kwargs: 支持 conversation_id 参数
  79. Returns:
  80. 最终答案
  81. """
  82. conversation_id = kwargs.pop("conversation_id", None)
  83. self.current_history = []
  84. current_step = 0
  85. print(f"\n🤖 {self.name} 开始处理问题: {input_text}")
  86. while current_step < self.max_steps:
  87. current_step += 1
  88. print(f"\n--- 第 {current_step} 步 ---")
  89. tools_desc = self.tool_registry.get_tools_description()
  90. history_str = "\n".join(self.current_history)
  91. prompt = self.prompt_template.format(
  92. tools=tools_desc, question=input_text, history=history_str
  93. )
  94. messages = [{"role": "user", "content": prompt}]
  95. response_text = self.llm.invoke(messages, **kwargs)
  96. if not response_text:
  97. print("❌ 错误:LLM未能返回有效响应。")
  98. break
  99. thought, action = self._parse_output(response_text)
  100. if thought:
  101. print(f"🤔 思考: {thought}")
  102. if not action:
  103. print("⚠️ 警告:未能解析出有效的Action,流程终止。")
  104. break
  105. if action.startswith("Finish"):
  106. final_answer = self._parse_action_input(action)
  107. print(f"🎉 最终答案: {final_answer}")
  108. self._save_conversation_messages(
  109. input_text, final_answer, conversation_id
  110. )
  111. return final_answer
  112. tool_name, tool_input = self._parse_action(action)
  113. if not tool_name or tool_input is None:
  114. self.current_history.append("Observation: 无效的Action格式,请检查。")
  115. continue
  116. print(f"🎬 行动: {tool_name}[{tool_input}]")
  117. observation = self.tool_registry.execute_tool(tool_name, tool_input)
  118. print(f"👀 观察: {observation}")
  119. self.current_history.append(f"Action: {action}")
  120. self.current_history.append(f"Observation: {observation}")
  121. print("⏰ 已达到最大步数,流程终止。")
  122. final_answer = "抱歉,我无法在限定步数内完成这个任务。"
  123. self._save_conversation_messages(input_text, final_answer, conversation_id)
  124. return final_answer
  125. def stream_run(self, input_text: str, **kwargs) -> Iterator[StreamEvent]:
  126. """
  127. 流式运行ReAct Agent,输出Thought/Action/Observation事件
  128. Args:
  129. input_text: 用户问题
  130. **kwargs: 支持 conversation_id 参数
  131. Yields:
  132. StreamEvent: 流式事件
  133. """
  134. conversation_id = kwargs.pop("conversation_id", None)
  135. yield StreamEvent.status(f"开始处理问题: {input_text}")
  136. self.current_history = []
  137. current_step = 0
  138. while current_step < self.max_steps:
  139. current_step += 1
  140. yield StreamEvent.status(f"第 {current_step}/{self.max_steps} 步")
  141. tools_desc = self.tool_registry.get_tools_description()
  142. history_str = "\n".join(self.current_history)
  143. prompt = self.prompt_template.format(
  144. tools=tools_desc, question=input_text, history=history_str
  145. )
  146. messages = [{"role": "user", "content": prompt}]
  147. full_response = ""
  148. for chunk in self.llm.stream_invoke(messages, **kwargs):
  149. if chunk:
  150. full_response += chunk
  151. yield StreamEvent.text(chunk)
  152. if not full_response:
  153. yield StreamEvent.error("LLM未能返回有效响应")
  154. break
  155. thought, action = self._parse_output(full_response)
  156. if thought:
  157. yield StreamEvent.thought(thought)
  158. if not action:
  159. yield StreamEvent.status("未能解析出有效的Action,流程终止")
  160. break
  161. if action.startswith("Finish"):
  162. final_answer = self._parse_action_input(action)
  163. yield StreamEvent.action("Finish", action=action)
  164. yield StreamEvent.text(final_answer)
  165. self._save_conversation_messages(
  166. input_text, final_answer, conversation_id
  167. )
  168. yield StreamEvent.done(final_answer)
  169. return
  170. tool_name, tool_input = self._parse_action(action)
  171. if not tool_name or tool_input is None:
  172. self.current_history.append("Observation: 无效的Action格式")
  173. continue
  174. yield StreamEvent.action(action, tool_name=tool_name, tool_input=tool_input)
  175. observation = self.tool_registry.execute_tool(tool_name, tool_input)
  176. yield StreamEvent.observation(str(observation))
  177. self.current_history.append(f"Action: {action}")
  178. self.current_history.append(f"Observation: {observation}")
  179. yield StreamEvent.status("已达到最大步数,流程终止")
  180. final_answer = "抱歉,我无法在限定步数内完成这个任务。"
  181. self._save_conversation_messages(input_text, final_answer, conversation_id)
  182. yield StreamEvent.done(final_answer)
  183. def _parse_output(self, text: str) -> Tuple[Optional[str], Optional[str]]:
  184. """解析LLM输出,提取思考和行动"""
  185. thought_match = re.search(r"Thought: (.*)", text)
  186. action_match = re.search(r"Action: (.*)", text)
  187. thought = thought_match.group(1).strip() if thought_match else None
  188. action = action_match.group(1).strip() if action_match else None
  189. return thought, action
  190. def _parse_action(self, action_text: str) -> Tuple[Optional[str], Optional[str]]:
  191. """解析行动文本,提取工具名称和输入"""
  192. match = re.match(r"(\w+)\[(.*)\]", action_text)
  193. if match:
  194. return match.group(1), match.group(2)
  195. return None, None
  196. def _parse_action_input(self, action_text: str) -> str:
  197. """解析行动输入"""
  198. match = re.match(r"\w+\[(.*)\]", action_text)
  199. return match.group(1) if match else ""