LLM의 등장은 많은 부분을 바꾸어 놓았습니다. 질문을 던지면 자연스럽게 답을 주고, 복잡한 논리까지 이해하는 능력은 이미 사람을 능가할 정도입니다.
하지만 아무리 뛰어난 LLM이라도, 그 자체로는 여전히 풀지 못하는 근본적인 한계가 존재합니다.
최근 Italian Brainrot밈을 접한 저는 GPT에게 트랄랄레로 트랄랄라와 퉁퉁퉁 사후르가 싸우면 누가 이길지 질문해 보았습니다.
하지만 이 친구와 더이상 토론하는 것은 의미가 없다는 것을 깨달았습니다.
무료 버전의 GPT는 퉁퉁퉁 사후르가 무엇인지, 트랄랄레로 트랄랄라가 무엇인지 정확히 알지 못하였고, 결국 스스로 새로운 캐릭터를 상상해내며 답변을 이어갔습니다.
왜냐하면, 이 Italian Brainrot 밈은 2025년 초에 등장했기 때문입니다. LLM은 훈련 당시까지의 데이터에만 기반하기 때문에, 훈련 이후 새로 생긴 문화나 사건은 스스로 알 수 없습니다.
물론, GPT-4o와 같은 최신 모델들은 웹에 접근할 수 있는 기능을 통해 어느 정도 외부 정보를 기반으로 답변할 수 있습니다.
하지만 그럼에도 불구하고, 검색된 정보의 품질이나 신뢰성 문제는 여전히 남아있으며 최신 트렌드나 정보를 정확히 반영하기 위해서는 추가적인 검증 구조가 필요합니다.
LLM은 훈련이 완료된 시점의 데이터를 바탕으로 동작합니다. 모델을 학습시킬 때 수많은 문서, 책, 웹페이지를 사용하지만, 그 이후로 정보는 계속 변합니다.
최신 정보는 학습이 끝난 모델 안에는 존재하지 않습니다. LLM은 과거에 멈춰있는 지식만을 알고 있을 뿐, 시간이 지나도 스스로 새로운 사실을 배우거나 업데이트 할 수 없습니다.
또 하나 심각한 문제는 환각(hallucination) 입니다. LLM은 답을 “가장 그럴듯하게” 만들어내는 데 최적화되어 있습니다. 하지만, 실제 데이터나 사실과 상관없이 “그럴듯한 허구”를 지어내는 일이 종종 발생합니다.
많은 분들이 경험 하셨던 것처럼 없는 논문 제목을 만들어낸다거나, 존재하지 않는 회사 정보를 말하거나, 사실과 다르게 왜곡된 답변을 주기도 합니다.
그리고 앞서 봤던 것처럼 “퉁퉁퉁 사후르가 대지의 힘을 끌어올려 궁극기”를 사용해서 이길 것 같다는 허상된 얘기를 하고 있습니다. 퉁퉁퉁 사후르는 빠따가 주 무기인데 말이죠.
이것은 특히 기업용 시스템, 금융, 의료와 같은 분야와 같은 신뢰성이 중요한 도메인에서는 치명적인 결함으로 이어집니다.
LLM이 가진 ‘지식 고정’과 ‘환각’ 문제를 해결하기 위해 필요한 기술이 바로 RAG(Retrieval-Augmented Generation) 입니다.
RAG는 단순히 모델이 답변을 생성하는 데 그치지 않습니다. 답변을 생성하기 전에, 필요한 정보를 외부에서 찾아오는 과정을 먼저 추가합니다.
핵심 아이디어는 매우 단순합니다.
“모델이 답을 지어내게 두지 말고, 필요한 정보를 외부에서 검색해오게 하자.”
즉, LLM이 답변을 생성하기 전에 먼저 외부 지식 저장소(데이터베이스나 문서) 에서 관련 정보를 검색하고, 이 검색된 정보를 기반으로 답변을 생성합니다.
이를 통해, LLM은 단순히 과거에 학습한 지식을 넘어, 최신 정보와 신뢰할 수 있는 데이터를 참조할 수 있게 됩니다.
RAG는 크게 두 단계로 구성됩니다.
사용자의 질문을 바탕으로, 외부 데이터베이스나 문서 저장소에서 관련 정보를 검색해옵니다.
이때 주로 질문을 벡터 로 변환한 뒤, Vector DB를 이용해 의미적으로 가장 유사한 문서를 찾습니다.
관련 내용이 더 궁금하시다면 이전에 작성한 의미 기반 검색 포스팅에서 찾아볼 수 있습니다.
그러나 반드시 Vector DB만 사용하는 것은 아닙니다. 키워드 검색, 메타데이터 기반 검색 등 다양한 검색 방식을 조합할 수도 있습니다.
검색된 문서들을 기반으로, LLM이 답변을 생성합니다. 이때 단순히 검색 결과를 나열하는 것이 아니라, 검색된 문서의 내용을 이해하고, 질문과 연결지어 자연스럽고 신뢰성 있는 답변을 만들어냅니다.
RAG는 단순히 외부 정보를 가져오는 것에 그치지 않습니다. 사용자의 질문을 정확히 이해하고, 가장 관련성 높은 정보를 검색해, 이를 자연스럽게 응답 생성에 녹여내는 복합적인 과정을 거칩니다.
전체 과정을 살펴보면 다음과 같습니다.
가장 처음은 사용자로부터 자연어 형태의 질문을 입력받는 것입니다.
예를 들어, 사용자가 “트랄랄레로 트랄랄라와 퉁퉁퉁 사후르가 싸우면 누가 이기니?”라고 질문한다고 가정하겠습니다.
먼저, 입력된 질문을 벡터로 변환합니다. 이때 임베딩 모델을 사용해 질문을 고차원 벡터로 변환합니다.
“트랄라레로 트랄랄라와 퉁퉁퉁 사후르가 싸우면 누가 이기니?”라는 질문은 다음과 같은 고차원 벡터로 변환이 됩니다.
[-3.75890662e-03 6.81797937e-02 1.76047683e-02 ..., -5.10944389e-02 5.48638552e-02]
이 과정을 통해, 질문은 단순한 문자열이 아니라, 의미 기반 검색이 가능한 형태로 변환됩니다.
변환된 쿼리 벡터를 바탕으로 외부 데이터베이스에서 관련 정보를 검색합니다. 주로 Vector DB를 사용하여 벡터 간 유사도를 계산해 의미적으로 가장 가까운 문서들을 찾아냅니다.
하지만 꼭 벡터 검색만 사용하는 것은 아닙니다. 메타데이터 검색, 키워드 검색 등을 함께 조합해 더 풍부하고 정확한 검색 결과를 얻기도 합니다.
핵심은 “질문과 가장 근접한 외부 정보들을 가져오는 것” 입니다.
검색된 문서들을 그대로 LLM에 넘기지는 않습니다.
이 문서들을 LLM이 이해할 수 있도록 프롬프트로 가공하고 구성합니다.
프롬프트는 간단하게 다음과 같은 형식이 될 수도 있습니다.
“다음 정보를 참고해서 질문에 답변해줘:
문서1: 트랄랄레로 트랄랄라는 세 개의 다리를 가진 상어로, 나이키 운동화를 신고 있다..
문서2: 퉁퉁퉁퉁퉁퉁퉁퉁퉁 사후르는 야구 방망이 형태의 나무 조각 캐릭터이다. 강력한 전투력을 지닌 것으로 묘사되며, 이름을 세 번 부르면 대답하지 않으면 집까지 쫓아온다는 설정이 있다.
질문: 트랄랄레로 트랄랄라와 퉁퉁퉁 사후르가 싸우면 누가 이기니?”
이렇게 질문 + 관련 문서를 함께 LLM에 입력하게 됩니다.
마지막 단계는 LLM이 실제로 답변을 생성하는 것입니다.
LLM은 주어진 문서를 기반으로 질문과 문서 내용을 연결지어 자연스럽고 신뢰할 수 있는 답변을 만듭니다.
이 과정은 단순 요약이 아니라, 문서 내용에 근거해 질문에 최적화된 답변을 재구성하는 데 초점이 맞춰져 있습니다.
그럼 이제 GPT는 아래와 같은 답변을 할 것 입니다.
“주어진 정보에 따르면, 트랄랄레로 트랄랄라는 세 개의 다리를 가진 상어이며, 나이키 운동화를 신고 있다는 점에서 뛰어난 기동성과 민첩성을 가질 가능성이 있습니다. 반면, 퉁퉁퉁퉁퉁퉁퉁퉁퉁 사후르는 야구 방망이 형태의 나무 조각 캐릭터로, 강력한 전투력을 지녔다고 묘사되어 있습니다. 특히, 이름을 세 번 부르면 대답하지 않으면 집까지 쫓아올 정도로 집요함과 집착을 가진 특성을 보여줍니다.”
아직 진지한 토론을 해볼 정도는 아니지만 어느정도 기초 지식은 가지고 있는 것으로 보입니다.
앞서 살펴본 것처럼, RAG는 LLM의 지식 고정과 환각 문제를 극복하기 위한 강력한 도구 입니다.
이제 이론을 넘어, 실제로 작은 RAG 시스템을 직접 구현해보겠습니다.
이번 데모에서는 Italian Brainrot 밈 데이터를 간단하게 제공하여 LLM이 이를 바탕으로 자연스럽게 답변을 생성하는 과정을 간단하게 구성하고, 제공해준 데이터를 통해 LLM과 “트랄랄레로 트랄랄라와 퉁퉁퉁 사후르가 싸우면 누가 이기니?”에 대한 질문 응답을 받아보겠습니다.
이를 위해 두 가지 방식으로 구현해 보았습니다.
1. 직접 컴포넌트들을 구현한 RAG (NestJS 기반)
2. LangChain을 활용한 빠른 RAG 구축 (FastAPI 기반)
전체적인 구조를 확 이해하고 싶었기 때문에, 먼저 NestJS를 사용하여 임베딩, 검색, 프롬프트 구성, LLM 호출 과정을 하나하나 직접 연결하는 방식으로 구현해보았으며, 실제로는 LangChain을 이용한 구축 방식이 많이 사용되고 있다는 점을 고려하여 FastAPI와 LangChain을 활용한 버전도 함께 구현해보았습니다..
이번 데모에서 구현한 RAG 시스템은 크게 네 가지 핵심 컴포넌트로 구성됩니다.
임베딩 서버를 통해 문서나 질문을 고차원 벡터로 변환합니다.
Python으로 간단한 임베딩 서버를 띄워 REST API를 통해 임베딩 요청을 주고받았습니다.
ChromaDB를 사용해 임베딩된 문서 벡터를 저장하고, 질문 벡터와 가장 유사한 문서를 검색합니다.
단순한 키워드 매칭이 아니라, 의미적으로 가장 관련성 높은 문서를 빠르게 찾아줍니다.
단순 데모 제작 용도 이기에 Ollama와 함께 가벼운 llama3.2:1b 모델을 사용했습니다. 검색된 문서를 바탕으로, 최종 답변을 자연스럽게 생성합니다. 검색된 문서들을 단순 나열하는 게 아니라, 문맥을 이해하고 질문과 연결해 자연어로 재구성된 답변을 생성합니다.
이번 프로젝트에서는 사용자의 질문을 받아, 임베딩 → 검색 → 답변 생성의 전체 흐름을 하나의 API로 제공합니다.
구현 방식은 두 가지입니다.
각 방식의 핵심 코드는 다음과 같습니다.
NestJS 기반에서는 각 컴포넌트를 직접 연결했습니다.
Embedding 요청
const response = await fetch(`${this.embeddingServerUrl}/embed`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: this.preprocessText(text) }),
});
const data = await response.json();
return data.embedding;
Vector DB 검색
const queryEmbedding = await this.generateEmbedding(query);
const results = await this.collection.query({
queryEmbeddings: [queryEmbedding],
nResults: limit,
});
RAG 흐름
try {
// 1. Search for relevant documents
const searchResults = await this.embeddingService.search(question, 3);
if (!searchResults.documents?.[0]?.length) {
return {
answer: '죄송합니다. 관련된 정보를 찾을 수 없습니다.',
sources: [],
};
}
const documents = searchResults.documents[0];
if (!Array.isArray(documents)) {
throw new Error('Invalid documents format');
}
// 2. Construct the prompt
const context = documents.join('\n\n');
const prompt = `Here is information about Italian Brainrot characters:
${context}
Based on the above information, please answer the following question:
${question}
Please use only the provided information to answer. If the information is insufficient, please say so.`;
// 3. Generate answer with LLM
const answer = await this.generateResponse(prompt);
// 4. Return the answer with sources
return {
answer,
sources: documents.map((doc, i) => ({
content: doc,
metadata: searchResults.metadatas?.[0]?.[i] || {},
})),
};
} catch (error) {
console.error('Error in RAG:', error);
throw error;
}
이 방식을 통해 임베딩, 검색, 프롬프트 생성, LLM 응답 생성을 모두 직접 제어할 수 있습니다.
FastAPI와 LangChain을 사용하면 훨씬 빠르게 RAG를 구성할 수 있습니다.
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
chain_type_kwargs={"prompt": PROMPT},
return_source_documents=True
)
LangChain을 이용하면 검색과 답변 생성을 하나의 체인으로 손쉽게 연결할 수 있어, 훨씬 빠른 RAG 구축이 가능합니다.
이번에 구축한 RAG 시스템이 실제로 어떻게 작동하는지 확인하기 위해, 각 환경에서 동일한 질문을 던져 테스트를 진행했습니다.
“트랄랄레로 트랄랄라와 퉁퉁퉁 사후르가 싸우면 누가 이기니?”
우선, 가장 기본적인 형태로 Llama3.2:1b 모델에 단독으로 질문을 던졌습니다.
결과는 예상대로였습니다. 모델은 “트랄랄레로 트랄랄라” 와 “퉁퉁퉁 사후르” 에 대해 아무런 정보를 알지 못했습니다.
모델은 이들이 실존 인물이 아니거나 널리 알려진 엔티티가 아니라고 답하며, 추가적인 문맥이나 설명을 요청했습니다.
다음으로, NestJS 기반으로 직접 구성한 RAG 시스템을 통해 같은 질문을 던졌습니다.
NestJS RAG 시스템은 Italian Brainrot 관련 문서를 검색하여 트랄랄레로 트랄랄라와 퉁퉁퉁 사후르 각각의 특성을 비교한 뒤,
“트랄랄레로 트랄랄라는 더 공격적이고 힘이 강하며”, “퉁퉁퉁 사후르는 코스믹 파워를 가지고 있어 현실을 뒤틀 수 있다”는 식으로 논리적인 분석을 수행했습니다. 단순히 답변을 지어낸 것이 아니라, 제공된 문서에 기반하여 그럴듯하고 신뢰성 있는 추론을 생성해냈습니다.
마지막으로, FastAPI와 LangChain을 사용해 구축한 RAG 시스템을 테스트했습니다.
LangChain RAG 시스템 역시 Italian Brainrot 관련 문서를 기반으로 답변을 생성했습니다.
트랄랄레로 트랄랄라가 리듬감 있는 말을 기반으로 만들어진 별다른 의미없는 캐릭터라는 점, 퉁퉁퉁 사후르는 이슬람 라마단 새벽 식사 문화를 바탕으로 한 인도네시아 스타일 캐릭터라는 점을 설명했습니다. 이 또한, 단순히 답변을 지어낸 것이 아니라, 제공된 문서에 기반하여 그럴듯하고 신뢰성 있는 추론을 생성해냈습니다.
앞서 우리는 LLM이 가진 지식 고정성과 환각 문제를 해결하기 위해 RAG 시스템 알아보고 간단하게 구축해보았습니다.
이런 문제를 해결하기 위한 대표적인 방법으로 모델을 Fine-tuning하는 방법도 존재합니다. 그렇다면 RAG는 모델 Fine-tuning과 비교했을 때 어떤 차이를 가지고 있을까요?
RAG를 다시 돌아보자면 이는 외부 지식 검색과 텍스트 생성을 결합하는 구조입니다. 모델이 질문에 직접 답하는 대신, 외부 데이터베이스나 웹에서 관련 정보를 검색한 후, 그 결과를 바탕으로 답변을 생성합니다. 덕분에, 모델 자체를 재훈련할 필요 없이 광범위하고 실시간성 있는 지식을 활용할 수 있습니다.
반면 Fine-tuning은 사전 훈련된 모델에 대해 특정 목적이나 도메인 데이터로 추가 학습을 진행하는 방식입니다. 예를 들어, Italian Brainrot 밈에 대한 데이터를 새롭게 수집하고, 모델을 이 데이터로 다시 훈련시키는 과정을 거친다면, 모델은 이후부터 이 밈에 대해 자연스럽고 정확하게 답할 수 있게 됩니다.
Fine-tuning은 특정 작업이나 도메인에 대해 모델을 매우 깊이 있게 최적화할 수 있다는 큰 장점이 있습니다.
사전 훈련된 모델에 추가적인 데이터 학습을 진행함으로써, 모델이 해당 주제에 대해 직접적이고 일관성 있는 지식을 갖추게 할 수 있습니다.
그러나 Fine-tuning에는 몇 가지 중요한 한계도 존재합니다. 추가 훈련 과정에는 상당한 시간과 비용, 그리고 많은 컴퓨팅 자원이 소요됩니다. 또한 한 번 미세 조정된 모델은 특정 작업이나 도메인에 최적화되어 있기 때문에, 다른 작업으로 쉽게 전환하는 것이 어렵습니다.
특히 새로운 정보가 등장할 때마다 모델을 다시 Fine-tuning해야 한다는 점은 매우 번거로운 문제로 작용할 수 있습니다.
예를 들어, 매일 같이 Italian Brainrot 밈과 관련된 데이터가 추가되고 쌓이고 있는데 변경사항을 반영하기 위해 매번 모델을 다시 학습시키는 과정을 반복해야 하므로, 운영과 유지보수 측면에서도 상당한 부담이 발생할 수 있습니다.
RAG는 Fine-tuning과는 다른 접근 방식으로, 빠르게 구축할 수 있으며 데이터가 변경되더라도 모델 자체를 다시 훈련할 필요가 없다는 장점이 있습니다.
외부 지식을 실시간으로 통합하여 답변을 생성하기 때문에, 최신 정보를 반영하는 데에도 매우 효과적입니다. 또한 Fine-tuning에 비해 훨씬 적은 컴퓨팅 비용과 스토리지로 훨씬 넓은 범위의 지식을 커버할 수 있습니다.
하지만 RAG 역시 완벽하지는 않습니다. 검색된 정보의 품질이 부정확하거나 부실한 경우, 생성되는 답변 역시 부정확할 위험이 존재합니다. 또한 검색 시스템과 생성 시스템을 별도로 최적화해야 하므로, 두 시스템을 원활하게 통합하는 설계가 복잡해질 수 있습니다.
또한, RAG는 주로 사실 기반 응답에 강점을 가지므로, 상상력을 요구하는 창의적 콘텐츠 생성에는 상대적으로 제약이 있을 수 있습니다.
이번 글에서는 LLM이 가진 지식 고정성과 환각 문제를 극복하기 위한 방법으로 RAG 기술을 살펴보았습니다.
직접 간단한 RAG 시스템을 구축해 실험해본 결과, 변화가 빠른 정보, 예를 들어 Italian Brainrot 밈처럼 최신 트렌드를 다루는 데에는 RAG가 매우 유용하다는 점을 확인할 수 있었습니다.
아무리 뛰어난 LLM이라도, 처음부터 퉁퉁퉁 사후르가 누구고 트랄랄레로 트랄랄라가 뭔지 알 수는 없습니다.
하지만 RAG를 통해 필요한 지식을 주입시켜주면, “그들이 싸우면 누가 이길까?” 같은 매우매우 중요한 질문에 대해서도 진지하게 토론할 수 있는 세상이 열리고 있습니다.
특히 RAG는 고객 지원 챗봇, 법률, 금융, 기업 내부 검색 시스템 등 다양한 분야에서 실제로 활용되고 있으며, 앞으로 그 적용 범위는 더욱 확대될 것입니다.
또한 최근에는 LLM에 단순한 질의응답 기능만 부여하는 것을 넘어, 외부 툴이나 시스템과 자연스럽게 연결할 수 있도록 하는 기술들도 함께 발전하고 있습니다.
그 중 하나가 바로 이전에 소개한 MCP(Model Context Protocol) 입니다. MCP는 LLM이 사용자 요청을 더 정확하게 이해하고, 필요한 경우 외부 API나 데이터베이스와 직접 연동해 동작할 수 있도록 지원합니다.
즉, RAG가 “외부 지식을 가져와 답하는 방식”이라면, MCP는 “외부 시스템을 활용해 직접 행동을 수행하게 하는 방식”이라고 볼 수 있습니다.
이처럼 앞으로는 RAG와 MCP 혹은 다양한 기술을 함께 활용하여, LLM은 계속해서 지식 검색, 판단, 실행까지 아우르는 훨씬 강력한 시스템으로 발전해 나갈 것입니다.
그래서 누가 이기나요