State 상태의 설계

1. 상태 스키마 정의 방법

(1) TypedDict

  • Python의 내장 기능을 사용하는 가장 권장되는 방법
  • LangGraph의 기본 상태 스키마 정의 방법
  • 상태(State) 내 여러 필드가 모두 채워지지 않아도 운용이 가능하다는 장점이 있음
  • TypedDict 포스팅 : https://whdrns2013.github.io/python/20260411_001_python_typeddict/
  • 각 필드(키)의 타입 힌트를 작성해둘 수 있지만, 런타임에서 검증은 수행되지 않는다.
1
2
3
4
5
6
from typing import TypedDict, Annotated, Required, NotRequired

class MessageState(TypedDict):
    query: str
    messages: Annotated[list[str], add]
    response: str

(2) dataclass

  • Python의 내장 기능을 사용하는 방법
  • 기본값 설정이 가능한 장점이 있다.
  • 반면, 상태(State) 내 모든 필드가 채워져야 하는 제한사항이 있다.
  • TypedDict와 같이 타입 힌트를 설정해둘 수 있지만, 런타임에서 검증은 수행되지 않는다.
1
2
3
4
5
6
7
from dataclasses import dataclass

@dataclass
class MyClass:
    name: str
    hobby: list[str]
    age: int = 30

(3) Pydantic BaseModel

  • Pydantic이라는 모듈 내의 기능을 활용하는 방법 (파이썬 내장 기능이 아님)
  • 타입 힌트를 작성할 수 있으며, 타입에 대한 검증이 런타임에서 이뤄진다.
  • 따라서 타입이 맞지 않는 값에 대해 오류를 발생시킴
  • 단, 이러한 풍부한 검증에 따른 오버헤드가 발생하여 TypedDict나 dataclass보다는 느리다는 단점
  • 또한 Pydantic이라는 추가 의존성이 필요함
1
2
3
4
5
6
from pydantic import BaseModel

class MyClass(BaseModel):
    name: str
    age: int = 30    # 만약 문자열을 넣는다면, 오루 발생
    hobby: list[str]

요약

  TypedDict dataclass Pydantic BaseModel
장점 • 빠른 속도
• LangGraph의 기본 방법
• 모든 필드가 채워지지 않아도 됨
• 기본값 설정 가능
• 빠른 속도
• 런타임 타입 체크
• 다양한 유효성 검증
단점 • 기본값 설정 불가능
• 런타임 타입 체크 없음
• 모든 필드가 채워져야 하는 제한 • 의존성(Pydantic) 필요
• 비교적 느린 속도

2. 기본적인 상태 만들어보기

(1) 기본 상태

  • 애플리케이션 예시 : 사용자 질문에 대해, 로컬에 저장된 자료에서 참고자료를 찾고, 없으면 인터넷에서 관련 자료를 찾아 답변하는 RAG 애플리케이션
1
2
3
4
5
6
7
8
9
10
# 1. 기본적인 상태

from typing import TypedDict, Annotated
from operator import add

class GraphState(TypedDict):
    query: str  # 사용자 질의
    documents: Annotated[list[str], add] # 답변을 위해 검색된 내용
    response: str    # AI 답변
    success_flag: int  # AI 답변의 품질 검토 - 1:성공, 0:실패

(2) Input / Output 을 분리하여 관리하는 상태

  • 애플리케이션 예시는 동일
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 2. 입출력 스키마 구분

from typing import TypedDict, Annotated
from operator import add
from langgraph.graph import StateGraph

class GraphState(TypedDict):
    query: str  # 사용자 질의
    documents: Annotated[list[str], add] # 답변을 위해 검색된 내용
    response: str    # AI 답변
    success_flag: int  # AI 답변의 품질 검토 - 1:성공, 0:실패

class SearchInputState(TypedDict):
    query:str

class AnswerOutputState(TypedDict):
    response:str

def search_node(state: SearchInputState):
    def do_search(query: state["query"]):
        # do something with query
        return ["doc1", "doc2"]
    documents = do_search()
    return {
        "query" : state["query"],
        "documents" : documents
    }

def llm_invoke(state: GraphState) -> AnswerOutputState:
    def do_llm_invoke(state):
        prompt = f"user query: {state['query']}, documents: {state['documents']}"
        # do llm invoke
        return "llm response"
    response = do_llm_invoke(state)
    return {
        "response" : response
    }

# Graph 를 빌드할 때 input, output을 지정
builder = StateGraph(
    GraphState,
    input = SearchInputState,
    output = AnswerOutputState
)

Reference

Do it! LLM을 활용한 AI 에이전트 개발 입문 (이성용 저)

https://wikidocs.net/261579

https://www.youtube.com/watch?v=W_uwR_yx4-c

Comments