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
- Inspect the raw choice: print(choice) before transform to see whether message is empty and why
- If you control construction (tests/mocks), always populate Choices(message=Messages(content=...))
- For tool-call flows, translate tool_calls to Google functionCall parts before calling the adapter, or use litellm.completion directly
- 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
- Always populate Messages in mocks/tests
- Convert tool-call-only responses before adapter transforms
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
- Invalid completion response: no choices found
- Failed to transform streaming response
- Error calling litellm.acompletion for generate_content: {e}
- Error calling litellm.completion for generate_content: {e}
- tool call not supported: {tool_call}
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/e3464b5af79e634f.
Report an issue: GitHub.