This commit is contained in:
2026-04-13 16:29:27 +08:00
commit 962209617f
13 changed files with 4342 additions and 0 deletions

249
docs/agent_design.md Normal file
View File

@@ -0,0 +1,249 @@
# Geo Agent 设计文档
## 1. 项目目标
本项目用于实现一个“多目标地点最优路线规划”服务。
核心能力如下:
- 接收一个固定起点或当前位置语义的路线规划请求
- 解析终点和多个途经点
- 对候选送货顺序进行比较
- 逐段调用高德地图 MCP 工具计算距离和时长
- 根据策略选出最佳路线
- 返回结构化 JSON供前端直接消费
- 在需要时生成高德 deep link
当前阶段的目标不是构建一个复杂的调度系统,而是先交付一个可工作的第一版 Agent API。
## 2. 设计原则
- Prompt-first第一版以 `system_prompt` 驱动整体执行流程,优先保留灵活性
- Strong output contract最终输出必须符合固定的 Pydantic 结构
- Guardrail-driven对输入、执行规模和输出一致性增加代码护栏
- Stateless每次请求独立完成不依赖长期记忆
- Tool-grounded坐标、POI、距离、时长、deep link 必须来自地图工具,而不是模型臆造
## 3. 架构概览
当前工程结构:
- `main.py`
- FastAPI 入口
- 暴露 `/healthz``/route/plan`
- 负责 HTTP 错误映射
- `schemas.py`
- 定义请求和响应模型
- 实现输入校验
- `agent.py`
- 负责 LLM、MCP、prompt、运行护栏、结果护栏
- `.env`
- 管理模型配置、MCP 配置和运行护栏配置
运行链路:
1. 前端请求进入 FastAPI
2. Pydantic 校验请求结构
3. 服务侧执行预护栏检查
4. Agent 运行,并由 `system_prompt` 驱动模型调用高德 MCP 工具
5. Agent 返回结构化结果
6. 服务侧执行结果护栏检查
7. 返回 JSON 给前端
## 4. 关键技术选型与决策依据
### 4.1 为什么使用 pydantic_ai
- 原生支持结构化输出
- 可以直接挂接 MCP toolset
- 非常适合“模型编排 + 强类型结果”场景
- 与当前项目希望保留 prompt 灵活性的目标一致
### 4.2 为什么第一版采用 prompt-first
这次的核心业务不是纯算法问题,而是“模型理解任务 + 使用地图工具求事实 + 按规则组织返回”。
第一版采用 prompt-first 的原因:
- 业务规则还在收敛阶段prompt 更容易快速调整
- 高德 MCP 工具已经能直接提供底层能力,没有必要在第一版就再包一层 Python tool abstraction
- 当前最重要的是先建立“可靠结果 + 清晰约束 + 可持续迭代”的骨架
### 4.3 为什么不是纯 prompt-only
虽然第一版以 `system_prompt` 为核心,但没有把全部控制权放给模型。原因是以下几类问题更适合代码层兜底:
- 输入明显非法
- 候选排列爆炸
- 输出结构不一致
- 运行配置错误
因此最终方案是:
- Prompt 负责业务编排
- Pydantic 负责结构约束
- Python 代码负责运行护栏
## 5. 当前实现策略
### 5.1 LLM 和 MCP 接入
- LLM豆包 Ark OpenAI-compatible 接口
- MCP高德远程 `streamable_http`
- MCP 已验证可连通,当前可用工具包括:
- `maps_geo`
- `maps_text_search`
- `maps_search_detail`
- `maps_direction_driving`
- `maps_distance`
- `maps_schema_personal_map`
- `maps_schema_navi`
- `maps_weather`
### 5.2 Prompt 组织方式
`system_prompt` 中定义了以下行为约束:
- 必须显式调用地图工具
- 必须逐段计算驾车路线
- 必须区分 fixed origin 和 current location
- 必须按 `route_strategy` 做选择
- 不得伪造坐标、POI、距离、时长和 deep link
- 必须输出结构化结果
在实际运行时,还会再注入一次“用户级 prompt”其中包含
- 当前请求 JSON
- 本次候选顺序的硬上限
- 对起点模式的额外提醒
这样做的目的是把“长期规则”和“本次任务上下文”拆开,减少 prompt 污染。
## 6. 已实现的代码护栏
### 6.1 输入护栏
`schemas.py` 中已经实现:
- 清理空白字符串
- `stop.address` 不能为空
- `stops` 列表不能为空
- `destination_address` 不能为空
- `origin_mode=fixed` 时必须提供 `origin_address`
- 终点地址不能同时出现在 `stops`
- `max_permutations` 如果传入,必须大于 0
### 6.2 执行规模护栏
`agent.py` 中已经实现:
-`.env` 读取 `ROUTE_MAX_PERMUTATIONS`
- 当前默认值是 `20`
- 请求可以传更小值,但不能超过服务配置上限
- 在模型执行前,根据 `stops` 数量计算排列数 $n!$
- 如果排列数超过限制,直接报错并返回 422
当前策略是“超限直接拒绝”,暂不做启发式近似。
### 6.3 结果护栏
`agent.py` 中已经实现:
- 输出 `origin_mode` 必须和请求一致
- `resolved_destination.role` 必须是 `destination`
- `resolved_stops` 数量必须和输入一致
- 所有 `resolved_stops.role` 都必须是 `stop`
- `origin_mode=fixed` 时必须返回 `resolved_origin`
- `origin_mode=current_location` 时禁止返回固定 `resolved_origin`
- 成功结果必须至少有一个 candidate
- `best_route` 必须能在 `candidates` 中找到对应项
### 6.4 配置护栏
已实现以下配置检查:
- 必须提供模型 URL、API key、模型名
- MCP URL 必须存在
- MCP transport 仅允许 `streamable_http``http``sse`
- 自定义 MCP 认证头必须成对提供
- `ROUTE_MAX_PERMUTATIONS` 必须是正整数
- 模型和 MCP 的 timeout 参数必须是正数
### 6.5 超时护栏
已实现以下超时相关策略:
- 模型请求超时通过 `ARK_REQUEST_TIMEOUT_SECONDS` 配置
- MCP 连接超时通过 `AMAP_MCP_TIMEOUT_SECONDS` 配置
- MCP 读取超时通过 `AMAP_MCP_READ_TIMEOUT_SECONDS` 配置
- 上游 timeout 会被映射为 HTTP 504而不是泛化成 500
## 7. 当前已实施进展
当前已完成:
- FastAPI 服务可启动
- `/healthz` 正常返回 200
- `/route/plan` 已打通真实调用链
- 高德远程 MCP 连通性已验证
- 单个途经点请求可成功返回结构化结果
- 超限请求可返回 422并中止模型执行
当前尚未完成:
- 把文档中的每一步拆成显式 Python 服务函数
- `current_location` 场景的更细粒度行为控制
- 深链策略的更强一致性校验
- 自动化测试
- 前端展示层 HTML 输出
## 8. 关键取舍
### 8.1 已做取舍
- 选择 prompt-first而不是一开始就把所有流程写死在 Python 中
- 选择保留高德 MCP 原生工具,而不是先做二次工具封装
- 选择超限直接报错,而不是第一版就引入启发式近似搜索
- 选择结构校验和运行护栏优先,而不是先追求功能面最大化
### 8.2 这套取舍的收益
- 业务规则可快速迭代
- 系统仍然保有基本可控性
- 接口结构对前端稳定
- 便于后续逐步从 prompt-first 演进到“prompt + service function”混合架构
### 8.3 当前代价
- 模型仍然承担了较多流程理解责任
- 某些复杂场景的结果稳定性暂时依赖 prompt 质量
- 当前没有启发式优化,超限就会拒绝
- 当前上游调用仍然依赖外部服务稳定性,但 timeout 已具备可调能力和明确错误语义
## 9. 已知限制
- 当前版本的最优路线能力仍然高度依赖 LLM 遵守 prompt
- 当前对 `best_route` 的一致性校验是结构级护栏,不是全量数学证明
- `current_location` 模式尚未做专门增强
- `need_html` 目前尚未实现独立展示层
- 没有缓存机制,请求成本与工具调用次数直接相关
## 10. 下一阶段 TODO
-`current_location` 模式增加专门提示和更严格结果校验
-`deep_link_mode` 增加更明确的输出校验规则
- 追加自动化测试,覆盖成功路径和失败路径
- 将部分高频子流程下沉为显式服务函数
- 评估是否引入启发式近邻策略处理超限请求
- 增加请求日志和运行观测能力
- 增加 README 中的启动与调试说明
## 11. 文档维护建议
后续如果发生以下变更,应同步更新本文档:
- Prompt 主策略变化
- 输出结构变化
- 错误语义变化
- 护栏策略变化
- MCP 服务配置方式变化