Files
geo-setp/docs/agent_design.md
2026-04-13 16:29:27 +08:00

250 lines
7.9 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 设计文档
## 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 服务配置方式变化