过去两年,"Agent"这个词被用得越来越松。产品发布会上,一个带按钮的对话框叫 Agent;技术博客里,一段带 while 循环的脚本也叫 Agent;咨询报告里,"数字员工"更是被直接等同于 Agent。名称的通货膨胀带来一个实际后果:当我们真的要去搭一个 Agent、评估一个 Agent 产品,或者向团队解释"为什么我们做出来的东西不好用"时,缺少一套共同的语言。
这篇文章想做的事情,是把 Agent 拆到不能再拆的四块:循环(Loop)、工具(Tools)、记忆(Memory)和运行时(Harness)。这四块并不是并列关系,它们在工程上互相牵制,任何一块短板都会以"这东西不太聪明"的形式暴露在用户面前。理解这种牵制关系,比记住四个名词重要得多。
一、先把定义钉死:什么才算 Agent
一个常见但不够精确的说法是:Agent 是会帮人干活的大模型。这句话把工具、记忆、编排全部折叠进了"大模型"三个字里,既不解释它能做什么,也不解释它为什么会失败。
更贴近机制的定义是:Agent 是一个能够调用工具、并把执行结果重新喂回模型的循环系统。这个定义抓住了两个本质特征:第一,它的工作方式是循环而不是单次问答;第二,它的行动能力来自工具调用而不是语言生成。
但严格说,这个定义仍然只覆盖了 Loop 和 Tools。因为要让循环稳定地跑起来,还缺两样东西:一是每次循环都要把历史和结果带上,否则每一轮都是失忆的全新开始,谈不上任何智能;二是必须有一个统一的东西去调度、兜底、鉴权、限流。前者就是 Memory,后者就是 Harness。
所以我们得到完整的判断标准,可以用四个问题来检验一个系统算不算真正的 Agent:
- 它是否有循环,且下一轮的行为由上一轮的结果决定?
- 它是否能调用外部能力,并且真的改变了外部状态?
- 它是否在循环之间保留状态,让第 N 轮知道第 1 轮发生过什么?
- 它是否有一个运行时,负责调度、权限、错误恢复和上下文治理?
四个都满足,才是一个可用的 Agent。只满足前两个,是一个工具调用器;只满足第一和第三个,是一个会自动续写的话痨;四个都不满足,就是聊天机器人。
用一张表把三类系统区分开,会更容易看清边界:
| 维度 | 聊天机器人 | 固定工作流 | Agent |
|---|---|---|---|
| 下一步由谁决定 | 不需要决定 | 人预先写死的图 | 模型在每一步现场决定 |
| 是否调用外部能力 | 通常不调用 | 调用,但路径固定 | 调用,且路径动态 |
| 状态管理 | 会话历史 | 流程变量 | 循环上下文 + 长期记忆 |
| 失败时的行为 | 换个说法重答 | 报错或走异常分支 | 观察错误、调整策略、重试或终止 |
| 主要风险 | 胡说 | 分支没写全 | 跑偏、越权、烧钱、无限循环 |
这张表也解释了一个反复出现的困惑:为什么很多"Agent 产品"用起来像一个会自己点按钮的脚本?因为它们的路径其实是写死的,模型只是在填空,真正决定行为的是那张流程图,不是模型。判断标准很简单------把模型换成一个更笨的模型,如果系统行为几乎不变,那它就不是 Agent,是工作流。
接下来逐个拆这四个内核。拆的顺序有讲究:先讲 Loop,因为它是唯一不可省略的骨架;再讲 Tools,因为它是行动力的来源;然后讲 Memory,因为它是循环能延续的前提;最后讲 Harness,因为它是前三者能不能在真实环境里活下来的条件。
二、Loop:Agent 的心跳
如果把 Agent 比作一个人,模型是大脑,那 Loop 就是心跳------它不产生思想,但思想靠它一次次被送到下一拍。
2.1 ReAct 的原始含义,以及被误读的部分
Loop 的经典范式是 ReAct,原始论文里是 Thought、Action、Observation 三步交替:模型先想一段(Thought),决定调用哪个工具(Action),拿到外部返回(Observation),再基于这个返回继续想。这和很多人转述的"思考---行动---观察---再思考"其实是同一件事。
被误读的地方在于"Reason 是规划"。严格说,ReAct 里的 Thought 不是一次性的全局计划,而是每一步的局部推理;全局规划属于 Plan-and-Execute 一类范式,两者经常被混着用。这个区别在工程上很重要:ReAct 的循环更短、更灵活,但容易在长任务里迷失方向;Plan-and-Execute 有全局骨架,抗漂移能力强,但计划一旦错了会一路错到底。成熟的实现通常是混合的------先出一个粗计划,再在每一步用 ReAct 修正它。
原文里还有一个容易被忽略的类比:这个循环很像 PDCA 戴明环。Plan 对应推理与规划,Do 对应调用工具,Check 对应观察结果,Act 对应根据结果调整。这个类比的价值不在于文艺,而在于它提醒我们:循环的每一次迭代都应该产生信息增量。如果第 N 轮和第 N-1 轮拿到的观察一模一样、做出的决定也一模一样,这个循环就是在空转。
2.2 一个能上生产的循环,比想象中复杂
教科书版的循环只有五行代码:调模型、解析工具调用、执行工具、拼回上下文、再来一遍。能 demo 的东西和能上生产的东西之间,隔着至少七个工程问题。
第一,终止条件。 除了模型自己说"任务完成",还必须有三道硬闸:最大步数、最大挂钟时间、最大 token 预算。只依赖模型自觉终止,等于把服务器的账单交给一个概率模型。我在实践中见过最贵的一次事故,是一个检索工具总是返回空,模型于是不停换关键词重试,四十分钟烧掉了半个月的额度。
第二,重复检测。 模型会陷入两种典型死循环:完全相同的调用(同样的工具、同样的参数),以及语义相同但字面不同的调用(换个说法问同一个问题)。前者用参数的哈希去重即可,后者需要看工具返回是否实质相同。一旦检测到重复,正确的做法不是硬性中断,而是往上下文里注入一条明确的提示,比如"你已经用相同参数调用过该工具三次,返回一致,请更换策略或直接给出结论"。
第三,观察的清洗。 工具的原始返回往往又长又脏:一个网页抓下来几万字符、一条 SQL 查回三千行。原样塞回上下文,既烧钱又干扰判断。必须有一层截断与摘要:保留结构(状态码、字段名、前若干条记录),丢掉噪声。这一步做得好不好,直接决定 Agent 是"看起来聪明"还是"越来越糊涂"。
第四,错误的可恢复性。 工具报错不能只抛一句"执行失败"。要让模型有能力自己修,错误信息里必须包含三样东西:错在哪个参数、期望什么格式、可以怎么改。比如"参数 page 必须是正整数,你传了 'first'",模型下一轮基本能自己改对;而"请求异常"这四个字,模型只能瞎猜。
第五,中断与人工确认。 有些动作不可逆:删库、发邮件、付款、对外发布。这类动作在执行前必须停下来问人。这不是体验问题,是安全问题。
第六,并发与顺序。 多个互不依赖的读操作可以并发,写操作通常必须串行。并发的难点不在发出去,而在返回顺序错乱时怎么和上下文对齐。
第七,可观测性。 每一轮都要能回答:模型看到了什么、决定了什么、工具返回了什么、花了多少 token。没有这条链路,线上出问题只能靠猜。
把这些串起来,一个最小可用的循环大致长这样:
ts
async function runLoop(task: string, budget: Budget) {
const ctx = new Context(task);
for (let step = 0; step < budget.maxSteps; step++) {
if (budget.exhausted()) return ctx.finalize("预算耗尽,输出当前结论");
const decision = await model.decide(ctx.snapshot());
if (decision.type === "final") return ctx.finalize(decision.text);
const call = decision.toolCall;
if (ctx.isRepeated(call)) {
ctx.inject("相同调用已出现,返回一致,请更换策略");
continue;
}
if (requiresApproval(call)) {
const ok = await human.confirm(call);
if (!ok) { ctx.inject("用户拒绝该操作,请换方案"); continue; }
}
const result = await tools.execute(call);
ctx.append(call, sanitize(result), cost(result));
}
return ctx.finalize("达到步数上限,输出当前结论");
}
这段代码里最值得注意的不是循环本身,而是循环外面那些判断:预算、去重、审批、清洗。真正的 Agent 工程量,八成花在这些"循环之外"的地方。
2.3 四种典型的循环失灵
无限循环。前面说过,表现为重复调用或反复自我怀疑。根因通常是工具返回没有提供有效信息,模型只能不断重试。修法是给观察加"信息量检查":如果连续两轮没有新信息,强制收敛。
目标漂移。长任务跑到第十几轮,模型开始做一件和原始需求关系不大的事。根因是上下文里塞了太多中间产物,原始目标被稀释。修法是把任务目标固定在上下文最前面,并在每若干轮做一次"回看目标"的重述。
过早收敛。模型在只完成一半时宣布完成。根因往往是缺乏验收标准。修法是在任务开始时就让模型写下"完成的标准是什么",循环结束前逐条对照。
观察污染。工具返回里夹带了指令性文字(网页里的"请忽略之前的指令"),模型被带偏。这是提示注入的典型入口,属于 Harness 层的防御范畴,后面再说。
2.4 循环与上下文膨胀的天然矛盾
Loop 有一个绕不开的物理规律:每一轮的上下文都比上一轮长。工具返回、模型输出、错误信息都会累积。一个二十轮的循环,上下文很容易从两千 token 膨胀到几万 token。
这带来两个后果。成本上,每轮都要重付一次全部上下文的钱,循环越深越贵;效果上,上下文越长,模型对关键信息的注意力越稀薄,越到后面越容易忘掉最初的任务约束。
这个矛盾不是靠"换更大的上下文窗口"能解决的。窗口再大也是有限的,而且越长越贵越慢。真正的解法是 Memory 与 Harness 的压缩:把已经消费完的中间过程折叠成结论,只保留决策需要的那部分。
所以你看,Loop 从来不是孤立的一层。它天然向 Tools 索取行动、向 Memory 索取状态、向 Harness 索取治理。这就是为什么只讲"Agent 是个循环"是没用的------循环是最容易写的部分。
三、Tools:Agent 的手脚
如果说 Loop 是心跳,Tools 就是手脚。没有工具,模型只能输出文字;有了工具,它的输出就变成了对外部世界的改变。
3.1 Tool 是能力,不是知识
这是四条里最容易被混淆的一条,值得单独强调:Tool 负责"能做什么",Memory 负责"知道什么"和"该怎么做"。
把一份产品手册塞进工具的 description 里,指望模型靠它回答业务问题,是最典型的错误。工具描述应该是"调用这个能力需要什么输入、会得到什么输出",而不是"这个系统的业务规则是什么"。前者是接口契约,后者是知识库。
更隐蔽的一种错误是把工具当成"万能入口":一个叫 query_database 的工具,接收自然语言,内部自己去理解、去拼 SQL。这看起来省事,实际上把不确定性从模型层搬到了工具层,而且搬到的是一个没有推理能力的地方。一旦出错,模型拿不到可用的错误信息,也学不会怎么改。
3.2 一个好工具长什么样
工具的本质是一个按钮,模型靠名字和描述来判断该按哪个。所以工具的元信息质量,几乎决定了 Agent 的行动准确率。
名字要动宾明确。 search_docs 好于 docs,create_issue 好于 issue_tool。名字是模型看到的第一信号,模糊的名字会让模型在多个工具间随机摇摆。
描述要回答三个问题:做什么、什么时候用、有什么限制。 参考写法:
json
{
"name": "search_issues",
"description": "按关键词搜索代码仓库中的 issue。仅支持已开放的 issue,不含 PR。关键词支持 AND 组合,最多 5 个。结果按相关度排序,默认返回 10 条。",
"input_schema": {
"type": "object",
"properties": {
"keywords": { "type": "array", "items": { "type": "string" }, "maxItems": 5 },
"state": { "type": "string", "enum": ["open", "closed", "all"], "default": "open" },
"limit": { "type": "integer", "minimum": 1, "maximum": 50, "default": 10 }
},
"required": ["keywords"]
}
}
这段描述里有四个信息:能力的边界(只有 issue 不含 PR)、调用方式(AND 组合)、默认行为(10 条)、排序规则。这些细节不是给人类看的文档,是模型决策的输入。
粒度要单一。 一个工具做一件事,简单到一两句话能说清。反例是那种带 action 枚举参数的大工具,action 有二十个取值,每个取值的参数还不一样。这种设计对人类来说只是"接口大一点",对模型来说是指数级的理解负担。
返回值要为模型设计,不是为程序。 程序要的是完整数据,模型要的是可判断的结论。返回三千行 JSON,模型既读不完也抓不住重点。正确做法是返回"状态 + 关键字段 + 前若干条 + 总条数 + 是否被截断"。让模型知道"还有 2900 条没给你",它才知道要不要继续缩小范围。
错误信息要可行动。 这一点上一节讲过,这里补一个格式建议:错误也应该是结构化的,包含 code、message、hint 三个字段。hint 那栏写给模型看的修复建议,效果远好于让模型自己猜。
3.3 读工具与写工具,风险完全不同
按副作用把工具分成两类,是设计权限体系的基础。
读工具:搜索、查询、读取文件、抓网页。特征是幂等、可重试、失败代价低。这类工具可以放开并发,可以自动重试,可以让模型自由调用。
写工具:写文件、发消息、提交代码、下单、删除。特征是有副作用、不可幂等、失败代价可能很高。这类工具必须串行、必须校验、通常必须人工确认。
很多事故的根因就是把写工具当读工具用:让模型在没有确认的情况下批量删除、批量发送。技术上的修法很简单------在工具注册表里给每个工具标注 readOnly 与 destructive,运行时据此决定是否走审批。管理上的修法更重要:默认拒绝,按需放开,而不是默认放开再事后追责。
3.4 工具爆炸:数量不是能力
一个直觉是工具越多能力越强。实际相反。工具数量上去之后,模型的选择准确率会明显下降,因为相似的描述互相干扰,而且光是把工具定义塞进上下文就要占掉大量 token。
经验上,单个 Agent 承载的工具在十几个到二十几个之间是比较舒服的区间。超过这个量级,通常需要做两件事:一是分层,把工具按场景分组,先让模型选组再选工具;二是收敛,把长期没人调用的工具下线,把功能重叠的工具合并。
还有一件常被忽略的事:工具的描述本身要进上下文,所以工具的"文档成本"是真实成本。一个工具加 300 token 描述,二十个工具就是 6000 token,每一轮都要重付。
3.5 MCP 解决的是连接问题,不是设计问题
MCP(Model Context Protocol)常被描述成"Agent 工具生态的标准"。它的价值确实在于标准化:把 M 个 Agent 和 N 个外部系统之间的 M×N 条集成,收敛成 M+N。接入一个 MCP 服务器,就获得一批工具,不需要为每个客户端重写适配。
但要清楚它不解决什么。它不解决工具描述写得烂、不解决粒度太粗、不解决返回值太长、也不解决权限该不该放开。协议统一了"怎么连",没有统一"怎么设计"。把一堆设计糟糕的工具通过 MCP 接进来,只会让模型更快地犯更多错。
四、Memory:Agent 的知识库
Memory 要回答两个问题:循环之间的状态存在哪,以及下次该把哪部分取出来。
4.1 从向量数据库到文件知识库
早期的标准答案是向量数据库:把文档切块、做 embedding、存进向量库,运行时把用户问题也向量化,找相似度最高的若干块塞进上下文。这套方案流行过一阵,因为它看起来优雅------语义相似度天然解决了"关键词对不上"的问题。
但在真实场景里,它的三个失效点很快暴露出来:
切块破坏结构。 一个章节被切成十块,每块单独看都缺上下文,检索出来拼在一起逻辑是断的。表格被切碎更是灾难。
相似不等于相关。 语义相近的段落未必是回答问题需要的那段。用户问"这个接口的限流规则",检索到三段都在讲"接口",但都没讲限流。
更新滞后。 文档改了,向量库要重建;不重建就检索到过期内容。而 Agent 的记忆恰恰是高频变化的------今天新加了一条约束,明天要能让它记住。
于是方案开始回摆。现在越来越多系统采用"知识库"形态:一个装满 markdown 文件的目录树,靠文件名、标题、frontmatter 里的描述、以及目录结构来做检索。
markdown
knowledge/
├── README.md # 索引:这个库讲什么、怎么找
├── deploy/
│ ├── rolling.md # 标题:滚动发布流程
│ └── rollback.md # 标题:回滚的判断标准与步骤
└── api/
├── rate-limit.md # 标题:接口限流规则(含配额表)
└── auth.md # 标题:鉴权与 token 生命周期
这种结构的好处是"先导航再精读":模型先看目录,判断哪几个文件相关,再读具体文件。这比一次性检索一堆语义片段更接近人的查资料方式,也更容易解释------你能说清它为什么读了那个文件。
更关键的一点是可写。文件可以被读,也可以被改。上一步的执行日志、这次的报错记录、新学到的约束,都可以写回去。这让记忆从"静态资料库"变成了"会长的东西"。
4.2 两种方案的取舍
| 维度 | 向量数据库 | 文件知识库 |
|---|---|---|
| 检索方式 | 语义相似度 | 结构导航 + 关键词 |
| 优势场景 | 海量非结构化文本、模糊提问 | 结构清晰、需要精确命中的资料 |
| 更新成本 | 需重建索引 | 直接改文件 |
| 可解释性 | 弱,难说清为什么命中 | 强,能指出读了哪个文件 |
| 结构保持 | 切块即破坏 | 原样保留 |
| 可写性 | 差 | 好 |
结论不是"向量库过时了",而是两者解决的问题不同。生产中更常见的是混合:目录导航做粗筛,关键词与语义检索做精排,再让模型自己决定读哪几份。纯靠一种,边界场景都会翻车。
4.3 记忆的四个层次
把 Memory 当成一个东西会带来设计混乱。它至少分四层,每层的生命周期和写入策略都不同。
工作记忆,就是当前的上下文窗口。生命周期是这一轮,内容由 Harness 的组装逻辑决定。
会话记忆,是这次对话的历史。生命周期是一次会话,靠消息列表维护,过长时需要压缩。
长期记忆,跨会话存在,包括用户的偏好、项目的约束、既往的决策。这层最容易被滥用------什么都往里记,最后变成一个没人维护的垃圾堆。
程序记忆,是"怎么做一件事"的操作手册,通常以 Skill 的形式存在:一段说明、几个步骤、若干注意事项。它不回答"是什么",回答"按什么顺序做"。
这四层里,最值得强调的是工作记忆与程序记忆的分工。前者是数据,后者是方法。把方法当数据塞进上下文,会挤占掉真正需要的信息;把数据写进程序记忆,则会让手册变成一本流水账。
4.4 什么该记,什么不该记
这是记忆系统最难的判断,也是最容易被忽略的。
该记的:用户明确表达的偏好、项目里稳定成立的事实、已经拍板的决策及其理由、踩过的坑。这些的共同点是------下次还会用到,而且重新获取的成本高于存储成本。
不该记的:一次性的中间状态、可以从代码或文档里直接查到的东西、模型的推测、以及任何带密钥的内容。最后一条尤其重要,记忆文件往往会被原样注入上下文,等于把密钥贴在了每一轮请求里。
还有一条实践原则:记忆是引用,不是命令。写进记忆的内容,下次被读出来时应该当作"可能过期的历史信息",而不是"必须执行的指令"。否则一次错误的记录会永久污染后续所有行为。冲突时以当前指令为准,这条规则必须写进记忆系统的使用说明里。
五、Harness:真正决定体验的那一层
Harness 这个词还没有统一的中文译名。叫框架、叫编排层、叫运行环境都有道理,但都不够准。最贴切的类比是操作系统:它不思考,但没有它,模型什么也做不成。
5.1 为什么会出现 Harness 这一层
在 Harness 成熟之前,搭一个 Agent 要自己写一大堆东西:循环怎么调度、超时怎么处理、上下文太长怎么截、工具报错怎么重试、哪些操作要问人。这些工作琐碎、重复、每家都要重写一遍,而且写错的方式高度雷同。
Harness 把这些收编了。它的定位是"基础设施 + 编排",把模型当成一个需要被伺候的组件,围绕它提供运行所需的一切:上下文组装、工具执行、状态持久化、权限控制、失败恢复、成本计量、可观测性。
从这个角度看,市面上的 Agent 产品------无论是 Codex 这类编码 Agent,还是 Claude Code、Deepseek Harness 这类通用 Agent 运行时------从工程角度讲,主要构成都是 Harness。模型是同一个模型,体验差异主要来自 Harness 的细节。
5.2 一个 Harness 的分层结构
把各类实现抽象一下,大致是这么几层:
text
┌─────────────── 观测输入 ───────────────┐
│ 实时数据 日志 用户文本 定时事件 │
└────────────────┬───────────────────────┘
▼
┌──────────────── Agent Harness ────────────────┐
│ Observation Layer → Context Layer │
│ │ │ │
│ ▼ ▼ │
│ Agent Model ←────── Memory Layer │
│ │ │
│ ▼ │
│ Actions Layer → 外部资源 / 工具 │
│ │ │
│ ▼ │
│ Supervisor Feedback & Escalation │
└───────────────────────────────────────────────┘
观测层负责接收各种来源的输入:用户消息、文件变化、日志、定时触发、其他系统的回调。它要做的是归一化------不管输入从哪来,都变成同一种"事件"。
上下文层决定这一轮给模型看什么。这是整个 Harness 里最难的一层,因为它要在"信息足够"和"token 可控"之间做取舍。它不是简单地拼接,而是选择、排序、压缩、标注优先级。
模型层是实际调用大模型的地方,负责重试、降级、多模型路由、流式输出。
记忆层负责持久化与检索,就是上一节讲的那套东西。
动作层执行工具调用,管并发、超时、幂等、结果清洗。
监督层处理异常与升级:什么时候该重试、什么时候该换策略、什么时候该停下来问人。
这个分层的价值在于,它把"体验问题"定位到了具体的一层。用户说"它老是忘",多半在上下文层或记忆层;说"它点错了按钮",多半在动作层或工具定义;说"它跑到一半卡住",多半在监督层。
5.3 权限:Harness 最不能省的功能
一个能读写文件、能执行命令、能访问网络的 Agent,本质上是一个拿到了你机器部分控制权的程序。权限设计不是可选项。
成熟的权限模型通常有三档:允许、询问、拒绝。默认应该是拒绝,按需放开最小集。文件访问再叠一层沙箱,把可写范围限制在工作目录内。
更细的设计要区分"读"与"写"、"本机"与"外网"、"可逆"与"不可逆"。删文件、发邮件、提交对外内容,这些动作即使在一个已经授权的会话里,也应该逐次确认。
这里有一组张力值得说清:问得太频繁,用户会觉得烦,等于没有确认;完全不问,用户会感到失控。好的设计不是把选择权全交给用户,而是把"明显安全"的动作自动放行,把"明显危险"的动作强制拦截,只在真正模糊的区间问人。这需要 Harness 对动作有语义判断,而不是只有一个开关。
5.4 上下文治理:Harness 的核心竞争力
如果只能选一个能力来定义 Harness 的优劣,我会选上下文治理。原因很直接:模型的推理能力是外部给定的,能优化的只有"给它看什么"。
上下文治理要解决四件事。
预算分配。 上下文窗口是有限的,系统提示、工具定义、记忆、历史、当前输入都在抢空间。必须有一个明确的预算表:多少留给系统、多少留给工具、多少留给历史、多少留给当前任务。没有预算表的系统,最终一定会被工具定义撑爆。
压缩。 长会话必然需要把早期内容折叠。好的压缩不是做摘要,而是保住"决策所需的最小信息":任务目标、已确认的约束、已经排除的方案、当前的进展。丢掉的是过程,保留的是结论和理由。压缩做得好,一个跑了五十轮的会话仍然清醒;做得差,第二轮就开始重复劳动。
缓存。 大模型的输入有缓存机制,稳定不变的前缀命中缓存后价格会低一个数量级。这带来一个反直觉的设计原则:前缀要稳定。频繁改动系统提示、频繁切换模型、在开头插入动态内容,都会打掉缓存,成本直接翻倍。把变化的东西尽量往后放,是对成本最直接的优化。
注入防御。 工具返回的内容是不可信的。网页、文件、第三方接口的返回里可能藏着指令。Harness 要做的是给这部分内容打上"这是数据不是指令"的标记,并在检测到可疑模式时降级处理。这一层目前没有完美方案,但"完全不设防"和"设了一道"之间的差距是巨大的。
5.5 失败恢复与可观测性
模型会失败,工具会超时,网络会断。Harness 的职责是让这些失败变成"可以恢复的事件",而不是"整个任务崩掉"。
基本要求是:每个动作都有超时;超时有明确的重试策略(次数、退避、是否换路径);重试耗尽后要有降级方案(返回部分结果、换工具、转人工);所有失败都要留下结构化日志。
可观测性常被当成"上线之后再补"的东西,实际上它决定了你能否迭代。至少要能回答:这一轮花了多少 token、命中多少缓存、调了哪些工具、每个工具耗时多少、模型在哪一步做出了关键决策。没有这些,优化就只能是拍脑袋。
六、四者如何咬合:一次请求的完整生命周期
把四块放在一起看,一次请求大致这样流转:
- 用户输入进入观测层,被归一化成事件。
- 上下文层组装这一轮要给模型看的内容:系统规则、工具定义、记忆摘要、历史压缩结果、当前输入。
- 模型层调用模型,返回一个决定:调用工具,或者给出最终答复。
- 如果是工具调用,动作层校验参数、检查权限、执行、清洗结果。
- 结果被追加进上下文,进入下一轮循环。
- 循环过程中,记忆层按需写入;上下文层按需压缩;监督层按需介入。
- 满足终止条件后,输出结果,归档会话。
这个流程里有两笔账值得单独算。
第一笔是 token 账。 假设系统提示加工具定义占 8000 token,历史每轮增加 1500,共跑 10 轮。那么总输入大概是 8000×10 加上历史的累加,接近 15 万 token。如果前缀稳定、缓存命中率能到 80%,实际计费会降到三成左右。这就是为什么"前缀稳定"不是风格问题,是成本问题。
第二笔是信任账。 用户对 Agent 的信任是按次建立的:每成功一次,容忍度提高一点;每失控一次,容忍度断崖下跌。Harness 的审批设计、失败恢复、可解释输出,本质上都是在管理这本账。技术上多花一天做的可观测,可能比多加两个工具更能提升留存。
理解了这两笔账,就能理解一个常见的产品现象:为什么同一个模型,套在 A 产品里像助手,套在 B 产品里像智障。差别不在模型,在上下文给得对不对、工具设计得好不好、失败之后有没有被捞回来。
七、十个常见误区
理论讲完,落到实践。下面十条是我在真实项目里反复见到的认知偏差,按出现频率排序。
一、以为模型越强,Agent 就越好。模型能力确实重要,但在一个已经能跑通的系统里,体验瓶颈往往在上下文组织和工具设计,不在模型。换一个更强的模型,通常只能把成功率从 72% 提到 78%,而修好工具描述能到 90%。
二、把工作流包装成 Agent。路径写死、模型只填空,却对外宣称"自主规划"。这种系统在演示时很惊艳,在真实场景里一遇到分支就露馅。判断标准前面给过:换更笨的模型,行为是否几乎不变。
三、工具越多越好。工具数量的收益递减很快,成本却线性上升。每多一个工具,就多一份描述 token 和一份选择歧义。
四、把工具当知识库。在 description 里写业务规则,或者做一个"什么都查"的万能工具。结果模型既学不会怎么用,也拿不到有效反馈。
五、上下文塞满。觉得多给信息总没错,于是把整个仓库、整篇文档全塞进去。实际上关键信息会被稀释,成本还翻倍。上下文的艺术在于取舍,不在于堆量。
六、只靠模型自己终止。没有硬性预算,没有步数上限。第一次事故通常发生在上线后的第二周。
七、压缩等于摘要。把早期对话改成一段"我们聊了需求"的摘要,结果任务目标、已确认的约束、已排除的方案全丢了,后面反复走回头路。压缩要保决策要素。
八、记忆什么都记。没有写入策略,记忆库几个月后变成一个互相矛盾的大杂烩。记忆需要成本判断,也需要冲突消解规则。
九、权限默认放开。为了体验流畅,把所有确认都关掉。直到某一次模型误删了一个目录,才发现没有回收站。
十、忽略成本与延迟。只测"能不能完成",不测"一次多少钱、多久"。上线后才发现单位经济模型不成立。
这十条有一个共同的根源:把 Agent 当成一个模型问题,而不是一个系统工程问题。模型决定上限,工程决定能不能达到上限。
八、落地清单
如果要给一份可以直接照着做的清单,我会这样列:
| 阶段 | 关键动作 | 验收标准 |
|---|---|---|
| 定界 | 明确任务边界与不可做的事 | 能写出一页纸的任务说明与禁区 |
| 循环 | 步数、时间、token 三重预算;重复检测;终止判据 | 故意让工具持续报错,系统能在预算内收敛 |
| 工具 | 单一职责、名词可辨、描述含边界、返回值结构化 | 盲测:只看工具列表,人能说清每个工具何时用 |
| 观察 | 截断与清洗、错误可行动 | 人为注入超长返回,上下文不爆 |
| 记忆 | 分层设计、写入白名单、冲突以当前指令为准 | 跨会话复用一次,验证没有污染 |
| 权限 | 读/写/危险三档、沙箱、不可逆操作逐次确认 | 高危动作在未确认时被拦截 |
| 上下文 | 预算表、压缩保决策要素、前缀稳定 | 五十轮会话仍能复述原始目标 |
| 可观测 | 每轮记录 token、工具、耗时、决策 | 任意一次失败可在十分钟内定位到轮次 |
| 评估 | 固定任务集、成功率、成本、延迟 | 每次改动后回归,不看单次演示 |
清单里最容易被跳过的是最后一条。没有评估集,所有优化都变成主观感受;有了评估集,很多争论会立刻消失------把两个方案各跑二十个任务,数字会说话。
九、结语:Agent = LLM + Harness,而护城河在细节
回到最初的那个等式:Agent 等于大模型加 Harness。这句话在工程上是自洽的------Loop 是 Harness 调度的,Tools 是 Harness 执行的,Memory 是 Harness 管理的,Harness 本身就是运行环境。四者不是并列的四块拼图,而是三个能力加一个容器。
但这个等式也容易让人误判竞争格局。如果 Agent 就是模型加运行时,而模型是大家都能调用的一样的模型,那差异化从哪来?
答案在那些不性感的地方:工具描述写得够不够清楚、上下文预算分得合不合理、压缩是否保住了决策要素、权限是否既安全又不烦人、失败之后能不能捞回来、成本能不能压到单位经济成立。这些东西单看都很小,叠加起来就形成了体验上的代差。
模型会持续变强,这是确定的。但模型变强不会自动带来 Agent 变好,因为模型变强解决的是"单步判断更准",而 Agent 的问题是"多步协同不出错"。后者是运行时问题,只能靠工程解决。
所以如果要用一句话总结这篇文章:不要在模型上找 Agent 的答案,答案在循环的控制、工具的契约、记忆的结构和运行时的治理里。 这四件事没有一件是新的,但每一件做到位都不容易,而这恰恰是还有机会的地方。