Projects · · 4 min read
Adding Vector Search to My Python Chatbot with Supabase
I walk through how I added vector search to my Python chatbot using Supabase, Gemini embeddings, and a few simple code tweaks.
A few weeks ago I asked myself how to make my Python chatbot understand user intent better, and the answer was to add vector search to python chatbot. By storing text embeddings in Supabase and querying them with a similarity metric, the bot can retrieve relevant snippets instead of relying on keyword matches alone. In this post I share the step‑by‑step process I followed, the bits that worked, and the parts I’m still tweaking. If you liked my earlier guide on Supabase auth, you’ll find some familiar patterns here /blog/supabase-auth-python-chatbot.
Why I wanted vector search in my chatbot
The basic rule‑based chatbot I built for answering common questions about my projects was hitting a wall when users asked variations of a query. A simple keyword match missed synonyms and paraphrases. Vector search lets you compare the meaning of sentences by using embeddings – dense numeric representations that capture semantics. After reading about how Gemini can generate embeddings for free, I decided to try it out and see if the bot became more helpful.
Setting up Supabase for vector storage
Supabase recently added a pgvector extension, which means you can store and query vectors directly in a Postgres table. I created a new table called "faq_embeddings" with three columns: id, text, and embedding (type vector(768)). The steps were:
- Log in to Supabase and enable the pgvector extension from the dashboard.
- Run a SQL migration to create the table:
- INSERT INTO faq_embeddings (text, embedding) VALUES ('How do I reset my password?', ...);
create extension if not exists vector;
create table faq_embeddings (
id bigserial primary key,
text text not null,
embedding vector(768) not null
);
I used the Supabase Python client to insert rows later after generating embeddings with Gemini.
Generating embeddings with Gemini
Gemini’s embedding API returns a 768‑dimensional vector for any piece of text. I wrapped the call in a tiny helper function so I could reuse it for both the FAQ data and incoming user messages.
import os, requests, json
def get_embedding(text: str) -> list[float]:
api_key = os.getenv('GEMINI_API_KEY')
resp = requests.post(
'https://generativelanguage.googleapis.com/v1/models/embedding-001:generateEmbedding',
params={'key': api_key},
json={'text': text}
)
resp.raise_for_status()
return resp.json()['embedding']
I ran the function over all the FAQ strings I already had and bulk‑uploaded the results to Supabase. The code below shows how to do it in batches so I don’t hit rate limits.
from supabase import create_client
supabase = create_client(os.getenv('SUPABASE_URL'), os.getenv('SUPABASE_KEY'))
faqs = [
"How do I reset my password?",
"What is the pricing model?",
"Can I export my data?",
]
for q in faqs:
emb = get_embedding(q)
supabase.table('faq_embeddings').insert({
'text': q,
'embedding': emb
}).execute()
How to add vector search to python chatbot
When a user types a message, I now generate its embedding and ask Supabase for the most similar stored vectors. Supabase supports a simple "<=>" operator for cosine distance, so the query is straightforward.
def find_best_match(user_msg: str, top_k: int = 3):
user_emb = get_embedding(user_msg)
resp = supabase.rpc('match_vectors', {
'query_vec': user_emb,
'match_count': top_k
}).execute()
return resp.data # list of rows with text and distance
I created a Postgres function called "match_vectors" that orders rows by the distance between the stored embedding and the query vector. The function lives inside Supabase and looks like this:
create or replace function match_vectors(query_vec vector, match_count int)
returns setof faq_embeddings as $$
select * from faq_embeddings
order by embedding <=> query_vec
limit match_count;
$$ language sql stable;
The chatbot now picks the top matching FAQ and feeds the associated answer to the response generator. If no match is close enough (distance > 0.4 in my experiments), it falls back to the original rule‑based reply.
Testing the new retrieval layer
I tried a few paraphrased questions: "I forgot my password, how can I change it?" and "Is there a way to get my data out of the system?" The bot returned the correct FAQ each time, something it struggled with before. I also measured latency – the extra embedding call adds about 150 ms, and the Supabase query is under 30 ms, which is acceptable for my use case.
What didn’t work as expected
The biggest hiccup was handling very short user inputs like "price?" – embeddings for such fragments are noisy, and the similarity score can be misleading. I added a simple length check and a fallback to keyword matching for messages under five characters. Also, the free tier of Supabase limits storage size, so I keep the FAQ list short (under 200 items) until I upgrade.
Takeaways and next steps
Adding vector search to my Python chatbot was easier than I thought thanks to Supabase’s pgvector and Gemini’s free embedding endpoint. The main lessons are: - Store embeddings once; reuse them for every query. - Use a similarity threshold to decide when to fall back. - Keep an eye on API rate limits and storage caps. I plan to experiment with hybrid search (keyword + vector) and maybe add user‑generated content to the vector store. If you’re curious about the other AI tools I use, see my post on building JARVIS /blog/build-python-voice-assistant. You can also explore more of my experiments on the projects page /projects.
Frequently asked questions
What is vector search and why use it in a chatbot?
Vector search compares the meaning of text using numeric embeddings instead of exact keywords. It lets a chatbot find relevant answers even when users phrase questions differently.
Can I store embeddings in Supabase for free?
Yes, Supabase now supports the pgvector extension, which allows you to store and query vectors directly. The free tier has storage limits, so keep your dataset modest.
How do I get embeddings from Gemini?
Gemini offers an embedding API that returns a 768‑dimensional vector for any string. You call the endpoint with your API key, send the text, and receive the vector to store or compare.
What should I do if the bot returns irrelevant results?
Set a similarity threshold (e.g., cosine distance < 0.4) and fall back to keyword matching or a default response when the distance is too high. Short inputs may also need a separate handling rule.