검색 증강 생성에 대한 평가: TruLens + Milvus
이 글은 원래 The New Stack에 게시되었으며 허가를 받아 여기에 재게시되었습니다.
대규모 언어 모델(LLM)의 인기가 높아지면서 Milvus 및 Zilliz Cloud와 같은 목적 특화 벡터 데이터베이스, FAISS와 같은 벡터 검색 라이브러리, 기존 데이터베이스와 통합된 벡터 검색 플러그인을 포함한 벡터 검색 기술의 부상을 촉진했습니다.
점점 더 벡터 검색은 검색 증강 생성, 즉 RAG 형태의 생성형 AI를 위한 필수적인 엔터프라이즈 사용 사례이자 질의응답 애플리케이션이 되었습니다. 이러한 구성 방식은 LLM이 질문에 답하기 위한 맥락으로 사용할 수 있는 검증된 지식 베이스에 쉽게 접근할 수 있게 해줍니다. Milvus는 이 애플리케이션을 위해 목적 특화로 구축된 확장성이 뛰어난 오픈 소스 벡터 데이터베이스입니다.
RAG 구성하기
효과적인 RAG 스타일의 LLM 애플리케이션을 구축할 때, 검색 품질에 큰 영향을 미칠 수 있는 다양한 구성 선택지가 있습니다. 이러한 선택지에는 다음이 포함됩니다:
벡터 DB 구성하기
- 데이터 선택
- 임베딩 모델
- 인덱스 유형
애플리케이션의 요구 사항과 정확히 일치하는 고품질 데이터를 찾는 것은 매우 중요합니다. 올바른 데이터가 없으면 검색 과정에서 관련 없는 결과를 제공할 수 있습니다.
데이터를 선택한 후에는 사용하는 임베딩 모델을 고려하세요. 이는 검색 품질에 큰 영향을 미치기 때문입니다. 지식 베이스에 올바른 정보가 포함되어 있더라도, 임베딩 모델이 도메인에 대한 의미론적 이해를 필요로 한다면 검색기는 잘못된 결과를 생성할 수 있습니다.
맥락 관련성은 검색 품질을 평가하는 데 유용한 지표이며, 이러한 선택은 이에 큰 영향을 미칩니다.
마지막으로, 인덱스 유형은 의미 검색의 효율성에 상당한 영향을 미칠 수 있습니다. 이는 특히 대규모 데이터 세트에서 더욱 그렇습니다. 이 선택을 통해 재현율, 속도 및 리소스 요구 사항 간의 균형을 조정할 수 있습니다. Milvus는 플랫 인덱스, 곱 양자화 기반 인덱스, 그래프 기반 인덱스와 같은 다양한 인덱스 유형을 지원합니다. 다양한 인덱스 유형에 대해 더 읽어볼 수 있습니다.
검색
- 검색된 맥락의 양(top k)
- 청크 크기
검색 단계에 이르면, top k는 검색되는 맥락 청크의 수를 제어하는 자주 논의되는 매개변수입니다. 더 높은 top k는 필요한 정보를 검색할 가능성을 높여주지만, LLM이 관련 없는 정보를 답변에 포함할 가능성도 증가시킵니다. 간단한 질문의 경우, 더 낮은 top k가 가장 성능이 좋은 경우가 많습니다.
청크 크기는 검색되는 각 맥락의 크기를 제어합니다. 더 큰 청크 크기는 더 복잡한 질문에 유용할 수 있는 반면, 작은 청크는 아주 적은 양의 정보만으로 답할 수 있는 간단한 질문에 충분합니다.
이러한 선택 중 다수에는 만능 해결책이 없습니다. 성능은 데이터의 크기와 유형, 사용되는 LLM, 애플리케이션 등에 따라 크게 달라질 수 있습니다. 특정 사용 사례에 대한 이러한 검색의 품질을 평가하기 위한 평가 도구가 필요합니다. 바로 여기서 TruLens가 등장합니다.
LLM 추적 및 평가를 위한 TruLens
TruLens는 RAG와 같은 LLM 앱의 성능을 평가하고 추적하기 위한 오픈 소스 라이브러리입니다. TruLens를 사용하면 LLM 자체를 활용하여 출력, 검색 품질 등을 평가하는 기능도 얻을 수 있습니다.
LLM 애플리케이션을 만들 때 많은 사람들이 가장 중요하게 생각하는 문제는 환각입니다. RAG는 검색된 컨텍스트를 LLM에 제공함으로써 정확한 정보를 보장하는 데 큰 도움이 되지만, 이를 보장할 수는 없습니다. 평가가 바로 여기에서 우리 앱의 환각 부재를 검증하는 데 필수적입니다. TruLens는 이러한 필요를 위해 세 가지 테스트를 제공합니다: 컨텍스트 관련성, 근거성, 답변 관련성. 이것들이 우리에게 어떤 이점을 줄 수 있는지 이해하기 위해 각각을 살펴보겠습니다.
컨텍스트 관련성
모든 RAG 애플리케이션의 첫 번째 단계는 검색입니다. 검색 품질을 검증하기 위해, 우리는 각 컨텍스트 청크가 입력 쿼리와 관련이 있는지 확인하고자 합니다. 이는 LLM이 이 컨텍스트를 사용해 답변을 구성하기 때문에 매우 중요하며, 컨텍스트에 관련 없는 정보가 있으면 환각으로 엮일 수 있습니다.
근거성
컨텍스트가 검색된 후에는 LLM에 의해 답변으로 구성됩니다. LLM은 종종 제공된 사실에서 벗어나, 과장하거나 확장하여 그럴듯하게 들리는 답변을 만들어냅니다. 애플리케이션의 근거성을 검증하려면 응답을 개별 진술로 분리하고, 검색된 컨텍스트 내에서 각 진술을 뒷받침하는 증거를 독립적으로 찾아야 합니다.
답변 관련성
마지막으로, 우리의 응답은 여전히 원래 질문에 유용하게 답해야 합니다. 최종 응답이 사용자 입력과 얼마나 관련이 있는지를 평가함으로써 이를 검증할 수 있습니다.
환각 없는 RAG
이 세 가지 평가에서 만족스러운 결과에 도달함으로써, 우리는 애플리케이션의 정확성에 대해 미묘한 진술을 할 수 있습니다. 즉, 애플리케이션은 지식 베이스의 한계 내에서 환각이 없도록 검증됩니다. 다시 말해, 벡터 데이터베이스에 정확한 정보만 포함되어 있다면 RAG가 제공하는 답변 또한 정확합니다.
구체화하기
앞서 언급했듯이, RAG의 많은 구성 선택은 환각에 상당한 영향을 미칠 수 있습니다. 이를 설명하기 위해, 작은 도시 집합에 대한 Wikipedia 문서를 기반으로 RAG 질의응답 애플리케이션을 만들겠습니다. LlamaIndex가 이 애플리케이션의 프레임워크 역할을 할 것입니다.
이 예제를 Google Colab에서 따라 해 보세요.
Wikipedia에서 데이터 로드
벡터 저장소를 구성하려면 먼저 데이터를 로드해야 합니다. 여기서는 LlamaIndex의 데이터 로더를 사용하여 Wikipedia에서 직접 데이터를 로드하겠습니다.
from llama_index import WikipediaReader
cities = [
"Los Angeles", "Houston", "Honolulu", "Tucson", "Mexico City",
"Cincinatti", "Chicago"
]
wiki_docs = []
for city in cities:
try:
doc = WikipediaReader().load_data(pages=[city])
wiki_docs.extend(doc)
except Exception as e:
print(f"Error loading page for city {city}: {e}")
평가자 설정
다음으로, 평가자를 설정하고자 합니다. 구체적으로는 앞서 언급한 세 가지, 즉 컨텍스트 관련성, 근거성, 답변 관련성을 사용하여 환각을 테스트하겠습니다.
TruLens는 OpenAI, Anthropic 또는 HuggingFace와 같은 특정 모델 제공자를 사용하는 이 평가에 유용한 프롬프트가 포함된 평가자 또는 피드백 함수 세트를 제공합니다.
# Initialize OpenAI-based feedback function collection class:
openai_gpt4 = feedback.OpenAI()
모델 제공자를 설정한 후, 첫 번째 평가에 사용할 질문-진술 관련성을 선택합니다. 이 예제의 각 평가에서는 평가를 더 잘 이해하기 위해 chain-of-thought 이유도 사용하겠습니다. 이는 피드백 함수 접미사 1_with_cot_reason으로 표시됩니다.
이 작업을 수행할 때, 피드백 함수에 전달할 텍스트도 선택해야 합니다. TruLens는 애플리케이션을 직렬화하고, 이는 JSON과 유사한 구조로 인덱싱됩니다. 우리는 텍스트 선택을 위해 이 인덱스를 사용할 것입니다. TruLens는 이를 쉽게 만들기 위해 여러 도우미 함수를 제공합니다:
on_input()은 피드백 함수에 전달되는 첫 번째 텍스트로 사용하기 위해 LlamaIndex 애플리케이션에 전달된 주요 입력을 자동으로 찾습니다.TruLlama.select_source_nodes()는 LlamaIndex 검색에 사용된 소스 노드를 식별합니다.
마지막으로, 각 컨텍스트 조각의 관련성을 단일 점수로 집계해야 합니다. 이 예제에서는 가장 관련성이 높은 청크의 관련성을 측정하기 위해 집계에 최댓값을 사용하겠습니다. 평균이나 최솟값 같은 다른 메트릭도 사용할 수 있습니다.
# Question/statement relevance between question and each context chunk.
f_context_relevance = Feedback(openai.qs_relevance_with_cot_reason, name = "Context Relevance").on_input().on(
TruLlama.select_source_nodes().node.text
).aggregate(np.max)
근거성도 비슷하게 설정되지만, 집계 방식이 약간 다릅니다. 이 경우 각 문장의 최대 근거성 점수를 취한 다음, 모든 문장에 걸친 평균 근거성 점수를 계산합니다.
grounded = Groundedness(groundedness_provider=openai_gpt4)
f_groundedness = Feedback(grounded.groundedness_measure_with_cot_reason, name = "Groundedness").on(
TruLlama.select_source_nodes().node.text # context
).on_output().aggregate(grounded.grounded_statements_aggregator)
답변 관련성은 입력/출력에만 의존하므로 설정하기 가장 간단한 피드백 함수입니다. 이를 위해 새로운 TruLens 헬퍼 함수인 .on_input_output()을 사용할 수 있습니다.
# Question/answer relevance between overall question and answer.
f_qa_relevance = Feedback(openai.relevance_with_cot_reason,
name = "Answer Relevance").on_input_output()
구성 공간 정의하기
이제 데이터를 로드하고 평가기를 설정했으므로 RAG를 구축할 차례입니다. 이 과정에서 서로 다른 구성을 가진 일련의 RAG를 구축하고, 각각을 평가한 뒤 최적의 선택을 고를 것입니다.
앞서 언급했듯이, RAG에 영향을 크게 미치는 몇 가지 선택지로 구성 공간을 제한하겠습니다. 이 예제에서는 인덱스 유형, 임베딩 모델, top k 및 청크 크기를 테스트할 것입니다. 하지만 다양한 거리 메트릭과 검색 매개변수 같은 다른 구성도 테스트해 보기를 권장합니다.
선택지 반복하기
구성 공간을 정의한 후에는 itertools를 사용해 이러한 선택지의 모든 조합을 시도하고 각각을 평가합니다. 또한 Milvus는 overwrite 매개변수라는 좋은 이점을 제공합니다. 이를 통해 다른 벡터 데이터베이스에서 필요할 수 있는 느린 해체 및 인스턴스화 절차 없이 다양한 구성을 쉽게 반복할 수 있습니다.
각 반복에서 인덱스 매개변수 선택을 MilvusVectorStore와 스토리지 컨텍스트를 사용하는 애플리케이션에 전달합니다. 임베딩 모델은 서비스 컨텍스트에 전달한 다음 인덱스를 생성합니다.
vector_store = MilvusVectorStore(index_params={
"index_type": index_param,
"metric_type": "L2"
},
search_params={"nprobe": 20},
overwrite=True)
llm = OpenAI(model="gpt-3.5-turbo")
storage_context = StorageContext.from_defaults(vector_store = vector_store)
service_context = ServiceContext.from_defaults(embed_model = embed_model, llm = llm, chunk_size = chunk_size)
index = VectorStoreIndex.from_documents(wiki_docs,
service_context=service_context,
storage_context=storage_context)
그런 다음 이 인덱스를 사용해 쿼리 엔진을 구성할 수 있으며, 여기서 top_k를 정의합니다.
query_engine = index.as_query_engine(similarity_top_k = top_k)
구성이 끝나면 TruLens를 사용해 애플리케이션을 래핑합니다. 여기서는 쉽게 식별할 수 있는 이름을 부여하고, 구성을 앱 메타데이터로 기록하며, 평가를 위한 피드백 함수를 정의합니다.
tru_query_engine = TruLlama(query_engine,
app_id=f"App-{index_param}-{embed_model_name}-{top_k}",
feedbacks=[f_groundedness, f_qa_relevance, f_context_relevance],
metadata={
'index_param':index_param,
'embed_model':embed_model_name,
'top_k':top_k
})
이 tru_query_engine은 원래의 쿼리 엔진과 동일하게 동작합니다.
마지막으로, 평가를 위해 작은 테스트 프롬프트 세트를 사용하여 애플리케이션을 호출해 각 프롬프트에 대한 응답을 제공하도록 하겠습니다. OpenAI API를 빠르게 연속 호출하기 때문에, 지수 백오프를 통해 속도 제한 문제를 피하는 데 도움이 되도록 여기서는 Tenacity를 사용하는 것이 유용합니다.
@retry(stop=stop_after_attempt(10), wait=wait_exponential(multiplier=1, min=4, max=10))
def call_tru_query_engine(prompt):
return tru_query_engine.query(prompt)
for prompt in test_prompts:
call_tru_query_engine(prompt)
결과
어떤 구성이 가장 좋은 성능을 보였나요?
| Index Type | Embedding Model | Similarity Top k | Chunk Size |
|---|---|---|---|
| IVF Flat | text-embedding-ada-002 | 3 | 200 |
어떤 구성이 가장 나쁜 성능을 보였나요?
| Index Type | Embedding Model | Similarity Top k | Chunk Size |
|---|---|---|---|
| IVF Flat | Multilingual MiniLM L12 v2 | 1 | 500 |
어떤 실패 모드가 식별되었나요?
우리가 관찰한 실패 모드 중 하나는 잘못된 도시에 대한 정보를 검색한 것이었습니다. 아래의 사고 연쇄 추론에서 그 예를 볼 수 있는데, Houston 대신 Tucson에 대한 컨텍스트가 검색되었습니다.
마찬가지로, 올바른 도시에 대한 컨텍스트를 검색했지만 그 컨텍스트가 입력 질문과 관련이 없는 문제도 확인했습니다.
이 관련 없는 컨텍스트가 주어지자, 완성 모델은 환각을 일으켰습니다. 여기서 환각이 반드시 사실적으로 틀렸다는 의미는 아니라는 점이 중요합니다. 이는 단지 모델이 뒷받침 증거 없이 답변하는 경우를 말합니다.
또한, 관련 없는 답변의 예도 발견했습니다.
성능 이해하기
인덱스 유형별
인덱스 유형은 속도, 토큰 사용량 또는 평가 측면에서 성능에 의미 있는 영향을 미치지 않았습니다. 이는 이 예제에서 수집된 데이터의 크기가 작기 때문일 가능성이 높으며, 더 큰 코퍼스에서는 인덱스 유형이 더 중요한 선택 요소가 될 수 있습니다.
임베딩 모델별
Text-embedding-ada-002는 groundedness(평균 0.72 대 0.60)와 답변 관련성(평균 0.82 대 0.62)에서 MiniLM 임베딩 모델보다 뛰어났습니다. 두 임베딩 모델은 컨텍스트 관련성에서는 동일하게 좋은 성능을 보였습니다.
이러한 향상된 평가 점수는 Wikipedia 정보에 더 적합한 OpenAI 임베딩에 기인할 수 있습니다.
Similarity Top K
top k를 늘리면 최대 검색 품질(컨텍스트 관련성으로 측정)이 약간 향상되었습니다. 더 많은 수의 청크를 검색함으로써, retriever는 고품질 컨텍스트를 검색할 더 많은 시도를 할 수 있습니다.
더 높은 top k는 또한 groundedness(평균 0.71 대 0.62)와 답변 관련성(평균 0.76 대 0.68)을 향상시켰습니다. 더 많은 컨텍스트 청크를 검색함으로써, 완성 모델이 주장을 만들고 뒷받침할 더 많은 증거를 제공합니다.
예상대로, 이러한 개선은 훨씬 더 높은 토큰 사용량(호출당 평균 590개의 추가 토큰)이라는 비용을 수반합니다.
청크 크기
청크 크기를 늘리면 입력 질문과 관련 없는 주변 텍스트가 포함되도록 강제되어 retriever의 groundedness가 낮아졌습니다.
긍정적인 측면에서는, 더 큰 청크 크기가 대조해 확인할 더 많은 증거를 제공했습니다. 따라서 LLM이 주장을 할 때, 검색된 컨텍스트에 의해 뒷받침될 가능성이 더 높습니다.
마지막으로, 청크 크기를 늘리면 레코드당 평균 토큰 사용량이 400개 증가했습니다.
TruLens와 Milvus로 더 나은 RAG 구축하기
이번 게시물에서는 인덱스 유형, 임베딩 모델, top k 및 청크 크기를 포함한 다양한 구성과 매개변수로 RAG를 구축하는 방법을 배웠습니다. Milvus에서 지원되는 방대한 구성과 덮어쓰기 지원 덕분에 이러한 동적 실험이 가능했습니다. 중요하게도, 우리는 TruLens를 사용하여 각 실험을 추적하고 평가하며, 새로운 실패 모드를 식별하고 설명하고, 가장 성능이 뛰어난 조합을 빠르게 찾았습니다.
직접 시도해 보려면. 오픈 소스 TruLens 를 확인하고 오픈 소스 Milvus 또는 Zilliz Cloud 를 설치할 수 있습니다.
계속 읽기

How to Install and Run OpenClaw (Previously Clawdbot/Moltbot) on Mac
Turn your Mac into an AI gateway for WhatsApp, Telegram, Discord, iMessage, and more — in under 5 minutes.

DeepSeek-OCR Explained: Optical Compression for Scalable Long-Context and RAG Systems
Discover how DeepSeek-OCR uses visual tokens and Contexts Optical Compression to boost long-context LLM efficiency and reshape RAG performance.

Vector Databases vs. Hierarchical Databases
Use a vector database for AI-powered similarity search; use a hierarchical database for organizing data in parent-child relationships with efficient top-down access patterns.



