Skip to content

[code-review] stream_markdown 缓存前缀比较用 fence 补全后文本 — 流式输出代码块期间块级缓存复用率为 0(c5fb20a7 目标失效) #184

Description

@topcheer

文件与行号

internal/chat/stream_markdown.go:48/54(缓存判断)与 :249-255(closeOpenFences)

问题描述

缓存命中条件 strings.HasPrefix(normalized, cache.source),但 cache.source 存的是 closeOpenFences(text) 处理过的文本。当上一 chunk 停在未闭合代码 fence 内部时,closeOpenFences 给 cache.source 追加了 "\n```";下一 chunk 的 normalized 是旧文本+delta(不含这个追加的 fence),前缀匹配必然失败。

触发场景(已实测复现)

chunk1="```go\nfoo" → cache.source="```go\nfoo\n```";chunk2="```go\nfoobar"HasPrefix("```go\nfoobar\n```", "```go\nfoo\n```") = false(第 9 字符起 foo 后要求 \n 实际是 b)。控制组(无 fence 文本)缓存正常命中。

命中失败时 reuse=0,所有 blocks 全量重渲染——不仅是增长的尾部块。c5fb20a7 引入缓存的注释自述目标是避免 "every chunk re-renders the ENTIRE growing block",而流式输出代码块(恰恰是 LLM 回复最常见长块场景)期间该目标完全失效。

预期行为 vs 实际行为

  • 预期:流式期间未变块复用缓存
  • 实际:代码块未闭合期间每 chunk 全量重渲染(输出仍正确,纯性能回归,长回复+块多时 UI 卡顿风险放大)

修复建议

缓存 closeOpenFences 前的原始文本用于前缀比较,渲染时再补全 fence;或对前缀比较剥离尾部补全的 fence。

严重程度

Medium(经独立 subagent 复核 + 包内测试实测 chunk1/chunk2 均缓存未命中、无 fence 控制组命中)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions