그래프 02. 선형 그래프 — Pattern과 동일한 첫 걸음
학습 목표
이 튜토리얼을 마치면 다음을 할 수 있습니다.
- 가장 단순한 선형 Graph YAML을 직접 작성할 수 있다.
euleragent graph validate의 3단계 검증 과정을 이해한다.euleragent graph show로 그래프 구조를 시각적으로 확인한다.euleragent graph compile로 LangGraph IR을 생성하고graph_type: "graph"를 확인한다.- 선형 그래프에서
state_schema가 불필요한 이유를 설명할 수 있다. - 같은 YAML로
euleragent pattern validate도 통과함을 확인한다.
사전 준비
# 작업 디렉터리 생성
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 (재평가)
이 구조는 다음 특징을 갖습니다.
- 선형 + 루프 — 병렬 실행 없음
- HITL 승인 — research 노드의
force_tool: web.search로 웹 검색 전 사람 승인 필요 - Judge 라우팅 — evaluate 노드가 finalize/revise 중 하나를 선택
- 최대 반복 —
max_iterations: 2로 루프 횟수 제한
단계 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가 불필요합니다. 이유는 다음과 같습니다.
- 노드가 순차적으로 실행되므로 공유 상태 키 충돌이 없음
- LangGraph는 병렬 쓰기가 없을 때 기본 상태 타입으로도 동작
state_schema는parallel_groups와 함께 사용할 때만 필수
# 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