mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-30 01:52:18 +00:00
fix(vertex_ai): forward the per-turn-control beta for per-message output_config (#43558)
Claude Code attaches output_config to mid-conversation system messages and sends the per-turn-control-2026-07-01 beta with it. The Vertex beta map dropped that beta, so Vertex rejected the body with 'messages.N.output_config: Extra inputs are not permitted'. Forward the beta for vertex_ai, the way azure_ai already does, and add it on the Vertex Messages path whenever a message carries output_config.
This commit is contained in:
parent
60fca8298e
commit
0fb93ed9de
4 changed files with 63 additions and 4 deletions
|
|
@ -194,7 +194,7 @@
|
|||
"mcp-servers-2025-12-04": null,
|
||||
"output-128k-2025-02-19": null,
|
||||
"structured-output-2024-03-01": null,
|
||||
"per-turn-control-2026-07-01": null,
|
||||
"per-turn-control-2026-07-01": "per-turn-control-2026-07-01",
|
||||
"prompt-caching-scope-2026-01-05": null,
|
||||
"skills-2025-10-02": null,
|
||||
"structured-outputs-2025-11-13": null,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from typing import Any, Final
|
|||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
from litellm.llms.anthropic.pass_through.messages.transformation import (
|
||||
AnthropicMessagesConfig,
|
||||
_messages_carry_output_config,
|
||||
)
|
||||
from litellm.types.llms.anthropic import (
|
||||
ANTHROPIC_BETA_HEADER_VALUES,
|
||||
|
|
@ -111,6 +112,9 @@ class VertexAIPartnerModelsAnthropicMessagesConfig(AnthropicMessagesConfig, Vert
|
|||
if optional_params.get("safeguards") is not None:
|
||||
beta_values.add(ANTHROPIC_BETA_HEADER_VALUES.DANGEROUS_TOOL_USE_2026_09_03.value)
|
||||
|
||||
if _messages_carry_output_config(messages):
|
||||
beta_values.add(ANTHROPIC_BETA_HEADER_VALUES.PER_TURN_CONTROL_2026_07_01.value)
|
||||
|
||||
if beta_values:
|
||||
headers["anthropic-beta"] = ",".join(beta_values)
|
||||
|
||||
|
|
|
|||
|
|
@ -95,15 +95,16 @@ def test_added_per_turn_control_beta_survives_the_anthropic_allowlist():
|
|||
assert PER_TURN_CONTROL in _betas(filtered)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("provider", ["bedrock", "bedrock_converse", "vertex_ai", "databricks"])
|
||||
@pytest.mark.parametrize("provider", ["bedrock", "bedrock_converse", "databricks"])
|
||||
def test_per_turn_control_beta_is_dropped_for_providers_without_it(provider):
|
||||
filtered = update_headers_with_filtered_beta(headers={"anthropic-beta": PER_TURN_CONTROL}, provider=provider)
|
||||
|
||||
assert "anthropic-beta" not in filtered
|
||||
|
||||
|
||||
def test_per_turn_control_beta_is_forwarded_for_azure_ai():
|
||||
filtered = update_headers_with_filtered_beta(headers={"anthropic-beta": PER_TURN_CONTROL}, provider="azure_ai")
|
||||
@pytest.mark.parametrize("provider", ["azure_ai", "vertex_ai"])
|
||||
def test_per_turn_control_beta_is_forwarded_for_providers_with_it(provider):
|
||||
filtered = update_headers_with_filtered_beta(headers={"anthropic-beta": PER_TURN_CONTROL}, provider=provider)
|
||||
|
||||
assert _betas(filtered) == {PER_TURN_CONTROL}
|
||||
|
||||
|
|
|
|||
|
|
@ -126,6 +126,60 @@ def test_no_safeguards_leaves_dangerous_tool_use_beta_header_out():
|
|||
assert "dangerous-tool-use-2026-09-03" not in updated_headers.get("anthropic-beta", "")
|
||||
|
||||
|
||||
def _validate_vertex_headers(client_headers, messages):
|
||||
config = VertexAIPartnerModelsAnthropicMessagesConfig()
|
||||
litellm_params = {
|
||||
"vertex_ai_project": "test-project",
|
||||
"vertex_ai_location": "global",
|
||||
"vertex_credentials": "{}",
|
||||
}
|
||||
|
||||
with (
|
||||
patch.object(config, "_ensure_access_token", return_value=("token", "test-project")),
|
||||
patch.object(config, "get_complete_vertex_url", return_value="https://mock-url"),
|
||||
):
|
||||
updated_headers, _ = config.validate_anthropic_messages_environment(
|
||||
headers=client_headers,
|
||||
model="claude-opus-5-5",
|
||||
messages=messages,
|
||||
optional_params={"max_tokens": 64},
|
||||
litellm_params=litellm_params,
|
||||
api_base=None,
|
||||
)
|
||||
return updated_headers
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"client_headers",
|
||||
[{"anthropic-beta": "per-turn-control-2026-07-01"}, {}],
|
||||
ids=["client_sends_beta", "client_omits_beta"],
|
||||
)
|
||||
def test_per_message_output_config_reaches_vertex_with_per_turn_control_beta(client_headers, monkeypatch):
|
||||
"""Vertex rejects a message-level `output_config` as an extra input unless the per-turn-control beta is present, so the beta must survive the Vertex beta filter."""
|
||||
from litellm import anthropic_beta_headers_manager
|
||||
from litellm.anthropic_beta_headers_manager import update_headers_with_filtered_beta
|
||||
|
||||
monkeypatch.setenv("LITELLM_LOCAL_ANTHROPIC_BETA_HEADERS", "True")
|
||||
monkeypatch.setattr(anthropic_beta_headers_manager, "_BETA_HEADERS_CONFIG", None)
|
||||
|
||||
messages = [
|
||||
{"role": "user", "content": [{"type": "text", "text": "Hello"}]},
|
||||
{"role": "system", "content": [{"type": "text", "text": "# Environment"}], "output_config": {"effort": "low"}},
|
||||
]
|
||||
|
||||
filtered = update_headers_with_filtered_beta(
|
||||
headers=_validate_vertex_headers(client_headers, messages), provider="vertex_ai"
|
||||
)
|
||||
|
||||
assert filtered["anthropic-beta"].split(",").count("per-turn-control-2026-07-01") == 1
|
||||
|
||||
|
||||
def test_no_per_message_output_config_leaves_per_turn_control_beta_out():
|
||||
headers = _validate_vertex_headers({}, [{"role": "user", "content": "Hello"}])
|
||||
|
||||
assert "per-turn-control-2026-07-01" not in headers.get("anthropic-beta", "")
|
||||
|
||||
|
||||
def test_web_search_header_not_added_without_tool():
|
||||
"""Test that beta header is NOT added when web search tool is not present"""
|
||||
config = VertexAIPartnerModelsAnthropicMessagesConfig()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue