Spaces:
Sleeping
Sleeping
yc1838 commited on
Commit ·
dcd46a6
1
Parent(s): d256c19
feat: retry gaia task once after cooldown
Browse files- src/lilith_agent/runner.py +26 -4
- tests/test_runner.py +73 -1
src/lilith_agent/runner.py
CHANGED
|
@@ -4,6 +4,7 @@ import json
|
|
| 4 |
import logging
|
| 5 |
import os
|
| 6 |
import re
|
|
|
|
| 7 |
from pathlib import Path
|
| 8 |
from typing import Any
|
| 9 |
|
|
@@ -159,11 +160,19 @@ def run_agent_on_questions(graph: Any, questions: list[dict], checkpoint_dir: st
|
|
| 159 |
answers: list[dict] = []
|
| 160 |
|
| 161 |
from lilith_agent.config import Config
|
| 162 |
-
from lilith_agent.models import get_cheap_model
|
| 163 |
cfg = Config.from_env()
|
| 164 |
cheap_model = get_cheap_model(cfg)
|
| 165 |
|
| 166 |
total = len(questions)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
for idx, question in enumerate(questions, start=1):
|
| 168 |
reset_vision_state()
|
| 169 |
task_id = question.get("task_id")
|
|
@@ -205,10 +214,23 @@ def run_agent_on_questions(graph: Any, questions: list[dict], checkpoint_dir: st
|
|
| 205 |
"iterations": 0
|
| 206 |
}
|
| 207 |
|
| 208 |
-
from lilith_agent.memory import ephemeral_memory
|
| 209 |
try:
|
| 210 |
-
|
| 211 |
-
result =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
except Exception as exc:
|
| 213 |
log_runner.warning("[runner] task=%s agent error: %s", task_id, exc)
|
| 214 |
answers.append(
|
|
|
|
| 4 |
import logging
|
| 5 |
import os
|
| 6 |
import re
|
| 7 |
+
import time
|
| 8 |
from pathlib import Path
|
| 9 |
from typing import Any
|
| 10 |
|
|
|
|
| 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 |
|
| 167 |
total = len(questions)
|
| 168 |
+
|
| 169 |
+
def _invoke_task_once(task_state: dict, task_id: str):
|
| 170 |
+
from lilith_agent.memory import ephemeral_memory
|
| 171 |
+
|
| 172 |
+
with rate_limit_question_scope():
|
| 173 |
+
with ephemeral_memory():
|
| 174 |
+
return graph.invoke(task_state, {"configurable": {"thread_id": task_id}})
|
| 175 |
+
|
| 176 |
for idx, question in enumerate(questions, start=1):
|
| 177 |
reset_vision_state()
|
| 178 |
task_id = question.get("task_id")
|
|
|
|
| 214 |
"iterations": 0
|
| 215 |
}
|
| 216 |
|
|
|
|
| 217 |
try:
|
| 218 |
+
try:
|
| 219 |
+
result = _invoke_task_once(state, task_id)
|
| 220 |
+
except RateLimitCooldownError as exc:
|
| 221 |
+
log_runner.warning(
|
| 222 |
+
"[runner] task=%s rate limited provider=%s model=%s cooldown=%s",
|
| 223 |
+
task_id,
|
| 224 |
+
exc.provider,
|
| 225 |
+
exc.model,
|
| 226 |
+
exc.cooldown_seconds,
|
| 227 |
+
)
|
| 228 |
+
time.sleep(exc.cooldown_seconds)
|
| 229 |
+
result = _invoke_task_once(state, task_id)
|
| 230 |
+
except RateLimitCooldownError as exc:
|
| 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(
|
tests/test_runner.py
CHANGED
|
@@ -5,7 +5,11 @@ from pathlib import Path
|
|
| 5 |
|
| 6 |
import pytest
|
| 7 |
|
| 8 |
-
from
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
|
| 11 |
def test_wrap_escapes_closing_tag_to_prevent_injection():
|
|
@@ -57,3 +61,71 @@ def test_atomic_write_does_not_corrupt_existing_file_on_serialization_failure(tm
|
|
| 57 |
data = json.loads(dest.read_text())
|
| 58 |
assert data["submitted_answer"] == "good"
|
| 59 |
assert list(tmp_path.glob("*.tmp")) == []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
import pytest
|
| 7 |
|
| 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 |
|
| 15 |
def test_wrap_escapes_closing_tag_to_prevent_injection():
|
|
|
|
| 61 |
data = json.loads(dest.read_text())
|
| 62 |
assert data["submitted_answer"] == "good"
|
| 63 |
assert list(tmp_path.glob("*.tmp")) == []
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
@pytest.fixture
|
| 67 |
+
def runner_test_config() -> Config:
|
| 68 |
+
return Config(
|
| 69 |
+
cheap_provider="google",
|
| 70 |
+
cheap_model="gemini-3-flash-preview",
|
| 71 |
+
strong_provider="google",
|
| 72 |
+
strong_model="gemini-3.1-pro",
|
| 73 |
+
extra_strong_provider="google",
|
| 74 |
+
extra_strong_model="gemini-3.1-pro",
|
| 75 |
+
vision_provider="fal",
|
| 76 |
+
vision_model="gemini-3-flash-preview",
|
| 77 |
+
fal_vision_api_key="",
|
| 78 |
+
api_url="",
|
| 79 |
+
checkpoint_dir="",
|
| 80 |
+
whisper_model="base",
|
| 81 |
+
anthropic_api_key="",
|
| 82 |
+
google_api_key="",
|
| 83 |
+
huggingface_api_key="",
|
| 84 |
+
tavily_api_key="",
|
| 85 |
+
lmstudio_base_url="",
|
| 86 |
+
max_tokens=1024,
|
| 87 |
+
llm_formatter_enabled=True,
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
@pytest.fixture(autouse=True)
|
| 92 |
+
def _isolate_runner_model_setup(monkeypatch, runner_test_config):
|
| 93 |
+
monkeypatch.setattr(Config, "from_env", classmethod(lambda cls: runner_test_config))
|
| 94 |
+
monkeypatch.setattr("lilith_agent.models.get_cheap_model", lambda cfg: object())
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
class _GraphFailsOnceWithCooldown:
|
| 98 |
+
def __init__(self):
|
| 99 |
+
self.calls = 0
|
| 100 |
+
self.thread_ids = []
|
| 101 |
+
|
| 102 |
+
def invoke(self, state, config):
|
| 103 |
+
self.calls += 1
|
| 104 |
+
self.thread_ids.append(config["configurable"]["thread_id"])
|
| 105 |
+
if self.calls == 1:
|
| 106 |
+
raise RateLimitCooldownError(
|
| 107 |
+
provider="google",
|
| 108 |
+
model="gemini-3.1-pro",
|
| 109 |
+
cooldown_seconds=12,
|
| 110 |
+
original_error="429",
|
| 111 |
+
)
|
| 112 |
+
return {"messages": [AIMessage(content="Final Answer: 42")]}
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def test_runner_retries_same_question_once_after_cooldown(monkeypatch, tmp_path: Path):
|
| 116 |
+
monkeypatch.setattr("lilith_agent.runner._final_formatting_cleanup", lambda model, question, raw, llm_formatter_enabled=True: raw)
|
| 117 |
+
sleeps = []
|
| 118 |
+
monkeypatch.setattr("lilith_agent.runner.time.sleep", sleeps.append)
|
| 119 |
+
graph = _GraphFailsOnceWithCooldown()
|
| 120 |
+
|
| 121 |
+
answers = run_agent_on_questions(
|
| 122 |
+
graph,
|
| 123 |
+
[{"task_id": "task-1", "question": "What is 6*7?"}],
|
| 124 |
+
tmp_path,
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
assert graph.calls == 2
|
| 128 |
+
assert graph.thread_ids == ["task-1", "task-1"]
|
| 129 |
+
assert sleeps == [12]
|
| 130 |
+
assert answers == [{"task_id": "task-1", "submitted_answer": "Final Answer: 42"}]
|
| 131 |
+
assert (tmp_path / "task-1.json").exists()
|