BerriAI/litellm · error · ValueError

Expected document dict, got {type(document)}

Error message

Expected document dict, got {type(document)}

What it means

ValueError raised in the sync Vertex AI OCR request transformer (transform_ocr_request) when `document` is not a Python dict. The handler expects {'type': ..., 'image_url'|'document_url': ...}; the isinstance check fails for strings, lists, or None before URL-to-base64 conversion and request building start.

Source

Thrown at litellm/llms/vertex_ai/ocr/transformation.py:203

        Transform OCR request for Vertex AI, converting URLs to base64 data URIs (sync).

        Vertex AI OCR doesn't have internet access, so we automatically fetch
        any URLs and convert them to base64 data URIs synchronously.

        Args:
            model: Model name
            document: Document dict from user
            optional_params: Already mapped optional parameters
            headers: Request headers
            **kwargs: Additional arguments

        Returns:
            OCRRequestData with JSON data
        """
        verbose_logger.debug("Vertex AI OCR transform_ocr_request (sync) called")

        if not isinstance(document, dict):
            raise ValueError(f"Expected document dict, got {type(document)}")

        # Check if we need to convert URL to base64
        doc_type: Final = document.get("type")
        transformed_document: Final = document.copy()

        if doc_type == "document_url":
            document_url: Final = document.get("document_url", "")
            # If it's not already a data URI, convert it
            if document_url and not document_url.startswith("data:"):
                verbose_logger.debug("Vertex AI OCR: Converting document URL to base64 data URI (sync)")
                data_uri = self._convert_url_to_data_uri_sync(url=document_url)
                transformed_document["document_url"] = data_uri
        elif doc_type == "image_url":
            image_url: Final = document.get("image_url", "")
            # If it's not already a data URI, convert it
            if image_url and not image_url.startswith("data:"):
                verbose_logger.debug("Vertex AI OCR: Converting image URL to base64 data URI (sync)")
                data_uri = self._convert_url_to_data_uri_sync(url=image_url)

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Pass document as a dict: {'type': 'document_url', 'document_url': 'https://...'}
  2. json.loads() the payload first if it arrives as a JSON string
  3. Add a type guard before the call (see defense)

Example fix

# before
resp = litellm.ocr(model='vertex_ai/ocr-model', document='https://example.com/doc.pdf')

# after
resp = litellm.ocr(
    model='vertex_ai/ocr-model',
    document={'type': 'document_url', 'document_url': 'https://example.com/doc.pdf'},
)
Defensive patterns

Strategy: type-guard

Validate before calling

def is_ocr_document(doc: object) -> bool:
    return (
        isinstance(doc, dict)
        and doc.get('type') in ('image_url', 'document_url')
    )

assert is_ocr_document(document), f'document must be a dict, got {type(document)}'

Type guard

from typing import Any

def is_ocr_document(value: Any) -> bool:
    '''True when value is a dict of the shape the Vertex OCR handler accepts.'''
    if not isinstance(value, dict):
        return False
    t = value.get('type')
    if t not in ('image_url', 'document_url'):
        return False
    return isinstance(value.get(t), str) and bool(value[t])

Try / catch

try:
    resp = litellm.ocr(model=model, document=document)
except ValueError as e:
    if str(e).startswith('Expected document dict'):
        document = {'type': 'document_url', 'document_url': str(document)}
        resp = litellm.ocr(model=model, document=document)
    else:
        raise

Prevention

When it happens

Trigger: Calling sync OCR with document='https://...' (bare string) or document=[{...}] (list); forwarding an unparsed JSON string as the document.

Common situations: Reusing code from APIs that accept bare URLs; missing json.loads on serialized payloads; untyped request builders passing through whatever the caller supplied.

Related errors


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