标签 API稳定性 下的文章

编者按: 我们今天为大家带来的这篇文章,作者的核心观点是:相较于依赖复杂且高成本的动态 MCP 工具加载机制,以 Skills 为核心的能力摘要与自维护模式,在当前阶段反而更加高效、稳定且可控。

文章系统梳理了延迟工具加载(deferred tool loading)的工程现实与限制,指出即便工具可以延后注入,对话级别的工具集合仍然是静态的,且发现机制高度依赖正则匹配,收益并不如预期。作者进一步深入分析了 MCP 在上下文占用、API 稳定性、缓存失效与推理轨迹丢失等方面带来的隐性成本,并结合 Sentry MCP、Playwright 等实践案例,说明为何将 MCP 转换为 Skills,反而能让 Agent 更好地发挥既有工具的能力。文章最后还探讨了 MCP 是否可能完全转化为 Skills 的可行性,并坦率指出当前协议与生态在稳定性与摘要机制上的不足。

作者 | Armin Ronacher

(作者为 Flask、Jinja2 等开源项目的创建者)

编译 | 岳扬

我正把所有的 MCP 都迁移到 Skills 上,包括之前还在使用的最后一个:Sentry MCP(译者注:Sentry 是流行的应用监控与错误追踪平台)。早前我就已经完全弃用 Playwright(译者注:由 Microsoft 开发的现代 Web 自动化测试和浏览器自动化框架),转向使用 Playwright Skill。

过去一个月左右,关于使用“动态工具配置(dynamic tool loadouts)[1]”来推迟工具定义的加载的讨论一直不少。Anthropic 也在探索通过代码来串联 MCP 调用的思路,这一点我也尝试过[2]。

我想分享一下自己在这方面的最新心得,以及为什么 Anthropic 提出的“延迟工具加载方案(deferred tool loading)”并未改变我对 MCP 的看法。或许这些内容对他人会有所帮助。

01 什么是工具(Tool)?

当 Agent 通过强化学习或其他方式接触到工具定义时,它会被鼓励在遇到适合使用该工具的场景时,通过特殊的 token 输出工具调用。实际上,工具定义只能出现在系统提示词(system prompt)中特定的工具定义 token 之间。从历史经验来看,这意味着我们无法在对话状态的中途动态发出新的工具定义。因此,唯一的现实选择是在对话开始时就将工具加载好。

在智能体应用场景中,我们当然可以随时压缩对话状态,或更改系统消息中的工具定义。但这样做的后果是,我们会丢失推理轨迹(reasoning traces)以及缓存(cache)。以 Anthropic 为例,这将大幅增加对话成本:基本上就是从头开始,相比于缓存读取,需要支付完整的 token 费用,外加缓存写入成本。

Anthropic 最近的一项创新是“延迟工具加载”(deferred tool loading)。我们仍然需要提前在系统提示词(system message)中声明工具,但这些工具不会在系统提示词发出时就注入到对话中,而是会稍后才出现。不过据我所知,这些工具定义在整个对话过程中仍必须是静态的 —— 也就是说,哪些工具可能存在,是在对话开始时就确定好的。 Anthropic 发现这些工具的方式,纯粹是通过正则表达式(regex)搜索实现的。

02 与 Skills 的对比

尽管带延迟加载的 MCP 感觉上应该表现更优,实际上却需要在 LLM API 端做不少工程化工作。而 Skills 系统完全不需要这些,至少从我的经验来看,其表现依然更胜一筹。

Skills 实质上只是对现有能力及其说明文件位置的简短摘要。这些信息会被主动加载到上下文中。 因此,智能体能在系统上下文里(或上下文的其他位置)知晓自己具备哪些能力,并获知如何使用这些能力的“手册链接”。

关键在于,Skills 并不会真正把工具定义加载到上下文中。 可用工具保持不变:bash 以及智能体已有的其他工具。Skills 所能提供的,只是如何更高效使用这些工具的技巧和方法。

由于 Skills 主要教的是如何使用其他命令行工具和类似实用程序,因此组合与协调这些工具的基本方式其实并未改变。让 Claude 系列模型成为优秀工具调用者的强化学习机制,恰好能帮助处理这些新发现的工具。

03 MCP 能否转换为 Skills?

这自然引出了一个问题:既然 Skills 效果这么好,我能不能把 MCP 完全移出上下文,转而像 Anthropic 提议的那样,通过 CLI 来调用它?答案是:可以,但效果并不好。Peter Steinberger 的 mcporter[3] 就是其中一种方案。简单来说,它会读取 .mcp.json 文件,并将背后的 MCP 暴露为可调用的工具:

npx mcporter call 'linear.create_comment(issueId: "ENG-123", body: "Looks good!")'

确实,它看起来非常像一个 LLM 可以调用的命令行工具。但问题在于,LLM 根本不知道有哪些工具可用 —— 现在你得专门教它。于是你可能会想:那为什么不创建一些 Skills,来教 LLM 了解这些 MCP 呢?对我而言,这里的问题在于:MCP 服务器根本没有维持 API 稳定性的意愿。它们越来越倾向于将工具定义精简到极致,只为节省 token。 这种做法有其道理,但对 Skills 模式来说却适得其反。举个例子,Sentry MCP 服务器曾彻底将查询语法切换为自然语言。这对 Agent 来说是一次重大改进,但我之前关于如何使用它的建议反而成了障碍,而且我没能第一时间发现问题。

这其实和 Anthropic 的“延迟工具加载方案”非常相似:上下文中完全没有任何关于该工具的信息,我们必须手动创建一份摘要。我们过去对 MCP 工具采用的预加载(eager loading)方式,如今陷入了一个尴尬的局面:描述既太长,不便预加载;又太短,无法真正教会 Agent 如何使用它们。 因此,至少从我的经验来看,你最终还是得为通过 mcporter 或类似方式暴露出来的 MCP 工具,手动维护这些 Skills 摘要。

04 最省事的路线

这让我得出了目前的结论:我倾向于选择最省事的方式,也就是让 Agent 自己以“Skills”的形式编写所需的工具。 这样做不仅耗时不多,最大的好处还在于工具基本处于我的掌控之中。每当它出问题或需要新增功能时,我就让 Agent 去调整它。Sentry MCP 就是个很好的例子 —— 我认为它可能是目前设计得最好的 MCP 之一,但我已经不再使用它了。一方面是因为一旦在上下文中立即加载它,就会直接消耗约 8k 个 token;另一方面,我也一直没能通过 mcporter 让它正常工作。现在我让 Claude 为我维护一个对应的 Skill。没错,这个 Skill 可能有不少 bug,也需要不断更新,但由于是 Agent 自己维护的,整体效果反而更好。

当然,这一切很可能在未来发生变化。但就目前而言,手动维护的 Skills,以及让 Agent 自行编写工具,已成为我的首选方式。我推测,基于 MCP 的动态工具加载终将成为主流,但要实现这一点,可能还需要一系列协议层面的改进,以便引入类似 Skills 的摘要机制,以及为工具内置使用手册。 我也认为,MCP 如果能具备更强的协议稳定性,将大有裨益。目前 MCP 服务器随意更改工具描述的做法,与那些已经固化下来的调用方式(materialized calls)以及在 README 和技能文件中编写的外部工具说明很难兼容。

END

本期互动内容 🍻

❓抛开现有方案,你理想中的AI工具调用范式应该长什么样?用一句话描述你最核心的需求。

文中链接

[1]https://www.anthropic.com/engineering/advanced-tool-use

[2]https://lucumr.pocoo.org/2025/7/3/tools/

[3]https://github.com/steipete/mcporter

原文链接:

https://lucumr.pocoo.org/2025/12/13/skills-vs-mcp/

异步编程的核心矛盾,往往藏在API稳定性与演进张力的隐秘平衡中。多数开发者初次接触asyncio时,容易陷入对表面语法的迷恋,却忽视了其底层接口设计的深层逻辑—那些看似固定的调用方式背后,是一套动态调整的隐性契约。在长期的异步架构打磨中,逐渐发现asyncio的API稳定性并非静态固化,而是通过分层设计实现弹性兼容,核心接口的语义一致性被刻意保留,而扩展功能则以渐进式方式融入,这种演进策略既避免了破坏性更新带来的重构成本,又为新技术场景预留了生长空间。比如在协程调度的实践中,从Python 3.7到3.11的多个版本迭代中,用于创建和运行协程的核心接口始终保持着稳定的调用逻辑,即便底层调度器进行了多次性能优化,开发者无需修改一行代码,就能让旧项目享受到新版本的性能提升。而新增的调度增强功能,如任务优先级调整、协程组批量管理等,则以附加方法或可选参数的形式出现,既满足了复杂场景的需求,又不会对既有代码造成干扰。这种“核心不变、边缘迭代”的思路,正是asyncio能够在快速发展的异步编程领域保持生态稳定的关键,也让众多基于该库构建的项目得以平稳跨越版本周期,无需陷入无休止的重构泥潭。在实际开发中,曾多次经历Python版本的重大更新,从3.8的异步上下文管理器优化到3.10的任务组接口引入,核心业务代码始终未受影响,仅需根据新特性的优势,选择性地在新模块中引入扩展功能,这种平滑过渡的体验,让开发者能够更专注于业务创新,而非被技术迭代裹挟。

理解asyncio API的稳定性,需要穿透接口名称的表象,触及其设计的本质诉求。在异步编程的学习过程中,曾多次遇到不同Python版本间接口行为的细微差异,起初误以为是设计疏漏,深入探究后才发现,这些差异实则是对真实场景的精准适配。asyncio的维护者在演进过程中,始终以“场景驱动”为核心原则,当新的异步需求出现时,并非简单新增接口,而是先评估现有接口的适配潜力,尽可能通过扩展参数或优化内部实现来满足需求,只有当现有接口无法覆盖核心场景时,才会谨慎引入新接口,并为旧接口提供清晰的过渡路径。这种策略在事件循环的相关接口中体现得尤为明显,不同操作系统平台的事件循环实现存在底层差异,比如Windows平台的IOCP模型与Linux平台的epoll模型在处理异步事件时的机制截然不同,但对外暴露的核心接口始终保持一致,开发者无需关注底层实现细节,只需基于统一接口进行开发。例如在处理网络连接时,无论是在Windows还是Linux环境下,创建异步套接字、注册读写事件的接口调用方式完全相同,底层会根据平台自动适配最优实现。此外,在异步IO的缓冲处理、连接池管理等场景中,也能看到这种场景驱动的设计思路,比如某个用于数据接收的接口,通过新增“缓冲阈值”参数,既支持了高并发场景下的内存优化,又没有改变原有调用逻辑,让旧项目无需修改即可兼容。维护者们往往会通过社区调研、实际项目案例分析、开发者访谈等多种方式,收集不同场景下的使用痛点,再将这些需求转化为接口的优化方向,这种源于实践、服务实践的设计理念,让asyncio的API始终保持着强大的场景适配能力。

asyncio API的演进过程,本质上是社区共识与技术创新的动态平衡。在长期跟踪其版本更新日志与社区讨论的过程中,发现每一次接口调整都经过了充分的实践验证与意见征集。维护者会优先采纳来自大规模实践场景的反馈,那些在真实异步架构中被频繁使用、且被证明稳定可靠的模式,往往会被固化为标准接口,而一些实验性的功能则会以临时接口或扩展模块的形式存在,待其在社区中经过充分验证、积累足够多的使用案例后,再逐步整合到核心库中。这种“实践先行、共识后定”的演进模式,使得asyncio的API能够始终贴合开发者的真实需求,避免了过度设计或脱离实际的问题。例如在协程任务管理相关接口的演进中,社区曾围绕任务取消的时机、状态查询的粒度、异常传播的机制等问题展开长达数月的讨论,来自网络编程、异步爬虫、微服务架构等不同领域的开发者,纷纷分享了自己在实际项目中遇到的痛点——有的开发者需要精确控制任务取消后的资源释放,有的则希望简化任务组的管理逻辑。维护者基于这些反馈,反复打磨接口设计,最终推出的任务组接口,既支持批量创建和管理任务,又提供了灵活的异常处理机制,同时保持了与原有任务接口的兼容性。而像早期的异步文件IO功能,由于场景需求尚未完全明确,且实现方式存在争议,便以 aiofiles 这样的第三方扩展模块形式存在,待技术方案成熟后,才逐步将核心能力整合到asyncio中。长期以来,通过订阅asyncio的社区邮件列表、参与GitHub上的issue讨论,深刻体会到这种社区共建的力量,每一个接口的优化都凝聚着众多开发者的实践智慧,这也让asyncio的API在保持稳定性的同时,始终充满创新活力。

判断asyncio API的稳定性,需要建立一套基于场景适配度的评估框架,而非单纯依赖版本号或官方标注。在异步编程的实践中,逐渐总结出三个核心评估维度:接口使用频率、社区讨论热度与场景覆盖广度。那些被广泛应用于各类异步场景、社区讨论中争议较少、且能够适配多种业务需求的接口,往往具备更高的稳定性,其被废弃或变更的概率极低;而那些仅适用于特定场景、使用频率较低的接口,则可能随着场景的变迁而被优化或替换。具体来看,接口使用频率可以通过GitHub上的项目引用量、技术博客中的提及次数来判断,比如用于创建事件循环的核心接口,在数百万个异步项目中被引用,其稳定性不言而喻;社区讨论热度则体现在Stack Overflow的提问量、社区issue的关闭速度上,稳定的接口往往提问量少且问题多为使用误区,而非接口本身的设计缺陷;场景覆盖广度则表现为接口能否适配从简单异步脚本到复杂分布式系统的不同需求,比如某个用于异步任务同步的接口,既能满足小型爬虫的任务协调,又能适配大型微服务的跨节点通信,其稳定性自然更有保障。同时,还需要关注接口的语义一致性,真正稳定的API不仅接口名称与参数格式保持不变,其背后的行为逻辑与异常处理机制也会保持连贯,开发者能够基于过往经验放心使用,无需担心版本升级带来的行为突变。比如在处理异步连接超时的接口中,无论版本如何更新,其超时触发的条件、异常抛出的类型始终保持一致,即便底层实现进行了优化,开发者也无需调整异常处理逻辑。曾在项目中面临两个功能相近的接口选择,通过这套评估框架发现,其中一个接口使用频率高、社区争议少、适配场景广,而另一个则仅适用于特定的异步IO场景,最终选择了前者,后续历经三次Python版本升级,该接口始终保持稳定,避免了因接口变更导致的维护成本增加,这也让这套评估框架的实用性得到了充分验证。

应对asyncio API的演进,开发者需要构建一种“弹性适配”的编程思维,在依赖稳定接口的同时,为潜在的变更预留缓冲空间。在实际开发中,可通过抽象封装的方式隔离具体接口的调用细节,将核心业务逻辑与底层API解耦,比如构建一层异步工具封装层,所有对asyncio接口的调用都通过该层完成,封装层内部定义统一的抽象接口,底层根据不同Python版本或API状态,实现对应的适配逻辑。例如在封装异步任务提交接口时,抽象层定义 submit_task 方法,底层在Python 3.10及以上版本中,使用新增的任务组接口实现,而在低版本中,则使用传统的任务创建接口兼容,业务层无需关注底层实现差异,只需调用抽象层方法即可。同时,还应养成跟踪社区动态与版本更新的习惯,提前了解接口的演进规划,比如通过阅读Python的官方PEP文档、关注asyncio的版本更新日志、参与社区讨论等方式,及时掌握哪些接口被标记为待废弃、哪些新接口即将引入,对于标记为待废弃的接口,尽早制定替代方案,避免在版本升级时陷入被动。此外,合理利用官方提供的兼容工具与过渡接口,也是应对演进的有效策略,官方在废弃旧接口时,往往会提供一段时间的过渡期,并推出兼容模块或过渡接口,帮助开发者平滑迁移。比如在某次版本更新中,某个核心的异步调度接口被标记为废弃,官方同时提供了功能兼容的过渡接口,并在文档中详细说明了迁移步骤,通过封装层的适配,仅修改了封装层内部的实现逻辑,业务代码未做任何调整,就完成了版本升级,且未影响线上业务的稳定运行。这种弹性适配的思维,不仅适用于asyncio的使用,也同样适用于其他快速演进的技术栈,通过构建抽象层、跟踪技术动态、利用兼容工具,能够帮助开发者在技术迭代的浪潮中保持架构的稳定性与可扩展性,减少因API变更带来的业务冲击。

asyncio API的稳定性与演进策略,为异步编程领域提供了一套可借鉴的设计范式,其核心在于在创新与兼容之间找到精准的平衡点。从早期的接口探索到如今的成熟稳定,asyncio的演进之路充满了社区的智慧与实践的沉淀,每一次接口的调整与优化,都体现了对异步编程本质的深刻理解—异步编程的核心价值在于提升IO密集型场景的效率,而API的设计则需要为这种价值的实现提供稳定可靠的支撑,同时兼顾技术的持续创新。对于开发者而言,深入理解这套演进策略,不仅能够更好地使用asyncio构建可靠的异步系统,还能从中汲取技术设计的灵感,在自己的项目中实现功能创新与架构稳定的和谐共存。比如在设计内部异步框架的API时,借鉴asyncio的分层演进思路,将核心功能(如任务调度、事件循环)的接口保持稳定,确保现有业务不受影响,而扩展功能(如分布式任务协调、高性能IO优化)则通过插件化或扩展模块的形式实现,既满足了业务的多样化需求,又避免了API的碎片化。在实际的框架设计中,核心的任务提交、结果获取接口始终保持不变,而新增的任务优先级控制、资源限制等功能,则以可选参数或扩展类的形式添加,让旧业务无需改造即可使用新功能,新业务则能根据需求灵活选择。