feat: add support for SiliconFlow API key and update model providers

This commit is contained in:
X-iZhang
2026-02-12 19:51:09 +00:00
parent 5e83f4caba
commit 349693f9d6
4 changed files with 69 additions and 16 deletions
+4
View File
@@ -63,6 +63,7 @@ class EvoScientistConfig:
openai_api_key: str = ""
nvidia_api_key: str = ""
google_api_key: str = ""
siliconflow_api_key: str = ""
tavily_api_key: str = ""
# LLM Settings
@@ -213,6 +214,7 @@ _ENV_MAPPINGS = {
"openai_api_key": "OPENAI_API_KEY",
"nvidia_api_key": "NVIDIA_API_KEY",
"google_api_key": "GOOGLE_API_KEY",
"siliconflow_api_key": "SILICONFLOW_API_KEY",
"tavily_api_key": "TAVILY_API_KEY",
"default_mode": "EVOSCIENTIST_DEFAULT_MODE",
"default_workdir": "EVOSCIENTIST_WORKSPACE_DIR",
@@ -281,5 +283,7 @@ def apply_config_to_env(config: EvoScientistConfig) -> None:
os.environ["NVIDIA_API_KEY"] = config.nvidia_api_key
if config.google_api_key and not os.environ.get("GOOGLE_API_KEY"):
os.environ["GOOGLE_API_KEY"] = config.google_api_key
if config.siliconflow_api_key and not os.environ.get("SILICONFLOW_API_KEY"):
os.environ["SILICONFLOW_API_KEY"] = config.siliconflow_api_key
if config.tavily_api_key and not os.environ.get("TAVILY_API_KEY"):
os.environ["TAVILY_API_KEY"] = config.tavily_api_key
+30 -8
View File
@@ -7,10 +7,13 @@ short names for common models.
from __future__ import annotations
import os
from typing import Any
from langchain.chat_models import init_chat_model
_SILICONFLOW_BASE_URL = "https://api.siliconflow.cn/v1"
# Model registry: short_name -> (model_id, provider)
MODELS: dict[str, tuple[str, str]] = {
# Anthropic (ordered by capability)
@@ -20,11 +23,11 @@ MODELS: dict[str, tuple[str, str]] = {
"claude-haiku-4-5": ("claude-haiku-4-5-20251001", "anthropic"),
# OpenAI
"gpt-5.2-codex": ("gpt-5.2-codex", "openai"),
"gpt-5.2": ("gpt-5.2", "openai"),
"gpt-5.1": ("gpt-5.1", "openai"),
"gpt-5": ("gpt-5", "openai"),
"gpt-5-mini": ("gpt-5-mini", "openai"),
"gpt-5-nano": ("gpt-5-nano", "openai"),
"gpt-5.2": ("gpt-5.2-2025-12-11", "openai"),
"gpt-5.1": ("gpt-5.1-2025-11-13", "openai"),
"gpt-5": ("gpt-5-2025-08-07", "openai"),
"gpt-5-mini": ("gpt-5-mini-2025-08-07", "openai"),
"gpt-5-nano": ("gpt-5-nano-2025-08-07", "openai"),
# Google GenAI
"gemini-3-pro": ("gemini-3-pro-preview", "google-genai"),
"gemini-3-flash": ("gemini-3-flash-preview", "google-genai"),
@@ -33,8 +36,15 @@ MODELS: dict[str, tuple[str, str]] = {
"gemini-2.5-pro": ("gemini-2.5-pro", "google-genai"),
# NVIDIA
"glm4.7": ("z-ai/glm4.7", "nvidia"),
"deepseek-v3.2": ("deepseek-ai/deepseek-v3.2", "nvidia"),
"deepseek-v3.1": ("deepseek-ai/deepseek-v3.1-terminus", "nvidia"),
"kimi-k2.5": ("moonshotai/kimi-k2.5", "nvidia"),
"kimi-k2-thinking": ("moonshotai/kimi-k2-thinking", "nvidia"),
"minimax-m2.1": ("minimaxai/minimax-m2.1", "nvidia"),
"step-3.5-flash": ("stepfun-ai/step-3.5-flash", "nvidia"),
"nemotron-nano": ("nvidia/nemotron-3-nano-30b-a3b", "nvidia"),
# SiliconFlow (OpenAI-compatible API)
"glm-5": ("Pro/zai-org/GLM-5", "siliconflow"),
}
DEFAULT_MODEL = "claude-sonnet-4-5"
@@ -85,12 +95,24 @@ def get_chat_model(
else:
provider = "anthropic" # Default fallback
# SiliconFlow → route through OpenAI provider with base_url
_is_siliconflow = provider == "siliconflow"
if _is_siliconflow:
kwargs["base_url"] = _SILICONFLOW_BASE_URL
api_key = os.environ.get("SILICONFLOW_API_KEY", "")
if api_key:
kwargs["api_key"] = api_key
provider = "openai"
# Auto-enable thinking for Anthropic models
if provider == "anthropic" and "thinking" not in kwargs:
kwargs["thinking"] = {"type": "enabled", "budget_tokens": 2000}
if model_id.startswith("claude-opus-4-6"):
kwargs["thinking"] = {"type": "adaptive"}
else:
kwargs["thinking"] = {"type": "enabled", "budget_tokens": 2000}
# Auto-enable reasoning for OpenAI models
if provider == "openai" and "reasoning" not in kwargs:
# Auto-enable reasoning for OpenAI models (not for SiliconFlow)
if provider == "openai" and not _is_siliconflow and "reasoning" not in kwargs:
kwargs["reasoning"] = {"effort": "medium", "summary": "auto"}
# Auto-enable thinking visibility for Google GenAI models
+32 -5
View File
@@ -240,6 +240,27 @@ def validate_google_key(api_key: str) -> tuple[bool, str]:
return False, f"Error: {e}"
def validate_siliconflow_key(api_key: str) -> tuple[bool, str]:
"""Validate a SiliconFlow API key by making a test request.
Returns:
Tuple of (is_valid, message).
"""
if not api_key:
return True, "Skipped (no key provided)"
try:
import openai
client = openai.OpenAI(api_key=api_key, base_url="https://api.siliconflow.cn/v1")
client.models.list()
return True, "Valid"
except Exception as e:
error_str = str(e).lower()
if "401" in error_str or "unauthorized" in error_str or "invalid" in error_str or "authentication" in error_str:
return False, "Invalid API key"
return False, f"Error: {e}"
def validate_tavily_key(api_key: str) -> tuple[bool, str]:
"""Validate a Tavily API key by making a test request.
@@ -322,11 +343,12 @@ def _step_provider(config: EvoScientistConfig) -> str:
Choice(title="Anthropic (Claude models)", value="anthropic"),
Choice(title="OpenAI (GPT models)", value="openai"),
Choice(title="Google GenAI (Gemini models)", value="google-genai"),
Choice(title="NVIDIA (GLM, MiniMax, Kimi, etc.)", value="nvidia"),
Choice(title="NVIDIA (DeepSeek, Kimi, GLM, MiniMax, Step, etc.)", value="nvidia"),
Choice(title="SiliconFlow (GLM, DeepSeek, Qwen, etc.)", value="siliconflow"),
]
# Set default based on current config
default = config.provider if config.provider in ["anthropic", "openai", "google-genai", "nvidia"] else "anthropic"
default = config.provider if config.provider in ["anthropic", "openai", "google-genai", "nvidia", "siliconflow"] else "anthropic"
provider = questionary.select(
"Select your LLM provider:",
@@ -346,9 +368,10 @@ def _step_provider(config: EvoScientistConfig) -> str:
def _provider_key_info(config: EvoScientistConfig, provider: str):
"""Return (display_name, current_value, validate_fn) for a provider."""
mapping = {
"anthropic": ("Anthropic", config.anthropic_api_key or os.environ.get("ANTHROPIC_API_KEY", ""), validate_anthropic_key),
"nvidia": ("NVIDIA", config.nvidia_api_key or os.environ.get("NVIDIA_API_KEY", ""), validate_nvidia_key),
"google-genai": ("Google", config.google_api_key or os.environ.get("GOOGLE_API_KEY", ""), validate_google_key),
"anthropic": ("Anthropic", config.anthropic_api_key or os.environ.get("ANTHROPIC_API_KEY", ""), validate_anthropic_key),
"nvidia": ("NVIDIA", config.nvidia_api_key or os.environ.get("NVIDIA_API_KEY", ""), validate_nvidia_key),
"google-genai": ("Google", config.google_api_key or os.environ.get("GOOGLE_API_KEY", ""), validate_google_key),
"siliconflow": ("SiliconFlow", config.siliconflow_api_key or os.environ.get("SILICONFLOW_API_KEY", ""), validate_siliconflow_key),
}
return mapping.get(provider, ("OpenAI", config.openai_api_key or os.environ.get("OPENAI_API_KEY", ""), validate_openai_key))
@@ -1337,6 +1360,8 @@ def run_onboard(skip_validation: bool = False) -> bool:
config.nvidia_api_key = new_key
elif provider == "google-genai":
config.google_api_key = new_key
elif provider == "siliconflow":
config.siliconflow_api_key = new_key
else:
config.openai_api_key = new_key
else:
@@ -1346,6 +1371,8 @@ def run_onboard(skip_validation: bool = False) -> bool:
current = config.nvidia_api_key
elif provider == "google-genai":
current = config.google_api_key
elif provider == "siliconflow":
current = config.siliconflow_api_key
else:
current = config.openai_api_key
if not current:
+3 -3
View File
@@ -37,7 +37,7 @@ class TestModelsRegistry:
model_id, provider = value
assert isinstance(model_id, str), f"model_id for '{name}' is not a string"
assert isinstance(provider, str), f"provider for '{name}' is not a string"
assert provider in ("anthropic", "openai", "google-genai", "nvidia"), f"Unknown provider for '{name}': {provider}"
assert provider in ("anthropic", "openai", "google-genai", "nvidia", "siliconflow"), f"Unknown provider for '{name}': {provider}"
def test_anthropic_models_have_anthropic_provider(self):
"""Test that claude models use anthropic provider."""
@@ -116,7 +116,7 @@ class TestGetModelInfo:
def test_returns_correct_info(self):
"""Test that get_model_info returns correct info."""
model_id, provider = get_model_info("gpt-5-nano")
assert model_id == "gpt-5-nano"
assert model_id == "gpt-5-nano-2025-08-07"
assert provider == "openai"
@@ -159,7 +159,7 @@ class TestGetChatModel:
get_chat_model("gpt-5-mini")
call_kwargs = mock_init.call_args[1]
assert call_kwargs["model"] == "gpt-5-mini"
assert call_kwargs["model"] == "gpt-5-mini-2025-08-07"
assert call_kwargs["model_provider"] == "openai"
@mock.patch("EvoScientist.llm.models.init_chat_model")