BerriAI/litellm · error · ValueError

Failed to parse operation response

Error message

Failed to parse operation response: {e}

What it means

Thrown by transform_video_create_response after litellm.video_generation() returns from the Gemini (Veo) endpoint. The raw JSON body failed Pydantic validation against GeminiLongRunningOperationResponse, meaning Google returned a body whose fields are missing, unexpected, or of the wrong type (e.g. an error payload instead of an operation object). The original Pydantic message is embedded in '{e}', so it names the exact offending field.

Solutions

  1. Run with litellm.set_verbose = True (or LITELLM_LOG=DEBUG) and inspect the raw response body contained in nearby logs to see what Google actually returned.
  2. Upgrade litellm to the latest release (pip install -U litellm) so the GeminiLongRunningOperationResponse model matches the current Veo schema.
  3. If the body is an error payload, fix the underlying cause (API key, quota, invalid model name, safety rejection) before retrying.
  4. If the payload looks like a valid Veo operation but still fails to parse, open a litellm GitHub issue with the sanitized JSON so the Pydantic model can be updated.

Example fix

# before
video = litellm.video_generation(model="gemini/veo-3", prompt="a cat surfing")

# after
try:
    video = litellm.video_generation(model="gemini/veo-3", prompt="a cat surfing")
except ValueError as e:
    if "Failed to parse operation response" in str(e):
        litellm.print_verbose(f"Veo returned an unparseable body: {e}")
        raise
Defensive patterns

Strategy: try-catch

Try / catch

try:
    video = litellm.video_generation(model="gemini/veo-3", prompt=prompt)
except ValueError as e:
    if "Failed to parse operation response" in str(e):
        # unexpected Veo payload: log for triage, do not retry blindly
        logger.error("veo schema mismatch: %s", e)
        raise
    raise

Prevention

When it happens

Trigger: Calling litellm.video_generation(model='gemini/veo-3', ...) (or any 'gemini/' Veo model) where the API responds with a body that does not match the long-running-operation schema: an error JSON returned with HTTP 200, a proxy/gateway rewriting the payload, or a Veo schema change not yet modeled in your installed litellm version.

Common situations: Outdated litellm package after Google changed the Veo response shape; a corporate proxy returning an HTML/JSON error page; quota or safety errors delivered in a non-operation envelope; mocked/stubbed test servers returning minimal JSON.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/0b98910896355c25. Report an issue: GitHub.

Appendix: source

Thrown at litellm/llms/gemini/videos/transformation.py:321

        {
            "name": "operations/generate_1234567890",
            "metadata": {...},
            "done": false,
            "error": {...}
        }

        We return this as a VideoObject with:
        - id: operation name (used for polling)
        - status: "processing"
        - usage: includes duration_seconds and optional video_resolution for cost calculation
        """
        response_data: Final = raw_response.json()

        # Parse response using Pydantic model for type safety
        try:
            operation_response: Final = GeminiLongRunningOperationResponse(**response_data)
        except Exception as e:
            raise ValueError(f"Failed to parse operation response: {e}")

        operation_name: Final = operation_response.name
        if not operation_name:
            raise ValueError(f"No operation name in Veo response: {response_data}")

        if custom_llm_provider:
            video_id = encode_video_id_with_provider(operation_name, custom_llm_provider, model)
        else:
            video_id = operation_name

        video_obj: Final = VideoObject(
            id=video_id,
            object="video",
            status="processing",
            model=model,
        )

        usage_data: Final[dict[str, Any]] = {}

View on GitHub (pinned to 77b7c6c40c)