RAG (Retrieval-Augmented Generation)
- R (Retrieval, 검색): 외부 데이터베이스에서 관련 정보를 검색
- A (Augmented, 증강): 검색된 정보를 LLM 입력 (prompt) 에 추가하여 활용
- G (Generation, 생성): 프롬프트를 통해 제공된 외부 데이터를 참고하여 최종 답변을 생성
즉, RAG 는 시스템 설계자가 LLM 을 외부 데이터베이스와 미리 연결한 후, 질의가 들어올 때마다 외부 정보를 검색하여 프롬프트를 통해 정보를 증강시켜 답변 품질을 향상시키는 방식입니다.
검색 자체는 단순한 데이터 조회이며, 검색한 데이터가 LLM 의 모델 자체에 축적되지는 않기 때문에 검색해온 데이터를 프롬프트를 통해 LLM 에 직접 추가해주어야 합니다.
RAG 는 LLM 자체가 외부 데이터를 학습하거나 기억하는 것이 아니라, 매번 질의할 때마다 필요한 데이터를 검색해서 제공하는 방식이기 때문에 동일한 질의를 하더라도 검색과 증강 과정은 매번 새롭게 수행됩니다. (LangChain 에서 캐싱을 사용하면 동일한 질의에 대해 벡터 검색을 다시 수행하지 않고 이전 검색 결과를 재사용할 수 있지만, 그럼에도 LLM 자체는 상태를 저장하지 않기 때문에 검색된 데이터를 프롬프트를 통해 다시 제공해주어야 합니다.)
ChatGPT 는 OpenAI 에서 추가적인 맥락 저장 기능을 구현해두어 우리가 대화하는 동안 이전 대화를 기억하는 것처럼 동작하게 됩니다. 하지만 일반적인 LLM (GPT-4 API 등) 은 이러한 기능이 없으므로, 직접 문맥을 유지해야 합니다. 즉, RAG 에서는 매번 같은 질문을 하더라도 이전 정보를 기억하지 못합니다.
일반적인 사용자가 이용하는 ChatGPT 웹 인터페이스 (chat.openai.com) 에서는 벡터 DB 나 외부 문서 검색 기능이 내장되어 있지 않기 때문에 RAG 를 직접 적용할 수 없습니다. ChatGPT 에서는 Few-shot prompting 정도를 수행할 수 있습니다. 사용자가 ChatGPT 상에 특정한 문서를 업로드하고 이를 참조하는 기능이 있지만, 벡터 검색을 수행하는 RAG 와는 다른 개념입니다.
Chain
LLM 의 여러 호출 과정을 연결 (Chain) 하여 자동화하는 개념을 Chain 이라고 합니다. 예를들어 사용자 질문 분석, 외부 데이터 검색, 검색된 정보를 LLM 입력에 추가, 최종 답변 생성 등의 과정을 구현해야 한다고 할 때, LLM 을 단순 호출하는 대신 여러 개의 LLM 호출 과정을 연결해서 자동화할 수 있습니다. 즉, Chain 은 여러 개의 단계를 연결하여 하나의 흐름으로 실행하는 방식으로, 각 단계의 출력을 다음 단계의 입력으로 사용하는 구조입니다. Chain 은 LLM 을 활용한 작업에서 프롬프트 생성, 모델 호출, 후처리 등을 자동화하고 조합할 수 있도록 도와줍니다. RAG 구현 시 Chain 을 적용할 수 있습니다.
LangChain
Chain 은 LLM 호출 과정을 자동화하는 개념이며, LangChain 은 Chain 을 쉽게 구현하기 위한 프레임워크 입니다. LangChain 을 이용하여 RAG 를 다음과 같이 구현할 수 있습니다.
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import FAISS
from langchain.text_splitter import CharacterTextSplitter
from langchain.document_loaders import TextLoader
from langchain.llms import OpenAI
from langchain.chains import RetrievalQA
# 1. 문서 로드 및 벡터화
loader = TextLoader("sample_text.txt", encoding="utf-8") # 문서 로드
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=100)
texts = text_splitter.split_documents(documents) # 문서 분할
embeddings = OpenAIEmbeddings()
vectorstore = FAISS.from_documents(texts, embeddings) # FAISS 벡터 DB 생성
# 2. RAG 를 위한 Chain 구성
llm = OpenAI()
retriever = vectorstore.as_retriever()
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # 문서를 그대로 전달
retriever=retriever
)
# 3. 사용자 입력 처리
query = "LangChain이란 무엇인가?"
result = qa_chain.run(query)
print(result)
외부 데이터베이스 연결 방식
외부 데이터는 인터넷에서 직접 조회하거나, 벡터 DB 에 미리 저장해서 활용 가능합니다.
- 인터넷상의 외부 데이터 직접 조회
- 벡터화를 사용하는 경우:
- 사용자의 질의를 벡터화하고 인터넷상의 외부 데이터 (API 응답 데이터, 위키백과 혹은 논문 저장소 등의 웹 문서, 데이터베이스 형태의 외부 데이터 등) 도 벡터화 (사전에 벡터화되어 있는 경우도 존재) 하여 벡터 유사도를 비교하여 관련성이 높은 데이터를 검색
- 인터넷상의 외부 데이터가 사전에 벡터화되어 있지 않다면, 해당 데이터 전체를 매번 새롭게 벡터화 해야 하므로 비용이 큼
- 벡터화를 사용하지 않는 경우:
- 단순한 텍스트 기반 검색
- 의미적 검색이 어렵고, 단순한 키워드 일치 검색이므로 성능이 낮음
- 실시간 벡터화는 비용이 너무 크고, 키워드 매칭은 성능이 낮아 인터넷상의 데이터를 실시간으로 검색하는 방식은 비효율적
- 벡터화를 사용하는 경우:
- 벡터 DB 를 활용한 검색 (로컬 또는 클라우드 DB)
- 외부 데이터를 미리 벡터화하여 벡터 DB 에 저장해놓고 사용하는 방식
- 사전에 벡터화를 완료한 데이터를 사용하므로, 데이터를 매번 새롭게 벡터화할 필요가 없어 효율적
- 질의가 들어오면 사용자의 질의를 벡터화하고, 벡터 DB 에서 유사한 데이터를 검색
- 벡터 DB 에서 찾은 데이터는 자연어로 변환하여 LLM 프롬프트에 추가
- 벡터 DB 에 주로 자연어와 해당 벡터값이 쌍으로 저장됨. 검색된 데이터의 자연어 값을 벡터 DB 에서 가져옴
- LangChain 을 사용할 경우 검색된 데이터의 벡터 ID 값을 활용하여 자동으로 해당하는 자연어 데이터를 가져옴
- LangChain 을 사용하지 않을 경우 직접 검색된 데이터의 벡터 ID 값을 활용하여 해당하는 자연어 데이터를 가져와야 함
- 일부 DB 는 벡터만 저장하고 자연어 데이터는 별도 저장소 (DB, 파일, API) 에서 참조할 수도 있음
- 벡터 DB 에 주로 자연어와 해당 벡터값이 쌍으로 저장됨. 검색된 데이터의 자연어 값을 벡터 DB 에서 가져옴
- 외부 데이터를 미리 벡터화하여 벡터 DB 에 저장해놓고 사용하는 방식
벡터 DB 유형
- DBMS 형태의 벡터 DB
- 별도의 데이터 저장소를 제공 → 추가적으로 로컬에 저장할 필요 없음
- 라이브러리 형태의 벡터 DB (대표적으로 FAISS)
- FAISS (Facebook AI Similarity Search) 는 벡터 검색에 최적화된 라이브러리로, 벡터 인덱스를 저장하고 검색하는 기능만 제공
- FAISS 는 전통적인 DBMS 처럼 데이터를 관리하거나 트랜잭션을 지원하지 않는 대신 벡터 검색 속도가 빠르고, 대규모 데이터를 다룰 수 있음
- 데이터 관리 기능이 없어 데이터를 별도로 저장해야 함
- 벡터 인덱스를 메모리 또는 디스크에 저장
- 메모리 기반
- 데이터를 RAM 에 올려놓고 즉시 검색 수행
- 빠른 검색, 적은 데이터에 적합
- 즉, 벡터를 따로 저장하는 코드가 필요 없고, FAISS 인덱스만 유지하면 됨
- 디스크 기반
- 데이터가 많아 메모리에 올릴 수 없을 때 디스크에 저장 후 필요할 때 로딩
- 대용량 데이터 처리 가능, 저장소 필요
- 즉, 벡터를 저장하고 불러오는 코드가 필요함
- 메모리 기반
- FAISS (Facebook AI Similarity Search) 는 벡터 검색에 최적화된 라이브러리로, 벡터 인덱스를 저장하고 검색하는 기능만 제공
LLM 자체 지식 검색과 외부 데이터 검색 비율 설정
RAG 적용 시, LLM 이 데이터를 검색하는 방식은 보통 다음 중 하나로 설계됩니다.
- LLM 내장 지식 검색 + 외부 데이터 검색
- LLM 내장 지식 검색 후 원하는 답변 찾지 못하면 외부 데이터 검색
- 외부 데이터에서만 검색
이러한 활용 비율은 다음과 같이 코드를 통해 조정할 수 있습니다.
- LLM 내장 지식 검색 + 외부 데이터 검색 (프롬프트 함께 활용)
from langchain.chains import LLMChain
from langchain.chat_models import ChatOpenAI
from langchain.prompts import PromptTemplate
from langchain.tools import Tool
from langchain.agents import initialize_agent, AgentType
# LLM 설정
llm = ChatOpenAI(model_name="gpt-4")
# 외부 데이터 검색 (예: 벡터DB, 웹 검색 등)
retrieval_tool = Tool(
name="VectorDB_Search",
func=lambda query: "외부 검색 결과: ..." # 실제 검색 기능 연결 필요
)
# 프롬프트 설정 (LLM과 검색 병행)
prompt = PromptTemplate(
input_variables=["query", "retrieved_info"],
template="""
질문에 대한 답변을 생성할 때, 반드시 자체 지식과 외부 검색 데이터를 함께 활용하세요.
질문: {query}
외부 검색 결과: {retrieved_info}
두 가지 정보를 종합하여 최적의 답변을 제공하세요.
"""
)
# LLM 체인 생성
llm_chain = LLMChain(llm=llm, prompt=prompt)
def hybrid_search(query):
search_result = retrieval_tool.func(query) # 외부 검색 수행
response = llm_chain.run({"query": query, "retrieved_info": search_result})
return response
print(hybrid_search("양자 컴퓨팅의 최신 연구 동향은?"))
- LLM 내장 지식 검색 + 외부 데이터 검색 (프롬프트 미활용)
from langchain.agents import AgentExecutor
# 검색과 LLM이 동시에 실행되도록 설정
agent_executor = AgentExecutor(
agent=agent,
tools=[retrieval_tool], # 검색 기능 포함
return_intermediate_steps=True, # 검색 과정도 반영
verbose=True
)
def hybrid_agent_search(query):
response = agent_executor.run(query)
return response
print(hybrid_agent_search("블록체인의 보안 문제는?"))
- LLM 내장 지식 검색 후 원하는 답변 찾지 못하면 외부 데이터 검색 (프롬프트 함께 활용)
prompt = PromptTemplate(
input_variables=["query"],
template="""
먼저 자체 지식을 활용하여 답변하세요.
만약 질문에 대한 명확한 답이 없거나 확신할 수 없다면, "확실하지 않음"이라고 답한 후 외부 검색을 시도하세요.
질문: {query}
"""
)
llm_chain = LLMChain(llm=llm, prompt=prompt)
def smart_retrieval(query):
# LLM이 먼저 답변
initial_response = llm_chain.run(query)
# 만약 확실하지 않다고 답하면 검색 수행
if "확실하지 않음" in initial_response:
search_result = retrieval_tool.func(query)
return f"LLM 답변 부족 → 검색 수행: {search_result}"
else:
return initial_response
print(smart_retrieval("최신 AI 연구 동향은?"))
- LLM 내장 지식 검색 후 원하는 답변 찾지 못하면 외부 데이터 검색 (프롬프트 미활용)
def pipeline_search(query):
# LLM이 먼저 답변 시도
initial_response = llm_chain.run(query)
# 확신이 없는 경우 검색 수행
if "확실하지 않음" in initial_response:
search_result = retrieval_tool.func(query)
return f"{initial_response}\n\n[추가 검색 결과]\n{search_result}"
return initial_response
print(pipeline_search("블록체인의 보안 문제는?"))
- 외부 데이터에서만 검색 (프롬프트 함께 활용)
prompt = PromptTemplate(
input_variables=["query"],
template="""
모든 질문에 대해 자체 지식을 사용하지 말고, 반드시 외부 검색을 통해 답변하세요.
질문: {query}
"""
)
llm_chain = LLMChain(llm=llm, prompt=prompt)
def only_external_search(query):
search_result = retrieval_tool.func(query)
return f"외부 검색 결과: {search_result}"
print(only_external_search("양자 컴퓨팅이란?"))
- 외부 데이터에서만 검색 (프롬프트 미활용)
def direct_search(query):
return retrieval_tool.func(query)
print(direct_search("딥러닝 최신 연구는?"))