智能旅行规划助手:FastAPI 接口与 Pydantic 数据建模

写在前面

上一篇记录了智能旅行规划助手的需求拆解和整体架构。这一篇主要记录后端接口设计,重点是 FastAPI 和 Pydantic 在这个项目里的作用。

做 AI 应用时,很多人容易把注意力全部放在 Prompt 上,但实际开发中,接口和数据结构同样重要。因为前端、后端、工具 API、LLM 输出最终都要围绕一套稳定的数据格式协作。

为什么先设计 Schema

如果后端只返回一段文本,前端展示会非常麻烦:

  • 景点无法独立渲染成卡片
  • 每天行程无法折叠展示
  • 住宿、餐饮、交通无法分区
  • 错误处理和兜底逻辑不好做

所以我先定义 TripPlan 相关的数据结构,再让 Prompt 和模型输出去适配这个结构。

这也是我在项目里比较重要的一个体会:不要让前端去猜模型输出,应该让模型输出服从后端 Schema。

请求模型设计

用户提交旅行规划请求时,核心字段包括:

1
2
3
4
5
6
7
8
9
class TripPlanRequest(BaseModel):
destination: str
start_date: date
end_date: date
travelers: int = 1
transportation: str
accommodation: str
preferences: list[str] = []
extra_requirements: str | None = None

这层模型的作用有两个:

  1. 约束前端提交的数据格式
  2. 给后续 Prompt 构造提供稳定输入

比如 travelers 必须是数字,preferences 必须是列表。如果前端传错格式,FastAPI 会直接返回参数校验错误,不会进入后面的 LLM 调用流程。

响应模型设计

响应结构比请求复杂,因为它要服务前端展示。

我把旅行计划拆成了几类信息:

  • daily_plans:每日行程
  • attractions:景点安排
  • meals:餐饮建议
  • hotel:住宿信息
  • transportation:交通建议
  • weather_tips:天气提醒
  • budget:预算信息

大致结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Attraction(BaseModel):
name: str
address: str
duration: str
description: str
image_url: str | None = None

class DailyPlan(BaseModel):
day: int
date: date
summary: str
attractions: list[Attraction]
meals: list[Meal]
hotel: Hotel | None = None
transportation: str
weather_tips: str | None = None

class TripPlan(BaseModel):
destination: str
start_date: date
end_date: date
days: int
daily_plans: list[DailyPlan]
budget: Budget | None = None

这样前端不需要解析自然语言,只需要根据字段渲染页面。

FastAPI 接口实现

核心接口是:

1
2
3
4
@router.post("/api/trip/plan", response_model=TripPlan)
async def plan_trip(request: TripPlanRequest):
result = await trip_planner_service.plan(request)
return result

看起来很简单,但这里有几个关键点:

  • TripPlanRequest 负责请求校验
  • TripPlan 负责响应约束
  • service 层负责工具调用和 LLM 规划
  • controller 层不堆业务逻辑

这样后续即使替换模型、调整 Agent、增加工具 API,也不会影响接口层。

错误处理

AI 应用里的错误不只有普通后端异常,还包括:

  • 第三方 API 超时
  • LLM 输出不是合法 JSON
  • JSON 字段缺失
  • 模型生成了不符合业务逻辑的内容

所以我在接口层做了统一异常处理:

1
2
3
4
5
6
7
try:
plan = await planner.generate(request)
return plan
except ValidationError as e:
return fallback_plan(request)
except Exception as e:
raise HTTPException(status_code=500, detail="旅行计划生成失败")

这里的重点不是把所有异常吞掉,而是保证用户不会看到一堆后端堆栈,同时系统有机会返回一个基于真实 POI 数据的备用计划。

接口设计的收获

这个项目让我更清楚地意识到,AI 应用的后端接口不能只做“转发模型请求”。它至少要承担这些职责:

  • 参数校验
  • 工具数据组织
  • Prompt 上下文构造
  • LLM 输出解析
  • Schema 校验
  • 异常兜底
  • 响应结构约束

如果这些都没有,项目很容易停留在 Demo 阶段。

小结

这一阶段完成了 FastAPI 接口和 Pydantic 数据建模。下一篇会记录高德地图 API 的接入过程,以及为什么真实数据对降低模型幻觉很重要。


智能旅行规划助手:FastAPI 接口与 Pydantic 数据建模
https://zxyblog.top/2026/04/14/智能旅行规划助手-FastAPI接口与Pydantic数据建模/
作者
zxy
发布于
2026年4月14日
许可协议