Building a Magical AI-Powered Semantic Search from Scratch: A Practical Guide to Embeddings, pgvector, and LLM-Generated Answers
In-depth discussion
Easy to understand
0 0 1
The author describes building an AI-powered semantic search over their five-year blog archive, so readers can ask questions and receive digested answers drawn from their posts. The pipeline cleans MDX files, splits content into 100-token chunks, generates OpenAI embeddings (text-embedding-ada-002), and stores them in a Supabase pgvector database. Queries are embedded and matched via cosine similarity against a tuned threshold, and results are fed to an LLM for a human-readable response. The article includes conceptual explanations of embeddings and cosine similarity, interactive widgets, code snippets for indexing, storage, and querying, and discussion of threshold trade-offs.
main points
unique insights
practical applications
key topics
key insights
learning outcomes
• main points
1
Provides an end-to-end implementation walkthrough covering indexing, embedding generation, vector storage, retrieval, and response generation
2
Explains embeddings and cosine similarity intuitively, supported by interactive widgets and visualizations
3
Includes concrete, reusable code for MDX cleanup, token-based chunking, OpenAI embedding calls, Supabase insertion, and a pgvector match function
• unique insights
1
Practical guidance on choosing chunk size: chunks must be large enough to carry meaning yet small enough to match specific queries
2
Explains that for closely related domain content, cosine similarity scores cluster high, so the similarity threshold must be set well above typical baseline values (e.g., 0.85) to return only truly relevant matches
• practical applications
Readers gain a reusable blueprint for building retrieval-augmented, LLM-backed semantic search over their own content, including pitfalls around MDX parsing, chunking strategy, threshold tuning, and grounding LLM answers to reduce hallucination. Directly applicable to documentation sites, blogs, and knowledge bases.
• key topics
1
Text embeddings and vector representations
2
Cosine similarity and similarity thresholds for semantic search
3
Indexing pipelines: MDX cleanup and token-based chunking
4
Vector storage and querying with Supabase pgvector
5
Combining embedding retrieval with LLM completion for grounded answers
• key insights
1
A complete, from-scratch build of a personal-content AI search rather than a generic library overview
2
Interactive demonstrations that make embedding similarity tangible for readers new to the concept
3
Honest discussion of practical tuning trade-offs such as threshold selection and chunk size
• learning outcomes
1
Understand how text embeddings represent meaning and how cosine similarity measures semantic closeness
2
Build an indexing pipeline that cleans content, chunks it by tokens, generates embeddings, and stores them in pgvector
3
Implement a query flow that embeds user input, retrieves matches above a tuned similarity threshold, and feeds them to an LLM for grounded responses
“ Introduction: Why Build a Semantic Search for Your Own Content
Over several years of writing about React, Framer Motion, Three.js, shaders, and other front-end topics, a technical blog accumulates a substantial body of knowledge. Readers frequently reach out with questions that have already been answered somewhere across dozens of posts. The author wanted a way for readers, and for themselves, to type a question and receive a digest of everything previously written on the topic. Traditional keyword search falls short here because it returns links rather than answers and misses conceptually related content that uses different wording. A smarter, semantic approach was needed. Recent advances in large language models have made this kind of feature practical to build in just a few days, combining embedding generation for retrieval with a completion API for synthesis. The result is an interactive experience where answers are streamed to the user in real time, creating a 'magic moment' that encourages exploration of the content.
“ The Architecture: Two Core Problems to Solve
The project breaks down into two main challenges. The first is indexing: processing the blog's MDX files and storing a representation of their content in a retrievable form. This stage is the most critical, since errors here propagate into every query. The second is producing accurate, human-readable output: finding the sentences most similar to a user's query and transforming them into a coherent, non-hallucinated response. OpenAI's embedding and completion APIs address both sides. The indexing pipeline converts content chunks into embeddings, stores them in a database, and converts incoming queries into embeddings for comparison. The output pipeline passes the matching chunks to a GPT prompt with rules that constrain the model to rely on the author's own writing. The author chose Supabase, which provides a Postgres database with the pgvector extension, over specialized vector databases such as Pinecone or Chroma, reasoning that Postgres is a dependable foundation and that Supabase published a comprehensive guide for this exact use case.
“ Understanding Embeddings and Vector Similarity
Embeddings are multi-dimensional vectors that represent words or text passages as points in a mathematical space, where proximity reflects semantic relationship. Words like 'JavaScript' and 'code' sit close together, while 'coffee' lies farther away. Because embeddings can represent entire sentences and chunks of text, the meaning of a passage can be compared numerically with a user's query. Similarity is measured using cosine similarity, which evaluates the angle between two vectors: values near 1 indicate closely related content, while values near 0 indicate unrelated content. Choosing the right threshold is subtle. Since the blog consistently focuses on code and JavaScript, almost every query will show moderate similarity with most chunks, so the threshold must be set higher, around 0.85 in the author's experiments, to return only the most relevant results. The OpenAI embedding model used produces 1536-dimensional vectors for each input.
“ Preparing Content: Cleaning and Parsing MDX Files
The blog uses MDX, a Markdown variant that embeds React components for interactivity. While powerful, this complicates extracting raw text for embedding. Interactive widgets and Sandpack components should not be indexed, yet valuable content can be nested within them, and some legitimate code snippets resemble the components that must be removed. The solution is a small set of utility functions rather than a fragile collection of regular expressions. One function removes JSX tags, another extracts the display text from Markdown links, another replaces newline characters with spaces, and a main cleanup routine processes the file line by line. It tracks whether the current line falls inside a fenced code block, toggling a flag on triple-backtick markers. Lines outside code blocks are stripped of JSX and link syntax, while lines inside code blocks are preserved intact so that code examples remain searchable.
“ Chunking Text for Meaningful Retrieval
Embedding an entire article is ineffective because a user's question typically concerns one specific concept, and a whole-article vector would produce low similarity with most queries and return far too much material. Conversely, very small chunks yield vectors that lack context and may match queries inappropriately. The author adopted a chunk size of approximately 100 tokens, a strategy also used by Aleksandra Spasic in a similar project. The chunking function uses the gpt3-tokenizer library to encode the cleaned text into tokens, accumulates tokens into chunks until the limit is reached, and then records each chunk's text along with its start and end positions. Tokens may be words, subwords, or characters, and OpenAI's tokenizer can be used to visualize how a given passage is split. After chunking, each piece is ready to be sent to the embedding API.
“ Generating and Storing Embeddings with pgvector
For each chunk, the pipeline calls OpenAI's embeddings endpoint using the text-embedding-ada-002 model, which returns a 1536-dimensional vector. Each record stores the vector alongside the chunk text and metadata: the article title and URL. The chunk text is kept so it can later be supplied to the completion prompt, giving the language model concrete context, and the URL enables source attribution in responses. The author created a single documents table in Supabase with a bigserial primary key, a text content column, text url and title columns, and an embedding column of type vector(1536). Insertion is handled with the Supabase JavaScript client, which writes each chunk's metadata and embedding into the table. Error handling wraps each API call so that failures can be managed without halting the entire indexing run. This pipeline only needs to run when content changes, making the indexing cost a one-time effort per article.
“ Querying: Matching User Questions to Relevant Content
At query time, the user's question is sent to the same embedding model, producing a vector comparable with the stored chunks. The author defined a Postgres function, match_documents, that accepts the query embedding, a match threshold, and a maximum match count. The function computes similarity as one minus the pgvector cosine distance operator, filters rows whose similarity exceeds the threshold, orders results by descending similarity, and limits the output. The function is exposed through Supabase's remote procedure call interface, so the frontend or an edge function can invoke it directly. An edge function handler receives the query, requests its embedding, calls the RPC with the desired threshold and count, and returns the matching chunks with their titles, URLs, and similarity scores. Because the similarity computation lives in the database, the approach scales cleanly and keeps retrieval logic in one place.
“ Generating Human-Readable Answers with an LLM
Raw matching chunks are useful for developers but not ideal for readers seeking direct answers. The final stage passes the retrieved chunks into a carefully crafted GPT completion prompt. The prompt includes the user's question, the relevant excerpts, and rules instructing the model to answer only from the supplied material and to cite the source articles. Grounding the model in the author's own writing is the key defense against hallucination: because the context is explicit and restricted, the response reflects what the author has actually written. The author notes that this is not automatic, and prompt design plays an important role. The synthesized answer is then streamed to the frontend in real time, which amplifies the sense of responsiveness and contributes to the interactive experience. Streaming also reduces perceived latency, since users begin reading before the full response is complete.
“ Tuning the Similarity Threshold and Practical Lessons
The most important tuning parameter is the similarity threshold. If it is set too high, too few chunks are returned and the language model lacks enough context to write a coherent answer. If it is set too low, the retrieved material becomes vague or unrelated, producing answers that do not match the user's intent. Finding the right balance requires testing with representative queries and adjusting until results are both sufficiently numerous and accurate. Several practical lessons emerged. Cleaning MDX content carefully is essential to avoid polluting the index with interface code. Chunk size should be chosen to preserve enough context per vector while keeping matches focused. Storing the original text and source metadata enables both prompt construction and attribution. Finally, the author cautions that embedding and completion calls carry real costs at scale, so the implementation should be designed with efficiency in mind, avoiding unnecessary re-indexing and limiting the number of chunks sent to the model.
“ Conclusion and Next Steps
Building an AI-powered semantic search from scratch demonstrates how accessible modern tooling has become. By combining content cleaning, text chunking, OpenAI embeddings, a pgvector-enabled Postgres database, and a completion model with carefully designed prompts, a developer can transform years of written content into an interactive question-answering experience in a matter of days. The core ideas, namely converting text to vectors, retrieving by similarity, and grounding generated answers in source material, apply well beyond technical blogs to documentation, knowledge bases, and any corpus of owned content. Readers interested in replicating the project can follow the same pipeline: parse and clean the source files, chunk them into roughly 100-token segments, generate and store embeddings, expose a similarity search function, and stream grounded answers to the user. Careful threshold tuning and prompt design determine the quality of the final experience.
We use cookies that are essential for our site to work. To improve our site, we would like to use additional cookies to help us understand how visitors use it, measure traffic to our site from social media platforms and to personalise your experience. Some of the cookies that we use are provided by third parties. To accept all cookies click ‘Accept’. To reject all optional cookies click ‘Reject’.
Comment(0)