AI Agent技能包实战:从散装函数到可复用Agent Skills组织范式 过去这几年但凡接触过 AI Agent 开发的同学应该都经历过一个相似的阶段看教程的时候觉得思路很清晰模型会自己规划、自己调工具、自己总结结果可真到自己动手写代码时才发现怎么把“工具”交给模型这件事本身就充满了歧义。有人把工具直接塞进 System Prompt塞到上下文爆炸有人把每个功能都写成独立的 Function Calling 函数最后函数列表长得像本词典还有人干脆不用工具让模型“凭感觉”输出 JSON然后在前端硬解析。换句话说很多人学会的并不是 Agent 开发而是一堆零散的 API 调用。最近 Agent Skills 这个概念热度很高吴恩达也专门出过相关教程。很多开发者看完后最大的感受是原来工具能力不应该是一堆散装函数而应该是一套可复用、可发现、可组合的“技能包”。这篇文章不打算复述某个现成教程而是按照“为什么需要 - 概念辨析 - 核心原理 - 最小实现 - 实战示例 - 排错清单 - 工程建议”这条线带你完整跑通 Agent Skills 从入门到代码实战的整个过程。1. 这篇文章真正要解决的问题先说判断Agent Skills 并不是一个全新的技术框架也不是某个厂商的私有协议。它本质上是在回答一个非常现实的问题——当你给 Agent 开发技能时怎么让这些技能看起来不像临时补丁而像一套可以积累、可被模型自动发现和调用的标准能力库。为什么这个问题重要因为大多数开发者第一次做 Agent 时都会陷入下面三种困境之一。第一种工具函数越写越多但复用全靠复制粘贴。项目 A 里写了一个查询库存的函数项目 B 想做同样的功能只能把代码拷过去再改一改。时间一长同一种能力在多个项目里各自为政行为不一致修 bug 要修好几遍。第二种上下文塞满规则效果却越来越差。为了让模型知道“什么时候该用什么工具”有人把工具说明、参数含义、注意事项全部写进 System Prompt。Prompt 越来越长模型反而容易忽略关键信息还增加了 token 成本和推理延迟。第三种工具之间没有任何组合逻辑。模型只会“用某个函数”不会“把几个函数串起来完成一个完整任务”。真正的智能体应该会观察、调用、再观察、再调用而不是一次性输出所有参数的硬编码结果。Agent Skills 的思路是把工具能力从散装函数提升为带元信息的独立模块。每一个 Skill 都包含名称、描述、参数 schema、执行逻辑甚至还可以包含自己的 few-shot 示例和内部依赖。Agent 在运行时会先“看到”有哪些可用的 Skills再根据用户任务动态选择、加载和调用。读到这里你应该能感觉到这个方向解决的并不是“模型聪不聪明”的问题而是“工程化地组织模型能力”的问题。对大多数团队来说后者才是 Agent 能不能真正落到生产的关键。2. Agent Skills 与 Function Calling、Tools、Agent 的关系很多同学容易把这几个词混在一起我先把它们的边界讲清楚。Function Calling 是模型侧的一种能力指模型在收到用户请求后不是直接生成最终答案而是生成一个“我想调用某个函数参数是哪些”的结构化输出。它解决的是“模型如何表达调用意图”的问题。Tools 是开发者提供给模型的一组函数描述通常包括函数名、功能描述和参数 JSON Schema。模型在 Function Calling 时会从 Tools 列表里挑一个最匹配的。它解决的是“模型能选择哪些操作”的问题。Agent 是一个完整系统它把大模型、Tools、记忆、任务拆解、结果验证和执行循环整合在一起让模型可以自主完成多步任务。Tools 是 Agent 的手脚模型是 Agent 的大脑。Agent Skills 则是在 Tools 之上的一层组织和标准化机制。一个 Skill 可以是一个 Tool也可以是多个 Tools 的组合甚至可以包含自己的提示词片段、示例和前置条件。我用一个表格把它们的区别列清楚概念解决的问题粒度典型表现Function Calling模型怎么输出调用意图单次调用模型输出{name: get_weather, args: {...}}Tools / Function模型可以操作哪些外部能力单个函数get_weather(city)Agent如何拆解任务、循环执行、验证结果完整系统规划 - 调用 - 观察 - 再规划Agent Skills如何组织、声明、复用一组工具能力能力模块一个技能包包含多个函数、描述、示例和配置从这个表可以看出Agent Skills 不是要替代 Function Calling也不是要重写 Agent 框架而是补上了 Tools 和 Agent 之间的工程化短板。举一个更容易理解的类比如果把 Agent 比作一个开发者Tools 是这个开发者会写的单个函数Agent Skills 则是他把函数整理成了带文档、带示例、可被人模型检索调用的代码库。没有代码库他也能临时写函数有了代码库效率和质量才能稳定下来。3. Agent Skills 的核心原理与设计目标要理解 Agent Skills不需要先背协议而是先理解它设计上的三个核心目标。3.1 技能的可发现性一个技能如果不被模型知道就等于不存在。所以 Agent Skills 强调“技能描述要写得好”。这个描述不是给人看的文档而是给模型看的检索索引。描述写得越准确模型在需要某个能力时就越容易匹配到它。实际编码时每个技能都可以包含一个description字段这个字段会随着技能一起加载给模型。比如你写了一个处理时间数据的技能描述可以写成“将任意格式的时间字符串解析为标准时间对象支持时区转换和日期计算”。模型听到用户问“帮我算一下三天后是哪天”就会优先匹配这个技能。3.2 技能的自包含性一个技能最好把自己的“说明书”和“实现代码”放在一起。模型需要更多上下文时系统可以把技能的详细说明、示例、前置条件一并喂给模型。这种设计避免了一股脑把所有工具说明塞进 Prompt而是在真正需要某个技能时才加载完整的技能说明。这种按需加载的思路正好解决了开头提到的问题工具多了之后Prompt 不可能无限变长。Agent Skills 让“技能发现”和“技能执行”分离先用简短描述做完匹配再去加载需要的内容。3.3 技能的复用与组合一个 Skill 可以依赖另一个 Skill。比如“生成周报”这个技能可能内部会调用“获取项目数据”和“格式化 Markdown 表格”这两个子技能。这种依赖关系如果写死在代码里灵活性会很差如果通过技能清单来声明那么模型在任务执行时就能自己判断该调用哪些组合。这也是 Agent Skills 区别于普通工具函数最大的地方工具函数是平面的技能是立体的它可以有层级、有依赖、有上下文。理解了这三个目标再看后面的代码就会轻松很多。我们做的小框架不需要多复杂只要能体现这三个设计原则就已经跑通了 Agent Skills 的核心思路。4. 环境准备与前置条件接下来进入实操部分。下面的示例使用 Python 实现选择 Python 是因为它在 AI 生态中最通用示例代码尽量不依赖特定框架方便你看清 Agent Skills 本身的运行逻辑。需要准备的环境如下Python 3.9 及以上版本建议 3.10 或 3.11。一个可以调用的大模型 APIOpenAI 兼容格式即可。示例中会用到openai库如果你用的是其他模型服务只要兼容 OpenAI API 都可以替换。一个用于测试的 API Key。建议准备一个虚拟环境避免污染全局环境。安装依赖python -m venv venv source venv/bin/activate pip install openai如果网络环境下载速度慢可以使用国内镜像源pip install openai -i https://pypi.tuna.tsinghua.edu.cn/simple关于模型版本这里不做死板要求。实际开发时支持 Function Calling 的模型都可以跑通这套逻辑。如果你使用的模型不支持 Function Calling也可以用“让模型输出结构化 JSON”的方式替代后面会提到这种退化方案。5. Agent Skills 最小实现从零搭建技能注册与加载机制我们开始写代码。这一节先搭建一个最小的 Agent Skills 骨架不引入任何重型框架所有代码控制在几个文件里。5.1 定义技能目录结构与元信息先约定一个技能目录每个技能以文件夹形式存在里面包含一个skill.json描述文件和 Python 实现文件skills/ ├── get_time/ │ ├── skill.json │ └── skill.py ├── calculator/ │ ├── skill.json │ └── skill.py └── weather_query/ ├── skill.json └── skill.pyskill.json是这个技能的元信息它要告诉系统三件事这个技能是做什么的、它有什么参数、它暴露了什么函数。以get_time为例skill.json内容如下{ name: get_time, description: 获取当前时间支持时区转换与日期计算适合回答现在几点、三天后是哪天等时间类问题。, version: 1.0.0, functions: [ { name: fetch_current_time, description: 获取指定时区的当前时间, parameters: { type: object, properties: { timezone: { type: string, description: 时区名称例如 Asia/Shanghai } }, required: [timezone] } } ] }这个文件的价值在于它把技能的“发现信息”和“调用细节”解耦。系统在加载技能时可以只把name和description拼进 Prompt 给模型做选择等到模型真正调用了再去导入skill.py执行。5.2 写技能实现模块skill.py中实现真正的函数逻辑。这里要保证函数名和最外层的 JSON 结构一致否则运行时会找不到函数# 文件路径skills/get_time/skill.py from datetime import datetime, timedelta from zoneinfo import ZoneInfo def fetch_current_time(timezone: str Asia/Shanghai) - str: 获取指定时区的当前时间 try: tz ZoneInfo(timezone) except Exception: tz ZoneInfo(Asia/Shanghai) timezone Asia/Shanghai参数无效使用默认时区 now datetime.now(tz) return { timezone: timezone, current_time: now.strftime(%Y-%m-%d %H:%M:%S) }注意这里返回的是 JSON 可序列化的字典这样 Agent 拿到结果后可以直接把它转成字符串继续推理不用处理复杂对象。5.3 技能注册器 SkillRegistry现在需要一个注册器来扫描技能目录、读取描述、动态导入函数。这个类承担两个职责加载技能元信息、根据函数名分发调用。# 文件路径skill_registry.py import importlib import json from pathlib import Path from typing import Any, Callable, Dict class SkillRegistry: def __init__(self, skills_dir: str skills): self.skills_dir Path(skills_dir) self._skills_meta: Dict[str, Dict] {} self._function_map: Dict[str, Callable] {} def load_all_skills(self) - None: if not self.skills_dir.exists(): raise FileNotFoundError(f技能目录不存在: {self.skills_dir}) for skill_dir in self.skills_dir.iterdir(): if not skill_dir.is_dir(): continue meta_file skill_dir / skill.json skill_file skill_dir / skill.py if not meta_file.exists() or not skill_file.exists(): continue meta json.loads(meta_file.read_text(encodingutf-8)) skill_name meta[name] for func in meta[functions]: func_name func[name] self._function_map[func_name] self._import_function( fskills.{skill_name}.skill, func_name ) self._skills_meta[skill_name] meta def _import_function(self, module_name: str, func_name: str) - Callable: module importlib.import_module(module_name) return getattr(module, func_name) def get_tools_for_llm(self) - list: tools [] for meta in self._skills_meta.values(): for func in meta[functions]: tools.append( { type: function, function: { name: func[name], description: func[description], parameters: func[parameters], }, } ) return tools def execute(self, func_name: str, args: Dict[str, Any]) - Any: if func_name not in self._function_map: raise ValueError(f未注册的函数: {func_name}) return self._function_map[func_name](**args)这段代码看起来不多但它完成了整个 Agent Skills 的骨架load_all_skills()扫描目录读取skill.json注册函数。get_tools_for_llm()把技能元信息转成模型需要的 tools 格式。execute()根据函数名分发执行对模型来说它只需要知道函数名和参数完全不关心函数在哪个模块里。6. 实战实现查询天气技能与计算器技能为了验证这套机制能跑通我们再添加两个技能一个查询天气模拟数据一个执行数学计算。6.1 查询天气技能先创建目录mkdir -p skills/weather_queryskill.json内容{ name: weather_query, description: 查询指定城市当前天气支持获取温度和天气状况。, version: 1.0.0, functions: [ { name: get_weather, description: 获取指定城市的当前天气信息返回天气状况和温度, parameters: { type: object, properties: { city: { type: string, description: 城市名称例如 北京、上海、广州 } }, required: [city] } } ] }skill.py内容# 文件路径skills/weather_query/skill.py import random def get_weather(city: str) - str: 获取指定城市的模拟天气数据实际项目中可以替换为真实天气 API weather_map { 北京: {condition: 晴, temperature: 23}, 上海: {condition: 小雨, temperature: 19}, 广州: {condition: 多云, temperature: 28}, 深圳: {condition: 晴, temperature: 30}, } data weather_map.get(city, {condition: 未知, temperature: random.randint(15, 30)}) return {city: city, condition: data[condition], temperature: data[temperature]}6.2 计算器技能再添加一个计算器技能mkdir -p skills/calculatorskill.json内容{ name: calculator, description: 执行基础数学运算支持加减乘除。, version: 1.0.0, functions: [ { name: calculate, description: 计算两个数字的数学表达式结果, parameters: { type: object, properties: { expression: { type: string, description: 数学表达式例如 1 2 * 3 } }, required: [expression] } } ] }skill.py内容# 文件路径skills/calculator/skill.py import ast import operator def calculate(expression: str) - float: 安全计算只包含数字和四则运算的数学表达式 allowed_operators { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv, } def eval_expr(node): if isinstance(node, ast.Expression): return eval_expr(node.body) if isinstance(node, ast.BinOp) and type(node.op) in allowed_operators: left eval_expr(node.left) right eval_expr(node.right) return allowed_operators[type(node.op)](left, right) if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): return node.value raise ValueError(f不支持的表达式: {expression}) tree ast.parse(expression, modeeval) return eval_expr(tree)这里的实现故意只允许数字和四则运算是为了避免直接使用eval()带来的安全问题。在真实项目中如果技能里面要执行一段来自模型或用户的代码务必先做白名单校验不要直接信任输入。7. 把技能交给模型完整调用链技能装好了接下来写一个 Demo演示模型如何根据用户问题自动选择技能、生成参数并执行。# 文件路径agent_demo.py import json from openai import OpenAI from skill_registry import SkillRegistry def run_agent(user_query: str) - str: registry SkillRegistry() registry.load_all_skills() tools registry.get_tools_for_llm() client OpenAI() messages [ {role: system, content: 你是一个智能助手可以调用可用技能来回答用户问题。}, {role: user, content: user_query}, ] # 第一次请求让模型决定是否需要调用技能 response client.chat.completions.create( model你的模型名称, messagesmessages, toolstools, tool_choiceauto, ) response_message response.choices[0].message # 如果模型没有产生工具调用直接返回文本结果 if not response_message.tool_calls: return response_message.content # 执行每个技能调用 tool_results [] for tool_call in response_message.tool_calls: func_name tool_call.function.name func_args json.loads(tool_call.function.arguments) print(f[Agent 调用技能] {func_name}({func_args})) result registry.execute(func_name, func_args) tool_results.append( { tool_call_id: tool_call.id, role: tool, name: func_name, content: json.dumps(result, ensure_asciiFalse), } ) # 把技能结果回传给模型让模型做出最终回答 messages.append(response_message) messages.extend(tool_results) final_response client.chat.completions.create( model你的模型名称, messagesmessages, ) return final_response.choices[0].message.content if __name__ __main__: query 北京今天天气怎么样另外帮我算一下 12 * 8 5 等于多少。 answer run_agent(query) print(最终回答, answer)这段代码的流程是标准的 Agent 工具调用循环加载全部技能转换为模型可识别的tools格式。发送用户问题模型判断是否需要调用技能。如果模型返回了tool_calls逐条执行并收集结果。把工具执行结果再次发给模型让模型生成最终的自然语言回答。执行前需要把代码中的你的模型名称替换成实际可用的模型 ID。如果你使用的是 OpenAI可以填gpt-4o-mini如果使用的是国产模型、开源模型或其他兼容服务在初始化OpenAI()时传入base_url即可。8. 运行结果与效果验证运行 Demopython agent_demo.py正常情况下应看到类似下面的输出[Agent 调用技能] get_weather({city: 北京}) [Agent 调用技能] calculate({expression: 12 * 8 5}) 最终回答 北京今天天气晴朗气温 23 摄氏度。计算 12 * 8 5 的结果是 101。这代表整个 Agent Skills 链路已经通了模型发现需要两个技能、自动生成参数、通过注册器执行函数、再把结果综合成回答。如果执行过程中什么输出都没有或者模型直接返回了“我不知道”可以从以下几点排查技能描述是否足够清晰。描述写得太模糊模型可能匹配不到你的技能。模型是否支持 Function Calling。确认使用的模型 API 支持tools参数。是否存在导入路径问题。确保当前目录结构是skills/xxx/skill.py运行命令时在项目根目录下执行。如果模型不支持 Function Calling还有一种降级方案把get_tools_for_llm()返回的 JSON 结构直接拼进 System Prompt然后要求模型输出固定 JSON 格式的调用请求。代码会变成字符串解析稍微“脏”一点但思路完全一致。9. 常见问题与排查方法在实际开发中会遇到下面几个高频问题这里统一整理成表格方便收藏查阅。问题现象可能原因排查方式解决方案模型没有产生任何工具调用技能描述语义模糊模型不理解何时使用打印tools原样查看描述是否准确重写description突出“在什么情况下使用”提示函数不存在执行报错skill.json中函数名与skill.py中函数名不一致检查 JSON 中 functions.name 与 Python 定义统一命名建议使用相同字符串常量工具参数解析失败模型生成的参数 JSON 非法或字段缺失打印tool_call.function.arguments增加异常捕获参数校验后执行上下文过长请求报错工具描述过多或历史消息过长检查实际 token 消耗精简技能描述考虑对历史消息做摘要技能执行耗时过长技能内部调用了外部 API 或数据库增加超时控制和日志埋点为技能执行设置超时异步执行调用多技能并行调用互相干扰技能内部使用了不安全的全局状态检查技能代码是否有共享可变变量技能内部避免使用全局变量改为局部数据如果你的项目运行中出现了没有在表里的问题建议先在技能入口处加日志打印出“模型返回的原始内容”和“技能执行后的返回内容”。绝大多数 Agent 问题根源都在数据格式和参数传递上而不是模型能力本身。10. 最佳实践与工程建议跑通最小示例之后如果要在真实项目中使用 Agent Skills下面这些工程经验值得参考。10.1 技能描述先写“场景”再写“功能”模型选择技能靠的是语义匹配而不是关键词精确匹配。描述里最好告诉模型“什么时候用这个技能”。比如较差的描述天气查询函数 较好的描述当用户询问某城市当前天气、温度、降水情况时使用该技能获取实时气象数据。这样模型在遇到口语化问题时也能准确命中。10.2 技能包要控制体积一个 Skill 的描述和示例并不是越多越好。每次请求时所有技能的元信息都会占用上下文。如果技能数量膨胀到几十个模型反而容易“乱选”。建议控制单个技能的描述在 200 字以内技能总数控制在 10 个以内超出部分做多级分类或独立服务。10.3 技能执行要加超时和错误处理Agent 在真实环境中可能遇到网络超时、第三方 API 报错、数据格式异常等情况。任何一个技能抛出未捕获异常都会打断整个 Agent 循环。更稳妥的做法是让execute()永远返回可序列化的结果即使出错也把错误信息转成 JSON 返回给模型让模型决定如何向用户解释。def execute(self, func_name: str, args: Dict[str, Any]) - Any: if func_name not in self._function_map: return {error: f未注册的函数: {func_name}} try: return self._function_map[func_name](**args) except Exception as e: return {error: str(e)}这样调整之后Agent 遇到再复杂的失败情况都能继续对话而不是直接崩溃。10.4 安全边界与权限控制技能执行权限遵循最小权限原则。如果一个技能只是查询数据就不要给它数据修改权限如果一个技能需要访问数据库建议在技能内部走独立的只读账号或独立连接串。Agent 的调用入口很容易被提示注入影响不要盲目相信模型生成的参数对危险操作做二次确认。特别提醒不要在任何技能中使用裸eval()执行模型生成的代码除非你做了严格的白名单校验。实际项目中99% 的场景可以通过ast解析、参数约束或沙箱执行来解决。10.5 版本管理与灰度发布技能也是有版本的。推荐在skill.json中维护version字段并在技能目录名中保留版本信息。发布新版本技能时先让少量流量试用新版本稳定后再全量切换。回滚时直接把模型可加载的技能目录切回上一个版本即可。11. 总结与后续学习方向这篇文章从 Agent Skills 的定位讲起说清楚了它和 Function Calling、Tools、Agent 的关系然后通过一个最小框架实现了技能注册、描述加载、模型调用、函数分发、结果回传的完整链路。你现在能够做到的应该包括理解 Agent Skills 为什么不是新框架而是一套工具组织范式能够为自己的项目编写skill.json描述能够写一个基础的技能注册器能够让大模型自动发现并调用技能能够在技能执行失败时进行基础排查。下一步你可以往三个方向继续深入第一把技能从本地函数扩展为远程服务。技能内部调用 HTTP API 或微服务这样技能包就变成了一个跨项目复用的能力网关。第二给技能加入内部示例和 few-shot。在skill.json中增加示例输入输出模型在复杂场景下的参数生成准确率会明显提升。第三把技能和记忆机制结合。让 Agent 记住用户的偏好在执行任务时自动适配参数比如用户习惯查看摄氏温度还是华氏温度这类信息可以从历史对话中提取并注入技能调用参数。这些内容再往后就是 Agent 工程化最核心的设计题了。真正写好一个 Agent从来不只是堆模型能力而是把你熟悉的功能梳理成模型能看懂的、安全可控的、可复用的技能体系。建议把文章里的最小示例跑通一次然后挑一个你自己项目里的常用功能改造成第一个 Skill你会明显体会到这种写法和“塞入 Prompt”之间的差别。