从零开始构建智能体:详解工具调用、MCP与Skills实战
回顾过去一年的热门技术事件,DeepSeek的发布与Manus的问世无疑是两个最受瞩目的焦点。市场对这类产品展现出持续的热情,近期爆火的Agent形态产品OpenClaw(由Clawdbot演进为Moltbot)便是例证。
关于为什么需要Agent以及它究竟解决了哪些核心问题,我们在之前的文章中已有详细阐述。今天,我们将采取一种更为务实的视角,直接带领大家动手构建一个Agent,通过实践来深入理解其背后的技术难点与实现逻辑。
一、环境配置:从零搭建开发环境
在开发过程中,不同的项目往往需要不同版本的Python运行环境。为了高效管理多个Python版本,我们推荐使用pyenv这一工具。
macOS系统可以通过Homebrew进行安装:
brew update
brew install pyenv
或者,您也可以选择使用官方安装脚本:
curl https://pyenv.run | bash
Linux与Windows系统的安装命令如下:
curl -fsSL https://pyenv.run | bash
# Windows系统需使用pyenv-win:
# 1. 以管理员权限打开PowerShell。
# 2. 执行以下安装命令:
Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/pyenv-win/pyenv-win/master/pyenv-win/install-pyenv-win.ps1" -OutFile "./install-pyenv-win.ps1"; &"./install-pyenv-win.ps1"
安装完成后,可以通过运行以下命令来验证安装是否成功:
pyenv --version
若成功显示版本号,则表明安装正确。以下是一些常用的pyenv命令:
# 查看所有可安装的Python版本
pyenv install --list | grep 3.12
# 安装指定版本的Python,例如3.12.0
pyenv install 3.12.0
# 查看当前已安装的所有Python版本
pyenv versions
# 设置全局默认的Python版本
pyenv global 3.12.0
# 仅为当前目录设置特定的Python版本
pyenv local 3.12.0
Python包管理工具:uv
uv是一个轻量级的Python包管理工具,它集成了虚拟环境管理和依赖包管理功能,可以看作是pip与venv的组合体。使用uv能够方便地为不同项目创建独立的虚拟环境,并精确管理依赖版本。
确保已通过pyenv安装并切换至所需版本的Python后,即可安装uv。
macOS / Linux系统可通过官方脚本安装,或使用pip安装:
curl -fsSL https://uv.dev/install.sh | bash
# 或
pip install uv-cli
Windows系统的安装步骤如下:
- 打开PowerShell(建议使用管理员权限)。
- 执行命令:
pip install uv-cli
安装完成后,运行以下命令检查版本以确认安装成功:
uv --version
uv的常用命令包括:
# 初始化一个新项目(会自动创建虚拟环境)
uv init
# 查看当前项目所使用的Python版本
uv run python --version
# 安装依赖包,例如requests和flask
uv add requests flask
uv add openai
# 卸载指定的依赖包
uv remove requests
# 锁定当前环境的依赖版本(生成uv.lock文件)
uv lock
# 根据锁文件同步安装所有依赖
uv sync
开发工具:PyCharm
最后是集成开发环境的准备。建议从JetBrains官网下载并安装PyCharm。
后续开发中将涉及多个第三方服务的调用,因此需要提前准备相应的API密钥。
第一步,访问DeepSeek开发者平台,注册登录后创建一个API Key,用于大模型接口的调用。

第二步,访问高德开放平台官网,创建应用并选择“Web服务”类型。获取到的Key将用于查询酒店、景点、路径规划和天气等信息。个人开发者享有一定的免费调用额度,足以满足日常学习和测试需求。

第三步,将获取到的API密钥配置到项目的.env文件中,或直接设置为系统环境变量。
echo "DEEPSEEK_API_KEY=youkey" > .env
echo "AMAP_API_KEY=youkey" >> .env # 注意这里是两个大于号,表示追加

配置完成后,可以进行简单的连通性测试,确保环境与API均可正常访问。
# deepseek API测试示例
def deepseekDemo():
print("deepseekDemo,hello")
client = OpenAI(
api_key=os.environ.get('DEEPSEEK_API_KEY'),
base_url="https://api.deepseek.com")
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": "You are a helpful assistant"},
{"role": "user", "content": "Hello"},
],
stream=False
)
print(response.choices[0].message.content)
# 高德API测试示例
def amap_demo():
key = os.getenv("AMAP_API_KEY")
response = httpx.Client().get(
"https://restapi.amap.com/v3/weather/weatherInfo?key=" + key + "&city=110101&extensions=all")
print(response.json())
print(json.dumps(response.json(), indent=2,ensure_ascii=False))
基础环境搭建完毕后,我们将进入Agent能力的核心基石:Function Calling(工具调用)。
二、能力基石:理解并实现工具调用
大语言模型本身擅长文本的理解与生成,但无法直接访问外部数据或执行特定的业务逻辑,例如查询实时天气、搜索周边景点、计算导航路线等。
Tools(或称为函数调用)机制的作用,就是由应用程序提供一组预先定义好的、可被调用的函数。在大模型进行推理的过程中,由模型自身来判断:当前是否需要调用外部函数、具体应该调用哪一个函数、以及调用时应传入什么参数。模型只负责做出决策,而应用程序则负责真正地执行函数并返回结果。

工具调用的完整流程
工具调用并非一次性的请求-响应,而是一个多轮交互的循环过程:
1. 发起首次模型调用 应用程序首先向大模型发起请求,该请求中除了包含用户的问题,还附带了一份当前可调用工具的清单描述。
2. 接收模型的工具调用指令 模型根据对用户意图的理解进行判断。如果认为需要调用外部工具,则会返回一个结构化的JSON指令,明确指出需要调用的函数名称及其参数。如果判断无需调用工具,则会直接生成自然语言回复。
3. 在应用程序端执行工具 应用程序解析接收到的工具指令,找到对应的本地函数并执行,获取工具执行后的输出结果。
4. 发起第二次模型调用 将上一步获取的工具执行结果,作为新的上下文信息追加到对话历史中,再次向模型发起调用请求。
5. 接收模型的最终响应 模型综合工具返回的数据和用户原始问题,生成最终的、信息完整的自然语言回复,完成本次交互。
采用标准化工具调用的必要性
在工具调用能力标准化之前,Agent主要依赖复杂的提示词(Prompt)来驱动。开发者需要预先实现所有功能函数,然后通过冗长的提示词告知模型有哪些可用函数、在什么场景下使用、参数如何传递。模型通常以自然语言或半结构化的文本形式输出“行动计划”,应用程序还需要额外编写逻辑来解析这段文本,判断应调用哪个函数并手动执行,最后再将结果拼回上下文。
这种方式在工程实践中存在明显缺陷:
- 强耦合性:函数设计、调用规则、结果解析逻辑与提示词紧密绑定,任何业务逻辑的变动都可能需要同步修改代码和提示词,维护成本高。
- 稳定性差:模型输出的是非结构化的自然语言,格式上微小的变化就可能导致解析逻辑失效,线上服务风险较高。
- 提示词复杂难维护:为了精确约束模型行为,提示词往往会变得异常冗长和复杂,可读性和可维护性持续下降。
当然,除了优化提示词,也可以在模型微调阶段投入精力。然而,当主流大模型开始原生支持标准化的Tools(Function Calling)能力后,这无疑是当前最优的技术方案。
工具调用能力被标准化后,包含了明确的函数名称、功能描述和参数结构定义。模型不再输出“描述性”的行动计划,而是直接返回结构化的工具调用请求。应用程序或框架只需按照约定执行对应的函数并回传结果即可。
简而言之,提示词更适合用来描述任务目标和行为约束,而Tools则更适合承载具体、可执行的真实能力。当Agent需要具备实际行动能力时,使用标准化的Tools是必然选择。
典型应用案例:构建旅行规划Agent
旅行规划是展示Tools能力的经典场景,它要求Agent整合多种外部信息,非常适合作为示例。
一个实用的旅行规划Agent,至少需要具备景点搜索、天气查询、酒店筛选、行程建议和路线规划等能力。这些能力都无法仅凭模型的内置知识完成,必须通过调用外部接口来获取实时或动态的数据。
在本示例中,我们将旅行规划所需的核心能力拆解为多个独立的工具函数,例如:查询目的地景点信息、获取实时天气预报、根据预算筛选酒店、规划公共交通路线等。每个工具只负责一类明确的数据获取或计算任务,并返回结构化的数据,不参与高层的业务逻辑推理。
所有工具函数被统一定义在一个独立的tools模块中。与地图、地点、路线相关的能力通过封装高德开放平台的API来实现。工具本身专注于数据获取,而如何组合这些工具的结果、如何生成最终的旅行方案,则由大模型在多轮推理中自主决策完成。
准备好工具函数后,还需要一套配套的“桥梁”机制:将工具信息转换为模型能够识别的tools参数格式;在模型返回工具调用指令时,自动定位并执行对应的工具函数;将工具执行的结果封装并回传给模型。这部分逻辑构成了旅行Agent与Tools之间的交互桥梁,下文将通过具体代码进行阐述。
程序执行的一种可能时序如下图所示:


核心实现思路
为了构建这个旅行智能体,我们采用了模块化的设计,将“能力实现”与“智能决策”进行解耦。整个架构主要包含三个核心部分:
一、基于Python原生语法的工具定义
我们没有为模型专门编写一套独立的配置文件,而是直接利用Python语言本身的特性来定义工具。在TravelTools类中,每一个方法都遵循统一的规范:
- 类型提示:明确声明每个参数和返回值的类型,这既规范了代码,也为后续自动生成Schema提供了依据。
- 文档字符串:这是最关键的部分。我们使用自然语言详细描述“这个函数是做什么的”、“每个参数的具体含义”以及“返回的数据结构”。
- 异步设计:所有涉及网络I/O的操作均采用
async/await语法,确保在高并发场景下的性能。
class TravelTools:
"""
旅游规划助手工具类
"""
def __init__(self, amap_api_key: Optional[str] = None, request_delay: float = 0.2):
self.amap_api_key = amap_api_key or os.getenv("AMAP_API_KEY")
# ...
async def estimate_travel_cost(self, city: str, days: int, hotel_level: str = "舒适",
attractions: Optional[List[str]] = None) -> Dict[str, Any]:
"""
估算旅游费用(不含往返交通)
使用场景:
- 制定旅游预算
- 比较不同档次的旅游费用
- 规划旅游支出
Args:
city: 旅游城市名称,如'西安'、'北京'、'上海'
days: 旅游天数(含当天),如3表示2晚3天
hotel_level: 住宿档次,可选值:
- '经济': 150元/晚(快捷酒店)
- '舒适': 300元/晚(三星级酒店,默认)
- '豪华': 500元/晚(四星级及以上)
attractions: 计划游览的景点列表(可选),用于估算门票费用
如 ['兵马俑', '华清宫', '大雁塔']
Returns:
费用估算字典,包含:
- city: 城市名称
- days: 旅游天数
- breakdown: 费用明细(住宿、餐饮、交通、门票)
- total: 总费用
- tips: 温馨提示
"""
# ... 具体实现逻辑 ...
pass
这种方式让开发者可以专注于编写标准、清晰的Python函数,而无需手动编写繁琐的JSON Schema。
二、自动化的工具注册与Schema生成
为了搭建Python代码与大模型之间的桥梁,我们实现了一个ToolRegistry工具类。它的核心职责是“翻译”和“管理”:
- 自省与生成:利用Python的反射机制,自动读取工具函数的签名信息和文档字符串,将其动态转换为大模型能够理解的OpenAI Function Calling格式。
- 统一执行入口:提供了一个
execute_tool方法。当模型发出调用指令时,Registry负责解析参数、定位对应的Python函数并执行,最后将结果序列化为JSON格式返回。
@dataclass
class Tool:
"""工具定义数据模型"""
name: str # 工具名称/函数名
description: str # 工具描述(用于大模型理解)
parameters: Dict[str, Any] # 参数JSON Schema
function: Callable # 实际的执行函数
def to_dict(self) -> dict:
"""转换为OpenAI Function Calling格式"""
return {
"type": "function",
"function": {
"name": self.name,
"description": self.description,
"parameters": self.parameters
}
}
class ToolRegistry:
# ...
def register_from_class(self, cls, ...):
"""从类中自动注册异步方法为工具"""
# 利用 Python 的反射机制(Inspect模块)
# 自动读取工具函数的签名和文档
# ...
pass
async def execute_tool(self, tool_call: ToolCall) -> ToolResult:
"""执行单个工具调用"""
# ...
pass
三、实现ReAct决策循环 Agent的核心运行逻辑是一个经典的While循环,模拟了“观察-思考-行动”的过程:
- 构造Prompt:将系统指令、用户问题以及所有可用的工具描述(由Registry生成)一同发送给模型。
- 模型决策:
- 模型判断是否需要调用工具。如果需要,它会返回一个
tool_calls结构,包含具体的函数名和参数。 - 如果不需要,它会直接生成最终回复。
- 模型判断是否需要调用工具。如果需要,它会返回一个
- 工具执行:应用程序捕获
tool_calls,通过ToolRegistry执行具体的函数(如查询天气、搜索路线),获取真实数据。 - 结果回填:将工具执行的结果封装为
tool角色的消息,追加到对话历史中。 - 递归思考:带着最新的工具结果,再次请求模型。模型会根据新的上下文信息,决定是继续调用下一个工具,还是基于现有信息生成最终答案。
# ... 初始化 messages ...
while count < 15:
count = count + 1
# 发送请求给大模型
result = await llm_client_with_tools(messages, tool_registry.get_tools())
assistant_message = result.choices[0].message
# 检查是否有工具调用
if assistant_message.tool_calls:
# 1. 添加 assistant 消息到历史
messages.append({
"role": "assistant",
"content": assistant_message.content,
"tool_calls": [...]
})
# 2. 执行所有工具调用
for tool_call in assistant_message.tool_calls:
# 执行工具
tool_result = await tool_registry.execute_tool(...)
# 3. 添加工具结果到历史
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": tool_result.content
})
# 继续下一轮循环,将工具结果带给模型
continue
else:
# 没有工具调用,输出最终回复
print(assistant_message.content)
break
通过以上三层架构,我们将一个复杂Agent的构建流程清晰地拆解为:编写标准函数 -> 自动注册工具 -> 循环交互决策,极大地降低了开发和维护的复杂性。
最终实现的旅行规划Agent交互效果如下图所示:

在掌握了Tools这一基础能力后,我们进一步探讨上半年的另一个热门概念:MCP。
三、协议升级:MCP实现工具与模型的解耦
MCP(Model Context Protocol)是一种用于规范大模型与外部能力交互方式的开放式协议。
它关注的焦点不是某一个具体的工具函数,而是如何以统一、标准化的方式,将外部系统的能力、数据和上下文暴露给模型使用。
如果说Tools解决的是“模型如何调用一个函数”的问题,那么MCP解决的则是“模型如何与一个长期存在、可复用的能力服务进行交互”的问题。
在MCP体系架构中,大模型并不直接面对零散的函数定义,而是通过协议连接到一个MCP Server。这个Server可以对外提供多种能力,例如工具调用、资源读取、上下文查询等。模型则通过一个MCP Client来与Server进行通信。
MCP出现的背景与价值
随着Agent应用变得越来越复杂,仅依靠在单个应用内部定义Tools的模式逐渐暴露出一些问题:
- 复用困难:工具能力通常与特定项目绑定,当需要在不同项目或不同Agent之间复用时,往往需要重复实现。
- 生命周期不匹配:Tools通常随单次调用而创建和销毁,而许多底层能力(如数据库连接、搜索引擎服务)本身是长期运行的。
- 治理与边界模糊:随着可调用能力的数量增长,权限控制、访问审计和资源隔离等问题变得难以统一管理。
事实上,在MCP协议提出之前,社区中已经存在各种工程实践来试图解决这些问题,其中一些优秀的实现已经非常接近MCP的理念。而自MCP被正式提出后,它便为这些工程挑战提供了一个标准化的解决方案。
MCP协议提供了一套标准化的交互方式,将能力从具体的应用程序中抽离出来,形成独立、可复用、便于治理的服务层。通过采用MCP:
- 能力可以被集中管理和统一维护。
- 多个不同的模型或应用可以共享同一套底层能力服务。
- 明确了能力的边界,减少了重复实现和系统间的耦合。
至于MCP与Tools之间的关系,可以理解为:Tools是Agent的能力基石,而MCP则是在Tools数量庞大之后,对其进行工程化管理的实践结果。
- Tools:是模型调用具体能力的表达方式,通常是函数级别的实现。
- MCP:是模型访问、管理和调用这些能力的协议层和服务层。
在实践中,Tools通常作为MCP Server内部能力的实现而存在,但它们不再直接依赖某个特定的应用程序。模型通过MCP Client调用能力时,只需关注“如何使用”,而不必关心其实现细节和部署位置,从而实现更稳定、可复用且可扩展的Agent能力架构。
MCP的典型适用场景包括:
- 多个Agent需要共享的通用能力(如搜索引擎、数据库查询、统一业务系统接口)。
- 生命周期较长、需要持续维护和状态管理的服务。
- 需要统一进行权限管控、操作日志记录和安全审计的能力模块。
- 希望跨编程语言、跨项目复用的工具集合。
接下来,让我们通过代码来实际体验MCP的运作。
MCP Server 与 MCP Client 实践
MCP体系包含两个核心角色:
- MCP Server:对外提供标准化能力和上下文访问的服务端。
- MCP Client:运行在应用程序或Agent内部,负责与Server通信的客户端。
Server负责能力的定义、实际执行和统一管理,Client则负责将模型的请求转换为符合MCP协议的请求发送给Server,并将Server返回的结果交给模型处理。

