채점 루브릭

채점 루브릭 (Grading rubrics)

루브릭에 대해 만족할 때까지 반복하는 에이전트를 위한 LLM-as-a-judge 채점

`RubricMiddleware`는 `deepagents>=0.6.5`가 필요합니다. [**베타**](/oss/javascript/versioning) 상태이며, API는 앞으로 바뀔 수 있습니다.

일부 에이전트 작업은 작업 모델만으로는 첫 시도에 안정적으로 달성할 수 없는 "완료"의 명확한 정의가 있습니다. 예를 들어 올바른 음절 패턴의 하이쿠, 모든 테스트가 통과하는 리팩터링, 모든 필수 섹션을 맞추는 보고서 등이 있습니다. RubricMiddleware완료가 어떤 모습인지 루브릭으로 선언하고 에이전트가 루브릭이 충족될 때까지, 또는 구성된 최대 반복 상한에 도달할 때까지 자체 평가하고 반복하게 합니다.

LLM-as-a-judge는 한 언어 모델이 정의된 기준에 대해 다른 모델의 출력을 평가하는 패턴입니다. LangSmith 평가에서 LLM-as-a-judge 평가기는 배치로 오프라인에서 애플리케이션 출력을 채점합니다. RubricMiddleware는 같은 패턴을 런타임에 적용합니다. deep agent가 출력을 만든 뒤 전용 채점 모델이 루브릭에 대해 대화 기록을 검토하고 모든 기준이 통과될 때까지(또는 구성된 반복 상한에 도달할 때까지) 수정을 진행합니다.

deep agent가 추론을 마치면 LLM-as-a-judge 채점자 서브에이전트가 출력을 검토하고 평결을 반환합니다. needs_revision을 반환하면 기준별 피드백이 대화에 다시 주입되고 에이전트가 다시 실행됩니다. 루프는 satisfied, max_iterations_reached, failed, 또는 grader_error에서 종료됩니다.

graph LR
    Start[User invokes<br/>with rubric] --> Agent[Deep agent]
    Agent --> Grader{Grader<br/>verdict}

    Grader --> |satisfied| Done[Finish execution]
    Grader --> |failed| Done
    Grader --> |grader_error| Done
    Grader --> |needs_revision| Cap{Iterations < <br/> max_iterations?}

    Cap --> |yes| Inject[Re-prompt deep agent with per-criterion feedback]
    Cap --> |no| Done

    Inject --> Agent

    classDef trigger fill:#F6FFDB,stroke:#6E8900,stroke-width:2px,color:#2E3900
    classDef process fill:#E5F4FF,stroke:#006DDD,stroke-width:2px,color:#030710
    classDef decision fill:#FDF3FF,stroke:#7E65AE,stroke-width:2px,color:#504B5F
    classDef alert fill:#F8E8E6,stroke:#B27D75,stroke-width:2px,color:#634643

    class Start trigger
    class Agent,Inject process
    class Grader,Cap decision
    class Done,MaxOut alert

미들웨어 구성 (Configure the middleware)

create_deep_agent를 호출할 때 middleware 목록에 RubricMiddleware를 추가하세요:

```python Google theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} from deepagents import RubricMiddleware, create_deep_agent from langgraph.checkpoint.memory import InMemorySaver

agent = create_deep_agent( model="google_genai:gemini-3.6-flash", middleware=[ RubricMiddleware( model="anthropic:claude-haiku-4-5", max_iterations=3, ), ], checkpointer=InMemorySaver(), )


```python OpenAI theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
from deepagents import RubricMiddleware, create_deep_agent
from langgraph.checkpoint.memory import InMemorySaver

agent = create_deep_agent(
    model="openai:gpt-5.5",
    middleware=[
        RubricMiddleware(
            model="anthropic:claude-haiku-4-5",
            max_iterations=3,
        ),
    ],
    checkpointer=InMemorySaver(),
)
from deepagents import RubricMiddleware, create_deep_agent
from langgraph.checkpoint.memory import InMemorySaver

agent = create_deep_agent(
    model="anthropic:claude-sonnet-5",
    middleware=[
        RubricMiddleware(
            model="anthropic:claude-haiku-4-5",
            max_iterations=3,
        ),
    ],
    checkpointer=InMemorySaver(),
)
from deepagents import RubricMiddleware, create_deep_agent
from langgraph.checkpoint.memory import InMemorySaver

agent = create_deep_agent(
    model="openrouter:z-ai/glm-5.2",
    middleware=[
        RubricMiddleware(
            model="anthropic:claude-haiku-4-5",
            max_iterations=3,
        ),
    ],
    checkpointer=InMemorySaver(),
)
from deepagents import RubricMiddleware, create_deep_agent
from langgraph.checkpoint.memory import InMemorySaver

agent = create_deep_agent(
    model="fireworks:accounts/fireworks/models/glm-5p2",
    middleware=[
        RubricMiddleware(
            model="anthropic:claude-haiku-4-5",
            max_iterations=3,
        ),
    ],
    checkpointer=InMemorySaver(),
)
from deepagents import RubricMiddleware, create_deep_agent
from langgraph.checkpoint.memory import InMemorySaver

agent = create_deep_agent(
    model="baseten:zai-org/GLM-5.2",
    middleware=[
        RubricMiddleware(
            model="anthropic:claude-haiku-4-5",
            max_iterations=3,
        ),
    ],
    checkpointer=InMemorySaver(),
)
from deepagents import RubricMiddleware, create_deep_agent
from langgraph.checkpoint.memory import InMemorySaver

agent = create_deep_agent(
    model="ollama:north-mini-code-1.0",
    middleware=[
        RubricMiddleware(
            model="anthropic:claude-haiku-4-5",
            max_iterations=3,
        ),
    ],
    checkpointer=InMemorySaver(),
)
인자 필수 기본값 설명
model None LLM-as-a-judge 채점자 서브에이전트가 사용하는 채팅 모델. "provider:model-id" 문자열 또는 BaseChatModel 인스턴스를 받습니다. 보통 deep agent의 작업 모델보다 작거나 저렴한 모델입니다.
system_prompt 아니요 내장 채점자 프롬프트 커스텀 채점 지침. 채점자에게 평결 형식과 사용할 수 있는 도구를 가르치는 기본 시스템 프롬프트로 폴백합니다.
tools 아니요 None 채점자가 평결을 내리기 전에 증거를 모으기 위해 호출할 수 있는 도구(테스트 실행, 토큰 계산, 파일 읽기). 없으면 채점자는 대화 기록만으로 추론합니다.
max_iterations 아니요 3 루브릭 시도당 최대 채점자 반복 횟수. 양의 정수여야 합니다. satisfied 없이 상한에 도달하면 에이전트는 max_iterations_reached 상태로 종료합니다.
on_evaluation 아니요 None 모든 채점 반복 후 각 RubricEvaluation과 함께 호출되는 선택적 콜백. invoke(), stream(), stream_events() 중 무엇을 쓰든 호출됩니다. 로깅, 커스텀 메트릭, eval 데이터셋, UI 업데이트에 유용합니다.

호출 시 루브릭 전달 (Pass rubric on invocation)

호출 상태에 rubric 문자열을 전달해 자체 평가 루프를 시작하세요. 단일 블로킹 호출에는 invoke()를, CustomTransformer와 함께 stream_events(..., version="v3")를 사용하면 발생하는 대로 stream.custom에서 채점 이벤트를 받을 수 있습니다:

```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} from langchain.messages import HumanMessage
config = {"configurable": {"thread_id": "my-rubric-thread"}}
result = agent.invoke(
    {
        "messages": [HumanMessage("Write a haiku about spring.")],
        "rubric": (
            "- The poem has three lines\n"
            "- Lines follow a 5-7-5 syllable pattern\n"
            "- The theme is spring"
        ),
    },
    config=config,
)
```
```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} from langchain.messages import HumanMessage from langgraph.stream import CustomTransformer
config = {"configurable": {"thread_id": "my-rubric-thread"}}
stream = agent.stream_events(
    {
        "messages": [HumanMessage("Write a haiku about spring.")],
        "rubric": (
            "- The poem has three lines\n"
            "- Lines follow a 5-7-5 syllable pattern\n"
            "- The theme is spring"
        ),
    },
    config=config,
    version="v3",
    transformers=[CustomTransformer],
)

for event in stream.custom:
    event_type = event.get("type")
    if event_type == "rubric_evaluation_start":
        print(
            f"Grading iteration {event['iteration']} "
            f"(run {event['grading_run_id']})"
        )
    elif event_type == "rubric_evaluation_end":
        print(f"Verdict: {event['result']} — {event.get('explanation', '')}")
```

루브릭 채점은 `stream.custom`에 다음 커스텀 이벤트를 방출합니다:

