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

549 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Geo Agent API 对接文档
## 1. 文档目的
本文档面向前端对接,说明当前可用接口、请求结构、响应结构、错误语义和对接建议。
## 2. 服务概览
当前服务提供两个 HTTP 接口:
- `GET /healthz`
- `POST /route/plan`
- `POST /load/plan`
默认本地开发地址示例:
```text
http://127.0.0.1:8000
```
### 2.1 Authorization
`GET /healthz` 外,当前业务接口都需要通过 `Authorization` 请求头进行鉴权。
请求头格式:
```text
Authorization: <AGENT_HTTP_AUTH_KEY>
```
说明:
- 不使用 `Bearer` 前缀
- 服务端从 `.env` 中读取 `AGENT_HTTP_AUTH_KEY`
- 如果服务端未配置该值,请求会返回 `503`
- 如果请求头缺失或值不匹配,请求会返回 `401`
### 2.2 CORS
当前服务已启用 CORS。
- 默认允许本地开发来源:`localhost``127.0.0.1` 的任意端口
- 当前默认允许的方法:`GET``POST``OPTIONS`
- 当前默认允许所有请求头
- 如果前端部署到其他域名,需要后端调整 `.env` 中的 CORS 配置
## 3. 健康检查
### 3.1 请求
```http
GET /healthz
```
### 3.2 成功响应
```json
{
"status": "ok"
}
```
## 4. 路线规划接口
### 4.1 请求
```http
POST /route/plan
Authorization: <AGENT_HTTP_AUTH_KEY>
Content-Type: application/json
```
### 4.2 请求体
```json
{
"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`
- 必填
- 可选值:`fixed``current_location`
- `origin_name`
- 可选
- 起点展示名称
- `origin_address`
- `origin_mode=fixed` 时必填
- `origin_city`
- 可选
- `destination_name`
- 可选
- `destination_address`
- 必填
- `destination_city`
- 可选
- `stops`
- 必填
- 至少 1 个元素
- `route_strategy`
- 可选
- 可选值:`shortest_distance``fastest_time``balanced`
- `transport_mode`
- 当前固定为 `driving`
- `need_deep_link`
- 可选
- 当前必须为 `true`
- 该服务的成功结果必须包含 deep link
- `deep_link_mode`
- 可选
- 可选值:`personal_map``route_plan``auto`
- `need_html`
- 可选
- 当前建议始终传 `false`
- `max_permutations`
- 可选
- 本次请求希望允许的候选上限
- 不能超过服务端上限
## 5. 成功响应结构
### 5.1 响应示例
```json
{
"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`
- `origin``stop``destination`
- `input_name`
- 原始输入名
- `input_address`
- 原始输入地址
- `resolved_name`
- 实际命中的名称
- `city`
- 城市名
- `district`
- 区县名
- `location`
- `lon,lat` 字符串
- `lon`
- 经度
- `lat`
- 纬度
- `poi_id`
- 高德 POI ID可能为空
- `source`
- `geo``text_search``search_detail``manual_fallback`
- `confidence_note`
- 命中说明
### 6.2 CandidateRoute
- `stop_order_labels`
- 仅包含中间途经点顺序
- `full_order_labels`
- 包含起点和终点的完整顺序
- `legs`
- 每段路线信息
- `total_distance_m`
- 总距离,单位米
- `total_duration_s`
- 总时长,单位秒
- `ranking_reason`
- 为什么这条路线被这样排序
### 6.3 DeepLinks
- `personal_map`
- 点位导入型链接
- 适合把一组点位导入到高德地图
- 更偏“查看/导入点位方案”,不是严格的即时导航协议
- `android_route_plan`
- Android 导航链接
- `ios_route_plan`
- iOS 导航链接
补充说明:
- `deep_links` 中可能同时存在多个字段,也可能只有其中一个字段有值
- 当前实现中,成功结果会强制生成 `personal_map`
- 前端应只根据 `deep_links` 的字段值控制按钮展示,不要依赖 `summary` 推断应展示哪个按钮
- `summary` 里可能会提到“个人地图链接”或“导航链接”,但这里只是说明文字,不保证包含真实 URL
- 如果 `personal_map` 存在,表示当前更适合导入点位方案
- 如果 `android_route_plan``ios_route_plan` 存在,表示当前可以直接拉起导航
前端建议:
- 如果值为 `null`,对应按钮不要展示
- 如果 `warnings` 非空,建议在页面显式展示提示
- `summary` 只用于文案展示,不要从 `summary` 中抽取链接或做业务判断
## 7. 错误响应
### 7.1 422 输入或护栏错误
出现以下情况时,接口会返回 422
- 请求结构不合法
- `stops` 为空
- `need_deep_link=false`
- 固定起点缺少 `origin_address`
- 终点同时出现在 `stops`
- 任意点未能解析到足够精确的 POI
- 任意点缺少 `poi_id`
- 请求候选上限超过服务上限
- 实际排列数超过上限
错误可能有两种形态。
形态一FastAPI/Pydantic 字段校验错误
```json
{
"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": []
}
}
]
}
```
形态二:服务护栏错误
```json
{
"detail": "Candidate permutations exceed the configured limit: stops=4, permutations=24, limit=20"
}
```
### 7.2 503 配置错误
出现以下情况时,接口会返回 503
- 模型配置缺失
- MCP 配置缺失
- MCP transport 非法
错误示例:
```json
{
"detail": "Missing required environment variable: AMAP_MCP_URL"
}
```
### 7.3 500 内部错误
模型运行失败、第三方异常或未预期错误会返回 500。
### 7.4 504 上游超时
当模型服务或地图 MCP 服务超时,接口会返回 504。
错误示例:
```json
{
"detail": "Upstream request timed out: ..."
}
```
前端建议:
- 422 显示明确的用户提示
- 503 显示“服务暂不可用”
- 504 显示“请求处理超时,请稍后重试”
- 500 显示通用错误提示,并建议重试
## 8. 前端对接建议
- 直接按 `summary``best_route``warnings` 渲染即可完成第一版页面
- 如果需要路线详情页,可渲染 `candidates` 对比卡片
- 统一使用 `deep_links` 控制跳转按钮显隐
-`warnings` 保持可见,不要吞掉
-`current_location` 场景,要准备接受 `resolved_origin=null`
## 9. 当前接口现状
- 当前接口已经可用
- 当前已验证真实请求可成功返回结果
- 当前返回结构已稳定,可作为第一版前端对接基础
- 当前 `need_html` 还未真正实现 HTML 返回,不建议前端依赖该字段做页面内容请求
## 10. 对接 TODO
- 前端确认是否需要候选路线对比视图
- 前端确认 deep link 的按钮交互形式
- 前后端统一 422 错误展示文案
- 后续若输出结构调整,需要同步更新本文档
## 11. 装载规划接口
### 11.1 请求
```http
POST /load/plan
Authorization: <AGENT_HTTP_AUTH_KEY>
Content-Type: application/json
```
### 11.2 请求体
```json
{
"merchant_id": 1,
"area": "中大",
"license_plate": "粤A4Y0Y5"
}
```
### 11.3 请求字段说明
- `merchant_id`
- 必填
- 商户 ID
- `area`
- 必填
- 按区域筛选待出货出货单,精确匹配
- `license_plate`
- 必填
- 目标运输车辆车牌号
### 11.4 成功响应示例
```json
{
"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` 中明确提示