remember finds them and stores their coordinates, so the
graph knows where things are. The code is the package internal/geo (the resolvers) and memory_remember/remember_geo.go (the
stage of the pipeline); the data is the table gb_entity_geo.
For now geo is part of remember only: query does not use it yet (see the roadmap).
Turning it on
Off by default: it costs one more LLM call per text (an entity list is sent to thedetect_geo stage) and it sends the names of
places to a geocoder.
geo = trueingligabao.toml, orserve --geo: uses the public Nominatim geocoder of OpenStreetMap (cached; one request per second, as its usage policy asks).- A program that embeds gligabao sets
ServeOptions.GeoResolver(anygligabao.GeoResolver); that turns geo on by itself. For Go code that builds the call itself:memory_core.WithGeoResolver(r), andapi.WithGeoResolver(r)forapi.Register.
What the stage does
resolve_geo runs after the text (every chunk, for a long text) is stored and before the web search. It never fails the call:
errors are logged, recorded in the trajectory and run log, and returned in geo of the result.
- Candidates: the extracted entities that may be places (not a
person,concept,productorevent), at most 50, minus those that already have a row ingb_entity_geo: an entity is shared by every artifact that mentions it, so it is looked up once. A place the resolver did not know has no row, and is asked again the next time it is mentioned. - Detect (LLM, stage
detect_geo): which candidates are places, theirkind(country,region,cityorplace) and what to ask the resolver: the name plus what disambiguates it ("Paris, France"). It prefers precision: an entity it is not sure is a place is left out (“Bank of America” and “Paris Hilton” are not places). - Resolve: the geo resolver is asked for each place found (at most 25), one at a time. “Not found” is not a failure.
- Store: one transaction writes the rows of
gb_entity_geo.
detect_geo takes a model (models) and a reasoning level (reasoning) like the other LLM stages
(Remember). In the run log the stage is resolve_geo and holds the
detect_geo inference.
In the trajectory
An eval trajectory of a call with a geo resolver has, after the steps of the text itself, aselect_geo_entities step (the candidates), the detect_geo LLM call and a detect_geo step (the places it found, with kind and query), one resolve_geo step per place (found or not, the coordinates, latency_ms), and a store_geo step (how many places were stored). When there are no candidates, the stage stops after select_geo_entities.
The result
geo in the response of remember has one element per entity the LLM judged to be a place:
resolved: false with no error means the resolver does not know the place; with an error the resolver or the storage failed.
The table
gb_entity_geo has one row per geo entity, keyed by entity_id (a foreign key to gb_entities; the row goes with the entity):
Go:
graph.GetEntityGeo, graph.ListEntityGeo, and Tx.UpsertEntityGeo / Tx.GetEntityGeo inside a transaction.
Writing a resolver
geo.Cached(r) adds a cache, geo.NewNominatim(geo.NominatimOptions{BaseURL: ..., MinInterval: -1}) points at your own Nominatim
instance, and geo.Func adapts a function. A resolver must be safe for concurrent use and return coordinates in range
(Place.Validate), or the answer is treated as a failure.