항해 플러스 AI 후기 6주차 WIL

1. 이번 주 목표

  • 커리큘럼 상의 목표는 챗봇의 형태의 서비스를 구현해보는것이다.

  • 커리큘럼 자체보다는 LangChain에 관심이 많아 이를 더 잘 이해하기 위한 포석으로 삼는다.


2. 학습 내용


1. LangChain

기본적으로 챗봇은 다음과 같은 방식으로 동작한다.


  1. 유저의 메세지를 LLM에 입력으로 전달

  2. LLM inference를 통해 출력으로 답변을 획득

  3. 획득한 답변을 유저에게 반환


그런데 LLM inference는 다음과 같은 문제들을 수반한다.


  • 다양한 LLM inference: 지금도 계속해서 새로운 LLM이 생겨나고 있고, 서로 다른 interface를 제공한다. 예를들어 HuggingFace에서 LLM들과 API를 통해 사용할 수 있는 GPT-4는 아예 다른 방식으로 구현해야 한다. 따라서 LLM을 교체할 때마다 새로 구현을 해야하는 비용이 발생한다.

  • Prompt design: LLM inference의 성능은 prompting에 따라 좌우된다. 특정한 template이 주어졌을 때 유저의 메세지를 잘 수정해주는 코드를 구현하는 것은 많은 공수가 들어간다.

  • 높은 외부 data source 사용 난이도: RAG와 같은 방식은 외부 data source를 활용한다. 하지만 이렇게 외부 source를 활용하는 방식은 개인이 처음부터 구현하기에 난이도가 너무 높다.


이런 문제들을 해결하기 위한 도구 중 LangChain이 있다.


  • LangChain이란?

    💡 LangChain은 대규모 언어 모델(LLM)을 사용하여 애플리케이션을 개발하기 위한 오픈 소스 오케스트레이션 프레임워크이다. 챗봇 같은 LLM 기반 애플리케이션을 구축하는 과정을 간소화시킬 수 있다.


LangChain이 제공하는 기능들을 요약하면 다음과 같다.

  • LLM abstraction: 여러가지 LLM interface를 쉽게 대응할 수 있는 pipeline을 제공한다. HuggingFace, GPT-4, Gemini 등 다양한 LLM들을 최소한의 코드 수정으로 inference할 수 있게 해준다.

  • Prompt template: 유저의 메세지를 주어진 template에 맞춰 자동으로 수정하는 기능을 제공한다.

  • Indexes: 외부 data source를 사용한 LLM inference를 쉽게 진행할 수 있게 indexes를 제공한다.


2. LangGraph

커뮤니티에서 LangGraph에 대한 언급을 몇번 목격한 적이 있다. LangChain에 대해 정리하게 된 김에, LangGraph에 대해도 한번 정리해보면 좋을 것 같다는 생각이 들었다.

LangChain으로 pipeline을 구성해서 상태 전달을 하고, 그 과정에서 출력을 만들어 낸다. 이 과정에서 발생할 수 있는 문제는 다음과 같다.

  1. LLM pipeline 내에서 상태 전달을 할 때 특정 단계의 출력이 문제가 되어 전체 파이프라인에 문제가 발생

  2. 이런 문제를 해결하기 위해 전후처리나 분기처리등을 추가하곤 하는데 LLM의 특성상 잘 제어되지 않음.

LangGraph는 LangChain 생태계에서 발생하는 위와 같은 상태 전달 문제들을 보완하기 위해 만든 프레임워크이다. 심지어 LangChain 진영에서 직접 만들었다.


2.1 LangGraph의 특징

  1. 상태 기반 그래프 구조: 애플리케이션의 로직을 노드와 엣지로 구성된 그래프로 표현한다.

  2. 유연한 상태 관리: 복잡한 상태를 쉽게 정의하고 관리할 수 있다.

  3. 조건부 라우팅: 동적인 의사결정 프로세스를 구현할 수 있다.

  4. 체크포인팅: 그래프 실행 상태를 저장하고 복원할 수 있어 장기 실행 태스크와 오류 복구에 유용하다.

  5. 서브그래프 지원: 복잡한 시스템을 모듈화하여 관리할 수 있다.


2.2 LangChain과의 비교

LangChain과 LangGraph는 둘 다 LLM 애플리케이션 개발을 위한 도구지만, 약간의 차이가 있다.

| 특징         | LangGraph                                      | LangChain                                     |
|--------------|------------------------------------------------|-----------------------------------------------|
| 주요 목적    | 복잡한 워크플로우 및 의사결정 프로세스 구현    | LLM 통합 및 체인 구성                         |
| 구조         | 그래프 기반                                    | 체인 및 에이전트 기반                         |
| 상태 관리    | 명시적이고 세밀한 제어                         | 암시적이고 자동화된 관리                      |
| 유연성       | 높음 (커스텀 로직 쉽게 구현)                   | 중간 (미리 정의된 컴포넌트 중심)              |
| 학습 곡선    | 상대적으로 가파름                              | 상대적으로 완만함                             |
| 용도         | 복잡한 AI 시스템, 다중 에이전트                | 간단한 LLM 애플리케이션, RAG                  |

현재 LangChain만 사용해본 입장에서 이렇게 장단점을 테이블로 정리해도 크게 와닿지는 않는다. 다만 선택을 한다고 했을 때 프로덕트의 현재 단계나 목적에 따라 선택이 나뉠 수 있을 것 같다.


  • 빠른 프로토타이핑으로 검증하기 위해서라면: LangChain

  • 검증은 이미 되었고 사용성을 더 고도화 하기 위해서라면: LangGraph


프로덕트의 라이프 사이클에서도 자연스럽다고 느껴진다. LangChain으로 빠른 검증을 하고, LangGraph로 이주한다. 혹은 개념상으로 LangGraph상의 단일 노드가 이전 단계에서 LangChain으로 만들어놓은 결과물이 될 수도 있다.

만약 내가 멀티모달 형태의 서비스를 구현해야 한다면 애초에 입력부터 분기가 발생하기 때문에 주저하지 않고 LangGraph를 선택할 것 같다.


3. Streamlit

앞에서 LangChain에 대해서는 충분히 알아봤다. 그럼 Backend는 LangChain으로 구성한다고 치고, 챗봇은 Frontend도 필요하지 않은가? 처음부터 프론트엔드를 붙여서 개발하기에는, LangChain의 장점인 빠른 프로토타입을 통한 검증이 상쇄되는 느낌이다.

그래서 이럴 때 사용하기 좋은 Streamlit 이라는 오픈소스 웹 프레임워크가 존재하고, 다음과 같은 특징이 있다.

  • Pure Python: 순수하게 Python으로 구성되어 있다. Python만 사용해서 UI를 구성할 수 있으므로, HuggingFace와 같은 AI Model을 쉽게 활용할 수 있는 장점이 있다.

  • 다양한 Widget: slider, 차트, 챗봇과 같은 직접 구현하기 복잡한데 활용도는 높은 widget들을 제공하고 있다.


3.1 예시 코드

다음은 Streamlit으로 챗봇을 구성하는 예시 코드이다.

import streamlit as st

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage


model = ChatOpenAI(model="gpt-4o-mini")

st.title("GPT Bot")

# Session state 초기화
if "messages" not in st.session_state:
    st.session_state.messages = []

# 만약 app이 rerun하면 message들을 다시 UI에 띄우기
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

if prompt := st.chat_input("What is up?"):
    with st.chat_message("user"):
        st.markdown(prompt)
    st.session_state.messages.append({"role": "user", "content": prompt})

    with st.chat_message("assistant"):
        messages = []
        for m in st.session_state.messages:
            if m["role"] == "user":
                messages.append(HumanMessage(content=m["content"]))
            else:
                messages.append(AIMessage(content=m["content"]))

        result = model.invoke(messages)
        response = result.content
        st.markdown(response)

        st.session_state.messages.append({
            "role": "assistant",
            "content": response
        })

여기서 Gemma3 같은 오픈소스 LLM으로 변경하는 방법도 존재할 것 같다.


3. 느낀점

  • 이제 드디어 LangChain과 LangGraph의 경계가 분명해졌다.

  • 챗봇을 구현하는건 재밌었다. Gemma3를 파인튜닝한 후 서빙해보면 좋을 것 같다.

다음 내용이 궁금하다면?

또는

이미 회원이신가요?

2025년 5월 4일 오후 1:51

조회 70

댓글 0