Agent Skill入门到实战:构建可扩展的最小Agent系统
2026/9/1 10:54:18 网站建设 项目流程

最近在做 AI Agent 应用时,我发现一个非常奇特的局面:网上讲 Agent、讲 MCP、讲 Function Calling 的内容铺天盖地,但真正把 Agent Skill 这个概念讲清楚,并且能带着你从零写出可运行代码的教程却很少。很多人看完视频后仍然不清楚:Skill 和 Agent 到底什么关系?Skill 和 MCP 有什么区别?我自己的业务场景里到底要不要引入 Skill?

这篇文章就围绕 Agent Skill 展开。我会用一套完整、可复制的 Python 代码,带你从概念理解到代码实战,手把手实现一个支持 Skill 调用的最小 Agent 系统。文章内容适合正在做 AI 大模型应用开发、想深入理解 Agent 技术栈、或者准备在业务中引入智能体能力的开发者。读完你会明白 Agent Skill 是什么、如何设计、如何接入大模型、如何排错,以及如何在本地部署场景下适配。

1. 为什么要把 Agent Skill 单独拿出来讲

1.1 Agent 应用开发的核心问题

在实际开发中,我们使用大模型时最常遇到的问题是:模型“能说不能做”。你问它“帮我查一下今天的天气”,它能告诉你 API 应该怎么调用,但它自己并不会真的去请求天气服务;你让它“把上个月订单汇总成表格”,它能写出处理逻辑,但不会真正读取数据库、生成文件。

要解决这个问题,业内已经有了比较统一的思路:把模型作为“决策大脑”,把具体操作封装成可被模型调用的工具。模型不直接执行操作,而是输出“我要调用哪个工具、参数是什么”,然后由程序执行真实逻辑。这套机制的通用名称是 Function Calling 或 Tool Calling,而 Agent Skill 正是在这个机制之上,把“工具”进一步工程化、语义化、可维护化的一种能力封装方式。

但很多开发者把 Skill 简单理解成“给模型写个函数”,这就把问题想窄了。Skill 不只是函数,它包含两层:一层是给模型看的“说明书”,告诉模型什么时候该用它、参数怎么填;另一层是给程序跑的“实现体”,真正完成业务逻辑。两者缺一不可。这也是我在文章标题里强调“入门到代码实战”的原因——只有把这两层都写清楚,Skill 才真正可用。

1.2 Agent Skill 的通俗解释

用一个大家熟悉的例子说明。假设你把大模型看作一个新入职的实习生,它聪明、知识面广,但刚来公司,不知道你们内部系统的操作方式。Skill 就相当于给这个实习生配套的“岗位技能卡”。每张技能卡正面写着:这个技能是干什么的、在什么场景下使用、需要提供什么信息;反面写着:具体怎么执行。

实习生(Agent)拿到你的问题后,先看技能卡,判断应该使用哪张;然后把参数填好,交给执行部门(程序代码)去完成。执行部门把结果回传给实习生,实习生再整理成自然语言回复给你。

从这个角度看,Agent Skill 是由三部分组成的:

  1. Skill 元数据:包括技能名称、功能描述、参数定义。
  2. Skill 执行函数:真正运行业务逻辑的代码。
  3. Agent 调度逻辑:把用户输入转发给大模型,让模型决定调用哪个 Skill,再执行并把结果返回给模型。

1.3 为什么这个概念仍然值得系统学习

2026 年这个节点,大模型应用已经从前期的“你问我答”阶段,走向“真实任务执行”阶段。企业做 AI 落地,不再是简单接一个 ChatBot,而是希望 AI 能联动业务系统、数据库、审批流、第三方接口。Agent Skill 正是这套体系里非常基础、也非常重要的工程单元。

需要特别强调的是,Skill 的引入成本和模型微调相比要低得多。微调一个行业模型需要整理训练集、准备机器资源、反复实验,周期长、成本高;而定义一个 Skill 只需要写一个函数和一段描述,几分钟就能接入。尤其当业务规则频繁变化时,Skill 的维护成本远低于模型重训。因此,掌握 Agent Skill 几乎是应用层 AI 开发者的必备技能。

2. Agent、Skill、MCP 到底有什么区别

在写代码之前,有必要先把三个高频概念区分清楚。很多读者在群里问:Agent 和 Skill 是不是一回事?Skill 和 MCP 又是什么关系?这里统一梳理。

2.1 Agent 是“大脑”,Skill 是“能力包”

Agent 是一个完整的智能体,它具备任务理解、规划、记忆、工具调用、自我反思等能力。你可以把 Agent 理解成一个独立运行的程序,它有完整的循环逻辑。

我们常见的 Agent 执行循环可以简化为以下几步:

用户输入 ↓ 大模型理解任务并规划 ↓ 判断是否需要调用 Skill ↓ 执行 Skill 并拿到结果 ↓ 大模型整理结果并回复用户 ↓ (必要时)继续下一步任务

在这个循环里,Skill 是 Agent 的“能力包”。Agent 负责决定用不用、什么时候用、用哪一个;Skill 负责把具体动作做掉。没有 Skill,Agent 只是“会聊天的模型”;有了 Skill,Agent 才能真正“动手干活”。

2.2 MCP 是“外接工具协议”,Skill 是“内化技能”

MCP(Model Context Protocol)是一套用于连接 AI 应用与外部工具、数据源的开放协议。它的目标是标准化 AI 应用与外部能力之间的交互方式。如果一个公司有大量已经存在的内部系统,通过 MCP 可以让 AI 应用以一种统一的方式访问这些系统。

Skill 和 MCP 的关系不是替代,而是分层:

  • Skill 更侧重应用内的能力封装,通常是在 Agent 进程内直接执行逻辑,比如查内存数据、算积分、调用自定义函数。
  • MCP 更侧重跨系统连接,它定义了客户端、服务端、工具发现、调用规范等一整套协议,适合把外部服务接入 AI。

简单理解:Skill 是“我会做这件事”,MCP 是“我能通过标准协议连接其他系统来做这件事”。两者可以结合使用,比如 Skill 内部实现逻辑可以调用 MCP 客户端去远程拉数据。

2.3 何时使用 Skill,何时使用 MCP

选型建议如下:

场景推荐方案原因
在 Agent 内部封装一些自定义逻辑Skill实现简单,维护成本低
需要连接多个异构外部系统MCP协议统一,便于标准化接入
工具数量少且逻辑简单Skill 或直接 Function Calling不需要引入额外协议层
企业级工具生态,多个团队共用MCP有一致的发现和鉴权机制

结论是:如果你是初学者,或者业务场景集中在单个应用内,优先从 Skill 开始;当你的工具数量变多、需要打通多个系统时,再考虑引入 MCP。千万不要一开始就上重型架构。

3. 环境准备与版本说明

3.1 开发环境要求

本文的示例代码使用 Python 编写,建议使用 Python 3.9 及以上版本。我在本地测试时使用的是 Python 3.10,系统为 macOS,但代码本身不依赖特定操作系统,Windows 和 Linux 同样可以运行。

你需要准备以下基础环境:

  • Python 3.9+
  • pip 包管理工具
  • 一个支持发送 HTTP 请求的 Python 运行环境
  • 可选:支持 Tool Calling 的大模型 API(OpenAI 兼容协议即可)

这里需要特别说明,大模型 API 的版本迭代比较快,不同模型服务商的接口细节可能略有差异。本文中的代码基于常见的 OpenAI 兼容/chat/completions接口编写,这是目前大多数模型服务商都支持的标准格式。当你接入具体服务时,需要根据服务商文档微调BASE_URLMODEL等参数。

3.2 依赖安装

项目依赖非常少,核心只需要requests库。安装命令如下:

pip install requests

如果你的环境里已经有requests,可以跳过这一步。示例中其它功能(如 JSON 解析、模块导入)都使用 Python 标准库实现,无需额外安装。

3.3 示例项目结构

为了便于阅读,建议按照下面的目录结构创建项目:

agent_skill_demo/ ├── main.py ├── agent.py ├── skill_manager.py └── skills/ ├── __init__.py ├── user_skill.py └── weather_skill.py

后面每一小节都会明确说明代码应该放在哪个文件里。你也可以把代码合并成单个文件运行,但按模块拆分更接近真实项目结构,后续扩展新 Skill 时也更方便。

4. Skill 的核心设计:从工具调用到 Skill 描述

4.1 大模型如何“看”到 Skill

要理解 Skill 设计,必须先理解大模型的工具调用机制。以 OpenAI 兼容接口为例,当你在请求中额外传入tools参数时,模型在生成回复时除了输出普通文本,还可能输出一个结构化的tool_calls字段。

这个字段里就包含模型选择的工具名称和参数 JSON。程序拿到这个结构化输出后,执行对应的函数,再把结果以role: "tool"的消息追加进对话上下文。模型拿到工具结果后,继续推理并生成最终回复。

这是一种非常优雅的设计:模型不需要真的执行代码,它只负责“判断”和“描述参数”,真正的执行由外部程序完成。这既保证了灵活性,也方便做权限控制和审计。

4.2 Skill 的元数据结构

为了让模型能够正确选择 Skill,我们需要给每个 Skill 写一份机器可读的“说明书”。这份说明书的格式和 Tool Calling 中的 tools 参数保持一致,通常包含四个核心字段:

  • type:固定为function,表示这是一个函数型工具。
  • function.name:Skill 的唯一名称,模型将用这个名称发起调用。
  • function.description:对 Skill 功能的自然语言描述,模型主要靠它判断是否调用。
  • function.parameters:参数的 JSON Schema 描述,包括参数类型、是否必填、字段说明。

其中最重要的是descriptionparametersdescription写得是否清晰,直接决定模型会不会乱调用;parameters定义得是否准确,直接决定程序能不能正确执行。在实际项目中,很多调用失败都是因为这两个字段写得含糊。

4.3 一个最简单的 Skill 实现

下面我们定义一个“用户信息查询”的 Skill。它不依赖数据库,而是从内存字典中读取数据,方便你直接运行验证。

# 文件路径:skills/user_skill.py # 用户信息查询 Skill NAME = "get_user_info" DESCRIPTION = ( "根据用户ID查询用户的昵称、会员等级和积分信息。" "当用户询问“我的账户”“我的资料”“查用户信息”时使用。" ) PARAMETERS = { "type": "object", "properties": { "user_id": { "type": "string", "description": "用户ID,例如 10001" } }, "required": ["user_id"] } # 模拟数据库 USER_DB = { "10001": {"name": "张三", "level": "VIP", "points": 5200}, "10002": {"name": "李四", "level": "普通用户", "points": 300}, } def execute(user_id: str): """Skill 的实际执行逻辑""" if user_id not in USER_DB: return {"code": 1, "message": f"用户 {user_id} 不存在"} return {"code": 0, "data": USER_DB[user_id]}

这个文件中出现了四个模块级常量和一个函数:

  • NAME:Skill 名称,全项目唯一。
  • DESCRIPTION:给大模型看的描述,必须写清楚“什么时候用”。
  • PARAMETERS:参数定义,使用 JSON Schema 格式。
  • execute:真正执行逻辑的函数,接收参数并返回结果。

这里要注意,execute的返回结果应该统一为一个可 JSON 序列化的结构。因为后续需要把结果通过对话消息传给大模型,如果返回一个无法序列化的对象,会在传输过程中报错。

再来看一个天气查询 Skill:

# 文件路径:skills/weather_skill.py # 城市天气查询 Skill NAME = "query_weather" DESCRIPTION = ( "查询指定城市的实时天气情况,包括温度、天气现象和风力。" "当用户询问“今天天气”“会不会下雨”“气温多少度”时使用。" ) PARAMETERS = { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,例如:北京、上海、杭州" } }, "required": ["city"] } # 模拟天气数据 WEATHER_DB = { "北京": {"temperature": 18, "condition": "晴", "wind": "北风3级"}, "上海": {"temperature": 22, "condition": "多云", "wind": "东风2级"}, "杭州": {"temperature": 20, "condition": "小雨", "wind": "南风1级"}, } def execute(city: str): if city not in WEATHER_DB: return {"code": 1, "message": f"暂无城市 {city} 的天气数据"} return {"code": 0, "data": WEATHER_DB[city]}

你会发现,两个 Skill 的结构完全一致。这就是工程化的意义:只要新写一个文件,增加一个 Skill,我们的管理器就能自动识别并注册,不需要修改 Agent 核心代码。

5. 完整实战:从 0 到 1 搭建支持 Skill 的 Agent

5.1 需求与调度流程

我们现在要做的是一个极简但完整的 Agent 系统,用户输入自然语言,Agent 自动决定调用哪个 Skill,并返回可读的结果。

流程如下:

  1. 用户输入问题。
  2. Agent 把问题和大模型的系统提示词一起发送。
  3. 大模型判断需要调用哪个 Skill,并返回tool_calls
  4. Agent 根据tool_calls中的函数名和参数执行对应 Skill。
  5. Agent 把 Skill 执行结果作为 tool 消息追加到对话。
  6. 大模型根据执行结果生成最终自然语言回复。

为了实现这个流程,我们需要四部分代码:Skill 管理器、Agent 调度核心、入口文件、以及两个 Skill 模块。

5.2 实现 Skill 管理器

Skill 管理器的职责有三个:自动加载skills目录下的所有 Skill 模块、生成可供大模型识别的tools列表、根据函数名执行对应 Skill。

# 文件路径:skill_manager.py import importlib import pkgutil import skills class SkillManager: def __init__(self): self._skills = {} def load(self): """自动注册 skills 包下的所有 Skill 模块""" for module_info in pkgutil.iter_modules(skills.__path__): if module_info.name.startswith("_"): continue module = importlib.import_module(f"skills.{module_info.name}") self.register(module) def register(self, module): """将单个 Skill 模块注册到管理器""" required = ("NAME", "DESCRIPTION", "PARAMETERS", "execute") if not all(hasattr(module, key) for key in required): print(f"跳过模块 {module.__name__}:缺少 Skill 必需属性") return self._skills[module.NAME] = { "description": module.DESCRIPTION, "parameters": module.PARAMETERS, "execute": module.execute, } def get_tools(self): """生成大模型需要的 tools 参数""" tools = [] for name, skill in self._skills.items(): tools.append({ "type": "function", "function": { "name": name, "description": skill["description"], "parameters": skill["parameters"], } }) return tools def execute(self, name: str, arguments: dict): """根据名称执行 Skill""" skill = self._skills.get(name) if not skill: return {"code": 1, "message": f"Skill {name} 不存在"} try: return skill["execute"](**arguments) except TypeError as e: return {"code": 1, "message": f"Skill {name} 参数错误: {e}"}

这里有几个设计点值得说明:

  • 使用pkgutil.iter_modules自动遍历skills包下的所有模块,这样以后新增 Skill 时,只需要添加文件,不需要修改管理器。
  • register中做属性校验,避免某个模块写得不完整却静默注册成功。
  • execute中捕获TypeError,避免参数不匹配导致整个 Agent 流程崩溃。

使用这种方式,新增一个 Skill 的成本非常低:新建文件、定义四个必需属性、写 execute 函数,剩下的交给管理器自动处理。

5.3 实现 Agent 调度核心

Agent 调度核心是整个系统的“大脑”,它负责组织对话消息、调用大模型、处理工具调用结果。

# 文件路径:agent.py import json import os import requests from skill_manager import SkillManager # 从环境变量读取配置,避免把密钥写死在代码中 BASE_URL = os.getenv("LLM_BASE_URL", "https://api.openai.com/v1") API_KEY = os.getenv("LLM_API_KEY", "your-api-key") MODEL = os.getenv("LLM_MODEL", "gpt-4o-mini") def call_llm(messages, tools): """调用 OpenAI 兼容接口""" payload = { "model": MODEL, "messages": messages, "tools": tools, "tool_choice": "auto", } resp = requests.post( f"{BASE_URL}/chat/completions", headers={ "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" }, json=payload, timeout=60, ) resp.raise_for_status() return resp.json() def run_agent(user_input: str, mock: bool = False): """运行 Agent 主流程""" skill_manager = SkillManager() skill_manager.load() tools = skill_manager.get_tools() messages = [ {"role": "system", "content": "你是智能助手,需要根据用户问题调用合适的Skill。"}, {"role": "user", "content": user_input}, ] # 为了在没有大模型 API 的环境下也能演示完整链路,提供 mock 模式 if mock: if "天气" in user_input or "气温" in user_input: tool_calls = [{ "id": "call_1", "type": "function", "function": { "name": "query_weather", "arguments": json.dumps({"city": "北京"}, ensure_ascii=False) } }] else: tool_calls = [{ "id": "call_1", "type": "function", "function": { "name": "get_user_info", "arguments": json.dumps({"user_id": "10001"}, ensure_ascii=False) } }] response = { "choices": [{ "message": { "role": "assistant", "content": None, "tool_calls": tool_calls } }] } else: response = call_llm(messages, tools) message = response["choices"][0]["message"] messages.append(message) if message.get("tool_calls"): for tc in message["tool_calls"]: fn = tc["function"] arguments = json.loads(fn.get("arguments") or "{}") # 执行 Skill result = skill_manager.execute(fn["name"], arguments) # 将执行结果返回给大模型 messages.append({ "role": "tool", "tool_call_id": tc["id"], "content": json.dumps(result, ensure_ascii=False), }) # 真实模式下,继续让大模型生成最终回答 if not mock: response = call_llm(messages, tools) else: response = { "choices": [{ "message": { "role": "assistant", "content": "我帮你查到了:北京今天晴天,气温 18 度。", "tool_calls": None } }] } return response["choices"][0]["message"]["content"]

这个文件是整个系统的核心。重点理解以下几点:

  • messages列表是完整对话上下文,模型每次生成都基于这个列表。
  • 当模型返回tool_calls时,程序必须把原始 assistant 消息追加进 messages,否则上下文不完整。
  • 每个tool_calls需要对应一个role: "tool"的结果消息,并通过tool_call_id关联到原始调用。
  • 工具结果回传后,需要再次调用模型,让模型基于结果生成最终回复。

mock参数是给没有 API 环境的读者准备的。它能模拟大模型返回工具调用的过程,让你在完全离线的情况下看到整个 Skill 调度链路。

5.4 实现入口文件

入口文件负责接收用户输入,调用 Agent 主流程并打印结果。

# 文件路径:main.py import sys from agent import run_agent if __name__ == "__main__": mock = "--mock" in sys.argv # 默认演示天气查询 question = "帮我查一下北京的天气" if len(sys.argv) > 1 and not mock: question = sys.argv[1] print("用户问题:", question) print("Agent回答:", run_agent(question, mock=mock))

如果直接运行,程序会以“帮我查一下北京的天气”作为输入。你也可以在命令行传入自定义问题。

5.5 运行与验证

在项目根目录执行以下命令:

python main.py --mock

预期输出:

用户问题: 帮我查一下北京的天气 Agent回答: 我帮你查到了:北京今天晴天,气温 18 度。

这个过程中,mock 模式模拟了大模型返回query_weather的调用,SkillManager 自动加载了天气 Skill 并执行,最终生成了自然语言回复。虽然回复内容是 mock 的,但整个 Skill 注册、加载、调度、执行链路是真实跑通的。

如果你有真实的大模型 API,可以设置环境变量后运行:

export LLM_BASE_URL="你的API服务地址" export LLM_API_KEY="你的API Key" export LLM_MODEL="你的模型名称" python main.py "帮我查一下上海的天气"

在真实模式下,模型会根据你的问题自动判断调用哪个 Skill,并基于 Skill 结果生成回答。这个验证过程能够帮你确认你的模型服务是否支持 Tool Calling。

6. 本地部署 AI 大模型时如何适配 Skill

6.1 本地部署的价值

很多企业在实际落地 Agent 时不希望把内部数据发送到外部 API,这时就需要本地部署大模型。本地部署的好处比较明显:数据不出内网、成本可控、可以按业务做定制。但本地模型的能力上限通常不如顶级商业 API 模型,尤其在工具调用稳定性上需要额外适配。

如果你准备在内网环境部署 AI 大模型并配合 Agent Skill 使用,下面这些经验可以帮你少走弯路。

6.2 通过 OpenAI 兼容接口接入本地模型

目前常见做法是使用本地推理服务将模型包装成 OpenAI 兼容接口,比如通过 Ollama 等工具启动模型后,会暴露一个类似http://localhost:11434/v1的地址。此时你只需要修改环境变量LLM_BASE_URL,不需要改代码。

export LLM_BASE_URL="http://localhost:11434/v1" export LLM_MODEL="qwen2.5:7b"

这里要注意一个问题:不同模型服务对 Tool Calling 的支持程度不一样,有的模型需要特殊参数开启工具调用,有的模型虽然声称支持,但实际输出并不稳定。因此,在选择模型和推理框架时,务必提前验证该模型是否支持tools参数。

6.3 本地模型的 Skill 适配思路

如果你使用的本地模型不支持 Tool Calling,也不要放弃 Skill 方案。还有一种通用适配思路:把 Skill 描述写到 system 提示词中,让模型以固定 JSON 格式输出它想调用的 Skill,然后由程序解析 JSON 并执行。

示例提示词模板:

你是一个智能助手,需要根据用户问题调用以下技能之一: 1. get_user_info:根据用户ID查询用户信息,参数:user_id 2. query_weather:查询城市天气,参数:city 请严格输出 JSON 格式,不要输出多余内容: {"name": "技能名称", "arguments": {"参数名": "参数值"}}

模型输出这个 JSON 后,程序只需要解析并交给 SkillManager 执行即可。这种方案牺牲了一部分灵活性,但对模型的要求更低,在本地部署场景下非常实用。

7. 常见问题与排查思路

7.1 模型始终不调用 Skill

如果模型对你的问题没有任何反应,就像普通聊天一样回复,而不是输出工具调用,通常有几个原因:

问题现象常见原因解决思路
模型正常回复但不调用工具模型本身不支持 Tool Calling换一个支持工具调用的模型,或改用 JSON 输出提示词方案
模型只在部分问题上调用Skill 描述写得太泛,模型判断不了优化 Skill 的 description,加入明确的触发场景
模型报参数格式错误tools 参数结构不符合接口要求对照接口文档检查 tools 的 JSON 结构

排查时可以先把tools参数打印出来,人工确认结构是否正确;再用最简单的问题测试,比如“你好,帮我查一下北京天气”,逐步缩小问题范围。

7.2 参数解析失败

有时模型返回的arguments无法用json.loads解析。这通常是因为模型生成了带多余字符的 JSON,比如被反引号包裹。建议在解析时加上一层清理,去掉首尾的反引号和多余空格。

另外,如果模型返回了未定义的参数名,也会导致 Skill 执行时报TypeError。我们在 SkillManager 的execute里已经捕获了这类错误,但更稳妥的做法是解析后做一次参数白名单校验,只保留 Skill 定义的参数。

arguments = json.loads(fn.get("arguments") or "{}") allowed = set(skill["parameters"].get("properties", {}).keys()) arguments = {k: v for k, v in arguments.items() if k in allowed}

这段逻辑可以放在skill_manager.execute中,防止异常参数传入业务代码。

7.3 Skill 越来越多时模型选错技能

当项目中有几十个 Skill 时,模型很容易选错。解决办法有三个层面:

  1. 精简同时传给模型的 Skill 数量:把 Skill 按业务域分组,先让模型判断属于哪个域,再加载该域的 Skill。
  2. 优化 description 的质量:不要写成功能说明书,要写成触发条件说明书。写清楚“当用户表达 XX 意图时使用”。
  3. 增加反馈闭环:记录模型每次调用 Skill 的成功率,定期清理低频或重复的 Skill。

这一条在实际项目中非常重要。很多 Agent 体感“笨”,不是因为模型差,而是因为 Skill 太多太杂,模型根本选不过来。

7.4 本地模型输出不稳定

本地模型在 Tool Calling 上的稳定性通常不如商业模型,表现是:有时输出 JSON 不完整、有时把函数名拼错、有时参数值为空。应对策略包括:

  • 使用更高的采样温度或直接设置为 0;
  • 在 system 提示词中给出一个包含参数示例的调用样例;
  • 增加重试机制,解析失败后让模型重新生成一次;
  • 对模型输出做校验,不符合 Schema 时丢弃并重试。

不要把本地模型当商业模型用,要在上层做足够的容错处理,才能得到一个相对稳定的 Agent 系统。

8. 工程最佳实践

8.1 Skill 命名与描述规范

命名要统一,推荐使用动词_对象的结构,例如query_weathercreate_orderupdate_user,避免使用过于抽象的单词。名称一旦发布,不要频繁修改,因为模型会基于名称记忆历史调用模式。

描述要写清楚“触发场景”而不是“实现原理”。下面两句话对比:

  • 不推荐:实现天气查询功能。
  • 推荐:当用户询问指定城市的天气、温度、是否会下雨时使用,城市名必填。

模型非常依赖 description 做意图判断,这部分值得反复打磨。

8.2 参数设计原则

参数设计要贴近用户表达。比如用户习惯说“北京”,就不要让模型必须传city_code: "110000",这样模型很容易出错。参数尽量使用字符串、数字等基础类型,给出明确的示例值。枚举字段要写清允许值。

同时,尽可能给参数设置默认值,让模型在不确定时也能调用成功。例如查询天气可以默认城市为“北京”,只要在PARAMETERS中不把city设置为 required,并在 execute 中处理缺省情况。

8.3 错误处理与日志观测

Agent 链路中任何一环失败都会导致体验断崖式下降。这里建议做到三点:

  1. 所有外部调用都要设置超时时间,避免模型接口卡死导致整个服务挂起。
  2. Skill 执行结果统一返回{"code": 0/1, "data": ..., "message": ...}这样的结构,方便上层判断。
  3. 记录完整的调用日志,包括用户输入、模型返回的 tool_calls、Skill 执行结果、最终回复。

在定位问题时,日志几乎是唯一的依赖。我在项目中习惯把 tool_calls 的原始 JSON 和完整对话消息都打出来,排查效率会高很多。

8.4 安全与权限边界

Skill 在执行真实业务操作时,必须有权限控制。尤其涉及数据库更新、删除、转账、发送消息等操作时,要做到:

  • 只允许 Agent 调用当前用户身份允许执行的 Skill;
  • 危险操作在执行前要求二次确认;
  • 数据库操作前备份,涉及删除时先逻辑删除;
  • 所有 Skill 执行动作都要记录审计日志。

把大模型想象成一个“可能被提示词攻击的执行者”,所有关键操作都要按权限边界限制,避免出现越权调用。

8.5 Skill 版本管理与灰度发布

Skill 也有版本问题。当业务逻辑变化时,不要直接覆盖原 Skill,建议在NAME中加入版本后缀,例如create_order_v2,并在 description 中标注新旧版本的适用范围。这样模型在过渡期可以两边都调用,等新版本稳定后再下线旧版本。

如果条件允许,可以为 Skill 加入一个简单的上下线开关:开关关闭时,Skill 不进入get_tools()列表。这样就能实现灰度发布和快速回滚。

9. 总结与下一步学习路线

读完这篇文章,你应该已经具备以下能力:

  • 能清晰解释 Agent、Skill、MCP 三者的定位与区别;
  • 能独立写出一个结构完整的 Skill 模块;
  • 能实现一个支持 Skill 自动加载、调度、执行的 Agent 核心;
  • 知道如何接入 OpenAI 兼容 API,也知道本地部署模型的适配思路;
  • 掌握 Agent Skill 开发中最常见的排错手段和工程最佳实践。

下一步的学习方向,我建议按下面的路线推进:

  1. 先把本文的示例代码在本机跑通,尝试新增一个自己的 Skill。
  2. 学习 Function Calling 的更多参数,如tool_choice的强制指定。
  3. 了解 MCP 协议,尝试用 Skill 内部调用一个 MCP 服务。
  4. 学习 RAG(检索增强生成),把知识库能力也封装成 Skill。
  5. 研究多 Agent 协作,让不同 Agent 各自负责不同 Skill,配合完成复杂任务。

Agent Skill 是 AI 应用开发里非常值得投入时间去掌握的工程能力。它不是某个框架的私有概念,而是基于大模型工具调用机制的一套通用实践。建议你直接跑一遍 demo,然后把你业务里最常用的一个重复操作封装成第一个 Skill。跑通之后,你基本就掌握了 Agent 应用开发最核心的一环。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询