Skill or MCP Server? Ask What Stops the LLM From Disobeying
TL;DR · AI 摘要
Skill or MCP Server? Ask What Stops the LLM From Disobeying Towards Data Science Agentic AI Skill or MCP Server? Ask Wha...
核心要点
- 主题聚焦:Skill or MCP Server? Ask What Stops the LLM From
- 来源:Towards Data Science,建议结合原文判断细节。
- AI 分析暂不可用,本条为保底评分与摘要。
技能还是MCP服务器?问什么阻止了LLM的违抗 | Towards Data Science
智能代理AI
技能还是MCP服务器?问什么阻止了LLM的违抗
面向规格驱动的测试自动化,第4部分
Gal Arav
2026年10月20日
15分钟阅读
在Google上关注我们
分享
查理·卓别林和杰克·柯根在《小流浪汉》(1921)中的照片,美国公共领域
从我Python项目的第一个提交开始,有一条规则比其他任何规则都重要:编码代理永远不能看到验收标准。因此我构建了两个永不交流的代理:一个根据完整规范编写测试,另一个独立的编码代理则从相同规范中剔除验收标准后编写代码。在大多数编码环境中,同一个代理同时编写测试和代码,这会损害测试结果的有效性。我通过上下文组装(context assembly)强制实现了这一分离——这是构建编码代理提示的步骤,在提示生成前会先从任务文件中剔除验收标准部分。
很长一段时间我以为这种分离是严密的。但后来发现一个注释行打破了它:分离在此处失效,注释之后的所有验收标准都传递给了编码代理。一周后,一个跨行的验收标准也产生了同样的问题。这很糟糕。这两个错误都源于我,我恼火于这个核心问题再次浮现。因此这次在修复两个错误后,我增加了测试用例,确保未来构建若未按预期实现分离将直接失败。
作者图片
技能和MCP服务器经常被相互比较。一年前,Anthropic推出技能的当天,Simon Willison称其"可能比MCP更重要"。它们并非真正的竞争对手,对于我项目依赖的这条规则,答案最终证明两者都不是,我选择了上下文组装。但我最终还是推出了三者,因为每个方案都有其独特优势。
我希望用户能够通过聊天环境快速访问我的命令行工具qikly,例如通过编辑器或终端中的AI助手(如Claude Code或Cursor)。这涉及两个LLM:驱动qikly的助手,以及qikly自身的编码代理。两者都不应看到验收标准,否则都可能编写通过测试而非解决问题的代码。助手已经能够从命令行运行qikly,但缺乏判断何时使用它的能力。我设想通过"技能"(Skill)实现——当请求匹配描述时,助手会加载包含Markdown指令的文件夹。技能也是封装使用规范的最佳实践的好方法,助手会自动获取这些规范,无需用户手动粘贴。
编写技能让我意识到需要分离两个职责,其中只有一个是技能的职责。
技能的职责是提供建议:哪些内容应放入需求(编码代理会读取),哪些应放入验收标准(编码代理永远看不到)。这是判断,只能通过教学实现:助手通常会遵循建议,你可以评估它是否有效,但无法保证。
上下文组装的职责是每次构建编码代理提示时都剔除验收标准,无论是否安装了技能。
即使切割过程是自动进行的,这些建议仍然重要,因为切割操作仅移除规范中的标准部分。如果某个标准也被写入规范的“要求”部分,它仍然会传递给编码代理。因此我在 qikly 的 --validate 功能中添加了检查机制,当要求部分重复表述标准时会发出警告。
这个原则适用于你提供给任何 LLM 的规则:有些规则必须始终生效,无论模型做出什么决定;而另一些规则只有在模型理解它们时才能生效。两种常见的解决方案都存在局限性。更优的模型虽然会更频繁地遵循建议,但并非总是如此。将温度参数设为零只会让回答更可重复,而不会让模型更服从。
MCP 服务器与 Skill 的区别
两者都被描述为“为 LLM 新增能力”,确实如此,因此它们的差异容易被忽视。以下是它们的本质区别:
见封面图片的署名信息。附加文本由作者撰写。
MCP(Model Context Protocol)服务器是一个进程:可以将其视为 AI 助手的 API。它通过实时的客户端-服务器协议暴露类型化的函数作为工具(我的实现使用 Python)。托管环境(如 Claude Code 或 Cursor 这类运行助手的应用)通过内置的 MCP 客户端连接到服务器,并让 LLM 以代码调用函数的方式调用这些工具。服务器可以运行在你自己的机器上(由托管环境作为本地进程启动),也可以由服务提供商作为网络服务托管在某个 URL 上;qikly 使用的是本地运行的服务器。
如果你自己编写代理循环(即调用模型并执行模型请求的工具的代码),则不需要服务器:你可以在自己的 API 调用中直接传递工具的模式定义。MCP 服务器适用于循环属于 Claude Code 或 Cursor 这类你无法修改代码的助手的情况。我的项目 qikly 作为命令行工具运行,不需要服务器。它的 MCP 服务器是一个可选的附加功能,当助手为你驱动 qikly 时值得添加。它通过 qikly --install-mcp 安装,包含四个工具:qikly_run 启动运行,qikly_status 轮询状态,qikly_validate 检查任务文件,qikly_scaffold 从 Python 文件生成任务文件草稿。
Skill 是一个静态的、可移植的指令集,通常是一个 SKILL.md 文件,说明何时使用某项功能以及如何使用,可选地附带参考文件。它分为两个步骤使用:LLM 始终能看到 Skill 的名称和描述,仅当该描述看起来与当前操作匹配时才会打开完整文件。因此,与始终完整可见的系统提示不同,Skill 可能因未被阅读而失效:如果描述与请求无关,其余内容将永远不被打开。Skill 文件夹也可以包含脚本,但是否运行这些脚本由助手决定,因此即使 Skill 包含脚本,是否执行也是助手的选择,而非必须通过的代码。
最重要的问题
大多数比较会从知识与访问权限的角度衡量两者。这种框架忽略了更有价值的问题。在向 LLM 应用添加任何内容时,请问自己:
什么能阻止 LLM 违反规则?
我的 MCP 服务器是代码实现,因此它通过了“所有内容必须经过它”的测试。不过,无论助手如何提问,它都不会将验收标准传递给助手,因为拒绝条件是写入服务器代码中的,而非通过提示请求实现。
磁盘上的文件则有所不同。规则存储在任务文件中,助手可以使用自己的工具直接打开该文件,而无需经过qikly的MCP服务器,因此服务器只需守护自己的入口。qikly的编码代理从未依赖这个入口:它从不读取磁盘,而规则裁剪发生在上下文组装阶段,这是它无法绕过的。对于助手也有相应的机制:文档中展示了一条权限规则,阻止其进入由规则生成的测试代码中。
真正的问题不是“这应该是一个Skill还是服务器”。而是针对你正在添加的内容提出上述问题,然后选择符合你需求的格式。我曾寻找其他可用的格式,发现了很多选项。所有格式都属于同一类别的两种分组:一类是向LLM传达规则,另一类是通过代码强制执行规则。
这两个表格的唯一区别在于:当LLM更倾向于做其他事情时会发生什么。第一张表格的每一行都以LLM自身的判断为终点:无论规则表述得多坚定,LLM仍可能决定不遵循。在第二张表格中,LLM的偏好不会出现,因为其路径中的某些内容会自行拒绝。每张表格都从顶部最强的行到底部最弱的行排列。那么是什么阻止LLM违抗规则?对于Skill来说,没有任何东西能阻止。而路径中的代码可以阻止,因为LLM从未获得选择的机会。
qikly使用了上述12行中的8行,包括其核心主张所在的那一行。这8行中有一行已有名称:固定控制流,其中调用序列是代码而非计划,这与Anthropic的工作流与代理区分定义的工作流概念一致,相对于自主规划步骤的代理,qikly被定义为工作流。固定控制流决定了下一个提示的运行顺序,但不决定其中任何内容,因此规则裁剪仍必须通过代码机制而非排序实现。
该项目的来源
- 代码是开源的,着陆页链接了代码仓库。
- Skill在文档中有描述,通过qikly --install-skill安装。
- MCP服务器在文档中有描述,通过qikly --install-mcp安装。
这样划分规范与安全关键实践相呼应,验证必须独立于构建者,并且更进一步。在大多数团队中,开发人员会阅读整个需求单(包括规则),唯一的独立性是其他人编写测试。阅读过规则的开发人员会按照规则编写代码,因此测试无法再显示需求本身是否足够。在qikly中,这两个角色都由LLM担任,因此独立性无法来自不同的人。独立性来自各自看到的内容:测试生成器看到规则,而编码代理永远看不到。当测试失败时,编码代理会看到pytest的输出。默认情况下这包括失败测试的源代码,通常会重述其规则。qikly中有一个分阶段设置(默认关闭),初始时只显示单行错误,只有在持续失败时才会逐步扩展。本系列文章第一部分解释了这种权衡。
编码代理从不修改测试用例。它生成差异(diff),而任何涉及代码以外内容的差异都会被拒绝。集成测试和系统测试套件在代码编写之前就已经完成,并且始终保持原样。单元测试套件最后生成,且在代码可见的情况下进行,原因在于单元测试同样需要覆盖编码代理编写的辅助函数,而这些辅助函数在任务文件的接口部分并未命名,直到代码完成才会存在。
在第三部分中,我将这种方法称为独立规范驱动开发(Independent Spec-Driven Development, ISDD):编码代理被赋予决策权,测试生成器被赋予后果处理权,而独立性体现在各自所掌握的知识范围。这种方法只有在“标准裁剪”(criteria cut)始终执行的情况下才能奏效。
这个项目现已作为命令行工具、MCP服务器和代理技能(agent Skill)发布,已扩展到包含超过2000个测试用例,其中约70个测试用例专门用于守护这一规则,覆盖编码代理接收到的每个提示、MCP服务器发送的每个回复,以及所有可能导致标准裁剪失效或可能破坏标准裁剪的任务文件写法。
这个项目教会我的区分
代码可以执行并强制实施规则,而LLM(大语言模型)只能读取并提供信息。Skill被LLM读取,因此它永远无法包含这个项目所依赖的核心规则。而服务器代码可以被执行,因此可以包含该规则。我的实现拒绝返回标准裁剪规则,但该规则实际上根本不需要任何格式:无论qikly如何启动(从命令行还是工具调用),上下文组装机制都会将标准裁剪规则排除在编码代理的提示之外。
任何必须保证实现、与LLM的决策无关的规则,都必须通过代码实现,并且要放置在LLM无法绕过的接口之后,例如访问控制、标准裁剪、速率限制、审计追踪等。换句话说,任何你想要断言“这绝不可能发生”的规则都属于此类。
任何涉及知识、判断或流程的内容都应放在Skill中,因为工具调用没有其他存储位置。以下三个来自我的SKILL.md的具体例子:
- 规范中哪些行是某人做出的决策,哪些行是由此产生的后果。
- 修复循环(qikly的修复与重测循环)出现停滞并持续提交相同补丁时意味着什么。
- 为什么一个通过所有测试的测试套件可能仍然价值甚微。
写下你希望成立的句子,然后问一问是什么阻止LLM违背它。
为什么我仍然同时发布了两者
因为它们承担着不同的职责。
服务器负责启动并跟踪长时间运行的任务。一个运行可能需要几分钟到几小时,具体取决于其范围和复杂度,而工具调用无法长时间阻塞。因此,qikly_run会在后台以独立进程启动运行,并立即返回一个ID,而qikly_status会报告运行结果。通过这种方式,多个任务可以并行运行,每个任务一个进程。这种“启动-轮询”(start-and-poll)的分离是服务器的主要便利之处:助手可以像普通工具调用一样启动并检查运行状态,而无需直接操作shell。命令行也可以通过qikly --run和qikly --status实现相同功能,服务器正是基于这些命令构建的。服务器额外提供的功能包括:对参数进行模式验证、标注哪些调用可以安全地无人值守执行,以及为没有终端的助手提供访问权限。服务器的回复会进入助手的上下文,因此这是另一个准则可能泄露的地方,它也有自己的测试用例。
Skill 教授的是工具无法做到的部分。当助理调用 qikly_scaffold 时,会得到一个包含两个待填写部分的任务文件,但工具本身不会说明用户的哪些句子属于哪个部分。这就是上方的决策与后果分界线,也是本项目中最难判断的部分。由于没有对应的函数签名,必须以 LLM 可读的方式编写这一部分,这正是 Skill 的作用。这一部分至关重要,因为若决策隐藏在标准中,编码代理无法看到决策时,运行过程会更频繁地陷入停滞。在某个驾驶员辅助任务中,将决策保留在标准中时,10 次运行仅有 3 次成功收敛并生成通过所有测试的代码;而将决策移至需求说明后,10 次运行全部成功,次日重跑时 10 次中有 9 次成功。Skill 的四千五百字内容,大部分就是用于帮助明确这条分界线。它值得单独成文,未来也会如此。
Skill 还可能在任何工具介入之前就发挥作用。我的 Skill 以针对从未提及 qikly 的请求的建议开篇:若被要求编写测试但没有规范时,不要立即编写,而是提供两条优先生成测试的路径。工具的描述聚焦于工具的使用,因此对即将手动编写测试的助理无任何指导意义。这仍然是建议而非保证:助理通常会遵循建议,而这正是 Skill 能提供的全部承诺。
为何不采用 RAG(检索增强生成)?
数据科学家的首个疑问通常是 RAG 是否能胜任此任务。RAG 最多只能替代 Skill 的部分功能。检索绝非服务器的替代方案。RAG 将文本放入上下文窗口但不施加任何约束,因此会像 Skill 一样在相同测试中失败,而保证性内容无法存在于 RAG 中,正如它无法存在于 Skill 中。
与 Skill 相比,RAG 的适用性取决于选择和规模。检索通过查询猜测相关性,而 Skill 由作者在描述中明确声明相关性,因此检索以“为我编写一些测试”为关键词搜索时,不会找到 Skill 开篇的建议,即“暂勿编写测试”的说明。此外,Skill 以完整结构呈现,每条规则仍附带解释其的示例,而检索仅提供按评分排序的片段,若缺失第一步却执行第三步,效果甚至不如无检索。这种模式仅在整体规模足够小时有效。当达到两千页时,你别无选择,只能检索片段并接受排序结果,这正是检索需要基础设施的时刻。检索适用于知识过载的情形,而 Skill 适用于每项知识都至关重要的场景。
常见混淆点
一旦开始关注,这种混淆在公开资料中频繁出现,且可能向两个方向出错。
真正属于 Skill 的服务器。许多以 MCP 服务器形式发布的系统,本质是披着协议外衣的知识。即使单个工具也能生成服务器:它可以验证输入是否符合模式、持有 LLM 无法看到的凭证,或调用没有终端运行命令的助理。但如果该工具仅封装了助理本就能运行的命令行程序,它唯一增加的价值就是让助理知道该程序存在。Skill 同样能清晰表达这一点——在 SKILL.md 中命名程序并说明使用场景,无需配置、启动或维护进程。
技能本质上是承诺。一个SKILL.md文件中写有“绝不提交秘密”、“删除前必须询问”或“不要读取项目外的文件”这样的内容,不过是一种格式美观的希望。这些规则必须在LLM做出任何决定时都始终有效,因此需要一种能够拒绝的机制:权限系统、钩子函数或沙箱环境。如果仅仅作为指令存在,它只是装饰。
如果一条规则必须为真,它应该被放置在LLM输出必须经过的位置。如果只需被知晓,LLM读取的指令就是合适的工具,此时应停止称其为“安全措施”。
其他流行的规则保护格式
此处仅提及一些内容。受限解码和结构化输出无需任何服务器即可强制执行。你只需在API请求中传递JSON模式或语法规则,当模型生成文本时,任何会破坏规则的下一个词都会被排除在选项之外,从而无法生成无效答案。虽然这能保证答案格式正确,但无法确保答案本身正确。持续集成(CI)门控则是在事后而非事前强制执行:LLM按自己的方式运行,之后构建过程会因错误而失败。在这个项目中,CI是最后的防线,能捕捉到标准变更中的回归问题而非直接执行变更。输出验证器和护栏库处于中间位置,仅执行被要求检查的内容。最后,MCP服务器既能告知也能强制执行。除了本文介绍的工具,模型上下文协议(MCP)还能让服务器提供提示(现成的指令)和资源(LLM可读取的文档)。这些内容以文本形式传递给LLM,因此属于建议,与Skill完全相同;只是服务器端的工具可以运行能拒绝的代码。部署服务器本身并不意味着规则被强制执行:这取决于服务器中哪个部分持有该规则。
进一步阅读
本文描述了我处理一系列当前AI话题的个人经验,并基于Anthropic的免费课程《使用Claude API构建应用》。
Skill有自己独立的免费课程《代理技能入门》。
本文新增的内容来自我实际研究和思考的真实问题:一个用于分类任何LLM规则的问题,一个根据是否告知或强制执行划分的12种常见格式的图谱,以及一个同时部署Skill和MCP服务器的实际案例。
本系列的前几篇文章
第一部分论述了拆分的必要性:当一个AI同时编写代码及其测试时,“绿色测试套件可能毫无意义”。它解释了qikly如何从编码代理从未见过的标准中生成测试。
第二部分跟踪了qikly的一次完整运行,在此过程中,编码代理从未见过的测试捕获了其雷达数据范围检查中的错误。
第三部分介绍了新概念ISDD(独立规范驱动开发),并回顾了谷歌研究团队对SDD的最新测量结果。
关于作者
Gal Arav是《数据科学应用统计学》(qikly.com)的作者,同时也是qikly的维护者,qikly是一个新的开源Python项目,用于规范驱动的测试自动化。
鼓励读者尝试使用该项目,随着代码库的持续演进,任何(人类)反馈都将非常宝贵。
封面说明
封面使用了查理·卓别林和杰克·柯甘在《小流浪汉》(1921)中的剧照,该照片在美国属于公共领域,通过维基媒体公共领域:https://commons.wikimedia.org/wiki/File:Chaplin_The_Kid_4.jpg。封面图像灵感来自我童年房间中这幅图像的复制品。