Home > EulerAgent > Tutorials > Graph > Graph 06. State Schema In Depth — Types, ...

Graph 06. State Schema In Depth — Types, Reducers, and Design

Learning Objectives

After completing this tutorial, you will be able to:


Prerequisites

mkdir -p examples/graphs/state

# state_schema는 parallel_groups와 함께 사용됩니다
# 이 튜토리얼은 07_parallel_basics.md의 준비 단계이기도 합니다
euleragent graph --help

1. Why state_schema Is Needed

Parallel Write Conflict Scenario

In a linear graph without parallel branches, nodes execute sequentially and there are no state conflicts. However, when using parallel_groups, multiple nodes can access the same state key simultaneously.

선형 실행 (충돌 없음):
  research → findings 씀 → draft → findings 읽음 → finalize

병렬 실행 (충돌 가능):
  branch_a → findings 씀 ─┐
  branch_b → findings 씀 ─┤ → 동시에 같은 키에 쓰기 → 어느 값이 최종?
  branch_c → findings 씀 ─┘

LangGraph resolves this conflict using reducer functions. Without a reducer, an INVALID_CONCURRENT_GRAPH_UPDATE exception is raised.

The euleragent state_schema provides a declarative way to define these reducers.

LangGraph Reducer Internals

# state_schema의 append_list가 LangGraph 내부에서 변환되는 방식 (개념적)
from typing import Annotated, TypedDict
import operator

class GraphState(TypedDict):
    # merge: append_list → Annotated[list, operator.add]
    findings: Annotated[list, operator.add]

# 병렬 실행 시 리듀서 적용:
# branch_a가 쓴 값: ["결과 A"]
# branch_b가 쓴 값: ["결과 B"]
# 리듀서 적용: ["결과 A"] + ["결과 B"] = ["결과 A", "결과 B"]
# 최종 state["findings"] = ["결과 A", "결과 B"]

2. The 5 Types

type: list

Used to collect multiple items in order. Most commonly used for aggregating result lists from parallel branches.

state_schema:
  findings:
    type: list
    merge: append_list  # 권장: 병렬 결과 수집 시

type: string

Stores text data. Used for summaries, descriptions, path names, etc.

state_schema:
  summary:
    type: string
    merge: last_write   # 단일 브랜치가 쓸 때
  # 또는
  combined_text:
    type: string
    merge: concat_str   # 여러 브랜치 텍스트 연결 시

type: integer

Used for storing integer counters, counts, and quantities.

state_schema:
  result_count:
    type: integer
    merge: sum_int   # 권장: 각 브랜치의 결과 수 합산

type: float

Used for storing decimal scores, probabilities, and ratios.

state_schema:
  confidence_score:
    type: float
    merge: last_write   # 단일 브랜치 (float은 sum/append 없음)

type: dict

Used for storing structured data such as metadata, configuration, and complex results.

state_schema:
  metadata:
    type: dict
    merge: last_write   # dict는 last_write 또는 first_write만 가능

3. The 5 Merge Strategies

append_list — List Concatenation (list only)

Collects all list results from multiple branches. The gold standard for aggregating parallel branch results.

findings:
  type: list
  merge: append_list
# 동작: operator.add (리스트 연결)
["결과 A", "결과 B"] + ["결과 C"] = ["결과 A", "결과 B", "결과 C"]

sum_int — Integer Summation (integer only)

Sums counters across multiple branches.

result_count:
  type: integer
  merge: sum_int
# 동작: operator.add (정수 덧셈)
branch_a 기여: 3
branch_b 기여: 5
합산 결과: 3 + 5 = 8

concat_str — String Concatenation (string only)

Concatenates text from multiple branches. Caution is needed since concatenation is done without separators.

combined_notes:
  type: string
  merge: concat_str
# 동작: operator.add (문자열 연결)
branch_a: "첫 번째 메모. "
branch_b: "두 번째 메모."
결과: "첫 번째 메모. 두 번째 메모."

last_write — Last Write Wins (all types)

The value from the last branch to complete becomes the final value. This is nondeterministic.

summary:
  type: string
  merge: last_write
위험 시나리오: 2개 브랜치가 같은 last_write 키를 씀
  branch_a 완료: state["summary"] = "A의 요약"
  branch_b 완료: state["summary"] = "B의 요약"  ← 마지막 완료이므로 승리

  결과: "B의 요약" (하지만 다음 실행에서는 A가 마지막일 수도 있음)

