先说个背景。最近一年,只要是做 LLM 应用的团队,几乎都在同一个问题上打转:模型能力越来越强,但把它们接进真实业务、干成一连串具体任务,总是差那“最后一公里”。提示词写得再花哨,模型一遇到多步骤、多工具协同的场景就发飘;工具函数堆了一堆,Agent 该调哪个、什么时候调,全靠临场发挥。我自己的项目也卡在这上面很久,直到我把思路从“调模型”转成“给 Agent 配技能”,整套流程才顺起来。这篇就好好拆一拆 agent-skills 这个方向,把技能库的定位、落地方式、踩过的坑,一次说透。
1. 技能库的定位:为什么 Agent 需要一套“肌肉记忆”
1.1 从“模型推理”到“技能执行”的思路转变
早期做 Agent 的人都习惯把逻辑全压在提示词里:系统提示词写一大堆规则,用户说一句话,模型现场思考该干嘛,然后调工具。这事儿应付演示没问题,一上生产就露馅——模型上下文有限,推理链条一长就丢三落四;同一个操作,换个说法问,模型可能就走了完全不同的路径。
后来大家发现一个朴素的道理:真实业务里,大部分动作是重复的。数据库连不上了,就那几步排查;支付回调失败,就那几个字段要核对;文件格式不对,十有八九是编码和分隔符的问题。这些流程一旦被沉淀成“技能”,Agent 调用时就不需要重新推理,只需要“认出场景,执行技能”,这本质上是给人工程序的确定性套上了一层缓冲。
agent-skills 的核心思路就是这个:把所有可复用的操作流程、判定逻辑、工具组合,封装成结构化的技能模块。Agent 拿到任务后,先做场景匹配,匹配到技能就直接执行;匹配不到,再退回模型推理兜底。推理负责模糊地带,技能负责清晰地带,两边一配合,稳定性直线上升。
注意:我说的是“配合”,不是让技能完全替代推理。纯规则引擎早就被行业淘汰过一轮了,Agent 的价值恰恰在于处理那些规则覆盖不到的开放式问题。技能库不是要锁死 Agent 的行为,而是给它建立一个“常用动作的缓存”,让模型把有限的注意力集中在真正需要思考的地方。
1.2 技能和工具、提示词到底有什么区别
这是新手最容易混的一块。光说“技能就是封装好的工具+提示词”,方向对,但太粗糙,落不了地。我给个严格点的区分方式:
- 工具(Tool/Function):最小粒度的原子能力,比如“读取文件内容”“发一封邮件”“查一条数据库记录”。它不关心你为什么要做这件事,只负责把动作完成。
- 技能(Skill/Agent Skill):面向业务场景的组合能力,内部可能编排多个工具,带着明确的输入输出约定、边界条件和失败处理策略。比如“导入用户数据”是一个技能,它内部要处理文件解析、字段校验、去重、分批写入、错误回滚,对外只有一个入参(文件路径)和一个结果(导入报告)。
- 提示词(Prompt):给模型的行为指令,它是技能的一种描述载体,但不等于技能本身。技能里的提示词只描述“这个技能怎么执行”,不承载业务逻辑。
用一个生活化的类比:工具是乐高积木块,技能是按照图纸拼好的组件模块,提示词是图纸上的文字说明。你让一个新手照着图纸拼,他每一步都可能出错;直接给他拼好的模块,他只需要知道“这个模块管什么场景”就能上手用。Agent 技能库干的就是后者——把易错、繁琐、高复用的逻辑预制好,Agent 只负责调度。
1.3 技能抽象层:让模型和业务解耦
再往深一层说,技能库其实是在模型和业务之间插了一个抽象层。这个抽象层的价值,容易被低估。没有抽象层的时候,你换一个底层模型,所有业务逻辑的提示词都要重新调优;你换一套接口协议,所有工具函数都要重写。有了技能层之后,业务逻辑被锁在技能内部,模型和接口的变化影响都被挡在外面。
我自己的项目里,技能层还承担了权限控制和审计的职责。技能是一个完整的执行单元,它的准入条件、可用账号、资源配额都可以在技能注册时一次性声明。Agent 只能通过技能触达底层能力,就避免了大模型被诱导、直接调用危险操作的问题。这比在模型层面加一万条安全规则可靠得多——毕竟你控制不了一个概率模型的边界,但你能严格定义一条技能链路的边界。
2. 技能库的两种实现路线:纯提示词封装和代码执行封装
2.1 路线一:纯提示词封装,适合场景单一、外部依赖少
先说第一种,也是入门最快的方式:把技能定义成一段结构化的提示词集合,模型读取技能描述后,按提示词的引导完成推理和执行。实现上就是一个 JSON 或 YAML 配置块,里面写清楚技能名、适用场景、执行步骤、输出格式、边界条件。
举个例子,我早期写过一个“SQL 审计优化”的技能:
skill_name: sql_audit_and_optimize description: 分析一条 SQL 慢查询,判断索引和查询结构问题,给出优化建议 applicable_scenario: 当用户提到数据库慢查询、SQL 性能差、执行计划异常时触发 steps: 1. 确认 SQL 文本和执行环境(数据库类型、数据量级) 2. 拆解 SQL 结构,标记全表扫描、隐式类型转换、索引失效等风险点 3. 生成优化建议,输出格式:问题点 / 优化方案 / 预期效果 boundary: - 仅处理单条 SQL,不处理事务并发问题 - 不直接执行 DDL,只输出建议 output_schema: issues: array optimization: string expected_improvement: string这种方式的优点很明显:实现简单,不用写代码,迭代快;缺点是执行效果高度依赖模型能力,模型理解偏了,技能就偏了。所以纯提示词封装只适合两类场景——一是逻辑线性、结果开放(比如出方案、写文案、做分析),二是外部依赖少、不需要强一致性(比如查资料、写代码片段)。
2.2 路线二:代码执行封装,适合硬逻辑、强制校验场景
第二种路线,是把技能的核心逻辑用代码落地,LLM 只负责两件事:识别场景、解析输入。识别到匹配的技能后,直接把参数交给技能执行器,执行器跑完代码逻辑,把结构化结果返回给模型做最终总结。这个模式下,技能就是一个带标准接口的函数,只不过这个函数由“模型选路 + 代码定逻辑”共同完成。
同样拿 SQL 场景举例,代码封装的技能长这样(伪代码示意):
@skill.register( name="mysql_index_advisor", description="分析 MySQL 慢查询并给出索引优化建议", trigger_keywords=["慢查询", "索引优化", "执行计划"] ) def mysql_index_advisor(sql_text: str, db_config: dict) -> dict: # 1. 连接数据库,获取执行计划 plan = get_execution_plan(sql_text, db_config) # 2. 规则引擎判断风险点 risks = [] if plan.scan_type == "ALL": risks.append("存在全表扫描,建议检查 WHERE 条件涉及字段的索引") if plan.extra_contains("Using temporary"): risks.append("使用了临时表,常见于 GROUP BY / DISTINCT 与大表 JOIN 场景") if has_implicit_cast(sql_text): risks.append("存在隐式类型转换,可能导致索引失效") # 3. 生成建议 return {"risks": risks, "optimization_suggestions": suggest(risks)}这个路线的优势是确定性拉到最高:SQL 风险判断不依赖模型发挥,代码里的规则引擎输出结果恒稳定。代价是开发成本高,每新增一个技能都要写代码、做测试。适合的场景包括:数据校验、接口调用、文件处理、任何不能出错、必须精确的领域。
2.3 混合架构:绝大多数生产级技能库的现实形态
别被上面二选一给带偏了。实际落地的技能库几乎全是混合形态——同一个库里,有的技能是纯提示词,有的是纯代码,还有的是“代码判定 + 模型生成”的组合拳。
我目前跑得很稳的组合模式是:能规则化、确定化的逻辑全部下沉到代码;需要理解、归纳、创作的部分留给模型;两者之间用结构化的中间数据衔接。
举一个客服工单分类的技能实例:工单进来,先用正则和关键词把“退款”“发票”“物流”这类硬信号命中掉(代码逻辑);剩下的模糊工单交给模型,要求它带回结构化标签和置信度(提示词逻辑);最后汇总两个通道的结果,做冲突消解(代码逻辑)。一套技能里,模型和代码各干各的活,谁也不越界。
实操心得:新手做技能库最容易犯的错,是“什么都想让模型干”。我在项目前期也走过弯路——把字段提取、格式校验全塞给提示词,结果线上误判率高得离谱。后来定了一条铁律:凡是正则能解决的,绝不让模型猜;凡是代码能校验的,绝不让模型看。模型的能力应该花在“意图理解”和“内容生成”上,而不是干结构化判断的体力活。这条铁律贯彻下去,技能库的稳定性肉眼可见地提升。
3. 技能注册与发现:让模型知道该调哪个技能
3.1 技能描述该怎么写,模型才容易命中
技能库里技能一多,第一个问题就是:场景来了,模型找得到对的技能吗?技能注册信息里的“描述”字段,直接决定了模型能不能精准命中。这方面我踩过不少坑,总结出几条有效经验:
- 描述写“触发场景”,不要写“技术功能”。比如“处理 CSV 文件解析”远不如“当用户上传通讯录或报表文件时触发”直观。模型匹配的是语义,不是类名。
- 写上“不适用场景”,反向排除干扰。比如一个“商品推荐”技能,要注明“促销活动期间、或当用户情绪明显不满时不适用”,避免模型硬套。
- 块状描述优于长句。用关键词块、场景短语、触发条件组合,比一整段流畅文字命中率更高。模型对长句的语义编码容易漂移,对离散关键词的匹配更准。
我自己的技能注册模板大概长这样:
skill_id: order_refund_processor name: 订单退款处理 trigger_scenarios: - 用户申请退款 - 订单异常需要退款原路退回 - 退款失败后重新处理 not_applicable_scenarios: - 仅咨询退款政策,无实际退款操作 - 代金券/虚拟商品的核销 input_requirements: order_id: string, 必填, 订单系统内的唯一标识 refund_amount: float, 可选, 默认按实付金额整单退款 output_structure: refund_id: string status: enum(pending, success, failed) fail_reason: string, 仅失败时返回这样写的好处是,给模型的信息不是一股脑的说明文字,而是“什么情况来、什么情况别来、进来带什么、出去吐什么”的四要素结构。模型做场景匹配时,只需要把这四项和用户输入做语义对齐,判断路径清晰很多。
3.2 技能检索:别只靠模型“想”,要主动“捞”
注册写好了,接下来就是检索问题。技能库二三十个技能的时候,模型靠上下文里的技能描述集中“扫”一遍还撑得住;一旦技能上百,全量描述塞给模型就成了灾难——上下文长度不够、模型注意力分散、长尾技能永远不被看到。
生产环境要上两层检索机制:
第一层:关键词/标签索引。给每个技能打业务标签,比如“退款”“物流”“发票”作为一个域,“数据分析”“报表生成”作为另一个域。用户请求进来,先用轻量级匹配把域锁死,候选技能瞬间从上百砍到十几个。
第二层:语义向量召回。把技能描述预编码成向量,用户请求实时编码后用向量相似度取 TopK。这个召回方式适合糊匹配——表达方式和技能描述差很远,但语义接近的请求,全靠它兜底。
两层结合后,真正喂给模型的技能列表控制在 8 个以内,模型再在这 8 个里做精排。实测下来,这个检索策略同时解决了上下文膨胀和长尾技能饥饿两个问题。
注意:向量召回不是万能的,它在技能数量少时优势不明显,反而增加一层系统复杂度。技能库不到 30 个技能、且领域高度垂直的团队,可以从标签检索起步,后续技能膨胀了再上向量。别为了技术上“高级”而引入不必要的组件,能简化就简化。
3.3 技能冲突与优先级:同名技能、相似技能怎么排
技能库发展到中后期,一定会出现相似技能——比如“订单退款”和“订单取消退款”听起来就是同一件事的两个变体。技能定义时如果边界划不清,模型选错技能的概率会显著上升。我处理冲突的办法是三层递进:
- 第一层:场景互斥。定义技能时明确区分触发条件,让相似技能的适用场景不重叠。比如“订单取消退款”只处理买家主动取消订单的场景;“订单异常退款”只处理卖家/系统主动发起的补偿退款。边界清晰了,模型天然不会搞混。
- 第二层:默认优先级。如果两个技能场景确实可能重叠(比如既有通用客服技能,又有专门的售后技能),给技能加一个 priority 字段,高优先级的先试,失败再降级到通用技能兜底。
- 第三层:运行时校验。技能执行器在真正执行前,增加一个前置条件 checker。检查当前输入的字段结构、关键值是否满足该技能的最低要求,不满足就返回“技能不匹配”,让模型重新选择。
这三层加完,技能选择的错误率能压到很低的水平。当然,“很低”不代表“没有”,所以每一层我都在日志里记录选择路径,方便事后回溯是技能描述写得不清楚,还是模型理解跑偏了。
4. 实操过程:从零构建一个技能库核心链路
4.1 技能库的整体目录结构设计
搞技术的人都懂,目录结构决定心智模型。技能库的目录设计,我反复调过好几版,现在跑得最顺的一版是这样:
agent-skills/ ├── skills/ │ ├── data_processing/ # 数据域技能 │ │ ├── csv_cleaner/ │ │ │ ├── SKILL.md # 技能定义(描述、触发场景、边界) │ │ │ ├── schema.json # 输入输出结构定义 │ │ │ ├── handler.py # 执行逻辑(代码型技能) │ │ │ └── tests/ │ │ └── sql_optimizer/ │ ├── customer_service/ # 客服域技能 │ │ ├── refund_processor/ │ │ └── complaint_handler/ │ └── content_generation/ # 内容域技能 │ ├── article_writer/ │ └── summary_generator/ ├── registry/ │ ├── index.json # 技能注册索引(全局检索用) │ ├── vectors/ # 技能语义向量缓存 │ └── embeddings_builder.py # 向量构建脚本 ├── executor/ │ ├── skill_runner.py # 技能执行器(调度代码型技能) │ └── context_builder.py # 技能执行上下文组装 └── logs/ └── execution_trace.log # 技能调用链路追踪每个技能目录就是一个自包含的独立模块:定义、输入输出、执行逻辑、单测一个不缺。这样设计的好处是,技能之间不互相依赖,删掉某一个技能不影响其他技能的注册和检索;新技能上线就是在skills下加目录,跑一遍注册脚本,索引和向量自动更新。
4.2 技能定义的标准化流程:四步走
我给每个新技能上线定了一个固定流程,四步走完,质量兜底:
第一步:场景提炼。从真实用户会话里捞 20-30 条典型的、与目标技能相关的请求,提炼共性特征。这步的关键是搞清楚“用户真的会怎么说”,而不是“产品经理想象用户怎么说”。我遇到过最典型的坑:技能描述按产品文档写的接入词,结果真实用户根本不那么说话——描述写得再规范,场景对不上也是白搭。
第二步:输入输出 schema 定义。用 JSON Schema 严格定义技能的入参和出参结构。这步绝不能省。没有 schema 约束,模型传参随便来,执行器接到的数据五花八门,逻辑根本没法稳定。Schema 定义好后,还要定义“必填、选填”和“默认值”,给模型兜底。
第三步:执行逻辑实现与测试。代码型技能直接写单测,纯提示词技能要跑一批历史真实请求做回归验证。我个人的标准是:一个技能上线前,至少拿 30 条黄金样本跑一遍,准确率达到 90% 以上才允许进库。
第四步:注册并同步检索索引。技能测试通过后写入注册中心,用 embedding 模型把技能描述向量化,更新检索索引。这边有两个小细节:向量化建议同时编码“技能描述”和“触发场景”两个字段,召回效果比单编码描述好;技能描述后续每次改动,记得重新生成向量,否则旧向量还占着坑位,检索时会错配。
4.3 一个完整技能实例:文件导入校验技能
光讲流程太抽象,我拿一个已经跑在生产环境的“文件导入校验”技能来拆,这个技能负责处理用户上传的各种数据文件,校验格式、清洗内容、输出报告。
技能定义(SKILL.md)核心部分:
# Skill: File Import Validator ## Description 处理用户上传的数据文件(CSV、Excel),完成格式校验和内容清洗。 当用户说“上传文件/导入数据/帮我处理这个表格”时触发。 ## Not Applicable - 文件已确认损坏且无法读取时 - 用户未提供文件仅咨询导入流程时 ## Input - file_path: string, 上传文件的存储路径 - file_type: string, 可选值 csv/excel,默认自动识别 - expected_columns: array[string], 可选, 期望的列名列表 - dimension: enum("strict", "loose"), 必填, strict模式发现非法行直接阻断,loose模式跳过非法行继续 ## Output - total_rows: int - valid_rows: int - invalid_rows: int - errors: array[{row_idx, reason}] - cleaned_file_path: string, 清洗后的临时文件路径执行逻辑(handler.py)关键片段:
class FileImportValidator: def __init__(self, params: dict): self.file_path = params["file_path"] self.dimension = params.get("dimension", "loose") self.expected_columns = params.get("expected_columns", []) def execute(self) -> dict: # 1. 识别文件格式 fmt = detect_file_format(self.file_path) if fmt not in ("csv", "xlsx"): return {"total_rows": 0, "valid_rows": 0, "invalid_rows": 0, "errors": [{ "row_idx": -1, "reason": f"unsupported format: {fmt}" }]} # 2. 读取并逐一校验 errors = [] valid_rows = 0 total_rows = 0 for row in read_rows(self.file_path, fmt): total_rows += 1 row_errors = validate_row(row, self.expected_columns) if row_errors: errors.append({ "row_idx": total_rows, "reason": "; ".join(row_errors) }) if self.dimension == "strict": return {"total_rows": total_rows, "valid_rows": valid_rows, "invalid_rows": len(errors), "errors": errors} else: valid_rows += 1 # 3. 写清洗后文件 cleaned_path = None if self.dimension == "loose" and valid_rows > 0: cleaned_path = write_cleaned_file(self.file_path, fmt) return {"total_rows": total_rows, "valid_rows": valid_rows, "invalid_rows": len(errors), "errors": errors, "cleaned_file_path": cleaned_path}这套技能里没有让模型参与单行校验,全部走代码逻辑:格式检测靠文件头识别,列名匹配靠精确比对,非法值判断靠类型转换。模型只负责两件事——用户说“帮我导入这份文件”时,把这个技能从库里捞出来;以及把技能执行为后的错误报告,转述成用户能听懂的话。既有技术上的确定性,又有对话上的自然度。
运行时的表现也印证了这个设计:上线后,该技能的字段误判率几乎为零,全部错误均来自真实的数据质量问题(比如某列混入了日期格式),且错误均被准确捕获并给出了行号和原因。
实操心得:技能执行器的返回值设计,建议统一为“结构化结果 + 自然语言摘要”双通道。结构化结果给模型做后续决策(比如还要不要重试、要不要换方案),自然语言摘要直接让模型润色后回复用户。这比单独返回结构化数据好太多——模型读到结构化数据后自己组织语言,措辞常常干巴,或者把技术细节一股脑抛给用户。
5. 技能效果评估:如何判断一个技能“真正好用”
5.1 离线评估:用黄金样本集守住下限
技能库上线后,最怕的是“看起来能跑,一细问就露怯”。我用一套离线评估机制来给每个技能建立质量基线:每个技能上线前,准备一份覆盖全场景的黄金样本集(至少 50 条),里面既有正常请求,也有边界情况、绕弯说法、带敌意说法。每次技能描述或逻辑调整,必须拿这份样本集回归一遍。
指标上,我重点盯三个:
- 触发准确率:应该触发技能时,模型选对了没有。这个指标衡量的是“技能发现”质量。
- 参数完整率:技能触发后,需要的参数是否都被正确解析。漏参、错参都会直接影响执行结果。
- 执行成功率:真正执行后,有没有报错,结果符不符合预期 schema。
这三个指标的基线我定在 85%-90% 之间。低于 85% 的技能不进生产线,哪怕场景再重要也先回去打磨。别怕标准定得严,技能质量就是 Agent 质量的底盘,底盘不稳,上层全晃。
5.2 线上监控:调用链路的“透明度”设计
离线评估做得再好,线上还是会遇到新情况。所以技能的线上监控必须做到“看得清”。我给每个技能调用都做了全链路 trace,日志里至少记录如下信息:
- 触发来源:模型选的还是检索捞的,置信度多少
- 输入快照:原始请求 + 解析后的结构化参数
- 执行结果:成功/失败、耗时、失败原因
- 输出后处理:模型如何改写技能结果的(保留了哪些、改造了哪些)
这套日志跑熟了之后,能拿到很多意想不到的洞察。比如我通过 trace 发现,有些技能成功率看似 100%,但模型在把结构化结果转述给用户时,会自作主张添加“不保证正确”之类的免责声明,导致用户信任度受损。这种“执行成功、表达失败”的问题,在纯结构化评估里根本看不见,只有链路 trace 才能暴露。
5.3 技能迭代:别急着增,先看存量技能的健康度
技能库常见的一个失控方向是“技能越来越多,质量越来越差”。业务方今天提一个需求加一个技能,明天提一个场景加一个技能,技能库膨胀到几百个之后,检索命中率和执行成功率反而双双下滑。
我的对策是严格区分“新技能需求”和“存量技能调整”。每次收到新技能需求,先问三个问题:
- 这个需求能不能被现有技能覆盖或扩展?有七八成相似度的,优先扩展现有技能的字段和分支,而不是新建技能。
- 这个需求是不是一次性场景?如果只是偶尔一次的特殊请求,让它走模型推理兜底就好,不值得沉淀成技能。
- 这个技能如果上线,和现有技能冲突吗?有冲突的,先做冲突消解设计,再讨论上线。
用这个过滤器之后,我的技能库增长速度慢了很多,但整体质量稳定在较高水平。技能库不是越满越好,而是“该有的都有,多余的都没有”最好。
6. 常见问题与排查纪录
6.1 模型老是选错技能怎么办
这是支撑“技能发现”最常见的故障。遇到这种情况,第一步不是调模型,而是反查技能注册描述。我排查的顺序是固定的:
- 查描述术语是否和用户黑话对齐。用户在工单里说“退钱”,技能描述里写“退款申请”,语义层面是对齐的,但模型在匹配时对口语词的敏感度不一样。把触发场景里加上常见口语变体(“退钱”“不买了”“给我把钱打回来”),命中率立刻上一个台阶。
- 查是否有相似技能在分流。技能 A 和技能 B 场景有交叉时,模型经常“端水”——两个技能的置信度都不到阈值,最后谁也没触发。这时候果断给技能加场景互斥描述,或者降低其中一个技能在重叠区域的优先级。
- 查触发阈值设得是否太严。有些技能命中信号是得分阈值在控制,阈值定高了,模型觉得“有点像但不确定”,就放弃了。适当下调阈值,用“候选列表 Top3 再做规则筛选”来兜底,比死守高阈值更有效。
6.2 参数解析成功但执行报错
这是典型的“模型理解了场景,但没把参数给完整”。最常见的是必填参数缺失,或参数类型不匹配。很多人第一反应是怪模型,但我自己的排查经验是——多半是技能定义里的“参数说明”写得不够清楚。
举个例子:某个技能需要order_id,描述里只写了“订单号,必填”,模型面对用户说的“把 A123 订单退了”,能抽出A123,但如果用户说“把昨天买的那个退掉”,模型就懵了——无从知道“昨天买的那个”对应哪个订单号。这时候问题不在模型,在技能描述没有写清“当用户未提供订单号时,需要主动询问”的指令。
我的解决方式:在 input_requirements 里加一个missing_policy字段,告诉模型“缺了该怎么补”。值是ask_user就主动问,值是fetch_from_context就从会话上下文里找,值是use_default就直接用默认值。给模型把路指好,参数解析质量立竿见影。
6.3 技能执行结果和用户预期不一致
这个问题的技术根源其实很简单——技能的设计初衷和用户的真实意图有偏差。技能是按“业务方理想流程”设计的,但用户的请求经常属于“流程边缘状态”,被技能的边界条件弹出来了。
我在项目里用“用户意图分类 + 技能结果分层”来缓解这个问题:技能执行前,先跑一个轻量级的意图分类器,识别本次请求属于“标准请求”还是“边缘请求”;标准请求走技能全流程,边缘请求单独给模型一个“旁路说明”,允许模型在技能结果基础上做局部修正。
这么做的代价是多了一层分类逻辑,好处是显著降低了“生硬拒绝”类体验。Agent 产品最忌讳的就是让用户感觉“这个机器人只会按套路出牌,一跑偏就死机”。技能框架要做的是给确定性兜底,而不是成为灵活性的天花板。
6.4 技能库性能退化:技能越多,响应越慢
技能库膨胀导致的性能问题,主要是检索链路变慢。有一次我在线上观察到技能召回阶段的 P99 延迟从 300ms 涨到 2s,排查后发现是向量检索的索引没有增量更新,全量重建的频率又跟不上技能新增速度。
这个问题我用三步解决:
- 第一步,给技能索引加上版本号,索引变更时只重建增量部分。
- 第二步,向量检索的候选集从全量改为“业务域预筛 + 域内向量排序”,缩小区间。
- 第三步,热门技能加一层静态映射缓存,高频场景直接命中缓存,不走向量计算。
做完这三步,P99 延迟降回 400ms 以内。给做技能库的同行一个提醒:技能库的性能问题大部分不是模型问题,是检索系统架构问题。别一慢就想着换模型、降复杂度,先把索引、缓存、检索链路检查一遍。
6.5 新增一个小技巧:技能链路的可观测性增强
最后分享一个我最近在项目里加的小改进——给技能执行器增加一个“阶段标记”功能。每个技能在执行过程中,把状态节点(解析完成/前置检查通过/执行中/执行完成/结果后处理)实时推送到 trace 系统。原先只能看到“技能执行失败”,现在能精确定位到“参数解析完成,但前置检查未通过”这类具体环节。
这个改进看起来不起眼,但排查效率提升非常明显。以前出问题要翻原始日志逐行对,现在直接在 trace 面板上看标记,问题卡在哪一段一目了然。尤其是技能链路跨多个子系统调用时,这个能力几乎等于救命。
7. 从技能库到智能体的“工种化”升级
技能库做到一定规模后,自然会产生一个更高阶的需求:不同的技能给不同类型的“角色”使用。客服机器人、数据分析助手、内容小编,各自的技能组合完全不同。这时候单纯靠一个“大而全”的技能库已经不够了,要给技能做“工种分包”。
我的做法是把技能按使用角色分组,每个角色对应一份技能白名单。角色启动时,只加载自己白名单内的技能描述和检索索引。这样既降低了上下文占用,又天然做了权限隔离——数据分析助手查不到客服模块的技能,也不会被诱导去执行客服操作。这个分层结构,是把技能库从“工具集合”升级成“组织能力”的关键一步。
实际落地这块之后,我发现还有个额外好处:新角色上线时,不需要从零搭建,只需要从现有技能库里挑选组合。业务的响应速度从“两周开发一个技能”变成“三天拼装一个新角色”。对于需要快速验证新场景的团队来说,这种灵活性比单个技能的质量更重要。
当然,“工种化”也会带来新的管理复杂度:技能版本怎么跨角色同步、同一个技能在不同角色下的参数权限怎么区分、角色白名单的变更流程怎么设计。这些问题的解法各家有各家的招,但有个原则值得坚持:技能本身保持通用中立,角色化配置放在外层适配层。技能不做任何“我是给谁用的”假设,才能保证它的复用性和长期生命力。
我自己的项目从单技能跑通,到现在十几个角色共用一套技能库,中间跨过的沟不少。回头再想,agent-skills 这个方向最核心的价值,不光是给 Agent 提升了一截稳定性,更是逼迫你把业务逻辑重新梳理了一遍——技能边界划清楚了,业务边界自然就清楚了。