fix(diary): 防止消息较多时生成残缺日记并封盘 - #352
Conversation
ILoveBingLu
left a comment
There was a problem hiding this comment.
感谢 PR。方向同意:不封盘、不覆盖已有日记、校验截断、BOOKMARKS 整理解耦都是对的;分配器本身(code point 迭代、UTF-8 安全截断、剩余额度按权重重分配、空来源剔除)我跑过代码确认没问题,测试通过、tsc 无新增错误。但按原样合并会让默认配置的用户在普通日子也被截断,且有一处对推理模型的回归,请先处理以下几点:
P1. 默认 / 自定义服务商永远走"未知模型 → 32K",素材被砍到原来的 1/3~1/6(必改)
resolveProviderConfig.ts:40 从 def.modelDetails 取 contextWindow,而 catalog.ts 里 relayone(默认推荐)和 custom 的 modelDetails 是空数组,所以这两类用户 contextWindow 恒为 undefined。diarySourceBudget.ts:10 的 UNKNOWN_MODEL_CONTEXT_WINDOW = 32_768 减去 8192 预留只剩 24 576 字节,中文 3 字节/字,再按 5:3:1:1 切。实测:
- 中等日(聊天 4000 字 + AI 对话 3000 字 + 书签 500 + 未读 2000):AI 对话被切到 2769 字、未读 923 字——普通日子就开始截断
- 带 4000 字自定义提示:day 2097 / conv 1257 / 419 / 419
仓库既有约定是 aiCompaction.ts:17 的 DEFAULT_CONTEXT_WINDOW = 128_000 + CHARS_PER_TOKEN = 2.8。建议未知模型默认与之对齐(或直接复用),字节换算也改成同一套估算(至少按 1 中文字 ≈ 1 token,而不是 3 字节)。"字节 = token 上界"对中文本来就保守 2~4 倍,叠加 32K 等于把 issue 里"极端日截断"换成"默认用户天天截断"。
P2. 新增的 maxOutputTokens: 4096 对推理模型是回归(必改)
generateMemoryText 之前不传上限。OpenAI Responses 的 max_output_tokens、Gemini 2.5 的 maxOutputTokens 都包含思考 token,gpt-5 / gemini-2.5-pro 在几万字素材上思考常常超过 4096 → finishReason === 'length' 且正文为空 → 被新逻辑拒绝 → 这些配置下日记每次都失败。建议去掉这个参数(预算计算里的 4096 输出预留可以保留),只保留 finishReason 检查;如果一定要安全网,至少 ≥16K。
P3. 失败重试没有上限,"下次夜间重试"的实际语义是当天每小时重跑
nightlyMemoryService.ts 的 CHECK_INTERVAL_MS = 1h,getDailyConsolidationTarget 只返回"昨天"。失败不更新 lastConsolidatedDate → 从 summaryHour 起每小时重跑(约 22 次),过了午夜该日期永远不再处理且不留任何文件(以前至少有 stub)。如果失败是确定性的(P1 的上下文不足、P2、或 P4 校验不过),就是每天约 22 次 ×(日记调用 + consolidateDailyBookmarks 的 generateObject)持续烧 API;catch 路径每次重跑书签整理,LLM 措辞略变就会产生近似重复的记忆条目。建议加失败次数上限/退避(例如同一 date 失败 N 次后写 stub 并封盘,或在 meta 记 lastDiaryFailure)。
P4. isCompleteDiaryMarkdown 对模型输出零容错
diarySourceBudget.ts:97-101 要求 trim 后第一行就是 H1、标题行严格等于 ## 记忆线索、条目必须 - 。实测以下都会判为"不完整":被 ```markdown 包裹的输出、## 记忆线索:、### 记忆线索、* / 1. 列表、前置一句"以下是日记:"。与 main 上的 prompt 本身是匹配的,但某些模型会稳定触发,结合 P3 就是每小时重试到午夜。建议:先剥离首尾 ``` 围栏;H1 用 /^#\s+/m(存在且位于记忆线索之前即可);标题用 `/^#{2,3}\s记忆线索/m`;条目允许 `[-•]` 和 `\d+[.、]`。
次要
scripts/test-diary-source-budget.ts:22的4_192应为8_192(目前恰好都算出 0,断言碰巧成立)。- "上下文不足"分支抛错前仍调用
consolidateDailyBookmarks,其提示(80 行书签 + 已有记忆)同样可能装不下,只是内部吞错。
P1/P2 改完即可合,P3/P4 强烈建议一起处理。改完 @ 我。
跟进 #352 评审意见: - 未知模型上下文默认 32K → 128K(与 aiCompaction 一致);预算与截断改为按 Unicode 字符计数(1 字符 ≈ 1 token), 默认/自定义服务商(拿不到 contextWindow)在普通日子不再被截断 - 去掉 generateText 的 maxOutputTokens: 4096:OpenAI Responses / Gemini 的输出上限包含思考 token, 推理模型会稳定触发 finishReason=length 导致日记每次失败;保留 length 检查 - isCompleteDiaryMarkdown 容忍 ```markdown 围栏、标题前引语、### 记忆线索:、*/1. 列表 - 定时生成连续失败 3 次后写入兜底日记并封盘,避免确定性错误下每小时重试到午夜且不留文件; 手动「总结今天」(finalize=false)不计数 - 上下文不足分支并入 try,失败路径不再重复整理 BOOKMARKS - 测试同步更新(含 8_192 预留修正) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
已合并,感谢!评审里的 P1–P4 我在 d46da5c 直接补上了:未知模型默认 128K 且按字符计数(默认/自定义服务商普通日子不再截断)、去掉 |
修复
日记生成现在会根据当前模型的上下文窗口统一分配素材预算,避免消息较多时把当天聊天、AI 对话、BOOKMARKS 和未读消息一次性塞满上下文。
finishReason === "length",拒绝达到输出长度上限的结果## 记忆线索和非空索引项目,避免保存被截断的 Markdown测试
新增
test:diary-source-budget,覆盖:本地验证:
以上均通过。Vite 构建仅有项目现存的 Rollup 警告。
Fixes #351