Files
geo-setp/docs/frontend_api.md
2026-04-15 14:05:33 +08:00

13 KiB
Raw Blame History

Geo Agent API 对接文档

1. 文档目的

本文档面向前端对接,说明当前可用接口、请求结构、响应结构、错误语义和对接建议。

2. 服务概览

当前服务提供两个 HTTP 接口:

  • GET /healthz
  • POST /route/plan
  • POST /load/plan

默认本地开发地址示例:

http://127.0.0.1:8000

2.1 Authorization

GET /healthz 外,当前业务接口都需要通过 Authorization 请求头进行鉴权。

请求头格式:

Authorization: <AGENT_HTTP_AUTH_KEY>

说明:

  • 不使用 Bearer 前缀
  • 服务端从 .env 中读取 AGENT_HTTP_AUTH_KEY
  • 如果服务端未配置该值,请求会返回 503
  • 如果请求头缺失或值不匹配,请求会返回 401

2.2 CORS

当前服务已启用 CORS。

  • 默认允许本地开发来源:localhost127.0.0.1 的任意端口
  • 当前默认允许的方法:GETPOSTOPTIONS
  • 当前默认允许所有请求头
  • 如果前端部署到其他域名,需要后端调整 .env 中的 CORS 配置

3. 健康检查

3.1 请求

GET /healthz

3.2 成功响应

{
  "status": "ok"
}

4. 路线规划接口

4.1 请求

POST /route/plan
Authorization: <AGENT_HTTP_AUTH_KEY>
Content-Type: application/json

4.2 请求体

{
  "task_name": "multi-destination-route-planning",
  "origin_mode": "fixed",
  "origin_name": "北京站",
  "origin_address": "北京站",
  "origin_city": "北京市",
  "destination_name": "天安门",
  "destination_address": "天安门",
  "destination_city": "北京市",
  "stops": [
    {
      "name": "王府井",
      "address": "王府井",
      "city": "北京市",
      "contact": null
    }
  ],
  "route_strategy": "shortest_distance",
  "transport_mode": "driving",
  "need_deep_link": true,
  "deep_link_mode": "auto",
  "need_html": false,
  "max_permutations": 10
}

4.3 请求字段说明

  • task_name
    • 可选
    • 默认值为 multi-destination-route-planning
  • origin_mode
    • 必填
    • 可选值:fixedcurrent_location
  • origin_name
    • 可选
    • 起点展示名称
  • origin_address
    • origin_mode=fixed 时必填
  • origin_city
    • 可选
  • destination_name
    • 可选
  • destination_address
    • 必填
  • destination_city
    • 可选
  • stops
    • 必填
    • 至少 1 个元素
  • route_strategy
    • 可选
    • 可选值:shortest_distancefastest_timebalanced
  • transport_mode
    • 当前固定为 driving
  • need_deep_link
    • 可选
    • 当前必须为 true
    • 该服务的成功结果必须包含 deep link
  • deep_link_mode
    • 可选
    • 可选值:personal_maproute_planauto
  • need_html
    • 可选
    • 当前建议始终传 false
  • max_permutations
    • 可选
    • 本次请求希望允许的候选上限
    • 不能超过服务端上限

5. 成功响应结构

5.1 响应示例

