为什么需要 Agent 架构
传统 LLM 应用通常停留在单次请求与响应,而真实任务往往需要搜索信息、调用工具、保存上下文,并根据执行结果动态调整下一步动作。Agent 系统的价值,在于让模型从“回答问题”转向“完成目标”。
一个可靠的 Agent 不应该只是不断循环调用模型。它需要清晰的状态、有限的权限、可被观测的执行轨迹,以及随时能够停止的边界。换句话说,Agent 首先是一个软件系统,模型只是其中负责理解与决策的组件。
最小闭环通常包含五步:接收目标、读取状态、选择动作、执行工具、根据结果决定继续或结束。每一步都应留下结构化记录,而不是把所有信息堆进一段不断增长的对话历史。
Goal → Observe → Decide → Act → Verify
↑ │
└─────────────────┘
三个核心模块
1. 工具抽象
将搜索、计算、数据库和外部 API 统一封装为 Tool。每个工具应拥有明确的输入结构、输出结构、超时策略、权限边界和副作用说明。
interface Tool<TInput, TOutput> {
name: string;
description: string;
timeoutMs: number;
risk: 'read' | 'write' | 'destructive';
execute(input: TInput, signal: AbortSignal): Promise<ToolResult<TOutput>>;
}
type ToolResult<T> =
| { ok: true; data: T; traceId: string }
| { ok: false; code: string; retryable: boolean; message: string };
}
模型生成的参数永远是不可信输入。即使 TypeScript 已经约束了开发期类型,运行时仍要用 JSON Schema、Zod 或 Pydantic 做校验。涉及写文件、发消息、支付或删除数据的工具,还应在执行前增加确认或审批节点。
2. 记忆分层
- 工作记忆:保存当前任务的短时状态。
- 情景记忆:记录过去任务中的关键决策。
- 长期记忆:沉淀稳定事实与用户偏好。
记忆不是越多越好。上下文必须经过检索、排序和压缩,才能让模型持续聚焦。
实践中可以为每条记忆保存 source、createdAt、confidence 和 expiresAt。用户明确说出的稳定偏好可以进入长期记忆;一次任务中的猜测只应留在工作记忆。检索时先按权限过滤,再做相关性排序,避免把“能搜到”误当作“可以使用”。
3. 规划与执行分离
规划器负责生成有边界的步骤,执行器负责调用工具并验证结果。两者之间通过显式状态传递,失败时只重试当前节点,而不是重跑整条链路。
并非所有任务都需要自由规划。固定业务流程更适合状态机或工作流;只有当步骤无法预先穷举时,才让模型动态规划。一个实用的混合方案是:代码规定主干状态与退出条件,模型只在某些决策节点中选择下一步动作。
type RunState = {
goal: string;
step: number;
status: 'running' | 'waiting_approval' | 'succeeded' | 'failed';
observations: Observation[];
budget: { tokens: number; toolCalls: number; deadline: number };
};
预算字段很重要。没有最大轮数、Token 上限、工具调用上限和截止时间,Agent 遇到模糊目标时很容易在“继续尝试”中无限消耗资源。
一个好的 Agent 架构,首先是一个可调试的软件系统,其次才是一个聪明的模型包装器。
可观测性
为每次模型推理、工具调用和状态转换记录 trace。至少应能够回答以下问题:
- 用户目标经过了哪些状态?
- 模型看到的上下文和选中的工具是什么?
- 每次调用耗时多少、消耗多少 Token、是否重试?
- 最终答案引用了哪些工具结果?
- 失败来自模型、参数校验、外部服务,还是权限拒绝?
日志中不要直接保存 API Key、Cookie、身份证号等秘密信息。工具输入输出应先做字段级脱敏,并为大对象保存摘要或对象存储地址。
失败恢复与幂等
工具失败不等于整个任务失败。可以把错误分成三类:瞬时错误允许指数退避重试;参数错误交给模型修正一次;权限或业务冲突则立即停止并请求用户处理。
对“创建订单”“发送通知”这类写操作必须传入幂等键。否则网络超时后重试,可能产生两笔订单或两条消息。补偿动作也要显式设计,例如文件上传成功但数据库写入失败时,是删除文件还是保留孤立对象等待清理。
安全边界
我倾向于按能力而不是按角色授权:读取仓库、修改工作区、访问公网、发送外部消息分别授予。默认拒绝超出工作区的文件操作;命令执行使用白名单或沙箱;所有高风险动作展示将要发生的具体变化。
一条很实用的原则是:模型可以提出动作,但代码决定动作是否允许执行。
从原型走向可用系统
落地时可以按以下顺序推进:
- 先完成单任务、少工具的确定性闭环。
- 为状态、工具结果和错误建立统一数据结构。
- 加入 trace、回放和离线测试集。
- 再引入长期记忆、动态规划和多 Agent。
- 最后通过影子运行与小流量逐步开放写权限。
复杂度应该由真实失败推动,而不是一开始就堆叠框架。能用一个 Agent 和三个清晰工具解决的问题,不需要先创建一个“虚拟组织”。