LangChain LangGraph로 AI 챗봇 구축하기 – 개발 초년생 가이드

왜 LangChain과 LangGraph인가?

AI 챗봇을 처음 만들어보려는 개발자라면 “어디서부터 시작해야 할까?”라는 고민이 클 것입니다. OpenAI API를 직접 호출해서 만들 수도 있지만, 실제 서비스에서는 훨씬 복잡한 요구사항들이 있죠. 사용자의 대화 맥락을 기억해야 하고, 외부 데이터베이스에서 정보를 가져와야 하고, 때로는 여러 단계의 추론 과정을 거쳐야 합니다.

LangChain은 이런 복잡한 AI 애플리케이션을 쉽게 구축할 수 있게 해주는 프레임워크입니다. 마치 레고 블록처럼 각각의 기능(프롬프트, 모델, 메모리, 도구 등)을 조합해서 원하는 애플리케이션을 만들 수 있어요. LangGraph는 여기서 한 걸음 더 나아가, 복잡한 워크플로우를 그래프 형태로 설계할 수 있게 해줍니다. 예를 들어 “사용자 질문 → 데이터 검색 → 답변 생성 → 검증”과 같은 단계별 처리가 가능하죠.

실제로 많은 스타트업과 기업들이 이 두 도구를 활용해 고객 서비스 봇, 업무 자동화 도구, 개인 비서 등을 구축하고 있습니다. 코드 몇 줄로 시작해서 점진적으로 기능을 확장해나갈 수 있다는 것이 가장 큰 장점입니다.

개발 환경 준비하기

첫 번째 챗봇을 만들기 전에 개발 환경을 준비해봅시다. Python 3.8 이상이 필요하고, 가상환경을 만드는 것을 강력히 추천합니다.

# 가상환경 생성 및 활성화
python -m venv chatbot_env
source chatbot_env/bin/activate  # Windows: chatbot_env\Scripts\activate

# 필수 패키지 설치
pip install langchain langchain-openai langgraph streamlit python-dotenv

환경변수 파일(.env)을 만들어 API 키를 관리하세요:

OPENAI_API_KEY=your_openai_api_key_here

OpenAI API 키는 platform.openai.com에서 발급받을 수 있습니다. 처음에는 무료 크레딧이 제공되니 부담없이 시작할 수 있어요.

개발을 위한 폴더 구조도 미리 정리해두면 좋습니다:

chatbot_project/
├── .env
├── app.py
├── chains/
├── tools/
└── data/

IDE는 VS Code를 추천하며, Python Extension Pack을 설치하면 개발이 한결 편해집니다. 이제 본격적으로 첫 번째 챗봇을 만들어볼 준비가 끝났습니다!

첫 번째 챗봇: 기본 대화 봇

가장 간단한 챗봇부터 시작해봅시다. 사용자의 질문에 답변하는 기본적인 대화 봇을 만들어보겠습니다.

# app.py
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain.schema import HumanMessage, SystemMessage

load_dotenv()

# ChatOpenAI 인스턴스 생성
llm = ChatOpenAI(
    temperature=0.7,  # 창의성 조절 (0~1)
    model="gpt-3.5-turbo"
)

def simple_chat(user_input):
    messages = [
        SystemMessage(content="당신은 친근하고 도움이 되는 AI 어시스턴트입니다."),
        HumanMessage(content=user_input)
    ]
    
    response = llm(messages)
    return response.content

# 테스트
if __name__ == "__main__":
    while True:
        user_input = input("질문: ")
        if user_input.lower() == 'quit':
            break
        
        answer = simple_chat(user_input)
        print(f"챗봇: {answer}\n")

이 코드는 정말 간단하지만 실제로 작동하는 챗봇입니다! temperature 매개변수로 답변의 창의성을 조절할 수 있고, SystemMessage를 통해 챗봇의 성격을 정의할 수 있어요.

실행해보면 자연스러운 대화가 가능하지만, 이전 대화 내용을 기억하지 못한다는 한계가 있습니다. 다음 단계에서 이 문제를 해결해보겠습니다.

메모리 추가: 대화 맥락을 기억하는 챗봇

실제 대화에서는 이전에 말한 내용을 참고해야 하죠. “그것에 대해 더 자세히 알려줘”라고 했을 때, “그것”이 무엇인지 알 수 있어야 합니다. LangChain의 메모리 기능을 활용해봅시다.

from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain

# 메모리 설정
memory = ConversationBufferMemory()

# 대화 체인 생성
conversation = ConversationChain(
    llm=llm,
    memory=memory,
    verbose=True  # 내부 처리 과정을 보여줌
)

def chat_with_memory(user_input):
    response = conversation.predict(input=user_input)
    return response

# 테스트 예시
print(chat_with_memory("내 이름은 김철수야"))
print(chat_with_memory("내 이름이 뭐라고 했지?"))  # 이전 대화를 기억함

ConversationBufferMemory는 모든 대화 내용을 저장합니다. 하지만 대화가 길어지면 토큰 한계에 걸릴 수 있어요. 이럴 때는 ConversationSummaryMemory나 ConversationBufferWindowMemory를 사용할 수 있습니다.

from langchain.memory import ConversationBufferWindowMemory

# 최근 5번의 대화만 기억
memory = ConversationBufferWindowMemory(k=5)

이제 챗봇이 대화의 맥락을 유지하면서 더 자연스러운 대화를 할 수 있게 되었습니다. 메모리는 챗봇 구축에서 가장 중요한 요소 중 하나이니 꼭 마스터해보세요!

실제 데이터 활용: RAG 챗봇 구축하기

지금까지는 일반적인 대화만 했지만, 실제 서비스에서는 특정 도메인의 지식이 필요한 경우가 많습니다. 회사 내부 문서나 제품 매뉴얼을 기반으로 답변해야 하는 챗봇을 만들어봅시다. 이를 RAG(Retrieval-Augmented Generation) 방식이라고 합니다.

from langchain.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import FAISS
from langchain.chains import RetrievalQA

# 1. 문서 로딩 및 분할
loader = TextLoader("company_policy.txt", encoding="utf-8")
documents = loader.load()

text_splitter = CharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=100
)
texts = text_splitter.split_documents(documents)

# 2. 벡터 데이터베이스 생성
embeddings = OpenAIEmbeddings()
vectorstore = FAISS.from_documents(texts, embeddings)

# 3. RAG 체인 구성
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=vectorstore.as_retriever(),
    return_source_documents=True
)

def rag_chat(question):
    result = qa_chain({"query": question})
    return result["result"]

이 코드는 텍스트 파일을 읽어서 벡터 데이터베이스를 만들고, 사용자 질문과 관련된 문서를 찾아 답변을 생성합니다. chunk_size와 chunk_overlap 매개변수를 조정해서 문서 분할 방식을 최적화할 수 있어요.

실제로는 PDF, 웹페이지, 데이터베이스 등 다양한 소스의 데이터를 활용할 수 있습니다. 이렇게 구축한 RAG 챗봇은 할루시네이션(잘못된 정보 생성)을 크게 줄일 수 있어 실용성이 높습니다.

LangGraph로 복잡한 워크플로우 만들기

때로는 단순한 질문-답변을 넘어서 복잡한 처리 과정이 필요합니다. 예를 들어 “데이터를 검색하고, 없으면 웹에서 찾고, 그래도 없으면 사용자에게 더 구체적인 질문을 요청하기” 같은 워크플로우 말이죠. LangGraph가 이런 상황에 perfect합니다!

from langgraph.graph import Graph
from langgraph.prebuilt import ToolNode

def search_internal_db(query):
    # 내부 데이터베이스 검색 로직
    # 실제로는 데이터베이스 쿼리 실행
    if "정책" in query:
        return "회사 정책 문서에서 관련 정보를 찾았습니다."
    return None

def search_web(query):
    # 웹 검색 로직 (실제로는 웹 검색 API 사용)
    return f"{query}에 대한 웹 검색 결과입니다."

def ask_clarification(query):
    return "더 구체적인 질문을 해주세요. 어떤 부분이 궁금하신가요?"

# 그래프 워크플로우 정의
def create_workflow():
    workflow = Graph()
    
    def router(state):
        query = state["query"]
        
        # 1단계: 내부 DB 검색
        internal_result = search_internal_db(query)
        if internal_result:
            return {"answer": internal_result, "next": "END"}
        
        # 2단계: 웹 검색
        if len(query) > 10:  # 충분히 구체적인 질문인 경우
            web_result = search_web(query)
            return {"answer": web_result, "next": "END"}
        
        # 3단계: 명확화 요청
        clarification = ask_clarification(query)
        return {"answer": clarification, "next": "END"}
    
    workflow.add_node("router", router)
    workflow.set_entry_point("router")
    
    return workflow.compile()

이런 방식으로 복잡한 의사결정 트리를 구성할 수 있습니다. 각 노드에서 상태를 확인하고 다음 단계를 결정하는 구조라 디버깅도 쉽고 확장성도 좋아요.

실용적인 도구 연동: 함수 호출과 API 통합

현실적인 챗봇은 대화만 하는 게 아니라 실제 작업도 수행해야 합니다. 날씨 정보를 가져오거나, 일정을 등록하거나, 이메일을 보내는 등의 기능 말이죠. LangChain의 도구(Tools) 기능을 활용해봅시다.

