Build a recommendation system with vector search, LangChain, and OpenAI
A vector search runs to display similar products to the user. In this tutorial, you will build a simple product recommendation system. As products are added or updated, the embeddings in the database are automatically updated. Using a large language model (LLM) and vector search, you do not have to manually categorize the products. The embeddings determine similar products, reducing the effort to manage the recommendations.
Prerequisites
This tutorial requires the following:
-
An active Serverless (vector) database
-
An application token with the Database Administrator role.
-
A paid OpenAI account
-
Python 3.10 to 3.14
-
Node.js 18 or later
Prepare the tutorial project
-
Clone the tutorial’s Git repository, and then switch to that directory:
git clone https://github.com/datastax/astra-db-recommendations-starter/ cd astra-db-recommendations-starter -
Export the following environment variables:
export API_ENDPOINT=API_ENDPOINT export APPLICATION_TOKEN=ASTRA_APPLICATION_TOKEN export OPENAI_API_KEY=API_KEY export KEYSPACE_NAME=default_keyspace # A keyspace that exists in your database export VECTOR_DIMENSION=1536KEYSPACE_NAMEis optional. If not set,default_keyspaceis used.VECTOR_DIMENSIONis set to1536for the purposes of this tutorial.For information about the endpoint and credentials, see Prerequisites.
-
Install project dependencies.
npm install pip3 install -r requirements.txt
Load the tutorial data
Run the load_data.py script to load the tutorial data into your database, including embeddings for similarity search:
python3 populate_db/load_data.py populate_db/product_data.csv
If you encounter an OpenAI rate limit error, see the OpenAI rate limits documentation.
The data for this tutorial comes from a marketing sample produced by Amazon.
It contains 10,000 example Amazon products in CSV format, which you can see in the tutorial’s /populate_db folder.
The load_data.py script loads the product data into a Serverless (vector) database along with a vector embedding for each product.
Embeddings are generated from text data extracted from each product entry.
The original CSV file has 28 columns. It is important to store all of this information in your database, but not all columns are necessary for generating recommendations. This tutorial’s recommendation system is designed to suggest related products based on product name, description, and price. Therefore, only the columns that provide this information need to be embedded. Other columns, like image links and stock count, don’t need to be embedded because they aren’t relevant to the recommendation system’s similarity search. However, this information can inform other parts of the recommendation system, such as displaying product images or checking stock availability.
You could generate embeddings from the entire data set, but this is inefficient, likely to hit API or model limits, and it pollutes the embeddings with irrelevant information that can reduce the quality of the resulting recommendations.
To ensure the embeddings contain only the relevant information, the script extracts only the relevant fields from the original data set: Product name, brand name, category, selling price, description, and URL.
input_rows = load_csv_file(filepath)
for chunk_start in range(0, len(input_rows), EMBEDDING_CHUNK_SIZE):
chunk = input_rows[chunk_start : chunk_start + EMBEDDING_CHUNK_SIZE]
to_embed_strings = [
json.dumps(
{
key.lower().replace(" ", "_"): row[key]
for key in (
"Product Name",
"Brand Name",
"Category",
"Selling Price",
"About Product",
"Product Url",
)
}
)
for row in chunk
]
...
Then, the script converts the resulting JSON object to a string, and then embeds that string using an OpenAI embedding model.
This tutorial used text-embedding-ada-002 and produced vectors of length 1536.
The embedding vectors are computed in chunks to optimize the overhead of calling the embedding API.
The returned vector is added to the full product JSON object in $vector, and then the script loads the complete entry into the database.
The insertion is done through the Data API Python client, which automatically loads data in batches as needed.
...
embedding_vectors = embed_list(to_embed_strings)
documents_to_insert = []
for row, embedding_vector in zip(chunk, embedding_vectors):
document_to_insert = {
"$vector": embedding_vector,
**{key.lower().replace(" ", "_"): value for key, value in row.items()},
}
documents_to_insert.append(document_to_insert)
insertion_result = collection.insert_many(documents_to_insert)
Your vector database is ready to use for product search and recommendations.
Deploy the assistant locally
For this tutorial, deploy the recommendations assistant to a local environment. For production applications, you would deploy an assistant to a serverless environment, such as Vercel, along with other supporting services and infrastructure.
-
Start the backend server (API) that was installed from
requirements.txtin Prepare the tutorial project:uvicorn api.index:app --reload -
Because the backend server continues to run in the terminal, open a second terminal.
-
In the new terminal, start the web server that was installed from
requirements.txt:npm run dev -
In a browser, navigate to
http://localhost:3000to access the demo website and test the recommendation system. -
Enter a query in the search bar to get recommendations.
An embedding is generated from the search query, and then used for a vector search on your vector database. Each search result shows the product details from your database except the vector field, which isn’t needed to populate the user-facing product catalog. Because you stored the vectors in the database, you don’t need to generate embeddings every time you get recommendations. The code can use the current product’s ID to extract the vector from the database.
For this tutorial, the backend API is split between a few files:
index.py-
Contains the actual FastAPI code. It defines an API with a few routes, which make use of functions for database retrieval, embedding computation and LLM usage.
query.py-
Offers the primitive functions for product retrieval from the collection. It uses
local_creds.pyfor the database authentication credentials and parameters. recommender_utils.py-
Manages the LLM-assisted recommendations and the search of similar product through embedding vectors.
This script sends a prompt to an LLM. The prompt template uses the search and relevant recommendations as parameters:
# prompt that is sent to OpenAPI using the response from the vector database prompt_boilerplate = ( "Of the following products, all preceded with PRODUCT NUMBER, select the " + str(count) + " products most recommended to shoppers who bought the product preceded " + "by ORIGINAL PRODUCT below. Return the product_id corresponding to " + "those products." ) original_product_section = "ORIGINAL PRODUCT: " + json.dumps( # strip_blank_fields removes empty-string fields from documents that don't add value to the LLM-assisted evaluation # strip_for_query selects a subset of fields that are known to be relevant to the LLM-assisted evaluation strip_blank_fields(strip_for_query(get_product(product_id))) ) comparable_products_section = "\n".join(string_product_list) final_answer_boilerplate = "Final Answer: " nl = "\n" return ( prompt_boilerplate + nl + original_product_section + nl + comparable_products_section + nl + final_answer_boilerplate, long_product_list, )The response from the LLM includes a selection of the input products that are most similar to the initial product. This evaluation is performed by the model and uses similarity search for comparison.
Next steps
In this tutorial, you generated embeddings for Amazon products, stored those embeddings in a Serverless (vector) database, and used prompt engineering to retrieve and display similar products based on a user’s search query. You also created an application to interact with this recommendation system.
Next, try customizing the code to add your own products, use different prompts, and redesign the frontend.
You can also try building apps with the Astra DB Data API clients. To get started with the clients, see:
-
Quickstart for collections:
-
Quickstart for tables: