diff --git a/EvoScientist/config/onboard/constants.py b/EvoScientist/config/onboard/constants.py index 134476e..ed4fad6 100644 --- a/EvoScientist/config/onboard/constants.py +++ b/EvoScientist/config/onboard/constants.py @@ -21,6 +21,7 @@ VALID_PROVIDERS: frozenset[str] = frozenset( "zhipu", "zhipu-code", "volcengine", + "volcengine-code", "dashscope", "dashscope-code", "deepseek", diff --git a/EvoScientist/config/onboard/helpers.py b/EvoScientist/config/onboard/helpers.py index dc789bd..ac76ce4 100644 --- a/EvoScientist/config/onboard/helpers.py +++ b/EvoScientist/config/onboard/helpers.py @@ -102,6 +102,11 @@ def _provider_key_info(config: EvoScientistConfig, provider: str): config.volcengine_api_key or os.environ.get("VOLCENGINE_API_KEY", ""), validate_volcengine_key, ), + "volcengine-code": ( + "Volcengine Coding Plan", + config.volcengine_api_key or os.environ.get("VOLCENGINE_API_KEY", ""), + validate_volcengine_key, + ), "dashscope": ( "DashScope", config.dashscope_api_key or os.environ.get("DASHSCOPE_API_KEY", ""), diff --git a/EvoScientist/config/onboard/steps.py b/EvoScientist/config/onboard/steps.py index 0228ae6..0ba1b18 100644 --- a/EvoScientist/config/onboard/steps.py +++ b/EvoScientist/config/onboard/steps.py @@ -278,6 +278,10 @@ def _step_provider( title="Volcengine (火山引擎 — Doubao models)", value="volcengine", ), + Choice( + title="Volcengine Coding Plan (火山引擎代码计划 — coding models)", + value="volcengine-code", + ), Choice( title="DashScope (阿里云 — Qwen models)", value="dashscope", diff --git a/EvoScientist/config/onboard/validators.py b/EvoScientist/config/onboard/validators.py index 0335c44..f6e90ac 100644 --- a/EvoScientist/config/onboard/validators.py +++ b/EvoScientist/config/onboard/validators.py @@ -475,6 +475,9 @@ def validate_zhipu_key(api_key: str) -> tuple[bool, str]: def validate_volcengine_key(api_key: str) -> tuple[bool, str]: """Validate a Volcengine API key by making a test request. + Uses the general endpoint for validation; volcengine and volcengine-code + share the same API key and only differ in their runtime base URL. + Returns: Tuple of (is_valid, message). """ diff --git a/EvoScientist/config/onboard/wizard.py b/EvoScientist/config/onboard/wizard.py index ace7758..65a19bb 100644 --- a/EvoScientist/config/onboard/wizard.py +++ b/EvoScientist/config/onboard/wizard.py @@ -124,6 +124,7 @@ _PROVIDER_KEY_ATTR = { "zhipu": "zhipu_api_key", "zhipu-code": "zhipu_api_key", "volcengine": "volcengine_api_key", + "volcengine-code": "volcengine_api_key", "dashscope": "dashscope_api_key", "dashscope-code": "dashscope_api_key", "moonshot": "moonshot_api_key", diff --git a/EvoScientist/llm/context_window.py b/EvoScientist/llm/context_window.py index dc0bb24..19d3e39 100644 --- a/EvoScientist/llm/context_window.py +++ b/EvoScientist/llm/context_window.py @@ -32,6 +32,8 @@ _KNOWN_MODEL_CONTEXT_WINDOWS: dict[str, int] = { # Zhipu GLM-5.2 — 1M context, an exception to the ``glm-5`` family (203K). # Matches OpenRouter ``z-ai/glm-5.2`` via split('/')[-1]. "glm-5.2": 1_000_000, + # Volcengine Coding Plan's OpenAI-compatible alias for GLM-5.2. + "glm-5-2": 1_000_000, # Tencent Hunyuan HY3 — 262K context (OpenRouter ``tencent/hy3``). "hy3": 262_000, } diff --git a/EvoScientist/llm/models.py b/EvoScientist/llm/models.py index 50f1610..9ec648e 100644 --- a/EvoScientist/llm/models.py +++ b/EvoScientist/llm/models.py @@ -43,6 +43,7 @@ _SILICONFLOW_BASE_URL = "https://api.siliconflow.cn/v1" _ZHIPU_BASE_URL = "https://open.bigmodel.cn/api/paas/v4" _ZHIPU_CODE_BASE_URL = "https://open.bigmodel.cn/api/coding/paas/v4" _VOLCENGINE_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3" +_VOLCENGINE_CODE_BASE_URL = "https://ark.cn-beijing.volces.com/api/coding/v3" _DASHSCOPE_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1" _DASHSCOPE_CODE_BASE_URL = "https://coding.dashscope.aliyuncs.com/v1" @@ -114,6 +115,7 @@ _OPENAI_ROUTED_PROVIDERS: dict[str, tuple[str | None, str]] = { "zhipu": (_ZHIPU_BASE_URL, "ZHIPU_API_KEY"), "zhipu-code": (_ZHIPU_CODE_BASE_URL, "ZHIPU_API_KEY"), "volcengine": (_VOLCENGINE_BASE_URL, "VOLCENGINE_API_KEY"), + "volcengine-code": (_VOLCENGINE_CODE_BASE_URL, "VOLCENGINE_API_KEY"), "dashscope": (_DASHSCOPE_BASE_URL, "DASHSCOPE_API_KEY"), "dashscope-code": (_DASHSCOPE_CODE_BASE_URL, "DASHSCOPE_API_KEY"), "requesty": (_REQUESTY_BASE_URL, "REQUESTY_API_KEY"), @@ -283,6 +285,10 @@ _MODEL_ENTRIES: list[tuple[str, str, str]] = [ ("qwen3.5-122b", "qwen/qwen3.5-122b-a10b", "openrouter"), ("deepseek-v4-pro", "deepseek/deepseek-v4-pro", "openrouter"), ("deepseek-v4-flash", "deepseek/deepseek-v4-flash", "openrouter"), + # Volcengine Coding Plan (火山引擎代码计划 — coding-only endpoint) + # Listed before Zhipu so simple GLM lookups keep their existing default. + ("glm-5.2", "glm-5-2", "volcengine-code"), + ("kimi-k2.5", "kimi-k2-5", "volcengine-code"), # Zhipu CodePlan (智谱代码计划 — coding-only endpoint) ("glm-5.2", "glm-5.2", "zhipu-code"), ("glm-5.1", "glm-5.1", "zhipu-code"), diff --git a/tests/test_context_window.py b/tests/test_context_window.py index d576299..73e67e1 100644 --- a/tests/test_context_window.py +++ b/tests/test_context_window.py @@ -202,3 +202,8 @@ def test_lookup_is_case_insensitive(): # GLM-5 capitalized variant glm = SimpleNamespace(model_name="Pro/zai-org/GLM-5", profile=None) assert get_context_window(glm) == 203_000 + + +def test_volcengine_glm_5_2_alias_keeps_exact_context_window(): + model = SimpleNamespace(model_name="glm-5-2", profile=None) + assert get_context_window(model) == 1_000_000 diff --git a/tests/test_llm.py b/tests/test_llm.py index fb5c935..edefe09 100644 --- a/tests/test_llm.py +++ b/tests/test_llm.py @@ -41,6 +41,7 @@ class TestModelsRegistry: assert "zhipu" in providers assert "zhipu-code" in providers assert "volcengine" in providers + assert "volcengine-code" in providers assert "dashscope" in providers assert "dashscope-code" in providers assert "deepseek" in providers @@ -62,6 +63,7 @@ class TestModelsRegistry: "zhipu", "zhipu-code", "volcengine", + "volcengine-code", "dashscope", "dashscope-code", "custom-openai", @@ -1128,6 +1130,28 @@ class TestThirdPartyRouting: assert call_kwargs["base_url"] == "https://ark.cn-beijing.volces.com/api/v3" assert call_kwargs["api_key"] == "ve-key-123" + @pytest.mark.parametrize( + ("configured_model", "api_model"), + [("glm-5.2", "glm-5-2"), ("kimi-k2.5", "kimi-k2-5")], + ) + @patch("EvoScientist.llm.models.init_chat_model") + def test_volcengine_code_routes_through_openai( + self, mock_init, configured_model, api_model, monkeypatch + ): + """Volcengine Coding Plan uses its endpoint, IDs, and vendor API key.""" + mock_init.return_value = "mock_model" + monkeypatch.setenv("VOLCENGINE_API_KEY", "ve-code-key-123") + + get_chat_model(configured_model, provider="volcengine-code") + + call_kwargs = mock_init.call_args[1] + assert call_kwargs["model_provider"] == "openai" + assert call_kwargs["model"] == api_model + assert ( + call_kwargs["base_url"] == "https://ark.cn-beijing.volces.com/api/coding/v3" + ) + assert call_kwargs["api_key"] == "ve-code-key-123" + @patch("EvoScientist.llm.models.init_chat_model") def test_dashscope_routes_through_openai(self, mock_init, monkeypatch): """DashScope provider should route through OpenAI with correct base_url."""