尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

从零实现 Agent Skills:用 SKILL.md 给 AI 智能体装上可插拔技能包

发布时间:2026/9/26 10:31:58

资讯中心
01
ARTICLE

从零实现 Agent Skills:用 SKILL.md 给 AI 智能体装上可插拔技能包

从零实现 Agent Skills:用 SKILL.md 给 AI 智能体装上可插拔技能包
1. 为什么你的智能体提示词越写越长还越来越不听话做智能体应用的朋友大概率都经历过这个阶段一开始系统提示词只有几十行写着写着变成几百行最后膨胀到两千多行。代码审查规则、Git 操作流程、文件整理规范、API 测试步骤全塞在一起每次调用都要把这一大坨内容完整传给模型。结果就是三个字贵、慢、乱。贵在 token 消耗哪怕用户只是想让模型帮忙写个 commit message你也得把代码审查的完整规则一起喂进去慢在模型要在海量指令里找重点响应质量反而下降乱在调试时根本分不清模型到底在按哪段指令行动。Agent Skills 这个思路换了个角度解决问题别一次性把所有能力都告诉模型而是给它一份菜单需要哪个技能再现场加载哪个。启动时只加载几百字节的元数据真正用到某个技能时才把完整指令拉进来。核心思想一句话——技能就是结构化的、按需加载的提示词模板。这篇文章聚焦用 Python 从零搭建一个 Agent Skills 加载器用 SKILL.md 描述技能元数据结合 OpenAI function call 完成技能注册与调用。我会给出可复制的目录结构、SKILL.md 骨架和完整的 Python 注册代码并演示新增技能后重启即生效的验证步骤。适合正在做智能体、被长提示词折磨、想搞明白可插拔技能包怎么落地的开发者。2. 前置准备TaoToken 接入与项目骨架2.1 为什么用 TaoToken 做模型接入层Agent Skills 的加载器本身不依赖特定模型服务但你需要一个稳定的 OpenAI 兼容接口来跑 function call。TaoToken 提供的就是这种兼容层模型对话、Coding Plan、API Keys 都在一个控制台里管理接入代码基本不用改换模型只改 model 字段。如果你只是验证技能加载逻辑用模型对话页面手动测几轮就够如果要把这套 Skills 加载器长期用在编码助手或 Agent 工作流里建议直接上 Coding Plan省得每次调模型都单独算账。2.2 目录结构先建项目骨架每个技能一个子目录SKILL.md 放在里面mkdir -p agent-skills-demo/skills/code-review mkdir -p agent-skills-demo/skills/git-helper mkdir -p agent-skills-demo/skills/api-tester cd agent-skills-demo最终结构长这样agent-skills-demo/ ├── main.py ├── skills_manager.py └── skills/ ├── code-review/ │ └── SKILL.md ├── git-helper/ │ └── SKILL.md └── api-tester/ └── SKILL.md2.3 安装依赖pip install openai pyyamlopenai用来调 function callpyyaml用来解析 SKILL.md 顶部的 frontmatter。两个包都很轻没有额外负担。2.4 拿 API Key去 TaoToken 控制台的 API Keys 页面创建一个 key然后写进环境变量export TAOTOKEN_API_KEY你的key接入文档里有完整的 base_url 和参数说明照着填就行。base_url 用https://taotoken.net/api不要带多余路径。3. SKILL.md 骨架与技能元数据设计3.1 一个 SKILL.md 长什么样每个技能就是一个放在独立目录里的 SKILL.md 文件由两部分组成YAML frontmatter 写元数据下面的 Markdown 正文写详细指令。以代码审查技能为例skills/code-review/SKILL.md--- name: code-review description: 审查 Python/JavaScript 代码检查安全漏洞、PEP 8 规范和性能问题 version: 1.0.0 --- # 代码审查技能 你是一名资深代码审查员。 ## 重点关注 1. 安全性SQL 注入、XSS 攻击、鉴权绕过 2. 代码质量可读性、可维护性、DRY 原则 3. 性能N1 查询、内存泄漏、低效算法 ## 输出格式 - 总结整体评估 - 关键问题安全相关如有 - 改进建议优化建议 - 亮点做得好的地方3.2 description 字段是命门模型就是靠 description 这行字判断这个任务该激活哪个技能。写得糊弄模型就选得糊弄。反例帮助写代码——等于没写。正例审查 Python/JavaScript 代码检查安全漏洞、PEP 8 规范和性能问题——覆盖场景一目了然。3.3 结构化指令比大段散文管用清晰的小标题、列表、预期输出格式模型跟着走的依从度会高一个档次。要是写成一整段话糊脸上模型大概率会选择性失忆。这一点在多个技能同时存在时尤其明显因为模型需要在激活后快速抓住重点。4. Python 加载器发现、注册、激活、执行4.1 核心类 SkillsManager把发现、解析、激活三个动作封装成一个类skills_manager.pyimport yaml from pathlib import Path from typing import Dict, List, Optional from dataclasses import dataclass dataclass class Skill: name: str description: str path: Path content: Optional[str] None metadata: Optional[Dict] None def load_full_content(self) - str: if self.content is None: skill_file self.path / SKILL.md with open(skill_file, r, encodingutf-8) as f: self.content f.read() return self.content class SkillsManager: def __init__(self, skills_directory: str skills): self.skills_directory Path(skills_directory) self.skills: Dict[str, Skill] {} self._discover_skills() def _discover_skills(self): if not self.skills_directory.exists(): return for item in self.skills_directory.iterdir(): if item.is_dir(): skill_file item / SKILL.md if skill_file.exists(): try: skill self._parse_skill(skill_file, item) self.skills[skill.name] skill except Exception as e: print(f加载技能失败 {item}: {e}) def _parse_skill(self, skill_file: Path, skill_dir: Path) - Skill: with open(skill_file, r, encodingutf-8) as f: content f.read() metadata {} if content.startswith(---): parts content.split(---, 2) if len(parts) 3: try: metadata yaml.safe_load(parts[1]) except yaml.YAMLError as e: print(ffrontmatter 解析失败: {e}) return Skill( namemetadata.get(name, skill_dir.name), descriptionmetadata.get(description, 无描述), pathskill_dir, metadatametadata, ) def activate_skill(self, skill_name: str) - Optional[str]: skill self.skills.get(skill_name) if skill: return skill.load_full_content() return None关键点_discover_skills只解析 frontmatter正文不读。完整内容留在磁盘上等activate_skill被调用时才加载。这就是懒加载也是 Skills 省 token 的核心。4.2 转成 OpenAI function call 工具把每个 skill 转成一个可调用函数暴露给模型def get_skill_tools(self) - List[Dict]: tools [] for skill in self.skills.values(): tool { type: function, function: { name: factivate_skill_{skill.name.replace(-, _)}, description: f激活 {skill.name} 技能。{skill.description}, parameters: { type: object, properties: { context: { type: string, description: 任务上下文, } }, required: [context], }, }, } tools.append(tool) return tools函数命名统一加前缀activate_skill_调试时看日志一眼就知道发生了什么。4.3 对话主循环技能可以链式触发比如 api-tester 激活后还要调 execute_python 去实际发请求。所以处理逻辑必须是个循环直到某一轮模型不再返回 tool_callsmax_iterations 10 iteration 0 while iteration max_iterations: iteration 1 response self.llm_client.chat( messagesself.messages, toolstools if tools else None, ) if response[tool_calls]: self._handle_tool_calls(response) continue breakmax_iterations是兜底保险丝防止模型抽风循环调用把账单跑爆。4.4 处理 tool_calls 的细节def _handle_tool_calls(self, response: Dict): self.messages.append({ role: assistant, tool_calls: response[tool_calls], content: response.get(content), }) for tc in response[tool_calls]: tool_name tc[function][name] if tool_name.startswith(activate_skill_): skill_name tool_name.replace(activate_skill_, ).replace(_, -) skill_content self.skills_manager.activate_skill(skill_name) tool_result ( f技能 {skill_name} 已激活请按以下指令执行:\n\n{skill_content} if skill_content else f错误:找不到技能 {skill_name} ) else: tool_result f错误:未知工具 {tool_name} self.messages.append({ role: tool, tool_call_id: tc[id], content: tool_result, })第 3 步特别容易漏把带 tool_calls 的 assistant 消息加进对话历史。漏了之后 OpenAI 会直接甩你一个报错类似Missing required parameter: messages[1].tool_calls[0].type。5. 验证请求新增技能后重启即生效5.1 写一个最小可跑的 main.pyimport os from openai import OpenAI from skills_manager import SkillsManager client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlhttps://taotoken.net/api, ) manager SkillsManager(skills) tools manager.get_skill_tools() print(f已发现 {len(manager.skills)} 个技能: {list(manager.skills.keys())}) messages [ {role: user, content: 帮我审查这段代码def add(a,b): return ab} ] response client.chat.completions.create( modelgpt-4o-mini, messagesmessages, toolstools, ) choice response.choices[0].message if choice.tool_calls: for tc in choice.tool_calls: print(f模型选择激活: {tc.function.name}) print(f参数: {tc.function.arguments})5.2 跑起来看结果python main.py预期输出已发现 3 个技能: [code-review, git-helper, api-tester] 模型选择激活: activate_skill_code_review 参数: {context: 审查 add 函数}模型看到用户请求后从工具列表里选中了 code-review 技能说明 description 写得够清楚模型能准确匹配。5.3 新增技能验证热插拔现在新建一个技能不改任何代码mkdir -p skills/json-formatter写skills/json-formatter/SKILL.md--- name: json-formatter description: 格式化、校验和美化 JSON 数据处理嵌套结构和转义字符 version: 1.0.0 --- # JSON 格式化技能 你是 JSON 处理专家。 ## 能力 1. 格式化压缩的 JSON 2. 校验 JSON 合法性 3. 提取嵌套字段 ## 输出格式 返回格式化后的 JSON 和校验结果。重启python main.py输出变成已发现 4 个技能: [code-review, git-helper, api-tester, json-formatter]新增技能零代码改动丢一个 SKILL.md 进去就完事。这就是可插拔技能包的落地方式。6. 本篇常见错排查6.1 启动就把所有技能加载到内存不少人图省事在_discover_skills里直接把所有 SKILL.md 全读进内存。这完全背离了 Skills 的初衷等于又回到一次性加载所有指令的老路。元数据是元数据正文是正文必须分开处理。6.2 tool_calls 格式不对那个type: function和嵌套的function对象该怎么嵌套怎么嵌套别自作主张扁平化# 正确格式 { id: call_xxx, type: function, function: { name: activate_skill_code_review, arguments: {...} } }6.3 技能激活后调模型忘了传 tools技能可能还要调其他工具每一次 LLM 调用都得把 tools 带上。一旦忘了模型在执行技能指令的过程中就没法再调用其他工具技能的能力就瘸了半边。6.4 description 写得太虚帮助处理代码这种描述模型根本不知道该啥时候调。得写具体做啥、适用哪类任务、核心能力有哪些。6.5 技能粒度没把握好一个技能对应一个领域code-review、git-helper、api-tester 都是好例子。像 developer-tools 这种范围太大的模型根本选不准。6.6 frontmatter 解析失败YAML 对缩进敏感name:和description:后面要有空格冒号别用中文。解析失败时_parse_skill会打印错误但技能名会 fallback 到目录名容易掩盖问题建议启动时把解析异常直接抛出来。7. 下一步把技能包接进你的工作流这套加载器不到两百行 Python一个下午就能跑起来。建议先写一个技能试试水感觉顺手了再逐步扩展。如果你只是验证模型选择技能的逻辑用模型对话页面手动测几轮最快如果要把这套 Skills 加载器长期用在编码助手或 Agent 工作流里直接上 Coding Plan 更省心模型调用和技能管理都在一个地方接入过程中遇到 function call 格式或 base_url 配置问题去接入文档对照参数排查API Keys 在控制台随时可以重新生成。技能写多了之后你会发现真正值钱的不是加载器代码而是那些 SKILL.md 里沉淀下来的结构化指令。别人写的 api-tester 技能直接拷过来就能用零修改。相当于给智能体建了一个共享的能力库慢慢就能攒出一套自己的工具箱。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。