BerriAI/litellm · error · ValueError

logging_obj is required

Error message

logging_obj is required

What it means

litellm.create_file requires an initialized LiteLLM logging object passed in kwargs as litellm_logging_obj. It is normally injected by LiteLLM's router/main layer before the function runs; if it is absent (None), the function refuses to proceed because success/failure logging and cost tracking could not work.

Source

Thrown at litellm/files/main.py:164

    custom_llm_provider: FileCreateProvider | None = None,
    extra_headers: dict[str, str] | None = None,
    extra_body: dict[str, str] | None = None,
    **kwargs,
) -> OpenAIFileObject | Coroutine[Any, Any, OpenAIFileObject]:
    """
    Files are used to upload documents that can be used with features like Assistants, Fine-tuning, and Batch API.

    LiteLLM Equivalent of POST: POST https://api.openai.com/v1/files

    Specify either provider_list or custom_llm_provider.
    """
    try:
        _is_async: Final = kwargs.pop("acreate_file", False) is True
        optional_params: Final = GenericLiteLLMParams(**kwargs)
        litellm_params_dict: Final = dict(**kwargs)
        logging_obj: Final = cast(LiteLLMLoggingObj | None, kwargs.get("litellm_logging_obj"))
        if logging_obj is None:
            raise ValueError("logging_obj is required")
        client: Final = kwargs.get("client")

        ### TIMEOUT LOGIC ###
        timeout = optional_params.timeout or kwargs.get("request_timeout", 600) or 600
        # set timeout for 10 minutes by default

        if (
            timeout is not None
            and isinstance(timeout, httpx.Timeout)
            and supports_httpx_timeout(cast(str, custom_llm_provider)) is False
        ):
            read_timeout: Final = timeout.read or 600
            timeout = read_timeout  # default 10 min timeout
        elif timeout is not None and not isinstance(timeout, httpx.Timeout):
            timeout = float(timeout)
        elif timeout is None:
            timeout = 600.0

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Call the public API: litellm.create_file(file=..., purpose='fine-tune'|'batch'|'assistants', custom_llm_provider='openai') — it sets up logging for you
  2. If you must call create_file directly, pass a logging object: from litellm.litellm_core_utils.litellm_logging import Logging; kwargs['litellm_logging_obj'] = Logging(model, stream=False, call_type='apass_through_endpoint')
  3. Check that nothing in your wrapper pops or filters litellm_logging_obj out of kwargs before the call

Example fix

# before
from litellm.files.main import create_file
resp = create_file(file=open('f.jsonl','rb'), purpose='batch', custom_llm_provider='openai')

# after
import litellm
resp = litellm.create_file(file=open('f.jsonl','rb'), purpose='batch', custom_llm_provider='openai')
Defensive patterns

Strategy: validation

Validate before calling

kwargs.setdefault("litellm_logging_obj", None)
if kwargs["litellm_logging_obj"] is None and "litellm_logging_obj" not in kwargs:
    # call public API instead; it injects the logger
    import litellm  # use litellm.create_file(...)

Type guard

def has_logging_obj(kwargs: dict) -> bool:
    lo = kwargs.get("litellm_logging_obj")
    return lo is not None and hasattr(lo, "update_from_kwargs")

Prevention

When it happens

Trigger: Calling litellm.files.main.create_file (or the internal acreate_file path) directly instead of through the public litellm.create_file API, so kwargs never contain litellm_logging_obj; or a custom fork/wrapper that strips kwargs before delegating.

Common situations: Users importing internal modules to bypass the public API; mocking tests that call create_file with hand-built kwargs; upgrades that changed the internal kwarg plumbing while code called private entry points.

Related errors


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