你让 ChatGPT 算 37 乘 28,它能给你答案。你让它查一下今天北京飞上海的航班有没有晚点,它也能给你一段像模像样的回答。但如果你较真去核对,会发现它给的航班信息大概率是编的。这不是模型笨,而是它的工作方式决定的:大模型本质上是一个"文字接龙机器",它根据你输入的上下文,逐个预测下一个最可能出现的词。它没有浏览器,没有计算器,没有数据库,它唯一的本事是从训练时见过的文本里"回忆"出看起来合理的回答。

现在问题来了。你想让这个只会生成文字的东西去完成一件真实任务,比如"查一下这篇论文的引用格式对不对,然后按 APA 格式改好"。这件事需要两步:先查到 APA 规范,再对照修改。模型本身做不到第一步,因为它没有访问外部世界的能力。你当然可以把规范复制粘贴给它,但这就意味着每次任务你都得手动替它把外部信息喂到嘴边——那还要它干什么?

这就是 Agent 要解决的痛点。Agent 的英文原意是"代理人",在技术语境里,它指的是一个以大模型为核心、但额外配备了"手脚"的程序。这个程序不再满足于生成一段文字回复你,而是把你丢给它的一句话目标,拆成一系列可执行的步骤,自己决定每一步调用什么工具,然后根据工具返回的结果继续推进,直到任务完成。

打个比方。普通的大模型问答像是你问一个博学的朋友"巴黎铁塔多高",他凭记忆告诉你。Agent 则像是你雇了一个助理,你跟他说"帮我查一下巴黎铁塔的准确高度,顺便确认下维基百科和官方旅游网站的数据是否一致"。这个助理不会凭记忆瞎说,他会打开浏览器、访问网站、对比数据,最后给你一份带出处的结论。助理和那位博学朋友的区别,不在于谁更聪明,而在于助理被允许"动手"。

但这里有个工程上很棘手的问题:模型怎么知道该"动手"做什么?你不能在代码里写死每一步——如果任务是查航班,步骤是调航班 API;任务是算数学,步骤是调计算器;任务是改格式,步骤是调格式化工具。任务的种类无穷无尽,你不可能为每一种都写一套固定的调用逻辑。你需要的是让模型自己在每一步决定"现在我该做什么"。

这正是 Agent 框架最核心的设计难点,也是接下来要展开的机制:怎么让一个只会生成文字的东西,变成一个能自己拿主意、调用外部工具、并根据结果调整下一步行动的"代理人"。理解了这一点,你再看市面上各种 Agent 框架的代码,就不会觉得它们是在变魔术了。

当大模型答错一道两位数乘法,问题出在哪

先想一个更基础的问题:你平时是怎么指挥一个实习生干活的?你不会一次性把所有步骤都交代清楚,因为中间有太多不确定因素。你会说"你先去查一下这个客户的背景资料,查完告诉我他主要做什么业务,我再决定下一步怎么跟他谈"。实习生去查了,回来汇报,你根据新信息决定下一步。这个"先做一步、看结果、再决定下一步"的循环,就是 Agent 工作的基本形态。

ReAct 框架把这个循环形式化了。ReAct 是 Reasoning and Acting 的缩写,意思是"推理与行动"。它规定模型的输出必须交替出现三种内容:Thought(思考)、Action(行动)、Observation(观察),循环往复直到任务完成。

拆开看。Thought 是模型用自然语言说出它当前的判断,比如"用户想查航班晚点情况,我需要先获取实时航班数据"。Action 是模型决定调用的工具和参数,比如"调用 flight_search 工具,参数是 CA1837"。Observation 是工具执行后返回的结果,比如"CA1837 预计晚点 40 分钟"。模型看到这个结果后,再输出下一轮 Thought——“已经查到晚点信息,可以直接回答用户了”——然后输出 Final Answer。

这个循环看起来简单,但它解决了一个关键问题:模型不是在"一次性生成完整答案",而是在"每走一步都看一眼现实,再决定下一步"。这就像那个实习生每次查完都回来跟你汇报,而不是闷头干完一整天才告诉你"我搞砸了"。

但这里有个微妙的地方需要讲清楚:Thought、Action、Observation 这些内容,本质上都是模型生成出来的文字。Action 说"调用 flight_search 工具",模型并不会真的去调用任何东西——它只是输出了这行字。真正去执行工具调用的是你写的程序。程序读到模型输出的 Action,解析出工具名和参数,替模型去执行,然后把结果拼成 Observation 再喂回给模型。

