如何在 AI 智慧體中測試工具呼叫準確率
How to Test Tool-Calling Accuracy in AI Agents
本文介紹了評估 AI 智慧體工具呼叫準確率的三種核心方法,並提供透過 OpenRouter 跨模型進行測試的實作範例。
- 三大測試方法:包含無參考答案的 LLM 評審、確定性引數檢查(使用 JSON Schema)、以及軌跡比較。
文章提供評估工具呼叫準確率的三種方法與程式碼範例,工程師可據此建立跨模型評測套件。
代理在使用工具時可能在兩個地方失敗。它可能選錯工具,或選對工具卻傳遞錯誤的引數。
這些失敗會告訴你不同的問題。若代理呼叫 lookup_order 而非 refund_order,問題在於工具選擇。若它呼叫 refund_order 並傳遞錯誤的 order_id,則選對了工具但傳遞了錯誤的引數。
本指南說明測試工具呼叫行為的三種方法。第一種是無參考的大型語言模型(LLM)評分器。第二種是確定性引數檢查。第三種是軌跡比較。接著示範如何透過 OpenRouter 對多個具備工具功能的模型執行相同的測試案例。
簡而言之
- 分別測試工具選擇與引數正確性。代理可能選錯工具、無必要地呼叫工具,或選對工具卻傳遞錯誤引數。
- 當你知道預期的工具或引數值時,使用確定性檢查。若正確性取決於上下文或多個選項皆可行,則使用無參考 LLM 評分器。
- 使用 JSON Schema 檢測語法錯誤或結構不合法的引數,並分別檢查引數值。即使符合 schema,值仍可能錯誤。
- 當工具呼叫順序重要時,使用軌跡比較。若多條路徑能達到相同有效結果,則不必要求完全相同的順序。
- 比較模型時,保持所有測試案例、評分規則、模型設定與路由配置一致。
兩種需要不同測試的失敗模式
先檢視工具決策本身,再檢查模型產生的呼叫。
工具選擇
假設內部支援代理可使用 lookup_order、issue_refund 與 search_docs。
若使用者詢問退款政策,search_docs 是適當工具;若詢問退款訂單 ord_7281,代理可能需先查詢訂單再執行退款。測試集亦應包含模型已擁有足夠資訊可回答且不應呼叫工具的情境。
不呼叫工具的情況很重要,因為僅檢查回覆是否包含 tool_calls 已不足夠。即使模型呼叫了不必要或錯誤的函式,也會產生工具呼叫。
當明確預期使用單一工具時,可在程式碼中比較回傳的工具名稱與預期名稱。若多個工具皆能合理處理請求,精確匹配可能拒絕有效選擇,此時 LLM 評分器更為有用。
引數正確性
模型選擇工具後,檢查其產生的引數。此檢查分為結構與值兩部分。
結構驗證可捕捉語法錯誤、遺漏必要欄位、錯誤型別、無效列舉值以及工具不接受的引數。
結構合法的呼叫仍可能包含錯誤值。
{
"order_id": "ord_7282"
}若 order_id 被定義為字串,該負載符合 schema,但若使用者詢問 ord_7281,仍然錯誤。
現有評估框架亦作同樣區分。DeepEval 擁有獨立的 Tool Correctness 與 Argument Correctness 指標,Phoenix 亦有針對 tool selection 的獨立評估器。
方法一:無參考 LLM 評分器
無參考評分器在沒有固定預期答案的情況下評分工具呼叫。此方法在正確性依賴上下文或多個選項皆可行時特別有用,因為無法在程式碼中比較單一值…
在工具選擇時,將使用者的請求、代理可用的工具以及模型的輸出交給評審。接著請它判斷所選工具是否適當,包括模型是否本應避免使用工具。
考慮一個同時具備 web_search 與 search_internal_docs 的研究代理。可能不存在唯一正確的選擇。更佳的工具取決於使用者的需求以及對話中已有的資訊。
相同的問題也出現在引數上。搜尋查詢、描述或日期範圍可能符合 schema,卻仍無法表達使用者的意圖。若無固定可比對的值,評審可直接評估其含義。
即使是無參考的評審,也需要明確的正確標準說明。在比較候選模型時保持這些說明與評審模型不變,並先將評審的判定與自己審閱過的一小部分案例做比對,確定其準確性後再擴充套件至整個資料集。
若等值檢查、schema 驗證器或業務規則能可靠回答同樣問題,請改用它們。
方法二:對引數進行確定性 schema 檢查
並非所有引數錯誤都需再次呼叫模型。若工具 schema 能證明失敗,請在程式碼中進行驗證。
請參考此工具定義。
tools = [
{
"type": "function",
"function": {
"name": "lookup_order",
"description": "Look up an order by its ID.",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"include_items": {"type": "boolean"},
},
"required": ["order_id"],
"additionalProperties": False,
},
},
}
]同一份送給模型的 schema 亦可驗證其返回的引數。它能捕捉缺失的 order_id、include_items 期待布林值卻收到字串、或未宣告的欄位如 customer_email。
方法三:軌跡比較
工具選擇與引數檢查涵蓋單一呼叫。多步驟代理亦可能在呼叫序列中失敗。若該序列屬於需求,軌跡比較可直接測試。
退費工作流程可能需要按順序執行三次呼叫。
lookup_order
↓
verify_refund_eligibility
↓
issue_refund僅在需要順序時,嚴格匹配才有意義。LangSmith 的 trajectory evaluators 因此支援嚴格、非順序、子集與超集匹配。嚴格檢查強制執行單一序列;其他模式則接受不同順序或僅要求特定呼叫集合。
代理基準測試同樣處理此情況。在 τ²-bench 中,記錄的動作清單即為一條參考軌跡,透過重放可推匯出目標資料庫最終狀態。任何產生等效最終狀態的工具呼叫序列皆通過資料庫檢查。
若 lookup_customer 與 lookup_subscription 可任意順序發生,切勿因參考使用另一順序而失敗。改以評估所需呼叫或最終狀態為準。
| 評估方法 | 檢查專案 | 最佳適配 |
|---|---|---|
| 無參考 LLM 評審 | 工具選擇或引數值是否符合上下文 | 無法機械檢查的決策 |
| JSON Schema 驗證 | JSON 結構、必填欄位、型別、列舉與未宣告欄位 | 返回呼叫的結構驗證 |
| 軌跡比較 | 呼叫了哪些工具,以及必要時的順序 | 已知預期路徑的工作流程 |
測試案例可同時使用多項檢查。例如,你可以比較工具名稱、依 JSON Schema 驗證其引數,然後將已知引數值與預期有效載荷做比對。
在多個模型上執行相同評估
定義好測試案例與評分器後,即可在每個候選模型上執行相同的測試環境。
我們在支援的模型中提供單一 tool-calling interface,因此不需要為每個想比較的模型做獨立的供應商整合。
範例將 tool_choice 設為 "auto"。這是在你提供工具時的預設值,明確設定它可使無工具測試更易跟隨。
此範例使用 OpenAI Python SDK 與我們的 OpenAI 相容端點。先安裝相依套件。
pip install openai jsonschema在環境中設定 OPENROUTER_API_KEY,然後對每個候選模型執行相同的測試案例。
import json
import os
from jsonschema import Draft7Validator, ValidationError
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
)
tools = [
{
"type": "function",
"function": {
"name": "lookup_order",
"description": "Look up an order by its ID.",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string"},
},
"required": ["order_id"],
"additionalProperties": False,
},
},
}
]
tool_schemas = {
tool["function"]["name"]: tool["function"]["parameters"]
for tool in tools
}
test_cases = [
{
"name": "known order",
"messages": [
{
"role": "user",
"content": "Check the status of order ord_7281.",
}
],
"expected_calls": [
{
"name": "lookup_order",
"arguments": {"order_id": "ord_7281"},
}
],
},
{
"name": "no tool needed",
"messages": [
{
"role": "user",
"content": "What does an order status of 'shipped' mean?",
}
],
"expected_calls": [],
},
]
models = [
"anthropic/claude-opus-5",
"openai/gpt-5.6-sol",
"moonshotai/kimi-k3",
]
def grade_case(model, case):
response = client.chat.completions.create(
model=model,
messages=case["messages"],
tools=tools,
tool_choice="auto",
extra_body={
"reasoning": {"effort": "low"},
"provider": {"require_parameters": True},
},
)
calls = response.choices[0].message.tool_calls or []
expected_calls = case["expected_calls"]
actual_names = [call.function.name for call in calls]
expected_names = [call["name"] for call in expected_calls]
tool_selection = actual_names == expected_names
schema_results = []
parsed_calls = []
for call in calls:
name = call.function.name
schema = tool_schemas.get(name)
if schema is None:
schema_results.append(False)
continue
try:
arguments = json.loads(call.function.arguments)
Draft7Validator(schema).validate(arguments)
except (json.JSONDecodeError, ValidationError):
schema_results.append(False)
continue
schema_results.append(True)
parsed_calls.append(
{
"name": name,
"arguments": arguments,
}
)
schema_valid = all(schema_results) if calls else None
argument_values = None
if expected_calls:
argument_values = (
schema_valid is True
and parsed_calls == expected_calls
)
if expected_calls:
passed = (
tool_selection
and schema_valid is True
and argument_values is True
)
else:
passed = tool_selection
return {
"tool_selection": tool_selection,
"schema_valid": schema_valid,
"argument_values": argument_values,
"passed": passed,
}
for model in models:
results = [
grade_case(model, case)
for case in test_cases
]
passed = sum(result["passed"] for result in results)
print(f"{model}: {passed}/{len(results)} cases passed")
for case, result in zip(test_cases, results):
print(f" {case['name']}: {result}")正確的無工具案例會計入工具選擇與整體結果,且沒有要評分的 schema 或引數負載。對於返回工具的案例,測試環境會先驗證每一次呼叫,再將返回值與預期負載進行比較。
測試環境為每個候選模型將 reasoning.effort 設為 low,以確保預設推理努力的差異不會顯示為工具呼叫準確度的差異。上述三個模型皆在其 models 端點的 reasoning 物件的 supported_efforts 陣列中列出 low。它也將 provider.require_parameters 設為 true。模型的 supported_parameters 清單可能包含僅某些該模型供應商端點可接受的引數,且在預設路由下,未支援該引數的供應商仍會收到請求並忽略它。設定 require_parameters 後,我們僅將請求路由至支援其所有引數的供應商,因而每個評分回應都以測試環境要求的努力執行。請參閱 供應商路由 瞭解該欄位。測試環境未設定 temperature,因為 openai/gpt-5.6-sol 未在 supported_parameters 中列出 temperature。若你清單中的每個候選皆接受 temperature,亦請明確設定它。
上述模型 ID 為範例。models 端點中的每個條目都有一個 supported_parameters 陣列。當該陣列包含 tools 與 tool_choice 時,模型即支援此測試環境。於在長期評估套件中確定候選清單前,請先檢查目前的 工具呼叫模型集合。
若要進行實際比較,請多次執行每個測試案例,避免模型分數僅基於單一回應。更大的測試套件亦應包含應用程式會遇到的較難案例,例如缺少引數、相似工具說明、多次呼叫,以及不應使用工具的請求。
此測試環境評估單一工具呼叫回合。對於多步驟代理,請收集整個追蹤中的呼叫,並根據工作流程需求比較呼叫序列或最終狀態。
供應商路由亦會影響你比較的指標。Auto Exacto 預設會在每個包含工具的請求上執行,並為你選擇的模型重新排序供應商,因而可能改變提供工具呼叫請求的供應商端點。其輸入之一為工具呼叫錯誤率。對於每個包含工具的請求,我們檢查模型回傳的每一次工具呼叫,並將結構性失敗分類為 InvalidJson、UnknownName 或 SchemaMismatch,同時將 arguments 驗證 against 你在 JSON Schema Draft 7 下提供的 parameters schema。此指標測量供應商行為。它並未取代你自己測試環境中的本地 schema 驗證,這也是為何上述範例會自行驗證引數。
若你想測試應用程式在正式環境中將使用的路由設定,請為每個候選保留 Auto Exacto 啟用。若想進行端點層級比較,請使用我們的 供應商路由控制 來固定供應商。將 order 欄位設為 provider 物件中的該供應商 slug,並將 allow_fallbacks 設為 false,以確保每筆請求都送往同一個端點。
保持提示、工具、測試案例、評審模型與評估標準在各次執行間保持一致。明確設定抽樣與推理引數,例如 temperature 與 reasoning,不要依賴預設值,並檢查每個候選模型是否支援你設定的引數。不要在主控程式中設定 max_tokens。回應被截斷可能會切掉工具呼叫的 JSON,並顯示為 InvalidJson 失敗,這與模型的工具選擇無關。
如果你不想自行維護跨模型執行器,Ori Eval 會將你的代理機器人對抗候選模型,斷言其呼叫的工具與避免的工具,並以 LLM 評審對開放式答案進行評分。
常見錯誤
若測試案例或評分規則過於狹窄,工具呼叫評估可能會給你誤導性的結果。
- 僅測試乾淨請求。 包含缺失資訊、相似工具、無需呼叫工具的案例,以及模型應該詢問值而非自行編造的提示。
- 僅檢查
tool_calls[0]。 回應可能包含多個呼叫,請評分完整陣列。 - 將有效負載視為正確負載。 模式驗證無法告訴你有效值是否為正確的客戶、訂單、日期或金額。
- 在模型之間更改評估。 若工具、提示、評審、模型設定或路由政策在不同執行間變更,你將不再進行相同的比較。
來源:openrouter blog · openrouter.ai