11 KiB
Tool System Architecture
Overview
The TradingAgents tool system has been redesigned with a registry-based architecture that eliminates code duplication, reduces complexity, and makes it easy to add new tools.
Key Improvements
Before (Old System)
- 6-7 layers of function calls for a single data fetch
- Tools defined in 4+ places (duplicated)
- Dual registry systems (new unused, legacy used)
- Complex 3-level config lookup (tool → category → vendor)
- Manual agent-tool mapping scattered across files
- Unnecessary re-export layer (agent_utils.py)
- Adding a tool required changes in 6 files
After (New System)
- 3 layers for tool execution (clean, predictable)
- Single source of truth for all tool metadata
- One registry (TOOL_REGISTRY)
- Simplified routing with optional fallbacks
- Auto-generated agent-tool mappings
- Auto-generated LangChain @tool wrappers
- Adding a tool requires changes in 1 file
Architecture Components
1. Tool Registry (tradingagents/tools/registry.py)
The single source of truth for all tools. Each tool is defined once with complete metadata:
TOOL_REGISTRY: Dict[str, Dict[str, Any]] = {
"get_stock_data": {
"description": "Retrieve stock price data (OHLCV) for a given ticker symbol",
"category": "core_stock_apis",
"agents": ["market"], # Which agents can use this tool
"primary_vendor": "yfinance", # Primary data vendor
"fallback_vendors": ["alpha_vantage"], # Optional fallbacks
"parameters": {
"symbol": {"type": "str", "description": "Ticker symbol"},
"start_date": {"type": "str", "description": "Start date yyyy-mm-dd"},
"end_date": {"type": "str", "description": "End date yyyy-mm-dd"},
},
"returns": "str: Formatted dataframe containing stock price data",
},
# ... 15 more tools
}
Helper Functions:
get_tools_for_agent(agent_name)→ List of tool names for agentget_tool_metadata(tool_name)→ Complete metadata dictget_vendor_config(tool_name)→ Vendor configurationget_agent_tool_mapping()→ Full agent→tools mappingvalidate_registry()→ Check for issues
2. Tool Executor (tradingagents/tools/executor.py)
Simplified tool execution that replaces the complex route_to_vendor():
def execute_tool(tool_name: str, *args, **kwargs) -> Any:
"""Execute a tool using registry-based routing.
Workflow:
1. Get vendor config from registry
2. Build vendor list (primary + fallbacks)
3. Try each vendor in order
4. Return result or raise ToolExecutionError
"""
vendor_config = get_vendor_config(tool_name)
vendors_to_try = [vendor_config["primary"]] + vendor_config["fallbacks"]
for vendor in vendors_to_try:
try:
result = _execute_with_vendor(tool_name, vendor, *args, **kwargs)
return result
except Exception:
continue # Try next vendor
raise ToolExecutionError("All vendors failed")
Features:
- Clear error messages
- Debug logging
- Optional fallback support
- Backward compatible with old
route_to_vendor()
3. Tool Generator (tradingagents/tools/generator.py)
Auto-generates LangChain @tool wrappers from the registry:
def generate_langchain_tool(tool_name: str, metadata: Dict[str, Any]) -> Callable:
"""Generate a LangChain @tool wrapper for a specific tool.
This eliminates the need for manual @tool definitions.
"""
# Build parameter annotations from registry
param_annotations = {}
for param_name, param_info in metadata["parameters"].items():
param_type = _get_python_type(param_info["type"])
param_annotations[param_name] = Annotated[param_type, param_info["description"]]
# Create tool function dynamically
def tool_function(**kwargs):
return execute_tool(tool_name, **kwargs)
# Apply @tool decorator and return
return tool(tool_function)
Pre-Generated Tools:
# Generate all tools once at module import time
ALL_TOOLS = generate_all_tools()
# Export for easy import
get_stock_data = ALL_TOOLS["get_stock_data"]
get_news = ALL_TOOLS["get_news"]
# ... all 16 tools
Agent-Specific Tools:
def get_agent_tools(agent_name: str) -> list:
"""Get list of tool functions for a specific agent."""
agent_tools = generate_tools_for_agent(agent_name)
return list(agent_tools.values())
How to Add a New Tool
Old way: Edit 6 files (registry.py, vendor files, agent_utils.py, tools.py, config, tests)
New way: Edit 1 file (registry.py)
Example: Adding a "get_earnings" tool
- Open
tradingagents/tools/registry.py - Add one entry to
TOOL_REGISTRY:
"get_earnings": {
"description": "Retrieve earnings data for a ticker",
"category": "fundamental_data",
"agents": ["fundamentals"],
"primary_vendor": "alpha_vantage",
"fallback_vendors": ["yfinance"],
"parameters": {
"ticker": {"type": "str", "description": "Ticker symbol"},
"quarters": {"type": "int", "description": "Number of quarters", "default": 4},
},
"returns": "str: Earnings data report",
},
- Run
python -m tradingagents.tools.generatorto regenerate tools.py - Done! The tool is now available to all fundamentals agents
Call Flow
Old System (6-7 layers)
Agent calls tool
→ agent_utils.py re-export
→ tools.py @tool wrapper
→ route_to_vendor()
→ _get_vendor_for_category()
→ _get_vendor_for_tool()
→ VENDOR_METHODS lookup
→ vendor function
New System (3 layers)
Agent calls tool
→ execute_tool(tool_name, **kwargs)
→ vendor function
Integration Points
Trading Graph (tradingagents/graph/trading_graph.py)
def _create_tool_nodes(self) -> Dict[str, ToolNode]:
"""Create tool nodes using registry-based system."""
from tradingagents.tools.generator import get_agent_tools
tool_nodes = {}
for agent_name in ["market", "social", "news", "fundamentals"]:
tools = get_agent_tools(agent_name) # Auto-generated from registry
if tools:
tool_nodes[agent_name] = ToolNode(tools)
return tool_nodes
Discovery Graph (tradingagents/graph/discovery_graph.py)
from tradingagents.tools.executor import execute_tool
# Old: reddit_report = route_to_vendor("get_trending_tickers", limit=15)
# New:
reddit_report = execute_tool("get_trending_tickers", limit=15)
Agent Utils (tradingagents/agents/utils/agent_utils.py)
from tradingagents.tools.generator import ALL_TOOLS
# Re-export for backward compatibility
get_stock_data = ALL_TOOLS["get_stock_data"]
get_news = ALL_TOOLS["get_news"]
# ...
Testing
Run the comprehensive test suite:
python test_new_tool_system.py
This tests:
- Registry loading and validation
- Tool generation for all 16 tools
- Agent-specific tool mappings
- Tool executor functionality
- Integration with trading_graph
Configuration
The new system uses the same configuration format as before:
DEFAULT_CONFIG = {
"data_vendors": {
"core_stock_apis": "yfinance",
"technical_indicators": "yfinance",
"fundamental_data": "alpha_vantage",
"news_data": "reddit,alpha_vantage", # Multi-vendor with fallback
},
"tool_vendors": {
# Tool-level overrides (optional)
"get_stock_data": "alpha_vantage", # Override category default
},
}
Current Tools (16 Total)
Core Stock APIs (2)
get_stock_data- OHLCV price datavalidate_ticker- Ticker validation
Technical Indicators (1)
get_indicators- RSI, MACD, SMA, EMA
Fundamental Data (5)
get_fundamentals- Comprehensive fundamentalsget_balance_sheet- Balance sheet dataget_cashflow- Cash flow statementget_income_statement- Income statementget_recommendation_trends- Analyst recommendations
News & Insider Data (4)
get_news- Ticker-specific newsget_global_news- Global market newsget_insider_sentiment- Insider trading sentimentget_insider_transactions- Insider transaction historyget_reddit_discussions- Reddit discussions
Discovery Tools (4)
get_trending_tickers- Reddit trending stocksget_market_movers- Top gainers/losersget_tweets- Twitter search
Agent-Tool Mapping
| Agent | Tools | Count |
|---|---|---|
| market | get_stock_data, get_indicators | 2 |
| social | get_news, get_reddit_discussions | 2 |
| news | get_news, get_global_news, get_insider_sentiment, get_insider_transactions | 4 |
| fundamentals | get_fundamentals, get_balance_sheet, get_cashflow, get_income_statement, get_recommendation_trends | 5 |
Backward Compatibility
The new system maintains full backward compatibility:
-
Old imports still work:
from tradingagents.agents.utils.agent_utils import get_stock_data -
Legacy
route_to_vendor()still works:from tradingagents.tools.executor import route_to_vendor # Deprecated route_to_vendor("get_stock_data", symbol="AAPL") # Still works -
Old configuration format supported
Migration Guide
If you have custom code using the old system:
Before
from tradingagents.dataflows.interface import route_to_vendor
data = route_to_vendor("get_stock_data", symbol="AAPL", start_date="2024-01-01")
After
from tradingagents.tools.executor import execute_tool
data = execute_tool("get_stock_data", symbol="AAPL", start_date="2024-01-01")
Benefits Summary
✅ Simpler - 3 layers instead of 6-7 ✅ DRY - Single source of truth, no duplication ✅ Flexible - Optional fallbacks per tool ✅ Maintainable - Add tools by editing 1 file instead of 6 ✅ Type-Safe - Auto-generated type annotations ✅ Testable - Clear, isolated components ✅ Documented - Self-documenting registry ✅ Backward Compatible - Old code still works
Developer Experience
Adding a tool: Before vs After
| Step | Old System | New System |
|---|---|---|
| 1. Define metadata | Edit registry.py |
Edit registry.py |
| 2. Add vendor implementation | Edit vendor file | (already exists) |
| 3. Update VENDOR_METHODS | Edit interface.py |
(auto-handled) |
| 4. Create @tool wrapper | Edit tools.py |
(auto-generated) |
| 5. Re-export in agent_utils | Edit agent_utils.py |
(auto-generated) |
| 6. Update agent mapping | Edit multiple files | (auto-generated) |
| 7. Update config schema | Edit default_config.py |
(optional) |
| Total files to edit | 6 files | 1 file |
| Lines of code | ~100 lines | ~15 lines |
Future Improvements
Potential enhancements:
- Add tool usage analytics
- Performance monitoring per vendor
- Auto-retry with exponential backoff
- Caching layer for repeated calls
- Rate limiting per vendor
- Vendor health checks
- Tool versioning support