From f6f3c3938f59431f099714e4ffb36d61ef115930 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 15:07:38 +0000 Subject: [PATCH] fix(anthropic): map stop_sequences to stop in /v1/messages adapter --- .../adapters/transformation.py | 21 ++++++++++++ ...al_pass_through_adapters_transformation.py | 34 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py b/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py index 4c981dd36b3..d2a47670be4 100644 --- a/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py +++ b/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py @@ -324,6 +324,7 @@ class LiteLLMAnthropicMessagesAdapter: return [ "messages", "metadata", + "stop_sequences", "system", "tool_choice", "tools", @@ -903,6 +904,21 @@ class LiteLLMAnthropicMessagesAdapter: # metadata will be passed to litellm.acompletion(), it's a litellm_param new_kwargs["metadata"] = anthropic_message_request.pop("litellm_metadata") + def _translate_stop_sequences_to_openai( + self, + anthropic_message_request: AnthropicMessagesRequest, + new_kwargs: ChatCompletionRequest, + ) -> None: + """Translate Anthropic ``stop_sequences`` to OpenAI ``stop``. + + Anthropic-native backends map OpenAI ``stop`` back to ``stop_sequences``, + while OpenAI-format providers reject the ``stop_sequences`` field, so the + adapter must normalize to ``stop`` rather than passing it through verbatim. + """ + stop_sequences = anthropic_message_request.get("stop_sequences") + if stop_sequences: + new_kwargs["stop"] = stop_sequences + def _translate_tool_choice_to_openai( self, anthropic_message_request: AnthropicMessagesRequest, @@ -1082,6 +1098,11 @@ class LiteLLMAnthropicMessagesAdapter: anthropic_message_request=anthropic_message_request, new_kwargs=new_kwargs, ) + ## CONVERT STOP SEQUENCES + self._translate_stop_sequences_to_openai( + anthropic_message_request=anthropic_message_request, + new_kwargs=new_kwargs, + ) ## CONVERT TOOL CHOICE self._translate_tool_choice_to_openai( anthropic_message_request=anthropic_message_request, diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py index f9c55db72b5..6d0aca17f65 100644 --- a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py +++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py @@ -3111,3 +3111,37 @@ def test_translate_anthropic_tools_to_openai_preserves_parameters_type(): params = new_tools[0]["function"]["parameters"] assert params["type"] == "object" assert new_tools[0]["type"] == "function" + + +def test_translate_anthropic_to_openai_maps_stop_sequences_to_stop(): + """Regression for #33075: Anthropic `stop_sequences` must be translated to + the OpenAI `stop` param, otherwise OpenAI-format providers 400 on the + unknown `stop_sequences` field.""" + adapter = LiteLLMAnthropicMessagesAdapter() + request = { + "model": "azure_ai/gpt-4o", + "max_tokens": 100, + "messages": [{"role": "user", "content": "hi"}], + "stop_sequences": ["\n\nHuman:", "END"], + } + + result, _ = adapter.translate_anthropic_to_openai(anthropic_message_request=cast(Any, request)) + + assert result["stop"] == ["\n\nHuman:", "END"] + assert "stop_sequences" not in result + + +def test_translate_anthropic_to_openai_omits_stop_when_no_stop_sequences(): + """No `stop` key should be added when the Anthropic request has no + `stop_sequences`.""" + adapter = LiteLLMAnthropicMessagesAdapter() + request = { + "model": "azure_ai/gpt-4o", + "max_tokens": 100, + "messages": [{"role": "user", "content": "hi"}], + } + + result, _ = adapter.translate_anthropic_to_openai(anthropic_message_request=cast(Any, request)) + + assert "stop" not in result + assert "stop_sequences" not in result