Skip to content

Agent 基础原理

Agent 不是"模型突然有了自主意识",而是模型、工具、状态、循环和安全控制组合起来的一套任务执行系统。

前两阶段把聊天、工具调用、知识库这些单项能力都覆盖了。Agent 是把这些能力串起来的下一步——让系统不只回答单次问题,而是持续推进一项任务,直到达到目标或触碰边界。

本章目标

  • 能区分普通聊天、单步工具调用和多步 Agent 的差别
  • 理解 Plan / Act / Observe 这类 Agent Loop 的基本思想
  • 知道什么时候适合上 Agent,什么时候反而会过度复杂
  • 能说出 Agent 系统为什么比单步系统更难控制

从 RAG 到 Agent:为什么不够?

做完 RAG 知识库后,你可能会发现它有一类问题处理不好:多步推理

举个例子:用户问"帮我梳理这季度的产品反馈,找出被提到最多的问题,再搜索行业里有没有对应的解法"。

这个任务至少需要:

  1. 搜索并读取反馈资料
  2. 提取和归类问题
  3. 再次搜索外部信息
  4. 对比整合,最后输出结论

RAG 做的是单次"检索 + 生成",而上面的任务需要多次查询、多步决策和中间结果的传递——单次 RAG 处理不了。

Agent 的本质

Agent 的本质不是更聪明,而是把模型放进一个可持续决策的任务闭环里。这个闭环至少包含:

  • 用户目标
  • 可用工具
  • 当前状态(做到哪一步了,拿到了哪些中间结果)
  • 下一步决策
  • 终止条件(什么情况下应该停下)

换个角度看,Agent 真正改变的不是模型本身,而是模型所处的工作结构。

普通聊天时,模型只需要对当前输入给出一次响应;Agent 场景里,模型的每次输出都可能改变外部世界,也会反过来改变下一轮输入。它不再只是生成文本,而是在不断参与一个"读状态 -> 决策 -> 执行 -> 更新状态"的系统。

这也是为什么 Agent 的问题通常比聊天难。聊天出错,多半是某句话答错了;Agent 出错,可能是它在第 3 步做错一个判断,结果第 8 步还在沿着错误方向继续推进。

和普通聊天对比:

维度普通聊天Agent
轮次一问一答多步持续推进
工具可有可无通常必须有
状态对话上下文任务状态 + 中间结果
终止回答完就结束需要显式终止条件
风险说错一句话执行错误动作、成本失控

Agent 不是工作流脚本

这是一个特别容易混淆的地方。很多人一看到"多步执行",就把 Agent 和工作流编排混为一谈。

两者都有步骤,但本质不同:

  • 工作流脚本的步骤是人提前写死的
  • Agent 的下一步是在运行时由模型根据当前状态决定的

如果一个任务的路径很清楚,比如"读取表格 -> 清洗字段 -> 写入数据库 -> 发通知",那更像工作流问题。你完全可以把步骤写死,稳定、可测、可控。

Agent 适合的是另一类任务:你知道目标,但中间路径要根据现场信息决定。比如调研、排障、探索代码库、从多来源信息里逐步收敛答案。

所以一个很重要的判断标准不是"任务有几步",而是"这些步骤是不是必须运行时动态决定"。

Agent Loop

最经典的抽象是:

Plan → Act → Observe

Plan

判断当前目标是什么,下一步该做什么工具调用,或者是否已经可以给出结论。

Act

调用工具、执行查询、读取资料。

Observe

读取工具返回结果,判断是否成功、是否继续、是否需要换方法。

这个循环会持续运转,直到触发终止条件。问题是:很多 Agent 系统的 bug,不在模型本身,而在这个循环控制得不够稳

为什么 Agent Loop 本质上是一个状态机

教材常把 Agent Loop 写成 Plan -> Act -> Observe,这样好记,但还不够工程化。站在系统实现角度,它其实更像一个状态机。

系统每一轮都会回答几件事:

  1. 当前任务处于什么状态
  2. 允许进入哪些下一状态
  3. 哪些事件会触发状态迁移
  4. 哪些条件意味着必须停机或交给人工

