> EulerAgent > 튜토리얼 > 그래프 > 그래프 02. 선형 그래프 — Pattern과 동일한 ...

그래프 02. 선형 그래프 — Pattern과 동일한 첫 걸음

학습 목표

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


사전 준비

# 작업 디렉터리 생성
mkdir -p examples/graphs/linear

# euleragent 및 graph 모듈 동작 확인
euleragent graph --help

예상 출력:

Usage: euleragent graph [OPTIONS] COMMAND [ARGS]...

  Graph 모듈 — LangGraph 기반 고급 워크플로우 (실험적)

Options:
  --help  Show this message and exit.

Commands:
  compile   Graph YAML을 LangGraph IR로 컴파일
  list      사용 가능한 그래프 목록 조회
  show      그래프 구조 확인
  validate  Graph YAML 유효성 검사

단계별 실습

단계 1: 선형 그래프 YAML 작성

다음 내용으로 examples/graphs/linear/my_linear.yaml 파일을 작성합니다.

# examples/graphs/linear/my_linear.yaml
id: graph.my_linear
version: 1
category: demo
description: 단순 선형 그래프 — research → draft → evaluate → finalize

defaults:
  max_iterations: 2
  max_total_tool_calls: 30
  max_web_search_calls: 10

nodes:
  - id: research
    kind: llm
    runner:
      mode: plan
      force_tool: web.search
      min_proposals: 2
    guardrails:
      tool_call_budget:
        web.search: 3

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

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

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

edges:
  - from: research
    to: draft
    when: "approvals_resolved"
  - 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: report.md

이 YAML의 구조 이해

research (plan mode + web.search HITL)
    ↓ [approvals_resolved: 사람이 승인 후 진행]
  draft (execute mode → report.md 생성)
    ↓ [항상 진행]
 evaluate (judge → finalize or revise)
    ↓ [judge.route == finalize]          [judge.route == revise]
 finalize                                 revise (수정)
                                              ↓ [항상 진행]
                                           evaluate (재평가)

이 구조는 다음 특징을 갖습니다.

단계 2: graph validate 실행

euleragent graph validate examples/graphs/linear/my_linear.yaml

예상 출력:

검증 중: examples/graphs/linear/my_linear.yaml

단계 1/3: YAML 파싱...
  id: graph.my_linear
  version: 1
  노드 수: 4 (research, draft, evaluate, revise)
  엣지 수: 5
  완료

단계 2/3: Pattern 기본 검증...
  [✓] 노드 ID 유일성 검사
  [✓] 엣지 소스/타겟 존재 여부
  [✓] finalize 도달 가능성 (evaluate → finalize 경로 존재)
  [✓] judge 노드 route_values 커버리지
        evaluate: [finalize, revise] ← 엣지와 일치
  [✓] max_iterations 존재 (순환 루프 감지됨)
  완료

단계 3/3: Graph 추가 검증...
  [✓] parallel_groups 없음 — state_schema 불필요
  [✓] interrupt hooks 없음 (선택적 기능)
  완료

결과: 유효 ✓ (오류 없음, 경고 없음)

검증이 3단계로 나뉘어 있음을 확인하세요. 1. YAML 파싱 — 문법 검사 2. Pattern 기본 검증 — 노드/엣지/judge 규칙 검사 3. Graph 추가 검증 — 병렬 규칙, state_schema, interrupt 검사

단계 3: graph show로 구조 확인

euleragent graph show examples/graphs/linear/my_linear.yaml

예상 출력:

그래프: graph.my_linear (v1)
설명: 단순 선형 그래프 — research → draft → evaluate → finalize
카테고리: demo
유형: graph (선형)

노드:
  [llm]     research  — plan mode, force_tool: web.search
  [llm]     draft     — execute mode, artifacts: report.md
  [judge]   evaluate  — evaluator_v1, routes: [finalize, revise]
  [llm]     revise    — execute mode, artifacts: report.md
  [finalize] finalize — artifact: report.md

엣지:
  research  ──[approvals_resolved]──▶  draft
  draft     ──[always]──────────────▶  evaluate
  evaluate  ──[judge.route==finalize]▶ finalize
  evaluate  ──[judge.route==revise]──▶ revise
  revise    ──[always]──────────────▶  evaluate

병렬 그룹: 없음
상태 스키마: 없음 (선형 그래프)
interrupt hooks: 없음

단계 4: graph compile로 IR 생성

euleragent graph compile examples/graphs/linear/my_linear.yaml

예상 출력 (IR JSON 발췌):

{
  "graph_type": "graph",
  "id": "graph.my_linear",
  "version": 1,
  "category": "demo",
  "compiled_at": "2026-02-23T10:15:00Z",
  "langgraph_version": "1.0.9",
  "state_schema": null,
  "parallel_groups": [],
  "defaults": {
    "max_iterations": 2,
    "max_total_tool_calls": 30,
    "max_web_search_calls": 10
  },
  "nodes": [
    {
      "id": "research",
      "kind": "llm",
      "runner": {
        "mode": "plan",
        "force_tool": "web.search",
        "min_proposals": 2
      },
      "interrupt_before": false,
      "interrupt_after": false,
      "writes_state": [],
      "reads_state": []
    },
    {
      "id": "draft",
      "kind": "llm",
      "runner": { "mode": "execute" },
      "artifacts": { "primary": "report.md" },
      "interrupt_before": false,
      "interrupt_after": false,
      "writes_state": [],
      "reads_state": []
    },
    {
      "id": "evaluate",
      "kind": "judge",
      "judge": {
        "schema": "evaluator_v1",
        "route_values": ["finalize", "revise"]
      },
      "interrupt_before": false,
      "interrupt_after": false
    },
    {
      "id": "revise",
      "kind": "llm",
      "runner": { "mode": "execute" },
      "artifacts": { "primary": "report.md" },
      "interrupt_before": false,
      "interrupt_after": false,
      "writes_state": [],
      "reads_state": []
    }
  ],
  "edges": [
    { "from": "research", "to": "draft", "when": "approvals_resolved" },
    { "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": "report.md" },
  "langgraph_builder": {
    "state_type": "TypedDict",
    "add_nodes": ["research", "draft", "evaluate", "revise", "finalize"],
    "set_entry_point": "research",
    "add_edges": [
      ["research", "draft"],
      ["draft", "evaluate"],
      ["revise", "evaluate"]
    ],
    "add_conditional_edges": [
      {
        "source": "evaluate",
        "path_function": "route_evaluate",
        "path_map": {
          "finalize": "finalize",
          "revise": "revise"
        }
      }
    ],
    "interrupt_before": [],
    "interrupt_after": [],
    "set_finish_point": "finalize"
  }
}

핵심 확인 사항: - "graph_type": "graph" — Pattern IR("pattern")과 구별 - "state_schema": null — 선형 그래프이므로 null - "parallel_groups": [] — 병렬 그룹 없음 - langgraph_builder.add_conditional_edges — judge 라우팅이 LangGraph 조건부 엣지로 변환됨 - "interrupt_before": false, "interrupt_after": false — 모든 노드에 기본값으로 추가됨

단계 5: 파일로 저장

euleragent graph compile examples/graphs/linear/my_linear.yaml \
  --out examples/graphs/linear/my_linear_compiled.json

echo "컴파일 완료:"
ls -lh examples/graphs/linear/my_linear_compiled.json

예상 출력:

컴파일 완료:
-rw-r--r-- 1 user group 2.1K 2026-02-23 10:15 examples/graphs/linear/my_linear_compiled.json

단계 6: state_schema 없이도 동작함을 확인

선형 그래프에서는 state_schema불필요합니다. 이유는 다음과 같습니다.

# state_schema 없이 validate가 통과함을 확인
euleragent graph validate examples/graphs/linear/my_linear.yaml
# 결과: 유효 ✓ (state_schema 관련 에러 없음)

단계 7: Pattern validate와 비교

같은 YAML로 Pattern 검증도 실행합니다.

# Graph validate
euleragent graph validate examples/graphs/linear/my_linear.yaml

# Pattern validate (같은 파일)
euleragent pattern validate examples/graphs/linear/my_linear.yaml

예상 출력 비교:

# Graph validate 출력
결과: 유효  (graph 모듈 3단계 검증 완료)

# Pattern validate 출력
결과: 유효  (pattern 모듈 2단계 검증 완료)
  참고: graph 전용 필드(id 접두사 'graph.') 무시됩니다.

두 검증이 모두 통과합니다. 이는 Graph가 Pattern의 상위 집합임을 실제로 확인하는 것입니다. 단, id: graph.my_linear처럼 graph. 접두사는 관례이며, Pattern도 이를 허용합니다.

단계 8: graph list로 등록된 그래프 목록 확인

euleragent graph list

예상 출력:

사용 가능한 그래프:

  ID                              카테고리   버전   위치
  ──────────────────────────────  ─────────  ─────  ──────────────────────────────────────
  graph.my_linear                 demo       1      examples/graphs/linear/my_linear.yaml
  graph.simple_pipeline           demo       1      examples/graphs/linear/01_simple_pipeline.yaml
  graph.research_parallel         research   1      examples/graphs/parallel/02_research_parallel.yaml

총 3개 그래프

YAML 전체 구조 재확인

이 튜토리얼에서 작성한 YAML의 전체 구조를 다시 살펴봅니다.

# examples/graphs/linear/my_linear.yaml

# 최상위 메타데이터 — Pattern과 동일
id: graph.my_linear
version: 1
category: demo
description: 단순 선형 그래프 — research → draft → evaluate → finalize

# 실행 제약 — Pattern과 동일
defaults:
  max_iterations: 2          # judge 루프 최대 2회
  max_total_tool_calls: 30   # 전체 도구 호출 예산
  max_web_search_calls: 10   # 웹 검색 호출 예산

# 노드 목록 — Pattern과 동일한 구조
nodes:
  - id: research
    kind: llm
    runner:
      mode: plan             # HITL 모드: 계획 제안 → 사람 승인 → 실행
      force_tool: web.search # 반드시 web.search 도구 사용
      min_proposals: 2       # 최소 2개 계획 제안
    guardrails:
      tool_call_budget:
        web.search: 3        # 웹 검색 최대 3회

  - id: draft
    kind: llm
    runner:
      mode: execute          # 직접 실행 모드 (승인 불필요)
    artifacts:
      primary: report.md     # 결과물을 report.md에 저장

  - id: evaluate
    kind: judge              # Judge 노드: 품질 평가 후 라우팅
    judge:
      schema: evaluator_v1   # 내장 평가 스키마
      route_values: [finalize, revise]  # 가능한 라우팅 목적지

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

# 엣지 — 노드 간 연결 및 조건
edges:
  - from: research
    to: draft
    when: "approvals_resolved"  # research의 HITL 승인 완료 후

  - 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: revise
    to: evaluate
    when: "true"               # 수정 후 다시 평가

# 최종 노드
finalize:
  artifact: report.md          # 최종 결과물

예상 출력 요약

명령 예상 결과
graph validate 결과: 유효 ✓ (오류 없음, 경고 없음)
graph show 4개 노드, 5개 엣지 구조 표시
graph compile IR JSON, graph_type: "graph" 확인
pattern validate 결과: 유효 ✓ (Pattern도 통과)

핵심 개념 요약

개념 설명
선형 그래프 parallel_groups 없는 순차 그래프
state_schema 불필요 병렬 실행 없으면 리듀서 불필요
graph_type: "graph" Pattern IR의 "pattern"과 구별되는 마커
3단계 검증 YAML 파싱 → Pattern 검증 → Graph 추가 검증
상위 집합 관계 선형 Graph YAML은 Pattern validate도 통과
approvals_resolved HITL 승인 완료 조건 (force_tool 사용 시)
judge.route Judge 노드 라우팅 조건

흔한 오류

오류 1: judge.route 조건과 route_values 불일치

# 오류: route_values에 "escalate"가 있지만 엣지에 없음
- id: evaluate
  kind: judge
  judge:
    route_values: [finalize, revise, escalate]  # ← 3개

edges:
  - from: evaluate
    to: finalize
    when: "judge.route == finalize"
  - from: evaluate
    to: revise
    when: "judge.route == revise"
  # 'escalate' 엣지 없음!
오류: JUDGE_ROUTE_COVERAGE_ERROR
  노드 'evaluate'의 route_values ['finalize', 'revise', 'escalate'] 중
  'escalate'에 대한 엣지가 없습니다.
해결: 'escalate' 목적지 노드와 엣지를 추가하거나,
      route_values에서 'escalate'를 제거하세요.

오류 2: max_iterations 없이 루프

# 오류: 루프가 있는데 max_iterations가 없음
defaults:
  max_total_tool_calls: 30
  # max_iterations 없음!

edges:
  - from: evaluate
    to: revise
    when: "judge.route == revise"
  - from: revise
    to: evaluate   # ← 루프
    when: "true"
오류: UNBOUNDED_CYCLE
  evaluate → revise → evaluate 순환이 감지되었지만
  defaults.max_iterations가 설정되지 않았습니다.
해결: defaults에 max_iterations: N (N >= 1)을 추가하세요.

오류 3: from 노드 미존재

edges:
  - from: nonexistent_node   # ← 이 노드가 nodes 목록에 없음
    to: draft
    when: "true"
오류: EDGE_SOURCE_NOT_FOUND
  엣지의 소스 노드 'nonexistent_node'가 nodes 목록에 없습니다.
해결: 노드 ID를 확인하거나 해당 노드를 nodes에 추가하세요.

오류 4: finalize 도달 불가

edges:
  - from: research
    to: draft
    when: "true"
  # draft에서 evaluate로 가는 엣지 없음
  # finalize에 도달할 수 없음!
오류: FINALIZE_UNREACHABLE
  finalize 노드에 도달하는 경로가 없습니다.
  현재 경로: research → draft (막힘)
해결: draft에서 evaluate 또는 finalize로 이어지는 엣지를 추가하세요.

오류 5: YAML 들여쓰기 오류

nodes:
  - id: research
  kind: llm    # ← 들여쓰기 오류! runner가 같은 레벨에 있어야 함
오류: yaml.scanner.ScannerError
  line 4, column 3: mapping values are not allowed here
해결: nodes 항목 내의 필드는 모두 같은 들여쓰기 레벨을 사용하세요.

실습 과제

다음 선형 그래프를 직접 작성해보세요.

목표: 코드 리뷰 어시스턴트 - read_code 노드: 코드 파일 읽기 (mode: plan) - analyze 노드: 코드 분석 및 개선점 도출 (mode: execute) - review 노드: judge — quality_pass / needs_revision - suggest_fixes 노드: 개선 제안 작성 (mode: execute) - 최종 결과물: review_report.md

# 작성 후 검증
euleragent graph validate examples/graphs/linear/code_review.yaml
euleragent graph compile examples/graphs/linear/code_review.yaml

이전: 01_concepts.md | 다음: 03_judge_route.md

← 이전 목록으로 다음 →