news 2026/10/24 15:56:13

Agent技能库实战:从提示词失控到可维护的技能编排

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent技能库实战:从提示词失控到可维护的技能编排

1. agent-skills要解决的核心矛盾:能力堆在提示词里的失控

先讲一段真实经历。去年我在做内部工具型Agent的时候,接的需求其实不复杂:帮用户解析上传的PDF、提取关键字段、和公司内部的客户系统做比对,最后生成一份跟进摘要。最开始的做法想必很多人都熟悉——把所有能力都写在system prompt里,告诉模型"你是一个解析助手,遇到PDF要调用解析库,遇到客户信息要调用CRM接口,遇到日期要格式化成YYYY-MM-DD",然后就是不断往里面追加规则。

前两周看起来很美好,模型确实能处理七八成的问题。但第三周开始就失控了。用户问"上周的会议纪要和日程能整合成周报吗",模型把"生成周报"和"解析纪要"两个动作搅在一起,一会儿把纪要文件路径当成参数传给日历接口,一会儿又把周报模板当成了PDF解析配置。我试着往提示词里继续加约束,结果system prompt膨胀到三千多行,每次微调一个功能都要全量回归测试,改一处崩三处。

这个阶段让我彻底明白了一件事:把能力"堆"在提示词里是不可持续的。规则之间互相干扰,参数边界模糊,模型根本分不清什么时候该用哪个能力。后来我转向了agent-skills的思路——把每一项可复用的能力封装成独立可注册的技能单元,配上清晰的触发条件、输入输出协议和执行体,由Agent在运行时动态选择与组合。这条路走下来,系统的稳定性和可维护性都有了质的变化。

1.1 从"规则"到"技能"的本质区别

很多人在刚开始接触agent-skills时,会觉得这不过是换了个词把提示词里的规则拆出来。但在实际落地后,我发现规则和技能有本质区别。

规则是"如果遇到X,就做Y"的线性判断,它依附于某个具体的指令,离开了上下文就失效。而技能是"能独立完成某类操作的最小能力单元",它有明确的输入输出边界、有自包含的执行逻辑、有可观测的成功失败信号。换句话讲,规则告诉模型"怎么做",技能则向模型承诺"我能做什么,你只需要给我正确的输入"。

拿我刚才的例子来说,"解析PDF"就可以做成一个技能。它接收一个文件路径和要提取的字段列表,返回结构化的字段值。模型不用知道里面用了正则还是OCR,只用按照技能的输入协议传参,再消费返回结果。

1.2 技能四要素:一个合格技能的最低配置

在agent-skills的实践里,我把一个技能拆成四个要素,这也是我在代码里定义技能数据结构的基座:

  • 触发条件(when_to_use):一段给模型看的能力描述,说明这个技能适合解决什么问题、不适合解决什么问题。这也是后面做语义检索的核心字段。
  • 输入输出协议(io_schema):定义参数名、类型、必填项、输出结构。这一部分我会用JSON Schema来承载,既能让模型按格式填参,也能在代码侧做运行时校验。
  • 执行体(executor):真正干活的部分,可以是本地函数、HTTP接口调用、一段脚本,甚至是另一个Agent的子任务。
  • 校验与回退(validation & fallback):执行完成后判断结果是否可用的逻辑,以及失败时如何回到模型侧重新规划。

这里要强调一下第四个要素。很多初版技能库都忽略反馈闭环,结果技能执行失败后,模型拿着错误结果继续往下编,最后给用户的是一份看起来完整、其实全是幻觉的报告。校验与回退机制是agent-skills区别于普通函数调用库的关键一点,后面我会专门展开讲。

1.3 技能化改造后,三个我没想到的直接收益

说实话,我最初做技能化改造只是为了解决提示词混乱,但落地之后发现了三个额外的收益:

一是可测试性。技能是独立单元,我可以为每个技能单独写单元测试,不用每次都在多轮对话里手动验证。产品验收时直接跑测试集,比人工点几十条对话高效得多。

二是可复用性。"技能"天然是跨场景的。一个"格式化日期范围"的技能,可以同时被周报生成流程和日程冲突检查流程引用;同一个"解析PDF"技能,在合同审核Agent和客户跟进Agent里都能用。

三是可观测性。每个技能的调用记录、入参出参、执行时长、失败原因都能落日志。出了问题不再是"模型某次表现不好"这种玄学,而是"某个技能在某种输入下触发了回退"这种明确的故障点。

2. 技能库的骨架设计:注册、检索、执行三层结构

概念讲清楚了,接下来是设计问题。我实践的agent-skills架构基本分三层:注册中心、检索路由、执行器。这三层各有各的难点,分别处理"技能怎么存"、"模型怎么找到对的技能"、"技能怎么被安全地跑起来"。

2.1 注册层:把技能变成可被机器理解的元数据

注册层要解决的第一个问题不是存代码,而是存"技能的描述"。我建议每个技能至少包含下面这些元数据:

字段说明示例
id技能唯一标识pdf_parse_contract
name技能短名合同PDF解析
description给模型看的能力描述用于从合同类PDF中提取甲方、乙方、金额、期限等字段,适合格式相对固定的商务合同
parametersJSON Schema格式的参数定义{"file_path": {"type": "string", "required": true}}
returns输出结构说明返回对象字段及类型说明
tags领域标签,辅助检索["pdf", "contract", "extract"]
executor执行器标识,映射到真实处理函数handlers.parse_contract_pdf
enabled是否启用true

description字段是我踩坑最多的一个。写得太长,模型检索时注意力会被无关信息干扰;写得太短,模型又分不清这个技能和另一个技能的区别。后面我在第5节会专门讲description的写法暴力调试经验。

注册中心的存储用什么,我建议视规模而定。技能数量在几十个量级时,一个SQLite表加一个内存缓存就够了,不用上来就上向量数据库。技能数量到了几百上千,再把description和tags做embedding,扔进向量库做语义召回。

2.2 检索层:关键词与语义的双通道召回

检索层是Agent决定"用哪个技能"的关卡。我的做法是采用双通道召回再合并排序。

第一通道是关键词与标签匹配。技能库里每个技能都有一组tags,模型解析用户输入时可以先做一次词典匹配,例如发现"合同""PDF""提取"这些词,就把候选技能缩到几个。

第二通道是语义相似度。把用户的原始请求和每个技能的description做embedding余弦相似度计算,取Top-K。这个通道的好处是应对"说人话"的情况。比如用户说的是"帮我看看这份协议里一年给多少钱",它没有提"合同"也没有提"PDF",但语义上指向的就是合同解析技能。

合并排序的策略我用过两种。前期用简单的加权求和,关键词命中加1.0分,语义相似度乘0.8再加进去;后期技能变多之后改成了两步式路由——先用关键词找出可能相关的技能集合,再在这个集合里做语义排序,效果更稳定,也减少了向量检索的整体时延。

需要特别提醒的是,检索层一定要有"没有合适技能"的出口。模型的原话是:当所有技能的相关性都低于阈值时,明确告诉模型"当前没有可用技能,你需要向用户说明能力边界或建议其他解决方式",而不是强行从现有技能里选一个最不差的。这一步能挡住绝大部分错误调用。

2.3 执行层:参数校验、超时控制与审计日志

执行层是真正让代码跑起来的地方,也是稳定性最容易翻车的位置。我从线上事故中学到的教训是,一定要在调用真实执行体前做三件套:参数校验、超时控制、审计落盘。

参数校验需要严格按照注册时声明的JSON Schema来。这一步不是走形式。模型填参偶尔会给出不存在的字段名,甚至把字符串填成数组。校验不通过时,正确做法不是直接抛异常喊"执行失败",而是把校验错误信息结构化地反馈给模型,让它重新补参。

超时控制同样不能省。有些技能调用外部API,耗时不可控。我默认给外部调用类技能设8秒超时,纯本地计算类技能设3秒。超时之后返回一个明确的状态码,让Agent走重试或换路线的逻辑。

