Blindfire Protocol Activated
This commit is contained in:
parent
d2ebd6d587
commit
3644e82f4e
|
|
@ -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`.
|
||||
|
|
|
|||
40
cli/main.py
40
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),
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in New Issue