初始化第一版

This commit is contained in:
andy committed 2026-09-05 15:46:37 +08:00
commit 8a6c31c14d
83 files changed
+14302

No files matched your search

+82
View File
@@ -0,0 +1,82 @@
# 用户对话 API v1 架构
## 1. 组件边界
```mermaid
flowchart LR
UI[用户侧应用或 curl] -->|POST /api/chat\nChat Bearer + JSON| H[Chat Handler]
H -->|Prepare / Stream| S[Chat Service]
S -->|CreateSession / StreamMessage| A[SuperAgent Chat Adapter]
A -->|Open API Key + HTTPS/SSE| SA[SuperAgent]
SA -->|独立 MCP Bearer| M[POST /mcp]
M --> P[(PostgreSQL/PostGIS)]
```
三种凭证属于不同信任方向:
- Chat Bearer:用户侧应用到本 Go 服务,仅用于首版受控联调。
- SuperAgent Open API Key:本 Go 服务到 SuperAgent,只保存在服务端。
- MCP Bearer:SuperAgent 到本 Go 服务,只保护 `/mcp`。
三者必须使用不同值。
## 2. 请求生命周期
1. Handler 校验请求方法、精确 Origin、Chat Bearer、媒体类型、Accept、请求体大小和严格 JSON。
2. Service 校验消息和本地 `conversation_id`。
3. 首轮请求由 Service 生成随机 `conversation_id`,并使用服务端固定测试主体创建 SuperAgent Session。
4. Service 在内存中保存 `conversation_id -> provider session_id`;Provider Session ID 不返回客户端。
5. Handler 开始 SSE,先返回 `conversation` 事件。
6. Adapter 把消息发送到已准备的 Session,只将经过清洗的 `run.*` / `tool.*` 进度投影给 Handler。
7. Adapter 严格确认最终内容、成功 `run.completed` 和顶层 `end` 后,Handler 才发送 `message` 与 `done`。
8. 发生断流、超时、协议错误或失败 Run 时,不返回部分回答;当前映射失效,客户端下一次应创建新对话。
Handler 在没有可公开进度时每 15 秒发送不含数据的 SSE comment heartbeat,并把单请求写期限扩展到配置的总运行 deadline 之后 5 秒,以便返回终止错误。反向代理仍需显式关闭该路径的响应缓冲并设置相容的空闲超时。
## 3. 会话状态机
```text
不存在
-> 创建 Provider Session
-> busy
-> 成功:idle
-> 上游结果不确定或失败:删除映射
idle
-> 新一轮 Prepare:busy
-> TTL 到期:惰性删除
busy
-> 同 conversation_id 的并发请求:409
```
会话存储是单进程内存映射:
- 进程重启后全部丢失。
- 多实例之间不共享,未配置粘性路由时后续轮次可能返回 404。
- 只保存随机本地 ID、Provider Session ID、占用状态和最后使用时间,不保存消息历史。
- SuperAgent 负责其 Session 内的上下文;本服务不会把完整历史在每轮重新发送。
## 4. 安全设计
- 客户端 DTO 没有 `user_id`、`external_subject_id`、角色、镇街范围、Provider Session 或 metadata 字段;未知字段直接拒绝。
- 固定测试主体由服务端配置,不能作为最终用户身份或权限依据。
- CORS 使用精确 Origin 列表,不支持 `*` 或 credentials。
- Handler 日志只记录 request ID、结果、是否复用和耗时,不记录问题、答案或对话 ID。
- 进度只允许安全字符组成的 `run.*` / `tool.*` 事件,以及工具名和状态;消息 delta、Provider ID、工具参数和结果不向客户端透传。
- 错误映射为稳定代码,不回显 Provider 响应正文、URL、Session、堆栈或 Secret。
- API 默认关闭;启用必须同时具备有效 SuperAgent 配置和独立 Chat Bearer。
## 5. 伸缩与后续替换点
当前内存实现适合单实例受控联调。进入多实例或真实用户阶段前,需要把 `ChatService` 的本地状态替换或扩展为:
- 经验证的最终用户身份与动态数据授权上下文。
- 持久化或共享的会话映射,并明确并发租约、过期和恢复语义。
- 网关限流、滥用防护、请求配额和指标。
- 对话、工具调用和安全事件的脱敏持久审计。
- 主动取消 Provider Run,以及客户端断开后的终止策略。
这些扩展不能通过简单放宽当前静态 Token 或把客户端用户字段原样传给 Agent 来实现。
既有客户端所需的 DashScope 风格协议通过独立 Handler 适配并复用本文 Chat Service,不改变原生契约。公网入口和两种协议映射见 [`public-chat-entry-v1.md`](public-chat-entry-v1.md)。
+57
View File
@@ -0,0 +1,57 @@
# 公网兼容对话入口 v1 架构
## 1. 调用链
```mermaid
flowchart LR
C[既有用户客户端] -->|HTTPS + xtoken\nDashScope 风格 JSON/SSE| N[Nginx]
N -->|HTTP 127.0.0.1:8080| D[兼容 Chat Handler]
D -->|ChatRequest / ChatTurn| S[共享 Chat Service]
S -->|Provider-neutral port| A[SuperAgent Adapter]
A -->|Open API Key + HTTPS/SSE| SA[SuperAgent]
SA -->|独立 MCP Bearer| M[同域 /mcp]
M --> P[(PostgreSQL/PostGIS)]
```
Nginx 不再直接调用 DashScope。它只负责 TLS、精确公开路径、基础限流和 SSE 传输设置;所有应用鉴权、输入校验、会话映射和上游调用都在 Go 服务内完成。
## 2. 双入站协议、单一用例
| 入站接口 | 面向对象 | 鉴权 Header | 请求字段 | 成功事件 |
| --- | --- | --- | --- | --- |
| `/api/chat` | 本项目原生客户端 | `Authorization: Bearer` | `message`、`conversation_id` | `conversation`、`progress`、`message`、`done` |
| `/api/v1/apps/{app_id}/completion` | 既有 DashScope 风格客户端 | `xtoken` | `input.prompt`、`input.session_id`、受限 `parameters` | `result`,最终 `finish_reason=stop` |
两个 Handler 都调用同一个 `ChatService.Prepare` 和 `ChatTurn.Stream`。兼容层只负责协议转换,不复制会话或 SuperAgent 业务逻辑。
## 3. 标识映射
```text
兼容 output.session_id
= 本地 conversation_id
-> Chat Service 内存映射
-> Provider session_id(永不返回客户端)
```
兼容 URL 中的 `app_id` 是配置的公开路由标识。它不能选择任意 Profile,也不参与授权;SuperAgent 目标仍由服务端 Base URL、Key 和已发布 Profile 决定。
## 4. 严格结果边界
兼容 Handler 会尽早发送一个无正文的 `result` 事件,让客户端取得会话 ID并建立 SSE。正文不会跟随上游 `message.delta` 实时透传。只有既有 Adapter 验证最终内容、成功 Run 和流终止标记后,Handler 才发送 `finish_reason=stop` 与最终文本。
这样保留了当前应急辅助场景的失败语义:断流或 Provider 协议不完整时,客户端不会把半截模型文本误当成已完成方案。代价是首版没有逐字动画;如后续必须实时输出,需要独立安全决策、取消/失败 UX 和新 Spec。
## 5. 网络与伸缩边界
- Go 推荐只监听 `127.0.0.1:8080`,公网只开放 Nginx 443;PostgreSQL 5432 不对公网开放。
- Nginx 对兼容路径关闭缓冲、缓存、gzip 和重试,超时必须覆盖 Go Chat Run Timeout。
- `/mcp` 使用独立 Bearer;Chat `xtoken`、MCP Bearer 与 SuperAgent Open API Key 三者不得复用。
- 当前会话在单进程内存中。多实例部署必须先实现共享会话映射或粘性路由;否则后续轮次可能落到另一实例并返回 404。
- 浏览器可读取静态 `xtoken`,所以该入口仍只适用于受控联调;生产最终用户入口需要真实身份认证和动态授权。
## 6. 相关文档
- [`../specs/fire-safety-ymd-dashscope-compatible-chat-v1.md`](../specs/fire-safety-ymd-dashscope-compatible-chat-v1.md)
- [`chat-api-v1.md`](chat-api-v1.md)
- [`../workflows/user-chat.md`](../workflows/user-chat.md)
- [`../project/operations/nginx-public-entry.md`](../project/operations/nginx-public-entry.md)
+73
View File
@@ -0,0 +1,73 @@
# 空间只读 MCP v1 架构
## 决策摘要
首版 MCP 内嵌在现有 Go 服务中,使用标准库实现 HTTP/JSON-RPC 协议层,使用 `pgx/v5` 原生连接池访问 PostgreSQL/PostGIS。没有引入 MCP SDK、Web 框架或 ORM。
采用这一边界是因为当前只需要 SuperAgent 已验证的 `2025-06-18` 四个方法和同步 JSON 响应;业务复杂度位于固定空间查询、权限范围和安全语义,而不是协议框架。
## 模块关系
```mermaid
flowchart TD
APP["internal/app\n依赖装配与 readiness"]
H["internal/handler\nBearer、JSON-RPC、schema、限流边界"]
S["internal/service\n查询边界、超时、结果语义"]
D["internal/domain\n稳定空间领域对象"]
R["internal/repository\npgxpool、固定参数化 PostGIS SQL"]
DB["8 张既有 PostGIS 表"]
APP --> H
APP --> S
APP --> R
H --> S
S --> D
S --> R
R --> D
R --> DB
```
Handler 不知道物理表名,Repository 不组织自然语言回答,Domain 不依赖 MCP 或 pgx。可信数据范围在应用装配时由配置传入 Service,并由 Repository 在 SQL 中应用:默认 `town_allowlist` 使用参数化镇街数组过滤;显式 `all` 使用服务端布尔参数放开镇街过滤。范围选择不进入工具 schema,模型无法扩大权限。
## 数据映射
| 领域结果 | 物理表 |
| --- | --- |
| 地名候选 | 以下 8 张表的名称、镇街、村庄和几何字段 |
| 网格上下文、责任中队 | `st_2_fanghuowangge` |
| 水源候选 | `st_2_mpslfh_t_slfh_syd`、`st_2_xianyouxushuichiguan` |
| 指挥部候选设施 | `st_2_fanghuojianchazhan`、`st_2_fanghuoliaowangshao` |
| 通道候选 | `st_2_xianyoufanghuotongdao` |
| 风险区域 | `st_2_mudifenqu_mian`、`st_2_linqugongkuangqiye` |
姓名、联系电话和值班人员字段不进入领域结果,也没有出现在查询 SELECT 列表中。
## 空间约束
- MCP 入参固定为 WGS84 经纬度。
- 地名搜索是坐标查询前的候选发现:记录点直接返回二维坐标;线使用首个组成线的起点,面使用 `ST_PointOnSurface` 产生代表点,并以 `location_kind` 明确区分。代表点不得自动升级为用户确认的演练点。
- 防火通道原始 `geom` 已确认混合二维与 Z 维度,导入层必须使用不限定 typmod 的 `geometry` 保存原始事实。距离/最近点等 MCP v1 运算按二维地表语义解释;若底层函数需要降维,只能在只读查询或派生层显式处理,不得回写或静默修改原始几何。
- 启用前要求实库所有非空几何 SRID 为 4326、类型符合表用途且坐标位于 WGS84 合法范围;这些属于硬门禁。
- 点到点、点到线、点到面距离使用 PostGIS `geography` 米制计算。
- 面覆盖使用 `ST_Covers`,边界上的点也视为位于网格/风险区内。
- SQL 同时检查 SRID、类型和有效性。无效或空几何不自动修复、不改写原始事实,而是从全部 MCP 查询中排除;readiness 和工具结果明确告警结果可能不完整。readiness 发现异常类型、非 4326 SRID 或越界坐标时仍拒绝启用。
- 2026-09-05 实库 audit 发现防火网格 4 条、林区工矿企业 4 条、墓地坟区 27 条无效面几何。用户选择首版排除这 35 条记录,后续如需修复必须在派生副本中审查,不覆盖原始 `geom`。
- 距离结果只是地理邻近,不包含地形、路网、火势、天气和实时通行信息。
## 依赖决策
`github.com/jackc/pgx/v5 v5.10.0` 是当前唯一新增的直接依赖。项目只面向 PostgreSQL,并需要明确的连接池、context 和 PostgreSQL 参数行为,因此使用原生 `pgxpool`;空间计算仍全部由参数化 SQL/PostGIS 完成。
## 后续演进边界
- 动态用户/组织授权到位后,用可信身份解析器替换当前每 Token 的静态数据库全范围/镇街白名单,工具 schema 不增加可伪造的授权字段。
- 路线规划必须新增专门的图网络/地形数据和高风险 Spec,不能扩写当前通道候选工具的描述来冒充路线能力。
- 联系人字段如确需开放,必须有字段级权限、脱敏、审计和单独工具,不直接扩展当前结果。
- 写入、派遣或状态变更工具需要人工确认、幂等、审计和独立安全评审。
## 协议与依赖依据
- [MCP 2025-06-18 Lifecycle](https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle)
- [MCP 2025-06-18 Streamable HTTP Transport](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports)
- [MCP 2025-06-18 Tools](https://modelcontextprotocol.io/specification/2025-06-18/server/tools)
- [pgx 官方仓库与版本策略](https://github.com/jackc/pgx)