Documentation
¶
Overview ¶
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.
tool_schema_hash.go - 工具 Schema 变化追踪与缓存稳定性分类(模块 22.2).
解决的问题:
Anthropic Prompt Cache 会缓存工具列表(tools 数组). 只要工具的描述/Schema 发生任何变化,整个 tools 缓存前缀失效, 触发 cache write,该轮所有工具 token 按全价计费. 假设系统有 50 个工具,系统提示词 + 工具列表共 50k tokens: 命中缓存:50k × $3/M × 10% = $0.015/轮 未命中: 50k × $3/M = $0.15/轮(贵 10 倍) 如果一个工具描述含有动态内容(session ID,时间戳,每轮变化的列表), 每轮都会破坏所有工具的缓存.
本模块的职责:
- 精确识别哪个工具的 Schema 变了(不是"工具集变了"这种粗粒度)
- 统计每个工具的稳定性(过去 N 轮是否有变化)
- 提供 StableFirst 排序:稳定工具放前面,不稳定工具放后面 调用方据此把 cache_control 标记放在最后一个稳定工具后, 保护前缀缓存不被不稳定工具破坏
升华改进(ELEVATED): 早期实现用 djb2 32-bit 哈希, 碰撞率在 50 个工具场景下不可忽视(用于诊断日志,偶尔误判可接受). 我们改用 SHA-256:[32]byte 直接 == 比较,零分配,碰撞概率工程上可忽略. 早期方案仅做诊断日志;我们额外实现稳定性窗口 + StableFirst 排序,真正影响 API 费用. 替代方案:<沿用 djb2,只做诊断> - 否决:工具描述稳定性直接影响 Prompt Cache 命中率, 值得更可靠的哈希 + 可操作的排序输出.
Package cache 提供通用的 TTL 缓存实现.
TTLCache 是一个泛型的带过期时间的缓存,支持:
- 过期后返回旧值 + 后台异步刷新(stale-while-revalidate 模式)
- Thundering herd 防护(同一 key 只有一个 goroutine 在刷新)
- 缓存 miss 时同步加载(GetOrLoad)
- 手动设置和失效
设计要点:
- 读操作用 RWMutex,高并发读性能好
- 后台刷新用 goroutine,不阻塞读请求
- 刷新中标记防止多个 goroutine 同时刷新同一 key
Index ¶
- Variables
- type PromptHashTracker
- type TTLCache
- func (c *TTLCache[K, V]) Clear()
- func (c *TTLCache[K, V]) Close()
- func (c *TTLCache[K, V]) Get(key K) (V, bool)
- func (c *TTLCache[K, V]) GetOrLoad(key K) (V, error)
- func (c *TTLCache[K, V]) Invalidate(key K)
- func (c *TTLCache[K, V]) Len() int
- func (c *TTLCache[K, V]) Peek(key K) (V, bool)
- func (c *TTLCache[K, V]) Set(key K, value V)
- type ToolEntry
- type ToolSchemaChanges
- type ToolSchemaTracker
- func (t *ToolSchemaTracker) Reset()
- func (t *ToolSchemaTracker) StabilityReport() ToolStabilityReport
- func (t *ToolSchemaTracker) StableFirstWithBoundary(names []string) (sorted []string, stableCount int)
- func (t *ToolSchemaTracker) Track(tools []ToolEntry) ToolSchemaChanges
- func (t *ToolSchemaTracker) TurnCount() int
- type ToolStabilityReport
Constants ¶
This section is empty.
Variables ¶
var ErrNoLoader error = errNoLoader{}
ErrNoLoader 是 loader 为 nil 时 GetOrLoad 返回的哨兵错误.
Functions ¶
This section is empty.
Types ¶
type PromptHashTracker ¶
type PromptHashTracker struct {
// contains filtered or unexported fields
}
PromptHashTracker 跟踪系统提示词内容的 SHA-256 哈希.
线程安全:Check 可从任意 goroutine 调用(runLoop 主循环 + 潜在并发 Dream 请求).
精妙之处(CLEVER): 存储 [32]byte 而非 string/hex-- [32]byte 可直接用 == 比较(编译器展开为定长内存比较),无分配,无哈希碰撞. 相比 hex.EncodeToString 少一次 malloc,高频调用(每轮 API 请求前)收益明显.
func NewPromptHashTracker ¶
func NewPromptHashTracker() *PromptHashTracker
NewPromptHashTracker 创建一个新的 PromptHashTracker.
初始状态:未设置任何哈希.首次 Check 调用将返回 changed=true, 确保首次请求前必然触发 SectionCache 重置(初始化语义).
func (*PromptHashTracker) Check ¶
func (t *PromptHashTracker) Check(content []byte) (changed bool)
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 (*PromptHashTracker) LastHash ¶
func (t *PromptHashTracker) LastHash() ([32]byte, bool)
LastHash 返回当前记录的哈希(调试/监控用). 若从未调用 Check,返回全零哈希和 false.
func (*PromptHashTracker) Reset ¶
func (t *PromptHashTracker) Reset()
Reset 清除已记录的哈希,使下次 Check 一定返回 changed=true.
适用场景:
- 用户调用 /clear(完全重置会话,系统提示需重新缓存)
- 用户调用 /compact(上下文压缩后重建,缓存失效)
- 动态注入了新的 PromptBundle 或 Section(内容已知变化,跳过哈希计算)
升华改进(ELEVATED): 提供显式 Reset 而非只依靠内容哈希-- 某些场景(/clear,Bundle 切换)调用方明确知道缓存已失效, 显式 Reset 比等内容哈希检测更及时(避免哈希相同但语义已变的边界情况).
type TTLCache ¶
type TTLCache[K comparable, V any] struct { // contains filtered or unexported fields }
TTLCache 是一个泛型的带 TTL 的缓存. K 是键类型(必须 comparable),V 是值类型.
func NewTTLCache ¶
NewTTLCache 创建一个新的 TTL 缓存. ttl 是条目的生存时间. loader 是缓存 miss 时的加载函数(可以为 nil,但 GetOrLoad 会返回错误).
func (*TTLCache[K, V]) Close ¶
func (c *TTLCache[K, V]) Close()
Close 停止后台清理 goroutine 并释放资源.
精妙之处(CLEVER): sync.Once 保护 close(stopCh)-- 重复调用 Close()(如 defer + 显式调用)会 panic "close of closed channel". Once 使 Close() 幂等,调用方无需追踪是否已关闭.
func (*TTLCache[K, V]) Get ¶
Get 获取缓存值. 如果 key 存在且未过期,返回 (value, true). 如果 key 存在但已过期,返回旧值 (value, true) 并触发后台刷新. 如果 key 不存在,返回 (zero, false).
func (*TTLCache[K, V]) GetOrLoad ¶
GetOrLoad 获取缓存值,如果 miss 则同步加载. 与 Get 不同,这个方法在缓存 miss 时会阻塞等待加载完成. 如果 loader 为 nil,缓存 miss 时返回错误.
func (*TTLCache[K, V]) Invalidate ¶
func (c *TTLCache[K, V]) Invalidate(key K)
Invalidate 使指定 key 的条目失效. 下次 Get 会触发后台刷新(如果有 loader),GetOrLoad 会同步加载.
type ToolEntry ¶
ToolEntry 是提交给 ToolSchemaTracker 的最小单元.
Content 是工具的规范化字节表示--调用方负责生成,通常是 json.Marshal 后的 {name, description, input_schema} 结构体. cache_control 字段不应包含在 Content 中(它是元数据,不是工具"内容").
type ToolSchemaChanges ¶
type ToolSchemaChanges struct {
Added []string // 新增工具名(上轮没有,本轮出现)
Removed []string // 删除工具名(上轮有,本轮消失)
Changed []string // 变化工具名(同名但 Content 哈希不同)
Stable bool // true 当 Added=Removed=Changed=[]
}
ToolSchemaChanges 描述两次 Track 调用之间工具集的变化.
Stable = true 当且仅当 Added/Removed/Changed 均为空, 即本轮工具集与上一轮完全相同(字节级等价).
type ToolSchemaTracker ¶
type ToolSchemaTracker struct {
// contains filtered or unexported fields
}
ToolSchemaTracker 逐轮追踪工具 Schema 哈希,统计稳定性, 支持按稳定性排序以优化 Anthropic Prompt Cache 命中率.
线程安全:所有方法均持 mu 锁,可从任意 goroutine 调用. 生命周期:与 Engine 实例绑定(Engine 级单例),Session 不拥有 Tracker.
关键不变量:
- hashes[name] 始终是最近一次 Track 时该工具的 SHA-256
- lastChangeTurn[name] 是该工具最近一次内容变化的轮次号
- firstSeenTurn[name] 是该工具首次出现的轮次号
- turnCount 单调递增,每次 Track 调用 +1
func NewToolSchemaTracker ¶
func NewToolSchemaTracker() *ToolSchemaTracker
NewToolSchemaTracker 创建 ToolSchemaTracker,使用默认稳定性窗口(5 轮).
func NewToolSchemaTrackerWithWindow ¶
func NewToolSchemaTrackerWithWindow(window int) *ToolSchemaTracker
NewToolSchemaTrackerWithWindow 创建指定稳定性窗口的 ToolSchemaTracker. window < 1 时自动修正为 1.
func (*ToolSchemaTracker) Reset ¶
func (t *ToolSchemaTracker) Reset()
Reset 清除所有追踪状态,使下次 Track 从零开始.
适用场景:
- Plugin 系统重载(工具集可能大幅变化,旧稳定性数据无参考价值)
- Engine Close 后重用同一实例(理论上不会发生,但作为防御性接口提供)
func (*ToolSchemaTracker) StabilityReport ¶
func (t *ToolSchemaTracker) StabilityReport() ToolStabilityReport
StabilityReport 返回当前所有追踪工具的稳定性快照. 主要用于调试和 observer 事件(不影响请求路径).
func (*ToolSchemaTracker) StableFirstWithBoundary ¶
func (t *ToolSchemaTracker) StableFirstWithBoundary(names []string) (sorted []string, stableCount int)
StableFirstWithBoundary 返回按稳定性排序的工具名,以及稳定工具的数量.
返回值:
- sorted:工具名切片,前 stableCount 个是稳定工具,后面是不稳定工具
- stableCount:稳定工具的数量
调用方可据此把 cache_control 放在 sorted[stableCount-1] 对应的工具定义上, 保护前缀缓存不被后面的不稳定工具影响.
升华改进(ELEVATED): 早期实现 只计算 changedToolSchemas(诊断日志), 不影响工具列表顺序,也不放 cache_control--cache break 只能事后归因,不能预防. 我们的 StableFirstWithBoundary 让调用方能主动调整工具顺序 + cache_control 放置, 从"事后诊断"变为"主动预防",对 SDK/SaaS 场景每轮节省最多 90% 工具 token 费用. 替代方案:<仅做诊断日志,不排序> - 否决:诊断可以有,但费用优化更有价值.
func (*ToolSchemaTracker) Track ¶
func (t *ToolSchemaTracker) Track(tools []ToolEntry) ToolSchemaChanges
Track 记录本轮工具集,返回与上一轮相比的变化情况. 每次 API 请求前调用一次,入参是当轮将要发送的完整工具列表.
算法:
- turnCount++
- 逐工具计算 SHA-256(Content),与缓存哈希比较
- 新增 = 上轮没有,本轮有;删除 = 上轮有,本轮没有;变化 = 同名但哈希不同
- 更新内部状态(哈希,lastChangeTurn,firstSeenTurn)
- 返回 ToolSchemaChanges(切片已排序,便于确定性比较)
精妙之处(CLEVER): Added/Removed/Changed 切片按名称排序-- 相同变化集合的两次调用返回完全相同的切片,方便测试断言和日志去重. 替代方案:按遍历顺序(非确定性,依赖 map 迭代顺序,测试会 flaky).
func (*ToolSchemaTracker) TurnCount ¶
func (t *ToolSchemaTracker) TurnCount() int
TurnCount 返回累计 Track 轮次(调试用).
type ToolStabilityReport ¶
type ToolStabilityReport struct {
// StableTools 在过去 Window 轮内未变化.
// 把这些工具排在工具列表前面 + 在最后一个稳定工具处加 cache_control,
// 可以最大化 Anthropic Prompt Cache 命中率.
StableTools []string
// UnstableTools 在过去 Window 轮内至少变化一次.
// 排在工具列表后面,不影响前缀缓存.
UnstableTools []string
// TurnCount 当前累计轮次数(调试用).
TurnCount int
// Window 稳定性判定窗口(轮次).
Window int
}
ToolStabilityReport 是工具稳定性快照. 用于调试和外部决策(如决定 cache_control 边界放哪里).