קונסיירז' AI לבתי מלון: מערכת עוזר אורחים חכמה

אורחים מרבים לשאול שאלות מחוץ לשעות העבודה, ודלפק הקבלה לא תמיד יכול להגיב מיידית. אנחנו מפתחים קונסיירז' AI שמבין את ההקשר של הבקשה ומספק תשובות מדויקות, תוך התחשבות במיקום המלון, בשעה ובהעדפות האורח. הצוות שלנו מוסר את הפרויקט במפתח מלא—מבדיקת היתכנות ועד הטמעה ותמיכה שוטפת, תוך הבטחת פעילות יציבה של המערכת.

תחומי פיתוח AI

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1365
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • עיצוב לוגו לחברת B2B Advance
    עיצוב לוגו לחברת B2B Advance
    776
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח לוגו חברת AIDER
    פיתוח לוגו חברת AIDER
    1022
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113

AI קונסיירז' לבתי מלון: מערכת עוזר אורח חכם

אורח שולח הודעה בוואטסאפ בשעה 23:40: "איפה המסעדה הקרובה עם נוף לים שפתוחה עכשיו?" דלפק הקבלה עסוק בצ'ק-אין מאוחר. צ'אטבוט סטנדרטי מבוסס כפתורים מחזיר תפריט ניווט. אנו מציעים עוזר חכם שעונה באופן ספציפי, תוך התחשבות במיקום המלון, השעה הנוכחית והעדפות האורח מהפרופיל שלו. הפתרון שלנו כבר פרוס ביותר מ-20 מלונות, ומטפל ביותר מ-50,000 אינטראקציות בחודש. הוא מפחית את העומס על דלפק הקבלה בעד 74% ומעלה את ה-NPS של האורחים ב-12 נקודות. חיסכון חודשי טיפוסי בזמן צוות: $3,000.

לפי דוח Hospitality Tech, הטמעת קונסיירז' AI מפחיתה את העומס על דלפק הקבלה ב-70–80%.

כיצד פועל העוזר החכם

המערכת משלבת מספר מקורות נתונים: מאגר הידע של המלון (שירותים, כללים, תשתיות), ממשקי API חיצוניים (מזג אוויר, מסעדות, אטרקציות דרך Google Places), מערכת PMS (נתוני הזמנות ואורחים) והיסטוריית שיחות. כל שאילתה מועשרת בהקשר — מספר חדר, תאריכי צ'ק-אין/צ'ק-אאוט, שפת האורח, העדפות. היא משתמשת בצינור RAG: שליפת קטעים רלוונטיים מ-Chroma (אוסף עם embeddings מ-text-embedding-3-small) ויצירת התשובה דרך Claude או GPT. עבור בקשות קריטיות (תלונות, בעיות בריאותיות), מוגדר fallback לקונסיירז' אנושי דרך webhook ב-PMS.

Retrieval-Augmented Generation מאפשר חיבור דינמי של נתונים חיצוניים ללא אימון מחדש של המודל.

למה RAG במקום Fine-Tuning?

Fine-tuning של מודל על שאלות ותשובות של מלון נותן תשובות קבועות אך אינו מאפשר חיבור דינמי של נתונים חיצוניים (מזג אוויר, ביקורות, לוח זמנים עדכני). RAG עם Chroma ו-embeddings של 1536 ממדים מטפל ביותר מ-180 שאלות בפחות מ-200 אלפיות שנייה, ותומך בעדכוני תוכן ללא אימון מחדש. עבור מלון בוטיק (80 חדרים), עלויות ההסקה הן $0.002 לשאילתה, זול פי 10 מ-GPT-3.5 עם fine-tuning.

ארכיטקטורת המערכת

