☰
Agent技能库落地实践:从提示词堆叠到可管理、可评估的技能体系
2026/10/7 3:51:26 网站建设 项目流程

最近在重构手头的一个Agent项目时,我把原先散落在Prompt模板和工具函数里的各种能力,统一收拢成了一个起名很朴素的模块——agent-skills。这个名字背后是一个很直接的想法:既然Agent本质上是“大模型+工具+记忆”的组装体,那它对外表现出来的每一项本领,都应该被当作一个可管理、可评估、可迭代的“技能”来对待,而不是一段一段飘在提示词里的玄学。

这篇文章就来聊聊我在agent-skills上的完整落地过程:为什么要做技能库、技能怎么拆才能既通用又不含糊、注册和调用的工程链路怎么设计、以及最重要的——如何让这套体系在真实业务里越用越顺手。内容适合正在做Agent开发、或者准备把原型Agent推向生产环境的朋友,看完可以直接抄走一版能跑的架构思路。

1. 为什么Agent需要一套“技能库”,而不是更多提示词

1.1 从一次翻车现场说起

先说个让我下决心重构的翻车案例。当时我在做一个办公助手类Agent,需求是让它帮忙处理邮件、整理会议纪要、查排期。第一版很简单,一段系统提示词加上五六个Python函数,就把核心链路跑通了。Demo演示给同事看,效果不错。

问题出在场景扩展到20多个以后。今天加一个“查天气”功能,明天加一个“生成周报”功能,所有能力都塞进同一个system prompt里。提示词越来越长,模型开始顾此失彼:它会在不该调用工具的时候强行调用,也会在用户问“明天下午三点会议室还有没有空”这种很直接的问题时,兜一大圈先去查了日历API、又去翻了联系人列表,最后才反应过来要查楼层平面图。

更麻烦的是排错。某个技能出问题时,你根本说不清问题到底出在提示词描述不清晰、工具参数传错了、还是模型上下文被其他技能描述挤占。整个系统变成一个黑盒,改一句Prompt可能让A场景变好、B场景直接崩掉。

这个局面的本质是:我把“能力”和“提示词”绑定在一起,能力越多,提示词越臃肿,模型的决策空间被不相关信息污染得越严重。我需要的不是继续往Prompt里堆描述,而是把这些能力拆出来,变成一份份独立的、可被模型按需发现的技能清单。

1.2 提示词堆叠与硬编码调度的三大死穴

把Agent的所有能力都揉在提示词里,至少有三个绕不开的问题:

第一,上下文窗口被无效信息持续占用。模型每轮推理都要把完整的技能清单读一遍,哪怕这轮只需要其中两个技能。窗口被占满之后,要么强行截断历史,要么牺牲长对话能力,两样都伤体验。

第二,能力边界模糊。人话描述再怎么精炼,模型还是可能误解“这个技能到底在什么条件下该用”。比如你写了一句“当用户提到会议室时,调用会议室查询技能”,用户说“帮我找个小会议室”,模型可能纠结到底该不该触发,因为“会议室”这个词出现了,但意图其实是“预订”,不是“查询”。

第三,灰度迭代基本不可能。改了某个技能描述,等于改了整份Prompt,影响面是不可控的。你无法精确回答“这次改动到底影响了哪些对话”,只能靠回归测试碰运气。

硬编码调度则是另一个极端。把技能调用逻辑写成if-else或者规则引擎,虽然可控性上来了,但代价是Agent失去了灵活性——用户换一种说法,规则没覆盖到,能力就暴毙了。而且每加一个新技能,都要写一套匹配规则,维护成本线型上涨,场景一多就变成天坑。

1.3 技能库到底管的是什么

agent-skills想解决的问题,就是在“提示词堆叠”和“硬编码调度”之间找一个中间态:技能是独立描述、独立注册的,但技能的选择和执行交给模型,配合一个轻量级的路由层来兜底。

具体来说,它管四样东西:

  • 技能的元信息:这个技能叫什么、干什么、什么时候该用、参数结构是什么样的;
  • 技能的执行体:真正干活的代码或API调用逻辑;
  • 技能的上下文声明:哪些信息必须提前拿到,哪些信息可以现场推断;
  • 技能的评估结果:它过去接了多少次调用、成功率多少、平均耗时多少。

这套思想其实跟微服务很像——把单体拆成服务,服务之间有契约、有注册中心、有调用链追踪。Agent的“技能”就是微服务里的“服务”,只不过消费方从别的程序变成了大模型。

2. 技能拆解:把任务域切成一棵可复用的技能树

2.1 技能的标准五要素

在agent-skills里,每个技能都要按统一的Schema注册。我的做法是五要素,缺一不可:

要素作用设计要点
技能名称唯一标识用动词+名词结构,比如schedule_meeting
技能描述给模型看的“招聘广告”说清楚干什么、什么时候用、什么时候不用
参数Schema调用契约用JSON Schema定义入参,越精简越好
执行逻辑真正干活的部分一段函数、一个API封装,或一条工作流
触发条件路由兜底规则可选,用于在模型选错时做强制修正

技能名称和参数Schema是给代码看的,技能描述是给模型看的,这两者经常被混为一谈,后面我会专门讲这个坑。

2.2 一个实战案例:消息助手怎么拆成6个原子技能

拿一个很常见的“消息助手”需求举例。这个Agent要做的事是:理解用户用自然语言描述的消息意图,调用通讯工具发消息、拉群、查未读数,汇报发送结果。

一开始我把它做成一个巨大的process_message_request函数,参数里有五六个可选字段。结果模型经常漏填参数,要么没传接收人,要么消息内容带了Markdown符号去发到纯文本IM。

拆完以后变成6个原子技能:

  1. resolve_recipient:从“给产品组的张三发消息”里解析出通讯录中的具体账号;
  2. get_group_info:根据群名关键词查群ID和成员列表;
  3. send_text_message:发送纯文本消息;
  4. send_rich_message:发送带格式或附件的消息;
  5. check_unread_count:查询某个会话的未读数;
  6. summarize_send_result:把发送成功、失败、部分失败的结果整理成一段话回复用户。

拆完之后,模型在大部分场景下会自动按顺序调用:先resolve_recipient,再send_text_message,最后summarize_send_result。每个技能的入参都只有两到三个字段,漏传和错传的比例大幅下降。

这个案例说明一个道理:技能拆得越细,单次调用的认知负担越小,模型越不容易犯错。但拆得过细也有问题,后面会讲。

2.3 技能的粒度定多细才合适

我自己的经验是,用两个标准衡量粒度:一是入参数量不超过5个,二是技能描述能在一句话内说清楚。如果某个技能的描述需要三句话还说不明白,说明它承担了不止一个职责,继续拆。

反过来,如果几个技能总是被模型连续调用,而且调用顺序几乎固定,比如resolve_recipient后面永远跟着send_text_message,那就可以考虑合并成一个复合技能send_message_to_recipient,减少模型做多余决策的次数。

这里有个平衡:原子技能拆得太碎,模型可能为了完成一个简单的任务频繁发起多次调用,每一次调用都有延迟成本和失败风险;粒度太粗,又会重蹈参数爆炸的覆辙。我的建议是先拆碎,再根据实际调用日志合并,用数据驱动而非拍脑袋。第一版agent-skills里我就犯过这个错——上来就拆了60个技能,很多技能一个月都用不到两次,白白增加了模型每次读取技能列表的干扰。

3. 技能注册与调用链路的工程落地

3.1 注册中心与技能Schema定义

技能注册中心在agent-skills里承担的角色,相当于一个“技能的菜单”。模型每次决策前,系统会把菜单里的一部分技能描述连同它们的参数Schema塞进上下文,让模型“知道这里有这些能力可以用”。

菜单不能全量塞。60个技能全塞进去,跟上一种所有能力写进Prompt没有本质区别。所以注册中心要支持技能分组和按需加载。我的做法是给每个技能打标签,比如“通讯”“日历”“数据查询”,模型先按用户意图锁定一个标签组,再从这个组里选具体技能。

技能Schema我用JSON Schema格式存储,它的好处是:既能做运行时校验,又能直接转成模型的Function Calling参数结构。以Python环境为例,定义如下:

{ "name": "send_text_message", "description": "向指定的联系人发送一条纯文本消息。适用于用户明确要求发送文字信息且接收人已解析成功的场景。", "parameters": { "type": "object", "properties": { "recipient_id": { "type": "string", "description": "接收人在通讯录中的唯一标识,需先通过 resolve_recipient 获得" }, "content": { "type": "string", "description": "要发送的纯文本内容,长度不超过2000字" } }, "required": ["recipient_id", "content"] } }

这里有两个细节值得提。第一,description里必须写明“需先通过 resolve_recipient 获得”,这等于告诉模型技能之间的依赖关系,避免它拿一个原始人名直接塞进来。第二,参数要用object而不是裸字段,因为大部分Function Calling协议都要求结构化对象,后续做扩展也方便。

3.2 技能选择器的两条路径:规则路由与嵌入检索

模型模型不一定每次都能选对技能,所以agent-skills里还有一个技能选择器作为兜底。它跑在模型决策之前,做两件事:

第一件事是粗过滤。根据用户当前消息,用关键词规则或者一个轻量级意图分类模型,快速排掉明显无关的技能组。比如用户消息里出现了“开会”“会议室”,但没有任何与“发消息”相关的词,就把通讯组过滤掉,只留给模型会议相关技能。这一步能把候选技能数量从几十个压到五六个,模型的选择准确率会高很多。

第二件事是预检索。当技能数量超过一定规模之后,靠标签分组还是太粗糙,我直接用Embedding把每个技能的描述向量化,用户消息也向量化,算余弦相似度,召回Top-K个技能。这个方案在技术选型上很常见,实际效果也稳定。

但要注意,不能把嵌入召回当作唯一选择机制。模型Function Calling在“候选技能少且描述清晰”时表现得已经很好,嵌入检索只是缩小候选范围,最终拍板权还是交给模型。这样设计的目的只有一个:减少模型的决策负担,而不是取代模型的判断。

附上一个典型的调用流程图逻辑:

用户消息进入 → 技能选择器先做粗过滤/嵌入召回 → 候选技能列表(连同Schema)拼接到上下文 → 模型决策调用哪个技能 → 执行体运行 → 结果返回给模型 → 模型生成最终回复

这条链路里,每个环节都有功能够独立测试和替换。想换召回模型,只改选择器;想升级某个技能的参数Schema,只动那一个技能的注册内容。

3.3 执行引擎的工作流与超时保护

技能的执行体五花八门:有纯函数、有HTTP调用、有数据库查询,甚至有需要多步协调的工作流。agent-skills里我把它们统一包装成一个执行接口,输入是已经解析好的结构化参数,输出是一个标准结果对象:

执行结果对象: - status: success / failed / timeout - data: 实际返回的数据 - error: 错误信息(如果失败) - latency_ms: 耗时

执行引擎最重要的任务是超时保护。Agent场景里,模型调用技能是同步等待的,技能如果长时间不返回,用户的对话体验会直接崩掉。我的经验是默认超时设为5秒,复杂技能可以单独配置为15秒,但对大多数原子技能来说,5秒已经是上限了。

超时后怎么处理也很关键。我见过不少项目在超时后直接抛异常,让模型一脸懵地告诉用户“出错了”。更好的做法是把超时当作一种结果返回给模型,让模型基于已有信息尝试备选方案。比如send_text_message超时了,模型可以告诉用户“发送没有确认成功,我帮你再试一次,或者你先检查一下网络” —— 这比冷冰冰的“服务异常”舒服得多。

另外,执行引擎要支持技能重试。对于幂等的技能,比如查询类,超时后自动重试一次没问题;但对于有副作用的技能,比如“发送消息”,重试可能造成重复发送。所以,幂等性评估在注册技能时就要做,并在技能元信息里标注是否允许自动重试。

3.4 观测性:每个技能的调用都要留痕

没有观测性的Agent系统,等于开盲盒。我在agent-skills里强制要求:每一次技能调用都必须记录一条完整的日志,包含这样几个字段:

  • 用户消息原文(脱敏后);
  • 候选技能列表和最终选中的技能;
  • 入参和出参;
  • 调用耗时、是否超时、是否重试;
  • 模型最终生成的回复。

这些日志的价值,远不止排查故障。它们是后续技能评估和迭代的原材料,我在下一节会展开讲。如果没有这些留痕,技能改得好不好就只能靠感觉,这是生产环境绝对不能接受的。

4. 评估与迭代:让技能库越用越聪明

4.1 离线评估:用历史对话做技能召回率测试

技能库建好之后,最怕一件事:模型压根不知道该在什么时候调用某个技能。要防住这个问题,就得做召回率测试。

我从线上日志里抽出一批历史对话,每条记录都标注了“当时期望调用的技能”是什么。然后我做一个离线脚本,把用户消息喂给技能选择器,看它能不能在Top-K里召回正确的技能。召回率低于90%的技能,要么描述写得不够清晰,要么候选过滤太激进,要么就是技能本身和其他技能语义太接近,需要合并或重新切分。

这个评估方法类似搜索系统的召回率评估。技能描述就是索引里的文档,用户消息就是查询,目标是把正确的技能排到候选列表里。我会在代码里加一个简单的评估脚本,定期跑一遍,用召回率数字监控技能库的整体健康度。

# 伪代码示意:技能召回率离线评估 def evaluate_recall(test_cases, skill_selector, top_k=5): hit = 0 for case in test_cases: candidates = skill_selector.retrieve(case.user_message, top_k=top_k) if case.expected_skill in candidates: hit += 1 return hit / len(test_cases)

这个数字不求一上来就是100%,但每次调整技能描述后,它的变化趋势能告诉你改动方向对不对。

4.2 在线观察:从日志里找出技能调用的异常模式

离线评估看的是“能不能想起这个技能”,在线观察看的是“用起来顺不顺”。我在日志里特别关注三个指标:

第一个是调用成功率。低于95%的技能必须立即查原因,大概率是参数Schema设计不合理,或者执行体本身有Bug。第二个是平均调用耗时。超过3秒的技能要警惕,它们会拖累整条对话的响应速度。第三个是调用后的纠错频率——这是我最看重的指标。如果某次调用之后,模型紧接着又调用了另一个功能或重复提交,说明上一个技能的结果大概率没让模型满意。

还有一种更隐蔽的异常:模型绕过了正确的技能,用更笨的方式完成了任务。比如我明明提供了check_unread_count,但日志显示模型一直先调用get_group_info再自己推断未读数。这说明技能描述里没有说清楚“什么时候该用它”,或者它和其他技能的边界模糊。遇到这种情况,我会调整描述,明确写出“当用户直接询问未读消息数量时,应首先考虑此技能,而非查询群信息”。

4.3 技能版本与灰度发布:改技能不能靠手感

技能库是活的。今天调一个描述,明天改一个参数,如果每次改完都全量上线,风险太高。我自己的项目里后来加了简单的版本机制:

每个技能都带version字段,修改技能时新增一个版本,旧版本保留。线上运行时,可以做到按用户比例灰度——比如新版本先放10%的流量,观察调用成功率没有下降,再逐步放大到50%、100%。

灰度期间的对比指标主要看三个:调用成功率、平均耗时、最终回复中用户正向反馈的比例。如果新版本在10%灰度期这三项都不输旧版,我才会全量推。

这套机制初期可以做得很简单,不需要引入复杂的配置中心,一个数据库表加一个配置文件就够用了。但“可以简单”和“不做是两回事”——版本机制能让每一次技能改动都可回溯、可回滚,这是技能库能不能持续演进的地基。

5. 踩坑记录:agent-skills里最容易翻车的三个细节

5.1 参数Schema设计得太严,Agent直接罢工

