Spaces:
Sleeping
Sleeping
yc1838 commited on
Commit ·
2fade2b
1
Parent(s): a16b876
feat: pause batch on high rate limit window
Browse files- src/lilith_agent/runner.py +14 -0
- tests/test_runner.py +30 -0
src/lilith_agent/runner.py
CHANGED
|
@@ -164,6 +164,8 @@ def run_agent_on_questions(graph: Any, questions: list[dict], checkpoint_dir: st
|
|
| 164 |
BatchAbortRateLimitError,
|
| 165 |
QuestionRateLimitStreakError,
|
| 166 |
RateLimitCooldownError,
|
|
|
|
|
|
|
| 167 |
get_cheap_model,
|
| 168 |
rate_limit_question_scope,
|
| 169 |
)
|
|
@@ -179,6 +181,14 @@ def run_agent_on_questions(graph: Any, questions: list[dict], checkpoint_dir: st
|
|
| 179 |
with ephemeral_memory():
|
| 180 |
return graph.invoke(task_state, {"configurable": {"thread_id": task_id}})
|
| 181 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
for idx, question in enumerate(questions, start=1):
|
| 183 |
reset_vision_state()
|
| 184 |
task_id = question.get("task_id")
|
|
@@ -236,10 +246,12 @@ def run_agent_on_questions(graph: Any, questions: list[dict], checkpoint_dir: st
|
|
| 236 |
except RateLimitCooldownError as exc:
|
| 237 |
log_runner.warning("[runner] task=%s rate limited after retry: %s", task_id, exc)
|
| 238 |
answers.append({"task_id": task_id, "submitted_answer": "AGENT ERROR: RATE LIMITED"})
|
|
|
|
| 239 |
continue
|
| 240 |
except QuestionRateLimitStreakError as exc:
|
| 241 |
log_runner.warning("[runner] task=%s rate limit streak: %s", task_id, exc)
|
| 242 |
answers.append({"task_id": task_id, "submitted_answer": "AGENT ERROR: RATE LIMITED"})
|
|
|
|
| 243 |
continue
|
| 244 |
except BatchAbortRateLimitError as exc:
|
| 245 |
log_runner.warning("[runner] task=%s batch abort rate limit: %s", task_id, exc)
|
|
@@ -261,6 +273,7 @@ def run_agent_on_questions(graph: Any, questions: list[dict], checkpoint_dir: st
|
|
| 261 |
"submitted_answer": f"AGENT ERROR: {exc}",
|
| 262 |
}
|
| 263 |
)
|
|
|
|
| 264 |
continue
|
| 265 |
|
| 266 |
last_m = result["messages"][-1]
|
|
@@ -297,6 +310,7 @@ def run_agent_on_questions(graph: Any, questions: list[dict], checkpoint_dir: st
|
|
| 297 |
(submitted_answer[:160] + "…") if len(submitted_answer) > 160 else submitted_answer,
|
| 298 |
)
|
| 299 |
answers.append({"task_id": task_id, "submitted_answer": submitted_answer.strip()})
|
|
|
|
| 300 |
|
| 301 |
return answers
|
| 302 |
|
|
|
|
| 164 |
BatchAbortRateLimitError,
|
| 165 |
QuestionRateLimitStreakError,
|
| 166 |
RateLimitCooldownError,
|
| 167 |
+
batch_rate_limit_pause_seconds,
|
| 168 |
+
clear_batch_rate_limit_window,
|
| 169 |
get_cheap_model,
|
| 170 |
rate_limit_question_scope,
|
| 171 |
)
|
|
|
|
| 181 |
with ephemeral_memory():
|
| 182 |
return graph.invoke(task_state, {"configurable": {"thread_id": task_id}})
|
| 183 |
|
| 184 |
+
def _maybe_pause_for_batch_rate_limit() -> None:
|
| 185 |
+
pause_seconds = batch_rate_limit_pause_seconds()
|
| 186 |
+
if pause_seconds is None:
|
| 187 |
+
return
|
| 188 |
+
log_runner.warning("[runner] pausing batch for %ss due to rate limit window", pause_seconds)
|
| 189 |
+
time.sleep(pause_seconds)
|
| 190 |
+
clear_batch_rate_limit_window()
|
| 191 |
+
|
| 192 |
for idx, question in enumerate(questions, start=1):
|
| 193 |
reset_vision_state()
|
| 194 |
task_id = question.get("task_id")
|
|
|
|
| 246 |
except RateLimitCooldownError as exc:
|
| 247 |
log_runner.warning("[runner] task=%s rate limited after retry: %s", task_id, exc)
|
| 248 |
answers.append({"task_id": task_id, "submitted_answer": "AGENT ERROR: RATE LIMITED"})
|
| 249 |
+
_maybe_pause_for_batch_rate_limit()
|
| 250 |
continue
|
| 251 |
except QuestionRateLimitStreakError as exc:
|
| 252 |
log_runner.warning("[runner] task=%s rate limit streak: %s", task_id, exc)
|
| 253 |
answers.append({"task_id": task_id, "submitted_answer": "AGENT ERROR: RATE LIMITED"})
|
| 254 |
+
_maybe_pause_for_batch_rate_limit()
|
| 255 |
continue
|
| 256 |
except BatchAbortRateLimitError as exc:
|
| 257 |
log_runner.warning("[runner] task=%s batch abort rate limit: %s", task_id, exc)
|
|
|
|
| 273 |
"submitted_answer": f"AGENT ERROR: {exc}",
|
| 274 |
}
|
| 275 |
)
|
| 276 |
+
_maybe_pause_for_batch_rate_limit()
|
| 277 |
continue
|
| 278 |
|
| 279 |
last_m = result["messages"][-1]
|
|
|
|
| 310 |
(submitted_answer[:160] + "…") if len(submitted_answer) > 160 else submitted_answer,
|
| 311 |
)
|
| 312 |
answers.append({"task_id": task_id, "submitted_answer": submitted_answer.strip()})
|
| 313 |
+
_maybe_pause_for_batch_rate_limit()
|
| 314 |
|
| 315 |
return answers
|
| 316 |
|
tests/test_runner.py
CHANGED
|
@@ -227,3 +227,33 @@ def test_runner_stops_batch_and_writes_abort_marker_on_daily_quota(tmp_path: Pat
|
|
| 227 |
assert data["reason"] == "daily quota exhausted"
|
| 228 |
assert not (tmp_path / "task-abort.json").exists()
|
| 229 |
assert not (tmp_path / "task-never.json").exists()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
assert data["reason"] == "daily quota exhausted"
|
| 228 |
assert not (tmp_path / "task-abort.json").exists()
|
| 229 |
assert not (tmp_path / "task-never.json").exists()
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
class _GraphAlwaysSucceeds:
|
| 233 |
+
def __init__(self):
|
| 234 |
+
self.calls = 0
|
| 235 |
+
|
| 236 |
+
def invoke(self, state, config):
|
| 237 |
+
self.calls += 1
|
| 238 |
+
return {"messages": [AIMessage(content=f"answer-{self.calls}")]}
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
def test_runner_pauses_batch_when_window_trips(monkeypatch, tmp_path: Path):
|
| 242 |
+
pauses = [300, None]
|
| 243 |
+
sleeps = []
|
| 244 |
+
monkeypatch.setattr("lilith_agent.models.batch_rate_limit_pause_seconds", lambda: pauses.pop(0))
|
| 245 |
+
monkeypatch.setattr("lilith_agent.models.clear_batch_rate_limit_window", lambda: None)
|
| 246 |
+
monkeypatch.setattr("lilith_agent.runner.time.sleep", sleeps.append)
|
| 247 |
+
monkeypatch.setattr("lilith_agent.runner._final_formatting_cleanup", lambda model, question, raw, llm_formatter_enabled=True: raw)
|
| 248 |
+
|
| 249 |
+
answers = run_agent_on_questions(
|
| 250 |
+
_GraphAlwaysSucceeds(),
|
| 251 |
+
[
|
| 252 |
+
{"task_id": "task-a", "question": "a"},
|
| 253 |
+
{"task_id": "task-b", "question": "b"},
|
| 254 |
+
],
|
| 255 |
+
tmp_path,
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
assert sleeps == [300]
|
| 259 |
+
assert [answer["task_id"] for answer in answers] == ["task-a", "task-b"]
|