package engine // context_calibrator.go - 上下文窗口自适应校准器. // // 模块定位: // // ModelInfo.ContextWindow 是从文档获取的静态默认值. // 实际运行中若遇到 context_too_long 错误,说明静态值偏大, // 或 provider 的 token 计算口径与我们的估算不同. // 校准器记录失败点,持久化到磁盘,下次启动直接用校准后的有效窗口-- // "静态值"变成"有运行时纠错机制的动态变量". // // 数据流: // // API 返回 context_too_long // → engine 调用 RecordFailure(model, actual, max) // → 校准器取 min(历史有效窗口, max * (1 - margin)) // → 持久化到 {cwd}/.flyto/context_calibration.json // → 下次 compressor.SetContextWindowFn 调用 EffectiveWindow 时返回校准值 // // 设计原则: // - fail-open:文件不存在,格式损坏,权限不足时静默降级(返回静态默认值) // - 保守策略:多次失败取最小观测值(宁可早压缩,不冒崩溃) // - 持久化:校准结果跨进程保存,daemon 重启后不从零学习 // - 无外部依赖:仅用标准库 import ( "encoding/json" "fmt" "os" "path/filepath" "sync" "time" ) // contextCalibrationSafetyMargin 是有效窗口相对于观测上限的安全余量. // // 精妙之处(CLEVER): 10% 余量来自两个因素: // 1. 我们的 token 估算(基于字符计数)误差约 5-8% // 2. provider 计算口径差异(BPE tokenizer 与字符数不完全对应) // // 若余量太小 → 仍偶发 context_too_long;太大 → 压缩过激,浪费上下文空间. // 替代方案:<固定偏移(如 -20000 tokens)> - 否决:不同模型窗口大小差异显著, // 相对比例比绝对值更通用. const contextCalibrationSafetyMargin = 0.10 // calibrationRecord 单个模型的校准记录. type calibrationRecord struct { // EffectiveWindow 是计算出的有效上下文窗口(含安全余量). // 初始为 0(未校准),由 RecordFailure 写入. EffectiveWindow int `json:"effective_window"` // ObservedMaxTokens 是从 API 错误消息中提取的 provider 申报上限. // 0 表示错误消息未携带上限信息(只有 actual token 数). ObservedMaxTokens int `json:"observed_max_tokens"` // ObservedAt 是最后一次校准的时间. ObservedAt time.Time `json:"observed_at"` // FailureCount 是触发校准的失败次数(用于分析是偶发还是系统性问题). FailureCount int `json:"failure_count"` } // ContextWindowCalibrator 记录并校准各模型的有效上下文窗口. type ContextWindowCalibrator struct { mu sync.RWMutex path string records map[string]*calibrationRecord } // NewContextWindowCalibrator 创建校准器并从磁盘加载历史记录(fail-open). // path 是持久化文件路径,通常为 {cwd}/.flyto/context_calibration.json. func NewContextWindowCalibrator(path string) *ContextWindowCalibrator { c := &ContextWindowCalibrator{ path: path, records: make(map[string]*calibrationRecord), } c.load() return c } // RecordFailure 记录一次 context_too_long 失败并更新有效窗口. // // actualTokens = 本次请求的实际 token 数(触发失败的值) // maxTokens = provider 申报的上限(0 表示无法从错误消息中提取) // // 保守策略:多次失败取历史最小有效窗口,防止单次异常拉高阈值后复发. func (c *ContextWindowCalibrator) RecordFailure(model string, actualTokens, maxTokens int) { if model == "" || (actualTokens <= 0 && maxTokens <= 0) { return } // 优先用 provider 申报的 max(精确),否则用 actual(actual > max,偏保守) observedMax := maxTokens if observedMax <= 0 { observedMax = actualTokens } effective := int(float64(observedMax) * (1.0 - contextCalibrationSafetyMargin)) c.mu.Lock() rec, ok := c.records[model] if !ok { rec = &calibrationRecord{} c.records[model] = rec } rec.FailureCount++ rec.ObservedAt = time.Now() // 保守策略:取历史最小有效窗口 if rec.EffectiveWindow == 0 || effective < rec.EffectiveWindow { rec.EffectiveWindow = effective rec.ObservedMaxTokens = observedMax } // 升华改进(ELEVATED): 在锁内序列化,锁外执行文件 I/O-- // 早期方案 save() 持锁调用 os.WriteFile(毫秒级慢操作), // 导致并发 EffectiveWindow() 调用被阻塞. // json.MarshalIndent(微秒级)仍在锁内(需访问 c.records), // 文件写入移到锁外,不阻塞其他读者. // 替代方案:<保持 save() 在锁内> - 否决:高并发下 EffectiveWindow 延迟增加. data, _ := json.MarshalIndent(c.records, "", " ") c.mu.Unlock() c.persistData(data) // fail-open,锁外执行文件 I/O } // EffectiveWindow 返回模型的有效上下文窗口. // 有校准记录则返回校准值,否则返回 staticDefault(不修改任何状态). // // 升华改进(ELEVATED): staticDefault 由调用方传入而非内部查表-- // 校准器不依赖 ModelRegistry,可被任意来源的默认值覆盖, // 测试时可传入任意值而无需 mock registry. func (c *ContextWindowCalibrator) EffectiveWindow(model string, staticDefault int) int { c.mu.RLock() defer c.mu.RUnlock() if rec, ok := c.records[model]; ok && rec.EffectiveWindow > 0 { return rec.EffectiveWindow } return staticDefault } // Records 返回所有校准记录的快照(只读,用于日志/诊断). func (c *ContextWindowCalibrator) Records() map[string]calibrationRecord { c.mu.RLock() defer c.mu.RUnlock() out := make(map[string]calibrationRecord, len(c.records)) for k, v := range c.records { out[k] = *v } return out } // load 从磁盘加载校准记录(fail-open:文件不存在或格式错误时静默忽略). func (c *ContextWindowCalibrator) load() { data, err := os.ReadFile(c.path) if err != nil { return // 首次运行或文件被删除,正常情况 } var records map[string]*calibrationRecord if err := json.Unmarshal(data, &records); err != nil { return // 文件损坏,忽略(下次 save 会覆盖) } if records != nil { c.mu.Lock() c.records = records c.mu.Unlock() } } // newContextWindowCalibrator 创建校准器,路径规则: // - cwd 非空:{cwd}/.flyto/context_calibration.json(项目级,同 compact 断路器) // - cwd 为空:{UserHomeDir}/.flyto/context_calibration.json(全局) // - 两者都失败:内存临时校准器(path="",不持久化) // // 精妙之处(CLEVER): 项目级优先于全局-- // 同一台机器上跑多个项目时,A 项目的校准失败不影响 B 项目的校准记录. func newContextWindowCalibrator(cwd string) *ContextWindowCalibrator { var dir string if cwd != "" { dir = filepath.Join(cwd, ".flyto") } else if home, err := os.UserHomeDir(); err == nil { dir = filepath.Join(home, ".flyto") } var path string if dir != "" { path = filepath.Join(dir, "context_calibration.json") } return NewContextWindowCalibrator(path) } // persistData 将已序列化的 JSON 写入磁盘(fail-open:失败静默忽略). // // 精妙之处(CLEVER): 写临时文件后 rename-- // 若进程在 WriteFile 中途崩溃,rename 不会执行,旧文件保持完整, // 避免产生半写入的损坏 JSON 文件. // 替代方案:<直接 WriteFile(path, data)> - 否决:崩溃时文件截断,下次 load 失败. // // 调用方负责在锁外调用此方法(data 已在锁内序列化). func (c *ContextWindowCalibrator) persistData(data []byte) { if c.path == "" || data == nil { return } dir := filepath.Dir(c.path) if dir != "" && dir != "." { if mkErr := os.MkdirAll(dir, 0700); mkErr != nil { return } } tmp := c.path + ".tmp" if err := os.WriteFile(tmp, data, 0600); err != nil { fmt.Fprintf(os.Stderr, "context_calibrator: write tmp failed: %v\n", err) return } if err := os.Rename(tmp, c.path); err != nil { fmt.Fprintf(os.Stderr, "context_calibrator: rename failed: %v\n", err) } }