新闻详情

新闻详情

首页 / 资讯中心 / 详情

Agent技能库设计:从提示词碰运气到可注册可复用的能力系统

发布时间:2026/9/25 5:00:31来源:尧图网络
Agent技能库设计:从提示词碰运气到可注册可复用的能力系统
团队最近把Agent从demo推向真实业务最大的感慨是一个能稳定干活、能加新能力、能快速排障的Agent缺的从来不是花哨的提示词而是一套结构化的技能系统。这几年社区里陆续出现agent-skills这类思路的项目核心都在做同一件事——把Agent的能力从“提示词里碰运气”变成“注册表里挑技能”。这篇就完整聊聊我们落地这套技能体系的全过程包括为什么做、怎么设计、具体怎么实现以及我们在生产环境踩过的坑。如果你正在用LangChain、CrewAI、AutoGPT这类框架或者干脆自己手写Agent循环这篇文章应该能帮你少走不少弯路。我会尽量把设计取舍和失败案例讲透而不是只给一个能跑的demo。1. 为什么Agent需要一套“技能库”而不只是更好的提示词1.1 没有边界的Agent什么都会等于什么都不会先说说我们最早犯的错。第一版Agent很天真把所有工具都塞进System Prompt里再挂上Function Calling的列表感觉万事大吉。刚开始只有三五个工具时模型选得还挺准等工具数量慢慢涨到二十几个问题就来了提示词占了快三千token模型开始频繁选错工具两个功能相近的工具经常被混淆甚至有时候模型会在回答里“脑补”一个不存在的工具名。这就好比给一个新人发了一本五百页的说明书他翻到最后一页就忘了第一页写了什么。技能库解决的第一件事是给每个能力画清楚边界这个能力是干什么的、在什么场景下用、需要什么参数、不能用在哪里。模型不需要再靠模糊记忆去猜它只需要在各种技能卡片里做选择而每张卡片上的话是固定的、经过校验的不会被上下文冲淡。1.2 提示词、工具与技能到底差在哪很多人会问我直接写提示词让模型调用函数不也是技能吗我的理解不太一样。提示词是一次性的执行策略工具是原子操作而技能是一整套可复用的能力单元它包含触发条件、输入输出协议、内部实现和失败兜底策略。维度提示词Tool/FunctionSkill复用性低换个场景就要改中函数本身可复用高连同描述、校验、兜底一起复用可测试性差靠人工看回答中可直接调函数验证好可做单元测试和回归测试可发现性模型靠理解推断模型靠函数名和描述靠注册中心自动列出支持白名单容错能力几乎没有需要自己写try/catch内置fallback和错误结构化返回版本管理不可控函数版本就是代码版本元信息里可以带版本、成本和响应延迟一个技能可以只包装一个工具也可以把多个工具串起来。比如“查天气并发提醒”这个技能内部可以同时调天气接口和定时任务服务对外暴露给模型的只有一个入口。这种封装让Agent的对外能力图谱非常干净模型不需要理解“先调A再调B”。1.3 什么时候你才真正需要技能化不是所有项目都需要上技能库。如果只是写个脚本让Agent调用两个API直接Function Calling完全够了。我的判断标准有几个工具数量明显超过十个、多个场景要复用同样的能力、希望给能力做权限控制和灰度发布、需要让非技术的同学也能理解Agent到底会什么。当上面任意两条命中我就建议认真搭一套技能体系。它不会让单次调用的延迟变快但会让项目在“能力的数量”增长时保持可控这是最值钱的部分。2. 核心设计技能的结构、注册与调度机制2.1 技能的最小结构先别把接口设计复杂我们第一版的技能结构设计得过于复杂塞了十几个字段结果模型和代码都不好用。后来不断精简沉淀下来一套最小结构name、description、input_schema、execute、metadata外加一个可选的fallback。用Python的dataclass加pydantic表达大概长这样from dataclasses import dataclass, field from typing import Callable, Any, Optional import json dataclass class Skill: name: str # 技能唯一ID模型侧会用到 description: str # 给LLM看的人话说明 input_schema: dict # JSON Schema声明参数类型和约束 execute: Callable # 真正执行技能的函数 metadata: dict field(default_factorydict) fallback: Optional[Callable] None def run(self, **kwargs): try: return self.execute(**kwargs) except Exception as e: if self.fallback: return self.fallback(e) raisedescription严格来说是写给模型看的不是给人看的。我们所有技能描述都控制在两到三句话以内第一句说技能干什么第二句说什么场景触发第三句说什么情况不要用。input_schema用标准JSON Schema而不是用自然语言注释因为LLM对结构化参数的命中率远高于对文字的猜测而且我们可以在模型调用前做一次类型校验。2.2 注册中心与自动发现让Agent知道自己会什么技能需要有一个地方统一管理。我们用了最朴素也最靠谱的方案进程启动时扫描skills目录每个技能文件通过装饰器自动注册到全局的SkillRegistry里。# core/registry.py class SkillRegistry: _skills {} classmethod def register(cls, skill: Skill): if skill.name in cls._skills: raise ValueError(fduplicated skill: {skill.name}) cls._skills[skill.name] skill classmethod def get(cls, name: str) - Skill: return cls._skills.get(name) classmethod def all_skills(cls) - list: return list(cls._skills.values()) def skill(name: str, description: str, input_schema: dict, **meta): def decorator(func): s Skill( namename, descriptiondescription, input_schemainput_schema, executefunc, metadatameta, ) SkillRegistry.register(s) return func return decoratorregister的同时我们维护了一张“可对模型暴露的技能清单”和一张“隐藏技能清单”。隐藏技能只允许内部编排流程调用不允许模型直接发起调用。举个例子我们有一个“批量发送通知”的技能它内部会调用“查询用户列表”这个隐藏技能。如果让模型自由选它很可能把用户权限校验逻辑绕过去这属于边界问题必须在注册层就卡住。2.3 编排层的最佳实践让模型决策让代码执行技能库搭起来之后最难的问题是“怎么让多个技能协作”。我们踩过几个版本坑之后结论很明确不要让模型自己编排复杂的长链路模型只负责“选哪个技能、填什么参数”执行顺序、循环、重试、并发控制全部交给编排层的代码。我们用一个极简的DSL描述流程每个步骤声明调用哪个技能、参数怎么从上下文里取、出错之后是重试还是回退。这样做的好处是流程可测试、可观测、可回滚。我见过不少团队试图说服模型“自己规划三步调用”实测下来在链路超过三步、并且每步都有状态依赖的时候模型的稳定性断崖下跌。技能库的存在就是为了把“决策”和“执行”解耦决策可以走LLM执行必须走代码。3. 实操从零搭建一套agent-skills技能系统3.1 先搭一个最小可运行的技能库空谈设计没有用我们直接上手搭一套。先看目录结构agent-skills-demo/ ├── core/ │ ├── __init__.py │ ├── registry.py │ └── agent.py ├── skills/ │ ├── __init__.py │ ├── date_skill.py │ └── weather_skill.py ├── main.py └── requirements.txtregistry.py直接用上一节的代码skills/date_skill.py写一个计算日期的技能顺便演示日期加减和星期几查询这个技能代码短但参数校验有意义。# skills/date_skill.py from datetime import datetime, timedelta from core.registry import skill skill( namecalculate_date, description计算日期偏移例如三天后是几号、下周五是几号。仅用于日期计算不要用于设置提醒。, input_schema{ type: object, properties: { base_date: {type: string, description: 基准日期格式YYYY-MM-DD默认今天}, offset_days: {type: integer, description: 偏移天数正数为之后负数为之前} }, required: [offset_days] } ) def calculate_date(base_date: str None, offset_days: int 0): dt datetime.strptime(base_date, %Y-%m-%d) if base_date else datetime.now() result dt timedelta(daysoffset_days) return {result_date: result.strftime(%Y-%m-%d), weekday: result.strftime(%A)}core/agent.py里实现一个最简单的调用循环先把所有技能描述合并成系统消息再让模型根据用户输入选技能和参数校验通过之后执行最后把执行结果组装给模型生成回答。这里省略具体LLM API细节关键是流程# core/agent.py import json from core.registry import SkillRegistry def build_system_prompt(): lines [f可用技能:\n] for s in SkillRegistry.all_skills(): lines.append(f- {s.name}: {s.description}) lines.append(f 参数: {json.dumps(s.input_schema)}) lines.append(请严格按技能定义构造参数) return \n.join(lines) def run_agent(user_input: str, llm_call): system build_system_prompt() # 1. 让LLM选择技能和参数 decision llm_call(system, user_input) skill_name decision[skill] params decision[params] # 2. 参数校验与执行 skill SkillRegistry.get(skill_name) result skill.run(**params) # 3. 把结果交给模型生成最终回答 return llm_call(system, f技能执行结果: {result}\n请基于该结果回答用户)第一次跑起来你会发现很多问题模型可能选了技能但参数少传一个校验失败时没有任何反馈。别急着加复杂逻辑先把最小闭环跑通后面再逐层加固。3.2 技能描述这样写模型更容易选对技能描述是整套系统里性价比最高的优化点。我们最早写描述非常随意比如“用于计算日期”后来发现模型动不动就把日期技能当成“设置提醒”工具。改成“仅用于日期计算不要用于设置提醒”之后误判率明显下降。我总结了一个描述模板按这个顺序写基本不会出大错能力概述这个技能返回什么、能做到什么程度触发场景用户说哪些话、出现哪些关键词时调用典型示例给一个迷你问答示例模型理解成本最低使用限制哪些场景明确不能用防止边界外调用比如天气技能的描述可以这么写查询指定城市当前天气支持实时温度和天气状况。 当用户说“今天热不热”、“北京天气”、“明天上海下雨吗”时触发。 示例用户问“深圳今天几度”调用本技能city深圳。 不要用于查询历史天气、未来七天预报也不要用于空气污染指数查询。这段描述的实际token开销大约70个如果我们有50个技能描述总量也就3500token左右完全在可控范围内。同时在描述里明确“不要用于什么场景”比只写“用于什么场景”更有效果模型对否定限制的敏感度远高于正面功能描述这是我们在A/B测试里看到的现象。3.3 参数校验宁可让模型重新填也别带病执行参数校验是技能库最容易被忽略、但最影响稳定性的环节。我们早期图省事拿到模型的JSON参数直接硬传进函数结果数据里混进了单位、带了多余空格、日期格式五花八门函数崩了模型一脸懵。后来统一用JSON Schema做前置校验任何类型不合法、缺少必填参数、枚举值不对的请求都直接拒绝执行并把错误原因返回给模型让它重新组织参数。用pydantic做校验很省事from pydantic import BaseModel, ValidationError class DateSkillParams(BaseModel): base_date: str | None None offset_days: int def validate_skill_params(skill_name: str, params: dict): # 这里根据skill_name获取对应的pydantic模型类做校验 try: validated DateSkillParams(**params) return validated.model_dump(exclude_noneTrue) except ValidationError as e: raise SkillParamError(f参数不完整或格式错误: {e})这里要控制schema的复杂度。我们曾经把一个技能设计成三层嵌套对象模型第一反应是困惑参数错误率接近40%。改成扁平的字段结构并给每个字段加示例值之后成功率几乎翻倍。经验是技能对外暴露的参数最好不要超过五个能拆成两个技能就不要硬塞到一个技能里。4. 我用这套方案踩过的坑与排查技巧4.1 模型选错技能的常见场景模型选错技能是最常见的失败模式尤其是两个技能描述有重叠的时候。我们实际遇到过一个叫“查询订单金额”的技能一个叫“按金额筛选订单”的技能前者是查单笔订单多少钱后者是按金额范围筛订单列表。模型经常搞混后来我们给两个描述里都加了“不适用”的说明并在技能名上做了更清晰的区分混乱才真正止住。这里有一个重要原则废弃的技能要彻底下线不要留在注册表里“备用”。有一段时间我们保留了一个旧的“发送短信”技能只是没有再给模型暴露结果发现模型在调用“发送邮件”时竟然会“想象”出一个发短信方法因为它见过类似的函数名。技能库不是收藏夹留着旧版本只会让模型更困惑。4.2 上下文爆炸与结果截断技巧有些技能会返回很长的数据特别是查表、拉日志这类场景。我们遇到最夸张的一次一个查询技能返回了50000多个token的表格数据模型当场“失忆”完全忘了用户之前问过什么。我们后续做了三层防护技能内部优先返回摘要而不是原始数据比如查询订单列表默认返回前20条加总数外部包装层对技能返回值做长度截断超过2000字符就做精简并在结果里标注“已截断”在技能描述里提醒模型如果返回内容太长请先汇总后回答不要逐行复述这三种方式组合下来上下文溢出问题基本绝迹。还有一个小技巧把耗时长的技能结果先写进独立的存储模型只需要拿一个result_id后面要细节再按需读取类似分页思想。4.3 技能内部错误要“人话化”地抛给模型Agent技能报错有个独特的坑错误信息是给程序员看的但模型拿到之后根本没法处理。比如技能抛了一个KeyError: city模型看到这个根本不知道该怎么办。后来我们统一规范技能内部不允许裸抛语言异常所有业务错误都必须转成结构化的SkillError带上actionable_message字段告诉模型哪里错了以及应该怎么补救。class SkillError(Exception): def __init__(self, message: str, actionable_message: str): super().__init__(message) self.actionable_message actionable_message比如查天气这个技能如果参数里城市为空、或者城市列表里没有匹配项错误消息就不能是“城市无效”这么简单而应该返回“没有找到城市‘浦东新区’请确认城市名是否是市级行政区例如上海、北京”。模型拿到这个提示之后有相当大概率会自己修正参数再试一次整个链路看起来就像是模型自己学会了纠错。4.4 调试三板斧日志、单测、回放技能系统调试有一个特点问题往往不是固定复现的而是模型偶发选择导致的。我们靠三件事把排查效率拉起来。第一给每个技能调用打结构化日志不只是打印参数和结果还要把模型当时的完整“选择理由”记下来。这个理由能帮你快速判断模型选错是因为描述有歧义还是因为参数格式理解偏差。第二给每个技能都做单元测试这一条怎么强调都不过分。技能本身是纯函数输入输出清晰不写单测就没有快速回归的手段。第三做一个“回放”工具把线上某次出错的用户请求和当时的注册表快照存下来离线重复跑不断调描述、调schema、看效果直到问题稳定消失。4.5 常见问题速查表问题可能原因解决方案模型选错技能两个技能描述重叠、技能名相似重写description加入“不适用”场景改名下线废弃技能参数解析失败JSON Schema嵌套太深、字段缺少示例扁平化参数结构字段加examples用pydantic校验并提示修复上下文溢出不记得用户需求工具返回数据量过大技能返回摘要结果截断持久化大结果只返回result_id技能内部抛异常模型不知怎么处理裸抛程序异常统一转SkillError附actionable_message让模型能纠错重试多技能串行执行时链路卡死编排层超时设置缺失给每个技能设超时上限编排层加熔断和重试并发场景下资源竞争技能内部用了非线程安全的资源对数据库连接、文件句柄等做线程隔离或加锁这套速查表现在是我们团队新成员上手的必读文档很多问题其实在早期设计阶段就能避免并不需要等到线上才排查。最后说一点个人体会。做一个Agent技能库本质上不是在堆功能而是在定义能力和边界。每个技能都应该被当成一份写给协作者的接口文档只不过第一个消费这份文档的不是程序员而是LLM。把描述写得足够清晰、把参数约束做得足够严格、把错误处理做得足够友好模型的表现就会顺理成章地稳定下来。我建议你从两三个技能开始搭等结构和约定稳定了再慢慢扩充不要一上来就追求大而全。技能库和模型一样都是在边界清晰的前提下能力越大才真的越可靠。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

VLLM 详细学习笔记 第一章:开篇速览与环境就绪(TaoToken 统一 Key 接入配置) 2026/9/25 5:42:02

VLLM 详细学习笔记 第一章:开篇速览与环境就绪(TaoToken 统一 Key 接入配置)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
UDS诊断0x19服务0x06子功能详解:按DTC读取扩展数据记录 2026/9/25 5:41:56

UDS诊断0x19服务0x06子功能详解:按DTC读取扩展数据记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
JSON配置+TT模板:自动生成MyBatis全套CRUD代码 2026/9/25 5:41:56

JSON配置+TT模板:自动生成MyBatis全套CRUD代码

每次接到“给业务表加个查询接口”的需求,我心里都会先叹一口气。不是功能难写,而是要在实体类、Mapper接口、XML映射、DTO、Service、Controller之间来回补代码,同一个字段名要在七个文件里原封不动出现七八次。有一次我只改了实体没改XML&a…

阅读更多 →
Twig IntlExtension 的 currency_name 过滤器:在模板中把 ISO 4217 货币代码转为本地化货币名称 2026/9/25 5:41:50

Twig IntlExtension 的 currency_name 过滤器:在模板中把 ISO 4217 货币代码转为本地化货币名称

后端 【免费下载链接】Twig Twig, the flexible, fast, and secure template language for PHP 项目地址: https://gitcode.com/gh_mirrors/tw/Twig 点击查看 免费下载 currency_name 过滤器是 Twig 国际化扩展(twig/intl-extra 包中的 IntlExtension&a…

阅读更多 →
SUMO交通仿真入门:从零搭建交叉口仿真与TraCI控制 2026/9/25 5:41:50

SUMO交通仿真入门:从零搭建交叉口仿真与TraCI控制

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
开源可审计的AI代码审查新范式:LLM嵌入Git流程实践 2026/9/25 5:41:50

开源可审计的AI代码审查新范式:LLM嵌入Git流程实践

1. 项目概述:这不是一个“工具”,而是一套可落地的代码审查新范式“open-code-review”这个词乍看像某个开源项目名,但实际它代表的是一种正在快速成型的工程实践——用开源、透明、可审计的方式,把大语言模型(LLM&…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