免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

构建智能体的专业技能树:Agent Skills生态全析(中篇)——从零搭建可复用的技能注册与调度层

构建智能体的专业技能树: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 共享同一棵技能树以及技能版本管理和灰度发布。那部分会涉及注册表的版本字段和调度层的路由策略感兴趣可以先把手头这版跑通把两个示例技能换成你自己的业务逻辑试试。
返回列表