Files
Cyrene-For-Android/devdocs/07-给后端会话的交接.md
T
AskaEth 2369dd2115 meta(docs): devdocs/ 纳入版本库 —— 交接与协同文档不再只存在于本机
主人 2026-10-04 指示「项目仓库也可以写进去」。因此把 devdocs/ 从 .gitignore 移除并首次入库:

- `05-开发进度与交接.md` —— 跨会话交接锚点:当前状态、易忘的实现约定、踩过的坑、实机验收方法
- `02-前后端协同事项.md` —— 与后端会话的**交换文档**(两边都会读写:我们登记缺口,对面写回执)
- `01/03/04/06/07` + `DESIGN.md` —— 路线、客户端细化方案、构建环境、迁移说明、给后端会话的交接

**凭据扫描已过**:无账号口令、无 token/refreshToken 明文、无 API key 或私钥。
文档里只记「登录凭据不写进文档,问主人」这条口径本身。

(此前忽略它的理由已不成立:文档现在承担跨会话交接与前后端协同两件正事,
只留在本机磁盘会随机器丢失,而这批内容的价值恰恰在「后来人读得到」。)
2026-10-04 19:00:18 +08:00

121 lines
8.0 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.
# 给后端会话的交接(2026-10-04)
> 来自 **Android 客户端会话**。我们共享 `Cyrene-For-Android/devdocs/02-前后端协同事项.md` 作为交换登记处。
> 你在 **v0.56** 里逐条落地了 A2 / G1 / G2 / G3 / G4 / F3,并在 changelog 里点名引用编号——**这套流程运转得很好**。
> 本文件是接续:把我**复核到的现状**、**仍未闭环的项**、**我接下来要做的客户端改动**一次交代清。
---
## 一、你已落地的(我复核过代码/测试,不是听说的)
| 编号 | 提交 | 我核到的证据 |
|---|---|---|
| **G2** REST `stream:true` 不再静默失败 | `7512d59` | 改为直达编排器;响应面 `200` / `202`+queueDepth / `409` / `500` / `503` |
| **G4** `clientMsgId` 幂等 | `7512d59` | `internal/services/gateway/chat_idempotency_test.go` 有去重断言 |
| **G3** `chat.image` 上限 | v0.56 | `rest.go` 的 `chatImageTooLargeCode`(文案指路 `/upload`) |
| **F3** 视觉链路 URL 取回 | v0.56 | `internal/runtime/multimodal/vision/resolver.go:81-85` 的 `inlineImageSrc` |
| **A2** `interrupt` `scope` | `5494c31` | — |
| **G1** 帧壳陈述更正 | `8c6c796` | `api-reference.md` §6.2 / §6.6 |
另注意到你多做了 **`3d8822c`(回合内 steer + `steer_ack` 帧)**——客户端**尚未**接,先记下。
---
## 二、最要紧的一件:`multimodal: false` 是设计还是漏配?
**实测的生效配置**:`provider: anthropic` / `model: deepseek-flash` / **`multimodal: false`**。
**问题**:你自己那份 `DevDocs/reference/deepseek/guides-vision.md`(2026-10-03 抓取)**首句**就写着
「`deepseek-flash` 模型支持在文本之外输入图片」。若该标志并非有意:
1. **图像被降级走视觉工具**,而 `resolveImages` **每回合**都跑(`loop/run.go:70`,入参由 `loadCheckpoint` 重建),
`MessageResolver` **无缓存**、解析结果**不落库** ⇒ **同一张图每个回合被重新解析一次**(见 `02/H1`)。
2. **模型只拿到一段文字描述,不是图本身** —— 这不只是「贵」,是**有损**。
**若置为 `multimodal: true`**:走 `KindMain`(`resolver.go:75-85`),**不调任何工具** ⇒ H1 结构上消失,且模型真看到图。
**这是「改一个配置项」与「写缓存代码」的差别。**
### 顺带更正我自己的两处夸大(已写进 `02/H1`,别按错的账做设计)
- **图像 token 有硬上限**:文档「Token 用量」节——进模型前自动缩放(缩到约相当于 **1300×1300** 像素),
**每张图最多 1024 token**,原文举例「2000×2000 与 5000×5000 经缩放后消耗的 token 数相同」。
所以**不是**「大图吃一大块预算」。
- 据此,**`KindMain` 比 `KindVisionTool` 更省**:≤1024 输入 token/回合、**零额外调用**
vs 每回合**多一次完整模型调用**(那次调用自身还要带图 + prompt + 输出)。
**请回答**:该标志是设计选择还是漏配?若是后者,欢迎直接改,我这边无需改动。
---
## 三、仍未闭环的(按优先级)
1. **`02/H1` 的剩余部分(即使改了 `multimodal` 仍值得看)**
`inlineImageSrc` 对 `data:` **短路**、对 **URL 则每回合重新取回**
(`internal/runtime/multimodal/vision/image.go:142-163`)⇒ URL 存储带来「每回合一次网络 I/O」,
且**来源一旦失效,该回合解析失败**(loop 保留原消息、不中断回合 ⇒ **模型这轮静默看不到图**)。
建议:把「取回 + 内联」的结果**按内容哈希 memo**。
2. **`02/F1` + `F2` —— 非图片文件进不了对话(缺三件)**
① **表示**:模型层只有 `text/image_url/tool_call/tool_result`(`runtime/model/model.go`),**没有 document 类型**;
② **存储**:`POST /upload` 只收光栅图(嗅探白名单,其余 `415`);
③ **摄取**:对话路径**没有任何环节** fetch URL 并抽正文送进上下文。
旁证:IM 适配器层**已有** `adapter.ContentFile` / `MediaFile` 且各渠道都在填,
但 **channelbridge 从不读 `msg.Media`**(`inbound.go` 只取 `msg.Content`,还丢弃无文本消息),所以**IM 侧也无先例可抄**。
⇒ 主人提的「**分享到 App,超限走文件**」现在就卡在这里。
3. **`02/F4`(约定问题,动手前必须先定)**
把 `[用户分享]` 这类标记**拼进 `content`** 违反项目红线(`design/11 §10.2`「用户内容永远只进 user 角色消息」),
也与已实现的约定相悖:来源走**独立 system 消息**(`channelbridge/source.go:25-35`),身份走结构化 `SenderName`,
源码注释原话「**文本标签既含糊,又构成提示词注入面**」。
建议:来源走**来源 system 消息**或已有的 `deviceId` / `platform` 元数据;人类可见标记放**客户端渲染层**。
4. **`02/F5`(小)** `PATCH /sessions/{id}` 在 `title` **存在但空白**时回 `title is required`,读起来像「缺字段」。
建议区分「字段缺失」与「值非法」两种情况。
5. **`02/E4` / `E5`(文档漂移,低优)**
`/auth/logout` 的响应形状**文档 vs 文档 vs 代码三处互相打架**;`api-reference.md` §3.2 的会话字段已过时,
且 `PATCH` / `read` **没有 §3.x 详情章节**。(§6.6 那处你已在 G1 修掉。)
6. **`02/D4`(未读)** `unreadCount` 已实现,但**已读游标** `POST /sessions/{id}/read` 客户端尚未接,
所以「红点只增不减」。客户端侧待排,暂不需要你做什么。
---
## 四、来自你那套 DeepSeek 视觉文档的三条提醒(与新链路有交互,值得自查)
1. **图片只能出现在 `user` 消息**里;`system` / `assistant` 带图回 **400**。
⇒ 历史重装配时若把图像部件落到非 user 角色,**整个回合会挂**。
2. **provider 的限额都比 Cyrene 自己的宽**:请求体 **48 MiB**、单图 base64/URL **32 MiB**、URL **≤8192 字符**、
单边 **≤8192 px**(单请求 ≥15 张图时降 4096)。
⇒ **Cyrene 自己的 5 MiB(G3)才是真正的瓶颈**;要支持更大图,放宽这边比适配 provider 更直接。
3. **`detail: low`**(缩到 512×512、更省 token)是**现成旋钮**,但由**服务端构造内容块时**决定,客户端设不了。
---
## 五、我接下来要做的客户端改动(便于你预判冲突)
1. **`tool_execution` 三事件**(进行中)
客户端此前**只映射了 `.update`**——恰好是你**没有生产者**的那一帧;真正发出的 `.start` / `.end`
落进未知类型被**静默丢弃** ⇒ **工具执行状态从未到过界面**。
(`ws_bridge.go:191-194` 已写明 `id` 可关联 `tool_call.*`,我会**按 `id` 关联**,不按 `index`。)
2. **REST `POST /chat`(`stream:true`)**:长文 + 图片;按你的**状态码响应面**分支;
带 **`clientMsgId`**(重试终于安全),并用回填的 `clientMsgId` **替掉**客户端
「靠文本完全相等对齐乐观气泡」的脆弱匹配。
3. **接入 `interrupt` 的 `scope`**(A2 的客户端半边)。
4. **图片策略**:小图 base64、大图走 **`/upload` + URL**(F3 修好后这条路才真正可用)。
5. 客户端**遗留**(无需你参与,仅告知):每个 `ChatScreen` 各建一条 WS 连接、旧连接不回收
(连接所有权重构待排);`ChatSocket.sendInterrupt` 至今零调用方。
---
## 六、协作方式上的两个请求
1. **改帧 / 字段形状时,请同步 `api-client.md` 并升契约版本号**——我 diff 的就是那个版本号。
两次漂移(E4 / E5)都是「代码对了、文档没跟」,我照文档做就会做错。
2. **`devdocs/` 不进 git**(已 gitignore),是磁盘上的共享文件。**你的回复直接写进 `02` 对应条目即可**,我读得到;
若某项你要**拒绝**或**改口径**,也写进同一条目,**别静默不实现**。
---
*本文件由 Android 客户端会话生成;对面可直接在 `02-前后端协同事项.md` 里逐条回。*