feat: add display_map tool for point maps on OpenStreetMap#1206
Conversation
🧹 Preview RemovedThe preview deployment for this PR has been cleaned up. |
8cfda0a to
801f2e5
Compare
There was a problem hiding this comment.
All reported issues were addressed
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
801f2e5 to
65a79d5
Compare
|
Hello @tckaros I really really like this idea and this is something we should add in nao either with this PR or in another form so lets move it forward. 2 things to have in mind I think before the review:
Happy to hear your thoughts! |
65a79d5 to
2ed8ab6
Compare
socallmebertille
left a comment
There was a problem hiding this comment.
Hi, thanks a lot for this awesome contribution ! 👍💯
On top of your work, I added for now :
- the WhatsApp integration (just a link that redirects the user to see the map in the chat UI)
- the dark mode theme handling (and align the design to the rest of the app)
2ed8ab6 to
1072219
Compare
There was a problem hiding this comment.
All reported issues were addressed across 36 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Adds a new display_map agent tool that renders geographic point data from execute_sql results on an interactive MapLibre GL map, using OpenFreeMap (OSM-based vector tiles) as the default basemap, overridable via VITE_MAP_STYLE_URL. - Shared zod schema with query_id reference, lat/lng column keys and optional popup label/tooltip keys; map_type enum ready for a future heatmap mode - Point building/validation lives in @nao/shared (map-points.ts): column keys resolve exact-first then case-insensitively (resolveColumnName, with resolveDataKey moved to shared on top of it), rows with null or blank coordinates are rejected before Number() coercion so no fabricated (0,0) points are plotted, latitudes beyond the Web Mercator limit (85.051129°) are dropped instead of collapsing onto the map edge, and keys resolving to the same column are rejected instead of silently plotting lat=lng - The tool resolves the query_id through getQueryResult (current run, then persisted chat history) and fails with an actionable error when the query is missing, a column does not resolve, or no row carries plottable coordinates; on success it reports plotted and dropped row counts so the model can notice partial data and fix its SQL - Frontend component mirrors display_chart: resolves rows from the sibling execute_sql message part; MapLibre loads in a lazy chunk so the main bundle is unaffected; kept out of collapsed tool groups like charts; rendering is capped at 5000 points with a "showing first N" note - fitBounds wraps across the antimeridian (computeMapBounds), so points at 179°E/179°W fit a 2° span instead of a world-scale view - Popups are built with textContent only so query values cannot inject HTML - Slack, Teams and Telegram cannot render an interactive map, so a completed display_map degrades to a "View interactive map in nao" link card; automations, the MCP ask_nao sub-agent and WhatsApp exclude the tool (shared WEB_CHAT_ONLY_TOOLS list, getTools excludeBuiltinTools option) and the system prompt only advertises the tool when the run's tool set includes it Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
56613c7 to
d410b38
Compare
Hi @Bl3f, Thanks for your review! I actually had similar questions on my side :) If I manage to run some tests, I'll share my feedback on which direction (or evolution) seems to make the most sense. |
What
A new
display_mapagent tool that plots geographic point data fromexecute_sqlresults on an interactive MapLibre GL map, using OpenFreeMap (OSM-based vector tiles) as the default basemap.This is the proof of concept behind #1205 — it's fully working, but it's mainly here to make that discussion concrete. If maps are a direction you want, I fully expect remarks on the approach and I'm happy to rework it.
How it works
apps/shared/src/tools/display-map.ts):query_idreference, lat/lng column keys, optional popuplabel_key/tooltip_keys,map_typeenum ready for a future heatmap mode.apps/shared/src/map-points.ts): one implementation used by both backend validation and frontend rendering — case-insensitive column resolution (exact match first), rejection of null/blank coordinates beforeNumber()coercion (no fabricated (0,0) points), Web Mercator latitude limit, antimeridian-aware bounds.query_idviagetQueryResult(current run, then persisted chat history) and returns actionable errors (missing query, unresolvable column, no plottable rows) so the model is never told a map displayed when the UI can't render one. On success it reports plotted/dropped counts and warns about unresolvable popup keys or results over the render cap, so the model can self-correct its SQL.display_chart(same source-query resolution, now factored into a shareduseSourceQueryhook used by both). MapLibre loads as a lazy chunk; popups are built withtextContentonly so query values can't inject HTML; rendering is capped at 5,000 points with a "showing first N" note; WebGL-unavailable clients get a graceful fallback instead of a crash.display_mapdegrades to a "View interactive map in nao" link card. Automations, the MCPask_naosub-agent and WhatsApp exclude the tool entirely (WEB_CHAT_ONLY_TOOLS+ a newgetToolsexcludeBuiltinToolsoption), and the system prompt only advertises the tool when the run's tool set includes it.Tests
apps/shared/tests/map-points.test.ts— coordinate validation, key resolution, wrapped bounds (16 tests)apps/backend/tests/display-map-tool.test.ts— tool validation paths incl. history fallback (13 tests)apps/backend/tests/agent-tools.test.ts+ system-prompt gating testsKnown limitations / follow-ups
tiles.openfreemap.org) and theVITE_MAP_STYLE_URLoverride is build-time only — see [feature] Map visualization for latitude/longitude query results #1205 for the configurability question.generate-chart); WhatsApp link card TODO.🤖 Generated with Claude Code