diff --git a/ERROR_HANDLING_GUIDE.md b/ERROR_HANDLING_GUIDE.md new file mode 100644 index 00000000..7f4eb470 --- /dev/null +++ b/ERROR_HANDLING_GUIDE.md @@ -0,0 +1,102 @@ +# Enhanced Error Handling Guide + +## Overview +The TradingAgents system now includes comprehensive error handling for LLM API issues, providing clear, actionable feedback to users. + +## Supported Scenarios + +### 1. Invalid Model Configuration +**Error**: When an invalid model name is configured +**Response**: +- ❌ Clear error message indicating the invalid model +- 📋 List of valid models for the current provider +- 🔧 Specific configuration instructions + +### 2. API Quota Exceeded +**Error**: When API usage limits are reached +**Response**: +- ❌ Clear quota exceeded message +- 🔗 Direct links to billing/quota management +- 🔄 Alternative provider suggestions +- 📴 Offline tools recommendation + +### 3. Missing API Keys +**Error**: When required environment variables are not set +**Response**: +- ❌ Clear missing API key message +- 🔑 Exact export command to set the key +- 🔗 Links to get API keys + +### 4. Connection Issues +**Error**: When network/connectivity problems occur +**Response**: +- ❌ Connection problem identification +- 🌐 Possible causes (network, firewall, service down) +- 🔄 Alternative provider suggestions + +## Configuration Options + +### Switching Between Providers +```python +# In tradingagents/default_config.py + +# For OpenAI +"llm_provider": "openai", +"quick_think_llm": "gpt-4o-mini", +"deep_think_llm": "gpt-4o", + +# For Gemini +"llm_provider": "gemini", +"gemini_quick_think_llm": "gemini-1.5-flash", +"gemini_deep_think_llm": "gemini-1.5-pro", +``` + +### Using Offline Tools +```python +# Disable online tools to use local data sources +"online_tools": False, +``` + +## Valid Models + +### OpenAI Models +- `gpt-4o` +- `gpt-4o-mini` +- `gpt-4-turbo` +- `gpt-4` +- `gpt-3.5-turbo` +- `o1-preview` +- `o1-mini` + +### Gemini Models +- `gemini-1.5-pro` +- `gemini-1.5-flash` +- `gemini-1.0-pro` +- `gemini-pro` + +## Required Environment Variables + +### For OpenAI +```bash +export OPENAI_API_KEY=your_openai_key_here +``` + +### For Gemini +```bash +export GOOGLE_API_KEY=your_google_api_key_here +``` + +## Error Handling Flow + +1. **Agent Tool Called** → Online LLM function invoked +2. **API Error Occurs** → Comprehensive error handling triggers +3. **User-Friendly Message** → Detailed error with solutions returned +4. **Agent Continues** → Can use offline tools or different approach + +## Benefits + +- **Clear Problem Identification**: Emoji indicators and specific error types +- **Actionable Solutions**: Multiple alternatives provided for each error +- **Graceful Degradation**: Agents can continue with offline tools +- **User Education**: Links to documentation and setup guides +- **Configuration Guidance**: Exact settings and commands provided \ No newline at end of file diff --git a/main.py b/main.py index 6c8ae3d9..3cb5790d 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,5 @@ +from dotenv import load_dotenv +load_dotenv() from tradingagents.graph.trading_graph import TradingAgentsGraph from tradingagents.default_config import DEFAULT_CONFIG @@ -8,7 +10,7 @@ config["backend_url"] = "https://generativelanguage.googleapis.com/v1" # Use a config["deep_think_llm"] = "gemini-2.0-flash" # Use a different model config["quick_think_llm"] = "gemini-2.0-flash" # Use a different model config["max_debate_rounds"] = 1 # Increase debate rounds -config["online_tools"] = True # Increase debate rounds +config["online_tools"] = True # Initialize with custom config ta = TradingAgentsGraph(debug=True, config=config) diff --git a/tradingagents/agents/utils/agent_utils.py b/tradingagents/agents/utils/agent_utils.py index 0b07f044..b80d883e 100644 --- a/tradingagents/agents/utils/agent_utils.py +++ b/tradingagents/agents/utils/agent_utils.py @@ -368,7 +368,7 @@ class Toolkit: curr_date: Annotated[str, "Current date in yyyy-mm-dd format"], ): """ - Retrieve the latest news about a given stock by using OpenAI's news API. + Retrieve the latest news about a given stock by using LLM API (OpenAI/Gemini). Args: ticker (str): Ticker of a company. e.g. AAPL, TSM curr_date (str): Current date in yyyy-mm-dd format @@ -376,9 +376,12 @@ class Toolkit: str: A formatted string containing the latest news about the company on the given date. """ - openai_news_results = interface.get_stock_news_openai(ticker, curr_date) - - return openai_news_results + try: + openai_news_results = interface.get_stock_news_openai(ticker, curr_date) + return openai_news_results + except ValueError as e: + # Return the detailed error message to the agent + return f"⚠️ Online news tool failed:\n{str(e)}\n\nPlease use alternative offline tools or fix the configuration." @staticmethod @tool @@ -386,16 +389,19 @@ class Toolkit: curr_date: Annotated[str, "Current date in yyyy-mm-dd format"], ): """ - Retrieve the latest macroeconomics news on a given date using OpenAI's macroeconomics news API. + Retrieve the latest macroeconomics news on a given date using LLM API (OpenAI/Gemini). Args: curr_date (str): Current date in yyyy-mm-dd format Returns: str: A formatted string containing the latest macroeconomic news on the given date. """ - openai_news_results = interface.get_global_news_openai(curr_date) - - return openai_news_results + try: + openai_news_results = interface.get_global_news_openai(curr_date) + return openai_news_results + except ValueError as e: + # Return the detailed error message to the agent + return f"⚠️ Online global news tool failed:\n{str(e)}\n\nPlease use alternative offline tools or fix the configuration." @staticmethod @tool @@ -404,7 +410,7 @@ class Toolkit: curr_date: Annotated[str, "Current date in yyyy-mm-dd format"], ): """ - Retrieve the latest fundamental information about a given stock on a given date by using OpenAI's news API. + Retrieve the latest fundamental information about a given stock on a given date by using LLM API (OpenAI/Gemini). Args: ticker (str): Ticker of a company. e.g. AAPL, TSM curr_date (str): Current date in yyyy-mm-dd format @@ -412,8 +418,11 @@ class Toolkit: str: A formatted string containing the latest fundamental information about the company on the given date. """ - openai_fundamentals_results = interface.get_fundamentals_openai( - ticker, curr_date - ) - - return openai_fundamentals_results + try: + openai_fundamentals_results = interface.get_fundamentals_openai( + ticker, curr_date + ) + return openai_fundamentals_results + except ValueError as e: + # Return the detailed error message to the agent + return f"⚠️ Online fundamentals tool failed:\n{str(e)}\n\nPlease use alternative offline tools or fix the configuration." diff --git a/tradingagents/dataflows/interface.py b/tradingagents/dataflows/interface.py index 7fffbb4f..68ddd881 100644 --- a/tradingagents/dataflows/interface.py +++ b/tradingagents/dataflows/interface.py @@ -13,6 +13,7 @@ import pandas as pd from tqdm import tqdm import yfinance as yf from openai import OpenAI +from langchain_google_genai import ChatGoogleGenerativeAI from .config import get_config, set_config, DATA_DIR @@ -702,106 +703,237 @@ def get_YFin_data( return filtered_data -def get_stock_news_openai(ticker, curr_date): - config = get_config() - client = OpenAI(base_url=config["backend_url"]) +def _get_valid_models(provider): + """Get list of valid models for a given provider""" + if provider == "gemini": + return [ + "gemini-1.5-pro", + "gemini-1.5-flash", + "gemini-1.0-pro", + "gemini-pro" + ] + elif provider == "openai": + return [ + "gpt-4o", + "gpt-4o-mini", + "gpt-4-turbo", + "gpt-4", + "gpt-3.5-turbo", + "o1-preview", + "o1-mini" + ] + else: + return [] - response = client.responses.create( - model=config["quick_think_llm"], - input=[ - { - "role": "system", - "content": [ + +def _call_llm_api(prompt, config): + """Helper function to call either OpenAI or Gemini API based on configuration""" + provider = config["llm_provider"] + + if provider == "gemini": + # Use Gemini + import os + from google.api_core.exceptions import NotFound, ResourceExhausted + + # Check if API key is available + api_key = os.getenv("GOOGLE_API_KEY") + if not api_key: + raise ValueError( + "❌ GOOGLE_API_KEY environment variable is not set.\n" + "Please set your Google API key to use Gemini:\n" + "export GOOGLE_API_KEY=your_key_here" + ) + + model = config["gemini_quick_think_llm"] + valid_models = _get_valid_models("gemini") + + try: + gemini_model = ChatGoogleGenerativeAI( + model=model, + temperature=1, + max_tokens=4096, + google_api_key=api_key + ) + response = gemini_model.invoke(prompt) + return response.content + + except NotFound as e: + error_msg = ( + f"❌ Invalid Gemini model: '{model}'\n" + f"Valid Gemini models are:\n" + + "\n".join(f" • {m}" for m in valid_models) + + f"\n\nPlease update your configuration in default_config.py:\n" + f" 'gemini_quick_think_llm': 'gemini-1.5-flash' # or another valid model" + ) + raise ValueError(error_msg) from e + + except ResourceExhausted as e: + error_msg = ( + f"❌ Gemini API quota exceeded for model '{model}'\n" + f"You have hit your usage limits for the Gemini API.\n" + f"Options:\n" + f" • Wait for quota to reset (check: https://ai.google.dev/gemini-api/docs/rate-limits)\n" + f" • Upgrade your Gemini API plan\n" + f" • Switch to OpenAI by setting: 'llm_provider': 'openai' in default_config.py\n" + f" • Use offline tools by setting: 'online_tools': False in default_config.py" + ) + raise ValueError(error_msg) from e + + except Exception as e: + # Catch any other Gemini API errors + error_str = str(e).lower() + if "connection" in error_str or "network" in error_str: + error_msg = ( + f"❌ Gemini API connection failed\n" + f"Unable to connect to Google's Gemini API\n" + f"This could be due to:\n" + f" • Network connectivity issues\n" + f" • Invalid API key\n" + f" • Firewall blocking the connection\n" + f" • Google AI service temporarily unavailable\n" + f"\nAlternatives:\n" + f" • Switch to OpenAI: 'llm_provider': 'openai' in default_config.py\n" + f" • Use offline tools: 'online_tools': False in default_config.py" + ) + raise ValueError(error_msg) from e + elif "authentication" in error_str or "api key" in error_str: + error_msg = ( + f"❌ Gemini API authentication failed\n" + f"Your Google API key appears to be invalid or expired.\n" + f"Please check your GOOGLE_API_KEY environment variable.\n" + f"Get a valid key from: https://aistudio.google.com/app/apikey" + ) + raise ValueError(error_msg) from e + else: + # Re-raise other unexpected errors with provider context + error_msg = ( + f"❌ Gemini API error with model '{model}'\n" + f"Error: {str(e)}\n" + f"Valid models: {', '.join(valid_models[:5])}...\n" + f"\nAlternatives:\n" + f" • Switch to OpenAI: 'llm_provider': 'openai' in default_config.py\n" + f" • Use offline tools: 'online_tools': False in default_config.py" + ) + raise ValueError(error_msg) from e + + else: + # Use OpenAI (default) + import os + from openai import OpenAI, AuthenticationError, RateLimitError, NotFoundError + + # Check if API key is available + api_key = os.getenv("OPENAI_API_KEY") + if not api_key: + raise ValueError( + "❌ OPENAI_API_KEY environment variable is not set.\n" + "Please set your OpenAI API key:\n" + "export OPENAI_API_KEY=your_key_here" + ) + + model = config["quick_think_llm"] + valid_models = _get_valid_models("openai") + + try: + client = OpenAI(base_url=config["backend_url"]) + response = client.chat.completions.create( + model=model, + messages=[ { - "type": "input_text", - "text": f"Can you search Social Media for {ticker} from 7 days before {curr_date} to {curr_date}? Make sure you only get the data posted during that period.", + "role": "system", + "content": prompt, } ], - } - ], - text={"format": {"type": "text"}}, - reasoning={}, - tools=[ - { - "type": "web_search_preview", - "user_location": {"type": "approximate"}, - "search_context_size": "low", - } - ], - temperature=1, - max_output_tokens=4096, - top_p=1, - store=True, - ) + temperature=1, + max_tokens=4096, + top_p=1, + ) + return response.choices[0].message.content + + except NotFoundError as e: + error_msg = ( + f"❌ Invalid OpenAI model: '{model}'\n" + f"Valid OpenAI models are:\n" + + "\n".join(f" • {m}" for m in valid_models) + + f"\n\nPlease update your configuration in default_config.py:\n" + f" 'quick_think_llm': 'gpt-4o-mini' # or another valid model" + ) + raise ValueError(error_msg) from e + + except RateLimitError as e: + error_msg = ( + f"❌ OpenAI API quota exceeded for model '{model}'\n" + f"You have hit your usage limits for the OpenAI API.\n" + f"Options:\n" + f" • Check your billing and add credits: https://platform.openai.com/account/billing\n" + f" • Wait for quota to reset\n" + f" • Switch to Gemini by setting: 'llm_provider': 'gemini' in default_config.py\n" + f" • Use offline tools by setting: 'online_tools': False in default_config.py" + ) + raise ValueError(error_msg) from e + + except AuthenticationError as e: + error_msg = ( + f"❌ OpenAI API authentication failed\n" + f"Your API key appears to be invalid or expired.\n" + f"Please check your OPENAI_API_KEY environment variable.\n" + f"Get a valid key from: https://platform.openai.com/api-keys" + ) + raise ValueError(error_msg) from e + + except Exception as e: + # Catch any other OpenAI API errors (connection, etc.) + error_str = str(e).lower() + if "connection" in error_str: + error_msg = ( + f"❌ OpenAI API connection failed\n" + f"Unable to connect to OpenAI API at {config['backend_url']}\n" + f"This could be due to:\n" + f" • Network connectivity issues\n" + f" • Invalid backend URL\n" + f" • Firewall blocking the connection\n" + f" • OpenAI service temporarily unavailable\n" + f"\nAlternatives:\n" + f" • Switch to Gemini: 'llm_provider': 'gemini' in default_config.py\n" + f" • Use offline tools: 'online_tools': False in default_config.py" + ) + raise ValueError(error_msg) from e + elif "quota" in error_str or "rate limit" in error_str or "429" in error_str: + error_msg = ( + f"❌ OpenAI API quota/rate limit exceeded\n" + f"You have hit your usage limits for the OpenAI API.\n" + f"Options:\n" + f" • Check billing: https://platform.openai.com/account/billing\n" + f" • Wait for quota to reset\n" + f" • Switch to Gemini: 'llm_provider': 'gemini' in default_config.py\n" + f" • Use offline tools: 'online_tools': False in default_config.py" + ) + raise ValueError(error_msg) from e + else: + # Re-raise other unexpected errors with provider context + error_msg = ( + f"❌ OpenAI API error with model '{model}'\n" + f"Error: {str(e)}\n" + f"Valid models: {', '.join(valid_models[:5])}...\n" + f"\nAlternatives:\n" + f" • Switch to Gemini: 'llm_provider': 'gemini' in default_config.py\n" + f" • Use offline tools: 'online_tools': False in default_config.py" + ) + raise ValueError(error_msg) from e - return response.output[1].content[0].text + +def get_stock_news_openai(ticker, curr_date): + config = get_config() + prompt = f"Can you search Social Media for {ticker} from 7 days before {curr_date} to {curr_date}? Make sure you only get the data posted during that period." + return _call_llm_api(prompt, config) def get_global_news_openai(curr_date): config = get_config() - client = OpenAI(base_url=config["backend_url"]) - - response = client.responses.create( - model=config["quick_think_llm"], - input=[ - { - "role": "system", - "content": [ - { - "type": "input_text", - "text": f"Can you search global or macroeconomics news from 7 days before {curr_date} to {curr_date} that would be informative for trading purposes? Make sure you only get the data posted during that period.", - } - ], - } - ], - text={"format": {"type": "text"}}, - reasoning={}, - tools=[ - { - "type": "web_search_preview", - "user_location": {"type": "approximate"}, - "search_context_size": "low", - } - ], - temperature=1, - max_output_tokens=4096, - top_p=1, - store=True, - ) - - return response.output[1].content[0].text + prompt = f"Can you search global or macroeconomics news from 7 days before {curr_date} to {curr_date} that would be informative for trading purposes? Make sure you only get the data posted during that period." + return _call_llm_api(prompt, config) def get_fundamentals_openai(ticker, curr_date): config = get_config() - client = OpenAI(base_url=config["backend_url"]) - - response = client.responses.create( - model=config["quick_think_llm"], - input=[ - { - "role": "system", - "content": [ - { - "type": "input_text", - "text": f"Can you search Fundamental for discussions on {ticker} during of the month before {curr_date} to the month of {curr_date}. Make sure you only get the data posted during that period. List as a table, with PE/PS/Cash flow/ etc", - } - ], - } - ], - text={"format": {"type": "text"}}, - reasoning={}, - tools=[ - { - "type": "web_search_preview", - "user_location": {"type": "approximate"}, - "search_context_size": "low", - } - ], - temperature=1, - max_output_tokens=4096, - top_p=1, - store=True, - ) - - return response.output[1].content[0].text + prompt = f"Can you search for fundamental analysis discussions on {ticker} during the month before {curr_date} to the month of {curr_date}. Make sure you only get the data posted during that period. List as a table, with PE/PS/Cash flow/ etc" + return _call_llm_api(prompt, config) diff --git a/tradingagents/default_config.py b/tradingagents/default_config.py index 089e9c24..d0b3ca4e 100644 --- a/tradingagents/default_config.py +++ b/tradingagents/default_config.py @@ -9,10 +9,13 @@ DEFAULT_CONFIG = { "dataflows/data_cache", ), # LLM settings - "llm_provider": "openai", + "llm_provider": "openai", # "openai" or "gemini" "deep_think_llm": "o4-mini", "quick_think_llm": "gpt-4o-mini", "backend_url": "https://api.openai.com/v1", + # Gemini settings (used when llm_provider is "gemini") + "gemini_deep_think_llm": "gemini-1.5-pro", + "gemini_quick_think_llm": "gemini-1.5-flash", # Debate and discussion settings "max_debate_rounds": 1, "max_risk_discuss_rounds": 1,