import os
import re
from typing import List, Optional, Tuple
import pandas as pd
from langchain_community.chat_message_histories.in_memory import ChatMessageHistory
from langchain_core.language_models.chat_models import BaseChatModel
from langchain.output_parsers import PydanticOutputParser, OutputFixingParser,JsonOutputToolsParser
from langchain.schema.output_parser import StrOutputParser
from langchain.tools.base import BaseTool
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.tools import render_text_description
from pydantic import ValidationError
from langchain_core.prompts import HumanMessagePromptTemplate
from Agent.Action import Action
class ReActAgent:
@staticmethod
def extract_json_from_action_rst(text: str) -> str | None:
"""
从RST格式文本中提取JSON代码块内容
该函数使用正则表达式匹配文本中的json代码块,并返回最后一个匹配到的JSON字符串。
参数:
text (str): 包含RST格式的输入文本,可能包含代码块
返回值:
str | None: 如果找到JSON代码块则返回其中的内容(不包括json和标记),
如果没有找到则返回None
"""
# 使用正则表达式匹配json代码块
pattern = re.compile(r'```json(.*?)```', flags=re.DOTALL)
matches = pattern.findall(text)
if matches:
json_str = matches[-1]
return json_str
return None
@staticmethod
def format_observations(response: str, action: Action, observation: str) -> str:
"""
格式化观察结果,去除响应中的JSON代码块,并将动作和观察结果追加到响应末尾
参数:
response (str): 原始响应字符串,可能包含JSON代码块
action (Action): 执行的动作对象
observation (str): 动作执行后的观察结果
返回值:
str: 格式化后的字符串,包含清理后的响应内容、动作信息和观察结果
"""
rst = re.sub(r"```json(.*?)```", "", response, flags=re.DOTALL)
rst += "\n" + str(action) + "\n返回结果为:\n" + observation
return rst
def __init__(self, llm: BaseChatModel, tools: List[BaseTool], main_prompt_file_path: str):
"""
初始化智能体构造器
参数:
llm (BaseChatModel): 基础聊天模型实例
tools (List[BaseTool]): 可用工具列表
main_prompt_file_path (str): 主提示词文件路径
"""
self.llm = llm
self.tools = tools
self.output_parser = PydanticOutputParser(pydantic_object=Action)
try:
self.robust_parser = OutputFixingParser.from_llm(
parser=self.output_parser,
llm=llm
)
except Exception as e:
print(f"不能修复的输出. 错误: {e}")
print("请尝试提升提示词质量或者使用一个更加合适的大模型")
self.main_prompt_file_path = main_prompt_file_path
self.__init_prompts()
self.__init_chains()
def __init_prompts(self):
"""
初始化提示模板
该方法从指定文件路径读取主提示内容,并构建聊天提示模板。
模板包含聊天历史占位符和人类消息模板,同时将工具信息、工具名称列表
和输出格式指令作为部分参数绑定到模板中。
"""
with open(os.path.join(os.getcwd(), self.main_prompt_file_path), 'r', encoding='utf-8') as f:
self.prompt = ChatPromptTemplate.from_messages([
MessagesPlaceholder(variable_name="chat_history"),
HumanMessagePromptTemplate.from_template(f.read()), ]).partial(
tools=render_text_description(self.tools),
tool_names=",".join([tool.name for tool in self.tools]),
format_instructions=self.output_parser.get_format_instructions())
def __init_chains(self):
"""
初始化主链对象
该方法创建一个由提示词、语言模型和字符串输出解析器组成的处理链,
用于处理输入并生成相应的文本输出。
"""
self.main_chains = (self.prompt | self.llm | StrOutputParser())
def confirm_tool(self, tool_name: str) -> Optional[BaseTool]:
"""
根据工具名称查找并返回对应的工具对象
参数:
tool_name (str): 要查找的工具名称
返回值:
Optional[BaseTool]: 如果找到匹配的工具则返回该工具对象,否则返回None
"""
# print(f'confirm_tool:{tool_name}')
for tool in self.tools:
if tool.name == tool_name:
return tool
return None
def step_by_step(self, task, short_term_memory=None, chat_history=None, verbose=False) -> Tuple[Action, str]:
"""
执行逐步任务处理,将任务分解为多个步骤并生成相应的动作和响应
参数:
task (str): 需要处理的任务描述
short_term_memory (list, optional): 短期记忆列表,包含之前的执行步骤
chat_history (object, optional): 聊天历史对象,包含对话记录
verbose (bool): 是否启用详细输出模式
返回值:
Tuple[Action, str]: 返回一个元组,包含解析后的动作对象和完整的响应字符串
"""
inputs = {
"input": task,
"agent_scratchpad": "\n".join(short_term_memory),
"chat_history": chat_history.messages if chat_history else chat_history,
}
response = ""
for s in self.main_chains.stream(inputs):
response += s
print(f'response:{response}')
action_rst_json = self.extract_json_from_action_rst(response)
print(f'json_action:{action_rst_json}')
action = self.robust_parser.parse(
action_rst_json if isinstance(action_rst_json, dict) else response
)
print(f'action:{action}')
return action, response
def exec_actions(self, action: Action) -> str:
"""
执行指定的动作并返回观察结果
参数:
action (Action): 要执行的动作对象,包含工具名称和参数
返回值:
str: 动作执行后的观察结果或错误信息
"""
# print(f'action in exec_actions:{action}, action tool_name in exec_actions:{action.tool_name}')
tool = self.confirm_tool(action.tool_name)
# print(f'tool in exec_actions:{tool}')
if tool is None:
observation = (
f"Error:没找到待执行的工具或者指令 '{action.tool_name}'."
f"请从提供的工具或者指令列表中选择,注意按对顶格式输出内容。"
)
else:
try:
print(f'tool 执行:{action.args}')
observation = tool.run(action.args)
print(f'tool执行结果:{observation}')
except ValidationError as e:
observation = (
f"执行动作时参数有非法错误 in exec_actions:{str(e)},args:{action.args}"
)
print(f'action in exec_actions:{action.args}')
except Exception as e:
observation = f"Error in exec_actions:{str(e)},{type(e).__name__},args:{action.args}"
return observation
# 基础版
# def execute(self, task: str, uuid: int, chat_history: ChatMessageHistory, verbose=False) -> str:
# """
# 执行任务的主要方法
# 参数:
# task (str): 需要执行的任务描述
# uuid (int): 任务的唯一标识符
# chat_history (ChatMessageHistory): 聊天历史记录对象
# verbose (bool): 是否启用详细输出模式,默认为False
# 返回值:
# str: 执行任务后得到的答案
# """
# short_term_memory = []
# action, response = self.step_by_step(task=task, short_term_memory=short_term_memory,
# chat_history=chat_history, verbose=verbose)
# print(f'execute方法:action:{action},response:{response}')
#
# answer = self.exec_actions(action)
# print(f'answer:{answer}')
#
# short_term_memory.append(self.format_observations(response, action, answer))
#
# chat_history.add_user_message(task)
# chat_history.add_ai_message(answer)
# return answer
# 改进版
def execute(self, task: str, uuid: int, chat_history: ChatMessageHistory, verbose=False) -> str:
"""执行任务的主循环"""
short_term_memory = []
max_iterations = 10 # 防止无限循环
iteration = 0
while iteration < max_iterations:
iteration += 1
# 1. 思考:让LLM决定下一步行动
action, response = self.step_by_step(
task=task,
short_term_memory=short_term_memory,
chat_history=chat_history,
verbose=verbose
)
# 2. 判断是否应该终止(当action为"finish"时)
if action.tool_name == "finish" or action.tool_name == "Final Answer":
return action.args.get("answer", "任务完成")
# 3. 执行行动
observation = self.exec_actions(action)
# 4. 观察:记录结果到短期记忆
formatted_obs = self.format_observations(response, action, observation)
short_term_memory.append(formatted_obs)
if verbose:
print(f"Step {iteration}: {action.tool_name} -> {observation[:100]}...")
return "达到最大迭代次数,任务未完成"