first_write — First Write Wins (all types)

The value from the first branch to complete becomes the final value. Also nondeterministic.

primary_result:
  type: string
  merge: first_write

4. Type+Strategy Compatibility Table

euleragent only allows the following combinations. All others result in a STATE_SCHEMA_MERGE_TYPE_MISMATCH error.

Type append_list sum_int concat_str last_write first_write
list O X X O O
string X X O O O
integer X O X O O
float X X X O O
dict X X X O O

Bold entries: Recommended merge strategy for each type

Forbidden combinations (14): 1. list + sum_int 2. list + concat_str 3. string + append_list 4. string + sum_int 5. integer + append_list 6. integer + concat_str 7. float + append_list 8. float + sum_int 9. float + concat_str 10. dict + append_list 11. dict + sum_int 12. dict + concat_str 13. (Reserved) May be added in the future 14. (Reserved) May be added in the future


Step-by-Step Walkthrough

Step 1: Writing a Complete state_schema (3 Keys)

# examples/graphs/state/schema_demo.yaml
id: graph.schema_demo
version: 1
category: demo
description: state_schema 상세 데모 — findings + summary + error_count

state_schema:
  findings:
    type: list
    merge: append_list     # 각 브랜치가 수집한 결과 목록 합산

  summary:
    type: string
    merge: last_write      # 단일 노드가 최종 요약을 씀

  error_count:
    type: integer
    merge: sum_int         # 각 브랜치에서 발견한 오류 수 합산

defaults:
  max_iterations: 2
  max_total_tool_calls: 30

parallel_groups:
  - id: search_group
    branches: [web_branch, local_branch]
    join: merge_results

nodes:
  - id: start
    kind: llm
    runner:
      mode: execute

  - id: web_branch
    kind: llm
    runner:
      mode: execute
    writes_state: [findings, error_count]

  - id: local_branch
    kind: llm
    runner:
      mode: execute
    writes_state: [findings, error_count]

  - id: merge_results
    kind: llm
    runner:
      mode: execute
    reads_state: [findings, error_count]
    writes_state: [summary]
    artifacts:
      primary: summary.md

edges:
  - from: start
    to: web_branch
    when: "true"
  - from: start
    to: local_branch
    when: "true"
  - from: web_branch
    to: merge_results
    when: "true"
  - from: local_branch
    to: merge_results
    when: "true"
  - from: merge_results
    to: finalize
    when: "true"

finalize:
  artifact: summary.md
euleragent graph validate examples/graphs/state/schema_demo.yaml

Expected output:

단계 3/3: Graph 추가 검증...
  [✓] state_schema 존재 (parallel_groups 사용)
  [✓] state_schema 타입+merge 호환성:
        findings: list + append_list ✓
        summary: string + last_write ✓
        error_count: integer + sum_int ✓
  [✓] 모든 브랜치 writes_state 선언:
        web_branch: [findings, error_count] ✓
        local_branch: [findings, error_count] ✓
  [✓] merge_results reads_state 선언 ✓
  완료

결과: 유효 ✓

Step 2: Demonstrating STATE_SCHEMA_MERGE_TYPE_MISMATCH Error

See what happens when you assign merge: append_list to type: string.

# examples/graphs/state/schema_mismatch.yaml
id: graph.schema_mismatch
version: 1
category: demo

state_schema:
  findings:
    type: string        # ← string 타입
    merge: append_list  # ← 하지만 append_list는 list 전용! 불일치!

  count:
    type: integer
    merge: concat_str   # ← integer에 concat_str? 불일치!

  score:
    type: float
    merge: sum_int      # ← float에 sum_int? 불일치!

parallel_groups:
  - id: grp
    branches: [branch_a, branch_b]
    join: join_node

nodes:
  - id: branch_a
    kind: llm
    runner: {mode: execute}
    writes_state: [findings, count, score]

  - id: branch_b
    kind: llm
    runner: {mode: execute}
    writes_state: [findings, count, score]

  - id: join_node
    kind: llm
    runner: {mode: execute}
    reads_state: [findings, count, score]

edges:
  - {from: branch_a, to: join_node, when: "true"}
  - {from: branch_b, to: join_node, when: "true"}
  - {from: join_node, to: finalize, when: "true"}

finalize:
  artifact: output.md
euleragent graph validate examples/graphs/state/schema_mismatch.yaml