我在做日历技能时,一开始给create_event定义了8个必填参数,包括event_type、location、reminder_minutes、attendee_list等等。上线后发现一个头疼的问题:模型经常在用户没有明确说全信息的时候,直接编造一个默认值,比如顺手把地点填成“线上会议”。

这是因为模型为了满足必填参数约束,强行“补全”信息。更糟糕的是,这些补全的信息用户根本没确认过,Agent就在错误的信息上执行了操作。

解决方式:参数 Schema 遵循“最少必要”原则。必填参数只保留真正不能缺的,比如事件的开始时间;其余全部设为可选,并在描述里明确“如果用户未提供,请主动询问,不要擅自假设”。这样模型在信息不足时会更倾向于追问而不是自作主张。

5.2 技能复用与冗余的平衡:相似技能越来越多

技能库建到一定规模后,会出现一个尴尬的现象:为了适配不同的说法,你可能会建好几个功能几乎一致的技能。比如cancel_meeting和delete_calendar_event,本质上干的是同一件事,只是触发场景不同。

技能冗余会直接破坏召回准确率——模型在候选列表里看到两个语义高度相似的技能,很容易选错,而且选错后排查难度特别大。我后来定了一条规矩:新建技能前,必须搜索现有技能库,如果发现语义相似度超过某个阈值,优先扩展现有技能,而不是另起炉灶。

扩展的方式也很简单:在已有技能的description里增加一句适用范围,比如在delete_calendar_event的描述里补充“包括用户说‘把会议取消’、‘把日程删掉’等场景”。这样技能还是同一个,覆盖场景变宽了,模型的选择负担没有增加。

5.3 权限和沙箱:技能执行边界不清会出事

这是我最想强调的一点,而且是教训换来的。

当Agent技能开始调用真实的API、写入真实的数据时,权限模型必须跟技能一起设计。比如消息助手能发消息,那它能不能在未经确认的情况下自动发送?日历技能能建日程,那它能不能删掉别人的日程?

我在早期版本里放过这个错——为了演示方便,所有技能的执行体都跑在一个高权限服务账户下。结果有一次测试中,模型理解错了意图,把一个“帮我看看明天会议安排”的请求执行成了“发送一封邮件通知参会人会议取消”。虽然是在测试环境,也足以让我惊出一身冷汗。

现在的做法是给每个技能挂一份权限声明:它能访问哪些资源、能执行哪些操作、哪些操作需要用户二次确认。执行引擎在调度前会做一次权限校验,对需要确认的操作,先把结果透传给模型,让模型向用户提问,而不是闷头执行。

这个设计在技术上不难,但必须在技能库成型之前就定下来,否则后面补会很痛苦——因为补权限意味着要重新梳理每一个技能的执行边界,比一开始就考虑要费事得多。

5.4 别把技能库做成“提示词仓库”

最后再说一个方向上的坑。有人会把agent-skills理解成“把一段段优化好的Prompt存起来,按需取用”。这个理解是错的。

Prompt和技能的本质区别在于:Prompt是“说给模型听的”,技能是“让Agent去做的”。技能必须有可执行体、有输入输出契约、有权限边界、有观测日志。哪怕某个技能当前实现仅仅是“让模型输出一段话”,它也应该被包装成有Schema、有权限声明、有评估记录的完整技能,而不是一段裸的提示词文本。

只有把技能当成一类“一等公民”的对象来管理,你才能享受到复用、灰度、评估这些工程能力。否则你只是换了个地方堆提示词,系统该脆弱的还是脆弱。

写在最后

如果你也在做Agent开发,我的建议是:别等到场景多到失控了再做技能化改造。从第一个能力上线开始,就用agent-skills这套思路把它拆出来、注册好、挂上日志。前期会慢一点,但到了20个技能以上的阶段,你会庆幸当初没把所有东西堆在提示词里。

回看整个落地过程,我最大的体会是:Agent技能的工程化,本质上是把“模型的灵性”和“系统的确定性”结合到一起。技能库管住边界和契约,模型在边界内自由发挥。这两者配合好了,Agent才真正从一个demo玩具变成可交付的产品。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询