From .NET to AI Engineer — Part 3: Teaching a Machine What Words Mean
Part 3 of the series on staying current by adding AI, from a .NET background. Days 8–10 of the journey — the stage where "AI" finally stopped feeling abstract, because I could see how a machine compares meaning.
Up to here, everything had been familiar territory dressed in Python clothes. Stage 3 was the first time something felt genuinely new: turning a sentence into a list of numbers that captures what it means, so a computer can tell that "I led a backend team" and "managed engineers on a server platform" are talking about the same thing — even though they share no keywords.
That's embeddings, and it's the foundation of search, recommendations, and RAG. Three days, and worth slowing down for.
Theory
Embeddings: text as coordinates
An embedding model takes a piece of text and returns a vector — a long list of numbers (a few hundred to a couple of thousand). The trick is that the model places texts with similar meaning close together in that space. "Dog" and "puppy" land near each other; "dog" and "spreadsheet" land far apart.
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("all-MiniLM-L6-v2")
vectors = model.encode(["I led a backend team", "managed engineers on a platform"])
Once your text is vectors, "find similar meaning" becomes "find nearby vectors" — a math problem instead of a language problem.
Cosine similarity, and why keyword search isn't enough
The usual way to measure "nearby" is cosine similarity — roughly, the angle between two vectors. Close to 1 means very similar meaning; close to 0 means unrelated.
This is the leap past traditional keyword search (the kind Elasticsearch does brilliantly). Keyword search matches words; semantic search matches meaning. Ask a keyword engine for "ways to cut cloud spend" and it'll miss a document titled "reducing AWS costs." A semantic search won't.
Vector databases
You don't want to recompute similarities by hand every time. A vector database stores your embeddings and finds the nearest ones fast. While learning, I ran ChromaDB locally — no setup, no account. For scale you'd reach for a hosted option, but local is perfect for getting the concepts into your hands.
import chromadb
client = chromadb.PersistentClient(path="./chroma")
collection = client.get_or_create_collection("docs")
collection.add(
ids=["c1", "c2"],
embeddings=model.encode(chunks).tolist(),
documents=chunks,
metadatas=[{"source": "resume.pdf"}, {"source": "resume.pdf"}],
)
results = collection.query(
query_embeddings=model.encode(["leadership experience"]).tolist(),
n_results=3,
)
Chunking and reranking
You can't embed an entire document as one vector and expect good results — too much meaning crushed into one point. So you chunk text into smaller passages first, embed each, and retrieve the most relevant chunks. And because the top result isn't always the best result, a reranking step can reorder the candidates for quality. (Chunking is deep enough that it gets its own treatment later in the series — here, just know it exists and matters.)
Build: semantic search over PDFs
The Stage 3 project was a semantic search engine over a folder of PDFs: extract the text, split it into chunks, embed each chunk, store them in Chroma, then answer a typed question by retrieving the closest chunks. It's small, but the first time it surfaced the right passage from a phrasing it had never seen, the whole idea clicked.
The takeaway
Embeddings are the bridge between human language and machine math, and once you internalize "meaning becomes distance," a lot of modern AI stops being mysterious. Hold onto one distinction in particular: semantic search finds relevant text — it does not understand or reason about it. That gap is exactly what the next part is about, and it's the most useful lesson in the whole series.
The 3-day plan (if you want to follow along)
| Day | Time | Learn (theory) | Build | Why it matters | Reference | Output |
|---|---|---|---|---|---|---|
| 8 | ~5h | Embeddings — text as vectors | Embed sentences; eyeball which ones land close together | This is the bridge from language to math that everything AI sits on | OpenAI embeddings guide; SBERT | A script that scores similarity between sentences |
| 9 | ~6h | Cosine similarity; keyword vs semantic search; vector DBs | Store embeddings in ChromaDB and query them | "Find similar meaning" becomes "find nearby vectors" — fast and local | Pinecone "semantic search" learn; Chroma docs | A working similarity search over a small set |
| 10 | ~7h | Chunking and reranking basics | Semantic search over a folder of PDFs | Retrieval quality lives or dies on how you chunk | (your practice notebook) | A PDF search tool pushed to GitHub |
What I used to learn this
- OpenAI's embeddings guide: https://platform.openai.com/docs/guides/embeddings
- Sentence-Transformers (SBERT): https://www.sbert.net/
- Pinecone's "semantic search" learning material: https://www.pinecone.io/learn/
- ChromaDB docs: https://docs.trychroma.com/
Related code: https://github.com/ashaniwale-codestack/job-matching-engine
Next up — Part 4: the project that taught me what RAG really is. I built a retrieval system without an LLM, watched it fail in an instructive way, and finally understood what RAG is actually for.