BerriAI/litellm · error · ValueError

Invalid completion response: no message found in choice

Error message

Invalid completion response: no message found in choice

What it means

Deeper validation in the same transform: the first choice exists and is a Choices (non-streaming) instance, but choice.message is falsy. Without a message there is no content to build Google GenAI parts from, so translation stops with ValueError.

Source

Thrown at litellm/google_genai/adapters/transformation.py:498

        """
        Transform litellm completion response to Google GenAI generate_content format

        Args:
            response: ModelResponse from litellm.completion

        Returns:
            Dict in Google GenAI generate_content response format
        """

        # Extract the main response content
        choice: Final = response.choices[0] if response.choices else None
        if not choice:
            raise ValueError("Invalid completion response: no choices found")

        # Handle different choice types (Choices vs StreamingChoices)
        if isinstance(choice, Choices):
            if not choice.message:
                raise ValueError("Invalid completion response: no message found in choice")
            parts = self._transform_openai_message_to_google_genai_parts(choice.message)
        else:
            # Fallback for generic choice objects
            message_content = getattr(choice, "message", {}).get("content", "") or getattr(choice, "delta", {}).get(
                "content", ""
            )
            parts = [{"text": message_content}] if message_content else []

        # Create Google GenAI format response
        generate_content_response: Final[dict[str, object]] = {
            "candidates": [
                {
                    "content": {"parts": parts, "role": "model"},
                    "finishReason": self._map_finish_reason(getattr(choice, "finish_reason", None)),
                    "index": 0,
                    "safetyRatings": [],
                }
            ],

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Inspect the raw choice: print(choice) before transform to see whether message is empty and why
  2. If you control construction (tests/mocks), always populate Choices(message=Messages(content=...))
  3. For tool-call flows, translate tool_calls to Google functionCall parts before calling the adapter, or use litellm.completion directly
  4. Update litellm if a newer version maps tool-call-only choices into parts

Example fix

# before (test mock)
resp = ModelResponse(choices=[Choices()])  # message missing -> ValueError

# after
from litellm.types.utils import Choices, Message
resp = ModelResponse(choices=[Choices(message=Message(content='ok'))])
Defensive patterns

Strategy: validation

Validate before calling

choice = base.choices[0] if base.choices else None
if choice is not None and not getattr(choice, "message", None):
    raise RuntimeError("choice has no message (tool-call-only?); translate parts first")

Type guard

def choice_has_message(choice) -> bool:
    return bool(getattr(choice, "message", None))

Try / catch

try:
    r = generate_content(model=m, contents=c)
except ValueError as e:
    if "no message found" in str(e):
        handle_tool_call_only_response(base)

Prevention

When it happens

Trigger: A completion response whose choice carries only tool_calls or an empty message object with no content and no role data the parts-transformer can use; or custom response classes from hooks/mocks where message was never populated.

Common situations: Tool-call-only responses flowing through the generate_content adapter; response-scrubbing hooks; fake/mock ModelResponse objects built in tests without a message.

Related errors


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/e3464b5af79e634f. Report an issue: GitHub.