from langchain.tools import Tool
from langchain.agents import initialize_agent, AgentType
import requests

# 날씨 정보 도구
def get_weather(city):
    # 실제로는 날씨 API 호출
    api_key = "your_weather_api_key"
    url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}"
    
    try:
        response = requests.get(url)
        data = response.json()
        return f"{city}의 현재 날씨는 {data['weather'][0]['description']}입니다."
    except:
        return f"{city}의 날씨 정보를 가져올 수 없습니다."

# 계산기 도구
def calculator(expression):
    try:
        result = eval(expression)  # 실제로는 더 안전한 계산 방법 사용
        return f"{expression} = {result}"
    except:
        return "계산할 수 없는 식입니다."

# 도구 목록 정의
tools = [
    Tool(
        name="날씨조회",
        func=get_weather,
        description="도시 이름을 입력하면 현재 날씨를 알려줍니다."
    ),
    Tool(
        name="계산기",
        func=calculator,
        description="수학 계산을 수행합니다. 예: 2+2, 10*5"
    )
]

# 에이전트 생성
agent = initialize_agent(
    tools=tools,
    llm=llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True
)

# 사용 예시
response = agent.run("서울 날씨 어때?")
print(response)

이렇게 구성하면 챗봇이 사용자의 요청을 분석해서 적절한 도구를 선택하고 실행합니다. “서울 날씨 어때?”라고 물으면 자동으로 날씨조회 도구를 사용하죠. 도구는 얼마든지 추가할 수 있어서 확장성이 매우 좋습니다.

웹 인터페이스 구축: Streamlit으로 챗봇 배포하기

지금까지 터미널에서만 테스트했지만, 실제 사용자들은 웹 인터페이스를 선호합니다. Streamlit을 사용하면 몇 분 만에 멋진 웹 챗봇을 만들 수 있어요.

import streamlit as st
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain

# 페이지 설정
st.set_page_config(
    page_title="AI 챗봇",
    page_icon="🤖",
    layout="wide"
)

# 세션 상태 초기화
if "memory" not in st.session_state:
    st.session_state.memory = ConversationBufferMemory()
    st.session_state.conversation = ConversationChain(
        llm=llm,
        memory=st.session_state.memory
    )

if "messages" not in st.session_state:
    st.session_state.messages = []

# 제목
st.title("🤖 AI 챗봇")
st.write("안녕하세요! 무엇을 도와드릴까요?")

# 이전 대화 표시
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.write(message["content"])

# 사용자 입력
if prompt := st.chat_input("메시지를 입력하세요..."):
    # 사용자 메시지 추가
    st.session_state.messages.append({"role": "user", "content": prompt})
    
    with st.chat_message("user"):
        st.write(prompt)
    
    # AI 응답 생성
    with st.chat_message("assistant"):
        response = st.session_state.conversation.predict(input=prompt)
        st.write(response)
        st.session_state.messages.append({"role": "assistant", "content": response})

# 사이드바에 설정 옵션
with st.sidebar:
    st.header("설정")
    
    if st.button("대화 기록 삭제"):
        st.session_state.messages = []
        st.session_state.memory.clear()
        st.rerun()
    
    temperature = st.slider("창의성", 0.0, 1.0, 0.7)
    # 여기서 temperature 설정을 동적으로 변경할 수 있음

이제 streamlit run app.py 명령어로 웹 애플리케이션을 실행할 수 있습니다. 브라우저에 자동으로 인터페이스가 열리고, 채팅 형태로 AI와 대화할 수 있어요. 사이드바에는 추가 설정이나 기능을 배치할 수 있습니다.

Streamlit Community Cloud를 사용하면 무료로 배포도 가능합니다!

성능 최적화와 비용 관리

개발할 때는 크게 신경 쓰지 않았지만, 실제 서비스에서는 성능과 비용이 중요한 이슈가 됩니다. 몇 가지 핵심 최적화 기법을 소개하겠습니다.

캐싱 활용:

from functools import lru_cache

@lru_cache(maxsize=100)
def cached_embedding(text):
    return embeddings.embed_query(text)

스트리밍 응답:

def streaming_chat(query):
    for chunk in llm.stream(query):
        yield chunk.content

토큰 사용량 모니터링:

import tiktoken

def count_tokens(text, model="gpt-3.5-turbo"):
    encoding = tiktoken.encoding_for_model(model)
    return len(encoding.encode(text))

# 요청 전에 토큰 수 확인
token_count = count_tokens(user_input)
if token_count > 3000:
    st.warning("입력이 너무 깁니다. 줄여주세요.")

배치 처리: 여러 요청을 묶어서 처리하면 API 호출 횟수를 줄일 수 있습니다.