举个例子。一次网页研究任务里,"已拿到目标页面"、"页面读取失败"、"搜索结果不足"、"结论已足够输出" 就是不同状态;搜索成功、工具报错、用户打断,就是不同事件;重试、换工具、终止,就是不同迁移。

成熟 Agent 系统最后都会引入很多 demo 里看不到的控制逻辑:超时、预算、权限、重试次数、用户确认——本质上都是状态机里的转移规则。

循环控制不好会发生什么?

真实案例里最常见的失控场景:

  • 搜索工具返回了错误结果,Agent 继续基于它规划,越跑越偏
  • 工具超时没处理,Agent 一直等,不知道要重试还是跳过
  • 模型判断"任务还没完成",却没有新的推进方向,进入重复调用同一工具的死循环
  • 步数没有上限,一个简单任务跑了几十步,成本爆了

所以工程上的核心问题是:循环能不能在正确的时刻停下来,以及出错时怎么恢复

Tool Calling 和 Agent 的关系

  • Tool Calling 是单步能力:模型决定调哪个工具、传什么参数,拿到结果后回答
  • Agent 是多步工作流:在多轮循环里持续调用工具、管理状态、推进任务

可以理解为:

Agent = Tool Calling + 状态管理 + 多步循环 + 终止控制

Tool Calling 是 Agent 的基础能力之一,但有了 Tool Calling 不代表就有了 Agent——还需要状态和循环控制机制。

为什么 Agent 需要状态

没有状态管理,Agent 很容易:

  • 重复做同样的工具调用(上一步查过的资料再查一遍)
  • 忘记前一步的中间结果
  • 无法判断任务是否已经完成
  • 错误重试:失败原因没记录,每次重试都在重复同样的错

状态通常需要跟踪:

  • 用户原始目标
  • 已调用过的工具和参数
  • 中间结果(搜索到了什么、抽取到了什么)
  • 当前执行阶段
  • 错误信息和重试次数
  • 是否达到终止条件

Agent 里的状态至少有三类:任务状态(做到哪一步了)、环境状态(文件变了没有、工具返回了什么)、控制状态(花了多少步、失败了几次、是否接近边界)。

很多初学实现不稳定,是因为只记了第一类。模型知道"要继续做事",却不知道环境已经变了,也不知道预算快耗尽了。

为什么终止条件不能省

没有明确终止条件,Agent 可能:

  • 一直循环,不知道什么时候"任务完成"
  • 不断重试同类错误,浪费成本
  • 越来越多的工具调用,超出上下文窗口限制

常见的终止策略组合:

  • 任务完成(模型主动返回结论)
  • 达到最大步数(例如限制 10 步以内)
  • 连续失败次数超限(例如连续 3 次工具调用失败就停下)
  • 遇到高风险动作,暂停等待人工确认

最大步数是最重要的保险丝。任何 Agent 上线前都应该设一个合理的步数上限。

为什么终止条件本质上是边界管理

终止条件不是为了"让 Agent 早点结束",而是为了明确系统边界。

Agent 一旦具备多步能力,就天然会同时消耗三种资源:

  • 上下文
  • 金钱
  • 人类信任

前两种容易想到,最后一种经常被忽略。一个总是不停调用工具、迟迟不肯停的 Agent,会迅速让用户失去信任。因为用户看不到它到底是在认真推进,还是已经开始盲转。

所以终止机制不是附加优化,而是和权限系统一样重要的控制层。

什么任务适合 Agent

适合

  • 调研一个主题,需要多次搜索和筛选
  • 多步整理:搜索 → 提取 → 分类 → 汇总
  • 多工具协作:搜索 + 读网页 + 计算 + 写报告
  • 任务需要根据中间结果动态调整方向

不适合

  • 简单问答(一步就能回答的问题)
  • 单次文本改写或格式转换
  • 固定结构抽取(用 Structured Output 更稳定)
  • 任务目标清晰、路径固定(用写死的工作流更可控)

一个工程上常见的错误是:不是所有"感觉复杂"的任务都需要 Agent。如果你能提前写死步骤,就写死,更可控、更稳定。Agent 适合的是"步骤本身需要动态决策"的场景。

Agent 更容易出问题的地方

  • 规划错误:误判任务需要哪些步骤
  • 工具选择错误:用了不合适的工具,结果跑偏
  • 状态混乱:遗忘中间结果,重复操作
  • 循环失控:找不到方向却继续循环
  • 高风险动作执行过快:没有在危险操作前暂停确认

所以工程上,Agent 的重点不是只追求"更聪明的模型",而是:

  • 可控:有步数、有权限边界,有终止条件
  • 可观测:每一步都有日志,能回溯出错位置
  • 可恢复:失败时能中断、重试或降级,而不是崩溃

Claude Code 为什么能代表更真实的 Agent 实现

看多了论文图和博客里的闭环图,容易以为 Agent 就是模型自己神奇地不断思考。真实产品不是这样落地的。

来自 Claude Code v2.1.88 源码的 QueryEngine.ts 说明了一件事:真正的 Agent 循环总是运行在宿主系统里。宿主负责保存消息、执行工具、统计成本、处理中断、判定超限,模型只负责在这一轮给出下一步决策。Agent 不是模型单独完成的,而是"模型策略 + 宿主控制"共同完成的。

一个最小 Agent Loop 示例

把 Plan / Act / Observe 用代码串成一个最小闭环,帮你把概念和实现对应起来:

python
import json
from openai import OpenAI

client = OpenAI()

# 定义工具
tools = [
    {
        "type": "function",
        "function": {
            "name": "web_search",
            "description": "在网上搜索某个关键词,返回摘要结果列表",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {"type": "string", "description": "搜索关键词"}
                },
                "required": ["query"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "read_page",
            "description": "读取指定 URL 的网页正文内容",
            "parameters": {
                "type": "object",
                "properties": {
                    "url": {"type": "string", "description": "目标网页 URL"}
                },
                "required": ["url"]
            }
        }
    }
]

def execute_tool(name: str, args: dict) -> str:
    """工具执行层——实际场景里替换成真实实现"""
    if name == "web_search":
        return f"搜索 '{args['query']}' 的结果:[模拟搜索摘要]"
    if name == "read_page":
        return f"网页 {args['url']} 的内容:[模拟网页正文]"
    return "工具不存在"

def run_agent(task: str, max_steps: int = 10) -> str:
    """Agent Loop:Plan → Act → Observe,直到完成或达到步数上限"""
    messages = [
        {"role": "system", "content": "你是一个研究助手。用工具完成任务,结论要有来源。"},
        {"role": "user", "content": task}
    ]

    for step in range(max_steps):
        # Plan:让模型决定下一步
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=messages,
            tools=tools
        )
        message = response.choices[0].message
        messages.append(message)

        # 如果模型不再调工具,任务结束
        if not message.tool_calls:
            return message.content

        # Act + Observe:执行工具,把结果加回上下文
        for tool_call in message.tool_calls:
            args = json.loads(tool_call.function.arguments)
            result = execute_tool(tool_call.function.name, args)
            print(f"步骤 {step+1}: 调用 {tool_call.function.name}({args})")

            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": result
            })

    # 达到步数上限
    return "已达到最大步数限制,当前结论:" + (message.content or "无")

result = run_agent("帮我调研 2025 年最流行的前端框架")
print(result)

注意这里的几个关键设计:max_steps 是保险丝,不能省;工具执行结果要追加回 messages,模型才能看到上一步做了什么;模型不返回 tool_calls 时,就是它认为任务完成的信号。

对应项目

本章建议直接结合 Research Agent 理解:

  • 怎么拆解目标为若干搜索子问题
  • 怎么设步数上限
  • 怎么保留工具调用日志
  • 什么时候应该停下并告诉用户"不确定"

和后续章节的关系

  • AI 幻觉:多步执行链路越长,幻觉越容易积累。进入工程化阶段时,需要专门回到这个问题。
  • Prompt Injection 与 AI 安全:Agent 会主动读取外部内容(网页、文档),这些内容可能含有恶意指令。
  • AI 应用系统设计:Agent 如何放进完整系统里,状态、日志、安全如何落地。

从真实产品看 Agent 循环

前面讲的 Plan / Act / Observe 是概念模型。但真实 Agent 产品是怎么落地的?

Claude Code 是 Anthropic 出品的编程 Agent,核心执行逻辑在 QueryEngine.ts 里(来自 Claude Code v2.1.88 源码)。不是学术论文里的理想模型,而是跑在生产环境里的真实系统,值得参照。

Agent Loop 的工业实现

Claude Code 的循环不是一个显式的 while 里套三步,而是围绕消息流(AsyncGenerator)展开的事件驱动架构。但如果你把它的运行过程还原出来,结构和 Plan / Act / Observe 高度吻合:

Plan(规划):每一轮开始,模型接收到当前的对话历史(包括所有工具调用结果),然后决定下一步:是再调用一个工具,还是已经可以输出结论了。这个决策完全由模型在上下文里做,不需要外部规则。

Act(执行):模型返回一个 tool_use 类型的内容块,Claude Code 的宿主端(QueryEngine)读取这个内容块,找到对应的工具实现,执行它——比如真实地读取文件、运行命令、搜索代码。工具执行完毕后,结果以 tool_result 消息的形式追加进对话历史。

Observe(观察):追加了 tool_result 的对话历史被送回模型,模型读取结果,判断是否成功,再次规划下一步。这就完成了一次完整的循环。

整个循环的终止信号只有一个:模型不再返回 tool_use 内容块,只返回纯文本。QueryEngine 据此判定任务完成,输出最终结果。

逻辑和教材里的框架一致,实现细节换成了消息类型和异步流处理。

这一点特别重要。真实系统不是把 Plan、Act、Observe 写成三个硬编码函数轮流执行,而是把它们编码进消息流和工具协议里:

  • 模型输出 tool_use,表示进入执行分支
  • 宿主返回 tool_result,表示环境状态已更新
  • 模型读取新状态,再决定继续还是结束

这说明 Agent Loop 并不是某种神秘算法,它更像一套协议:模型按协议决策,宿主按协议执行,双方通过消息流维持循环。

多重终止机制

前面提到"终止条件不能省",Claude Code 的真实做法是设置多重防线,而不是只依赖一种终止机制:

自然终止:模型不再调用工具,直接输出结论。这是最理想的情况,说明任务顺利推进到了终点。

步数上限(maxTurns):QueryEngine 接受一个 maxTurns 参数。每次模型返回 user(工具结果)消息,内部计数器就加一。一旦超限,立即停止并返回 error_max_turns 状态,不会让循环无限延伸。源码里专门为这个超限情况定义了单独的错误子类型,方便调用方区分"任务完成"和"被强制截断"。

预算上限(maxBudgetUsd):这是许多教材不会提的细节——Claude Code 还设了一个美元预算上限。每轮结束后,都会检查累积花费是否超过阈值。超了就立即终止,返回 error_max_budget_usd。Agent 是"花时间、花钱"的系统,成本控制是终止机制的一部分,不是可选功能。

上下文压缩(autoCompact):当对话历史的 Token 数量接近模型的上下文窗口上限时,autoCompact.ts 会介入——它让模型对历史对话做自动摘要压缩,释放空间,让 Agent 可以继续运行,而不是因为"记不住"而崩溃。源码注释里明确写着:"conversation has unlimited context through automatic summarization"。这不是广告词,是架构决策:持续循环的 Agent 必须处理上下文长度问题,否则每隔几十步就会撞墙。

用户中断:QueryEngine 持有一个 AbortController 实例,interrupt() 方法直接调用 this.abortController.abort(),让整个异步流立即中断。用户随时可以发送中断信号,不需要等当前工具调用执行完毕。

这四道防线对应五种情况:"正常完成""无限循环""费用失控""上下文耗尽""用户反悔"。缺了任何一道,生产上迟早会踩到。

状态管理的真实方案

章节里说 Agent 需要状态管理,Claude Code 的实际做法分两个层次:

短期状态(当前对话):QueryEngine 内部维护一个 mutableMessages 数组,存储从对话开始到现在的所有消息——包括用户输入、模型思考、工具调用参数、工具返回结果。每次模型需要"回顾上下文",就是读这个数组。它既是上下文,也是任务推进记录。

任务清单(TodoWriteTool):Claude Code 内置了一个叫 TodoWriteTool 的工具,让 Agent 可以自己给自己维护任务列表。模型在推进复杂任务时,会主动调用这个工具写下"下一步要做什么""哪步已经完成""哪步失败了"——不依赖人为编写任务清单,而是 Agent 自我管理进度。

这个设计把"状态记录"从外部系统转移给了 Agent 本身。Agent 既是执行者,也是自己的进度表维护者。

这里可以看出一个很实用的工程思路:不要把所有状态都塞回自然语言对话里。对话适合表达语义,任务清单适合表达结构化进度。越复杂的任务,越需要把状态拆成不同载体存放。

多 Agent 协调(预告)

Claude Code 不只支持单 Agent,内部还有一套多 Agent 架构:一个 Coordinator Agent 负责拆分任务,把子任务派发给多个专用 Agent 并行执行——有负责探索代码库的、有负责验证结果的。结果汇回主控 Agent 做整合。

这部分在后续多 Agent 章节详细展开。目前只需知道:单 Agent 的循环控制学会之后,多 Agent 的核心挑战不是"更多 Agent",而是任务拆分粒度、上下文隔离、结果汇总——都建立在单循环的基础上。


Claude Code 里的 Agent 循环和教材框架没有本质差异,差异在工程细节——每个终止条件都有对应的错误类型,每个状态有明确归属,每种失败有独立处理路径。

落地时容易踩的坑

从 demo 到真实任务,最常见的几个教训:

路径能写死就别交给模型。 Agent 适合处理动态决策的任务,但如果你对执行路径已经很确定,硬编码的工作流永远比让模型"临场发挥"更稳。

循环本身不难,难的是循环的边界。 有没有步数上限?失败时能不能回退?状态是否清晰?这些比"模型会不会规划"重要得多。

控制权要留在宿主手里。 模型决定下一步做什么,但预算、权限、中断、步数上限——这些不能让模型自己管自己。

状态别只存在聊天记录里。 对话越长,模型越容易丢信息。任务清单、结构化日志、中间结果文件,都是防止 Agent 跑着跑着就失忆的手段。

稳比聪明重要。 好用的 Agent 往往不是最"聪明"的那个,而是知道什么时候该停、什么时候该认输、什么时候该把控制权交回给人。

常见面试考点

Agent 方向的面试题通常会从“概念定义”很快转到“怎么控制”。这一章至少要准备下面几类问题:

  1. Agent 和单次调用的区别:Agent 不是一次 Tool Calling,而是工具、状态、循环和终止条件组合出来的多步任务系统。
  2. Agent Loop:Plan / Act / Observe 每一轮分别做什么,工具结果为什么必须回写到上下文或状态里。
  3. 死循环防护:最大步数、连续失败次数、预算上限、重复动作检测和人工确认,分别挡住哪类失控。
  4. 终止条件:自然完成、达到上限、遇到高风险动作、资料不足时停止,都是系统边界的一部分。
  5. 状态管理:需要记录用户目标、已调用工具、中间结果、错误和重试次数;只靠聊天历史很容易失控。
  6. 适用场景判断:路径能写死时优先用工作流,只有中间路径需要运行时动态决策时,Agent 才真正有价值。

三个问题可以拿来自测:Agent 循环里哪些控制逻辑必须在宿主侧实现,不能交给模型?什么任务其实用写死的工作流更合适?Claude Code 的终止机制有哪几道防线,分别对应哪种失控场景?

下一章:CoT 与 ReAct——先把推理提示和工具行动模式分清楚,再继续看框架和协议会更顺。

面向开发者的 AI 实战路线——Vibe Coding 与 AI 应用开发