api.routes ========== .. py:module:: api.routes .. autoapi-nested-parse:: FastAPI 路由定义:POI 查询、行程规划、Agent 对话、历史记录。 Attributes ---------- .. autoapisummary:: api.routes.router Functions --------- .. autoapisummary:: api.routes._build_poi_cache api.routes.poi_lookup api.routes.suggest api.routes.plan api.routes.chat api.routes.list_history api.routes.get_history_detail api.routes.create_history api.routes.delete_history Module Contents --------------- .. py:data:: router .. py:function:: _build_poi_cache(req: backend.api.schemas.PlanRequest) 将 PlanRequest 转换为 run_planning 所需的 poi_cache 格式。 前端传来的坐标数据可直接映射,无需额外转换。 时间窗以 (start, end) 元组形式传递。 Args: req: 前端传入的规划请求,含酒店/景点坐标及时间窗。 Returns: dict: {"hotel": {...酒店信息...}, "spots": [...景点列表...]}。 .. py:function:: poi_lookup(req: backend.api.schemas.POILookupRequest) :async: 批量查询 POI 坐标和地址。 前端传入城市 + 名称列表,后端调用高德 POI 搜索 API, 返回每个名称的坐标和地址。未找到的名称列入 failed 列表, 若跨城市则附带建议地址。 Args: req: POI 查询请求,含城市名和名称列表。 Returns: POILookupResponse: 查询结果,items 为成功项,failed 为失败列表。 .. py:function:: suggest(req: backend.api.schemas.PlanRequest) :async: 获取方案建议列表。 不指定 n_days,run_planning 内部回退到 ca_suggest(), 遍历多种聚类方法 × 天数,返回建议列表。 响应中附带 cost_matrix/dist_matrix/polylines,供后续深度规划复用。 Returns: dict: 含 suggestions(建议列表)、algo_time、cost_matrix、dist_matrix、 polylines、amap_api_key、amap_security_code。 Raises: HTTPException 500: 建议搜索引擎内部错误。 .. py:function:: plan(req: backend.api.schemas.PlanRequest) :async: 执行完整规划,返回每日行程与高德地图可视化数据。 n_days 为必填,mode 可选 "fast"(CA) 或 "deep"(VNS)。 若 req 携带 cost_matrix/dist_matrix(来自 suggest 响应), 则将矩阵作为 override 传给 run_planning,跳过驾车 API 调用。 Returns: dict: 含 solution、best_days、daily_schedules、cost_matrix、dist_matrix、 polylines、commentary、amap_api_key、amap_security_code。 Raises: HTTPException 400: n_days 未指定时。 HTTPException 500: 规划引擎内部错误。 .. py:function:: chat(req: backend.api.schemas.ChatRequest) :async: LLM Agent 对话接口,SSE 流式输出。 Mock 模式返回死 token,方便前端联调。 正式上线后设置 MOCK_MODE=False 即可切换 DeepSeek 真实调用。 Raises: HTTPException 500: LLM 调用异常或数据格式错误。 .. py:function:: list_history(page: int = Query(default=1, ge=1), page_size: int = Query(default=20, ge=1, le=100), session: sqlalchemy.ext.asyncio.AsyncSession = Depends(get_session)) :async: 获取历史记录分页列表。 仅返回摘要字段(id/city/n_days/cost/spot_count/note/created_at), 不加载 JSONB 大字段(plan_result),避免列表页传输大量数据。 Args: page: 页码,从 1 开始。 page_size: 每页条数,最大 100。 Returns: HistoryListResponse: { items, total, page, page_size }。 .. py:function:: get_history_detail(record_id: uuid.UUID, session: sqlalchemy.ext.asyncio.AsyncSession = Depends(get_session)) :async: 获取单条历史记录的完整数据(含 plan_result 全量 JSONB)。 Args: record_id: 记录 UUID。 Returns: HistoryDetail: 含 plan_result/request_params 等完整字段。 Raises: HTTPException 404: 记录不存在。 .. py:function:: create_history(req: backend.api.schemas.HistoryCreate, session: sqlalchemy.ext.asyncio.AsyncSession = Depends(get_session)) :async: 保存一条历史记录(分享方案到分享站)。 设计说明:device_id 由前端 localStorage 自动生成,服务端不做强鉴权—— 这是软鉴权设计。核心考量: 1. 不引入注册/登录系统,保持访客零门槛 2. device_id 仅用于删除时校验「是否是本人」,防止误删他人方案 3. device_id 无法防恶意攻击(前端可伪造),但此场景无敏感数据,可接受 Args: req: HistoryCreate,包含 city/n_days/plan_result 等必填字段。 Returns: dict: { id: str } 新创建的记录 UUID。 Raises: HTTPException 422: 请求体校验失败(Pydantic 自动处理)。 .. py:function:: delete_history(record_id: uuid.UUID, req: backend.api.schemas.HistoryDeleteRequest, session: sqlalchemy.ext.asyncio.AsyncSession = Depends(get_session)) :async: 删除一条历史记录(需 device_id 匹配创建者)。 Args: record_id: 记录 UUID。 req: HistoryDeleteRequest,包含 device_id。 Returns: dict: { ok: true } Raises: HTTPException 404: 记录不存在。 HTTPException 403: device_id 不匹配,无权删除。