最后是审计日志。每个技能的调用记录包含:请求原文、路由命中的技能ID、入参、出参、执行时长、校验是否通过、回退是否触发。这条日志链是后期排查一切问题的起点。早期我没重视,出问题了只能靠用户复述,效率极低。

3. 技能编排:从单一技能到组合工作流

单一技能只能解决点状问题。真实业务里,用户的需求往往是链状的:解析PDF → 提取字段 → 查重比对 → 生成摘要。这要求agent-skills不只是一个技能工具箱,还必须有编排能力,让多个技能按顺序、按条件地组合起来。

3.1 技能之间的依赖关系与数据传递

做编排之前,先要建立技能之间的依赖表达。最常见的依赖有两种:

  • 前置依赖(requires):执行技能B之前,必须已经执行过技能A。比如"生成周报"技能,依赖"解析会议纪要"技能的输出结果。
  • 并行依赖(requires_any):只需要多个前置技能中的某一个成功即可继续。比如"获取用户信息",可以通过CRM接口,也可以通过本地数据库,两者有一个能跑通就行。

数据传递是另一个容易被忽略的问题。技能A的输出,到技能B的输入,字段名往往不一致。比如解析技能输出的是party_a_name,查重技能的入参却是company_name。我的做法是引入一个轻量的字段映射表,在编排定义里声明source_field -> target_field的映射关系。这样技能本身可以保持通用性,不需要为了配合某一个下游而改自己的输出结构。

3.2 编排模板与条件分支

实际操作中,我不建议一开始就上重型的工作流引擎。大部分Agent任务的编排逻辑是相对固定的,我倾向于用一个声明式的模板来描述。

下面是一个简化版的周报生成编排定义:

workflow: weekly_report steps: - id: step1_parse skill: meeting_minutes_parser input: file_path: "{user_uploaded_file}" - id: step2_calendar_check skill: schedule_conflict_detector input: events: "{step1_parse.output.events}" - id: step3_summarize skill: weekly_report_generator input: parsed_events: "{step1_parse.output.events}" conflicts: "{step2_calendar_check.output.conflicts}" when: "{step1_parse.status == success}"

这个模板的核心价值是它可审计、可定向调试。编排逻辑和模型推理分开,哪一步失败了就直接定位到对应技能和对应的输入数据,而不是在几十轮对话记录里翻找。

另外我建议在编排模板里加入分支字段when。它支持的表达式不多,无非是等于、不等于、存在、为空这几种,但足够覆盖绝大多数真实场景。分支出现得越来越复杂的时候,就说明流程本身需要拆解,而不是把编排模板做成一个图灵完备的语言。

3.3 反馈闭环:技能自省与动态修复

这是agent-skills最值钱也最容易被忽略的一块。技能执行失败后,不能只返回一个error_code就结束。要形成一个反馈闭环,把失败信息变成模型下一步决策的输入。

我在实现里的做法是定义三种失败类型:

  • 参数类失败(invalid_argument):模型给的参数不符合Schema。此时把具体的校验错误返回给模型,让它重新给参。
  • 环境类失败(environment_error):依赖的外部服务挂了。此时通知模型换用备选技能,或者直接告知用户当前服务暂不可用。
  • 执行类失败(execution_error):代码内部错误。此时把堆栈摘要脱敏后写入日志,同时触发一个"技能诊断"动作,让另一个Agent分析是否需要调整技能描述或参数定义。

这个闭环的价值在于,它让技能库拥有了自愈能力。第一次遇到某类错误时,系统可能会回退到"联系人工处理";但同一类错误出现三次以上,我会给技能库加一个"已知问题"的描述字段,提醒模型下次遇到类似情况时提前规避。

4. 一个可运行的技能库最小实现

讲了这么多设计,给出一个可以直接跑起来的最小实现。我用Python写的,主要是因为它写原型快,而且生态里不管是做embedding还是做工具调用都很方便。生产环境换成Java或TypeScript也完全没问题,核心思路是一样的。

4.1 定义技能:从JSON Schema到执行函数

先看技能定义。我把每个技能的元数据用一个dataclass承载,底层用JSON Schema做参数校验。

import json import jsonschema from dataclasses import dataclass, field from typing import Callable, Any @dataclass class Skill: id: str name: str description: str parameters_schema: dict returns_schema: dict tags: list[str] = field(default_factory=list) enabled: bool = True fallback_message: str = "" def __post_init__(self): # 预编译参数校验器,避免每次调用时重复compile jsonschema.Draft202012Validator.check_schema(self.parameters_schema) def validate_parameters(self, params: dict) -> list[str]: errors = [] try: jsonschema.validate(params, self.parameters_schema) except jsonschema.ValidationError as e: errors.append(str(e.message)) return errors

实际注册一个技能时,还需要执行函数。执行函数我建议统一收口成一个函数签名:接收params字典,返回一个ExecutionResult。

@dataclass class ExecutionResult: status: str # success | failed output: Any = None error_code: str = "" error_message: str = "" execution_time_ms: int = 0

下面是一个最简单的"计算统计数据"技能的完整实现:

def execute_statistics(params): import time start = time.time() numbers = params.get("numbers", []) if not isinstance(numbers, list) or not all(isinstance(x, (int, float)) for x in numbers): return ExecutionResult(status="failed", error_code="invalid_argument", error_message="numbers must be a list of int/float") try: output = { "count": len(numbers), "sum": sum(numbers), "mean": sum(numbers) / len(numbers) if numbers else 0, } return ExecutionResult(status="success", output=output, execution_time_ms=int((time.time()-start)*1000)) except Exception as e: return ExecutionResult(status="failed", error_code="execution_error", error_message=str(e))

4.2 注册与加载:技能管理器

技能管理器负责把skill定义和executor绑定起来,并提供检索和调用接口。核心代码如下:

class SkillRegistry: def __init__(self): self._skills: dict[str, Skill] = {} self._executors: dict[str, Callable] = {} def register(self, skill: Skill, executor: Callable): if skill.id in self._skills: raise ValueError(f"duplicate skill id: {skill.id}") self._skills[skill.id] = skill self._executors[skill.id] = executor def list_skills(self) -> list[Skill]: return [s for s in self._skills.values() if s.enabled] def get_skill(self, skill_id: str) -> Skill | None: return self._skills.get(skill_id) def execute(self, skill_id: str, params: dict) -> ExecutionResult: skill = self._skills.get(skill_id) if skill is None or not skill.enabled: return ExecutionResult(status="failed", error_code="skill_not_found", error_message=f"skill {skill_id} not found or disabled") validation_errors = skill.validate_parameters(params) if validation_errors: return ExecutionResult(status="failed", error_code="invalid_argument", error_message="; ".join(validation_errors)) executor = self._executors[skill_id] return executor(params)

4.3 模型侧的调用链路

上面都是基础管道,真正的Agent调用逻辑在于一个Router函数:把用户请求转化成"选技能+填参数+看结果"的循环。用一个简化的伪代码表示:

def run_agent(user_request, registry, llm_client): # 步骤1:把用户请求和技能列表交给LLM,让模型做技能选择 skills_meta = [ {"id": s.id, "name": s.name, "description": s.description, "parameters_schema": s.parameters_schema} for s in registry.list_skills() ] tool_choice = llm_client.choose_skills(user_request=user_request, skills=skills_meta) # 步骤2:校验模型输出 if not tool_choice or tool_choice["skill_id"] not in [s.id for s in registry.list_skills()]: return {"response": "目前我没有找到合适的能力来处理这个需求。"} # 步骤3:执行技能 result = registry.execute(tool_choice["skill_id"], tool_choice["arguments"]) # 步骤4:根据执行结果决定是继续调用还是输出给用户 if result.status == "success": final_answer = llm_client.generate(user_request=user_request, tool_output=result.output) return {"response": final_answer, "trace": {"skill_id": tool_choice["skill_id"]}} elif result.error_code == "invalid_argument": # 把校验错误塞回LLM,让它重新给参 return run_agent(user_request, registry, llm_client) else: return {"response": "这个能力暂时不可用,请稍后再试或调整输入。", "trace": {"error": result.error_message}}

注意步骤4里的一个细节:对于invalid_argument错误,我选择让Agent自动重试一次。但重试要有次数上限,否则模型会陷入无限循环。我的经验是同一个技能最多重试两次,两次都失败就切换其他技能或直接拒绝。

4.4 最小验证:跑一个多技能任务

上面实现跑通后,拿一个组合任务验证。假设技能库里注册了meeting_minutes_parser和weekly_report_generator两个技能,用户输入是"把这份会议记录整理成周报"。

实际运行链路是:

  1. 模型选择meeting_minutes_parser,传入file_path参数;
  2. 解析技能返回结构化的事件列表;
  3. 系统把事件列表作为weekly_report_generator的输入;
  4. 生成技能返回最终周报;
  5. 模型把周报包装成最终给用户的话术。

这个最小实现里没有向量检索,几十个技能规模下靠LLM直接选择也够用。但我建议规格提升到上百技能时,再在步骤1之前加一道检索预筛,避免把全部技能元数据一次性塞给模型。

5. 上线之后的真实翻车现场与补救方案

设计说得再漂亮,不如一次上线跑回来的教训多。下面这几类问题,基本是每个做agent-skills项目的人都会遇到的,我把自己的排查链路和最终方案写出来,算是给大家排雷。

5.1 翻车一:description膨胀导致技能乱选

上线一周后,我遇到最诡异的问题是:用户问"今天天气怎么样",模型却调用了"周报生成器"。查日志发现,周报生成器的description里写了"整合一段或多段非结构化信息",而天气信息在小模型的语义空间里也被归到了"非结构化信息"里,意图匹配直接偏了。

问题根源是description写得太泛,覆盖了大量不该覆盖的场景。我用的补救方案有三步:

  • 收敛描述范围:description只写最能代表该技能的2-3个典型场景,并明确加上否定边界("不适用于计算类问题""不适用于实时数据查询")。以周报生成器为例,描述改为"仅用于把一组结构化事件或会议记录合并为固定模板的周报,不用于回答事实性问题"。
  • 引入典型触发词:在tags里补充"周报、weekly report、会议记录、meeting notes"这类数据,让关键词通道把一个技能稳稳钉在它该在的位置。
  • 做路由回归测试集:准备50条典型用户输入和对应期望技能,每次改description都要全量跑一遍,防止修好一个又弄坏另一个。

5.2 翻车二:技能之间参数抢食

第二个问题是两个技能都有file_path参数,但一个期望是本地磁盘路径,另一个期望是URL。模型在选择技能时,经常把刚才那个技能的参数直接带过来传过去,导致校验失败。

我采取了两层措施。第一层是参数作用域隔离——在技能描述里明确写"本技能的file_path仅接受本地路径,如果输入是URL请先调用文件下载技能"。第二层是加了适配器,两个技能之间如果经常出现参数转换需求,就直接做一个组合技能,把转换逻辑包在里面,对外只暴露一个上层参数,模型就没机会用错参数结构了。

5.3 翻车三:模型把技能当摆设

还有一种头疼的情况是,技能都注册好了,description也写了,但模型就是不主动用,用户问题明明能由技能解决,模型硬是靠自己的知识编了个答案。

这类问题主要出现在两类模型上:一类是工具调用能力比较弱的,另一类是研发为了省token把技能列表砍得太短了。我的排查顺序如下:

  1. 先看技能元数据有没有进入模型的实际可见上下文。有些框架层把技能列表截断了,模型根本不知道有技能存在。
  2. 再检查技能的description是不是太口语化。模型对"工具描述"这种文体的响应是最稳定的,太像闲聊的话,模型反而不会触发调用。
  3. 最后给2-3条few-shot样本,在系统提示里给出相似的"用户问题→技能调用"完整示例。很多情况下,这一招比修改description更有效。

5.4 我的技能准入清单:三个硬性指标

有了这些教训,我现在给一个技能入库定了一个简单的准入清单,宁可少一点,也要保证每个技能是可靠的:

  • 单一职责:一个技能只干一类事情。如果一个技能里又有解析又有计算又有格式化,就拆开重做。
  • 明确失败模式:写代码的时候就必须定义好三类失败可能(参数错、环境错、执行错),并在返回结构里留好字段。这也是我要求必须做压测的标准。
  • 有独立的验收用例:至少5条标准输入和对应预期输出,入库之前先跑通再来注册。

这三个标准把技能库的质量门槛从"能跑"提升到了"可维护"。至少在我这边,遵循这套清单之后,线上因为技能本身质量问题引起的故障少了大半。

最后说一点个人体会。agent-skills本质上不是银弹,它解决的是Agent系统里"能力组织方式"的问题,而不是"模型够不够聪明"的问题。如果你的模型在多轮推理上本来就弱,那再好的技能库也只是降低了它犯错的概率。但反过来,当你发现Agent项目里prompt越来越长、规则互相打架、每次改功能都像是在解耦一团乱麻时,先别急着换模型,试试把能力搬进独立的技能单元里。我实际做下来,这个改造的性价比比调prompt高得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 14:08:58

Hadoop+Spark信贷风控系统:从数据仓库分层到评分卡落地

简介:一份基于Hadoop与Spark的大数据金融信贷风险控系统毕业设计源码,面向计算机相关专业的在校生、教师或从业者,适用于毕设、课设、项目初期立项演示,也适合对大数据风控感兴趣的学习者参考进阶。素材包共23个文件,压…

作者头像 李华
网站建设 2026/10/22 2:32:39

AI基础设施变局:Copilot重构、TPU逆袭与千问开源落地

早上刷信息流的时候,三条新闻几乎同时出现在我的时间线上:微软把 Copilot 往“智能体操作系统”的方向重构,谷歌新一代 TPU 在算力榜单上把英伟达逼到了墙角,苹果那边又传出基于千问开源基座做端侧模型的落地动作。单独看每一条都…

作者头像 李华
网站建设 2026/10/19 12:11:56

Agent技能化设计:从工具调用到可插拔能力编排的工程实践

1. 技能化设计:Agent 工程的必然走向接手 Agent 项目的朋友,大概率都经历过这个阶段:一开始,Agent 的能力逻辑直接写在主流程代码里,判断条件、调用分支、临时工具散落得到处都是。跑通 Demo 没问题,一旦往…

作者头像 李华
网站建设 2026/10/17 8:21:18

AI Agent四大核心技能:CLI、API、UI、SSH工程化实践

1. 项目概述:什么是 agent-skills?它不是个玩具,而是AI工程落地的“肌肉记忆”“agent-skills”这个词最近在开发者社区里频繁刷屏,但它既不是某个新发布的开源框架,也不是某家大厂刚推出的SaaS产品。它本质上是一套可…

作者头像 李华
网站建设 2026/10/19 9:11:14

Superpowers技能包:让AI编程助手从“写代码”到“会干活”

最近我发现,身边用AI编程助手的人分成了两派:一派觉得就是玩具,写点小脚本还凑合,一上大工程就露馅;另一派却能让AI把一个几万行的仓库啃下来,改bug、写方案、做code review都像模像样。差别在哪&#xff1…

作者头像 李华
网站建设 2026/10/19 11:36:15

Java Web聊天系统课设速通:Spring Boot+WebSocket+MySQL完整实现

简介:一套基于前后端分离架构的Java Web聊天系统大作业项目,适合需要完成课程设计、期末大作业或毕设的在校生。后端按分层规范拆分为config、controller、service、dao、entity、dto、utils、vo等模块,config统一管理配置,contro…

作者头像 李华