关于 ag2-add-custom-tool
面向 AG2 开发者的技能,介绍如何为 Agent 添加 Python 工具,涵盖模式生成、同步与异步执行、类型化结果、输入验证和依赖注入。
主要功能
- 01根据注解生成工具模式
- 02支持同步与异步函数
- 03使用 Pydantic Field 验证输入
- 04返回类型化及多模态结果
- 05注入执行时依赖
适用场景
- 01让 Agent 调用 API
- 02通过工具查询数据库
- 03返回图像或结构化数据
- 04用最终工具结果结束回合
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ag2ai/ ag2-skills --skill ag2-add-custom-tool安装后在 Claude Code 中输入/ag2-add-custom-tool确认已加载。
附带文件references/dependency_injection.md
详细介绍
添加自定义 Python 工具
适用场景
用户希望自己的 Agent 执行现实世界中的操作:调用 API、查询数据库、进行计算或返回图像。如果需要已随附的工具(网络搜索、代码执行、shell),请改用 ag2-use-builtin-tools 和 ag2-shell-tool。
60 秒快速上手
from ag2 import Agent, tool
from ag2.config import OpenAIConfig
@tool
def calculate_shipping_cost(destination: str, weight_kg: float) -> str:
"""Calculates shipping cost for a package to a destination."""
return "$15.00"
agent = Agent(
"shipping",
prompt="Use tools when helpful.",
config=OpenAIConfig(model="gpt-4o-mini"),
tools=[calculate_shipping_cost],
)
@tool 装饰器会根据函数签名、类型提示和文档字符串生成面向 LLM 的架构。文档字符串是 LLM 能看到的描述——请面向 LLM 编写,而不只是面向人类读者。
也可以在 tools=[...] 中传入未装饰的普通函数,AG2 会自动为其添加封装:
def get_weather(location: str) -> str:
"""Returns the current weather for a given location."""
return "Sunny, 22°C"
agent = Agent("weather", tools=[get_weather])
也可以使用 @agent.tool 将工具附加到现有 Agent:
agent = Agent("calc")
@agent.tool
def multiply(a: int, b: int) -> int:
"""Multiplies two integers and returns the result."""
return a * b
同步与异步
支持 def 和 async def。默认情况下,同步工具会在线程中运行,避免阻塞 I/O 冻结事件循环。对于超快的纯 Python 工具,可以选择不在线程中运行:
@tool(sync_to_thread=False)
def format_name(first: str, last: str) -> str:
"""Formats a full name."""
return f"{last.upper()}, {first.capitalize()}"
原生异步工具会直接在主事件循环中运行:
import aiohttp
@tool
async def fetch(url: str) -> str:
"""Fetches a URL with aiohttp."""
async with aiohttp.ClientSession() as session:
async with session.get(url) as r:
return await r.text()
使用 Pydantic Field 验证输入
使用 Annotated[T, Field(...)] 为 LLM 设置严格的边界。框架会将这些信息传递到 JSON Schema 中:
from typing import Annotated
from pydantic import Field
from ag2 import tool
@tool
def set_temperature(
temp: Annotated[int, Field(description="Target temperature.", ge=10, le=30)],
mode: Annotated[str, Field(description="Mode.", pattern="^(heat|cool|auto)$")],
) -> str:
"""Sets the thermostat."""
return f"Set to {temp}°C in {mode} mode."
也可以在装饰器中覆盖工具名称和描述:
@tool(name="custom_math_tool", description="Performs advanced math.")
def math_op(a: int, b: int) -> int:
return a + b
返回类型化的 Input / ToolResult
普通的 str 返回值会自动封装为 TextInput。对于更丰富的负载,可以返回 Input 子类型,或与 ToolResult 组合使用:
from ag2 import DataInput, ImageInput, TextInput, ToolResult, tool
@tool
def get_status(task_id: str) -> TextInput:
return TextInput(f"Task {task_id} is in progress.")
@tool
def get_user_profile(user_id: str) -> DataInput:
return DataInput({"id": user_id, "name": "Alice", "role": "admin"})
@tool
def fetch_chart(chart_id: str) -> ImageInput:
return ImageInput(f"https://charts.example.com/{chart_id}.png")
@tool
def analyze_product(product_id: str) -> ToolResult:
"""Returns image + structured metadata in one tool call."""
return ToolResult(
ImageInput(f"https://cdn.example.com/products/{product_id}.jpg"),
{"id": product_id, "name": "Widget Pro", "stock": 42},
)
对于任意格式的原始字节,请使用 BinaryInput(data=..., media_type="application/pdf")。
使用 final=True 提前结束轮次
当工具已知确切的最终答案时,可以跳过额外的 LLM 往返:
from ag2 import ToolResult, tool
@tool
def handoff_to_human(ticket_id: str) -> ToolResult:
"""Escalates and returns the final user-facing message verbatim."""
return ToolResult(f"Ticket {ticket_id} was escalated.", final=True)
final=True 的 ToolResult 必须且只能包含一个部分(TextInput 或 DataInput)。
依赖注入(Context / Inject / Variable / Depends)
工具可以获取运行时值,而无需将这些值暴露给 LLM。完整表格请参阅 references/dependency_injection.md;基本用法如下:
from typing import Annotated
from ag2 import Context, Inject, Variable, tool
@tool
def query_db(query: str, ctx: Context) -> str:
"""Runs a SQL query."""
db = ctx.dependencies["db"]
return db.execute(query)
@tool
def fetch(url: str, http: Annotated[object, Inject("http_session")]) -> str:
"""Fetches with a shared HTTP session."""
return http.get(url).text
@tool
def send(text: str, api_key: Annotated[str, Variable()]) -> str:
"""Sends a message via the configured channel."""
...
Inject 注解会从面向 LLM 的架构中移除——它们是一种内部注入机制。
深入了解
references/dependency_injection.md—Context、Inject、Variable和Depends的区别、默认值、工厂、可变性及覆盖。website/docs/user-guide/tools/tools.mdx— 完整的@tool参考,包括生成的 JSON Schema。website/docs/user-guide/depends.mdx—Depends生命周期、基于 yield 的清理、缓存和测试覆盖。website/docs/user-guide/multimodal/inputs.mdx—Input工厂层级和提供商支持矩阵。website/docs/user-guide/tools/toolkits.mdx— 将相关工具打包到可复用的Toolkit中。website/docs/user-guide/tools/tool_middleware.mdx— 单个工具的异步钩子(验证、脱敏、审批——另请参阅ag2-hitl)。
常见问题
- 文档字符串含糊不清 — LLM 会据此判断何时调用工具。“根据目的地和重量计算运费”远胜于“运费计算”。
- 缺少类型提示 — 没有类型提示,框架就无法生成实用的 JSON Schema;LLM 可能根本不会调用你的工具。
- 阻塞事件循环 — 如果你编写了一个使用
def(同步)的工具来执行繁重的 CPU 或网络任务,并传入sync_to_thread=False,事件循环就会被阻塞。默认行为(在线程中运行)是安全的;只有在执行开销很低的纯 Python 操作时才选择不在线程中运行。 - 在工具内的函数级作用域导入 — 仓库约定不允许这样做。请将
import提到模块顶部。 - 在工具函数体内定义嵌套函数 — 这也不允许(每次调用都会重新创建函数)。
- 本想返回结构化数据却直接返回
dict— 请将其封装在DataInput(...)中,这样框架才会将其视为结构化数据,而不是强制转换为文本。 - 忘记
final=True要求只能包含一个部分 — 使用final=True组合多个Input会触发错误。