
这就是“agent-skills”在实战中最真实的三个回答一方面它告诉你智能体的能力边界不是模型决定的而是你给它装备了多少可落地的技能另一方面它逼着你把“让模型变聪明”的模糊愿望翻译成“有输入、有输出、有失败处理”的工程动作。我见过太多团队把大量预算砸在调模型上结果真正卡住进度的永远是“模型连个时间都查不准”。所以这篇文章我会从概念、设计、实操到踩坑完整拆解一套能直接照着做的智能体技能库搭建思路没有任何框架滤镜全是自己在项目里试出来的方法。1. 先搞明白agent-skills 到底在讲什么1.1 一句话回答技能是智能体的“手和脚”不管“agent-skills”这个词在外网被包装成什么花哨概念落到工程上它就是一层“把大模型的能力和外部世界连接起来的中间层”。一个智能体光有推理能力最多只能算一个“会想但不会动”的编辑器一旦你给它装上技能它才能去查数据库、调接口、操作文件、发消息、跑分析脚本。你可以把技能理解成智能体的手和脚思考过程发生在模型内部但所有实际动作都得靠技能去完成。在我做过的项目里一个智能体通常会被赋予三到五类基础技能查资料类检索文档、调API、搜索网页、操作类建表格、写文件、发请求、分析类跑Python脚本、聚合数据、生成图表。这些技能不是散装的功能点而是一套被标准化定义、注册和调用的能力单元。换句话说技能库的质量直接决定了智能体是真干活还是在聊天。1.2 技能工程和提示工程是两码事不少刚接触的人会把“技能”理解成“写一段更长的提示词”这其实是个大坑。提示工程解决的是“模型如何想”的问题技能工程解决的是“模型如何做”的问题。举个我自己项目里遇到的例子早期我们想让智能体帮忙做数据周报最开始只是把周报的格式要求和数据源描述塞进系统提示词结果模型经常编数字、找错字段。后来我把“查询数据表并返回结构化结果”封装成一个标准技能定义好入参数据源、时间范围、维度和出参表格对象、异常信息模型只需要决定“调用哪个技能、传什么参数”剩下的执行路径完全可控。这个转变的本质区别在于提示词是软约束模型的输出充满了概率性而技能是硬能力一旦定义清楚模型只需要做“选择”和“填空”做错和做对的结果是可观测、可回滚的。所以在实际落地时我更愿意把精力优先放在“技能边界和接口设计”上而不是反复调提示词。1.3 这套思路适合谁、解决什么场景如果你是做智能客服、内部知识助手、自动化工作流这类产品的那“agent-skills”这套思路基本是你绕不开的必修课。它能解决的核心场景有这几类第一模型幻觉问题——技能可以让模型在回答具体数据时先调用查询技能而不是凭空编造第二操作类自动化——比如自动生成合同、自动整理日报、自动分类工单模型负责判断意图技能负责真正执行第三知识库的活学活用——把检索能力封装成技能再配合模型的理解比单纯做向量数据库检索要灵活得多。不用函数调用框架也能做技能库无非是原始一点但如果你在一个长期迭代的项目里我还是建议认真按工程化的思路来设计它。技能不是一次性脚本它是要被反复注册、使用、迭代的模块。2. 技能体系的设计思路与结构拆解2.1 技能不等于工具概念边界先对齐我们在聊技能之前必须先把概念边界对齐。现在市面上的框架里“工具”通常指一个可以被模型调用的函数或API粒度较细比如“发送HTTP请求”“读文件”“执行SQL”而“技能”是一个更大粒度的能力单元它可能是多个工具组合起来的业务流程比如“生成数据周报”这个技能内部会用到“查询数据库”“格式化表格”“发送邮件”三个工具。我自己的设计原则是工具做原子技能做组合。一个技能内部可以编排多个工具并且带有明确的输入、输出和失败处理逻辑。为什么这么做因为模型在做决策时最怕面对太细的选项——你给它50个细粒度工具它往往会选错但你给它5个场景化的技能它选对的概率就会高很多。这本质上是缩小了模型的决策空间从而降低错误率。另外技能还应该具备“可描述性”。一个技能必须能清晰回答三个问题它负责什么、在什么场景下被调用、需要哪些参数。这个描述不是给程序员看的文档而是给模型看的说明书。模型在推理时需要通过描述判断“该不该用这个技能”描述写得模糊模型就会漏用或误用。2.2 技能的三种表达形态与格式选择一个技能在工程上通常有三种表达形态我建议根据团队情况和场景来选择。第一种是自然语言描述型。也就是在系统提示词或者工具定义里用一段话描述技能的功能和参数。比如“当用户想要生成销售报表时调用generate_sales_report技能参数包括start_date、end_date、dimension可选默认by_day”。这种形态实现成本最低适合快速验证但问题是它依赖模型的语义理解技能一多模型就容易混淆。第二种是结构化定义型也就是用JSON Schema或YAML定义技能的参数、类型、必填项和返回值。这种形态是目前主流框架的标准做法好处是模型可以更稳定地生成符合要求的参数校验和报错也都方便。比如OpenAI的Function Calling、Anthropic的工具调用底层都是这种思路。第三种是代码即技能型。把技能实现成独立的Python类或函数通过标准的注册接口暴露给执行引擎。这种形态对开发者最友好因为逻辑可以很复杂可以做鉴权、重试、数据清洗而不只是简单的API转发。我个人的推荐是初期用结构化定义快速跑通等技能逻辑变复杂后再升级到代码即技能型让执行层变得可控和可扩展。2.3 一个可落地的技能目录长什么样技能目录不是一堆文件的堆砌而是一个有层次、有约定的目录结构。我常用的组织方式是把技能按领域分组每组内包含定义文件、实现代码、测试脚本三件套。目录结构大概长这样skills/ __init__.py common/ fetch_url.py execute_sql.py report/ generate_weekly_report.py schema.json test_report.py customer_service/ query_order.py refund_process.py skills_manifest.json这个结构的好处在于common目录放原子工具业务目录放组合技能skills_manifest.json是全局的技能注册清单。运行智能体时只需要加载manifest里登记的技能不需要把所有代码都塞进提示词这样既干净又高效。对应到实际技能的定义我一般会给每个技能写一个简短的“身份卡”{ name: generate_weekly_report, description: 根据销售数据库生成周报支持按渠道、区域等维度汇总数据返回Markdown表格, parameters: { type: object, properties: { start_date: {type: string, description: 周报起始日期格式YYYY-MM-DD}, end_date: {type: string, description: 周报结束日期格式YYYY-MM-DD}, dimension: {type: string, enum: [channel, region, product], default: channel} }, required: [start_date, end_date] } }注意description字段我写得非常具体连“返回Markdown表格”这种看似细节的内容都写进去了。为什么因为模型需要知道这个技能的输出形态才能更好地决定如何使用结果。如果描述只是笼统地写“生成报表”模型可能把返回结果当作普通文本反而影响后续处理。3. 实操从零搭一套自己的技能库3.1 环境准备与方案选型动手之前先确定技术栈。我默认你用的是Python因为这是智能体生态最丰富的语言。基础环境需要Python 3.10以上、一个LLM接口OpenAI兼容的就行也可以用本地模型、一个便于存储技能清单的地方本地目录或数据库都可以前期用本地文件足够。方案选型上有大而全的框架比如LangChain、LlamaIndex也有轻量的函数调用方案。我的建议是如果你只是在做一个内部工具或Demo不要刚上手就引入框架先用最原始的Function Calling机制把技能跑通这样能更好理解每一层在做什么。框架的作用是帮你省去胶水代码但也会隐藏很多细节导致出问题时无从排查。最好用一个支持工具调用或函数调用的大模型接口。下面是一个最简化的调用示例import json import openai client openai.OpenAI(api_keyyour_api_key, base_urlyour_base_url) def call_model_with_skills(user_query, skills): response client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: user_query}], toolsskills, tool_choiceauto, ) return response.choices[0].message这里面的skills参数就是我们在技能目录里定义的结构化清单。模型收到后如果认为需要某个技能就会返回一个tool_calls对象里面包含了技能名称和参数。3.2 技能定义与注册定义一个技能关键是定义它的“外部契约”——模型看到什么、调用什么、得到什么。内部实现其实不复杂难的是接口稳定。我习惯把每个技能实现成一个类统一的执行入口减少模型侧的适配成本class GenerateWeeklyReportSkill: def execute(self, **kwargs): start_date kwargs[start_date] end_date kwargs[end_date] dimension kwargs.get(dimension, channel) data self._query_sales_data(start_date, end_date, dimension) return self._format_markdown(data) def _query_sales_data(self, start_date, end_date, dimension): # 这里调数据库查询返回原始记录 pass def _format_markdown(self, data): # 把数据整理成Markdown表格 pass一个技能类只需要暴露execute方法其余内部方法随意。这样注册和执行时框架层面可以统一调用不需要为每个技能写分支判断。注册机制也很关键。我用一个装饰器来做技能注册既清晰又不容易漏掉SKILL_REGISTRY {} def register_skill(name, description, parameters): def decorator(cls): SKILL_REGISTRY[name] { class: cls, name: name, description: description, parameters: parameters } return cls return decorator这样每新写一个技能只要在类上挂一个装饰器就会自动进入注册表供后续的模型调用和技能发现使用。3.3 技能发现与路由让智能体“知道该用哪个”技能定义好了、注册好了接下来关键的一步是技能发现。这里分两种情况技能数量少时直接把技能清单全部喂给模型让模型自己选技能数量多时比如超过20个就得引入检索层先从技能库里检索出最相关的5个再给模型做选择。技能路由的核心是“缩小选择空间”。我在一个项目中注册了30多个技能如果全部发给模型模型的准确率会明显下降尤其是相似技能之间容易混淆。后来我加了一层简单的向量检索——把每个技能的description做Embedding用户的请求也做Embedding用余弦相似度选Top-K个技能再交给模型做最终决策。这里附上技能检索的简化代码import numpy as np from sentence_transformers import SentenceTransformer model SentenceTransformer(all-MiniLM-L6-v2) def retrieve_top_skills(query, skill_items, top_k5): query_emb model.encode(query) skill_embs model.encode([s[description] for s in skill_items]) scores np.dot(skill_embs, query_emb) / ( np.linalg.norm(skill_embs, axis1) * np.linalg.norm(query_emb) ) top_indices np.argsort(scores)[::-1][:top_k] return [skill_items[i] for i in top_indices]检索层的引入让技能库可以无限扩展不影响模型的推理质量。这一步看起来简单但在实际项目里对准确率的提升比换更强模型还明显。嵌入模型可以选更轻量的本地模型比如bge-small-zh中文场景表现稳定部署成本也不高。3.4 执行闭环与自我修正最后一步也是最容易忽视的一步执行完技能之后要形成一个闭环不能执行完就结束。模型拿到技能返回结果后需要对结果做二次判断——这个结果是否符合用户的原始诉求如果不符合模型应能基于错误信息进行自我修正。比如我遇到过一个场景用户问“本周销售额最高的三个渠道分别是哪些”智能体调用了生成周报技能返回了Markdown表格。但模型只读了表格并没有真正“回答”用户的问题。后来我在技能返回里加了一个summary字段要求技能执行器必须返回结论摘要并且提示模型“如果查询结果为空请告诉用户详细原因不要编造数据”。def execute_skill(skill_name, params): skill_info SKILL_REGISTRY.get(skill_name) if not skill_info: return {error: fskill {skill_name} not found} skill_instance skill_info[class]() try: result skill_instance.execute(**params) # 统一包装返回结果增加状态字段 return {status: success, result: result} except Exception as e: # 异常也要结构化成模型能读懂的错误信息 return {status: error, error_message: str(e)}核心经验是让错误信息“模型可读”。不要说“第3行IndexError”而要说“查询数据失败字段dimension传入的值不合法允许值为channel/region/product”。这样模型才能基于错误信息自愈而不是傻傻地再调用一遍。4. 避坑手册技能落地过程中的真实经验4.1 常见的故障、根因与排查技能落地过程中的故障大多数不是代码问题而是设计和交互问题。我把最常见的故障类型整理成一个速查表方便你对照排查。现象根因解决思路模型完全不调用技能描述太抽象模型不知道何时该用在description里补上触发场景例如“当用户询问销售数据时”模型调用技能但参数传错parameters定义不清晰缺少enum或格式示例用enum限定可选值properties中给每个字段写示例值技能执行时报错但模型还在继续错误信息不可读模型不知道发生了什么把异常转成自然语言错误描述返回给模型相似技能之间频繁选错技能描述过于相似边界模糊合并技能或差异化描述写清各自的适用场景技能返回内容太长模型抓不住重点返回格式不适合模型阅读在技能执行器内做摘要或把返回结果分层这些坑我基本都踩过一遍。尤其是第一个“模型不调用技能”的问题我一开始以为是模型能力不够换了好几个模型都无解。后来才发现是我的description写得太虚全是“用于生成数据报表”这种泛泛的说辞模型根本无法判断何时该触发。把触发条件写清楚之后准确率肉眼可见地提升了。4.2 技能边界与安全控制技能本质上是给智能体开放了“执行权限”权限越强风险越大。在做技能工程时我强烈建议把安全边界当成第一优先级来设计。首先是权限最小化。每个技能只分配它所需的最小权限比如查询技能只给只读数据库账号文件操作技能只允许访问指定目录。不要让所有技能共享一个权限很大的服务账号这一步能阻止大多数“模型被诱导做危险操作”的场景。其次是参数校验。不要信任模型生成的参数在技能执行器内部再做一次严格校验。比如查询SQL的技能必须对参数里的表名、字段名做白名单校验防止模型拼出的SQL包含危险字符或访问未授权的表。参数校验得好很多提示注入攻击会被拦在门外。最后是操作确认。对于不可逆操作删除、修改、发消息、下单技能执行器应该支持dry-run或二次确认模式。我个人习惯是在技能定义里增加一个“risk_level”字段对高风险技能强制人类确认低风险技能自动执行。这听起来像多了一道流程但在真实部署里能防住大量误操作。4.3 失败的技能描述比没有技能更伤人这一点我想单独拎出来说因为它的影响面太大了。技能描述写得不好不仅不会提升智能体的能力反而会让模型在无关场景下强行调用导致整个对话跑偏。举一个反例。我有一个技能原本叫“get_current_time”description写的是“获取当前时间”。看起来没什么问题对吧但实际使用中模型经常在用户问“今天周几”“这个月有几周”时去调用它或者用户只是闲聊“时间过得真快啊”模型也想调用一下。原因就是描述缺乏场景约束。后来我把description改成“获取系统当前日期时间用于回答与具体日期、星期、时间相关的查询例如‘今天是几号’‘现在几点’非必要不调用”误用率立刻降了下来。所以我的经验是技能description里至少要包含三层信息——“做什么”告诉模型这个技能的能力“什么时候用”告诉模型触发条件“什么时候不用”告诉模型不要滥用。第三个信息往往比前两个更重要。另外一个常见的描述误区是堆叠太多细节。description写得像接口文档一个字段解释写三行模型反而抓不住重点。核心信息应该精炼让模型一眼就能理解。这里分享一个我后来固定使用的描述模板技能功能一句话说明这个技能做什么结果是什么格式 适用场景两到三条具体场景示例 不适用场景明确列出哪些情况不要调用 注意事项如果涉及数据时效、输出格式等容易出错的地方用一句话补充用这个模板写出来的技能模型的选择准确率比自由发挥时高出一截。还有一点值得说技能命名也很重要。名字要直白避免用缩写或者过于抽象的单词。比如技能名“gen_rpt”和“generate_weekly_report”模型对后者的理解显然更准确。虽然看起来不太“高级”但工程上让模型少犯错比代码美观重要得多。最后技能不是写完就固定的它和提示词一样需要根据真实运行日志持续迭代。我会建议每周至少看一次技能调用的成功率、误用率和用户反馈。那些一个月没人调用过的技能该删就删那些频繁被误用的技能该改描述就改描述。技能库的维护是一个长期过程但这也是智能体真正从“Demo”走到“生产可用”的必经之路。我在实际项目中还有一个小习惯每次给技能库加一个新技能之前先拿它跑一组“负样本”——专门看看哪些问题不应该触发这个技能确保描述不会过度泛化。这个流程坚持了几个月技能质量越来越稳定模型的整体表现也随之明显改善。你要是也正卡在智能体“能聊不能干”的尴尬阶段强烈建议从技能工程入手把每个技能当成一个小产品去打磨数据和效果会给你正向反馈的。