一个 Agent 接上 20 个工具时,团队通常会问,模型会不会选错工具。

等工具长到 200 个、2000 个,真正先爆掉的,往往不是工具选择,而是上下文。

10 月 8 日,LangChain 重构 Deep Agents 的 Skills 支持,给了三个很工程化的动作。工具可以绑定到某项技能,只在读取该技能时进入上下文。用户可以显式固定某项技能,在第一轮模型调用前装入。长线程也可以让技能元数据失效后重新加载。这不是一次 API 小改动,它把一个常被忽略的问题摊到了台面上,Agent 的能力库正在变成一个需要调度的运行时。

我一直觉得,很多 Agent 项目死得很冤。

团队花了几周接 MCP、补工具、整理 SOP,最后把一大段工具定义、权限说明、示例参数、业务规则,一次性塞进 system prompt。演示时它很全能。进生产后,模型开始把找订单的工具和退款工具混在一起,明明只要查一张表,却先读了十几页无关说明。有人会把它归因于模型不够聪明。坦率地讲,很多时候是我们把一个有目录的仓库,硬塞成了一页目录页。

这篇文章想聊的不是某个框架的用法,而是一个更实际的问题。技能库越来越大时,Agent 应该怎样知道自己有什么能力,又怎样避免因为知道得太多反而干不好一件小事。

先别急着加工具,看看上下文里到底挤了什么

传统工具调用有一个很自然的写法。

1
2
3
4
system prompt
+ 全部工具名称、描述、参数 schema
+ 全部业务规则
+ 用户消息

工具少的时候,这套结构没有什么问题。一个天气查询、一个知识库检索、一个发消息接口,放在一起也不会造成多大负担。模型甚至能从完整说明里学到工具之间的边界。

问题出在规模跨过一个临界点以后。

假设一个企业 Agent 需要覆盖会议准备、CRM 查询、合同审阅、报价生成、工单处理、代码发布、数据分析。每个领域又有十几个工具,每个工具的 JSON Schema、错误处理约束和安全提示都不短。还没开始推理,首轮上下文已经被操作手册占了大半。

这会带来三笔账。

第一笔是 token 账。每轮都携带一遍工具说明,长会话里重复付费,缓存能缓解,却不能让模型不读它们。更麻烦的是,真正有价值的用户上下文和任务证据被挤到后面。你以为是在给 Agent 增加能力,实际是在稀释它对当前任务的注意力。

第二笔是选择账。模型不是一个可以精确索引的函数调用器。几十个名字相近的工具同时出现时,工具描述本身会互相干扰。search_customer、get_customer、lookup_account 看起来都合理,模型却得先理解组织内部的命名历史。工具越多,歧义不是线性增加的。

第三笔是治理账。把所有工具默认暴露给同一个 Agent,权限面会悄悄变大。一个只该做会议纪要的任务,理论上也看得到发布、退款、删除类工具。即便模型没调用,审计和风险评估也已经变难了。

很多朋友可能会说,做一层 tool router 不就行了。

可以,但 router 只能回答「这一次选哪个工具」。它没有解决「哪些知识、哪些操作边界该进入这次工作区」。如果每次路由前仍要把全部工具描述给一个模型看,那只是把拥挤从执行层挪到了路由层。

Skills 不是工具包,它更像按需挂载的工作目录

LangChain 这次的设计里,最有价值的一点是工具可绑定到技能,只有技能被读取后,相关工具才会加载。这个变化听着朴素,背后却是一次职责拆分。

Tool 是原子能力。它完成一个明确动作,比如查询 Jira、创建分支、读取数据库。

Skill 则是一段带场景的工作说明。它不只告诉模型能调用什么,还应该交代什么时候用、先后顺序、输入输出约定、风险边界、失败后如何收尾。比如「会前准备」这个 Skill 可以包含日程读取、参与人背景查询、最近项目文档检索、会议摘要模板。它下面可以挂多个 Tool,但对 Agent 暴露的是一个有业务语义的入口。

这层抽象很像后端服务里的模块边界。

在 Java 或 Spring 项目里,我们不会让一个 Controller 直接看见所有 DAO、所有 MQ producer、所有第三方 SDK。否则任何一次改动都可能跨过本不该跨的边界。我们会按领域拆 Service,借由接口控制依赖,再用权限和事务约束副作用。Agent 的 Skill 也该承担类似角色。

