BerriAI/litellm · error · CustomCodeCompilationError
'apply_guardrail' must be a callable function
Error message
'apply_guardrail' must be a callable function
What it means
CustomCodeCompilationError raised when the sandboxed code defines a top-level apply_guardrail name but it is not callable (e.g. assigned an int, string, or None). The compile pipeline found the symbol yet cannot use it as the guardrail entry function.
Source
Thrown at litellm/proxy/guardrails/guardrail_hooks/custom_code/custom_code_guardrail.py:163
GuardrailEventHooks.during_mcp_call,
GuardrailEventHooks.logging_only,
]
def _do_compile(self) -> None:
"""Internal compilation method without lock. Expected to run inside _compile_lock."""
exec_globals: Final = build_sandbox_globals()
compiled: Final = compile_sandboxed(self.custom_code)
exec(compiled, exec_globals) # noqa: S102
if "apply_guardrail" not in exec_globals:
raise CustomCodeCompilationError(
"Custom code must define an 'apply_guardrail' function. "
"Expected signature: apply_guardrail(inputs, request_data, input_type)"
)
apply_fn: Final = exec_globals["apply_guardrail"]
if not callable(apply_fn):
raise CustomCodeCompilationError("'apply_guardrail' must be a callable function")
self._compiled_function = apply_fn
def _compile_custom_code(self) -> None:
"""
Compile the custom code and extract the apply_guardrail function.
The code runs in a sandboxed environment with only the allowed primitives.
"""
with self._compile_lock:
if self._compiled_function is not None:
return
try:
self._do_compile()
verbose_proxy_logger.debug("Custom code guardrail '%s' compiled successfully", self.guardrail_name)
except SyntaxError as e:View on GitHub (pinned to 77b7c6c40c)
Solutions
- Make apply_guardrail a plain def (or other callable) at top level
- Remove any variable, constant, or doc string that rebinds the name
- Re-run the sandbox preflight (compile + exec + callable check) before redeploying
Example fix
# before: name shadowed by a constant
apply_guardrail = 'block if flagged'
# after
def apply_guardrail(inputs, request_data, input_type):
return allow() Defensive patterns
Strategy: validation
Validate before calling
from litellm.proxy.guardrails.guardrail_hooks.custom_code.sandbox import (
build_sandbox_globals, compile_sandboxed,
)
def preflight(custom_code: str) -> None:
g = build_sandbox_globals()
exec(compile_sandboxed(custom_code), g)
fn = g.get('apply_guardrail')
assert fn is not None, 'apply_guardrail not defined'
assert callable(fn), 'apply_guardrail must be a function, not a value' Type guard
def is_valid_entrypoint(exec_globals: dict) -> bool:
fn = exec_globals.get('apply_guardrail')
return fn is not None and callable(fn) Prevention
- Lint custom_code for rebindings of the apply_guardrail name (treat it as reserved)
- Keep module top level to the single function definition; put flags/constants under different names
When it happens
Trigger: custom_code contains apply_guardrail = None, apply_guardrail = 'see docs', or a variable that shadows a previously defined function of that name; compilation succeeds up to the callable() check and then aborts.
Common situations: Reusing the name for a flag/config constant; a templating step assigns a string value over the function; accidental paste of documentation text at module level.
Related errors
- Custom code must define an 'apply_guardrail' function. Expec
- Syntax error in custom code: {e}
- Failed to compile custom code: {e}
- Unsupported HTTP method: {method}
- augmented assignment {op!r} is not supported
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/5cd657bfba1e5693.
Report an issue: GitHub.