처음부터 시작하는 LangGraph RAG 에이전트 구축 — 모든 단계를 보여주는 실시간 UI와 함께
요약
LangChain과 LangGraph를 사용하여 기초적인 LLM 호출부터 실시간 시각화가 가능한 RAG 에이전트까지 구축하는 단계별 가이드입니다. React UI와 FastAPI를 결합하여 에이전트의 실행 루프를 실시간으로 모니터링하는 전체 파이프라인을 다룹니다.
핵심 포인트
- LangChain의 LCEL을 활용한 컴포넌트 구성 방식 학습
- LangGraph를 이용한 에이전트의 상태 머신 모델링
- 도구 호출(Tool Calling)의 작동 원리와 실행 구조 이해
- SSE를 활용한 에이전트 루프의 실시간 UI 시각화 구현
저는 LangChain과 LangGraph를 단계별로 가르쳐주는 학습 프로젝트를 만들었습니다. 가공되지 않은 LLM 호출에서 시작하여, RAG(Retrieval-Augmented Generation)를 기반으로 하는 완전한 ReAct 에이전트까지 구현하며, 이를 SSE(Server-Sent Events)를 통해 에이전트 루프의 모든 노드를 실시간으로 시각화하는 React UI로 스트리밍합니다.
이 포스트는 전체 과정을 안내합니다: 각 개념이 무엇을 하는지, 어떻게 다음 단계로 연결되는지, 그리고 실시간 파이프라인 뷰가 어떻게 작동하는지를 다룹니다.
우리가 만드는 것
frontend/ ← React + Vite 채팅 UI (실시간 에이전트 루프 시각화)
backend/ ← RAG 에이전트를 래핑하는 FastAPI 서버
step*.py ← 6개의 단계별 학습 파일
에이전트는 속도 제한(rate limiting) 알고리즘에 관한 질문에 답합니다. 이는 단지 도메인일 뿐이며, 진짜 목표는 LangChain과 LangGraph가 어떻게 서로 맞물려 작동하는지를 이해하는 것입니다.
6단계 학습 경로
| 파일 | 도입되는 개념 |
|---|---|
step1_llm_basics.py | 채팅 모델(Chat models), 메시지(messages), .invoke(), 무상태성(statelessness) |
| ... |
Step 1 — Raw LLM 호출
가장 단순한 작업: 모델을 호출하고 응답을 읽는 것입니다.
from langchain_groq import ChatGroq
from langchain_core.messages import SystemMessage, HumanMessage
...
핵심 통찰: LLM은 무상태(stateless)입니다. 모든 호출은 독립적입니다. 매번 전체 메시지 리스트를 전달함으로써 대화 기록(conversation history)을 직접 관리해야 합니다.
Step 2 — 프롬프트 템플릿(Prompt templates)과 LCEL 체인
LangChain Expression Language (LCEL)를 사용하면 Unix 파이프와 동일한 방식으로 | 파이프 연산자를 사용하여 컴포넌트를 구성할 수 있습니다.
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
...
핵심 통찰: LCEL 체인은 지연 실행(lazy)됩니다. .stream()과 .batch()가 일급 객체(first-class)로 취급되어 별도의 추가 코드 없이도 사용할 수 있습니다.
Step 3 — 도구(Tools)와 도구 호출(tool calling)
도구(Tools)를 사용하면 LLM이 행동을 취할 수 있습니다. @tool 데코레이터는 Python 함수를 모델이 호출할 수 있는 형태로 변환합니다.
from langchain_core.tools import tool
from langchain_groq import ChatGroq
...
핵심 통찰 (Key insight): bind_tools()는 도구 스키마 (tool schemas)를 모델로 전송합니다. 모델은 구조화된 tool_calls 리스트를 반환하며, 모델 스스로 도구를 실행하지는 않습니다. 사용자가 도구를 실행하고 그 결과를 다시 모델에 전달해야 합니다.
4단계 — LangGraph 기초
LangGraph는 에이전트를 **상태 머신 (state machine)**으로 모델링합니다. 다음 요소들을 정의합니다:
- 상태 (State) — 그래프를 통해 흐르는 타입이 지정된 딕셔너리 (typed dict)
- 노드 (Nodes) — 상태를 전달받아 업데이트를 반환하는 Python 함수
- 엣지 (Edges) — 노드 간의 연결 (조건부 분기 포함)
from langgraph.graph import StateGraph, END
from langgraph.graph.message import add_messages
from typing import Annotated
...
핵심 통찰 (Key insight): add_messages는 **리듀서 (reducer)**입니다. 노드가 {"messages": [new_msg]}를 반환하면, LangGraph는 이를 기존 리스트를 교체하는 대신 리스트에 추가(append)합니다. 이를 통해 대화 기록 (conversation history)이 자동으로 누적됩니다.
5단계 — 전체 ReAct 에이전트
ReAct 패턴 (Reason + Act, 추론 + 행동)은 다음과 같습니다: LLM이 무엇을 할지 결정함 → 도구가 이를 실행함 → LLM이 결과를 확인함 → 반복.
LangGraph의 ToolNode는 실행 측면을 자동으로 처리합니다.
from langgraph.graph import StateGraph, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
...
루프 (The loop):
START → [llm] → tool_calls가 있는가? → YES → [tools] → [llm]으로 복귀
→ NO → END
6단계 — RAG 추가
검색 증강 생성 (Retrieval-Augmented Generation, RAG)은 에이전트에게 문서로부터 얻은 장문 지식을 제공합니다. 우리는 문서를 FAISS 벡터 스토어 (vector store)에 임베딩 (embed)하고 이를 도구로 노출합니다.
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_text_splitters import RecursiveCharacterTextSplitter
...
핵심 통찰 (Key insight): 에이전트의 관점에서 RAG는 단지 하나의 도구일 뿐입니다. LLM은 질문을 바탕으로 RAG를 언제 호출할지 결정합니다. 리트리버 (retriever)는 쿼리 (query)를 임베딩 (embedding)으로 변환하고, FAISS에서 가장 가까운 청크 (chunks)를 찾아 컨텍스트 (context)로 반환합니다.
FastAPI 백엔드 — SSE 스트리밍 (SSE streaming)
백엔드는 에이전트 (agent)를 FastAPI 서버로 감쌉니다. 흥미로운 부분은 스트리밍 엔드포인트 (streaming endpoint)로, 그래프 내의 모든 내부 상태 변화에 대해 이벤트를 발생시키는 세밀한 비동기 제너레이터 (async generator)인 agent.astream_events()를 사용한다는 점입니다.
from fastapi.responses import StreamingResponse
from langchain_core.messages import HumanMessage
import json
...
astream 대신 astream_events를 사용하는 이유는 무엇인가요?
astream()은 완료된 각 _노드 (node)_당 하나의 이벤트를 제공하므로 입도가 거칩니다 (coarse-grained). 반면 astream_events(version="v2")는 모델 시작/스트림/종료, 도구 (tool) 시작/종료, 체인 (chain) 시작/종료와 같은 모든 내부 라이프사이클 훅 (lifecycle hook)에 대해 이벤트를 발생시킵니다. 이를 통해 개별 토큰 (token)과 라우팅 결정 (routing decision)을 실시간으로 보여줄 수 있습니다.
React UI — 실시간 에이전트 루프 시각화
모든 어시스턴트 응답에는 접을 수 있는 에이전트 루프 (Agent Loop) 패널이 표시됩니다. 각 노드 카드 (node card)는 SSE 스트림으로부터 해당 이벤트가 도착함에 따라 실시간으로 나타나고 업데이트됩니다.
🚀 StateGraph Initialized [langgraph]
StateGraph.compile() · add_messages reducer
↓
...
노드들은 색상으로 구분됩니다 (colour-coded):
- 파란색 테두리 + 스피너 (spinner) = 현재 활성화됨
- 진한 녹색 테두리 + ✓ = 완료됨
- 점선 테두리 = 라우팅 (routing) / 조건부 엣지 (conditional edge)
배지 (badge)를 통해 어떤 프레임워크가 담당하고 있는지 식별할 수 있습니다: langgraph (보라색) vs langchain (주황색).
타자기 효과 (Typewriter effect)
LLM으로부터 오는 토큰들은 SSE를 통해 버스트 (bursts) 형태로 도착합니다. 토큰을 즉시 적용하는 대신, 문자 큐 (character queue)를 고정된 속도(문자당 18ms)로 비워내어 텍스트가 읽기 좋은 속도로 타이핑되도록 합니다:
const CHAR_DELAY = 18 // 문자당 ms
// 토큰 이벤트가 도착하면, 각 문자를 큐에 넣습니다
...
스택 (Stack)
| 계층 (Layer) | 기술 (Technology) |
|---|---|
| LLM | Groq — llama-4-scout-17b (도구 호출 (tool calling)), llama-3.3-70b (텍스트) |
| ... |
실행하기
# Python 의존성 (시스템 Python 문제를 피하기 위해 uv 사용)
uv venv .venv --python 3.12
uv pip install -r requirements.txt
...
**http://localhost:5173**을 엽니다. 첫 실행 시 임베딩 모델(~90 MB)을 다운로드하고 캐시합니다.
배운 점
LangChain은 모델(models), 프롬프트 템플릿(prompt templates), 도구(tools), LCEL 체인(LCEL chains), 벡터 스토어(vector stores)와 같은 빌딩 블록(building blocks)을 제공합니다.
LangGraph는 제어 흐름(control flow)을 제공합니다. 즉, 루프(loop), 분기(branching), 그리고 중단 시점을 직접 결정할 수 있는 상태 머신(state machine)입니다.
이 두 가지는 자연스럽게 결합됩니다. LangGraph 노드(nodes)가 LangChain 컴포넌트(components)를 호출하고, LangChain 도구(tools)는 add_messages를 통해 결과를 LangGraph 상태(state)로 다시 전달합니다.
가장 명확하게 이해되었던 부분은 루프를 보여주는 UI를 구축하는 것이었습니다. 그래프가 실시간으로 실행되는 것을 지켜볼 때 — LLM 노드가 밝아지고, 라우팅 결정(routing decision)이 실행되며, ToolNode가 회전하고, 다시 LLM 노드가 실행되는 과정 — ReAct 패턴은 더 이상 추상적인 개념이 아니라 눈으로 직접 확인할 수 있는 실체가 됩니다.
_전체 소스 코드는 GitHub에 있습니다. step_.py 파일들은 순서대로 읽도록 설계되었습니다. 각 파일은 독립적이며 정확히 하나의 새로운 개념을 소개합니다.
*
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기