AI Concierge for Hotels: Smart Guest Assistant System
A guest messages via WhatsApp at 23:40: "Where's the nearest restaurant with a sea view that's open now?" The front desk is busy with late check-ins. A standard button-based chatbot returns a navigation menu. We offer a smart assistant that answers specifically, factoring in the hotel's location, current time, and guest preferences from their profile. Our solution is already deployed in 20+ hotels, handling over 50,000 interactions per month. It reduces front desk load by up to 74% and boosts guest NPS by 12 points. Typical monthly savings on staff time: $3,000.
According to the Hospitality Tech Report, implementing an AI concierge reduces front desk load by 70–80%.
How the Smart Assistant Works
The system combines multiple data sources: the hotel's knowledge base (services, rules, infrastructure), external APIs (weather, restaurants, attractions via Google Places), the PMS system (booking and guest data), and dialogue history. Each query is enriched with context — room number, check-in/check-out dates, guest language, preferences. It uses a RAG pipeline: retrieving relevant fragments from Chroma (collection with embeddings from text-embedding-3-small) and generating the answer via Claude or GPT. For critical requests (complaints, health issues), a fallback to a live concierge is configured via a webhook in the PMS.
Retrieval-Augmented Generation allows dynamic connection of external data without retraining the model.
Why RAG Instead of Fine-Tuning?
Fine-tuning a model on hotel Q&As gives fixed answers but does not allow dynamic connection of external data (weather, reviews, current schedule). RAG with Chroma and 1536-dim embeddings handles 180+ questions in under 200 ms, supports content updates without retraining. For a boutique hotel (80 rooms), inference costs are $0.002 per query, 10x cheaper than a fine-tuned GPT-3.5.
System Architecture
The code below implements the assistant's core with tools for information retrieval, weather lookup, and service request creation. It uses an LLM with tool use — an agentic loop that decides which tools to call.
from anthropic import Anthropic
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
import httpx
from datetime import datetime
import json
client = Anthropic()
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
class HotelConciergeAssistant:
def __init__(self, hotel_id: str, hotel_config: dict):
self.hotel_id = hotel_id
self.config = hotel_config # coordinates, language, hotel category
self.vectorstore = Chroma(
collection_name=f"hotel_{hotel_id}_kb",
embedding_function=embeddings,
)
self.conversations: dict[str, list] = {}
self.tools = self._define_tools()
def _define_tools(self) -> list[dict]:
return [
{
"name": "search_hotel_knowledge",
"description": "Search for information about services, amenities, rules, events of the hotel",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"},
"category": {
"type": "string",
"enum": ["services", "dining", "spa", "transport", "policies", "events", "local"],
}
},
"required": ["query"],
},
},
{
"name": "get_local_recommendations",
"description": "Recommendations for restaurants, attractions, shops nearby",
"input_schema": {
"type": "object",
"properties": {
"category": {
"type": "string",
"enum": ["restaurant", "cafe", "bar", "attraction", "museum", "shopping", "beach"],
},
"open_now": {"type": "boolean"},
"radius_meters": {"type": "integer", "default": 1000},
},
"required": ["category"],
},
},
{
"name": "get_weather",
"description": "Current weather and forecast",
"input_schema": {
"type": "object",
"properties": {
"days_ahead": {"type": "integer", "default": 0},
},
},
},
{
"name": "create_service_request",
"description": "Creates a service request: housekeeping, room service, taxi, wake-up call",
"input_schema": {
"type": "object",
"properties": {
"service_type": {
"type": "string",
"enum": ["housekeeping", "room_service", "taxi", "wake_up_call", "luggage", "maintenance"],
},
"details": {"type": "string"},
"time": {"type": "string", "description": "ISO datetime or 'now'"},
},
"required": ["service_type"],
},
},
]
async def _execute_tool(self, tool_name: str, tool_input: dict, guest_id: str) -> str:
if tool_name == "search_hotel_knowledge":
results = self.vectorstore.similarity_search(
tool_input["query"], k=4,
filter={"category": tool_input["category"]} if tool_input.get("category") else None
)
return "\n\n".join([doc.page_content for doc in results]) or "Information not found"
elif tool_name == "get_local_recommendations":
# Integration with Google Places API
async with httpx.AsyncClient() as http:
resp = await http.get(
"https://maps.googleapis.com/maps/api/place/nearbysearch/json",
params={
"location": f"{self.config['lat']},{self.config['lng']}",
"radius": tool_input.get("radius_meters", 1000),
"type": tool_input["category"],
"opennow": tool_input.get("open_now", False),
"language": "ru",
"key": self.config["google_places_key"],
}
)
places = resp.json().get("results", [])[:5]
return json.dumps([{
"name": p["name"],
"rating": p.get("rating"),
"address": p.get("vicinity"),
"open_now": p.get("opening_hours", {}).get("open_now"),
"price_level": p.get("price_level"),
} for p in places], ensure_ascii=False)
elif tool_name == "get_weather":
async with httpx.AsyncClient() as http:
resp = await http.get(
"https://api.openweathermap.org/data/2.5/forecast",
params={
"lat": self.config["lat"],
"lon": self.config["lng"],
"appid": self.config["openweather_key"],
"units": "metric",
"lang": "ru",
"cnt": 8 * (tool_input.get("days_ahead", 0) + 1),
}
)
data = resp.json()
forecasts = data["list"][:3]
return json.dumps([{
"time": f["dt_txt"],
"temp": f["main"]["temp"],
"description": f["weather"][0]["description"],
"wind": f["wind"]["speed"],
} for f in forecasts], ensure_ascii=False)
elif tool_name == "create_service_request":
# Send to PMS via webhook
request_id = await self._send_pms_request(guest_id, tool_input)
return f"Request #{request_id} accepted. Estimated time: 15–20 minutes."
return "Tool unavailable"
async def _send_pms_request(self, guest_id: str, service: dict) -> str:
"""Sends request to Property Management System"""
async with httpx.AsyncClient() as http:
resp = await http.post(
f"{self.config['pms_url']}/api/service-requests",
headers={"Authorization": f"Bearer {self.config['pms_token']}"},
json={
"guest_id": guest_id,
"hotel_id": self.hotel_id,
"service_type": service["service_type"],
"details": service.get("details", ""),
"requested_time": service.get("time", "now"),
"source": "ai_concierge",
}
)
return resp.json().get("request_id", "N/A")
async def chat(self, guest_id: str, message: str, guest_profile: dict) -> str:
"""Main conversation with guest"""
history = self.conversations.get(guest_id, [])
# System prompt with guest context
system = f"""You are a virtual concierge for the hotel "{self.config['hotel_name']}" (5 stars, {self.config['city']}).
Guest: {guest_profile.get('name', 'Dear Guest')}
Room: {guest_profile.get('room', '?')}
Check-in: {guest_profile.get('check_in', '?')} — Check-out: {guest_profile.get('check_out', '?')}
Language: {guest_profile.get('language', 'ru')}
Current time: {datetime.now().strftime('%H:%M')}
Be attentive, specific, and proactive. Offer relevant hotel services when appropriate.
If the guest asks to organize something, use the tools to create a request."""
history.append({"role": "user", "content": message})
# Agentic loop with tools
messages = history.copy()
while True:
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
system=system,
tools=self.tools,
messages=messages,
)
if response.stop_reason == "end_turn":
assistant_text = response.content[0].text
history.append({"role": "assistant", "content": assistant_text})
self.conversations[guest_id] = history[-20:] # store last 20 messages
return assistant_text
# Process tool calls
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = await self._execute_tool(block.name, block.input, guest_id)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
})
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
How to Implement a Guest Assistant: 5 Steps
- Infrastructure audit: assess current PMS, communication channels, front desk load.
- Knowledge base collection: prepare 100–200 Q&As, service descriptions, rules, menus.
- RAG pipeline setup: index in Chroma, create embeddings.
- Channel integration: connect WhatsApp, Telegram, web chat via API.
- Testing and launch: A/B testing with a small group of guests, then full rollout.
Technical details for DevOps
Deployment: Kubernetes (EKS/GKE) with Triton Inference Server for LLM, vLLM for inference optimization. Monitoring: Weights & Biases for accuracy, p99 latency, tokens. CI/CD via GitLab CI with automatic knowledge base updates.Practical Case: Our Client — Boutique Hotel, 80 Rooms
Problem: The reception couldn't handle the volume of repetitive questions in messengers (WhatsApp + Telegram). 60–70% of inquiries were "What time is breakfast?", "Is there parking?", "How to get to the center?" Staff spent up to 12 minutes per response during non-peak times.
Implementation:
- Hotel knowledge base: 180 Q&As, service descriptions, restaurant menu — indexed in Chroma with 1536-dim embeddings.
- Integration with Google Places API for local recommendations.
- Webhook to PMS (Opera) for creating service requests.
- WhatsApp Business API via 360dialog.
- Fallback to live concierge when
requires_human: true.
Results after 3 months:
| Metric | Before | After |
|---|---|---|
| Automated inquiries share | 0% | 74% |
| Average response time | 8–12 min | 45 sec |
| Night shift load | 2 hrs/night | 0 hrs |
| Answer quality rating (by guests) | — | 4.6/5.0 |
| Monthly labor savings | $0 | ~$3,000 |
| NPS of interacted with AI | base +12 points vs non-interacted |
The system pays for itself within 3 months.
What's Included: Deliverables
- Full architecture and API documentation (OpenAPI specs).
- Source code of modules (LLM, RAG, integrations).
- Deployment on bare-metal or Kubernetes (Triton Inference Server, vLLM).
- Monitoring of p99 latency, accuracy, safety via Weights & Biases.
- Staff training (admins, operators).
- 30-day technical support after launch.
Common Implementation Mistakes
- Hallucinations on rare questions: solved by adding a confidence threshold < 0.7 -> fallback.
- Prompt injection: a guest might try to modify the system prompt. Protection — strict instruction isolation and input filter validation.
- LLM overloading: without rate limiting and caching of frequent queries ("breakfast"), inference costs rise. Our solution: cache with a 5-minute TTL on identical queries.
Guest Assistant vs Standard Chatbot
| Parameter | Button-based chatbot | Virtual concierge (RAG + LLM) |
|---|---|---|
| Natural language understanding | Limited (intent) | Full (NLU) |
| Personalization | No | Yes (profile, history) |
| Integration with external data | No | Weather, Places, PMS |
| Service request creation | UI only | Voice/text |
| Cost per 1K requests | $0.02 (server) | ~$0.20 (LLM) |
| Setup time per hotel | 1–2 days | 1–2 weeks |
Estimated Timelines
- Basic assistant with FAQ + Chroma: from 1 week.
- WhatsApp/Telegram integration: from 3 to 5 days.
- Google Places + weather: from 2 to 3 days.
- PMS integration (Opera, Fidelio, Apaleo): from 1 to 2 weeks.
- Full system with analytics: from 4 to 6 weeks.
Pricing is calculated individually based on the hotel configuration. Typical implementation cost ranges from $5,000 to $20,000 depending on integration complexity, with monthly subscription from $500. We will assess your project in 2 business days — contact us for an audit of your current IT infrastructure. Our team has over 5 years of experience in hospitality automation and has completed 50+ AI projects. We guarantee NDA for all guest data.







