from enum import Enum from typing import Literal, Optional from pydantic import BaseModel, ConfigDict, Field, model_validator def to_camel(value: str) -> str: parts = value.split("_") return parts[0] + "".join(part.capitalize() for part in parts[1:]) class APIModel(BaseModel): model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True) class Duration(str, Enum): HALF_DAY = "half_day" FULL_DAY = "full_day" class Pace(str, Enum): RELAXED = "relaxed" MODERATE = "moderate" COMPACT = "compact" class Transport(str, Enum): WALKING = "walking" DRIVING = "driving" PUBLIC_TRANSPORT = "public_transport" class BudgetLevel(str, Enum): ECONOMY = "economy" STANDARD = "standard" QUALITY = "quality" Theme = Literal["亲子", "情侣", "朋友", "银发", "研学"] Interest = Literal["自然风光", "文化场馆", "生态科普", "美食", "摄影"] class TravelPreferences(APIModel): destination: Literal["深圳市光明区"] = "深圳市光明区" themes: list[Theme] = Field(default_factory=list) duration: Duration pace: Pace interests: list[Interest] = Field(default_factory=list) adults: int = Field(default=2, ge=0, le=20) children: int = Field(default=0, ge=0, le=20) child_ages: list[int] = Field(default_factory=list) transport: Transport = Transport.PUBLIC_TRANSPORT budget_level: BudgetLevel = BudgetLevel.STANDARD extra_requirements: str = Field(default="", max_length=500) @model_validator(mode="after") def validate_group_and_preferences(self) -> "TravelPreferences": if self.adults + self.children < 1: raise ValueError("出行总人数至少为1") if self.child_ages and len(self.child_ages) != self.children: raise ValueError("儿童年龄数量必须与儿童人数一致") if any(age < 0 or age > 17 for age in self.child_ages): raise ValueError("儿童年龄须在0至17岁之间") if not self.themes and not self.interests: raise ValueError("主题或特色偏好至少选择一项") return self class ItineraryItem(APIModel): start_time: str end_time: str place_id: str place_name: str activity: str reason: str transfer_from_previous_minutes: Optional[int] = Field(default=None, ge=0) tips: list[str] = Field(default_factory=list) class GeneratedItinerary(APIModel): title: str summary: str total_minutes: int = Field(ge=1, le=720) estimated_cost_text: str items: list[ItineraryItem] = Field(min_length=1, max_length=5) notes: list[str] = Field(default_factory=list) class Source(APIModel): place_id: str source_name: str source_url: str updated_at: str class Itinerary(GeneratedItinerary): sources: list[Source] class PlanResponse(APIModel): conversation_id: str assistant_message: str itinerary: Itinerary change_summary: Optional[str] = None class MessageRequest(APIModel): message: str = Field(min_length=1, max_length=500) class HealthResponse(APIModel): status: str llm_mode: str model_configured: bool knowledge_count: int