1. 先搞清楚“Agent Skill”到底在解决什么问题

如果你最近在关注AI应用开发,尤其是想做一个能自己调用工具、处理复杂任务的智能体(Agent),那“Skill”这个概念你一定绕不开。很多人一上来就去看各种框架和代码,结果被一堆术语搞晕:Agent、Skill、Tool、Action、RAG……它们之间到底是什么关系?为什么我的Agent只会聊天,一让它干点实际的,比如查个天气、发封邮件就报错?

“Agent Skill”要解决的,就是这个“让智能体具备实际做事能力”的核心问题。 你可以把它理解为智能体的“技能包”。一个只会对话的Agent是“大脑”,但它没有“手”和“脚”。Skill就是为这个大脑安装的“手”和“脚”,让它能执行具体的操作,比如调用一个API、操作一个数据库、运行一段脚本。

现在很多教程一上来就讲大模型微调、讲RAG(检索增强生成),这当然重要,但那是解决“知识”和“记忆”的问题。而Skill解决的是“行动”的问题。一个真正有用的AI助手,必然是既懂知识(通过RAG),又能行动(通过Skill)的。

所以,这个主题最适合两类人:

  1. 已经会用大模型API做对话,但想让AI自动执行任务的人 :比如自动整理会议纪要并发送邮件、监控数据异常并触发告警。
  2. 在评估或使用LangChain、AutoGen、CrewAI等Agent框架的开发者 :想弄明白如何在这些框架里自定义和扩展功能。

最关键的 价值 在于,掌握了Skill的设计和集成方法,你就能把任何现有的API、函数或脚本,变成AI可以理解和调用的能力,从而构建出真正能落地的自动化流程,而不是停留在演示阶段。

2. 动手之前:理清概念、选好环境

在写第一行代码之前,必须把几个关键概念和它们之间的关系掰扯清楚,不然看任何框架的文档都会一头雾水。

2.1 Agent、Skill、Tool、Action 到底是什么关系?

这是最让人混乱的地方。不同框架的叫法可能不同,但核心逻辑是相通的。我们可以这样理解:

  • Agent(智能体) :这是总指挥。它拥有一个“大脑”(通常是LLM大语言模型),负责理解用户意图、制定计划、决定下一步该调用哪个技能,并解析技能返回的结果。你可以把它看作一个项目经理。
  • Skill / Tool(技能/工具) :这是可被调用的能力单元。一个Skill通常对应一个具体的、可执行的操作。 在大多数上下文中,Skill和Tool是等同的概念 。比如“发送邮件”是一个Skill,“查询数据库”也是一个Skill。
  • Action(动作) :这是Skill/Tool内部的具体执行步骤。有时一个复杂的Skill可能包含多个Action。但在简单场景下,一个Skill可能只包含一个Action。你可以认为Action是Skill的实现细节。

它们的关系链是: 用户请求 -> Agent(大脑分析)-> 决定调用某个 Skill -> Skill 执行内部 Action -> 返回结果给 Agent -> Agent 组织语言回复用户。

举个例子:用户说“帮我查一下北京明天的天气,然后发邮件提醒我带伞”。

  1. Agent理解后,制定计划:先调用“天气查询Skill”,再调用“邮件发送Skill”。
  2. Agent调用“天气查询Skill”,该Skill执行“调用天气API”这个Action,返回“北京明天小雨,15-20°C”。
  3. Agent将结果组织成文本,调用“邮件发送Skill”,该Skill执行“连接SMTP服务器并发送”这个Action。
  4. Agent最终告诉用户:“已查询天气并发送提醒邮件。”

2.2 RAG 和 Skill 有什么区别?

这也是高频问题。简单说:

  • RAG(检索增强生成) :解决“ 知道什么 ”的问题。它让Agent能从外部知识库(如文档、数据库)中查找信息,并用这些信息来生成更准确的回答。 重点是信息获取和增强
  • Skill :解决“ 能做什么 ”的问题。它让Agent能执行操作,改变外部状态(如发送邮件、创建工单、控制设备)。 重点是行动和执行

一个强大的Agent往往会同时使用两者:用RAG来获取必要的知识,用Skill来执行具体的任务。

2.3 环境与框架选择

