LangChain.js로 간단한 RAG 앱 구현

LangChain.js로 간단한 RAG 앱 구현

RAG Application

RAG(Retrieval-Augmented Generation)는 대규모 언어모델(LLM)이 외부 데이터를 활용하여 더 정확한 답변을 제공하는 기법입니다. LangChain.js를 사용하여 간단한 RAG 앱을 구현해보겠습니다.

목차

  • RAG 기본 개념 이해
  • LangChain.js 설치
  • 벡터 데이터베이 설정
  • 문서 로드 및 분할
  • 임베딩 및 저장
  • 검색 및 응답 생성
  • 간단한 웹 UI

1. RAG 기본 개념

RAG는 다음과 같은 과정으로 작동합니다:

  1. 문서 준비: 외부 문서를 여러 개의 청크로 나눕어 벡터로 변환하여 저장
  2. 검색(Retrieval): 사용자 질문과 관련된 문서 청크를 벡터 유사도 기반으로 검색
  3. 응답 생성(Generation): 검색된 청크를 컨텍스트로 제공하여 LLM이 답변 생성

장점:

  • LLM의 지식 한계 극복
  • 정보의 최신성 유지
  • 환각(Hallucination) 감소
  • 데이터 출처 추적 가능

2. 프로젝트 설정

mkdir rag-app
cd rag-app
npm init -y
npm install @langchain/openai @langchain/community dotenv

.env 파일:

OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxx

3. 문서 로드 및 분할

먼저 문서를 작은 조각(청크)으로 나누어야 합니다.

import { TextLoader } from "langchain/document_loaders/fs/text";
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";

async function loadDocuments(filePath) {
  // 문서 로드
  const loader = new TextLoader(filePath);
  const docs = await loader.load();

  // 문서 분할
  const splitter = new RecursiveCharacterTextSplitter({
    chunkSize: 1000,
    chunkOverlap: 200,
  });

  const chunks = await splitter.splitDocuments(docs);
  return chunks;
}

4. 임베딩 및 벡터 스토어

문서 청크를 임베딩하고 벡터 스토어에 저장합니다.

import { OpenAIEmbeddings } from "@langchain/openai";
import { MemoryVectorStore } from "langchain/vectorstores/memory";

async function createStore(chunks) {
  // 임베딩 모델
  const embeddings = new OpenAIEmbeddings({
    openAIApiKey: process.env.OPENAI_API_KEY,
  });

  // 벡터 스토어 생성 (인메모리 버전, 개발용)
  const vectorStore = await MemoryVectorStore.fromDocuments(
    chunks,
    embeddings
  );

  return vectorStore;
}

5. 검색 및 응답 생성

이제 질문을 받아 관련 문서를 검색하고 답변을 생성합니다.

import { ChatOpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";

async function askQuestion(vectorStore, question) {
  const embeddings = new OpenAIEmbeddings({
    openAIApiKey: process.env.OPENAI_API_KEY,
  });

  // 유사 문서 검색
  const results = await vectorStore.similaritySearch(question, 3);

  // 프롬프트 생성
  const prompt = PromptTemplate.fromTemplate(
    "다음 문맥을 바탕으로 질문에 답변하세요:

문맥:
{context}

질문:
{question}

답변:"
  );

  const formattedPrompt = await prompt.format({
    context: results.map(doc => doc.pageContent).join("

"),
    question: question,
  });

  // LLM 호출
  const llm = new ChatOpenAI({
    openAIApiKey: process.env.OPENAI_API_KEY,
    modelName: "gpt-4o-mini",
    temperature: 0.7,
  });

  const response = await llm.predict(formattedPrompt);
  return response;
}

6. 전체 예제

require('dotenv').config();
const { TextLoader } = "langchain/document_loaders/fs/text";
const { RecursiveCharacterTextSplitter } = "langchain/text_splitter";
const { OpenAIEmbeddings } = "@langchain/openai";
const { MemoryVectorStore } = "langchain/vectorstores/memory";
const { ChatOpenAI } = "@langchain/openai";
const { PromptTemplate } = "@langchain/core/prompts";

async function main() {
  // 1. 문서 로드
  const loader = new TextLoader('./my-document.txt');
  const docs = await loader.load();

  // 2. 문서 분할
  const splitter = new RecursiveCharacterTextSplitter({
    chunkSize: 1000,
    chunkOverlap: 200,
  });
  const chunks = await splitter.splitDocuments(docs);

  console.log(`분할된 청크 수: ${chunks.length}`);

  // 3. 임베딩 및 저장
  const embeddings = new OpenAIEmbeddings();
  const vectorStore = await MemoryVectorStore.fromDocuments(
    chunks,
    embeddings
  );

  // 4. 질문 응답
  const question = "이 문서에서 가장 중요한 주제는 무엇인가요?";
  const results = await vectorStore.similaritySearch(question, 3);

  const prompt = PromptTemplate.fromTemplate(
    "다음 문맥을 바탕으로 질문에 답변하세요:

문맥:
{context}

질문:
{question}

답변:"
  );

  const formattedPrompt = await prompt.format({
    context: results.map(doc => doc.pageContent).join("

"),
    question: question,
  });

  const llm = new ChatOpenAI({
    modelName: "gpt-4o-mini",
    temperature: 0.7,
  });

  const response = await llm.predict(formattedPrompt);
  
  console.log("
검색된 문서 조각:");
  results.forEach((doc, i) => {
    console.log(`${i + 1}. ${doc.pageContent.substring(0, 100)}...`);
  });

  console.log("
질문:", question);
  console.log("답변:", response);
}

main().catch(error => {
  console.error('Error:', error);
});

7. 프로덕션 환경 권장 사항

개발 환경에서는 인메모리 벡터 스토어를 사용했지만, 프로덕션에서는 다음을 권장합니다:

  • Pinecone: 가장 인기있는 관리형 벡터 DB
  • Weaviate: 오픈소스 벡터 DB
  • Milvus: 고성능 벡터 DB
  • Qdrant: 가볍고 설정 쉬운 벡터 DB

8. 추가로 구현할 기능

  1. 여러 문서 지원: PDF, 웹페이지 등 다양한 형식
  2. 메타데이터 필터링: 문서별, 섹션별 검색
  3. 재랭킹: 검색 결과의 순위 개선
  4. 스트리밍 응답: 실시간 응답 표시

결론

LangChain.js를 사용하면 RAG 앱을 쉽게 구현할 수 있습니다! 이 예제로 다음을 배웠습니다:

  • 문서 로드 및 분할
  • 임베딩 및 벡터 스토어
  • 유사 문서 검색
  • LLM과 통합한 응답 생성

다음으로는 더 복잡한 RAG 앱을 만들어보세요! 🚀

질문이 있거나 막히는 부분이 있다면 댓글로 남겨주세요! 😊