Let's talk
Menu

AI systems / Search

Search by meaning.

Drag or use arrow keys to orbit

Vector search

Semantic retrieval and recommendation systems tuned for your domain, latency, and operating cost.

What we build

Useful from
day one.

EmbeddingspgvectorQdrantReranking

Relevant representations

We select embeddings and chunking using your content and query distribution.

Hybrid retrieval

Semantic and lexical signals work together where either approach alone falls short.

Measured relevance

A repeatable evaluation set turns search quality into a number we can improve.

The route to production

Clear steps.
Visible progress.

01

Discover

We map the real workflow, risks, users, and success measure before choosing an implementation.

02

Build

Working software lands in visible increments, with the hardest assumptions tested first.

03

Prove

We validate against production-shaped data, failure paths, performance, and real use.

04

Ship

Deployment, monitoring, documentation, and a clean handover are part of the build.

View all services ↗

Verified client reviews

In their words.

“Fantastic and easy to communicate with!”
CreativeZeth DesignsChicago, USA · 5.0 ★
“Everything was set up perfectly and worked exactly as we needed. Super professional, easy to communicate with, and delivered right on time.”
Soft SolutionsAPNs & Firebase · Islamabad, Pakistan · 5.0 ★

Before you ask

Questions,
answered.

When is vector search the right tool, and when is keyword search enough?

Embeddings are good at paraphrase and bad at identifiers. Someone typing an error code or a SKU gets fuzzy nearest neighbours that share a topic but not the token that actually mattered, while lexical search nails those and is poor at a question phrased in the user's own words. If your queries are mostly exact terms, codes and names, the search you already have may be doing the job. Most real products need both signals working together.

Which embedding model should we use?

The one that performs on your content and query distribution, decided by measurement rather than a leaderboard. On a 400,000-ticket corpus, moving to a bigger and slower embedding model bought about two points of nDCG@10; bolting a reranker onto the cheap model bought eleven. Model choice matters far less than most teams expect, and far less than what you do after retrieval.

Why does search work in testing and fall apart in production?

Because forty vectors have no failure modes yet. The interesting part starts around a million, when a brute-force scan is too slow, the approximate index you swapped in returns results that are subtly worse, and a negated query returns exactly what it was meant to exclude. None of those throw an error, none spike a dashboard, and a demo corpus cannot produce them.

What is the filtered-search trap?

One we have debugged in production. A client shipped semantic search over 2 million support tickets, and a tenant with 40 documents got an empty page for every query — not bad results, nothing — because the search took a global top 100 and only then filtered by tenant, leaving nothing to keep. Post-filtering fails hardest exactly when the filter is most selective. The fix depends on the engine: pass the filter into the graph traversal where the engine supports it, raise the search effort or build partial indexes on pgvector, and brute-force the subset outright when a filter cuts to a few thousand rows.

Do we need a dedicated vector database?

Usually not at first. pgvector inside Postgres keeps retrieval next to the application data and removes a moving part, which is the right default for most products. A dedicated store such as Qdrant earns its own operational life at scale, or where the index needs its own deployment and tuning cadence. Either way the index parameters get tuned against your corpus rather than left at the defaults.

How do you tune an approximate index?

By measuring instead of arguing. An HNSW index is not a mode you switch on, it is a few numbers whose defaults were tuned for public benchmark datasets rather than your corpus. Take a thousand real queries, compute the true top ten by brute force, then sweep the runtime search parameter and plot recall against p95 latency. There is a knee where recall sits around 0.98 and latency is still in double-digit milliseconds; past it you pay several times the latency for a fraction of a point. That plot is a twenty-minute script and the most useful artefact in the project.

Why add a reranker?

Because a bi-encoder compresses a whole document into one vector before it has ever seen your query. That compression is why search is fast and also why the top result is so often almost right. A cross-encoder reads query and document in the same pass and resolves what the embedding smeared — notably negation, where an unpaid and a paid invoice sit at nearly identical similarity on most models. Reranking a hundred candidates costs 50 to 150 milliseconds and is the cheapest large lever in the stack.

How do you know the search is actually good?

With a golden set and two numbers. Take a hundred real queries from your logs, have someone who knows the domain label the correct documents, then track recall at the candidate stage and nDCG on the reranked list in CI. They fail differently: low recall means the answer was never in the candidates and no reranker or prompt will conjure it, while high recall with poor ranking means retrieval is fine and the reranker is the problem. That split turns a vague complaint into a bug with an owner.

Our team blames the model when search feels wrong. Is that usually right?

Rarely. The model is downstream and it is what the user sees, so it takes the blame, and teams burn a month on prompt engineering to fix a problem where the right passage was never in the context window at all. Measuring retrieval separately from generation is what stops that month being spent.

What breaks quietly after launch?

Metric and dimension mismatches, mostly. An embedding model trained for cosine similarity handed to an index configured for Euclidean distance does not error; it returns results that are quietly wrong and you blame the model. If the vectors happen to be normalised the mismatch hides in every test you wrote, until someone bumps the model in a dependency file and the rankings diverge with no stack trace and no deploy to blame. We normalise explicitly at write time and keep a small CI test asserting the dimensions and a known similarity.

From the journal

Further reading.

Start with a paragraph, a Figma, or a Loom.

Build this properly.Reply within one business day · one clear scope · one fixed number