From 038de4d32e7040a1a7dd1f498d2ac50c3fa17283 Mon Sep 17 00:00:00 2001 From: hazeone <709547807@qq.com> Date: Wed, 29 Oct 2025 15:49:45 +0800 Subject: [PATCH 1/2] opt agent card, remove additional info --- .../agent_cards/auto_trading_agent.json | 137 ++---------------- 1 file changed, 14 insertions(+), 123 deletions(-) diff --git a/python/configs/agent_cards/auto_trading_agent.json b/python/configs/agent_cards/auto_trading_agent.json index 78233db76..be8bd5f66 100644 --- a/python/configs/agent_cards/auto_trading_agent.json +++ b/python/configs/agent_cards/auto_trading_agent.json @@ -8,67 +8,20 @@ "push_notifications": true }, "skills": [{ - "id": "auto_trading_setup", - "name": "Setup Auto Trading", - "description": "Configure the automated trading agent with initial capital, crypto symbols, risk parameters, and AI model selection.", - "examples": [ - "Setup trading with $100,000 for BTC-USD and ETH-USD using DeepSeek, Grok, Claude model", - "Configure auto trader with $50,000 capital, trade BTC-USD, ETH-USD, SOL-USD with 1.5% risk", - "Setup trading agent with default settings for Bitcoin" - ], - "tags": [ - "setup", - "configuration", - "initialization" - ] - }, - { - "id": "auto_trading_notify", - "name": "Live Trading Monitoring", - "description": "Start continuous automated trading with real-time technical analysis, trade execution notifications, and portfolio updates.", - "examples": [ - "Start automated trading for BTC-USD with Claude model", - "Monitor and trade ETH-USD and SOL-USD using GPT-4 analysis", - "Begin live trading with portfolio notifications" - ], - "tags": [ - "trading", - "monitoring", - "real-time", - "notifications" - ] - }, - { - "id": "technical_analysis", - "name": "Technical Analysis", - "description": "Calculate and analyze technical indicators including MACD, RSI, EMA, and Bollinger Bands for trading decisions.", - "examples": [ - "Analyze BTC-USD technical indicators", - "Show MACD and RSI signals for ETH-USD", - "Calculate Bollinger Bands for SOL-USD" - ], - "tags": [ - "analysis", - "indicators", - "signals" - ] - }, - { - "id": "position_management", - "name": "Position Management", - "description": "Manage trading positions with risk controls, automatic entry/exit, and portfolio tracking.", - "examples": [ - "Show current positions and portfolio value", - "What is my P&L on open trades?", - "Display portfolio summary" - ], - "tags": [ - "positions", - "portfolio", - "risk-management" - ] - } - ], + "id": "auto_trading_setup", + "name": "Setup Auto Trading", + "description": "Configure the automated trading agent with initial capital, crypto symbols, risk parameters, and AI model selection.", + "examples": [ + "Setup trading with $100,000 for BTC-USD and ETH-USD using DeepSeek, Grok, Claude model", + "Configure auto trader with $50,000 capital, trade BTC-USD, ETH-USD, SOL-USD with 1.5% risk", + "Setup trading agent with default settings for Bitcoin" + ], + "tags": [ + "setup", + "configuration", + "initialization" + ] + }], "enabled": true, "provider": { "organization": "ValueCell", @@ -85,74 +38,12 @@ "ai-trading", "real-time" ], - "supported_crypto_symbols": [ - "BTC-USD", - "ETH-USD", - "USDT-USD", - "BNB-USD", - "XRP-USD", - "SOL-USD", - "USDC-USD", - "TRX-USD", - "DOGE-USD", - "ADA-USD", - "MATIC-USD", - "WBTC-USD", - "LINK-USD", - "XLM-USD", - "BCH-USD", - "SUI-USD", - "HBAR-USD", - "AVAX-USD", - "TON-USD", - "LTC-USD", - "MNT-USD", - "NEAR-USD", - "FLOW-USD", - "OKB-USD", - "APE-USD", - "ATOM-USD", - "QNT-USD", - "BSV-USD", - "EOS-USD", - "NEO-USD", - "DYDX-USD", - "CRV-USD", - "KLAY-USD", - "MINA-USD", - "CHZ-USD", - "AXS-USD", - "FTM-USD", - "IMX-USD", - "ZEC-USD", - "ALGO-USD", - "XDC-USD", - "ICP-USD", - "BTG-USD", - "BLZ-USD", - "ENJ-USD", - "KSM-USD", - "VGX-USD", - "RUNE-USD", - "BAT-USD", - "THETA-USD", - "QTUM-USD" - ], "technical_indicators": [ "MACD (12/26/9)", "RSI (14)", "EMA (12/26/50)", "Bollinger Bands (20, 2σ)" ], - "supported_models": [ - "deepseek/deepseek-v3.1-terminus", - "openai/gpt-5", - "anthropic/claude-sonnet-4.5", - "x-ai/grok-4", - "qwen/qwen-max", - "google/gemini-2.5-pro", - "meta-llama/llama-3.1-70b-instruct" - ], "trading_features": [ "Long and Short positions", "Risk-based position sizing", From de50e504f995a496c5e32d997397b99e1aab3745 Mon Sep 17 00:00:00 2001 From: hazeone <709547807@qq.com> Date: Wed, 29 Oct 2025 15:50:10 +0800 Subject: [PATCH 2/2] opt model provider fallback logic --- python/configs/agents/auto_trading_agent.yaml | 10 +- python/configs/agents/research_agent.yaml | 9 +- python/configs/agents/super_agent.yaml | 8 +- python/valuecell/adapters/models/factory.py | 121 +++++++++++++++++- python/valuecell/config/manager.py | 47 ++++++- 5 files changed, 179 insertions(+), 16 deletions(-) diff --git a/python/configs/agents/auto_trading_agent.yaml b/python/configs/agents/auto_trading_agent.yaml index 4e842d8e6..cae530ab6 100644 --- a/python/configs/agents/auto_trading_agent.yaml +++ b/python/configs/agents/auto_trading_agent.yaml @@ -15,10 +15,14 @@ enabled: true models: primary: model_id: "deepseek/deepseek-v3.1-terminus" - provider: openrouter + provider: "openrouter" # Must explicitly specify provider (not null) + # Provider-specific model mappings for fallback + provider_models: + openrouter: "anthropic/claude-haiku-4.5" # Fast and reasoning capable + google: "gemini-2.5-flash" # Fast and efficient parameters: - temperature: 0.5 - max_tokens: 2048 + # temperature: 0.5 + # max_tokens: 2048 # Environment Variable Overrides # Format: ENV_VAR_NAME -> config.path diff --git a/python/configs/agents/research_agent.yaml b/python/configs/agents/research_agent.yaml index a4f69de80..969dfc495 100644 --- a/python/configs/agents/research_agent.yaml +++ b/python/configs/agents/research_agent.yaml @@ -11,6 +11,10 @@ models: primary: model_id: "google/gemini-2.5-flash" provider: "openrouter" + # Provider-specific model mappings for fallback + provider_models: + siliconflow: "Qwen/Qwen3-235B-A22B-Thinking-2507" + google: "gemini-2.5-flash" parameters: # temperature: 0.7 # max_tokens: 8192 @@ -19,7 +23,10 @@ models: # Note: If not specified, will auto-select from available providers embedding: model_id: "Qwen/Qwen3-Embedding-4B" # Optional, uses provider default - provider: "siliconflow" # Optional, auto-detects if not specified + provider: "siliconflow" # Must explicitly specify provider + # Provider-specific embedding model mappings for fallback + provider_models: + google: "gemini-embedding-001" parameters: dimensions: ${EMBEDDER_DIMENSION:2560} # encoding_format: "float" diff --git a/python/configs/agents/super_agent.yaml b/python/configs/agents/super_agent.yaml index 67fe76944..cb24432a7 100644 --- a/python/configs/agents/super_agent.yaml +++ b/python/configs/agents/super_agent.yaml @@ -21,7 +21,13 @@ models: # Primary model for triage, reasoning, and query enrichment primary: model_id: "anthropic/claude-haiku-4.5" - provider: "openrouter" # Can be: openrouter, siliconflow, or null (uses system primary_provider) + provider: "openrouter" # Must explicitly specify provider (not null) + + # Provider-specific model mappings for fallback + # Used when primary provider fails and fallback is attempted + provider_models: + siliconflow: "zai-org/GLM-4.6" # Similar capability to Claude Haiku + google: "gemini-2.5-flash" # Fast and efficient like Claude Haiku # Environment Variable Overrides # Format: ENV_VAR_NAME -> config.path diff --git a/python/valuecell/adapters/models/factory.py b/python/valuecell/adapters/models/factory.py index edfb7441f..d2ec63ee7 100644 --- a/python/valuecell/adapters/models/factory.py +++ b/python/valuecell/adapters/models/factory.py @@ -296,6 +296,7 @@ def create_model( model_id: Optional[str] = None, provider: Optional[str] = None, use_fallback: bool = True, + provider_models: Optional[dict] = None, **kwargs, ): """ @@ -310,6 +311,8 @@ def create_model( model_id: Specific model ID (optional, uses provider default) provider: Provider name (optional, uses primary_provider) use_fallback: Try fallback providers if primary fails + provider_models: Dict mapping provider names to model IDs for fallback + e.g., {"siliconflow": "deepseek-ai/DeepSeek-V3.1-Terminus"} **kwargs: Additional arguments for model creation Returns: @@ -323,8 +326,16 @@ def create_model( >>> model = factory.create_model() # Uses primary provider + default model >>> model = factory.create_model(provider="google") # Specific provider >>> model = factory.create_model(model_id="gpt-4", provider="openrouter") + >>> # With provider-specific models + >>> model = factory.create_model( + ... model_id="anthropic/claude-3.5-sonnet", + ... provider="openrouter", + ... provider_models={"siliconflow": "deepseek-ai/DeepSeek-V3.1-Terminus"} + ... ) """ provider = provider or self.config_manager.primary_provider + if provider_models is None: + provider_models = {} # Try primary provider try: @@ -341,9 +352,29 @@ def create_model( continue # Skip already tried provider try: + # Determine model ID for fallback provider + fallback_model_id = model_id + + # Priority: provider_models > default_model + if fallback_provider in provider_models: + fallback_model_id = provider_models[fallback_provider] + logger.info( + f"Using provider-specific model for {fallback_provider}: {fallback_model_id}" + ) + else: + # Use provider's default model + fallback_provider_config = ( + self.config_manager.get_provider_config(fallback_provider) + ) + if fallback_provider_config: + fallback_model_id = fallback_provider_config.default_model + logger.info( + f"Using default model for {fallback_provider}: {fallback_model_id}" + ) + logger.info(f"Trying fallback provider: {fallback_provider}") return self._create_model_internal( - model_id, fallback_provider, **kwargs + fallback_model_id, fallback_provider, **kwargs ) except Exception as fallback_error: logger.warning( @@ -435,11 +466,44 @@ def create_model_for_agent( f"model_id={model_config.model_id}, provider={model_config.provider}" ) + # Check if specified provider is available (has API key) + provider = model_config.provider + model_id = model_config.model_id + is_valid, error_msg = self.config_manager.validate_provider(provider) + + if not is_valid: + # If configured provider is not available, use primary provider instead + fallback_provider = self.config_manager.primary_provider + logger.warning( + f"Configured provider '{provider}' for agent '{agent_name}' is not available: {error_msg}. " + f"Falling back to primary provider: {fallback_provider}" + ) + provider = fallback_provider + + # Update model_id for the fallback provider + # Priority: provider_models[fallback_provider] > provider's default_model + if fallback_provider in model_config.provider_models: + model_id = model_config.provider_models[fallback_provider] + logger.info( + f"Using provider-specific model for fallback: {fallback_provider} -> {model_id}" + ) + else: + # Use provider's default model + provider_config = self.config_manager.get_provider_config( + fallback_provider + ) + if provider_config: + model_id = provider_config.default_model + logger.info( + f"Using default model for fallback provider '{fallback_provider}': {model_id}" + ) + # Create model return self.create_model( - model_id=model_config.model_id, - provider=model_config.provider, + model_id=model_id, + provider=provider, use_fallback=use_fallback, + provider_models=model_config.provider_models, **merged_params, ) @@ -470,9 +534,42 @@ def create_embedder_for_agent( logger.info( f"Creating embedder for agent '{agent_name}': model_id={emb.model_id}, provider={emb.provider}, params={merged_params}" ) + + # Check if specified provider is available (has API key) + provider = emb.provider + model_id = emb.model_id + is_valid, error_msg = self.config_manager.validate_provider(provider) + + if not is_valid: + # If configured provider is not available, use primary provider instead + fallback_provider = self.config_manager.primary_provider + logger.warning( + f"Configured embedding provider '{provider}' for agent '{agent_name}' is not available: {error_msg}. " + f"Falling back to primary provider: {fallback_provider}" + ) + provider = fallback_provider + + # Update model_id for the fallback provider + # Priority: provider_models[fallback_provider] > provider's default_embedding_model + if fallback_provider in emb.provider_models: + model_id = emb.provider_models[fallback_provider] + logger.info( + f"Using provider-specific embedding model for fallback: {fallback_provider} -> {model_id}" + ) + else: + # Use provider's default embedding model + provider_config = self.config_manager.get_provider_config( + fallback_provider + ) + if provider_config and provider_config.default_embedding_model: + model_id = provider_config.default_embedding_model + logger.info( + f"Using default embedding model for fallback provider '{fallback_provider}': {model_id}" + ) + return self.create_embedder( - model_id=emb.model_id or None, - provider=emb.provider, + model_id=model_id or None, + provider=provider, use_fallback=use_fallback, **merged_params, ) @@ -483,9 +580,21 @@ def create_embedder_for_agent( logger.info( f"Creating embedder for agent '{agent_name}' using primary provider: {primary.provider}" ) + + # Check if primary provider is available + provider = primary.provider + is_valid, error_msg = self.config_manager.validate_provider(provider) + + if not is_valid: + logger.warning( + f"Primary provider '{provider}' for agent '{agent_name}' is not available: {error_msg}. " + f"Using system primary provider: {self.config_manager.primary_provider}" + ) + provider = self.config_manager.primary_provider + return self.create_embedder( model_id=None, - provider=primary.provider, + provider=provider, use_fallback=use_fallback, **merged_params, ) diff --git a/python/valuecell/config/manager.py b/python/valuecell/config/manager.py index d365f2328..e15a8c5a5 100644 --- a/python/valuecell/config/manager.py +++ b/python/valuecell/config/manager.py @@ -47,11 +47,19 @@ def __post_init__(self): @dataclass class AgentModelConfig: - """Agent model configuration""" + """Agent model configuration with provider-specific model mappings""" model_id: str provider: str parameters: Dict[str, Any] + # Provider-specific model mappings for fallback + # e.g., {"siliconflow": "deepseek-ai/DeepSeek-V3.1-Terminus", "google": "gemini-2.0-flash"} + provider_models: Optional[Dict[str, str]] = None + + def __post_init__(self): + """Initialize default values for optional fields""" + if self.provider_models is None: + self.provider_models = {} @dataclass @@ -149,12 +157,33 @@ def primary_provider(self) -> str: @property def fallback_providers(self) -> List[str]: - """Get fallback provider chain""" - # Can be overridden by FALLBACK_PROVIDERS env var (comma-separated) + """Get fallback provider chain + + Returns all enabled providers with valid API keys except the primary provider. + Can be overridden by FALLBACK_PROVIDERS env var (comma-separated). + + Priority: + 1. FALLBACK_PROVIDERS env var (explicit override) + 2. All enabled providers with valid API keys except primary (auto-detected) + """ + # 1. Can be overridden by FALLBACK_PROVIDERS env var (comma-separated) env_fallbacks = os.getenv("FALLBACK_PROVIDERS") if env_fallbacks: + logger.debug(f"Using fallback providers from env: {env_fallbacks}") return [p.strip() for p in env_fallbacks.split(",")] - return self._config.get("models", {}).get("fallback_providers", []) + + # 2. Auto-load enabled providers (with valid API keys) except primary + primary = self.primary_provider + enabled_providers = self.get_enabled_providers() + + # Filter out the primary provider and return others + fallbacks = [p for p in enabled_providers if p != primary] + + logger.debug( + f"Auto-loaded fallback providers: {fallbacks} " + f"(enabled: {enabled_providers}, primary: {primary})" + ) + return fallbacks def get_provider_config( self, provider_name: Optional[str] = None @@ -278,18 +307,26 @@ def get_agent_config(self, agent_name: str) -> Optional[AgentConfig]: global_defaults = global_models.get("defaults") or {} merged_params = {**global_defaults, **parameters} + # Get provider-specific model mappings + provider_models = primary.get("provider_models", {}) + primary_model = AgentModelConfig( - model_id=model_id or "", provider=provider, parameters=merged_params + model_id=model_id or "", + provider=provider, + parameters=merged_params, + provider_models=provider_models, ) # Extract embedding model config if present embedding_model = None embedding_data = models.get("embedding") if embedding_data: + embedding_provider_models = embedding_data.get("provider_models", {}) embedding_model = AgentModelConfig( model_id=embedding_data.get("model_id", ""), provider=embedding_data.get("provider", "openai"), parameters=embedding_data.get("parameters", {}), + provider_models=embedding_provider_models, ) # Extract API keys