Skip to content

Use case Building a local guide agent

mavrovde edited this page Sep 27, 2026 · 1 revision

Use case: Building a local guide agent

Five tools that turn a vague name into a coordinate and a coordinate back into a sentence — and one licence, the ODbL, that asks something of you if you keep the results.

The situation

You are building an agent that answers local questions about Germany: "where is the adesso office in Dortmund", "what is at 52.5163, 13.3777", "which Neustadt do you mean", "what's near the station". The hard part is not the lookup. The hard part is that a user says a name and you do not know what kind of thing it is.

"Neustadt" is one of about twenty towns. "adesso" is a company. "Hauptstraße 12, 36037 Fulda" is a door. "Köln Hbf" is a station. "A7" is a motorway. Each one lives in a different table, and pointing the wrong tool at it gets you nothing or, worse, the wrong Neustadt.

What you ask

Wo ist das adesso-Büro in Dortmund?

Which Neustadt is near Hamburg?

What's at 52.5163, 13.3777?

Wo ist die nächste Apotheke zum Leipziger Hauptbahnhof?

When the kind is genuinely unknown, there is a prompt for that: find_a_place with query (and optionally near to break a tie). It tries each table in turn and stops at the first that answers — the right recovery move after a tool has reported a name as ambiguous. Once you know the kind, skip the prompt and go straight to the tool: a settlement to find_place, a company or landmark to find_poi, a street address to find_address.

The five tools, and the one each is for

you have you want tool
a settlement, district, Land, station, stop, motorway or junction name which one it is, and its coordinate find_place
a business or landmark name, or a brand its address and coordinate find_poi
a street and house number the coordinate of that door find_address
a coordinate where that is, in words describe_location
a point what is around it find_nearby

There is a sixth way in that costs no tool call: the resource template viafrei://place/{query} resolves a name exactly the way every tool's place argument resolves it — a point, a short list of candidates, or an honest no. Read it once, pin the coordinate, and reuse it as lat/lon for every subsequent call. That is the cheapest way to stop re-resolving the same name.

find_place — which one of them is it

{ "query": "Neustadt", "near": "Hamburg", "radius_km": 50, "limit": 5, "language": "de" }

Returns every match with its kind, its official key (AGS/RS), population, English name and coordinate. near is the disambiguator: pass it and every result carries its distance from that point. lat+lon does the same job when you already hold a coordinate. radius_km (1–200) drops candidates outside the circle; omit it to rank by distance without dropping any.

kind narrows to one of city, district (Kreis), admin (Land or Regierungsbezirk), station, stop, motorway, junction. Omit it unless the person was specific.

This is the recovery tool. When any other tool reports a name as ambiguous, find_place is where you find out which one is meant — then you ask the user, then you pass the coordinate. English names and spellings without umlauts both work.

At most 10 results.

find_poi — the named thing

{ "name": "adesso", "in": "Dortmund", "category": "office", "limit": 5, "language": "de" }

Finds a company, shop, clinic, hotel, office or landmark and returns its address, category and coordinate. Part of the name is enough — "adesso" finds "adesso SE" — and brands are matched as well as names, so a chain name works.

Three things that decide whether this tool feels fast or slow:

  • Give in (a town) whenever you can. It is both far faster and far less ambiguous than a nationwide search. Use in or near/lat+lon, never both.
  • near + radius_km (1–50, default 10) is for "the nearest X" rather than "X in Y". radius_km is ignored when in is used.
  • category narrows to one OpenStreetMap family: office (companies, agencies), shop, amenity (fuel, pharmacy, school, restaurant), healthcare, tourism (hotels, attractions), leisure, industrial, building. Omit unless the person was specific — the wrong family returns nothing rather than something close.

The coordinate it returns passes straight to any other tool as lat/lon. At most 10 results.

find_address — the door

{ "query": "Hauptstraße 12, 36037 Fulda", "limit": 3, "language": "de" }

Needs a street and a number, plus a town or a postcode. Both orders work. Returns the coordinate and what OpenStreetMap holds under it.

One thing to get right in your UI: more than one result does not mean the address is ambiguous — it means the door is mapped more than once in OpenStreetMap. At most 5 results. Do not present them as alternative addresses.

And one honest caveat, because it has misled a careful measurement before: an address that does not resolve says nothing about its region. A real house number in a fully imported Bundesland can come back unfound simply because it is not in OpenStreetMap under the spelling it was asked for. Address and POI coverage spans all sixteen Länder (measured 2026-09-27); a miss is a miss on that door, not a gap in that Land.

describe_location — the other direction

{ "lat": 52.5163, "lon": 13.3777, "language": "en" }

Turns a coordinate into words: the nearest address, settlement, Kreis, administrative area and motorway junction, each with its own distance. Only this direction — it never looks a name up.

Two caveats it states about itself, and both matter if you build on it:

  • The gazetteer facts are the nearest point, not a boundary lookup. Near a border the nearest Kreis point can belong to the other side.
  • The administrative point can be a Regierungsbezirk rather than a Land. Do not build a jurisdiction or tax rule on it.

The explain_this_coordinate prompt wraps this into a sentence a traveller understands, with the same caveats stated in the answer.

find_nearby — what is around it

{ "place": "Leipzig Hbf", "radius_km": 3,
  "categories": ["fuel", "charging", "parking"], "language": "de" }

One glance across five categories — fuel, charging, parking, station (railway station), junction (motorway junction) — each with its distance. Radius 1–15 km, default 5; up to 3 per category. Omit categories for all five, because the full picture is usually the useful answer.

It is deliberately broad and shallow. It does not answer a fuel grade's price (find_cheapest_fuel), a connector or live charger status (find_charging_station), parking kind or occupancy (find_parking), a name lookup (find_place/find_poi/find_address) or a coordinate's address (describe_location). The plan_local_errand prompt does the glance first and then goes deeper on one category only if the user asks — that is the pattern to copy.

A pattern that works

user names something
   ↓
kind unknown?  →  viafrei://place/{query}  or the find_a_place prompt
   ↓
ambiguous?     →  find_place with `near`  →  ask the user which one
   ↓
pin lat/lon once
   ↓
every subsequent call takes lat+lon, not the name
   ↓
describe_location / find_nearby to say where it is and what is there

Pinning the coordinate matters for more than speed. Every tool that takes a place re-resolves it, and place resolution falls through the server's own gazetteer to the OpenStreetMap tables — so the same name can be answered from a different table on a different call, which changes the licence on the answer. Pin it once, and you know what you are holding.

What you get back

Candidates with kind, official key, population, English name, coordinate and distance; POIs with address, category and coordinate; addresses with the point and what is mapped there; a coordinate rendered as five facts with five distances; a neighbourhood as up to three things per category. All in the language of the question, with place names never translated.

viafrei://gazetteer tells you what the place index actually holds — rows and last load per source and kind, how many entries carry an English name, and the sixteen Länder it can name. Read it when you want to know the shape of the coverage rather than probing it with queries.

Attribution — what the OpenStreetMap licence asks of you

This is the page where the ODbL applies most of the time, so it is worth understanding rather than skimming.

Address and point-of-interest results are OpenStreetMap-derived under the ODbL 1.0, and they carry this line:

OSM-Standortdaten: © OpenStreetMap-Mitwirkende, ODbL 1.0

Reproduce that line, not a shorter one, and reproduce the version the server sent you. (An earlier register printed a Geokodierung: prefix; it is wrong, and no answer carries it. If you copied it, change it.)

Which answers are affected — and how to tell

The test is which table answered, not which tool you called and not what your input looked like:

  • A place the server resolved from its own gazetteer is not OSM-derived and carries neither the line nor the obligation. Nor does a station, nor a motorway.
  • A place it resolved from OpenStreetMap does. And because place resolution falls through the gazetteer to the OSM tables, any tool that takes a place can come back OSM-derived — a weather warning, a road status, a charging station, a car park, find_nearby. Measured on 2026-09-27, a weather warning asked for Zeiss-Großplanetarium named ["dwd", "osm"] and carried the ODbL line; one for Allianz Arena named ["osm"] alone.

So "did I ask for an address?" is the wrong question, and so is "is this a geocoding tool?". Read _meta.sources and the attribution line on the answer you actually got. That is a positive test, it costs nothing, and it is the only one that is right.

What you owe if you keep the results

ODbL's share-alike is on the database, not on the sentence. Showing a user an address you looked up is ordinary use. But:

If you build your own database out of address or point-of-interest results and use it publicly, you owe your recipients the same offer ViaFrei makes.

That offer is ODbL § 4.6, and ViaFrei's side of it stands: ask, and you get both extracts — addresses and points of interest, one file each — plus the alterations made to them, under ODbL 1.0 with a single licence notice covering the two. The server states the offer in viafrei://attribution, and viafrei://addresses reports what the address table holds per Bundesland, with the extract date. An issue on the repository reaches the maintainers, and the server's own register names the contact route as well.

Practically, for a local-guide agent:

  • Answering a user's question and forgetting it: ordinary use. Show the line.
  • Caching a resolved coordinate for a session: ordinary use. Show the line.
  • Accumulating the results into a place database and publishing a product on top of it: you have a Derivative Database. Show the line, and make the § 4.6 offer to your own recipients.

The other lines you will see on this page

find_place's candidates are not only OSM. The gazetteer is built from GeoNames (CC BY 4.0) and the BKG's GN250 administrative names under dl-de/by-2-0, whose source note is printed on the product's own terms, character for character. Station names come from Deutsche Bahn's station directory under CC BY 4.0. Which means a single find_place answer can carry several lines at once — show all of them, in the form the server sent them.

Several lines end in bearbeitet ("edited") because an answer is always a derived form and CC BY 4.0 § 3(a)(1)(B) makes you say that you changed the material. That is part of the string, not an afterthought.

And the general rule, which applies here as everywhere: when a result carries _meta.purposeNote, reproduce that sentence verbatim. It is a legal condition of the data, not a caption.

Full register: SOURCES.md, or the live resource viafrei://attribution, which is authoritative if the two ever disagree.

Next

Clone this wiki locally