Esplorare tre strategie chiave per creare una Retrieval Augmented Generation (RAG) efficiente
Retrieval Augmented Generation (RAG) è una tecnica utile per usare i tuoi dati in un Chatbot basato sull'IA. In questo post del blog, ti guiderò attraverso tre strategie chiave per ottenere il massimo da RAG:
Suddivisione Intelligente del Testo in Chunk 📦:
- Il primo passo è suddividere i tuoi dati testuali in chunk significativi e gestibili. Questo passaggio assicura che il tuo Vector Database possa recuperare le informazioni più rilevanti in modo rapido e accurato.
Iterazione su Diversi Modelli di Embedding 🔍:
- Iterare sul modello di embedding è fondamentale. Il modello di embedding determina come i tuoi dati vengono rappresentati come vettori. I vettori sono la lingua franca dell'IA, migliorando il modo in cui il Vector Database può recuperare i giusti frammenti di informazioni.
Sperimentazione con Diversi LLM o Modelli Generativi 🧪:
- Ogni API di Language Model (LLM) ha costi, latenze e accuratezze diversi. Testarle ti consente di scegliere quella che funziona meglio per il tuo carico di lavoro.
Immergiamoci ed esploriamo come funzionano queste strategie e come puoi identificare le configurazioni con le migliori prestazioni per le tue applicazioni RAG reali con le valutazioni! 🚀📚
Suddivisione Intelligente del Testo in Chunk
La suddivisione del testo in chunk è come tagliare una lunga storia in pezzi più piccoli e facili da gestire, così che un computer possa trovare e usare facilmente le parti più importanti quando risponde a domande o aiuta con dei compiti.
Di seguito, spiegherò alcune tecniche diverse. Queste tecniche sono spiegate molto bene e in profondità in questo articolo originale di Greg Kamradt.
Suddivisione Ricorsiva del Testo per Caratteri 🔄:
- Suddividere il testo in chunk in base al conteggio dei caratteri assicura che ogni pezzo sia gestibile e coerente.
Suddivisione del Testo dal Piccolo al Grande 📏:
- Iniziare con chunk più grandi e suddividerli progressivamente in chunk più piccoli. Cercare usando il piccolo, ma recuperare usando il Grande.
Suddivisione Semantica del Testo 🧠:
- Dividere il testo in base al significato in modo che ogni chunk rappresenti un'idea o un argomento completo, assicurando che il contesto sia preservato.
Questi metodi ti aiuteranno a organizzare e recuperare il testo in modo efficace per varie applicazioni. Immergiti per esplorare come funziona ogni tecnica!
Suddivisione Ricorsiva del Testo per Caratteri 🔄
Inizia suddividendo il testo in chunk di dimensione fissa con sovrapposizione di dimensione fissa usando RecursiveCharacterTextSplitter di LangChain.
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')
Immagine dell'autore: grafico Matplotlib delle lunghezze dei chunk di RecursiveCharacterTextSplitter, codice completo disponibile su GitHub.
Suddivisione del Testo dal Piccolo al Grande 📏
Questa tecnica effettua la ricerca usando piccoli blocchi (figli), ma recupera usando grandi blocchi (genitori) di testo. Vengono usati due archivi di memoria: 1) archiviazione dei documenti e 2) archiviazione vettoriale. Il codice qui sotto usa MultiVectorRetriever di LangChain.
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')
chunking da piccolo a grande.png
Immagine dell’autore: grafico Matplotlib delle lunghezze dei blocchi da piccolo a grande; il codice completo è disponibile su github.
Suddivisione semantica del testo 🧠
Questo chunker funziona determinando quando "spezzare" le frasi. Svolge questo compito calcolando le distanze del coseno tra frasi adiacenti. Osservando tutte queste distanze del coseno, cerca distanze anomale oltre una certa soglia. Queste distanze anomale determinano quando i blocchi vengono suddivisi.
Ci sono alcuni modi per determinare quella soglia, che sono controllati dal breakpoint_threshold_type kwarg.
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')
Utilizzeremo la documentazione di Milvus come nostri dati e Ragas come metodo di valutazione per il tuo RAG. Leggi il mio blog su come usare RAGAS.
Il risultato è stato:
- Metodo di chunking = Recursive Character Text Splitter con top_k=2 è stato il migliore.
Diversi modelli di embedding
Fissando il metodo di chunking a Recursive Character Text Splitter con top_k=2, ho provato due diversi modelli di embedding.
BAAI/bge-large-en-v1.5
Text-embedding-3-small con embedding-dim = 512
Usando la documentazione di Milvus e il metodo di valutazione Ragas, il risultato è stato:
- Modello di embedding = BAAI/bge-large-en-v1.5 è stato il migliore.
Diversi LLM
Dopo aver fissato il metodo di chunking come Recursive Character Text Splitter con top_k=2 e il modello di embedding come BAAI/bge-large-en-v1.5, ho provato sei diversi endpoint API LLM.
Usando la documentazione di Milvus e il metodo di valutazione Ragas, il risultato è stato:
- LLM = MistralAI mixtral_8x7b_instruct usando Anyscale Endpoints è stato il migliore.
Conclusione
La valutazione della pipeline RAG varierà a seconda dei tuoi dati specifici e del caso d'uso. Un punto chiave tratto dall'esperienza personale e dalla letteratura è che i miglioramenti più significativi spesso derivano dal perfezionamento delle strategie di recupero. 🛠️
Usando i dati della documentazione di Milvus e la valutazione Ragas, questo blog ha osservato:
Miglioramento del 35% cambiando la strategia di chunking 📦
Miglioramento del 27% cambiando il modello di embedding 🔍
Miglioramento del 6% cambiando il modello LLM 🤖
Iterare su questi elementi può aiutare a ottimizzare la tua pipeline RAG per ottenere risultati migliori!
Riferimenti
Tutorial di Greg Kamradt su 5 diversi livelli di suddivisione del testo: 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
Come usare RAGAS per valutare la tua pipeline RAG: https://medium.com/towards-data-science/rag-evaluation-using-ragas-4645a4c6c477
Continua a leggere

How Zilliz Ended Up at the Center of NVIDIA’s Unstructured Data Story at GTC 2026
If unstructured data is the context of AI, then the ceiling of AI applications will be set not just by models, but by how mature the infrastructure for unstructured data becomes.

Introducing Zilliz MCP Server: Natural Language Access to Your Vector Database
Developers can easily manage and query vector databases with natural language via Zilliz MCP Server in AI-native environments.

Vector Databases vs. Key-Value Databases
Use a vector database for AI-powered similarity search; use a key-value database for high-throughput, low-latency simple data lookups.



