Home > EulerAgent > Tutorials > Graph > Graph 02. Linear Graph — Your First Step, Same as Pattern

Graph 02. Linear Graph — Your First Step, Same as Pattern

Learning Objectives

After completing this tutorial, you will be able to:


Prerequisites

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

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

Expected output:

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 유효성 검사

Step-by-Step Hands-On

Step 1: Write a Linear Graph YAML

Create the file examples/graphs/linear/my_linear.yaml with the following content.

# 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

Understanding This YAML Structure

research (plan mode + web.search HITL)
    ↓ [approvals_resolved: proceeds after human approval]
  draft (execute mode → generates report.md)
    ↓ [always proceeds]
 evaluate (judge → finalize or revise)
    ↓ [judge.route == finalize]          [judge.route == revise]
 finalize                                 revise (revision)
                                              ↓ [always proceeds]
                                           evaluate (re-evaluation)

This structure has the following characteristics.

Step 2: Run graph validate

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

Expected output:

검증 중: 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 없음 (선택적 기능)
  완료

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

Notice that the validation is split into 3 stages. 1. YAML Parsing — syntax check 2. Pattern Base Validation — node/edge/judge rule checks 3. Graph Additional Validation — parallel rules, state_schema, interrupt checks

Step 3: Inspect Structure with graph show

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

Expected output:

그래프: 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: 없음

Step 4: Generate IR with graph compile

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

Expected output (IR JSON excerpt):

{
  "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"
  }
}

Key points to verify: - "graph_type": "graph" — distinguishes from Pattern IR ("pattern") - "state_schema": null — null because this is a linear graph - "parallel_groups": [] — no parallel groups - langgraph_builder.add_conditional_edges — judge routing is converted into LangGraph conditional edges - "interrupt_before": false, "interrupt_after": false — added as default values on all nodes

Step 5: Save to a File

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

Expected output:

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

Step 6: Confirm It Works Without state_schema

In a linear graph, state_schema is unnecessary. Here is why.

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

Step 7: Compare with Pattern validate

Run Pattern validation on the same YAML.

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

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

Expected output comparison:

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

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

Both validations pass. This practically confirms that Graph is a superset of Pattern. Note that the id: graph.my_linear prefix graph. is a convention; Pattern also accepts it.

Step 8: View Registered Graphs with graph list

euleragent graph list

Expected output:

사용 가능한 그래프:

  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개 그래프

Reviewing the Full YAML Structure

Let us revisit the full structure of the YAML written in this tutorial.

# 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          # 최종 결과물

Expected Output Summary

Command Expected Result
graph validate Result: Valid (no errors, no warnings)
graph show Displays structure with 4 nodes and 5 edges
graph compile IR JSON, verify graph_type: "graph"
pattern validate Result: Valid (Pattern also passes)

Key Concepts Summary

Concept Description
Linear graph Sequential graph without parallel_groups
state_schema not required Reducers are unnecessary when there is no parallel execution
graph_type: "graph" Marker that distinguishes from Pattern IR's "pattern"
3-stage validation YAML parsing → Pattern validation → Graph additional validation
Superset relationship A linear Graph YAML also passes Pattern validate
approvals_resolved HITL approval completion condition (when force_tool is used)
judge.route Judge node routing condition

Common Errors

Error 1: Mismatch Between judge.route Condition and 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'를 제거하세요.

Error 2: Loop Without 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)을 추가하세요.

Error 3: Non-Existent from Node

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

Error 4: Unreachable finalize

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

Error 5: YAML Indentation Error

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

Practice Exercise

Try writing the following linear graph on your own.

Goal: Code Review Assistant - read_code node: Read code files (mode: plan) - analyze node: Analyze code and identify improvements (mode: execute) - review node: judge -- quality_pass / needs_revision - suggest_fixes node: Write improvement suggestions (mode: execute) - Final artifact: review_report.md

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

Previous: 01_concepts.md | Next: 03_judge_route.md

← Prev Back to List Next →