fix(graph): key checkpoints on graph shape and expose the LLM retry budget
- checkpoint resume keyed only by ticker+date silently continued the old graph under a different analyst selection / depth / asset mode; fold a run signature into the thread id #1089 - add llm_max_retries + TRADINGAGENTS_LLM_MAX_RETRIES, forwarded to every provider when set (int-coerced, rejects negatives/booleans), so a 429 burst can't abort a run #1091
This commit is contained in:
5 files changed
+244
-13
No files matched your search
@@ -25,9 +25,17 @@ def _db_path(data_dir: str | Path, ticker: str) -> Path:
|
||||
return p / f"{safe}.db"
|
||||
|
||||
|
||||
def thread_id(ticker: str, date: str) -> str:
|
||||
"""Deterministic thread ID for a ticker+date pair."""
|
||||
return hashlib.sha256(f"{ticker.upper()}:{date}".encode()).hexdigest()[:16]
|
||||
def thread_id(ticker: str, date: str, signature: str = "") -> str:
|
||||
"""Deterministic thread ID for a ticker+date pair.
|
||||
|
||||
``signature`` folds in graph-shape-affecting run choices so a resume under a
|
||||
different graph can't reuse this checkpoint (#1089); omitting it keeps the
|
||||
legacy ID.
|
||||
"""
|
||||
base = f"{ticker.upper()}:{date}"
|
||||
if signature:
|
||||
base = f"{base}:{signature}"
|
||||
return hashlib.sha256(base.encode()).hexdigest()[:16]
|
||||
|
||||
|
||||
@contextmanager
|
||||
@@ -43,17 +51,17 @@ def get_checkpointer(data_dir: str | Path, ticker: str) -> Generator[SqliteSaver
|
||||
conn.close()
|
||||
|
||||
|
||||
def has_checkpoint(data_dir: str | Path, ticker: str, date: str) -> bool:
|
||||
def has_checkpoint(data_dir: str | Path, ticker: str, date: str, signature: str = "") -> bool:
|
||||
"""Check whether a resumable checkpoint exists for ticker+date."""
|
||||
return checkpoint_step(data_dir, ticker, date) is not None
|
||||
return checkpoint_step(data_dir, ticker, date, signature) is not None
|
||||
|
||||
|
||||
def checkpoint_step(data_dir: str | Path, ticker: str, date: str) -> int | None:
|
||||
def checkpoint_step(data_dir: str | Path, ticker: str, date: str, signature: str = "") -> int | None:
|
||||
"""Return the step number of the latest checkpoint, or None if none exists."""
|
||||
db = _db_path(data_dir, ticker)
|
||||
if not db.exists():
|
||||
return None
|
||||
tid = thread_id(ticker, date)
|
||||
tid = thread_id(ticker, date, signature)
|
||||
with get_checkpointer(data_dir, ticker) as saver:
|
||||
config = {"configurable": {"thread_id": tid}}
|
||||
cp = saver.get_tuple(config)
|
||||
@@ -73,12 +81,12 @@ def clear_all_checkpoints(data_dir: str | Path) -> int:
|
||||
return len(dbs)
|
||||
|
||||
|
||||
def clear_checkpoint(data_dir: str | Path, ticker: str, date: str) -> None:
|
||||
def clear_checkpoint(data_dir: str | Path, ticker: str, date: str, signature: str = "") -> None:
|
||||
"""Remove checkpoint for a specific ticker+date by deleting the thread's rows."""
|
||||
db = _db_path(data_dir, ticker)
|
||||
if not db.exists():
|
||||
return
|
||||
tid = thread_id(ticker, date)
|
||||
tid = thread_id(ticker, date, signature)
|
||||
conn = sqlite3.connect(str(db))
|
||||
try:
|
||||
for table in ("writes", "checkpoints"):
|
||||
|
||||
Reference in new issue
Block a user