前阵子帮一个做传统后端的朋友梳理方案,他把需求文档推过来,里面有一句写得很顺:"这里用 LLM 做意图识别,外面套一层 Agent,工具统一走 MCP,复杂流程封装成 Skill。"我看完问了他一句:这四个东西,你能各用一句话说清各自负责什么吗?他盯着屏幕愣了几秒,说大概知道,但真让我讲清楚边界,讲不准。这个状态其实特别普遍——LLM、Agent、MCP、Skill 这四个词在过去一年多里几乎是绑定出现的,方案里、架构图里、招聘要求里到处都是,可真落到"我这段代码到底该写在哪一层""这个报错是模型的问题还是编排的问题",很多人立刻就卡住了。
这篇就把这四个概念按层级拆开讲一遍,重点不是给定义,而是讲清它们各自解决什么问题、边界在哪、组合起来是什么样,以及在我自己搭过的几个小系统里,哪一层最容易出问题。适合刚接触这套东西、看着文档一头雾水的人,也适合已经在写 Agent 但总觉得结构拧巴、想重新捋一遍的人。不需要你有很深的机器学习背景,会写点脚本、能看懂 JSON 就够了。
1. 先把四个词按"层级"排一排,很多争论就消失了
这四个词之所以容易混,是因为它们不在同一个抽象层级上。有人拿 Agent 和 LLM 比,有人拿 MCP 和 Skill 比,比来比去发现"好像都差不多",其实是把发动机、整车、充电接口、驾驶手册放在一起比较。先把层级摆清楚,后面的讨论才有公共语言。
我的排法是:LLM 在最底下,是能力来源;Agent 在它上面,是使用能力的方式;MCP 是横向的一层,解决"怎么接外部世界";Skill 是纵向的一层,解决"这类活儿该怎么干"。前两个是"主体",后两个是"配套"。
1.1 LLM:它只做一件事,预测下一个片段
把大模型想象成一个见过极大量文本的"接话高手"。你给它一段前文,它算的是下一个词(严格说是 token)最可能是什么,然后把这个词接到后面,再算下一个。整个过程就这么朴素,没有隐藏的推理引擎,也没有内置的计算器。
这解释了很多反直觉的现象。比如你让它算 987 乘 654,它给的结果可能是错的,因为它不是在列竖式,而是在回忆"这种算式后面通常跟什么数字"。同理,你问它某个函数的参数顺序,它答得头头是道,但那可能是它见过的某个版本的写法,未必是你项目里装的那个版本。
还有一个关键点:LLM 本身是无状态的。两次调用之间它不记得你上次说了什么,所谓"多轮对话",是把历史消息重新拼进上下文再发一次。上下文窗口就是它一次能看的全部内容,超了就得裁。这个特性决定了后面所有架构设计的基本约束——记忆要自己管,状态要自己存,历史要自己压缩。
1.2 Agent:把一次生成变成一次闭环
如果只有 LLM,你能做的就是"输入一段文字,输出一段文字"。这在问答场景够用,但要做"帮我查一下这批订单里异常的那些,然后按地区汇总"这种活儿就不行了,因为它需要多步、需要看中间结果、需要在看到结果后调整下一步。
Agent 补的就是这个循环:想一步 → 做一步 → 看结果 → 再想下一步,直到满足终止条件。学术点叫 ReAct,工程上就是一个 while 循环加一个退出判断。循环里通常包含这几样东西:
- 一份系统提示,说明角色、可用工具、输出格式约束
- 一个工具清单,告诉模型它能调哪些函数、参数是什么
- 一个执行器,负责真正把工具调用跑起来
- 一份会话状态,记录已经做了什么、拿到了什么
- 一个终止条件,比如模型输出"完成"、或者轮次到上限、或者某个校验通过
看着简单,实际写起来九成的坑都在这个循环里。后面第 2 节会细说。
1.3 MCP:把"接线"这件事标准化
假设你写了一个 Agent,要接数据库、接文件系统、接内部接口。你为数据库写了一套适配,为文件系统写了一套,为内部接口又写了一套。现在换一个模型厂商,或者换一个客户端框架,这些适配可能得重写一遍。这就是没有统一协议时的处境。
MCP(Model Context Protocol,模型上下文协议)想干的事,说白了就是给"模型应用"和"外部能力提供方"之间定一套通用的说话方式。类比一下:以前每个牌子的充电器都有自己的接口,出门要带一把线;后来统一成一种口,一根线走天下。MCP 就是这个"统一接口"的角色。
注意它的定位——它不提升模型智商,也不改变 Agent 的循环逻辑,它只负责让"接"这件事变成一次性的、可复用的。这一点想明白,很多"装了 MCP 怎么还是不好用"的困惑就通了:接口通了不等于活儿干得好,那是 Skill 和提示词的事。
1.4 Skill:把"怎么干"沉淀成文档
Skill 这个词在不同语境下含义略有差别,但核心意思是一致的:把一类任务的操作规程打包成一个可复用的单元,里面包含什么时候该用它、按什么步骤做、每步怎么校验、出错了怎么办、最后输出成什么样。
它和工具函数的区别很关键。工具是"能力原子",比如"读一个文件""发一个请求""查一次数据库",它只回答"能做什么"。Skill 是"操作规程",它回答"这件事该怎么做才对"。举个例子,"读 Excel"是工具,"把这份格式混乱的月度报表清洗成规范表并按业务规则核对总计"就是 Skill——它可能调用好几个工具,中间有判断、有校验、有约定俗成的处理方式。
说得再直白点:工具是锤子和锯子,Skill 是"打一个书架"的图纸加施工要点。有了图纸,换个新手也能做出八九不离十的东西;没有图纸,老师傅走了手艺就没了。
2. Agent 不是"更长的提示词":循环、状态、工具三件事缺一不可
很多人第一次写 Agent,写出来的东西本质是一段很长的提示词,把所有可能的情况都用 if-else 式的自然语言描述一遍,然后指望模型照着走。跑两三个简单用例还行,稍微复杂一点就开始飘。问题不在模型,在于缺了循环需要的几个基础设施。
2.1 单轮问答的天花板在哪里
单轮调用的结构是"一次性把所有信息给足,让模型一次给出完整答案"。这条路的天花板很明显:模型没法在生成过程中获取新信息。它只能基于你给的那点上下文作答,一旦任务需要"先看一眼数据再决定怎么处理",就必然抓瞎。
我踩过最典型的一次:让它根据一份日志判断服务异常原因。日志有一万多行,全塞进上下文既超长又稀释注意力,它给出的结论基本是在猜。后来改成两阶段——先让它写一段筛选规则,用代码执行筛出可疑片段,再把片段喂回去分析——准确率立刻上来了。这个"先做一步、看结果、再决定"的结构,就是 Agent 的最小形态。
判断要不要用 Agent,有个简单的经验法则:如果这个任务你能用一段确定性的脚本写完,就别上 Agent。Agent 的价值在于处理"步骤不确定、需要临场判断"的场景,纯流程化的活儿用 Agent 反而是给自己找麻烦,还贵。
2.2 状态管理是最容易被低估的部分
循环跑起来之后,第一个撞上的问题就是上下文膨胀。每一轮的工具返回都往历史里塞,跑十几轮之后上下文就满了,然后你开始裁历史,裁完之后模型忘了前面做过什么,开始重复劳动。
我在几个项目里试下来,比较稳的做法是分层记录,而不是把所有东西一股脑塞进消息历史:
| 层级 | 存什么 | 放在哪 | 保留策略 |
|---|---|---|---|
| 工作记忆 | 当前任务的中间结论、待办清单 | 系统提示的固定区块 | 每轮重写,只留结论不留过程 |
| 事件日志 | 每次工具调用的入参和结果摘要 | 独立存储,按需检索 | 全量落盘,上下文里只留最近几条 |
| 长期事实 | 用户偏好、项目约定、历史决策 | 外部存储,按关键词召回 | 命中才注入 |
关键是中间结论要压缩。工具返回的原始 JSON 可能几千字,但真正有用的是其中三个字段。让模型(或者一段规则代码)在拿到结果后立刻提炼成一句话写进工作记忆,原始内容落盘备查。这一招能把上下文占用降一个数量级,而且模型的判断质量明显更稳定,因为它看到的都是提炼过的信息,噪声少。
注意:压缩这一步本身也可能丢信息。我的做法是保留"结论 + 出处指针",需要细节时再用工具按指针取回来,而不是把原文整个丢掉。
2.3 工具调用的三种失败模式
跑久了你会发现,Agent 出错基本集中在三类,而且这三类的排查方法完全不同。
第一类是选错工具。工具描述写得太像,模型分不清。比如同时有search_docs和query_knowledge_base,名字和描述都含糊,模型就随机挑一个。解决办法不是改模型,是改描述——把"什么时候用它、什么时候不要用它"写进去,加一两个反例,效果立竿见影。
第二类是参数填错。字段名记混、类型搞错、必填项漏填。这类最好治,把参数 schema 写严格,加上枚举约束和格式说明,再在服务端做一次校验,错了直接把错误信息返回给模型让它重试。
第三类是结果误读。工具明明返回了"未找到",模型理解成"找到了但是空的",然后基于空结果编了一段分析。这类最隐蔽,因为它不报错。我的处理办法是在工具返回里显式带上状态字段,比如{"status": "not_found", "hint": "该条件下无记录,请调整查询条件"},用明确的语义把模型往正确方向推。
3. MCP 的价值在"换个人也能接上",不在"让模型更聪明"
第一次接触 MCP 的人常有落差感:装好了,接上了,感觉也没比原来自己写适配强多少。这个感受是合理的,因为 MCP 解决的不是"能力"问题,是"复用和协作"问题。它的收益要在"多人、多工具、多客户端"的场景下才显出来。
3.1 没有统一协议时的 N×M 适配地狱
设想你有 3 个用到模型的内部应用(一个命令行工具、一个编辑器插件、一个内部网页),要接 5 个数据源(文件系统、数据库、内部接口、文档库、日志平台)。没有统一协议时,你得写 3×5=15 套适配代码,每套都要处理连接、鉴权、错误、超时。
有了协议之后,理想情况是每个数据源写一个服务端(5 个),每个应用实现一个客户端(3 个),总量从 15 降到 8,而且服务端可以独立部署、独立升级,甚至可以让别的团队来维护。这就是协议层的价值——把组合爆炸变成加法。
这个账在小项目里不划算,一个人写两个适配也没什么。但一旦工具数量上去、参与方变多,"谁改了什么导致我这边挂了"就会变成日常,这时候协议层的收益就体现出来了。
3.2 三个角色:宿主、客户端、服务端
MCP 的角色划分比较清楚,理解这三个词,读文档就不会迷路:
- 宿主应用:用户直接打交道的那个程序,负责和模型交互、渲染界面
- 客户端:宿主内部的一个组件,负责和某个服务端维持一条连接,转发请求
- 服务端:能力提供方,暴露工具、资源、提示模板,实际干活
一条连接对应一个服务端,宿主可以同时挂多个客户端。这个设计的好处是隔离——某个服务端挂了,其他连接不受影响,宿主只需要处理这一条连接的错误。
3.3 工具、资源、提示模板,三类能力别搞混
服务端能暴露的东西分三类,用途差别很大,混用会让结构变得很乱。
**工具(Tools)**是可执行的、可能有副作用的操作,比如写文件、发请求、提交表单。模型调用它是"请求执行",执行结果回填给模型。这类要特别小心权限,因为它真的会改变外部状态。
**资源(Resources)**是只读的数据,比如一个文件内容、一段配置、一份文档。它更像"给模型看的材料",而不是"让模型做的动作"。把只读查询做成资源而不是工具,能避免模型反复调用同一个查询浪费轮次。
**提示模板(Prompts)**是预设的指令片段,用于把一类常见任务的提示词固化下来,用户或宿主可以主动选用。它更像"快捷方式",不是模型自主调用的东西。
我见过不少实现把所有东西都塞进"工具",结果工具列表长得离谱,模型选择准确率直线下降。按这三类分清楚,工具列表能瘦一半。
3.4 一次工具调用的完整往返
把流程走一遍,出问题时就知道该在哪一环加日志:
- 宿主启动时连接各服务端,拉取工具清单(名称、描述、参数 schema)
- 宿主把清单按一定格式注入到发给模型的请求里
- 模型生成输出,其中包含"要调用某个工具、参数是这些"的结构化内容
- 宿主解析出来,通过对应客户端转发给服务端
- 服务端校验参数、执行、返回结果或错误
- 宿主把结果作为一条新消息追加到上下文
- 模型基于新上下文继续生成,可能再调工具,也可能输出最终答案
排查时按这个顺序看:清单拉到了吗?注入格式对吗?模型输出的结构符合预期吗?转发到正确的服务端了吗?服务端返回的是成功还是错误?错误信息有没有被正确回填?七个检查点,基本能覆盖九成的连接类问题。
4. Skill 解决的是"经验没法传递"这个问题
前面三个概念多少都有点"技术设施"的味道,Skill 更偏"知识管理"。它回答的是:这套活儿我们团队摸索了半年才摸顺,怎么让下一个接手的人(或者模型)不用再摸半年。
4.1 Skill 与提示词、工具函数的分工
这三者的关系经常被混淆,我用一个具体的例子说明。假设要做"根据一份需求文档生成接口设计草案"这件事:
工具函数是read_file、search_codebase、write_file,它们只负责读写,不知道业务。
提示词是"你是一个资深后端,请根据需求生成接口设计",它是一句话的指令,很轻,但每次都得重新想要说什么。
Skill 是"接口设计草案生成规程",里面写着:先读需求文档,提取出实体和动作;再检索现有代码里同类实体的命名约定;然后按团队模板输出路径、方法、入参、出参、错误码;输出后自查三件事(命名是否一致、是否有幂等要求、错误码是否复用了已有定义);如果需求里有歧义,先列问题清单而不是硬猜。
看出来了吧,Skill 里既有提示词,又有工具调用顺序,还有校验规则和失败处理。它是"把一类任务的完整做法写成文档",而不是"一句更长的指令"。
4.2 一个能用的 Skill 应该包含哪几块
写废过几个之后,我总结出一个能落地的 Skill 至少要有这几块,缺一块就容易在实际使用中翻车:
| 组成 | 作用 | 常见错误 |
|---|---|---|
| 名称与一句话描述 | 让调度方判断该不该加载 | 描述写得太泛,什么都能沾边 |
| 触发条件 | 明确什么情况下用、什么情况下别用 | 只写"什么时候用",不写"什么时候别用" |
| 前置检查 | 开工前确认输入齐不齐 | 缺了这项,跑到一半发现少材料 |
| 步骤序列 | 按顺序列出动作和对应的工具 | 步骤粒度太粗,等于没说 |
| 校验点 | 每步或关键节点怎么判断做对了 | 完全没有,错了也不知道 |
| 失败处理 | 出错、超时、结果为空时怎么办 | 只写顺利路径 |
| 输出规范 | 最终产物长什么样 | 每次输出格式都不一样 |
其中"什么时候别用"这一条最容易被忽略,但价值很高。一个 Skill 如果没有边界,模型会在任何沾边的场景下都加载它,然后强行套用不适用的流程,结果比不用还糟。
4.3 按需加载:为什么不能把所有 Skill 都塞进上下文
一开始我想得很美:把十几个 Skill 全写进系统提示,模型需要哪个自己挑。实际跑下来效果很差——上下文被占掉一大半,模型注意力被稀释,选错 Skill 的概率明显上升,而且每次改一个 Skill 都要动全局提示,维护成本极高。
后来改成两段式:系统提示里只放一份"技能目录",每个技能一行,写名称加一句话说明用途;模型判断需要某个技能时,调用一个元工具把该技能的完整内容读进来,再按里面的规程执行。这就是所谓的渐进式披露。
这个改动的收益很直接:系统提示从八千字降到八百字,响应速度上来了,选错的概率也下来了。代价是多一次工具调用往返,但这点开销完全值得。
提示:目录里的一句话描述要写成"做什么 + 什么场景",比如"生成接口设计草案,适用于已有需求文档、需要快速产出初稿的场景",而不是只写"接口设计"。描述的质量直接决定选择准确率。
5. 串成一个真实任务:四层各自站在哪个位置
概念讲完,最有说服力的还是把它们放进一个具体任务里看。我用一个自己练手时搭的小系统举例,场景是:拿到一批格式混乱的月度流水记录,清洗、核对、按维度汇总,最后生成一份带结论的简报。
5.1 逐层拆解:谁负责哪一段
先看 LLM 在这件事里干什么。它负责理解"按维度汇总"这句话里维度可能指哪些字段、负责读清洗后的数据并写出结论文本、负责在遇到歧义时提出澄清问题。它不负责算数——求和、去重、排序这些必须交给代码,因为模型算这些不可靠。
Agent 负责把整个流程串起来:先调工具列目录看有哪些文件,再调工具读文件并识别格式,判断格式是否需要特殊处理,调清洗工具,调校验工具核对总计,发现对不上就回头查哪一步出了问题,最后调写文件工具产出简报。这个"发现对不上就回头查"的回头动作,就是 Agent 存在的意义,纯脚本写不出来这么灵活的东西。
MCP 负责让这些工具以标准方式暴露出来。文件读取、表格处理、数据校验分别做成独立服务端,宿主那边只要实现一次客户端,以后接别的数据源也是同一套。这么做在单人小项目里显得绕,但当我后来想把这套东西接到另一个入口(比如一个网页界面)时,几乎没改服务端代码就接上了,那一刻才觉得这层没白加。
Skill 负责把"这类报表该怎么处理"的经验固化下来。比如清洗步骤里的几条硬规矩:日期列统一成 ISO 格式、金额列去掉千分位、空值分"缺失"和"零"两种处理、地区名做别名归一。这些是踩过坑才知道的,不写进 Skill,下次换个模型或者换个人,又得重新踩一遍。
5.2 抽掉任意一层,看看会退化成什么样
用反证法最能看清各层的必要性,我在做方案评审时经常这么干:
| 抽掉哪层 | 退化后的样子 | 具体表现 |
|---|---|---|
| 抽掉 LLM | 变成一个固定脚本 | 遇到格式变化就崩,没法处理"这句话什么意思"类的问题 |
| 抽掉 Agent | 变成一次问答 | 只能一次性给结果,中间发现问题无法补救,复杂任务直接放弃 |
| 抽掉 MCP | 每个数据源写死适配 | 能用,但换入口、换数据源要重写,工具多了维护成本爆炸 |
| 抽掉 Skill | 每次重新摸索 | 同一个坑反复踩,不同人做出来结果不一致,质量靠运气 |
这张表我自己留着当检查清单用。每次有人提"要不要上 Agent""要不要引入协议层",我就对照一下:抽掉它,系统会退化成什么样?如果退化不明显,说明这层是多余的,先别加。
5.3 一个容易搞反的因果关系
有个认知误区值得单独说:很多人以为顺序是"先有 LLM,再做 Agent,然后接 MCP,最后沉淀 Skill",好像必须按这个顺序搭。实际做下来,更合理的顺序往往是反的——先想清楚这类活儿该怎么干(Skill 的雏形),再看需要哪些能力(工具),再决定这些能力怎么暴露(要不要 MCP),最后才是编排(Agent)。
原因是,如果你连"这件事的正确做法"都没想清楚,直接上手写 Agent,写出来的就是一个不断试错的循环,跑得慢、烧钱、结果还不稳定。而先把操作规程写下来,你会发现很多步骤根本不需要模型判断,用代码就行,Agent 只需要处理剩下那几处真正需要判断的地方,整体结构立刻清爽。
我现在的习惯是:任何要上 Agent 的场景,先手写一份"如果是我手动做,步骤是什么"的清单。这份清单写不出来,说明业务逻辑还没理清,这时候写代码纯属浪费。
6. 动手搭最小骨架与几个绕不开的坑
理论说完,给一套能直接抄的最小结构。不用追求完整,先把四层各放一个最小实现跑通,比看十篇文档管用。
6.1 目录与文件组织
project/ host/ main.py # 主循环 tools_registry.py # 工具注册与调用分发 servers/ fs_server.py # 文件读写服务端 table_server.py # 表格处理服务端 skills/ monthly_report.md # 技能定义 skills_index.md # 技能目录(只放名称+一句话)这个结构的用意是把"能力"和"规程"物理隔开。servers里是可执行的代码,skills里是纯文本的规程,改规程不用碰代码,改代码不影响规程,两边可以独立迭代。
6.2 工具定义:描述比实现更费心思
{ "name": "read_table", "description": "读取指定路径的表格文件并返回前若干行预览与列名。适用于需要了解表格结构或抽样查看内容的场景;不适用于需要完整数据的统计计算,统计请改用 aggregate_table。", "input_schema": { "type": "object", "properties": { "path": { "type": "string", "description": "表格文件的绝对路径,必须以 .csv 或 .xlsx 结尾" }, "preview_rows": { "type": "integer", "description": "返回的预览行数,默认 5,最大 50", "minimum": 1, "maximum": 50 } }, "required": ["path"] } }这段里最值钱的是 description 里那半句"不适用于……请改用……"。加了这句之后,模型在需要统计时误调read_table的情况基本消失了。写工具描述的时候,把它当成写给一个聪明但完全不了解你项目的新人看,效果最好。
6.3 Skill 定义:把踩过的坑写进去
# 月度报表处理 ## 适用场景 已有原始流水文件,需要产出按地区与品类汇总的月度简报。 不适用于:实时查询、单条记录核对、跨月对比分析。 ## 前置检查 - 确认文件存在且可读 - 确认至少包含日期、金额、地区、品类四列 - 任一条件不满足,停止执行并列出缺失项 ## 步骤 1. 调用 read_table 获取列名与 5 行预览 2. 比对列名与预期字段,不一致时先做字段映射,映射结果写进简报附录 3. 调用 clean_table 执行清洗: - 日期统一为 YYYY-MM-DD - 金额去千分位、统一保留两位小数 - 地区名按别名表归一 - 空值区分「缺失」与「零」,缺失单独计数 4. 调用 aggregate_table 按地区、品类汇总 5. 调用 verify_total 校验:汇总总额与清洗后明细总额差值必须为 0 6. 差值不为 0 时,回到步骤 3 检查清洗规则,最多重试 2 次 7. 调用 write_file 输出简报 ## 输出规范 - 开头一段结论,不超过三句 - 中间一张汇总表 - 结尾列出数据质量问题(缺失数、异常值、字段映射情况) - 全文不出现原始文件路径这份 Skill 里有三处是纯经验:字段映射要写进附录(不然下次没人知道你怎么对的)、空值要分两种(混在一起会导致汇总偏差)、校验失败要限重试次数(不然会死循环烧钱)。这些内容写在代码注释里没人看,写在 Skill 里每次执行都会被读到。
6.4 主循环:把终止条件写死
MAX_STEPS = 20 MAX_RETRY_PER_TOOL = 2 def run(task, tools, skills_index): messages = build_initial_messages(task, skills_index) retry_counter = {} for step in range(MAX_STEPS): resp = call_model(messages, tools) if resp.type == "final": return resp.content if resp.type == "tool_call": name = resp.tool_name try: result = dispatch(name, resp.arguments) retry_counter.pop(name, None) except Exception as e: retry_counter[name] = retry_counter.get(name, 0) + 1 if retry_counter[name] > MAX_RETRY_PER_TOOL: result = {"status": "failed", "hint": "该工具连续失败,请换一种方式或直接说明无法完成"} else: result = {"status": "error", "message": str(e)} messages.append(tool_result_message(name, result)) continue return "无法继续:模型输出格式不符合预期" return "达到最大步数限制,任务未完成,已执行的步骤见日志"这段代码里有两个地方是血泪教训。一是MAX_STEPS,没有这个上限,模型有时会在两个工具之间反复横跳,一晚上烧掉的钱够买台显示器。二是工具失败时的 hint 文案,直接回{"status": "error"}模型会一直重试同一个错误调用,加上"请换一种方式"之后它会主动改策略。
6.5 调优时最先看的三个指标
跑通之后想优化,别一上来就换模型。先看这三个数:
平均步数。一个任务平均走多少轮。如果简单任务也要走十几轮,通常是工具粒度太细或者 Skill 写得不够明确,模型在反复试探。把几个高频连续调用的工具合并成一个粗粒度工具,步数往往能砍一半。
工具选择错误率。抽样看几十条日志,统计模型选的工具和"人工判断应该选的"是否一致。低于九成就要回头改描述,重点补"不适用于"这类负向说明。
重试率。哪个工具被重试最多,就去看它的报错信息是否足够明确。很多重试是因为错误信息只写了"参数错误",模型不知道该改哪个参数,只能瞎试。
这三个数不需要什么复杂工具,从日志里 grep 就能算出来。但它们指向的问题完全不同,先定位再动手,比盲目调提示词高效得多。
7. 几个绕不开的判断标准
写到这里,把我自己反复用到的几条判断标准列一下,都是踩过坑之后总结的,不是什么普适真理,但在我经手的几个小项目里挺管用。
能用确定代码解决的,不要交给模型。算数、字符串处理、格式转换、字段映射,这些代码写得又快又准。模型的价值在于处理模糊和例外,把它用在确定性的活儿上是浪费,还会引入不稳定性。
工具数量控制在十几个以内。超过这个量,选择准确率会明显下滑。如果确实需要很多工具,就按场景分组,每组做成一个独立服务端,按任务类型动态加载其中一组,而不是全部塞给模型。
Skill 宁可少而精。写十个半成品 Skill,不如写两个经过反复打磨的。判断标准很简单:这个 Skill 执行十次,输出质量的波动有多大?如果波动明显,说明流程里还有没想清楚的地方,别急着上生产。
给每一步留可观测的痕迹。每次工具调用的入参、出参、耗时都落盘。出问题时能一眼看出是模型选错了、参数填错了、还是工具本身挂了。没有日志的 Agent 就是个黑盒,调起来全靠玄学。
权限从最小开始。工具一旦带上写操作,就得考虑最坏情况。只读的工具放开用,写操作的工具加上路径白名单、操作确认、行数上限这些约束。我一般先全用只读版本跑通,确认流程没问题再逐步放开写权限。
最后分享一个我自己觉得挺有用的小习惯:每接一个新场景,先不写代码,手写一遍"如果我来做"的步骤清单,写完再看里面哪些步骤是纯机械的、哪些需要判断。机械的部分写成工具,判断的部分留给模型,中间用 Agent 串起来,工具之间用统一的方式暴露。这套流程走下来,方案基本就成型了,剩下的只是把它翻译成代码。