RAG2026-5일18차시 · State 이해8 / 19← 이전목차다음 →MetaContents

18차시. State 이해

5. Reducer — 덮어쓰지 않고 쌓기

출처: 원본 PDF 18차시_State이해.pdf 11~12쪽 · 쪽 그림 11쪽 12쪽

기본은 덮어쓰기입니다. 그런데 쌓아야 하는 값도 있습니다.

5-1) 덮어쓰기가 문제가 되는 경우

출력
■ 실행 로그를 남기고 싶다면
──────────────────────────────────────
def retrieve(state):
    return {"log": ["검색 완료"]}

def generate(state):
    return {"log": ["생성 완료"]}

기대  : ['검색 완료', '생성 완료']
실제  : ['생성 완료']          ← 덮어써짐!

5-2) Reducer로 해결하기

Python
from typing import TypedDict, Annotated, List
import operator

class DemoState(TypedDict):
    question: str
    answer: str
    # ★ Reducer 지정 — 덮어쓰지 말고 이어 붙여라
    log: Annotated[List[str], operator.add]

🔗 Annotated의 의미
Annotated[List[str], operator.add]
앞부분 List[str] → "이 키는 문자열 리스트다"
뒷부분 operator.add → "같은 키가 오면 더해라"
리스트끼리 더하면 이어 붙습니다.
['검색 완료'] + ['생성 완료']
= ['검색 완료', '생성 완료'] ✅

5-3) 어떤 키에 Reducer를 쓸까?

출처: 원본 PDF 12쪽

키Reducer이유
question없음 (덮어쓰기)질문은 하나입니다
documents없음 (덮어쓰기)재검색하면 새것으로 교체
answer없음 (덮어쓰기)최신 답변만 필요합니다
logoperator.add실행 이력은 쌓아야 합니다
tried_queriesoperator.add시도한 질의를 모두 기록
retries없음 (덮어쓰기)카운터는 최신값만 유효

⚠️ 헷갈리기 쉬운 지점
"retries도 쌓아야 하는 거 아닌가?"
아닙니다. 카운터는 노드가 직접 +1해서 반환합니다.
return {"retries": state.get("retries", 0) + 1}
Reducer를 붙이면 오히려 [1, 2, 3] 처럼 리스트가 됩니다.

← 이전 절18차시 · 4. 실습 ① — State 정의하고 흐름 보기다음 절 →18차시 · 6. 실습 ② — Reducer 비교 실험