-
Notifications
You must be signed in to change notification settings - Fork 623
[FIX] Strip deprecated sampling params for Claude Opus 4.7 #1934
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
| import inspect | ||
| import logging | ||
| import os | ||
| import re | ||
| from abc import ABC, abstractmethod | ||
| from importlib import import_module | ||
| from typing import TYPE_CHECKING | ||
|
|
@@ -15,6 +16,79 @@ | |
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| # Anthropic models that have deprecated sampling parameters (`temperature`, | ||
| # `top_p`, `top_k`). The patterns are substring-matched against the model id | ||
| # after lowercasing and normalizing `.` / `_` to `-`, so a single entry covers: | ||
| # - Native Anthropic `claude-opus-4-7`, `anthropic/claude-opus-4-7` | ||
| # - Bedrock foundation model `anthropic.claude-opus-4-7-<date>-v1:0` | ||
| # - Bedrock route prefixes `converse/...`, `invoke/...` | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LOW — misleading bullet: route prefixes are not part of the pattern “Bedrock route prefixes |
||
| # - Bedrock cross-region profile `us.anthropic.claude-opus-4-7-...`, | ||
| # `eu.`, `apac.`, `global.` variants | ||
| # - Bedrock foundation-model ARN `arn:aws:bedrock:<region>::foundation-model/ | ||
| # anthropic.claude-opus-4-7-...` | ||
| # - Bedrock inference-profile ARN `arn:aws:bedrock:<region>:<account>: | ||
| # inference-profile/us.anthropic.claude-opus-4-7-...` | ||
| # - Vertex AI `vertex_ai/claude-opus-4-7@<date>` | ||
| # - Azure AI Foundry deployments whose name embeds `claude-opus-4-7` | ||
| # Add new entries here when Anthropic deprecates sampling on more models. | ||
| # Patterns are anchored at the trailing edge: the version digit must be | ||
| # followed by end-of-string or one of `-:@/v`, so unrelated future ids like | ||
| # `claude-opus-4-70`, `claude-opus-4-75` do not match. The allowed delimiters | ||
| # cover the date suffix (`-20260101-v1:0`), Vertex `@<date>`, ARN paths, and | ||
| # the `v1:0` version tag. | ||
| # See https://platform.claude.com/docs/en/about-claude/models/whats-new-claude-4-7 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LOW — verify canonical docs URL
|
||
| _SAMPLING_DEPRECATED_MODEL_PATTERNS: tuple[re.Pattern[str], ...] = ( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OPTIONAL (simplify) — tuple-of-patterns + With exactly one regex and exactly two id-bearing fields, the indirection costs more than it saves. If you want to reduce surface: _SAMPLING_DEPRECATED_MODEL_RE = re.compile(r"claude-opus-4-7(?=$|[-:@/v])")
…
def _strip_deprecated_sampling_params(validated):
if _has_deprecated_sampling_params(validated.get("model")) \
or _has_deprecated_sampling_params(validated.get("model_id")):
for param in _DEPRECATED_SAMPLING_PARAMS:
validated.pop(param, None)
return validatedGoing back to a tuple/loop when (if) a second deprecation lands is a 2-line change. Reasonable people will disagree — the current shape is readable; this is purely a YAGNI call. Skip if you prefer keeping the table for forward-compat. |
||
| re.compile(r"claude-opus-4-7(?=$|[-:@/v])"), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. HIGH — anchor lookahead admits
Fix: require the version pattern explicitly, e.g. re.compile(r"claude-opus-4-7(?=$|[-:@/]|v\d)")Keeps |
||
| ) | ||
| _DEPRECATED_SAMPLING_PARAMS: tuple[str, ...] = ("temperature", "top_p", "top_k") | ||
| # Fields whose value can carry a model id. `model` is universal; `model_id` is | ||
|
pk-zipstack marked this conversation as resolved.
|
||
| # Bedrock's separate ARN field used for Application Inference Profile cost | ||
| # tracking — when callers route through an AIP, the standard model id often | ||
| # only appears here, not in `model`. | ||
| _MODEL_ID_FIELDS: tuple[str, ...] = ("model", "model_id") | ||
|
|
||
|
|
||
| def _has_deprecated_sampling_params(model: str | None) -> bool: | ||
| """Return True when the model rejects sampling parameters. | ||
|
|
||
| Anthropic deprecated `temperature`, `top_p`, and `top_k` starting with | ||
| Claude Opus 4.7; sending any of them yields a 400 from Anthropic and from | ||
| the providers that proxy it (Bedrock, Azure AI Foundry, Vertex AI). | ||
|
|
||
| The check normalizes case and `.`/`_` separators to `-`, then substring- | ||
| matches against the patterns. This catches every format that embeds the | ||
| model id (foundation model ids, cross-region profiles, foundation-model | ||
| ARNs, inference-profile ARNs, Vertex `@`-suffixed ids). | ||
|
pk-zipstack marked this conversation as resolved.
|
||
|
|
||
| It does NOT catch: | ||
| - Bedrock Application Inference Profile ARNs (e.g. | ||
| `arn:aws:bedrock:...:application-inference-profile/abcd1234`), whose | ||
| tail is an opaque profile id — the underlying model is not recoverable | ||
| from the string. Pass the AIP ARN in `model_id` and keep the standard | ||
| model id in `model`, or the strip won't fire. | ||
| - Azure AI Foundry deployment names that omit the model id; rename the | ||
| deployment to include `claude-opus-4-7` so detection works. | ||
| """ | ||
| if not model: | ||
| return False | ||
| normalized = model.lower().replace(".", "-").replace("_", "-") | ||
| return any(rx.search(normalized) for rx in _SAMPLING_DEPRECATED_MODEL_PATTERNS) | ||
|
|
||
|
|
||
| def _strip_deprecated_sampling_params(validated: dict[str, "Any"]) -> dict[str, "Any"]: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MED — mutate-and-return is inconsistent with surrounding callers The rest of this file consistently builds Fix (lowest-risk): rename to Type-design note: invariant strength would also improve if the |
||
| """Drop sampling params for models that reject them (e.g. Claude Opus 4.7). | ||
|
|
||
| Pydantic's `model_dump()` re-emits the default `temperature=0.1` even when | ||
|
pk-zipstack marked this conversation as resolved.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LOW — docstring “why” understates the scope of the strip “Pydantic’s (Distinct from the existing pk-zipstack / chandrasekharan-zipstack thread on this line about whether litellm handles the cleanup upstream.) |
||
| the caller never set one, so the field must be popped after validation to | ||
| keep it off the wire. Checks both `model` and `model_id` so Bedrock callers | ||
| routing through an Application Inference Profile are covered when the | ||
| standard model id only appears in `model_id`. | ||
| """ | ||
| if any(_has_deprecated_sampling_params(validated.get(f)) for f in _MODEL_ID_FIELDS): | ||
| for param in _DEPRECATED_SAMPLING_PARAMS: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MED — post-strip
Fix: grep platform-service / SDK consumers of |
||
| validated.pop(param, None) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. HIGH — silent no-op when sampling params are present but no model-id field matches If a Bedrock Application Inference Profile ARN with an opaque tail (or an Azure Foundry deployment whose name omits the model id) reaches this helper while Fix: add a single if matched:
for p in _DEPRECATED_SAMPLING_PARAMS:
validated.pop(p, None)
elif any(p in validated for p in _DEPRECATED_SAMPLING_PARAMS):
logger.debug(
"Sampling-strip skipped; model ids did not match deprecation patterns: %s",
{f: validated.get(f) for f in _MODEL_ID_FIELDS if validated.get(f)},
)Keeps the AIP/Foundry fallback non-fatal but observable. |
||
| return validated | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MED — zero committed tests for the new helpers or the four wired call sites
Suggested minimal lock-in (
|
||
|
|
||
|
|
||
| def register_adapters(adapters: dict[str, dict[str, "Any"]], adapter_type: str) -> None: | ||
| """Register all SDK v1 adapters of given type. | ||
|
|
@@ -462,7 +536,7 @@ def validate(adapter_metadata: dict[str, "Any"]) -> dict[str, "Any"]: | |
| if "thinking" in result_metadata: | ||
| validated_data["thinking"] = result_metadata["thinking"] | ||
|
|
||
| return validated_data | ||
| return _strip_deprecated_sampling_params(validated_data) | ||
|
|
||
| @staticmethod | ||
| def validate_model(adapter_metadata: dict[str, "Any"]) -> str: | ||
|
|
@@ -538,7 +612,7 @@ def validate(adapter_metadata: dict[str, "Any"]) -> dict[str, "Any"]: | |
| if enable_thinking and "thinking" in result_metadata: | ||
| validated["thinking"] = result_metadata["thinking"] | ||
|
|
||
| return validated | ||
| return _strip_deprecated_sampling_params(validated) | ||
|
|
||
| @staticmethod | ||
| def validate_model(adapter_metadata: dict[str, "Any"]) -> str: | ||
|
|
@@ -627,7 +701,7 @@ def validate(adapter_metadata: dict[str, "Any"]) -> dict[str, "Any"]: | |
| if enable_extended_context: | ||
| validated["extra_headers"] = {"anthropic-beta": "context-1m-2025-08-07"} | ||
|
|
||
| return validated | ||
| return _strip_deprecated_sampling_params(validated) | ||
|
|
||
| @staticmethod | ||
| def validate_model(adapter_metadata: dict[str, "Any"]) -> str: | ||
|
|
@@ -837,7 +911,11 @@ def validate(adapter_metadata: dict[str, "Any"]) -> dict[str, "Any"]: | |
| adapter_metadata | ||
| ) | ||
|
|
||
| return AzureAIFoundryLLMParameters(**adapter_metadata).model_dump() | ||
| validated = AzureAIFoundryLLMParameters(**adapter_metadata).model_dump() | ||
| # Azure AI Foundry proxies Anthropic models that reject sampling params | ||
| # (e.g. Claude Opus 4.7); detection relies on the deployment name | ||
| # embedding the model id. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LOW — inline comment is redundant and understates the failure mode The helper docstring already documents the Foundry deployment-name caveat, so the three lines here mostly restate it. If the comment stays, strengthen the second half: “detection relies on the deployment name embedding the Anthropic model id; opaque deployment names skip the strip and will surface as a 400 from the provider.” Otherwise delete — the helper call reads self-explanatorily. |
||
| return _strip_deprecated_sampling_params(validated) | ||
|
|
||
| @staticmethod | ||
| def validate_model(adapter_metadata: dict[str, "Any"]) -> str: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LOW — comment drift: “substring-matched” no longer accurate
Lines 20 and 58 still describe matching as “substring-matched against the model id”, but the implementation now uses
re.compile(...)+.search()with a trailing-edge lookahead. A pure substring match would (correctly) flagclaude-opus-4-70; the current regex does not. Reword as e.g. “regex-searched against the normalized id, with a trailing-edge boundary so thatclaude-opus-4-70does not match.” Keeps the comment honest after the anchor change in7fb66f15.