Agent:原理、架构与工程实践(中篇)

工具、记忆、自主度与多 Agent:生产级系统怎么搭

如果说上篇讲的是 Agent 的"脑子"------规划、推理、反思与执行循环,那么中篇更接近真正落地时最容易出问题的部分:工具、记忆、自主度、多 Agent 协作

这四件事看起来都很工程,但实际上决定了 Agent 能不能从"能演示"走到"能上线"。

现实里的 Agent 往往不是"不会想",而是:

  • 看不到关键上下文
  • 选不到合适工具
  • 记不住任务进度
  • 长任务执行到一半失去状态
  • 多 Agent 协作时彼此放大错误

所以这一篇不谈抽象能力,只谈一件事:系统要怎么设计,才能让 Agent 稳定干活。


一、工具设计:Agent 能做什么,首先由工具边界决定

上下文决定模型能看到什么,工具决定模型能做什么。

这是 Agent 工程里最基础的一条原则。

模型再强,没有合适工具,也只能"想";工具设计不对,Agent 就会在错误接口上反复试错。

1.1 工具不是越多越好,关键是贴不贴近任务

很多团队一开始会把已有 API 全量封装成工具,觉得"覆盖越全越好"。

实际效果往往相反:

  • 工具太多,选择成本高
  • 工具粒度太细,完成一个目标要拼很多步
  • 工具描述太弱,模型不知道什么时候该用
  • 工具返回太原始,模型还得自己提炼信息
  • 出错只给一句 Error,Agent 不知道怎么修

如果一个工具只描述"它能做什么",却没说明:

  • 什么时候用
  • 什么时候不用
  • 参数应该怎么填
  • 出错后怎么改

那它对 Agent 来说通常不够好。

1.2 好工具与差工具的差别

|----|---------------------|--------------------------------------------|
| 维度 | 好工具 | 差工具 |
| 粒度 | 对应 Agent 要完成的目标 | 对应 API 能做的操作 |
| 示例 | update_yuque_post | get_post + update_content + update_title |
| 返回 | 与下一步决策直接相关 | 完整原始数据 |
| 错误 | 结构化,带修正建议 | 通用字符串 "Error" |
| 描述 | 说明适用边界 | 只写功能说明 |

工具不是功能清单,工具是动作接口。

1.3 工具设计如何演进

第一代:API 封装

最早的做法是把 API Endpoint 直接包成工具。

问题是粒度太细,Agent 要自己拼路径、管中间状态、猜调用顺序。

这对人类工程师还行,对 Agent 负担很重。

第二代:ACI,Agent-Computer Interface

ACI 的核心是:工具应该对应 Agent 的目标,而不是底层 API 的边界。

不要给 Agent 一个只表达底层操作的通用接口,比如:

复制代码
update(id, content)

而是给:

复制代码
update_yuque_post(post_id, title, content_markdown)

它表达的是一个完整动作,而不是一个零碎接口。

给 Agent 的不是 API,而是可执行意图。

第三代:Advanced Tool Use

到了更成熟的阶段,不只是工具本身,连工具发现、工具调用、工具表达方式都要一起优化。

1)Tool Search:按需发现工具

不要把所有工具一次性塞给模型,而是按任务动态发现。

这样可以显著降低上下文负载,也让模型只在相关工具集合里做选择。

2)Programmatic Tool Calling:代码编排

不要让中间结果一轮轮穿过模型,而是让代码负责执行链路。

模型决定策略,代码负责流水线,中间状态留在运行环境里,不再反复进入上下文。

3)Tool Use Examples:示例驱动

JSON Schema 只能告诉模型参数类型,不能告诉它调用场景。

给工具附上真实示例,往往比纯文字说明更有效。

1.4 设计工具时,先看能不能修回来

工具设计不是看"能不能调",而是看:

调错了以后,Agent 能不能自己修回来。

这也是判断工具质量的重要标准。

三个原则:

  1. 参数要明确
    尤其是 ID、路径、时间窗口、资源名等字段,必须尽量减少歧义。
  2. 错误要结构化
    失败不能只返回一句 Error,要告诉 Agent 错在哪里、能否重试、怎么改。
  3. 定义和实现要绑定
    schema、描述、运行逻辑、错误提示最好统一维护,避免文档和实现脱节。

1.5 调试 Agent,先查工具,不要先怪模型

大多数"工具选错了"的问题,根因不在模型,而在工具定义。

优先检查:

  • 工具描述是否清楚说明适用边界
  • 输入参数是否过宽、歧义是否太大
  • 返回值是否包含下一步所需信息
  • 错误是否结构化
  • 是否存在不该新增的冗余工具

能用 Shell 处理的,不要专门做工具;只是静态知识的,不要做工具。


二、工具消息隔离:框架状态不要直接喂给 LLM

很多 Agent 框架会在运行过程中产生大量内部事件,例如:

  • 压缩发生了
  • 某个工具调用被跳过了
  • 通知队列有新消息
  • 某个状态字段被更新了

这些信息对框架很重要,但不一定适合直接进入 LLM。

如果全部喂给模型,只会带来两个问题:

  • 模型看到一堆它不需要理解的字段
  • token 被白白消耗

2.1 分层消息设计

比较稳妥的做法是区分两类消息:

AgentMessage

给应用层或框架层使用,可以携带任意自定义字段。

Message

真正送入 LLM 的消息,只保留标准类型:

  • user
  • assistant
  • tool_result

这样做的好处是:

  • 会话历史保留完整框架状态
  • LLM 输入更干净
  • 更容易做压缩、过滤和审计

框架要保留状态,模型只需要看到决策输入。


三、记忆系统:Agent 记不住,不是模型问题,是系统没设计好

Agent 没有原生时间连续性。

会话结束后,上下文清空,下一次启动不会自动记得上次发生了什么。

所以要让 Agent 具备跨会话一致性,记忆层必须单独设计。

记忆不是附加功能,而是基础设施。

3.1 四种记忆,不是四种存储,而是四类问题

1)上下文窗口:工作记忆

当前任务所需的最小信息。容量有限,必须主动管理。

2)Skills:程序性记忆

"怎么做某件事"的固定流程、领域规范、工具使用方式。适合按需加载,不应默认常驻。

3)JSONL 会话历史:情景记忆

记录"发生了什么",支持跨会话检索、回放、审计。

4)MEMORY.md:语义记忆

Agent 主动写入的重要事实、稳定偏好、长期结论。下一次启动时注入系统提示。

3.2 记忆系统的核心不是存储,而是注入控制

真正重要的问题不是"存哪儿",而是:

  • 什么时候注入
  • 注入多少
  • 注入到哪里
  • 过期信息怎么处理
  • 整合失败怎么回退

成熟的记忆系统,不是把所有历史都留下,而是把最有用的状态,以最小成本带回当前任务

3.3 Skills 和 MEMORY.md 的分工

  • Skills 放稳定流程、操作规范、领域方法
  • MEMORY.md 放长期稳定事实、已验证偏好、历史结论

可以简单理解为:

方法放 Skill,事实放 Memory,别混。

3.4 记忆整合:只移动指针,不删除原文

记忆分层后,下一步不是"要不要存",而是:

什么时候整合,以及整合失败怎么办。

一个典型流程是:

  • 消息流持续增长
  • tokenUsage / maxTokens >= 0.5
  • 触发整合
  • 对待整合消息执行 summarize
  • 成功:写入 MEMORY.md,并移动整合指针
  • 失败:原始消息写入 archive/

关键原则只有一个:

系统只移动指针,不删除原始消息。

这样即使摘要失败,也能回到原始存档继续工作。

这比"摘要写得漂亮"重要得多,因为它保证了可回退、可审计。

3.5 记忆实现上,别一开始就过度复杂

很多场景下,记忆系统不需要一上来就做复杂向量库。

结构化 Markdown + 关键词搜索,往往已经足够:

  • 可读
  • 可改
  • 可调试
  • 成本低
  • 维护简单

只有当规模上来、语义召回确实有收益时,再引入更重的检索方案更合理。


四、逐步放开自主度:先补基础设施,再谈放权

这里说的自主度,不是"少几次人工确认",而是让 Agent 能在更长时间跨度内稳定推进任务。

但前提不是直接放权,而是先补齐三类能力:

  1. 跨 session 续跑
  2. 单 session 内进度约束
  3. 慢速 I/O 的后台接入

4.1 长任务如何跨 session 继续

长任务最常见的失败,不是单步报错,而是 session 结束时任务还没做完。

更稳定的模式,是把长任务拆成两个角色:

Initializer Agent

只运行一次,负责生成:

  • feature-list.json
  • init.sh
  • 初始 git commit
  • claude-progress.txt

作用是把任务外化成可持久化状态。

Coding Agent

后续多个 session 循环执行:

  • 从文件恢复现场
  • 定位当前任务
  • 实现一个功能
  • 跑测试
  • 更新 passes
  • 提交代码后退出

进度要放在文件里,不要放在上下文里。

4.2 任务状态要显式写出来

跨 session 解决的是"下次从哪里继续",

单 session 内还要解决"当前做到哪一步"。

一个简单状态结构可以是:

复制代码
{
  "tasks": [
    {"id": "1", "desc": "读取现有配置", "status": "completed"},
    {"id": "2", "desc": "修改数据库 schema", "status": "in_progress"},
    {"id": "3", "desc": "更新 API 接口", "status": "pending"}
  ]
}

建议约束:

  • 同一时间只能有一个 in_progress
  • 每完成一步,先更新状态,再继续下一步
  • 长时间不更新状态时,可注入轻量提醒

这类显式状态管理,本质上是在给 Agent 加外部执行骨架。

4.3 后台 I/O 接入:别让慢操作拖死主循环

当 Agent 自主度提高后,真正拖慢主循环的,通常不是推理,而是文件读写、网络请求和长耗时命令。

