diff --git a/EvoScientist/config/onboard.py b/EvoScientist/config/onboard.py index f07b5dc..15cbd0b 100644 --- a/EvoScientist/config/onboard.py +++ b/EvoScientist/config/onboard.py @@ -422,9 +422,9 @@ 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 (DeepSeek, Kimi, GLM, MiniMax, Step, etc.)", value="nvidia"), - Choice(title="SiliconFlow (third party)", value="siliconflow"), - Choice(title="OpenRouter (third party)", value="openrouter"), + Choice(title="NVIDIA (third party — limited free requests)", value="nvidia"), + Choice(title="SiliconFlow (third party — GLM, Kimi, MiniMax, etc.)", value="siliconflow"), + Choice(title="OpenRouter (third party — Grok, Gemini, Qwen, etc.)", value="openrouter"), Choice(title="Ollama (local models)", value="ollama"), Choice(title="Other (OpenAI-compatible)", value="custom"), ] @@ -545,18 +545,6 @@ def _step_provider_api_key( ) -_THIRD_PARTY_EXAMPLES: dict[str, list[tuple[str, str]]] = { - "openrouter": [ - ("minimax/minimax-m2.5", "MiniMax M2.5"), - ("x-ai/grok-4.1-fast", "Grok 4.1 Fast"), - ], - "siliconflow": [ - ("Pro/zai-org/GLM-5", "GLM 5"), - ("Pro/moonshotai/Kimi-K2.5", "Kimi K2.5"), - ], -} - - def _step_base_url(config: EvoScientistConfig) -> str: """Prompt for custom provider base URL. @@ -678,44 +666,6 @@ def _step_model( console.print(f" [dim]Using default: {model}[/dim]") return model - # Third-party providers: select from examples or type custom model name - if provider in _THIRD_PARTY_EXAMPLES: - examples = _THIRD_PARTY_EXAMPLES[provider] - _CUSTOM_SENTINEL = "__custom__" - choices = [ - Choice(title=f"{label} ({mid})", value=mid) - for mid, label in examples - ] - choices.append(Choice(title="Customize your model...", value=_CUSTOM_SENTINEL)) - - selected = questionary.select( - "Select model:", - choices=choices, - default=choices[0].value, - style=WIZARD_STYLE, - qmark=QMARK, - use_indicator=True, - ).ask() - if selected is None: - raise KeyboardInterrupt() - - if selected != _CUSTOM_SENTINEL: - return selected - - model = questionary.text( - "Model name:", - style=WIZARD_STYLE, - qmark=QMARK, - placeholder=FormattedText([("fg:#858585", " e.g. owner/model-name")]), - ).ask() - if model is None: - raise KeyboardInterrupt() - model = model.strip() - if not model: - model = examples[0][0] - console.print(f" [dim]Using default: {model}[/dim]") - return model - # Get models for the selected provider entries = get_models_for_provider(provider) @@ -734,9 +684,11 @@ def _step_model( provider_models = [name for name, _ in entries] # Create choices with model IDs as hints + _CUSTOM_SENTINEL = "__custom__" choices = [] for name, model_id in entries: choices.append(Choice(title=f"{name} ({model_id})", value=name)) + choices.append(Choice(title="Type a model name...", value=_CUSTOM_SENTINEL)) # Determine default if config.model in provider_models: @@ -744,7 +696,7 @@ def _step_model( else: default = provider_models[0] - model = questionary.select( + selected = questionary.select( "Select model:", choices=choices, default=default, @@ -753,9 +705,24 @@ def _step_model( use_indicator=True, ).ask() - if model is None: + if selected is None: raise KeyboardInterrupt() + if selected != _CUSTOM_SENTINEL: + return selected + + model = questionary.text( + "Model name:", + style=WIZARD_STYLE, + qmark=QMARK, + placeholder=FormattedText([("fg:#858585", " e.g. owner/model-name")]), + ).ask() + if model is None: + raise KeyboardInterrupt() + model = model.strip() + if not model: + model = provider_models[0] + console.print(f" [dim]Using default: {model}[/dim]") return model diff --git a/EvoScientist/llm/models.py b/EvoScientist/llm/models.py index ccd974f..e5c2789 100644 --- a/EvoScientist/llm/models.py +++ b/EvoScientist/llm/models.py @@ -46,9 +46,22 @@ _MODEL_ENTRIES: list[tuple[str, str, str]] = [ ("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.5", "minimaxai/minimax-m2.5", "nvidia"), ("minimax-m2.1", "minimaxai/minimax-m2.1", "nvidia"), + ("qwen3.5-397b", "qwen/qwen3.5-397b-a17b", "nvidia"), ("step-3.5-flash", "stepfun-ai/step-3.5-flash", "nvidia"), ("nemotron-nano", "nvidia/nemotron-3-nano-30b-a3b", "nvidia"), + # SiliconFlow + ("minimax-m2.5", "Pro/MiniMaxAI/MiniMax-M2.5", "siliconflow"), + ("glm-5", "Pro/zai-org/GLM-5", "siliconflow"), + ("kimi-k2.5", "Pro/moonshotai/Kimi-K2.5", "siliconflow"), + ("glm-4.7", "Pro/zai-org/GLM-4.7", "siliconflow"), + # OpenRouter + ("minimax-m2.5", "minimax/minimax-m2.5", "openrouter"), + ("grok-4.1-fast", "x-ai/grok-4.1-fast", "openrouter"), + ("qwen3.5-122b", "qwen/qwen3.5-122b-a10b", "openrouter"), + ("gemini-3-flash", "google/gemini-3-flash-preview", "openrouter"), + ("claude-sonnet-4.6", "anthropic/claude-sonnet-4.6", "openrouter"), ] # Public dict for simple lookups (last entry wins for duplicate names). @@ -147,6 +160,9 @@ def get_chat_model( api_key = os.environ.get("SILICONFLOW_API_KEY", "") if api_key: kwargs["api_key"] = api_key + # Disable thinking — LangChain drops reasoning_content from history, + # causing SiliconFlow to reject multi-turn requests (error 20015). + kwargs.setdefault("extra_body", {})["enable_thinking"] = False provider = "openai" elif provider == "openrouter": kwargs["base_url"] = _OPENROUTER_BASE_URL diff --git a/tests/test_llm.py b/tests/test_llm.py index f83b59d..e4493cc 100644 --- a/tests/test_llm.py +++ b/tests/test_llm.py @@ -33,7 +33,7 @@ class TestModelsRegistry: def test_entries_are_valid_tuples(self): """Test that _MODEL_ENTRIES contains valid (name, model_id, provider) tuples.""" - valid_providers = {"anthropic", "openai", "google-genai", "nvidia"} + valid_providers = {"anthropic", "openai", "google-genai", "nvidia", "siliconflow", "openrouter"} for entry in _MODEL_ENTRIES: assert len(entry) == 3, f"Entry {entry} doesn't have 3 elements" name, model_id, provider = entry @@ -49,9 +49,11 @@ class TestModelsRegistry: assert isinstance(name, str) assert isinstance(model_id, str) - # Third-party providers have no registered models (user types model name) + # Third-party providers now have registered models openrouter_models = get_models_for_provider("openrouter") - assert len(openrouter_models) == 0 + assert len(openrouter_models) > 0 + siliconflow_models = get_models_for_provider("siliconflow") + assert len(siliconflow_models) > 0 # =============================================================================