对于入门和实战,我不建议一开始就追求最火、最复杂的框架。从简单、易理解、文档清晰的开始,更容易建立直觉。

1. 基础环境准备:

  • Python :3.8或以上版本。这是绝大多数AI框架的语言。
  • 包管理 :使用 pip 即可。强烈建议使用虚拟环境( venv conda ),避免包冲突。
    # 创建虚拟环境
    python -m venv agent_skill_env
    # 激活(Windows)
    agent_skill_env\Scripts\activate
    # 激活(Mac/Linux)
    source agent_skill_env/bin/activate
    
  • LLM API Key :你需要一个大型语言模型的API访问权限,这是Agent的“大脑”。国内场景可以考虑百度文心、阿里通义千问、智谱GLM等;国际场景OpenAI的GPT系列、Anthropic的Claude是常见选择。准备好你的API Key。

2. 框架选择建议: 对于学习Skill开发,我推荐按以下路径:

  • 第一阶段(理解概念) :使用 LangChain 。它是目前生态最丰富、文档最全的框架之一,对Tool/Skill的定义非常标准。虽然它组件繁多显得复杂,但正是通过它,你能最清晰地看到Tool是如何被定义、描述和调用的。
  • 第二阶段(实战开发) :可以继续用LangChain深化,或者尝试 AutoGen 。AutoGen由微软推出,特别擅长多Agent协作,其 UserProxyAgent AssistantAgent 的对话模式,能让你非常直观地看到Agent如何自动调用你提供的Tool。
  • 第三阶段(生产考虑) :研究 CrewAI Semantic Kernel LangGraph 。它们提供了更结构化的任务编排、工作流和状态管理,适合更复杂的业务场景。

本篇教程,我们将以 LangChain 为核心进行讲解,因为它是最佳的教学样板。理解了它的模式,迁移到其他框架会非常容易。

3. 核心实战:从零定义一个可用的Skill

我们跳过理论,直接动手。目标是创建一个能让Agent使用的“天气查询Skill”。

3.1 第一步:安装与最小化启动

首先,安装必要的库。我们使用OpenAI的模型作为Agent大脑(你需要有自己的API Key)。

pip install langchain langchain-openai requests

requests 库用于我们的Skill中调用外部天气API。

接下来,我们先不写Skill,而是测试一下最基本的LangChain调用LLM的能力,确保大脑能工作。

import os
from langchain_openai import ChatOpenAI

# 设置你的API Key,这里以OpenAI为例,其他模型类似
os.environ["OPENAI_API_KEY"] = "你的-api-key-here"

# 初始化LLM
llm = ChatOpenAI(model="gpt-3.5-turbo")

# 测试简单对话
response = llm.invoke("你好,请简单介绍一下你自己。")
print(response.content)

如果这一步能正常输出模型自我介绍,说明你的LLM环境配置成功了。这是所有Agent工作的基础。

3.2 第二步:创建你的第一个Tool(Skill)

在LangChain中,Skill就是 Tool 。创建一个Tool最基本的方式是定义一个函数,然后用 @tool 装饰器来装饰它。

我们来创建一个查询指定城市天气的Tool。

import requests
from langchain.tools import tool

# 使用一个免费的天气API示例(例如 openweathermap,需要注册获取免费API Key)
# 这里仅作演示,请替换为你自己的API Key或使用其他稳定API
WEATHER_API_KEY = "你的-天气api-key"
BASE_URL = "http://api.openweathermap.org/data/2.5/weather"

@tool
def get_current_weather(city: str) -> str:
    """获取指定城市的当前天气情况。输入应为城市名,例如:北京。"""
    try:
        # 构造请求参数
        params = {
            "q": city,
            "appid": WEATHER_API_KEY,
            "units": "metric",  # 使用摄氏度
            "lang": "zh_cn"
        }
        response = requests.get(BASE_URL, params=params, timeout=10)
        response.raise_for_status()  # 检查请求是否成功
        data = response.json()

        # 解析返回的JSON数据
        city_name = data['name']
        temp = data['main']['temp']
        weather_desc = data['weather'][0]['description']
        humidity = data['main']['humidity']

        result = f"{city_name}的当前天气:{weather_desc},温度 {temp}°C,湿度 {humidity}%。"
        return result
    except requests.exceptions.RequestException as e:
        return f"请求天气API时出错:{e}"
    except KeyError:
        return "无法解析天气API返回的数据,请检查API Key或城市名。"
    except Exception as e:
        return f"获取天气时发生未知错误:{e}"

