До 70% туристів відмовляються від бронювання після невдалого пошуку. Ціна помилки висока: людина планує відпустку за десятки тисяч гривень, а контекст змінюється — подорож з дітьми потребує одних критеріїв, романтична подорож — інших. Ми будуємо AI, який розуміє ці нюанси з історії пошуку, бронювань і явних вподобань, формуючи персональний профіль мандрівника. Наш досвід — 10+ років в AI/ML, десятки проєктів у travel-сфері. Гарантуємо прозорість роботи моделі та повну підтримку на всіх етапах. Розробляємо AI-системи рекомендації турів із семантичним пошуком та персоналізованим підбором.
Як AI розуміє вподобання мандрівника?
Ключова проблема — зібрати розрізнені дані в єдину картину. Історія бронювань показує бюджет та тривалість, пошукові запити — інтереси, а кліки — неявні вподобання. Алгоритми машинного навчання, такі як матрична факторизація та нейромережеві ембеддінги, дозволяють витягти приховані патерни. Наприклад, користувач, який часто бронює готелі з високим рейтингом, але шукає недорогі авіаквитки — швидше за все, цінує якість проживання, але економить на перельоті. Ми будуємо профіль з десятків таких ознак. Для вирішення проблеми холодного старту використовуємо коротку анкету — 5 питань про тип поїздки, бюджет та інтереси. На старті також застосовуємо колаборативну фільтрацію на основі схожих користувачів.
Які проблеми вирішує система?
- Холодний старт. Нові користувачі без історії — система використовує анкету та демографічні дані, щоб запропонувати перші тури. Конверсія при такому підході на 40% вища, ніж при випадковому показі.
- Розрідженість даних. Більшість користувачів бронюють 1–2 рази на рік, що дає мало сигналів. Нейромережевий колаборативний фільтр з side-інформацією (вік, географія) компенсує нестачу даних, підвищуючи recall на 25%.
- Зміна вподобань. Користувач, який раніше подорожував сам, може почати їздити з сім'єю. Система перераховує профіль після кожного бронювання, адаптуючись за 2–3 поїздки.
Профіль мандрівника та контекстні рекомендації
import pandas as pd
import numpy as np
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
from anthropic import Anthropic
import json
class TravelerProfiler:
"""Профіль мандрівника з історії поїздок"""
TRAVEL_STYLES = [
'adventure', 'cultural', 'relaxation', 'gastronomy',
'family', 'romantic', 'business', 'budget', 'luxury'
]
def build_profile(self, booking_history: pd.DataFrame,
search_history: pd.DataFrame,
user_id: str) -> dict:
"""Профіль з бронювань та пошуку"""
bookings = booking_history[booking_history['user_id'] == user_id]
searches = search_history[search_history['user_id'] == user_id]
if bookings.empty and searches.empty:
return {'user_id': user_id, 'is_new': True}
profile = {
'user_id': user_id,
'is_new': False,
'total_trips': len(bookings),
# Ціновий сегмент
'avg_budget_per_person': bookings.get('price_per_person', pd.Series([0])).mean(),
'hotel_star_preference': bookings.get('hotel_stars', pd.Series([3])).mean(),
# Тип напрямків
'preferred_climate': self._infer_climate_preference(bookings),
'preferred_destination_type': self._infer_destination_type(bookings),
'international_ratio': (bookings.get('is_international', pd.Series([False]))).mean(),
# Організація поїздки
'avg_trip_duration_days': bookings.get('duration_days', pd.Series([7])).mean(),
'advance_booking_days': bookings.get('days_booked_in_advance', pd.Series([30])).mean(),
'solo_vs_group': bookings.get('travelers_count', pd.Series([2])).mean(),
# Активності з пошуку
'activity_interests': self._extract_activity_interests(searches),
}
# Визначаємо стиль подорожей
profile['travel_style'] = self._classify_travel_style(profile)
return profile
def _infer_climate_preference(self, bookings: pd.DataFrame) -> str:
if 'destination_climate' not in bookings.columns:
return 'mixed'
climate_counts = bookings['destination_climate'].value_counts()
return climate_counts.index[0] if len(climate_counts) > 0 else 'mixed'
def _infer_destination_type(self, bookings: pd.DataFrame) -> str:
if 'destination_type' not in bookings.columns:
return 'mixed'
type_counts = bookings['destination_type'].value_counts()
return type_counts.index[0] if len(type_counts) > 0 else 'mixed'
def _extract_activity_interests(self, searches: pd.DataFrame) -> list[str]:
interests = set()
activity_keywords = {
'skiing': ['ski', 'snow', 'winter'],
'beach': ['beach', 'sea', 'ocean', 'resort'],
'hiking': ['hike', 'trek', 'mountain', 'nature'],
'museums': ['museum', 'culture', 'history', 'art'],
'gastronomy': ['food', 'restaurant', 'cuisine', 'wine'],
}
if 'query' not in searches.columns:
return []
for query in searches['query'].str.lower():
for interest, keywords in activity_keywords.items():
if any(kw in query for kw in keywords):
interests.add(interest)
return list(interests)
def _classify_travel_style(self, profile: dict) -> str:
budget = profile.get('avg_budget_per_person', 0)
stars = profile.get('hotel_star_preference', 3)
if stars >= 4.5 or budget > 3000:
return 'luxury'
elif budget < 500:
return 'budget'
elif 'beach' in profile.get('activity_interests', []):
return 'relaxation'
elif profile.get('preferred_destination_type') == 'city':
return 'cultural'
return 'mixed'
class TourRecommendationEngine:
"""Рекомендації турів з семантичним пошуком"""
def __init__(self):
self.encoder = SentenceTransformer('paraphrase-multilingual-mpnet-base-v2')
self.llm = Anthropic()
def semantic_search(self, query: str,
tours_catalog: pd.DataFrame,
top_k: int = 20) -> pd.DataFrame:
"""Семантичний пошук турів за запитом"""
query_embedding = self.encoder.encode(query, normalize_embeddings=True)
# Кодуємо описи турів (в production: індекс попередньо обчислений та завантажений)
if 'description_embedding' not in tours_catalog.columns:
tours_catalog['description_embedding'] = tours_catalog['description'].apply(
lambda x: self.encoder.encode(str(x), normalize_embeddings=True)
)
similarities = cosine_similarity(
query_embedding.reshape(1, -1),
np.stack(tours_catalog['description_embedding'].values)
)[0]
tours_catalog = tours_catalog.copy()
tours_catalog['semantic_score'] = similarities
return tours_catalog.nlargest(top_k, 'semantic_score')
def personalized_ranking(self, candidates: pd.DataFrame,
traveler_profile: dict) -> pd.DataFrame:
"""Персоналізоване ранжування з семантичних кандидатів"""
df = candidates.copy()
# Ціновий збіг
avg_budget = traveler_profile.get('avg_budget_per_person', 1000)
df['price_fit'] = 1.0 - (abs(df['price_per_person'] - avg_budget) / avg_budget).clip(0, 1)
# Стиль подорожей
travel_style = traveler_profile.get('travel_style', 'mixed')
df['style_match'] = df.get('tour_style', pd.Series(['mixed'] * len(df))).apply(
lambda s: 1.0 if s == travel_style else 0.5 if s == 'mixed' else 0.3
)
# Інтереси-активності
user_interests = set(traveler_profile.get('activity_interests', []))
df['activity_match'] = df.get('activities', pd.Series([[]] * len(df))).apply(
lambda acts: len(user_interests & set(acts)) / max(len(user_interests), 1) if user_interests else 0.5
)
# Тривалість
preferred_duration = traveler_profile.get('avg_trip_duration_days', 7)
df['duration_fit'] = 1.0 - (abs(df.get('duration_days', 7) - preferred_duration) / 14).clip(0, 1)
df['final_score'] = (
df['semantic_score'] * 0.30 +
df['price_fit'] * 0.25 +
df['style_match'] * 0.20 +
df['activity_match'] * 0.15 +
df['duration_fit'] * 0.10
)
return df.sort_values('final_score', ascending=False)
def generate_tour_pitch(self, tour: dict,
traveler_profile: dict) -> str:
"""Персоналізований опис туру для користувача"""
response = self.llm.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=150,
messages=[{
"role": "user",
"content": f"""Write a 3-sentence personalized pitch for this tour. Russian language.
Tour: {tour.get('name')}, {tour.get('destination')}
Key features: {tour.get('highlights', [])}
Traveler profile: {traveler_profile.get('travel_style')} traveler,
interests: {traveler_profile.get('activity_interests', [])},
typical budget: ${traveler_profile.get('avg_budget_per_person', 1000)}/person.
Highlight what's most relevant to THIS specific traveler."""
}]
)
return response.content[0].text
Чому семантичний пошук кращий за ключовий?
Ключовий пошук по турах страждає від неповноти: користувач вводить «Туреччина 5 зірок» — система видає лише тури з точним збігом. Семантичний пошук розуміє контекст: «відпочинок на морі з дітьми» знаходить і тури в Туреччину, і в Єгипет, і на Кіпр, якщо опис відповідає. Наші тести показали: click-through rate видачі зростає на 22-35% порівняно з ключовим пошуком. Персоналізоване ранжування доводить конверсію з перегляду в бронювання до +18-25%.
| Параметр | Ключовий пошук | Семантичний пошук |
|---|---|---|
| Розуміння запиту | Точний збіг слів | Сенс і синоніми |
| Охоплення турів | Тільки з ключовими словами | Всі тури, близькі за змістом |
| Вплив на CTR | Базовий | +22-35% |
| Конверсія в бронь | - | +18-25% |
Вплив персоналізації на ключові метрики
| Метрика | До впровадження | Після впровадження |
|---|---|---|
| Середній час підбору туру | 45 хв | 12 хв |
| Частка відмов від видачі | 68% | 42% |
| Конверсія бронювання | 3.2% | 4.8% |
| Середній чек | - | +15% |
Як ми будуємо систему під ключ?
Процес включає п'ять етапів:
- Аналітика та збір вимог. Аудит даних клієнта: історія бронювань, каталог турів, архітектура CRM. Визначаємо бізнес-метрики (конверсія, середній чек, NPS).
- Проектування архітектури. Обираємо векторну БД (Pgvector або Qdrant), модель ембеддінгів (multilingual MPNet), LLM для генерації описів (Claude 3.5, GPT-4o).
- Реалізація та навчання. Пишемо пайплайни на PyTorch, налаштовуємо LoRA для донавчання, піднімаємо Triton Inference Server. Типова тривалість — 4–8 тижнів.
- Тестування та A/B-тест. Запускаємо спліт-тест на 20% трафіку: порівнюємо нову систему з поточною. Вимірюємо latency p99, конверсію, задоволеність користувачів.
- Деплой та запуск. Розгортаємо в продакшен (хмара або on-premise), надаємо API-документацію та дашборд метрик. Після запуску — три місяці підтримки.
Приклад архітектури системи
- Data Layer: PostgreSQL (pgvector), S3 для зберігання ембеддінгів.
- Model Serving: Triton Inference Server з ONNX Runtime, INT8 квантизація для зниження latency.
- Orchestration: Airflow для перерахунку профілів кожні 24 години.
- API Gateway: FastAPI, єдиний endpoint
/recommend.
Результат і гарантії
Ми передаємо не просто код. На вас чекають:
- API-документація (Swagger) та інструкція з експлуатації,
- доступ до Git-репозиторію з навченими моделями,
- дашборд метрик (latency p99, конверсія, кількість бронювань),
- навчання двох співробітників роботі з системою,
- гарантія на код та SLA за часом відповіді (p99 < 200 мс).
Терміни та вартість
Терміни — від 6 до 12 тижнів залежно від обсягу даних та кількості інтеграцій. Вартість проекту — від $50,000 до $150,000. Економія на бронюваннях досягає $15-25 на одного туриста. Вартість розраховується індивідуально після аудиту ваших даних та інфраструктури. Оцінимо ваш проект за 2 робочі дні. Отримайте консультацію: напишіть нам з коротким описом завдання — запропонуємо оптимальне рішення. Замовте розробку AI-системи для вашого туроператора і переконайтеся в ефективності підбору турів.







