BerriAI/litellm · error · ValueError
litellm_settings.callbacks entry '{error.entry}' resolved to
Error message
litellm_settings.callbacks entry '{error.entry}' resolved to the class {error.loaded.__module__}.{error.loaded.__qualname__}, which is neither a CustomLogger instance nor a callable, so the proxy would never run it. Point it at an instance instead, e.g. add `proxy_handler_instance = {error.loaded.__name__}()` to {module_path} and set `callbacks: ["{module_path}.proxy_handler_instance"]`. What it means
Startup ValueError from callback resolution: a litellm_settings.callbacks entry resolved to a Python class object (isinstance(loaded, type)) rather than a CustomLogger instance or a callable. _classify_loaded_callback deliberately rejects classes (calling the class would create an instance at an unpredictable time), and _raise_callback_load_error tells you to point the entry at a module-level instance instead. The message names the exact entry, the class, and the module to fix.
Source
Thrown at litellm/proxy/common_utils/callback_utils.py:95
Decide whether what a ``litellm_settings.callbacks`` dotted path resolved to can be dispatched.
A dotted path only ever runs as a ``CustomLogger`` instance or as a callback function. Anything
else (most commonly a class instead of an instance) used to load without complaint and then be
skipped on every request, with no log line and no error.
"""
if isinstance(loaded, CustomLogger) or (callable(loaded) and not isinstance(loaded, type)):
return loaded
if isinstance(loaded, type):
return _CallbackResolvedToClass(entry=entry, loaded=loaded)
return _CallbackNotDispatchable(entry=entry, loaded=loaded)
def _raise_callback_load_error(error: _CallbackLoadError) -> NoReturn:
"""The one edge that raises: map a load error onto config load's failure contract."""
match error:
case _CallbackResolvedToClass():
module_path: Final = error.entry.rsplit(".", 1)[0] if "." in error.entry else error.entry
raise ValueError(
f"litellm_settings.callbacks entry '{error.entry}' resolved to the class "
f"{error.loaded.__module__}.{error.loaded.__qualname__}, which is neither a "
"CustomLogger instance nor a callable, so the proxy would never run it."
f" Point it at an instance instead, e.g. add `proxy_handler_instance = {error.loaded.__name__}()` to "
f'{module_path} and set `callbacks: ["{module_path}.proxy_handler_instance"]`.'
)
case _CallbackNotDispatchable():
raise ValueError(
f"litellm_settings.callbacks entry '{error.entry}' resolved to "
f"{type(error.loaded).__name__} {error.loaded!r}, which is neither a "
"CustomLogger instance nor a callable, so the proxy would never run it."
)
assert_never(error)
def _loaded_callback_or_raise(entry: str, loaded: object) -> CustomLogger | Callable[..., object]:
resolved: Final = _classify_loaded_callback(entry=entry, loaded=loaded)
if isinstance(resolved, _CallbackResolvedToClass | _CallbackNotDispatchable):View on GitHub (pinned to 77b7c6c40c)
Solutions
- Instantiate at module level in my_callbacks.py: proxy_handler_instance = MyCustomLogger().
- Reference the instance in config: callbacks: ['my_callbacks.proxy_handler_instance'].
- Alternatively expose any plain function (functions are callable and accepted): def my_hook(kwargs): ... ; callbacks: ['my_callbacks.my_hook'].
- Restart the proxy after fixing; this fails fast at config load.
Example fix
// before (my_callbacks.py)
class MyCustomLogger(CustomLogger):
...
# config.yaml: litellm_settings: callbacks: ["my_callbacks.MyCustomLogger"]
// after (my_callbacks.py)
class MyCustomLogger(CustomLogger):
...
proxy_handler_instance = MyCustomLogger()
# config.yaml: litellm_settings: callbacks: ["my_callbacks.proxy_handler_instance"] Defensive patterns
Strategy: validation
Validate before calling
import importlib
from litellm.integrations.custom_logger import CustomLogger
def validate_callback_entries(entries):
for entry in entries:
module_name, _, attr = entry.rpartition('.')
obj = getattr(importlib.import_module(module_name), attr)
assert not isinstance(obj, type), f'{entry} is a class; point at an instance'
assert isinstance(obj, CustomLogger) or callable(obj), f'{entry} is not dispatchable' Type guard
def is_dispatchable_callback(entry: str) -> bool:
module_name, _, attr = entry.rpartition('.')
try:
obj = getattr(importlib.import_module(module_name), attr)
except Exception:
return False
return isinstance(obj, CustomLogger) or (callable(obj) and not isinstance(obj, type)) Prevention
- Convention: every custom-logger module exposes a module-level instance named proxy_handler_instance.
- Run the config validator in CI (litellm --config config.yaml --test or a small importlib check).
- Prefer function hooks when possible - plain functions always pass the callable check.
When it happens
Trigger: Configuring callbacks: ['my_callbacks.MyCustomLogger'] where MyCustomLogger is declared with class MyCustomLogger(CustomLogger) and never instantiated. The dotted path is imported successfully, but the attribute found is the class object itself.
Common situations: Writing a first custom logger and following intuition ('point at the class'); refactoring a module so a former instance is now a class; copying example configs that show class names; name collisions where the instance and class share a name and the class wins.
Related errors
- litellm_settings.callbacks entry '{error.entry}' resolved to
- No active custom logger found for callback name: {callback_n
- Callback param '{param}' (from {source}) contains an 'os.env
- Internal Error: Cache cannot be empty - internal_usage_cache
- skip_pre_call_logic=True requires litellm_logging_obj to be
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/faf0a0a95680939d.
Report an issue: GitHub.