Разработка AI-системы AI-консьержа для отелей

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1 услугВсе 1566 услуг
Разработка AI-системы AI-консьержа для отелей
Средняя
~1-2 недели
Часто задаваемые вопросы
Направления AI-разработки
Этапы разработки AI-решения
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1218
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    853
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1047
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    825

AI-консьерж для отеля: система ассистента гостей

Гость пишет в WhatsApp в 23:40: «Где ближайший ресторан с видом на море, открытый сейчас?» Ресепшн занят поздними заездами. Стандартный чат-бот с кнопками вернёт меню навигации. AI-консьерж ответит конкретно, с учётом местоположения отеля, текущего времени и предпочтений гостя из профиля.

Архитектура системы

AI-консьерж объединяет несколько источников данных: база знаний отеля (услуги, правила, инфраструктура), внешние API (погода, рестораны, достопримечательности), PMS-система (данные о брони и госте), история диалогов.

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})

Практический кейс: boutique-отель, 80 номеров

Проблема: ресепшн не справлялся с потоком однотипных вопросов в мессенджерах (WhatsApp + Telegram). 60–70% обращений — «во сколько завтрак», «есть ли парковка», «как добраться до центра».

Реализация:

  • База знаний отеля: 180 Q&A, описание услуг, меню ресторана — проиндексировано в Chroma
  • Интеграция с Google Places API для локальных рекомендаций
  • Webhook в PMS (Opera) для создания заявок на услуги
  • WhatsApp Business API через 360dialog
  • Fallback на живого консьержа при requires_human: true

Результаты за 3 месяца:

  • Автоматически закрыто 74% обращений без участия персонала
  • Среднее время ответа: 45 сек (был 8–12 мин в непиковое время)
  • Ночные смены: экономия 2 ч работы ресепшн ежедневно
  • Оценка гостей качества ответов ассистента: 4.6/5.0
  • NPS гостей, взаимодействовавших с ботом, выше на 12 пунктов vs не взаимодействовавших

Сроки

  • Базовый ассистент с FAQ + Chroma: 1 неделя
  • WhatsApp/Telegram интеграция: 3–5 дней
  • Google Places + погода: 2–3 дня
  • PMS-интеграция (Opera, Fidelio, Apaleo): 1–2 недели
  • Полная система с аналитикой: 4–6 недель