11import re
2- from collections import Counter
32from pathlib import Path
43from typing import Sequence
54
87
98logger = get_logger (__name__ )
109
11- # Regex to find tool call function names in the log content
12- # Matches tool calls (with "arguments") but NOT tool definitions (with "description")
13- # Pattern: "function": {"name": "tool_name", "arguments": ...}
14- TOOL_CALL_PATTERN = re .compile (
15- r'"function"\s*:\s*\{\s*"name"\s*:\s*"([^"]+)"\s*,\s*"arguments"' ,
16- re .MULTILINE ,
17- )
18-
1910# Regex to count LLM requests (turns) in the log
2011# Each "--- Start of group: Sending request to the AI model ---" indicates a new LLM call
2112TURN_COUNT_PATTERN = re .compile (r"--- Start of group: Sending request to the AI model ---" )
@@ -29,23 +20,9 @@ def _parse_token_count(s: str) -> int:
2920 return int (float (s ))
3021
3122
32- def parse_session_log (log_path : Path ) -> tuple [dict [str , int ], int ]:
33- """Parse tool usage and step count from a single Copilot CLI log file.
34-
35- The log file format is timestamped text with embedded JSON responses.
36- Tool calls appear in response JSON under choices[].message.tool_calls[].
37- Step count is determined by counting LLM requests.
38-
39- Args:
40- log_path: Path to the Copilot CLI log file
41-
42- Returns:
43- Tuple of (tool_usage dict mapping tool names to call counts, turn_count)
44- """
23+ def parse_turn_count_from_log (log_path : Path ) -> int :
4524 content = log_path .read_text (encoding = "utf-8" )
46- tool_usage = dict (Counter (TOOL_CALL_PATTERN .findall (content )))
47- turn_count = len (TURN_COUNT_PATTERN .findall (content ))
48- return tool_usage , turn_count
25+ return len (TURN_COUNT_PATTERN .findall (content ))
4926
5027
5128def parse_metrics (output_lines : Sequence [str ], session_log_path : Path | None = None ) -> AgentMetrics | None :
@@ -81,20 +58,14 @@ def parse_metrics(output_lines: Sequence[str], session_log_path: Path | None = N
8158 llm_duration : float | None = None
8259 prompt_tokens : int | None = None
8360 completion_tokens : int | None = None
84- tool_usage : dict [str , int ] | None = None
8561 turn_count : int | None = None
8662
87- # Parse tool usage and turn count from session log if provided
63+ # Parse turn count from session log if provided
8864 if session_log_path :
8965 try :
90- tool_usage , turn_count = parse_session_log (session_log_path )
91- if not tool_usage :
92- tool_usage = None # Convert empty dict to None
93- if turn_count == 0 :
94- turn_count = None # Convert zero to None
66+ turn_count = parse_turn_count_from_log (session_log_path ) or None
9567 except Exception as e :
96- logger .warning (f"Failed to parse tool usage from { session_log_path } : { e } " )
97- tool_usage = None
68+ logger .warning (f"Failed to parse turn count from { session_log_path } : { e } " )
9869 turn_count = None
9970
10071 try :
@@ -133,14 +104,13 @@ def parse_metrics(output_lines: Sequence[str], session_log_path: Path | None = N
133104 prompt_tokens = _parse_token_count (tokens_match .group (1 ))
134105 completion_tokens = _parse_token_count (tokens_match .group (2 ))
135106
136- if execution_time is not None or llm_duration is not None or prompt_tokens is not None or completion_tokens is not None or tool_usage is not None or turn_count is not None :
107+ if execution_time is not None or llm_duration is not None or prompt_tokens is not None or completion_tokens is not None or turn_count is not None :
137108 return AgentMetrics (
138109 execution_time = execution_time ,
139110 llm_duration = llm_duration ,
140111 turn_count = turn_count ,
141112 prompt_tokens = prompt_tokens ,
142113 completion_tokens = completion_tokens ,
143- tool_usage = tool_usage ,
144114 )
145115
146116 logger .warning ("No metrics found in output" )
0 commit comments