From 6cddd26d6eab51e12ca8ab73b02bf9372980ca19 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 29 Mar 2026 19:19:01 +0000 Subject: [PATCH] feat: multi-language output support for analyst reports and final decision (#472) --- cli/main.py | 37 ++++++++++++------- cli/utils.py | 34 +++++++++++++++++ .../agents/analysts/fundamentals_analyst.py | 4 +- .../agents/analysts/market_analyst.py | 2 + tradingagents/agents/analysts/news_analyst.py | 2 + .../agents/analysts/social_media_analyst.py | 3 +- .../agents/managers/portfolio_manager.py | 4 +- tradingagents/agents/utils/agent_utils.py | 14 +++++++ tradingagents/default_config.py | 3 ++ 9 files changed, 86 insertions(+), 17 deletions(-) diff --git a/cli/main.py b/cli/main.py index 53837db2..29294d8d 100644 --- a/cli/main.py +++ b/cli/main.py @@ -519,10 +519,19 @@ def get_user_selections(): ) analysis_date = get_analysis_date() - # Step 3: Select analysts + # Step 3: Output language console.print( create_question_box( - "Step 3: Analysts Team", "Select your LLM analyst agents for the analysis" + "Step 3: Output Language", + "Select the language for analyst reports and final decision" + ) + ) + output_language = ask_output_language() + + # Step 4: Select analysts + console.print( + create_question_box( + "Step 4: Analysts Team", "Select your LLM analyst agents for the analysis" ) ) selected_analysts = select_analysts() @@ -530,32 +539,32 @@ def get_user_selections(): f"[green]Selected analysts:[/green] {', '.join(analyst.value for analyst in selected_analysts)}" ) - # Step 4: Research depth + # Step 5: Research depth console.print( create_question_box( - "Step 4: Research Depth", "Select your research depth level" + "Step 5: Research Depth", "Select your research depth level" ) ) selected_research_depth = select_research_depth() - # Step 5: OpenAI backend + # Step 6: LLM Provider console.print( create_question_box( - "Step 5: OpenAI backend", "Select which service to talk to" + "Step 6: LLM Provider", "Select your LLM provider" ) ) selected_llm_provider, backend_url = select_llm_provider() - - # Step 6: Thinking agents + + # Step 7: Thinking agents console.print( create_question_box( - "Step 6: Thinking Agents", "Select your thinking agents for analysis" + "Step 7: Thinking Agents", "Select your thinking agents for analysis" ) ) selected_shallow_thinker = select_shallow_thinking_agent(selected_llm_provider) selected_deep_thinker = select_deep_thinking_agent(selected_llm_provider) - # Step 7: Provider-specific thinking configuration + # Step 8: Provider-specific thinking configuration thinking_level = None reasoning_effort = None anthropic_effort = None @@ -564,7 +573,7 @@ def get_user_selections(): if provider_lower == "google": console.print( create_question_box( - "Step 7: Thinking Mode", + "Step 8: Thinking Mode", "Configure Gemini thinking mode" ) ) @@ -572,7 +581,7 @@ def get_user_selections(): elif provider_lower == "openai": console.print( create_question_box( - "Step 7: Reasoning Effort", + "Step 8: Reasoning Effort", "Configure OpenAI reasoning effort level" ) ) @@ -580,7 +589,7 @@ def get_user_selections(): elif provider_lower == "anthropic": console.print( create_question_box( - "Step 7: Effort Level", + "Step 8: Effort Level", "Configure Claude effort level" ) ) @@ -598,6 +607,7 @@ def get_user_selections(): "google_thinking_level": thinking_level, "openai_reasoning_effort": reasoning_effort, "anthropic_effort": anthropic_effort, + "output_language": output_language, } @@ -931,6 +941,7 @@ def run_analysis(): config["google_thinking_level"] = selections.get("google_thinking_level") config["openai_reasoning_effort"] = selections.get("openai_reasoning_effort") config["anthropic_effort"] = selections.get("anthropic_effort") + config["output_language"] = selections.get("output_language", "English") # Create stats callback handler for tracking LLM/tool calls stats_handler = StatsCallbackHandler() diff --git a/cli/utils.py b/cli/utils.py index 0166cd95..62b50c9c 100644 --- a/cli/utils.py +++ b/cli/utils.py @@ -281,3 +281,37 @@ def ask_gemini_thinking_config() -> str | None: ("pointer", "fg:green noinherit"), ]), ).ask() + + +def ask_output_language() -> str: + """Ask for report output language.""" + choice = questionary.select( + "Select Output Language:", + choices=[ + questionary.Choice("English (default)", "English"), + questionary.Choice("Chinese (中文)", "Chinese"), + questionary.Choice("Japanese (日本語)", "Japanese"), + questionary.Choice("Korean (한국어)", "Korean"), + questionary.Choice("Hindi (हिन्दी)", "Hindi"), + questionary.Choice("Spanish (Español)", "Spanish"), + questionary.Choice("Portuguese (Português)", "Portuguese"), + questionary.Choice("French (Français)", "French"), + questionary.Choice("German (Deutsch)", "German"), + questionary.Choice("Arabic (العربية)", "Arabic"), + questionary.Choice("Russian (Русский)", "Russian"), + questionary.Choice("Custom language", "custom"), + ], + style=questionary.Style([ + ("selected", "fg:yellow noinherit"), + ("highlighted", "fg:yellow noinherit"), + ("pointer", "fg:yellow noinherit"), + ]), + ).ask() + + if choice == "custom": + return questionary.text( + "Enter language name (e.g. Turkish, Vietnamese, Thai, Indonesian):", + validate=lambda x: len(x.strip()) > 0 or "Please enter a language name.", + ).ask().strip() + + return choice diff --git a/tradingagents/agents/analysts/fundamentals_analyst.py b/tradingagents/agents/analysts/fundamentals_analyst.py index 990398a6..3f70c734 100644 --- a/tradingagents/agents/analysts/fundamentals_analyst.py +++ b/tradingagents/agents/analysts/fundamentals_analyst.py @@ -8,6 +8,7 @@ from tradingagents.agents.utils.agent_utils import ( get_fundamentals, get_income_statement, get_insider_transactions, + get_language_instruction, ) from tradingagents.dataflows.config import get_config @@ -27,7 +28,8 @@ def create_fundamentals_analyst(llm): system_message = ( "You are a researcher tasked with analyzing fundamental information over the past week about a company. Please write a comprehensive report of the company's fundamental information such as financial documents, company profile, basic company financials, and company financial history to gain a full view of the company's fundamental information to inform traders. Make sure to include as much detail as possible. Provide specific, actionable insights with supporting evidence to help traders make informed decisions." + " Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read." - + " Use the available tools: `get_fundamentals` for comprehensive company analysis, `get_balance_sheet`, `get_cashflow`, and `get_income_statement` for specific financial statements.", + + " Use the available tools: `get_fundamentals` for comprehensive company analysis, `get_balance_sheet`, `get_cashflow`, and `get_income_statement` for specific financial statements." + + get_language_instruction(), ) prompt = ChatPromptTemplate.from_messages( diff --git a/tradingagents/agents/analysts/market_analyst.py b/tradingagents/agents/analysts/market_analyst.py index f5d17acd..680f9019 100644 --- a/tradingagents/agents/analysts/market_analyst.py +++ b/tradingagents/agents/analysts/market_analyst.py @@ -4,6 +4,7 @@ import json from tradingagents.agents.utils.agent_utils import ( build_instrument_context, get_indicators, + get_language_instruction, get_stock_data, ) from tradingagents.dataflows.config import get_config @@ -47,6 +48,7 @@ Volume-Based Indicators: - Select indicators that provide diverse and complementary information. Avoid redundancy (e.g., do not select both rsi and stochrsi). Also briefly explain why they are suitable for the given market context. When you tool call, please use the exact name of the indicators provided above as they are defined parameters, otherwise your call will fail. Please make sure to call get_stock_data first to retrieve the CSV that is needed to generate indicators. Then use get_indicators with the specific indicator names. Write a very detailed and nuanced report of the trends you observe. Provide specific, actionable insights with supporting evidence to help traders make informed decisions.""" + """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read.""" + + get_language_instruction() ) prompt = ChatPromptTemplate.from_messages( diff --git a/tradingagents/agents/analysts/news_analyst.py b/tradingagents/agents/analysts/news_analyst.py index 3697c6f6..42fc7a61 100644 --- a/tradingagents/agents/analysts/news_analyst.py +++ b/tradingagents/agents/analysts/news_analyst.py @@ -4,6 +4,7 @@ import json from tradingagents.agents.utils.agent_utils import ( build_instrument_context, get_global_news, + get_language_instruction, get_news, ) from tradingagents.dataflows.config import get_config @@ -22,6 +23,7 @@ def create_news_analyst(llm): system_message = ( "You are a news researcher tasked with analyzing recent news and trends over the past week. Please write a comprehensive report of the current state of the world that is relevant for trading and macroeconomics. Use the available tools: get_news(query, start_date, end_date) for company-specific or targeted news searches, and get_global_news(curr_date, look_back_days, limit) for broader macroeconomic news. Provide specific, actionable insights with supporting evidence to help traders make informed decisions." + """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read.""" + + get_language_instruction() ) prompt = ChatPromptTemplate.from_messages( diff --git a/tradingagents/agents/analysts/social_media_analyst.py b/tradingagents/agents/analysts/social_media_analyst.py index 43df2258..67d78f4c 100644 --- a/tradingagents/agents/analysts/social_media_analyst.py +++ b/tradingagents/agents/analysts/social_media_analyst.py @@ -1,7 +1,7 @@ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder import time import json -from tradingagents.agents.utils.agent_utils import build_instrument_context, get_news +from tradingagents.agents.utils.agent_utils import build_instrument_context, get_language_instruction, get_news from tradingagents.dataflows.config import get_config @@ -17,6 +17,7 @@ def create_social_media_analyst(llm): system_message = ( "You are a social media and company specific news researcher/analyst tasked with analyzing social media posts, recent company news, and public sentiment for a specific company over the past week. You will be given a company's name your objective is to write a comprehensive long report detailing your analysis, insights, and implications for traders and investors on this company's current state after looking at social media and what people are saying about that company, analyzing sentiment data of what people feel each day about the company, and looking at recent company news. Use the get_news(query, start_date, end_date) tool to search for company-specific news and social media discussions. Try to look at all sources possible from social media to sentiment to news. Provide specific, actionable insights with supporting evidence to help traders make informed decisions." + """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read.""" + + get_language_instruction() ) prompt = ChatPromptTemplate.from_messages( diff --git a/tradingagents/agents/managers/portfolio_manager.py b/tradingagents/agents/managers/portfolio_manager.py index acdf940b..970efb46 100644 --- a/tradingagents/agents/managers/portfolio_manager.py +++ b/tradingagents/agents/managers/portfolio_manager.py @@ -1,4 +1,4 @@ -from tradingagents.agents.utils.agent_utils import build_instrument_context +from tradingagents.agents.utils.agent_utils import build_instrument_context, get_language_instruction def create_portfolio_manager(llm, memory): @@ -50,7 +50,7 @@ def create_portfolio_manager(llm, memory): --- -Be decisive and ground every conclusion in specific evidence from the analysts.""" +Be decisive and ground every conclusion in specific evidence from the analysts.{get_language_instruction()}""" response = llm.invoke(prompt) diff --git a/tradingagents/agents/utils/agent_utils.py b/tradingagents/agents/utils/agent_utils.py index e4abc4cd..4ba40a80 100644 --- a/tradingagents/agents/utils/agent_utils.py +++ b/tradingagents/agents/utils/agent_utils.py @@ -20,6 +20,20 @@ from tradingagents.agents.utils.news_data_tools import ( ) +def get_language_instruction() -> str: + """Return a prompt instruction for the configured output language. + + Returns empty string when English (default), so no extra tokens are used. + Only applied to user-facing agents (analysts, portfolio manager). + Internal debate agents stay in English for reasoning quality. + """ + from tradingagents.dataflows.config import get_config + lang = get_config().get("output_language", "English") + if lang.strip().lower() == "english": + return "" + return f" Write your entire response in {lang}." + + def build_instrument_context(ticker: str) -> str: """Describe the exact instrument so agents preserve exchange-qualified tickers.""" return ( diff --git a/tradingagents/default_config.py b/tradingagents/default_config.py index 898e1e1e..31952c00 100644 --- a/tradingagents/default_config.py +++ b/tradingagents/default_config.py @@ -16,6 +16,9 @@ DEFAULT_CONFIG = { "google_thinking_level": None, # "high", "minimal", etc. "openai_reasoning_effort": None, # "medium", "high", "low" "anthropic_effort": None, # "high", "medium", "low" + # Output language for analyst reports and final decision + # Internal agent debate stays in English for reasoning quality + "output_language": "English", # Debate and discussion settings "max_debate_rounds": 1, "max_risk_discuss_rounds": 1,