{
  "success": true,
  "origin_mode": "fixed",
  "resolved_origin": {
    "role": "origin",
    "input_name": null,
    "input_address": "北京站",
    "resolved_name": "北京站",
    "city": "北京市",
    "district": "东城区",
    "location": "116.427354,39.902830",
    "lon": 116.427354,
    "lat": 39.90283,
    "poi_id": null,
    "source": "geo",
    "confidence_note": "地址解析高置信"
  },
  "resolved_destination": {
    "role": "destination",
    "input_name": null,
    "input_address": "天安门",
    "resolved_name": "天安门",
    "city": "北京市",
    "district": "东城区",
    "location": "116.397463,39.909187",
    "lon": 116.397463,
    "lat": 39.909187,
    "poi_id": null,
    "source": "geo",
    "confidence_note": "地址解析高置信"
  },
  "resolved_stops": [
    {
      "role": "stop",
      "input_name": null,
      "input_address": "王府井",
      "resolved_name": "王府井",
      "city": "北京市",
      "district": "东城区",
      "location": "116.412422,39.908966",
      "lon": 116.412422,
      "lat": 39.908966,
      "poi_id": "B000A8WS91",
      "source": "search_detail",
      "confidence_note": "北京市内热点地名POI查询高置信"
    }
  ],
  "candidates": [
    {
      "stop_order_labels": ["王府井"],
      "full_order_labels": ["北京站", "王府井", "天安门"],
      "legs": [
        {
          "from_label": "北京站",
          "to_label": "王府井",
          "origin_location": "116.427354,39.902830",
          "destination_location": "116.412422,39.908966",
          "distance_m": 3014,
          "duration_s": 845
        },
        {
          "from_label": "王府井",
          "to_label": "天安门",
          "origin_location": "116.412422,39.908966",
          "destination_location": "116.397463,39.909187",
          "distance_m": 2858,
          "duration_s": 1158
        }
      ],
      "total_distance_m": 5872,
      "total_duration_s": 2003,
      "ranking_reason": "仅有的可行路线,总距离最短"
    }
  ],
  "best_route": {
    "stop_order_labels": ["王府井"],
    "full_order_labels": ["北京站", "王府井", "天安门"],
    "legs": [
      {
        "from_label": "北京站",
        "to_label": "王府井",
        "origin_location": "116.427354,39.902830",
        "destination_location": "116.412422,39.908966",
        "distance_m": 3014,
        "duration_s": 845
      },
      {
        "from_label": "王府井",
        "to_label": "天安门",
        "origin_location": "116.412422,39.908966",
        "destination_location": "116.397463,39.909187",
        "distance_m": 2858,
        "duration_s": 1158
      }
    ],
    "total_distance_m": 5872,
    "total_duration_s": 2003,
    "ranking_reason": "仅有的可行路线,总距离最短"
  },
  "deep_links": {
    "personal_map": null,
    "android_route_plan": "androidamap://route?...",
    "ios_route_plan": "iosamap://route?..."
  },
  "summary": "本次规划从北京站出发途经王府井最终到达天安门总距离约5.87公里总耗时约33分钟符合最短距离策略要求。",
  "warnings": [
    "路线时长受实时交通状况影响,实际行驶可能存在偏差",
    "若起点为当前位置,最优路线可能随定位变化调整"
  ]
}

5.2 顶层字段说明

  • success
    • 是否成功生成路线规划结果
  • origin_mode
    • 与请求保持一致
  • resolved_origin
    • origin_mode=fixed 时通常不为 null
    • origin_mode=current_location 时通常为 null
  • resolved_destination
    • 终点解析结果
  • resolved_stops
    • 所有途经点解析结果
  • candidates
    • 所有候选路线
  • best_route
    • 被选中的最佳路线
  • deep_links
    • 给前端做按钮跳转使用
    • 这是唯一应被前端当作链接处理的字段
    • 当前成功结果至少会包含 personal_map
  • summary
    • 可直接展示给用户的简要说明
    • 这是纯展示文案,不是结构化链接字段,也不应被前端解析为跳转地址
  • warnings
    • 风险提示和降级说明,前端建议展示

6. 关键嵌套结构说明

6.1 ResolvedPoint

  • role
    • originstopdestination
  • input_name
    • 原始输入名
  • input_address
    • 原始输入地址
  • resolved_name
    • 实际命中的名称
  • city
    • 城市名
  • district
    • 区县名
  • location
    • lon,lat 字符串
  • lon
    • 经度
  • lat
    • 纬度
  • poi_id
    • 高德 POI ID可能为空
  • source
    • geotext_searchsearch_detailmanual_fallback
  • confidence_note
    • 命中说明

6.2 CandidateRoute

  • stop_order_labels
    • 仅包含中间途经点顺序
  • full_order_labels
    • 包含起点和终点的完整顺序
  • legs
    • 每段路线信息
  • total_distance_m
    • 总距离,单位米
  • total_duration_s
    • 总时长,单位秒
  • ranking_reason
    • 为什么这条路线被这样排序
  • personal_map
    • 点位导入型链接
    • 适合把一组点位导入到高德地图
    • 更偏“查看/导入点位方案”,不是严格的即时导航协议
  • android_route_plan
    • Android 导航链接
  • ios_route_plan
    • iOS 导航链接

补充说明:

  • deep_links 中可能同时存在多个字段,也可能只有其中一个字段有值
  • 当前实现中,成功结果会强制生成 personal_map
  • 前端应只根据 deep_links 的字段值控制按钮展示,不要依赖 summary 推断应展示哪个按钮
  • summary 里可能会提到“个人地图链接”或“导航链接”,但这里只是说明文字,不保证包含真实 URL
  • 如果 personal_map 存在,表示当前更适合导入点位方案
  • 如果 android_route_planios_route_plan 存在,表示当前可以直接拉起导航

前端建议:

  • 如果值为 null,对应按钮不要展示
  • 如果 warnings 非空,建议在页面显式展示提示
  • summary 只用于文案展示,不要从 summary 中抽取链接或做业务判断

7. 错误响应

7.1 422 输入或护栏错误

出现以下情况时,接口会返回 422

  • 请求结构不合法
  • stops 为空
  • need_deep_link=false
  • 固定起点缺少 origin_address
  • 终点同时出现在 stops
  • 任意点未能解析到足够精确的 POI
  • 任意点缺少 poi_id
  • 请求候选上限超过服务上限
  • 实际排列数超过上限

错误可能有两种形态。

形态一FastAPI/Pydantic 字段校验错误

{
  "detail": [
    {
      "type": "value_error",
      "loc": ["body"],
      "msg": "Value error, stops must contain at least one stop",
      "input": {
        "origin_mode": "fixed",
        "origin_address": "北京站",
        "destination_address": "天安门",
        "stops": []
      }
    }
  ]
}

形态二:服务护栏错误

{
  "detail": "Candidate permutations exceed the configured limit: stops=4, permutations=24, limit=20"
}

7.2 503 配置错误

出现以下情况时,接口会返回 503

  • 模型配置缺失
  • MCP 配置缺失
  • MCP transport 非法

错误示例:

{
  "detail": "Missing required environment variable: AMAP_MCP_URL"
}

7.3 500 内部错误

模型运行失败、第三方异常或未预期错误会返回 500。

7.4 504 上游超时

当模型服务或地图 MCP 服务超时,接口会返回 504。

错误示例:

{
  "detail": "Upstream request timed out: ..."
}

前端建议:

  • 422 显示明确的用户提示
  • 503 显示“服务暂不可用”
  • 504 显示“请求处理超时,请稍后重试”
  • 500 显示通用错误提示,并建议重试

8. 前端对接建议

  • 直接按 summarybest_routewarnings 渲染即可完成第一版页面
  • 如果需要路线详情页,可渲染 candidates 对比卡片
  • 统一使用 deep_links 控制跳转按钮显隐
  • warnings 保持可见,不要吞掉
  • current_location 场景,要准备接受 resolved_origin=null

9. 当前接口现状

  • 当前接口已经可用
  • 当前已验证真实请求可成功返回结果
  • 当前返回结构已稳定,可作为第一版前端对接基础
  • 当前 need_html 还未真正实现 HTML 返回,不建议前端依赖该字段做页面内容请求

10. 对接 TODO

  • 前端确认是否需要候选路线对比视图
  • 前端确认 deep link 的按钮交互形式
  • 前后端统一 422 错误展示文案
  • 后续若输出结构调整,需要同步更新本文档

11. 装载规划接口

11.1 请求

POST /load/plan
Authorization: <AGENT_HTTP_AUTH_KEY>
Content-Type: application/json

11.2 请求体

{
  "merchant_id": 1,
  "area": "中大",
  "license_plate": "粤A4Y0Y5"
}

11.3 请求字段说明

  • merchant_id
    • 必填
    • 商户 ID
  • area
    • 必填
    • 按区域筛选待出货出货单,精确匹配
  • license_plate
    • 必填
    • 目标运输车辆车牌号

11.4 成功响应示例

{
  "success": true,
  "license_plate": "粤A4Y0Y5",
  "selected_shipment_ids": [13],
  "summary": "待出货出货单共1个出货单ID13的销售品幅宽均为170cm判定为针织面料包含2条销售品未超过车辆针织最大装载容量120条因此选择该出货单进行装载。",
  "warnings": [
    "当前装载条数为2条未达到针织面料最大运输容量120条属于欠载方案。"
  ]
}

11.5 响应字段说明

  • success
    • 是否成功完成装载规划
  • license_plate
    • 本次规划对应的车牌号
  • selected_shipment_ids
    • 被选中的出货单 ID 列表
  • summary
    • 可直接展示给用户的装载说明
  • warnings
    • 欠载、字段缺失、无法判定面料等提示信息

11.6 错误语义

  • 401
    • Authorization 请求头缺失或值不正确
  • 422
    • 装载规则护栏错误或输入不满足规划要求
  • 503
    • 装载模型配置、业务 API 配置或服务端鉴权配置缺失
  • 502
    • 上游业务 API 返回错误,例如车辆不存在或接口请求失败
  • 504
    • 装载模型或业务 API 超时
  • 500
    • 未预期的内部错误

11.7 当前行为说明

  • Agent 会先查询指定 area 的未出货出货单,再查询指定车牌的车辆容量
  • 面料类型仅通过 sales_items[].printing_job_width 判断
  • 装载量按销售品条数计算,不按 quantity 长度或米数计算
  • 默认不允许混装针织和梭织
  • 优先选择不超载且尽量接近最大容量的方案
  • 如果只能欠载,会在 warnings 中明确提示