LangChain.js로 간단한 RAG 앱 구현
RAG(Retrieval-Augmented Generation)는 대규모 언어모델(LLM)이 외부 데이터를 활용하여 더 정확한 답변을 제공하는 기법입니다. LangChain.js를 사용하여 간단한 RAG 앱을 구현해보겠습니다.
목차
- RAG 기본 개념 이해
- LangChain.js 설치
- 벡터 데이터베이 설정
- 문서 로드 및 분할
- 임베딩 및 저장
- 검색 및 응답 생성
- 간단한 웹 UI
1. RAG 기본 개념
RAG는 다음과 같은 과정으로 작동합니다:
- 문서 준비: 외부 문서를 여러 개의 청크로 나눕어 벡터로 변환하여 저장
- 검색(Retrieval): 사용자 질문과 관련된 문서 청크를 벡터 유사도 기반으로 검색
- 응답 생성(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. 추가로 구현할 기능
- 여러 문서 지원: PDF, 웹페이지 등 다양한 형식
- 메타데이터 필터링: 문서별, 섹션별 검색
- 재랭킹: 검색 결과의 순위 개선
- 스트리밍 응답: 실시간 응답 표시
결론
LangChain.js를 사용하면 RAG 앱을 쉽게 구현할 수 있습니다! 이 예제로 다음을 배웠습니다:
- 문서 로드 및 분할
- 임베딩 및 벡터 스토어
- 유사 문서 검색
- LLM과 통합한 응답 생성
다음으로는 더 복잡한 RAG 앱을 만들어보세요! 🚀
질문이 있거나 막히는 부분이 있다면 댓글로 남겨주세요! 😊