feat: multi-language output support for analyst reports and final decision (#472)

This commit is contained in:
Yijia-Xiao 2026-03-29 19:19:01 +00:00
parent c61242a28c
commit 6cddd26d6e
No known key found for this signature in database
9 changed files with 86 additions and 17 deletions

View File

@ -519,10 +519,19 @@ def get_user_selections():
) )
analysis_date = get_analysis_date() analysis_date = get_analysis_date()
# Step 3: Select analysts # Step 3: Output language
console.print( console.print(
create_question_box( 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() 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)}" f"[green]Selected analysts:[/green] {', '.join(analyst.value for analyst in selected_analysts)}"
) )
# Step 4: Research depth # Step 5: Research depth
console.print( console.print(
create_question_box( 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() selected_research_depth = select_research_depth()
# Step 5: OpenAI backend # Step 6: LLM Provider
console.print( console.print(
create_question_box( 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() selected_llm_provider, backend_url = select_llm_provider()
# Step 6: Thinking agents # Step 7: Thinking agents
console.print( console.print(
create_question_box( 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_shallow_thinker = select_shallow_thinking_agent(selected_llm_provider)
selected_deep_thinker = select_deep_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 thinking_level = None
reasoning_effort = None reasoning_effort = None
anthropic_effort = None anthropic_effort = None
@ -564,7 +573,7 @@ def get_user_selections():
if provider_lower == "google": if provider_lower == "google":
console.print( console.print(
create_question_box( create_question_box(
"Step 7: Thinking Mode", "Step 8: Thinking Mode",
"Configure Gemini thinking mode" "Configure Gemini thinking mode"
) )
) )
@ -572,7 +581,7 @@ def get_user_selections():
elif provider_lower == "openai": elif provider_lower == "openai":
console.print( console.print(
create_question_box( create_question_box(
"Step 7: Reasoning Effort", "Step 8: Reasoning Effort",
"Configure OpenAI reasoning effort level" "Configure OpenAI reasoning effort level"
) )
) )
@ -580,7 +589,7 @@ def get_user_selections():
elif provider_lower == "anthropic": elif provider_lower == "anthropic":
console.print( console.print(
create_question_box( create_question_box(
"Step 7: Effort Level", "Step 8: Effort Level",
"Configure Claude effort level" "Configure Claude effort level"
) )
) )
@ -598,6 +607,7 @@ def get_user_selections():
"google_thinking_level": thinking_level, "google_thinking_level": thinking_level,
"openai_reasoning_effort": reasoning_effort, "openai_reasoning_effort": reasoning_effort,
"anthropic_effort": anthropic_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["google_thinking_level"] = selections.get("google_thinking_level")
config["openai_reasoning_effort"] = selections.get("openai_reasoning_effort") config["openai_reasoning_effort"] = selections.get("openai_reasoning_effort")
config["anthropic_effort"] = selections.get("anthropic_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 # Create stats callback handler for tracking LLM/tool calls
stats_handler = StatsCallbackHandler() stats_handler = StatsCallbackHandler()

View File

@ -281,3 +281,37 @@ def ask_gemini_thinking_config() -> str | None:
("pointer", "fg:green noinherit"), ("pointer", "fg:green noinherit"),
]), ]),
).ask() ).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

View File

@ -8,6 +8,7 @@ from tradingagents.agents.utils.agent_utils import (
get_fundamentals, get_fundamentals,
get_income_statement, get_income_statement,
get_insider_transactions, get_insider_transactions,
get_language_instruction,
) )
from tradingagents.dataflows.config import get_config from tradingagents.dataflows.config import get_config
@ -27,7 +28,8 @@ def create_fundamentals_analyst(llm):
system_message = ( 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." "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." + " 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( prompt = ChatPromptTemplate.from_messages(

View File

@ -4,6 +4,7 @@ import json
from tradingagents.agents.utils.agent_utils import ( from tradingagents.agents.utils.agent_utils import (
build_instrument_context, build_instrument_context,
get_indicators, get_indicators,
get_language_instruction,
get_stock_data, get_stock_data,
) )
from tradingagents.dataflows.config import get_config 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.""" - 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.""" + """ 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( prompt = ChatPromptTemplate.from_messages(

View File

@ -4,6 +4,7 @@ import json
from tradingagents.agents.utils.agent_utils import ( from tradingagents.agents.utils.agent_utils import (
build_instrument_context, build_instrument_context,
get_global_news, get_global_news,
get_language_instruction,
get_news, get_news,
) )
from tradingagents.dataflows.config import get_config from tradingagents.dataflows.config import get_config
@ -22,6 +23,7 @@ def create_news_analyst(llm):
system_message = ( 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." "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.""" + """ 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( prompt = ChatPromptTemplate.from_messages(

View File

@ -1,7 +1,7 @@
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
import time import time
import json 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 from tradingagents.dataflows.config import get_config
@ -17,6 +17,7 @@ def create_social_media_analyst(llm):
system_message = ( 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." "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.""" + """ 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( prompt = ChatPromptTemplate.from_messages(

View File

@ -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): 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) response = llm.invoke(prompt)

View File

@ -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: def build_instrument_context(ticker: str) -> str:
"""Describe the exact instrument so agents preserve exchange-qualified tickers.""" """Describe the exact instrument so agents preserve exchange-qualified tickers."""
return ( return (

View File

@ -16,6 +16,9 @@ DEFAULT_CONFIG = {
"google_thinking_level": None, # "high", "minimal", etc. "google_thinking_level": None, # "high", "minimal", etc.
"openai_reasoning_effort": None, # "medium", "high", "low" "openai_reasoning_effort": None, # "medium", "high", "low"
"anthropic_effort": None, # "high", "medium", "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 # Debate and discussion settings
"max_debate_rounds": 1, "max_debate_rounds": 1,
"max_risk_discuss_rounds": 1, "max_risk_discuss_rounds": 1,