A dual-pipeline conversational system over Inside Airbnb data for Rome. It combines Retrieval-Augmented Generation (RAG) on guest reviews with Text-to-SQL analytics on listings, exposed through a React web app backed by a FastAPI service. Storage, vector search, and analytics run on Databricks; only a local cross-encoder reranker runs on the machine hosting the API.
A single chat interface answers three kinds of questions, routed automatically by an LLM intent classifier:
- Analytics (
Text-to-SQL): "What is the average price per room type?", "How many listings are in the Eur district?". The question is translated to Spark SQL, executed on a Databricks SQL Warehouse over thelistings_enrichedview, and the result is summarised in natural language. - RAG (
reviews): "Do guests complain about noise?", "What do reviews say about cleanliness?". The query hits a Databricks Vector Search index of review chunks, results are re-ranked locally with a cross-encoder, and an LLM produces a grounded answer that cites concrete guest feedback. - Conversational: greetings and meta questions.
The map on the left shows every listing that has reviews in the vector index, grouped by the 15 official municipi. Users can click a listing to scope the chat to that specific property, select a district from the dropdown to scope by neighbourhood, or ask a listing by name ("What do guests say about Santini Home?"). When a question is too broad for the whole city, the assistant asks the user to pick a district first.
| Layer | Technology |
|---|---|
| Batch ETL | PySpark notebooks on Databricks (serverless) |
| Storage | Unity Catalog Delta tables; UC Volume for raw files |
| Retrieval | Databricks Vector Search (Delta Sync, server-side embeddings) |
| Analytics | Databricks SQL Warehouse (Statement Execution API) |
| Reranker | cross-encoder/mmarco-mMiniLMv2-L12-H384-v1 (local, MPS/CPU) |
| LLM + embeddings | Databricks serving endpoints (databricks-gpt-oss-20b, databricks-qwen3-embedding-0-6b) |
| Backend | FastAPI (REST + SSE streaming); serves the frontend build |
| Frontend | React + Vite + Leaflet (map) + Recharts (metrics) |
| Config | .env via pydantic-settings |
Ingestion & preparation (databricks/ notebooks, run on Databricks):
01_ingest_clean— reads the raw CSVs from a UC Volume, casts columns by name (robust to schema drift), cleans prices (drops out-of-range outliers), and samples reviews with a stratified strategy per district (top K listings per municipio, N reviews each) so the map is balanced across neighbourhoods instead of dominated by the city centre. Writesbronze_*andsilver_*Delta tables.02_chunk— turns each review into one chunk with listing metadata; writesgold_review_chunkswith Change Data Feed enabled (required by Vector Search Delta Sync).03_analytics— builds the denormalisedlistings_enrichedview used by Text-to-SQL.04_vector_index— creates the Vector Search endpoint and a Delta Sync index; Databricks computes the embeddings server-side fromchunk_text.
Serving (api/ + core/, run locally or anywhere with network access to Databricks):
intent_classifierroutes the query.analytics_coregenerates SELECT-only SQL (validated against an allowlist) and runs it on the SQL Warehouse.rag_corequeries Vector Search, re-ranks with the local cross-encoder, and builds a grounded prompt.llm_clientstreams the answer token by token (Server-Sent Events), skipping the reasoning tokens of the model.- Responses are cached per
(query, scope); observability counters (intents, cache hit/miss, per-phase latency) are persisted to disk and shown on the metrics page.
databricks/ Databricks notebooks (.ipynb): 01 ingest+clean, 02 chunk, 03 analytics, 04 vector index
core/ in-process libraries: config, llm_client (streaming), intent_classifier,
reranker, vectorstore (Vector Search client), analytics_core (SQL), rag_core, observability
api/ FastAPI backend: main.py (routes + SSE + SPA hosting), services.py, assets/ (geojson)
web/ React + Vite frontend (Home map+chat, Observability dashboard)
scripts/ check_databricks.py — workspace connectivity check
evaluation/ retrieval (Recall/MRR/nDCG), generation (LLM-as-judge), latency benchmarks
docs/ screenshots
Requirements: Python 3.11+, Node 18+, Java 11+ (only for running the notebooks locally; on Databricks it is provided), a Databricks workspace with model-serving credits.
# backend
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # fill in DATABRICKS_HOST, DATABRICKS_TOKEN, SQL_WAREHOUSE_ID
# frontend
cd web && npm install && npm run build && cd ..uvicorn api.main:app --port 8000 # open http://localhost:8000For frontend development with hot reload, run the backend and cd web && npm run dev (Vite
proxies /api to port 8000).
evaluation/query_set.json contains 18 labelled queries built with a known-item
methodology: each query paraphrases a real review (including distinctive details) and is
labelled with the listing it comes from. Latest results on the synced index:
| Metric | @3 | @5 | @10 |
|---|---|---|---|
| Recall | 0.889 | 1.000 | 1.000 |
| nDCG | 0.862 | 0.929 | 0.983 |
MRR = 0.794 · LLM-as-judge (1–5): faithfulness = 4.83, relevance = 4.89


