
做 AI 应用的人应该都有过这种体会功能越加越多代码越堆越乱。一开始只是几个工具函数后来变成几十个 API 封装再后来连自己都分不清某个技能到底在哪个模块里。我去年在做一个内部项目时专门设计了一套名为 HagiCode Skill 的技能管理平台核心目标就一句话让 AI 能力的增加、替换、下线都像装插件一样简单而不是每次都要改主程序、重新部署。这篇文章把整个系统的设计思路、核心实现和踩坑过程完整拆开给想自己做技能管理平台或正在被“模型工具越接越多”困扰的同学一个可以直接抄作业的参考。这套系统做的事本质上就是把“模型能调用的能力”抽象成标准化的技能单元通过注册、调度、执行、监控一套机制来统一管理。它适合三类人看一是做 Agent 或 AI 应用开发、被工具管理搞得焦头烂额的开发者二是想做平台化产品、需要给第三方提供技能接入能力的技术负责人三是单纯想搞懂大型语言模型工具调用机制、想理解“技能系统”到底在解决什么问题的学习者。我会从架构设计讲到代码实现再讲到我实际运行中踩过的坑尽量把每个决策背后的原因都说清楚。1. 整体设计思路先想清楚要解决什么问题1.1 没有技能系统时AI 接入能力是什么状态我见过不少团队的 AI 应用模型能调用的工具从几个增长到几十个之后代码就开始失控。最常见的形态是一个巨大的工具注册文件里面堆满了函数定义和对应的大模型函数描述也就是 function calling 里的 schema新增一个能力就要去改这个文件测试要全量回归部署要整个服务重启。更麻烦的是团队里几个人同时改这个文件合并冲突能让人崩溃。这还不是最痛的。真正的问题在于这些“能力”和业务逻辑是强耦合的。比如某个工具函数里直接读了配置文件、直接连了数据库你很难把它单独拆出来给别的项目复用。时间一长所谓“AI 能力”就变成了一锅粥谁也理不清边界。1.2 技能系统带来的核心转变HagiCode Skill 想做的转变是把“能力”从“代码里的函数”提升为“平台里的资源”。每个技能都是一个独立的包有自己的元数据、依赖、执行入口甚至可以有独立的版本。主程序不再关心技能内部怎么实现只通过统一的接口去调用它。这个转变带来的直接好处有三个。第一新增技能不需要改主程序往技能目录里放一个包、调一次注册接口就行第二技能可以独立版本迭代出了问题的技能单独回滚不影响其他能力第三技能的边界强制清晰因为它必须按照约定的接口实现否则根本注册不进去。1.3 什么才算“可扩展”很多系统说自己可扩展实际上只是把代码写成了插件式结构。我的判断标准比较朴素不加一行主程序代码能不能完成下面四件事接入一个新技能类型、替换某个技能的实现、给所有技能加一个统一的行为比如全链路日志、让技能按维度分组并控制调用权限。如果这四件事需要动到核心代码那就不叫可扩展。HagiCode Skill 在架构上花了最多精力的就是保证这四个场景全部通过配置和扩展点完成。后面我会展开讲实现方式。2. 核心架构拆解技能系统的四层结构2.1 分层模型概述整个 HagiCode Skill 分为四层接入层、注册中心、执行引擎、技能包仓库。接入层负责接收外部请求和模型调用注册中心保存所有技能的最新元数据和状态执行引擎是运行时的心脏负责把一次技能调用从“技能名 参数”变成真实执行结果技能包仓库则是存放所有技能包文件的地方可以是本地目录也可以是对象存储。这四层各干各的活互相之间只通过明确的接口通信。接入层不关心技能怎么执行注册中心不关心技能实现细节执行引擎不关心请求从哪来。这样划分最难的部分在于接口边界的定义一旦定义清楚后面扩展就顺畅了。2.2 技能包结构与元数据设计一个技能包在 HagiCode Skill 里通常就是一个目录包含 manifest.json、entry.py或任意语言入口、依赖声明和资源文件。manifest.json 是技能的身份证明它声明了技能名称、版本、描述、入口函数、需要的权限、超时时间、以及暴露给模型的功能描述。manifest.json 的设计非常关键它决定了后续一切管理能力的基础。我用一个最小示例来说明{ name: weather_query, version: 1.2.0, description: 查询指定城市当前天气, entry: main:query_weather, timeout: 3000, permissions: [http:api.weather.example.com], schemas: { input: { type: object, properties: { city: {type: string, description: 城市名} }, required: [city] } } }这里的 schemas.input 在注册时会被自动转换成大模型 function calling 所需的 JSON Schema不用像传统做法那样在代码里手写两遍。我实践下来这套“一份 schema两个用途”的设计让技能开发者的工作量少了一大截也不容易出现模型描述和实际参数不一致的问题。2.3 注册中心的实现要点注册中心实际上是一个内存态的服务索引它维护着“技能名 - 技能实例”的映射。为什么不用数据库做主存储因为技能注册信息的变化频率不高但读取频率极高每次模型调用都要查。内存态配合定期持久化性能和可靠性都可以兼顾。注册中心对外提供三个核心接口register注册新技能、unregister下线技能、resolve根据技能名和版本解析出具体的执行入口。注册时要做的关键事情是校验 manifest 的合法性包括 schema 格式、入口函数是否存在、依赖是否满足。这步没做好后面执行时才会爆雷所以前置校验必须严格。3. 技能生命周期管理从注册到下线3.1 注册、更新与版本策略我用最朴素的字符串版本号方案格式是 x.y.z遵循语义化版本。变化规则也很简单修复 bug 升 patch新增兼容功能升 minor破坏性变更升 major。注册中心会把同一个技能的多个版本都保留着但默认只暴露“当前稳定版本”给模型调用需要灰度时可以按调用方维度指定版本。这里有个我强烈建议的做法不要在 manifest 里写“兼容的最低主版本”因为实际维护中很少有人认真更新它。更好的做法是注册中心自动记录每个版本的 API Schema 指纹发布新版本时自动对比如果模型侧的 input schema 发生了变化必须显式确认是改进还是破坏系统再决定是否允许覆盖式发布。这个机制多写不了多少代码但能挡住一大批事故。3.2 技能安全与隔离技能系统最麻烦的问题之一是安全性——你执行的是第三方能力代码它可能写文件、开网络、占内存。HagiCode Skill 在隔离上分了三个梯度。第一个梯度是权限声明manifest 里明确声明技能需要哪些权限比如只允许访问特定域名、只允许读写特定目录。执行引擎在启动技能进程或线程时强制注入这些限制超出声明范围的操作直接拒绝。第二个梯度是进程级隔离对风险较高的技能用独立进程或容器执行通过消息通信拿结果。代价是单次调用会多出几十毫秒开销但换来的是真正的故障隔离——一个技能把系统搞崩了其他技能毫发无损。第三个梯度是资源限额每个技能都有独立的超时时间、内存上限、单次调用次数上限。实现上Python 环境我用资源和信号限制容器环境就直接用 CGroup 限定额度。这套三层结构并行的方案让我在安全性和性能之间找到了一个比较舒服的平衡点。3.3 健康检查与熔断技能上线后不是一劳永逸。HagiCode Skill 会给每个技能加健康检查和熔断机制。健康检查很简单每隔一段时间用最小参数集调用一次技能的 ping 接口连续失败超过阈值就把技能标记为不健康并从路由表中摘除。熔断则是应对突发故障的当某个技能的错误率在 10 秒窗口内超过 50%执行引擎会自动打开熔断开关后续请求直接走降级策略比如返回预设兜底文案或路由到备用技能不再实际执行。这样做的目的不是修复技能而是防止一个慢技能拖垮整个调用链路。我在实际运行中熔断阈值设为 50%、恢复窗口设为 30 秒效果比较理想。4. 执行引擎把技能调用变成可靠的服务4.1 执行流程与上下文传递执行引擎收到一次技能调用的请求后会经过解析、鉴权、路由、执行、返回五个步骤。一个值得注意的设计是上下文对象。每次调用引擎会构造一个 SkillContext里面封装了调用方信息、链路追踪 ID、超时控制信号、以及模型对话历史摘要。技能函数可以直接从 context 里读取这些信息不需要到处传参。为什么要把上下文单独抽出来因为我遇到过一个很现实的场景技能执行到一半上层的用户已经取消请求如果技能还在跑数据库查询既浪费资源又可能写脏数据。所以在 context 里放了一个取消信号技能内部在关键的耗时代码处主动检查这个信号。虽然这个“协作式取消”没有强制中断那么简单粗暴但在可控性上强很多技能可以保证自己不留下半截状态。4.2 动态加载机制与语言选型技能包有几种加载方式进程隔离执行、线程池内动态导入、以及外部服务化。HagiCode Skill 主要用的是前两种根据技能类别自动选择。如果是 Python 技能包动态导入看起来很简单直接 importlib.import_module 就行。但这里有个大坑卸载模块很麻烦Python 没有官方支持的 unload 机制旧模块对象会一直占内存。我在 2.x 版本里踩过这个坑几千次技能更新之后内存涨得吓人。我的解决思路是把“技能包的加载”和“技能实例的管理”分开。真正干活的是一个独立的 Python 解释器进程池每个技能包在一个子进程中加载。更新技能时直接杀掉旧进程、用新代码拉一个新进程。这样既实现了“看起来是热更新”又绕开了 Python 模块卸载的问题。代价是进程间需要序列化消息对性能敏感的读者要注意控制传参的体积。如果团队主语言是 Java可以走 Java 的类加载器隔离方案URLClassLoader 加自定义类加载策略每次技能版本更新就换一个新的类加载器。这个方向的坑在类加载器泄漏后文会专门讲。4.3 把技能暴露给大模型的协议设计HagiCode Skill 一个核心功能是让大模型能正确调用技能这里的关键是把每个技能描述成符合模型工具调用规范的格式。现在主流模型比如 OpenAI 兼容接口、百炼、通义等都支持类似 function calling 的机制区别只在请求格式的细节。执行引擎里做了一个轻量的协议适配层把注册中心里的技能元数据实时转换成目标模型要求的格式。比如模型侧推 OpenAI 风格就用 tools 参数推某些国产模型就用它们自己的 tool 字段。这一层让技能平台跟模型解耦模型招到主程序之外再调整也是可扩展性的重要来源。这里我最想强调的一点是技能描述要写清楚“什么时候用”以及“参数之间的依赖关系”。大模型不是人不会看图说话描述越模糊它就越可能误用。比如一个技能“查询多个城市天气”必须在描述里写清楚“城市参数最多传 10 个用逗号分隔”而不是让模型自己猜。4.4 性能预算与资源治理技能不可能无限快但执行引擎要做的是把开销“可预期”化。我在系统里给每次技能调用都设了三个硬指标排队时间不超过 100ms实际执行不超过 manifest 声明的超时时间整个链路从接入层收到请求到返回结果不超过技能超时时间加 500ms。为了达到这三个指标执行引擎用了有界线程池。技能提交任务时如果线程池满了不是无限排队而是快速失败返回“当前技能负载过高请稍后重试”。这一招在流量尖峰时特别好用它把“所有请求都卡住”变成“一部分请求成功一部分请求快速失败”整体的用户体验反而更好。5. 常见问题与排查实录5.1 技能包更新后的内存泄漏这个坑在前面提过Python 子进程方案下最关键的是确认旧进程真的被回收了。有一次我发现内存持续上涨排查了半天发现是子进程还有定时器线程在跑导致进程退出前被强杀会留下僵尸状态。解决办法是技能进程统一提供 shutdown 方法收到退出信号先清理资源超时再强杀。另外一个容易忽略的地方是依赖包版本。技能更新后如果依赖了一版新的三方库而旧进程还在运行时两者会同时存在。因此我在系统里加了强制规则主版本更新必须走双实例模式等新的健康检查通过后才允许回收旧实例。5.2 技能间的命名与描述冲突多个技能都叫“查询用户信息”的情况很常见它们可能面向不同业务域。直接按名字注册后面的会把前面的覆盖掉。我用了“命名空间技能名”的命名规则比如“crm:query_user”和“erp:query_user”。这么做虽然让名字长了一点但避免了覆盖冲突也让调用方在指定技能时能明确领域。还有一个我遇到过的问题两个技能的功能相近大模型在选择时总是选错。后来我把每个技能描加了“适用场景”和“与另一技能的差异”模型选错率明显下降了。这件事上别嫌麻烦描述本身就是性能的一部分。5.3 模型幻觉导致的工具误调用模型幻觉是一个绕不开的话题即使技能描述写得再好模型也有可能在不需要工具的时候调用工具或者用明显错误的参数调用。我在平台层加了两种防护。第一调用前置校验执行引擎在真正执行前会用 JSON Schema 校验模型传进来的参数必填项缺失、类型不对直接返回“参数校验失败请修正后重试”然后把这个错误结果反馈给模型让它自己纠正。这个机制成本极低但能拦截掉大量错误调用。第二调用审计与回滚系统记录每一次技能调用的完整入参、出参和模型上下文片段。当发现某类错误调用反复发生时就可以针对性修改技能描述或调整模型参数。如果是一个技能本身质量太差直接回滚版本就行。5.4 排查技巧与工具清单技能系统调试的难点在于链路长模型发起、引擎调度、技能执行、外部依赖返回每一步都可能出问题。我的习惯是第一步就查链路 ID。整个系统在刚开始就统一植入了链路追踪每次请求生成一个 trace_id打印日志时带上它。没有这个基础排查分散在几十个服务里的日志会痛不欲生。排查顺序上我一般是先看监控面板技能调用成功率、P99 延迟、资源使用率。如果指标异常再去日志系统按 trace_id 拉全链路日志。遇到外部依赖超时先在技能内部分析是网络问题还是依赖本身问题。这里有个小技巧给技能平台配置一个“诊断模式”开启后会对技能调用的入参出参和耗时做全量日志记录平时只记录采样子集避免日志量过大。6. 扩展与演进从技能管理到技能生态6.1 技能分组与权限模型当技能数量超过五十个时简单的平铺列表就不够用了。我引入了技能组的概念相当于给技能加了一个领域标签比如“数据分析组”“消息通知组”“风控能力组”。模型调用时可以按组来约束也可以按组来做测试和发布。权限模型也建议和技能组绑定。内部使用场景下可以为每个团队分配他们能调用的技能组集合。当一个技能从一个组挪到另一个组时系统自动通知相关调用方确认避免权限扩散。6.2 技能市场化的思考如果平台稳定运行下一步顺其自然就是技能商店。技能开发者可以把技能发布到仓库平台提供统一的定价、计量、质量评分机制。这个方向技术难度不大难的是治理规则怎么保证技能质量、怎么处理技能对第三方数据的依赖、怎么处理多个技能之间的协作编排。我的建议是先从内部技能共享做起让不同团队把重复建设的能力沉淀为平台技能积攒出足够多的优质技能之后再考虑对外开放。步子太大会死在治理上。6.3 后续可以做什么就我个人经验来说这个系统后续至少有四个有价值的扩展点第一技能编排把多个技能按 DAG 结构组合成复杂工作流第二技能自学习根据历史调用记录自动调整技能描述和参数提示第三跨集群部署技能的注册信息在服务网格里同步让技能可以在多个机房就近执行第四技能评估体系建立一套离线评测集技能更新发布前自动跑一遍评测。每一个都够单独开一篇来写。回到最初的问题可扩展的 AI 技能管理平台到底怎么打造我的体会是技术上没有玄学就是边界清晰、协议标准化、生命周期管理严格。真正的难点在于你愿不愿意为“新增能力不动主程序”这个目标持续做减法愿不愿意把每个技能当作一个正式的软件产品来对待。HagiCode Skill 这套系统做到现在回头看最有价值的不是哪段代码而是这一整套让技能有序生长的方法论。希望这篇拆解能给你一些启发哪怕只有一个设计点能用到你的项目里就不算白写。