| 이벤트 | 발생 시점 | 페이로드 필드 |
| ------------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `rubric_evaluation_start` | 채점자가 실행되기 전. | <ul><li>`type`: 이벤트 이름</li><li>`grading_run_id`: 하나의 루브릭 시도 내 모든 이벤트에 공유</li><li>`iteration`: 현재 채점 실행의 0 기반 인덱스</li></ul> |
| `rubric_evaluation_end` | 채점자가 반환한 후 또는 채점자 예외 후. | <ul><li>`type`: 이벤트 이름</li><li>`grading_run_id`: 하나의 루브릭 시도 내 모든 이벤트에 공유</li><li>`iteration`: 현재 채점자 패스의 0 기반 인덱스</li><li>`result`: 이 패스의 최종 평결</li><li>`explanation`: 채점자의 요약</li><li>`criteria`: 기준별 평결</li></ul> |

루브릭 평결 (Rubric verdicts)

deep agent가 추론을 마치고 출력이 있으면 LLM-as-a-judge 채점자 서브에이전트가 루브릭에 대해 출력을 검토하고 다음 평결 중 하나를 만듭니다:

상태 의미 루프백?
satisfied 루브릭의 모든 기준이 통과합니다. 아니요
needs_revision 하나 이상의 기준이 실패합니다. 채점자 피드백이 주입되고 에이전트가 다시 실행됩니다.
max_iterations_reached 채점자가 여전히 수정을 원하지만 max_iterations에 도달했습니다. 아니요
failed 채점자가 루브릭이 잘못되었거나 대화 기록에 대해 평가할 수 없다고 판단했습니다. 아니요
grader_error LLM-as-a-judge 채점자 서브에이전트 자체가 예외를 발생시켰습니다(프로바이더 타임아웃, 자격 증명 누락, 잘못된 구조화된 응답 등). 아니요

반복 진행 관찰 (Observe iteration progress)

on_evaluationinvoke()를 호출하든 stream_events()를 호출하든 각 채점 반복 후 채점자의 평결과 함께 발생하는 콜백입니다. stream.custom(CustomTransformer 사용)에서 루브릭 이벤트를 읽지 않거나 LangSmith로 실행을 추적하지 않는다면, 채점 중에 무슨 일이 있었는지 검사하는 주요 방법입니다.

```python Google theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} from deepagents import RubricMiddleware, create_deep_agent from deepagents.middleware.rubric import RubricEvaluation from langchain.messages import HumanMessage from langgraph.checkpoint.memory import InMemorySaver

def log_evaluation(ev: RubricEvaluation) -> None: print(f"iteration {ev['iteration']}: {ev['result']} — {ev['explanation']}")

agent = create_deep_agent( model="google_genai:gemini-3.6-flash", middleware=[ RubricMiddleware( model="google_genai:gemini-3.6-flash", on_evaluation=log_evaluation, ), ], checkpointer=InMemorySaver(), )

config = {"configurable": {"thread_id": "rubric-eval-session"}} agent.invoke( { "messages": [HumanMessage("Write a one-sentence summary of photosynthesis.")], "rubric": ( "- The answer is one sentence\n" "- The answer mentions light and chlorophyll" ), }, config=config, )


```python OpenAI theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
from deepagents import RubricMiddleware, create_deep_agent
from deepagents.middleware.rubric import RubricEvaluation
from langchain.messages import HumanMessage
from langgraph.checkpoint.memory import InMemorySaver


def log_evaluation(ev: RubricEvaluation) -> None:
    print(f"iteration {ev['iteration']}: {ev['result']} — {ev['explanation']}")


agent = create_deep_agent(
    model="openai:gpt-5.5",
    middleware=[
        RubricMiddleware(
            model="openai:gpt-5.5",
            on_evaluation=log_evaluation,
        ),
    ],
    checkpointer=InMemorySaver(),
)

config = {"configurable": {"thread_id": "rubric-eval-session"}}
agent.invoke(
    {
        "messages": [HumanMessage("Write a one-sentence summary of photosynthesis.")],
        "rubric": (
            "- The answer is one sentence\n"
            "- The answer mentions light and chlorophyll"
        ),
    },
    config=config,
)
from deepagents import RubricMiddleware, create_deep_agent
from deepagents.middleware.rubric import RubricEvaluation
from langchain.messages import HumanMessage
from langgraph.checkpoint.memory import InMemorySaver


def log_evaluation(ev: RubricEvaluation) -> None:
    print(f"iteration {ev['iteration']}: {ev['result']} — {ev['explanation']}")


agent = create_deep_agent(
    model="anthropic:claude-sonnet-5",
    middleware=[
        RubricMiddleware(
            model="anthropic:claude-sonnet-5",
            on_evaluation=log_evaluation,
        ),
    ],
    checkpointer=InMemorySaver(),
)

config = {"configurable": {"thread_id": "rubric-eval-session"}}
agent.invoke(
    {
        "messages": [HumanMessage("Write a one-sentence summary of photosynthesis.")],
        "rubric": (
            "- The answer is one sentence\n"
            "- The answer mentions light and chlorophyll"
        ),
    },
    config=config,
)
from deepagents import RubricMiddleware, create_deep_agent
from deepagents.middleware.rubric import RubricEvaluation
from langchain.messages import HumanMessage
from langgraph.checkpoint.memory import InMemorySaver


def log_evaluation(ev: RubricEvaluation) -> None:
    print(f"iteration {ev['iteration']}: {ev['result']} — {ev['explanation']}")


agent = create_deep_agent(
    model="openrouter:z-ai/glm-5.2",
    middleware=[
        RubricMiddleware(
            model="openrouter:z-ai/glm-5.2",
            on_evaluation=log_evaluation,
        ),
    ],
    checkpointer=InMemorySaver(),
)

config = {"configurable": {"thread_id": "rubric-eval-session"}}
agent.invoke(
    {
        "messages": [HumanMessage("Write a one-sentence summary of photosynthesis.")],
        "rubric": (
            "- The answer is one sentence\n"
            "- The answer mentions light and chlorophyll"
        ),
    },
    config=config,
)
from deepagents import RubricMiddleware, create_deep_agent
from deepagents.middleware.rubric import RubricEvaluation
from langchain.messages import HumanMessage
from langgraph.checkpoint.memory import InMemorySaver


def log_evaluation(ev: RubricEvaluation) -> None:
    print(f"iteration {ev['iteration']}: {ev['result']} — {ev['explanation']}")


agent = create_deep_agent(
    model="fireworks:accounts/fireworks/models/glm-5p2",
    middleware=[
        RubricMiddleware(
            model="fireworks:accounts/fireworks/models/glm-5p2",
            on_evaluation=log_evaluation,
        ),
    ],
    checkpointer=InMemorySaver(),
)

config = {"configurable": {"thread_id": "rubric-eval-session"}}
agent.invoke(
    {
        "messages": [HumanMessage("Write a one-sentence summary of photosynthesis.")],
        "rubric": (
            "- The answer is one sentence\n"
            "- The answer mentions light and chlorophyll"
        ),
    },
    config=config,
)
from deepagents import RubricMiddleware, create_deep_agent
from deepagents.middleware.rubric import RubricEvaluation
from langchain.messages import HumanMessage
from langgraph.checkpoint.memory import InMemorySaver


def log_evaluation(ev: RubricEvaluation) -> None:
    print(f"iteration {ev['iteration']}: {ev['result']} — {ev['explanation']}")


agent = create_deep_agent(
    model="baseten:zai-org/GLM-5.2",
    middleware=[
        RubricMiddleware(
            model="baseten:zai-org/GLM-5.2",
            on_evaluation=log_evaluation,
        ),
    ],
    checkpointer=InMemorySaver(),
)

config = {"configurable": {"thread_id": "rubric-eval-session"}}
agent.invoke(
    {
        "messages": [HumanMessage("Write a one-sentence summary of photosynthesis.")],
        "rubric": (
            "- The answer is one sentence\n"
            "- The answer mentions light and chlorophyll"
        ),
    },
    config=config,
)
from deepagents import RubricMiddleware, create_deep_agent
from deepagents.middleware.rubric import RubricEvaluation
from langchain.messages import HumanMessage
from langgraph.checkpoint.memory import InMemorySaver


def log_evaluation(ev: RubricEvaluation) -> None:
    print(f"iteration {ev['iteration']}: {ev['result']} — {ev['explanation']}")


agent = create_deep_agent(
    model="ollama:north-mini-code-1.0",
    middleware=[
        RubricMiddleware(
            model="ollama:north-mini-code-1.0",
            on_evaluation=log_evaluation,
        ),
    ],
    checkpointer=InMemorySaver(),
)

config = {"configurable": {"thread_id": "rubric-eval-session"}}
agent.invoke(
    {
        "messages": [HumanMessage("Write a one-sentence summary of photosynthesis.")],
        "rubric": (
            "- The answer is one sentence\n"
            "- The answer mentions light and chlorophyll"
        ),
    },
    config=config,
)

미들웨어는 각 채점자 패스RubricEvaluation 딕셔너리로 함수를 호출합니다. RubricEvaluation 딕셔너리는 다음을 포함합니다:

필드 유형 설명
grading_run_id str 하나의 루브릭 시도에서 모든 평가가 공유하는 식별자. 호출자가 다른 rubric을 제공하거나, 최종 평결 후 같은 rubric이 다시 호출되면 새 실행이 시작됩니다.
iteration int 그 실행 내 현재 채점자 패스의 0 기반 인덱스.
result str 이 패스의 채점자 평결: satisfied, needs_revision, failed, 또는 grader_error.
explanation str 채점자의 자유 형식 요약. 인프라 실패 시 예외 유형과 메시지를 포함합니다.
criteria list 기준별 평결. 각 항목은 {name, passed: true} 또는 gap이 실패 기준에 대한 실행 가능한 피드백인 {name, passed: false, gap}입니다.

채점자 패스 이벤트 (Grader pass events)

이벤트 설명
성공적인 채점 중간 needs_revision 평결과 최종 satisfied 또는 failed 평결을 포함해 패스당 한 번 발생합니다.

채점자가 needs_revision을 반환했지만 max_iterations에 도달한 경우에도 콜백은 result: "needs_revision"(채점자의 평결)을 받습니다. 실행의 최종 상태는 평가 기록이 아니라 비공개 상태 _rubric_statusmax_iterations_reached입니다. invoke 완료 후 _rubric_status를 검사하거나, _rubric_iterations와 함께 _rubric_evaluations의 마지막 항목을 읽어 상한 소진에 분기하세요.
채점자 예외 result: "grader_error", 예외에서 파생된 설명, 빈 criteria 목록으로 발생합니다.
콜백의 오류 예외는 기록되고 억제됩니다. 채점 루프는 계속됩니다. 제어 흐름을 강제하는 데(예: 에이전트를 멈추기 위해 raise) on_evaluation을 사용하지 마세요.

호출에 걸쳐 루브릭 유지 (Persist rubrics across invocations)

단일 agent.invoke() 또는 agent.stream_events() 호출은 루브릭 루프를 완료까지 실행하고 최종 평결(satisfied, failed, 또는 max_iterations_reached)로 끝납니다.

후속 호출로 루브릭을 이어가려면 체크포인터를 연결하고 호출과 함께 같은 thread_id를 전달하세요. 이 경우 새 것을 전달할 때까지 같은 rubric이 미래의 invoke() 또는 stream_events() 호출에 걸쳐 유지됩니다.

인터럽트(KeyboardInterrupt, asyncio.CancelledError)는 잡히지 않고 agent.invoke()agent.stream_events() 밖으로 전파됩니다. 체크포인트된 스레드에서는 같은 루브릭이 있는 다음 호출이 진행 중이던 채점 실행을 재개합니다.

예시: 검증된 Python 코드 생성 (Example: generate vetted Python code)

다음 예시는 find_duplicates 함수를 작성하는 deep agent를 만듭니다. RubricMiddleware를 한 번 정의해 에이전트에 연결한 뒤 invoke 시점에 rubric 문자열을 전달합니다.

채점자에게 정확성에 대해 추상적으로 추론하라고 요청하는 대신, 예시는 동작을 직접 검증할 run_test_suite 도구를 줍니다. 채점자는 평결을 내리기 전에 추가 정보를 위해 이 도구를 호출하고, 도구가 제공되지 않으면 대화 기록에서 추론하는 것으로 폴백합니다.

