构建智能体的专业技能树:Agent Skills生态全析(中篇)——从零搭建可复用的技能注册与调度层
1. 为什么你的 Agent 技能总是“一次性”的很多人搭智能体时都遇到过这个场景写了一个能查天气、能读文件、能调接口的 Agent跑通一次挺开心但换个任务就得重写一遍。技能和业务逻辑搅在一起参数校验散落在各个函数里调度全靠 if-else 硬编码。结果就是——技能不可复用Agent 越写越臃肿。这篇要解决的就是这个问题给智能体搭一棵可插拔的技能树。核心思路是把技能从 Agent 主逻辑里抽出来做成独立的注册表让技能发现、参数校验、调度执行三层解耦。你可以把它理解成给 Agent 装了一个“应用商店”技能按统一格式注册进来Agent 运行时按需查找、校验、调用用完即走。适合谁看如果你正在做多 Agent 协作、想让技能跨项目复用或者单纯觉得现在的 Agent 代码太乱想重构这篇的配置和脚本可以直接抄。我会用两个示例技能一个查汇率、一个算文本统计走完整流程注册 → 发现 → 校验 → 调度 → 验证返回。全程本地可跑不依赖任何外部服务最后再讲怎么把调用凭证统一管起来。先明确一个概念边界。Skills 和 Tools 不是一回事Tools 是原子能力读文件、发请求Skills 是编排好的工作流先校验参数、再调工具、最后格式化输出。我们要搭的注册与调度层管的是 Skills 这一层。MCP 负责数据接入Subagents 负责并行隔离这些是上下游本篇聚焦中间那层“技能怎么管”。2. TaoToken 前置把调用凭证从技能里剥出来技能树要可复用有个前提容易被忽略技能本身不能绑死某一家模型的 Key。如果每个技能里都硬编码一个 API Key那技能就没法跨环境迁移也没法集中轮换凭证。所以第一步是把模型调用通道统一出去。我用的方案是 TaoToken它提供一个统一的 Key/API 通道兼容 OpenAI 风格的接口格式。好处是技能注册表里只存“模型标识”不存凭证真正调用时由调度层统一注入 Base URL 和 Key。这样技能文件可以进 Git凭证留在环境变量里。你需要准备三样东西我列成表格方便对照项目值说明Base URLhttps://taotoken.net/api统一入口不加任何多余路径API Key在控制台生成形如sk-开头只存环境变量Model ID按需选择调度层配置里引用技能文件不写死获取 Key 的入口在控制台的 API Keys 页面生成后复制一次即可页面刷新后不再完整显示。如果你还没账号从官网进https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册后在控制台左侧找到 API Keys。这里有个工程习惯值得养成把凭证读取封装成一个函数所有技能调度都走它。这样以后换通道、加限流、做审计只改一处。下面是我用的最小封装放在config/llm_client.pyimport os from openai import OpenAI def get_client(): base_url os.environ.get(TAOTOKEN_BASE_URL, https://taotoken.net/api) api_key os.environ.get(TAOTOKEN_API_KEY) if not api_key: raise RuntimeError(TAOTOKEN_API_KEY 未设置请先导出环境变量) return OpenAI(base_urlbase_url, api_keyapi_key) DEFAULT_MODEL os.environ.get(TAOTOKEN_MODEL, gpt-4o-mini)环境变量这样导出Linux/macOSexport TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_MODELgpt-4o-miniWindows PowerShell 用$env:TAOTOKEN_API_KEYsk-...。注意 Base URL 结尾不要带/v1SDK 会自己拼多写一层会 404。这一步做完技能树就有了统一的“电源接口”后面所有技能都从这里取电。3. 可复制的技能注册表与调度脚本现在进入核心部分。技能树要落地得先定义技能的“身份证”格式。我用 JSON 存注册表每个技能一条记录包含名称、描述、参数 schema、执行入口。这样调度层可以只读注册表就知道有哪些技能、怎么校验参数不用 import 具体实现。先建目录结构agent_skills/ ├── registry/ │ └── skills.json ├── skills/ │ ├── currency_convert.py │ └── text_stats.py ├── config/ │ └── llm_client.py └── dispatcher.py注册表registry/skills.json内容如下两个示例技能都注册进去{ version: 1.0, skills: [ { name: currency_convert, description: 把一种货币金额换算成另一种货币使用固定汇率表, entry: skills.currency_convert:run, parameters: { type: object, properties: { amount: { type: number, minimum: 0 }, from_currency: { type: string, enum: [CNY, USD, EUR] }, to_currency: { type: string, enum: [CNY, USD, EUR] } }, required: [amount, from_currency, to_currency] } }, { name: text_stats, description: 统计一段文本的字符数、词数和行数, entry: skills.text_stats:run, parameters: { type: object, properties: { text: { type: string, minLength: 1 } }, required: [text] } } ] }注意entry字段用的是模块路径:函数名格式调度层用importlib动态加载。这样加新技能只需改 JSON不用动调度代码——这就是“可插拔”的关键。两个技能实现skills/currency_convert.pyRATES { (CNY, USD): 0.14, (USD, CNY): 7.15, (CNY, EUR): 0.13, (EUR, CNY): 7.70, (USD, EUR): 0.92, (EUR, USD): 1.09, } def run(amount, from_currency, to_currency): if from_currency to_currency: return {result: amount, rate: 1.0} rate RATES.get((from_currency, to_currency)) if rate is None: raise ValueError(f不支持的货币对: {from_currency}-{to_currency}) return {result: round(amount * rate, 2), rate: rate}skills/text_stats.pydef run(text): lines text.splitlines() words text.split() return { chars: len(text), words: len(words), lines: len(lines), }调度层dispatcher.py负责三件事加载注册表、用 JSON Schema 校验参数、动态调用。校验用jsonschema库没装的话pip install jsonschemaimport json import importlib from pathlib import Path from jsonschema import validate, ValidationError REGISTRY_PATH Path(__file__).parent / registry / skills.json class SkillDispatcher: def __init__(self): self.registry {} self._load() def _load(self): data json.loads(REGISTRY_PATH.read_text(encodingutf-8)) for item in data[skills]: self.registry[item[name]] item def list_skills(self): return [ {name: k, description: v[description]} for k, v in self.registry.items() ] def call(self, name, params): if name not in self.registry: raise KeyError(f技能未注册: {name}) meta self.registry[name] try: validate(instanceparams, schemameta[parameters]) except ValidationError as e: raise ValueError(f参数校验失败: {e.message}) module_path, func_name meta[entry].split(:) module importlib.import_module(module_path) func getattr(module, func_name) return func(**params)这段代码里list_skills就是“技能发现”接口call就是“校验 调度”。技能发现和调度彻底解耦Agent 主逻辑只需要拿到技能列表决定调哪个剩下的交给 dispatcher。4. 验证请求注册两个技能后触发调用配置写完得验证路由和返回是否符合预期。我写了个验证脚本verify.py依次做四件事列出技能、正常调用、故意传错参数、调用不存在的技能。from dispatcher import SkillDispatcher d SkillDispatcher() print( 1. 技能发现 ) for s in d.list_skills(): print(f- {s[name]}: {s[description]}) print(\n 2. 正常调用 currency_convert ) print(d.call(currency_convert, { amount: 100, from_currency: CNY, to_currency: USD })) print(\n 3. 正常调用 text_stats ) print(d.call(text_stats, {text: hello agent skills\nsecond line})) print(\n 4. 参数校验金额为负) try: d.call(currency_convert, { amount: -5, from_currency: CNY, to_currency: USD }) except ValueError as e: print(已拦截:, e) print(\n 5. 未注册技能 ) try: d.call(not_exist, {}) except KeyError as e: print(已拦截:, e)跑python verify.py预期输出 1. 技能发现 - currency_convert: 把一种货币金额换算成另一种货币使用固定汇率表 - text_stats: 统计一段文本的字符数、词数和行数 2. 正常调用 currency_convert {result: 14.0, rate: 0.14} 3. 正常调用 text_stats {chars: 30, words: 5, lines: 2} 4. 参数校验金额为负 已拦截: 参数校验失败: -5 is less than the minimum of 0 5. 未注册技能 已拦截: 技能未注册: not_exist看到这个输出说明路由正确、校验生效、异常可控。第 2 步返回14.0是 100 CNY 按 0.14 汇率换算的结果第 3 步chars是 30含换行符words是 5都对得上。如果你想让 Agent 自己决定调哪个技能可以把list_skills()的结果塞进模型上下文让模型输出技能名和参数再交给 dispatcher。这一步的模型调用就走第 2 章封装的 clientfrom config.llm_client import get_client, DEFAULT_MODEL client get_client() resp client.chat.completions.create( modelDEFAULT_MODEL, messages[ {role: system, content: 你是技能路由器根据用户请求输出技能名和JSON参数。}, {role: user, content: 帮我把 200 美元换成人民币} ] ) print(resp.choices[0].message.content)这一步能跑通说明“模型决策 本地调度”的链路是通的。模型只负责选技能和填参数真正的执行和校验在本地安全边界清晰。5. 本篇常见错排查401、校验失败与路由异常技能树搭起来后报错基本集中在四类。我把真实遇到的错误和定位方法列出来你对照着查。第一类401 Unauthorized / invalid api key。这个几乎都是凭证问题。先确认TAOTOKEN_API_KEY真的导出了用echo $TAOTOKEN_API_KEY看有没有值。如果值对但还报 401检查 Base URL 是不是写成了https://taotoken.net/api/v1——多一层/v1会导致路径拼接错误。正确写法就是https://taotoken.net/api。还有一种情况是 Key 复制时带了空格或换行重新生成一次最省事。第二类参数校验失败报is not of type number或is not one of。这是 JSON Schema 在起作用不是 bug。常见原因是模型返回的参数类型不对比如把amount输出成字符串100。解决办法是在调度层加一层轻量转换或者在 system prompt 里明确要求“数值字段输出数字类型”。enum报错则是货币代码不在允许列表里检查注册表的enum是否覆盖了实际用到的值。第三类ModuleNotFoundError: No module named skills。动态加载时模块路径找不到。确认dispatcher.py和skills/目录在同一级且skills/下有__init__.py空文件即可。如果是从其他目录运行脚本importlib的搜索路径可能不对在dispatcher.py顶部加一句把项目根目录塞进sys.pathimport sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent))第四类reading choices相关报错比如KeyError: choices或返回体里没有 choices。这通常说明请求根本没到模型或者返回的是错误结构。先打印完整响应体看error字段。如果是local proxy failed这类提示说明网络层有问题检查 Base URL 是否可达。如果响应正常但结构不对确认 SDK 版本和接口格式匹配——TaoToken 兼容 OpenAI 格式用官方openaiSDK 即可。排查顺序建议固定成先看凭证401→ 再看参数校验→ 再看模块路径import→ 最后看响应结构choices。按这个顺序走九成问题五分钟内能定位。6. 把技能树接上统一通道下一步做并行调度到这里一棵最小可用的技能树就跑起来了注册表管技能元数据dispatcher 管发现和校验技能实现只管业务逻辑凭证统一走 TaoToken 通道。这套结构的好处是加第三个、第十个技能时你只需要往skills.json里追加一条记录再写一个纯函数调度层一行都不用改。如果你要把这套东西用到实际项目里有两个方向可以继续。一是把list_skills()的输出做成工具描述喂给模型让模型自主路由这就是“模型决策 本地执行”的 Agent 形态。二是把 dispatcher 的call改成异步配合 Subagents 做并行调用——多个互不依赖的技能同时跑主线程只收结果上下文不被污染。凭证这块建议你尽早把 Key 从代码里彻底剥离。我现在的做法是本地用环境变量CI 里用 secrets所有模型调用都走config/llm_client.py一个出口。这样以后要换通道、加限流、做用量统计改一个文件就够了。需要生成新 Key 或者查看用量从控制台的 API Keys 进想先试试模型对话效果可以从模型对话页面直接测如果打算长期跑编码类 AgentCoding Plan 的额度更划算。接入细节看文档里面有各语言的示例。下一篇会讲技能树的第二层怎么让多个 Agent 共享同一棵技能树以及技能版本管理和灰度发布。那部分会涉及注册表的版本字段和调度层的路由策略感兴趣可以先把手头这版跑通把两个示例技能换成你自己的业务逻辑试试。

