fix(anthropic): strip leaked think tags from messages

This commit is contained in:
Gennaro Malafronte 2026-04-23 00:16:00 +02:00
parent ced8b44e3c
commit 0712f8ba6c
3 changed files with 68 additions and 22 deletions

View file

@ -1621,6 +1621,9 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
if thinking_content is not None:
reasoning_content += thinking_content
if "</think>" in text_content:
text_content = text_content.split("</think>", 1)[1].lstrip()
return (
text_content,
citations,

View file

@ -1249,10 +1249,14 @@ class LiteLLMAnthropicMessagesAdapter:
)
# Handle text content
if choice.message.content is not None:
text_content = choice.message.content
if isinstance(text_content, str) and "</think>" in text_content:
text_content = text_content.split("</think>", 1)[1].lstrip()
if text_content:
new_content.append(
AnthropicResponseContentBlockText(
type="text", text=choice.message.content
type="text", text=text_content
).model_dump()
)
# Handle tool calls (in parallel to text content)

View file

@ -45,6 +45,41 @@ def _should_route_to_responses_api(custom_llm_provider: Optional[str]) -> bool:
return custom_llm_provider in _RESPONSES_API_PROVIDERS
def _sanitize_think_tag_text_blocks(
response: Union[AnthropicMessagesResponse, AsyncIterator]
) -> Union[AnthropicMessagesResponse, AsyncIterator]:
if not isinstance(response, dict):
return response
content = response.get("content")
if not isinstance(content, list):
return response
sanitized_content: List[Dict[str, Any]] = []
for block in content:
if not isinstance(block, dict):
sanitized_content.append(block)
continue
if block.get("type") != "text":
sanitized_content.append(block)
continue
text = block.get("text")
if not isinstance(text, str) or "</think>" not in text:
sanitized_content.append(block)
continue
cleaned_text = text.split("</think>", 1)[1].lstrip()
if cleaned_text:
sanitized_block = dict(block)
sanitized_block["text"] = cleaned_text
sanitized_content.append(sanitized_block)
response["content"] = sanitized_content
return response
####### ENVIRONMENT VARIABLES ###################
# Initialize any necessary instances or variables here
base_llm_http_handler = BaseLLMHTTPHandler()
@ -286,7 +321,7 @@ async def anthropic_messages(
response = await init_response
else:
response = init_response
return response
return _sanitize_think_tag_text_blocks(response)
def validate_anthropic_api_metadata(metadata: Optional[Dict] = None) -> Optional[Dict]:
@ -421,10 +456,12 @@ def anthropic_messages_handler(
**kwargs,
)
if _should_route_to_responses_api(custom_llm_provider):
return LiteLLMMessagesToResponsesAPIHandler.anthropic_messages_handler(
**_shared_kwargs
return _sanitize_think_tag_text_blocks(
LiteLLMMessagesToResponsesAPIHandler.anthropic_messages_handler(
**_shared_kwargs
)
)
return (
return _sanitize_think_tag_text_blocks(
LiteLLMMessagesToCompletionTransformationHandler.anthropic_messages_handler(
**_shared_kwargs
)
@ -441,20 +478,22 @@ def anthropic_messages_handler(
params=local_vars
)
)
return base_llm_http_handler.anthropic_messages_handler(
model=model,
messages=messages,
anthropic_messages_provider_config=anthropic_messages_provider_config,
anthropic_messages_optional_request_params=dict(
anthropic_messages_optional_request_params
),
_is_async=is_async,
client=client,
custom_llm_provider=custom_llm_provider,
litellm_params=litellm_params,
logging_obj=litellm_logging_obj,
api_key=api_key,
api_base=api_base,
stream=stream,
kwargs=kwargs,
return _sanitize_think_tag_text_blocks(
base_llm_http_handler.anthropic_messages_handler(
model=model,
messages=messages,
anthropic_messages_provider_config=anthropic_messages_provider_config,
anthropic_messages_optional_request_params=dict(
anthropic_messages_optional_request_params
),
_is_async=is_async,
client=client,
custom_llm_provider=custom_llm_provider,
litellm_params=litellm_params,
logging_obj=litellm_logging_obj,
api_key=api_key,
api_base=api_base,
stream=stream,
kwargs=kwargs,
)
)