LangGraph, PGVector 및 MCP를 활용하여 포트폴리오에 RAG 챗봇을 구축한 방법
요약
LangGraph, PGVector, MCP를 활용하여 실시간 GitHub 데이터를 연동하는 RAG 기반 포트폴리오 챗봇 구축 방법을 설명합니다. 단순한 텍ml 검색을 넘어 에이전트가 도구를 결정하고 실시간 데이터를 가져오는 아키텍처를 다룹니다.
핵심 포인트
- PGVector와 점수 임계값을 활용한 환각 방지 RAG 구현
- LangGraph의 StateGraph를 이용한 에이전트 기반 도구 호출 제어
- MCP(Model Context Protocol)를 통한 실시간 GitHub 데이터 연동
- 대화 맥락 유지를 위한 메모리 레이어 및 스트리밍 응답 적용
대부분의 포트폴리오는 지루한 'About Me' 문단으로 구성되어 있습니다. 저는 이를 사용자가 직접 대화할 수 있는 것으로 대체했습니다. 즉, 저에 대한 질문에 답하고, 실시간 GitHub 활동을 가져오며, 대화를 기억하는 AI 터미널입니다. 지금 바로 제 포트폴리오에서 시도해 보실 수 있습니다: rehbarkhan.in.
저는 Full Stack 및 Gen-AI 개발자인 Rehbar Khan이며, 이 글에서는 이것이 정확히 어떻게 작동하는지—RAG 파이프라인, LangGraph 에이전트, 메모리 레이어, 그리고 MCP를 사용하여 실제 GitHub 데이터를 연결한 방법까지—자세하게 설명할 것입니다. 군더더기 없이, 오직 아키텍처만 다룹니다.
문제점 (The problem)
저는 다음과 같은 기능을 가진 포트폴리오 비서가 필요했습니다:
- 제 배경에 대한 질문에 정확하게 답할 수 있어야 합니다. 환각된 직장이나 가짜 프로젝트는 안 됩니다.
- 오래된 스냅샷이 아닌, 실시간 데이터를 가져와야 합니다 (최신 GitHub 활동).
- 메시지 전반에 걸쳐 대화를 기억해야 합니다.
- 실제 터미널처럼 토큰 단위로 응답을 스트리밍해야 합니다.
이는 단순히
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_postgres import PGVector
...
chunk_size=500 / overlap=80을 사용한 이유? 제 프로필 데이터는 밀도가 높고 사실 중심적입니다. 따라서 짧은 청크를 유지하면 관련 없는 컨텍스트가 끌어와지지 않으면서 각 사실을 검색할 수 있습니다. 오버랩(overlap)은 문장이 청크 경계에서 중간에 잘리는 것을 방지합니다.
쿼리 시에는 단순히 상위 k개(top-k)만 가져오는 것이 아니라 **점수 임계값(score threshold)**을 사용하여 검색하기 때문에, 관련 없는 일치 항목들이 프롬프트를 채우는 대신 걸러집니다:
retriever = vectorstore.as_retriever(
search_type="similarity_score_threshold",
search_kwargs={"k": 2, "score_threshold": 0.4},
...
이것이 반환각성(anti-hallucination) 트릭입니다: 임계값을 통과하는 것이 아무것도 없다면, 모델은 컨텍스트를 받지 못하고 무언가를 지어내기보다 "모릅니다"라고 답변합니다.
2. LangGraph 에이전트
선형 체인(linear chain) 대신 그래프를 사용해서 모델이 도구를 호출할지(예: GitHub 데이터 가져오기) 아니면 직접 답변할지를 _결정_하도록 했습니다:
from langgraph.graph import StateGraph, START
from langgraph.prebuilt import ToolNode, tools_condition
...
tools_condition은 모델이 도구 호출을 방출할 때만 도구 노드(tool node)로 라우팅하고 다시 루프합니다. 깔끔하며 취약한 if/else 라우팅보다 좋습니다.
3. MCP를 통한 실시간 GitHub 데이터
봇은 제가 GitHub MCP 서버를 도구 소스(tool source)로 연결했기 때문에 "Rehbar가 최근에 무엇을 만들었는지?"와 같은 질문에 실제 데이터를 가지고 답변할 수 있습니다:
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient({
...
MCP가 핵심입니다. API 래퍼(API wrappers)를 직접 작성하지 않고도 전체 도구 세트를 얻을 수 있습니다. 에이전트가 스스로 적절한 도구를 선택합니다.
4. Redis를 사용한 메모리
LangGraph 체크포인터(checkpointer) 덕분에 대화 메모리가 거의 무료입니다. 저는 세션 ID로 키를 지정하는 Redis 기반 저장소(Redis-backed saver)를 사용하여 방문자 각자가 자신만의 스레드를 갖도록 했습니다:
graph = builder.compile(checkpointer=AsyncRedisSaver(...))
제가 배운 것
제가 배운 것
- Score 임계값(Score thresholds)이 사실 기반 RAG에서 top-k를 능가합니다. 잘못된 사실의 비용이 높을 때는 재현율(recall)보다 정밀도(precision)가 중요합니다.
- MCP는 접착 코드(glue code)를 제거합니다. 라이브 데이터 소스를 추가하는 과정이 'API 클라이언트 작성'에서 '서버를 가리키기만 하는 것'으로 바뀌었습니다.
- 조건부 도구 사용(conditional tool use)이 필요해지는 순간, 그래프가 체인보다 우수합니다. (Graphs > chains)
직접 해보세요
이 챗봇은 제 포트폴리오에 라이브로 구현되어 있습니다. 제 작업물에 대해 무엇이든 물어보세요: rehbarkhan.in. 전체 스택은 Next.js + FastAPI + LangGraph + pgvector + Redis입니다.
저는 Full Stack & Gen-AI 개발자인 Rehbar Khan이며, LangChain, RAG 및 LLM을 사용해 지능형 시스템을 구축하고 있습니다. 더 많은 정보는 rehbarkhan.in · GitHub · LinkedIn에서 확인하세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기