
최근 LLM(Large Language Model) 기반 애플리케이션이 폭발적으로 증가함에 따라, LangChain 계열 라이브러리를 활용해 챗봇을 구축하는 사례도 점차 늘어나고 있습니다. 그중 LangGraph는 다중 노드(다중 에이전트) 워크플로우를 직관적으로 만들 수 있도록 돕는 프레임워크로, 상태(State)를 쉽게 관리하고 제어 흐름을 유연하게 구성해줍니다.
이번 글에서는 LangGraph와 Anthropic Claude를 간단히 연동해, 터미널에서 동작하는 챗봇 예제를 만들어보겠습니다.
1. 사전 준비
Python 가상환경
python -m venv .venv
source .venv/bin/activate
필요 라이브러리 설치
pip install langgraph langsmith langchain_anthropic grandalf
Anthropic API Key 발급
- Anthropic Console에서 API Key를 생성
- 아래와 같이 환경변수 설정(예: Linux/Mac):
export ANTHROPIC_API_KEY="여러분의_키"
이 과정을 거치면 준비는 끝났습니다.
2. 코드 구조 개요
- LangGraph는
StateGraph
라는 객체에 각 노드(함수)와 엣지(실행 흐름)를 등록하고,compile()
을 통해 실제 실행 가능한 그래프(CompiledGraph
)를 생성합니다. - Anthropic Claude는
langchain_anthropic.ChatAnthropic
클래스로 LLM 객체를 만들고,
여기에.invoke(messages)
메서드를 사용해 대화를 주고받습니다.
3. 전체 코드
아래 ch1_chatbot.py
코드를 작성한 후, python ch1_chatbot.py
로 실행하면 됩니다.
import os
# LangChain + Anthropic 연동을 위한 클래스
from langchain_anthropic import ChatAnthropic
# LangGraph 기본 구성요소 (StateGraph, START, END, add_messages 등)
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
# 타입 어노테이션
from typing import Annotated
from typing_extensions import TypedDict
################################################################################
# 1) State(상태) 정의
################################################################################
# - LangGraph에서는 "상태(State)"가 매우 중요합니다.
# - 챗봇이라면, 이전까지 오간 모든 메시지를 'messages' 리스트에 저장할 수 있습니다.
# - 아래는 그런 목적을 가진 State를 정의합니다.
# - 'add_messages'를 통해, 새 메시지를 덧붙이는 형태로 유지(append)할 것입니다.
class State(TypedDict):
messages: Annotated[list, add_messages]
################################################################################
# 2) 그래프 빌더(StateGraph) 생성
################################################################################
# - LangGraph의 핵심: "그래프(Graph)"라는 개념.
# - 그래프는 여러 "노드(Node)"들이 서로 연결(Edge)되어 있는 형태를 말합니다.
# - StateGraph(...) 생성 시, 우리가 어떤 'State' 구조를 쓸지 지정해야 합니다.
# - 추후 노드를 등록하고, 엣지(edge)로 어떤 순서로 실행할지 짜면 "워크플로우"가 완성됩니다.
graph_builder = StateGraph(State)
################################################################################
# 3) LLM(Anthropic Claude) 객체 생성
################################################################################
# - LangChain의 ChatAnthropic 클래스를 이용해 Anthropic Claude 모델과 연결합니다.
# - model: "claude-3-5-sonnet-20241022" 등 원하는 버전명으로 지정 가능
# - anthropic_api_key: Anthropic 계정에서 발급받은 API 키를 환경변수로 설정 후 가져오기
llm = ChatAnthropic(
model="claude-3-5-sonnet-20241022",
anthropic_api_key=os.environ.get("ANTHROPIC_API_KEY")
)
################################################################################
# 4) 노드(Node) 정의: chatbot 함수
################################################################################
# - "노드(Node)"란, 그래프 내에서 특정 기능(로직)을 담당하는 '단계(step)'를 의미합니다.
# - 여기서는 'chatbot' 노드가, "LLM에게 메시지를 던지고 응답을 받는 역할"을 합니다.
# - LangGraph가 노드를 실행할 때, 현재 상태(State)를 함수 인자로 넣어주고
# 함수가 반환한 dict를 통해 state가 업데이트됩니다.
def chatbot(state: State):
"""
chatbot 노드 함수:
1) state["messages"] 내 기존 메시지(사용자 발화 등)를 LLM에 전달
2) LLM이 준 새 AI 응답을 'messages' 리스트에 추가한다.
"""
# LLM.invoke(...) -> state["messages"]를 토대로 AI가 답변(메시지 객체)을 생성
ai_resp = llm.invoke(state["messages"])
# 반환: {"messages": [새로운 메시지 객체]}
# -> LangGraph가 add_messages 방식으로 기존 리스트에 덧붙임
return {
"messages": [ai_resp]
}
################################################################################
# 5) 그래프 빌더에 "노드" 등록
################################################################################
# - 아래에서 graph_builder.add_node("chatbot", chatbot)를 호출:
# => "chatbot"이란 이름의 노드(Node)를 그래프에 추가
# => 실제 로직은 chatbot(...) 함수로 실행
# - 노드 이름(첫 번째 인자) = "chatbot"
# - 노드 함수(두 번째 인자) = 위에서 정의한 chatbot 함수
graph_builder.add_node("chatbot", chatbot)
################################################################################
# 6) 그래프(Workflow) 내 "엣지(Edge)" 정의
################################################################################
# - 그래프 = 노드 + 엣지(노드와 노드를 연결)
# - LangGraph가 "시작(START)" 노드로부터 어디로 가야 하는지, 끝(END)은 어디인지 정의.
# - 여기서는 단순히,
# START -> "chatbot" -> END
# 라는 흐름을 만든다.
# - 즉, 그래프를 실행할 때:
# (1) START 노드로부터 시작
# (2) "chatbot" 노드를 실행 (chatbot 함수가 동작)
# (3) "chatbot" 노드 완료되면 END로 가서 종료
graph_builder.add_edge(START, "chatbot")
graph_builder.add_edge("chatbot", END)
################################################################################
# 7) 그래프를 "컴파일(compile)"하여 사용 가능하게 만듦
################################################################################
# - graph_builder.compile()을 호출하면, 우리가 지금까지 정의한 노드와 엣지를 바탕으로
# 실제로 실행 가능한 'CompiledGraph' 객체가 생성됩니다.
# - compile()을 하지 않으면, 아직은 그래프가 "설계도 상태"입니다.
# - compile() 결과물인 graph는 .stream() 또는 .invoke()를 통해 실행할 수 있습니다.
graph = graph_builder.compile()
################################################################################
# 그래프 시각화를 위한 ASCII 아트 생성
################################################################################
# - graph.get_graph().draw_ascii()를 호출하여 그래프 구조를 ASCII 아트로 표현
# - 노드와 엣지의 연결 관계를 시각적으로 보여줌
# - START -> chatbot -> END 형태의 단순한 흐름이 표현됨
ascii_graph = graph.get_graph().draw_ascii()
print(ascii_graph)
################################################################################
# 8) 간단한 메인 함수(main)
################################################################################
# - 아래 함수는 콘솔용 대화 루프를 제공합니다.
# - 사용자가 입력을 하면, LangGraph의 graph.stream(...)으로 메시지를 주고
# AI 응답을 받아 출력하는 식입니다.
# - 'quit', 'exit', 'q' 등을 입력하면 대화를 종료.
def main():
while True:
# 사용자 입력 받음
user_input = input("User: ")
# 특정 단어 입력 시 종료
if user_input.lower() in ["quit", "exit", "q"]:
print("Goodbye!")
break
# (A) graph.stream({"messages": [("user", user_input)]})
# => LangGraph에 "messages" 키로 [('user', user_input)] 튜플을 넘김
# => State 에는 [('user', '...')] 형태가 메시지로 추가됨
#
# (B) for event in graph.stream(...):
# => 그래프 실행 과정에서 발생하는 "상태 업데이트" 이벤트들을 순차적으로 받음
# => chatbot 노드가 실행되면 AI의 응답 메시지를 생성하여 event.values()에 포함
#
# (C) event.values() 안의 "messages"를 찾아, 가장 마지막 메시지를 출력 (AI 답변)
for event in graph.stream({"messages": [("user", user_input)]}):
# event = {"(노드이름)": {"messages": [...]} ...} 형태 등
# event.values() = 각 노드 실행 결과(딕셔너리)들의 값
for value in event.values():
# value["messages"] -> 이번 단계에서 추가된 메시지 리스트
# [-1] => 가장 마지막 메시지(새로 생성된 AI 메시지)
print("Assistant:", value["messages"][-1].content)
################################################################################
# 9) 실행 엔트리포인트
################################################################################
# - 아래 if 문은 "이 파일을 python 으로 직접 실행했을 때"만 main() 함수를 호출.
# - 이 파일이 다른 모듈에서 import될 경우 main()은 자동으로 실행되지 않는다.
if __name__ == "__main__":
main()
실행 예시
$ python ch1_chatbot.py
+-----------+
| __start__ |
+-----------+
*
*
*
+---------+
| chatbot |
+---------+
*
*
*
+---------+
| __end__ |
+---------+
User: 안녕
Assistant: 안녕하세요! 무엇을 도와드릴까요?
User: LangGraph가 뭐야?
Assistant: LangGraph는 ...
...
- User: 라고 입력하면 Anthropic Claude에게 메시지를 전달하고,
- Assistant: Claude가 생성한 응답을 출력해줍니다.
- quit, exit, q 등을 입력하면 프로그램을 종료합니다.
4. 정리 및 확장 아이디어
- 이 예시는 가장 기본적인 흐름인
START -> chatbot -> END
만 사용했습니다. - LangGraph의 강점은 다양한 노드(Node)를 연결해 여러 단계(Workflow)로 확장할 수 있다는 점입니다.
- 예: “챗봇 노드 -> 검색 노드 -> 요약 노드 -> 챗봇 노드” 등
- 메모리 체크포인트, Human-in-the-loop, ToolNode 등을 추가하면 훨씬 강력한 다중 단계 챗봇/에이전트가 만들어집니다.
langchain_community.tools
등 다양한 툴을 연동해 검색, DB 조회, API 호출 등을 자유자재로 수행시킬 수 있습니다.
이상으로 LangGraph와 Anthropic Claude를 연동해 간단한 터미널 챗봇을 만드는 방법을 살펴봤습니다. 더 자세한 내용은 LangGraph 공식 문서나 LangChain의 다양한 레퍼런스를 참고해보시기 바랍니다.