工具
工具讓代理(agent)能夠執行動作:例如擷取資料、執行程式碼、呼叫外部 API,甚至操作電腦。在 Agent SDK 中,工具分為三種類型:
- 託管工具(Hosted tools):這些工具在大型語言模型 (LLM) 伺服器上與 AI 模型一起運行。OpenAI 提供檢索、網頁搜尋和電腦操作等託管工具。
- 函式呼叫(Function calling):這讓你可以將任何 Python 函式作為工具來使用。
- 代理作為工具(Agents as tools):這讓你可以將代理作為工具使用,使代理能夠呼叫其他代理而不需完全交接控制權。
託管工具(Hosted tools)
當你使用 [OpenAIResponsesModel
][agents.models.openai_responses.OpenAIResponsesModel] 時,OpenAI 提供數個內建工具:
- [
WebSearchTool
][agents.tool.WebSearchTool] 讓代理能夠搜尋網路。 - [
FileSearchTool
][agents.tool.FileSearchTool] 可從你的 OpenAI 向量儲存庫(Vector Stores)擷取資訊。 - [
ComputerTool
][agents.tool.ComputerTool] 可自動化電腦操作任務。 - [
CodeInterpreterTool
][agents.tool.CodeInterpreterTool] 讓 LLM 能在沙箱環境中執行程式碼。 - [
HostedMCPTool
][agents.tool.HostedMCPTool] 將遠端 MCP 伺服器的工具暴露給模型使用。 - [
ImageGenerationTool
][agents.tool.ImageGenerationTool] 可根據提示生成圖片。 - [
LocalShellTool
][agents.tool.LocalShellTool] 可在你的機器上執行 shell 指令。
from agents import Agent, FileSearchTool, Runner, WebSearchTool
agent = Agent(
name="Assistant",
tools=[
WebSearchTool(),
FileSearchTool(
max_num_results=3,
vector_store_ids=["VECTOR_STORE_ID"],
),
],
)
async def main():
result = await Runner.run(agent, "Which coffee shop should I go to, taking into account my preferences and the weather today in SF?")
print(result.final_output)
函式工具
你可以將任何 Python 函式作為工具使用。Agents SDK 會自動設定該工具:
- 工具名稱將會是 Python 函式的名稱(或你可以自訂名稱)
- 工具描述會取自該函式的 docstring(或你可以自訂描述)
- 函式輸入的 schema 會根據函式參數自動建立
- 每個輸入參數的描述會從函式的 docstring 取得,除非你選擇停用
我們使用 Python 的 inspect
模組來擷取函式簽章,並搭配 griffe
解析 docstring,以及使用 pydantic
來建立 schema。
import json
from typing_extensions import TypedDict, Any
from agents import Agent, FunctionTool, RunContextWrapper, function_tool
class Location(TypedDict):
lat: float
long: float
@function_tool # (1)!
async def fetch_weather(location: Location) -> str:
# (2)!
"""Fetch the weather for a given location.
Args:
location: The location to fetch the weather for.
"""
# In real life, we'd fetch the weather from a weather API
return "sunny"
@function_tool(name_override="fetch_data") # (3)!
def read_file(ctx: RunContextWrapper[Any], path: str, directory: str | None = None) -> str:
"""Read the contents of a file.
Args:
path: The path to the file to read.
directory: The directory to read the file from.
"""
# In real life, we'd read the file from the file system
return "<file contents>"
agent = Agent(
name="Assistant",
tools=[fetch_weather, read_file], # (4)!
)
for tool in agent.tools:
if isinstance(tool, FunctionTool):
print(tool.name)
print(tool.description)
print(json.dumps(tool.params_json_schema, indent=2))
print()
- 你可以在函式的參數中使用任何 Python 類型,且該函式可以是同步(sync)或非同步(async)。
- 若有提供 docstring,將會用於擷取描述與參數說明。
- 函式可以選擇性地接收
context
(必須作為第一個參數)。你也可以設定覆寫選項,例如工具名稱、描述、要使用的 docstring 風格等。 - 你可以將已裝飾(decorated)的函式傳遞到工具清單中。
展開以查看輸出
fetch_weather
Fetch the weather for a given location.
{
"$defs": {
"Location": {
"properties": {
"lat": {
"title": "Lat",
"type": "number"
},
"long": {
"title": "Long",
"type": "number"
}
},
"required": [
"lat",
"long"
],
"title": "Location",
"type": "object"
}
},
"properties": {
"location": {
"$ref": "#/$defs/Location",
"description": "The location to fetch the weather for."
}
},
"required": [
"location"
],
"title": "fetch_weather_args",
"type": "object"
}
fetch_data
Read the contents of a file.
{
"properties": {
"path": {
"description": "The path to the file to read.",
"title": "Path",
"type": "string"
},
"directory": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "The directory to read the file from.",
"title": "Directory"
}
},
"required": [
"path"
],
"title": "fetch_data_args",
"type": "object"
}
自訂函式工具
有時候,你可能不想將 Python 函式作為工具來使用。如果你願意,也可以直接建立一個 [FunctionTool
][agents.tool.FunctionTool]。你需要提供:
name
description
params_json_schema
,這是參數的 JSON schemaon_invoke_tool
,這是一個非同步(async)函式,會接收一個 [ToolContext
][agents.tool_context.ToolContext] 以及以 JSON 字串形式傳遞的參數,並且必須回傳工具的輸出結果(字串)。
from typing import Any
from pydantic import BaseModel
from agents import RunContextWrapper, FunctionTool
def do_some_work(data: str) -> str:
return "done"
class FunctionArgs(BaseModel):
username: str
age: int
async def run_function(ctx: RunContextWrapper[Any], args: str) -> str:
parsed = FunctionArgs.model_validate_json(args)
return do_some_work(data=f"{parsed.username} is {parsed.age} years old")
tool = FunctionTool(
name="process_user",
description="Processes extracted user data",
params_json_schema=FunctionArgs.model_json_schema(),
on_invoke_tool=run_function,
)
自動參數與註解字串(docstring)解析
如前所述,我們會自動解析函式簽名,以擷取工具的 schema,並解析註解字串(docstring),以擷取工具本身及各個參數的描述。相關說明如下:
- 簽名解析是透過
inspect
模組完成。我們利用型別註解來判斷參數的型別,並動態建立一個 Pydantic model 來表示整體 schema。這支援大多數型別,包括 Python 原生型別、Pydantic models、TypedDicts 等等。 - 我們使用
griffe
來解析註解字串。支援的註解字串格式有google
、sphinx
和numpy
。我們會嘗試自動偵測註解字串的格式,但這僅是盡力而為,你也可以在呼叫function_tool
時明確指定格式。你也可以將use_docstring_info
設為False
來停用註解字串解析。
schema 擷取的相關程式碼位於 [agents.function_schema
][]。
以代理(Agent)作為工具
在某些工作流程中,你可能希望由一個中央代理來協調一個專業代理網路,而不是直接交出控制權。你可以透過將代理建模為工具來達成這個目的。
from agents import Agent, Runner
import asyncio
spanish_agent = Agent(
name="Spanish agent",
instructions="You translate the user's message to Spanish",
)
french_agent = Agent(
name="French agent",
instructions="You translate the user's message to French",
)
orchestrator_agent = Agent(
name="orchestrator_agent",
instructions=(
"You are a translation agent. You use the tools given to you to translate."
"If asked for multiple translations, you call the relevant tools."
),
tools=[
spanish_agent.as_tool(
tool_name="translate_to_spanish",
tool_description="Translate the user's message to Spanish",
),
french_agent.as_tool(
tool_name="translate_to_french",
tool_description="Translate the user's message to French",
),
],
)
async def main():
result = await Runner.run(orchestrator_agent, input="Say 'Hello, how are you?' in Spanish.")
print(result.final_output)
自訂 tool-agents
agent.as_tool
函式是一個方便的方法,可以輕鬆地將代理(agent)轉換為工具(tool)。但它並不支援所有的設定,例如,你無法設定 max_turns
。若有進階的使用情境,請直接在你的工具實作中使用 Runner.run
:
@function_tool
async def run_my_agent() -> str:
"""A tool that runs the agent with custom configs"""
agent = Agent(name="My agent", instructions="...")
result = await Runner.run(
agent,
input="...",
max_turns=5,
run_config=...
)
return str(result.final_output)
自訂輸出擷取
在某些情況下,你可能希望在將工具代理(tool-agent)的輸出返回給中央代理(central agent)之前,先對其進行修改。這在以下情境會很有用:
- 從子代理(sub-agent)的聊天記錄中擷取特定資訊(例如:JSON 載荷)。
- 轉換或重新格式化代理的最終答案(例如:將 Markdown 轉換為純文字或 CSV)。
- 驗證輸出內容,或在代理回應缺失或格式錯誤時提供預設值。
你可以透過在 as_tool
方法中傳入 custom_output_extractor
參數來達成上述目的:
async def extract_json_payload(run_result: RunResult) -> str:
# Scan the agent’s outputs in reverse order until we find a JSON-like message from a tool call.
for item in reversed(run_result.new_items):
if isinstance(item, ToolCallOutputItem) and item.output.strip().startswith("{"):
return item.output.strip()
# Fallback to an empty JSON object if nothing was found
return "{}"
json_tool = data_agent.as_tool(
tool_name="get_data_json",
tool_description="Run the data agent and return only its JSON payload",
custom_output_extractor=extract_json_payload,
)
條件式工具啟用
你可以使用 is_enabled
參數,在執行時有條件地啟用或停用代理工具。這讓你能根據情境、使用者偏好或執行時條件,動態篩選哪些工具可供大型語言模型 (LLM) 使用。
import asyncio
from agents import Agent, AgentBase, Runner, RunContextWrapper
from pydantic import BaseModel
class LanguageContext(BaseModel):
language_preference: str = "french_spanish"
def french_enabled(ctx: RunContextWrapper[LanguageContext], agent: AgentBase) -> bool:
"""Enable French for French+Spanish preference."""
return ctx.context.language_preference == "french_spanish"
# Create specialized agents
spanish_agent = Agent(
name="spanish_agent",
instructions="You respond in Spanish. Always reply to the user's question in Spanish.",
)
french_agent = Agent(
name="french_agent",
instructions="You respond in French. Always reply to the user's question in French.",
)
# Create orchestrator with conditional tools
orchestrator = Agent(
name="orchestrator",
instructions=(
"You are a multilingual assistant. You use the tools given to you to respond to users. "
"You must call ALL available tools to provide responses in different languages. "
"You never respond in languages yourself, you always use the provided tools."
),
tools=[
spanish_agent.as_tool(
tool_name="respond_spanish",
tool_description="Respond to the user's question in Spanish",
is_enabled=True, # Always enabled
),
french_agent.as_tool(
tool_name="respond_french",
tool_description="Respond to the user's question in French",
is_enabled=french_enabled,
),
],
)
async def main():
context = RunContextWrapper(LanguageContext(language_preference="french_spanish"))
result = await Runner.run(orchestrator, "How are you?", context=context.context)
print(result.final_output)
asyncio.run(main())
is_enabled
參數可接受以下類型:
- 布林值:True
(永遠啟用)或 False
(永遠停用)
- 可呼叫函式:接收 (context, agent)
並回傳布林值的函式
- 非同步函式:用於複雜條件邏輯的 async 函式
被停用的工具在執行時會完全對大型語言模型 (LLM) 隱藏,這在以下情境特別有用: - 根據使用者權限進行功能控管(feature gating) - 依據環境(開發 vs 正式)決定工具可用性 - A/B 測試不同的工具組合 - 根據執行時狀態動態篩選工具
處理函式工具中的錯誤
當你透過 @function_tool
建立函式工具時,可以傳入 failure_error_function
。這是一個當工具呼叫發生錯誤時,提供錯誤回應給大型語言模型 (LLM) 的函式。
- 預設情況下(即你沒有傳入任何東西),會執行
default_tool_error_function
,告知 LLM 發生錯誤。 - 如果你傳入自訂的錯誤處理函式,則會執行該函式,並將回應傳送給 LLM。
- 如果你明確傳入
None
,則任何工具呼叫錯誤都會重新拋出,讓你自行處理。這可能是ModelBehaviorError
(例如模型產生了無效的 JSON),或是UserError
(例如你的程式碼發生錯誤)等情況。
from agents import function_tool, RunContextWrapper
from typing import Any
def my_custom_error_function(context: RunContextWrapper[Any], error: Exception) -> str:
"""A custom function to provide a user-friendly error message."""
print(f"A tool call failed with the following error: {error}")
return "An internal server error occurred. Please try again later."
@function_tool(failure_error_function=my_custom_error_function)
def get_user_profile(user_id: str) -> str:
"""Fetches a user profile from a mock API.
This function demonstrates a 'flaky' or failing API call.
"""
if user_id == "user_123":
return "User profile for user_123 successfully retrieved."
else:
raise ValueError(f"Could not retrieve profile for user_id: {user_id}. API returned an error.")
如果你是手動建立 FunctionTool
物件,那麼你必須在 on_invoke_tool
函式內處理錯誤。