> EulerAgent > 튜토리얼 > 패턴 > 패턴 01. 패턴이란 무엇인가? — 개념과 아키텍처

패턴 01. 패턴이란 무엇인가? — 개념과 아키텍처

학습 목표

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

사전 준비

# 환경 확인
euleragent --version
euleragent doctor

1. 패턴이란 무엇인가?

에이전트에게 "보고서 써줘"라고 하면 어떻게 될까요? 에이전트는 한 번에 글을 쓰고 끝낼 수도 있고, 먼저 조사하고 초안을 쓰고 검토하는 단계를 밟을 수도 있습니다. 패턴(Pattern)은 이 작업 흐름을 명시적으로 정의하는 방법입니다.

패턴은 YAML로 정의된 방향성 그래프(Directed Graph)입니다. 노드는 작업 단계이고, 엣지는 단계 간의 전환 조건입니다.

단순 에이전트 요청:
  "보고서 써줘" → (에이전트가 알아서 판단) → 결과

패턴을 사용한 에이전트 요청:
  "보고서 써줘" → [outline] → [gather] → [draft] → [evaluate] → [revise or finalize] → 결과
                                              ↑__________________________|
                                              (평가 점수가 낮으면 재작성)

패턴의 핵심 가치는 다음 세 가지입니다.

예측 가능성: 에이전트가 어떤 단계를 밟는지 사전에 알 수 있습니다. 블랙박스 대신 투명한 워크플로우.

제어: 특정 단계에서 인간 승인을 강제하고, 도구 사용을 제한하고, 반복 횟수를 캡으로 묶을 수 있습니다.

재사용: 한 번 정의한 패턴은 여러 에이전트와 작업에 재사용할 수 있습니다.


2. YAML → IR → 런타임 파이프라인

패턴 YAML 파일은 실행 전에 여러 단계를 거칩니다.

┌─────────────────────────────────────────────────────────────────┐
│                    패턴 처리 파이프라인                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  my_pattern.yaml                                                │
│       │                                                         │
│       ▼                                                         │
│  ┌─────────────┐                                                │
│  │  Stage 1    │  JSON Schema 검증                              │
│  │  Schema     │  - 필수 필드 존재 여부                          │
│  │  Validator  │  - 타입 검증 (string, int, enum)               │
│  └──────┬──────┘                                                │
│         │ 통과                                                   │
│         ▼                                                       │
│  ┌─────────────┐                                                │
│  │  Stage 2    │  YAML 구조적 검증                              │
│  │  YAML       │  - 중복 노드 ID                                │
│  │  Structural │  - 엣지 참조 유효성                             │
│  │  Validator  │  - Judge 라우트 선언                            │
│  └──────┬──────┘                                                │
│         │ 통과                                                   │
│         ▼                                                       │
│  ┌─────────────┐                                                │
│  │  Compiler   │  YAML → IR (Intermediate Representation)      │
│  │             │  JSON 형식의 내부 표현으로 변환                   │
│  └──────┬──────┘                                                │
│         │                                                       │
│         ▼                                                       │
│  ┌─────────────┐                                                │
│  │  Stage 3    │  IR 분석 검증                                   │
│  │  IR         │  - 진입점 유일성                                 │
│  │  Analyzer   │  - 사이클 경계 확인                              │
│  │             │  - 도달 불가 노드                                │
│  │             │  - 예산 일관성                                   │
│  └──────┬──────┘                                                │
│         │ 통과                                                   │
│         ▼                                                       │
│  ┌─────────────┐                                                │
│  │  Runtime    │  IR을 로드해서 에이전트 실행                      │
│  │  Engine     │  - 노드별 LLM 호출                              │
│  │             │  - HITL pause/resume                          │
│  │             │  - 이벤트 스트림                                 │
│  └─────────────┘                                                │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

euleragent pattern validate 명령은 Stage 1, 2, 3을 모두 실행합니다. euleragent pattern compile 명령은 컴파일만 수행하고 IR JSON을 출력합니다.


3. 핵심 구조: 노드와 엣지

노드 타입 3가지

llm 노드: LLM을 호출해서 실제 작업을 수행합니다. 조사, 초안 작성, 코드 생성 등 "생산" 작업에 사용합니다.

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

judge 노드: LLM에게 이전 단계의 결과를 평가하도록 합니다. 평가 결과에 따라 다른 노드로 라우팅합니다.

nodes:
  - id: evaluate
    kind: judge
    judge:
      schema: evaluator_v1
      route_values: [finalize, revise]

finalize 노드: 그래프의 종료 지점입니다. 최종 결과물을 지정된 아티팩트 경로에 저장하고 실행을 완료합니다.

finalize:
  artifact: result.md

주의: finalizenodes 배열 안에 정의하지 않습니다. 최상위 키로 별도 선언합니다.

엣지와 when 조건

엣지는 노드 간의 전환을 정의합니다. when 필드가 전환 조건입니다.

