After successfully loading, processing, and chunking your documents, and associating relevant metadata with each chunk, the next significant step is to prepare this data for efficient retrieval. The core idea is to enable semantic search, allowing the system to find chunks based on meaning rather than just keyword matching. This involves transforming the text of each chunk into a numerical representation (a vector embedding) and then storing these embeddings, along with the original text and metadata, in a specialized database optimized for vector operations: a vector database.
Before storage, each processed chunk needs to be converted into a vector embedding. As discussed in Chapter 2, embedding models (like Sentence-BERT, MPNet, or OpenAI's Ada models) transform text into high-dimensional vectors where semantically similar text passages result in vectors that are close to each other in the vector space.
You typically iterate through your collection of chunks, feeding the text content of each chunk into your chosen embedding model. The output for each chunk is a dense vector, often with hundreds or thousands of dimensions.
# Example using a hypothetical embedding library
from embedding_library import EmbeddingModel
from data_preparation import processed_chunks # Assuming this holds our chunks
embedding_model = EmbeddingModel("sentence-transformers/all-MiniLM-L6-v2") # Example model
embeddings = []
for chunk in processed_chunks:
# Generate embedding for the chunk's text content
vector = embedding_model.embed(chunk['text_content'])
embeddings.append({
"id": chunk['id'], # Unique ID for the chunk
"vector": vector,
"metadata": chunk['metadata'], # Associated metadata (source, page, etc.)
"text": chunk['text_content'] # Store original text for context later
})
# 'embeddings' now contains a list of objects ready for the vector database
It's important to store not just the vector but also a unique identifier for the chunk, its original text content, and the associated metadata (like source document name, page number, or section title). The original text is needed to provide context to the LLM later, and the metadata is essential for source attribution and potential filtering during retrieval.
Standard relational or NoSQL databases are generally not designed for efficient similarity searches in high-dimensional vector spaces. Finding the "nearest" vectors to a query vector quickly requires specialized indexing structures and search algorithms. This is where vector databases excel.
Introduced in Chapter 2, vector databases (like Pinecone, Weaviate, Chroma, Qdrant, Milvus, etc.) provide the infrastructure to:
The process of adding your processed data to a vector database is often referred to as indexing or upserting (update or insert). You typically connect to your chosen vector database instance (whether local or cloud-based) using its client library and then add your prepared data, often in batches for efficiency.
# Example using a hypothetical vector database client
from vector_db_client import VectorDatabaseClient
# Assume 'embeddings' is the list generated in the previous step
vector_db = VectorDatabaseClient(api_key="YOUR_API_KEY", environment="gcp-starter") # Example connection
index_name = "my-knowledge-base"
# Ensure the index/collection exists (specific API calls vary)
if not vector_db.index_exists(index_name):
vector_db.create_index(
name=index_name,
dimension=len(embeddings[0]['vector']), # Dimension must match embedding model
metric='cosine' # Common similarity metric
)
# Add data in batches
batch_size = 100
for i in range(0, len(embeddings), batch_size):
batch = embeddings[i : i + batch_size]
# Prepare batch for the specific DB's API (might involve tuples, objects, etc.)
prepared_batch = [
(item['id'], item['vector'], {**item['metadata'], 'text': item['text']})
for item in batch
]
vector_db.upsert(index_name=index_name, vectors=prepared_batch)
print(f"Successfully indexed {len(embeddings)} chunks.")
Each item indexed typically includes:
The following diagram illustrates the overall flow from a document to indexed data in the vector database:
This diagram shows a document being processed into chunks with metadata. Each chunk's text is converted into a vector embedding using an embedding model. Finally, these embeddings, along with their IDs, metadata, and original text, are indexed in a vector database.
Once this indexing process is complete, your knowledge source is effectively transformed into a searchable vector space. The retriever component, which we discussed earlier and will implement later, can now query this vector database using the embedding of an incoming user question to find the most relevant chunks of information swiftly. This forms the foundation for providing grounded, context-aware responses from the LLM.
© 2025 ApX Machine Learning