Last post ended with a confession: most_similar works, but the vectors it ranks are handmade. We sat there scoring "biryani" on a foody axis ourselves, and nobody is doing that for fifty blog posts. Today a model takes over the scoring. This is the piece that turns our little ranking function into actual search.

Renting a cartographer
An embedding model has exactly one job: read text, return coordinates. Send it a sentence, get back the 1536 numbers that place that sentence on the meaning map. It cannot chat, it cannot summarize, it just places things. A very good cartographer, nothing more.
We will use OpenAI's text-embedding-3-small, the model behind the 1536-number examples from the last two posts. Which means the series needs its first real API key. Create one at platform.openai.com, put it in an environment variable, never paste it into code. If the price worries you, it shouldn't: embeddings are the cheap aisle of GenAI. Embedding all fifty posts of our Blog costs less than one chat reply.
import osimport httpxurl = "https://api.openai.com/v1/embeddings"headers = {"Authorization": f"Bearer {os.environ['OPENAI_API_KEY']}"}payload = { "model": "text-embedding-3-small", "input": "I love pizza",}response = httpx.post(url, headers=headers, json=payload, timeout=30)vector = response.json()["data"][0]["embedding"]print(len(vector)) # 1536print(vector[:4]) # [0.0182, -0.0271, 0.0093, -0.0154], yours will differThree things worth noticing:
The request looks almost exactly like the chat call from the First LLM API Call post, just
inputinstead ofmessages. Same POST, same header style, same family of APIs.The reply nests the goods at
data["data"][0]["embedding"], the embedding cousin of thechoices[0]path you already have as muscle memory.
Fifty posts, one call
input also accepts a list, so you can embed a whole batch of texts in one request:
texts = [ "Deploying FastAPI with Docker", "Shipping your app to production", "My grandma's biryani recipe",]payload = {"model": "text-embedding-3-small", "input": texts}response = httpx.post(url, headers=headers, json=payload, timeout=30)vectors = [item["embedding"] for item in response.json()["data"]]Now feed these real vectors to the cosine_similarity function from last post:
print(cosine_similarity(vectors[0], vectors[1])) # ~0.5print(cosine_similarity(vectors[0], vectors[2])) # ~0.1Your exact digits will differ, and remember the rule from last post: don't interrogate a lone score, compare them. The comparison says everything. "Deploying FastAPI with Docker" and "Shipping your app to production" share almost no words, yet they score way above the biryani recipe. That is the Ctrl+F problem from the Vectors post, defeated in twelve lines. 😁
