首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >千万级skill会像mcp和function call一样因上下文爆炸而选错吗?翻了翻OpenClaw的技能加载机制,答案是:不会。但引入了另一个问题。。。

千万级skill会像mcp和function call一样因上下文爆炸而选错吗?翻了翻OpenClaw的技能加载机制,答案是:不会。但引入了另一个问题。。。

作者头像
烟雨平生
发布2026-04-21 14:27:43
发布2026-04-21 14:27:43
1740
举报

先说结论:LLM不会选错,但OpenClaw可能选不到你认为最最最最匹配的那一个。从结果上看,仍然是选错了skill,但不是LLM的锅。

这周在clawhub上看到一个有意思的讨论:如果我的OpenClaw本地有1000万个skill,会不会因为上下文过载,导致AI选错skill?

直觉上确实有可能:1000万个skill的description塞进提示词,token数早就爆炸了。

但OpenClaw的实际实现,和你想的不太一样。

核心答案:不会"选错",但会"漏掉"——解决方案并不彻底

先说结论:OpenClaw不会因为上下文过载而"选错"skill,但可能会因为截断而"看不到"某些skill。

更关键的是:OpenClaw的解决方案并不彻底,它假设skill数量不会太多。

这是两个不同的概念:

"选错":AI看到了skill A和skill B,两个都匹配,结果选了不太合适的那个。

"看不到":AI压根没看到skill C,因为它在截断时被丢掉了。虽然从结果上看是选错了,但不是LLM的上下文爆炸而选错。。。

源码解析:Skill加载的完整流程

本文基于 OpenClaw 2026.4.16 版本分析

▪ 1. 没有基于用户问题的筛选

很多人以为OpenClaw会用向量数据库或RAG来检索skill,但实际代码里根本没有这回事。

src/agents/skills/workspace.ts 的核心逻辑:

// 1. 加载skill时,从多个来源收集所有skill const skillEntries = loadSkillEntries(workspaceDir, opts); // 2. 前置筛选:过滤掉不符合运行条件的skill const eligible = filterSkillEntries( skillEntries, opts?.config, effectiveSkillFilter, opts?.eligibility, ); // 3. 按字母排序 const promptSkills = compactSkillPaths(resolvedSkills) .slice() .sort((a, b) => a.name.localeCompare(b.name, "en")); // 4. 应用Token限制 const { skillsForPrompt, truncated, compact } = applySkillsPromptLimits({ skills: promptSkills, config: opts?.config, agentId: opts?.agentId, }); // 5. 格式化成XML,塞进提示词 const prompt = formatSkillsForPrompt(skillsForPrompt);

关键发现:

  • 没有embedding
  • 没有similarity计算
  • 没有top-k检索
  • 没有基于用户问题的筛选

就是前置筛选 → 字母排序 → Token限制 → 全部格式化 → 塞给LLM

▪ 2. 前置筛选:只过滤运行条件,不匹配用户问题

filterSkillEntries 的核心是 shouldIncludeSkill

export function shouldIncludeSkill(params: { entry: SkillEntry; config?: OpenClawConfig; eligibility?: SkillEligibilityContext; }): boolean { // 1. 检查skill是否被显式禁用 if (skillConfig?.enabled === false) { return false; } // 2. 检查bundled skill是否在白名单中 if (!isBundledSkillAllowed(entry, allowBundled)) { return false; } // 3. 运行时环境检查(最关键的筛选) return evaluateRuntimeEligibility({ os: entry.metadata?.os, remotePlatforms: eligibility?.remote?.platforms, always: entry.metadata?.always, requires: entry.metadata?.requires, hasBin: hasBinary, hasRemoteBin: eligibility?.remote?.hasBin, hasAnyRemoteBin: eligibility?.remote?.hasAnyBin, hasEnv: (envName) => Boolean( process.env[envName] || skillConfig?.env?.[envName] || (skillConfig?.apiKey && entry.metadata?.primaryEnv === envName), ), isConfigPathTruthy: (configPath) => isConfigPathTruthy(config, configPath), }); }

前置筛选只会过滤这些:

筛选1:系统平台检查

--- metadata: os: - darwin - linux ---

如果你在Windows上运行,标注了 os: [darwin, linux] 的skill会被过滤掉。

筛选2:依赖检查

--- metadata: requires: bins: - git - gh env: - OPENAI_API_KEY config: - browser.enabled ---

  • 缺少 git/gh 命令 → 被过滤
  • 缺少 OPENAI_API_KEY → 被过滤
  • browser.enabledfalse → 被过滤
筛选3:bundled skill白名单

{ "skills": { "allowBundled": ["github", "weather"] } }

不在白名单的bundled skill被过滤掉。

筛选4:skillFilter白名单

{ "agents": { "defaults": { "skills": ["github", "git", "coding-agent"] } } }

只保留白名单中的skill。

▪ 3. 前置筛选后的数量:可能还是很多

关键发现: 前置筛选只过滤"运行条件",不过滤"用户问题相关性"。

假设你有1000万个skill,但:

  • 全部是跨平台的os: [] 或不设)
  • 没有任何依赖requires: {}
  • 都在白名单里

筛选后还是1000万个!

这时候,Token防线会触发:

第一道:数量限制(默认150个)

const DEFAULT_MAX_SKILLS_IN_PROMPT = 150; const byCount = params.skills.slice(0, limits.maxSkillsInPrompt);

只保留前150个(按字母排序)。

第二道:字符数检查(默认18000字符)

const DEFAULT_MAX_SKILLS_PROMPT_CHARS = 18_000; if (!fitsFull(skillsForPrompt)) { // 切换到compact格式(只有name + location) compact = true; }

第三道:Compact格式后还超限,二分搜索截断

if (!fitsCompact(skillsForPrompt)) { // 二分搜索找到最大的能塞进去的prefix skillsForPrompt = skillsForPrompt.slice(0, lo); }

▪ 4. 匹配逻辑完全交给LLM

src/agents/system-prompt.ts 的指令:

function buildSkillsSection(params: { skillsPrompt?: string; readToolName: string }) { return [ "## Skills (mandatory)", "Before replying: scan entries.", "- If exactly one skill clearly applies: read its SKILL.md...", "- If multiple could apply: choose the most specific one...", "- If none clearly apply: do not read any SKILL.md.", trimmed, ]; }

"scan entries" — 让LLM自己扫描所有skill的description

不是系统预先筛选出最相关的skill,而是把所有符合运行条件的skill的description都给LLM,让LLM自己选。

为什么说解决方案不彻底?

▪ 问题1:假设skill数量不会太多

OpenClaw的默认配置:

  • maxSkillsInPrompt: 150
  • maxSkillsPromptChars: 18000

这意味着:

  • OpenClaw假设前置筛选后,skill数量不会太多
  • 如果筛选后还是上千个,就暴力截断

这不是智能方案,是暴力方案。

▪ 问题2:没有基于用户问题的筛选

如果用户问"GitHub issue怎么处理":

  • 前置筛选不会筛选出"GitHub相关"的skill
  • 所有符合运行条件的skill(可能是50万个)都会传给LLM
  • LLM扫描50万个description,找到github-issues

50万个description的token是多少?

假设每个description平均100字符:

  • 50万 × 100 = 5000万字符
  • 远超18000字符限制
  • 触发Token防线:截断到前150个

如果github-issues在前150个(按字母排序):

  • LLM看到150个完整description
  • 精准匹配,成功

如果github-issues在第151个:

  • LLM看不到
  • 这是"漏掉",不是"选错"

▪ 问题3:字母排序截断的局限性

const promptSkills = compactSkillPaths(resolvedSkills) .slice() .sort((a, b) => a.name.localeCompare(b.name, "en"));

skill按字母顺序排序,然后截断。

这意味着:

  • github-issues 会被排在前面(g开头)
  • zzz-helper 会被排在后面(z开头),很可能被截断

这不是基于相关性排序,而是基于字母顺序。

▪ 问题4:MCP/Function Call不影响筛选

你可能会问:一个skill可能会用到多个MCP或Function Call,这些不参与筛选吗?

答案是:不参与。

MCP(Model Context Protocol)和Function Call是skill内部使用的工具,是skill运行时需要的资源,不是筛选skill的依据。

OpenClaw的筛选逻辑:

  • 只看skill本身的运行条件(os, requires, enabled, allowBundled, skillFilter)
  • 不看skill内部会用到什么MCP/Function Call

这意味着:

  • 即使一个skill需要5个MCP服务器和10个Function Call
  • 只要这些MCP/Function Call可用,这个skill就会通过筛选
  • 筛选不关心skill内部会调用什么

实际场景分析

▪ 场景1:依赖筛选很严格(常见情况)

你是一个后端开发者,安装了1000万个skill:

  • 200万个前端相关(React、Vue、Webpack) → 需要Node.js,你的环境没有 → 被过滤
  • 300万个移动端相关(iOS、Android) → 需要Xcode/Android SDK → 被过滤
  • 150万个AI模型相关(需要GPU) → 需要CUDA,你的机器没有 → 被过滤
  • 250万个Linux专用 → 你在macOS上 → 被过滤
  • 100万个需要特定API Key → 你没配置 → 被过滤

最终只剩10个skill!

这时候:

  • 10个 < 150个限制 → 数量防线通过
  • 不需要compact模式
  • AI看到10个完整description,精准度极高

这种情况下,OpenClaw的方案是有效的。

▪ 场景2:依赖筛选不严格,但数量限制兜底

你是一个全栈开发者,环境配置很全:

  • 所有skill的依赖都满足
  • 筛选后还是1000万个

这时候:

  • 数量限制截断到前150个
  • 这150个的完整description都可见
  • 如果你要用的skill在前150个,精准选择
  • 如果你在第151个,AI看不到

这种情况下,OpenClaw的方案是"暴力截断",不是智能筛选。

▪ 场景3:极端情况,全部通过筛选

所有1000万个skill都通过筛选:

  • 切换到compact模式
  • 截断到前N个
  • AI只能看到name,看不到description

这种情况下,OpenClaw的方案完全失效。

实战建议:如何缓解这个问题?

▪ 1. 合理设置metadata(最重要)

❌ 不好:依赖太少

--- name: my-skill description: Do something ---

这个skill会在所有环境通过筛选,增加候选数量。

✅ 好:明确依赖

--- name: github-issues description: Fetch GitHub issues, spawn sub-agents to implement fixes metadata: requires: bins: - git - gh config: - browser.enabled os: - darwin - linux ---

这个skill只在有git/gh命令、浏览器启用、非Windows环境时通过筛选。

▪ 2. 使用skillFilter(最有效)

如果你知道这次任务只需要某个技能集,直接过滤:

{ "agents": { "defaults": { "skills": ["github", "git", "coding-agent"] } } }

这样:

  • 前置筛选后,可能只有100个通过
  • skillFilter再过滤,只看3个
  • AI看到的是3个skill的完整description
  • 精准度100%

这是最有效的解决方案。

▪ 3. 按环境拆分workspace

如果你的skill数量真的超过300个,考虑按环境拆分:

~/workspace/backend/ # 后端相关skill(100个) ~/workspace/frontend/ # 前端相关skill(100个) ~/workspace/ai/ # AI相关skill(100个)

用不同的agent配置加载不同的workspace:

{ "agents": { "list": [ { "id": "backend-dev", "skills": ["backend/*"], "workspace": "~/workspace/backend" }, { "id": "frontend-dev", "skills": ["frontend/*"], "workspace": "~/workspace/frontend" } ] } }

这是终极方案:不是让一个agent背负所有skill,而是让多个agent各司其职。

▪ 4. 调整limits(慎用)

如果经常遇到截断,可以调大限制:

{ "skills": { "limits": { "maxSkillsInPrompt": 300, "maxSkillsPromptChars": 36000 } } }

但要注意:

  • 调大限制会增加token消耗,增加成本
  • 如果筛选后还是太多,调限制治标不治本
  • 应该优化metadata和skillFilter,而不是依赖调限制

源码中的隐藏细节

▪ 1. 路径压缩节省token

// /Users/alice/.bun/.../skills/github/SKILL.md // → ~/.bun/.../skills/github/SKILL.md

每个skill路径节省5-6个token,100个skill就是500-600个token。

▪ 2. 字母顺序排序

.sort((a, b) => a.name.localeCompare(b.name, "en"));

技巧: 重要skill的name用字母表前半部分开头(如a-github),避免被截断。

▪ 3. 截断时的二分搜索

不是简单的slice(0, N),而是二分搜索找到最大的能塞进去的prefix。

结论

OpenClaw的解决方案并不彻底,它假设:

  1. 前置筛选会过滤掉大量不满足运行条件的skill
  2. 筛选后skill数量不会太多(默认最多150个)
  3. 如果筛选后还是太多,就暴力截断

但这在以下情况下会失效:

  1. skill都是跨平台的(os: [])
  2. skill都没有特殊依赖(requires: {})
  3. skill数量真的很多(上千个)

给你的建议:

  1. 合理设置metadata明确依赖,减少不必要的候选
  2. 使用skillFilter明确告诉AI哪些skill是本次任务相关的(最有效)
  3. 按环境拆分workspace用多个agent各司其职(终极方案)
  4. 重要skill的name用字母表前半部分开头避免被截断

最后,给一个终极建议:

如果你的skill数量真的超过300个,需要对skill进行治理:

考虑按环境/用途拆分成多个workspace,用不同的agent配置来加载不同的skill子集。

不是让一个agent背负所有skill,而是让多个agent各司其职。

觉得有用?欢迎点赞、在看、转发!

有任何问题,评论区留言,我会一一回复。

一文讲清:LLM、CoT、Function Calling、MCP、Skills、Agent、Agent OS

别再问LLM、Workflow、Function Call、MCP、Skill、Agent、OpenClaw是什么了,看完这篇秒懂!有配套源码

带你实现一个Agent,从Tools、MCP到Skills,从理论到代码,干货满满

“从Prompt到Skills,RAG到底还行不行?!”

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-04-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 的数字化之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 筛选1:系统平台检查
  • 筛选2:依赖检查
  • 筛选3:bundled skill白名单
  • 筛选4:skillFilter白名单
  • 第一道:数量限制(默认150个)
  • 第二道:字符数检查(默认18000字符)
  • 第三道:Compact格式后还超限,二分搜索截断
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档