From 5774c845d89b66b37e00814ffdf8dc6723edab87 Mon Sep 17 00:00:00 2001 From: "sherif.waly" Date: Thu, 19 Feb 2026 12:25:18 +0000 Subject: [PATCH] Convert thinking_blocks to content blocks for hosted_vllm multi-turn For multi-turn conversations, convert thinking_blocks on assistant messages into content blocks prepended before the rest of the content, so reasoning context is passed back to the hosted_vllm API. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../llms/hosted_vllm/chat/transformation.py | 23 ++++- .../test_hosted_vllm_chat_transformation.py | 92 +++++++++++++++++++ 2 files changed, 113 insertions(+), 2 deletions(-) diff --git a/litellm/llms/hosted_vllm/chat/transformation.py b/litellm/llms/hosted_vllm/chat/transformation.py index e955800b94..35dfa8a385 100644 --- a/litellm/llms/hosted_vllm/chat/transformation.py +++ b/litellm/llms/hosted_vllm/chat/transformation.py @@ -137,10 +137,29 @@ class HostedVLLMChatConfig(OpenAIGPTConfig): self, messages: List[AllMessageValues], model: str, is_async: bool = False ) -> Union[List[AllMessageValues], Coroutine[Any, Any, List[AllMessageValues]]]: """ - Support translating video files from file_id or file_data to video_url + Support translating: + - video files from file_id or file_data to video_url + - thinking_blocks on assistant messages to content blocks """ for message in messages: - if message["role"] == "user": + if message["role"] == "assistant": + thinking_blocks = message.pop("thinking_blocks", None) # type: ignore + if thinking_blocks: + new_content: list = [ + {"type": block["type"], "thinking": block.get("thinking", "")} + if block.get("type") == "thinking" + else {"type": block["type"], "data": block.get("data", "")} + for block in thinking_blocks + ] + existing_content = message.get("content") + if isinstance(existing_content, str): + new_content.append( + {"type": "text", "text": existing_content} + ) + elif isinstance(existing_content, list): + new_content.extend(existing_content) + message["content"] = new_content # type: ignore + elif message["role"] == "user": message_content = message.get("content") if message_content and isinstance(message_content, list): replaced_content_items: List[ diff --git a/tests/test_litellm/llms/hosted_vllm/chat/test_hosted_vllm_chat_transformation.py b/tests/test_litellm/llms/hosted_vllm/chat/test_hosted_vllm_chat_transformation.py index ddc01db0ec..4dc7c7a405 100644 --- a/tests/test_litellm/llms/hosted_vllm/chat/test_hosted_vllm_chat_transformation.py +++ b/tests/test_litellm/llms/hosted_vllm/chat/test_hosted_vllm_chat_transformation.py @@ -165,3 +165,95 @@ def test_hosted_vllm_supports_thinking(): drop_params=False, ) assert optional_params["reasoning_effort"] == "low" + + +def test_hosted_vllm_thinking_blocks_prepended_to_assistant_content(): + """ + Test that thinking_blocks on assistant messages are converted to content + blocks prepended before the existing content. + """ + config = HostedVLLMChatConfig() + messages = [ + { + "role": "user", + "content": "Hello", + }, + { + "role": "assistant", + "content": "Here is my answer.", + "thinking_blocks": [ + { + "type": "thinking", + "thinking": "Let me reason about this...", + "signature": "abc123", + } + ], + }, + { + "role": "user", + "content": "Follow up question", + }, + ] + transformed = config.transform_request( + model="hosted_vllm/llama-3.1-70b-instruct", + messages=messages, + optional_params={}, + litellm_params={}, + headers={}, + ) + assistant_msg = transformed["messages"][1] + assert assistant_msg["role"] == "assistant" + assert isinstance(assistant_msg["content"], list) + assert assistant_msg["content"][0] == { + "type": "thinking", + "thinking": "Let me reason about this...", + } + assert assistant_msg["content"][1] == { + "type": "text", + "text": "Here is my answer.", + } + assert "thinking_blocks" not in assistant_msg + + +def test_hosted_vllm_thinking_blocks_with_list_content(): + """ + Test thinking_blocks prepended when assistant content is already a list. + """ + config = HostedVLLMChatConfig() + messages = [ + { + "role": "assistant", + "content": [{"type": "text", "text": "Response text"}], + "thinking_blocks": [ + { + "type": "thinking", + "thinking": "Step 1 reasoning", + "signature": "sig1", + }, + { + "type": "thinking", + "thinking": "Step 2 reasoning", + "signature": "sig2", + }, + ], + }, + ] + transformed = config.transform_request( + model="hosted_vllm/llama-3.1-70b-instruct", + messages=messages, + optional_params={}, + litellm_params={}, + headers={}, + ) + assistant_msg = transformed["messages"][0] + assert len(assistant_msg["content"]) == 3 + assert assistant_msg["content"][0] == { + "type": "thinking", + "thinking": "Step 1 reasoning", + } + assert assistant_msg["content"][1] == { + "type": "thinking", + "thinking": "Step 2 reasoning", + } + assert assistant_msg["content"][2] == {"type": "text", "text": "Response text"} + assert "thinking_blocks" not in assistant_msg