# 测试一下这个Tool本身是否工作
print(get_current_weather.invoke("北京"))

关键点解释:

  1. @tool 装饰器 :这是LangChain将普通函数标记为Tool的关键。它会让LangChain自动为这个函数生成描述,以便LLM理解何时调用它。
  2. 函数文档字符串(Docstring) """获取指定城市的当前天气情况...""" 这部分 极其重要 !LLM(Agent的大脑)就是靠阅读这个描述来决定是否以及如何调用这个Tool的。描述必须清晰、准确。
  3. 参数类型提示 city: str -> str 帮助框架理解输入输出格式。
  4. 健壮性处理 :Tool内部必须有完善的错误处理( try...except ),因为外部API可能失败。一个总是崩溃的Skill会让整个Agent瘫痪。

运行上面的代码,你应该能看到打印出的北京天气信息(如果API Key有效)。这说明你的Skill本身逻辑是通的。

3.3 第三步:将Tool交给Agent,让它自主调用

现在,我们有了“大脑”(LLM)和“手”(Weather Tool)。接下来就是用LangChain把两者连接起来,创建一个能自动决定何时使用这只“手”的Agent。

LangChain提供了多种创建Agent的方式, create_react_agent 是一个经典且易于理解的选择,它基于ReAct(Reasoning + Acting)范式。

from langchain.agents import create_react_agent, AgentExecutor
from langchain import hub  # 用于拉取预设的提示词

# 1. 准备Tools列表
tools = [get_current_weather] # 把我们刚定义的Tool放进去

# 2. 拉取一个专门为ReAct Agent设计好的提示词模板
# 这个模板会告诉LLM如何思考、何时使用工具
prompt = hub.pull("hwchase17/react")

# 3. 创建ReAct Agent
agent = create_react_agent(llm, tools, prompt)

# 4. 创建Agent执行器,它负责运行Agent的循环:思考->行动->观察->再思考...
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)

# 5. 运行测试!
result = agent_executor.invoke({
    "input": "请问上海和北京的天气怎么样?"
})
print("\n--- Agent最终回答 ---")
print(result["output"])

运行过程解读(注意看 verbose=True 输出的日志): 当你运行这段代码时,控制台会打印出详细的思考过程,类似这样:

> Entering new AgentExecutor chain...
我需要比较上海和北京的天气。我应该分别查询这两个城市的天气。
行动:get_current_weather
行动输入:上海
观察:上海的当前天气:多云,温度 22°C,湿度 65%。
我需要再查询北京的天气。
行动:get_current_weather
行动输入:北京
观察:北京的当前天气:晴,温度 18°C,湿度 40%。
现在我有两个城市的信息,可以给出比较后的回答。
思考:上海多云22度,北京晴18度。北京更凉爽干燥。
最终答案:上海目前多云,气温22°C,湿度65%。北京天气晴朗,气温18°C,湿度40%。相比之下,北京更凉爽干燥一些。
> Finished chain.

这就是Skill(Tool)工作的核心魔法时刻! Agent(LLM)自动识别出问题需要查询天气,自动选择了正确的Tool( get_current_weather ),自动生成了正确的输入参数(“上海”、“北京”),并最终将Tool返回的结果组织成了通顺的回答。

3.4 第四步:技能进阶——让Skill更强大可靠

一个能跑通的Skill只是开始。要用于实际项目,必须考虑更多。

1. 结构化输出: 上面的Tool返回的是字符串。但有时后续步骤需要结构化的数据。我们可以让Tool返回字典(Dict)。

from typing import Dict, Any
from pydantic import BaseModel, Field
from langchain.tools import StructuredTool

# 定义输出数据的结构
class WeatherOutput(BaseModel):
    city: str = Field(description="城市名称")
    temperature: float = Field(description="温度,单位摄氏度")
    description: str = Field(description="天气描述")
    humidity: int = Field(description="湿度百分比")

