ag2-add-custom-tool
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAdd a custom Python tool
添加自定义Python工具
When to use
适用场景
The user wants their to take a real-world action: hit an API, query a database, compute something, return an image. If they want shipped tools (web search, code exec, shell), see and instead.
Agentag2-use-builtin-toolsag2-shell-tool用户希望其执行实际操作:调用API、查询数据库、进行计算、返回图片。如果需要使用内置工具(网页搜索、代码执行、Shell),请查看和。
Agentag2-use-builtin-toolsag2-shell-tool60-second recipe
60秒快速入门
python
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],
)The decorator generates the LLM-facing schema from the function signature, type hints, and docstring. The docstring is the description the LLM sees — write it for an LLM reader, not just a human.
@toolYou can also pass plain undecorated functions in and AG2 wraps them automatically:
tools=[...]python
def get_weather(location: str) -> str:
"""Returns the current weather for a given location."""
return "Sunny, 22°C"
agent = Agent("weather", tools=[get_weather])Or attach a tool to an existing agent with :
@agent.toolpython
agent = Agent("calc")
@agent.tool
def multiply(a: int, b: int) -> int:
"""Multiplies two integers and returns the result."""
return a * bpython
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你也可以在中传入未装饰的普通函数,AG2会自动为其包装:
tools=[...]python
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上:
@agent.toolpython
agent = Agent("calc")
@agent.tool
def multiply(a: int, b: int) -> int:
"""Multiplies two integers and returns the result."""
return a * bSync vs async
同步与异步
Both and are supported. Synchronous tools run in a thread by default so blocking I/O does not freeze the event loop. For ultra-fast pure-Python tools, opt out:
defasync defpython
@tool(sync_to_thread=False)
def format_name(first: str, last: str) -> str:
"""Formats a full name."""
return f"{last.upper()}, {first.capitalize()}"Native async tools run in the main event loop directly:
python
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()defasync defpython
@tool(sync_to_thread=False)
def format_name(first: str, last: str) -> str:
"""Formats a full name."""
return f"{last.upper()}, {first.capitalize()}"原生异步工具直接在主事件循环中运行:
python
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()Validating inputs with Pydantic Field
Field使用Pydantic Field
验证输入
FieldUse to give the LLM strict bounds. The framework forwards these into the JSON Schema:
Annotated[T, Field(...)]python
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."You can also override the tool name and description on the decorator:
python
@tool(name="custom_math_tool", description="Performs advanced math.")
def math_op(a: int, b: int) -> int:
return a + b使用为LLM设置严格的边界。框架会将这些约束转发到JSON Schema中:
Annotated[T, Field(...)]python
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."你也可以在装饰器上覆盖工具名称和描述:
python
@tool(name="custom_math_tool", description="Performs advanced math.")
def math_op(a: int, b: int) -> int:
return a + bReturning typed Input
/ ToolResult
InputToolResult返回带类型的Input
/ToolResult
InputToolResultA plain return is wrapped in automatically. For richer payloads, return an subtype or compose with :
strTextInputInputToolResultpython
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},
)For raw bytes of arbitrary format, use .
BinaryInput(data=..., media_type="application/pdf")普通返回值会自动包装为。对于更丰富的负载,可以返回子类型或与组合:
strTextInputInputToolResultpython
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")End the turn early with final=True
final=True使用final=True
提前结束对话轮次
final=TrueWhen the tool already knows the exact final answer, skip the extra LLM round-trip:
python
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)A must contain exactly one part ( or ).
final=TrueToolResultTextInputDataInput当工具已经知道确切的最终答案时,可以跳过额外的LLM往返:
python
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=TrueToolResultTextInputDataInputDependency injection (Context / Inject / Variable / Depends)
依赖注入(Context / Inject / Variable / Depends)
Tools can pull execution-time values without exposing them to the LLM. See for the full table; the basics:
references/dependency_injection.mdpython
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.mdpython
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."""
...InjectGoing deeper
深入学习
- —
references/dependency_injection.mdvsContextvsInjectvsVariable, defaults, factories, mutability, overrides.Depends - — full
website/docs/user-guide/tools/tools.mdxreference, including the synthesized JSON Schema.@tool - —
website/docs/user-guide/depends.mdxlifecycle, yield-based teardown, caching, test overrides.Depends - — the
website/docs/user-guide/multimodal/inputs.mdxfactory hierarchy and provider support matrix.Input - — bundle related tools into a reusable
website/docs/user-guide/tools/toolkits.mdx.Toolkit - — async hooks around a single tool (validation, redaction, approval — see also
website/docs/user-guide/tools/tool_middleware.mdx).ag2-hitl
- ——
references/dependency_injection.md、Context、Inject与Variable的对比、默认值、工厂函数、可变性、重写。Depends - ——完整的
website/docs/user-guide/tools/tools.mdx参考,包括生成的JSON Schema。@tool - ——
website/docs/user-guide/depends.mdx生命周期、基于yield的清理、缓存、测试重写。Depends - ——
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
Common pitfalls
常见陷阱
- Vague docstring — the LLM uses it to decide when to call the tool. "Calculates shipping cost based on destination and weight" is much better than "Shipping calc".
- No type hints — without them the framework can't generate a useful JSON Schema; the LLM may not call your tool at all.
- Blocking the event loop — if you write (sync) tool with heavy CPU or network and pass
def, the loop blocks. Default behaviour (run in a thread) is safe; only opt out for cheap pure-Python work.sync_to_thread=False - Function-level imports inside tools — repo convention disallows them. Hoist to module top.
import - Nested function definitions inside the tool body — also disallowed (recreates the function on every call).
- Returning directly when you wanted structured data — wrap it in
dictso the framework treats it as structured rather than coercing to text.DataInput(...) - Forgetting requires exactly one part — combining multiple
final=Trues withInputwill raise.final=True
- 模糊的文档字符串——LLM会根据它决定何时调用工具。“根据目的地和重量计算运费”比“运费计算”要好得多。
- 无类型提示——没有类型提示,框架无法生成有用的JSON Schema;LLM可能根本不会调用你的工具。
- 阻塞事件循环——如果你编写的(同步)工具包含大量CPU或网络操作,且设置了
def,会阻塞事件循环。默认行为(在线程中运行)是安全的;仅在处理低成本纯Python任务时选择关闭该特性。sync_to_thread=False - 工具内部的函数级导入——仓库规范禁止这种写法。请将提升到模块顶部。
import - 工具体内部的嵌套函数定义——同样被禁止(每次调用都会重新创建函数)。
- 直接返回而你需要的是结构化数据——请将其包装在
dict中,这样框架会将其视为结构化数据,而非强制转换为文本。DataInput(...) - 忘记要求恰好包含一个部分——将多个
final=True与Input组合会引发错误。final=True