Agent Tool Builder(代理工具建構者)
工具是 AI 代理與世界互動的方式。一個設計良好的工具,是區分能正常運作的代理與產生幻覺、靜默失敗或消耗比所需多出 10 倍 token 的代理的關鍵。
此技能涵蓋從 Schema 到錯誤處理的工具設計。JSON Schema 最佳實踐、真正能幫助 LLM 的描述撰寫技巧、驗證方法,以及正成為 AI 工具通用語言的 MCP 新興標準。
關鍵見解:工具描述比工具實作更重要。LLM 永遠不會看到你的程式碼——它只能看到 Schema 和描述。
原則
- 對於 LLM 準確性而言,描述品質 > 實作品質
- 目標是少於 20 個工具——更多會造成混淆
- 每個工具都需要明確的錯誤處理——靜默失敗會毒害代理
- 回傳字串,而非物件——LLM 處理的是文字
- 在執行前先進行驗證——拒絕、修正或升級,絕不靜默失敗
- 使用 LLM 測試工具,而不是僅靠單元測試
能力範圍
- agent-tools
- function-calling
- tool-schema-design
- mcp-tools
- tool-validation
- tool-error-handling
範疇
- multi-agent-coordination → multi-agent-orchestration
- agent-memory → agent-memory-systems
- api-design → api-designer
- llm-prompting → prompt-engineering
工具與框架
標準
- JSON Schema - 使用時機:所有工具定義。備註:工具 Schema 的通用格式。
- MCP (Model Context Protocol) - 使用時機:建構可重複使用、跨平台的工具。備註:Anthropic 提出的開放標準,已被廣泛採用。
框架
- Anthropic SDK - 使用時機:基於 Claude 的代理。備註:Beta 版本的 tool runner 處理了大部分複雜性。
- OpenAI Functions - 使用時機:基於 OpenAI 的代理。備註:使用嚴格模式以確保 Schema 合規。
- Vercel AI SDK - 使用時機:多供應商工具處理。備註:抽象化了不同供應商之間的差異。
- LangChain Tools - 使用時機:基於 LangChain 的代理。備註:將 MCP 工具轉換為 LangChain 格式。
模式
工具 Schema 設計
為工具建立清晰、無歧義的 JSON Schema
使用時機:為代理定義任何新工具時
工具 Schema 最佳實踐:
1. 詳細的描述(最重要)
""" 錯誤示範 - 過於模糊: { "name": "get_stock_price", "description": "取得股票價格", "input_schema": { "type": "object", "properties": { "ticker": {"type": "string"} } } }
正確示範 - 全面: { "name": "get_stock_price", "description": "取得指定股票代號的當前股價。股票代號必須是在主要美國證券交易所(如 NYSE 或 NASDAQ)公開交易公司的有效符號。回傳最新成交價格(美元)。當用戶詢問當前或近期股價時使用。不提供歷史數據、公司資訊或預測。", "input_schema": { "type": "object", "properties": { "ticker": { "type": "string", "description": "股票代號,例如 AAPL 代表 Apple Inc." } }, "required": ["ticker"] } } """
2. 參數描述
""" 每個參數都需要:
- 它是什麼
- 預期的格式
- 範例值
- 邊界情況/限制
{ "location": { "type": "string", "description": "城市和州/國家。格式:美國使用 '城市, 州'(例如 'San Francisco, CA'),國際使用 '城市, 國家'(例如 'Tokyo, Japan')。請勿使用郵遞區號或座標。" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "溫度單位。若未指定,則預設為用戶的地區設定。美國用戶使用 'fahrenheit',其他用戶使用 'celsius'。" } } """
3. 盡可能使用列舉(Enum)
""" 列舉將 LLM 限制在有效值內:
"priority": { "type": "string", "enum": ["low", "medium", "high", "critical"], "description": "任務優先級" }
"action": { "type": "string", "enum": ["create", "read", "update", "delete"], "description": "要執行的 CRUD 操作" } """
4. 必填與選填
""" 明確標示哪些是必填:
{ "type": "object", "properties": { "query": {...}, // 必填 "limit": {...}, // 選填,有預設值 "offset": {...} // 選填 }, "required": ["query"], "additionalProperties": false // 嚴格模式 } """
附帶輸入範例的工具
使用範例來引導 LLM 使用工具
使用時機:具有巢狀物件或格式敏感輸入的複雜工具
工具使用範例(Anthropic Beta 功能):
""" 範例向 Claude 展示了 Schema 無法表達的具體模式。 在複雜操作上,可將準確率從 72% 提升至 90%。 """
{ "name": "create_calendar_event", "description": "建立行事曆事件,可包含選填的參與者和提醒", "input_schema": { "type": "object", "properties": { "title": {"type": "string", "description": "事件標題"}, "start_time": { "type": "string", "description": "ISO 8601 日期時間格式,例如 2024-03-15T14:00:00Z" }, "duration_minutes": {"type": "integer", "description": "事件持續時間(分鐘)"}, "attendees": { "type": "array", "items": {"type": "string"}, "description": "與會者的電子郵件地址" } }, "required": ["title", "start_time", "duration_minutes"] }, "input_examples": [ { "title": "團隊站立會議", "start_time": "2024-03-15T09:00:00Z", "duration_minutes": 30, "attendees": ["[email protected]", "[email protected]"] }, { "title": "快速通話", "start_time": "2024-03-15T14:00:00Z", "duration_minutes": 15 }, { "title": "專案審查", "start_time": "2024-03-15T16:00:00-05:00", "duration_minutes": 60, "attendees": ["[email protected]"] } ] }
範例設計原則:
- 使用真實數據,而非佔位符
- 展示最小、部分和完整規格模式
- 保持簡潔:每個工具 1-5 個範例
- 專注於模糊不清的情況
工具錯誤處理
回傳能幫助 LLM 恢復的錯誤資訊
使用時機:任何可能失敗的工具
錯誤處理最佳實踐:
回傳資訊豐富的錯誤
""" 錯誤示範: {"error": "Failed"} {"error": true}
正確示範: { "error": true, "error_type": "not_found", "message": "在天氣資料庫中找不到位置 'Atlantis'。請提供一個真實的城市名稱,例如 'San Francisco, CA'。", "suggestions": ["San Francisco, CA", "Los Angeles, CA"] } """
Anthropic 工具結果(含錯誤)
""" { "type": "tool_result", "tool_use_id": "toolu_01A09q90qw90lq917835lq9", "content": "Error: 在天氣資料庫中找不到位置 'Atlantis'。請提供一個真實的城市名稱,例如 'San Francisco, CA'。", "is_error": true } """
需要處理的錯誤類別
"""
-
輸入驗證錯誤
- 缺少必填參數
- 格式無效
- 數值超出範圍
-
外部服務錯誤
- API 不可用
- 速率限制
- 超時
-
業務邏輯錯誤
- 資源未找到
- 權限不足
- 衝突/重複
-
內部錯誤
- 未預期的例外
- 數據損毀 """
實作模式
""" from dataclasses import dataclass from typing import Union
@dataclass class ToolResult: success: bool content: str error_type: str = None suggestions: list[str] = None
def to_response(self) -> dict:
if self.success:
return {"content": self.content}
return {
"content": f"Error ({self.error_type}): {self.content}",
"is_error": True
}
def get_weather(location: str) -> ToolResult: # 驗證輸入 if not location or len(location) < 2: return ToolResult( success=False, content="位置必須至少包含 2 個字元", error_type="validation_error" )
try:
data = weather_api.fetch(location)
return ToolResult(
success=True,
content=f"溫度:{data.temp}°F,天氣狀況:{data.conditions}"
)
except LocationNotFound:
return ToolResult(
success=False,
content=f"找不到位置 '{location}'",
error_type="not_found",
suggestions=weather_api.suggest_locations(location)
)
except RateLimitError:
return ToolResult(
success=False,
content="天氣服務速率限制超標。請在 60 秒後重試。",
error_type="rate_limit"
)
except Exception as e:
return ToolResult(
success=False,
content=f"未預期的錯誤:{str(e)}",
error_type="internal_error"
)
"""
MCP 工具模式
使用 Model Context Protocol 建構工具
使用時機:建立可重複使用的跨平台工具
MCP 工具實作:
""" MCP (Model Context Protocol) 是 Anthropic 提出的開放標準, 用於將 AI 代理連接到外部系統。一次建構,處處可用。 """
基本 MCP 伺服器 (TypeScript)
""" import { Server } from "@modelcontextprotocol/sdk/server"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio";
const server = new Server({ name: "weather-server", version: "1.0.0" });
// 定義工具 server.setRequestHandler("tools/list", async () => ({ tools: [ { name: "get_weather", description: "取得指定位置的天氣。回傳溫度、天氣狀況和濕度。用於查詢特定城市的天氣。", inputSchema: { type: "object", properties: { location: { type: "string", description: "城市和州,例如 'San Francisco, CA'" }, unit: { type: "string", enum: ["celsius", "fahrenheit"], default: "fahrenheit" } }, required: ["location"] } } ] }));
// 處理工具呼叫 server.setRequestHandler("tools/call", async (request) => { const { name, arguments: args } = request.params;
if (name === "get_weather") {
try {
const weather = await fetchWeather(args.location, args.unit);
return {
content: [
{
type: "text",
text: JSON.stringify(weather)
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: Error: ${error.message}
}
],
isError: true
};
}
}
throw new Error(Unknown tool: ${name});
});
// 啟動伺服器 const transport = new StdioServerTransport(); await server.connect(transport); """
MCP 的優勢
"""
- 跨 LLM 供應商的通用相容性
- 可重複使用的工具庫
- 支援串流和 SSE 傳輸
- 內建可觀測性
- 工具存取控制 """
工具執行器模式
使用 SDK 工具執行器進行自動處理
使用時機:建構無需手動管理的工具循環
工具執行器(Anthropic SDK Beta):
""" 工具執行器會自動處理工具呼叫循環:
- 在 Claude 呼叫工具時執行它們
- 管理對話狀態
- 處理錯誤重試
- 提供串流支援 """
Python 範例
""" import anthropic from anthropic import beta_tool
client = anthropic.Anthropic()
@beta_tool def get_weather(location: str, unit: str = "fahrenheit") -> str: '''在指定位置取得當前天氣。
Args:
location: 城市和州,例如 San Francisco, CA
unit: 溫度單位,'celsius' 或 'fahrenheit'
'''
# 實作
return json.dumps({"temperature": "72°F", "conditions": "Sunny"})
@beta_tool def search_web(query: str) -> str: '''搜尋網路資訊。
Args:
query: 搜尋查詢
'''
# 實作
return json.dumps({"results": [...]})
工具執行器處理循環
runner = client.beta.messages.tool_runner( model="claude-sonnet-4-5", max_tokens=1024, tools=[get_weather, search_web], messages=[ {"role": "user", "content": "巴黎的天氣如何?"} ] )
處理每條訊息
for message in runner: print(message.content[0].text)
或直接取得最終結果
final = runner.until_done() """
TypeScript 搭配 Zod
""" import { Anthropic } from '@anthropic-ai/sdk'; import { betaZodTool } from '@anthropic-ai/sdk/helpers/beta/zod'; import { z } from 'zod';
const anthropic = new Anthropic();
const getWeatherTool = betaZodTool({ name: 'get_weather', description: '在指定位置取得當前天氣', inputSchema: z.object({ location: z.string().describe('城市和州,例如 San Francisco, CA'), unit: z.enum(['celsius', 'fahrenheit']).default('fahrenheit') }), run: async (input) => { // 類型安全的輸入! return JSON.stringify({temperature: '72°F'}); } });
const runner = anthropic.beta.messages.toolRunner({ model: 'claude-sonnet-4-5', max_tokens: 1024, tools: [getWeatherTool], messages: [{ role: 'user', content: "巴黎的天氣如何?" }] });
for await (const message of runner) { console.log(message.content[0].text); } """
平行工具執行
同時執行多個工具
使用時機:可以平行執行的獨立工具呼叫
平行工具執行:
""" 預設情況下,Claude 可以在一次回應中呼叫多個工具。 這能顯著降低獨立操作的延遲。 """
處理平行結果
"""
Claude 回傳多個 tool_use 區塊:
response.content = [ {"type": "text", "text": "我來檢查這兩個地點..."}, {"type": "tool_use", "id": "toolu_01", "name": "get_weather", "input": {"location": "San Francisco, CA"}}, {"type": "tool_use", "id": "toolu_02", "name": "get_weather", "input": {"location": "New York, NY"}}, {"type": "tool_use", "id": "toolu_03", "name": "get_time", "input": {"timezone": "America/Los_Angeles"}}, {"type": "tool_use", "id": "toolu_04", "name": "get_time", "input": {"timezone": "America/New_York"}} ]
在 Python 中處理:
import asyncio
async def handle_tool_call(tool_call): if tool_call.name == "get_weather": return await get_weather(**tool_call.input) elif tool_call.name == "get_time": return await get_time(**tool_call.input) # 其他工具...
async def execute_parallel(tool_calls): # 同時執行所有工具 results = await asyncio.gather( *[handle_tool_call(tc) for tc in tool_calls] ) # 給每個結果加上對應的 tool_use_id return [ {"tool_use_id": tc.id, "content": result} for tc, result in zip(tool_calls, results) ]
使用
all_results = await execute_parallel(response.content.tool_calls) """
平行執行常見陷阱
"""
- 速率限制:同時發送大量請求可能觸發 API 限制
- 副作用順序:若工具之間有依賴關係,請勿平行執行
- 錯誤放大:一個工具失敗時要決定是否取消其他任務
- 狀態一致性:確保平行呼叫不會造成資料競爭 """
工具清單管理
管理代理的工具集合
使用時機:隨著代理發展而組織和維護工具有效性
工具清單管理:
""" 工具清單是代理能力與使用者體驗之間的橋樑。 管理不善的工具會讓代理困惑,並導致不良的使用者體驗。 """
工具清單大小
""" 研究顯示,太多的工具會顯著降低 LLM 的決策品質:
- < 10 個工具:最佳準確率(>85%)
- 10-20 個工具:可接受的準確率(70-85%)
- 20-30 個工具:快速下降的表現(50-70%)
-
30 個工具:接近隨機選擇(<50%)
因此,請優先考慮工具品質而非數量。 """
工具命名約定
""" 好的命名規則可幫助 LLM 更快找到正確的工具:
✓ 好範例:
- search_web - 說明做什麼
- get_current_weather - 包含上下文
- create_calendar_event - 使用動詞-名詞格式
✗ 壞範例:
- web - 過於模糊
- weather_info - 不確定是取得還是設定
- func123 - 對 LLM 毫無意義
使用一致的模式:動詞_名詞(例如 get_stock_price, send_email) """
工具分組
當工具數量超過 10 個時,考慮建立子集:
""" 核心工具(始終可用 - 5 個工具):
- search_web
- get_current_time
- read_file
- write_file
- execute_code
進階工具(按需載入):
- 資料庫查詢工具
- 檔案系統操作工具
- API 整合工具 """
使用情境的系統提示
在代理的系統提示中引導工具選擇:
"""
可用工具分類
用於回答一般性問題:
- search_web, get_weather, get_time
用於程式碼和檔案操作:
- read_file, write_file, execute_code
用於資料庫操作:
- query_database, create_record, update_record
當用戶的問題涉及多個類別時,優先使用相關類別中的工具。 """
工具除錯
診斷和修復工具問題
使用時機:當代理持續選擇錯誤的工具或產生無效的輸入時
工具除錯:
""" 工具問題通常表現為代理行為不如預期。 以下是四種最常見的問題模式及其修復方法: """
問題 1:代理未使用正確的工具
""" 徵兆:代理以文字回答,而非使用正確工具 或者持續使用錯誤工具
原因:
- 工具名稱不明確
- 描述語氣暗示不應使用此工具
- 有重疊的工具
修復方法:檢查工具名稱和描述:
- 名稱應明確表達用途
- 描述中應包含使用此工具的具體情境
- 如果兩個工具功能相似,考慮合併或刪除一個 """
問題 2:代理持續產生無效的參數
""" 徵兆:代理產生的參數總是缺少某個鍵, 或格式錯誤,或值超出範圍
原因:
- Schema 不明確
- 描述中缺少範例
- 預設值未指定
修復方法:
- 在描述中加入範例值
- 對字串使用 pattern 屬性
- 對數值使用 minimum/maximum
- 對集合使用 enum
- 設定 additionalProperties: false """
問題 3:代理從工具錯誤中無法恢復
""" 徵兆:代理在收到錯誤後重複呼叫同一個工具 或放棄並停止處理
原因:
- 錯誤訊息未提供如何修復的指引
- 錯誤訊息過於技術性
修復方法:
- 在錯誤訊息中包含具體的修復步驟
- 如果可能,提供建議值
- 使用標準的錯誤結構 """
問題 4:代理變得緩慢或昂貴
""" 徵兆:回應時間長、token 使用量高
原因:
- 不必要的工具被包含在清單中
- 工具回傳過多的數據
- 代理執行不必要的步驟
修復方法:
- 減少工具數量
- 從工具回傳的文字中移除冗餘資訊
- 考慮將複雜任務拆分為更簡單的工具
- 使用快取來避免重複計算 """
除錯工具
可用於診斷問題的工具:
"""
-
日誌所有工具呼叫
- 記錄每個工具呼叫的輸入和輸出
- 記錄時間戳記和持續時間
- 記錄錯誤和重試
-
監控 token 使用量
- 注意異常高的使用量
- 比較不同工具的成本
-
A/B 測試不同的描述
- 比較準確率和 token 成本
- 迭代測試
-
使用代理模擬進行測試
- 用不同的提示測試工具
- 邊界測試 """
結語
回憶一下技巧:
工具描述比工具實作更重要
只要代理無法讀取你的程式碼, Schema 和描述就是它僅有的資訊來源。 花時間讓它們保持清晰、精準且有用。
驗證閘道應在執行之前:拒絕、修正或升級,絕不靜默失敗
對於代理,靜默失敗比明顯的錯誤更糟。 至少要提供足夠的資訊,讓代理知道何時該求助。
保持工具數量少於 20 個
不是每個函數都需要成為一個 LLM 工具。 篩選你的工具:只暴露那些對使用者任務至關重要的工具。