def get_weather_structured(city: str) -> Dict[str, Any]:
    """获取指定城市的结构化天气信息。"""
    # ... 内部调用API的逻辑与之前类似 ...
    # 假设从API获取到数据后
    return {
        "city": "北京",
        "temperature": 18.5,
        "description": "晴朗",
        "humidity": 40
    }

# 使用StructuredTool进行包装
structured_weather_tool = StructuredTool.from_function(
    func=get_weather_structured,
    name="get_structured_weather",
    description="获取城市天气的结构化数据",
    args_schema=None, # 可以定义输入schema
    return_schema=WeatherOutput
)

结构化输出便于其他Tool或程序进一步处理,例如筛选温度高于20度的城市。

2. 异步支持: 如果Skill执行的是网络请求等IO密集型操作,使用异步可以显著提升Agent在并发时的效率。

import asyncio
import aiohttp
from langchain.tools import tool

@tool
async def get_weather_async(city: str) -> str:
    """异步获取城市天气。"""
    async with aiohttp.ClientSession() as session:
        async with session.get(BASE_URL, params={"q": city, "appid": WEATHER_API_KEY}) as resp:
            data = await resp.json()
            # ... 解析数据 ...
            return f"{city}天气是..."

对应的Agent执行器也需要使用 ainvoke 异步方法调用。

3. 技能依赖与管理: 当你有几十个Skill时,需要管理。常见的做法是:

  • 分类 :按领域分类,如 data_tools communication_tools system_tools
  • 懒加载 :不是一次性加载所有Tool,而是根据Agent的任务类型动态加载所需的Tool集合。
  • 权限控制 :某些敏感Skill(如删除数据、发送消息)需要对调用者进行权限校验。这通常在Tool函数内部或Agent调用前的中介层实现。

4. 避坑指南与效能提升

在实际开发和调试中,你会遇到比教程更复杂的情况。下面是我从实际项目中总结的几个关键点和避坑经验。

4.1 为什么我的Agent不调用Tool?—— 描述与提示词工程

这是新手最常见的问题。你写好了Tool,但Agent就是无视它,总是用自己的知识瞎编答案。问题通常出在两方面:

  1. Tool的描述(Docstring)不够清晰 :LLM根据描述决定是否调用。描述必须 准确说明功能、输入格式和输出内容

    • 差描述 “获取天气。”
    • 好描述 “获取指定城市的当前天气情况,包括温度、天气状况和湿度。输入应为城市名称字符串,例如‘北京’或‘New York’。如果城市不存在,会返回错误信息。”
    • 技巧 :在描述中自然地包含可能的关键词。如果用户问“今天下雨吗?”,你的Tool描述里有“天气状况”,LLM就更容易关联上。
  2. 提示词(Prompt)没有充分激发Agent使用Tool :我们之前用的 hwchase17/react 提示词模板已经内置了使用Tool的指令。但如果你自定义提示词, 必须明确告诉LLM它可以且应该使用提供的工具 。一个简单的模板应该包含:

    • 系统角色设定(如“你是一个有帮助的助手,可以调用工具来回答问题。”)
    • 工具列表及其描述。
    • 指令(如“如果问题需要查询实时信息或执行操作,请务必调用合适的工具。”)
    • 思考格式(如“思考:... 行动:... 行动输入:... 观察:...”)。

调试方法 :开启 verbose=True ,观察Agent的“思考”链。如果它根本没进入“行动”步骤,说明它不认为需要调用工具,优先检查上述两点。

4.2 处理复杂任务与多技能协作

单个Skill很简单,但现实任务往往需要多个Skill按顺序或条件执行。这就是Agent规划能力的体现。

  • 场景 :用户说“查一下北京天气,如果温度低于10度就发邮件提醒我加衣服。”
  • 分析 :这需要 get_weather Skill和 send_email Skill。Agent需要先调用前者,根据结果(温度值)做判断,再决定是否调用后者。

LangChain的 create_react_agent 本身具备一定的顺序推理能力。但对于更复杂的工作流(如循环、并行),你需要更强大的编排工具:

  • 使用LangGraph :这是LangChain官方的工作流库,可以用图的方式定义Agent和Tool的执行流程,支持循环、分支、并行。
  • 使用CrewAI的Task :在CrewAI中,你可以定义多个Task,每个Task指定一个Agent和一系列Tool,并设置Task之间的依赖关系。
  • 手动控制流 :在最底层,你可以自己写逻辑,根据一个Tool的输出,决定下一个调用什么。但这失去了Agent自动规划的意义。

