WayFy’s AI Assistant translates natural-language queries into structured map searches without requiring users to know filter slugs or category names. Type (or say) something like “accessible restaurants near the Sagrada Família” and the assistant extracts the location, identifies the relevant OSM category (Documentation Index
Fetch the complete documentation index at: https://mintlify.com/jhonyes04/new-wayfy/llms.txt
Use this file to discover all available pages before exploring further.
gastronomia), sets the wheelchair filters to ['yes', 'limited'], geocodes the POI, and flies the map to Barcelona — all in a single interaction. The assistant is powered by the Groq API running llama-3.3-70b-versatile and is accessible via the floating input bar at the bottom of the Accessibility Map.
Architecture Overview
API Endpoints
- POST /api/ai/mapgpt
- GET /api/ai/geocode
- GET /api/ai/geocode-poi
Parses a natural-language query and returns a structured JSON object.Rate limit: 20 requests / minuteRequestResponseError responses
| Status | Body | Cause |
|---|---|---|
400 | {"msg": "Prompt vacío"} | Empty or whitespace-only prompt |
429 | Rate limit header | More than 20 requests/minute |
500 | {"msg": "Falta GROQ_API_KEY en el servidor"} | GROQ_API_KEY env var not set |
502 | {"msg": "La IA devolvio una respuesta invalida"} | Groq returned non-JSON content |
Groq Model Configuration
The controller (ai_controller.py) initialises the Groq client and calls chat.completions.create with the following parameters:
| Parameter | Value |
|---|---|
model | llama-3.3-70b-versatile |
response_format | { "type": "json_object" } |
temperature | 0.2 (near-deterministic for consistent structured output) |
max_tokens | 300 |
System Prompt Logic
TheMAPGPT_SYSTEM_PROMPT in backend/src/api/prompts/mapgpt_prompt.py instructs the model to always return a JSON object with exactly six keys. Here is the contract:
Output Schema
Named place with a proper noun — a landmark, station, neighbourhood, or specific venue. Empty string if absent.
A street address, partial or complete. Empty string if absent.
A city, district, region, or country. Empty string if absent.
One or more of the valid category slugs. If the user doesn’t specify a category, all 11 default categories are returned.Valid values:
alojamiento, gastronomia, transporte, salud, cultura_turismo, recreacion, deporte, gobierno, baños, dinero, tiendas, otrosWheelchair accessibility filter values. If the user doesn’t mention accessibility, defaults to
["yes", "limited"].Valid values: yes, limited, noA short (max 12 words) human-readable description of the search. Displayed in the map’s AI query info banner.
Location Priority
When the user mentions multiple locations, the model picks the most specific one: POI > ADDRESS > PLACE. A street address like “Calle Alcalá 43 Madrid” populatesaddress, not place. A named landmark like “Sol” populates poi.
Filter Rules
| User says | filters output |
|---|---|
| Nothing about accessibility | ["yes", "limited"] |
| ”accesible” (general) | ["yes", "limited"] |
| ”totalmente accesible” | ["yes"] |
| ”accesibilidad parcial” | ["limited"] |
| ”no accesibles” | ["no"] |
Example Queries & Responses
"restaurantes en madrid"
"restaurantes en madrid"
Input: The geocoder resolves
restaurantes en madridResponse:madrid via GET /api/ai/geocode?q=madrid and the map flies to the centre of Madrid with only the gastronomia category active."hoteles accesibles cerca de atocha"
"hoteles accesibles cerca de atocha"
Input:
hoteles accesibles cerca de atochaResponse:fetchMapData appends the place context to the POI: "atocha" → GET /api/ai/geocode-poi?q=atocha → geocoded to Madrid Atocha station."farmacias en calle alcala 43 madrid"
"farmacias en calle alcala 43 madrid"
Input: The frontend geocodes the address via
farmacias en calle alcala 43 madridResponse:GET /api/ai/geocode?q=calle+alcala+43+madrid."bares no accesibles en gran via"
"bares no accesibles en gran via"
Input: Only the
bares no accesibles en gran viaResponse:no filter is active; the map shows venues explicitly marked as non-accessible."restaurantes y hoteles en malasaña"
"restaurantes y hoteles en malasaña"
Input: Multiple categories can be returned when the user explicitly names more than one.
restaurantes y hoteles en malasañaResponse:Frontend Integration
AIAssistant Component
TheAIAssistant component renders a floating form fixed to the bottom centre of the Accessibility Map. It includes:
- A text input (
placeholder="Pregunta a Wayfy...") bound to localquerystate - Submit on Enter (without Shift) or the form submit button
- A microphone icon (
fa-microphone-lines) that toggles the Web Speech API - A full-screen loading overlay while
isProcessingis true
focus-ai-input DOM event so other parts of the UI can programmatically focus the input.
useAIAssistant Hook
processQuery uses an AbortController so rapidly-submitted queries cancel the previous in-flight request. Voice recognition is configured for es-ES (Spanish) and automatically calls processQuery with the transcript on recognition.onresult.
Geocoding Resolution Order
fetchMapData in mapgpt.api.js tries to resolve a geographic coordinate in this priority order:
geocode-poi(poi + " " + place)— POI name with place contextgeocode-poi(poi)— POI name alonegeocode(address + " " + place)— address with place contextgeocode(address)— address alonegeocode(place)— place/city name
feature is null and the map position is not changed (only filters and categories update).
Rate Limits
| Endpoint | Limit |
|---|---|
POST /api/ai/mapgpt | 20 requests / minute per IP |
GET /api/ai/geocode | 30 requests / minute per IP |
GET /api/ai/geocode-poi | 30 requests / minute per IP |
api.utils.limiter). Exceeding a limit returns HTTP 429.
The Nominatim geocoder (used in both
/geocode and /geocode-poi fallback) requires a descriptive User-Agent header per the OpenStreetMap usage policy. WayFy sends WayfyAI/1.0 (contact@example.com) — set the CONTACT_EMAIL environment variable on the backend to customise the address included in this header.