mcpField
A
ag2-add-custom-tool

ag2-add-custom-tool

github.com/ag2aiAgent 工作流1268 次安装13 周前更新
代码仓库

关于 ag2-add-custom-tool

面向 AG2 开发者的技能,介绍如何为 Agent 添加 Python 工具,涵盖模式生成、同步与异步执行、类型化结果、输入验证和依赖注入。

主要功能

  1. 01根据注解生成工具模式
  2. 02支持同步与异步函数
  3. 03使用 Pydantic Field 验证输入
  4. 04返回类型化及多模态结果
  5. 05注入执行时依赖

适用场景

  1. 01让 Agent 调用 API
  2. 02通过工具查询数据库
  3. 03返回图像或结构化数据
  4. 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 会触发错误。

评论

支持 Markdown