mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-29 06:33:28 +00:00
fix(azure_ai): strip provider_specific_fields recursively in tool_calls
The Anthropic /v1/messages adapter attaches the thought signature at tool_calls[].function.provider_specific_fields (adapters/transformation.py), not just at the top level of the assistant message. The previous commit popped provider_specific_fields at the top level only, so the nested copy inside tool_calls survived serialization and strict upstreams (Fireworks-on-Azure) still rejected it with Extra inputs are not permitted, field: 'messages[n].tool_calls[m].function.provider_specific_fields'. Because Claude Code does multi-turn tool use, this is the next failure a user hits after thinking_blocks is fixed. Split the stripped fields into two sets: top-level output-only fields (thinking_blocks, reasoning_content) removed with a plain pop, and fields that can nest (cache_control, provider_specific_fields) removed recursively via filter_value_from_dict so nested tool-call signatures are caught too. reasoning_content is accepted on input by the Fireworks backend but is dropped here as a defensive default for a generic multi-backend provider; the direct fireworks_ai provider strips thinking_blocks and the mistral provider (MistralConfig) strips both, and this applies the union since Azure AI Foundry fronts multiple backends with different strictness. Added a regression test for the nested tool_calls[].function.provider_specific_fields case; it fails against a top-level-only pop and passes only with the recursive strip.
This commit is contained in:
parent
ec4c331684
commit
bb46cc678c
2 changed files with 95 additions and 11 deletions
|
|
@ -29,9 +29,28 @@ class AzureFoundryErrorStrings(str, enum.Enum):
|
|||
SET_EXTRA_PARAMETERS_TO_PASS_THROUGH = "Set extra-parameters to 'pass-through'"
|
||||
|
||||
|
||||
_OUTPUT_ONLY_MESSAGE_FIELDS: Tuple[str, ...] = (
|
||||
# Fields that only ever appear at the top level of a chat message and are
|
||||
# removed with a plain ``pop``. ``thinking_blocks`` and ``reasoning_content``
|
||||
# are output-only reasoning fields; the Fireworks backend accepts
|
||||
# ``reasoning_content`` on input, but the direct ``fireworks_ai`` provider
|
||||
# strips ``thinking_blocks`` and Azure AI Foundry fronts multiple backends, so
|
||||
# drop both as a defensive default for a generic multi-backend provider.
|
||||
_TOP_LEVEL_OUTPUT_ONLY_FIELDS: Tuple[str, ...] = (
|
||||
"thinking_blocks",
|
||||
"reasoning_content",
|
||||
)
|
||||
|
||||
# Fields that can nest inside a message (e.g. inside ``tool_calls[].function``
|
||||
# or content blocks) and must be removed recursively. ``cache_control`` is an
|
||||
# Anthropic prompt-caching annotation; ``provider_specific_fields`` carries
|
||||
# Anthropic thought signatures and is attached by the Anthropic
|
||||
# ``/v1/messages`` adapter at ``tool_calls[].function.provider_specific_fields``
|
||||
# (``adapters/transformation.py``), so a top-level ``pop`` misses it and
|
||||
# strict upstreams still reject it with
|
||||
# ``Extra inputs are not permitted, field:
|
||||
# 'messages[n].tool_calls[m].function.provider_specific_fields'``.
|
||||
_RECURSIVELY_STRIPPED_FIELDS: Tuple[str, ...] = (
|
||||
"cache_control",
|
||||
"provider_specific_fields",
|
||||
)
|
||||
|
||||
|
|
@ -46,14 +65,15 @@ def _strip_unsupported_message_fields(message: AllMessageValues) -> None:
|
|||
``/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.
|
||||
'messages[n].<field>'``. Top-level output-only fields are popped; fields
|
||||
that can nest (``cache_control``, and ``provider_specific_fields`` which
|
||||
the adapter attaches inside ``tool_calls[].function``) are stripped
|
||||
recursively so nested tool-call signatures are removed too.
|
||||
"""
|
||||
filter_value_from_dict(cast(dict, message), "cache_control")
|
||||
m = cast(dict, message)
|
||||
for field in _OUTPUT_ONLY_MESSAGE_FIELDS:
|
||||
for field in _RECURSIVELY_STRIPPED_FIELDS:
|
||||
filter_value_from_dict(m, field)
|
||||
for field in _TOP_LEVEL_OUTPUT_ONLY_FIELDS:
|
||||
m.pop(field, None)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -309,6 +309,11 @@ def test_transform_request_drops_thinking_blocks_for_fireworks_model():
|
|||
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'``.
|
||||
Also covers the nested ``tool_calls[].function.provider_specific_fields``
|
||||
signature the Anthropic ``/v1/messages`` adapter attaches on tool-use
|
||||
replays, which otherwise 400s with
|
||||
``Extra inputs are not permitted, field:
|
||||
'messages[n].tool_calls[m].function.provider_specific_fields'``.
|
||||
"""
|
||||
config = AzureAIStudioConfig()
|
||||
messages = [
|
||||
|
|
@ -324,6 +329,17 @@ def test_transform_request_drops_thinking_blocks_for_fireworks_model():
|
|||
"cache_control": {},
|
||||
}
|
||||
],
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "toolu_1",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "read_file",
|
||||
"arguments": '{"path": "foo.txt"}',
|
||||
"provider_specific_fields": {"thought_signature": "sig"},
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
{"role": "user", "content": "go ahead"},
|
||||
]
|
||||
|
|
@ -336,8 +352,56 @@ def test_transform_request_drops_thinking_blocks_for_fireworks_model():
|
|||
headers={},
|
||||
)
|
||||
|
||||
def _assert_no_unsupported_fields(node):
|
||||
if isinstance(node, dict):
|
||||
assert "thinking_blocks" not in node
|
||||
assert "reasoning_content" not in node
|
||||
assert "provider_specific_fields" not in node
|
||||
assert "cache_control" not in node
|
||||
for value in node.values():
|
||||
_assert_no_unsupported_fields(value)
|
||||
elif isinstance(node, list):
|
||||
for item in node:
|
||||
_assert_no_unsupported_fields(item)
|
||||
|
||||
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
|
||||
_assert_no_unsupported_fields(message)
|
||||
|
||||
|
||||
def test_transform_messages_strips_nested_tool_call_provider_specific_fields():
|
||||
"""Regression: the Anthropic ``/v1/messages`` adapter attaches the thought
|
||||
signature at ``tool_calls[].function.provider_specific_fields``
|
||||
(``adapters/transformation.py``). A top-level ``pop`` of
|
||||
``provider_specific_fields`` from the message misses the nested copy, so
|
||||
strict upstreams (Fireworks-on-Azure) still 400 with
|
||||
``Extra inputs are not permitted, field:
|
||||
'messages[1].tool_calls[0].function.provider_specific_fields'``.
|
||||
``_transform_messages`` must strip it recursively.
|
||||
"""
|
||||
config = AzureAIStudioConfig()
|
||||
messages = [
|
||||
{"role": "user", "content": "Run the tool."},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Calling the tool now.",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "toolu_1",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "read_file",
|
||||
"arguments": '{"path": "foo.txt"}',
|
||||
"provider_specific_fields": {"thought_signature": "sig"},
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
{"role": "user", "content": "show me the result"},
|
||||
]
|
||||
|
||||
out = config._transform_messages(messages, model="glm-5.2")
|
||||
|
||||
tool_call = out[1]["tool_calls"][0]
|
||||
assert "provider_specific_fields" not in tool_call
|
||||
assert "provider_specific_fields" not in tool_call["function"]
|
||||
assert tool_call["function"]["name"] == "read_file"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue