Skip to content

fix(diary): 防止消息较多时生成残缺日记并封盘 - #352

Merged
ILoveBingLu merged 1 commit into
ILoveBingLu:mainfrom
garnetlyx:fix/diary-generation-overflow
Aug 19, 2026
Merged

fix(diary): 防止消息较多时生成残缺日记并封盘#352
ILoveBingLu merged 1 commit into
ILoveBingLu:mainfrom
garnetlyx:fix/diary-generation-overflow

Conversation

@garnetlyx

Copy link
Copy Markdown
Contributor

修复

日记生成现在会根据当前模型的上下文窗口统一分配素材预算,避免消息较多时把当天聊天、AI 对话、BOOKMARKS 和未读消息一次性塞满上下文。

  • 新增日记素材预算器:以 UTF-8 字节作为 tokenizer 无关的保守上界,按 5:3:1:1 优先保留当天聊天、AI 对话、BOOKMARKS 和未读消息
  • 为固定提示和最多 4096 输出 token 预留空间;未知模型按保守的 32K 上下文处理,自定义提示也计入预算
  • 检查 AI SDK finishReason === "length",拒绝达到输出长度上限的结果
  • 写盘前校验一级标题、## 记忆线索 和非空索引项目,避免保存被截断的 Markdown
  • 生成失败时不再用“日记没有完全写成 / 零散对话痕迹”覆盖已有日记,也不 finalize;下一次夜间检查仍可重试同一日期
  • BOOKMARKS 长期整理与日记正文生成解耦,正文失败时仍尝试整理
  • 极小上下文无法容纳任何素材时返回明确错误,不伪造空日记

测试

新增 test:diary-source-budget,覆盖:

  • 纯中文大素材的预算上限和来源优先级
  • 未知模型上下文
  • 自定义提示占用
  • 极小上下文
  • 中英混合与 emoji 的 UTF-8 安全截断
  • 完整与截断 Markdown 的结构校验

本地验证:

npm run test:diary-source-budget
npx tsc --noEmit
npx vite build
git diff --check

以上均通过。Vite 构建仅有项目现存的 Rollup 警告。

Fixes #351

@ILoveBingLu ILoveBingLu left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢 PR。方向同意:不封盘、不覆盖已有日记、校验截断、BOOKMARKS 整理解耦都是对的;分配器本身(code point 迭代、UTF-8 安全截断、剩余额度按权重重分配、空来源剔除)我跑过代码确认没问题,测试通过、tsc 无新增错误。但按原样合并会让默认配置的用户在普通日子也被截断,且有一处对推理模型的回归,请先处理以下几点:

P1. 默认 / 自定义服务商永远走"未知模型 → 32K",素材被砍到原来的 1/3~1/6(必改)

resolveProviderConfig.ts:40def.modelDetailscontextWindow,而 catalog.tsrelayone(默认推荐)和 custommodelDetails 是空数组,所以这两类用户 contextWindow 恒为 undefined。diarySourceBudget.ts:10UNKNOWN_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:17DEFAULT_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.tsCHECK_INTERVAL_MS = 1hgetDailyConsolidationTarget 只返回"昨天"。失败不更新 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:224_192 应为 8_192(目前恰好都算出 0,断言碰巧成立)。
  • "上下文不足"分支抛错前仍调用 consolidateDailyBookmarks,其提示(80 行书签 + 已有记忆)同样可能装不下,只是内部吞错。

P1/P2 改完即可合,P3/P4 强烈建议一起处理。改完 @ 我。

@ILoveBingLu
ILoveBingLu merged commit fd00afd into ILoveBingLu:main Aug 19, 2026
ILoveBingLu added a commit that referenced this pull request Aug 19, 2026
跟进 #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>
@ILoveBingLu

Copy link
Copy Markdown
Owner

已合并,感谢!评审里的 P1–P4 我在 d46da5c 直接补上了:未知模型默认 128K 且按字符计数(默认/自定义服务商普通日子不再截断)、去掉 maxOutputTokens: 4096(保留 length 检查)、结构校验容忍围栏/引语/三级标题/*/有序列表、定时生成连续失败 3 次后写兜底日记并封盘、测试同步更新。下个版本发布生效。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

每日日记在消息较多时可能生成残缺内容并被永久封盘

2 participants