langchain-ai/deepagents · error · TypeError

RubricMiddleware grader returned unexpected…

Error message

RubricMiddleware grader returned unexpected structured_response of type {type(graded).__name__}.

What it means

After reading structured_response, the middleware expects a GraderResponse instance and tolerates a dict (validated via pydantic) for forward-compatibility. Any other type means the grader's response_format resolved to something unexpected, so a TypeError is raised to surface the mismatch.

Solutions

  1. Configure the grader with response_format=GraderResponse.
  2. Return a plain dict matching GraderResponse fields — dicts are accepted and validated.
  3. Log type(graded) to identify which schema the grader actually used.
  4. Align any custom response model with GraderResponse or convert before returning.

Example fix

// before
grader = create_agent(model, tools=[...], response_format=MyGrade)
// after
from deepagents.middleware.rubric import GraderResponse
grader = create_agent(model, tools=[...], response_format=GraderResponse)
Defensive patterns

Strategy: try-catch

Validate before calling

raw = result.get("structured_response")
if raw is not None and not isinstance(raw, (GraderResponse, dict)):
    raise TypeError(f"expected GraderResponse or dict, got {type(raw).__name__}")

Type guard

def is_valid_graded(v: object) -> TypeGuard[GraderResponse | dict]:
    return isinstance(v, (GraderResponse, dict))

Try / catch

try:
    graded = middleware._extract_graded(result)
except TypeError as e:
    if "unexpected structured_response" in str(e):
        graded = GraderResponse.model_validate(result["structured_response"].model_dump())
    else:
        raise

Prevention

When it happens

Trigger: A custom grader agent whose response_format is a different pydantic model, so structured_response is e.g. MyOtherModel; a hand-rolled grader returning an arbitrary object in structured_response.

Common situations: Pointing RubricMiddleware at an existing agent configured with a different response format; refactoring GraderResponse and leaving an old schema in place.

Related errors


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

Appendix: source

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

            )
        )
        return self._extract_graded(result)

    @staticmethod
    def _extract_graded(result: dict[str, Any]) -> GraderResponse:
        graded = result.get("structured_response")
        if graded is None:
            msg = "RubricMiddleware grader did not return a structured_response. The grader sub-agent must use response_format=GraderResponse."
            raise RuntimeError(msg)
        if not isinstance(graded, GraderResponse):
            # `create_agent` returns whatever the grader's response_format
            # resolves to; we expect a `GraderResponse` instance but accept
            # a `dict` for forward-compat.
            if isinstance(graded, dict):
                graded = GraderResponse.model_validate(graded)
            else:
                msg = f"RubricMiddleware grader returned unexpected structured_response of type {type(graded).__name__}."
                raise TypeError(msg)
        return graded

    def _build_grader_payload(
        self,
        state: RubricState,
        iteration: int,
        correction: str | None = None,
    ) -> str:
        """Assemble the grader's first user message.

        Wraps the caller-supplied rubric and the transcript in
        nonce-bracketed delimiters and scrubs any literal closing tags
        from the content before interpolation.

        Two modes. With no frozen criterion list the grader is asked to
        enumerate the rubric itself; once a list exists it is replayed as a
        numbered checklist and the grader is asked for that exact count, which
        keeps the criterion set from shrinking across iterations of one run.

View on GitHub (pinned to a1af029e6e)