적절한 모델 선택:

  • 간단한 작업: gpt-3.5-turbo (저렴)
  • 복잡한 추론: gpt-4 (비싸지만 정확)
  • 코드 생성: code-davinci-002

이런 최적화를 통해 응답 속도를 개선하고 API 비용을 절약할 수 있어요. 실제 서비스에서는 이런 세부사항들이 사용자 경험에 큰 영향을 미칩니다.

실제 배포와 운영 고려사항

챗봇을 실제 서비스로 배포할 때 고려해야 할 요소들을 정리해봅시다. 개발할 때와는 완전히 다른 관점이 필요해요.

보안:

# API 키 관리
import os
from cryptography.fernet import Fernet

# 환경변수나 외부 비밀 관리 서비스 사용
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")

# 입력 검증
def validate_input(user_input):
    if len(user_input) > 1000:
        return False, "입력이 너무 깁니다."
    
    dangerous_patterns = ["__import__", "eval", "exec"]
    if any(pattern in user_input for pattern in dangerous_patterns):
        return False, "허용되지 않는 입력입니다."
    
    return True, "OK"

로깅과 모니터링:

import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def chat_with_logging(user_input):
    logger.info(f"User query: {user_input}")
    
    try:
        response = conversation.predict(input=user_input)
        logger.info(f"Bot response: {response}")
        return response
    except Exception as e:
        logger.error(f"Error: {str(e)}")
        return "죄송합니다. 오류가 발생했습니다."

확장성 고려:

  • 데이터베이스: 대화 기록 저장을 위한 PostgreSQL, MongoDB
  • 캐싱: Redis로 자주 사용되는 응답 캐싱
  • 큐잉: Celery로 무거운 작업 비동기 처리
  • 모니터링: Prometheus + Grafana로 성능 모니터링

A/B 테스팅: 다양한 프롬프트나 모델을 테스트해서 최적의 성능을 찾아야 합니다.

실제 운영에서는 사용자 피드백을 지속적으로 수집하고 모델을 개선하는 것이 중요합니다. 처음에는 완벽하지 않아도 괜찮으니 빠르게 배포하고 개선해나가는 것을 추천합니다!

다음 단계: 더 고급 기능들

기본적인 챗봇을 마스터했다면 이제 더 흥미로운 기능들을 도전해볼 차례입니다. 실제 비즈니스에서 활용되는 고급 패턴들을 소개합니다.

멀티모달 챗봇:

from langchain.schema.messages import HumanMessage

def analyze_image_and_text(image_path, text_query):
    with open(image_path, "rb") as image_file:
        base64_image = base64.b64encode(image_file.read()).decode()
    
    message = HumanMessage(
        content=[
            {"type": "text", "text": text_query},
            {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}}
        ]
    )
    
    response = llm([message])
    return response.content

개인화된 추천: 사용자의 대화 기록을 분석해서 맞춤형 응답을 제공하는 시스템을 구축할 수 있습니다.

다국어 지원:

from langdetect import detect

def multilingual_chat(user_input):
    detected_lang = detect(user_input)
    
    if detected_lang != 'ko':
        # 번역 후 처리
        translated_input = translate_to_korean(user_input)
        response = conversation.predict(input=translated_input)
        return translate_from_korean(response, detected_lang)
    
    return conversation.predict(input=user_input)

음성 인터페이스: Whisper API와 TTS를 연동해서 음성 기반 챗봇도 만들 수 있어요.

이런 고급 기능들을 하나씩 추가해보면서 자신만의 독특한 챗봇을 만들어보세요. 가능성은 무한합니다!

맺음말

지금까지 LangChain과 LangGraph를 활용한 AI 챗봇 구축 여정을 함께 했습니다. 처음에는 복잡해 보였지만, 단계별로 접근하면 생각보다 어렵지 않다는 것을 느꼈을 거예요.

가장 중요한 것은 완벽한 챗봇을 처음부터 만들려고 하지 말고, 간단한 것부터 시작해서 점진적으로 개선해나가는 것입니다. 사용자의 피드백을 받고, 데이터를 분석하고, 새로운 기능을 추가하는 반복적인 과정이 진짜 좋은 AI 서비스를 만드는 비결이에요.

이제 여러분도 직접 챗봇을 만들어보세요. 어떤 문제를 해결하고 싶은지, 누구를 위한 서비스인지 명확히 하고 시작하면 더 의미 있는 프로젝트가 될 것입니다.

개발하면서 막히는 부분이 있다면 LangChain 공식 문서와 커뮤니티를 적극 활용하세요. AI 기술은 빠르게 발전하고 있으니 계속 학습하고 실험하는 자세가 중요합니다.

Leave a Comment