edges:
  - from: draft
    to: evaluate
    when: "true"                            # 항상 전환

  - from: evaluate
    to: finalize
    when: "judge.route == finalize"         # judge 결과가 finalize일 때

  - from: evaluate
    to: revise
    when: "judge.route == revise"           # judge 결과가 revise일 때

  - from: gather
    to: draft
    when: "approvals_resolved"              # HITL 승인이 완료됐을 때

when DSL의 4가지 형식:

형식 예시 사용 시점
"true" when: "true" 항상 전환
"approvals_resolved" when: "approvals_resolved" HITL 승인 완료 후
"judge.route == <값>" when: "judge.route == finalize" Judge 라우팅
"judge.score >= <값>" when: "judge.score >= 0.9" Judge 점수 임계값

4. 진입점과 최종 노드

진입점 (Entry Node)

수신 엣지(incoming edge)가 없는 유일한 노드가 진입점이 됩니다. 패턴 실행 시 가장 먼저 실행되는 노드입니다.

진입점이 없거나 두 개 이상이면 검증 오류가 발생합니다.

올바른 예:
  [outline] → [gather] → [draft] → [evaluate]
       ↑
   진입점 (수신 엣지 없음)

잘못된 예 (진입점 없음):
  [outline] ⇄ [gather]   # 두 노드가 서로를 가리켜서 둘 다 수신 엣지가 있음

잘못된 예 (진입점 둘):
  [outline]                # 수신 엣지 없음 ← 진입점 1
  [gather]   → [draft]     # 수신 엣지 없음 ← 진입점 2

최종 노드 (Finalize)

finalize 키에 선언한 아티팩트 경로가 최종 결과물입니다. 런타임 엔진은 finalize 노드에 도달하면 실행을 종료하고 결과물을 저장합니다.

finalize:
  artifact: result.md    # 최종 결과물 저장 경로

5. Judge 라우팅과 JUDGE_ROUTE_COVERAGE_ERROR

Judge 노드는 route_values에 선언한 값들 중 하나를 선택합니다. 모든 route_values에 대응하는 엣지가 반드시 있어야 합니다. 그렇지 않으면 JUDGE_ROUTE_COVERAGE_ERROR가 발생합니다.

# 올바른 예
nodes:
  - id: evaluate
    kind: judge
    judge:
      schema: evaluator_v1
      route_values: [finalize, revise]   # 두 개 선언

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

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


# 잘못된 예 — JUDGE_ROUTE_COVERAGE_ERROR
nodes:
  - id: evaluate
    kind: judge
    judge:
      schema: evaluator_v1
      route_values: [finalize, revise]   # 두 개 선언

edges:
  - from: evaluate
    to: finalize
    when: "judge.route == finalize"      # finalize만 커버
  # revise에 대한 엣지가 없음! → 에러

6. 사이클 경계와 max_iterations

패턴은 DAG(비순환 방향 그래프)가 아닙니다. 사이클(루프)을 허용합니다. 하지만 무한 루프를 방지하기 위해 사이클이 있으면 반드시 max_iterations를 선언해야 합니다.

defaults:
  max_iterations: 3    # 평가-수정 루프를 최대 3회 반복

max_iterations가 없는 상태에서 사이클이 감지되면 UNBOUNDED_CYCLE 에러가 발생합니다.


7. YAML과 IR의 차이

YAML은 사람이 읽기 좋은 형식입니다. IR(Intermediate Representation)은 런타임 엔진이 읽기 좋은 형식입니다.

주요 변환 내용:

YAML IR
노드 목록 + 엣지 목록 인접 리스트(adjacency list) 형태의 그래프
when: "judge.route == finalize" 파싱된 AST 형태의 조건식
runner.force_tool HITL 플래그 포함된 실행 설정
judge.route_values 유효한 라우트 집합 + 커버리지 맵
defaults 각 노드에 적용된 resolved 설정

8. 단계별 실습

단계 1: 내장 패턴 목록 확인

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

4 patterns available. Use 'euleragent pattern show <id>' for details.

단계 2: 패턴 구조 확인

euleragent pattern show report.evidence

예상 출력:

Pattern: report.evidence
Category: research
Version: 1
Description: Evidence-based report writing with web research and quality loop

Defaults:
  max_iterations: 3
  max_total_tool_calls: 30
  max_web_search_calls: 10
  pass_threshold: 0.85
  dedupe_web_search: true

Nodes:
  [entry] outline     llm      mode=execute
          gather      llm      mode=plan  force_tool=web.search
          draft       llm      mode=execute
          evaluate    judge    schema=evaluator_v1  routes=[finalize, revise]
          revise      llm      mode=execute
  [fin]   finalize    finalize artifact=report.md

Edges:
  outline      → gather      when: true
  gather       → draft       when: approvals_resolved
  draft        → evaluate    when: true
  evaluate     → finalize    when: judge.route == finalize
  evaluate     → revise      when: judge.route == revise
  revise       → evaluate    when: true

Finalize:
  artifact: report.md

단계 3: 패턴 검증

euleragent pattern validate report.evidence

예상 출력:

Validating pattern: report.evidence

  Stage 1 (Schema)      PASS  All required fields present, types valid
  Stage 2 (Structural)  PASS  Nodes, edges, judge routes all consistent
  Stage 3 (IR Analysis) PASS  Entry node unique, cycles bounded, all nodes reachable

Validation complete: 0 errors, 0 warnings

JSON 형식으로 결과 받기:

euleragent pattern validate report.evidence --format json

예상 출력:

{
  "pattern_id": "report.evidence",
  "stages": {
    "schema": { "status": "pass", "errors": [] },
    "structural": { "status": "pass", "errors": [] },
    "ir_analysis": { "status": "pass", "errors": [], "warnings": [] }
  },
  "overall": "pass",
  "error_count": 0,
  "warning_count": 0
}

단계 4: 패턴 컴파일

euleragent pattern compile report.evidence

예상 출력 (IR JSON):

{
  "id": "report.evidence",
  "version": 1,
  "category": "research",
  "entry_node": "outline",
  "nodes": {
    "outline": {
      "id": "outline",
      "kind": "llm",
      "runner": {
        "mode": "execute",
        "force_tool": null,
        "exclude_tools": [],
        "min_proposals": null,
        "max_loops": null
      },
      "resolved_defaults": {
        "max_iterations": 3,
        "max_total_tool_calls": 30
      },
      "outgoing_edges": ["gather"]
    },
    "gather": {
      "id": "gather",
      "kind": "llm",
      "runner": {
        "mode": "plan",
        "force_tool": "web.search",
        "hitl_required": true
      },
      "outgoing_edges": ["draft"],
      "edge_conditions": {
        "draft": { "type": "approvals_resolved" }
      }
    }
  },
  "adjacency": {
    "outline": ["gather"],
    "gather": ["draft"],
    "draft": ["evaluate"],
    "evaluate": ["finalize", "revise"],
    "revise": ["evaluate"]
  },
  "cycles": [
    { "path": ["evaluate", "revise", "evaluate"], "bounded_by": "max_iterations" }
  ],
  "finalize": {
    "artifact": "report.md"
  }
}

9. 주요 개념 설명

YAML vs 에이전트 프롬프트

패턴 없는 에이전트는 시스템 프롬프트와 태스크만으로 동작합니다. 에이전트가 스스로 어떤 단계를 밟을지 결정합니다. 이는 유연하지만 예측이 어렵습니다.

패턴을 사용하면 워크플로우가 YAML에 고정됩니다. 에이전트는 각 노드에서 정해진 역할만 수행합니다. 예측 가능성이 높아지고, 특정 단계에 HITL을 삽입하거나 예산을 제어하기 쉬워집니다.

IR이 필요한 이유

YAML은 사람이 편집하기 좋지만, 런타임이 직접 실행하기에는 적합하지 않습니다. IR은 다음을 사전에 계산해둡니다.

런타임은 YAML을 다시 파싱하지 않고 IR을 직접 로드합니다.

evaluator_v1 스키마

judge.schema: evaluator_v1은 내장 평가 스키마입니다. 이 스키마를 사용하면 Judge 노드는 다음 구조로 LLM에게 평가를 요청합니다.


10. 흔한 오류와 해결법

오류 1: NO_FINALIZE

ERROR [NO_FINALIZE] Pattern has no finalize section

원인: 최상위 finalize: 키가 없습니다.

해결: YAML 최상위에 finalize: 섹션을 추가합니다.

finalize:
  artifact: result.md

오류 2: JUDGE_ROUTE_COVERAGE_ERROR

ERROR [JUDGE_ROUTE_COVERAGE_ERROR] Node 'evaluate': route_value 'revise' has no outgoing edge

원인: route_values에 선언한 값에 대응하는 엣지가 없습니다.

해결: 누락된 route_value에 대한 엣지를 추가합니다.

오류 3: UNBOUNDED_CYCLE

ERROR [UNBOUNDED_CYCLE] Cycle detected: evaluate → revise → evaluate. Set defaults.max_iterations.

원인: 사이클이 있는데 max_iterations가 없습니다.

해결: defaults 섹션에 max_iterations를 추가합니다.

defaults:
  max_iterations: 3

오류 4: MULTIPLE_ENTRY_NODES

ERROR [MULTIPLE_ENTRY_NODES] Multiple nodes with no incoming edges: outline, gather

원인: 두 개 이상의 노드에 수신 엣지가 없습니다.

해결: 진입점이 아닌 노드에 수신 엣지를 추가하거나, 진입점을 하나로 통합합니다.


11. 다음 단계

개념을 이해했으니 실제로 내장 패턴을 실행해보겠습니다.

← 이전 목록으로 다음 →