18차시. State 이해
8. 실습 ③ — RAGState 완성
출처: 원본 PDF 18차시_State이해.pdf 17~18쪽 · 쪽 그림 17쪽 18쪽
📝 src/graph_state.py
from typing import TypedDict, Annotated, List, Any
import operator
class RAGState(TypedDict, total=False):
# ── 입력 ──
question: str # 사용자 원본 질문 (변경 금지)
query: str # 검색용 질문 (재작성 가능)
# ── 검색 결과 ──
documents: List[Any] # 검색된 조각
scores: List[float] # 유사도 점수
retrieval_ok: bool # 검색 성공 여부
# ── 생성 결과 ──
answer: str # 생성된 답변
insufficient: bool # 근거 부족 자가신고
has_citation: bool # 출처 번호 포함 여부
# ── 검증 결과 ──
grade: str # "pass" | "retry"
reason: str # 판정 이유
# ── 제어 ──
retries: int # 재시도 횟수
top_k: int # 검색 개수 (동적 조절용)
# ── 이력 (누적) ──
log: Annotated[List[str], operator.add]
tried_queries: Annotated[List[str], operator.add]💡 total=False 는 무엇인가요?
기본적으로 TypedDict는 모든 키가 필수입니다.
그러면 invoke할 때 9개 키를 다 넣어야 합니다.
total=False를 붙이면 전부 선택 사항이 됩니다.
app.invoke({"question": "환불 규정은?"})
이렇게 하나만 넣어도 됩니다.
나머지는 노드들이 채워 나갑니다.
실습 코드 · 18/graph_state.py
실습 코드: 실습/src/18/graph_state.py · 위의 원본 PDF 18차시 17쪽 「8️⃣ 실습 ③ — RAGState 완성」과 이어집니다.
# ============================================================
# 18차시 | State 이해
# 교안: 18차시_State이해.pptx
# 역할: 이후 RAG 그래프가 공유할 State 칸(키)만 정의한다. 실행하지 않는다.
# 입력: 없음 (클래스 정의)
# 출력: 없음
#
# State = 아래 RAGState 딕셔너리. 노드 함수는 이 파일에 없다.
# reducer = log, tried_queries 만 Annotated[..., operator.add].
# 노드가 ["한 줄"] 을 돌려주면 기존 리스트 뒤에 붙는다.
# 그 외 키는 return 한 값으로 그 칸만 덮어쓴다.
# total=False = 초기 State에 모든 키가 없어도 타입 검사에서 빠진다.
#
# 이 파일은 그래프를 compile 하지 않는다. 칸의 흐름만 적는다.
#
# question / query
# |
# v
# documents / scores / retrieval_ok
# |
# v
# answer / insufficient / has_citation
# |
# v
# grade / reason
# retries, top_k 는 반복 제어용 칸
# log, tried_queries 는 reducer 로 누적
# ============================================================
from typing import TypedDict, Annotated, Any
import operator
# ============================================================
# RAG State 정의
# ============================================================
class RAGState(TypedDict, total=False):
"""RAG 그래프의 모든 노드가 함께 사용하는 데이터 구조입니다."""
# --------------------------------------------------------
# 1. 사용자 입력
# --------------------------------------------------------
# 사용자가 처음 입력한 질문입니다.
question: str
# 실제 검색에 사용할 질문입니다.
# 검색 실패 시 다른 문장으로 변경할 수 있습니다.
query: str
# --------------------------------------------------------
# 2. 검색 결과
# --------------------------------------------------------
# 검색된 문서 조각들을 저장합니다.
documents: list[Any]
# 검색된 문서들의 유사도 점수를 저장합니다.
scores: list[float]
# 검색 결과를 사용할 수 있는지 저장합니다.
retrieval_ok: bool
# --------------------------------------------------------
# 3. 답변 생성 결과
# --------------------------------------------------------
# LLM이 생성한 최종 답변입니다.
answer: str
# 근거가 부족한지 저장합니다.
insufficient: bool
# 답변에 출처 번호가 포함되었는지 저장합니다.
has_citation: bool
# --------------------------------------------------------
# 4. 답변 검증 결과
# --------------------------------------------------------
# 답변 검증 결과를 저장합니다.
# 예: "pass", "retry"
grade: str
# 검증 결과가 나온 이유를 저장합니다.
reason: str
# --------------------------------------------------------
# 5. 그래프 제어 정보
# --------------------------------------------------------
# 답변 생성 등을 다시 시도한 횟수입니다.
retries: int
# 검색할 문서 개수입니다.
top_k: int
# --------------------------------------------------------
# 6. 실행 이력
# --------------------------------------------------------
# 각 노드의 실행 기록을 계속 누적합니다.
# reducer: 노드가 돌려준 리스트를 기존 log 뒤에 이어 붙인다.
log: Annotated[list[str], operator.add]
# 지금까지 사용한 검색어를 계속 누적합니다.
# reducer: 같은 방식으로 tried_queries 뒤에 이어 붙인다.
tried_queries: Annotated[list[str], operator.add]실행 결과 샘플 (교안 기록. API 키가 필요한 코드는 키 없이 맞춘 예시이고, 모델의 답은 실행할 때마다 달라집니다)
===== 실행 결과 샘플 =====
실행: cd 실습/src/18
python graph_state.py
(실제 실행 출력)
(표준 출력 없음. State 칸만 정의하고 그래프는 실행하지 않는다.)코드 해설
이후 RAG 그래프가 공유할 State 칸(키)만 정의한다. 실행하지 않는다. 입력은 없음 (클래스 정의)이고, 출력은 없음입니다.
코드 앞에서 고정해 두는 말입니다.
- 18차시 | State 이해
- 교안: 18차시_State이해.pptx
- State = 아래 RAGState 딕셔너리. 노드 함수는 이 파일에 없다.
- reducer = log, tried_queries 만 Annotated[..., operator.add].
- 노드가 ["한 줄"] 을 돌려주면 기존 리스트 뒤에 붙는다.
- 그 외 키는 return 한 값으로 그 칸만 덮어쓴다.
- total=False = 초기 State에 모든 키가 없어도 타입 검사에서 빠진다.
- 이 파일은 그래프를 compile 하지 않는다. 칸의 흐름만 적는다.
함수와 클래스로 나누어 읽습니다.
RAGState: RAG 그래프의 모든 노드가 함께 사용하는 데이터 구조입니다.
파일 맨 아래 실행 결과 샘플 주석은 실행되지 않습니다. 화면 출력은 다음 절에 다시 적어 두었습니다.
8-1) 초기값 채우기
출처: 원본 PDF 18쪽
def make_initial_state(question: str) -> dict:
# invoke에 넣을 초기 State를 만든다
return {
"question": question,
"query": question, # 처음엔 원본과 동일
"retries": 0,
"log": [],
"tried_queries": [],
}
# 사용
app.invoke(make_initial_state("환불은 며칠 이내인가요?"))⚠️ 가장 흔한 실수
query를 초기화하지 않는 것
app.invoke({"question": "환불 규정은?"})
retrieve 노드가 state["query"]를 읽으려는데
아직 없어서 KeyError가 납니다.
해결책 두 가지
① 초기 State에 query를 함께 넣기 (위 방식)
② 노드에서 state.get("query") or state["question"]