이 미들웨어는 기본 에이전트 위에 LLM-as-a-judge 채점자 루프를 추가합니다. 채점자 모델, 선택적 커스텀 프롬프트, 증거 수집 도구, 최대 반복 상한을 구성하세요.
<CodeGroup>
  ```python Google theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  from deepagents import RubricMiddleware
  from langchain.tools import tool


  @tool
  def run_test_suite(code: str) -> dict:
      """Run the find_duplicates test suite against Python source code."""
      namespace: dict = {"__builtins__": __builtins__}
      try:
          exec(code, namespace)
      except Exception as exc:
          return {"ok": False, "failures": [f"Failed to execute code: {exc}"]}

      find_duplicates = namespace.get("find_duplicates")
      if find_duplicates is None:
          return {"ok": False, "failures": ["Function find_duplicates is not defined"]}

      tests = [
          ("test_basic", [1, 2, 2, 3, 1], [2, 1]),
          ("test_empty", [], []),
          ("test_no_duplicates", [1, 2, 3], []),
          ("test_unhashable", [[1], [1], 2], [[1]]),
      ]
      failures: list[str] = []
      for name, args, expected in tests:
          try:
              actual = find_duplicates(args)
              if actual != expected:
                  failures.append(f"{name}: expected {expected}, got {actual}")
          except Exception as exc:
              failures.append(f"{name}: {exc}")

      return {"ok": not failures, "failures": failures}


  rubric_middleware = RubricMiddleware(
      model="google_genai:gemini-3.6-flash",
      system_prompt="You are a code reviewer grading generated code against a rubric.",
      tools=[run_test_suite],
      max_iterations=5,
  )
  ```

  ```python OpenAI theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  from deepagents import RubricMiddleware
  from langchain.tools import tool


  @tool
  def run_test_suite(code: str) -> dict:
      """Run the find_duplicates test suite against Python source code."""
      namespace: dict = {"__builtins__": __builtins__}
      try:
          exec(code, namespace)
      except Exception as exc:
          return {"ok": False, "failures": [f"Failed to execute code: {exc}"]}

      find_duplicates = namespace.get("find_duplicates")
      if find_duplicates is None:
          return {"ok": False, "failures": ["Function find_duplicates is not defined"]}

      tests = [
          ("test_basic", [1, 2, 2, 3, 1], [2, 1]),
          ("test_empty", [], []),
          ("test_no_duplicates", [1, 2, 3], []),
          ("test_unhashable", [[1], [1], 2], [[1]]),
      ]
      failures: list[str] = []
      for name, args, expected in tests:
          try:
              actual = find_duplicates(args)
              if actual != expected:
                  failures.append(f"{name}: expected {expected}, got {actual}")
          except Exception as exc:
              failures.append(f"{name}: {exc}")

      return {"ok": not failures, "failures": failures}


  rubric_middleware = RubricMiddleware(
      model="openai:gpt-5.5",
      system_prompt="You are a code reviewer grading generated code against a rubric.",
      tools=[run_test_suite],
      max_iterations=5,
  )
  ```

  ```python Anthropic theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  from deepagents import RubricMiddleware
  from langchain.tools import tool


  @tool
  def run_test_suite(code: str) -> dict:
      """Run the find_duplicates test suite against Python source code."""
      namespace: dict = {"__builtins__": __builtins__}
      try:
          exec(code, namespace)
      except Exception as exc:
          return {"ok": False, "failures": [f"Failed to execute code: {exc}"]}

      find_duplicates = namespace.get("find_duplicates")
      if find_duplicates is None:
          return {"ok": False, "failures": ["Function find_duplicates is not defined"]}

      tests = [
          ("test_basic", [1, 2, 2, 3, 1], [2, 1]),
          ("test_empty", [], []),
          ("test_no_duplicates", [1, 2, 3], []),
          ("test_unhashable", [[1], [1], 2], [[1]]),
      ]
      failures: list[str] = []
      for name, args, expected in tests:
          try:
              actual = find_duplicates(args)
              if actual != expected:
                  failures.append(f"{name}: expected {expected}, got {actual}")
          except Exception as exc:
              failures.append(f"{name}: {exc}")

      return {"ok": not failures, "failures": failures}


  rubric_middleware = RubricMiddleware(
      model="anthropic:claude-sonnet-5",
      system_prompt="You are a code reviewer grading generated code against a rubric.",
      tools=[run_test_suite],
      max_iterations=5,
  )
  ```

  ```python OpenRouter theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  from deepagents import RubricMiddleware
  from langchain.tools import tool


  @tool
  def run_test_suite(code: str) -> dict:
      """Run the find_duplicates test suite against Python source code."""
      namespace: dict = {"__builtins__": __builtins__}
      try:
          exec(code, namespace)
      except Exception as exc:
          return {"ok": False, "failures": [f"Failed to execute code: {exc}"]}

      find_duplicates = namespace.get("find_duplicates")
      if find_duplicates is None:
          return {"ok": False, "failures": ["Function find_duplicates is not defined"]}

      tests = [
          ("test_basic", [1, 2, 2, 3, 1], [2, 1]),
          ("test_empty", [], []),
          ("test_no_duplicates", [1, 2, 3], []),
          ("test_unhashable", [[1], [1], 2], [[1]]),
      ]
      failures: list[str] = []
      for name, args, expected in tests:
          try:
              actual = find_duplicates(args)
              if actual != expected:
                  failures.append(f"{name}: expected {expected}, got {actual}")
          except Exception as exc:
              failures.append(f"{name}: {exc}")

      return {"ok": not failures, "failures": failures}


  rubric_middleware = RubricMiddleware(
      model="openrouter:z-ai/glm-5.2",
      system_prompt="You are a code reviewer grading generated code against a rubric.",
      tools=[run_test_suite],
      max_iterations=5,
  )
  ```

  ```python Fireworks theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  from deepagents import RubricMiddleware
  from langchain.tools import tool


  @tool
  def run_test_suite(code: str) -> dict:
      """Run the find_duplicates test suite against Python source code."""
      namespace: dict = {"__builtins__": __builtins__}
      try:
          exec(code, namespace)
      except Exception as exc:
          return {"ok": False, "failures": [f"Failed to execute code: {exc}"]}

      find_duplicates = namespace.get("find_duplicates")
      if find_duplicates is None:
          return {"ok": False, "failures": ["Function find_duplicates is not defined"]}

      tests = [
          ("test_basic", [1, 2, 2, 3, 1], [2, 1]),
          ("test_empty", [], []),
          ("test_no_duplicates", [1, 2, 3], []),
          ("test_unhashable", [[1], [1], 2], [[1]]),
      ]
      failures: list[str] = []
      for name, args, expected in tests:
          try:
              actual = find_duplicates(args)
              if actual != expected:
                  failures.append(f"{name}: expected {expected}, got {actual}")
          except Exception as exc:
              failures.append(f"{name}: {exc}")

      return {"ok": not failures, "failures": failures}


  rubric_middleware = RubricMiddleware(
      model="fireworks:accounts/fireworks/models/glm-5p2",
      system_prompt="You are a code reviewer grading generated code against a rubric.",
      tools=[run_test_suite],
      max_iterations=5,
  )
  ```

  ```python Baseten theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  from deepagents import RubricMiddleware
  from langchain.tools import tool


  @tool
  def run_test_suite(code: str) -> dict:
      """Run the find_duplicates test suite against Python source code."""
      namespace: dict = {"__builtins__": __builtins__}
      try:
          exec(code, namespace)
      except Exception as exc:
          return {"ok": False, "failures": [f"Failed to execute code: {exc}"]}

      find_duplicates = namespace.get("find_duplicates")
      if find_duplicates is None:
          return {"ok": False, "failures": ["Function find_duplicates is not defined"]}

      tests = [
          ("test_basic", [1, 2, 2, 3, 1], [2, 1]),
          ("test_empty", [], []),
          ("test_no_duplicates", [1, 2, 3], []),
          ("test_unhashable", [[1], [1], 2], [[1]]),
      ]
      failures: list[str] = []
      for name, args, expected in tests:
          try:
              actual = find_duplicates(args)
              if actual != expected:
                  failures.append(f"{name}: expected {expected}, got {actual}")
          except Exception as exc:
              failures.append(f"{name}: {exc}")

      return {"ok": not failures, "failures": failures}


  rubric_middleware = RubricMiddleware(
      model="baseten:zai-org/GLM-5.2",
      system_prompt="You are a code reviewer grading generated code against a rubric.",
      tools=[run_test_suite],
      max_iterations=5,
  )
  ```

  ```python Ollama theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  from deepagents import RubricMiddleware
  from langchain.tools import tool


  @tool
  def run_test_suite(code: str) -> dict:
      """Run the find_duplicates test suite against Python source code."""
      namespace: dict = {"__builtins__": __builtins__}
      try:
          exec(code, namespace)
      except Exception as exc:
          return {"ok": False, "failures": [f"Failed to execute code: {exc}"]}

      find_duplicates = namespace.get("find_duplicates")
      if find_duplicates is None:
          return {"ok": False, "failures": ["Function find_duplicates is not defined"]}

      tests = [
          ("test_basic", [1, 2, 2, 3, 1], [2, 1]),
          ("test_empty", [], []),
          ("test_no_duplicates", [1, 2, 3], []),
          ("test_unhashable", [[1], [1], 2], [[1]]),
      ]
      failures: list[str] = []
      for name, args, expected in tests:
          try:
              actual = find_duplicates(args)
              if actual != expected:
                  failures.append(f"{name}: expected {expected}, got {actual}")
          except Exception as exc:
              failures.append(f"{name}: {exc}")

      return {"ok": not failures, "failures": failures}


  rubric_middleware = RubricMiddleware(
      model="ollama:north-mini-code-1.0",
      system_prompt="You are a code reviewer grading generated code against a rubric.",
      tools=[run_test_suite],
      max_iterations=5,
  )
  ```
</CodeGroup>
에이전트의 `system_prompt`는 작업을 어떻게 수행할지 알려주고, 루브릭은 작업을 어떻게 판단할지 채점자에게 알려줍니다.
<CodeGroup>
  ```python Google theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  from deepagents import create_deep_agent
  from langgraph.checkpoint.memory import InMemorySaver

  agent = create_deep_agent(
      model="google_genai:gemini-3.6-flash",
      system_prompt=(
          "You are a careful Python engineer. Write correct, readable code. "
          "Follow the user's instructions exactly."
      ),
      middleware=[rubric_middleware],
      checkpointer=InMemorySaver(),
  )
  ```

  ```python OpenAI theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  from deepagents import create_deep_agent
  from langgraph.checkpoint.memory import InMemorySaver

  agent = create_deep_agent(
      model="openai:gpt-5.5",
      system_prompt=(
          "You are a careful Python engineer. Write correct, readable code. "
          "Follow the user's instructions exactly."
      ),
      middleware=[rubric_middleware],
      checkpointer=InMemorySaver(),
  )
  ```

  ```python Anthropic theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  from deepagents import create_deep_agent
  from langgraph.checkpoint.memory import InMemorySaver

  agent = create_deep_agent(
      model="anthropic:claude-sonnet-5",
      system_prompt=(
          "You are a careful Python engineer. Write correct, readable code. "
          "Follow the user's instructions exactly."
      ),
      middleware=[rubric_middleware],
      checkpointer=InMemorySaver(),
  )
  ```

  ```python OpenRouter theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  from deepagents import create_deep_agent
  from langgraph.checkpoint.memory import InMemorySaver

  agent = create_deep_agent(
      model="openrouter:z-ai/glm-5.2",
      system_prompt=(
          "You are a careful Python engineer. Write correct, readable code. "
          "Follow the user's instructions exactly."
      ),
      middleware=[rubric_middleware],
      checkpointer=InMemorySaver(),
  )
  ```

  ```python Fireworks theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  from deepagents import create_deep_agent
  from langgraph.checkpoint.memory import InMemorySaver

  agent = create_deep_agent(
      model="fireworks:accounts/fireworks/models/glm-5p2",
      system_prompt=(
          "You are a careful Python engineer. Write correct, readable code. "
          "Follow the user's instructions exactly."
      ),
      middleware=[rubric_middleware],
      checkpointer=InMemorySaver(),
  )
  ```

  ```python Baseten theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  from deepagents import create_deep_agent
  from langgraph.checkpoint.memory import InMemorySaver

  agent = create_deep_agent(
      model="baseten:zai-org/GLM-5.2",
      system_prompt=(
          "You are a careful Python engineer. Write correct, readable code. "
          "Follow the user's instructions exactly."
      ),
      middleware=[rubric_middleware],
      checkpointer=InMemorySaver(),
  )
  ```

  ```python Ollama theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  from deepagents import create_deep_agent
  from langgraph.checkpoint.memory import InMemorySaver

  agent = create_deep_agent(
      model="ollama:north-mini-code-1.0",
      system_prompt=(
          "You are a careful Python engineer. Write correct, readable code. "
          "Follow the user's instructions exactly."
      ),
      middleware=[rubric_middleware],
      checkpointer=InMemorySaver(),
  )
  ```
</CodeGroup>
호출 시점에 `messages`에 사용자 요청을, 채점자가 충족됐다고 표시해야 하는 줄바꿈으로 구분된 체크리스트를 `rubric`에 제공하세요. 입력 상태에 `rubric`이 없으면 미들웨어는 실행되지 않습니다.
```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
from langchain.messages import HumanMessage

result = agent.invoke(
    {
        "messages": [
            HumanMessage(
                content=(
                    "Write a Python function `find_duplicates(lst)` that returns a list of "
                    "all elements that appear more than once in the input list, in the order "
                    "they first appear."
                )
            )
        ],
        "rubric": (
            "- All tests pass in run_test_suite\n"
            "- The function is named `find_duplicates` and accepts a single list argument\n"
        ),
    },
    config={"configurable": {"thread_id": "code-generation-session"}},
)
print(result["messages"][-1].text)
```

에이전트가 출력을 만든 뒤 채점자가 개입해 각 기준에 대해 출력을 확인합니다. 예를 들어 입력에 해시 불가능한 유형이 포함될 때 test_unhashableTypeError로 실패하는지 같은 것입니다. 문제가 있으면 채점자가 이 피드백을 제공하고 에이전트는 구현을 수정해 채점자에게 다시 반환합니다.

더 알아보기