How to Generate kNN Vector Embeddings for Vector Search in Amazon OpenSearch
kNN vector embeddings are generated using Amazon Bedrock's Cohere embedding model and stored in OpenSearch's native knn_vector field, enabling semantic similarity search through the k-NN query clause with optional hybrid filtering.
The aws-samples/sample-for-amazon-opensearch-service-tutorials-101 repository implements an end-to-end vector search pipeline that transforms product metadata into dense vector representations. This solution demonstrates how to generate 1024-dimensional embeddings using fully managed ML models and leverage OpenSearch's approximate nearest neighbor (ANN) engine for low-latency semantic retrieval across both persistent and in-memory storage tiers.
Generating kNN Vector Embeddings with Amazon Bedrock
The get_embedding Helper Function
In artifacts/index_lambda/opensearch_index.py, the get_embedding function handles all embedding generation by invoking the Cohere embed-english-v3 model through Amazon Bedrock. This function accepts a text string and returns a 1024-dimensional float array representing the semantic meaning of the input.
def get_embedding(text):
"""Gets embedding for text using Cohere model via Bedrock."""
body = json.dumps({
"texts": [text],
"input_type": "search_document",
"truncate": "END",
"embedding_types": ["float"]
})
response = bedrock_client.invoke_model(
modelId=MODEL_ID,
accept='application/json',
contentType='application/json',
body=body
)
response_body = json.loads(response.get('body').read())
return response_body['embeddings']['float'][0]
Source: opensearch_index.py – get_embedding
The function sets input_type to "search_document" when indexing content and implicitly uses query-time embeddings for search (handled by the same function with equivalent payload structure). The returned vector captures semantic relationships between product attributes, enabling similarity matching beyond simple keyword overlap.
Indexing Embeddings in OpenSearch
Creating Vector Index Mappings
Before ingesting data, the pipeline establishes dedicated indices with knn_vector field mappings. The repository provides two storage configurations:
- On-disk index (
create_vector_index_on_disk_mode): Persists vectors to disk using HNSW (Hierarchical Navigable Small World) algorithm with FAISS engine, suitable for large-scale collections. - In-memory index (
create_vector_index_in_memory_mode): Stores vectors in RAM for ultra-low latency retrieval on smaller datasets.
Both indices define the vector_embedding field with dimension 1024 and cosine similarity space:
# On-disk index mapping structure
"properties": {
"vector_embedding": {
"type": "knn_vector",
"dimension": 1024,
"method": {
"name": "hnsw",
"space_type": "cosinesimil",
"engine": "faiss"
}
}
}
Sources: create_vector_index_on_disk_mode, create_vector_index_in_memory_mode
Bulk Indexing Products
The vectorize_and_index_products function orchestrates the embedding pipeline. It reads the product catalog, concatenates relevant text fields (title, category, description), generates embeddings via get_embedding, and bulk-indexes documents into both storage tiers simultaneously.
def vectorize_and_index_products(event):
# … read products from source …
for product in batch:
combined_text = f"{product['title']}, Category: {product['category']}, Description: {product['description']}"
vector_embedding = get_embedding(combined_text)
product['vector_embedding'] = vector_embedding
# Prepare bulk payload for on-disk index
bulk_data_on_disk.append({"index": {"_index": VECTOR_INDEX_NAME_ON_DISK, "_id": uuid.uuid4().hex}})
bulk_data_on_disk.append(product)
# Prepare bulk payload for in-memory index
bulk_data_in_memory.append({"index": {"_index": VECTOR_INDEX_NAME_IN_MEMORY, "_id": uuid.uuid4().hex}})
bulk_data_in_memory.append(product)
# … execute bulk API call …
Source: vectorize_and_index_products
This dual-indexing strategy allows runtime selection between cost-effective persistent storage and high-performance memory-based search without reprocessing the source data.
Executing kNN Vector Search Queries
Pure Vector Search Implementation
The search Lambda in artifacts/search_lambda/opensearch_search.py handles incoming requests via API Gateway. For vector search requests ("type": "vector_search"), it generates a query-time embedding and constructs an OpenSearch knn query clause targeting the vector_embedding field.
elif body["type"] == "vector_search":
search_text = body["attribute_value"]
vector_embedding = get_embedding(search_text)
search_body = {
"size": 100,
"_source": {"excludes": ["vector_embedding"]},
"query": {
"knn": {
"vector_embedding": {"vector": vector_embedding, "k": 100}
}
}
}
Source: vector_search handling
The query retrieves the top k=100 most similar vectors using the same cosine similarity metric defined in the index mapping. The _source exclusion prevents returning the large vector arrays to the client, reducing payload size.
Selecting Storage Modes at Query Time
The Lambda inspects the "mode" parameter to route queries to the appropriate index:
if body["mode"] == "on_disk":
response = ops_client.search(index=VECTOR_INDEX_NAME_ON_DISK, body=search_body)
elif body["mode"] == "in_memory":
response = ops_client.search(index=VECTOR_INDEX_NAME_IN_MEMORY, body=search_body)
Source: search mode dispatch
Implementing Hybrid Search
For complex retrieval scenarios, the repository supports hybrid search that combines kNN vector similarity with traditional Boolean filters. This approach matches semantic intent while respecting hard constraints like category or color attributes.
search_body = {
"size": 100,
"_source": {"excludes": "vector_embedding"},
"query": {
"hybrid": {
"queries": [
{"bool": {"should": should_match_conditions, "minimum_should_match": 1}},
{"knn": {"vector_embedding": {"vector": vector_embedding, "k": 100}}}
]
}
},
"post_filter": {"bool": {"must": should_match_conditions}},
"search_pipeline": SEARCH_PIPELINE_NAME
}
Source: hybrid search construction
The hybrid query structure runs both the keyword match and vector search in parallel, then normalizes scores through the specified search pipeline. The post_filter ensures results meet all metadata constraints while preserving the semantic ranking.
Practical Implementation Examples
Generating an Embedding for Product Data
from artifacts.index_lambda.opensearch_index import get_embedding
product_description = "Women's lightweight running shoe with breathable mesh upper"
embedding = get_embedding(product_description)
print(f"Vector dimension: {len(embedding)}") # Output: 1024
Indexing a Product Catalog
Invoke the indexing Lambda to process products_content.jsonl and populate both vector indices:
python -m artifacts.index_lambda.opensearch_index vectorize_and_index_products
This executes vectorize_and_index_products, which automatically chunks the dataset, generates embeddings via Bedrock, and bulk-uploads to OpenSearch.
Performing Pure kNN Search via API
POST /search
{
"type": "vector_search",
"attribute_value": "red waterproof hiking boots",
"mode": "on_disk"
}
The search Lambda converts the query text to a vector embedding and executes the knn query against the on-disk index, returning the 100 most semantically similar products.
Executing Hybrid Vector + Keyword Search
POST /search
{
"type": "hybrid_search",
"attribute_value": "comfortable office chair",
"mode": "in_memory"
}
This triggers the hybrid query pipeline, combining vector similarity with metadata filters to return relevant results that match both semantic intent and categorical constraints.
Summary
- Embedding Generation: The
get_embeddingfunction inartifacts/index_lambda/opensearch_index.pygenerates 1024-dimensional vectors using Amazon Bedrock's Cohereembed-english-v3model. - Index Architecture: The solution maintains parallel indices for on-disk persistence and in-memory speed, both utilizing OpenSearch's
knn_vectorfield type with HNSW/FAISS indexing. - Vector Search: Query-time embeddings feed into native OpenSearch
knnqueries against thevector_embeddingfield, retrieving the top 100 matches by cosine similarity. - Hybrid Capability: The search Lambda supports combining vector similarity with traditional term filters using OpenSearch's hybrid query syntax and search pipelines.
Frequently Asked Questions
What embedding model does the repository use for generating kNN vector embeddings?
The implementation uses the Cohere embed-english-v3 model hosted on Amazon Bedrock. This model outputs 1024-dimensional float vectors optimized for semantic similarity tasks, with specific input_type parameters distinguishing between document indexing and query generation.
What is the difference between on-disk and in-memory vector indices in OpenSearch?
The on-disk index persists vectors to storage using the FAISS engine with HNSW graphs, enabling cost-effective search across large datasets (millions of vectors). The in-memory index loads vectors entirely into RAM, eliminating disk I/O latency for sub-millisecond retrieval on smaller, high-throughput workloads. Both use identical 1024-dimensional mappings but trade cost against latency.
How does the hybrid search combine keyword and vector similarity?
Hybrid search constructs a query containing both a knn clause for vector similarity and a bool clause for metadata filtering. OpenSearch's hybrid query processor executes both sub-queries simultaneously, applies normalization through a search pipeline, and blends the relevance scores. The post_filter ensures hard constraints (like exact color matches) are preserved in the final result set.
What is the dimension size of the vector embeddings used in this implementation?
The vector embeddings are 1024 dimensions wide. This matches the output specification of the Cohere embed-english-v3 model and is explicitly configured in the OpenSearch index mappings via the dimension: 1024 parameter in both create_vector_index_on_disk_mode and create_vector_index_in_memory_mode functions.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →