오늘날 정보 폭발 시대에 AI 시스템은 우리 삶의 모든 측면에 침투했습니다. 의료 및 건강 보조원부터 교육 튜터링 도구, 기업 지식 관리 로봇에 이르기까지 AI는 우리가 지식을 더 효율적으로 습득하고 처리하는 데 도움이 됩니다. 그러나 적용 시나리오가 점점 더 복잡해짐에 따라 기존 AI 시스템은 많은 과제에 직면하게 됩니다. 정말로 관련성 있는 답변을 생성하는 방법은 무엇일까요? 복잡하고 여러 차례 주고받는 대화를 이해하는 방법은? 오류 메시지를 자신있게 피하는 방법은? 이러한 문제는 RAG(Retrieval-Augmented Generation) 기반 시스템에서 특히 두드러집니다.
RAG는 문서 검색 능력과 언어 생성의 유창성을 결합하여 시스템이 맥락에 따라 정보에 기반한 응답을 제공할 수 있도록 합니다. 그러나 기본 RAG 시스템은 복잡한 질의, 여러 차례의 대화, 도메인별 전문 지식을 처리할 때 종종 실패하며, 종종 "환각"(잘못된 정보 생성)을 경험하거나 맥락을 놓치는 경우가 많습니다. 그렇다면 RAG 시스템을 어떻게 업그레이드하여 더욱 스마트하고 안정적으로 만들 수 있을까요? 오늘은 첨단 RAG 기술을 통해 질의응답 시스템의 성능을 개선하는 방법을 알아보겠습니다!
기본 RAG의 한계
먼저 기본 RAG 시스템의 아키텍처를 살펴보겠습니다. 작업 흐름은 대략 다음과 같습니다. 먼저 문서가 로드되고 다양한 블록 기술을 통해 작은 블록으로 분할됩니다. 그런 다음 이러한 작은 블록이 임베딩 모델을 사용하여 벡터로 변환되고 벡터 데이터베이스에 저장됩니다. 사용자가 질문을 하면 시스템은 벡터 데이터베이스에서 해당 질문과 관련된 문서 조각을 검색하고, 이 조각들을 질문과 함께 언어 모델에 전달한 후 마지막으로 답변을 생성합니다.
간단하죠? 하지만 이러한 단순성 때문에 기본 RAG 시스템에는 많은 문제가 발생합니다.
- 환각 문제 : 모델은 원본 문서와 관련이 없거나 심지어 잘못된 콘텐츠를 생성할 수 있습니다. 정확성이 가장 중요한 의학이나 법률 분야에서는 이는 치명적인 결함이 될 수 있습니다.
- 도메인 특이성 부족 : 기본 RAG 시스템은 특정 도메인의 복잡한 주제를 다룰 때 종종 실패하는데, 이는 관련성이 없거나 부정확한 정보를 검색하기 때문입니다.
- 여러 라운드로 구성된 대화의 딜레마 : 여러 라운드로 구성된 대화에서는 기본 RAG 시스템이 맥락을 쉽게 잃어버려 사용자 요구를 충족할 수 없는 단편적인 답변이 나올 수 있습니다.
그러면 이러한 한계를 어떻게 극복할 수 있을까? 이를 위해서는 RAG 시스템의 각 링크(인덱싱, 검색, 생성)를 최적화하고 업그레이드하기 위한 고급 RAG 기술의 도입이 필요합니다.
인덱싱 및 청킹: 견고한 기반 구축
좋은 지수는 RAG 시스템의 핵심입니다. 먼저, 데이터를 효율적으로 가져오고, 분할하고, 저장하는 방법을 고려해야 합니다. 다음으로, 몇 가지 고급 인덱싱 및 청킹 방법을 살펴보겠습니다.
1. HNSW: 효율적인 검색을 위한 강력한 도구
HNSW(Hierarchical Navigable Small Worlds) 알고리즘은 대규모 데이터 세트에서 유사한 항목을 빠르게 찾을 수 있는 강력한 도구입니다. 근사 최근접 이웃(ANN)을 효율적으로 찾기 위해 그래프 기반 구조를 구축합니다. 구체적으로 다음과 같은 주요 특징이 있습니다.
- 근접 그래프 : HNSW는 각 지점이 근처 지점에 연결된 그래프를 구축하여 검색 프로세스를 보다 효율적으로 만듭니다.
- 계층적 구조 : 이 알고리즘은 점들을 여러 층으로 구성하는데, 맨 위 층은 멀리 떨어진 점들을 연결하고 맨 아래 층은 가까이 있는 점들을 연결하여 검색 속도를 높입니다.
- 탐욕적 라우팅 : HNSW는 검색할 때 상위 계층의 한 지점에서 시작하여 점차 하위 계층으로 이동하여 국소 최소값을 찾습니다. 이를 통해 유사한 항목을 찾는 데 필요한 시간이 크게 줄어듭니다.
실제 응용 프로그램에서는 매개변수(예: 각 노드의 이웃 수, 그래프를 구축할 때 고려하는 이웃 수 등)를 설정하여 HNSW의 성능을 최적화할 수 있습니다. HNSW를 사용하면 방대한 데이터에서 사용자 질문과 가장 관련성이 높은 문서 조각을 빠르고 정확하게 찾을 수 있으며, 이를 통해 후속 답변 생성을 위한 견고한 기반을 제공할 수 있습니다.
HNSW의 실무 경험
다음으로, 코드를 통해 HNSW 알고리즘을 구현해 보겠습니다. 여기서는 FAISS 라이브러리를 사용합니다. 이는 효율적인 유사성 검색 라이브러리이며 HNSW와 함께 사용하기에 매우 적합합니다.
import faiss
import numpy as np
# HNSW 인자 설정
d = 128 # 벡터의 차원
M = 32 # 노드당 이웃 수
# HNSW 인덱스 초기화
index = faiss.IndexHNSWFlat(d, M)
# efConstruction 인자를 설정하여 인덱스를 작성할 때 고려할 이웃의 수를 제어합니다
efConstruction = 200
index.hnsw.efConstruction = efConstruction
# 랜덤 데이터를 생성하고 색인에 추가하기
n = 10000 # 인덱싱할 벡터의 수
xb = np.random.random((n, d)).astype('float32')
index.add(xb) # 인덱스 만들기
# efSearch 인자를 설정하여 검색 과정에 영향을 줍니다.
efSearch = 100
index.hnsw.efSearch = efSearch
# 검색 실행
nq = 5 # 쿼리 벡터의 수
xq = np. random. random((nq, d)). astype('float32')
k = 5 # 가장 가까운 이웃의 수 검색하기
distances, indices = index. search(xq, k)
# 결과 출력
print('쿼리 벡터:\n", xq)
print("\n가장 가까운 이웃 색인:\n", indices)
print("\n가장 가까운 이웃:\n", distances)
위의 코드를 통해 HNSW가 대규모 데이터 세트를 처리하는 데 있어 효율성과 정확성을 얼마나 높이는지 확인할 수 있습니다. 이를 통해 쿼리 벡터와 가장 유사한 문서 조각을 빠르게 찾아 후속 언어 모델 생성 프로세스에 고품질 입력을 제공할 수 있습니다.
2. 의미적 청킹: 정보를 더 의미 있게 만들기
기존의 청킹 방법은 일반적으로 고정된 크기에 따라 텍스트를 분할하지만, 이 접근 방식은 완전한 개념이나 정보를 조각낼 수 있습니다. 의미적 청킹은 다릅니다. 이는 텍스트를 의미에 따라 여러 개의 덩어리로 나누고, 각 덩어리는 일관된 정보 단위를 나타냅니다. 구체적인 작업은 문장 임베딩 사이의 코사인 거리를 계산하고, 두 문장이 의미적으로 유사한 경우(특정 임계값 아래) 동일한 블록으로 그룹화하는 것입니다. 이 접근 방식의 장점은 보다 의미 있고 일관된 청크를 생성하여 검색 정확도를 향상시킬 수 있다는 것입니다. 하지만 BERT 등의 기반 인코더를 사용해야 하므로 계산 비용이 상대적으로 높습니다.
의미적 청킹의 실습
다음으로, 코드를 통해 의미적 분할을 구현합니다. 여기서는 OpenAI의 임베딩 모델을 활용하여 의미적 청킹을 구현하는 LangChain 라이브러리의 클래스를 사용합니다 .SemanticChunker
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai.embeddings import OpenAIEmbeddings
# 의미 분류기 초기화
text_splitter = SemanticChunker(OpenAIEmbeddings())
# 문서를 의미 관련 조각으로 나누기
docs = text_splitter.create_documents([document])
print(docs[0].page_content)
위의 코드에서 우리는 의미적 청킹을 통해 텍스트의 의미적 내용을 기반으로 더욱 의미 있는 청크를 생성할 수 있다는 것을 알 수 있습니다. 이는 후속 검색 및 생성 단계에 매우 유용합니다.
3. 언어 모델 기반 청킹: 텍스트 구조를 정확하게 포착
이 접근 방식은 강력한 언어 모델(예: 70억 개의 매개변수를 가진 모델)을 사용하여 텍스트를 처리하고, 완전한 문장으로 분할한 다음, 이 문장들을 블록으로 결합합니다. 이를 통해 각 블록의 무결성을 보장할 뿐만 아니라 맥락 정보도 고려합니다. 이 방법은 계산 집약적이기는 하지만, 텍스트의 구체적인 내용에 따라 세분화 방법을 유연하게 조정하고 고품질 세그먼트를 생성할 수 있습니다. 특히 텍스트 구조에 대한 요구 사항이 높은 애플리케이션 시나리오에 적합합니다.
언어 모델 기반 청킹의 실습
다음으로, 코드를 통해 언어 모델 기반 청킹을 구현합니다. 여기서는 OpenAI의 GPT-4o 모델을 사용하여 비동기 호출을 통해 각 블록에 대한 컨텍스트 정보를 생성합니다.
import asyncio
from langchain_openai import ChatOpenAI
async def generate_contexts(document, chunks):
async def process_chunk(chunk):
response = await client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "Generate a brief context explaining how this chunk relates to the full document."},
{"role": "user", "content": f"<document> \n{document} \n</document> \nHere is the chunk we want to situate within the whole document \n<chunk> \n{chunk} \n</chunk> \nPlease give a short succinct context to situate this chunk within the overall document for the purposes of improving search retrieval of the chunk. Answer only with the succinct context and nothing else."}
],
temperature=0.3,
max_tokens=100
)
context = response.choices[0].message.content
return f"{context} {chunk}"
# 모든 조각들을 병렬로 처리합니다.
contextual_chunks = await asyncio.gather(
*[process_chunk(chunk) for chunk in chunks]
)
return contextual_chunks
위의 코드를 통해 언어 모델 기반 청킹을 통해 고품질 청크를 생성하고 각 청크에 대한 컨텍스트 정보를 생성할 수 있다는 것을 알 수 있습니다. 이는 후속 검색 및 생성 단계에 매우 유용합니다.
4. 메타데이터 활용: 검색에 더 많은 맥락을 추가하세요
메타데이터는 날짜, 환자 나이, 과거 병력 등과 같은 문서에 추가적인 맥락적 정보를 제공할 수 있습니다. 검색할 때 이러한 메타데이터를 필터링하면 관련 없는 정보를 제외하고 검색 결과를 더 정확하게 만들 수 있습니다. 예를 들어, 의료 분야에서 어린이와 관련된 콘텐츠를 쿼리하는 경우 18세 이상 환자의 기록을 직접 필터링할 수 있습니다. 인덱싱 중에 텍스트와 함께 메타데이터를 저장하면 검색의 효율성과 관련성을 크게 향상시킬 수 있습니다.
메타데이터를 활용한 실습
다음으로, 코드를 통해 메타데이터의 사용을 구현합니다. 여기서는 LangChain 라이브러리의 클래스를 사용하여 Document 문서에 메타데이터를 저장할 수 있습니다.
from langchain_core.documents import Document
# 메타데이터가 있는 문서 만들기
doc = Document(
page_content="This is a sample document.",
metadata={"id": "doc1", "source": "https://example.com"}
)
# 문서 내용과 메타데이터
print(doc.page_content)
print(doc.metadata)
위의 코드에서 우리는 메타데이터가 문서에 대한 더욱 맥락적인 정보를 제공할 수 있다는 것을 알 수 있는데, 이는 이후의 검색 및 생성에 매우 유용합니다.
검색: 핵심 정보 정확히 파악
검색은 RAG 시스템의 핵심 링크로, 방대한 데이터에서 사용자 질문과 실제로 관련성이 있는 문서를 찾을 수 있는지 여부를 결정합니다. 다음으로, 검색 성능을 개선하기 위한 몇 가지 기술을 살펴보겠습니다.
5. 하이브리드 검색: 의미론과 키워드의 완벽한 조합
하이브리드 검색은 벡터 검색(의미 검색)과 키워드 검색을 결합하여 두 가지의 장점을 최대한 활용합니다. AI 기술과 같은 일부 분야에서는 알고리즘 이름, 기술 용어 등 구체적인 키워드인 용어가 많습니다. 벡터 검색만 하면 이러한 중요한 정보를 놓칠 수 있지만, 키워드 검색을 하면 이러한 주요 용어가 고려됩니다. 두 가지 검색을 동시에 실행하고 가중치 시스템에 따라 결과를 결합하고 정렬하면 더욱 포괄적이고 정확한 검색 결과 목록을 얻을 수 있습니다.
하이브리드 검색에 대한 실무 경험
다음으로, 코드를 통해 하이브리드 검색을 구현합니다. 여기서는 Weaviate 벡터 데이터베이스의 벡터 검색과 키워드 검색 기능을 결합한 LangChain 라이브러리의 클래스를 사용합니다 .WeaviateHybridSearchRetriever
from langchain_community.retrievers import WeaviateHybridSearchRetriever
# 혼합 검색기 초기화
retriever = WeaviateHybridSearchRetriever(
client=client,
index_name="LangChain",
text_key="text",
attributes=[],
create_schema_if_missing=True,
)
# 혼합 검색 실행
results = retriever.invoke("the ethical implications of AI")
print(results)
위의 코드를 통해 하이브리드 검색은 벡터 검색과 키워드 검색의 장점을 결합하여 더욱 포괄적이고 정확한 검색 결과를 생성할 수 있음을 알 수 있습니다.
6. 쿼리 재작성: 질문을 더 "친숙하게" 만들기
사람들이 묻는 질문은 데이터베이스나 언어 모델이 이해하기에 가장 적합한 형태가 아닌 경우가 많습니다. 언어 모델을 사용하여 쿼리를 다시 작성하면 검색 효과를 크게 개선할 수 있습니다. 예를 들어, "AI 에이전트란 무엇이고 왜 2025년의 차세대 주요 기술일까요?"라는 내용을 "AI 에이전트의 2025년 주요 기술"로 다시 쓰는 것이 데이터베이스의 검색 논리에 더 부합합니다. 또한, 프롬프트 단어를 다시 작성하여 언어 모델과의 상호 작용을 최적화하여 결과의 품질과 정확성을 개선할 수 있습니다.
실습 쿼리 재작성
다음으로, 코드를 통해 쿼리를 다시 쓰는 것을 구현합니다. 여기서는 LangChain 라이브러리의 클래스를 사용하여 OpenAI의 언어 모델을 사용하여 쿼리를 다시 작성할 수 있습니다.ChatOpenAI
from langchain_openai import ChatOpenAI
# 언어모델 초기화
chatgpt = ChatOpenAI(model_name="gpt-4o", temperature=0)
# 조회 다시 쓰기
query = "what are AI agents and why they are the next big thing in 2025"
rewritten_query = chatgpt.invoke(query)
print(rewritten_query)
위의 코드를 통해 쿼리를 다시 작성하면 사람의 질문을 데이터베이스와 언어 모델 이해에 더 적합한 형태로 변환하여 검색 효과를 향상시킬 수 있다는 것을 알 수 있습니다.
7. 다중 쿼리 검색: 다양한 각도에서 정보 마이닝
검색 결과는 질의의 표현에 따라 크게 달라질 수 있습니다. 다중 쿼리 검색기는 대규모 언어 모델(LLM)을 사용하여 사용자 입력을 기반으로 다양한 각도에서 여러 쿼리를 생성한 다음, 각 쿼리에 대한 관련 문서를 별도로 검색하고 마지막으로 모든 쿼리의 결과를 집계하여 더 광범위한 관련 문서 컬렉션을 제공합니다. 이 접근 방식을 사용하면 광범위한 수동 조정이 필요하지 않고도 유용한 정보를 찾을 확률이 높아집니다.
다중 쿼리 검색을 통한 실습
다음으로, 코드를 통해 다중 쿼리 검색을 구현합니다. 여기서는 LangChain 라이브러리의 클래스를 사용하여 MultiQueryRetriever OpenAI의 언어 모델을 사용하여 여러 쿼리를 생성하고 Chroma 벡터 데이터베이스에서 관련 문서를 검색할 수 있습니다.
from langchain.retrievers.multi_query import MultiQueryRetriever
# 다중 쿼리 검색기 초기화
mq_retriever = MultiQueryRetriever.from_llm(
retriever=similarity_retriever3, llm=chatgpt,
include_original=True
)
# 다중 조회 검색 실행
query = "what is the capital of India?"
docs = mq_retriever.invoke(query)
print(docs)
위의 코드에서 볼 수 있듯이 다중 쿼리 검색은 여러 각도에서 쿼리를 생성하고 벡터 데이터베이스에서 관련 문서를 검색하여 유용한 정보를 찾을 확률을 높일 수 있습니다.
생성: 고품질 답변 생성
마지막으로 RAG 시스템의 생성에 대해 알아보겠습니다. 이 단계의 목표는 관련 없는 정보로 인한 "환각"을 피하기 위해 질문과 최대한 관련성이 높은 맥락을 언어 모델에 제공하는 것입니다. 빌드 품질을 개선하기 위한 몇 가지 팁을 소개합니다.
8. 자동 자르기: 관련 없는 정보 제거
자동 정리 기술을 사용하면 질문과 관련이 없는 데이터베이스에서 검색된 정보를 걸러내고 언어 모델이 잘못 이해되는 것을 방지할 수 있습니다. 구체적인 작업은 검색 중에 유사도 점수를 기반으로 상당히 감소하는 임계점을 찾고, 이 임계점보다 낮은 점수를 가진 객체를 제외하여 언어 모델에 전달되는 정보가 가장 관련성이 높도록 하는 것입니다.
자동 자르기 실습
다음으로, 코드를 통해 자동 자르기를 구현합니다. 여기서는 LangChain 라이브러리의 클래스를 사용하는데 PineconeVectorStore , 이를 통해 Pinecone 벡터 데이터베이스를 사용하여 유사성 검색을 수행하고 유사성 점수에 따라 정보를 필터링할 수 있습니다.
from langchain_pinecone import PineconeVectorStore
from langchain_openai import OpenAIEmbeddings
# 벡터 메모리 초기화
vectorstore = PineconeVectorStore.from_documents(
docs, index_name="sample", embedding=OpenAIEmbeddings()
)
# 유사도 검색 및 유사도 점수 획득
docs, scores = vectorstore.similarity_search_with_score("dinosaur")
for doc, score in zip(docs, scores):
doc.metadata["score"] = score
print(docs)
위의 코드에서 자동 자르기를 통해 유사도 점수에 따라 관련 없는 정보를 걸러내고, 이를 통해 언어 모델에 전달되는 정보의 품질을 향상시킬 수 있음을 알 수 있습니다.
9. 재정렬: 중요한 정보의 우선순위를 정하세요
재순위 매기기 기술은 보다 진보된 모델(일반적으로 교차 인코더)을 사용하여 처음 검색된 객체를 다시 평가하고 순위를 매깁니다. 이는 쿼리와 각 객체 간의 쌍별 유사성을 고려하고, 관련성을 다시 결정하고, 가장 관련성이 높은 문서를 맨 위에 놓습니다. 이런 방식으로 언어 모델이 수신하는 데이터의 품질이 높아지고, 생성된 답변의 정확도가 높아집니다.
실습 재정렬
다음으로, 코드를 통해 재정렬을 구현합니다. 여기서는 LangChain 라이브러리의 클래스를 사용하는데 FlashrankRerank , 이를 통해 고급 모델을 사용하여 검색된 문서를 다시 평가하고 순위를 매길 수 있습니다.
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import FlashrankRerank
# 초기화 재배열기
compressor = FlashrankRerank()
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor, base_retriever=retriever
)
# 정렬 실행
query = "What did the president say about Ketanji Jackson Brown"
compressed_docs = compression_retriever.invoke(query)
print([doc.metadata["id"] for doc in compressed_docs])
print(compressed_docs)
위의 코드에서 우리는 재순위 지정을 통해 쿼리와 문서 간의 유사성을 기반으로 재순위를 지정함으로써 언어 모델에 전달되는 정보의 품질을 향상시킬 수 있음을 알 수 있습니다.
10. 언어 모델 미세 조정: 모델이 해당 분야를 더 잘 이해하도록 합니다.
사전 훈련된 언어 모델을 미세 조정하면 검색 성능을 크게 향상시킬 수 있습니다. 특정 분야(예: 의학)에서는 관련 데이터(예: MedCPT 시리즈)로 사전 학습된 모델을 선택하고, 자체 데이터를 수집하고, 미세 조정을 위해 양성 및 음성 샘플 쌍을 만든 다음, 모델이 해당 분야의 특정 관계를 학습하도록 할 수 있습니다. 미세 조정된 모델은 도메인별 검색 및 생성 작업에서 더 나은 성능을 보입니다.
언어 모델 미세 조정의 실습
다음으로, 코드를 통해 언어 모델을 미세 조정하는 작업을 구현하겠습니다. 여기서는 LangChain 라이브러리의 클래스를 사용하는데 ChatOpenAI , 이를 통해 OpenAI의 언어 모델을 사용하여 세부 조정할 수 있습니다.
from langchain_openai import ChatOpenAI
# 언어모델 초기화
chatgpt = ChatOpenAI(model_name="gpt-4o", temperature=0)
# 미세 조정 언어 모델
# 여기에는 미세 조정을 위한 자체 데이터 세트를 제공해야 합니다.
# 예를 들어, 의학 분야의 데이터 세트를 사용하여 미세 조정합니다.
# fine_tuned_model = chatgpt.fine_tune(dataset)
위의 코드를 통해 언어 모델을 미세 조정하면 특정 분야에서 모델의 성능이 크게 향상되고, 생성된 답변의 품질도 향상되는 것을 알 수 있습니다.
고급 RAG 기술: AI 답변의 신뢰성을 더욱 높여줍니다
위에 나열된 일련의 고급 RAG 기술을 통해 인덱싱, 검색 및 생성 등 RAG 시스템의 각 링크를 최적화하고 업그레이드하여 시스템의 전반적인 성능을 개선할 수 있습니다. 의료 보조원, 교육 상담 도구, 기업 지식 관리 로봇 등 어떤 기술이든 이러한 기술은 AI 시스템이 복잡한 정보 요구 사항을 처리하는 데 더욱 능숙해지고, 더 정확하고 신뢰할 수 있으며 상황에 맞는 답변을 생성할 수 있도록 해줍니다.
간단히 말해, 응용 시나리오가 더욱 복잡해짐에 따라 AI 시스템은 계속해서 발전해야 합니다. 첨단 RAG 기술은 더욱 스마트하고 강력한 질의응답 시스템을 구축할 수 있는 효과적인 방법을 제공하며, AI를 지식 습득 및 문제 해결에 있어 실질적인 조수로 만들어줍니다!
'AI' 카테고리의 다른 글
2025년 4월 18일 AI 뉴스 (0) | 2025.04.18 |
---|