feat: add explicit provider profile registry
This commit is contained in:
@@ -0,0 +1,76 @@
|
|||||||
|
"""provider 注册表(D11): 消灭 `"qwen" in provider` 式字符串猜测。
|
||||||
|
|
||||||
|
每个 provider 显式声明 thinking 参数注入形态与响应处理差异;查找按名字
|
||||||
|
**精确匹配**,未注册即装配期报错。注册是纯函数——返回新表,不修改共享
|
||||||
|
状态(纯 asyncio 中立铁律);client 经 `registry` 参数持有自己的表。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from types import MappingProxyType
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ProviderProfile:
|
||||||
|
"""单个 provider 的能力与差异声明。
|
||||||
|
|
||||||
|
thinking_on/thinking_off 分别是 `SourceConfig.enable_thinking` 为
|
||||||
|
True/False 时并入请求体的参数片段(None 时二者都不注入,用模型默认);
|
||||||
|
strip_think_tags 声明响应 content 需剥离 ``<think>`` 标签(qwen 系);
|
||||||
|
supports_native_schema 供 D14 阶梯选择原生 response_format 策略。
|
||||||
|
"""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
thinking_on: dict[str, Any]
|
||||||
|
thinking_off: dict[str, Any]
|
||||||
|
strip_think_tags: bool
|
||||||
|
supports_native_schema: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_PROFILES: Mapping[str, ProviderProfile] = MappingProxyType(
|
||||||
|
{
|
||||||
|
# 注入片段出处: VT llm.py:130-144(开启形态)与 CHS invokers.py:230-238(关闭形态)
|
||||||
|
"qwen": ProviderProfile(
|
||||||
|
name="qwen",
|
||||||
|
thinking_on={"enable_thinking": True},
|
||||||
|
thinking_off={"enable_thinking": False},
|
||||||
|
strip_think_tags=True,
|
||||||
|
),
|
||||||
|
"deepseek": ProviderProfile(
|
||||||
|
name="deepseek",
|
||||||
|
thinking_on={"thinking": {"type": "enabled"}},
|
||||||
|
thinking_off={"thinking": {"type": "disabled"}},
|
||||||
|
strip_think_tags=False,
|
||||||
|
),
|
||||||
|
"openai": ProviderProfile(
|
||||||
|
name="openai",
|
||||||
|
thinking_on={},
|
||||||
|
thinking_off={},
|
||||||
|
strip_think_tags=False,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_provider(
|
||||||
|
name: str, *, registry: Mapping[str, ProviderProfile] | None = None
|
||||||
|
) -> ProviderProfile:
|
||||||
|
"""按名字精确查找 profile;未注册直接报错(严禁默认值掩盖配置错误)。"""
|
||||||
|
table = DEFAULT_PROFILES if registry is None else registry
|
||||||
|
profile = table.get(name)
|
||||||
|
if profile is None:
|
||||||
|
raise ValueError(
|
||||||
|
f"未注册的 provider: {name!r}(已注册: {sorted(table)});"
|
||||||
|
f"新 provider 用 register_provider(ProviderProfile(...)) 注册后经 registry 参数传入"
|
||||||
|
)
|
||||||
|
return profile
|
||||||
|
|
||||||
|
|
||||||
|
def register_provider(
|
||||||
|
profile: ProviderProfile, *, base: Mapping[str, ProviderProfile] | None = None
|
||||||
|
) -> dict[str, ProviderProfile]:
|
||||||
|
"""纯函数注册: 返回 base(缺省 DEFAULT_PROFILES)+ 新条目的新表,同名覆盖。"""
|
||||||
|
table = dict(DEFAULT_PROFILES if base is None else base)
|
||||||
|
table[profile.name] = profile
|
||||||
|
return table
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
"""providers.py 注册表测试(M1 设计 §7;register_provider 为纯函数,无可变全局)。"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from polygateway.providers import (
|
||||||
|
DEFAULT_PROFILES,
|
||||||
|
ProviderProfile,
|
||||||
|
get_provider,
|
||||||
|
register_provider,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDefaultProfiles:
|
||||||
|
def test_qwen_profile(self):
|
||||||
|
p = get_provider("qwen")
|
||||||
|
assert p.thinking_on == {"enable_thinking": True}
|
||||||
|
assert p.thinking_off == {"enable_thinking": False}
|
||||||
|
assert p.strip_think_tags is True
|
||||||
|
assert p.supports_native_schema is False
|
||||||
|
|
||||||
|
def test_deepseek_profile(self):
|
||||||
|
p = get_provider("deepseek")
|
||||||
|
assert p.thinking_on == {"thinking": {"type": "enabled"}}
|
||||||
|
assert p.thinking_off == {"thinking": {"type": "disabled"}}
|
||||||
|
assert p.strip_think_tags is False
|
||||||
|
|
||||||
|
def test_openai_baseline_profile(self):
|
||||||
|
p = get_provider("openai")
|
||||||
|
assert p.thinking_on == {} and p.thinking_off == {}
|
||||||
|
assert p.strip_think_tags is False
|
||||||
|
|
||||||
|
def test_unknown_provider_fails_loudly(self):
|
||||||
|
"""消灭子串猜测: 未注册 provider 装配期即报错,不做模糊匹配。"""
|
||||||
|
with pytest.raises(ValueError, match="glm"):
|
||||||
|
get_provider("glm")
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
get_provider("qwen2") # 子串相似也不放行
|
||||||
|
|
||||||
|
|
||||||
|
class TestPureFunctionRegistration:
|
||||||
|
def test_register_returns_new_mapping(self):
|
||||||
|
glm = ProviderProfile(name="glm", thinking_on={}, thinking_off={}, strip_think_tags=False)
|
||||||
|
table = register_provider(glm)
|
||||||
|
assert get_provider("glm", registry=table) is glm
|
||||||
|
# 默认表未被污染(无可变全局状态铁律)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
get_provider("glm")
|
||||||
|
|
||||||
|
def test_register_on_custom_base_and_override(self):
|
||||||
|
custom_qwen = ProviderProfile(
|
||||||
|
name="qwen", thinking_on={"x": 1}, thinking_off={}, strip_think_tags=False
|
||||||
|
)
|
||||||
|
table = register_provider(custom_qwen, base=DEFAULT_PROFILES)
|
||||||
|
assert get_provider("qwen", registry=table).thinking_on == {"x": 1}
|
||||||
|
assert get_provider("qwen").thinking_on == {"enable_thinking": True}
|
||||||
|
|
||||||
|
def test_default_profiles_mapping_is_read_only(self):
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
DEFAULT_PROFILES["hack"] = None # type: ignore[index]
|
||||||
Reference in New Issue
Block a user