書寫日期:2025/4/30
原始 OpenAI 官方文件請搭配:
目前 OpenAI Agents SDK 支援三種工具使用:- OpenAI 既有的工具(Hosted tools):這些工具運作在 LLM 伺服器端(也就是 OpenAI),與 AI 模型並存。OpenAI 提供擷取、網路搜尋與電腦操作等託管工具。e.g. 檔案搜尋、網路搜尋、電腦操作(Computer Use)等功能。
- 函式呼叫(Function calling):這類工具讓你可以將任何 Python 函式作為工具使用。
- Agents 作為工具(Agents as tools):這讓你可以將 Agent 作為工具來使用,使 Agent 能夠呼叫其他 Agent 而無須將控制權完全交給對方。
OpenAI 既有的工具(Hosted tools)
官方案例:
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)
在 tools= [] 中新增 WebSearchTool
、FileSearchTool
、ComputerTool
即可完成。
上述案例中,當使用者詢問一個問題(Which coffee shop should I go to, taking into account my preferences and the weather today in SF?)時,AI Agent 會選擇 tools 中的工具進行操作後,最終給予使用者回覆。
如果只有一個工具,它也可以使用。
from agents import Agent, Runner, WebSearchTool
from dotenv import load_dotenv # 使用 .env 的 OPENAI_API_KEY
import asyncio
load_dotenv() # 使用 .env 的 OPENAI_API_KEY
WebSearchagent = Agent(
name="Web search agent",
instructions="你是一個新聞編輯以繁體中文回覆。你需要搜尋新聞事件,並搜尋可靠的資訊來源,例如各大媒體新聞。盡你所能搜尋最新的資料。",
tools=[ WebSearchTool() ],
)
async def main():
result = await Runner.run(WebSearchagent, "基隆民政局長交保,理由為何?")
print(result.final_output)
if __name__ == "__main__":
asyncio.run(main())
(venv) chiatzuho@ChiadeMacBook-Pro agents_playground % python examples/example-web_search_agent.py
基隆地檢署偵辦罷免民進黨基隆市議員鄭文婷及張之豪案,發現提議人名冊中涉嫌有已故者連署的情況。經搜索、約談後,檢方於4月29日向法院聲請羈押基隆市政府民政處長張淵翔、鄭文婷案領銜人紀文荃及國民黨仁愛區黨部主任張金發。同時,國民黨基隆市黨部主委吳國勝以新台幣30萬元交保,鄭文婷案備補領銜人許紹業及張之豪案領銜人游正義,則分別以3萬元及15萬元交保。 ([rti.org.tw](https://www.rti.org.tw/news/view/id/2247592?utm_source=openai))
檢調懷疑,張淵翔利用戶政系統,協助查詢相關個人資料,介入罷免連署,違反行政中立。經檢察官複訊後,向基隆地方法院聲請羈押張淵翔、紀文荃、張金發3人。至於吳國勝、許紹業、游正義則分別以30萬元、3萬元、15萬元交保。 ([rti.org.tw](https://www.rti.org.tw/news/view/id/2247592?utm_source=openai))
目前尚未有進一步資訊說明法院對張淵翔的羈押聲請是否獲准,或其交保的具體理由。一般而言,法院在決定是否交保時,會考量被告的犯罪嫌疑、逃亡或串供之虞,以及對社會的危害程度等因素。
筆者書寫當下,民政局長已經交保的新聞已超過12小時,但似乎還無法網搜到。因此需注意,筆者書寫當下,OpenAI 的 WebSearchTool
似乎不夠即時!
函式呼叫
官方範例:
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
async def fetch_weather(location: Location) -> str:
"""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")
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],
)
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()
官方案例中,定義了兩個功能函式:「取得天氣資訊」、「讀檔案」。
接著,在定義一個 Agent,這樣當使用者詢問問題時,這個 Agent 會自動判斷應該使用哪個工具,並執行該工具後,最後回傳結果。
舉例而言,如果要問「今天台北天氣為何」?
LLM 會自動認知道你要查詢天氣,執行「取得天氣資訊」的函式,但以這份 code 為例,這個函式的 input 是需要緯度、經度,而這時 LLM 將自動轉換成符合函式需求的 input,並送進函式中執行,這大概就是 function calling 最重要的一部分了。
筆者寫的另一個範例:
from agents import Agent, Runner, FunctionTool, RunContextWrapper, function_tool
from typing_extensions import TypedDict, Any, NotRequired
import asyncio
from dotenv import load_dotenv
from datetime import datetime
import os
import requests
import json
from pprint import pprint as pp
load_dotenv()
class PhotoSearchParams(TypedDict):
key_word: str # 必須填寫的參數
# 一個搜尋圖片的 API 範例
@function_tool
async def search_photo(params: PhotoSearchParams) -> dict:
key_word = params['key_word'] # 必須提供
url = "API 連結"
headers = {
"Content-Type": "application/json",
}
# 請求體
payload = {
"KeyWord": key_word,
}
# 發送POST請求
response = requests.post(url, headers=headers, data=json.dumps(payload))
# 檢查響應
if response.status_code == 200:
return response.json()
else:
return {
"Result": "N",
"Message": f"請求失敗,狀態碼: {response.status_code}",
"ResultData": None
}
PhotoSearchAgent = Agent(
name="Photo Search Agent",
instructions="你是一個新聞編輯,使用者會給你新聞文章,你需負責使用一個最適合的關鍵字去尋找 API 並搜尋照片。這個關鍵字很重要,一定得是一個實體,一個適合拿來當關鍵字搜尋的實體,例如人名、地名、組織名稱、地方、活動名稱等,並拿去搜尋,這樣才會更有可能查詢的到對應照片且適合的照片。你需注意這些圖片的說明可能有很多文字,但要確定主體人物。如果使用者給的新聞文字有人物,盡量使用人物姓名直接搜尋。使用繁體中文直接去搜尋。提供至少四張圖片給我。",
model="gpt-4.1",
tools=[ search_photo ] )
async def main():
result = await Runner.run(PhotoSearchAgent, input="行政院長卓榮泰今天為「因應國際情勢強化經濟社會及國土安全韌性特別條例」草案拜會立法院朝野黨團")
print(result.final_output)
if __name__ == "__main__":
asyncio.run(main())
假設我有一個搜尋圖片的函式,定義一個專業的配圖 Agent,當我送入任何文字,他就會將這段文字轉化成關鍵字後送進這個配圖搜尋工具中搜尋,最後回傳搜尋結果。
需注意,「教導 LLM 這個函式有哪些 input 接口,讓 LLM 知道如何將自然語言轉化成格式化的內容送進函式」蠻重要的,可以透過 typing_extensions
或 pydamic
這類套件包加以使用
個人認為寫超好的 Pydamic 指引:
另外幾個走過的雷(疑惑)也記錄在這邊:
函式說明 docstring
函式中的「'''說明'''
」也挺重要,在官方文件稱為「docstring」,如果有寫的話,會用來讓LLM理解此函數功能與參數使用。
"""Fetch the weather for a given location.
Args:
location: The location to fetch the weather for.
"""
@functional tool 裝飾器
裝飾器,用於從函式創建 FunctionTool。預設情況下將會:
- 解析函式簽名以建立工具參數的 JSON 架構。
- 使用函式的 Docstring 來填寫工具的描述。
- 使用函式的 Docstring 來填寫參數描述。
- 文件字串的風格會自動偵測,但你也可以手動覆寫(override)。
所以可以寫成類似
@function_tool(name_override="fetch_data")
設定 name_override、description_override、docstring_style 等,可以避免 LLM 自行判斷,不設定的話就會讀取原始名稱。
代理作為工具(Agents as tools)
簡單來說,上述的「tools=[]」裡面不外乎放 OpenAI 既有工具、Python 函式,其實 tools 也可以包 Agents。直接來看官方範例:
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)
應該也蠻好懂了!上述是一個「主腦」Agent,會根據使用者內容,決定要把任務給「法文 Agent」還是「西班牙文 Agent」並加以翻譯。
這個例子可能會讓人覺得「我根本可以直接翻譯就好」,但我們將格局放大,這個意義也代表著,如果我有更細緻的操作或分析,可以更能分層次在各個 Agent 做客製化,而不拘泥於通用任務。
Debug
在操作 OpenAI Agents 時,尤其是使用 Function Calling 更需注意實際 AI Agents 流程如何進行。官方推薦可以使用 API 後台的「Traces」加以查看:

除了可以看整個工作流,還可以看到哪個階段時間花費為何,進而持續優化自己的 Agent 流程!
最後附上詳細 API reference 頁面參考: