tileserver-rs includes an AI assistant that lets you interact with your maps using natural language. It can run on three backends:
| Provider | Where inference runs | Key | Prompts leave the device? |
|---|---|---|---|
| Local (WebLLM) — default | Your browser, via WebGPU | None | No |
| OpenAI-compatible / Anthropic | Your own provider (bring your own key) | Yours, in your browser | Yes, to your provider |
| Server provider | The provider configured in [llm] | Held by the server | Yes, via this server |
Pick the provider with the settings button (gear icon) in the chat palette header.
How It Works
The AI chat is powered by WebLLM, which runs open-source language models directly in your browser using WebGPU acceleration. The assistant can:
- Navigate the map — fly to cities, countries, or coordinates
- Query features — find what's visible in the viewport or search tile data
- Modify styles — change colors, opacity, visibility of map layers
- Highlight features — temporarily highlight features matching a filter
- Inspect data — get layer schemas, source statistics, and spatial queries
The entire conversation stays in your browser. Chat history is persisted to localStorage via TanStack DB and survives page refreshes.
Requirements
WebGPU is required. Use Chrome 113+, Edge 113+, or any Chromium-based browser with WebGPU support. Firefox and Safari do not fully support WebGPU yet.
| Requirement | Minimum | Recommended |
|---|---|---|
| Browser | Chrome 113+ | Chrome 120+ |
| GPU VRAM | 2 GB | 6 GB |
| Free RAM | 2 GB | 8 GB |
| Disk Space | 1 GB | 5 GB (for larger models) |
Getting Started
- Open any style in the map viewer (e.g.,
http://localhost:8080/styles/protomaps-light/) - Press ⌘K (Mac) or Ctrl+K (Windows/Linux) to open the chat palette
- On first use, the model downloads and compiles for your GPU (~30 seconds)
- Type a message or select a suggested prompt
Subsequent loads are fast (~2–5 seconds) because the compiled model is cached in IndexedDB.
Suggested Prompts
The chat palette shows suggested prompts when the conversation is empty:
- "Fly to Paris, France and show me the Eiffel Tower area"
- "What layers are available on this map?"
- "Take me to Tokyo, Japan at a good zoom level"
- "Show me the entire Mediterranean Sea region"
Available Models
The local provider ships with four Qwen3.5 models (WebLLM prebuilt builds). Choose based on your hardware and needs:
| Model | VRAM | Best For |
|---|---|---|
| Qwen3.5 4B (default) | 3.9 GB | Best balance of quality and speed |
| Qwen3.5 9B | 6.4 GB | Best quality, needs more VRAM |
| Qwen3.5 2B | 2.2 GB | Lightweight, lower VRAM systems |
| Qwen3.5 0.8B | 1.6 GB | Minimal hardware, basic interactions |
Local models use a text-based tool parser — the LLM emits structured [MAP_ACTION] blocks that are parsed and executed client-side. Remote providers use native tool calling and have the full tool set.
You can switch models at any time using the model selector dropdown in the chat palette.
Remote Providers (Bring Your Own Key)
Use a provider you already pay for — Anthropic Claude, OpenAI, OpenRouter, Groq, or a self-hosted server (Ollama, LM Studio, vLLM). Remote mode needs no WebGPU and no model download.
- Open the chat palette and click the settings (gear) button.
- Choose OpenAI-compatible or Anthropic (Claude).
- Enter the base URL, model, and API key, then click Test connection.
| Provider | Base URL | Notes |
|---|---|---|
| Anthropic | https://api.anthropic.com | Sent with anthropic-dangerous-direct-browser-access: true, which Anthropic provides for bring-your-own-key apps |
| OpenAI | https://api.openai.com/v1 | |
| OpenRouter | https://openrouter.ai/api/v1 | Many models behind one key |
| Ollama | http://localhost:11434/v1 | No key. Start Ollama with OLLAMA_ORIGINS set to this site's origin |
| LM Studio | http://localhost:1234/v1 | No key. Enable CORS in the server settings |
The browser calls the provider directly, so the provider must allow browser (CORS) requests. If it does not, use the server proxy instead.
How the key is stored:
- By default the key is kept in
sessionStorageand cleared when you close the tab. - Remember key on this device saves it in
localStorageinstead. Any script running on the page can read it — only enable this on a trusted deployment. - Forget key removes it from both.
- The key is only sent in request headers to the provider you entered. It is never put in a URL, logged, or sent to tileserver-rs.
Server Proxy
Operators can configure one provider on the server. The chat palette then offers a Server provider option; users need no key, and there is no CORS limit.
[llm]
enabled = true
provider = "anthropic" # or "openai-compatible"
base_url = "https://api.anthropic.com"
api_key_env = "TILESERVER_LLM_API_KEY" # the key itself stays in the environment
model = "claude-sonnet-4-5"
allow_user_keys = true # set false to offer only Local + Server provider
TILESERVER_LLM_API_KEY=sk-ant-... tileserver-rs --config config.toml
| Endpoint | Description |
|---|---|
GET /api/llm/config | Whether the proxy is enabled, its provider family, model, and upstream host. Never returns the key. |
POST /api/llm/chat | Forwards an OpenAI Chat Completions or Anthropic Messages body upstream and streams the response back. The server sets model and the auth headers. |
The proxy returns 404 when disabled, 413 for bodies over max_body_bytes, 429 (with Retry-After: 60) over rate_limit_per_minute, and 502 when the upstream cannot be reached. All [llm] keys are listed in the configuration reference.
Map Tools
The AI assistant has access to 13 tools organized into three categories: navigation, styling, and data queries.
Navigation Tools
fly_to
Animate the map camera to a specific location.
"Fly to the Colosseum in Rome"
"Show me downtown Manhattan at zoom 15"
"Go to coordinates 139.7, 35.7 with a 45° bearing"
| Parameter | Type | Required | Description |
|---|---|---|---|
lng | number | ✅ | Longitude (-180 to 180) |
lat | number | ✅ | Latitude (-90 to 90) |
zoom | number | Zoom level 0–22 (default 12) | |
bearing | number | Bearing in degrees (default 0) | |
pitch | number | Pitch 0–85° (default 0) |
fit_bounds
Fit the map camera to a bounding box — useful for countries, regions, or areas.
"Show me all of Japan"
"Zoom to the Mediterranean Sea"
"Fit the map to the continental United States"
| Parameter | Type | Required | Description |
|---|---|---|---|
west | number | ✅ | West longitude |
south | number | ✅ | South latitude |
east | number | ✅ | East longitude |
north | number | ✅ | North latitude |
padding | number | Padding in pixels (default 50) |
get_map_state
Get the current map center, zoom, bearing, pitch, and visible layers. The assistant uses this to understand what you're looking at before making changes.
Styling Tools
set_layer_visibility
Show or hide a map layer by its ID.
"Hide the water layer"
"Show the buildings layer"
"Turn off all label layers"
set_layer_paint
Change a paint property of a map layer — color, opacity, width, and more.
"Make the water dark blue"
"Set the building fill opacity to 0.5"
"Change road line width to 3"
set_layer_filter
Apply a MapLibre filter expression to a layer. Only features matching the filter are shown.
"Only show parks in the landuse layer"
"Filter buildings taller than 50 meters"
add_highlight
Temporarily highlight features matching a filter with a colored circle. Highlights auto-remove after 8 seconds.
"Highlight all hospitals"
"Show me where the schools are in red"
generate_style
Apply multiple style changes at once from a natural language description.
"Make this a dark mode map"
"Give me a satellite-style color scheme"
"Make all text labels larger"
Data Query Tools
These tools query the tile data served by tileserver-rs — either from the rendered viewport or directly from the vector tile sources.
query_rendered_features
Query features currently visible in the map viewport. Returns properties and geometry type.
"What features are visible right now?"
"Show me the properties of buildings in view"
"List all points of interest I can see"
get_source_schema
Get the schema of a tile source — available layers, field names and types, zoom range, and bounds.
"What layers are in the openmaptiles source?"
"Show me the fields available in the buildings layer"
get_source_stats
Get statistics for a tile source — bounds, zoom range, layer count, attribution.
"What's the zoom range of this data source?"
"Show me the attribution for the terrain data"
spatial_query
Query features from a tile source within a bounding box. This queries the actual vector tile data on the server, not just what's rendered in the viewport.
"Find all buildings within 1km of the Eiffel Tower"
"What points of interest are in this area?"
| Parameter | Type | Required | Description |
|---|---|---|---|
source | string | ✅ | Source ID to query |
bbox | number | Bounding box [west, south, east, north] | |
zoom | number | Tile resolution (default 14) | |
layers | string | Layer IDs to query | |
limit | number | Max features to return (default 100) |
get_overlays
List all user-dropped file overlays on the map — file names, formats, feature counts, colors, and visibility. Works with files dragged onto the map viewer (GeoJSON, KML, GPX, CSV, Shapefiles, PMTiles).
Keyboard Shortcuts
| Shortcut | Action |
|---|---|
| ⌘K / Ctrl+K | Toggle chat palette |
| Esc | Close chat palette |
| Enter | Send message |
Architecture
User Input → Chat Palette (Vue)
↓
useLlmPanel (composable) ← useLlmProvider (provider, settings, key)
↓
useLlmChat → useChat({ connection: stream(adapter) })
↓
stream() adapter — converts provider output → AG-UI protocol events
├── Local: WebLLM engine (browser, WebGPU)
├── OpenAI-compatible: POST {base}/chat/completions (SSE)
├── Anthropic: POST /v1/messages (SSE)
└── Server provider: POST /api/llm/chat → configured upstream
↓
AG-UI events stream back to TanStack AI Vue
↓
Tool results auto-executed client-side
↓
Chat Palette renders messages + tool results
Key packages:
@mlc-ai/web-llm— Browser-local LLM inference via WebGPU@tanstack/ai-vue— Vue integration withuseChathookzod— Tool input schema validation
Chat Persistence
Chat messages are automatically saved to your browser's localStorage via TanStack DB. This includes:
- All user and assistant messages
- Tool call records (which tool was called, with what arguments)
- Spatial query results
Messages persist across page refreshes and browser restarts. They are stored locally; only the active remote provider (if any) receives the conversation, as part of each request.
Troubleshooting
"WebGPU is not supported"
Your browser doesn't support WebGPU. Use Chrome 113+ or Edge 113+. On macOS, Safari technology previews have partial WebGPU support but may not work reliably with WebLLM.
Model download is slow
The first download transfers 1.6–6.4 GB depending on the model. After the first download, the compiled model is cached in IndexedDB and loads in 2–5 seconds. Try a smaller model (Qwen3.5 0.8B at 1.6 GB) if bandwidth is limited, or use a remote provider.
Tool calls don't execute
If the assistant describes an action but doesn't execute it, try a larger model. Qwen3.5 4B or Qwen3.5 9B have the best instruction-following for tool execution. Smaller models (0.8B, 2B) may occasionally describe actions in prose instead of emitting the structured [MAP_ACTION] blocks the parser expects.
"Could not reach …" with a remote provider
The provider is unreachable or blocks browser requests (CORS). Check the base URL; for Ollama set OLLAMA_ORIGINS; otherwise ask the operator to enable the server proxy.
"The API key was rejected"
The provider returned 401/403. Re-enter the key, or click Forget key and paste it again.
Chat palette won't open
Make sure you're on a style viewer page (/styles/{style}/). The AI chat is only available in the map viewer, not on the home page or data inspector.
GPU out of memory
Try a smaller model. Qwen3.5 0.8B requires only ~1.6 GB of VRAM. Close other GPU-intensive tabs or applications. On systems with shared GPU memory (integrated graphics), ensure enough system RAM is available.
Privacy & Security
- Local (default): no data leaves your browser — the LLM runs entirely via WebGPU, with no keys and no token costs
- Remote providers send prompts off the device — the palette shows "Prompts are sent to <host>" whenever a remote provider is active, and calls are billed to the provider account in use
- Your own key never reaches tileserver-rs — it goes from the browser straight to the provider, in request headers only
- The server proxy key stays on the server — it is read from an environment variable and never returned by any endpoint
- Chat history is local — stored in localStorage, never uploaded to tileserver-rs
- Server-side tools query your own data — spatial queries go to your tileserver-rs instance, not any external service
Live Demo
Try the AI chat on the live demo at demo.tileserver.app. Open any style, press ⌘K, and start talking to the map.
Next Steps
- Quickstart — Set up tileserver-rs
- Configuration — All configuration options
- Vector Tiles — Serving vector tiles
- File Drop — Drag & drop files onto the map