Expected output:

단계 3/3: Graph 추가 검증...
  [✗] state_schema 타입+merge 호환성 검사 실패:

오류: STATE_SCHEMA_MERGE_TYPE_MISMATCH (3개)

  1. 키 'findings': type=string, merge=append_list
     append_list는 type=list에서만 사용 가능합니다.
     해결: merge를 last_write, first_write, 또는 concat_str로 변경하세요.

  2. 키 'count': type=integer, merge=concat_str
     concat_str는 type=string에서만 사용 가능합니다.
     해결: merge를 last_write, first_write, 또는 sum_int로 변경하세요.

  3. 키 'score': type=float, merge=sum_int
     sum_int는 type=integer에서만 사용 가능합니다.
     해결: merge를 last_write 또는 first_write로 변경하세요.

결과: 유효하지 않음 (오류 3개)

Step 3: Re-validate After Fixing

Fix the errors.

# 수정된 state_schema
state_schema:
  findings:
    type: string        # string이라면
    merge: concat_str   # ← concat_str 사용 (또는 타입을 list로 변경)

  count:
    type: integer
    merge: sum_int      # ← integer에는 sum_int

  score:
    type: float
    merge: last_write   # ← float에는 last_write 또는 first_write
euleragent graph validate examples/graphs/state/schema_mismatch.yaml
# 결과: 유효 ✓ (수정 후)

Step 4: Demonstrating PARALLEL_NONDETERMINISTIC_MERGE

Observe the warning that occurs when 2 branches write to the same last_write key.

# examples/graphs/state/schema_nondeterministic.yaml
id: graph.schema_nondeterministic
version: 1
category: demo
description: 비결정론적 merge 경고 시연

state_schema:
  summary:
    type: string
    merge: last_write   # ← 두 브랜치가 이 키를 씀 → 비결정론적!

parallel_groups:
  - id: grp
    branches: [branch_a, branch_b]
    join: join_node

nodes:
  - id: branch_a
    kind: llm
    runner: {mode: execute}
    writes_state: [summary]    # ← summary에 씀

  - id: branch_b
    kind: llm
    runner: {mode: execute}
    writes_state: [summary]    # ← branch_a와 같은 키에 씀 → 충돌!

  - id: join_node
    kind: llm
    runner: {mode: execute}
    reads_state: [summary]

edges:
  - {from: branch_a, to: join_node, when: "true"}
  - {from: branch_b, to: join_node, when: "true"}
  - {from: join_node, to: finalize, when: "true"}

finalize:
  artifact: output.md
euleragent graph validate examples/graphs/state/schema_nondeterministic.yaml

Expected output:

단계 3/3: Graph 추가 검증...

경고: PARALLEL_NONDETERMINISTIC_MERGE
  상태 키 'summary' (type=string, merge=last_write)가 2개 이상의 브랜치에서 쓰입니다:
    branch_a: writes_state=[summary]
    branch_b: writes_state=[summary]

  last_write 리듀서는 마지막으로 완료된 브랜치의 값을 선택합니다.
  브랜치 완료 순서는 실행마다 달라질 수 있으므로 결과가 비결정론적입니다.

  해결 방법:
    1. summary 타입을 list로 변경하고 merge를 append_list로 변경하세요.
    2. 또는 summary를 쓰는 브랜치를 하나로 줄이세요.
    3. 또는 concat_str로 변경하여 두 브랜치 텍스트를 이어 붙이세요.

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

The warning is present but validation passes. However, if nondeterministic results are unacceptable, follow the suggested resolution methods.


5. state_schema Design Checklist

Use the following checklist when designing a state_schema for parallel graphs.

Checklist

1. parallel_groups가 있으면 → state_schema 필수
   □ state_schema 최상위에 선언됨

2. 각 브랜치의 writes_state 파악
   □ 모든 브랜치의 writes_state 목록화
   □ 각 키를 몇 개 브랜치가 쓰는지 확인

3. 공유 키 식별 (2개 이상 브랜치가 쓰는 키)
   □ 공유 키에 last_write 사용 시 → 비결정론적 경고 확인
   □ 공유 키가 list 타입 → append_list 사용
   □ 공유 키가 integer 타입 → sum_int 사용
   □ 공유 키가 string 타입 → concat_str 고려 (또는 list로 변경)

