Search
Code115
description: >- How to build semantic search with embeddings for Val Town within Val Town itselfWe'll look at a completely different sorting mechanism called “semantic search”, which uses one of those crazy features to come out of the world of machine learning: embeddings. It works like this: you put in a string into an API, and you get an array of numbers, called an “embedding”, with the property that things that have the same “vibe” will get similar numbers.So we need to generate an embedding when a document is created or updated. Then, when someone searches for something, we create an embedding for their search query. Then we find the document embeddings that are closest to the query embedding.That's the idea, though in practice embeddings can be quite unpredictable and surprising. Our examples above already break down… “animal that barks” vibes more with “woof woof woof” than with “dog”. And “dog bites man” and “man bites dog” have even more similar vibes than that, presumably because there's lots of biting going on in both cases.<figure style="max-width: 700px; margin: 0 auto"> <img src="https://assets.blog.val.town/legacy/val-vibes/compare-embeddings.png" alt="" /> <figcaption>1. **Indexing**: generating embeddings for all public vals.2. **Querying**: generating an embedding for the search query, and comparing to the indexed embeddings.```sqlCREATE TABLE vals_embeddings (id TEXT PRIMARY KEY, embedding VECTOR(1536));```We filter out vals for which we have existing embeddings, put the rest in batches of 100 vals.Then we fetch embeddings from OpenAI for each batch of 100 in parallel, and save them to Postgres.description: >- How to build semantic search with embeddings for Val Town within Val Town itselfWe'll look at a completely different sorting mechanism called “semantic search”, which uses one of those crazy features to come out of the world of machine learning: embeddings. It works like this: you put in a string into an API, and you get an array of numbers, called an “embedding”, with the property that things that have the same “vibe” will get similar numbers.So we need to generate an embedding when a document is created or updated. Then, when someone searches for something, we create an embedding for their search query. Then we find the document embeddings that are closest to the query embedding.That's the idea, though in practice embeddings can be quite unpredictable and surprising. Our examples above already break down… “animal that barks” vibes more with “woof woof woof” than with “dog”. And “dog bites man” and “man bites dog” have even more similar vibes than that, presumably because there's lots of biting going on in both cases.<figure style="max-width: 700px; margin: 0 auto"> <img src="https://assets.blog.val.town/legacy/val-vibes/compare-embeddings.png" alt="" /> <figcaption>1. **Indexing**: generating embeddings for all public vals.2. **Querying**: generating an embedding for the search query, and comparing to the indexed embeddings.```sqlCREATE TABLE vals_embeddings (id TEXT PRIMARY KEY, embedding VECTOR(1536));```We filter out vals for which we have existing embeddings, put the rest in batches of 100 vals.Then we fetch embeddings from OpenAI for each batch of 100 in parallel, and save them to Postgres.description: >- How to build semantic search with embeddings for Val Town within Val Town itselfWe'll look at a completely different sorting mechanism called “semantic search”, which uses one of those crazy features to come out of the world of machine learning: embeddings. It works like this: you put in a string into an API, and you get an array of numbers, called an “embedding”, with the property that things that have the same “vibe” will get similar numbers.So we need to generate an embedding when a document is created or updated. Then, when someone searches for something, we create an embedding for their search query. Then we find the document embeddings that are closest to the query embedding.That's the idea, though in practice embeddings can be quite unpredictable and surprising. Our examples above already break down… “animal that barks” vibes more with “woof woof woof” than with “dog”. And “dog bites man” and “man bites dog” have even more similar vibes than that, presumably because there's lots of biting going on in both cases.<figure style="max-width: 700px; margin: 0 auto"> <img src="https://assets.blog.val.town/legacy/val-vibes/compare-embeddings.png" alt="" /> <figcaption>1. **Indexing**: generating embeddings for all public vals.2. **Querying**: generating an embedding for the search query, and comparing to the indexed embeddings.```sqlCREATE TABLE vals_embeddings (id TEXT PRIMARY KEY, embedding VECTOR(1536));```We filter out vals for which we have existing embeddings, put the rest in batches of 100 vals.Then we fetch embeddings from OpenAI for each batch of 100 in parallel, and save them to Postgres.description: >- How to build semantic search with embeddings for Val Town within Val Town itselfWe'll look at a completely different sorting mechanism called “semantic search”, which uses one of those crazy features to come out of the world of machine learning: embeddings. It works like this: you put in a string into an API, and you get an array of numbers, called an “embedding”, with the property that things that have the same “vibe” will get similar numbers.So we need to generate an embedding when a document is created or updated. Then, when someone searches for something, we create an embedding for their search query. Then we find the document embeddings that are closest to the query embedding.That's the idea, though in practice embeddings can be quite unpredictable and surprising. Our examples above already break down… “animal that barks” vibes more with “woof woof woof” than with “dog”. And “dog bites man” and “man bites dog” have even more similar vibes than that, presumably because there's lots of biting going on in both cases.<figure style="max-width: 700px; margin: 0 auto"> <img src="https://assets.blog.val.town/legacy/val-vibes/compare-embeddings.png" alt="" /> <figcaption>1. **Indexing**: generating embeddings for all public vals.2. **Querying**: generating an embedding for the search query, and comparing to the indexed embeddings.```sqlCREATE TABLE vals_embeddings (id TEXT PRIMARY KEY, embedding VECTOR(1536));```We filter out vals for which we have existing embeddings, put the rest in batches of 100 vals.Then we fetch embeddings from OpenAI for each batch of 100 in parallel, and save them to Postgres.description: >- How to build semantic search with embeddings for Val Town within Val Town itselfWe'll look at a completely different sorting mechanism called “semantic search”, which uses one of those crazy features to come out of the world of machine learning: embeddings. It works like this: you put in a string into an API, and you get an array of numbers, called an “embedding”, with the property that things that have the same “vibe” will get similar numbers.So we need to generate an embedding when a document is created or updated. Then, when someone searches for something, we create an embedding for their search query. Then we find the document embeddings that are closest to the query embedding.That's the idea, though in practice embeddings can be quite unpredictable and surprising. Our examples above already break down… “animal that barks” vibes more with “woof woof woof” than with “dog”. And “dog bites man” and “man bites dog” have even more similar vibes than that, presumably because there's lots of biting going on in both cases.<figure style="max-width: 700px; margin: 0 auto"> <img src="https://assets.blog.val.town/legacy/val-vibes/compare-embeddings.png" alt="" /> <figcaption>1. **Indexing**: generating embeddings for all public vals.2. **Querying**: generating an embedding for the search query, and comparing to the indexed embeddings.```sqlCREATE TABLE vals_embeddings (id TEXT PRIMARY KEY, embedding VECTOR(1536));```We filter out vals for which we have existing embeddings, put the rest in batches of 100 vals.Then we fetch embeddings from OpenAI for each batch of 100 in parallel, and save them to Postgres.It does this by comparing [embeddings from OpenAI](https://platform.openai.com/docs/guides/embeddings) generated for the code of all public vals, to an embedding of your search query.I implemented three backends, which you can switch between in the search UI. Check out these vals for details on their implementation.- **Neon:** storing and searching embeddings using the [pg_vector](https://neon.tech/docs/extensions/pgvector) extension in Neon's Postgres database. - Searching: [janpaul123/semanticSearchNeon](https://www.val.town/v/janpaul123/semanticSearchNeon) - Indexing: [janpaul123/indexValsNeon](https://www.val.town/v/janpaul123/indexValsNeon)- **Blobs:** storing embeddings in Val Town's [standard blob storage](https://docs.val.town/std/blob/), and iterating through all of them to compute distance. Slow and terrible, but it works! - Searching: [janpaul123/semanticSearchBlobs](https://www.val.town/v/janpaul123/semanticSearchBlobs) ); const { OpenAIEmbeddings } = await import( "https://esm.sh/langchain/embeddings/openai" ); [{ id: 2 }, { id: 1 }, { id: 3 }], new OpenAIEmbeddings({ openAIApiKey: process.env.OPENAI_API_KEY,import { searchEmojis } from "https://esm.town/v/maxm/emojiVectorEmbeddings";import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo"; <br /> Built on Val Town with sqlite vector search and openai embeddings. <br />Uses vector embeddings to get "vibes" search on emojis
```async function calculateEmbeddings(text) { const url = `https://yawnxyz-ai.web.val.run/generate?embed=true&value=${encodeURIComponent(text)}`; } catch (error) { console.error('Error calculating embeddings:', error); return null; ); const { OpenAIEmbeddings } = await import( "https://esm.sh/langchain/embeddings/openai" ); [{ id: 2 }, { id: 1 }, { id: 3 }, { id: 4 }, { id: 5 }], new OpenAIEmbeddings({ openAIApiKey: process.env.OPENAI_API_KEY,/** * Call OpenAPI Embeddings api to vectorize a query string * Returns an array of 1536 numbers}): Promise<number[]> => fetchJSON("https://api.openai.com/v1/embeddings", { method: "POST", ); const { OpenAIEmbeddings } = await import( "https://esm.sh/langchain/embeddings/openai" ); [{ id: 2 }, { id: 1 }, { id: 3 }], new OpenAIEmbeddings({ openAIApiKey: process.env.OPENAI_API_KEY,async function generateEmbedding(text: string): Promise<number[]> { const response = await openai.embeddings.create({ model: "text-embedding-ada-002",