Fix missing content in Anthropic to OpenAI conversion (#17693)

This commit is contained in:
Yuge Zhang
2025-12-12 07:31:47 +08:00
committed by GitHub
parent a13ee39da2
commit 1aed37b8ea
2 changed files with 86 additions and 8 deletions
@@ -613,7 +613,14 @@ class LiteLLMAnthropicMessagesAdapter:
)
)
# Handle tool calls
# Handle text content
if choice.message.content is not None:
new_content.append(
AnthropicResponseContentBlockText(
type="text", text=choice.message.content
)
)
# Handle tool calls (in parallel to text content)
if (
choice.message.tool_calls is not None
and len(choice.message.tool_calls) > 0
@@ -642,13 +649,6 @@ class LiteLLMAnthropicMessagesAdapter:
provider_specific_fields
)
new_content.append(tool_use_block)
# Handle text content
elif choice.message.content is not None:
new_content.append(
AnthropicResponseContentBlockText(
type="text", text=choice.message.content
)
)
return new_content
@@ -1,5 +1,6 @@
import os
import sys
from typing import Any, cast
import pytest
@@ -20,7 +21,9 @@ from litellm.types.utils import (
Delta,
Function,
Message,
ModelResponse,
StreamingChoices,
Usage,
)
@@ -341,6 +344,81 @@ def test_translate_openai_content_to_anthropic_empty_function_arguments():
assert result[0].input == {}, "Empty function arguments should result in empty dict"
def test_translate_openai_content_to_anthropic_text_and_tool_calls():
"""Ensure content blocks contain both the assistant text + tool call data."""
openai_choices = [
Choices(
message=Message(
role="assistant",
content="Calling get_weather now.",
tool_calls=[
ChatCompletionAssistantToolCall(
id="call_weather",
type="function",
function=Function(
name="get_weather",
arguments='{"location": "Boston"}',
),
)
],
)
)
]
adapter = LiteLLMAnthropicMessagesAdapter()
result = adapter._translate_openai_content_to_anthropic(choices=openai_choices)
assert len(result) == 2
assert result[0].type == "text"
assert result[0].text == "Calling get_weather now."
assert result[1].type == "tool_use"
assert result[1].id == "call_weather"
assert result[1].name == "get_weather"
assert result[1].input == {"location": "Boston"}
def test_translate_openai_response_to_anthropic_text_and_tool_calls():
"""`translate_openai_response_to_anthropic` should surface assistant text even when tools fire."""
openai_response = ModelResponse(
id="resp_text_tool",
model="gpt-4o-mini",
choices=[
Choices(
finish_reason="tool_calls",
message=Message(
role="assistant",
content="Let me grab the current weather.",
tool_calls=[
ChatCompletionAssistantToolCall(
id="call_tool_combo",
type="function",
function=Function(
name="get_weather", arguments='{"location": "Paris"}'
),
)
],
),
)
],
usage=Usage(prompt_tokens=5, completion_tokens=2),
)
adapter = LiteLLMAnthropicMessagesAdapter()
anthropic_response = adapter.translate_openai_response_to_anthropic(
response=openai_response
)
anthropic_content = anthropic_response.get("content")
assert anthropic_content is not None
assert len(anthropic_content) == 2
assert cast(Any, anthropic_content[0]).type == "text"
assert cast(Any, anthropic_content[0]).text == "Let me grab the current weather."
assert cast(Any, anthropic_content[1]).type == "tool_use"
assert cast(Any, anthropic_content[1]).id == "call_tool_combo"
assert cast(Any, anthropic_content[1]).input == {"location": "Paris"}
assert anthropic_response.get("stop_reason") == "tool_use"
def test_translate_streaming_openai_chunk_to_anthropic_with_partial_json():
"""Test that partial tool arguments are correctly handled as input_json_delta."""
choices = [