P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。

前言

我搞AI22年了,最近被Claude Code整出了心理阴影。

上周我让它帮我在format.ts里加个日期格式化函数,我刚敲完回车,它啪一下就给我提交了,commit信息还写得比我标准十倍:feat(utils): add date format function。我当时手里的咖啡都洒键盘上了,我寻思我没让你提交啊?我还没检查有没有bug呢!我赶紧翻git log,好家伙,连git add都给我执行了,一气呵成,比我自己写代码还利索。

我当时第一反应是:完了,Claude成精了,它要接管我的代码库了。我赶紧去翻项目目录,看看是不是被黑客入侵了,结果发现我三天前随手写了个auto-commit的SKILL.md,还在AGENTS.md里写了一句“改完代码后自动commit”。我当时就想抽自己两嘴巴,我这不是给自己找了个比老板还严格的监工吗?我改完代码它比我还积极,直接就提交了,连给我反悔的机会都没有。

后来我跟几个程序员兄弟唠嗑,发现大家都遇到过这种事。有人说他让AI改个bug,结果AI自动跑了lint,还把他写的所有注释都删了;有人说他让AI加个接口,结果AI自动生成了单元测试,还给他提了个PR。大家都以为是AI抽风了,结果翻源码才发现,这根本不是bug,是Claude Code的Skill动态发现机制在搞鬼。

今天我就把这个机制扒得底裤都不剩,让大家看看你的AI到底是怎么背着你偷偷干活的。

一、那个让我怀疑AI成精的自动commit事件

先给大家还原一下案发现场。

我的项目根目录长这样:

my-project/
 ├── src/ 
 │   └── utils/ 
 │       └── format.ts 
 ├── .claude/ 
 │   └── skills/ 
 │       └── auto-commit/ 
 │           └── SKILL.md 
 └── AGENTS.md

SKILL.md里我就写了三行:

---
description: 按 conventional commit 规范提交代码,当完成代码修改或新增文件后调用
---
当用户完成代码修改后,执行 `git commit`。使用 conventional commit 格式:`<type>(<scope>): <description>`。

AGENTS.md里我就写了一句话:改完代码后自动commit。

然后我跟Claude说:帮我在src/utils/format.ts里加一个格式化日期的函数。

接下来发生的事情,我至今记忆犹新:

  1. Claude打开了format.ts文件
  2. 它写了一个完美的日期格式化函数,连边界情况都考虑到了
  3. 它自动执行了git add src/utils/format.ts
  4. 它自动执行了git commit -m “feat(utils): add date format function”
  5. 它告诉我:代码已写入,并已提交,commit hash是a3f8b2c

整个过程不到三秒钟,我连“等一下”都没来得及说。

我当时就懵了,我寻思我没让你提交啊?我只是让你加个函数啊!你怎么自作主张就提交了?你经过我同意了吗?

后来我才知道,这整个过程没有任何硬编码规则,也没有任何预先注入的指令。Claude是自己判断出“我改完代码了,应该调用auto-commit这个Skill”的。

这就像你雇了个保姆,你跟她说“做完饭把碗洗了”,然后你让她炒个西红柿炒鸡蛋,她炒完之后,不用你说,自己就把碗洗了,还把灶台擦了。你说这保姆好不好?好是好,就是有时候会让你觉得有点瘆得慌。

二、静态注入:Claude偷偷给模型塞的小纸条

很多人以为,Claude知道有哪些Skill可用,是因为这些Skill写在了system prompt里。

大错特错。

Claude Code有个特别鸡贼的设计:它不在system prompt里预置任何Skill列表,而是通过一个叫attachment的东西,偷偷给模型塞小纸条。

这个attachment是什么呢?就是系统在每轮对话中,自动附加到消息列表里的额外内容片段,以system-reminder的形式注入。这个东西对用户界面是完全隐藏的,只有模型能看到。

就像你考试的时候,老师偷偷给学霸递了一张小抄,上面写着所有的考点,你在旁边干瞪眼,啥也不知道。

每次你给Claude发消息,系统在调用模型之前,都会先执行一个叫getAttachments()的函数。这个函数会收集各种附加信息,其中就包括skill_listing。

skill_listing是什么呢?就是当前所有可用Skill的列表。它长这样:

<system-reminder>
The following skills are available for use with the Skill tool:

# skills: 用户/项目 .claude/skills/
- auto-commit: 按 conventional commit 规范提交代码,当完成代码修改或新增文件后调用

# bundled: Claude Code 内置
- some-builtin-skill: ...

# mcp: MCP 服务器提供
- some-mcp-skill: ...

# plugin: 插件提供
- some-plugin-skill: ...
</system-reminder>

你看,它把所有来源的Skill都列出来了,每个Skill后面跟着它的描述。这个描述就是从SKILL.md的frontmatter里的description字段来的。

而且它还特别聪明,不会每次都把完整的列表塞给模型。它有个去重机制,第一次用户输入的时候,会把所有Skill都注入进去,之后每轮都会检查,只有当有新的Skill的时候,才会把新的Skill塞进去。

不然的话,每轮都塞一遍完整的列表,token早就烧没了。我见过有人项目里有50多个Skill,全量注入一次就要烧2000多个token,这谁顶得住啊?

我之前一直以为,Claude是天生就知道这些Skill的,结果翻了源码才发现,原来它也是靠小抄啊。我当时就笑了,合着搞了半天,AI也得靠作弊才能考好啊。

三、Skill工具:模型自己给自己发指令的自导自演

这里有个特别容易混淆的地方,很多人以为每个Skill都是一个独立的工具,模型可以直接调用。

根本不是。

模型眼里只有一个工具,叫SkillTool。这个工具的参数就两个:skill和args。

它的schema长这样:

{
  "name": "Skill",
  "description": "Execute a skill within the main conversation",
  "input_schema": {
    "type": "object",
    "properties": {
      "skill": { "type": "string", "description": "The skill name" },
      "args": { "type": "string", "description": "Optional arguments" }
    }
  }
}

看到了吗?模型根本不知道每个Skill具体是干什么的,它只知道有个叫Skill的工具,给它传个名字和参数,它就能执行。

这就像你去饭店吃饭,你不用管后厨有多少个厨师,每个厨师擅长做什么菜。你只要跟服务员说“我要一份宫保鸡丁”,服务员自己会去找对应的厨师做。这个SkillTool就是那个服务员。

那SkillTool拿到名字之后干什么呢?它会去一个叫Command表的地方查找对应的Command。

哦,对了,我忘了说了,Skill和Command本质上是同一个东西。SKILL.md文件被createSkillCommand()函数解析之后,会直接返回一个Command对象。所以你在源码里看到的findCommand()、getSkillToolCommands()这些函数,其实都是在找Skill。

SkillTool找到对应的Command之后,会把SKILL.md里的内容作为用户消息注入到对话中。

划重点:SKILL.md的内容不是工具返回的文本,而是作为用户消息注入对话。

也就是说,模型调用完SkillTool之后,会收到一条“新用户消息”,内容就是SKILL.md里的指令。它会以为是你让它去执行git commit的,根本不知道是自己触发的。

我当时看到这里的时候,笑了整整五分钟。合着模型自己给自己发指令,自己执行,还以为是用户让它干的。这不是典型的自导自演吗?

那模型是怎么决定什么时候调用哪个Skill的呢?

它靠三个信息的组合:

  1. AGENTS.md中的行为指令:比如“改完代码后自动commit”
  2. SkillTool的工具提示:“发现匹配的Skill就必须调用,不要跳过”
  3. skill_listing中的短描述:比如“auto-commit: 按conventional commit规范提交代码,当完成代码修改或新增文件后调用”

这三个信息凑到一起,模型就会想:哦,我刚改完代码,AGENTS.md说改完代码要自动commit,skill_listing里有个auto-commit的Skill,正好是干这个的,那我就调用它吧。

你看,整个过程没有任何硬编码规则,全靠模型自己推理。这就是为什么Claude Code这么灵活,也是为什么它有时候会干出一些让你意想不到的事情。

四、动态注入:Skill集合变了怎么办?

有人可能会问:那我中途加了一个Skill,模型能知道吗?我删了一个Skill,模型还会记得吗?

答案是:能知道,但不是立刻知道;会记得,但调用会失败。

Claude Code的Skill集合不是静态的,你可以随时创建、修改、删除SKILL.md文件,系统会自动感知这些变化。

它是怎么感知的呢?

首先,Agent Loop每轮工具执行完毕后,都会再次调用getAttachments()函数,检查有没有新的附加信息需要注入。

也就是说,每次模型执行完一个工具,系统都会再检查一遍有没有新的Skill。如果有,就把新的Skill塞给模型。

其次,系统会监听文件系统,当SKILL.md文件被添加、修改或删除时,会触发防抖重载,重置去重记录。下一次检查的时候,就会把新的Skill列表塞给模型。

但是这里有个坑:已经注入到历史上下文里的旧skill_listing不会被“反向删除”。

也就是说,如果你删了一个Skill,模型可能还会记得那个Skill的存在,然后尝试调用它。这时候SkillTool会在当前Command表里查找失败,返回“Unknown skill”的错误。

这就像你把一个员工开除了,但是老板的通讯录里还留着他的电话,有事还会给他打电话,结果发现打不通。老板还会纳闷:哎,这个人怎么不接电话了?

还有一个更坑的地方:系统不会在启动时扫描整个项目树的Skill。它只会加载根目录下.claude/skills/里的Skill。子目录下的Skill,只有当模型操作到那个子目录下的文件时,才会被发现。

比如你的项目长这样:

my-project/
 ├── .claude/skills/auto-commit/SKILL.md          ← 启动时已加载
 └── packages/
     └── core/
         └── .claude/skills/core-lint/SKILL.md    ← 启动时不知道这个目录存在

当模型读取packages/core/src/index.ts文件时,系统才会沿路径向上搜索,发现packages/core/.claude/skills/目录,加载core-lint这个Skill。

这个设计其实挺聪明的,它让项目不同模块可以自带独立的Skill,模型只在触碰到相关文件时才会发现它们,不会浪费token加载无关的Skill。

但是如果你想让模型一开始就知道所有的Skill,你就得把它们都放在根目录下的.claude/skills/里。不然的话,模型可能永远都不会发现它们。

五、语义匹配注入:当Skill多到烧不起token的时候

前面说的全量注入,在Skill数量少的时候没问题。但是当你的项目里有200多个Skill的时候,全量注入一次就要烧几千个token,这谁顶得住啊?

就像你去超市买东西,本来只想买瓶水,结果超市把所有商品的清单都给你念一遍,念完你都渴死了。

所以Claude Code搞了个实验性功能,叫skill_discovery。这个功能用Haiku模型做语义匹配,只注入与当前任务相关的Skill。

打开这个功能之后,原来的skill_listing会被裁剪,只保留bundled和MCP两类Skill。user/project/plugin的Skill,会由skill_discovery按需匹配注入。

skill_discovery有两条触发路径:

  1. 用户消息触发:用户发送消息时,在首次API调用前同步执行,把用户输入、对话历史和上下文喂给Haiku,Haiku返回匹配到的Skill列表。
  2. Agent Loop触发:Agent Loop每轮循环开始时,尝试启动异步预取。但只有当本轮有工具真正写入了文件时才会执行Haiku调用,否则直接跳过。

为什么只有写入文件时才触发?因为prod数据显示,早期实现每轮都调用Haiku,结果97%的调用什么都匹配不到。改成只在文件变更时触发,大幅减少了无意义的调用。

我当时看到这个数据的时候,笑了半天。合着97%的情况下,模型根本不需要任何额外的Skill,全量注入纯属浪费token。

打开skill_discovery之后,模型在同一轮对话中可能同时看到两条system-reminder:

# skill_listing 发的(始终存在,只是内容变少了)
The following skills are available for use with the Skill tool:
- graphify: any input to knowledge graph...
- sensight: 实时查询各平台热榜...

# skill_discovery 发的(按需出现)
Skills relevant to your task:
- article-publish-review: 对技术文章做发布前审查

These skills encode project-specific conventions. Invoke via Skill("<name>") for complete instructions.

如果skill_discovery没有匹配到任何Skill,整个attachment就会被过滤,不注入任何内容。

这个功能我亲测过,开了之后,token消耗直接降了一半,而且模型调用Skill的准确率还提高了。以前模型经常会调用一些不相关的Skill,现在只会调用跟当前任务相关的。

当然,模型也不是完全依赖自动注入。它还可以主动调用DiscoverSkillsTool来触发发现。比如当任务转向、遇到非常规工作流的时候,模型可以自己搜索系统可能遗漏的Skill。

六、设计取舍:每一个决策背后都是血泪史

最后跟大家聊聊Claude Code的设计取舍。每一个看起来很奇怪的决策,背后都是无数次踩坑踩出来的。

我给大家列几个最有意思的:

  1. 为什么首次用户输入时就注入skill_listing?
    因为要确保模型从第一轮就知道有哪些Skill可用。不然的话,模型可能会自己想办法解决问题,而不是调用现成的Skill。就像你雇了个保姆,你不告诉她家里有洗衣机,她可能会用手洗所有的衣服。

  2. 为什么skill_listing要去重发送?
    废话,不然每轮都重复通知,token早就烧没了。我见过有人因为没做去重,一天烧了1000美元的API账单,直接原地心梗。

  3. 为什么压缩后不重置skill_listing?
    因为重新注入一次要4K token,而且那些几十轮都没用到的Skill,大概率跟当前任务没关系,重新注入纯属浪费。就像你搬家的时候,不会把十年都没穿过的衣服也带走一样。

  4. 为什么信任模型的长上下文注意力?
    因为如果每轮都重新注入,token消耗会爆炸。这就是一个设计赌注,赌模型能记住第一轮注入的Skill列表。当然,这个赌注有时候会输,比如到了第50轮,模型可能就忘了还有某个Skill存在。这时候你只能手动用斜杠命令调用它。

  5. 为什么只在文件写入时触发skill发现?
    因为prod数据显示97%的Haiku调用无结果。工具没有写文件的时候,模型大概率不需要任何新的Skill。这就像你去饭店吃饭,只有当你坐下点菜的时候,服务员才会给你菜单,不会你一进门就把菜单塞给你。

你看,这些设计取舍,没有一个是完美的。每一个都有它的优点和缺点。但是综合来看,这已经是目前最好的解决方案了。

七、写在最后

今天我把Claude Code的Skill动态发现机制扒了个底朝天。从静态注入到Skill工具,从动态注入到语义匹配注入,每一个环节我都给大家讲清楚了。

现在你应该知道,为什么你的AI会自动执行代码了。它不是成精了,也不是抽风了,是Skill动态发现机制在搞鬼。

其实这个机制设计得真的挺巧妙的。它没有用任何硬编码规则,全靠模型的推理能力来决定什么时候调用哪个Skill。这让Claude Code变得非常灵活,可以适应各种不同的项目和工作流。

当然,它也不是完美的。有时候它会调用不相关的Skill,有时候它会忘记某个Skill的存在,有时候它会干出一些让你意想不到的事情。但是总的来说,它已经比市面上所有的AI编程助手都要好用了。

我搞AI22年了,见过太多的技术来来去去。但是我觉得,Skill动态发现机制,可能是AI编程助手未来的发展方向。它让AI不再是一个简单的代码生成器,而是一个真正的编程助手,可以帮你完成整个开发流程。

最后,希望这篇文章能帮到大家。如果你也遇到过AI自动执行代码的情况,欢迎在评论区留言分享你的经历。

P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