Drei Schlüsselstrategien für den Aufbau effizienter Retrieval Augmented Generation (RAG)
Retrieval Augmented Generation (RAG) ist eine nützliche Technik, um Ihre eigenen Daten in einem KI-gestützten Chatbot zu verwenden. In diesem Blogbeitrag führe ich Sie durch drei zentrale Strategien, um das Beste aus RAG herauszuholen:
Intelligente Textaufteilung 📦:
- Der erste Schritt besteht darin, Ihre Textdaten in sinnvolle, handhabbare Abschnitte aufzuteilen. Dieser Schritt stellt sicher, dass Ihre Vektordatenbank die relevantesten Informationen schnell und präzise abrufen kann.
Iteration mit verschiedenen Embedding-Modellen 🔍:
- Die Iteration mit dem Embedding-Modell ist entscheidend. Das Embedding-Modell bestimmt, wie Ihre Daten als Vektoren dargestellt werden. Vektoren sind die Lingua franca der KI und verbessern, wie die Vektordatenbank die richtigen Informationsstücke abrufen kann.
Experimentieren mit verschiedenen LLMs oder generativen Modellen 🧪:
- Jede Language-Model-(LLM) API hat unterschiedliche Kosten, Latenzen und Genauigkeiten. Durch Tests können Sie diejenige auswählen, die für Ihre Workload am besten funktioniert.
Tauchen wir ein und erkunden wir, wie diese Strategien funktionieren und wie Sie mit Evaluierungen die leistungsstärksten Konfigurationen für Ihre realen RAG-Anwendungen identifizieren können! 🚀📚
Intelligente Textaufteilung
Textaufteilung ist wie das Zerschneiden einer langen Geschichte in kleinere, mundgerechte Stücke, damit ein Computer die wichtigsten Teile beim Beantworten von Fragen oder Unterstützen bei Aufgaben leicht finden und verwenden kann.
Im Folgenden erkläre ich einige verschiedene Techniken. Diese Techniken werden in diesem Originalartikel von Greg Kamradt ausführlich und anschaulich erklärt.
Rekursive zeichenbasierte Textaufteilung 🔄:
- Das Aufteilen von Text in Abschnitte basierend auf der Zeichenanzahl stellt sicher, dass jedes Stück handhabbar und kohärent ist.
Small-to-Big-Textaufteilung 📏:
- Mit größeren Abschnitten beginnen und diese schrittweise in kleinere aufteilen. Suchen mit kleinen, aber Abrufen mit großen Abschnitten.
Semantische Textaufteilung 🧠:
- Text nach Bedeutung aufteilen, sodass jeder Abschnitt eine vollständige Idee oder ein Thema repräsentiert und der Kontext erhalten bleibt.
Diese Methoden helfen Ihnen, Text für verschiedene Anwendungen effektiv zu organisieren und abzurufen. Tauchen Sie ein, um zu erkunden, wie jede Technik funktioniert!
Rekursive zeichenbasierte Textaufteilung 🔄
Beginnen Sie damit, Text mithilfe von LangChains RecursiveCharacterTextSplitter in Abschnitte fester Größe mit Überlappung fester Größe aufzuteilen.
from langchain.text_splitter import RecursiveCharacterTextSplitter
CHUNK_SIZE = 512
chunk_overlap = np.round(CHUNK_SIZE * 0.10, 0)
print(f"chunk_size: {CHUNK_SIZE}, chunk_overlap: {chunk_overlap}")
# The splitter to use to create smaller (child) chunks.
child_text_splitter = RecursiveCharacterTextSplitter(
chunk_size=CHUNK_SIZE,
chunk_overlap=chunk_overlap,
length_function = len, # use built-in Python len function
separators = ["\n\n", "\n", " ", ". ", ""], # defaults
)
# Child docs directly from raw docs
sub_docs = child_text_splitter.split_documents(docs)
# Inspect chunk lengths
print(f"{len(docs)} docs split into {len(sub_docs)} child documents.")
plot_chunk_lengths(sub_docs, 'Recursive Character')
Bild vom Autor: Matplotlib-Diagramm der Abschnittslängen des RecursiveCharacterTextSplitter, vollständiger Code auf GitHub verfügbar.
Small-to-Big-Textaufteilung 📏
Diese Technik sucht mithilfe kleiner (untergeordneter) Chunks, ruft jedoch große (übergeordnete) Text-Chunks ab. Es werden zwei Memory Stores verwendet: 1) Dokumentspeicher und 2) Vektorspeicher. Der folgende Code verwendet LangChains MultiVectorRetriever.
from langchain_milvus import Milvus
from langchain.text_splitter import RecursiveCharacterTextSplitter
import uuid
from langchain.storage import InMemoryByteStore
from langchain.retrievers.multi_vector import MultiVectorRetriever
# Create doc storage for the parent documents
store = InMemoryByteStore()
id_key = "doc_id"
# Create vectorstore for vector index and retrieval.
COLLECTION_NAME = "MilvusDocs"
vectorstore = Milvus(
collection_name=COLLECTION_NAME,
embedding_function=embed_model,
connection_args={"uri": "./milvus_demo.db"},
auto_id=True,
# Set to True to drop the existing collection if it exists.
drop_old=True,
)
# The MultiVectorRetriever (empty to start)
retriever = MultiVectorRetriever(
vectorstore=vectorstore,
byte_store=store,
id_key=id_key,
)
PARENT_CHUNK_SIZE = 1586
# The splitter to use to create bigger (parent) chunks
parent_text_splitter = RecursiveCharacterTextSplitter(
chunk_size=PARENT_CHUNK_SIZE,
length_function = len, # use built-in Python len function
# separators=["\n\n"], # split at end of paragraphs
)
# Parent docs directly from raw docs
parent_docs = parent_text_splitter.split_documents(docs)
doc_ids = [str(uuid.uuid4()) for _ in parent_docs]
# Inspect chunk lengths
print(f"{len(docs)} docs split into {len(parent_docs)} parent documents.")
plot_chunk_lengths(parent_docs, 'Parent')
CHUNK_SIZE = 512
chunk_overlap = np.round(CHUNK_SIZE * 0.10, 0)
print(f"chunk_size: {CHUNK_SIZE}, chunk_overlap: {chunk_overlap}")
# The splitter to use to create smaller (child) chunks.
child_text_splitter = RecursiveCharacterTextSplitter(
chunk_size=CHUNK_SIZE,
chunk_overlap=chunk_overlap,
length_function = len, # use built-in Python len function
separators = ["\n\n", "\n", " ", ". ", ""], # defaults
)
# Child docs directly from parent docs
sub_docs = child_text_splitter.split_documents(parent_docs)
# Inspect chunk lengths
print(f"{len(docs)} docs split into {len(sub_docs)} child documents.")
plot_chunk_lengths(sub_docs, 'Small-to-big')
Small-to-big-Chunking.png
Bild vom Autor: Matplotlib-Diagramm der Small-to-big-Chunk-Längen; der vollständige Code ist auf github verfügbar.
Semantische Textaufteilung 🧠
Dieser Chunker funktioniert, indem er bestimmt, wann Sätze voneinander „getrennt“ werden sollen. Diese Aufgabe erfüllt er, indem er Kosinusdistanzen zwischen benachbarten Sätzen berechnet. Betrachtet er all diese Kosinusdistanzen, sucht er nach Ausreißerdistanzen oberhalb eines bestimmten Schwellenwerts. Diese Ausreißerdistanzen bestimmen, wann Chunks aufgeteilt werden.
Es gibt einige Möglichkeiten, diesen Schwellenwert zu bestimmen, die durch das breakpoint_threshold_type kwarg gesteuert werden.
from langchain_experimental.text_splitter import SemanticChunker
semantic_docs = []
for doc in docs:
# Extract and clean document content.
cleaned_content = clean_text(doc.page_content)
# Initialize the SemanticChunker with the embedding model.
text_splitter = SemanticChunker(embed_model)
semantic_list = text_splitter.create_documents([cleaned_content])
# Append the list of semantic chunks to semantic_docs.
semantic_docs.extend(semantic_list)
# Inspect chunk lengths
print(f"Created {len(semantic_docs)} semantic documents from {len(docs)}.")
plot_chunk_lengths(semantic_docs, 'Semantic')
Wir werden die Milvus-Dokumentation als unsere Daten und Ragas als Evaluierungsmethode für dein RAG verwenden. Lies meinen Blog darüber, wie man RAGAS verwendet.
Das Ergebnis war:
- Chunking-Methode = Recursive Character Text Splitter mit top_k=2 war die beste.
Verschiedene Embedding-Modelle
Nachdem ich die Chunking-Methode auf Recursive Character Text Splitter mit top_k=2 festgelegt hatte, habe ich zwei verschiedene Embedding-Modelle ausprobiert.
BAAI/bge-large-en-v1.5
Text-embedding-3-small mit embedding-dim = 512
Unter Verwendung der Milvus-Dokumentation und der Evaluierungsmethode Ragas war das Ergebnis:
- Embedding-Modell = BAAI/bge-large-en-v1.5 war das beste.
Verschiedene LLMs
Nachdem ich die Chunking-Methode auf einen Recursive Character Text Splitter mit top_k=2 und das Embedding-Modell auf BAAI/bge-large-en-v1.5 festgelegt hatte, habe ich sechs verschiedene LLM-API-Endpunkte ausprobiert.
Unter Verwendung der Milvus-Dokumentation und der Evaluierungsmethode Ragas war das Ergebnis:
- LLM = MistralAI mixtral_8x7b_instruct unter Verwendung von Anyscale Endpoints war das beste.
Fazit
Die Evaluierung einer RAG-Pipeline variiert je nach deinen spezifischen Daten und deinem Anwendungsfall. Eine wichtige Erkenntnis aus persönlicher Erfahrung und der Literatur ist, dass die größten Verbesserungen oft durch die Verfeinerung deiner Retrieval-Strategien erzielt werden. 🛠️
Unter Verwendung von Milvus-Dokumentationsdaten und Ragas-Evaluierung stellte dieser Blog fest:
35% Verbesserung durch Änderung der Chunking-Strategie 📦
27% Verbesserung durch Änderung des Embedding-Modells 🔍
6% Verbesserung durch Änderung des LLM-Modells 🤖
Die Iteration über diese Elemente kann helfen, deine RAG-Pipeline für bessere Ergebnisse zu optimieren!
Referenzen
Greg Kamradt Tutorial zu 5 verschiedenen Ebenen des Text Splitting: https://github.com/FullStackRetrieval-com/RetrievalTutorials/blob/main/tutorials/LevelsOfTextSplitting/5_Levels_Of_Text_Splitting.ipynb
LangChain Recursive Character Text Splitter: https://python.langchain.com/v0.2/docs/how_to/recursive_text_splitter/
LangChain Multivector Retriever: https://python.langchain.com/v0.2/docs/how_to/multi_vector/#smaller-chunks
LangChain Semantic Chunker: https://python.langchain.com/v0.2/docs/how_to/semantic-chunker/#standard-deviation
So verwendest du RAGAS zur Evaluierung deiner RAG-Pipeline: https://medium.com/towards-data-science/rag-evaluation-using-ragas-4645a4c6c477
Weiterlesen

Why Teams Are Migrating from Weaviate to Zilliz Cloud — and How to Do It Seamlessly
Explore how Milvus scales for large datasets and complex queries with advanced features, and discover how to migrate from Weaviate to Zilliz Cloud.

Zilliz Cloud Enterprise Vector Search Powers High-Performance AI on AWS
Zilliz Cloud on AWS powers secure, scalable, ultra-fast vector search for enterprise AI apps, with BYOC, sub-10ms latency, and zero-DevOps simplicity.

Zilliz Cloud Introduces Advanced BYOC-I Solution for Ultimate Enterprise Data Sovereignty
Explore Zilliz Cloud BYOC-I, the solution that balances AI innovation with data control, enabling secure deployments in finance, healthcare, and education sectors.