这就引出了 ReAct 设计的第一个关键点:模型和工具之间的"翻译官"是程序代码。模型负责做决策,程序负责执行。这种分工的好处是,模型不需要真的会调用 API——它只需要学会"用文字表达想调用哪个工具",剩下的事情交给代码。这也意味着,你给模型提供的工具清单越清晰,它的决策就越靠谱。

第二个关键点是思维链的作用。你可能听说过 Chain of Thought(思维链)这个词,它本质上是一种提示词技巧:在 prompt 里要求模型"一步一步思考再给出答案"。为什么要这样?因为实验发现,如果你直接问模型一个复杂问题,它倾向于跳步直接给结论,容易出错;但如果你要求它把推理过程写出来,它的准确率会明显提升。ReAct 把思维链从"答题技巧"升级成了"工作方式"——模型每一步的 Thought 就是思维链的一环,它被迫想清楚"我现在知道什么、还缺什么、该做什么"才能继续。

第三个关键点是观察如何形成闭环。模型输出 Action 之后,程序执行工具并返回 Observation,这个 Observation 会成为下一轮 prompt 的一部分。也就是说,模型的每一轮推理都建立在"上一轮行动的真实结果"之上,而不是靠猜。这正是 Agent 和普通问答的本质区别:普通问答是"一次生成、一次输出",Agent 是"多轮生成、每轮都引入新信息"。

用一个具体例子串起来。假设你给模型配了两个工具:一个计算器 calculator,一个搜索引擎 web_search。用户问:"2023 年全球人口排名前十的国家中,人口超过 1 亿的有几个?"模型的推理过程可能是这样的:

第一轮 Thought:“我需要先知道 2023 年全球人口排名前十的国家有哪些。” Action:“调用 web_search,查询 2023 年全球人口排名。” Observation 返回一个列表。第二轮 Thought:“列表里有中国、印度、美国、印尼、巴基斯坦、尼日利亚、巴西、孟加拉国、俄罗斯、墨西哥。我需要数一下人口超过 1 亿的有几个。” Action:“调用 calculator,逐个比较。” 但它发现 calculator 只做算术,不能比较大小——于是它重新思考,直接用 Observation 里的数据人工数一遍,得出"9 个",输出 Final Answer。

注意这个过程里模型"试错"了:它一开始想用 calculator,后来发现不合适,转而直接用已有信息推理。这种灵活性正是 ReAct 的价值——模型不是按预设脚本机械执行,而是根据每步的观察动态调整策略。

现在你已经理解了 ReAct 的机制,但还有一个问题没解决:模型怎么知道它有哪些工具可用?工具的参数格式是什么?这就需要一个"工具说明书"——在 prompt 里告诉模型每个工具的名字、功能、参数格式。这部分内容在工程实现上通常是提前定义好的,也是下一章实例里你要亲手写的东西。

让模型"边想边做":ReAct 循环如何把决策权交给模型

理论讲完了,你可能会觉得"这听起来不错,但真的能跑起来吗?"这一章我们就动手验证。目标很简单:用 Python 写一个最小的 Agent,给大模型配两个工具——一个做加减乘除的计算器,一个假装能查天气的函数。然后让模型回答两类问题,观察它是怎么自己决定调用哪个工具的。

先说清楚我们要用什么。为了让例子足够短、不依赖重量级框架,我们直接用 OpenAI 的 API。但这一章的重点不是 API 用法,而是让你看到 ReAct 循环在代码层面长什么样、模型输出的 Thought 和 Action 是怎么被程序解析和执行的。

第一步:定义工具清单

先定义两个工具函数,一个计算器,一个天气查询。注意天气查询是假的,只是为了演示模型会调用它。

def calculator(expression: str) -> str:
    """计算简单的算术表达式,如 '37*28'"""
    return str(eval(expression))

def get_weather(city: str) -> str:
    """查询城市天气(模拟数据)"""
    return f"{city} 今天晴,25°C"

这两个函数就是模型的"手"。模型本身不会执行它们,但模型会在输出里说"我想调用 calculator,参数是 ‘37*28’",然后由我们的代码去执行。

第二步:构造工具说明书

模型需要知道有哪些工具可用。我们不直接写一大段自然语言描述,而是用一个结构化的工具列表,里面写清楚每个工具的名字、功能描述和参数格式。

tools = [
    {
        "name": "calculator",
        "description": "计算算术表达式,参数 expression 是字符串形式的算式",
        "parameters": {"type": "object", "properties": {"expression": {"type": "string"}}}
    },
    {
        "name": "get_weather",
        "description": "查询指定城市的天气,参数 city 是城市名",
        "parameters": {"type": "object", "properties": {"city": {"type": "string"}}}
    }
]

