AI 生活化应用设计:智能旅行规划的个性化行程生成实践

cover

一、旅行规划的信息过载:当选择太多反而无法出发

计划一次旅行,需要处理海量信息:目的地的景点排名、交通路线、住宿选择、天气情况、预算分配。传统的旅行攻略网站提供的是通用信息,无法适配每个人的偏好——有人喜欢博物馆,有人偏爱自然风光;有人追求性价比,有人看重体验品质。用户往往在信息海洋中迷失,最终放弃规划或选择跟团。

AI 旅行规划助手的核心价值是"个性化":基于用户的偏好(旅行风格、预算、时间、同行人),生成专属行程。但个性化不是简单的推荐排序——它需要理解用户隐含的需求(如"带老人出行"意味着减少步行、"亲子游"意味着安排午休时间),并在多个约束条件(时间、距离、预算)之间找到最优解。

flowchart TB
    Input[用户输入<br/>目的地/日期/偏好] --> Parse[偏好解析<br/>显性+隐性需求]
    Parse --> Constraint[约束建模<br/>时间/距离/预算/体力]
    Constraint --> Generate[行程生成引擎<br/>多目标优化]
    Generate --> Timeline[可视化时间线]
    Timeline --> Adjust{用户调整}
    Adjust -->|修改偏好| Parse
    Adjust -->|拖拽行程| Timeline
    Adjust -->|满意| Export[导出行程单]

    Parse -->|隐性需求推断| Implicit[带老人→减少步行<br/>亲子→安排午休<br/>预算紧→免费景点优先]

二、个性化行程生成的核心机制

2.1 偏好解析与隐性需求推断

用户输入的偏好通常是模糊的(如"轻松一点"、"不要太赶")。AI 需要将模糊偏好转化为可计算的约束条件:"轻松"意味着每天不超过 3 个景点,景点间步行距离不超过 1 公里;"不要太赶"意味着每个景点至少停留 1.5 小时。隐性需求则通过上下文推断:同行人包含 60 岁以上老人,自动减少爬山类景点;包含 5 岁以下儿童,自动增加午休时段。

2.2 多约束行程优化

行程生成是一个多约束优化问题:在有限时间内,最大化体验满意度,同时满足时间窗口(景点开放时间)、距离约束(景点间交通时间)、预算约束和体力约束。采用贪心 + 回溯策略:先按满意度排序景点,贪心选择,当约束冲突时回溯调整。

sequenceDiagram
    participant User as 用户
    participant Engine as 行程引擎
    participant POI as 景点数据库

    User->>Engine: 京都3日游,偏好文化,带老人
    Engine->>Engine: 解析偏好: 文化>自然<br/>隐性: 减少步行, 避免爬山

    Engine->>POI: 查询京都文化类景点
    POI-->>Engine: 金阁寺, 清水寺, 伏见稻荷...

    Engine->>Engine: 过滤: 移除爬山类(伏见稻荷)<br/>排序: 文化评分×可达性

    Engine->>Engine: 生成Day1: 金阁寺→龙安寺→二条城<br/>步行<1km, 午休12:00-14:00

    Engine->>User: 展示行程时间线
    User->>Engine: 把二条城换成三十三间堂
    Engine->>Engine: 重新计算路线和交通
    Engine->>User: 更新行程

三、生产级代码实现

3.1 偏好解析与行程生成引擎

from dataclasses import dataclass, field
from typing import List, Dict, Optional, Set
from enum import Enum
import logging

logger = logging.getLogger(__name__)


class TravelStyle(Enum):
    CULTURE = "culture"        # 文化历史
    NATURE = "nature"          # 自然风光
    FOOD = "food"              # 美食探店
    RELAXATION = "relaxation"  # 休闲度假
    ADVENTURE = "adventure"    # 冒险体验


@dataclass
class TravelerProfile:
    """旅行者画像"""
    styles: List[TravelStyle]          # 旅行风格偏好
    budget_level: int                  # 预算等级 1-5
    pace: str = "moderate"             # 节奏: relaxed/moderate/fast
    has_elderly: bool = False          # 是否有老人同行
    has_children: bool = False         # 是否有儿童同行
    max_walking_km: float = 5.0        # 每日最大步行距离
    must_visit: List[str] = field(default_factory=list)  # 必去景点
    avoid: List[str] = field(default_factory=list)       # 避免类型


@dataclass
class POI:
    """景点信息"""
    id: str
    name: str
    category: str
    lat: float
    lng: float
    visit_duration_hours: float
    rating: float
    walking_intensity: int  # 1-5, 5=爬山
    ticket_price: float
    open_hours: str
    tags: List[str] = field(default_factory=list)


@dataclass
class DayPlan:
    """单日行程"""
    day: int
    activities: List[Dict] = field(default_factory=list)
    total_walking_km: float = 0.0
    total_cost: float = 0.0
    free_time_hours: float = 0.0


class ItineraryEngine:
    """行程生成引擎

    设计考量:
    - 隐性需求推断:根据同行人自动调整约束
    - 多约束优化:时间、距离、预算、体力
    - 可调整性:用户可随时修改,引擎重新计算
    """

    # 节奏对应的每日景点数上限
    PACE_LIMITS = {"relaxed": 3, "moderate": 4, "fast": 6}

    # 隐性需求规则
    IMPLICIT_RULES = {
        "has_elderly": {
            "max_walking_km": 3.0,
            "avoid_walking_intensity": 4,  # 避免步行强度>=4的景点
            "lunch_break_hours": 2.0,       # 午休时间
        },
        "has_children": {
            "lunch_break_hours": 2.0,
            "nap_time": "12:30-14:30",
            "avoid_walking_intensity": 3,
        },
    }

    def generate(
        self,
        destination: str,
        days: int,
        profile: TravelerProfile,
        pois: List[POI],
    ) -> List[DayPlan]:
        """生成个性化行程"""
        # 1. 应用隐性需求约束
        constraints = self._build_constraints(profile)

        # 2. 过滤和排序景点
        filtered_pois = self._filter_pois(pois, profile, constraints)
        scored_pois = self._score_pois(filtered_pois, profile)

        # 3. 逐日生成行程
        itinerary = []
        remaining_pois = list(scored_pois)

        for day in range(1, days + 1):
            day_plan = self._plan_day(day, remaining_pois, constraints, profile)
            itinerary.append(day_plan)

            # 移除已安排的景点
            visited_ids = {a["poi_id"] for a in day_plan.activities}
            remaining_pois = [p for p in remaining_pois if p.id not in visited_ids]

        return itinerary

    def _build_constraints(self, profile: TravelerProfile) -> Dict:
        """构建约束条件,包含隐性需求"""
        constraints = {
            "max_pois_per_day": self.PACE_LIMITS.get(profile.pace, 4),
            "max_walking_km": profile.max_walking_km,
            "avoid_intensity": 0,
            "lunch_break_hours": 1.0,
        }

        # 应用隐性需求
        if profile.has_elderly:
            rules = self.IMPLICIT_RULES["has_elderly"]
            constraints["max_walking_km"] = min(
                constraints["max_walking_km"], rules["max_walking_km"]
            )
            constraints["avoid_intensity"] = max(
                constraints["avoid_intensity"], rules["avoid_walking_intensity"]
            )
            constraints["lunch_break_hours"] = rules["lunch_break_hours"]

        if profile.has_children:
            rules = self.IMPLICIT_RULES["has_children"]
            constraints["avoid_intensity"] = max(
                constraints["avoid_intensity"], rules["avoid_walking_intensity"]
            )
            constraints["lunch_break_hours"] = rules["lunch_break_hours"]

        return constraints

    def _filter_pois(
        self,
        pois: List[POI],
        profile: TravelerProfile,
        constraints: Dict,
    ) -> List[POI]:
        """过滤不符合约束的景点"""
        filtered = []
        for poi in pois:
            # 过滤避免类型
            if poi.category in profile.avoid:
                continue
            # 过滤步行强度过高的景点
            if poi.walking_intensity >= constraints["avoid_intensity"]:
                continue
            filtered.append(poi)
        return filtered

    def _score_pois(self, pois: List[POI], profile: TravelerProfile) -> List[POI]:
        """为景点评分并排序"""
        for poi in pois:
            # 基础评分:景点评分
            score = poi.rating
            # 偏好加成
            for style in profile.styles:
                if style.value in poi.tags:
                    score += 0.5
            # 必去景点最高优先级
            if poi.name in profile.must_visit:
                score += 5.0
            poi.rating = score  # 复用 rating 字段存储综合评分

        return sorted(pois, key=lambda p: p.rating, reverse=True)

    def _plan_day(
        self,
        day: int,
        pois: List[POI],
        constraints: Dict,
        profile: TravelerProfile,
    ) -> DayPlan:
        """规划单日行程"""
        activities = []
        total_walking = 0.0
        total_cost = 0.0
        current_time = 9.0  # 9:00 出发

        for poi in pois:
            if len(activities) >= constraints["max_pois_per_day"]:
                break

            # 检查时间窗口
            end_time = current_time + poi.visit_duration_hours
            if end_time > 18.0:  # 18:00 结束当天行程
                break

            # 午休安排
            if current_time < 12.0 <= end_time and profile.has_elderly or profile.has_children:
                activities.append({
                    "type": "lunch_break",
                    "start": "12:00",
                    "duration": constraints["lunch_break_hours"],
                    "note": "午休时间" if profile.has_elderly else "午睡时间",
                })
                current_time = 14.0

            activities.append({
                "type": "visit",
                "poi_id": poi.id,
                "name": poi.name,
                "start": f"{int(current_time)}:{'00' if current_time % 1 == 0 else '30'}",
                "duration": poi.visit_duration_hours,
                "cost": poi.ticket_price,
                "walking_intensity": poi.walking_intensity,
            })

            total_cost += poi.ticket_price
            current_time += poi.visit_duration_hours + 0.5  # 0.5小时交通时间

        return DayPlan(
            day=day,
            activities=activities,
            total_walking_km=total_walking,
            total_cost=total_cost,
            free_time_hours=max(0, 18.0 - current_time),
        )

四、边界分析与架构权衡

4.1 实时数据的时效性

景点开放时间、门票价格和交通状况是动态变化的。如果数据源更新不及时,生成的行程可能包含已关闭的景点或过时的价格。解决方案是接入实时数据 API(如 Google Places、高德地图),但增加了外部依赖和 API 调用成本。

4.2 个性化与隐私的平衡

深度个性化需要收集用户的旅行历史和偏好数据,这涉及隐私问题。用户可能不愿意分享详细的出行记录。解决方案是使用本地化偏好模型——偏好数据只存储在用户设备上,AI 在本地推理,不上传个人数据。

4.3 行程的灵活性

生成的行程是静态的,但旅行中充满变数——天气变化、景点临时关闭、用户临时改变主意。行程需要支持实时调整:用户拖拽时间线上的活动,AI 自动重新计算受影响的部分。这要求引擎支持增量计算,而非每次从头生成。

五、总结

AI 旅行规划助手的核心价值在于将模糊的旅行愿望转化为可执行的行程方案。隐性需求推断让 AI 理解"带老人出行"不只是标签,而是一系列具体的约束条件。多约束优化确保行程在时间、距离、预算和体力之间找到平衡。

落地路线建议:第一步,实现基础的偏好解析和景点排序,生成简单的逐日行程;第二步,添加隐性需求推断,根据同行人自动调整约束;第三步,接入实时数据源,确保景点信息的时效性;第四步,实现可视化时间线和拖拽调整,让用户能直观地修改行程。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