maodd commited on
Commit
05bcb11
·
verified ·
1 Parent(s): 8be6e91

Use smolagents' native RPM/retry controls, cap steps, trim memory

Browse files

Custom step_callback throttling couldn't see smolagents' own internal retries on rate-limit errors. Switch to LiteLLMModel's native requests_per_minute=25 and retry=False. Cap max_steps=7 and add MemoryTrimmer to stop unbounded context growth (the real driver of TPM failures). Prioritizes speed - a failed question is skipped, not retried.

Files changed (1) hide show
  1. app.py +37 -47
app.py CHANGED
@@ -1,7 +1,5 @@
1
  import os
2
  import tempfile
3
- import time
4
- from collections import deque
5
  import gradio as gr
6
  import requests
7
  import inspect
@@ -38,50 +36,30 @@ Your final answer should be a number OR as few words as possible OR a comma sepa
38
  If you are asked for a number, don't use commas to write it, and don't use units such as $ or % unless specified otherwise.
39
  If you are asked for a string, don't use articles or abbreviations (e.g. for cities), and write digits in plain text unless specified otherwise.
40
  If you are asked for a comma separated list, apply the above rules to each element depending on whether it's a number or a string.
 
41
  """
42
 
43
- class RateLimiter:
44
  """
45
- Step_callback that throttles both request frequency (RPM) and actual
46
- token volume (TPM) against the model provider's free-tier limits. Groq's
47
- free tier for llama-3.3-70b-versatile caps at 30 RPM and 12,000 TPM - and
48
- TPM is the tighter constraint here, since a CodeAgent's context (tool
49
- outputs, growing conversation history) can easily run several thousand
50
- tokens per call. Tracks actual usage reported per step in a trailing
51
- 60s window and sleeps as needed to stay under a token budget (with
52
- margin below the real cap).
53
  """
54
- def __init__(self, min_seconds_between_calls: float = 2.5, tokens_per_minute_budget: int = 9000):
55
- self.min_seconds_between_calls = min_seconds_between_calls
56
- self.tokens_per_minute_budget = tokens_per_minute_budget
57
- self._last_call_at: float | None = None
58
- self._usage_window: deque[tuple[float, int]] = deque()
59
 
60
  def __call__(self, memory_step: ActionStep, agent: CodeAgent) -> None:
61
- now = time.monotonic()
62
-
63
- # RPM guard
64
- if self._last_call_at is not None:
65
- wait = self.min_seconds_between_calls - (now - self._last_call_at)
66
- if wait > 0:
67
- time.sleep(wait)
68
- now = time.monotonic()
69
-
70
- # TPM guard, based on actual usage from the step that just completed
71
- usage = getattr(memory_step, "token_usage", None)
72
- tokens = (usage.input_tokens + usage.output_tokens) if usage else 0
73
- self._usage_window.append((now, tokens))
74
- cutoff = now - 60
75
- while self._usage_window and self._usage_window[0][0] < cutoff:
76
- self._usage_window.popleft()
77
- window_tokens = sum(t for _, t in self._usage_window)
78
- if window_tokens > self.tokens_per_minute_budget and self._usage_window:
79
- wait = 60 - (now - self._usage_window[0][0]) + 0.5
80
- if wait > 0:
81
- print(f"Rate limiter: {window_tokens} tokens in the last 60s (budget {self.tokens_per_minute_budget}), sleeping {wait:.1f}s")
82
- time.sleep(wait)
83
-
84
- self._last_call_at = time.monotonic()
85
 
86
 
87
  # --- Basic Agent Definition ---
@@ -93,12 +71,27 @@ class BasicAgent:
93
  if not api_key:
94
  print("Warning: GROQ_API_KEY is not set - the agent will fail to call the model.")
95
 
96
- self.model = LiteLLMModel(model_id=model_id, api_key=api_key, temperature=0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  self.agent = CodeAgent(
98
  model=self.model,
99
  tools=[
100
  WebSearchTool(),
101
- VisitWebpageTool(max_output_length=4000), # default 40000 chars blows the TPM budget in one call
102
  WikipediaSearchTool(content_type="summary"), # "text" (default) returns the full article
103
  ],
104
  add_base_tools=True, # adds DuckDuckGo search + Whisper audio transcriber
@@ -106,11 +99,8 @@ class BasicAgent:
106
  "pandas", "numpy", "math", "re", "json", "itertools",
107
  "collections", "statistics", "datetime", "io", "openpyxl", "PIL",
108
  ],
109
- max_steps=12,
110
- step_callbacks=[RateLimiter(
111
- min_seconds_between_calls=float(os.getenv("RATE_LIMIT_SECONDS_BETWEEN_CALLS", "2.5")),
112
- tokens_per_minute_budget=int(os.getenv("RATE_LIMIT_TOKENS_PER_MINUTE", "9000")),
113
- )],
114
  )
115
  print("BasicAgent initialized.")
116
 
 
1
  import os
2
  import tempfile
 
 
3
  import gradio as gr
4
  import requests
5
  import inspect
 
36
  If you are asked for a number, don't use commas to write it, and don't use units such as $ or % unless specified otherwise.
37
  If you are asked for a string, don't use articles or abbreviations (e.g. for cities), and write digits in plain text unless specified otherwise.
38
  If you are asked for a comma separated list, apply the above rules to each element depending on whether it's a number or a string.
39
+ Work efficiently: if a search or lookup doesn't find what you need after 1-2 tries, try a meaningfully different approach rather than repeating similar queries, and give your best-guess final_answer rather than exhausting all steps.
40
  """
41
 
42
+ class MemoryTrimmer:
43
  """
44
+ Step_callback that collapses old tool outputs in the agent's memory.
45
+ CodeAgent re-sends the *entire* step history on every call, so input
46
+ tokens grow every single step (2k -> 5k -> 8k -> ... -> 20k+ by step 6),
47
+ which both wrecks the TPM budget and slows every later step far more
48
+ than it needs to. Keeps the most recent `keep_recent` steps' tool
49
+ outputs intact (the agent still needs that detail) and truncates older
50
+ ones to a short placeholder, keeping per-call token cost roughly flat
51
+ across a run instead of growing unbounded.
52
  """
53
+ def __init__(self, keep_recent: int = 2, max_old_observation_chars: int = 300):
54
+ self.keep_recent = keep_recent
55
+ self.max_old_observation_chars = max_old_observation_chars
 
 
56
 
57
  def __call__(self, memory_step: ActionStep, agent: CodeAgent) -> None:
58
+ action_steps = [step for step in agent.memory.steps if isinstance(step, ActionStep)]
59
+ for step in action_steps[:-self.keep_recent]:
60
+ observations = getattr(step, "observations", None)
61
+ if observations and len(observations) > self.max_old_observation_chars:
62
+ step.observations = observations[:self.max_old_observation_chars] + " [...older output truncated to save context]"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
 
65
  # --- Basic Agent Definition ---
 
71
  if not api_key:
72
  print("Warning: GROQ_API_KEY is not set - the agent will fail to call the model.")
73
 
74
+ # requests_per_minute: smolagents' own ApiModel throttles calls at this
75
+ # rate before ever hitting the API (a bit under Groq's 30 RPM cap).
76
+ # retry=False: smolagents' default retries a failed call several
77
+ # times internally (invisible to us - confirmed via Groq's dashboard,
78
+ # 429s with sub-second gaps between requests). Only ~30% of GAIA
79
+ # questions need to succeed here, so a call that still gets rate
80
+ # limited (e.g. on tokens/minute, which this doesn't track) should
81
+ # just fail fast - the per-question try/except below skips it and
82
+ # moves on, which is much cheaper than retry-storming.
83
+ self.model = LiteLLMModel(
84
+ model_id=model_id,
85
+ api_key=api_key,
86
+ temperature=0,
87
+ requests_per_minute=float(os.getenv("RATE_LIMIT_RPM", "25")),
88
+ retry=False,
89
+ )
90
  self.agent = CodeAgent(
91
  model=self.model,
92
  tools=[
93
  WebSearchTool(),
94
+ VisitWebpageTool(max_output_length=3000), # default 40000 chars blows the TPM budget in one call
95
  WikipediaSearchTool(content_type="summary"), # "text" (default) returns the full article
96
  ],
97
  add_base_tools=True, # adds DuckDuckGo search + Whisper audio transcriber
 
99
  "pandas", "numpy", "math", "re", "json", "itertools",
100
  "collections", "statistics", "datetime", "io", "openpyxl", "PIL",
101
  ],
102
+ max_steps=7, # keep runs short: growing history otherwise blows the TPM budget by step ~6
103
+ step_callbacks=[MemoryTrimmer()],
 
 
 
104
  )
105
  print("BasicAgent initialized.")
106