4. 타입+merge 호환성
   □ list 키: append_list, last_write, first_write 중 선택
   □ string 키: last_write, first_write, concat_str 중 선택
   □ integer 키: last_write, first_write, sum_int 중 선택
   □ float 키: last_write, first_write 중 선택
   □ dict 키: last_write, first_write 중 선택

5. 조인 노드의 reads_state
   □ 조인 노드가 필요한 모든 키를 reads_state에 선언

6. 최종 검증
   □ euleragent graph validate 통과
   □ 경고(PARALLEL_NONDETERMINISTIC_MERGE) 검토 후 수용 또는 수정

Design Example: A Correct state_schema

# 3개 브랜치, 여러 키를 사용하는 올바른 설계
state_schema:
  # 3개 브랜치 모두 쓰는 공유 키 → append_list (결정론적)
  all_findings:
    type: list
    merge: append_list

  # 3개 브랜치 모두 숫자를 더하는 키 → sum_int (결정론적)
  total_sources:
    type: integer
    merge: sum_int

  # branch_a만 쓰는 키 → last_write (단일 브랜치이므로 안전)
  web_summary:
    type: string
    merge: last_write

  # branch_b만 쓰는 키 → last_write (단일 브랜치이므로 안전)
  local_summary:
    type: string
    merge: last_write

  # 조인 노드만 쓰는 키 → last_write (병렬 아님)
  final_report:
    type: string
    merge: last_write

Expected Output Summary

Command Expected Result
graph validate schema_demo.yaml Valid, all 3 keys pass compatibility check
graph validate schema_mismatch.yaml STATE_SCHEMA_MERGE_TYPE_MISMATCH x3
graph validate schema_nondeterministic.yaml Valid (1 warning: PARALLEL_NONDETERMINISTIC_MERGE)

Key Concepts Summary

Concept Description
state_schema Defines types and reducers for shared state keys
Reducer Function that resolves parallel write conflicts
append_list List concatenation -- list type only, deterministic
sum_int Integer summation -- integer type only, deterministic
concat_str String concatenation -- string type only, deterministic
last_write Last write wins -- all types, potentially nondeterministic
first_write First write wins -- all types, potentially nondeterministic
STATE_SCHEMA_MERGE_TYPE_MISMATCH Forbidden type+merge combination used
PARALLEL_NONDETERMINISTIC_MERGE 2+ branches write to the same last_write key

Common Errors

Error 1: Using Hyphens in state_schema Key Names

state_schema:
  my-findings:   # ← 하이픈 불가! 언더스코어 사용
    type: list
    merge: append_list
오류: STATE_SCHEMA_INVALID_KEY_NAME
  'my-findings': 키 이름에 하이픈(-)을 사용할 수 없습니다.
  언더스코어(_) 또는 camelCase를 사용하세요: my_findings

Error 2: Declaring a Key in writes_state That Is Not in state_schema

state_schema:
  findings:
    type: list
    merge: append_list

nodes:
  - id: branch_a
    writes_state: [findings, nonexistent_key]   # ← nonexistent_key는 schema에 없음
오류: WRITES_STATE_KEY_NOT_IN_SCHEMA
  노드 'branch_a'의 writes_state에 'nonexistent_key'가 있지만
  state_schema에 해당 키가 없습니다.
  해결: state_schema에 nonexistent_key를 추가하거나
        writes_state에서 제거하세요.

Error 3: Using sum_int with float

state_schema:
  score:
    type: float
    merge: sum_int  # float은 sum_int 불가!
오류: STATE_SCHEMA_MERGE_TYPE_MISMATCH
  키 'score': type=float, merge=sum_int
  sum_int는 type=integer에서만 사용 가능합니다.
  float 타입에서 합산이 필요하면 last_write 또는 first_write를 사용하세요.
  (float sum 리듀서는 현재 미지원)

Practice Exercise

Exercise: Competitor Analysis state_schema Design

Design a state_schema for a 3-branch parallel competitor analysis graph with the following specification.

Branch configuration: - price_research branch: collect pricing information - feature_research branch: collect feature comparisons - review_research branch: collect user reviews

Requirements: - Aggregate results from each branch into a single list - Sum the number of competitors found by each branch (integer) - Only price_research records the average price (float) - The merge_findings join node writes the final analysis report (string)

# 설계 후 검증
euleragent graph validate examples/graphs/state/competitor_analysis.yaml

Previous: 05_interrupt_hooks.md | Next: 07_parallel_basics.md

← Prev Back to List Next →