// PromptHashTracker - Prompt Cache 内容哈希刷新机制. // // 背景:Anthropic Prompt Cache 采用内容哈希匹配--相同内容的请求命中同一缓存 slot. // 当系统提示词发生变化时(如注入了新工具描述,新记忆片段,新 FLYTO.md 节), // 旧缓存 slot 自动失效,下一次请求会产生新的 cache slot(引发 cache write). // // 本模块的职责:在引擎侧感知"提示词是否变化",变化时主动重置 SectionCache, // 让 buildSystemPromptWithContext 触发完整重渲--确保 Anthropic 收到 // 与上次不同的内容,令缓存 slot 正确更新. // // 升华改进(ELEVATED): 早期实现 无此机制,每次请求都重新渲染,浪费计算; // 我们用 sha256 哈希做内容感知,仅当内容真正变化时才重置-- // 静态内容不变时 SectionCache 命中,CPU + latency 双赢. // 替代方案:<轮询时间戳判断> // - 否决:时间戳无法感知内容等价(同一内容在不同时间重新生成,哈希相同可省去重置). // // 替代方案2:<每次强制重置 SectionCache> // - 否决:SectionCache 里有 FLYTO.md 文件读取等 I/O,无谓重置浪费 latency. package cache import ( "crypto/sha256" "sync" ) // PromptHashTracker 跟踪系统提示词内容的 SHA-256 哈希. // // 线程安全:Check 可从任意 goroutine 调用(runLoop 主循环 + 潜在并发 Dream 请求). // // 精妙之处(CLEVER): 存储 [32]byte 而非 string/hex-- // [32]byte 可直接用 == 比较(编译器展开为定长内存比较),无分配,无哈希碰撞. // 相比 hex.EncodeToString 少一次 malloc,高频调用(每轮 API 请求前)收益明显. type PromptHashTracker struct { mu sync.Mutex lastHash [32]byte hasFirst bool // 首次 Check 总是视为"已变化"(从未有过初始哈希) } // NewPromptHashTracker 创建一个新的 PromptHashTracker. // // 初始状态:未设置任何哈希.首次 Check 调用将返回 changed=true, // 确保首次请求前必然触发 SectionCache 重置(初始化语义). func NewPromptHashTracker() *PromptHashTracker { return &PromptHashTracker{} } // Check 计算 content 的 sha256 哈希并与上次结果对比. // // 返回 changed=true 的情况: // - 首次调用(从未有过基准哈希) // - content 内容与上次调用时不同 // // 返回 changed=false:content 与上次完全相同(字节级等价). // // 精妙之处(CLEVER): sha256.Sum256 是一次性计算,无需 New()+Write()+Sum()-- // 对于系统提示词(通常 2-20KB),Sum256 直接计算比流式 Hash 快(无 Write 开销). // 替代方案:<用 CRC32/FNV-1a 哈希> // - 否决:CRC32 碰撞率高(提示词相近时假阴性风险),SHA256 碰撞概率可工程上忽略. func (t *PromptHashTracker) Check(content []byte) (changed bool) { h := sha256.Sum256(content) t.mu.Lock() defer t.mu.Unlock() if !t.hasFirst || t.lastHash != h { t.lastHash = h t.hasFirst = true return true } return false } // Reset 清除已记录的哈希,使下次 Check 一定返回 changed=true. // // 适用场景: // - 用户调用 /clear(完全重置会话,系统提示需重新缓存) // - 用户调用 /compact(上下文压缩后重建,缓存失效) // - 动态注入了新的 PromptBundle 或 Section(内容已知变化,跳过哈希计算) // // 升华改进(ELEVATED): 提供显式 Reset 而非只依靠内容哈希-- // 某些场景(/clear,Bundle 切换)调用方明确知道缓存已失效, // 显式 Reset 比等内容哈希检测更及时(避免哈希相同但语义已变的边界情况). func (t *PromptHashTracker) Reset() { t.mu.Lock() defer t.mu.Unlock() t.hasFirst = false t.lastHash = [32]byte{} } // LastHash 返回当前记录的哈希(调试/监控用). // 若从未调用 Check,返回全零哈希和 false. func (t *PromptHashTracker) LastHash() ([32]byte, bool) { t.mu.Lock() defer t.mu.Unlock() return t.lastHash, t.hasFirst }