ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Agent技能库设计:语义检索与动态组合实战框架

Agent技能库设计:语义检索与动态组合实战框架 1. 这不是“调用API”——而是让Agent真正理解“该用哪个技能、怎么搭起来用”你有没有遇到过这种情况写好了一堆Skill——查天气、搜文档、算日期、发邮件、调数据库……可一到真实任务里Agent要么死活找不到该用哪个要么硬凑两个不搭界的技能一起跑结果返回一堆乱码或直接报错我去年带三个团队落地Agent项目80%的交付延期都卡在这一步技能库不是插件仓库是能力语义网络检索和组合不是算法题是意图解构工程。标题里说的“怎样找到并组合正确的Skill”核心根本不在代码怎么写而在于——你有没有把Skill当成“有上下文、有边界、有依赖关系的活体能力单元”而不是冷冰冰的函数列表。热搜词里反复出现的“agent开发”“rag检索”“组合算法”“意图识别到检索语义”其实都在指向同一个底层矛盾LLM能生成逻辑但不能天然建立技能间的因果链。比如用户说“帮我对比上周和上月的销售数据生成PPT发给王总”这个请求里隐含了至少5层能力调度时间解析“上周/上月”→ 数据库查询销售表时间过滤→ 表格计算同比/环比→ PPT生成模板图表嵌入→ 邮件发送收件人附件正文。任何一个环节的Skill选错整条链就断。这不是靠“相似度打分最高”就能解决的而是要构建一套可验证、可追溯、可干预的能力调度图谱。本文不讲抽象理论只分享我在电商、金融、政务三类Agent项目中沉淀下来的实操框架从Skill定义规范、语义索引结构、动态组合策略到线上灰度验证方法——所有内容都经过日均30万次调用的真实压力检验。适合正在写第一个Skill、或正被“Agent执行终止”错误折磨的开发者也适合技术负责人评估团队Agent能力基建是否扎实。2. Skill不是函数是带“身份证”和“关系网”的能力实体2.1 为什么90%的Skill定义从一开始就埋了雷很多团队第一步就错了把Skill当成一个Python函数封装。比如写个get_weather(city: str) - dict然后扔进技能库。问题在哪当Agent看到“北京今天热不热”时它需要判断这是不是天气相关意图→ 需要语义理解get_weather能不能处理“热不热”这种主观描述→ 需要知道该Skill的输出是否含体感温度字段如果用户接着问“那上海呢”要不要复用同一个Skill实例→ 需要状态管理标识如果天气API超时有没有降级方案比如查缓存→ 需要容错声明这些信息光靠函数签名根本无法承载。我们团队强制推行Skill三证制能力身份证Capability ID不是随便起名而是结构化命名weather.forecast.v2其中weather是领域域forecast是动作类型v2是版本号。这样在检索时可按前缀快速过滤避免getWeather/fetchWeather/queryWeather这类同义词爆炸。语义说明书Semantic Spec用YAML明确定义输入约束、输出Schema、支持的实体类型、典型触发句式、失败兜底策略。例如input_constraints: - field: city type: string required: true entity_types: [GPE, LOCATION] # 支持地理实体识别 - field: date type: string required: false default: today format: YYYY-MM-DD or today/tomorrow output_schema: temperature_feel: string # 明确包含热/冷/舒适等体感描述 humidity: number warning_level: enum [none, yellow, orange, red] trigger_phrases: - 今天XX热不热 - XX现在温度怎么样 - 体感温度 fallback_strategy: cache_last_24h提示这个YAML不是文档是运行时可解析的元数据。我们用Pydantic Model自动校验任何字段缺失或类型错误在注册Skill时就抛异常绝不让“半残废”Skill入库。关系联络图Dependency Graph明确标注该Skill依赖哪些其他Skill或外部服务。比如weather.forecast.v2依赖geo.resolve.v1把“朝阳区”转成经纬度而geo.resolve.v1又依赖geocoding.api服务。这个图不是画在纸上而是以JSON-LD格式存入图数据库供组合引擎实时查询依赖路径。2.2 技能库不是“文件夹”是支持多维检索的语义索引系统把Skill按文件夹分类如/skills/weather/,/skills/email/是初级做法。真实场景中用户说“把会议纪要发给张三和李四”你需要同时匹配文档处理Skill提取纪要文本邮件发送Skill但必须支持多收件人联系人查询Skill把“张三”转成邮箱权限校验Skill确认当前用户能否发给李四这要求技能库具备四维检索能力意图维度基于用户Query做语义向量检索我们用Sentence-BERT微调版专门针对中文办公语料训练比通用模型准确率高27%能力维度按capability_id前缀精确匹配如email.send.*约束维度按input_constraints字段动态过滤如required: true且entity_types含PERSON上下文维度结合对话历史中的已执行Skill排除重复调用如刚执行过doc.extract.v1则不再检索同类Skill。我们用Elasticsearch Neo4j混合架构实现Elasticsearch 存储Skill的文本描述、触发短语、YAML元数据全文支撑意图和能力检索Neo4j 存储Skill节点及其依赖关系、调用频次、成功率、平均耗时支撑上下文感知和动态排序。关键设计点不依赖单一向量相似度打分。比如用户说“订明天下午三点的会议室”向量检索可能召回calendar.create.v1准确和weather.forecast.v2因“下午”“三点”有弱语义关联。这时约束维度立刻生效calendar.create.v1的input_constraints明确要求time字段为datetime类型且required:true而weather.forecast.v2的date字段required:false直接过滤掉后者。实测下来这种多维过滤使首条命中率从61%提升到93%。2.3 “组合”不是拼积木是构建可执行的调度拓扑找到单个Skill只是开始。真正的难点在于如何把多个Skill连成一条能跑通的流水线很多人用简单规则“如果A输出含X字段则调用B”。但现实更复杂A输出是结构化JSONB输入要XML格式 → 需要转换SkillB执行失败要回退到C而非重试B → 需要定义fallback路径D和E可并行执行如同时查库存和查物流→ 需要DAG调度器F依赖G和H的输出合并 → 需要聚合Skill。我们的解决方案是组合模板Composition Template不是代码而是声明式DSLname: order_status_check steps: - id: get_order skill: order.query.v3 input_mapping: order_id: $.user_input.order_id output_alias: order_data - id: check_stock skill: inventory.check.v2 input_mapping: sku: $.order_data.items[0].sku parallel_with: [check_logistics] # 并行执行 - id: check_logistics skill: logistics.track.v1 input_mapping: tracking_no: $.order_data.shipping.tracking_no - id: aggregate_result skill: util.aggregate.v1 input_mapping: stock_status: $.check_stock.status logistics_status: $.check_logistics.status order_info: $.get_order output_alias: final_report这个模板被编译成DAG图由轻量级调度器执行。关键创新点输入映射支持JMESPath语法$.order_data.items[0].sku直接从上游输出取值避免手写转换代码并行标记自动插入Barrier节点确保aggregate_result等待check_stock和check_logistics都完成才启动每个Step绑定超时和重试策略check_stock设3秒超时、最多重试1次aggregate_result设1秒超时、不重试纯内存操作输出别名形成局部变量空间后续Step可直接引用$.check_stock.status无需关心上游Skill的原始字段名。这套DSL不是凭空设计而是从我们处理过的237个真实业务流程中抽象出来的。比如电商“售后退款”流程平均含8.3个Skill其中3.2个需并行1.7个需条件分支如“金额500需财务审批”全部用模板声明开发效率提升4倍且运维人员可直接修改模板调整流程无需动代码。3. 实操从零搭建可检索、可组合的技能库附完整代码片段3.1 Step 1定义Skill注册协议Python SDK我们不手写YAML而是用Python Class声明SkillSDK自动生成元数据from agent_skills import Skill, InputField, OutputField, TriggerPhrase class WeatherForecastSkill(Skill): capability_id weather.forecast.v2 inputs [ InputField( namecity, typestr, requiredTrue, entity_types[GPE, LOCATION], description城市名称支持模糊匹配如朝阳区 ), InputField( namedate, typestr, requiredFalse, defaulttoday, description日期支持today/tomorrow或2024-05-20 ) ] outputs [ OutputField( nametemperature_feel, typestr, description体感温度描述炎热/凉爽/舒适等 ), OutputField( namehumidity, typefloat, description相对湿度百分比 ) ] trigger_phrases [ TriggerPhrase(今天{city}热不热), TriggerPhrase({city}现在温度怎么样), TriggerPhrase(体感温度) ] def execute(self, city: str, date: str today) - dict: # 真实调用天气API return { temperature_feel: 炎热, humidity: 65.0 } # 注册时自动校验并生成YAML元数据 WeatherForecastSkill.register()SDKregister()方法会校验inputs中所有entity_types是否在预定义白名单内防止乱填PERSON检查trigger_phrases是否含未定义占位符如{country}但inputs无country字段生成标准YAML存入ES并同步写入Neo4j的Skill节点将Class方法execute包装成统一接口屏蔽底层实现差异可对接HTTP、gRPC、本地函数。实操心得我们强制要求每个Skill必须有trigger_phrases且至少3个。没写触发短语的Skill注册时直接拒绝。因为这是连接用户语言和机器能力的唯一桥梁——没有它检索就失去锚点。3.2 Step 2构建多维检索引擎Elasticsearch配置ES索引skill_catalog的关键Mapping{ mappings: { properties: { capability_id: {type: keyword}, semantic_vector: {type: dense_vector, dims: 768}, input_constraints: { properties: { field: {type: keyword}, type: {type: keyword}, required: {type: boolean}, entity_types: {type: keyword} } }, trigger_phrases: {type: text, analyzer: ik_max_word}, success_rate_7d: {type: float}, avg_latency_ms: {type: float} } } }检索Query示例用户Query“查张三的邮箱”{ query: { bool: { must: [ { knn: { field: semantic_vector, query_vector: [0.1,0.9,...], k: 10 } }, { term: { input_constraints.entity_types: PERSON } } ], filter: [ { term: { capability_id: contact.find.v1 } }, { range: { success_rate_7d: { gte: 0.8 } } } ] } }, sort: [ { _score: { order: desc } }, { success_rate_7d: { order: desc } } ] }这里的关键是filter优先于knn先用entity_types和capability_id前缀快速缩小候选集从10万Skill降到200个再对这200个做向量检索。实测响应时间从1200ms降到87ms且首条准确率更高——因为向量检索在小集合上更稳定。3.3 Step 3实现组合模板编译器核心算法模板编译的核心是DAG构建与依赖解析def compile_template(template_yaml: dict) - nx.DiGraph: graph nx.DiGraph() # Step 1: 添加所有节点 for step in template_yaml[steps]: graph.add_node( step[id], skillstep[skill], input_mappingstep.get(input_mapping, {}), timeoutstep.get(timeout, 5), retriesstep.get(retries, 0) ) # Step 2: 解析依赖关系基于input_mapping中的$.引用 for step in template_yaml[steps]: for key, value in step.get(input_mapping, {}).items(): if value.startswith($.): # 提取上游Step ID如$.get_order.items[0].sku → get_order upstream_id value.split(.)[1] if upstream_id in graph.nodes: graph.add_edge(upstream_id, step[id]) # Step 3: 处理并行标记 for step in template_yaml[steps]: if parallel_with in step: for peer_id in step[parallel_with]: # 添加虚拟Barrier节点确保所有并行Step完成后才执行当前Step barrier_id fbarrier_{step[id]} graph.add_node(barrier_id, typebarrier) graph.add_edge(step[id], barrier_id) graph.add_edge(peer_id, barrier_id) return graph调度器执行时同时启动所有入度为0的节点如get_order每个节点执行完检查其下游节点入度是否降为0是则启动遇到barrier节点等待所有上游节点完成才继续任一节点失败按预设fallback路径跳转如check_stock失败则跳check_stock_fallback。注意我们禁止在模板中写if/else逻辑。所有分支都通过独立Skill实现比如approval.need_finance.v1判断是否需财务审批和approval.direct.v1直接审批由LLM根据用户角色选择调用哪个。这样保证模板纯粹是调度指令不掺杂业务逻辑。3.4 Step 4上线灰度与效果验证真实数据看板技能库上线不是“一次发布”而是三级灰度沙箱验证新Skill注册后先在测试环境用100条历史Query跑回归测试检查是否被正确检索命中率≥95%执行是否超时P952s输出是否符合Schema字段名、类型、必填项小流量AB测试对1%真实用户将新Skill加入候选集对比旧流程的任务完成率用户是否得到最终答案平均步骤数越少越好说明组合更精准用户主动中断率用户中途说“算了”全量监控看板核心指标实时展示指标计算方式健康阈值Skill复用率(被调用≥2次的Skill数 / 总Skill数)≥65%组合链成功率(成功执行完所有Step的流程数 / 总流程数)≥88%首步命中延迟从Query到首个Skill启动的毫秒数P95 ≤ 150msFallback触发率(走fallback路径的流程数 / 总流程数)≤5%我们曾发现email.send.v2的Fallback触发率突然升到12%排查发现是SMTP服务器证书过期。但更重要的是看板显示email.send.v2的复用率仅31%——说明大部分用户需求没被覆盖于是我们紧急上线了email.send_batch.v1支持群发复用率一周内升至79%。4. 常见问题与避坑指南来自237个真实故障现场4.1 “Agent execution terminated due to error”——90%源于Skill间的数据契约断裂这个报错看似是代码异常实则是上下游Skill对数据格式的理解错位。典型案例doc.extract.v1输出{ text: 会议纪要... }summary.generate.v2输入要求{ content: ... }组合模板没写input_mapping导致summary.generate.v2收到{text: ...}解析失败。避坑方案强制所有Skill输出用统一Schema我们定义StandardOutput基类含data、metadata、error字段在调度器中插入契约校验中间件执行前检查上游输出是否含下游所需字段缺失则自动注入默认值或报清晰错误如“summary.generate.v2requires fieldcontent, but gottext”开发者工具链集成VS Code插件实时高亮模板中input_mapping的字段名若上游无此字段则标红。我踩过的最深的坑某次升级weather.forecast.v2新增了uv_index字段但没更新weather.display.v1的输入Schema。结果所有天气查询页面崩溃。后来我们加了CI检查任何Skill变更必须运行所有依赖它的组合模板的单元测试。4.2 “检索不到Skill”——不是向量不准是语义锚点缺失用户说“把这份合同发给法务部”检索却召回email.send.v2正确和file.upload.v1错误。问题出在file.upload.v1的trigger_phrases只有“上传文件”没覆盖“发给法务部”这种业务场景表述。根治方法触发短语必须来自真实对话日志我们每天从客服系统抓取1000条含“发给/转给/抄送”等动词的句子用NER标注出实体如“法务部”→DEPARTMENT批量生成TriggerPhrase为Skill添加业务标签在YAML中加business_tags: [contract, legal_review, approval]检索时用terms查询补充语义人工审核机制新Skill上线前由业务专家用50个典型Query测试命中率低于90%则打回重写。4.3 “组合结果不对”——不是算法问题是上下文丢失用户连续问“查北京天气” → 返回“炎热”“那上海呢” → 却返回“北京天气”复用上一步结果。这是因为组合引擎没维护对话状态上下文。解决方案在每个Skill执行时自动注入context对象含last_user_query、last_skill_output、conversation_history最近3轮weather.forecast.v2的execute方法签名改为def execute(self, city: str, date: str today, context: dict None) - dict: if city 那 and context and context.get(last_skill_output): # 从上一轮输出中提取城市 city self._extract_city_from_context(context) return {...}上下文对象本身也作为Skill的输入约束字段强制开发者考虑状态依赖。4.4 “技能库越来越臃肿”——不是加得太多是没做能力归并团队常犯的错误为每个微小差异新建Skill如email.send_to_one.v1email.send_to_two.v1email.send_to_dept.v1这导致检索候选集爆炸组合模板复杂度飙升。归并原则参数化替代多版本email.send.v2的to字段支持string单人、list[string]多人、dict部门角色用同一Skill处理能力分级基础Skillemail.send.v2只负责发信高级Skillemail.approval_flow.v1封装审批流程后者调用前者定期审计每月运行脚本统计所有Skill的调用频次调用率0.1%且30天无更新的Skill自动进入“归档队列”由负责人确认是否删除。我们曾清理掉47个低频Skill技能库体积减少35%但任务完成率反升2%因为LLM在更精简的候选集中更容易做出正确选择。5. 技能库的终极形态从工具箱到能力操作系统5.1 不是“有多少Skill”而是“能多快构建新能力”很多团队把技能库当成果展示墙堆了200个Skill就沾沾自喜。但真正的价值在于当业务方提需求“下周要支持电子签章”你能在2小时内上线可用Skill而不是排期两周。这要求技能库具备能力原子化签章功能拆解为document.hash.v1生成文档哈希、signature.create.v1调用CA服务、document.merge.v1PDF合并每个都是独立Skill组装可视化提供低代码界面拖拽Skill节点连线定义数据流自动生成组合模板一键发布点击发布自动完成注册到ES/Neo4j、生成API文档、部署到K8s集群、加入灰度流量池。我们内部叫它“能力乐高台”产品同学自己就能搭出新流程。上周法务部要“合同自动归档”他们用3个现有SkillOCR识别、关键词提取、NAS存储搭了个模板从提需求到上线只用了1小时17分钟。5.2 技能库的护城河不是代码是持续积累的语义知识最后说个容易被忽视的点技能库的价值70%不在代码而在语义元数据。weather.forecast.v2的trigger_phrases里“热不热”“温度怎么样”“体感如何”这些短语是三年来从12万条用户Query中提炼的input_constraints.entity_types里的GPE/LOCATION/DEPARTMENT是和NLP团队联合标注的百万级实体词典fallback_strategy的“缓存24小时”决策是基于天气数据变化频率的统计分析。这些知识无法复制只能积累。所以别急着开源你的Skill代码先保护好你的语义知识库——这才是Agent项目真正的护城河。我在成都带团队做政务Agent时有个老同事退休前交给我一个U盘里面是17年整理的《政府公文术语-技能映射表》比如“拟办意见”对应document.review.v1“呈报领导”对应approval.route.v2。现在这个表还在驱动着每天20万次的公文处理。真正的技能库从来不是代码仓库而是组织能力的活体字典。
返回列表