langchain-ai/deepagents · error · TypeError

RubricMiddleware: `{name}` must be callable.

Error message

RubricMiddleware: `{name}` must be callable.

What it means

The optional hooks `prepare_messages_for_grader` and `build_grader_state` must be callable if provided. Passing a non-callable (e.g. a class instance, dict, or string) is rejected with TypeError at construction since the middleware invokes them as functions.

Source

Thrown at libs/deepagents/deepagents/middleware/rubric.py:588

        if not model:
            msg = "RubricMiddleware: `model` is required."
            raise ValueError(msg)
        if not isinstance(max_iterations, int) or isinstance(max_iterations, bool):
            msg = f"RubricMiddleware: `max_iterations` must be an int, got {type(max_iterations).__name__}."
            raise TypeError(msg)
        if max_iterations < 1:
            msg = f"RubricMiddleware: `max_iterations` must be positive, got {max_iterations}."
            raise ValueError(msg)
        if grader_state_schema is None and build_grader_state is not None:
            msg = "RubricMiddleware: `grader_state_schema` is required with `build_grader_state`."
            raise ValueError(msg)
        for name, callback in (
            ("prepare_messages_for_grader", prepare_messages_for_grader),
            ("build_grader_state", build_grader_state),
        ):
            if callback is not None and not callable(callback):
                msg = f"RubricMiddleware: `{name}` must be callable."
                raise TypeError(msg)

        self.max_iterations = max_iterations
        self._model = model
        self._model_label = _configured_model_label(model)
        self._system_prompt = system_prompt or GRADER_SYSTEM_PROMPT
        self._tools: list[BaseTool] = list(tools) if tools else []
        self._grader_middleware = grader_middleware or ()
        self._grader_context_schema = grader_context_schema
        self._grader_state_schema = grader_state_schema
        self._prepare_messages_for_grader = prepare_messages_for_grader
        self._build_grader_state = build_grader_state
        self._on_evaluation = on_evaluation
        # Built lazily so importing the middleware doesn't construct a model
        # client (which can trigger env-var lookups / API key validation).
        self._grader: Any = None
        self._resolved_model: BaseChatModel | None = None

    def before_agent(

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Pass the function itself, not its result: build_grader_state=build_state, not build_grader_state=build_state().
  2. Verify with callable(fn) before constructing.
  3. If using a method, bind it (self.method) and confirm it's a bound method.
  4. Check for accidental shadowing of the function name by data.

Example fix

// before
RubricMiddleware(model=model, build_grader_state=make_builder())
// after
RubricMiddleware(model=model, build_grader_state=make_builder()(3) if callable_needed else build_state)
Defensive patterns

Strategy: type-guard

Validate before calling

hooks = {"prepare_messages_for_grader": prep, "build_grader_state": build}
for name, fn in hooks.items():
    if fn is not None and not callable(fn):
        raise TypeError(f"{name} must be callable, got {type(fn).__name__}")

Type guard

def is_callback(v: object) -> TypeGuard[Callable]:
    return v is None or callable(v)

Try / catch

try:
    mw = RubricMiddleware(model=model, build_grader_state=build)
except TypeError as e:
    if "must be callable" in str(e):
        mw = RubricMiddleware(model=model)  # drop the bad hook
    else:
        raise

Prevention

When it happens

Trigger: RubricMiddleware(model=..., prepare_messages_for_grader=my_dict) or build_grader_state=SomeClass (an uninstantiated/decorated object that isn't callable) or the result of a call like build_grader_state=make_builder() where make_builder returns non-callable data.

Common situations: Assigning the wrong variable from a config; calling the factory instead of passing the factory; forgetting a decorator like @staticmethod/@functools.wraps so the attribute isn't callable; JSON-deserialized config where functions became strings.

Related errors


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/5fccc01631749cbe. Report an issue: GitHub.