Agent 是什么:模型、工具、状态与循环
这两年,“Agent”几乎成了所有 AI 产品都会出现的词。聊天机器人叫 Agent,自动填表的脚本叫 Agent,能写代码的助手也叫 Agent。名字越来越大,概念反而越来越模糊。
我刚开始研究这个方向时,也曾把“能调用一个函数”当成 Agent。后来发现这会带来很多误解:一个按钮触发一个 API,不一定是 Agent;一条固定的审批流程,也不一定需要 Agent。真正值得讨论的问题不是“它有没有 Agent 这个名字”,而是系统能不能围绕一个目标感知当前状态、选择下一步行动、调用外部能力,再根据结果调整计划。
说得更白一点:普通程序是我们提前写好每一步,Agent 则允许模型在一定边界内决定下一步。但这个“允许”非常关键。Agent 不是把方向盘丢给模型,而是给它一辆有刹车、有护栏、有仪表盘的车。
一、先区分聊天机器人、工作流和 Agent
三者都可以使用大模型,但决策权不同。
聊天机器人:用户问题 → 模型回答
工作流: 步骤 A → 步骤 B → 步骤 C
Agent: 目标 → 观察 → 决定动作 → 获得结果 → 再决定
聊天机器人适合解释、改写、问答和内容生成。它通常在一次请求里完成任务,流程简单,容易控制。
工作流适合步骤稳定的业务。例如先校验订单,再查询库存,最后生成通知。每一步都可以写成确定性代码,结果更容易测试,延迟和成本也比较可预测。
Agent 适合步骤不完全确定的问题。用户只说“帮我调查这个故障并给出修复建议”,系统可能需要查看日志、搜索文档、比较配置、运行检查,然后根据每一步结果决定下一步。提前把所有路径写死,工作量可能比让模型做有限决策更大。
但“步骤不确定”不代表“完全自由”。生产系统往往是工作流和 Agent 的混合体:外层用代码规定权限、预算和关键节点,局部用 Agent 选择搜索词、整理证据或判断下一项检查。让模型只负责它擅长的部分,通常比让它统治整个流程可靠。
二、Agent 的四个基本零件
1. 模型:负责理解和选择
模型读取目标、历史状态和当前观察,提出下一步计划或动作。它擅长处理模糊语言、归纳信息和在多个可能路径中做选择;它不擅长天然保证事实、权限和副作用安全。
模型是决策器,不是数据库,也不是事务引擎。把它放在错误的位置,就会要求它承担不该承担的责任。
2. 工具:连接真实世界
工具可以是搜索、数据库查询、代码执行、浏览器操作、发消息或修改业务数据。没有工具,Agent 只能在文本空间里推测;有了工具,它才有机会观察当前世界并改变某些状态。
工具接口要比普通函数更严格,因为调用者不是一个总能理解上下文的程序员,而是一个概率模型。参数 Schema、权限、超时、错误分类和幂等性,都应该在工具边界上处理。
3. 状态:记住任务走到哪里
状态不只是聊天历史。它还应该记录目标、已经验证的事实、调用过的工具、产生的文件、审批结果、失败原因和剩余预算。没有显式状态,Agent 每一步都可能重新猜测任务进度。
4. 循环:让结果影响下一步
循环是 Agent 与一次性生成最大的区别。模型提出动作,系统执行动作,外部世界返回结果,模型再根据结果做决定。循环必须有停止条件,否则一个小问题也可能变成无限调用。
三、把 Agent 写成一个可观察的状态机
我不建议一开始就实现一个“会自己思考一切”的超级 Agent。先定义状态和事件,系统会清楚很多:
type AgentState = {
taskId: string
goal: string
phase: 'planning' | 'acting' | 'observing' | 'waiting' | 'done' | 'failed'
observations: Array<{
source: string
content: unknown
verified: boolean
}>
actions: Array<{
tool: string
input: unknown
outcome: 'pending' | 'succeeded' | 'failed'
}>
stepCount: number
deadlineAt: string
}
状态机的价值,是把“模型说了什么”和“系统承认发生了什么”分开。模型可以提出“订单已经退款”,但只有数据库返回成功记录后,系统才能把这个事实写入 observations。模型的猜测是输入,系统的状态才是事实。
事件也应该带上关联 ID。一次任务可能跨越多轮模型请求、多个工具和人工审批,只有通过 taskId、actionId 和 traceId 串起来,事后才能还原执行轨迹。
四、最小 Agent 循环
下面这个骨架故意保持简单:模型只在“继续行动”和“结束任务”之间选择,工具执行和边界控制都由代码完成。
type Decision =
| { kind: 'call_tool'; tool: 'search'; input: { query: string } }
| { kind: 'finish'; answer: string; sources: string[] }
async function runAgent(goal: string) {
const state = createInitialState(goal)
for (;;) {
if (state.stepCount >= 8 || isPastDeadline(state.deadlineAt)) {
return failTask(state, '超过任务预算')
}
const decision: Decision = await model.chooseNextAction({
goal: state.goal,
observations: state.observations,
actions: state.actions,
})
state.stepCount += 1
if (decision.kind === 'finish') {
return validateAndFinish(state, decision)
}
const tool = allowedTools[decision.tool]
if (!tool) return failTask(state, '工具不在允许范围内')
const result = await tool.execute(decision.input, {
taskId: state.taskId,
timeoutMs: 5000,
})
state.actions.push({
tool: decision.tool,
input: decision.input,
outcome: result.ok ? 'succeeded' : 'failed',
})
state.observations.push({
source: decision.tool,
content: result.value,
verified: result.ok,
})
}
}
这不是完整产品,但它体现了几个不能省略的原则:工具必须在白名单里,调用有时间预算,循环有最大步数,最终结果要经过验证。模型可以决定“查什么”,却不能偷偷决定“允许访问什么”。
五、计划不是越详细越好
Agent 常被宣传成“先规划,再执行”。规划确实有用,但我研究失败轨迹时发现,计划写得太长往往适得其反。外部世界每发生一次变化,原计划就可能过期;模型为了遵守旧计划,还会继续执行已经没有意义的步骤。
好的计划应该是可更新的假设,而不是不可修改的施工图。比较合适的粒度是:说明当前目标、下一步动作、完成标准和失败后的备选路径。做完一步后重新观察,再决定是否需要调整。
对于确定性强的步骤,用代码编排;对于需要判断的局部,用模型选择。这样既能享受 Agent 的灵活性,也不会把整个业务变成不可预测的 Prompt。
六、工具调用时,模型只是在提议
这是我最想强调的一句话:模型提出工具调用,系统决定是否执行。
模型可能因为上下文误解而选择错误工具,可能生成不存在的 ID,也可能把网页中的恶意指令当成任务要求。收到调用后,服务端至少要重新检查参数、身份、权限、资源状态和风险等级。
只读搜索一般可以自动执行;创建、修改、发送、删除和付款则要根据风险要求预览、确认、审批或延迟执行。高风险动作不能因为模型输出了 approved: true 就获得授权,授权必须来自可信的用户或策略系统。
此外,工具结果也可能不可信。外部网页、用户上传文件和第三方接口都可能包含 Prompt Injection 或错误数据。它们可以作为待分析的资料,不能改变系统指令、工具权限和任务目标。
七、记忆不等于把所有聊天记录塞回去
Agent 的短期状态用于完成当前任务,长期记忆用于跨任务保留有价值的信息。两者混在一起会造成隐私和准确性问题。
例如当前任务的搜索结果应该随着任务结束而归档;用户明确确认的语言偏好,才可能适合进入长期画像。任何长期记忆都应说明来源、置信度、更新时间和删除方式。模型自己猜出的“用户喜欢某种产品”,不能未经确认就当成永久事实。
记忆还会带来上下文污染。过时的偏好、错误的历史结论和别的租户数据,都会影响当前决策。读取记忆前要做范围过滤,写入记忆前要做来源和敏感信息检查。记得更多,不等于理解得更好。
八、Agent 的失败不是一种失败
线上遇到“Agent 没完成任务”,至少要继续拆分:
- 感知失败:没有读到正确的文件、页面或数据库状态。
- 推理失败:看到了证据,却选择了错误的下一步。
- 工具失败:参数或权限正确,但外部服务超时或报错。
- 状态失败:中途丢失上下文、重复执行或错误恢复。
- 目标失败:用户的要求本身含糊,系统应该追问却直接行动。
不同失败需要不同修复。感知问题要改善数据和工具,推理问题要改模型或示例,工具问题要改重试和隔离,状态问题要做检查点和幂等,目标问题则要增加澄清步骤。只提高模型温度或换一个更大的模型,通常解决不了所有问题。
九、怎么评测一个 Agent
最终答案的正确率当然重要,但还不够。一个生产 Agent 至少应该观察:任务成功率、工具选择准确率、参数校验失败率、平均步骤数、循环率、人工介入率、P95 延迟、Token 成本和副作用重复率。
评测集要包含正常问题,也要包含无答案、权限不足、工具超时、重复请求和恶意输入。更要保留完整轨迹,而不是只保存最后一句回答。你需要知道它为什么成功、为什么失败、在哪一步开始偏离。
我尤其建议把“应该拒绝”的样本加入测试。一个只会完成任务的 Agent 很容易看起来聪明;一个知道证据不足、权限不够或风险太高时停下来的 Agent,才真正值得信任。
十、我的总结:自主性必须和边界一起增长
Agent 的本质不是“模型变成了人”,而是我们把一部分流程决策权交给了模型。交出去的每一分自主性,都应该配套一分可观察性和一分约束:能自主选择工具,就要记录选择;能修改数据,就要有权限和幂等;能长时间运行,就要有预算、检查点和恢复机制。
如果任务步骤稳定,老老实实写工作流;如果只有一次生成,使用普通模型调用;只有当任务需要根据环境反馈不断选择下一步时,Agent 才真正有用。这个判断听起来保守,却能帮团队省掉大量不必要的复杂度。
我最后把 Agent 想成一位刚加入团队的同事:它读得快、记得多、提出方案很积极,但还不能凭一句话获得所有权限。我们要给它清楚的目标、有限的工具、可见的状态和明确的停止条件,也要允许它在不知道的时候停下来。这样培养出来的不是一个会不断说话的模型,而是一个能够在真实世界里谨慎行动、接受检查、从失败中恢复的系统。