mirror of
https://github.com/NCBM/plyngent.git
synced 2026-11-07 23:47:54 +08:00
test/config: cover model-level preset routing
This commit is contained in:
+2
-1
@@ -20,7 +20,8 @@
|
|||||||
- memory: Storage controlling for sessions and messages.
|
- memory: Storage controlling for sessions and messages.
|
||||||
- router: Multi-source capability routing (Phase H; not implemented).
|
- router: Multi-source capability routing (Phase H; not implemented).
|
||||||
- config: Plyngent configuration center (TOML), including ``[plugins]`` and
|
- config: Plyngent configuration center (TOML), including ``[plugins]`` and
|
||||||
``[networking]`` (e.g. fetch SSRF Fake-IP CIDR exemptions).
|
``[networking]`` (e.g. fetch SSRF Fake-IP CIDR exemptions). Model entries
|
||||||
|
may override provider ``preset`` / ``url`` for mixed API routing.
|
||||||
- agent: Tool loop, streaming, usage, compact; `@tool` / tags / registry.
|
- agent: Tool loop, streaming, usage, compact; `@tool` / tags / registry.
|
||||||
- tools: Workspace file/process/VCS/chat/todo/net tools; catalog; plugins;
|
- tools: Workspace file/process/VCS/chat/todo/net tools; catalog; plugins;
|
||||||
instance/session context and views.
|
instance/session context and views.
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ access_key_or_token = "sk-replace-me"
|
|||||||
#
|
#
|
||||||
# [providers.openai.models]
|
# [providers.openai.models]
|
||||||
# "gpt-4o-mini" = { text = true }
|
# "gpt-4o-mini" = { text = true }
|
||||||
|
# # Model-level preset/url overrides let one provider route models via different APIs.
|
||||||
|
# # `preset = "openai"` means /responses; `openai-compatible` means /chat/completions;
|
||||||
|
# # `anthropic` means native /messages (support planned).
|
||||||
|
# # "claude-sonnet" = { text = true, preset = "anthropic", url = "https://api.anthropic.com/v1" }
|
||||||
|
|
||||||
# --- DeepSeek (OpenAI-compatible convention) ---
|
# --- DeepSeek (OpenAI-compatible convention) ---
|
||||||
# Default models if [providers.*.models] is omitted: deepseek-v4-flash, deepseek-v4-pro.
|
# Default models if [providers.*.models] is omitted: deepseek-v4-flash, deepseek-v4-pro.
|
||||||
|
|||||||
@@ -129,10 +129,10 @@ access_key_or_token = "sk"
|
|||||||
usage={},
|
usage={},
|
||||||
)
|
)
|
||||||
|
|
||||||
monkeypatch.setattr("plyngent.cli.app.create_client", lambda _p: DummyClient())
|
monkeypatch.setattr("plyngent.cli.app.create_client", lambda _p, **_kw: DummyClient())
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"plyngent.cli.state.create_client",
|
"plyngent.cli.state.create_client",
|
||||||
lambda _p: DummyClient(),
|
lambda _p, **_kw: DummyClient(),
|
||||||
)
|
)
|
||||||
|
|
||||||
from platformdirs import user_data_path
|
from platformdirs import user_data_path
|
||||||
|
|||||||
@@ -189,6 +189,30 @@ models = {}
|
|||||||
_ = config.promote_provider("hollow", [])
|
_ = config.promote_provider("hollow", [])
|
||||||
|
|
||||||
|
|
||||||
|
def test_model_level_preset_url_overrides_parse(tmp_path: Path) -> None:
|
||||||
|
path = tmp_path / "mixed.toml"
|
||||||
|
_ = path.write_text(
|
||||||
|
"""
|
||||||
|
[providers.mixed]
|
||||||
|
preset = "openai-compatible"
|
||||||
|
url = "https://gateway.example/v1"
|
||||||
|
access_key_or_token = "sk-test"
|
||||||
|
|
||||||
|
[providers.mixed.models]
|
||||||
|
"claude" = { text = true, preset = "anthropic", url = "https://api.anthropic.com/v1" }
|
||||||
|
"qwen" = { text = true }
|
||||||
|
""",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
config = plyngent.config.load(path)
|
||||||
|
provider = config.providers["mixed"]
|
||||||
|
assert isinstance(provider, OpenAICompatibleProvider)
|
||||||
|
assert provider.models["claude"].preset == "anthropic"
|
||||||
|
assert provider.models["claude"].url == "https://api.anthropic.com/v1"
|
||||||
|
assert provider.models["qwen"].preset == ""
|
||||||
|
assert provider.models["qwen"].url == ""
|
||||||
|
|
||||||
|
|
||||||
def test_read_invalid_config() -> None:
|
def test_read_invalid_config() -> None:
|
||||||
with pytest.raises(ConfigFormatError):
|
with pytest.raises(ConfigFormatError):
|
||||||
_ = plyngent.config.load(Path(__file__).parent / "plyngent-invalid.toml")
|
_ = plyngent.config.load(Path(__file__).parent / "plyngent-invalid.toml")
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import pytest
|
|||||||
from plyngent.config.models import (
|
from plyngent.config.models import (
|
||||||
AnthropicProvider,
|
AnthropicProvider,
|
||||||
DeepseekProvider,
|
DeepseekProvider,
|
||||||
|
ModelConfig,
|
||||||
OpenAICompatibleProvider,
|
OpenAICompatibleProvider,
|
||||||
OpenAIProvider,
|
OpenAIProvider,
|
||||||
)
|
)
|
||||||
@@ -65,16 +66,44 @@ def test_deepseek_openai_convention() -> None:
|
|||||||
assert "deepseek-v4-pro" in provider.models
|
assert "deepseek-v4-pro" in provider.models
|
||||||
|
|
||||||
|
|
||||||
def test_deepseek_anthropic_convention_not_implemented() -> None:
|
def test_deepseek_ignores_legacy_convention_extra() -> None:
|
||||||
provider = DeepseekProvider(
|
provider = DeepseekProvider(
|
||||||
access_key_or_token="sk-test",
|
access_key_or_token="sk-test",
|
||||||
extras={"convention": "anthropic"},
|
extras={"convention": "anthropic"},
|
||||||
)
|
)
|
||||||
with pytest.raises(ProviderNotSupportedError, match="anthropic"):
|
client = create_client(provider)
|
||||||
_ = create_client(provider)
|
assert isinstance(client, DeepseekOpenAIClient)
|
||||||
|
|
||||||
|
|
||||||
def test_anthropic_not_implemented() -> None:
|
def test_anthropic_not_implemented() -> None:
|
||||||
provider = AnthropicProvider(access_key_or_token="sk-test")
|
provider = AnthropicProvider(access_key_or_token="sk-test")
|
||||||
with pytest.raises(ProviderNotSupportedError, match="anthropic"):
|
with pytest.raises(ProviderNotSupportedError, match="anthropic"):
|
||||||
_ = create_client(provider)
|
_ = create_client(provider)
|
||||||
|
|
||||||
|
|
||||||
|
def test_model_level_preset_url_overrides_parent() -> None:
|
||||||
|
provider = OpenAICompatibleProvider(
|
||||||
|
access_key_or_token="sk-test",
|
||||||
|
url="https://gateway.example/v1",
|
||||||
|
models={
|
||||||
|
"gpt": ModelConfig(preset="openai", url="https://api.openai.com/v1"),
|
||||||
|
"qwen": ModelConfig(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
openai_client = create_client(provider, model="gpt")
|
||||||
|
assert openai_client.__class__.__name__ == "ResponsesChatClient"
|
||||||
|
assert provider_to_openai_config(provider, model="gpt").base_url == "https://api.openai.com/v1"
|
||||||
|
|
||||||
|
compat_client = create_client(provider, model="qwen")
|
||||||
|
assert isinstance(compat_client, OpenAICompatibleClient)
|
||||||
|
assert provider_to_openai_config(provider, model="qwen").base_url == "https://gateway.example/v1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_model_level_anthropic_preset_not_implemented() -> None:
|
||||||
|
provider = OpenAICompatibleProvider(
|
||||||
|
access_key_or_token="sk-test",
|
||||||
|
url="https://gateway.example/v1",
|
||||||
|
models={"claude": ModelConfig(preset="anthropic", url="https://api.anthropic.com/v1")},
|
||||||
|
)
|
||||||
|
with pytest.raises(ProviderNotSupportedError, match="anthropic"):
|
||||||
|
_ = create_client(provider, model="claude")
|
||||||
|
|||||||
Reference in New Issue
Block a user