Graph 03. Judge Routing in Graph
Learning Objectives
After completing this tutorial, you will be able to:
- Write a 2-route judge pattern in Graph YAML.
- Write a 3-route judge pattern in Graph YAML.
- Intentionally trigger a
JUDGE_ROUTE_COVERAGE_ERRORand fix it. - Verify how the judge AST is represented in the IR via
graph compile. - Understand that judge routing compiles to LangGraph's
add_conditional_edges. - Explain the principle that branching is determined at runtime by the
state["route"]value.
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.
excellent-- finalize immediatelygood-- minor polish then finalizeneeds_work-- full revise then re-evaluatereject-- re-draft from scratch
# 작성 후 검증
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.
quality_judge: Quality assessment -- high_quality / low_qualitysafety_judge: Safety assessment -- safe / unsafe
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