建议 :先从简单的多Tool场景开始,依赖ReAct Agent的推理能力。当逻辑过于复杂且不稳定时,再考虑引入LangGraph等编排框架。

4.3 错误处理与稳定性保障

一个脆弱的Agent是没用的。你必须为Skill和Agent本身设计容错机制。

  1. Skill内部错误处理 :如前所述,每个Tool函数内部必须有 try...except ,返回明确的错误信息,而不是抛出异常导致整个Agent崩溃。
  2. 超时控制 :网络请求必须设置超时( timeout 参数),避免一个慢速API拖死整个Agent。
  3. Agent执行器的错误处理 AgentExecutor handle_parsing_errors=True 参数很重要,它能处理LLM输出不符合Tool调用格式的解析错误。你还可以自定义一个错误回调函数。
  4. 重试机制 :对于可能因网络抖动失败的Tool,可以加入简单的重试逻辑(如 tenacity 库)。
  5. 验证输入 :在Tool函数开头验证输入参数是否合法(如城市名是否为空、邮箱格式是否正确)。

4.4 性能与成本考量

  • Token消耗 :Agent的每一步“思考”和“行动”都会消耗LLM的Token。复杂的思考链和频繁的Tool调用会显著增加成本。在 verbose 模式下观察,如果Agent为了一个简单问题进行了过多无用的“思考”,可能需要优化提示词或Tool描述。
  • 并发与速率限制 :如果你部署的Agent服务会同时处理多个用户请求,要注意:
    • LLM API的速率限制 :避免并发请求超过配额。
    • 下游Skill API的速率限制 :比如天气API可能有每分钟调用次数限制。
    • 解决方案 :在Agent服务层实现请求队列、限流和缓存(例如,对同一城市天气的查询,5分钟内缓存结果)。

4.5 从开发到部署的最后一公里

本地跑通只是第一步。要让别人能用,你需要:

  1. 封装为API :使用FastAPI或Flask将你的Agent和Skill封装成HTTP接口。接收用户问题,返回Agent的回答。
  2. 技能注册与发现 :建立一个技能注册中心,新的Skill可以通过配置文件或API动态注册,而不需要重启主服务。
  3. 日志与监控 :记录每一次用户交互、Agent的思考链、Tool的调用和结果。这对于调试和优化至关重要。监控Token消耗、响应时间和错误率。
  4. 安全性
    • 输入过滤 :防止用户输入恶意指令导致Tool执行危险操作(如“删除所有文件”)。
    • Tool权限 :对不同用户或角色,暴露不同的Tool集合。
    • 敏感信息 :Tool中使用的API Key等敏感信息必须通过环境变量或密钥管理服务获取,绝不能硬编码在代码中。

5. 总结:Skill开发的本质是“能力封装”与“意图理解”

回过头看,开发Agent Skill的过程,本质上在做两件事:

  1. 能力封装 :将一段确定性的、可执行的功能(函数、API调用、脚本)包装成一个标准化的、有清晰描述的接口(Tool)。这是工程活,考验的是代码健壮性和设计能力。
  2. 意图理解与路由 :通过精心设计的提示词和Tool描述,引导LLM(Agent)准确理解用户意图,并路由到正确的Skill上。这是提示词工程和AI产品设计的活,考验的是对人机交互和LLM能力的理解。

因此,一个优秀的Agent Skill开发者,需要同时具备 软件工程 AI产品 的思维。不要只埋头写Tool函数,更要不断测试和优化Agent调用这些Tool的准确性和效率。

最后给一个最实在的建议 :不要一开始就追求大而全的技能库。从一个你最熟悉的、最有把握的领域(比如查询你公司的内部知识库、操作一个你负责的云服务)开始,打造一个 极其稳定、可靠 的Skill。把这个流程跑通、跑稳,包括开发、测试、部署、监控、迭代。这个过程中积累的经验,远比你看十篇教程更有价值。之后,再沿着这个模式去扩展第二个、第三个Skill,你会顺利得多。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