From 3644e82f4efa6ed87297c8c96e47bd59e1737527 Mon Sep 17 00:00:00 2001 From: "swj.premkumar" Date: Fri, 9 Jan 2026 21:57:42 -0600 Subject: [PATCH] Blindfire Protocol Activated --- CHANGELOG.md | 9 ++++-- cli/main.py | 40 ++++++++++++++++-------- tradingagents/utils/anonymizer.py | 51 +++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d390b1a..e01d63e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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`. diff --git a/cli/main.py b/cli/main.py index c49c0d13..f03fec71 100644 --- a/cli/main.py +++ b/cli/main.py @@ -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), diff --git a/tradingagents/utils/anonymizer.py b/tradingagents/utils/anonymizer.py index e0bfc2cd..196fdfb9 100644 --- a/tradingagents/utils/anonymizer.py +++ b/tradingagents/utils/anonymizer.py @@ -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()