提到“agent-skills”你可能以为又是一个把大模型包装成“全能助手”的营销词。实际上我前几个月接手了一个内部项目要给基于大语言模型的智能体补上“干活能力”绕了一大圈才发现绝大多数团队卡住的根本不是模型推理而是不知道如何把一次性脚本沉淀成可复用、可组合、可进化的技能模块。这篇就把我当时的设计思路、代码骨架、踩坑记录和后来总结的优化手段一并写出来希望能给你省下几晚上的摸索时间。1. Agent技能库的诞生背景不是所有能力都该写死在提示词里1.1 第一次让Agent干活时的失控现场最初我搭的智能体很简单用户问一句模型答一句。后来业务方提出要让它“自动收集竞品信息”我想也没想就往系统提示词里塞了一大段“你要先访问网站再提取价格然后导出表格”的操作步骤。结果上线第一天就翻车——模型一本正经地编造了三个不存在的行业报告来源还把它们标成了“已核实”。问题的根源在于我把“知道怎么做”和“真正能做”混为一谈了。大模型擅长的是语言生成它可以把流程描述得像模像样但一旦涉及真实的工具调用、数据处理、异常恢复就必须依赖外部代码来兜底。这就像你给实习生一本操作手册他照样可能摔碎烧杯但如果你给他一套带防护的自动化装置至少不会把实验室点了。1.2 重新思考“技能”在智能体体系里的位置后来我开始研究市面上的智能体框架发现大家都提到了一个词tool也就是工具。但工具的粒度太粗了。一个“搜索API”是工具可真正干活的时候你需要的是“先搜索A关键词再筛选近一周的结果然后去重最后整理成表格”这样一套完整动作这套动作本身就是一个技能。于是我把项目定名为agent-skills核心思路是把一切可重复执行的流程封装成独立技能。每个技能有三个特征输入输出有清晰契约、内部逻辑可被Agent理解、执行结果可被追踪。这样一来Agent只需要负责规划“该用哪个技能、按什么顺序调用”而技能内部的具体操作由代码保证稳定性。这里的关键认知转变是技能不是给模型背的提示词而是给模型按的“按钮”。模型只负责按按钮按钮内部怎么运作与模型无关。这样既降低了模型的幻觉风险又让整个技能库像乐高积木一样可以任意拼装。1.3 这套技能库到底适合哪些人如果你只是做一个问答机器人或者给内部员工做个文档摘要助手那完全可以直接用现成的函数调用能力没必要自己造轮子。但如果你面临以下三种情况agent-skills的思路会很值得参考你手头有大量重复性、多步骤的业务操作比如数据采集、报表生成、批量审核。你的智能体需要接入多个内部系统而这些系统的API风格差异极大。你希望不同业务线能共享底层能力而不是每个项目都从零写一遍工具调用。换句话说只要你的智能体开始“做事情”而不仅仅是“说话”你就需要一套技能管理体系。2. agent-skills的核心设计描述、注册与调用三者如何分工2.1 给模型看的技能描述要刻意“反模糊”很多人在写技能描述的时候会陷入两种极端要么一句话带过要么写一篇五百字小作文。我的经验是技能描述本质上是一份“API文档”但它面向的不是人类程序员而是大模型。因此它需要具备三个要素触发条件、前置依赖、输出格式承诺。最初我的技能描述是“根据用户问题查询数据库并返回结果”模型经常困惑于该在什么时候调用它。后来我改成“当用户询问采购金额、订单数量、库存批次等业务数据时调用此技能若涉及历史趋势对比先调用时间序列归一化技能返回结果一律为包含字段名的JSON数组”。模型调用命中率显著上升因为描述里已经包含了决策规则。更反直觉的一点是描述里要明确写“不做什么”。例如某个技能只负责数据清洗我就必须在描述里标注“本技能不会生成统计报表如需报表请调用report_builder技能”。这听起来啰嗦但非常有效它能减少模型在边界任务上的自由发挥。2.2 技能注册表一堆函数的动态编排中心技能库的核心是一个注册表它保存着每个技能的名称、版本、描述、入参Schema、出参Schema、依赖关系和健康状态。我用Python字典加JSON Schema来管理没有引入太重的外部服务因为技能数量一开始不会超过五十个一个注册表进程就够用了。注册表的妙处在于它让技能发现和技能调用解耦。模型看到的是一份面向人类可读的技能清单真正执行时则由调度器通过注册表查询对应函数。举个例子同样的“数据透视”技能我可以注册成v1.0和v2.0两个版本默认走v2.0出问题可以一键回滚到v1.0模型和调用方几乎无感知。我还为每个技能添加了“调用成本”字段比如预估消耗多少token、耗时多少毫秒。虽然暂时没用它做负载均衡但至少可以帮助定位性能瓶颈也能避免模型在一个简单问题上反复调用昂贵技能。2.3 技能调用的上下文传递机制刚开始我犯过一个低级的错误每次调用技能都重新构造参数导致同一个用户会话里出现了严重的上下文断层。后来我引入了“技能会话上下文”对象它会在一次任务执行期间持续存在保存用户意图、原始信息、中间结果和全局配置。这个上下文对象有四个核心组成部分user_goal用户原始目标的持久化语义表示artifacts各技能产出的中间数据对象execution_trace每一步调用的输入输出摘要constraints权限、超时时间、重试次数等边界条件。有了这个机制智能体在处理复杂任务时就表现出类似人类的“工作记忆”。我们可以让一个技能读取另一个技能刚刚生成的脏数据还能在模型规划出错时依照execution_trace回溯并重新调整调用顺序。这一步让整条技能链的稳定性上了一个台阶。3. 从零搭建一个最小可用的agent-skills框架3.1 环境准备与核心依赖我建议你一开始不要引入任何Agent框架只靠Python标准库加一个轻量HTTP服务框架就够了这样能逼自己把每个细节都过一遍。我自己的实验环境是Python 3.10加上FastAPI和Pydantic没有用LangChain之类的重型库。你说得没错不引入框架意味着要自己处理模型调用、上下文存储、工具调度等琐碎工作。但恰恰是这个过程让我彻底理解了技能库的边界到底该划在哪里。等框架跑通后再去看别人的上层封装你会有一种“原来那些抽象都是在解决这两个痛点”的通透感。我用SQLite做技能的注册存储每个技能一行记录字段包括name、description、input_schema、output_schema、handler_path、status、created_at。在开发阶段这样的设计允许我直接打开数据库改描述实时生效不用重启服务。3.2 技能函数的定义与注册代码详解一个技能handler就是一个普通的Python函数它接收统一的上下文参数返回一个可行的数据结构。我给它起的规范名称是skill_handler每个handler都必须实现execute(ctx)接口ctx里面包含了上面提到的上下文对象。来看一个具体的技能实现它负责把一段原始文本按句子切分并去重。为了让Agent能正确调用它我在函数头写了一段非常明确的描述性文字然后用一个装饰器把它注册进技能表from pydantic import BaseModel, Field from typing import Any class TextSplitInput(BaseModel): text: str Field(description原始文本内容) min_length: int Field(default10, description保留句子的最小长度) class TextSplitOutput(BaseModel): sentences: list[str] dropped_count: int skill_register( nametext_split_dedupe, description将长文本按句号切分并去重。仅当用户需要从大段文本中提取独立观点时使用。不负责摘要生成。, version1.0, input_schemaTextSplitInput, output_schemaTextSplitOutput ) def text_split_dedupe(ctx): text ctx.params.text min_len ctx.params.min_length raw_sentences [s.strip() for s in text.replace(\n, ).split(。) if s.strip()] seen set() result [] for s in raw_sentences: if s not in seen and len(s) min_len: seen.add(s) result.append(s) return {sentences: result, dropped_count: len(raw_sentences) - len(result)}这里的skill_register装饰器会做三件事校验输入Schema、把handler对象存入全局技能表、往日志里写入一条注册记录。对于没有用过的读者我可以简化地认为它就是给这个函数发了一张“员工卡”。3.3 调度器怎么把Agent的意图转成一次真正的调用下一步是写一个调度器它负责让大模型决定调用哪个技能并处理调用过程中产生的异常。我用的是OpenAI风格的函数调用接口也就是把技能清单铺给模型模型返回一个结构化的调用请求然后由调度器执行真实函数。调度器内部逻辑不算复杂但有几个位置必须处理得足够细致。首先是参数合法性校验模型偶尔会生成溢出边界的数值比如把min_length填成-5Pydantic的校验会在函数执行前直接拦截。其次一个技能可能执行几十秒我需要支持超时中断防止技能A卡死导致整个任务链瘫痪。我摘一段调度器的核心分支代码def run_skill(skill_name: str, params: dict, ctx: Any): skill registry.get(skill_name) if not skill: return {error: fSkill {skill_name} not found} validated skill.input_schema(**params) # 校验失败会抛异常 result skill.handler(ctx.replace_params(validated)) return {status: ok, data: result}这里的异常会被统一捕获然后编成一条“调用失败原因”附加到上下文对象里反馈给模型让模型有机会选择替代技能。这一步极其重要否则模型会误以为上一次调用成功了然后往下产生一堆无意义的连锁调用。3.4 给技能库加一份“可观测性”日志调试技能调用依赖链时我意识到一个问题没有日志技能库就是一坨神秘的黑盒。于是我在调度器里加了结构化日志记录每次技能调用的前因后果调用前模型给出的意图摘要和参数调用后返回结果里被截断的前200个字符如果失败记录异常堆栈的关键行。日志统一写到JSON文件里后续可以灌入任意分析工具。每隔一段时间我会根据趋势找出最常用的技能和最常失败的技能然后针对性地优化描述文案或内部实现。一个意外的收获是这些日志还成了我写“技能使用指南”的第一手材料比看代码直观多了。4. 技能库落地时最容易踩的坑我帮你提前排一遍4.1 把技能描述写成功能清单模型完全没有调用欲望这是最容易犯的错。你以为自己在帮模型实际上你在给它制造阅读负担。原本我的某个技能描述是“计算两个日期间的差值、工作日天数、自然日天数返回日历天数和可用工时”半年后我重新审视发现模型几乎调用不中它。后来我把描述改成“当用户询问‘请假几天’、‘项目何时截止’、‘还剩多少工作日’时使用若用户只问单一日期无对比场景不要调用”。改完之后调用率直接翻了一倍。这说明一个道理技能描述的任务不是详述功能而是做意图路由。模型是根据“用户此刻想干什么”来找技能的它并不真的关心你的实现细节。所以描述里应该多写触发场景、排除场景少写逻辑过程。4.2 技能间依赖处理过于粗暴出了问题整条链路全崩技能库规模大了以后必然存在技能A先产出数据、技能B再消费数据的依赖关系。我初期没有建立依赖声明只在调度逻辑里手动拼接顺序结果某次数据源升级后技能B接收了结构完全不同的中间结果直接抛KeyError任务链当场断掉。后来我在技能注册表里增加depends_on字段声明一个技能执行前需要哪个技能先跑完。调度器据此自动插入前置调用不用模型操心。同时我会在技能输出里保留schema_version让下游技能能迅速判断数据格式是否兼容。这个机制让整个调用链的抗变化能力强了很多。4.3 上下文无限膨胀最后一口气把记忆体挤爆Agent执行一个复杂任务时技能之间传递的中间结果会被全部累积到上下文对象里看起来大而全实际上非常致命。有一次我让Agent处理一个包含两千行商品记录的表格每个技能都把原始表格保存到上下文导致后续调用时token开销爆炸响应慢得没法用。我吸取教训后规定了上下文对象里的artifacts只保留轻量级元数据比如行数、列名、统计摘要完整文件一律写到临时目录并返回路径。这样既不影响技能间传递信息又控制了内存和token成本。你如果也想做类似项目建议一开始就明确“上下文只存指针不存实体”。4.4 盲目给Agent加“全知”权限结果技能库变成提款机考虑到安全你肯定不希望Agent在用户请求的诱导下调用一个“发送邮件”技能给收件人列表里的所有人都发一遍垃圾邮件。所以我在每个技能注册时加入了permission_level权限等级分为只读、内网输出、外部提交、不可逆操作四级。调度器在调用前会检查当前用户会话的权限层低于技能要求时直接拒绝并把拒绝理由返回给模型。这个设计的代价是初始化时要花不少时间给每个技能定义权限等级但收益极其明显即使模型被恶意Prompt绕开了规划限制它也跳不出代码层的权限校验。任何技能库没有权限分级的隔离都不配称为生产级。5. 进阶玩法从“单技能调用”到“技能链编排”5.1 技能链让Agent自动发现可组合路径基础技能库一旦跑通你会很快遇到新的需求某个任务不是单一技能能解决的而是需要两三个技能串联。比如“分析本周销售趋势”可能需要先调用“数据拉取技能”再调用“异常值清洗技能”最后调用“图表生成技能”。我实现了一个轻量级的技能链编排模块它允许用户或模型指定一个大致的执行序列技能链管理器负责按顺序调用并统一收集结果。与硬编码流程不同的是这个链里的每个环节仍然独立注册且中间可以插入“条件分支”、“循环重试”等自定义节点。我把这个模式称为“技能管道”它在不牺牲模块化的前提下解决了复杂任务的组合问题。5.2 技能版本管理与A/B测试的工程化技能库会一直演化。这次改一处逻辑下次调一段描述你都应该把旧版本保留下来。我用Git管理每个技能handler的源码同时在注册表里维护一个活跃版本号。发布新版时只需要修改版本号并重新加载注册表不用重启整个服务。我还尝试过给同一个技能注册两个不同的handler版本然后按用户会话比例做A/B测试用日志中的成功率和耗时来决定保留哪个版本。这个操作成本很低但能带来实打实的质量提升。毕竟你对技能的描述和实现好不好终究要通过真实调用数据来证明。5.3 把技能库回馈给业务方从个人工具到团队资产当技能库稳定运行一段时间后我开始整理“技能卡片”每一张卡片包含技能名、触发样例、入参出参说明、常见问题。这份文档比代码注释重要得多。业务方甚至不用理解模型原理只要读出对应的技能卡片就能向Agent提出合理的任务请求。这个项目最后沉淀的不只是一个技能注册表和几十个函数而是一套关于智能体能力的组织方法论。团队再遇到新的自动化需求时不再陷入“这个能不能让AI做”的讨论而是直接问“这属于哪个技能的变体需要注册一个新技能还是扩展现有技能的Schema”。这才是Agent技能体系真正进入良性迭代的标志。我在带这个项目的过程中最深的一个体会是再聪明的模型也需要一块稳固的“知识飞轮”来帮它稳稳落地到业务上。技能库的本质就是把不可控的模型行为逐渐收敛成可预期、可维护、可组合的执行单元。你当然可以不搞一套完整的agent-skills工程但只要你的智能体开始接触真实任务早点想清楚“技能从哪来、谁能调用、怎么进化”这三个问题一定不会亏。