הקוד שלהלן מממש את ליבת העוזר עם כלים לשליפת מידע, בדיקת מזג אוויר ויצירת בקשות שירות. הוא משתמש ב-LLM עם שימוש בכלים — לולאה אגנטית שמחליטה אילו כלים להפעיל.

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  # координаты, язык, категория отеля
        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": "Поиск информации о services, amenities, rules, events отеля",
                "input_schema": {
                    "type": "object",
                    "properties": {
                        "query": {"type": "string", "description": "Поисковый запрос"},
                        "category": {
                            "type": "string",
                            "enum": ["services", "dining", "spa", "transport", "policies", "events", "local"],
                        }
                    },
                    "required": ["query"],
                },
            },
            {
                "name": "get_local_recommendations",
                "description": "Рекомендации ресторанов, достопримечательностей, магазинов в окрестностях",
                "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": "Текущая погода и прогноз",
                "input_schema": {
                    "type": "object",
                    "properties": {
                        "days_ahead": {"type": "integer", "default": 0},
                    },
                },
            },
            {
                "name": "create_service_request",
                "description": "Создаёт заявку на услугу: уборка, доставка еды, трансфер, будильник",
                "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 или '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 "Информация не найдена"
        elif tool_name == "get_local_recommendations":
            # Интеграция с 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":
            # Отправляем в PMS через webhook
            request_id = await self._send_pms_request(guest_id, tool_input)
            return f"Заявка #{request_id} принята. Ожидаемое время: 15–20 минут."
        return "Инструмент недоступен"

    async def _send_pms_request(self, guest_id: str, service: dict) -> str:
        """Отправляет заявку в 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:
        """Основной диалог с гостем"""
        history = self.conversations.get(guest_id, [])
        # Системный промпт с контекстом гостя
        system = f"""Ты — виртуальный консьерж отеля «{self.config['hotel_name']}» (5 звёзд, {self.config['city']}).
Гость: {guest_profile.get('name', 'Уважаемый гость')}
Номер: {guest_profile.get('room', '?')}
Заезд: {guest_profile.get('check_in', '?')} — Выезд: {guest_profile.get('check_out', '?')}
Язык: {guest_profile.get('language', 'ru')}
Текущее время: {datetime.now().strftime('%H:%M')}
Будь внимателен, конкретен и проактивен. Предлагай релевантные услуги отеля когда уместно. Если гость просит что-то организовать — используй инструменты для создания заявки."""
        history.append({"role": "user", "content": message})
        # Agentic loop с инструментами
        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:]  # храним 20 сообщений
                return assistant_text
            # Обрабатываем вызовы инструментов
            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})

כיצד ליישם עוזר אורח: 5 שלבים

  1. בדיקת תשתיות: הערכת PMS נוכחי, ערוצי תקשורת, עומס על דלפק הקבלה.
  2. איסוף מאגר ידע: הכנת 100–200 שאלות ותשובות, תיאורי שירותים, כללים, תפריטים.
  3. הקמת צינור RAG: אינדוקס ב-Chroma, יצירת embeddings.
  4. שילוב ערוצים: חיבור WhatsApp, Telegram, צ'אט אינטרנטי דרך API.
  5. בדיקות והשקה: בדיקות A/B עם קבוצה קטנה של אורחים, ולאחר מכן פריסה מלאה.
פרטים טכניים ל-DevOps פריסה: Kubernetes (EKS/GKE) עם Triton Inference Server ל-LLM, vLLM לאופטימיזציית הסקה. ניטור: Weights & Biases לדיוק, זמן השהיה p99, אסימונים. CI/CD דרך GitLab CI עם עדכונים אוטומטיים למאגר הידע.

מקרה פרקטי: הלקוח שלנו — מלון בוטיק, 80 חדרים

בעיה: דלפק הקבלה לא יכול היה להתמודד עם נפח השאלות החוזרות בהודעות (WhatsApp + Telegram). 60–70% מהפניות היו "באיזו שעה ארוחת בוקר?", "יש חניה?", "איך מגיעים למרכז?" הצוות בילה עד 12 דקות לכל תשובה בשעות לא עמוסות.

יישום:

  • מאגר ידע של המלון: 180 שאלות ותשובות, תיאורי שירותים, תפריט מסעדה — מאונדקס ב-Chroma עם embeddings של 1536 ממדים.
  • שילוב עם Google Places API להמלצות מקומיות.
  • Webhook ל-PMS (Opera) ליצירת בקשות שירות.
  • WhatsApp Business API דרך 360dialog.
  • Fallback לקונסיירז' אנושי כאשר 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 # координаты, язык, категория отеля 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": "Поиск информации о services, amenities, rules, events отеля", "input_schema": { "type": "object", "properties": { "query": {"type": "string", "description": "Поисковый запрос"}, "category": { "type": "string", "enum": ["services", "dining", "spa", "transport", "policies", "events", "local"], } }, "required": ["query"], }, }, { "name": "get_local_recommendations", "description": "Рекомендации ресторанов, достопримечательностей, магазинов в окрестностях", "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": "Текущая погода и прогноз", "input_schema": { "type": "object", "properties": { "days_ahead": {"type": "integer", "default": 0}, }, }, }, { "name": "create_service_request", "description": "Создаёт заявку на услугу: уборка, доставка еды, трансфер, будильник", "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 или '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 "Информация не найдена" elif tool_name == "get_local_recommendations": # Интеграция с 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": # Отправляем в PMS через webhook request_id = await self._send_pms_request(guest_id, tool_input) return f"Заявка #{request_id} принята. Ожидаемое время: 15–20 минут." return "Инструмент недоступен" async def _send_pms_request(self, guest_id: str, service: dict) -> str: """Отправляет заявку в 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: """Основной диалог с гостем""" history = self.conversations.get(guest_id, []) # Системный промпт с контекстом гостя system = f"""Ты — виртуальный консьерж отеля «{self.config['hotel_name']}» (5 звёзд, {self.config['city']}). Гость: {guest_profile.get('name', 'Уважаемый гость')} Номер: {guest_profile.get('room', '?')} Заезд: {guest_profile.get('check_in', '?')} — Выезд: {guest_profile.get('check_out', '?')} Язык: {guest_profile.get('language', 'ru')} Текущее время: {datetime.now().strftime('%H:%M')} Будь внимателен, конкретен и проактивен. Предлагай релевантные услуги отеля когда уместно. Если гость просит что-то организовать — используй инструменты для создания заявки.""" history.append({"role": "user", "content": message}) # Agentic loop с инструментами 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:] # храним 20 сообщений return assistant_text # Обрабатываем вызовы инструментов 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}) .

תוצאות לאחר 3 חודשים:

מדד לפני אחרי
חלק הפניות האוטומטיות 0% 74%
זמן תגובה ממוצע 8–12 דקות 45 שניות
עומס משמרת לילה שעתיים/לילה 0 שעות
דירוג איכות תשובות (על ידי אורחים) — 4.6/5.0
חיסכון חודשי בעבודה $0 ~$3,000
NPS של אלה שדיברו עם AI בסיס +12 נקודות לעומת אלה שלא

המערכת מחזירה את ההשקעה תוך 3 חודשים.

מה כלול: תוצרים

  • תיעוד מלא של ארכיטקטורה ו-API (מפרטי OpenAPI).
  • קוד מקור של המודולים (LLM, RAG, אינטגרציות).
  • פריסה על bare-metal או Kubernetes (Triton Inference Server, vLLM).
  • ניטור זמן השהיה p99, דיוק, בטיחות דרך Weights & Biases.
  • הכשרת צוות (מנהלים, מפעילים).
  • תמיכה טכנית ל-30 יום לאחר ההשקה.

טעויות נפוצות ביישום

  • הזיות על שאלות נדירות: נפתר על ידי הוספת סף ביטחון < 0.7 -> fallback.
  • Prompt injection: אורח עלול לנסות לשנות את ה-system prompt. הגנה — בידוד קפדני של הוראות וסינון אימות קלט.
  • עומס יתר על LLM: ללא הגבלת קצב וקאשינג של שאילתות תכופות ("ארוחת בוקר"), עלויות ההסקה עולות. הפתרון שלנו: קאש עם TTL של 5 דקות לשאילתות זהות.

עוזר אורח לעומת צ'אטבוט סטנדרטי

פרמטר צ'אטבוט מבוסס כפתורים קונסיירז' וירטואלי (RAG + LLM)
הבנת שפה טבעית מוגבלת (כוונה) מלאה (NLU)
התאמה אישית לא כן (פרופיל, היסטוריה)
שילוב עם נתונים חיצוניים לא מזג אוויר, Places, PMS
יצירת בקשות שירות רק דרך ממשק קול/טקסט
עלות ל-1,000 בקשות $0.02 (שרת) ~$0.20 (LLM)
זמן הקמה למלון 1–2 ימים 1–2 שבועות

לוחות זמנים משוערים

  • עוזר בסיסי עם FAQ + Chroma: משבוע אחד.
  • שילוב WhatsApp/Telegram: מ-3 עד 5 ימים.
  • Google Places + מזג אוויר: מ-2 עד 3 ימים.
  • שילוב PMS (Opera, Fidelio, Apaleo): משבוע עד שבועיים.
  • מערכת מלאה עם אנליטיקה: מ-4 עד 6 שבועות.

התמחור מחושב באופן פרטני לפי תצורת המלון. עלות יישום טיפוסית נעה בין $5,000 ל-$20,000 בהתאם למורכבות האינטגרציה, עם מנוי חודשי מ-$500. נבחן את הפרויקט שלך תוך 2 ימי עסקים — צור קשר לבדיקת התשתית ה-IT הנוכחית שלך. לצוות שלנו ניסיון של למעלה מ-5 שנים באוטומציה של תחום האירוח והוא השלים יותר מ-50 פרויקטי AI. אנו מבטיחים NDA לכל נתוני האורחים.