BerriAI/litellm · error · NotImplementedError

transform_ocr_response must be implemented by provider

Error message

transform_ocr_response must be implemented by provider

What it means

Base OCR transformation stub for transform_ocr_response: mapping a provider's raw httpx.Response to the standard OCRResponse is provider-specific, so the base class raises NotImplementedError. Seeing it means the request may have even succeeded at HTTP level but no response parser was wired in the active config.

Source

Thrown at litellm/llms/base_llm/ocr/transformation.py:222

            model=model,
            document=document,
            optional_params=optional_params,
            headers=headers,
            **kwargs,
        )

    def transform_ocr_response(
        self,
        model: str,
        raw_response: httpx.Response,
        logging_obj: LiteLLMLoggingObj,
        **kwargs,
    ) -> OCRResponse:
        """
        Transform provider-specific OCR response to standard format.
        Override in provider-specific implementations.
        """
        raise NotImplementedError("transform_ocr_response must be implemented by provider")

    async def async_transform_ocr_response(
        self,
        model: str,
        raw_response: httpx.Response,
        logging_obj: LiteLLMLoggingObj,
        **kwargs,
    ) -> OCRResponse:
        """
        Async transform provider-specific OCR response to standard format.
        Optional method - providers can override if they need async transformations
        (e.g., Azure Document Intelligence for async operation polling).

        Default implementation falls back to sync transform_ocr_response.

        Args:
            model: Model name
            raw_response: Raw HTTP response

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Implement transform_ocr_response in the config to build OCRResponse from the provider JSON.
  2. If only the async variant is implemented, route calls through the async path (async_transform_ocr_response).
  3. Verify the model's provider resolves to the intended config class.
  4. Use a built-in OCR provider with full response support.

Example fix

# before: no response transform
class MyOCRConfig(BaseOCRConfig): ...

# after
class MyOCRConfig(BaseOCRConfig):
    def transform_ocr_response(self, model, raw_response, logging_obj, **kwargs):
        body = raw_response.json()
        return OCRResponse(text=body['content'], model=model, source='ocr')
Defensive patterns

Strategy: type-guard

Type guard

def can_transform_ocr_response(config) -> bool:
    base = BaseOCRConfig
    return (type(config).transform_ocr_response is not base.transform_ocr_response
            or type(config).async_transform_ocr_response is not base.async_transform_ocr_response)

Try / catch

try:
    ocr_resp = config.transform_ocr_response(model, raw_response, logging_obj)
except NotImplementedError:
    ocr_resp = await config.async_transform_ocr_response(model, raw_response, logging_obj)  # async-only config

Prevention

When it happens

Trigger: A provider config without transform_ocr_response reached the response-handling stage of an OCR call; async providers overriding only async_transform_ocr_response but invoked on a sync path (or vice versa); base config selected via misrouted provider name.

Common situations: Custom OCR integrations that stop at request building; sync/async method mismatches; version skew between litellm core and provider configs.

Related errors


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