> EulerAgent > 튜토리얼 > 패턴 > 패턴 09. 고급 패턴 — 이중 Judge, 복합 토폴...

패턴 09. 고급 패턴 — 이중 Judge, 복합 토폴로지, 배포

학습 목표

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

사전 준비

euleragent pattern list
euleragent agent list

1. 이중 Judge 패턴이 필요한 경우

단일 Judge는 하나의 관점에서 평가합니다. 때로는 서로 독립적인 두 가지 관점의 평가가 필요합니다.

블로그 + SEO: 콘텐츠 품질(가독성, 정보성)과 SEO 최적화(키워드, 메타데이터)는 서로 다른 전문성이 필요합니다.

코드 + 보안: 기능 정확성과 보안 취약점은 독립적으로 평가해야 합니다. 기능이 완벽해도 보안 취약점이 있으면 안 됩니다.

문서 + 법률 검토: 기술적 정확성 검토 후 별도의 법률 준수 검토가 필요한 경우.

설계 원칙: 두 Judge는 서로의 루프를 교차하지 않아야 합니다. 독립적인 체인으로 설계합니다.


2. 이중 Judge 아키텍처

직렬 이중 Judge (순차적)

첫 번째 Judge를 통과한 후 두 번째 Judge가 독립적으로 평가합니다.

[draft] → [content_judge] → content_revise → content_judge (루프)
               │ (finalize 선택 시)
               ▼
          [technical_judge] → tech_revise → technical_judge (루프)
               │ (finalize 선택 시)
               ▼
          [FINALIZE]

이 설계의 특징: - content_judge 루프와 technical_judge 루프가 완전히 분리됨 - 콘텐츠가 충분히 좋을 때만 기술 평가로 넘어감 - 각 Judge가 독립적인 max_iterations를 가짐


3. 패턴 설계: 블로그 + SEO 이중 평가

노드 흐름도

┌─────────────────────────────────────────────────────────────────┐
│ writing.dual_judge 패턴 흐름도                                    │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  [draft]                                                        │
│     │ 블로그 초안 작성 (llm/execute)                               │
│     │ when: true                                                │
│     ▼                                                           │
│  ─── STAGE 1: 콘텐츠 평가 사이클 ─────────────────────────────   │
│                                                                 │
│  [content_judge] ── when: judge.route == content_ok ────────────┐
│     │ 콘텐츠 품질 평가 (judge/evaluator_v1)                       │
│     │ routes: [content_ok, content_revise]                      │
│     │ when: judge.route == content_revise                       │
│     ▼                                                           │
│  [content_revise]                                               │
│     │ 콘텐츠 개선 (llm/execute)                                  │
│     └──────────────────────────► [content_judge] (최대 3회)      │
│                                                                 │
│  ─── STAGE 2: SEO 평가 사이클 ─────────────────────────────────  │
│                                                                 │
│  [seo_optimize] ◄────────────────────────────────────────────-──┘
│     │ SEO 최적화 (llm/execute)                                   │
│     │ when: true                                                │
│     ▼                                                           │
│  [seo_judge] ── when: judge.route == seo_ok ────────────────────┐
│     │ SEO 품질 평가 (judge/evaluator_v1)                         │
│     │ routes: [seo_ok, seo_revise]                              │
│     │ when: judge.route == seo_revise                           │
│     ▼                                                           │
│  [seo_revise]                                                   │
│     │ SEO 재최적화 (llm/execute)                                  │
│     └──────────────────────────► [seo_judge] (최대 2회)          │
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │ [FINALIZE]  final_post.md 저장                           │◄──┘
│  └──────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

4. YAML 작성

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

id: writing.dual_judge
version: 1
category: writing
description: "콘텐츠 품질 + SEO 최적화 이중 Judge 블로그 작성 패턴"

defaults:
  # 이중 사이클이 있으므로 충분한 max_iterations
  # content_judge 사이클 최대 3회 + seo_judge 사이클 최대 2회
  # defaults.max_iterations는 전체 사이클 총합에 적용됨
  max_iterations: 5
  max_total_tool_calls: 30
  pass_threshold: 0.85

nodes:
  # ── STAGE 1: 초안 작성 ──

  - id: draft
    kind: llm
    runner:
      mode: execute
      exclude_tools: [web.search, web.fetch, shell.exec]
    prompt:
      system_append: |
        당신은 기술 블로그 작가입니다.
        주제에 대한 완성된 블로그 포스트 초안을 작성하세요.

        요구사항:
        - 길이: 1000-1500 단어
        - 구성: 도입 → 본문(3섹션) → 결론
        - 코드 예시 포함
        - 마크다운 포맷
    artifacts:
      primary: draft.md

  # ── STAGE 1: 콘텐츠 평가 사이클 ──

  - id: content_judge
    kind: judge
    judge:
      schema: evaluator_v1
      # content 전용 route_values — seo_judge와 독립적
      route_values: [content_ok, content_revise]
    prompt:
      system_append: |
        당신은 기술 블로그 편집장입니다.
        다음 기준으로 콘텐츠 품질만 평가하세요. (SEO는 나중에 별도 평가)

        평가 기준:
        - 기술적 정확성 (35%): 정보가 정확하고 최신인가?
        - 독자 가치 (30%): 실용적 통찰과 배움이 있는가?
        - 구성과 흐름 (20%): 논리적으로 연결되는가?
        - 코드 품질 (15%): 예시가 실행 가능하고 명확한가?

        score >= 0.85 → 'content_ok' (SEO 단계로 진행)
        score < 0.85 → 'content_revise' (콘텐츠 개선 필요)

  - id: content_revise
    kind: llm
    runner:
      mode: execute
      exclude_tools: [web.search, web.fetch, shell.exec]
    prompt:
      system_append: |
        편집장의 콘텐츠 피드백을 반영하여 블로그 포스트를 개선하세요.
        SEO는 아직 고려하지 마세요. 콘텐츠 품질에만 집중하세요.
    artifacts:
      primary: draft.md

  # ── STAGE 2: SEO 최적화 + 평가 사이클 ──

  - id: seo_optimize
    kind: llm
    runner:
      mode: execute
      exclude_tools: [web.search, web.fetch, shell.exec]
    prompt:
      system_append: |
        콘텐츠 품질 평가를 통과한 포스트를 SEO 관점에서 최적화하세요.

        SEO 최적화 항목:
        1. 제목 태그: H1에 주요 키워드 포함 (60자 이내)
        2. 메타 설명: 150-160자, 키워드 포함, 클릭 유도
        3. 헤딩 구조: H1→H2→H3 계층 구조
        4. 키워드 밀도: 1-2% (과다 사용 금지)
        5. 내부 링크 제안: [관련 포스트: ...] 형식
        6. 이미지 alt 텍스트 제안 (이미지가 있는 경우)
        7. 포스트 하단에 SEO 메타데이터 섹션 추가:
           ```
           <!-- SEO
           title: ...
           description: ...
           keywords: ...
           -->
           ```
    artifacts:
      primary: seo_draft.md

  - id: seo_judge
    kind: judge
    judge:
      schema: evaluator_v1
      # seo_judge 전용 route_values — content_judge와 독립적
      route_values: [seo_ok, seo_revise]
    prompt:
      system_append: |
        당신은 SEO 전문가입니다.
        콘텐츠 품질은 이미 검증됐습니다. SEO 측면만 평가하세요.

        평가 기준:
        - 키워드 최적화 (30%): 자연스럽게 키워드가 포함됐는가?
        - 제목 최적화 (25%): 클릭률을 높이는 제목인가?
        - 메타 설명 (20%): 검색 결과에서 클릭을 유도하는가?
        - 구조 최적화 (25%): 헤딩 계층, 가독성이 검색 엔진 친화적인가?

        score >= 0.80 → 'seo_ok' (최종 발행 준비 완료)
        score < 0.80 → 'seo_revise' (SEO 재최적화 필요)

  - id: seo_revise
    kind: llm
    runner:
      mode: execute
      exclude_tools: [web.search, web.fetch, shell.exec]
    prompt:
      system_append: |
        SEO 전문가의 피드백을 반영하여 SEO를 재최적화하세요.
        콘텐츠 내용은 변경하지 마세요. SEO 요소만 개선하세요.
    artifacts:
      primary: seo_draft.md

edges:
  # Stage 1: 초안 → 콘텐츠 평가
  - from: draft
    to: content_judge
    when: "true"

  # Stage 1: 콘텐츠 사이클
  - from: content_judge
    to: seo_optimize        # content_ok → Stage 2로 진행
    when: "judge.route == content_ok"

  - from: content_judge
    to: content_revise      # content_revise → 콘텐츠 개선
    when: "judge.route == content_revise"

  - from: content_revise
    to: content_judge       # 콘텐츠 재평가
    when: "true"

  # Stage 2: SEO 사이클
  - from: seo_optimize
    to: seo_judge
    when: "true"

  - from: seo_judge
    to: finalize            # seo_ok → 최종 완료
    when: "judge.route == seo_ok"

  - from: seo_judge
    to: seo_revise          # seo_revise → SEO 재최적화
    when: "judge.route == seo_revise"

  - from: seo_revise
    to: seo_judge           # SEO 재평가
    when: "true"

finalize:
  artifact: seo_draft.md

5. 검증

euleragent pattern validate dual_judge_blog.yaml

예상 출력:

Validating pattern: dual_judge_blog.yaml

  Stage 1 (Schema)      PASS
  Stage 2 (Structural)  PASS
    Judge node 'content_judge':
      content_ok     → seo_optimize ✓
      content_revise → content_revise ✓
    Judge node 'seo_judge':
      seo_ok     → finalize ✓
      seo_revise → seo_revise ✓
    All route_values covered ✓

  Stage 3 (IR Analysis) PASS
    Cycles detected:
      1. content_judge → content_revise → content_judge
         bounded_by: max_iterations=5 ✓
      2. seo_judge → seo_revise → seo_judge
         bounded_by: max_iterations=5 ✓
    Cycles are independent (no intersection) ✓
    Entry node: draft ✓
    All paths reach finalize ✓

Validation complete: 0 errors, 0 warnings

6. 복잡한 토폴로지 설계 시 주의사항

주의사항 1: Entry Node 유일성

진입 노드(수신 엣지 없는 노드)는 반드시 하나여야 합니다.

# 잘못된 예 — MULTIPLE_ENTRY_NODES
nodes:
  - id: draft       # 수신 엣지 없음 ← 진입점 1
  - id: outline     # 수신 엣지 없음 ← 진입점 2

edges:
  - from: draft
    to: content_judge
    when: "true"
  # outline에서 content_judge로 가는 엣지도 있다면?
  # → MULTIPLE_ENTRY_NODES
euleragent pattern validate broken_multi_entry.yaml
ERROR [MULTIPLE_ENTRY_NODES]
  Nodes with no incoming edges: draft, outline
  Only one entry node is allowed.

  Fix: Connect one of these nodes with an incoming edge,
  or merge them into a single entry node.

주의사항 2: 사이클 교차 방지

두 사이클이 노드를 공유하면 복잡한 동작이 발생합니다.

# 잘못된 예 — 사이클 교차
content_judge → content_revise → content_judge  (사이클 1)
seo_judge → content_revise → seo_judge          (사이클 2)
            ↑ content_revise가 두 사이클에 속함!

이런 설계는 max_iterations 카운팅이 복잡해지고, 어느 Judge가 revise를 요청했는지 컨텍스트가 섞입니다. 사이클은 독립적으로 유지하세요.

주의사항 3: FINALIZE_UNREACHABLE

모든 경로가 finalize에 도달해야 합니다.

# 컴파일 출력에서 모든 경로 확인
euleragent pattern compile dual_judge_blog.yaml | \
  python3 -c "
import json, sys
d = json.load(sys.stdin)
for route, info in d.get('route_coverage', {}).items():
    print(f'{route}: reaches_finalize={info[\"reaches_finalize\"]}')
"

주의사항 4: 도달 불가 노드

정의됐지만 어떤 엣지에도 연결되지 않은 노드:

WARNING [UNREACHABLE_NODE]
  Node 'orphan_node' is defined but has no incoming edges
  and is not the entry node. It will never be executed.

이는 WARNING이고 ERROR가 아닙니다. 검증을 통과하지만 실행되지 않습니다.


7. 실행 예시

cp dual_judge_blog.yaml .euleragent/patterns/

euleragent pattern run writing.dual_judge my-agent \
  --task "쿠버네티스 Horizontal Pod Autoscaler(HPA) 완전 가이드" \
  --project default \
  --stream

스트리밍 출력:

[event] pattern.start  {pattern: writing.dual_judge}
[event] node.start     {node: draft}
[event] node.complete  {node: draft, words: 1234}
[event] node.start     {node: content_judge, iteration: 1}
[event] node.complete  {node: content_judge, score: 0.79, route: content_revise}
[event] node.start     {node: content_revise, iteration: 1}
[event] node.complete  {node: content_revise}
[event] node.start     {node: content_judge, iteration: 2}
[event] node.complete  {node: content_judge, score: 0.88, route: content_ok}
[event] edge.traverse  {from: content_judge, to: seo_optimize, reason: "judge.route == content_ok"}
[event] node.start     {node: seo_optimize}
[event] node.complete  {node: seo_optimize}
[event] node.start     {node: seo_judge, iteration: 1}
[event] node.complete  {node: seo_judge, score: 0.91, route: seo_ok}
[event] edge.traverse  {from: seo_judge, to: finalize, reason: "judge.route == seo_ok"}
[event] pattern.complete  {artifact: seo_draft.md}

최종 요약:

[run:n5e8j2k6] Pattern: writing.dual_judge

  ✓ draft            Completed (13s) — 1,234 words
  ✓ content_judge    Completed (7s)  — score: 0.79 → content_revise (iteration 1)
  ✓ content_revise   Completed (15s) — improved
  ✓ content_judge    Completed (6s)  — score: 0.88 → content_ok (iteration 2)
  ✓ seo_optimize     Completed (9s)
  ✓ seo_judge        Completed (5s)  — score: 0.91 → seo_ok (iteration 1)
  ✓ finalize         Completed

Iterations: content_judge×2, seo_judge×1
Artifact: .euleragent/runs/n5e8j2k6/artifacts/seo_draft.md

8. ID 네이밍 컨벤션

패턴 ID는 카테고리.설명자 형식을 권장합니다.

내장 패턴 예시 분석

패턴 ID 카테고리 설명자 의미
report.evidence report evidence 증거 기반 보고서
code.tdd code tdd 테스트 주도 개발
ops.triage ops triage 운영 트리아지
research.broad_to_narrow research broad_to_narrow 광범위→심층 연구

권장 카테고리

카테고리 설명 예시
report 보고서 작성 report.evidence, report.competitor
code 코드 관련 code.tdd, code.pr_review, code.refactor
ops 운영/인프라 ops.triage, ops.incident, ops.deploy
research 조사/분석 research.broad_to_narrow, research.lit_review
writing 문서/콘텐츠 writing.blog, writing.newsletter
security 보안 security.audit, security.threat_model
docs 내부 문서 docs.faq_update, docs.onboarding

잘못된 ID 형식

id: myPattern          # 카멜케이스 — 비권장
id: my_pattern         # 언더스코어 — 비권장
id: MY.PATTERN         # 대문자 — 비권장
id: my.complex.pattern.v2  # 3단계 이상 — 비권장
id: blog               # 카테고리 없음 — 비권장
id: writing.dual_judge  # 권장
id: ops.internal_triage # 권장
id: code.security_audit # 권장

9. 워크스페이스 배포

완성된 패턴을 워크스페이스에 배포합니다.

# 패턴 디렉토리 확인
ls .euleragent/patterns/

# 배포
cp dual_judge_blog.yaml .euleragent/patterns/dual_judge_blog.yaml

# 배포 확인
euleragent pattern list

예상 출력:

Built-in Patterns
─────────────────────────────────────────────────────────
  report.evidence          research   Evidence-based report writing
  code.tdd                 code       Test-driven development workflow
  ops.triage               ops        Operations ticket triage
  research.broad_to_narrow research   Broad-to-narrow research synthesis

Workspace Patterns (.euleragent/patterns/)
─────────────────────────────────────────────────────────
  blog.quality_loop        writing    Judge 노드로 품질 루프를 갖춘 블로그 작성 패턴
  blog.web_research        writing    웹 검색 + Judge 루프가 결합된 고품질 블로그 작성 패턴
  code.pr_review           code       PR 코드 리뷰 — 승인/수정요청/거절 3-way Judge 라우팅
  docs.faq_update          docs       사내 FAQ 업데이트 — 에어갭, 인간 검토 게이트
  my_first.pattern         writing    주제를 입력받아 기술 블로그 포스트를 작성하는 단순 선형 패턴
  ops.internal_triage      ops        에어갭 고객 지원 트리아지
  ops.policy_compliant     ops        내부 정책 로드 → 분류 → 초안 → 정책 준수 검토 게이트
  writing.dual_judge       writing    콘텐츠 품질 + SEO 최적화 이중 Judge 블로그 작성 패턴
  writing.human_review     writing    초안 작성 후 인간 검토 게이트를 통과하는 문서 작성 패턴

13 patterns available (4 built-in + 9 workspace).

배포된 패턴은 ID로 직접 참조 가능합니다:

euleragent pattern validate writing.dual_judge
euleragent pattern show writing.dual_judge
euleragent pattern run writing.dual_judge my-agent --task "..."

10. 실습 과제: 블로그 작성 + SEO 이중 평가 변형

과제: 글로벌 배포 패턴

한국어 원문을 작성하고, 번역 품질과 SEO를 각각 별도로 평가하는 패턴.

[draft_korean] → [translation] → [translation_quality_judge]
                                       │ ok → [seo_optimize_english]
                                       │         → [seo_judge] → finalize
                                       │ revise → [retranslate] → [translation_quality_judge]
id: writing.global_publish
version: 1
category: writing
description: "한국어 원문 작성  영어 번역 품질 평가  SEO 최적화 이중 Judge 패턴"

defaults:
  max_iterations: 4
  max_total_tool_calls: 25

nodes:
  - id: draft_korean
    kind: llm
    runner:
      mode: execute
      exclude_tools: [web.search, web.fetch, shell.exec]
    prompt:
      system_append: |
        한국어로 완성된 기술 블로그 포스트를 작성하세요.
        길이: 800-1000 단어. 전문적이고 명확한 문체.

  - id: translation
    kind: llm
    runner:
      mode: execute
      exclude_tools: [web.search, web.fetch, shell.exec]
    prompt:
      system_append: |
        한국어 블로그 포스트를 영어로 번역하세요.
        직역 대신 자연스러운 영어 표현을 사용하세요.
        기술 용어는 영어 원어를 사용하세요.

  - id: translation_quality_judge
    kind: judge
    judge:
      schema: evaluator_v1
      route_values: [translation_ok, retranslate]
    prompt:
      system_append: |
        영어 번역의 품질을 평가하세요.
        - 자연스러운 영어 표현 (40%)
        - 기술적 정확성 유지 (35%)
        - 문화적 적절성 (25%)

        score >= 0.82 → 'translation_ok'
        score < 0.82 → 'retranslate'

  - id: retranslate
    kind: llm
    runner:
      mode: execute
      exclude_tools: [web.search, web.fetch, shell.exec]
    prompt:
      system_append: |
        번역 품질 피드백을 반영하여 영어 번역을 개선하세요.
        원문의 의미를 유지하면서 더 자연스러운 영어로 수정하세요.

  - id: seo_optimize_english
    kind: llm
    runner:
      mode: execute
      exclude_tools: [web.search, web.fetch, shell.exec]
    prompt:
      system_append: |
        영어 번역 포스트를 영어권 독자를 위한 SEO로 최적화하세요.
        영어 SEO 키워드 전략을 적용하세요.

  - id: seo_judge
    kind: judge
    judge:
      schema: evaluator_v1
      route_values: [seo_ok, seo_revise]
    prompt:
      system_append: |
        영어권 SEO 기준으로 평가하세요.
        score >= 0.78 → 'seo_ok'
        score < 0.78 → 'seo_revise'

  - id: seo_revise
    kind: llm
    runner:
      mode: execute
      exclude_tools: [web.search, web.fetch, shell.exec]
    prompt:
      system_append: SEO 피드백 반영. 콘텐츠 내용 변경 금지.

edges:
  - from: draft_korean
    to: translation
    when: "true"

  - from: translation
    to: translation_quality_judge
    when: "true"

  - from: translation_quality_judge
    to: seo_optimize_english
    when: "judge.route == translation_ok"

  - from: translation_quality_judge
    to: retranslate
    when: "judge.route == retranslate"

  - from: retranslate
    to: translation_quality_judge
    when: "true"

  - from: seo_optimize_english
    to: seo_judge
    when: "true"

  - from: seo_judge
    to: finalize
    when: "judge.route == seo_ok"

  - from: seo_judge
    to: seo_revise
    when: "judge.route == seo_revise"

  - from: seo_revise
    to: seo_judge
    when: "true"

finalize:
  artifact: final_english_post.md

검증:

euleragent pattern validate writing_global_publish.yaml

두 Judge의 route_values가 모두 커버됐는지 확인하세요.


11. 흔한 오류와 해결법

오류 1: 사이클 내에서 max_iterations 공유

이중 Judge 패턴에서 두 사이클이 모두 defaults.max_iterations를 공유합니다. max_iterations: 3으로 설정했다면 content_judge가 3회, seo_judge가 1회를 사용할 수도 있습니다. 충분히 큰 값을 설정하세요.

오류 2: JUDGE_DEAD_END in complex topology

복잡한 그래프에서 특정 Judge 라우트가 finalize에 도달하지 못하는 경우:

euleragent pattern validate complex_pattern.yaml --format json | \
  python3 -c "
import json, sys
result = json.load(sys.stdin)
for err in result['stages']['ir_analysis']['errors']:
    print(err['code'], err['message'])
"

오류 3: 메모리 컨텍스트 혼재

이중 Judge 패턴에서 stage 2(SEO) Judge가 stage 1(content)의 평가 컨텍스트를 받으면 평가가 혼재될 수 있습니다. system_append에서 "SEO만 평가하세요"처럼 명확히 제한하세요.


다음 단계

고급 패턴 설계를 완성했습니다. 이제 모든 필드, 에러 코드, 설계 체크리스트를 담은 완전한 레퍼런스로 넘어갑니다.

← 이전 목록으로 다음 →