今天几乎所有带有大模型能力的软件都愿意称自己为 Agent。给模型加一段角色设定,可以叫 Agent;给聊天机器人接上搜索,也可以叫 Agent;把一套固定工作流包装成对话界面,仍然可以叫 Agent。这个词覆盖的范围越来越大,也因此越来越难以指导工程设计。
Agent Engineering 需要一个更严格的起点。我们不能用产品名称判断一个系统是不是 Agent,而要看这个系统运行时到底发生了什么:谁接收目标,谁保存状态,谁决定下一步,动作是否会影响环境,动作结果是否会进入下一轮判断,以及系统为什么结束。
这一章先从最小单位——一次 LLM 调用——开始。
怎样阅读本 Playbook 的代码块
本 Playbook 使用代码块,不只为了展示可以运行的程序,也用它把抽象概念转换成容易检查的结构和关系。每个代码块前都有“代码块说明”,应先根据说明判断它属于哪一种表达:
- Playbook 参考 Schema:用 JSON 或 YAML 展示对象、字段、引用和版本关系。除非正文明确链接并声明遵循某项规范,否则它不是外部协议的标准格式,也不要求实现者照搬字段名。
- 伪代码:用 Python 风格展示控制顺序和关键判断。它会主动省略工程细节,不能直接视为生产实现。
- 流程、层级、结构或关系式:用纯文本表达状态变化、父子关系或概念组成。它描述逻辑模型,不等于部署图、数据库表或行业标准公式。
- 接口或存储约束示例:用 HTTP 或 SQL 说明系统边界与一致性要求。端点、表名和具体语法需要映射到实际技术栈与协议版本。
因此,代码块首先回答“这些对象怎样关联、执行怎样推进”,而不是宣称“行业已经统一采用这套 Schema”。当示例来自 MCP、A2A、CloudEvents、OpenTelemetry 等正式标准时,正文会明确给出来源与适用范围;没有这种声明的结构化片段,应按 Playbook 的解释性参考模型阅读。
LLM 是一个生成下一段输出的模型
从工程视角看,大语言模型可以暂时简化成一个函数:
代码块说明|关系式: 这段表达式把“LLM 是一个生成下一段输出的模型”压缩成便于比较的组成关系。它是分析模型,不是可执行代码,也不是要求实现者采用的行业标准公式。
output = LLM(context)
输入是当前上下文中的 token 序列,输出是模型根据这些输入生成的下一段内容。输出可能是自然语言、JSON、代码,也可能是一段符合工具调用协议的数据。
这个简化非常重要,因为它提醒我们:模型只负责根据当前输入生成输出。它本身并不天然拥有文件系统、数据库、浏览器、任务队列,也不会因为一次调用结束后还有工作没做,就自动醒来继续执行。
一次独立的模型调用通常不包含下面这些能力:
- 不知道外部环境刚刚发生了什么,除非信息被放进 Context。
- 不保存跨调用的任务进度,除非外部系统把状态再次传给它。
- 不能真正修改文件或发送邮件,只能生成“应该修改”“应该发送”的文本。
- 不会自行重复执行,除非外部程序再次调用它。
- 不知道自己的结论是否已经在真实环境中成立。
所以,LLM 可以成为 Agent 的决策组件,但 LLM 本身不是 Agent。非 LLM 系统同样可以构成 Agent,例如使用规则、搜索算法或强化学习策略选择动作的机器人。我们在本书中讨论的是以 LLM 为主要决策组件的 Agent。
Prompt 是一次指令,不是一个执行系统
Prompt 是提供给模型的输入指令。例如:
代码块说明|结构示意: 这段文本把“Prompt 是一次指令,不是一个执行系统”中的关键对象并列出来,帮助读者识别各自责任与边界。它用于概念建模,不代表唯一的产品命名或实现结构。
你是一名数据库专家。请分析下面的慢查询,并给出优化建议。
角色设定可以影响模型的表达方式、关注重点和输出格式,却不会自动产生状态、工具和执行循环。无论 Prompt 有多长,只要运行过程仍然是“输入一次、生成一次、然后结束”,它就是一次模型调用。
代码块说明|流程示意: 这段文本图按箭头方向阅读,用来说明“Prompt 是一次指令,不是一个执行系统”中的执行顺序、状态变化或责任流转。它表达逻辑关系,不代表组件必须按图中的数量和位置部署。
Prompt → LLM → Response
因此,下面这个判断可以作为全书的第一条边界:
LLM 加 Prompt 不等于 Agent。把模型称为“数据库 Agent”,不会让它自动获得读取数据库、执行查询和验证优化结果的能力。
这并不意味着一次模型调用没有价值。分类、抽取、改写、总结和结构化生成等任务,通常一次调用就足够。工程设计的目标不是把所有功能都做成 Agent,而是在任务确实需要持续观察和行动时,才引入 Agent 的复杂性。
Context 是模型这一轮能够看见的全部信息
Prompt 经常被当成模型的全部输入,但在真实系统中,模型看到的通常不止用户刚刚输入的一句话。
代码块说明|层级示意: 这段文本图按缩进和树枝符号阅读,用来展开“Context 是模型这一轮能够看见的全部信息”中的父子关系与归属边界。它描述的是逻辑结构,不要求数据库或服务按同样层级拆分。
Context
├── System Prompt
├── 用户目标和当前指令
├── 对话历史
├── 当前任务状态
├── 被选择的 Skill
├── 检索出的长期记忆
├── 文件、网页或数据库内容
├── 可调用的 Tool 描述
└── 上一轮 Tool 返回结果
Context 不是一个静态文档,而是 Runtime 在每次模型调用前组装出来的工作集。同一个模型、同一个用户目标,因为加载了不同的状态、Skill 或工具结果,可能做出完全不同的判断。
这也解释了一个常见误区:上下文窗口很大,不代表应该把所有信息全部放进去。Context 是 Agent 最有限的认知资源之一。哪些信息被加载、以什么顺序加载、什么时候移除,会直接影响 Agent 的行为。后面的状态与记忆章节会专门讨论这个问题。
什么才叫 Agent
本书采用下面这个工程定义:
Agent 是一个面向目标持续运行的软件系统。它维护一个不断演进的执行状态,并根据当前状态、环境反馈和决策策略动态选择或调整下一次状态转换,直到任务成功、失败、等待或需要人工介入。
这里使用“状态转换”,而不只使用“工具调用”或“行动”。因为 Agent 的下一步既可能是调用 Tool,也可能是生成计划、创建 Task、调整任务依赖、请求人工确认或者等待一个外部事件。不同 Agent 的运行表象差异很大,底层共同点是目标驱动的状态持续演进。
这个定义包含五个最小元素:
| 元素 | 回答的问题 |
|---|---|
| Goal | 系统试图达成什么结果? |
| State | 任务现在进行到哪里? |
| Environment | 哪些事实和对象存在于模型之外? |
| Transition | 系统可以怎样改变当前执行状态? |
| Loop | 转换结果如何进入下一轮决策? |
其中最关键的不是“模型会推理”,也不是必须采用某一种固定循环,而是每次执行结果能够更新状态,并改变后续决策。如果模型只生成一份计划,系统既不执行计划,也不跟踪计划状态,它只是在生成文本。如果系统能够执行计划中的任务、记录结果,并根据结果继续、重排、补充或终止任务,它才形成了 Agent 的执行闭环。
Agent Loop 不只有 ReAct
“观察—判断—行动—反馈”描述的是最常见的 ReAct 模式。在 ReAct 中,模型通常一次选择一个 Tool,Tool 结果立即返回模型,形成细粒度循环。但 ReAct 只是 Agent Loop 的一种实现。
| 模式 | 一轮的执行单位 | 主要状态 | 下一步怎样产生 |
|---|---|---|---|
| ReAct | 一次 Tool 调用 | 对话、观察与工具结果 | 模型根据最新观察选择动作 |
| Plan-and-Execute | 一个计划步骤 | Plan、步骤结果与完成进度 | Planner 或 Scheduler 选择、修改计划 |
| Task-driven | 一个 Task | Todo List 或 Task Graph | Scheduler 选择就绪任务,模型可以增删或重排任务 |
| Event-driven | 一个事件 | 持久状态与等待条件 | 事件到达后由 Runtime 恢复并推进状态 |
Plan 并没有取代 Loop,而是把 Loop 的粒度从“一次动作”提升为“一个计划步骤”。Todo 或 Task 模式则把执行状态显式外化,每一轮选择一个可执行任务,完成后更新任务图,再决定下一项工作。
它们可以统一表达为:
代码块说明|流程示意: 这段文本图按箭头方向阅读,用来说明“Agent Loop 不只有 ReAct”中的执行顺序、状态变化或责任流转。它表达逻辑关系,不代表组件必须按图中的数量和位置部署。
Goal
↓
Current State
↓
Policy / Planner / Scheduler
↓
State Transition
↓
Updated State
↺ 直到进入终态
如果计划和任务列表完全由人提前写好,程序只按固定顺序执行,并且不会根据结果重新评估路径,它更接近 Workflow。Agent 性来自系统能够依据执行中的新状态,在允许的边界内动态选择或修改后续路径。
Agent 是系统,不是其中一个零件
一个实际运行的 LLM Agent 通常可以分成六个部分:
| 组成 | 作用 |
|---|---|
| Model | 根据当前 Context 产生判断或候选动作 |
| Context | 决定模型这一轮能够看见什么 |
| State | 保存目标、进度、已确认事实和等待事项 |
| Skills | 提供完成某类任务的方法、规则和材料 |
| Tools | 提供读取或改变外部环境的动作接口 |
| Runtime | 组装 Context,执行循环,管理权限、恢复和退出 |
这六个部分并不是六个必须独立部署的服务。一个几十行的 Python 程序也可以同时承担 Context 组装、状态管理和循环控制。这里的划分是一组设计责任,用来定位问题究竟发生在哪一层。
例如:
- Agent 不知道如何审查 Pull Request,可能缺少合适的 Skill。
- Agent 知道审查步骤,却读不到代码变更,可能缺少 Tool 或权限。
- Agent 读过代码,但下一轮忘了关键发现,可能是 State 或 Context 组装问题。
- Agent 已经完成任务却仍在继续调用工具,可能是 Runtime 的退出策略有问题。
如果把这些问题全部归因于“模型不够聪明”,系统就无法被有针对性地改进。
Agent 不等于自主程度越高越好
Agent 的价值来自根据反馈动态选择动作,而不是无限扩大自主权。一个可以直接删除生产数据、发送邮件和部署代码,却没有权限控制和终止条件的 Agent,并不比一个受约束的系统更先进。
自主程度可以分层设计:
代码块说明|流程示意: 这段文本图按箭头方向阅读,用来说明“Agent 不等于自主程度越高越好”中的执行顺序、状态变化或责任流转。它表达逻辑关系,不代表组件必须按图中的数量和位置部署。
只生成建议
↓
执行只读动作
↓
执行可撤销修改
↓
修改前请求确认
↓
在限定边界内自主执行
具体应该停在哪一层,取决于动作风险、环境可恢复性和验证能力。Agent Engineering 的目标不是让模型接管尽可能多的步骤,而是让系统在明确边界内持续、可控地完成目标。
判断一个系统是不是 Agent
遇到一个自称 Agent 的产品或框架,可以不看它的宣传名称,直接检查下面几个问题:
- 系统是否接收一个需要多步推进的目标?
- 它是否保存目标和当前执行状态?
- 它是否能够观察模型之外的环境?
- 它是否拥有一个以上可选择的状态转换?
- 下一步转换是否会根据最新状态动态选择或调整?
- 执行结果是否会更新状态并影响后续决策?
- 系统是否拥有成功、失败、等待和人工介入等出口?
如果一个系统只有 Prompt、一次 LLM 调用和一次 Response,它只是一次模型调用。Agent 的关键不在于采用 ReAct 或任何一种固定循环,而在于系统是否围绕目标维护持续演进的执行状态,并通过模型判断、任务调度或环境反馈选择下一次状态转换,直到进入明确终态。ReAct、Plan-and-Execute、Task-driven 和 Event-driven,都是这种执行系统的不同实现。
本章检查
- 是否把 LLM 能力与 Agent 系统能力分开描述。
- 是否区分了 Prompt 和完整 Context。
- 是否明确了 Agent 的目标、状态、环境、状态转换和循环。
- 执行结果是否能够更新状态并影响下一轮决策。
- 系统是否真的需要 Agent,而不是一次模型调用就能完成。