解析 Langchain deepagents 的 Skills 实现
type
status
date
slug
summary
tags
category
icon
password
AI summary
传统的 Agent 设计思路:把 Agent 可能需要的所有知识、工具说明、工作流程都写进 system prompt——在启动时一次性加载全部上下文。这种”静态预加载”模式忽视了一个关键事实:上下文窗口是一种具有递减收益边际的有限资源。
这种方式存在三个根本性问题:
- Token 膨胀与成本失控。一个功能丰富的 Agent 可能有几十甚至上百个 tool,每个 tool 的详细说明可能有几百 token。以 Manus 的实践数据为例,Agent 的平均输入与输出 token 比例约为 100:1,这意味着预填充阶段的成本占据绝对主导。更关键的是,工具定义在序列化后通常位于上下文的前部,任何变更都会使后续所有动作和观察的 KV 缓存失效——未缓存的 token 成本可能是缓存 token 的 10 倍。
- 上下文腐烂(Context Rot)。Chroma 团队的研究表明,模型性能下降受三个因素驱动:上下文长度增加导致注意力稀释、问题与关键信息的语义相似度降低、以及关键信息与周围干扰内容的语义相似度过高。这三个因素会相互放大。当 system prompt 过长时,精心设计的核心指令会被淹没在工具描述中,模型越来越难以准确匹配到真正相关的指令——即使技术上支持 128K 甚至 1M 的上下文窗口,最大上下文窗口 ≠ 最佳注意力窗口。
- 动态性与可维护性缺失。静态加载意味着想修改某个 tool 就得重新部署整个 Agent。
deepagents-cli 的解法来自一个简单的洞察:Agent 不需要一开始就知道所有事情,它只需要知道”有哪些事情可以知道”。
渐进式披露:Anthropic 的 Agent Skills 模式
这个设计模式来自 Anthropic 的工程实践(其实最早是 Manus 提出来的🤗),称之为 Progressive Disclosure。核心思想是:
- 启动时:只告诉 Agent “有一些 skills 可用”(名称 + 一句话描述)
- 运行时:当任务匹配某个 skill 时,Agent 主动读取完整说明
- 执行时:按照说明完成任务
这就像一个专业人士的工作方式:他知道自己有哪些参考手册,但只在需要时才翻开具体章节。

Claude Code 是闭源的,但最近 Langchain 推出了 deepagents-cli ,同时也更新了 Claude Code 的 skills 机制。So, 感谢开源,能够学习内部的逻辑实现,接下来让我引用代码来展示这个机制是如何实现的。
实现解剖:从目录结构到运行时
Skill 的物理结构
每个 skill 是一个独立的目录,必须包含一个
SKILL.md 文件:SKILL.md 的格式很关键。它用 YAML frontmatter 提供元数据,正文是详细的使用说明:这个设计的精妙之处在于:frontmatter 和正文都是给 Agent 读的,但在不同阶段按需加载。启动时 Agent 只读取 frontmatter(摘要层)来构建全局索引,知道”有哪些 skill 可用”;运行时当 Agent 判断某个 skill 相关时,才按需读取正文(核心层)来获取完整的使用说明。这正是渐进式披露(Progressive Disclosure)的核心——用元信息替代完整信息,按需加载而非预加载。

数据结构:SkillMetadata
这四个字段足够让 Agent 做出”是否需要这个 skill”的判断,同时
path 字段让 Agent 知道去哪里获取详细信息。加载逻辑:安全优先
具体的加载逻辑有几个值得注意的设计:
路径安全校验。在读取任何文件之前,先验证路径是否在合法目录内:
这防止了目录遍历攻击。比如一个恶意的 skill 目录包含指向
../../.ssh/id_rsa 的符号链接——这段代码会拒绝它。文件大小限制。防止 DoS 攻击:
两级覆盖机制。支持用户级和项目级 skills,项目级可以覆盖同名的用户级 skill:
这个设计让团队可以在项目中定制 skill,而不影响个人的全局配置。
Middleware:Agent 生命周期的钩子系统
要理解 skills 如何在运行时生效,需要先理解 middleware 机制。
什么是 Middleware?
Middleware 是一种在请求处理流程中插入自定义逻辑的模式。在 deepagents 中,middleware 可以:
- 在 Agent 执行前修改状态(
before_agent)
- 在每次 LLM 调用时修改请求/响应(
wrap_model_call)
- 在 Agent 执行后处理结果(
after_agent)
这不是 deepagents 发明的概念——Web 框架(Express、Django)、消息队列、数据库驱动都有类似机制。它的核心价值是解耦:让不同的关注点(skills、memory、human-in-the-loop)可以独立实现和组合。
SkillsMiddleware 的实现
两个关键方法:
before_agent():此钩子在 agent 开始执行之前运行。换句话说,它发生在代理的实际功能执行之前,用来对代理的初始状态进行准备。每一次 invoke 只会运行一次,也就是说,不管 Agent 后续的行为涉及到多少 model 或者 tool 调用,这个钩子只会在最初的那一刻触发一次,而不会在流程中重复运行。wrap_model_call():每次 LLM 调用时触发,注入 skills 信息到 system promptbefore_agent- 对单次
agent.invoke({...}):跑一次;
- 对同一个 thread 连续多次调
invoke:每次invoke又会再跑一遍 before_agent;
- 中途的
jump_to: "model"、jump_to: "tools"不会让before_agent再次触发(它们只会重跑before_model等)。
最终注入的内容大致呈现为:
注意这里只有名称、描述和路径——完整的 SKILL.md 内容并没有被加载。
Middleware 的组装
在创建 Agent 时,SkillsMiddleware 和其他 middleware 一起被注册:
运行时:Skills 在 Agent 生命周期中的完整运行机制
前面阐述了 middleware 的执行顺序和
wrap_model_call 的工作原理,现在来完整地追踪一个 skill 从”被发现”到”被使用”的全过程。理解这个过程很重要,因为它揭示了渐进式披露模式的精髓:Agent 不是一开始就知道如何使用 skill,而是在运行时逐步”学习”的。
Agent 生命周期概览
在深入细节之前,先看一下 Agent 处理一次用户请求的完整生命周期:
注意 Phase 2 中的”Agent 循环”——这是 Agent 的核心工作模式。Agent 不是一次性完成任务的,而是通过多轮 LLM 调用和工具调用来逐步推进。每一轮 LLM 调用都会触发
wrap_model_call,这意味着 skills 列表会被反复注入到 system prompt 中。模拟示例:搜索 deep learning 论文
让我们用一个具体的例子来追踪整个过程。用户输入:“帮我搜索一下 deep learning 相关的论文”
Phase 1: 初始化阶段
当用户按下回车的那一刻,Agent 开始处理这个请求。首先,所有 middleware 的
before_agent() 方法会按顺序执行。对于 SkillsMiddleware 来说,这是它”发现” skills 的时刻:这个方法做了什么?它调用
list_skills() 函数,扫描用户级和项目级的 skills 目录。对于每个找到的 skill 目录,它会:- 检查目录下是否存在
SKILL.md文件
- 验证路径安全性(防止目录遍历攻击)
- 读取
SKILL.md的前几行,解析 YAML frontmatter
- 提取
name和description字段
假设用户的 skills 目录下有
arxiv-search、web-research、langgraph-docs 三个 skill,扫描完成后,state 中会存储这样的数据:Note,此时 Agent 只知道这些 skill 的名称和一句话描述,完整的
SKILL.md 内容还没有被读取。这就是”渐进式披露”的第一步:让 Agent 知道有哪些能力可用,但不告诉它具体怎么用。Phase 2: Agent 循环
初始化完成后,Agent 进入核心的工作循环。这个循环会持续进行,直到 Agent 认为任务已经完成。
第一轮 LLM 调用:分析请求,决定使用 skill
在调用 LLM 之前,
wrap_model_call() 会被触发。SkillsMiddleware 在这里把 skills 列表注入到 system prompt:注入后,LLM 看到的 system prompt 末尾会多出这样一段:
现在 LLM 收到了用户的请求”帮我搜索一下 deep learning 相关的论文”,同时它也看到了可用的 skills 列表。LLM 的推理过程大概是这样的:
用户想搜索论文… 让我看看有什么 skill 可以帮忙…arxiv-search: “Search arXiv preprint repository for papers…” —— 这个看起来很匹配!按照 Skills System 的指引,我应该先读取这个 skill 的完整说明。
于是 LLM 决定调用
read_file 工具:Agent 执行
read_file 工具,读取 arxiv-search/SKILL.md 的完整内容。这个文件可能有几百行,包含:这是渐进式披露的第二步:当 Agent 确定需要某个 skill 时,才读取完整的使用说明。
第二轮 LLM 调用:根据 SKILL.md 执行任务
现在 Agent 已经读取了完整的 SKILL.md,它知道了:
- 这个 skill 适用于什么场景
- 如何调用 Python 脚本
- 需要传递什么参数
- 有哪些示例可以参考
LLM 根据这些信息,决定调用 shell 工具执行搜索:
Agent 执行 shell 命令,运行
arxiv_search.py 脚本。这个脚本会:- 连接 arXiv API
- 搜索包含 “deep learning” 的论文
- 返回前 5 篇论文的标题和摘要
脚本输出可能是这样的:
第三轮 LLM 调用:整理结果,回复用户
Agent 收到脚本的输出后,需要把这些原始数据整理成用户友好的回复。LLM 会分析搜索结果,提取关键信息,然后生成一个结构化的回复:
Phase 3: 返回阶段
任务完成后,所有 middleware 的
after_agent() 方法会依次执行。对于 SkillsMiddleware 来说,这个阶段通常不需要做什么特殊处理。但其他 middleware 可能会在这里做一些整理工作,比如保存日志、更新缓存等。如果用户问的是其他问题?
假设用户问的是”LangGraph 怎么实现状态管理?“,整个流程会有所不同:
- Phase 1 同样会扫描所有 skills,得到相同的
skills_metadata
- 第一轮 LLM 调用时,LLM 会发现
langgraph-docsskill 更匹配这个问题
- Agent 会读取
langgraph-docs/SKILL.md而不是arxiv-search/SKILL.md
- 后续的执行会按照
langgraph-docsskill 的指引进行
如果用户问的是”1+1等于几?“,LLM 可能会判断没有任何 skill 适用于这个简单问题,直接回答而不读取任何 SKILL.md。
这就是渐进式披露的威力:Agent 只在需要时才获取知识,不需要时完全不加载。
为什么这个设计有效?
回顾整个过程,我们可以看到几个关键的设计决策:
1. 元数据与详情分离
在 Phase 1,Agent 只加载了 skill 的名称和描述。完整的 SKILL.md 只在真正需要时才被读取。如果用户问的是”今天天气怎么样”,
arxiv-search 的 SKILL.md 根本不会被加载。2. LLM 自主决策
Agent 不是被动地执行预设的规则,而是由 LLM 自主判断是否需要使用 skill。这种设计让 Agent 能够灵活应对各种请求,而不需要为每种情况编写硬编码的逻辑。
3. 自然语言作为接口
SKILL.md 是用自然语言写的,LLM 可以直接理解并执行。这比传统的 API 文档或配置文件更灵活——Agent 可以根据具体情况调整执行方式,甚至在遇到问题时自主尝试其他方法。
4. 多轮对话的必要性
整个过程涉及多轮 LLM 调用:第一轮决定使用哪个 skill,第二轮根据 skill 说明执行任务,第三轮整理结果。这种多轮模式是 Agent 能够处理复杂任务的关键。
这个模式的普适性
Skills 机制的核心洞察 —— 延迟加载 + 按需获取——其实是一个通用的系统设计原则。
在操作系统中,这叫虚拟内存:程序不需要一开始就把所有代码加载到内存,用到哪页加载哪页。
在 Web 开发中,这叫懒加载:页面不需要一开始就加载所有资源,滚动到哪里加载哪里。
在知识管理中,这叫渐进式学习:你不需要先学完所有知识才能开始工作,遇到问题时再深入学习。
deepagents-cli 把这个原则应用到了 AI Agent 的能力管理上。随着 Agent 能力越来越丰富,这种”知道自己不知道什么,但知道去哪里找”的设计会越来越重要。
Have a try~
如果你想体验这个机制,可以:
- 安装 deepagents-cli:
pip install deepagents-cli
- 创建一个简单的 skill:
- 写一个 SKILL.md:
- 运行
deepagents,然后问一个匹配你 skill 描述的问题
观察 Agent 是如何发现并使用你的 skill 的。
Skills 机制展示了一个重要的设计理念:好的抽象不是隐藏复杂性,而是让复杂性在正确的时机出现。Agent 不需要一开始就知道所有事情,它只需要知道如何在需要时获取知识,这可能是构建真正可扩展的 AI Agent 系统的关键。
参考
Loading...