From e259613b6e5caacac925db21ae1e712f6ffe01e8 Mon Sep 17 00:00:00 2001 From: pk-zipstack Date: Fri, 10 Jul 2026 15:33:06 +0530 Subject: [PATCH] UN-3707 [FIX] Cover Claude 5 family in deprecated sampling-param strip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Anthropic deprecated temperature/top_p/top_k starting with Claude Opus 4.7 and continued it across the Claude 5 family. The shared detection regex _SAMPLING_DEPRECATED_MODEL_PATTERNS only matched claude-opus-4-7, so Claude Sonnet 5 (and Opus 4.8 / Fable 5) reached the provider with the default temperature and 400'd — surfaced as the Azure AI Foundry "temperature is deprecated for this model" adapter-test failure. Extend the shared tuple to also match claude-opus-4-8, claude-sonnet-5, claude-fable-5, and claude-mythos-5. All four Claude-capable adapters (Anthropic, Bedrock, Vertex AI, Azure AI Foundry) read the same tuple, so the single change fixes every adapter. Opus 4.6 / Sonnet 4.6 and older still accept sampling params and are intentionally left out. Tests extended to pin the new model encodings (native, Bedrock ARN, Vertex @date, Azure deployment names), lock the trailing-edge anchor against collisions (claude-sonnet-50, claude-sonnet-5verbose), and assert the Sonnet 5 strip across all four adapters. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../sdk1/src/unstract/sdk1/adapters/base1.py | 18 +++-- unstract/sdk1/tests/test_sampling_strip.py | 81 ++++++++++++++++++- 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/unstract/sdk1/src/unstract/sdk1/adapters/base1.py b/unstract/sdk1/src/unstract/sdk1/adapters/base1.py index ebd93e3dbd..9bcc7591f7 100644 --- a/unstract/sdk1/src/unstract/sdk1/adapters/base1.py +++ b/unstract/sdk1/src/unstract/sdk1/adapters/base1.py @@ -18,11 +18,15 @@ logger = logging.getLogger(__name__) # Anthropic models that have deprecated sampling parameters (`temperature`, -# `top_p`, `top_k`). The patterns are regex-searched against the model id -# after lowercasing and normalizing `.` / `_` to `-`. The match is anchored at -# the trailing edge so that unrelated future ids (`claude-opus-4-70`, -# `claude-opus-4-75`, `claude-opus-4-7verbose`) do not match. A single entry -# covers every encoding of the id we have observed: +# `top_p`, `top_k`) — sending any of them yields a 400. Anthropic began this +# with Claude Opus 4.7 and continued it across the Claude 5 family (Opus 4.8, +# Sonnet 5, Fable 5, Mythos 5); adjacent older families (Opus 4.6, Sonnet 4.6, +# and earlier) still ACCEPT these params and must NOT be listed here. +# The patterns are regex-searched against the model id after lowercasing and +# normalizing `.` / `_` to `-`. Each match is anchored at the trailing edge so +# that unrelated ids (`claude-opus-4-70`, `claude-sonnet-50`, +# `claude-sonnet-5verbose`) do not match. One entry per model covers every +# encoding of that id we have observed (shown here for `claude-opus-4-7`): # - Native Anthropic `claude-opus-4-7`, `anthropic/claude-opus-4-7` # - Bedrock foundation model `anthropic.claude-opus-4-7--v1:0` # - Bedrock cross-region profile `us.anthropic.claude-opus-4-7-...`, @@ -44,6 +48,10 @@ # See https://docs.claude.com/en/about-claude/models/whats-new-claude-4-7 _SAMPLING_DEPRECATED_MODEL_PATTERNS: tuple[re.Pattern[str], ...] = ( re.compile(r"claude-opus-4-7(?=$|[-:@/]|v\d)"), + re.compile(r"claude-opus-4-8(?=$|[-:@/]|v\d)"), + re.compile(r"claude-sonnet-5(?=$|[-:@/]|v\d)"), + re.compile(r"claude-fable-5(?=$|[-:@/]|v\d)"), + re.compile(r"claude-mythos-5(?=$|[-:@/]|v\d)"), ) _DEPRECATED_SAMPLING_PARAMS: tuple[str, ...] = ("temperature", "top_p", "top_k") # Fields whose value can carry a model id. `model` is universal; `model_id` is diff --git a/unstract/sdk1/tests/test_sampling_strip.py b/unstract/sdk1/tests/test_sampling_strip.py index 3e3ce8b405..3839d23312 100644 --- a/unstract/sdk1/tests/test_sampling_strip.py +++ b/unstract/sdk1/tests/test_sampling_strip.py @@ -1,8 +1,11 @@ -"""Tests for Claude Opus 4.7 sampling-parameter strip. +"""Tests for the Anthropic sampling-parameter strip. + +Covers Claude Opus 4.7 and the Claude 5 family (Opus 4.8, Sonnet 5, Fable 5, +Mythos 5) — every model that rejects `temperature`/`top_p`/`top_k` with a 400. Pins the detection regex and the four-adapter wiring against the failure modes that surfaced in PR #1934 review: -- prefix collisions (`claude-opus-4-70`, `-75`, `4-7verbose`) +- prefix collisions (`claude-opus-4-70`, `-75`, `4-7verbose`, `claude-sonnet-50`) - Bedrock Application Inference Profile ARN fallback via `model_id` - mutate-and-return regression (input dict must be preserved) - silent skip with sampling params present must emit a debug breadcrumb @@ -68,6 +71,44 @@ def test_has_deprecated_sampling_params_positive(model: str) -> None: assert _has_deprecated_sampling_params(model) +# Claude 5 family — the deprecation continued from Opus 4.7 to Opus 4.8, +# Sonnet 5, Fable 5, and Mythos 5. Sonnet 5 on Azure AI Foundry is the +# reported case that motivated adding these entries. +CLAUDE_5_POSITIVES: list[str] = [ + # Opus 4.8 + "claude-opus-4-8", + "anthropic/claude-opus-4-8", + "anthropic.claude-opus-4-8-20260101-v1:0", + "us.anthropic.claude-opus-4-8-20260101-v1:0", + "vertex_ai/claude-opus-4-8@20260101", + "azure_ai/claude-opus-4-8", + "claude-opus-4-8v1", + # Sonnet 5 (reported: Azure AI Foundry) + "claude-sonnet-5", + "anthropic/claude-sonnet-5", + "anthropic.claude-sonnet-5", + "us.anthropic.claude-sonnet-5-20260101-v1:0", + "vertex_ai/claude-sonnet-5", + "azure_ai/claude-sonnet-5", + "azure_ai/my-claude-sonnet-5-deployment", + "claude.sonnet.5", + "claude_sonnet_5", + "claude-sonnet-5v1", + # Fable 5 + "claude-fable-5", + "anthropic/claude-fable-5", + "azure_ai/claude-fable-5", + # Mythos 5 + "claude-mythos-5", + "vertex_ai/claude-mythos-5", +] + + +@pytest.mark.parametrize("model", CLAUDE_5_POSITIVES) +def test_has_deprecated_sampling_params_claude5_positive(model: str) -> None: + assert _has_deprecated_sampling_params(model) + + # ── detection: negatives ──────────────────────────────────────────────────── NEGATIVES: list[str | None] = [ @@ -92,6 +133,17 @@ def test_has_deprecated_sampling_params_positive(model: str) -> None: "claude-opus-4-7verbose", "claude-opus-4-7vnext", "claude-opus-4-7variant", + "claude-sonnet-5verbose", + # Claude 5 prefix collisions — trailing digit is not a boundary. + "claude-opus-4-80", + "claude-sonnet-50", + "claude-fable-50", + "claude-mythos-50", + # Adjacent Claude families that still accept sampling params. + "claude-sonnet-4-6", + "claude-sonnet-4-5", + "claude-haiku-4-6", + "claude-fable-4", # Opaque Bedrock Application Inference Profile ARN — model id is not # recoverable from the string. Strip-detection is expected to skip; # callers must keep the standard id in `model` or `model_id`. @@ -277,6 +329,31 @@ def test_vertex_validate_strips_temperature_for_opus_4_7() -> None: assert param not in result, f"vertex: {param} should be stripped" +@pytest.mark.parametrize( + "name,cls,extra", + ADAPTER_CASES, + ids=lambda v: v if isinstance(v, str) else "", +) +def test_validate_strips_temperature_for_sonnet_5( + name: str, cls: type, extra: dict[str, Any] +) -> None: + """Reported case: Claude Sonnet 5 on Azure AI Foundry (and its peers).""" + model = { + "anthropic": "claude-sonnet-5", + "bedrock": "anthropic.claude-sonnet-5", + "azure_ai_foundry": "claude-sonnet-5", + }[name] + result = cls.validate({"model": model, "temperature": 0.5, **extra}) + for param in _DEPRECATED_SAMPLING_PARAMS: + assert param not in result, f"{name}: {param} should be stripped" + + +def test_vertex_validate_strips_temperature_for_sonnet_5() -> None: + result = VertexAILLMParameters.validate(_vertex_metadata("claude-sonnet-5")) + for param in _DEPRECATED_SAMPLING_PARAMS: + assert param not in result, f"vertex: {param} should be stripped" + + @pytest.mark.parametrize( "name,cls,extra", ADAPTER_CASES,