初始化提交
This commit is contained in:
commit
887c1f449e
963 files changed
+166869
No files matched your search
@@ -0,0 +1,305 @@
|
||||
"""Tests for the parameter handler mechanism (Task 4 extension)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from app.ai_provider.config.models import ModelConfig, ParameterMapping, ProviderConfig
|
||||
from app.ai_provider.pipeline import PipelineContext, PipelineFactory
|
||||
from app.ai_provider.pipeline_handlers import ParameterHandlerRegistry
|
||||
|
||||
|
||||
class TestParameterHandlerRegistry:
|
||||
"""Test the handler registry itself."""
|
||||
|
||||
def test_register_and_get(self):
|
||||
@ParameterHandlerRegistry.register("test_handler_1")
|
||||
def my_handler(_ctx, _result, _param_key=None):
|
||||
pass
|
||||
|
||||
handler = ParameterHandlerRegistry.get("test_handler_1")
|
||||
assert handler is my_handler
|
||||
|
||||
def test_get_unknown_handler_raises(self):
|
||||
with pytest.raises(KeyError, match="Unknown parameter handler"):
|
||||
ParameterHandlerRegistry.get("nonexistent_handler")
|
||||
|
||||
def test_list_handlers(self):
|
||||
handlers = ParameterHandlerRegistry.list()
|
||||
assert "anthropic_thinking" in handlers
|
||||
assert "validate_max_tokens" in handlers
|
||||
assert "strip_unsupported_capability_params" in handlers
|
||||
|
||||
|
||||
class TestAnthropicThinkingHandler:
|
||||
"""Test the anthropic_thinking handler via the pipeline."""
|
||||
|
||||
def test_thinking_true_converts_to_dict(self):
|
||||
"""标量 thinking=True → {type: enabled}"""
|
||||
provider = ProviderConfig(
|
||||
name="anthropic",
|
||||
format="anthropic",
|
||||
base_url_env="ANTHROPIC_BASE_URL",
|
||||
api_key_env="ANTHROPIC_API_KEY",
|
||||
parameter_mappings={
|
||||
"thinking": ParameterMapping(
|
||||
provider_param="thinking",
|
||||
handler="anthropic_thinking",
|
||||
),
|
||||
"max_tokens": ParameterMapping(provider_param="max_tokens"),
|
||||
},
|
||||
)
|
||||
pipeline = PipelineFactory.create(provider)
|
||||
ctx = PipelineContext(params={"thinking": True, "max_tokens": 4096})
|
||||
result = pipeline.run(ctx)
|
||||
assert result.params["thinking"] == {"type": "enabled"}
|
||||
assert result.params["max_tokens"] == 4096
|
||||
|
||||
def test_thinking_budget_enforces_max_tokens(self):
|
||||
"""thinking.budget_tokens 确保 max_tokens > budget_tokens"""
|
||||
provider = ProviderConfig(
|
||||
name="anthropic",
|
||||
format="anthropic",
|
||||
base_url_env="X",
|
||||
api_key_env="Y",
|
||||
parameter_mappings={
|
||||
"thinking": ParameterMapping(
|
||||
provider_param="thinking",
|
||||
handler="anthropic_thinking",
|
||||
),
|
||||
"max_tokens": ParameterMapping(provider_param="max_tokens"),
|
||||
},
|
||||
)
|
||||
pipeline = PipelineFactory.create(provider)
|
||||
# budget=8192, max_tokens=4096 → 不满足,应被抬到 8193
|
||||
ctx = PipelineContext(params={"thinking": {"budget_tokens": 8192}, "max_tokens": 4096})
|
||||
result = pipeline.run(ctx)
|
||||
assert result.params["max_tokens"] >= 8193
|
||||
|
||||
def test_thinking_removes_temperature(self):
|
||||
"""开启 thinking 时 temperature 被移除"""
|
||||
provider = ProviderConfig(
|
||||
name="anthropic",
|
||||
format="anthropic",
|
||||
base_url_env="X",
|
||||
api_key_env="Y",
|
||||
parameter_mappings={
|
||||
"thinking": ParameterMapping(
|
||||
provider_param="thinking",
|
||||
handler="anthropic_thinking",
|
||||
),
|
||||
"temperature": ParameterMapping(
|
||||
provider_param="temperature",
|
||||
min=0,
|
||||
max=1,
|
||||
),
|
||||
"max_tokens": ParameterMapping(provider_param="max_tokens"),
|
||||
},
|
||||
)
|
||||
pipeline = PipelineFactory.create(provider)
|
||||
ctx = PipelineContext(params={"thinking": True, "temperature": 0.7, "max_tokens": 4096})
|
||||
result = pipeline.run(ctx)
|
||||
assert "temperature" not in result.params
|
||||
|
||||
|
||||
class TestValidateMaxTokensHandler:
|
||||
"""Test the validate_max_tokens handler."""
|
||||
|
||||
def test_clamps_to_model_limit(self):
|
||||
"""max_tokens 应被裁剪到模型上限"""
|
||||
model = ModelConfig(
|
||||
name="test-model",
|
||||
provider="anthropic",
|
||||
model="claude-sonnet-4-6",
|
||||
format="anthropic",
|
||||
max_output_tokens=16000,
|
||||
)
|
||||
provider = ProviderConfig(
|
||||
name="anthropic",
|
||||
format="anthropic",
|
||||
base_url_env="X",
|
||||
api_key_env="Y",
|
||||
parameter_mappings={
|
||||
"max_tokens": ParameterMapping(provider_param="max_tokens"),
|
||||
},
|
||||
parameter_handlers=["validate_max_tokens"],
|
||||
)
|
||||
pipeline = PipelineFactory.create(provider)
|
||||
ctx = PipelineContext(
|
||||
params={"max_tokens": 999999},
|
||||
model_config=model,
|
||||
provider_config=provider,
|
||||
)
|
||||
result = pipeline.run(ctx)
|
||||
assert result.params["max_tokens"] == 16000
|
||||
|
||||
def test_within_limit_unchanged(self):
|
||||
"""max_tokens 在合理范围内时不变"""
|
||||
model = ModelConfig(
|
||||
name="test-model",
|
||||
provider="anthropic",
|
||||
model="claude-sonnet-4-6",
|
||||
format="anthropic",
|
||||
max_output_tokens=16000,
|
||||
)
|
||||
provider = ProviderConfig(
|
||||
name="anthropic",
|
||||
format="anthropic",
|
||||
base_url_env="X",
|
||||
api_key_env="Y",
|
||||
parameter_mappings={
|
||||
"max_tokens": ParameterMapping(provider_param="max_tokens"),
|
||||
},
|
||||
parameter_handlers=["validate_max_tokens"],
|
||||
)
|
||||
pipeline = PipelineFactory.create(provider)
|
||||
ctx = PipelineContext(
|
||||
params={"max_tokens": 8000},
|
||||
model_config=model,
|
||||
provider_config=provider,
|
||||
)
|
||||
result = pipeline.run(ctx)
|
||||
assert result.params["max_tokens"] == 8000
|
||||
|
||||
|
||||
class TestStripUnsupportedCapabilityParams:
|
||||
"""Test the strip_unsupported_capability_params handler."""
|
||||
|
||||
def test_strips_image_params_when_no_vision(self):
|
||||
"""模型不支持 vision 时移除 image_url"""
|
||||
model = ModelConfig(
|
||||
name="test-model",
|
||||
provider="openai",
|
||||
model="gpt-4o",
|
||||
format="openai",
|
||||
supports_vision=False,
|
||||
)
|
||||
provider = ProviderConfig(
|
||||
name="openai",
|
||||
format="openai",
|
||||
base_url_env="X",
|
||||
api_key_env="Y",
|
||||
parameter_mappings={
|
||||
"max_tokens": ParameterMapping(provider_param="max_tokens"),
|
||||
"image_url": ParameterMapping(provider_param="image_url"),
|
||||
},
|
||||
parameter_handlers=["strip_unsupported_capability_params"],
|
||||
)
|
||||
pipeline = PipelineFactory.create(provider)
|
||||
ctx = PipelineContext(
|
||||
params={"max_tokens": 100, "image_url": "http://example.com/img.png"},
|
||||
model_config=model,
|
||||
provider_config=provider,
|
||||
)
|
||||
result = pipeline.run(ctx)
|
||||
assert "image_url" not in result.params
|
||||
assert "max_tokens" in result.params
|
||||
|
||||
def test_keeps_image_params_when_vision_supported(self):
|
||||
"""模型支持 vision 时保留 image_url"""
|
||||
model = ModelConfig(
|
||||
name="test-model",
|
||||
provider="openai",
|
||||
model="gpt-4o",
|
||||
format="openai",
|
||||
supports_vision=True,
|
||||
)
|
||||
provider = ProviderConfig(
|
||||
name="openai",
|
||||
format="openai",
|
||||
base_url_env="X",
|
||||
api_key_env="Y",
|
||||
parameter_mappings={
|
||||
"max_tokens": ParameterMapping(provider_param="max_tokens"),
|
||||
"image_url": ParameterMapping(provider_param="image_url"),
|
||||
},
|
||||
parameter_handlers=["strip_unsupported_capability_params"],
|
||||
)
|
||||
pipeline = PipelineFactory.create(provider)
|
||||
ctx = PipelineContext(
|
||||
params={"max_tokens": 100, "image_url": "http://example.com/img.png"},
|
||||
model_config=model,
|
||||
provider_config=provider,
|
||||
)
|
||||
result = pipeline.run(ctx)
|
||||
assert "image_url" in result.params
|
||||
|
||||
def test_strips_thinking_when_not_supported(self):
|
||||
"""模型不支持 thinking 时移除 thinking 参数"""
|
||||
model = ModelConfig(
|
||||
name="test-model",
|
||||
provider="openai",
|
||||
model="gpt-4o",
|
||||
format="openai",
|
||||
supports_thinking=False,
|
||||
)
|
||||
provider = ProviderConfig(
|
||||
name="openai",
|
||||
format="openai",
|
||||
base_url_env="X",
|
||||
api_key_env="Y",
|
||||
parameter_mappings={
|
||||
"thinking": ParameterMapping(provider_param="thinking"),
|
||||
},
|
||||
parameter_handlers=["strip_unsupported_capability_params"],
|
||||
)
|
||||
pipeline = PipelineFactory.create(provider)
|
||||
ctx = PipelineContext(
|
||||
params={"thinking": True},
|
||||
model_config=model,
|
||||
provider_config=provider,
|
||||
)
|
||||
result = pipeline.run(ctx)
|
||||
assert "thinking" not in result.params
|
||||
|
||||
|
||||
class TestHandlerIntegration:
|
||||
"""Test full pipeline integration with handlers."""
|
||||
|
||||
def test_anthropic_pipeline_end_to_end(self):
|
||||
"""模拟完整的 Anthropic pipeline 流程"""
|
||||
provider = ProviderConfig(
|
||||
name="anthropic",
|
||||
format="anthropic",
|
||||
base_url_env="ANTHROPIC_BASE_URL",
|
||||
api_key_env="ANTHROPIC_API_KEY",
|
||||
parameter_mappings={
|
||||
"max_tokens": ParameterMapping(provider_param="max_tokens", default=4096),
|
||||
"thinking": ParameterMapping(
|
||||
provider_param="thinking", handler="anthropic_thinking"
|
||||
),
|
||||
"temperature": ParameterMapping(
|
||||
provider_param="temperature", min=0, max=1, default=1.0
|
||||
),
|
||||
},
|
||||
parameter_handlers=[
|
||||
"strip_unsupported_capability_params",
|
||||
"validate_max_tokens",
|
||||
],
|
||||
)
|
||||
model = ModelConfig(
|
||||
name="claude-sonnet",
|
||||
provider="anthropic",
|
||||
model="claude-sonnet-4-6",
|
||||
format="anthropic",
|
||||
supports_vision=True,
|
||||
supports_thinking=True,
|
||||
max_output_tokens=16000,
|
||||
)
|
||||
pipeline = PipelineFactory.create(provider)
|
||||
ctx = PipelineContext(
|
||||
params={
|
||||
"thinking": {"budget_tokens": 8000},
|
||||
"temperature": 0.7,
|
||||
},
|
||||
model_config=model,
|
||||
provider_config=provider,
|
||||
)
|
||||
result = pipeline.run(ctx)
|
||||
|
||||
# thinking → provider_param name
|
||||
assert "thinking" in result.params
|
||||
# max_tokens injected via default, then lifted by handler
|
||||
assert result.params["max_tokens"] >= 8001
|
||||
# temperature removed by handler
|
||||
assert "temperature" not in result.params
|
||||
Reference in new issue
Block a user