mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-29 06:33:28 +00:00
fix(azure_ai): strip thinking_blocks and other output-only fields from chat messages
AzureAIStudioConfig._transform_messages only converted content lists to strings and passed image/audio blocks through. It did not drop the output-only / Anthropic-only fields that LiteLLM's Anthropic /v1/messages adapter attaches to the OpenAI-format assistant messages it builds when replaying multi-turn history from an Anthropic-format client such as Claude Code. Those fields (thinking_blocks, reasoning_content, provider_specific_fields, and the Anthropic cache_control annotation) are not part of the OpenAI chat-completions message schema, so they reached Azure AI Foundry-hosted backends whose chat-message schema sets additionalProperties: false (for example Fireworks GLM) and triggered 400 Extra inputs are not permitted, field: 'messages[n].thinking_blocks'. Strip those fields from each message before the request is serialized, mirroring what the direct fireworks_ai provider (FireworksAIConfig._transform_messages_helper) and the mistral provider (MistralChatConfig._strip_output_only_fields) already do. The strip reuses the existing filter_value_from_dict helper for cache_control and pops the flat output-only fields directly. Regression tests cover both _transform_messages and the full transform_request path that the reported end-to-end scenario hits.
This commit is contained in:
parent
bd44c9e305
commit
ec4c331684
2 changed files with 109 additions and 0 deletions
|
|
@ -11,6 +11,7 @@ from litellm._logging import verbose_logger
|
|||
from litellm.litellm_core_utils.prompt_templates.common_utils import (
|
||||
_audio_or_image_in_message_content,
|
||||
convert_content_list_to_str,
|
||||
filter_value_from_dict,
|
||||
)
|
||||
from litellm.llms.azure.common_utils import BaseAzureLLM
|
||||
from litellm.llms.base_llm.chat.transformation import LiteLLMLoggingObj
|
||||
|
|
@ -28,6 +29,34 @@ class AzureFoundryErrorStrings(str, enum.Enum):
|
|||
SET_EXTRA_PARAMETERS_TO_PASS_THROUGH = "Set extra-parameters to 'pass-through'"
|
||||
|
||||
|
||||
_OUTPUT_ONLY_MESSAGE_FIELDS: Tuple[str, ...] = (
|
||||
"thinking_blocks",
|
||||
"reasoning_content",
|
||||
"provider_specific_fields",
|
||||
)
|
||||
|
||||
|
||||
def _strip_unsupported_message_fields(message: AllMessageValues) -> None:
|
||||
"""Remove input-forbidden fields from a chat message before it is sent to
|
||||
an Azure AI Foundry hosted model.
|
||||
|
||||
Azure AI Foundry fronts many OpenAI-compatible backends (Fireworks,
|
||||
Mistral, xAI, ...) whose chat-message schemas set ``additionalProperties:
|
||||
false``. LiteLLM-internal / output-only fields that the Anthropic
|
||||
``/v1/messages`` adapter attaches when replaying assistant turns from an
|
||||
Anthropic-format client (e.g. Claude Code) are not part of that schema and
|
||||
trigger ``400 Extra inputs are not permitted, field:
|
||||
'messages[n].<field>'``. ``cache_control`` is an Anthropic prompt-caching
|
||||
annotation with the same problem. ``thinking_blocks`` and
|
||||
``reasoning_content`` are output-only; ``provider_specific_fields`` carries
|
||||
Anthropic thought signatures. None are valid OpenAI input, so drop them.
|
||||
"""
|
||||
filter_value_from_dict(cast(dict, message), "cache_control")
|
||||
m = cast(dict, message)
|
||||
for field in _OUTPUT_ONLY_MESSAGE_FIELDS:
|
||||
m.pop(field, None)
|
||||
|
||||
|
||||
class AzureAIStudioConfig(OpenAIConfig):
|
||||
def get_supported_openai_params(self, model: str) -> List:
|
||||
model_supports_tool_choice = True # azure ai supports this by default
|
||||
|
|
@ -171,6 +200,7 @@ class AzureAIStudioConfig(OpenAIConfig):
|
|||
2. If message contains an image or audio, send as is (user-intended)
|
||||
"""
|
||||
for message in messages:
|
||||
_strip_unsupported_message_fields(message)
|
||||
# Do nothing if the message contains an image or audio
|
||||
if _audio_or_image_in_message_content(message):
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -262,3 +262,82 @@ def test_drop_tool_level_extra_fields_strips_copilot_mcp_server_name():
|
|||
assert "copilot_mcp_server_name" not in tool
|
||||
assert result["tools"][0]["type"] == "function"
|
||||
assert result["tools"][1]["function"]["name"] == "read_file"
|
||||
|
||||
|
||||
def test_transform_messages_strips_anthropic_replay_fields():
|
||||
"""Regression: Anthropic /v1/messages adapter attaches ``thinking_blocks``
|
||||
(and ``provider_specific_fields`` / ``cache_control``) to the OpenAI-format
|
||||
assistant messages it builds when replaying Claude Code turns. Azure AI
|
||||
Foundry hosted model backends with strict chat-message schemas (e.g.
|
||||
Fireworks GLM) reject them with
|
||||
``Extra inputs are not permitted, field: 'messages[n].thinking_blocks'``.
|
||||
``_transform_messages`` must drop them before the request is sent.
|
||||
"""
|
||||
config = AzureAIStudioConfig()
|
||||
messages = [
|
||||
{"role": "user", "content": "Read a file."},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "I can help.",
|
||||
"thinking_blocks": [
|
||||
{
|
||||
"type": "thinking",
|
||||
"thinking": "The user wants me to read a file.",
|
||||
"signature": "",
|
||||
"cache_control": {},
|
||||
}
|
||||
],
|
||||
"reasoning_content": "internal reasoning",
|
||||
"provider_specific_fields": {"thought_signature": "sig"},
|
||||
"cache_control": {"type": "ephemeral"},
|
||||
},
|
||||
{"role": "user", "content": "go ahead"},
|
||||
]
|
||||
|
||||
out = config._transform_messages(messages, model="glm-5.2")
|
||||
|
||||
assert "thinking_blocks" not in out[1]
|
||||
assert "reasoning_content" not in out[1]
|
||||
assert "provider_specific_fields" not in out[1]
|
||||
assert "cache_control" not in out[1]
|
||||
assert out[1]["content"] == "I can help."
|
||||
|
||||
|
||||
def test_transform_request_drops_thinking_blocks_for_fireworks_model():
|
||||
"""End-to-end of the reported scenario: a Fireworks model deployed on Azure
|
||||
AI Foundry receives a replayed assistant turn carrying ``thinking_blocks``.
|
||||
The outgoing request payload built by ``transform_request`` must not carry
|
||||
it, or the Fireworks backend 400s with
|
||||
``Extra inputs are not permitted, field: 'messages[2].thinking_blocks'``.
|
||||
"""
|
||||
config = AzureAIStudioConfig()
|
||||
messages = [
|
||||
{"role": "user", "content": "Read a file."},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "I can help.",
|
||||
"thinking_blocks": [
|
||||
{
|
||||
"type": "thinking",
|
||||
"thinking": "The user wants me to read a file.",
|
||||
"signature": "",
|
||||
"cache_control": {},
|
||||
}
|
||||
],
|
||||
},
|
||||
{"role": "user", "content": "go ahead"},
|
||||
]
|
||||
|
||||
payload = config.transform_request(
|
||||
model="glm-5.2",
|
||||
messages=messages,
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
for message in payload["messages"]:
|
||||
assert "thinking_blocks" not in message
|
||||
assert "reasoning_content" not in message
|
||||
assert "provider_specific_fields" not in message
|
||||
assert "cache_control" not in message
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue