1. 为什么需要一套技能后台维护机制
先说个场景。某个 AI 助理系统的技能库里,技能数量从几十个涨到几百个,维护成本就开始失控了。新技能照常上线,旧技能没人敢删,因为不知道还有没有业务流程在依赖;有些技能只是某个版本被替换过,旧版本却被“遗忘”在仓库里;还有一些技能从上线那天起就没有被调用过一次,却还占着索引、占着文档、占着每次加载时的内存开销。我当时面对的就是这种局面——单纯的“定期人工清理”根本撑不住,因为清理本身需要知道每一条技能的调用历史、依赖关系、语义相似度,这些信息分散在日志、代码仓库、配置中心和文档里,靠人肉翻查完全不现实。
这也是我做 Hermes Curator 的出发点。它本质上是一个技能后台维护机制,专门解决 AI 助理场景下技能生命周期管理的问题。核心思路非常直白:不靠“某个月末大扫除”,而是把维护动作拆成两个相位——自动归档相位负责发现该退场的技能、执行归档并保留可回滚记录;LLM 整合相位负责把归档结果、技能间的语义关系、维护建议全部沉淀成可供人查询和决策的信息。两个相位交替运转,形成一条“采集信号—判定状态—归档处置—生成建议—人工复核”的闭环。
这篇文章适合两类人。一类是正在维护 AI 助理技能库、插件库或工具库的后端开发者,你们大概率遇到过“技能越加越多,系统越来越慢,谁都不敢删”的困境;另一类是对 LLM 在系统工程中的应用感兴趣的同学,Hermes Curator 里用 LLM 的方式不是做一个聊天机器人,而是把它当做一个“结构化的后台分析引擎”,这种用法对生产系统更有参考价值。文章后面会给出完整的模块拆解、判定规则和实操记录,按步骤就能复现一版属于自己的技能维护系统。
2. 双相位设计的核心思路:为什么“自动”和“智能”要分开
2.1 自动归档相位:让过期技能自己“退场”
自动归档是整个系统的底盘,它的职责只有一个——产生一个可靠的“该归档了”的结论。这里说的可靠,是指结论可以解释、可以复核、可以回滚,而不是一个黑盒。
我把技能的状态定义成五种:活跃(Active)、待观察(Watching)、建议归档(Archive_Candidate)、已归档(Archived)、已删除(Deleted)。归档相位做的事情,就是把技能从活跃或待观察推向建议归档,并把建议归档的技能真正搬进归档存储区。
为什么需要“待观察”这一层?这是踩坑踩出来的教训。最开始我设计的是“活跃 / 已归档”二态模型,结果误归档率很高:某个季度性的技能(比如双十一大促相关的优惠计算技能)在非活动期调用量为零,直接被判定为废弃,等到活动期重新启用时才发现归档流程虽然支持回滚,但回滚前的重新上线审批还是耽误了一天。后来加入待观察状态,让零调用技能先进入一个观察窗口,窗口期内只要有一次调用就会自动复活,这才算解决了误杀问题。
自动归档相位还承担一个关键动作:把归档行为本身记录成事件流。每次归档都写入一条记录,包含归档触发原因(规则名 + 具体分数)、操作时间、责任人(系统代触发还是人工触发)、归档前状态哈希。这样后续任何一次“这个技能为什么不见了”的疑问,都能直接回溯到具体事件上。
2.2 LLM 整合相位:让维护从“人肉”变“智能”
自动归档解决的是“明确该退场的”,但技能库里还有大量“灰色地带”技能——它们调用量不高,但语义上很重要;它们和另一个技能功能重叠,但各自又有独特参数;它们的使用频率在下降,但原因可能是新技能上线后的自然分流,而不是技能本身失效。这些判断如果全部靠人工,每周都得搭进去一整个下午。LLM 整合相位就是为这些灰色地带服务的。
这个相位不直接替人做决定,而是做三件事:语义分析、维护建议生成、自然语言查询。
语义分析用的是 embedding 相似度。我定期对所有技能描述做向量化,计算两两相似度,把相似度过高的技能对抽出来,让 LLM 判断是“应该合并”、“应该区分”还是“误报”。维护建议生成则是把技能的使用统计、状态流转历史、依赖关系、相似技能列表拼成一个结构化上下文,让 LLM 产出一段维护建议,比如“技能 X 已被技能 Y 在 90% 的调用场景中替代,建议在观察 30 天后归档”。自然语言查询则是给维护人员开一个口子——不需要会写 SQL,直接输入“哪些技能三个月没被调用过但还在被其他技能依赖”,LLM 负责转成查询计划并返回解释。
这里有一个设计原则:LLM 永不直接触发归档动作。LLM 的输出只是“建议”,归档动作只能由自动归档相位里的规则引擎执行,或者由人工确认后执行。这个约束是安全底线,因为 LLM 幻觉在生产环境里是真实风险,一次错误的删除可能影响线上业务流程。把“建议”和“执行”彻底分开,才能既享受智能化带来的效率,又保住系统的可控性。
3. 关键模块的实现细节与判定规则
3.1 技能元数据模型:所有判断的基础
归档判断、语义分析、依赖分析都建立在元数据之上。如果元数据采集不全,后面的规则和 LLM 都等于在盲人摸象。我给每条技能定义的元数据大致是这样的结构:
{ "skill_id": "skill_order_price_calc_v3", "name": "订单价格计算器 v3", "version": "3.2.1", "status": "active", "registered_at": "2024-05-12T10:00:00Z", "last_called_at": "2025-01-08T15:33:21Z", "call_count_30d": 1284, "call_count_90d": 4023, "avg_latency_ms": 236, "error_rate_30d": 0.0012, "owners": ["team_checkout"], "depends_on": ["promo_engine_v2", "currency_convert_v1"], "depended_by": ["checkout_flow_v3", "order_api_gw"], "description": "计算订单最终价格,支持优惠券叠加与多币种转换", "similar_skills": ["skill_order_price_legacy_v1", "skill_order_price_fast_v2"], "tags": ["order", "price", "checkout"] }这里面有几个字段容易被忽略,但实际很重要。depended_by是反向依赖关系,归档时如果没有检查它,可能出现“父流程还在调用、子技能却被归档”的线上故障。similar_skills不是人工维护的,而是由定时任务跑 embedding 相似度后自动回填的。error_rate_30d用于识别另一种情况:有些技能不是没人用,而是报错率太高被上层逻辑降级绕开了——这类技能如果只看调用量会误判成“低频废弃”,实际上它是“故障待修”。
我会要求所有技能在注册时必须提供depends_on,否则不予上线。这个强制校验一开始被吐槽“增加接入成本”,但后来发生了一次事故——某个技能归档时发现它有二十多个隐藏依赖没有被声明,导致按依赖顺序回滚时一团乱麻。从那以后团队就统一了认知:依赖声明不是文档任务,是运行时的安全边界。
3.2 自动归档的判定规则:分数量化 + 窗口确认
自动归档相位核心是一个打分规则引擎。每条技能每 24 小时跑一次评分,分数超过阈值才会进入候选队列。我用的公式是加权求和:
archive_score = w1 * staleness_score + w2 * dependency_risk_score + w3 * similarity_score三个分项含义各不相同。
staleness_score(活跃度衰减分)考察调用量变化趋势。我把近 90 天拆成三个 30 天窗口,分别计算调用量,然后按时间距离加权。公式是:
staleness_score = (last_30d_count * 0.5 + mid_30d_count * 0.3 + first_30d_count * 0.2) / expected_daily_callsexpected_daily_calls是技能注册时预估的日调用规模。比如一个支付回调技能预估每天 10000 次,但最近 30 天平均只有 15 次,衰减分就会靠近 1。这里用“比率”而不是“绝对值”,是为了避免不同量级技能之间的误判——低频但正常的技能(比如每月一次的月结报表技能)不会被绝对值阈值误杀。
dependency_risk_score(依赖风险分)计算的是被其他活跃技能依赖的程度。被人依赖的技能风险分低,即使调用量暴跌也不建议立刻归档。这个分项我采用了负向减分机制:
dependency_risk_score = base_score - 0.3 * count(active_dependents)base_score初始值设成 0.5,意味着只要有一个活跃的上游依赖,风险分就会降到 0.2,归档总分数大幅降低。这个设计确保归档操作不会破坏线上流程。
similarity_score(语义重叠分)来自 embedding 相似度计算。如果技能 A 和技能 B 的 description 向量余弦相似度超过 0.85,且 A 的调用量只有 B 的十分之一,说明 A 很可能是 B 的旧版本或功能子集。这个分项专门捕捉“功能被替代但没人手动下线”的情况。
最终判定规则如下:
- 总评分超过 0.75:进入建议归档队列;
- 连续 7 天在建议归档队列中且期间无任何调用:正式执行归档;
- 观察期内出现一次调用:立即移出队列,评分重新计算。
最后这条“调用即复活”规则,解决了我之前说的季度性技能误杀问题。另外还有一个后台兜底策略:即使评分规则全部通过,如果depended_by中还有活跃状态的父技能,归档动作会被强制中断并生成告警事件,等待人工确认。
3.3 LLM 整合相位的调用链路与提示词设计
LLM 整合相位不是简单“把数据扔给大模型”,而是有清晰的调用链路。我把它分成四步。
第一步是定期向量化。这里有个坑:不是每次跑分析都重新对全部技能做 embedding,那样成本太高。我的做法是只在技能注册、描述变更或归档状态变更时触发单条向量更新,全量重算每七天一次作为校准。向量存在独立的向量数据库里,和技能元数据存储分离。
第二步是候选对生成。对全量向量做相似度检索,筛选出相似度大于阈值的技能对。这里阈值要谨慎,我最初设的是 0.80,结果每天生成几百个候选对,噪音太大;调到 0.88 之后才比较合理,剩下的候选对基本都是有真实重叠关系的技能。
第三步是让 LLM 产出结构化建议。每个候选对先被拼成一个上下文片段,包含两个技能的元数据摘要、调用量对比、依赖关系、共同标签。提示词要求 LLM 输出 JSON,格式固定:
{ "pair_id": "skill_a|skill_b", "relation_type": "merge_into_b | merge_into_a | split_keep | false_positive", "confidence": "high | medium | low", "reason": "一句话解释判断依据", "suggested_action": "建议保留哪个、归档哪个、或都保留" }split_keep这个类别很重要,它表示两个技能虽然语义相似但面向不同场景,应当同时保留。最初我只有合并和建议归档两种输出,后来发现很多技能对看起来像重复,实际上服务不同业务流程,如果规则只给合并选项,就等于诱导 LLM 给出激进结论。增加“保持分离”选项后,误判率明显下降。
第四步是把建议写入维护工单池。LLM 输出不会直接触发任何归档,而是汇总成一份“待人工复核的维护建议列表”,按置信度排序,高置信度且 relation_type 为合并的排在前面。人工确认后,对应的技能才会进入自动归档的正式流程。
自然语言查询功能相对简单一些。我把技能元数据表的字段和常用过滤条件整理成 schema 描述,直接让 LLM 把用户问题转成查询语句。但生产环境里我不会允许 LLM 直接执行查询,而是让它输出结构化的查询意图(涉及哪些字段、需要哪些聚合、时间范围是什么),由我自己的代码去执行。这样即使用户问题里有模糊表述,也不会产生错误的查询行为。
4. 落地实操:从零搭起一套可运行的 Curator
4.1 基础架构与组件选型
Hermes Curator 我拆成四个模块:采集模块(从调用日志和注册服务收集元数据)、判别模块(规则引擎跑分 + 状态机流转)、分析模块(向量化 + LLM 调用 + 工单生成)、处置模块(归档执行、备份、回滚)。
存储方面分成三块:PostgreSQL 存技能元数据和状态流转记录,Redis 存实时调用计数,向量库存 embedding。这三个存储各有各的用途,不需要统一到一个引擎里。日志采集通过消息队列接入,技能每产生一次调用就异步上报一条调用事件,采集模块聚合后更新 Redis 计数,每天凌晨再批量刷回 PostgreSQL 的长周期统计表。
这个架构不算复杂,但胜在每个模块职责单一。判别模块只消费统计数据和元数据,不直接碰日志;分析模块只读数据并产出建议,不直接操作归档;处置模块只接收明确的归档指令。模块间通过事件总线解耦,归档事件发出后,处置模块消费并执行。
4.2 核心流程跑通的关键步骤
复现这套系统,我会建议按下面的顺序推进,每一步都有明确验收标准。
第一步,先做元数据规范化。写一个工具扫描现有技能库,检查每条技能是否满足字段完整性要求,重点看depends_on和last_called_at有没有缺失。这一步没什么技术难度,但如果不做,后续所有规则都是空谈。验收标准是:核心技能元数据完整率达到 99% 以上。
第二步,搭建规则引擎。我用的是纯代码实现打分函数,没有引入规则引擎框架,因为归档规则量小、变化频率低,函数式实现反而更容易调试和测试。打分函数是纯函数,输入元数据,输出分数和得分拆解,方便定位“为什么这个技能得了高分”。验收标准是:对一批人工标注过的历史技能,打分结果与人工判断的一致率达到 85% 以上。
第三步,接通向量化和候选对生成。这一步需要跑通 embedding 服务和相似度检索。我用了一个小的定时任务,每小时扫描一次新增或变更的技能描述,更新向量。候选对生成脚本每天跑一次,结果写入一张skill_similarity_pairs表。验收标准是:候选对中确属真正重叠关系的比例(精确率)不低于 60%。
第四步,接入 LLM 建议生成。构造提示词模板,定义 JSON 输出格式,接入模型服务。这里要注意,生产环境不能省掉超时控制,LLM 调用必须设置超时和重试上限,否则某个模型服务抖动会拖垮整个分析流程。验收标准是:模型返回的 JSON 可以 100% 被解析,且 relation_type 分布在各项之间没有偏移。
第五步,打通归档执行链路。归档不是一个“删除”操作,而是“状态变更 + 冷存储搬迁 + 事件记录”。我实现的是软归档:技能状态标记为已归档,可执行代码从热加载目录移到归档存储桶,元数据保留在主库但打上归档标记,同时生成一份归档快照。回滚时从快照恢复,整个过程不删任何原始数据。验收标准是:归档后技能不再被路由到,但通过回滚操作可以在五分钟内恢复。
4.3 资源成本与性能控制
LLM 整合相位最大的顾虑就是成本。我在实际运行中把每次全量分析控制在约 2000 个技能以内,一天的 token 消耗折合下来不算高——关键在三个策略:批量复用、分级调用、缓存结果。
批量复用是指把多个候选对合并到同一次 LLM 请求中处理,减少往返次数。一次请求传 5 到 8 个技能对,让模型逐个输出,单位成本能降一半以上。分级调用是指对置信度要求不同的问题用不同的模型规格,简单分类任务用轻量模型,复杂的维护总结用强模型。缓存结果则是给每个技能对的判断打上哈希缓存,只有技能元数据变化了才重新请求模型。
规则引擎本身几乎没有成本,就是在普通服务器上跑一个定时任务。向量化如果用现成的 embedding 服务,全量 2000 条技能一次大约消耗少量 token,七天才全量跑一次,负担很小。整个 Curator 对基础设施的要求不高,普通的两台应用服务器加一台数据库就能扛住。
4.4 上线初期的一些数据表现
这套系统上线两个月后积累了一些真实数据。开始阶段,每天跑完规则引擎会产出 30 到 50 条建议归档候选,人工复核后真正需要归档的大概占四成;LLM 建议生成方面,relation_type 的分布大概是:merge_into_*占 35%,split_keep占 45%,false_positive占 20%。人工复核结果显示,LLM 判断和人工判断的一致率达到 75% 左右,误报主要集中在两个技能的 description 用语高度相似但实际接口完全不同的情况。
这个精度对生产环境来说是可以接受的,因为最终的动作仍然有人工确认环节。75% 的一致率意味着人工每周要处理大约 20 条建议,而全人工维护模式下同等工作量需要处理 100 多条,效率提升是实打实的。
5. 常见问题与排查实录
5.1 技能被误归档后怎么快速恢复
误归档是最容易遇到的问题。有一次某个低频技能被规则判为候选,但它在观察期的第 6 天正好被一个延时任务调用了一次。按规则应该复活,但我排查时发现调用事件没有成功上报——原因是采集模块对该技能做了采样丢弃,低频技能的样本直接被丢了,导致状态机看到的是零调用。
这个问题暴露了两个缺陷:一是日志采样策略不能一刀切,低频技能要全量保留调用事件;二是复活逻辑应该增加一个兜底——即使规则判定要归档,最终执行前再查一次实时调用量,而不是只看聚合统计。修复后,我在处置模块里加了一个“执行前校验”,归档任务真正执行前会向采集模块发起一次实时查询,确认过去 24 小时确实没有调用才继续。这个改动把误归档率降到了接近于零。
恢复流程本身我建议做成一键式:归档快照包含完整元数据和代码包,回滚就是读快照、恢复状态、重新注册。整个操作控制在五分钟以内。
5.2 LLM 生成建议的格式不稳定怎么处理
模型偶尔会输出和 JSON schema 不一致的内容,比如多了注释、字段名变了、嵌套层级错了。直接让下游解析器去兼容这些变体不是长久之计,我的处理方式是三层防御。
第一层是提示词里明确输出格式,并给出一个示例填充。第二层是解析失败时做一次修复重试,把模型输出的原始文本和期望 schema 一起交给模型让它自己纠正。第三层是最终兜底——如果两次都解析失败,这个候选对池的本次分析任务会跳过该条,并在日志里标注,不阻断整体流程。这样即使模型偶尔发疯,也不会让整条分析流水线崩溃。
5.3 依赖关系缺失导致的风险如何提前暴露
前面提到依赖声明是强制项,但历史技能总有缺失。我给采集模块加了一个静态扫描器,解析技能代码里的显式 import 和函数调用,自动生成一份“推测依赖”。推算结果不会直接写进元数据,而是作为候选依赖进入人工确认队列,确认后才会合并进正式的depends_on字段。这个机制上线后补全了 60% 以上的历史依赖关系,剩余一些动态加载场景实在无法静态推断,只能靠运行时调用链追踪来补充。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查思路 | 解决方案 |
|---|---|---|---|
| 低频技能被误归档 | 采集模块采样丢弃低频调用事件 | 检查日志采样配置,比对实时调用量和统计表 | 低频技能全量采样,归档前增加实时调用校验 |
| 候选技能观察期没有复活 | 复活事件延迟超过窗口时间 | 查看事件队列积压情况和时间戳 | 复活检查在归档执行前一并触发,不依赖观察期定时器 |
| LLM 建议 JSON 解析失败 | 模型输出偏离 schema | 查看原始输出和解析器报错日志 | 增加一次修复重试,仍失败则跳过并记录日志 |
| 相似技能对精确率偏低 | 向量相似度阈值过低 | 抽检相似度落在 0.80-0.85 区间的样本 | 上调阈值到 0.88,增加标签重叠作为辅助条件 |
| 归档执行后仍有路由请求 | 路由缓存未同步清空 | 检查网关缓存 TTL | 归档动作同时触发路由缓存主动失效 |
| LLM 分析任务阻塞主流程 | 模型调用超时无限制 | 检查调用链路超时设置 | 给模型调用加超时上限和熔断机制 |
6. 关于这套机制,我的一些体会
做 Hermes Curator 的过程中,我最大的体会是:技能维护问题的本质不是技术问题,而是组织治理问题。一个技能被废弃但没人删,往往不是因为没人发现,而是因为没有人有权限、有依据、有信心去删。技术手段能解决的,是提供“依据”和降低“试错成本”。依据来自数据——调用趋势、依赖图谱、语义重叠;试错成本来自回滚机制——归档不是删除,随时可以回来。
所以正确的系统设计顺序,不是先搭 LLM 分析,再补规则,而是反过来。先把元数据、依赖关系、归档与回滚的机制打牢,让系统具备安全处置的能力,然后才考虑引入 LLM 去提升分析和决策的效率。没有前者,LLM 的建议再聪明也没有落地的抓手;没有后者,前者的规则再完备也处理不了灰色地带的模糊判断。
另一个经验是:LLM 在后台维护任务中的价值,不在于“替代人做决定”,而在于把决策需要的信息密度提升一个量级。过去人看一个技能是否该归档,要自己翻调用日志、对比相似文档、排查依赖关系;现在 LLM 把这些信息压缩成一段带理由的结论,人只需要做最终确认。这个模式可以推广到很多后台运营场景,比如权限策略维护、配置项治理、数据字典管理——凡是“规则加模糊判断”的领域,都适用这套双相位思路。
如果你想在自己项目里复刻这套机制,我的建议是从最小闭环开始:先给技能库加上元数据和依赖声明,再实现一个最简单的打分规则,然后对准一个长期无人维护的技能动手归档一次。把这套流程走通,再逐步加入向量化和 LLM 分析。你会发现,最难的部分永远不是模型调用或算法公式,而是让团队信任这个系统——而信任,来自每一次归档都有据可查、可回滚。