它不是给 prompt 起一个好听的文件夹名。

它应该是能力发现、上下文装载和权限收敛的共同边界。

把它想成 Linux 的挂载点可能更直观。机器上可以有很多磁盘,但一个进程不需要把所有目录都映射到自己眼前。任务进入「会议准备」后,再挂载会议日历、CRM 和文档检索这块空间。它完成工作,别的领域依旧保持隔离。这样做不只为了省 token,更为了让任务的可见世界保持小而清楚。

懒加载解决了什么,又没有解决什么

只在读取 Skill 时加载工具,可以直接改善首轮上下文的体积和噪声。技能库从几十项扩展到数千项时,这个收益会越来越明显。它也给了组织一种更健康的维护方式,业务团队维护自己的 Skill,平台团队维护加载协议、权限模型和可观测性,而不是每次新增能力都去改一份巨大的系统提示词。

但别把懒加载当成万能药。

第一个仍然存在的问题是发现。Agent 不知道有某个 Skill,就不会读取它。于是系统还需要一个足够短、足够稳定的技能目录。目录里不该塞完整操作指南,只需要名称、适用场景、风险等级、触发词和少量示例。它的目标不是替代技能正文,而是让模型能做出「我该去翻哪本手册」的判断。

这很像 IDE 的代码导航。你不会把整个仓库都打开给开发者看,但至少得让他能通过包名、符号和搜索结果找到入口。Skill catalog 就是 Agent 的代码索引。

第二个问题是加载后的边界。一个 Skill 被读取,不等于该任务就能无条件执行所有动作。读取「生产发布」流程,和获得生产发布权限,是两回事。真正的系统至少要把三件事分开。

  • 发现权限,Agent 是否能看到这个 Skill 的存在
  • 解释权限,Agent 是否能读取它的执行说明
  • 执行权限,当前用户、环境和审批状态是否允许调用对应 Tool

少了这层区分,所谓 Skill marketplace 很容易变成一个更漂亮的权限黑洞。尤其是在企业内网里,技能文本本身也可能泄露业务流程、客户字段或内部地址。把 Skill 当文档管理,而不是安全对象管理,后面一定会补课。

第三个问题是版本。技能不是静态 prompt。CRM 字段会改,审批链路会改,工具 API 会升级,甚至同一个工具在不同租户的行为都可能不同。长线程里,如果 Agent 一直持有旧技能定义,它有可能用过期流程修改新系统。LangChain 提到通过将 skills_metadata 设为 None 重载新增或变更的技能,正是在处理这个现实。

这里有个容易被低估的细节。重载不是简单刷新缓存。

如果一次任务已经根据旧版 Skill 规划了五步,第三步才发现规则变了,系统需要留下版本证据。它要能回答,当时读取的是哪个版本,已执行了哪些副作用,接下来是继续、回滚,还是请求人工确认。没有这些证据,线上事故发生时,日志里只会写着「Agent 调用了工具」,没人知道它为什么这么做。

固定技能,是给关键任务一份明确的合同

这次更新里的第二个动作是用户可以通过类似 /meeting-prep 的显式请求固定技能,使其在第一次模型调用前加载。

我挺喜欢这个设计,因为它承认了一件事,不是所有任务都该让模型猜。

用户说「帮我准备明天和客户的会」,系统可以让 Agent 自主判断是否需要会议准备 Skill。用户说「/meeting-prep 帮我准备明天和客户的会」,则是在表达更强的意图。这时平台不该装作完全自动化,而应该把它当作一个可审计的契约。任务从哪个 Skill 起步,遵循哪套流程,哪些工具会被考虑,都有了明确起点。

这对高风险场景尤其重要。

以发布服务为例,纯自然语言的「把这个功能上线」太含糊了。它可能指开发环境,也可能是灰度,还可能是生产全量。一个显式的 /prod-release 不会自动消除风险,但它至少可以触发固定的预检流程,比如读取变更单、确认目标环境、检查回滚预案、要求审批令牌。系统终于有了一个稳定的治理钩子。

不是说自由对话不好。探索性任务里,Agent 自己发现和加载 Skill 的体验更顺滑。只是到了有副作用的任务,确定性比「看起来很聪明」更值钱。

你想想看,CI/CD 里不会因为开发者写了一句「看起来可以」就直接部署生产。那为什么 Agent 的外部操作要靠一句模糊自然语言放行。

一个能落地的 Skill 架构,不该只有 Markdown

如果正在做 LangGraph、Spring AI 或自研 Agent 平台,我建议先别从「建一个技能库」开始。这个说法太空,最后经常长成一堆没人维护的 Markdown。

可以从一份可执行的 Skill manifest 开始。它不需要复杂,关键是把运行时真正要判断的东西写出来。

1
2
3
4
5
6
7
8
9
10
11
12
name: meeting-prep
version: 1.4.0
intent:
- 准备客户会议
- 汇总会前信息
entry_tools:
- calendar.search
- crm.account_summary
- docs.recent_search
risk: read-only
reload_policy: session-on-change
owner: sales-ops

这个 manifest 的意义不在 YAML,而在后续可以围绕它建立机制。

平台可以根据 intent 建技能索引,根据 risk 决定是否需要审批,根据 entry_tools 在加载时授予最小能力集,根据 version 把每一步执行关联到具体定义。owner 则解决了另一个常被跳过的问题,出问题到底找谁维护。

在运行时,我会把链路拆成四层。

目录层 只输出轻量元数据。Agent 先从这里判断候选 Skill,不加载长指令,不注入全部工具 schema。

装载层 根据任务、用户显式选择和策略引擎拉取 Skill 正文,解析依赖,生成本轮可见的 Tool 集。这里需要做缓存,但缓存键不能只有 Skill 名,还要包含版本、租户和权限上下文。

执行层 每次 Tool 调用都经过独立的 policy check。模型在 prompt 中读到了工具,不代表网关就该放行。对于写操作,网关应要求任务 ID、Skill 版本、用户主体和审批状态。它看起来有点啰嗦,但这是把 Agent 从玩具推到生产的必修课。

证据层 记录技能何时被发现、何时加载、使用了哪个版本、模型选择了什么工具、工具返回什么摘要、是否发生了重载。注意记录摘要和引用,不要把客户原始数据无差别塞进日志。可观测性和数据最小化要一起做。

这四层里,模型只是中间的决策组件。真正决定系统能不能被长期维护的,是模型前后的边界。

别追求一个万能 Agent,追求可收敛的任务世界

大而全的能力清单很容易让人兴奋。你会觉得 Agent 今天能写代码、明天能查库存、后天能做财报,离「数字员工」只差一步。

我自己的感受恰好相反。能力越多,越要让每次任务可见的世界变小。

一个好的 Skill 系统不是让 Agent 随时拥有一万把钥匙,而是让它在开一扇门时,只拿到那扇门需要的钥匙。这样模型的判断更稳定,权限审计更清楚,团队也能把一项业务流程当作独立资产去测试和迭代。

LangChain 这次围绕绑定工具、显式固定和线程内重载的改动,真正值得抄的不是几个字段名。它提醒了我们,Agent 的扩展方式不该是无限加 prompt,而应当像软件系统一样管理模块、生命周期和版本。

下一次有人提议「把所有工具都给 Agent 吧,它会自己选」,你可以先问一句。

它准备解决的这件事,到底需要看见多少世界。

给正在扩展 Agent 能力库的团队一张检查表

  • 技能目录是否只含发现所需的短元数据,而非完整操作手册
  • 每个 Skill 是否有 owner、版本、适用意图和风险分级
  • Tool 是否在读取对应 Skill 后才注入,而非全局常驻
  • 读取 Skill、看见 Tool、执行 Tool 是否有三套独立授权
  • 长任务遇到 Skill 更新时,是否能识别版本漂移并留下重载证据
  • 高风险 Skill 是否支持显式固定,并把预检和审批接到固定入口
  • 日志能否关联任务、Skill 版本、Tool 调用和最终副作用
  • 是否有 Skill 级别的离线测试,而不是只测模型最后一句回复

先把这八个问题答清楚,再谈把能力库扩到几千项。

那时候,工具数量才会变成资产,而不是上下文里越来越贵的一团噪声。

参考资料