akseljoonas HF Staff commited on
Commit
fd8e4cd
·
1 Parent(s): 5975e96

Research stats tick every second via background timer

Browse files
agent/tools/research_tool.py CHANGED
@@ -221,25 +221,10 @@ async def research_handler(
221
  except Exception:
222
  pass
223
 
224
- import time as _time
225
-
226
- _start = _time.monotonic()
227
  _tool_uses = 0
228
  _total_tokens = 0
229
 
230
- def _format_stats() -> str:
231
- elapsed = _time.monotonic() - _start
232
- if elapsed < 60:
233
- time_str = f"{elapsed:.0f}s"
234
- else:
235
- time_str = f"{elapsed / 60:.0f}m {elapsed % 60:.0f}s"
236
- if _total_tokens >= 1000:
237
- tok_str = f"{_total_tokens / 1000:.1f}k"
238
- else:
239
- tok_str = str(_total_tokens)
240
- return f"{_tool_uses} tool uses · {tok_str} tokens · {time_str}"
241
-
242
- await _log("stats:" + _format_stats())
243
 
244
  # Run the research loop (max 20 iterations — research should be focused)
245
  max_iterations = 20
@@ -260,13 +245,13 @@ async def research_handler(
260
  # Track tokens
261
  if response.usage:
262
  _total_tokens += response.usage.total_tokens
 
263
 
264
  choice = response.choices[0]
265
  msg = choice.message
266
 
267
  # If no tool calls, we have our final answer
268
  if not msg.tool_calls:
269
- await _log("stats:" + _format_stats())
270
  await _log("Research complete.")
271
  content = msg.content or "Research completed but no summary generated."
272
  return content, True
@@ -309,7 +294,7 @@ async def research_handler(
309
  tool_name, tool_args, session=session
310
  )
311
  _tool_uses += 1
312
- await _log("stats:" + _format_stats())
313
  # Truncate tool output for the research context
314
  if len(output) > 8000:
315
  output = output[:4800] + "\n...(truncated)...\n" + output[-3200:]
 
221
  except Exception:
222
  pass
223
 
 
 
 
224
  _tool_uses = 0
225
  _total_tokens = 0
226
 
227
+ await _log("Starting research sub-agent...")
 
 
 
 
 
 
 
 
 
 
 
 
228
 
229
  # Run the research loop (max 20 iterations — research should be focused)
230
  max_iterations = 20
 
245
  # Track tokens
246
  if response.usage:
247
  _total_tokens += response.usage.total_tokens
248
+ await _log(f"tokens:{_total_tokens}")
249
 
250
  choice = response.choices[0]
251
  msg = choice.message
252
 
253
  # If no tool calls, we have our final answer
254
  if not msg.tool_calls:
 
255
  await _log("Research complete.")
256
  content = msg.content or "Research completed but no summary generated."
257
  return content, True
 
294
  tool_name, tool_args, session=session
295
  )
296
  _tool_uses += 1
297
+ await _log(f"tools:{_tool_uses}")
298
  # Truncate tool output for the research context
299
  if len(output) > 8000:
300
  output = output[:4800] + "\n...(truncated)...\n" + output[-3200:]
agent/utils/terminal_display.py CHANGED
@@ -65,30 +65,73 @@ def print_tool_output(output: str, success: bool, truncate: bool = True) -> None
65
 
66
 
67
  class SubAgentDisplay:
68
- """Live-updating display: header with stats + rolling 2-line tool calls."""
69
 
70
  _MAX_VISIBLE = 2
71
 
72
  def __init__(self):
73
  self._calls: list[str] = []
74
- self._stats: str = ""
 
 
75
  self._lines_on_screen = 0
 
76
 
77
- def set_stats(self, stats: str) -> None:
78
- """Update the stats line and redraw."""
79
- self._stats = stats
 
 
 
 
 
80
  self._redraw()
 
 
 
 
 
 
 
 
 
81
 
82
  def add_call(self, tool_desc: str) -> None:
83
- """Add a tool call and redraw."""
84
  self._calls.append(tool_desc)
85
  self._redraw()
86
 
87
  def clear(self) -> None:
 
 
 
88
  self._erase()
89
  self._lines_on_screen = 0
90
  self._calls = []
91
- self._stats = ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
  def _erase(self) -> None:
94
  if self._lines_on_screen > 0:
@@ -102,9 +145,10 @@ class SubAgentDisplay:
102
  self._erase()
103
  lines = []
104
  # Header: ▸ research (stats)
 
105
  header = f"{_I}\033[1;36m▸ research\033[0m"
106
- if self._stats:
107
- header += f" \033[2m({self._stats})\033[0m"
108
  lines.append(header)
109
  # Last 2 tool calls, gray
110
  visible = self._calls[-self._MAX_VISIBLE:]
@@ -122,10 +166,14 @@ _subagent_display = SubAgentDisplay()
122
  def print_tool_log(tool: str, log: str) -> None:
123
  """Handle tool log events — sub-agent calls get the rolling display."""
124
  if tool == "research":
125
- if log.startswith("stats:"):
126
- _subagent_display.set_stats(log[6:])
127
  elif log == "Research complete.":
128
  _subagent_display.clear()
 
 
 
 
129
  else:
130
  _subagent_display.add_call(log)
131
  else:
 
65
 
66
 
67
  class SubAgentDisplay:
68
+ """Live-updating display: header with stats (ticks every second) + rolling 2-line tool calls."""
69
 
70
  _MAX_VISIBLE = 2
71
 
72
  def __init__(self):
73
  self._calls: list[str] = []
74
+ self._tool_count = 0
75
+ self._token_count = 0
76
+ self._start_time: float | None = None
77
  self._lines_on_screen = 0
78
+ self._ticker_task = None
79
 
80
+ def start(self) -> None:
81
+ """Begin the display with a 1-second ticker."""
82
+ import asyncio
83
+ import time
84
+ self._calls = []
85
+ self._tool_count = 0
86
+ self._token_count = 0
87
+ self._start_time = time.monotonic()
88
  self._redraw()
89
+ self._ticker_task = asyncio.ensure_future(self._tick())
90
+
91
+ def set_tokens(self, tokens: int) -> None:
92
+ self._token_count = tokens
93
+ # no redraw — ticker handles it
94
+
95
+ def set_tool_count(self, count: int) -> None:
96
+ self._tool_count = count
97
+ # no redraw — ticker handles it
98
 
99
  def add_call(self, tool_desc: str) -> None:
 
100
  self._calls.append(tool_desc)
101
  self._redraw()
102
 
103
  def clear(self) -> None:
104
+ if self._ticker_task:
105
+ self._ticker_task.cancel()
106
+ self._ticker_task = None
107
  self._erase()
108
  self._lines_on_screen = 0
109
  self._calls = []
110
+ self._start_time = None
111
+
112
+ async def _tick(self) -> None:
113
+ import asyncio
114
+ try:
115
+ while True:
116
+ await asyncio.sleep(1.0)
117
+ self._redraw()
118
+ except asyncio.CancelledError:
119
+ pass
120
+
121
+ def _format_stats(self) -> str:
122
+ import time
123
+ if self._start_time is None:
124
+ return ""
125
+ elapsed = time.monotonic() - self._start_time
126
+ if elapsed < 60:
127
+ time_str = f"{elapsed:.0f}s"
128
+ else:
129
+ time_str = f"{elapsed / 60:.0f}m {elapsed % 60:.0f}s"
130
+ if self._token_count >= 1000:
131
+ tok_str = f"{self._token_count / 1000:.1f}k"
132
+ else:
133
+ tok_str = str(self._token_count)
134
+ return f"{self._tool_count} tool uses · {tok_str} tokens · {time_str}"
135
 
136
  def _erase(self) -> None:
137
  if self._lines_on_screen > 0:
 
145
  self._erase()
146
  lines = []
147
  # Header: ▸ research (stats)
148
+ stats = self._format_stats()
149
  header = f"{_I}\033[1;36m▸ research\033[0m"
150
+ if stats:
151
+ header += f" \033[2m({stats})\033[0m"
152
  lines.append(header)
153
  # Last 2 tool calls, gray
154
  visible = self._calls[-self._MAX_VISIBLE:]
 
166
  def print_tool_log(tool: str, log: str) -> None:
167
  """Handle tool log events — sub-agent calls get the rolling display."""
168
  if tool == "research":
169
+ if log == "Starting research sub-agent...":
170
+ _subagent_display.start()
171
  elif log == "Research complete.":
172
  _subagent_display.clear()
173
+ elif log.startswith("tokens:"):
174
+ _subagent_display.set_tokens(int(log[7:]))
175
+ elif log.startswith("tools:"):
176
+ _subagent_display.set_tool_count(int(log[6:]))
177
  else:
178
  _subagent_display.add_call(log)
179
  else: