跳到主要内容

为 Web 环境 Agent 设计 Claude Skills 扩展系统

发布于 2026-01-31 23:40:0011 分钟阅读
不如怀念
Web 前端工程师 (Web Front-end Engineer)

一些基于 Vercel AI SDK 将 Claude Skills 扩展架构移植到 Web 环境的 Agent 开发实践经验。Claude Skills 提供了比 MCP 更灵活的 Agent 扩展机制,但官方实现依赖本地文件系统等环境。如何让它在浏览器里跑起来?

Agent 的插件机制​

Claude Skills 是一种扩展 Agent 能力的插件机制。类似的机制在其他编码工具中也有实现,如 Gemini CLI 的扩展系统、Codex CLI 的工具集等。但 Claude Skills 在近期获得了更高的关注度,这与 Claude 在编码领域的领先表现密切相关。

相比传统的 MCP(Model Context Protocol),Claude Skills 的核心优势在于其渐进式披露策略。MCP 将所有 Tools 的描述一次性注入上下文,当 Tool 数量增加时,容易导致上下文窗口溢出。而 Claude Skills 采用分层暴露机制:模型首先仅获知有哪些 Skill 可用,仅在明确调用时才加载对应 Skill 的 Skill.md。这种按需加载的模式有效缓解了上下文爆炸问题。

然而,Claude Skills 的官方实现深度依赖本地文件系统(通过文件目录组织 Skill 模块),这天然限制了其在 Web 环境的适用性。浏览器缺乏直接访问本地文件系统的能力,也无法通过文件路径解析模块结构。因此,将 Claude Skills 架构迁移到 Web 环境需要重新设计存储层和模块加载机制。

下文将基于 Vercel AI SDK 的实践,探讨如何在浏览器环境中实现一套类似 Claude Skills 规范的扩展系统。

Claude Skills 的环境依赖​

理解 Claude Skills 的环境依赖,需要先看清楚一个 Skill 的完整结构。根据官方规范,一个典型的 Skill 文件组织如下:

my-skill/
├── SKILL.md # 核心文件(包含 YAML frontmatter + 指令内容)
├── scripts/ # 可执行脚本(Python、Bash 等)
│ ├── init_skill.py
│ └── validate.py
├── references/ # 参考文档
│ └── api-reference.md
└── assets/ # 模板和资源文件
└── template.json

Claude Skills 架构对运行环境有两项核心依赖:

1. 文件系统

SKILL.md 中会引用 scripts/、references/、assets/ 目录下的文件。例如:

python {baseDir}/scripts/init_skill.py

这种引用机制要求底层提供文件系统能力,能够按路径定位并读取这些分散的文件。没有文件系统,Skill 无法加载其依赖的脚本和资源。

2. 代码执行环境

scripts/ 目录包含的可执行脚本需要运行时环境。Claude Code 的实现依赖 Bash tool 来执行这些脚本,提供:

  • 进程隔离能力
  • 受限的权限控制(通过 allowed-tools 配置)
  • 标准化的脚本执行接口

Web 浏览器天然缺乏这两类能力:无法直接操作本地文件系统,也没有类似 Node.js 的进程模型来执行外部脚本。这意味着将 Claude Skills 迁移到 Web 环境时,必须重新设计存储层(替代文件系统)和执行层(替代沙箱环境)。

Web 环境的替代方案​

恰好 Vercel 近期发布了一篇博文,介绍了为 Agent 提供 bash 和 file 工具将大大增强其能力。其背后的逻辑是:LLM 在预训练阶段已经对命令行操作和文件系统操作建立了深刻的理解,这些是最基础、最普遍的计算机交互范式。

基于这个思路,Vercel 为 AI SDK 构建并发布了 bash-tool。但实测后发现,这些工具依赖 Node.js 运行时,无法在浏览器环境中直接使用。不过这提供了很好的思路:我们可以在 Web 环境中实现虚拟的 bash 和虚拟文件系统。

  • 虚拟文件系统:使用 IndexedDB 或 Memory Storage 模拟文件树结构,提供类似 readFile、writeFile、glob、grep 等文件操作接口。这样 SKILL.md 中的 {baseDir} 引用就可以映射到虚拟路径上。
  • 虚拟 Bash:实现一个支持常用命令(ls、cat、mkdir、rm 等)的轻量级命令解析器,将命令映射到虚拟文件系统的操作上。复杂的脚本逻辑则可以用 JavaScript/TypeScript 在沙箱中执行。

另外,Claude Skills 中 scripts/ 目录的本质——工程代码实现,其实与 Vercel AI SDK 的 Tool 概念是一致的。Tool 就是一个可调用的函数,接收参数、执行逻辑、返回结果。因此,我们可以直接用 AI SDK 的 Tool 机制来替代 scripts 的概念:每个 Skill 注册一组 Tools,当 Skill 被调用时,这些 Tools 对模型可用。

这样,存储层(虚拟文件系统)+ 执行层(Tool 机制)的组合,基本上就解决了在 Web 环境中实现类似 Claude Skills 插件机制的核心依赖问题。

插件机制设计​

Vercel AI SDK 提供了非常灵活的基础能力:ToolLoopAgent 支持 agent loop,tools 参数支持外部工具注册。这些能力实际上构成了 Agent 设计的核心模块——无论是早期的 Function Call、后来的 MCP,还是现在的 Claude Skills,本质上都是 LLM 的模型服务调用循环和外部工具调用的组合。

Skills 作为元工具​

实现一个支持 Skills 的 Agent,核心思路是将 Skills 抽象为一个特殊的 Tool,不妨称之为 Skill Tool。这个 Tool 的职责是为 Agent 提供 Skills 的动态发现和激活能力:

1. 动态发现

Skill Tool 的描述(description)需要包含所有已注册 Skills 的名称和描述。当模型收到用户输入时,会先看到这个 Skill Tool 的描述,从而了解有哪些 Skills 可用。

2. 激活机制

Skill Tool 接受一个参数 skill_name,当模型决定调用某个 Skill 时,会触发 Skill Tool 并传入对应的名称。Skill Tool 根据名称查找并返回:

  • SKILL.md 的内容(注入到上下文中)
  • 该 Skill 关联的 Tools 列表
  • 可选的 references 和 assets

3. 动态工具注册

这里有一个关键难点:Skills 被激活后,其对应的 Tools 需要动态加入到 Agent 的可用工具集中。 幸运的是,AI SDK 已经提供了动态工具切换的能力(activeTools 配置),让我们可以根据 Skills 的激活状态实时调整可用工具列表。

Skill Registry 设计​

为了支持上述机制,我们需要实现一个 Skill Registry(Skill 注册中心),其核心职责包括:

  • Skill 注册:支持预先注册(built-in skills)和动态注册(user-added skills)
  • Skill 查询:根据名称查找 Skill 的元数据和资源
  • 状态管理:维护每个 Skill 的激活状态(active/inactive)
  • 工具聚合:根据当前激活的 Skills,返回合并后的可用工具列表
interface SkillRegistry {
// 注册 Skill
register(skill: Skill): void;

// 激活 Skill,返回 SKILL.md
activate(skillName: string): Skill;

// 获取所有可用 Tools Id
getAvailableTools(): string[];

// 获取所有 Skills 的 Tools
getAllSkillsToolSets(): Toolset;

// 生成 Skill Tool 的描述(用于模型发现)
getSkillToolDescription(): string;
}

调用流程​

  1. 初始化:所有 Skills 注册到 Skill Registry,但只有 Skill Tool 暴露给模型
  2. 发现阶段:模型通过 Skill Tool 描述了解有哪些 Skills 可用
  3. 激活阶段:Agent 请求触发 Skill Tool,传入 skill_name,Skill 被激活,其 Tools 加入可用列表
  4. 执行阶段:模型使用激活 Skills 提供的 Tools 完成任务

这种设计保留了 Claude Skills 渐进式披露的核心理念,同时适配了 AI SDK 的 Tool 机制。

动态工具切换实现​

AI SDK 提供了 prepareStep hook,可以在 Agent loop 的每个步骤中动态调整激活的工具集。这正好满足 Skills 激活后需要动态扩展可用工具的需求:

import { streamText } from 'ai';

const result = ToolLoopAgent({
model: "anthropic/claude-sonnet-4.5",
messages: conversationHistory,
tools: {
// 基础工具始终可用
...BASE_TOOLS,
// Skill Tool 用于发现和激活 Skills
skill: skillTool,
// 所有 Skills 的 Tools 都注册在这里,但通过 activeTools 控制可见性
...skillRegistry.getAllSkillsToolSets(),
},
// 动态控制哪些工具对模型可见
prepareStep: (...args) => {
return {
activeTools: [
// 基础工具始终激活
...Object.keys(BASE_ACTIVE_TOOLS),
// 根据当前激活的 Skills 动态添加其 Tools
...skillRegistry.getAvailableTools(),
],
};
},
});

在这个实现中:

  • 注册阶段:所有可能的 Tools(包括所有 Skills 的 Tools)都注册到 tools 参数中
  • 控制阶段:prepareStep 在每次模型调用前执行,根据当前激活的 Skills 返回 activeTools 列表
  • 隔离阶段:未激活 Skills 的 Tools 对模型不可见,避免上下文污染

这正是 Skills 渐进式披露策略的技术实现——模型只能看到已激活 Skills 的工具,而非全部。

整体架构​

  1. 初始化阶段:Skills 从存储中加载,注册到 Skill Registry,VFS 完成初始化
  2. 技能发现阶段:Agent 理解用户需求,通过 Skill Tool 发现并激活特定 Skill,获取该 Skill 的指令(SKILL.md)、参考文档(references/)和工具集
  3. 执行阶段:模型阅读 Skill 指令和参考文档,通过虚拟 Bash 操作虚拟文件系统,并调用相关 Tools 完成具体任务
  4. 响应阶段:执行结果返回给模型,对话继续进行,或根据上下文激活其他 Skills

上下文管理的必要性​

当然,仅为 Agent 引入 Skills 插件扩展能力还不够,上下文管理同样关键。SKILL.md 中可能包含大量的指令、参考文档和示例代码,一个复杂的 Skill 完全可能占据数万字的上下文空间。如果没有有效的上下文管理策略,激活几个 Skills 就会导致上下文溢出。

在 Web 环境中,可以考虑以下策略来缓解这一问题:

  • Token 预算控制:在激活 Skill 前估算其大致的 Token 占用,确保总上下文在安全范围内
  • 动态卸载:当某个 Skill 的任务完成后,及时将其从激活状态移除,释放上下文空间
  • 内容摘要:对于大型参考文档,可以先提取关键摘要加载到上下文中,而非全文注入
  • 对话压缩:当对话历史过长时,对早期消息进行摘要压缩,保留关键信息的同时节省空间

这些策略与渐进式披露相结合,才能在 Web 环境中真正发挥 Skills 架构的优势。

结语​

至此,在 Web 环境中为 Agent 添加类似 Claude Skills 的扩展能力基本实现。通过虚拟文件系统替代本地文件系统,通过 AI SDK Tool 机制替代脚本执行,我们成功将原本依赖本地环境的 Skills 架构迁移到了浏览器中。

这个过程也让我们对插件模式有了更深的理解:Skills 本质上解决的是能力边界的扩展问题——在不修改核心系统的前提下,动态地为 Agent 注入新的知识和工具。同时,这也引发了对 LLM 服务如何与外部资源交互的思考:从最初的 Function Call 到 MCP,再到现在的 Skills,本质上都是在探索如何让模型更安全、更高效地调用外部能力。

Web 环境的 Agent 开发仍在快速演进中,期待更多类似的架构创新能够打破环境限制,让强大的 AI 能力在各种场景下都能发挥作用。

参考​