AI 代理與函式呼叫:為 LLM 打造工具
你正在打造一個需要取得即時資料、執行計算或與外部 API 互動的 AI 代理。但你要如何 bridging LLM 的文字生成與實際程式碼執行之間的落差?函式呼叫(也稱為工具使用)就是答案。它讓 LLM 可以要求特定動作,並由你的程式碼執行。本指南將帶你了解如何為 AI 代理設計、實作與除錯函式呼叫。
什麼是函式呼叫?
函式呼叫是一種機制,讓 LLM 可以輸出結構化請求來呼叫你定義的函式。模型不會產生自由文字,而是回傳一個包含函式名稱與引數的 JSON 物件。你的應用程式接著執行該函式,並將結果饋回模型。這讓代理能夠執行文字生成以外的動作,例如查詢資料庫、寄送電子郵件或呼叫 API。
OpenAI、Anthropic 和 Google 等主要 LLM 供應商都支援函式呼叫。核心概念一致:你描述可用的工具,模型決定何時使用它們,而你負責處理執行。
為 LLM 設計工具
設計良好的工具對於可靠代理行為至關重要。請遵循以下原則:
- 清楚的名稱與描述:使用描述性的函式名稱與詳細描述。LLM 依賴這些內容來選擇正確的工具。
- 簡單的參數:讓參數保持最少,並使用標準型別(string、number、boolean、array、object)。盡可能避免複雜的巢狀結構。
- 冪等性:盡可能將工具設計為冪等(可安全重試),以優雅地處理失敗。
- 錯誤處理:回傳有意義的錯誤訊息,讓 LLM 可以調整做法。
範例:天氣工具
以下是 JSON schema 格式的簡單工具定義,常用於 OpenAI 的 API:
{
"name": "get_weather",
"description": "Get the current weather for a given city",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city name, e.g., San Francisco"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit"
}
},
"required": ["city"]
}
}
逐步實作函式呼叫
讓我們使用 OpenAI 的 API 建立一個最小代理迴圈(此模式也適用於其他供應商)。
1. 定義你的工具
建立工具 schema 清單,以及從函式名稱到實際 Python 函式的對應。
import json
import openai
# Tool schemas
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
}
]
# Actual functions
def get_weather(city: str, unit: str = "celsius") -> dict:
# In reality, call a weather API
return {"city": city, "temperature": 22, "unit": unit, "condition": "sunny"}
# Map names to functions
function_map = {
"get_weather": get_weather
}
2. 建立代理迴圈
代理迴圈會將訊息傳送給 LLM、檢查工具呼叫、執行它們,並重複直到模型回傳最終答案。
def run_agent(user_message: str):
messages = [{"role": "user", "content": user_message}]
while True:
response = openai.ChatCompletion.create(
model="gpt-4",
messages=messages,
tools=tools,
tool_choice="auto"
)
message = response.choices[0].message
messages.append(message)
# If no tool calls, return the content
if not message.get("tool_calls"):
return message["content"]
# Execute each tool call
for tool_call in message.tool_calls:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
if function_name in function_map:
result = function_map[function_name](**arguments)
else:
result = {"error": f"Unknown function: {function_name}"}
# Append tool result to messages
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result)
})
此迴圈會持續進行,直到 LLM 產生一個不含工具呼叫的回應,表示它已擁有足夠資訊。
3. 處理錯誤與邊緣情況
真實世界的代理必須處理:
- 無效引數:在解析引數或呼叫函式時捕捉例外。
- 未知工具:向 LLM 回傳錯誤訊息,讓它可以自行修正。
- 逾時:為外部 API 呼叫設定逾時,以避免懸置。
- 速率限制:實作具指數退避的重試。
可靠代理的最佳實務
- 限制工具數量:太多工具會讓模型混淆。將相關函式分組,或使用路由器。
- 驗證輸入:在執行前清理並驗證所有引數。
- 記錄一切:記錄工具呼叫與結果,以供除錯與稽核。
- 以多樣提示測試:確保代理在不同情境下都能選對工具。
- 提供備援:如果某個工具失敗,代理應嘗試替代方案或要求釐清。
函式呼叫支援比較
| 供應商 | 功能名稱 | 格式 |
|---|---|---|
| OpenAI | Function Calling | JSON Schema |
| Anthropic | Tool Use | JSON Schema |
| Function Calling | OpenAPI Schema |
進階模式
隨著代理成長,請考慮以下模式:
- 平行工具呼叫:有些模型可以一次要求多個工具。並行執行它們以提升速度。
- 人類參與迴圈:對於敏感動作(例如匯款),在執行前要求人類核准。
- 記憶:儲存對話歷史與工具結果,以便在長時間工作階段中提供脈絡。
- 工具路由:在呼叫主要 LLM 之前,使用輕量分類器選擇相關工具。
除錯函式呼叫
出錯時,請檢查:
- 工具描述是否清楚且無歧義?
- 參數名稱與型別是否正確?
- 模型是否有足夠脈絡來選擇正確工具?
- 你是否正確處理工具結果(例如 JSON 序列化)?
使用記錄來擷取完整訊息歷史與工具呼叫。問題往往是預期與實際引數之間的不一致。
常見問題
函式呼叫與工具使用有何不同?
它們指的是同一個概念。OpenAI 稱之為「function calling」,而 Anthropic 使用「tool use」。兩者都允許 LLM 要求執行外部函式。
我可以將函式呼叫與開源模型搭配使用嗎?
可以,部分開源模型如 Llama 3.1 支援函式呼叫,而 LangChain 等框架提供抽象層。然而支援程度不一,你可能需要微調或使用特定提示格式。
如何防止 LLM 呼叫危險函式?
絕不要直接暴露危險函式。使用允許清單、驗證輸入,並實作權限檢查。對於敏感操作,在執行前要求人類確認。
準備好打造你自己的 AI 代理了嗎?從定義一個簡單工具並測試代理迴圈開始。如需更多開發者工具,請查看我們的 JSON Formatter 來除錯工具呼叫承載內容。