MCP 案例代码
我们继续以旅行规划为例。首先,MCP Server端的代码负责将之前TravelTools中的能力通过MCP协议暴露出去:
from typing import Dict, List, Optional, Any, Annotated
from pydantic import Field
from fastmcp import FastMCP
# 导入之前定义的TravelTools类
from code.Function_Calling.tools import TravelTools
mcp = FastMCP(name="旅游规划助手")
# 初始化TravelTools实例
travel_tools = TravelTools()
@mcp.tool("get_current_weather", description="获取当前天气信息")
async def get_current_weather(
city: Annotated[str, Field(description="城市名称,如'西安'")],
province: Annotated[str, Field(description="省份名称,如'陕西'")]
) -> Dict[str, Any]:
try:
weather = await travel_tools.get_weather(city, province)
return weather.to_dict()
except Exception as e:
return {"error": str(e)}
# ... 此处省略其他工具(如geocode, search_poi, route_planning等)的装饰器定义,其结构类似
if __name__ == '__main__':
mcp.run(transport="http", port=8001)
其次,MCP Client端的代码负责连接Server,并将Server提供的工具列表传递给大模型:
import asyncio
import json
import os
from openai import OpenAI
from fastmcp import Client
from code.Working_with_LLMs.llm_client import llm_client_with_tools
# 系统提示词
SYSTEM_PROMPT = """你是一个专业的旅游规划助手。当用户提出旅游规划需求时,请:
1. 理解需求:确认目的地、天数、预算、出行人数、特殊偏好
2. 使用工具搜索:
- 使用 search_poi 搜索热门景点
- 使用 get_attraction_info 获取景点详情
- 使用 estimate_travel_cost 估算费用
- 使用 route_planning 规划路线
3. 输出格式:
按天输出详细行程,每天包含:
- 上午/下午/晚上的景点安排
- 餐饮推荐
- 景点间交通方式和时间
- 当日预估花费
4. 费用把控:根据用户预算合理分配费用
5. 贴心提醒:提供穿着建议、必带物品、注意事项
请确保使用工具获取最新信息,并给出具体的行程安排。"""
async def chat_with_mcp(user_input: str):
client = OpenAI(api_key=os.getenv("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com")
# 连接到MCP Server
mcpClient = Client("http://127.0.0.1:8001/mcp")
await mcpClient.__aenter__()
# 从Server获取可用工具列表
mcp_tools = await mcpClient.list_tools()
print("可用工具:", mcp_tools)
llm_tools = []
for tool in mcp_tools:
llm_tools.append({
"type": "function",
"function": {
"name": tool.name,
"description": tool.description,
"parameters": tool.inputSchema,
},
})
# 后续的ReAct循环与之前类似,但工具执行改为调用 mcpClient.call_tool
# ...
# 例如执行工具:
# tool_result = await mcpClient.call_tool(tool_name, arguments)
if __name__ == '__main__':
asyncio.run(chat_with_mcp("西安2日游"))
四、高阶封装:Skills如何优化任务流程
尽管MCP解决了多Agent对Tools的调用解耦问题,但Agent开发中更深层次的挑战依然存在,核心可归纳为两点:
- 工具数量膨胀导致调用不准:当可用工具非常多时,模型可能难以精准选择最合适的工具。
- 复杂任务流程的不稳定性:对于需要多步决策和执行的任务,仅靠基础的ReAct循环,模型的决策路径可能不够稳定或高效。
针对这些问题,业界同样积累了许多工程优化手段,例如:
- 动态按需加载工具,先对用户问题进行意图识别,再加载相关的工具子集。
- 在提示词中嵌入类似工作流(Workflow)的步骤说明,强引导模型的推理过程。
与MCP类似,这些工程实践很快也被官方以更标准化的方式所吸纳和定义。Anthropic在其官方文档中提出了Skills(技能)的解决方案。一个完整的Skill包含三个层次,从抽象到具体依次是:
- 元数据:Skill的名称、描述、标签等基础信息,用于路由和识别。
- 指令:Skill具体的工作流程和Prompt指令。
- 资源:Skill运行所附带的相关资源文件,例如可执行的脚本、配置文件等。

虽然Skills概念最初由Anthropic的Claude提出,但其体现的设计思想已成为构建复杂Agent的一种事实标准。
Skills设计思想深度解析
Claude Skills的设计遵循了一个至关重要的原则:渐进式披露。
它并非在任务开始时就将所有技能的全部信息一股脑儿塞入模型的上下文窗口,而是分阶段、按需加载。整个过程分为三个层次,与上述三要素一一对应:
第一层:元数据(始终加载)
Claude在启动时会扫描所有已安装Skills的元数据文件(如meta.json),并将其纳入系统提示中。这层信息的作用是:
- 让Claude知道“自己拥有哪些潜在的技能”。
- 用于后续用户请求时的意图匹配和技能触发判断。
- 由于只包含名称和简短描述,占用上下文空间极小。
第二层:核心指令(触发时加载)
当用户的请求与某个Skill的描述相匹配时,Claude会从文件系统中读取对应的skill.md文件,并将其完整内容加载进当前的对话上下文。这解决了:
- 固化SOP:将原本需要反复在提示词中解释的复杂任务流程,固化为一个稳定、可复用的指令模块。
- 提供清晰逻辑:为模型执行该技能提供明确、步骤化的指导。
第三层:代码与资源(按需加载) 一个复杂的Skill可能包含多个支持文件,例如Python脚本、数据模板等,构成一个完整的知识包。Skill可以将这些资源与指令打包在一起,当指令中提及需要调用某个脚本时,模型可以找到并执行它,实现从理解到执行的完整闭环。
通过 元数据 → 指令 → 代码与资源 这三层递进结构,一个Skill不仅能被模型正确识别和触发,还能真正地完成从“理解用户需求”到“执行具体任务”的完整闭环。其典型的目录结构如下:
└── skill-name/ # 技能根目录
├── meta.json # [路由层] 技能元数据,告诉模型这个技能是干嘛的
├── skill.md # [逻辑层] 核心指令,包含System Prompt和标准作业程序
└── scripts/ # [执行层] 实际干活的Python/Bash脚本等资源

接下来,我们看一个具体的Skill创建与使用案例。
Skills的安装与使用实践
在Claude Desktop或类似环境中,你可以通过以下方式使用Skills:
方法一:使用官方技能市场(推荐)
# 添加官方技能库源
/plugin marketplace add anthropics/skills
# 浏览所有可用技能
/plugin list
# 安装特定技能,如文档处理技能
/plugin install document-skills@anthropic-agent-skills
安装完成后,你可以直接询问Claude它现在拥有哪些技能。

方法二:手动创建自定义技能 如果你想创建一个自定义技能,例如“自动总结抖音视频”,过程非常简单:
- 在用户目录下的
~/.claude/skills/中创建一个新文件夹,例如douyin-summary。 - 在该文件夹内创建
skill.md文件,编写详细的技能描述和工作流程。 - (可选)在文件夹内创建
scripts/子目录,并放入实际执行任务的Python脚本。
目录结构示例如下:

具体步骤:
- 创建技能目录:
mkdir -p ~/.claude/skills/douyin-summary - 编写
skill.md:--- name: douyin-summary description: 抖音视频总结助手。当用户提供抖音视频链接时,自动调用此技能获取文案并总结。 --- # 抖音视频总结助手 ## 工作流程 1. 识别用户输入中的douyin.com链接 2. 调用scripts/fetch_douyin.py获取视频文案 3. 提取核心观点并结构化输出 - 实际使用: 配置完成后,通常需要重启Claude Desktop(新版本可能支持热加载)以使新技能生效。随后可以查看技能是否安装成功。

使用该Skill提取抖音视频内容的效果如下图所示:

Skills的核心理念与优势
Skills本质上是一种模块化、可复用的能力单元。 它不仅仅包含“能做什么”(对应Function Call),还包含了“怎么做”(详细的Prompt/指令)以及“用什么做”(执行脚本或资源),其核心设计思想在于:
- 按需加载:只有任务匹配时,才加载相关技能的完整上下文,极大地节省了宝贵的Token资源。
- 结构化封装:将提示词、代码、配置像乐高积木一样封装在一起,形成一个独立的可交付物。
- 关注任务流程:引导模型关注如何完成一项高层次任务,而不仅仅是进行底层的API调用。
在Claude的实现中,一个Skill表现为文件系统中的一个标准目录,包含了描述文件、指令文件和可选的执行脚本。

Skills与Function Calling的关系辨析
两者概念容易混淆,我们可以通过下表来厘清它们的本质区别:
| 特性 | Function Calling (Tools) | Skills |
|---|---|---|
| 定位 | 原子能力 | 任务模块 |
| 构成 | 仅包含函数定义(JSON Schema) | 包含元数据、Prompt指令、代码和资源 |
| 关注点 | “我可以调用这个API” | “我知道如何完成这项具体工作” |
| 上下文 | 通常无状态,一次性调用 | 有状态,通过详细的Prompt引导多步推理和执行 |
| 复用性 | 代码级别的复用 | 业务逻辑级别的复用 |
一句话总结:Tools 是能力的底层接口(API);Skills 是任务的高级模板(SOP + Tools)。
一个Skill内部通常会调用一个或多个Tools来完成具体工作,但它更强调任务的流程编排、知识注入和用户体验。
Skills的设计优势
Skills最初由Anthropic形式化提出,但其代表的 “能力即文件” 的设计思想,已经超越了特定平台的限制,成为构建复杂、可维护Agent的一种通用最佳实践。这也是各大模型平台纷纷跟进或提出类似概念的原因。其核心优势在于:
一、动态上下文管理 传统Agent开发常将所有能力的Prompt一次性写入System Prompt,导致:
- 上下文窗口浪费:大量未使用的指令占用了本可用于思考的Token。
- 模型注意力分散:模型在庞杂的指令中容易迷失,遵循指令的能力可能下降。
Skills模式通过按需加载机制完美解决了这个问题:只有当模型判定需要处理特定任务时,系统才会读取对应的技能文件并注入其详细指令。这种渐进式披露机制让Agent能以有限的上下文承载近乎无限的能力库。
当然,当Skills的数量本身也增长到一定规模时,又可能产生“技能路由”的新问题,届时可能需要基于Skills的元数据进行二次聚类和调度,这可以看作是“Skills的技术”。
二、标准化的能力封装 Skill三位一体的结构(元数据 + 指令 + 代码),使得Agent能力可以像软件库一样被:
- 版本控制:使用Git进行管理。
- 独立测试:每个Skill可以单独运行和调试。
- 社区共享:直接复制文件夹即可使用,促进了生态发展。
三、模型无关性 Skills本质上只是一种文件和目录结构的约定。只要我们编写代码去解析这些文件,并根据元数据描述与大模型进行交互,就可以让任何具备Tool Calling能力的模型(如DeepSeek、GPT等)拥有“加载和使用Skills”的能力。这意味着其设计思想具有普适性。
实践:在DeepSeek上实现Skills引擎
既然Skills的理念是模型无关的,我们完全可以自己实现一个简单的Skills引擎,让DeepSeek等模型也能享用这种便利。核心思路如下:
1. 加载技能元数据
首先,扫描本地技能目录,读取meta.json,将其转换为模型能理解的Tool Definition格式。
def load_skills_from_meta():
"""扫描目录,从 meta.json 加载技能元数据"""
# 示例:假设我们读取到了 simple_weather_skill
with open("./simple_weather_skill/meta.json", "r", encoding="utf-8") as f:
meta = json.load(f)
# 构造兼容 OpenAI/DeepSeek 格式的工具定义
return [{
"type": "function",
"function": {
"name": meta["name"],
"description": meta["description"],
"parameters": {
"type": "object",
"properties": {},
"required": []
},
},
}]
2. 基于元数据的意图识别 当用户提问时,我们把所有技能的元数据(作为Tool定义)发给DeepSeek,让它进行意图识别,判断用户想使用哪个Skill。
# 1. 准备技能元数据工具列表
skills = load_skills_from_meta()
# 2. 发送请求,让DeepSeek选择技能
messages = [{"role": "user", "content": user_input}]
result = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=skills # 关键:把Skill元数据当作Tools传进去,用于路由
)
3. 技能上下文注入
如果模型决定调用某个Skill(例如weather_skill),我们并不直接执行它,而是读取该Skill的skill.md文件,将其内容作为新的System Prompt注入上下文。
if result.choices[0].message.tool_calls:
skill_name = result.choices[0].message.tool_calls[0].function.name
print(f"模型命中技能: {skill_name}")
# 读取对应的技能指令文件
with open(f"./{skill_name}/skill.md", "r") as f:
skill_prompt = f.read()
# 【核心步骤】:构建新的对话上下文,注入技能知识
skill_messages = [
{"role": "system", "content": skill_prompt}, # 注入详细技能指令
{"role": "user", "content": user_input} # 重放用户问题
]
# 此时,DeepSeek已经“变身”为该技能的专家
4. 执行技能脚本
在注入了技能指令的新会话中,模型会根据Prompt的指引,去调用scripts/目录下的具体Python脚本。我们通常需要一个通用的脚本执行工具。
def execute_script(script_path, args=None):
"""一个通用的脚本执行器工具"""
cmd = ["python", script_path] + (args or [])
res = subprocess.run(cmd, capture_output=True, text=True)
return res.stdout
通过以上机制,我们就在DeepSeek上成功模拟了Claude Skills的核心流程:路由识别 -> 加载详细指令 -> 执行具体脚本,实现了能力的模块化和按需加载。
结语
至此,我们已经对Agent的核心能力实现进行了一次深入的梳理,从最基础的Tools调用,到工程化的MCP协议,再到更高级的Skills封装。
希望这篇内容详实的指南能帮助你更系统地理解Agent的构建之道。在后续的内容中,我们将继续深入探讨记忆系统、更完善的ReAct框架设计,并通过一个完整的综合案例将这些技术点串联起来,敬请期待。