yc1838 commited on
Commit
a16b876
·
1 Parent(s): a2a0291

feat: handle rate limit streak and batch abort

Browse files
Files changed (2) hide show
  1. src/lilith_agent/runner.py +23 -1
  2. tests/test_runner.py +45 -1
src/lilith_agent/runner.py CHANGED
@@ -160,7 +160,13 @@ def run_agent_on_questions(graph: Any, questions: list[dict], checkpoint_dir: st
160
  answers: list[dict] = []
161
 
162
  from lilith_agent.config import Config
163
- from lilith_agent.models import RateLimitCooldownError, get_cheap_model, rate_limit_question_scope
 
 
 
 
 
 
164
  cfg = Config.from_env()
165
  cheap_model = get_cheap_model(cfg)
166
 
@@ -231,6 +237,22 @@ def run_agent_on_questions(graph: Any, questions: list[dict], checkpoint_dir: st
231
  log_runner.warning("[runner] task=%s rate limited after retry: %s", task_id, exc)
232
  answers.append({"task_id": task_id, "submitted_answer": "AGENT ERROR: RATE LIMITED"})
233
  continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  except Exception as exc:
235
  log_runner.warning("[runner] task=%s agent error: %s", task_id, exc)
236
  answers.append(
 
160
  answers: list[dict] = []
161
 
162
  from lilith_agent.config import Config
163
+ from lilith_agent.models import (
164
+ BatchAbortRateLimitError,
165
+ QuestionRateLimitStreakError,
166
+ RateLimitCooldownError,
167
+ get_cheap_model,
168
+ rate_limit_question_scope,
169
+ )
170
  cfg = Config.from_env()
171
  cheap_model = get_cheap_model(cfg)
172
 
 
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)
246
+ answers.append({"task_id": task_id, "submitted_answer": "AGENT ERROR: RATE LIMITED"})
247
+ _write_checkpoint_atomic(
248
+ checkpoint_root / "rate_limit_abort.json",
249
+ {
250
+ "task_id": task_id,
251
+ "reason": exc.reason,
252
+ "original_error": exc.original_error,
253
+ },
254
+ )
255
+ return answers
256
  except Exception as exc:
257
  log_runner.warning("[runner] task=%s agent error: %s", task_id, exc)
258
  answers.append(
tests/test_runner.py CHANGED
@@ -8,7 +8,7 @@ import pytest
8
  from langchain_core.messages import AIMessage
9
 
10
  from lilith_agent.config import Config
11
- from lilith_agent.models import RateLimitCooldownError
12
  from lilith_agent.runner import run_agent_on_questions, _wrap_user_question, _write_checkpoint_atomic
13
 
14
 
@@ -183,3 +183,47 @@ def test_runner_uses_fresh_ephemeral_memory_for_retry(monkeypatch, tmp_path: Pat
183
  )
184
 
185
  assert events == ["enter", "exit", "enter", "exit"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  from langchain_core.messages import AIMessage
9
 
10
  from lilith_agent.config import Config
11
+ from lilith_agent.models import BatchAbortRateLimitError, QuestionRateLimitStreakError, RateLimitCooldownError
12
  from lilith_agent.runner import run_agent_on_questions, _wrap_user_question, _write_checkpoint_atomic
13
 
14
 
 
183
  )
184
 
185
  assert events == ["enter", "exit", "enter", "exit"]
186
+
187
+
188
+ class _GraphQuestionStreak:
189
+ def invoke(self, state, config):
190
+ raise QuestionRateLimitStreakError(count=50)
191
+
192
+
193
+ class _GraphBatchAbort:
194
+ def invoke(self, state, config):
195
+ raise BatchAbortRateLimitError(reason="daily quota exhausted", original_error="429")
196
+
197
+
198
+ def test_runner_skips_question_on_rate_limit_streak(tmp_path: Path):
199
+ answers = run_agent_on_questions(
200
+ _GraphQuestionStreak(),
201
+ [
202
+ {"task_id": "task-streak", "question": "first"},
203
+ {"task_id": "task-next", "question": "second"},
204
+ ],
205
+ tmp_path,
206
+ )
207
+
208
+ assert answers[0] == {"task_id": "task-streak", "submitted_answer": "AGENT ERROR: RATE LIMITED"}
209
+ assert not (tmp_path / "task-streak.json").exists()
210
+
211
+
212
+ def test_runner_stops_batch_and_writes_abort_marker_on_daily_quota(tmp_path: Path):
213
+ answers = run_agent_on_questions(
214
+ _GraphBatchAbort(),
215
+ [
216
+ {"task_id": "task-abort", "question": "first"},
217
+ {"task_id": "task-never", "question": "second"},
218
+ ],
219
+ tmp_path,
220
+ )
221
+
222
+ assert answers == [{"task_id": "task-abort", "submitted_answer": "AGENT ERROR: RATE LIMITED"}]
223
+ marker = tmp_path / "rate_limit_abort.json"
224
+ assert marker.exists()
225
+ data = json.loads(marker.read_text())
226
+ assert data["task_id"] == "task-abort"
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()