ReAct,reasoning-acting,全称是 Synergizing Reasoning and Acting in Language Models(在语言模型中协同推理与行动);他是一种限制大模型思考方式的机制,核心TAO是一个循环:
Thought(思考)→Acting(执行)→Observation(观察)
当我们把一个问题抛给ReAct模型时,他会先进入Thought步骤:“基于我的知识和推理能力能否直接回答这个问题,如果不能,我还缺少什么信息”;当模型在第一步骤中发现并确认了解答问题所缺失的信息时,会进入Acting步骤:尝试调用工具来获取需要的信息(api/代码/数据库查询等);在拿到了acting步骤的返回结果后,模型会进入第三步Observation,提取/整理acting的结果并将其拼接到下一轮的上下文中,再喂给Thought...
最终在某一次的thought中,模型认为当前上下文中包含的信息(加上模型自身的知识储备和推理能力)已经能解答问题了,于是不在进行acting,而是输出final answer;
所以本质上分工是这样的:
thought负责判断当前问题能否回答/还缺少哪些信息/对于缺少的信息需要调用哪些工具来获取
acting负责调用工具
observation负责获取并整理acting的结果,将结果拼接到下一轮的上下文(提示词)中
这个时候我们发现,随着循环次数的递增,observation不断更新/堆叠上下文,每一次丢给模型进行thought的上下文会越来越长,毫无疑问这会烧掉越来越多的token;
怎么解决这个问题,有以下几个办法:
1.直接截断(Truncation):
context = context[-k:],只保留最近k轮次的对话,保留“短期记忆”,避免上下文爆炸;但是本质上是会“遗忘”之前的信息的
2.总结(Summarization Memory):
把该轮的对话结果整合/压缩为一句话(summary)再添加到context中,避免原始信息(TAO)过多导致context迅速增长;
这里要讲几种主流的压缩方式:
1.全量压缩:
在不改变原TAO内容的情况下,在每次的TAO结束并更新之后对已有的所有TAO内容进行总结和压缩;每次总结压缩的原文本都在增长;
summary = LLM(全部历史context)
context = [summary] (当前轮次用的context)
- 成本极高
- 每次都喂全部上下文给 LLM
- 信息不断丢失(信息熵递减)
- summary → 再 summary → 越来越模糊
- 不稳定(hallucination 累积)
2.单步压缩:
只对当前轮次的TAO内容进行summary
step_summary = LLM(当前 Thought + Action + Observation)
context.append(step_summary)
优点
- 控制 token(单次文本短)
- 比较稳定
但是缺乏全局语义整合
例如:
Step1: CEO = Tim Cook
Step2: 出生地 = Alabama
模型可能意识不到这是同一个人
3.增量压缩(Incremental Summary)(主流方案):
用“已有 summary + 新 step”生成新的 summary
New Summary = f(Old Summary + New TAO),保证了每轮TAO之间的关联性
案例:
Step 1
TAO:
- 查Apple CEO → Tim Cook
生成:
Summary: Apple CEO is Tim Cook
Step 2
新 TAO:
- 查出生地 → Alabama
更新
输入给 LLM:
Old Summary: Apple CEO is Tim Cook
New Info: Tim Cook was born in Alabama
输出:
Summary: Apple CEO Tim Cook was born in Alabama
Step 3(继续)
New Info: He became CEO in 2011
更新:
Summary: Apple CEO Tim Cook, born in Alabama, has led Apple since 2011
3.结构化Observation:
原本的获取结果:Observation: 18°C, cloudy, 20% rain
但在工程中常用:
Observation: {
"temperature": 18,
"weather": "cloudy",
"rain_prob": 0.2
}
优势
- 更短(针对长文本情况时,token可能更少)
- 更容易被模型利用
- 可控(避免废话)
简单实战案例:
import dashscope
import time
from datetime import datetime
import os
from dotenv import load_dotenv
load_dotenv()
# 设置你的 DashScope API Key
dashscope.api_key = os.getenv("api_key")
# 工具1:获取当前日期(返回格式:YYYY年MM月DD日)
def get_current_date():
"""返回当前日期,格式:2025年9月15日"""
now = datetime.now()
return f"{now.year}年{now.month}月{now.day}日"
# 工具2:查询节假日(根据月份查询)
def search_holidays(month):
"""
查询指定月份的法定节假日
month: 月份字符串,如 "9月"
"""
# 模拟节假日数据
holidays = {
"1月": ["元旦:1月1日"],
"2月": ["春节:1月28日-2月3日"],
"3月": [],
"4月": ["清明节:4月4日-6日"],
"5月": ["劳动节:5月1日-5日", "端午节:5月31日-6月2日"],
"6月": [],
"7月": [],
"8月": [],
"9月": [], # 2025年9月没有法定节假日
"10月": ["中秋节:10月6日-8日", "国庆节:10月1日-7日"],
"11月": [],
"12月": ["元旦:12月31日"]
}
# 获取指定月份的节假日
holidays_list = holidays.get(month, [])
if holidays_list:
return f"2025年{month}有以下法定节假日:\n" + "\n".join(holidays_list)
else:
return f"2025年{month}没有法定节假日。"
# 工具注册
TOOLS = {
"get_current_date": get_current_date,
"search_holidays": search_holidays
}
# 调用Qwen模型
def call_qwen(prompt):
response = dashscope.Generation.call(
model='qwen-max',
messages=[{'role': 'user', 'content': prompt}],
)
return response.output.text
# 解析模型输出
def parse_model_output(output):
"""
解析模型输出,提取 Thought, Action, Action Input
不使用正则表达式,使用简单的字符串处理
"""
thought = ""
action = ""
action_input = ""
lines = output.split('\n') #.split()返回切割后的结果数组
print(f"lines: {lines}")
for line in lines:
line = line.strip() # 去除空格和换行
if line.startswith('Thought:'):
thought = line.replace('Thought:', '').strip() # 去掉前缀
elif line.startswith('Action:'):
action = line.replace('Action:', '').strip()
elif line.startswith('Action Input:'):
action_input = line.replace('Action Input:', '').strip()
return thought, action, action_input
# 从输入中提取月份信息
def extract_month(text):
"""
从文本中提取月份信息,如 "9月"
"""
# 检查常见的月份表示方式
months = ["1月", "2月", "3月", "4月", "5月", "6月",
"7月", "8月", "9月", "10月", "11月", "12月"]
for month in months:
if month in text:
return month
# 如果没有找到明确的月份,返回当前月份
current_month = datetime.now().month
return f"{current_month}月"
# ReAct主循环
def react_solve(question):
print(f"问题:{question}\n")
steps = [] # 用来存储每一步的输出
max_iterations = 5
print("开始ReAct推理流程...\n")
for i in range(max_iterations):
# 构建上下文(包含之前的所有步骤)
context = "\n".join(steps)
prompt = f"""
你是一个使用ReAct范式的智能代理,必须严格按以下格式输出:
Thought: <你的思考>
Action: <要执行的动作,从 [{', '.join(TOOLS.keys())}] 中选择,或 Final Answer>
Action Input: <动作输入,或最终结果输出(仅当Action为Final Answer时)>
当前上下文:
{context}
问题:{question}
"""
# 调用Qwen生成下一步
output = call_qwen(prompt)
print(f"模型输出(第{i + 1}步):\n{output}\n")
# 解析输出
thought, action, action_input = parse_model_output(output)
# 检查解析结果
if not thought or not action:
steps.append(f"Error: 无法解析输出格式。输出: {output}")
print("解析失败,继续尝试...\n")
continue
# 记录历史步骤
steps.append(f"Thought: {thought}")
steps.append(f"Action: {action}")
# 如果是最终答案
if action == "Final Answer":
print("任务完成!最终答案:")
print(f" {action_input}\n")
return action_input
# 执行工具
if action in TOOLS:
print(f"执行工具: {action} | 输入: {action_input}")
# try:
# 传递参数给工具
if action == "search_holidays":
# 使用新的月份提取方法
month = extract_month(action_input)
result = TOOLS[action](month)
else:
result = TOOLS[action]()
steps.append(f"Action Input: {action_input}")
steps.append(f"Observation: {result}")
print(f"Observation: {result}\n")
time.sleep(0.5) # 避免频繁调用
else:
result = f"无效动作: {action}"
steps.append(f"Action Input: {action_input}")
steps.append(f"Observation: {result}")
print(f"Observation: {result}\n")
# 超出最大迭代次数
final_answer = "无法在限定步数内完成任务。"
print(f"任务失败: {final_answer}")
return final_answer
# 运行示例
if __name__ == "__main__":
# 运行主程序
question = "这个月有几个法定节假日?分别是什么?"
result = react_solve(question)
在代码中需要注意的是:在prompt设置和解析TAO输出阶段中,使用了一个指定的格式参数Acting_input;这里需要特别说明,该参数在Acting有选用工具的时候充当调用工具的输出参数,当Acting为Final Answer时,该参数会作为最终的输出结果;
在语义上诚然是解释不通的,Acting_input怎么看都是函数的输入参数,很难把他和最终的result/output联想到一起;下面来解释一下原因:
为什么代码中 Action Input 会作为最终结果?
在很多轻量级的 ReAct 实现中,为了保持输出格式的高度一致性,开发者会强行规定模型在每一步都必须输出 Thought/Action/Action Input 三元组。
-
当 Action 是具体工具时:
Action Input是传给函数的参数。 -
当 Action 是 "Final Answer" 时:为了复用同一套解析逻辑(Parser),开发者把
Action Input位当作了存放“答案文本”的容器。
Thought / Action / Action Input 这种三段式结构,实际上是 LangChain 早期 ReAct 模版 的标准格式。
-
在 Qwen、GPT 等模型的微调数据集中,包含了海量模仿这种格式的推理数据。
-
对于模型来说,这更像是一种“固定句式”。它在预测下一个 Token 时,由于前面的
Action: Final Answer已经出现,根据它在训练中见过的成千上万个 ReAct 案例,它会形成强烈的统计概率:“当 Action 是结束标志时,接下来的 Action Input 槽位就该填最终回复内容。”
在 ReAct 的标准循环中,模型的思维逻辑是闭环的:
如上图所示,模型被训练为必须完成这个循环。当它进入“最终状态”时,它依然在尝试填充当前的 JSON 或文本模版。如果它不输出 Action Input,它可能觉得这个“动作”没有完成,会导致解析错误(即它在尝试做自我对齐)。

2048

被折叠的 条评论
为什么被折叠?



