Home > EulerAgent > Tutorials > Graph > Graph 03. Judge Routing in Graph

Graph 03. Judge Routing in Graph

Learning Objectives

After completing this tutorial, you will be able to:


Prerequisites

mkdir -p examples/graphs/loops

# 이전 튜토리얼 완료 확인
euleragent graph validate examples/graphs/linear/my_linear.yaml

Judge Routing Fundamentals

A Judge node evaluates the output quality of the preceding node and decides the next step. Both Pattern and Graph support judge routing, but in Graph this routing compiles to LangGraph's add_conditional_edges API.

Judge node execution flow:
  1. LLM evaluates the previous node's output using the evaluator_v1 schema
  2. Extracts the route value from the evaluation result (e.g., "finalize" or "revise")
  3. Sets state["route"] = "finalize" (updates LangGraph shared state)
  4. add_conditional_edges' path_function returns state["route"]
  5. Proceeds to the next node according to the path_map

Step-by-Step Hands-On

Step 1: Write a 2-Route Judge Pattern

Write the most common 2-route judge (finalize or revise).

# examples/graphs/loops/judge_2route.yaml
id: graph.judge_2route
version: 1
category: demo
description: 2-route judge — finalize 또는 revise

defaults:
  max_iterations: 3
  max_total_tool_calls: 20

nodes:
  - id: draft
    kind: llm
    runner:
      mode: execute
    artifacts:
      primary: output.md

  - id: evaluate
    kind: judge
    judge:
      schema: evaluator_v1
      route_values: [finalize, revise]   # 2개 route

  - id: revise
    kind: llm
    runner:
      mode: execute
      max_loops: 2
    artifacts:
      primary: output.md

edges:
  - from: draft
    to: evaluate
    when: "true"
  - from: evaluate
    to: finalize
    when: "judge.route == finalize"
  - from: evaluate
    to: revise
    when: "judge.route == revise"
  - from: revise
    to: evaluate
    when: "true"

finalize:
  artifact: output.md
euleragent graph validate examples/graphs/loops/judge_2route.yaml

Expected output:

단계 2/3: Pattern 기본 검증...
  [✓] judge 노드 route_values 커버리지
        evaluate: route_values=[finalize, revise]
        엣지: judge.route==finalize → finalize ✓
        엣지: judge.route==revise → revise ✓
  완료
결과: 유효 ✓

Step 2: Write a 3-Route Judge Pattern

Write a more granular 3-route judge (finalize / revise / escalate).

# examples/graphs/loops/judge_3route.yaml
id: graph.judge_3route
version: 1
category: demo
description: 3-route judge — finalize, revise, 또는 escalate

defaults:
  max_iterations: 4
  max_total_tool_calls: 30

nodes:
  - id: draft
    kind: llm
    runner:
      mode: execute
    artifacts:
      primary: output.md

  - id: evaluate
    kind: judge
    judge:
      schema: evaluator_v1
      route_values: [finalize, revise, escalate]   # 3개 route

  - id: revise
    kind: llm
    runner:
      mode: execute
    artifacts:
      primary: output.md

  - id: escalate
    kind: llm
    runner:
      mode: plan             # 에스컬레이션은 HITL 승인 필요
      force_tool: web.search
    artifacts:
      primary: escalation_report.md

edges:
  - from: draft
    to: evaluate
    when: "true"
  - from: evaluate
    to: finalize
    when: "judge.route == finalize"
  - from: evaluate
    to: revise
    when: "judge.route == revise"
  - from: evaluate
    to: escalate
    when: "judge.route == escalate"
  - from: revise
    to: evaluate
    when: "true"
  - from: escalate
    to: evaluate
    when: "approvals_resolved"

finalize:
  artifact: output.md
euleragent graph validate examples/graphs/loops/judge_3route.yaml

Expected output:

단계 2/3: Pattern 기본 검증...
  [✓] judge 노드 route_values 커버리지
        evaluate: route_values=[finalize, revise, escalate]
        엣지: judge.route==finalize → finalize ✓
        엣지: judge.route==revise → revise ✓
        엣지: judge.route==escalate → escalate ✓
  완료
결과: 유효 ✓

Step 3: Intentionally Trigger JUDGE_ROUTE_COVERAGE_ERROR

Verify the error that occurs when an edge is missing for a route declared in route_values.

# examples/graphs/loops/judge_broken.yaml
id: graph.judge_broken
version: 1
category: demo
description: 의도적으로 broken된 judge — 에러 학습용

defaults:
  max_iterations: 3

nodes:
  - id: draft
    kind: llm
    runner:
      mode: execute

  - id: evaluate
    kind: judge
    judge:
      schema: evaluator_v1
      route_values: [finalize, revise, escalate]  # 3개 선언

edges:
  - from: draft
    to: evaluate
    when: "true"
  - from: evaluate
    to: finalize
    when: "judge.route == finalize"
  - from: evaluate
    to: revise
    when: "judge.route == revise"
  # escalate 엣지 없음! ← 의도적 오류

finalize:
  artifact: output.md
euleragent graph validate examples/graphs/loops/judge_broken.yaml

Expected output:

단계 2/3: Pattern 기본 검증...
  [✗] judge 노드 route_values 커버리지
        evaluate: route_values=[finalize, revise, escalate]
        엣지: judge.route==finalize → finalize ✓
        엣지: judge.route==revise → revise ✓
        엣지: judge.route==escalate → ✗ (엣지 없음!)

오류: JUDGE_ROUTE_COVERAGE_ERROR
  노드 'evaluate'의 route_values에 'escalate'가 선언되어 있지만
  'judge.route == escalate' 조건의 엣지가 없습니다.

  해결 방법:
  1. 'escalate' 목적지 노드와 엣지를 추가하세요:
       - id: escalate
         kind: llm
         ...
       edges:
         - from: evaluate
           to: escalate
           when: "judge.route == escalate"

  2. 또는 route_values에서 'escalate'를 제거하세요:
       route_values: [finalize, revise]

결과: 유효하지 않음 (오류 1개)

This error proactively prevents a situation where the judge returns "escalate" at runtime but there is no destination to route to.

Step 4: Inspect the Judge AST via graph compile

Compile a valid 2-route judge and examine the judge representation in the IR.

euleragent graph compile examples/graphs/loops/judge_2route.yaml \
  --out /tmp/judge_2route_ir.json

python -m json.tool /tmp/judge_2route_ir.json | grep -A 30 '"judge"'

Expected output (IR excerpt):

{
  "id": "evaluate",
  "kind": "judge",
  "judge": {
    "schema": "evaluator_v1",
    "route_values": ["finalize", "revise"],
    "ast": {
      "type": "conditional_router",
      "source_node": "evaluate",
      "state_key": "route",
      "branches": [
        {
          "route_value": "finalize",
          "target_node": "finalize",
          "condition": "judge.route == finalize"
        },
        {
          "route_value": "revise",
          "target_node": "revise",
          "condition": "judge.route == revise"
        }
      ]
    }
  }
}

The "ast" section is the Abstract Syntax Tree of the judge routing. This AST is converted into add_conditional_edges during LangGraph compilation.

Also inspect the langgraph_builder section of the IR.

python -m json.tool /tmp/judge_2route_ir.json | \
  python -c "import sys,json; d=json.load(sys.stdin); \
  print(json.dumps(d['langgraph_builder']['add_conditional_edges'], indent=2))"

Expected output:

[
  {
    "source": "evaluate",
    "path_function": "route_evaluate",
    "path_map": {
      "finalize": "finalize",
      "revise": "revise"
    }
  }
]

Step 5: Understanding Compilation to LangGraph conditional_edges

The add_conditional_edges information in the IR is materialized into the following LangGraph code at runtime.

# euleragent가 내부적으로 생성하는 개념적 LangGraph 코드

from langgraph.graph import StateGraph, END
from typing import TypedDict

class GraphState(TypedDict):
    route: str    # judge가 설정하는 라우팅 값

def route_evaluate(state: GraphState) -> str:
    """judge가 state["route"]에 설정한 값으로 분기 결정"""
    return state.get("route", "finalize")

# StateGraph 생성 및 노드 추가
graph = StateGraph(GraphState)
graph.add_node("draft", draft_fn)
graph.add_node("evaluate", evaluate_fn)
graph.add_node("revise", revise_fn)

# 일반 엣지
graph.add_edge("draft", "evaluate")
graph.add_edge("revise", "evaluate")

# judge 라우팅 → conditional_edges
graph.add_conditional_edges(
    "evaluate",           # 소스 노드
    route_evaluate,       # 경로 결정 함수
    {
        "finalize": END,  # finalize는 종료 노드
        "revise": "revise"
    }
)

graph.set_entry_point("draft")
compiled = graph.compile()

Key points: - state["route"] is set when the evaluate node (judge) executes - The route_evaluate function reads this value to determine the next node - path_map maps each route_value to a node name

Step 6: Inspect the 3-Route Judge IR

Also compile the 3-route judge for comparison.

euleragent graph compile examples/graphs/loops/judge_3route.yaml \
  --out /tmp/judge_3route_ir.json

python -m json.tool /tmp/judge_3route_ir.json | \
  python -c "import sys,json; d=json.load(sys.stdin); \
  print(json.dumps(d['langgraph_builder']['add_conditional_edges'], indent=2))"

Expected output:

[
  {
    "source": "evaluate",
    "path_function": "route_evaluate",
    "path_map": {
      "finalize": "finalize",
      "revise": "revise",
      "escalate": "escalate"
    }
  }
]

The only difference between 2-route and 3-route is one additional entry in the path_map. LangGraph supports an arbitrary number of routes.


Expected Output Summary

Command Expected Result
graph validate judge_2route.yaml Valid
graph validate judge_3route.yaml Valid
graph validate judge_broken.yaml JUDGE_ROUTE_COVERAGE_ERROR
graph compile judge_2route.yaml Verify add_conditional_edges in IR
graph compile judge_3route.yaml Verify 3 entries in path_map

Key Concepts Summary

Concept Description
route_values List of routing values that a judge node can return
judge.route == X Edge when condition -- when the judge selects X
state["route"] The route key in the LangGraph shared state
add_conditional_edges LangGraph API that implements judge routing
path_function Function that reads state["route"] and returns the next node
path_map Mapping from route_value to next node name
JUDGE_ROUTE_COVERAGE_ERROR Missing edge for a route_values entry

Pattern vs Graph: Differences in Judge Processing

Aspect Pattern Graph
Judge declaration Identical Identical
route_values processing Runtime condition evaluation Compiled to add_conditional_edges
Route value storage Internal execution context state["route"] (shared state)
Runtime branch decision Sequential execution engine LangGraph path_function
Validation method Pattern validator Graph validator (same rules)

The behavior of judge routing in Graph is completely identical to Pattern. The only difference is in the internal implementation.


Common Errors

Error 1: Typo in when Condition

edges:
  - from: evaluate
    to: finalize
    when: "judge.route == Finalize"  # ← 대문자 F — route_values와 불일치
오류: JUDGE_ROUTE_COVERAGE_ERROR
  'Finalize'에 대한 엣지가 있지만 route_values=['finalize', 'revise']에
  'Finalize'가 없습니다. route_values는 소문자를 권장합니다.
해결: when: "judge.route == finalize" (소문자로 통일)

Error 2: Multiple Edges for the Same Route

edges:
  - from: evaluate
    to: finalize
    when: "judge.route == finalize"
  - from: evaluate
    to: publish   # ← 같은 route에 두 번째 엣지
    when: "judge.route == finalize"
오류: JUDGE_ROUTE_DUPLICATE_EDGE
  'evaluate' → route 'finalize'에 대한 엣지가 2개입니다:
    finalize, publish
  하나의 route는 하나의 목적지만 허용됩니다.

Error 3: Declaring runner on a Judge Node

- id: evaluate
  kind: judge
  runner:           # ← judge 노드에 runner는 불필요/무시됨
    mode: execute
  judge:
    schema: evaluator_v1
    route_values: [finalize, revise]
경고: JUDGE_RUNNER_IGNORED
  judge 노드 'evaluate'에 runner가 선언되어 있습니다.
  judge 노드는 자체 실행 방식이 있으므로 runner는 무시됩니다.

Error 4: All Edges Have the Same when Condition

edges:
  - from: evaluate
    to: finalize
    when: "true"   # ← judge.route 조건 없이 항상 진행
  - from: evaluate
    to: revise
    when: "true"   # ← 둘 다 "true" — 어디로 가야 할지 모름
오류: AMBIGUOUS_EDGE_CONDITION
  노드 'evaluate'에서 나가는 엣지가 2개 있는데 모두 when="true"입니다.
  judge 노드의 엣지는 judge.route 조건을 사용해야 합니다.

Practice Exercises

Exercise 1: Write a 4-Route Judge

Write a judge pattern with the following 4 routes.

# 작성 후 검증
euleragent graph validate examples/graphs/loops/judge_4route.yaml
euleragent graph compile examples/graphs/loops/judge_4route.yaml

Exercise 2: Two Judge Nodes

Write a pattern that uses two independent judge nodes in a single graph.

draft → quality_judge → [low_quality: revise] → quality_judge
                       → [high_quality: safety_judge]
                         → [unsafe: sanitize] → safety_judge
                         → [safe: finalize]

Previous: 02_linear_graph.md | Next: 04_bounded_loop.md

← Prev Back to List Next →