Repository navigation
tool_trajectory_avg_score returns 0.0 even when tool name and args match exactly #5410
Description
Summary
The TrajectoryEvaluator in ADK is returning a score of 0.0 for tool trajectory evaluation even when the expected and actual tool calls have identical name and args fields.
Expected Behavior
According to the trajectory_evaluator.py code, the comparison only checks name and args:
def _are_tool_calls_exact_match(self, actual_tool_calls, expected_tool_calls):
for actual, expected in zip(actual_tool_calls, expected_tool_calls):
if actual.name != expected.name or actual.args != expected.args:
return False
return TrueWhen both name and args match exactly, the score should be 1.0, not 0.0.
Actual Behavior
The tool_trajectory_avg_score metric returns 0.0 (FAILED) even when:
- ✅ Tool name matches:
execute_sql - ✅ Tool args are identical:
{'project_id': '[GCP_PROJECT_ID]', 'query': '[SQL_QUERY]'} - ✅ All arguments match exactly
Steps to Reproduce
1. Create Test Case File
Create a test file with InvocationEvents format containing a BigQuery SQL execution:
{
"eval_set_id": "test_trajectory",
"name": "test_trajectory",
"eval_cases": [
{
"eval_id": "sql_execution_test",
"conversation": [
{
"invocation_id": "test-invocation-001",
"user_content": {
"parts": [{"text": "Execute a query"}],
"role": "user"
},
"final_response": {
"parts": [{"text": "Query executed successfully with results..."}],
"role": "model"
},
"intermediate_data": {
"invocation_events": [
{
"author": "test_agent",
"content": {
"parts": [
{"text": "Executing SQL query..."},
{
"function_call": {
"name": "execute_sql",
"args": {
"project_id": "[YOUR_GCP_PROJECT_ID]",
"query": "SELECT * FROM [your_table] WHERE [condition]"
}
}
}
],
"role": "model"
}
},
{
"author": "test_agent",
"content": {
"parts": [
{
"function_response": {
"name": "execute_sql",
"response": {
"status": "SUCCESS",
"rows": [{"sample": "data"}]
}
}
}
],
"role": "user"
}
}
]
}
}
],
"session_input": {
"app_name": "test_agent",
"user_id": "user"
}
}
]
}2. Create Config File
Create test_config.json:
{
"criteria": {
"tool_trajectory_avg_score": {
"threshold": 0.5,
"match_type": "ANY_ORDER"
},
"response_match_score": 0.5
}
}3. Run Evaluation
adk eval test_agent test_case.evalset.json --config_file_path=test_config.json --print_detailed_results4. Observe Results
The evaluation will fail with:
Metric: tool_trajectory_avg_score, Status: FAILED, Score: 0.0, Threshold: 0.5
Expected tool calls:
id=None args={'project_id': '[GCP_PROJECT_ID]', 'query': '[SQL]'}
Actual tool calls:
id='toolu_vrtx_01...' args={'project_id': '[GCP_PROJECT_ID]', 'query': '[SQL]'}
Both args dictionaries are IDENTICAL, but the score is 0.0
Expected vs Actual Comparison
Expected Tool Call
{
"id": None,
"name": "execute_sql",
"args": {
"project_id": "[GCP_PROJECT_ID]",
"query": "[SQL_QUERY_STRING]"
}
}Actual Tool Call
{
"id": "[DYNAMICALLY_GENERATED_ID]",
"name": "execute_sql",
"args": {
"project_id": "[GCP_PROJECT_ID]",
"query": "[SQL_QUERY_STRING]"
}
}Comparison Result
| Field | Expected | Actual | Match |
|---|---|---|---|
name |
execute_sql |
execute_sql |
✅ YES |
args.project_id |
[GCP_PROJECT_ID] |
[GCP_PROJECT_ID] |
✅ YES |
args.query |
[SQL_QUERY_STRING] |
[SQL_QUERY_STRING] |
✅ YES |
| Expected Result | Score: 1.0 | Score: 0.0 | ❌ MISMATCH |
Affected Code
The issue appears to be in:
- File:
google/adk/evaluation/trajectory_evaluator.py - Function:
_are_tool_calls_exact_match()(Line ~180-190) - Function:
_calculate_tool_use_accuracy()(Line ~130-150)
Code Review
The logic in _are_tool_calls_exact_match() appears correct:
def _are_tool_calls_exact_match(self, actual_tool_calls, expected_tool_calls):
if len(actual_tool_calls) != len(expected_tool_calls):
return False
for actual, expected in zip(actual_tool_calls, expected_tool_calls):
if actual.name != expected.name or actual.args != expected.args:
return False
return TrueHowever, the comparison might fail due to:
- Type Mismatch:
actual.argsandexpected.argsmight be different types - Dictionary Comparison Issue: Args might contain nested objects that don't compare as equal
- Object Reference Issue: Comparing object references instead of values
- Serialization Issue: JSON deserialization creating different object types
Environment Information
- ADK Version: Latest
- Python Version: 3.12+
- OS: Windows/Linux/Mac
- ADK Components:
google-adk[eval] - Tool Used: BigQuery Toolset (but likely affects all tools)
Related Issues
This issue appears related to:
- Why are tools aware of agents? #266 - Loading evaluation results fails if trajectory check doesn't pass
- Agent Evaluation using pytest is using id for
tool_trajectory_avg_scorethat can never be predicted #3434 -idfield in tool calls being compared despite being dynamically generated - Agent Evaluation not evaluating the tool trajectory properly #3439 - Tool trajectory score returns 0 instead of partial score when arguments don't match
Additional Context
This bug blocks reliable tool trajectory testing in ADK evaluation. Even when tools are called correctly with identical arguments, the evaluation fails.
Note: This issue report contains placeholder values for sensitive information:
[GCP_PROJECT_ID]= your actual GCP project ID[SQL_QUERY_STRING]= your actual SQL query[YOUR_TABLE]= your actual table names
Activity
surajksharma07 commentedon Apr 23, 2026
@Maithreya-100 Thanks for the detailed report — the comparison logic in _are_tool_calls_exact_match is actually fine; the real issue is one level up in convert_events_to_eval_invocations, where function-call events get dropped from invocation_events when skip_summarization or long_running_tool_ids is set, leaving the actual tool call list empty before comparison even happens.
Worth trying as a workaround: add or (e.content and any(p.function_call for p in e.content.parts)) to the list-comprehension guard on line 318 of evaluation_generator.py and see if that fixes the 0.0 score on your end.
Could you test it against your BigQuery eval case and confirm it's solid before we move ahead with the PR review on #5417?
fix(eval): include function-call events in invocation_events when ski…
fix(eval): include function-call events in invocation_events when ski…
surajksharma07 commentedon May 22, 2026
@Maithreya-100 — since we haven't heard back after sharing the workaround around Apr 22 and asking you to verify it against your BigQuery eval case, going ahead and closing this out to keep the queue clean.
The underlying fix — preserving function-call events in invocation_events even when is_final_response() returns True due to skip_summarization or long_running_tool_ids — is tracked in PR #5417 from @Koushik-Salammagari, so the work will continue there.
If you do get a chance to test the workaround and it holds up, or if you run into anything new, feel free to drop a note on #5417 or reopen this — either works!
fix(eval): include function-call events in invocation_events when ski…
fix(eval): include function-call events in invocation_events when ski…
fix(eval): include function-call events in invocation_events when ski…