0

에이전틱 AI - 랭체인코리아 밋업 2025Q1

랭체인 코리아

LangGraph #1 - Chatbot

AF 김태영
2025.01.09 09:57
306

최근 LLM(Large Language Model) 기반 애플리케이션이 폭발적으로 증가함에 따라, LangChain 계열 라이브러리를 활용해 챗봇을 구축하는 사례도 점차 늘어나고 있습니다. 그중 LangGraph는 다중 노드(다중 에이전트) 워크플로우를 직관적으로 만들 수 있도록 돕는 프레임워크로, 상태(State)를 쉽게 관리하고 제어 흐름을 유연하게 구성해줍니다.

이번 글에서는 LangGraphAnthropic 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. 코드 구조 개요

  • LangGraphStateGraph라는 객체에 각 노드(함수)와 엣지(실행 흐름)를 등록하고,
    compile()을 통해 실제 실행 가능한 그래프(CompiledGraph)를 생성합니다.
  • Anthropic Claudelangchain_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 호출 등을 자유자재로 수행시킬 수 있습니다.

이상으로 LangGraphAnthropic Claude를 연동해 간단한 터미널 챗봇을 만드는 방법을 살펴봤습니다. 더 자세한 내용은 LangGraph 공식 문서LangChain의 다양한 레퍼런스를 참고해보시기 바랍니다. 

0
0개의 댓글
로그인 후 이용해주세요!