fix(cloudflare): flatten content-part message arrays to a string

Cloudflare Workers AI rejects message content that is an array, so requests using OpenAI content parts failed with a type mismatch. Flatten them in _transform_messages, the way the other string-only providers do.
This commit is contained in:
Vineeth Sai 2026-10-26 15:08:27 -04:00
parent 07a82a287b
commit d6d584f2d7
2 changed files with 98 additions and 1 deletions

View file

@ -1,8 +1,12 @@
from typing import Final
from collections.abc import Coroutine
from typing import Final, Literal, overload
import httpx
from litellm._logging import verbose_logger
from litellm.litellm_core_utils.prompt_templates.common_utils import (
handle_messages_with_content_list_to_str_conversion,
)
from litellm.litellm_core_utils.url_utils import join_api_base
from litellm.llms.base_llm.chat.transformation import BaseLLMException
from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig
@ -87,6 +91,33 @@ class CloudflareChatConfig(OpenAIGPTConfig):
api_base=api_base,
)
@overload
def _transform_messages(
self,
messages: list[AllMessageValues], # mutable-ok: base signature
model: str,
is_async: Literal[True],
) -> Coroutine[object, object, list[AllMessageValues]]: ... # mutable-ok: base signature
@overload
def _transform_messages(
self,
messages: list[AllMessageValues], # mutable-ok: base signature
model: str,
is_async: Literal[False] = False,
) -> list[AllMessageValues]: ... # mutable-ok: base signature
def _transform_messages(
self,
messages: list[AllMessageValues], # mutable-ok: base signature
model: str,
is_async: bool = False,
) -> list[AllMessageValues] | Coroutine[object, object, list[AllMessageValues]]: # mutable-ok: base signature
flattened: Final = handle_messages_with_content_list_to_str_conversion(messages)
if is_async:
return super()._transform_messages(messages=flattened, model=model, is_async=True)
return super()._transform_messages(messages=flattened, model=model, is_async=False)
def get_error_class(self, error_message: str, status_code: int, headers: dict | httpx.Headers) -> BaseLLMException:
return CloudflareError(
status_code=status_code,

View file

@ -205,6 +205,72 @@ def test_validate_environment_sets_bearer_and_content_type():
assert headers["Content-Type"] == "application/json"
def test_transform_request_flattens_content_part_arrays_to_string():
config = CloudflareChatConfig()
messages = [
{
"role": "user",
"content": [
{"type": "text", "text": "Hello"},
{"type": "text", "text": "More text"},
],
}
]
body = config.transform_request(
model="@cf/meta/llama-2-7b-chat-int8",
messages=messages,
optional_params={},
litellm_params={},
headers={},
)
assert body["messages"][0]["content"] == "HelloMore text"
def test_transform_request_leaves_string_content_untouched():
config = CloudflareChatConfig()
messages = [
{"role": "system", "content": "you are terse"},
{"role": "user", "content": "weather in nyc?"},
]
body = config.transform_request(
model="@cf/meta/llama-2-7b-chat-int8",
messages=messages,
optional_params={},
litellm_params={},
headers={},
)
assert [m["content"] for m in body["messages"]] == ["you are terse", "weather in nyc?"]
def test_async_transform_request_flattens_content_part_arrays_to_string():
config = CloudflareChatConfig()
messages = [
{
"role": "user",
"content": [
{"type": "text", "text": "Hello"},
{"type": "text", "text": "More text"},
],
}
]
body = asyncio.run(
config.async_transform_request(
model="@cf/meta/llama-2-7b-chat-int8",
messages=messages,
optional_params={},
litellm_params={},
headers={},
)
)
assert body["messages"][0]["content"] == "HelloMore text"
def _chat_response() -> dict[str, object]:
return {
"id": "chatcmpl-cf",