这段代码做的事情,相当于给模型发了一份"岗位说明书":你有这两个工具可用,各自干什么用、参数怎么填,都写清楚了。模型读到这份说明书后,在需要的时候就会输出一个结构化的调用请求。

第三步:写 ReAct 循环的主逻辑

这是核心部分。程序要做的事情是:把用户的问题和工具说明书一起发给模型,让模型决定是直接回答还是调用工具;如果模型说要调用工具,程序就执行对应的函数,把结果拼回去再发给模型,直到模型给出最终答案。

def run_agent(user_query: str, max_rounds: int = 5):
    messages = [{"role": "user", "content": user_query}]
    for _ in range(max_rounds):
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages,
            tools=tools  # 把工具说明书传给模型
        )
        msg = response.choices[0].message
        if msg.tool_calls:  # 模型决定调用工具
            for call in msg.tool_calls:
                fn_name = call.function.name
                args = json.loads(call.function.arguments)
                result = globals()[fn_name](**args)  # 执行对应函数
                messages.append({"role": "tool", "tool_call_id": call.id, "content": result})
        else:  # 模型给出最终回答
            return msg.content
    return "超过最大轮数,任务未完成"

这段代码是 ReAct 循环的骨架。注意几个关键点:tools=tools 把工具说明书传给了模型,让模型知道它有什么可用;msg.tool_calls 判断模型是否决定调用工具——如果模型只是普通回答,这个字段为空;如果模型想调用工具,这个字段里会有工具名和参数。程序拿到这些信息后,用 globals()[fn_name](**args) 动态调用对应的 Python 函数,把结果以 role: "tool" 的消息追加回对话历史。

第四步:跑两个问题验证

先问一个需要计算的问题:

print(run_agent("37乘以28等于多少?"))

你会看到模型先输出一个 tool_call 请求,调用 calculator 工具,参数是 "37*28"。程序执行后返回 "1036",模型看到这个结果后给出最终回答。整个过程中,模型没有自己算——它把计算交给了工具,自己只负责判断"这题需要计算器"。

再问一个需要外部信息的问题:

print(run_agent("北京今天天气怎么样?"))

模型会调用 get_weather 工具,参数是 "北京",程序返回模拟的天气数据,模型基于这个数据组织回答。注意,这个例子里的天气函数是假的,但模型并不知道——它只知道自己调用了工具、拿到了结果、可以回答用户了。

第五步:观察模型"想错"和"改主意"

如果你把问题换成"37乘以28等于多少?顺便告诉我巴黎天气",模型会依次调用两个工具,先算数再查天气。更有意思的是,如果你问一个模糊的问题,比如"我该带伞吗",模型可能会先查天气再回答——它自己判断出"要不要带伞取决于天气",于是主动调用了天气工具。

跑完这个例子,你应该能亲眼看到一件事:模型并没有被写死"遇到乘法就调计算器",它是根据问题的语义自己判断该调用哪个工具的。这个判断能力来自大模型本身的理解力,而 ReAct 循环只是给了它一个"可以调用工具"的通道。这就是 Agent 和普通问答的本质区别——模型不再只是"想",它还能"做",而且做的结果会反馈给它,影响它下一步怎么想。

用三十行代码验证:让模型自己决定"该查资料还是该算数"

你跑通了上面的例子,会觉得 Agent 很聪明。但如果你直接把它搬进生产环境,很快会遇到几类问题。这些问题不是代码 bug,而是 ReAct 机制本身的固有缺陷,理解它们能帮你在设计阶段就避开坑。

第一个坑是工具幻觉。模型可能调用一个不存在的工具,或者给工具传了错误的参数。比如你只给它配了 calculator 和 get_weather 两个工具,它却输出"调用 send_email 工具"——因为它在训练数据里见过这个工具名,以为你有。这是模型"编造"能力的延伸:它不仅能编造事实,还能编造工具调用。应对方式是在程序里做严格的工具名白名单校验——模型输出的工具名不在你定义的列表里,就直接终止或提示模型重新选择,而不是傻乎乎地去执行。

第二个坑是循环失控。ReAct 循环没有天然的终止条件,模型可能陷入"思考→行动→观察→再思考"的死循环,永远不给出 Final Answer。比如你问"帮我写一份市场分析报告",模型可能会不停地调用搜索工具,查了一个数据又想查另一个,永远觉得信息不够。这就是为什么上一章代码里我加了 max_rounds 参数——你必须给循环设一个硬上限。真实系统里这个上限通常设得比较小,比如 5 到 10 轮,超过就强制终止并返回"任务超时,请细化你的需求"。这不是偷懒,而是因为每多一轮就意味着多一次 API 调用,成本和延迟都会线性增长。

第三个坑是上下文膨胀。每一轮循环的 Observation 都会追加到对话历史里,随着轮数增加,发给模型的 prompt 越来越长。大模型的输入长度是有上限的,而且越长响应越慢、越贵。如果你让 Agent 做一件需要 20 轮工具调用的任务,每轮 Observation 是 500 字,最后光历史记录就有一万多字——这还没算模型自己输出的 Thought 和 Action。真实系统里需要做上下文压缩,比如只保留最近的几轮对话,把早期的 Observation 摘要成一句话。但压缩又会带来信息丢失的问题——模型可能忘了它第一轮查到的关键数据。这是一个需要根据任务类型权衡的设计决策。

第四个坑是工具本身会失败。Observation 不一定总是成功的结果,它可能是报错信息、超时提示、空数据。模型看到这些异常后,能不能正确处理?有时候会,比如它看到"API 超时"可能会说"我换个方式再试一次";但更多时候它会一本正经地基于错误信息编造一个答案。比如天气工具返回"查询失败",模型可能直接说"北京今天天气未知"——这个回答倒是诚实,但如果你问的是"帮我查一下竞品的定价",工具返回空结果,模型可能就会编一个不存在的价格出来。所以工具返回的结果一定要结构化成"成功/失败"的格式,失败时明确告诉模型"没有拿到数据,请如实告知用户",而不是把裸报错丢给模型。

什么时候不该用 Agent?如果你的任务流程是固定的——每次都是同样的步骤、同样的工具调用顺序——那就不需要用 ReAct 让模型自己决策,直接写一段普通的代码按顺序调用工具就行,又快又稳又便宜。Agent 的价值在于应对"不可预知"的任务:你不知道用户会问什么、需要调哪个工具、要调几轮。如果你的场景里任务类型高度标准化,用 Agent 反而是在用一个不可控的模型替代一段可控的代码,属于过度设计。

另一个不该用 Agent 的场景是"工具调用不能出错"的任务。比如你做一个自动转账的 Agent,模型如果调错参数转了双倍金额,后果不是"重新试一次"能解决的。这类场景的正确做法是把决策权从模型手里收回来:模型可以判断"用户想转账",但转账金额、收款账号这些关键参数必须由程序二次校验,或者干脆让用户确认后再执行。记住一个原则:ReAct 适合做"信息获取和加工"类任务,不适合做"不可逆操作"类任务——除非你在模型和工具之间加一道人工确认的闸门。

最后提醒一个容易被忽略的点:ReAct 的效果高度依赖底层模型的推理能力。如果你用的是一个小参数模型,它可能连"该调用哪个工具"都判断不准,更别提多轮推理了。ReAct 不是一种能让弱模型变强的魔法,它只是给强模型提供了一个发挥能力的框架。模型本身不够聪明,循环再规范也白搭。

这条路径会在哪里翻车:工具幻觉、循环失控和上下文膨胀

回到开头那个问题:为什么大模型会一本正经地编造航班信息?因为它只能"想",不能"做"。Agent 的整套设计,就是给这个只会想的模型装上了"手"——通过 ReAct 循环让它在每一步先想清楚"我现在知道什么、还缺什么、该调用什么工具",然后让程序替它执行工具,把真实结果喂回去,再进入下一轮思考。

你可以带走四条判断:

第一,Agent 和大模型问答的区别不在模型本身,而在工作方式——问答是一次生成,Agent 是多轮"推理→行动→观察"的循环,每轮都引入外部信息。

第二,模型并不真的会调用工具,它只是输出一段结构化的文字表达"我想调用什么",真正执行的是你的代码。所以工具清单的定义质量直接决定 Agent 的表现。

第三,ReAct 循环必须设轮数上限,工具调用结果必须结构化处理,工具名必须白名单校验——这三个是生产环境的基本防护,缺一个就会出事故。

第四,如果你的任务流程是固定的,别用 Agent;如果工具调用出错后果严重,别让模型全权决策。Agent 适合的是"不可预知但可逆"的任务。

你可以在自己的项目里试一个最简单的验证:给模型配一个搜索工具和一个计算器,然后问它"查一下你出生那年全球人口最多的国家,算一下它的人口是 2000 年的几倍"。看看模型会不会自己拆成"先查→再算"两步。如果它做到了,你就真正理解了 Agent 的工作方式。如果你来设计一个 Agent 系统,你会给它配哪三个工具?

素材来源

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