fix(dataflows): read the vendors of the run in progress (#1369)

- propagate and settle_pending bind the graph's config for the length of the run
- a graph built later, or running concurrently, no longer changes another graph's vendors
This commit is contained in:
Yijia-Xiao
2026-09-23 19:28:56 +00:00
parent a9cc3be731
commit 96daaf1152
3 changed files with 176 additions and 11 deletions
+32 -8
View File
@@ -1,3 +1,5 @@
from contextlib import contextmanager
from contextvars import ContextVar
from copy import deepcopy
import tradingagents.default_config as default_config
@@ -5,6 +7,11 @@ import tradingagents.default_config as default_config
# Use default config but allow it to be overridden
_config: dict | None = None
# The config of the run in progress. A graph binds its own for the length of a
# run, so the data tools it calls read that graph's vendors even when several
# graphs share a process. LangGraph carries the context into tool calls.
_run_config: ContextVar[dict | None] = ContextVar("tradingagents_run_config", default=None)
def initialize_config():
"""Initialize the configuration with default values."""
@@ -13,6 +20,16 @@ def initialize_config():
_config = deepcopy(default_config.DEFAULT_CONFIG)
def _merge(base: dict, config: dict) -> dict:
"""Merge ``config`` into ``base``: dict-valued keys one level deep, scalars replaced."""
for key, value in deepcopy(config).items():
if isinstance(value, dict) and isinstance(base.get(key), dict):
base[key].update(value)
else:
base[key] = value
return base
def set_config(config: dict):
"""Update the configuration with custom values.
@@ -20,18 +37,25 @@ def set_config(config: dict):
partial update like ``{"data_vendors": {"core_stock_apis": "alpha_vantage"}}``
keeps the other nested keys from the default; scalar keys are replaced.
"""
global _config
initialize_config()
incoming = deepcopy(config)
for key, value in incoming.items():
if isinstance(value, dict) and isinstance(_config.get(key), dict):
_config[key].update(value)
else:
_config[key] = value
_merge(_config, config)
@contextmanager
def run_config(config: dict):
"""Serve ``config``, over the defaults, to every read made inside the block."""
token = _run_config.set(_merge(deepcopy(default_config.DEFAULT_CONFIG), config))
try:
yield
finally:
_run_config.reset(token)
def get_config() -> dict:
"""Get the current configuration."""
"""Get the configuration of the run in progress, else the process-wide one."""
scoped = _run_config.get()
if scoped is not None:
return deepcopy(scoped)
if _config is None:
initialize_config()
return deepcopy(_config)
+5 -3
View File
@@ -29,7 +29,7 @@ from tradingagents.agents.utils.agent_utils import (
resolve_instrument_identity,
)
from tradingagents.agents.utils.memory import TradingMemoryLog
from tradingagents.dataflows.config import set_config
from tradingagents.dataflows.config import run_config, set_config
from tradingagents.dataflows.utils import get_current_date, safe_ticker_component
from tradingagents.default_config import DEFAULT_CONFIG
from tradingagents.llm_clients import create_llm_client
@@ -452,7 +452,8 @@ class TradingAgentsGraph:
trade_date = _validate_trade_date(trade_date)
self.ticker = company_name
with self.checkpoint_scope(company_name, trade_date, asset_type, portfolio) as thread_id_value:
with run_config(self.config), \
self.checkpoint_scope(company_name, trade_date, asset_type, portfolio) as thread_id_value:
return self._run_graph(
company_name, trade_date, asset_type=asset_type,
checkpoint_thread_id=thread_id_value, portfolio=portfolio,
@@ -564,7 +565,8 @@ class TradingAgentsGraph:
that is done analyzing a ticker (a backtest sweep, a scheduled job) calls
this to settle it now.
"""
self._resolve_pending_entries(company_name)
with run_config(self.config):
self._resolve_pending_entries(company_name)
def record_decision(self, company_name, trade_date, final_state):
"""Log a finished run's decision for reflection on the next same-ticker run."""