[Bugfix][Frontend] Emit a content block for empty Anthropic completions (#46525)
Signed-off-by: EazyReal <8047065+EazyReal@users.noreply.github.com>
Maxwill Lin committed
Jun 23, 2026 at 17:04 UTC
e48f2aa4cab84eaca9e8473552ae704835bbbf61
2 files changed
+45
tests/entrypoints/anthropic/test_anthropic_messages_conversion.py
+39
@@ -26,8 +26,11 @@ from vllm.entrypoints.anthropic.serving import (
26
_get_cached_tokens,
27
)
28
from vllm.entrypoints.openai.chat_completion.protocol import (
29
+ ChatCompletionResponse,
30
+ ChatCompletionResponseChoice,
31
ChatCompletionResponseStreamChoice,
32
ChatCompletionStreamResponse,
33
+ ChatMessage,
34
)
35
from vllm.entrypoints.openai.engine.protocol import (
36
DeltaFunctionCall,
@@ -1381,3 +1384,39 @@ class TestDetectMergeInlineSystem:
1384
def test_no_template_defaults_merge(self):
1385
"""No chat_template → conservative default: merge."""
1386
assert AnthropicServingMessages._detect_merge_inline_system(None) is True
1387
+
1388
+
1389
+# ======================================================================
1390
+# Full (non-streaming) response conversion: messages_full_converter
1391
+# ======================================================================
1392
+
1393
+
1394
+def _make_full_converter():
1395
+ obj = MagicMock(spec=AnthropicServingMessages)
1396
+ obj.messages_full_converter = (
1397
+ AnthropicServingMessages.messages_full_converter.__get__(obj)
1398
+ )
1399
+ return obj
1400
+
1401
+
1402
+class TestMessagesFullConverter:
1403
+ def test_empty_completion_emits_one_text_block(self):
1404
+ """An empty completion still yields exactly one (empty) text block."""
1405
+ generator = ChatCompletionResponse(
1406
+ id="chatcmpl-empty",
1407
+ model="test-model",
1408
+ choices=[
1409
+ ChatCompletionResponseChoice(
1410
+ index=0,
1411
+ message=ChatMessage(role="assistant", content=None),
1412
+ finish_reason="stop",
1413
+ )
1414
+ ],
1415
+ usage=UsageInfo(prompt_tokens=10, completion_tokens=0, total_tokens=10),
1416
+ )
1417
+
1418
+ result = _make_full_converter().messages_full_converter(generator)
1419
+
1420
+ assert len(result.content) == 1
1421
+ assert result.content[0].type == "text"
1422
+ assert result.content[0].text == ""
vllm/entrypoints/anthropic/serving.py
+6
@@ -665,6 +665,12 @@ class AnthropicServingMessages(OpenAIServingChat):
665
)
666
content += [anthropic_tool_call]
667
668
+ # Anthropic's canonical shape for an empty completion is a single
669
+ # empty text block, not []. Some strict clients assume content[0]
670
+ # exists, so emit one here.
671
+ if not content:
672
+ content.append(AnthropicContentBlock(type="text", text=""))
673
+
674
result.content = content
675
676
return result