mirror of
https://github.com/NCBM/plyngent.git
synced 2026-11-07 07:17:54 +08:00
config: resolve thinking strength across agent, provider, and model layers
This commit is contained in:
@@ -29,6 +29,8 @@ def test_agent_section_defaults(tmp_path: Path) -> None:
|
||||
assert store.agent_config.allow_paths == {}
|
||||
assert store.agent_config.auto_continue_limits is False
|
||||
assert store.agent_config.max_context_tokens == 200_000
|
||||
assert store.agent_config.reasoning_effort == ""
|
||||
assert store.agent_config.thinking_budget_tokens == 0
|
||||
assert store.plugins_config.enable == []
|
||||
assert store.plugins_config.disable == []
|
||||
assert store.networking_config.ssrf_assume_public_cidrs == []
|
||||
|
||||
@@ -299,6 +299,70 @@ def test_read_bad_config() -> None:
|
||||
assert isinstance(config.bad_providers, Mapping)
|
||||
|
||||
|
||||
def test_reasoning_effort_from_toml(tmp_path: Path) -> None:
|
||||
path = tmp_path / "reasoning.toml"
|
||||
_ = path.write_text(
|
||||
"""
|
||||
[agent]
|
||||
reasoning_effort = "low"
|
||||
thinking_budget_tokens = 4000
|
||||
|
||||
[providers.ds]
|
||||
preset = "deepseek"
|
||||
access_key_or_token = "sk-test"
|
||||
reasoning_effort = "high"
|
||||
|
||||
[providers.ds.models]
|
||||
"deepseek-v4-pro" = { text = true, reasoning_effort = "max", thinking_budget_tokens = 32000 }
|
||||
"deepseek-v4-flash" = { text = true }
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
config = plyngent.config.load(path)
|
||||
agent_cfg = config.agent_config
|
||||
assert agent_cfg.reasoning_effort == "low"
|
||||
assert agent_cfg.thinking_budget_tokens == 4000
|
||||
provider = config.providers["ds"]
|
||||
assert provider.reasoning_effort == "high"
|
||||
assert provider.thinking_budget_tokens == 0
|
||||
assert provider.models["deepseek-v4-pro"].reasoning_effort == "max"
|
||||
assert provider.models["deepseek-v4-pro"].thinking_budget_tokens == 32000
|
||||
assert provider.models["deepseek-v4-flash"].reasoning_effort == ""
|
||||
assert provider.models["deepseek-v4-flash"].thinking_budget_tokens == 0
|
||||
|
||||
|
||||
def test_reasoning_effort_defaults_are_unset(tmp_path: Path) -> None:
|
||||
path = tmp_path / "reasoning-default.toml"
|
||||
_ = path.write_text(
|
||||
"""
|
||||
[providers.ds]
|
||||
preset = "deepseek"
|
||||
access_key_or_token = "sk-test"
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
config = plyngent.config.load(path)
|
||||
assert config.agent_config.reasoning_effort == ""
|
||||
assert config.agent_config.thinking_budget_tokens == 0
|
||||
assert config.providers["ds"].reasoning_effort == ""
|
||||
|
||||
|
||||
def test_reasoning_effort_invalid_is_bad_provider(tmp_path: Path) -> None:
|
||||
path = tmp_path / "reasoning-bad.toml"
|
||||
_ = path.write_text(
|
||||
"""
|
||||
[providers.ds]
|
||||
preset = "deepseek"
|
||||
access_key_or_token = "sk-test"
|
||||
reasoning_effort = "highest"
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
config = plyngent.config.load(path)
|
||||
assert "ds" not in config.providers
|
||||
assert "ds" in config.bad_providers
|
||||
|
||||
|
||||
def test_provider_with_empty_models_is_recoverable(tmp_path: Path) -> None:
|
||||
path = tmp_path / "empty-models.toml"
|
||||
_ = path.write_text(
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
"""Tests for thinking-strength resolution across config layers and surfaces."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from plyngent.config.models import (
|
||||
AgentConfig,
|
||||
AnthropicProvider,
|
||||
DeepseekProvider,
|
||||
ModelConfig,
|
||||
OpenAICompatibleProvider,
|
||||
OpenAIProvider,
|
||||
)
|
||||
from plyngent.config.reasoning import resolve_reasoning
|
||||
|
||||
|
||||
def _compat() -> OpenAICompatibleProvider:
|
||||
return OpenAICompatibleProvider(access_key_or_token="sk-test", url="https://example.com/v1")
|
||||
|
||||
|
||||
def test_agent_layer_only() -> None:
|
||||
reasoning = resolve_reasoning(
|
||||
AgentConfig(reasoning_effort="high", thinking_budget_tokens=9000),
|
||||
_compat(),
|
||||
"gpt-4o-mini",
|
||||
)
|
||||
assert reasoning.effort == "high"
|
||||
assert reasoning.thinking_budget_tokens == 9000
|
||||
|
||||
|
||||
def test_provider_overrides_agent_and_model_overrides_provider() -> None:
|
||||
provider = OpenAICompatibleProvider(
|
||||
access_key_or_token="sk-test",
|
||||
url="https://example.com/v1",
|
||||
reasoning_effort="low",
|
||||
models={"gpt-4o-mini": ModelConfig(reasoning_effort="xhigh")},
|
||||
)
|
||||
agent = AgentConfig(reasoning_effort="minimal")
|
||||
assert resolve_reasoning(agent, provider, "gpt-4o-mini").effort == "xhigh"
|
||||
assert resolve_reasoning(agent, provider, "other-model").effort == "low"
|
||||
assert resolve_reasoning(agent, provider, None).effort == "low"
|
||||
|
||||
|
||||
def test_fields_fall_back_independently() -> None:
|
||||
provider = OpenAICompatibleProvider(
|
||||
access_key_or_token="sk-test",
|
||||
url="https://example.com/v1",
|
||||
reasoning_effort="high",
|
||||
thinking_budget_tokens=20000,
|
||||
models={"gpt-4o-mini": ModelConfig(thinking_budget_tokens=4000)},
|
||||
)
|
||||
reasoning = resolve_reasoning(AgentConfig(reasoning_effort="low"), provider, "gpt-4o-mini")
|
||||
# Model sets the budget only → the effort still comes from the provider.
|
||||
assert reasoning.effort == "high"
|
||||
assert reasoning.thinking_budget_tokens == 4000
|
||||
|
||||
|
||||
def test_agent_layer_optional() -> None:
|
||||
provider = OpenAICompatibleProvider(
|
||||
access_key_or_token="sk-test",
|
||||
url="https://example.com/v1",
|
||||
reasoning_effort="medium",
|
||||
)
|
||||
assert resolve_reasoning(None, provider, "gpt-4o-mini").effort == "medium"
|
||||
|
||||
|
||||
def test_unset_layers_stay_unspecified() -> None:
|
||||
reasoning = resolve_reasoning(None, _compat(), "gpt-4o-mini")
|
||||
assert reasoning.effort == ""
|
||||
assert reasoning.thinking_budget_tokens == 0
|
||||
|
||||
|
||||
def test_non_positive_budget_is_ignored() -> None:
|
||||
provider = OpenAICompatibleProvider(
|
||||
access_key_or_token="sk-test",
|
||||
url="https://example.com/v1",
|
||||
thinking_budget_tokens=0,
|
||||
)
|
||||
resolved = resolve_reasoning(AgentConfig(thinking_budget_tokens=-5), provider, "gpt-4o-mini")
|
||||
assert resolved.thinking_budget_tokens == 0
|
||||
|
||||
|
||||
def test_max_survives_only_on_deepseek_chat() -> None:
|
||||
chat = DeepseekProvider(access_key_or_token="sk-test", reasoning_effort="max")
|
||||
assert resolve_reasoning(None, chat, "deepseek-v4-pro").effort == "max"
|
||||
|
||||
responses = DeepseekProvider(access_key_or_token="sk-test", convention="responses", reasoning_effort="max")
|
||||
assert resolve_reasoning(None, responses, "deepseek-v4-pro").effort == "xhigh"
|
||||
|
||||
anthropic = AnthropicProvider(access_key_or_token="sk-test", reasoning_effort="max")
|
||||
assert resolve_reasoning(None, anthropic, "claude-test").effort == "xhigh"
|
||||
|
||||
openai = OpenAIProvider(access_key_or_token="sk-test", reasoning_effort="max")
|
||||
assert resolve_reasoning(None, openai, "gpt-5.4").effort == "xhigh"
|
||||
|
||||
|
||||
def test_effort_levels_pass_through_unchanged() -> None:
|
||||
provider = _compat()
|
||||
for effort in ("none", "minimal", "low", "medium", "high", "xhigh"):
|
||||
resolved = resolve_reasoning(AgentConfig(reasoning_effort=effort), provider, "gpt-4o-mini")
|
||||
assert resolved.effort == effort
|
||||
Reference in New Issue
Block a user