Blindfire Protocol Activated

This commit is contained in:
swj.premkumar 2026-01-09 21:57:42 -06:00
parent d2ebd6d587
commit 3644e82f4e
3 changed files with 86 additions and 14 deletions

View File

@ -5,6 +5,9 @@ All notable changes to the **TradingAgents** project will be documented in this
## [Unreleased] - 2026-01-09
### Added
- **Blindfire Protocol Activated**: Fully integrated `TickerAnonymizer` into all analyst agents (`Market`, `News`, `Fundamentals`, `Social`) and data tools. The LLM now only sees "ASSET_XXX" in prompts, preventing data contamination.
- **Anonymization Middleware**: Implemented transparent request interception in `core_stock_tools.py` and other tool files to deanonymize inputs and anonymize outputs automatically.
- **State Persistence**: Added auto-persistence to `TickerAnonymizer` (`ticker_map.json`) to ensure consistent ticker mapping across isolated agent and tool instances.
- **API Key Verification**: Added `verify_google_key.py` script to isolate and verify Google API Key functionality for embeddings.
- **Environment Management**: Added `load_dotenv` to `cli/main.py` and `verify_google_key.py` to ensure `.env` variables are correctly loaded.
- **Start Script Enhancements**: Updated `start.sh` to check for `GOOGLE_API_KEY` existence and warn the user.
@ -14,8 +17,10 @@ All notable changes to the **TradingAgents** project will be documented in this
- **Embedding Model Error**: Fixed `BadRequestError` / `404 Not Found` when using Google (Gemini) provider by explicitly setting `text-embedding-004` and using the Google-compatible OpenAI endpoint (`generativelanguage.googleapis.com`).
- **Data Fetching Failure**: Resolved `RuntimeError: All vendor implementations failed for method 'get_fundamentals'` by implementing a fallback to `yfinance` in `tradingagents/dataflows/y_finance.py` and registering it in `interface.py`.
- **Report Saving Crash**: Fixed `TypeError: write() argument must be str, not list` in `cli/main.py` by converting structured list content to string before writing to files.
- **API Rate Limiting**: Added `max_retries` handling (exponential backoff) to both `ChatGoogleGenerativeAI` (10 retries) and `OpenAI` embedding client (5 retries) to robustly handle `429 RESOURCE_EXHAUSTED` errors.
- **Payload Size Error**: Implemented input truncation (max 9000 chars) in `memory.py`'s `get_embedding` method to prevent massive payloads from crashing the API.
- **API Rate Limiting**: Added `max_retries` handling (exponential backoff) to both `ChatGoogleGenerativeAI` (10 retries) and `OpenAI` embedding client (5 retries).
- **Import Errors**: Fixed `NameError: name 'tool' is not defined` by restoring `langchain_core` imports in data tools that were accidentally removed during Blindfire integration.
- **Payload Size Error**: Implemented input truncation (max 9000 chars) in `memory.py`.
- **Display Layer De-Anonymization**: Added `deanonymize_text` to `TickerAnonymizer` and patched `cli/main.py` to reverse-map "ASSET_XXX" to real company names in the final report, effectively resolving "[Company Name]" placeholders for the user while keeping the internal system blind.
### Changed
- **LLM Configuration**: Updated `tradingagents/default_config.py` and `cli/utils.py` to use valid Gemini model names (e.g., `gemini-1.5-flash`, `gemini-1.5-pro`) and `gemini-pro`.

View File