更务实的做法是把慢速 subprocess 放到后台线程,通过通知队列在下一轮 LLM 调用前注入结果。

主循环只需要每轮开始前检查是否有新结果,再决定继续执行、等待还是调整计划。

这通常比把整个 loop 改成复杂 async runtime 更稳,也更容易维护。


五、多 Agent 如何组织:先解决隔离,再解决协作

一说到多 Agent,很多人先想到并行。

但工程上首先要解决的,其实不是并行,而是隔离与协作

5.1 指挥者模式 vs 统筹者模式

指挥者模式

同步协作,人与单个 Agent 紧密互动,每一轮都要人工调整决策。

问题是 session 一结束,context 就没了,产出短暂。

统筹者模式

异步委派,人在开始时设定目标,中间让多个 Agent 并行工作,最后审查产出。

人只在起点和终点出现,中间产出变成可持久化工件。

多 Agent 的价值,不是多开几个模型,而是把人的持续参与变成对工件的最终审核。

5.2 多 Agent 的典型拓扑

常见组织方式是:

  • 主 Agent 作为 Orchestrator 统筹全局
  • 下挂多个子 Agent 并行工作
  • 子 Agent 通过 JSONL inbox 协议通信
  • 用 Worktree 隔离文件修改
  • 用任务图管理依赖关系

为什么要这样做?

因为多 Agent 协作的核心不是"分工",而是:

  • 分隔上下文
  • 保留探索历史
  • 让结果可追溯
  • 防止彼此污染

5.3 子 Agent 适合做什么

子任务里的搜索、试错和调试过程,不应该污染主 Agent 的上下文。

主 Agent 真正需要的是结论,不是过程。

复制代码
const result = await runAgentLoop(task, { messages: [] });
return summarize(result);

这样做的好处是:

  • 主上下文更清爽
  • 子任务可大胆试错
  • 失败历史不会拖垮主任务
  • 并行更自然

5.4 多 Agent 协作必须写成协议

多 Agent 协作一旦靠自然语言对齐,很快就会失控。

模型记不稳谁承诺了什么、谁在等谁、谁已经完成、谁还没开始。

所以必须把协作写成协议,而不是写成聊天。

复制代码
{
  request_id, from_agent, to_agent,
  content,
  status: 'pending' | 'approved' | 'rejected',
  timestamp
}

落地方式通常是:

  • 写入:.team/inbox/{agentId}.jsonl
  • 特点:append-only,崩溃可恢复
  • 读取:按行解析,按 status 过滤

5.5 幻觉会互相放大,交叉验证不能省

多 Agent 频繁互动时,错误会层层放大:

  • Agent A 先带偏
  • Agent B 跟着强化
  • Agent C 继续叠加
  • 最终所有 Agent 收敛到同一个高置信度错误结论

所以交叉验证很重要。

它的作用不是"再找一个模型复读",而是打断错误共识的形成链路。

5.6 子 Agent 的两个边界

1)深度限制

防止无限递归生成孙 Agent,设一个最大深度即可。

2)最小系统提示

只给必要的三部分:

  • Tooling
  • Workspace
  • Runtime

不要带 Skills 和 Memory 指令。

这样可以避免权限外泄,也避免破坏隔离边界。


结语

中篇讲的不是"模型有多强",而是"系统怎么把模型用对"。

  • 工具决定它能做什么
  • 记忆决定它能记住什么
  • 自主度决定它能做多远
  • 多 Agent 决定它能不能扩展规模

生产级 Agent 的核心,从来不是一句"更智能",而是四个更具体的问题:

  • 工具是否贴近任务语义
  • 记忆是否可控、可回退、可审计
  • 自主度是否有明确边界
  • 多 Agent 是否有协议、有隔离、有验证

真正决定 Agent 上限的,往往不是"模型能不能想",而是"系统能不能让它持续、稳定、可验证地把事情做完"。

相关推荐
码以致用3 小时前
DeerFlow Memory架构
人工智能·ai·架构·agent
2603_954708313 小时前
如何确保微电网标准化架构设计流程的完整性?
网络·人工智能·物联网·架构·系统架构
雨奔8 小时前
TSF 微服务熔断实战:从原理到落地,杜绝级联故障
微服务·云原生·架构
智能化咨询8 小时前
(196页PPT)集团IT应用架构规划详解(附下载方式)
架构
2501_933329558 小时前
企业级舆情监测系统技术解析:Infoseek数字公关AI中台架构与实践
开发语言·人工智能·自然语言处理·架构
安卓蓝牙Vincent8 小时前
Android BLE SDK 设计手册(一):一次参数改动,让我重新设计了整套架构
android·架构
小丑依然是我9 小时前
终端 AI 助手的上下文压缩与持久化记忆设计
架构·openai
uzong10 小时前
架构师的必修课:分布式系统发布理论设计要点
后端·架构
张忠琳10 小时前
【vllm】(二)vLLM v1 Engine — 模块超深度逐行分析之二
ai·架构·vllm