Agent 推理模式有哪些?ReAct 具体是怎么实现的?

#RAG#Agent#面试八股

Agent 推理模式有哪些?ReAct 具体是怎么实现的?

"推理模式"指模型在决定下一个动作之前,用什么方式组织自己的思考过程。 常见的有思维链、ReAct、计划执行、树搜索、反思式重试等,它们的区别是思考与行动的交错程度,以及是否引入外部反馈。

1. 结论:五种模式,两条判断线

模式英文思考与行动的关系外部反馈适用
思维链Chain-of-Thought只思考,不行动无数学、逻辑、单轮推理
ReActReason + Act思考一步、行动一步,交替有,每步都有需要查外部信息的任务
计划执行Plan-and-Execute先全部思考,再行动阶段性步骤多的长任务
树搜索Tree-of-Thoughts多路并行思考再选优无或内部评估需要回溯的难题
反思重试Reflexion行动后批判再重来有,且用于改进有验收标准的产出

两条判断线:要不要外部信息(决定是否引入工具)、一次想多远(决定计划与执行是否分离)。

2. ReAct 的机制:三个标记撑起整个循环

ReAct(Reasoning and Acting)把模型的输出约束成三种片段,循环推进:

Thought:      我要先查订单状态          ← 显式推理,供人审计
Action:       query_order({"id":"A1"}) ← 结构化动作,运行时执行
Observation:  {"status":"SHIPPED"}     ← 由环境注入,不是模型生成的

循环:Thought → Action → Observation → Thought → ...
终止:模型输出 Finish[答案],或达到 max_steps

三个实现要点缺一不可:

  1. 提示里放工具清单和格式样例,并明确"每次只能输出一个 Action";
  2. 解析器要宽松:容忍多余空格、单双引号、中文括号、JSON 前后夹带解释;
  3. Observation 必须由运行时拼,绝不能让模型自己编造观察结果,否则等于让它自问自答。

3. 代码:不依赖框架的 ReAct 实现

import json, re

PROMPT = """你可以使用以下工具:
{tools}

严格按格式输出,每次只做一步:
Thought: 你的推理
Action: 工具名[参数JSON]
当信息足够时输出:Finish[最终答案]"""

def react(question: str, tools: dict, llm, max_steps: int = 8) -> str:
    scratchpad = ""
    for step in range(max_steps):
        out = llm(PROMPT.format(tools=render_tools(tools)) + question + scratchpad)

        if "Finish[" in out:                       # 终止条件一:主动结束
            return re.search(r"Finish\[(.*)\]", out, re.S).group(1).strip()

        name, args = parse_action(out)             # 终止条件二:解析失败要能自救
        if name not in tools:
            obs = f"错误:没有工具 {name},可用工具:{list(tools)}"
        else:
            try:
                obs = tools[name](**args)
            except Exception as exc:
                obs = f"错误:{type(exc).__name__}: {exc}"
        scratchpad += f"\n{out}\nObservation: {str(obs)[:1500]}"   # 观察截断,防爆窗口

    return "步骤超限,未能完成"                     # 终止条件三:硬预算

def parse_action(text: str):
    m = re.search(r"Action:\s*([A-Za-z_]\w*)\s*[\[\(](.*?)[\]\)]\s*$", text, re.M | re.S)
    if not m:
        raise ValueError(f"无法解析动作:{text[-200:]}")
    raw = m.group(2).strip()
    return m.group(1), (json.loads(raw) if raw.startswith("{") else {"input": raw})

这段三十行的实现已经具备生产雏形:解析失败时把错误当观察喂回去、工具不存在时列出可用清单、观察结果截断到 1500 字符。

4. 各模式的取舍

关注点思维链ReAct计划执行树搜索反思重试
调用次数1步数1 + 步数分支数 × 深度2~3 × 步数
能否纠错不能能,靠新观察能,靠重规划能,靠回溯能,靠批判
上下文压力小大中极大大
可观测性差(黑盒)好(每步可见)好差好

5. 常见追问

问:ReAct 和函数调用(Function Calling)是什么关系? 函数调用是"动作的编码方式"升级:把自然语言的 Action: 换成模型原生输出的结构化 tool_calls,解析更稳、支持并行调用。循环结构完全一样,可以理解为"结构化输出的 ReAct"。

问:为什么还要显式写 Thought?模型不是能直接给动作吗? 三个理由:可观测(失败时能看到它在想什么)、可干预(可以注入提示纠正)、可评测(Thought 质量单独打分)。代价是 token,简单任务可以省掉。

问:ReAct 死循环怎么防? 四道闸:max_steps 硬上限;相同 (工具, 参数) 连续出现两次即中断;观察结果连续无变化则换策略;总 token/时长预算。再加一条:重复调用同一工具时,把"你已经查过,结果如下"明确写进提示。

问:观察结果太长怎么办? 先截断(保留头尾),再把完整结果存到外部,只回填一个引用 id 让模型按需再取。切忌把几万字原始日志直接塞进上下文。

小结

推理模式的差异在于"想与做如何交错"以及"有没有外部反馈"。ReAct 是工程上最通用的基线:Thought 保证可解释,Action 保证能落地,Observation 保证有反馈,再配好解析、截断和步数上限就能稳定运行。其余模式都是在特定成本约束下对这三要素做取舍。

笔记加载中…