The embedding approach was overkill: it embedded only 'name — kind'
(short strings), spent a 4B model + ~2.5GB VRAM + 4.6GB of vectors + a
one-time 450k-row job to do what indexed SQL does directly.
New default — no embedding model, no extra VRAM:
- The LLM agent is the semantic layer: it maps the user's concept to
OSM kinds + local-language name keywords. It is already in VRAM for
chat, so this costs nothing.
- /kinds: returns the tag vocabulary that actually exists (GROUP BY kind
with counts) so the agent grounds its choices in real data.
- /search: indexed retrieval — kind IN/ILIKE (poi_kind), name FTS
(to_tsvector) + trigram (pg_trgm) for fuzzy/substring, optional
ST_DWithin radius. Ranked by trigram similarity then distance.
- schema.sql: real trigram GIN index (poi_name_trgm_ops); renamed the
misnamed FTS index to poi_name_fts.
- Agent tools: poi_semantic -> poi_kinds + poi_search (both pin results
on the map).
pgvector demoted to an opt-in path (embed/ + /semantic) — still works
if poi_vec is built, but no longer the default. Dropped the half-built
poi_vec and stopped the background embed run.