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 에이전트 개발 입문 (이성용 저)
Comments