Spaces:
Sleeping
Sleeping
Make rate limiter token-aware; trim verbose tool outputs
Browse filesGroq's 429s were a TPM (12k/min) limit, not RPM. RateLimiter now tracks actual token usage per step in a trailing 60s window; VisitWebpageTool and WikipediaSearchTool outputs trimmed to reduce per-call token volume.
app.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import os
|
| 2 |
import tempfile
|
| 3 |
import time
|
|
|
|
| 4 |
import gradio as gr
|
| 5 |
import requests
|
| 6 |
import inspect
|
|
@@ -41,25 +42,45 @@ If you are asked for a comma separated list, apply the above rules to each eleme
|
|
| 41 |
|
| 42 |
class RateLimiter:
|
| 43 |
"""
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
|
|
|
| 51 |
"""
|
| 52 |
-
def __init__(self, min_seconds_between_calls: float = 2.5):
|
| 53 |
self.min_seconds_between_calls = min_seconds_between_calls
|
|
|
|
| 54 |
self._last_call_at: float | None = None
|
|
|
|
| 55 |
|
| 56 |
def __call__(self, memory_step: ActionStep, agent: CodeAgent) -> None:
|
| 57 |
now = time.monotonic()
|
|
|
|
|
|
|
| 58 |
if self._last_call_at is not None:
|
| 59 |
wait = self.min_seconds_between_calls - (now - self._last_call_at)
|
| 60 |
if wait > 0:
|
| 61 |
-
print(f"Rate limiter: sleeping {wait:.1f}s to stay under the RPM limit")
|
| 62 |
time.sleep(wait)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
self._last_call_at = time.monotonic()
|
| 64 |
|
| 65 |
|
|
@@ -75,14 +96,21 @@ class BasicAgent:
|
|
| 75 |
self.model = LiteLLMModel(model_id=model_id, api_key=api_key, temperature=0)
|
| 76 |
self.agent = CodeAgent(
|
| 77 |
model=self.model,
|
| 78 |
-
tools=[
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
add_base_tools=True, # adds DuckDuckGo search + Whisper audio transcriber
|
| 80 |
additional_authorized_imports=[
|
| 81 |
"pandas", "numpy", "math", "re", "json", "itertools",
|
| 82 |
"collections", "statistics", "datetime", "io", "openpyxl", "PIL",
|
| 83 |
],
|
| 84 |
max_steps=12,
|
| 85 |
-
step_callbacks=[RateLimiter(
|
|
|
|
|
|
|
|
|
|
| 86 |
)
|
| 87 |
print("BasicAgent initialized.")
|
| 88 |
|
|
|
|
| 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
|
|
|
|
| 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 |
|
|
|
|
| 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
|
| 105 |
additional_authorized_imports=[
|
| 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 |
|