agent-skills:为LLM代理打造可复用的技能仓库
个项目名字很朴素就叫“agent-skills”。但我实际把它当成了一套给 AI 代理用的“技能仓库”模型负责理解人话仓库负责把理解变成可执行的动作。上个月我重构了自己手头一个答疑机器人把里面天女散花一样塞在提示词里的小工具全部拆成了独立技能模块效果比预期好不少——至少机器人不再经常把参数读错了。这篇就聊聊我为什么做这套东西、怎么设计、代码怎么落以及踩过的几个真实坑。内容偏实操适合正在做 LLM 应用开发、想给对话模型接上工具能力但还不太清楚从哪下手的人。1. 为什么需要 agent-skills模型有嘴但没手1.1 一个尴尬的现场先讲个最早期的翻车经历。某次我跟一个测试用的对话机器人说“帮我记一下明天上午十点给设备做维护。”模型嘴上答应得特别好回了一句“好的已为你记录”然后就没有然后了。它根本不知道日历在哪也不知道怎么把事件写进某个存储文件里。大模型的表现力越来越强但能力边界很明显它没有持久记忆之外的感官也没有操作外部系统的入口。要让它“干活”必须围着一个外部执行器来做也就是把模型的自然语言输出翻译成一段真实可运行的操作。这段“翻译层”怎么组织决定了整个应用是稳定可用还是动不动就抽风。1.2 技能碎片化的痛最早的方案特别粗暴把所有能力写成系统提示词一长串文字塞给模型。比如用户提到天气时你要调用天气接口。用户提到计算时你要调用计算器。用户提到闹钟时你要…此处省略几百字这样写肯定能跑但在项目稍微变大时问题全来了。第一提示词越长模型对指令的注意力越容易被稀释它经常忽略后面的工具说明第二想新增一个技能得改一大段提示词还要担心破坏原有的行为第三没法复用换个新项目这些被埋在提示词里的逻辑又得重新复制一遍。后来我把思路翻转了一下既然模型不擅长长文本里的夹带说明那就把技能做成独立模块每一个技能都自带说明文件、参数结构和执行函数。模型面对的永远是结构化描述程序面对的永远是清晰接口。1.3 这套方案适合谁我建议这几类人重点参考刚接触 LLM 工具调用、函数调用想给对话机器人加动作能力的开发者已经在用超长系统提示词发现维护成本越来越高的个人开发者还有想把某个能力反复复用到多个项目里的自动化爱好者。核心价值就是一点把“模型该做的事”和“程序该做的事”之间那层胶水整理干净。2. agent-skills 的整体设计2.1 一个技能的四件套我在项目里规定每个技能就是一个目录里面必须包含四个部分说明文件用自然语言告诉模型“这个技能什么时候用怎么用”相当于产品说明书。参数结构一份 JSON Schema约束模型应该输出哪些字段、字段类型、哪些必须提供。执行函数真正跑代码的地方接收参数返回结果。观测结果执行函数回传的文本会被再喂回给模型作为下一步判断的依据。一句话总结说明文件给模型看参数结构约束模型的输出执行函数干真实的活观测结果让模型知道活干得怎么样。整个过程形成一个闭环。2.2 一个最小技能长什么样以“计算器”技能为例目录结构如下。agent-skills/ ├── skills/ │ └── calculator/ │ ├── SKILL.md │ ├── schema.json │ └── run.pySKILL.md 可以极其短但必须把触发条件讲明白# Calculator 当用户给出一个数学表达式或者上下文中有明确的数值计算需求时使用本技能。 不要尝试自己心算必须调用技能完成计算。 示例输入12 * (3 4) 示例输入每月房租 2800连续交 6 年一共多少钱schema.json 负责定义参数边界{ type: object, properties: { expression: { type: string, description: 用户提供的数学表达式支持 - * / 和括号, examples: [12 * (3 4), 2800 * 12 * 6] } }, required: [expression] }run.py 是执行函数现实项目里不能直接用 eval我用了 AST 做安全的表达式求值import ast import operator _OPERATORS { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv, ast.Pow: operator.pow, ast.USub: operator.neg, } def run(arguments: dict) - dict: expression arguments[expression] code compile(expression, calc, eval) result _safe_eval(code) return {status: ok, result: result} def _safe_eval(node): if isinstance(node, ast.Expression): return _safe_eval(node.body) if isinstance(node, ast.Constant): if isinstance(node.value, (int, float)): return node.value raise ValueError(unsupported constant) if isinstance(node, ast.BinOp): left _safe_eval(node.left) right _safe_eval(node.right) op _OPERATORS[type(node.op)] return op(left, right) if isinstance(node, ast.UnaryOp): operand _safe_eval(node.operand) op _OPERATORS[type(node.op)] return op(operand) raise ValueError(unsupported expression)这里有个值得记住的原则执行函数必须永远不裸抛异常。不管用户输入多离谱、参数多错误run都要返回一个可读的文本结果错误也一样包装成{status: error, message: ...}。原因很简单模型只能消费文本你给它一个堆栈它大概率只会复读错误信息。2.3 为什么不让模型直接“看一眼”函数代码有人会问我直接在系统提示词里写好“函数签名”不就完了为什么非要搞独立的 schema 和 run.py因为提示词本质上是不可测试的代码。改一个标点都有可能影响模型的输出而你很难在发布前自动化测试提示词本身。独立技能模块则不一样run.py 可以写单测schema.json 可以校验输出SKILL.md 可以单独维护版本。模型、参数、执行逻辑三者解耦出错时能快速定位到底是哪一环出了问题。这个取舍换来三层收益可替换性——想换一个天气数据源只改 run.py不用动提示词可测试性——执行函数可以被直接调用不经过模型也能验证可复用性——同一套技能搬去另一个项目目录复制过去即可。3. 核心机制从自然语言到机器动作3.1 意图识别不训分类器让模型自己选实现 agent-skills 的时候我没有为每个技能做独立意图分类而是把决定权交给模型本身。系统提示词里会拼出全部技能的说明然后要求模型输出一段固定结构的 JSON{ thought: 用户想计算房租总额这是一个数学计算场景, skill: calculator, arguments: { expression: 2800 * 12 * 6 } }“thought”字段是我私心加上的它让模型在选技能之前先做一次推理显著降低选错技能的概率。而代码侧会做一个强校验首先检查 skill 字段是否在注册表里其次用 schema 校验 arguments任何一个环节失败就当作模型输出异常处理而不是硬着头皮去执行。3.2 参数提取最容易翻车技能选对了参数填错是最常见的事故现场。模型天然对数字不敏感也不会主动区分单位。我见过它把“明天”填成本地时间戳也见过把“北京”填成“北京市”。所以 schema 里的 description 一定要包含单位、取值口径和示例。比如天气技能的 schema{ type: object, properties: { city: { type: string, description: 城市名称使用中文全称例如北京市、上海市, examples: [北京市] }, days: { type: integer, description: 查询未来天数范围为 1 到 7, minimum: 1, maximum: 7, examples: [3] } }, required: [city, days] }字段描述写“使用中文全称”还是“使用拼音缩写”结果差很多。模型非常吃这一套 explicit 的约束把口径写死能少掉一半的返工。3.3 执行与观测回传文本才是闭环执行之后技能返回的结果不能直接丢给模型就算完。要把它做成一段简短的观测文本再拼接回对话消息里让模型根据实际结果生成最终回复。这一步非常重要。举个例子。用户问“北京明天会下雨吗”执行函数返回{status: ok, result: {city: 北京市, date: 2025-07-03, will_rain: true}}观测文本可以是技能计算结果北京市 2025-07-03 预计有雨概率 80%。模型看到这句话后会组织最后一段回复“北京明天八成会下雨出门记得带伞。”你看从用户提问到最终回复模型经历了一次“决策-执行-观测-总结”的循环这不是一次性工具调用而是 agent 的标准动作闭环。我在某个多轮任务里反复用到这个闭环效果非常稳。4. 实操过程从零搭一套 agent-skills4.1 准备环境我默认你有 Python 3.10 以上环境另外需要一个支持“工具调用”或“结构化输出”的模型接口。现在市面上主流的大模型服务基本都开放了这类能力选一个你熟悉的就行。另外准备一个命令行工具后面调试技能 run.py 时可以直接跑单测。核心依赖其实就一个能请求模型接口的 HTTP 库。项目里我封装了一层llm_client这里为了演示直接用伪代码表示。4.2 写出注册表注册表负责扫描 skills 目录把每个技能加载成一条描述记录。# core/registry.py import json from pathlib import Path SKILLS_ROOT Path(skills) def load_all_skills(): skills {} for skill_dir in SKILLS_ROOT.iterdir(): if not skill_dir.is_dir(): continue skill_name skill_dir.name skill_md (skill_dir / SKILL.md).read_text() schema_path skill_dir / schema.json schema json.loads(schema_path.read_text()) skills[skill_name] { name: skill_name, prompt: skill_md, schema: schema, } return skills4.3 构建系统提示词把刚才加载好的技能说明拼进系统提示词顺便告诉模型输出格式。我用的模板大致长这样def build_system_prompt(skills): lines [ 你是一个可以调用外部技能完成任务的助手。, 当用户的需求匹配某个技能时你必须输出以下 JSON 格式不要加多余说明, {thought: 你的推理过程, skill: 技能名, arguments: {参数名: 参数值}}, , 如果当前需求没有任何技能匹配输出, {skill: none, reply: 你的普通回复}, , 可用技能如下, ] for skill in skills.values(): lines.append(f---\n{skill[prompt]}) return \n.join(lines)系统提示词的末尾一定要给示例这是成本最低的校准手段。我在某个项目里连续给模型吃三个“输入-输出”对选技能准确率直接肉眼可见地提升。4.4 执行器执行技能并回传结果执行器做的事情不多但每一步都要干净# core/executor.py import importlib import json def execute_skill(skill_name: str, arguments: dict) - dict: try: module importlib.import_module(fskills.{skill_name}.run) result module.run(arguments) if not isinstance(result, dict): result {status: ok, result: result} except Exception as exc: result {status: error, message: f{type(exc).__name__}: {exc}} return result注意只吞掉异常还不够要把异常转成模型能看懂的文本。我给execute_skill加了一层强制包装不管是执行函数内部抛出的异常还是返回值格式不对最后都会统一成{status: ...}字典然后序列化成字符串扔回给模型。4.5 主循环装配最后是代理主循环也就是 agent 的“大脑”def run_agent(user_input: str, history: list) - str: skills load_all_skills() system_prompt build_system_prompt(skills) messages [{role: system, content: system_prompt}] messages.extend(history) messages.append({role: user, content: user_input}) response llm_client.complete_text(messages) parsed parse_structured_json(response) if parsed is None or parsed.get(skill, none) none: return parsed.get(reply, 我没法回答这个问题。) skill_name parsed[skill] arguments parsed.get(arguments, {}) if skill_name not in skills: return 识别到的技能不存在请重新组织你的请求。 observation execute_skill(skill_name, arguments) messages.append({role: assistant, content: response}) messages.append({role: tool, content: json.dumps(observation, ensure_asciiFalse)}) final_answer llm_client.complete_text(messages) return final_answer这段代码可以继续扩展的地方很多技能超时限制、观测文本压缩、多轮记忆管理但这些是后话。先保证最小闭环跑起来再逐步加复杂度是比较稳妥的做法。5. 常见问题与排查我踩过的那些坑5.1 模型根本不调用技能症状是用户说“算一下 25 的平方根”模型直接回答“约为5”根本没走计算器。大多数时候不是模型笨而是 SKILL.md 写得太一言难尽。我最初写的描述是“用于数学运算”模型根本没法把这句话和具体需求连接起来。解法很简单描述里写清楚触发条件和反面条件。我改成“当用户给出数值表达式、需要精确数值结果时使用本技能涉及逻辑推理、文本理解时不要使用本技能。”模型对“精确实数”和“估算”的边界判断会清楚很多。5.2 参数幻觉数字和单位对不上有一次我让机器人查“未来七天的气温”它把 days 填成了 7.0schema 里 integer 校验就直接报了错整个链路崩掉。后来我在 schema 里加了 description“整数天数范围 1 到 7”同时在 examples 里放上周五的合法值幻觉概率才降下来。经验是能写示例就别只写描述能列枚举就别放开放字段。5.3 技能报错信息被模型复读记得刚搭好的时候某个技能内部接口超时run.py 返回了HTTPError: timeout之类的内容。模型看到后最后回复变成了“抱歉遇到了 HTTPError: timeout”。这显然不是用户想看到的。我后来强制所有技能都返回可读的观测文本比如“天气服务暂时不可用请稍后再试”模型就会顺着这个文本组织一句像人话的回答。5.4 代理陷入死循环最隐蔽的坑是模型在执行结果不理想时会不停重试同一个技能把自己锁在循环里。比如连续调用三次天气接口就因为结果里有一句“夜间有雨”它反复查同一个城市。方法是在主循环里加一个最大轮次计数器默认 3 轮超出之后强制转人工默认回复。虽然粗暴但非常有效。下面我把几个高频问题整理成速查表现象常见原因处理后效果模型不调用技能SKILL.md 缺少触发场景描述补上触发与不触发的边界参数是错误类型schema 的 description 没写单位写明单位与合法取值给示例执行报错又复读异常文本直接暴露给模型统一转成友好观测文本多轮重复执行缺循环上限加最大轮次并强制结束这四条不是纸上谈兵都是我真实遇到并按上面的方法修复的。现在项目里 20 多个技能生命周期超过三个月稳定性明显比最早那版提示词大杂烩高出很多。6. 一点后续思考把技能当积木用6.1 技能组合成工作流单个技能干一件事多个技能组合起来就能干复杂的事。比如“安排明天上午的会议”听起来是一个需求实际是查日历、查会议室占用、写备忘三个能力叠在一起。我不建议一次性让模型跨三个技能同时执行那会失控。我更习惯的做法是先把“查日历”和“写备忘”分别做成独立技能再用一层编排逻辑把它们串起来失败时定位到具体环节。6.2 给未来的自己留后路这个项目从一开始就保持了一个习惯每次新增技能都严格补全 SKILL.md、schema.json 和准备几个单测用例。短期内确实多花几分钟长期看收益非常大。因为三个月后你可能完全不记得某个接口到底要怎么调、字段代表什么但 SKILL.md 会替你记住。6.3 个人体会做 agent-skills 这段时间我最深的感受是大模型应用真正的工程瓶颈往往不在模型本身而在模型和外部世界之间那层脆弱的接口。把这层接口做成“有说明书、有参数约束、有错误回传”的技能模块比费劲调提示词更值得投入。这个思路在个人项目里适用再往后走到多代理协作或多场景复用也一样适用——你现在拆出来的每一个技能都是未来那块积木。

相关新闻

从零打通统一大模型网关:用litellm托管所有LLM调用

从零打通统一大模型网关:用litellm托管所有LLM调用

从零打通统一大模型网关:我用 litellm 托管了项目里所有 LLM 调用先抛一个场景:你负责一个需要同时对接多个大模型 API 的项目,早先用的是 OpenAI 格式,后来加上 Claude 兼容接口,又接了开源模型的内网服务&#xff0c…

2026/10/13 23:45:36 阅读更多 →
多Agent协作为什么容易互相“甩锅”?从任务分配到结果验证设计协作工作流

多Agent协作为什么容易互相“甩锅”?从任务分配到结果验证设计协作工作流

一、引言:多 Agent 系统里的"责任真空" 把单 Agent 拆成 Planner、Retriever、Coder、Reviewer、Executor 之后,很多团队会遇到一个反直觉的现象:**系统的单点能力提升了,端到端的成功率反而下降,而且失败后…

2026/10/13 23:44:36 阅读更多 →
HDFS读写链路深度拆解:从数据块到副本放置与故障恢复

HDFS读写链路深度拆解:从数据块到副本放置与故障恢复

1. 从一次数据写入说起:HDFS 不只是“能存”这么简单做大数据的人都知道,HDFS 是整个生态的地基。你辛辛苦苦搭好集群、调好参数,跑起 Spark、Flink、Hive,但数据落到最后,全部要进 HDFS。所以 HDFS 的水平直接决定了上…

2026/10/13 23:44:36 阅读更多 →

最新新闻

Tecplot 后处理实战:从数据导入到宏与 Python 批量出图全解析

Tecplot 后处理实战:从数据导入到宏与 Python 批量出图全解析

简介:《Tecplot教程合集》面向流体动力学、热力学与工程仿真领域的工程师、科研人员及高校学生,帮助零基础或有一定经验的用户系统掌握这款科学数据可视化与分析软件。资源以视频教程、真实案例和中文指南三条主线展开,覆盖数据导入与管理、二…

2026/10/14 0:34:52 阅读更多 →
XMLSchema核心概念与工程实践详解

XMLSchema核心概念与工程实践详解

1. XMLSchema 基础概念解析XMLSchema(通常缩写为XSD)是W3C推荐的标准XML结构定义语言。作为DTD的现代替代方案,它使用XML语法本身来描述XML文档的结构约束。我第一次接触XSD是在2008年参与银行系统接口开发时,当时需要确保交易报文…

2026/10/14 0:34:52 阅读更多 →
航空发动机壳体三维设计与加工仿真关键技术

航空发动机壳体三维设计与加工仿真关键技术

1. 项目背景与核心价值飞机减速系统壳体作为航空发动机传动装置的关键承力部件,其结构设计合理性直接影响整机安全性能。这类零件通常采用高强度钛合金或镍基高温合金材料,具有复杂的异形曲面特征和严格的公差要求(通常控制在IT6-IT7级&#…

2026/10/14 0:34:52 阅读更多 →
SpringBoot+Vue企业级贸易CRM系统架构与优化实践

SpringBoot+Vue企业级贸易CRM系统架构与优化实践

1. 企业级贸易CRM系统架构解析作为一名长期从事企业级系统开发的工程师,我深知贸易行业对CRM系统的特殊需求。传统贸易企业面临客户信息分散、订单跟踪困难、数据分析滞后等痛点,这套基于SpringBootVueMyBatis的解决方案正是针对这些痛点设计的完整实现。…

2026/10/14 0:34:52 阅读更多 →
毕业设计选题与实现:从技术路线到项目实战

毕业设计选题与实现:从技术路线到项目实战

1. 毕业设计选题指南:从避坑到创新的完整攻略又到了一年一度的毕业设计季,作为经历过这个阶段的老学长,我深知选题的重要性。一个好的选题不仅能让你顺利通过答辩,更能成为简历上的亮点。最近两年,高校对毕业设计的要求…

2026/10/14 0:34:52 阅读更多 →
tidyr数据整形核心指南:从宽表长表转换到字段拆解

tidyr数据整形核心指南:从宽表长表转换到字段拆解

1. 为什么“整理数据”比“分析数据”更耗时——从一个真实项目说起我带过不少刚接触R语言的某高校数据分析课程学员,也帮某公司做过几轮业务数据清洗支持。几乎所有人第一次拿到原始数据时,第一反应都不是建模或画图,而是盯着Excel里那些“看…

2026/10/14 0:33:52 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/12 18:11:17 阅读更多 →