diff --git a/src/celeste/modalities/text/providers/anthropic/models.py b/src/celeste/modalities/text/providers/anthropic/models.py index a78a378..cc6c427 100644 --- a/src/celeste/modalities/text/providers/anthropic/models.py +++ b/src/celeste/modalities/text/providers/anthropic/models.py @@ -94,7 +94,7 @@ streaming=True, parameter_constraints={ Parameter.MAX_TOKENS: Range(min=1, max=64000), - TextParameter.THINKING_BUDGET: Range(min=-1, max=32000), + TextParameter.THINKING_BUDGET: Range(min=1024, max=32000), TextParameter.OUTPUT_SCHEMA: Schema(), TextParameter.TOOLS: ToolSupport(tools=[WebSearch]), TextParameter.TOOL_CHOICE: ToolChoiceSupport(), @@ -217,3 +217,15 @@ }, ), ] + +# Models that support web_search dynamic filtering (web_search_20260209); others use basic. +DYNAMIC_FILTERING_MODELS = frozenset( + { + "claude-fable-5", + "claude-opus-4-8", + "claude-opus-4-7", + "claude-opus-4-6", + "claude-sonnet-5", + "claude-sonnet-4-6", + } +) diff --git a/src/celeste/modalities/text/providers/anthropic/parameters.py b/src/celeste/modalities/text/providers/anthropic/parameters.py index e8d370d..3992f72 100644 --- a/src/celeste/modalities/text/providers/anthropic/parameters.py +++ b/src/celeste/modalities/text/providers/anthropic/parameters.py @@ -28,6 +28,7 @@ from celeste.types import TextContent from ...parameters import TextParameter +from .models import DYNAMIC_FILTERING_MODELS class TemperatureMapper(_TemperatureMapper): @@ -80,10 +81,24 @@ class OutputSchemaMapper(_OutputFormatMapper): class ToolsMapper(_ToolsMapper): - """Map tools to Anthropic's tools parameter.""" + """Map tools to Anthropic's tools parameter (web_search version per model).""" name = TextParameter.TOOLS + def map( + self, + request: dict[str, Any], + value: object, + model: Model, + ) -> dict[str, Any]: + """Upgrade web_search to dynamic filtering (web_search_20260209) where the model supports it.""" + request = super().map(request, value, model) + if model.id in DYNAMIC_FILTERING_MODELS: + for tool in request.get("tools", []): + if tool.get("name") == "web_search": + tool["type"] = "web_search_20260209" + return request + class ToolChoiceMapper(_ToolChoiceMapper): """Map tool_choice to Anthropic's tool_choice parameter.""" diff --git a/src/celeste/providers/anthropic/messages/parameters.py b/src/celeste/providers/anthropic/messages/parameters.py index 96f0d7f..d2cbc67 100644 --- a/src/celeste/providers/anthropic/messages/parameters.py +++ b/src/celeste/providers/anthropic/messages/parameters.py @@ -74,10 +74,8 @@ def map( class ThinkingLevelMapper(ParameterMapper[TextContent]): """Map thinking_level to adaptive thinking plus output_config.effort. - Emits {"thinking": {"type": "adaptive"}, "output_config": {"effort": }} - where is one of "low" | "medium" | "high" | "xhigh" | "max" — the - Messages API rejects an "effort" key inside the thinking object - ("thinking.adaptive.effort: Extra inputs are not permitted"). + display="summarized" so thinking summaries stream (API default "omitted" is empty). + effort goes in output_config, not the thinking object (the API rejects it there). """ def map( @@ -90,7 +88,7 @@ def map( validated_value = self._validate_value(value, model) if validated_value is None: return request - request["thinking"] = {"type": "adaptive"} + request["thinking"] = {"type": "adaptive", "display": "summarized"} request.setdefault("output_config", {})["effort"] = validated_value return request