מערכת המלצות טיולים ונסיעות מבוססת AI

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

תחומי פיתוח AI

שאלות נפוצות

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

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

עד 70% מהתיירים נוטשים את ההזמנה שלהם לאחר חיפוש כושל. מחיר הטעות גבוה: אדם מתכנן חופשה בשווי עשרות אלפי שקלים, אך ההקשר משתנה—טיול עם ילדים דורש קריטריונים שונים, חופשה רומנטית דורשת אחרים. אנו בונים AI שמבין את הניואנסים הללו מהיסטוריית חיפושים, הזמנות והעדפות מפורשות, ויוצר פרופיל מטייל מותאם אישית. הניסיון שלנו: 10+ שנים ב-AI/ML, עשרות פרויקטים בתחום התיירות. אנו מבטיחים שקיפות מודל ותמיכה מלאה בכל השלבים. אנו מפתחים מערכות המלצת טיולים מבוססות AI עם חיפוש סמנטי והתאמה אישית.

כיצד AI מבין העדפות מטיילים

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

אילו בעיות המערכת פותרת?

  • התחלה קרה. משתמשים חדשים ללא היסטוריה—המערכת משתמשת בשאלון ובנתונים דמוגרפיים כדי להציע טיולים ראשוניים. ההמרה בגישה זו גבוהה ב-40% מהצגה אקראית.
  • דלילות נתונים. רוב המשתמשים מזמינים 1–2 פעמים בשנה, ומספקים מעט אותות. מסנן שיתופי נוירוני עם מידע צדדי (גיל, גיאוגרפיה) מפצה על מחסור בנתונים, ומגדיל את ה-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 כוכבים"—המערכת מחזירה רק טיולים עם התאמות מדויקות. חיפוש סמנטי מבין הקשר: "חופשת חוף עם ילדים" מוצא טיולים בטורקיה, מצרים וקפריסין אם התיאור תואם. הבדיקות שלנו הראו ששיעור הקליקים על תוצאות עולה ב-22-35% בהשוואה לחיפוש מילות מפתח. דירוג מותאם אישית מעלה את ההמרה מצפייה להזמנה ב-+18-25%.

פרמטר חיפוש מילות מפתח חיפוש סמנטי
הבנת שאילתה התאמת מילים מדויקת משמעות ומילים נרדפות
כיסוי טיולים רק מילות מפתח כל הטיולים הקרובים סמנטית
השפעה על CTR קו בסיס +22-35%
המרת הזמנות - +18-25%

השפעת ההתאמה האישית על מדדים מרכזיים

מדד לפני היישום אחרי היישום
זמן בחירת טיול ממוצע 45 דקות 12 דקות
שיעור נטישה מתוצאות 68% 42%
המרת הזמנות 3.2% 4.8%
צ'ק ממוצע - +15%

כיצד אנו בונים מערכת סוהר

התהליך כולל חמישה שלבים:

  1. אנליטיקה ואיסוף דרישות. ביקורת נתוני לקוח: היסטוריית הזמנות, קטלוג טיולים, ארכיטקטורת CRM. הגדרת מדדים עסקיים (המרה, צ'ק ממוצע, NPS).
  2. עיצוב ארכיטקטורה. בחירת מסד וקטורי (Pgvector או Qdrant), מודל embedding רב-לשוני (MPNet), LLM ליצירת תיאורים (Claude 3.5, GPT-4o).
  3. יישום ואימון. כתיבת pipelines ב-PyTorch, הגדרת כוונון עדין LoRA, פריסת Triton Inference Server. משך זמן טיפוסי: 4–8 שבועות.
  4. בדיקות ובדיקת A/B. הרצת בדיקה מפוצלת על 20% מהתנועה: השוואת המערכת החדשה לנוכחית. מדידת זמן השהייה p99, המרה, שביעות רצון משתמשים.
  5. פריסה והשקה. פריסה לייצור (ענן או on-premise), מתן תיעוד API ולוח מחוונים למדדים. לאחר ההשקה, שלושה חודשי תמיכה.
דוגמה לארכיטקטורת מערכת
  • שכבת נתונים: PostgreSQL (pgvector), S3 לאחסון embeddings.
  • שירות מודלים: Triton Inference Server עם ONNX Runtime, קוונטיזציית INT8 להפחתת זמן השהייה.
  • תזמור: Airflow לחישוב מחדש של פרופילים כל 24 שעות.
  • שער API: FastAPI, נקודת קצה אחת 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 .

תוצאות ואחריות

אנו מספקים יותר מסתם קוד. אתה מקבל:

  • תיעוד API (Swagger) ומדריך הפעלה,
  • גישה למאגר Git עם מודלים מאומנים,
  • לוח מחוונים למדדים (זמן השהייה p99, המרה, מספר הזמנות),
  • הכשרה לשני עובדים לשימוש במערכת,
  • אחריות על קוד ו-SLA לזמן תגובה (p99 < 200 אלפיות השנייה).

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

לוחות זמנים: 6 עד 12 שבועות בהתאם לנפח הנתונים ומספר האינטגרציות. העלות נקבעת באופן אישי לאחר ביקורת הנתונים והתשתית שלך. החיסכון לכל הזמנה יכול להגיע ל-$15-25 למטייל. אנו נעריך את הפרויקט שלך תוך 2 ימי עסקים. קבל ייעוץ: כתוב לנו עם תיאור קצר של המשימה—נציע פתרון אופטימלי. הזמן פיתוח מערכת AI עבור מפעיל הטיולים שלך וראה את היעילות של בחירת טיולים.