feat(models): add new models for SiliconFlow and OpenRouter providers
test(models): update tests to validate new providers and registered models
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+5
-3
@@ -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
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user