初始化提交
This commit is contained in:
commit
887c1f449e
963 files changed
+166869
No files matched your search
@@ -0,0 +1,153 @@
|
||||
"""参数处理器注册中心与内置 handlers。
|
||||
|
||||
处理跨参数依赖、结构性转换等复杂场景——这些无法通过
|
||||
YAML ParameterMapping 的 min/max/transform/default 表达。
|
||||
|
||||
使用方式(在 providers.yaml 中):
|
||||
```yaml
|
||||
parameter_mappings:
|
||||
thinking: { provider_param: thinking, handler: anthropic_thinking }
|
||||
|
||||
parameter_handlers:
|
||||
- strip_unsupported_capability_params
|
||||
```
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from app.ai_provider.pipeline import PipelineContext, PipelineResult
|
||||
|
||||
# Type alias for a parameter handler function.
|
||||
# param_key is passed for per-param handlers (from ParameterMapping.handler);
|
||||
# for global handlers (from ProviderConfig.parameter_handlers), param_key is None.
|
||||
ParameterHandler = Callable[[PipelineContext, PipelineResult, str | None], None]
|
||||
|
||||
|
||||
class ParameterHandlerRegistry:
|
||||
"""全局参数处理器注册中心。
|
||||
|
||||
使用 register() 装饰器注册 handler,名称字符串即可在 YAML 中引用。
|
||||
"""
|
||||
|
||||
_handlers: dict[str, ParameterHandler] = {}
|
||||
|
||||
@classmethod
|
||||
def register(cls, name: str) -> Callable[[ParameterHandler], ParameterHandler]:
|
||||
"""装饰器:将函数注册为命名 handler。"""
|
||||
|
||||
def decorator(handler: ParameterHandler) -> ParameterHandler:
|
||||
cls._handlers[name] = handler
|
||||
return handler
|
||||
|
||||
return decorator
|
||||
|
||||
@classmethod
|
||||
def get(cls, name: str) -> ParameterHandler:
|
||||
"""按名称获取 handler。未找到时抛出 KeyError。"""
|
||||
if name not in cls._handlers:
|
||||
msg = f"Unknown parameter handler: '{name}'. "
|
||||
msg += f"Available: {', '.join(sorted(cls._handlers))}"
|
||||
raise KeyError(msg)
|
||||
return cls._handlers[name]
|
||||
|
||||
@classmethod
|
||||
def list(cls) -> list[str]:
|
||||
"""返回所有已注册的 handler 名称。"""
|
||||
return list(cls._handlers.keys())
|
||||
|
||||
|
||||
# ── 内建 Handlers ──────────────────────────────────────────────
|
||||
|
||||
|
||||
@ParameterHandlerRegistry.register("anthropic_thinking")
|
||||
def handle_anthropic_thinking(
|
||||
_ctx: PipelineContext,
|
||||
result: PipelineResult,
|
||||
_param_key: str | None = None,
|
||||
) -> None:
|
||||
"""处理 Anthropic thinking 参数的跨参数约束。
|
||||
|
||||
Anthropic API 要求:
|
||||
- max_tokens 必须大于 thinking.budget_tokens
|
||||
- 开启 thinking 时 temperature 必须为 1(不应传此参数)
|
||||
- thinking 参数自身需转换为 {type, budget_tokens} 结构
|
||||
"""
|
||||
thinking = result.params.get("thinking")
|
||||
if thinking is None:
|
||||
return
|
||||
|
||||
# 将标量 thinking=True 转换为启用 extended thinking
|
||||
if thinking is True:
|
||||
result.params["thinking"] = {"type": "enabled"}
|
||||
|
||||
# 提取 budget_tokens
|
||||
if isinstance(thinking, dict):
|
||||
budget = thinking.get("budget_tokens", 0)
|
||||
# 确保 max_tokens > budget_tokens
|
||||
max_tokens = result.params.get("max_tokens", 0)
|
||||
if budget > 0 and max_tokens <= budget:
|
||||
result.params["max_tokens"] = budget + 1
|
||||
|
||||
# 开启 thinking 时移除 temperature 参数(Anthropic 静默忽略,
|
||||
# 但显式移除更安全干净)
|
||||
result.params.pop("temperature", None)
|
||||
|
||||
|
||||
@ParameterHandlerRegistry.register("validate_max_tokens")
|
||||
def validate_max_tokens(
|
||||
ctx: PipelineContext,
|
||||
result: PipelineResult,
|
||||
_param_key: str | None = None,
|
||||
) -> None:
|
||||
"""校验 max_tokens 不超过模型上限。"""
|
||||
model_config = ctx.model_config
|
||||
if model_config is None:
|
||||
return
|
||||
max_tokens = result.params.get("max_tokens")
|
||||
if max_tokens is None:
|
||||
return
|
||||
limit = model_config.max_output_tokens
|
||||
if limit and max_tokens > limit:
|
||||
result.params["max_tokens"] = limit
|
||||
|
||||
|
||||
@ParameterHandlerRegistry.register("validate_temperature")
|
||||
def validate_temperature(
|
||||
_ctx: PipelineContext,
|
||||
_result: PipelineResult,
|
||||
_param_key: str | None = None,
|
||||
) -> None:
|
||||
"""温度参数跨提供商规范校验。
|
||||
|
||||
非 reasoning/thinking 模式下强制温度 1.0 不是必需的规则,
|
||||
OpenAI/DeepSeek 无此要求,此 handler 留作扩展点。
|
||||
"""
|
||||
# 预留:可为特定 provider 添加温度校验逻辑
|
||||
pass
|
||||
|
||||
|
||||
@ParameterHandlerRegistry.register("strip_unsupported_capability_params")
|
||||
def strip_unsupported_capability_params(
|
||||
ctx: PipelineContext,
|
||||
result: PipelineResult,
|
||||
_param_key: str | None = None,
|
||||
) -> None:
|
||||
"""根据模型能力过滤不支持的参数。
|
||||
|
||||
当模型不支持 vision 时,移除 image_url 等视觉参数。
|
||||
当模型不支持 tool_use 时,移除 tools 相关参数。
|
||||
"""
|
||||
model_config = ctx.model_config
|
||||
if model_config is None:
|
||||
return
|
||||
|
||||
caps = model_config.resolved_capabilities()
|
||||
|
||||
if not caps.get("vision", False):
|
||||
result.params.pop("image_url", None)
|
||||
result.params.pop("image", None)
|
||||
|
||||
if not caps.get("thinking", False):
|
||||
result.params.pop("thinking", None)
|
||||
Reference in new issue
Block a user