logo

Agent 是什么:模型、工具、状态与循环

Published on

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。一次任务可能跨越多轮模型请求、多个工具和人工审批,只有通过 taskIdactionIdtraceId 串起来,事后才能还原执行轨迹。

四、最小 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 想成一位刚加入团队的同事:它读得快、记得多、提出方案很积极,但还不能凭一句话获得所有权限。我们要给它清楚的目标、有限的工具、可见的状态和明确的停止条件,也要允许它在不知道的时候停下来。这样培养出来的不是一个会不断说话的模型,而是一个能够在真实世界里谨慎行动、接受检查、从失败中恢复的系统。

🤪 您也可以编辑此页: