Semantic Icons

Writing

Should icon search use embeddings or keywords?

3 min read searchembeddingsretrieval

Should icon search use embeddings or keyword matching?

Use both, in that order. A lexical ranker answers most queries in the page with no request and no key. Send only the queries it scores below a measured floor to a vector search. Words are cheaper and more predictable; vectors catch the query whose words appear nowhere in your set.

The question is usually asked as a choice. It is not one. The two methods fail in different places, and the failures barely overlap.

What each one is good at

A lexical ranker reduces the query to stems and scores every icon on phrase, keyword and category hits, weighted by term rarity. It runs in the page over a static index. No request, no key, no latency, and the same query always gives the same answer.

It fails when the reader's words are not in your set at all. a filibuster matches nothing, however good the tags are.

A vector search embeds the query and finds the nearest icon by cosine distance. It matches meaning, so it can answer with stopwatch for a phrase that never says time.

It fails on precision. Everything has a nearest neighbour, so it always returns something, and it is confidently wrong when nothing in the set is close. It also costs a network request, a key, and a model that must never change without re-embedding the whole set.

LexicalVector
LatencyNone, runs locallyA network request
PredictableYesMostly
Unfamiliar wordsFailsOften works
Confidently wrongRareCommon without a floor
Cost per queryZeroSmall but real

Run them in order

The design that works is a cascade with a measured threshold.

  1. Score the query lexically. If the best score is above the floor, answer with it and stop. This is most queries.
  2. Below the floor, embed the query and search the vectors. If the nearest icon is above a similarity floor, answer with it.
  3. Below both floors, draw something new, or say you have nothing.

Our floors are 38 for the lexical score and 0.62 cosine similarity for the vector stage. Both were measured, not chosen. The lexical floor came from a labelled set of 64 prose phrases: it is the point below which the top answer stops being right.

The important property is that stage two only runs when stage one is not confident. So the common case costs nothing, and the key is only needed for the hard queries.

Embed the right text

A vector over an icon needs text to embed, and the obvious choice, the icon's name, is the wrong one. Embed everything the icon knows:

[icon.phrase, icon.category.replace(/-/g, ' '), icon.concept, icon.keywords.join(', ')].join('. ')

This matters more than the model choice. Thin tags make thin vectors, so the work described in how many keywords does an icon need improves both layers at once.

Store the model id with the vector

This is the failure that bites people six months later.

Vectors from one model are meaningless to a query embedded by another. If you switch model, or a provider silently updates one, every stored vector becomes noise that still returns confident results.

Write the model id on every row and filter on it:

select ... from icons
where embedding_model = $model_id
order by embedding <=> $query

Now a model change makes the vector stage find nothing, and the cascade falls through to the next stage. That is the correct failure: it degrades to the previous behaviour instead of returning nonsense.

Which model

For retrieval, pick the model with the better retrieval benchmark, not the one that is easiest to sign up for. We got this wrong once, chose the convenient provider, and reversed it. The cost difference across 3000 icons was about two cents. Convenience should never have outranked recall at that price.

Whatever you pick, keep the same model for the documents and for the queries, and re-embed the whole set when you change.

Questions people ask

Do I need a vector database?

Not for a few thousand icons. pgvector with an HNSW index handles this comfortably, and you probably already run Postgres. A dedicated vector store earns its place at a much larger scale.

How do I choose the similarity floor?

Take twenty queries you expect to answer and twenty you expect to miss. Record the top similarity for each. The floor sits in the gap. If there is no gap, the embedding text is too thin.

Can embeddings replace keywords entirely?

Only if you accept a request on every search, a confident wrong answer whenever the set has no match, and no offline use. For an icon picker in an editor, none of those is acceptable.

How often do I need to re-embed?

When the model changes, and when an icon's phrase, concept or keywords change. Adding an icon only needs one new vector.

Search 3006 icons with a sentence

Ask for an icon the way you would say it.

Try the search