1+ import json
2+
3+ def compare_tool_calls (expected_calls , actual_calls ):
4+ """
5+ Compare expected tool calls with actual tool calls, allowing for flexible matching.
6+
7+ Args:
8+ expected_calls (list): List of expected tool call dictionaries
9+ actual_calls (list): List of actual tool call dictionaries
10+
11+ Returns:
12+ tuple: (bool, str) - (success, reason)
13+ """
14+ if len (expected_calls ) != len (actual_calls ):
15+ mismatch_reason = f"Tool call count mismatch: Expected { len (expected_calls )} , got { len (actual_calls )} "
16+ print (mismatch_reason )
17+ return False , mismatch_reason
18+
19+ # Create a copy of actual calls that we can mark as matched
20+ remaining_actual_calls = actual_calls .copy ()
21+
22+ for i , expected in enumerate (expected_calls ):
23+ print (f"\n Checking expected tool call { i + 1 } :" )
24+ print (f"Expected: { json .dumps (expected , indent = 2 )} " )
25+
26+ expected_tool_name = expected ['tool_name' ]
27+ expected_input = json .loads (expected ['tool_input' ]) if isinstance (expected ['tool_input' ], str ) else expected ['tool_input' ]
28+ expected_status = expected ['tool_output' ]['status' ]
29+
30+ # Find a matching actual call
31+ match_found = False
32+ matched_call_index = - 1
33+
34+ for j , actual in enumerate (remaining_actual_calls ):
35+ if actual is None : # Skip already matched calls
36+ continue
37+
38+ actual_tool_name = actual ['tool_name' ]
39+ actual_input = json .loads (actual ['tool_input' ]) if isinstance (actual ['tool_input' ], str ) else actual ['tool_input' ]
40+ actual_status = actual ['tool_output' ]['status' ]
41+
42+ # Check if tool name matches
43+ if expected_tool_name != actual_tool_name :
44+ continue
45+
46+ # Check if tool output status matches
47+ if expected_status != actual_status :
48+ continue
49+
50+ # Check if all expected keys are in actual input
51+ keys_match = True
52+ for key , value in expected_input .items ():
53+ if key not in actual_input :
54+ keys_match = False
55+ break
56+
57+ # For values in angle brackets, just check that the actual value exists
58+ if isinstance (value , str ) and value .startswith ("<" ) and value .endswith (">" ):
59+ if actual_input [key ] is None or actual_input [key ] == "" :
60+ keys_match = False
61+ break
62+ print (f"Placeholder { value } matched with actual value { actual_input [key ]} " )
63+ # For regular values, check for exact match
64+ elif expected_input [key ] != actual_input [key ]:
65+ keys_match = False
66+ break
67+
68+ if keys_match :
69+ match_found = True
70+ matched_call_index = j
71+ print (f"Matched with actual call: { json .dumps (actual , indent = 2 )} " )
72+ if any (isinstance (value , str ) and value .startswith ("<" ) and value .endswith (">" ) for value in expected_input .values ()):
73+ print ("Placeholder values were successfully matched" )
74+ break
75+
76+ if not match_found :
77+ mismatch_reason = f"No matching actual call found for expected call { i + 1 } "
78+ print (mismatch_reason )
79+ return False , mismatch_reason
80+
81+ # Mark the matched call as used
82+ remaining_actual_calls [matched_call_index ] = None
83+
84+ print ("All expected tool calls were matched!" )
85+ return True , "All tool calls match"
0 commit comments