> EulerAgent > 튜토리얼 > 패턴 > 다중 경로 Judge

패턴 07. 다중 경로 Judge — 3개 이상의 루트 처리

학습 목표

이 튜토리얼을 마치면 다음을 할 수 있습니다.

사전 준비

euleragent agent list
euleragent pattern list

1. 왜 3-way 라우팅이 필요한가?

4 튜토리얼의 Judge는 이진(binary) 결정이었습니다: finalize 또는 revise. 하지만 실제 비즈니스 프로세스는 종종 더 복잡합니다.

PR 코드 리뷰 시나리오: - approve: 코드가 좋음, 바로 머지 - request_changes: 수정 필요, 개발자에게 피드백 전달 - reject: 근본적인 문제, 재설계 필요

문서 검토 시나리오: - publish: 즉시 발행 - minor_edit: 가벼운 수정 후 발행 - major_revision: 전면 수정 필요

지원 티켓 처리 시나리오: - resolve: 해결 완료 - escalate: 다음 레벨로 에스컬레이션 - reject: 지원 범위 밖, 거절


2. 다중 라우트의 규칙

규칙 1: 모든 route_values는 엣지를 가져야 한다

judge:
  route_values: [approve, request_changes, reject]

edges:
  - from: evaluate
    to: finalize
    when: "judge.route == approve"       # approve 커버

  - from: evaluate
    to: revise
    when: "judge.route == request_changes"  # request_changes 커버

  - from: evaluate
    to: finalize          # 같은 목적지도 가능!
    when: "judge.route == reject"        # reject 커버

규칙 2: 여러 route_value가 같은 목적지로 갈 수 있다

approvereject 모두 finalize로 가는 패턴도 유효합니다. (approve는 성공으로 완료, reject는 즉시 종료로 완료)

규칙 3: JUDGE_DEAD_END 방지

Judge 노드에서 출발하는 모든 경로가 결국 finalize에 도달해야 합니다. 도달 불가능한 분기가 있으면 JUDGE_DEAD_END 에러가 발생합니다.


3. PR 리뷰 패턴 설계

PR 코드를 분석하고, 3-way로 평가하는 패턴:

approve           → finalize (PR 승인, 머지 안내)
request_changes   → revise → PR 수정 → evaluate (루프)
reject            → finalize (PR 거절, 재설계 안내)

완전한 흐름:

┌─────────────────────────────────────────────────────────────────┐
│ code.pr_review 패턴 흐름도                                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  [analyze]                                                      │
│     │ PR 코드 분석 (llm/execute)                                  │
│     │ when: true                                                │
│     ▼                                                           │
│  [evaluate] ─────── when: judge.route == approve ───────────────┐
│     │ 3-way 평가 (judge/evaluator_v1)                            │
│     ├─────── when: judge.route == reject ────────────────────────┤
│     │ when: judge.route == request_changes                      │
│     ▼                                                           │
│  [draft_feedback]                                               │
│     │ 수정 요청 피드백 작성 (llm/execute)                          │
│     │ when: true                                                │
│     ▼                                                           │
│  [apply_changes]                                                │
│     │ 수정 사항 적용 (llm/execute)                                │
│     │ when: true                                                │
│     └────────────────────────► [evaluate] (루프 최대 3회)         │
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │ [FINALIZE]  review_result.md 저장                        │◄──┘
│  └──────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

4. YAML 작성

pr_review.yaml 파일을 생성합니다.

id: code.pr_review
version: 1
category: code
description: "PR 코드 리뷰 — 승인/수정요청/거절 3-way Judge 라우팅"

defaults:
  # request_changes 루프 최대 3회
  max_iterations: 3
  max_total_tool_calls: 20
  pass_threshold: 0.85

nodes:
  # ── 노드 1: analyze ──
  - id: analyze
    kind: llm
    runner:
      mode: execute
      # PR 분석은 코드 읽기만 허용
      exclude_tools: [web.search, web.fetch, shell.exec]
    prompt:
      system_append: |
        당신은 시니어 코드 리뷰어입니다.
        제공된 PR(Pull Request)을 분석하세요.

        분석 항목:
        1. 코드 변경 범위 및 목적
        2. 설계 패턴 준수 여부
        3. 잠재적 버그 또는 엣지 케이스
        4. 테스트 커버리지
        5. 성능 영향
        6. 보안 고려사항

        출력: 구조화된 분석 보고서 (마크다운)
    artifacts:
      primary: analysis.md

  # ── 노드 2: evaluate (3-way Judge) ──
  - id: evaluate
    kind: judge
    judge:
      schema: evaluator_v1
      # 3개의 route_values — 모두 엣지가 필요!
      route_values: [approve, request_changes, reject]
    prompt:
      system_append: |
        당신은 테크 리드입니다. PR 분석을 바탕으로 최종 결정을 내리세요.

        결정 기준:
        - 'approve' (score >= 0.85):
          * 코드 품질이 높고 버그 없음
          * 테스트 충분
          * 설계 원칙 준수
          → 즉시 머지 가능

        - 'request_changes' (score 0.55-0.84):
          * 수정 가능한 문제가 있음
          * 구체적인 피드백으로 개선 가능
          * 전면 재설계는 불필요
          → 수정 후 재검토

        - 'reject' (score < 0.55):
          * 근본적인 설계 문제
          * 수정으로 해결 불가
          * 재설계 또는 다른 접근법 필요
          → 이번 PR 종료, 재시작

        중요: suggestions에 구체적이고 실행 가능한 피드백을 작성하세요.

  # ── 노드 3: draft_feedback ──
  # request_changes 라우트에서만 실행
  - id: draft_feedback
    kind: llm
    runner:
      mode: execute
      exclude_tools: [web.search, web.fetch]
    prompt:
      system_append: |
        코드 리뷰 피드백 문서를 작성하세요.
        Judge의 suggestions를 바탕으로 개발자가 이해하기 쉬운
        구체적인 수정 요청사항을 작성하세요.

        형식:
        ## 필수 수정사항 (must fix)
        ## 권장 수정사항 (should fix)
        ## 선택 개선사항 (nice to have)

        각 항목에 코드 예시나 참고 링크를 포함하세요.
    artifacts:
      primary: feedback.md

  # ── 노드 4: apply_changes ──
  - id: apply_changes
    kind: llm
    runner:
      mode: execute
      exclude_tools: [web.search, web.fetch]
    prompt:
      system_append: |
        피드백(feedback.md)을 반영하여 코드를 수정하세요.
        수정된 코드와 변경 사항 요약을 작성하세요.

        출력:
        1. 수정된 코드 파일 (코드 블록 포함)
        2. 변경사항 요약 (무엇을, 왜 변경했는지)
    artifacts:
      primary: revised_code.md

edges:
  # 분석 완료 후 평가
  - from: analyze
    to: evaluate
    when: "true"

  # ─── 3-way Judge 라우팅 ───

  # approve → finalize (성공 완료)
  - from: evaluate
    to: finalize
    when: "judge.route == approve"

  # request_changes → 피드백 작성 → 수정 → 재평가
  - from: evaluate
    to: draft_feedback
    when: "judge.route == request_changes"

  # reject → finalize (거절 완료)
  # 같은 목적지(finalize)이지만 다른 라우트 — 유효한 패턴
  - from: evaluate
    to: finalize
    when: "judge.route == reject"

  # request_changes 루프 연결
  - from: draft_feedback
    to: apply_changes
    when: "true"

  - from: apply_changes
    to: evaluate
    when: "true"

finalize:
  artifact: review_result.md

5. 검증

euleragent pattern validate pr_review.yaml

예상 출력:

Validating pattern: pr_review.yaml

  Stage 1 (Schema)      PASS
  Stage 2 (Structural)  PASS
    Judge node 'evaluate': route_values coverage check
      approve          → finalize (when: judge.route == approve) ✓
      request_changes  → draft_feedback (when: judge.route == request_changes) ✓
      reject           → finalize (when: judge.route == reject) ✓
    All 3 route_values covered ✓
  Stage 3 (IR Analysis) PASS
    Cycle: evaluate → draft_feedback → apply_changes → evaluate (bounded: max_iterations=3) ✓
    Dead-end check: all paths reach finalize ✓
      approve path: evaluate → finalize ✓
      request_changes path: evaluate → draft_feedback → apply_changes → evaluate → ... → finalize ✓
      reject path: evaluate → finalize ✓

Validation complete: 0 errors, 0 warnings

6. 컴파일 — 라우트 커버리지 확인

euleragent pattern compile pr_review.yaml

컴파일 출력에서 Judge의 route_coverage 확인:

{
  "nodes": {
    "evaluate": {
      "kind": "judge",
      "judge": {
        "route_values": ["approve", "request_changes", "reject"],
        "route_coverage": {
          "approve": {
            "covered": true,
            "destination": "finalize",
            "path_to_finalize": ["finalize"]
          },
          "request_changes": {
            "covered": true,
            "destination": "draft_feedback",
            "path_to_finalize": ["draft_feedback", "apply_changes", "evaluate", "...finalize"]
          },
          "reject": {
            "covered": true,
            "destination": "finalize",
            "path_to_finalize": ["finalize"]
          }
        },
        "all_paths_reach_finalize": true
      }
    }
  }
}

7. 실행 시뮬레이션

시나리오 A: approve 경로

cp pr_review.yaml .euleragent/patterns/

euleragent pattern run code.pr_review my-agent \
  --task "PR #142: 사용자 인증 미들웨어 리팩토링 — JWT 토큰 검증 로직 개선, 테스트 12개 추가" \
  --project default

예상 출력 (approve 경로):

[run:j1a4f8e2] Starting pattern: code.pr_review

  ✓ analyze      Completed (9s)
                 변경: 3개 파일, +247/-89 lines
                 테스트: 12개 추가, 커버리지 87% → 94%
  ✓ evaluate     Completed (6s)
                 score: 0.91 → route: approve
                 reason: "코드 품질 우수, 테스트 충분, JWT 보안 처리 올바름"
  ✓ finalize     Completed

Review result: APPROVED
Artifact: .euleragent/runs/j1a4f8e2/artifacts/review_result.md

시나리오 B: request_changes 경로

euleragent pattern run code.pr_review my-agent \
  --task "PR #143: 결제 처리 모듈 추가 — Stripe 통합, 에러 핸들링 부재" \
  --project default

예상 출력 (request_changes 경로):

[run:k2b5g9f3] Starting pattern: code.pr_review

  ✓ analyze      Completed (11s)
                 문제 발견: 에러 핸들링 부재, 트랜잭션 롤백 미구현
  ✓ evaluate     Completed (7s)
                 score: 0.62 → route: request_changes
                 suggestions:
                   - "결제 실패 시 롤백 로직 추가 (Stripe.error.CardError 처리)"
                   - "멱등성 키(idempotency key) 구현 — 중복 결제 방지"
                   - "단위 테스트 추가 (최소 실패 케이스 5개)"
  ✓ draft_feedback  Completed (8s) — feedback.md 작성
  ✓ apply_changes   Completed (14s) — 에러 핸들링 추가, 테스트 작성
  ✓ evaluate     Completed (6s)
                 score: 0.88 → route: approve
  ✓ finalize     Completed

Review result: APPROVED (after 1 revision)
Artifact: .euleragent/runs/k2b5g9f3/artifacts/review_result.md

시나리오 C: reject 경로

euleragent pattern run code.pr_review my-agent \
  --task "PR #144: 전체 인증 시스템 재작성 — 설계 문서 없음, 기존 API 호환성 파괴" \
  --project default

예상 출력 (reject 경로):

[run:l3c6h0g4] Starting pattern: code.pr_review

  ✓ analyze      Completed (10s)
                 심각: 기존 API 34개와 하위 호환성 파괴
  ✓ evaluate     Completed (8s)
                 score: 0.31 → route: reject
                 reason: "근본적인 설계 문제 — 하위 호환성 파괴, 마이그레이션 경로 없음"
  ✓ finalize     Completed

Review result: REJECTED
Artifact: .euleragent/runs/l3c6h0g4/artifacts/review_result.md

reject 경로는 draft_feedback, apply_changes 노드를 거치지 않고 즉시 finalize로 이동합니다.


8. JUDGE_ROUTE_COVERAGE_ERROR 의도적으로 발생시키기

reject 라우트에 대한 엣지를 제거해봅니다.

edges:
  - from: evaluate
    to: finalize
    when: "judge.route == approve"

  - from: evaluate
    to: draft_feedback
    when: "judge.route == request_changes"

  # reject 엣지 제거!

  - from: draft_feedback
    to: apply_changes
    when: "true"

  - from: apply_changes
    to: evaluate
    when: "true"

검증:

euleragent pattern validate pr_review_broken.yaml

예상 출력:

  Stage 2 (Structural)  FAIL

  ERROR [JUDGE_ROUTE_COVERAGE_ERROR]
    Node 'evaluate': route_value 'reject' has no outgoing edge.
    All values in route_values must have a corresponding edge
    with matching 'when: "judge.route == reject"' condition.

    route_values declared: [approve, request_changes, reject]
    route_values covered:  [approve, request_changes]
    route_values missing:  [reject]

    Fix: Add an edge from 'evaluate' for route 'reject'.

9. JUDGE_DEAD_END 이해하기

JUDGE_DEAD_END는 특정 라우트의 경로가 finalize에 도달하지 못할 때 발생합니다.

# 잘못된 예 — JUDGE_DEAD_END
nodes:
  - id: evaluate
    kind: judge
    judge:
      route_values: [approve, request_changes, reject]

  - id: dead_end_node    # finalize로 가는 엣지가 없는 노드

edges:
  - from: evaluate
    to: finalize
    when: "judge.route == approve"

  - from: evaluate
    to: draft_feedback
    when: "judge.route == request_changes"

  - from: evaluate
    to: dead_end_node    # 이 노드는 finalize에 도달 불가!
    when: "judge.route == reject"

  - from: draft_feedback
    to: evaluate
    when: "true"

  # dead_end_node에서 다른 노드로 가는 엣지 없음

이 패턴은 JUDGE_ROUTE_COVERAGE_ERROR는 통과하지만 (엣지는 있으므로), Stage 3 IR 분석에서 JUDGE_DEAD_END를 감지합니다.


10. 주요 개념 설명

같은 목적지에 여러 라우트

edges:
  - from: evaluate
    to: finalize
    when: "judge.route == approve"    # 성공 완료

  - from: evaluate
    to: finalize
    when: "judge.route == reject"     # 거절도 완료

이는 완전히 유효한 패턴입니다. 두 경로 모두 finalize로 가지만, review_result.md에 기록되는 내용이 다릅니다 (approve_message vs reject_message). 런타임은 Judge의 reason을 finalize 아티팩트에 포함합니다.

복잡한 분기에서 max_iterations 계산

request_changes 루프 (evaluate → draft_feedback → apply_changes → evaluate)에서 max_iterations: 3이면: - 1회: request_changes → 수정 → 재평가 - 2회: request_changes → 수정 → 재평가 - 3회: request_changes → 수정 → 재평가 - 4회: max_iterations 도달 → 강제 finalize

approvereject 경로는 루프에 포함되지 않으므로 카운트에 영향을 주지 않습니다.

route_values 순서

route_values의 선언 순서는 라우팅에 영향을 주지 않습니다. Judge LLM이 자율적으로 값을 선택합니다. 순서는 문서화 목적으로만 사용합니다.


11. 실습 과제: PR 리뷰 패턴 확장

과제 1: 보안 리뷰 노드 추가

approve 전에 보안 특화 검토를 추가합니다.

evaluate → approve → security_check → finalize
         → request_changes → (루프)
         → reject → finalize

security_check 노드: - kind: llm - 보안 취약점만 집중 검토 - 심각한 보안 문제 발견 시 reject로 다시 보내는 Judge 노드 추가

과제 2: 4-way 라우팅

route_values: [approve, minor_fix, major_revision, reject]

각 라우트 설계: - approve: 즉시 finalize - minor_fix: 작은 수정 → apply_changes → finalize (루프 없음) - major_revision: 전면 수정 → analyze → evaluate (루프) - reject: 즉시 finalize (거절)

검증 후 실행해보세요.


다음 단계

다중 경로 Judge 패턴을 완성했습니다. 이제 보안과 규정 준수를 위한 특수 패턴을 배웁니다.

← 이전: 명시적 인간 검토 게이트 목록으로 다음: 에어갭 패턴과 Ops 설계 →