相关新闻

CMake UseSWIG 模块完全指南:用 swig_add_library 将 C/C++ 封装为 Python、Java、C 等语言扩展

CMake UseSWIG 模块完全指南:用 swig_add_library 将 C/C++ 封装为 Python、Java、C 等语言扩展

构建工具开发工具CLI 【免费下载链接】CMake Mirror of CMake upstream repository 项目地址: https://gitcode.com/gh_mirrors/cm/CMake 点击查看 免费下载 UseSWIG 是 CMake 官方模块,用于在构建系统中集成 SWIG 中的底层实现原理(自定义命…

2026/10/9 12:12:20 阅读更多 →
gemini-notebook-mcp-cli v0.9.12 维护版深度解析:查询会话隔离与空答案防御机制

gemini-notebook-mcp-cli v0.9.12 维护版深度解析:查询会话隔离与空答案防御机制

【免费下载链接】gemini-notebook-mcp-cli Programmatic access to Gemini Notebook - via command-line interface (CLI), Model Context Protocol (MCP) server, and AI agent skills. 项目地址: https://gitcode.com/gh_mirrors/not/gemini-notebook-mcp-cli 点击…

2026/10/9 12:12:20 阅读更多 →
分治 - 归并排序

分治 - 归并排序

1.归并排序 912. 排序数组 - 力扣(LeetCode)https://leetcode.cn/problems/sort-an-array/description/ 归并排序核心思想也是用递归实现的,给了一个数组,首先选一个中间点mid,根据中间点mid 把数组分成了两部分。…

