개념
add_messages는 LangGraph에서 제공하는 reducer 함수로, 메시지 리스트를 지능적으로 병합합니다.
python
from langgraph.graph import add_messages
# 기본 사용법
from typing import Annotated, TypedDict
class State(TypedDict):
messages: Annotated[list, add_messages]
핵심 동작 원리
1. 기본 병합 (누적)
python
from langgraph.graph import add_messages
# 초기 상태
existing = [
{"role": "user", "content": "안녕하세요"}
]
# 새 메시지
new = [
{"role": "assistant", "content": "반갑습니다!"}
]
# add_messages 적용 결과
result = add_messages(existing, new)
# [
# {"role": "user", "content": "안녕하세요"},
# {"role": "assistant", "content": "반갑습니다!"}
# ]
2. ID 기반 덮어쓰기
python
from langchain_core.messages import AIMessage
# 초기 메시지
existing = [
AIMessage(content="초안입니다", id="msg_1")
]
# 같은 ID로 업데이트
new = [
AIMessage(content="수정된 내용입니다", id="msg_1")
]
result = add_messages(existing, new)
# [AIMessage(content="수정된 내용입니다", id="msg_1")]
# 기존 메시지가 교체됨
3. 혼합 동작
python
from langchain_core.messages import HumanMessage, AIMessage
existing = [
HumanMessage(content="질문", id="msg_1"),
AIMessage(content="초안 답변", id="msg_2")
]
new = [
AIMessage(content="수정된 답변", id="msg_2"), # 덮어쓰기
HumanMessage(content="추가 질문", id="msg_3") # 새로 추가
]
result = add_messages(existing, new)
# [
# HumanMessage(content="질문", id="msg_1"),
# AIMessage(content="수정된 답변", id="msg_2"), # 수정됨
# HumanMessage(content="추가 질문", id="msg_3") # 추가됨
# ]
사용 케이스
케이스 1: 기본 챗봇
python
from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, add_messages, START, END
from langchain_core.messages import HumanMessage, AIMessage
class ChatState(TypedDict):
messages: Annotated[list, add_messages]
def chatbot(state: ChatState):
user_message = state["messages"][-1].content
response = f"당신은 '{user_message}'라고 말했습니다."
return {
"messages": [AIMessage(content=response)]
}
# 그래프 구성
workflow = StateGraph(ChatState)
workflow.add_node("chat", chatbot)
workflow.add_edge(START, "chat")
workflow.add_edge("chat", END)
app = workflow.compile()
# 실행
result = app.invoke({
"messages": [HumanMessage(content="안녕하세요")]
})
print(result["messages"])
# [HumanMessage("안녕하세요"), AIMessage("당신은 '안녕하세요'라고 말했습니다.")]
케이스 2: 스트리밍 응답 수정
python
from typing import Annotated, TypedDict
from langgraph.graph import add_messages
from langchain_core.messages import AIMessage
import uuid
class StreamState(TypedDict):
messages: Annotated[list, add_messages]
def streaming_agent(state: StreamState):
# 스트리밍 시뮬레이션
message_id = str(uuid.uuid4())
# 1단계: 부분 응답
yield {
"messages": [AIMessage(content="생각 중", id=message_id)]
}
# 2단계: 업데이트
yield {
"messages": [AIMessage(content="생각 중...", id=message_id)]
}
# 3단계: 최종 응답
yield {
"messages": [AIMessage(content="최종 답변입니다!", id=message_id)]
}
# 같은 ID를 사용하므로 메시지가 계속 업데이트됨
케이스 3: 멀티턴 대화
python
from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, add_messages, START, END
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
class ConversationState(TypedDict):
messages: Annotated[list, add_messages]
def add_system_message(state: ConversationState):
return {
"messages": [SystemMessage(content="당신은 친절한 AI 어시스턴트입니다.")]
}
def respond(state: ConversationState):
# 마지막 사용자 메시지 가져오기
last_user_message = None
for msg in reversed(state["messages"]):
if isinstance(msg, HumanMessage):
last_user_message = msg
break
if last_user_message:
return {
"messages": [AIMessage(content=f"답변: {last_user_message.content}")]
}
return {"messages": []}
# 그래프 구성
workflow = StateGraph(ConversationState)
workflow.add_node("system", add_system_message)
workflow.add_node("respond", respond)
workflow.add_edge(START, "system")
workflow.add_edge("system", "respond")
workflow.add_edge("respond", END)
app = workflow.compile()
# 여러 턴 대화
messages = []
for user_input in ["안녕하세요", "날씨 어때요?", "고마워요"]:
messages.append(HumanMessage(content=user_input))
result = app.invoke({"messages": messages})
messages = result["messages"]
print(f"User: {user_input}")
print(f"AI: {messages[-1].content}\n")
케이스 4: 에이전트 체인
python
from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, add_messages, START, END
from langchain_core.messages import HumanMessage, AIMessage
class AgentState(TypedDict):
messages: Annotated[list, add_messages]
current_agent: str
def research_agent(state: AgentState):
"""정보 수집 에이전트"""
return {
"messages": [AIMessage(
content="[연구 에이전트] 관련 자료를 수집했습니다.",
name="researcher"
)],
"current_agent": "writer"
}
def writer_agent(state: AgentState):
"""작성 에이전트"""
return {
"messages": [AIMessage(
content="[작성 에이전트] 보고서를 작성했습니다.",
name="writer"
)],
"current_agent": "reviewer"
}
def reviewer_agent(state: AgentState):
"""검토 에이전트"""
return {
"messages": [AIMessage(
content="[검토 에이전트] 검토를 완료했습니다.",
name="reviewer"
)],
"current_agent": "done"
}
def route_agent(state: AgentState):
agent = state.get("current_agent", "researcher")
if agent == "done":
return END
return agent
# 그래프 구성
workflow = StateGraph(AgentState)
workflow.add_node("researcher", research_agent)
workflow.add_node("writer", writer_agent)
workflow.add_node("reviewer", reviewer_agent)
workflow.add_edge(START, "researcher")
workflow.add_conditional_edges(
"researcher",
route_agent,
{"writer": "writer", END: END}
)
workflow.add_conditional_edges(
"writer",
route_agent,
{"reviewer": "reviewer", END: END}
)
workflow.add_conditional_edges(
"reviewer",
route_agent,
{END: END}
)
app = workflow.compile()
# 실행
result = app.invoke({
"messages": [HumanMessage(content="보고서를 작성해주세요")],
"current_agent": "researcher"
})
for msg in result["messages"]:
print(f"{getattr(msg, 'name', 'user')}: {msg.content}")
케이스 5: 메시지 필터링과 함께 사용
python
from typing import Annotated, TypedDict
from langgraph.graph import add_messages
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage, RemoveMessage
class FilteredState(TypedDict):
messages: Annotated[list, add_messages]
def cleanup_old_messages(state: FilteredState):
"""오래된 메시지 제거"""
messages = state["messages"]
# 최근 10개만 유지
if len(messages) > 10:
# 오래된 메시지 ID 수집
old_message_ids = [msg.id for msg in messages[:-10]]
# RemoveMessage 사용하여 제거
return {
"messages": [RemoveMessage(id=msg_id) for msg_id in old_message_ids]
}
return {"messages": []}
케이스 6: 조건부 메시지 추가
python
from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, add_messages, START, END
from langchain_core.messages import HumanMessage, AIMessage
class ConditionalState(TypedDict):
messages: Annotated[list, add_messages]
include_examples: bool
def responder(state: ConditionalState):
user_msg = state["messages"][-1].content
messages_to_add = [
AIMessage(content=f"질문: {user_msg}에 대한 답변입니다.")
]
# 조건부로 예시 추가
if state.get("include_examples", False):
messages_to_add.append(
AIMessage(content="예시: 이렇게 사용할 수 있습니다.")
)
return {"messages": messages_to_add}
workflow = StateGraph(ConditionalState)
workflow.add_node("respond", responder)
workflow.add_edge(START, "respond")
workflow.add_edge("respond", END)
app = workflow.compile()
# 예시 포함
result1 = app.invoke({
"messages": [HumanMessage(content="도움말")],
"include_examples": True
})
print(len(result1["messages"])) # 3개 (user + 2 AI)
# 예시 미포함
result2 = app.invoke({
"messages": [HumanMessage(content="도움말")],
"include_examples": False
})
print(len(result2["messages"])) # 2개 (user + 1 AI)
add_messages vs 일반 리스트 비교
python
from typing import Annotated, TypedDict
from langgraph.graph import add_messages
# 방법 1: add_messages 사용 (병합)
class StateWithReducer(TypedDict):
messages: Annotated[list, add_messages]
# 방법 2: 일반 리스트 (덮어쓰기)
class StateWithoutReducer(TypedDict):
messages: list
# 테스트
def test_node(state):
return {"messages": [{"role": "ai", "content": "새 메시지"}]}
# StateWithReducer 사용 시:
# 입력: {"messages": [{"role": "user", "content": "안녕"}]}
# 출력: {"messages": [
# {"role": "user", "content": "안녕"},
# {"role": "ai", "content": "새 메시지"}
# ]}
# StateWithoutReducer 사용 시:
# 입력: {"messages": [{"role": "user", "content": "안녕"}]}
# 출력: {"messages": [{"role": "ai", "content": "새 메시지"}]} # 기존 메시지 사라짐!
주요 장점
- 자동 병합: 명시적인 병합 코드 불필요
- ID 기반 업데이트: 스트리밍, 수정 시나리오에 유용
- 대화 히스토리 유지: 멀티턴 대화에 필수
- 에러 감소: 실수로 메시지 덮어쓰는 것 방지
주의사항
- LangGraph 전용 기능 (일반 Python 코드에서는 불가)
- 메시지는 LangChain 메시지 타입 권장 (HumanMessage, AIMessage 등)
- 메모리 관리 필요 시 주기적으로 오래된 메시지 제거
with Claude
'공부방 > Python & AI' 카테고리의 다른 글
| [AI] ChatPromptTemplate.from_messages (0) | 2025.10.13 |
|---|---|
| [Python] Sequence (0) | 2025.10.12 |
| [Python] Annotated (0) | 2025.10.12 |
| [AI] MessagesPlaceholder (0) | 2025.10.12 |
| [AI] ChatPromptTemplate (0) | 2025.10.12 |