✨ 复制成功!

以文会友,打造好学人设!

📋 已复制到剪贴板:

《Agent Harness入门教程》


1 AI Agent与Harness概述
    1.1  什么是AI Agent

    1.2  什么是Agent Harness

    1.3  为什么需要Harness

2 核心概念与架构模式
    2.1  Agent的核心循环

    2.2  Harness的典型架构

    2.3  单Agent vs 多Agent

    2.4  Agent生命周期

    2.5  核心术语表

3 环境搭建与基础组件
    3.1  选择LLM Provider

    3.2  搭建最小Harness

    3.3  实现工具调用机制

    3.4  结构化输出与解析

    3.5  日志与调试基础

4 工具系统设计
    4.1  工具的定义与注册

    4.2  工具Schema设计

    4.3  安全沙箱

5 记忆与会话管理
6 Agent推理与规划
7 商业项目实战练习

ReAct模式:推理与行动交替

创建时间:2026-09-07 更新时间:2026-09-07 阅读次数:1103 次

一个简单的观察

回到你在第1章写的第一个Agent。它的核心循环是:

调用LLM → 如果返回工具调用 → 执行工具 → 把结果放回去 → 再调用LLM

这个循环看起来简单,但它隐含了一个深刻的问题:LLM在每一步都做了什么?

当用户问"北京今天多少度?这个数字乘以3再加5等于多少?"时,LLM的决策过程是:

第1步:我需要知道北京的温度 → 调用get_weather
第2步:温度是26,我需要计算26×3+5 → 调用calculate
第3步:结果是83 → 回答用户

每一步都包含两个动作:先推理("我需要做什么"),再行动(调用工具)。这种"推理-行动"交替进行的模式,就是ReAct的核心思想。

ReAct是什么

ReAct(Reasoning + Acting)是2022年由Google研究团队提出的一种Agent推理模式。它的核心主张是:让LLM在每一步都显式地说出它的推理过程,然后基于这个推理采取行动。

在ReAct模式下,Agent的每一步循环是这样的:

思考(Thought):我现在需要做什么?我知道什么?我不知道什么?
行动(Action):基于思考,我决定调用哪个工具,参数是什么?
观察(Observation):工具返回了什么结果?
(然后开始下一轮思考...)

这与你之前实现的循环本质相同,但有一个关键区别:"思考"从隐式变成了显式。

在之前的实现中,LLM的"思考"是隐式的——它直接输出工具调用请求,你不会看到它为什么选择这个工具。而在ReAct模式中,LLM被要求先输出一段推理文本,解释它的思考过程,然后再输出行动。

为什么显式推理很重要

你可能会问:让LLM把思考过程写出来,有什么实际价值?它不只是多消耗了一些token吗?

价值一:更好的决策质量。

多个研究发现,当LLM被要求"先思考再行动"时,它的决策质量会提升。这类似于人类解决问题的方式——说出声来(或写下来)的思考,比默默在脑中想更清晰、更严谨。

一个具体的例子:

没有显式推理的Agent:

用户:帮我查一下下周三有没有时间开会

Agent直接调用:query_calendar(date="2026-09-09")

有显式推理的Agent:

思考:用户想知道下周三是否有空。今天是2026-09-07(周日),下周三应该是2026-09-09。我需要查询那天的日历。
行动:query_calendar(date="2026-09-09")
观察:2026-09-09 09:30 一对一沟通
思考:那天上午9:30有会。用户问"有没有时间",我需要判断空闲时段。9:30有会,但下午看起来是空的。我应该告诉用户上午有会,下午有空。
回答:下周三上午9:30有一对一沟通,下午时间空闲。

后者不仅给出了回答,还解释了判断依据("下午看起来是空的"),这让你能验证它的推理是否正确。

价值二:可调试性。

当Agent出错时,显式的思考过程是最有价值的调试信息。你可以看到Agent在哪一步推理出了偏差——是日期计算错了?是忽略了某个日历事件?还是对"有空"的理解有误?

没有显式推理,你只能看到"Agent调用了某个工具,最终给出了某个回答",中间的决策过程是黑盒。

价值三:防止"跳跃式"错误。

LLM有时会跳过必要的中间步骤,直接给出结论。这在简单任务中没问题,但在复杂任务中会导致错误。要求显式推理,相当于强迫LLM放慢速度,逐步推进。

在Harness中实现ReAct

方式一:通过系统提示词引导

最简单的方式:在系统提示词中要求LLM遵循ReAct格式:

REACT_SYSTEM_PROMPT = """
你是一个具备工具调用能力的智能助手。在解决任务时,请遵循以下模式:

思考(Thought):分析当前情况,判断下一步该做什么。考虑你已经知道什么、还需要知道什么。
行动(Action):如果需要调用工具,输出工具调用。如果已经可以回答,直接输出最终答案。
观察(Observation):工具执行后,你会看到结果。基于结果继续思考。

重要规则:
1. 每次只执行一个行动
2. 在调用工具前,先输出你的思考
3. 如果工具返回错误,思考如何调整策略
4. 当你确信已经完成任务时,直接输出最终答案
"""

但这种方式有一个问题:LLM的"思考"和"行动"是混合在同一个响应中的。 你需要在解析时区分哪些是思考文本、哪些是工具调用。

在OpenAI的API中,当LLM调用工具时,响应中的content字段可以包含思考文本,tool_calls字段包含工具调用。两者可以在同一个响应中共存:

response = client.chat.completions.create(
    model=model,
    messages=messages,
    tools=tool_schemas,
)

message = response.choices[0].message

# 思考文本在content中
thought = message.content  # "我需要先查询日历..."

# 行动在tool_calls中
tool_calls = message.tool_calls  # [{"name": "query_calendar", ...}]

方式二:结构化的ReAct循环

更工程化的做法:在Harness的循环中显式地处理"思考"和"行动"两个阶段:

class ReActRuntime:
    """实现ReAct模式的Agent运行时"""

    def __init__(self, llm_client, tool_registry, memory, 
                 max_iterations: int = 10,
                 verbose: bool = True):
        self.client = llm_client
        self.tools = tool_registry
        self.memory = memory
        self.max_iterations = max_iterations
        self.verbose = verbose

        # 记录完整的推理轨迹
        self.reasoning_trace = []

    def run(self, user_input: str) -> str:
        """执行ReAct循环"""
        self.memory.initialize(user_input)

        for iteration in range(self.max_iterations):
            if self.verbose:
                print(f"\n{'='*40}")
                print(f"第{iteration + 1}轮")
                print(f"{'='*40}")

            # 阶段1:思考+行动(LLM调用)
            response = self._call_llm()
            message = response.choices[0].message

            # 记录思考
            if message.content:
                self._record_thought(iteration, message.content)

            # 检查是否有工具调用
            if message.tool_calls:
                # 阶段2:行动(执行工具)
                self.memory.add_llm_message(message)

                for tool_call in message.tool_calls:
                    action = {
                        "tool": tool_call.function.name,
                        "arguments": json.loads(tool_call.function.arguments),
                    }
                    self._record_action(iteration, action)

                    # 执行工具
                    result = self.tools.execute(
                        action["tool"], action["arguments"]
                    )

                    # 阶段3:观察(记录工具结果)
                    self._record_observation(iteration, result.content)

                    # 将结果加入记忆
                    self.memory.add_tool_result(result)
            else:
                # 没有工具调用,直接返回最终答案
                final_answer = message.content or ""
                self._record_final_answer(iteration, final_answer)
                return final_answer

        return "达到最大迭代次数,未能完成任务。"

    def _call_llm(self):
        """调用LLM"""
        return self.client.chat.completions.create(
            model="gpt-4o-mini",
            messages=self.memory.get_context(),
            tools=self.tools.get_schemas(),
            temperature=0.0,
        )

    def _record_thought(self, iteration: int, thought: str):
        """记录思考"""
        entry = {"iteration": iteration, "type": "thought", "content": thought}
        self.reasoning_trace.append(entry)
        if self.verbose:
            print(f"思考:{thought}")

    def _record_action(self, iteration: int, action: dict):
        """记录行动"""
        entry = {"iteration": iteration, "type": "action", "content": action}
        self.reasoning_trace.append(entry)
        if self.verbose:
            print(f"行动:{action['tool']}({action['arguments']})")

    def _record_observation(self, iteration: int, observation: str):
        """记录观察"""
        entry = {"iteration": iteration, "type": "observation", "content": observation}
        self.reasoning_trace.append(entry)
        if self.verbose:
            print(f"观察:{observation[:200]}")

    def _record_final_answer(self, iteration: int, answer: str):
        """记录最终答案"""
        entry = {"iteration": iteration, "type": "final_answer", "content": answer}
        self.reasoning_trace.append(entry)
        if self.verbose:
            print(f"最终答案:{answer}")

    def get_reasoning_trace(self) -> list:
        """获取完整的推理轨迹"""
        return self.reasoning_trace

运行示例

用户:帮我看看下周有没有空参加一个2小时的会议

========================================
第1轮
========================================
思考:用户想知道下周是否有空参加2小时的会议。我需要先查询下周的日历,看看有哪些空闲时段。下周是2026-09-08到2026-09-12。我应该查询这些日期的日程。
行动:query_calendar({"start_date": "2026-09-08", "end_date": "2026-09-12"})
观察:2026-09-08: 14:00周会;2026-09-09: 09:30一对一沟通;其他日期无安排

========================================
第2轮
========================================
思考:根据日历结果,9月8日下午2点有周会,9月9日上午9:30有一对一沟通。其他时间没有安排。用户需要2小时的空闲时段。9月10日、11日、12日整天都空闲。9月8日下午2点后有周会占用,9月9日上午9:30有一对一沟通占用。我应该告诉用户哪些时段可以安排。
最终答案:下周的日程如下:9月8日下午2点有周会,9月9日上午9:30有一对一沟通。其余时间(包括9月10-12日全天)都是空闲的,可以安排2小时的会议。

ReAct模式的实际效果

效果一:参数提取更准确

在之前的实现中,LLM直接从用户输入中提取工具参数。在ReAct模式下,LLM会先推理参数应该是什么,再输出工具调用。这个额外的推理步骤能提高参数提取的准确率。

效果二:错误恢复更自然

当工具返回错误时,ReAct模式的"思考"步骤让LLM有机会明确地分析错误原因并调整策略:

思考:工具返回"未找到订单ORD123"。可能是订单号有误。让我尝试用其他信息查询,或者询问用户确认订单号。
行动:search_orders({"keyword": "最近的订单"})
观察:找到3个最近的订单...

效果三:多步任务更有条理

对于需要多步完成的任务,ReAct的显式推理让每一步的目的更清晰:

思考:用户想比较两个产品再决定买哪个。我需要先查询两个产品的信息,然后对比,最后给用户提供决策依据。
行动:get_product_info({"product_id": "A001"})
观察:产品A:价格999元,评分4.5,特点...
思考:已经获取了产品A的信息。现在需要产品B的信息。
行动:get_product_info({"product_id": "B002"})
观察:产品B:价格1299元,评分4.8,特点...
思考:两个产品的信息都已获取。A便宜300元但评分稍低,B贵但评分更高。我应该列出对比,让用户自己做决定。
最终答案:以下是两个产品的对比...

ReAct的局限性

局限一:额外token消耗。每轮循环中的"思考"文本会消耗额外的token。如果思考过于冗长,会显著增加成本。

应对:在提示词中要求思考保持简洁("用一句话说明你的思考")。

局限二:思考可能出错,错误会被放大。如果LLM在思考中得出了错误的结论(比如日期算错了),后续的行动都建立在错误之上。

应对:思考不是"免检"的——它在后续循环中会接受工具结果的检验。如果行动后观察到的结果与思考的预期不符,LLM有机会修正。

局限三:不适用于所有场景。对于非常简单的任务(如"计算2+3"),显式推理是浪费。

应对:根据任务的复杂度决定是否启用ReAct模式。简单任务用直接模式(LLM直接回答),复杂任务用ReAct模式。

ReAct与你已有的Harness的关系

你可能已经注意到:你在前面章节构建的Harness,本质上已经是ReAct模式的。

  • "感知"对应ReAct的"观察"

  • "思考"对应ReAct的"思考"

  • "行动"对应ReAct的"行动"

区别只在于:你的Harness没有显式地记录和展示"思考"这一步。 LLM的内部推理是隐式的,你只看到它输出的工具调用。

本节的价值在于:让隐式的推理变显式。 通过在循环中加入"思考"的记录和展示,你获得了:

  • 更透明的决策过程

  • 更丰富的调试信息

  • 更好的LLM决策质量(当你在提示词中要求它"先思考"时)

本节小结

ReAct模式是Agent推理的最基础也最重要的模式。它的核心是:推理与行动交替进行,显式的推理引导行动,行动的结果修正推理。

实现ReAct不需要特殊的框架或复杂的代码——它只需要两件事:

  • 在系统提示词中要求LLM"先思考,再行动"

  • 在Harness循环中记录和展示思考、行动、观察的完整序列

你现在已经具备了实现ReAct的所有基础。在下一节中,我们将讨论规划能力——如何让Agent不只走一步看一步,而是能够在执行前制定完整的计划。

本教程共32节,当前为第26节!
本教程最新修订时间为:2026-09-30 09:02:04

📌 面试天下网:一款便捷的大模型学习口袋书,让富士康流水线的打工人也能摸到改命逆天的机会!
📌 网站公告:【悬赏 200 元/篇】寻找“大模型面试战场”的一手回忆录
📌 网站公告:【大模型/Agent面试陪练1V1指导】正式启动......

评论专区

评论加载中...