@ -13,6 +13,7 @@ from rich.spinner import Spinner
from rich.live import Live
from rich.columns import Columns
from rich.markdown import Markdown
from tradingagents.utils.anonymizer import TickerAnonymizer
from rich.layout import Layout
from rich.text import Text
from rich.live import Live
@ -524,6 +525,21 @@ def display_complete_report(final_state):
"""Display the complete analysis report with team-based panels."""
console.print("\n[bold green]Complete Analysis Report[/bold green]\n")
# Initialize Anonymizer for Output
anonymizer = TickerAnonymizer()
real_ticker = final_state.get("company_of_interest", "")
def process_text(text):
if not text:
return ""
# 1. Deanonymize ASSET_XXX -> AAPL
text = anonymizer.deanonymize_text(text)
# 2. Fix placeholders like "[Company Name]"
if real_ticker:
text = text.replace("[Company Name]", real_ticker)
text = text.replace("[Company]", real_ticker)
return text
# I. Analyst Team Reports
analyst_reports = []
@ -531,7 +547,7 @@ def display_complete_report(final_state):
if final_state.get("market_report"):
analyst_reports.append(
Panel(
Markdown(final_state["market_report"]),
Markdown(process_text(final_state["market_report"])),
title="Market Analyst",
border_style="blue",
padding=(1, 2),
@ -542,7 +558,7 @@ def display_complete_report(final_state):
if final_state.get("sentiment_report"):
analyst_reports.append(
Panel(
Markdown(final_state["sentiment_report"]),
Markdown(process_text(final_state["sentiment_report"])),
title="Social Analyst",
border_style="blue",
padding=(1, 2),
@ -553,7 +569,7 @@ def display_complete_report(final_state):
if final_state.get("news_report"):
analyst_reports.append(
Panel(
Markdown(final_state["news_report"]),
Markdown(process_text(final_state["news_report"])),
title="News Analyst",
border_style="blue",
padding=(1, 2),
@ -564,7 +580,7 @@ def display_complete_report(final_state):
if final_state.get("fundamentals_report"):
analyst_reports.append(
Panel(
Markdown(final_state["fundamentals_report"]),
Markdown(process_text(final_state["fundamentals_report"])),
title="Fundamentals Analyst",
border_style="blue",
padding=(1, 2),
@ -590,7 +606,7 @@ def display_complete_report(final_state):
if debate_state.get("bull_history"):
research_reports.append(
Panel(
Markdown(debate_state["bull_history"]),
Markdown(process_text(debate_state["bull_history"])),
title="Bull Researcher",
border_style="blue",
padding=(1, 2),
@ -601,7 +617,7 @@ def display_complete_report(final_state):
if debate_state.get("bear_history"):
research_reports.append(
Panel(
Markdown(debate_state["bear_history"]),
Markdown(process_text(debate_state["bear_history"])),
title="Bear Researcher",
border_style="blue",
padding=(1, 2),
@ -612,7 +628,7 @@ def display_complete_report(final_state):
if debate_state.get("judge_decision"):
research_reports.append(
Panel(
Markdown(debate_state["judge_decision"]),
Markdown(process_text(debate_state["judge_decision"])),
title="Research Manager",
border_style="blue",
padding=(1, 2),
@ -634,7 +650,7 @@ def display_complete_report(final_state):
console.print(
Panel(
Panel(
Markdown(final_state["trader_investment_plan"]),
Markdown(process_text(final_state["trader_investment_plan"])),
title="Trader",
border_style="blue",
padding=(1, 2),
@ -654,7 +670,7 @@ def display_complete_report(final_state):
if risk_state.get("risky_history"):
risk_reports.append(
Panel(
Markdown(risk_state["risky_history"]),
Markdown(process_text(risk_state["risky_history"])),
title="Aggressive Analyst",
border_style="blue",
padding=(1, 2),
@ -665,7 +681,7 @@ def display_complete_report(final_state):
if risk_state.get("safe_history"):
risk_reports.append(
Panel(
Markdown(risk_state["safe_history"]),
Markdown(process_text(risk_state["safe_history"])),
title="Conservative Analyst",
border_style="blue",
padding=(1, 2),
@ -676,7 +692,7 @@ def display_complete_report(final_state):
if risk_state.get("neutral_history"):
risk_reports.append(
Panel(
Markdown(risk_state["neutral_history"]),
Markdown(process_text(risk_state["neutral_history"])),
title="Neutral Analyst",
border_style="blue",
padding=(1, 2),
@ -698,7 +714,7 @@ def display_complete_report(final_state):
console.print(
Panel(
Panel(
Markdown(risk_state["judge_decision"]),
Markdown(process_text(risk_state["judge_decision"])),
title="Portfolio Manager",
border_style="blue",
padding=(1, 2),

View File

@ -303,6 +303,57 @@ class TickerAnonymizer:
return self.reverse_map.get(anon_ticker)
def deanonymize_text(self, text: str) -> str:
"""
Restore original company information in text.
Args:
text: Anonymized text
Returns:
Deanonymized text with real names and tickers.
"""
if not text:
return text
# 1. Reverse Product Maps (Product A -> iPhone)
# We need a reverse product map for this
reverse_product_map = {v: k for k, v in self.product_map.items()}
for anon_prod, real_prod in reverse_product_map.items():
text = re.sub(
rf'\b{re.escape(anon_prod)}\b',
real_prod,
text,
flags=re.IGNORECASE
)
# 2. Reverse Ticker and Company Name
# Iterate through all known mappings in reverse map
# Sort by length desc to handle potential overlaps if any (though ASSET_XXX is fixed len)
for anon_ticker, real_ticker in self.reverse_map.items():
# Replace "Company ASSET_XXX" -> "Apple" (or "Company Name" if stored)
if real_ticker in self.company_names:
real_name = self.company_names[real_ticker]
# Try to catch "Company ASSET_XXX" pattern first
text = re.sub(
rf'Company {anon_ticker}\b',
real_name,
text,
flags=re.IGNORECASE
)
# Replace remaining ASSET_XXX -> AAPL
text = re.sub(rf'\b{anon_ticker}\b', real_ticker, text, flags=re.IGNORECASE)
# 3. Catch-all: Replace "[Company Name]" if we can guess the target
# Since we usually run this for a specific target report, we might not know which "Real Name"
# to put in validly unless we know the context.
# But if we have ONE main ticker in our map that we just analyzed, we can start with that.
# For now, let's just stick to the text reversion logic.
return text
# Example usage
if __name__ == "__main__":
anonymizer = TickerAnonymizer()