2026/10/9 12:12:20 阅读更多 →

最新新闻

Loop Engineering实战:用Claude Code、Codex、Cursor搭建AI编程闭环

Loop Engineering实战:用Claude Code、Codex、Cursor搭建AI编程闭环

1. 从"写提示词"到"搭回路":Loop Engineering 到底在解决什么问题 如果你最近在折腾 Claude Code、Codex、Cursor 这类 AI 编程工具,大概率经历过这样一个阶段:一开始觉得"哇,一句话就能生成代码"&…

2026/10/9 12:51:26 阅读更多 →
openGauss数据库源码解析:核心特性、架构与编译入门

openGauss数据库源码解析:核心特性、架构与编译入门

从去年年底开始,我把openGauss的源码当成了主要研究对象。这篇是系列文章的第二篇,继续把openGauss这个数据库的底子摸一遍,讲清楚它的核心特性、整体架构、源码目录和编译体验,算是给后面的源码解析铺路。上一篇讲了openGauss的来…

2026/10/9 12:51:26 阅读更多 →
Text-to-CAD实战:用自然语言生成可编辑STEP模型

Text-to-CAD实战:用自然语言生成可编辑STEP模型

做了十年机械设计,再往前数几年,天天跟CAD打交道最烦的三件事:装完CAD发现缺 SHX 字体,图纸莫名打不开;想卸载重装又卸不干净,二次授权直接卡住;画个法兰盘改了三版尺寸,模型树里全…

2026/10/9 12:51:26 阅读更多 →
CAXA电子图板2026箭头全攻略:从尺寸标注到打印缩水一次解决

CAXA电子图板2026箭头全攻略:从尺寸标注到打印缩水一次解决

搞机械设计和工程制图的,谁没跟箭头打过交道?可越是常见的东西,越容易被草率处理。前几天帮朋友看图纸,一套泵体零件图,尺寸标注的箭头歪歪扭扭,引出线的箭头指向了空气,序号线缠在一起——问题…

2026/10/9 12:51:26 阅读更多 →
模板编译期计算:从模板元编程到constexpr的进阶实践

模板编译期计算:从模板元编程到constexpr的进阶实践

模板编译期计算:把“计算”这件事彻底塞给编译器写过几年C的老哥应该都有这种体会:很多代码写出来,其实根本不是给运行时的CPU跑的,而是写给编译器看的。你想要的不是“程序运行时算出一个结果”,而是“代码编译期间就…

2026/10/9 12:51:26 阅读更多 →
Flink数据倾斜实战:定位热点Key与加盐两阶段聚合治理

Flink数据倾斜实战:定位热点Key与加盐两阶段聚合治理

做实时数仓的同行,大概率都经历过这样的深夜:一个运行了半年的 Flink 数据倾斜问题突然爆发,某个并行子任务 CPU 直接打满,Kafka 消费延迟像坐火箭一样往上蹿,而相邻的 TaskManager 却闲得发慌。群里开始刷屏&#xff…

2026/10/9 12:50:25 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

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

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

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

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

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

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 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/8 21:13:17 阅读更多 →
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/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练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/9 6:17:20 阅读更多 →