feat: implement Context Intelligence v2 module#433
Conversation
## 目标 实现 Context Intelligence v2 模块(`vip.mate.context.intelligence`),通过动态探测每个 `(provider, model)` 的真实上下文窗口,替代原先硬编码的 128K 静态配置,优化历史消息裁剪与 token 预算分配。 **核心目标**: - 自动学习每个模型的 effective context window,按需分配 token 预算 - 信号采集与推理主线程解耦,零阻塞 - 三层降级保证:动态 snapshot → yml 静态配置 → 128K 硬编码兜底 - 零侵入扩展:新增感知维度只需加 `@EventListener`,ReasoningNode 不变 --- ## v1 复盘 → v2 重构 ### v1 问题清单(附录 A) v1 `vip.mate.context.adaptive` 模块存在 5 个 Bug、4 个死代码、2 个风格问题: | # | v1 问题 | 影响 | |---|---------|------| | Bug 1 | `loopContextWindowTokens` 参数顺序反转 | 窗口查询错位 | | Bug 2 | `GatewayDistribution.recordOverflow` 从未被调用 | 溢出时多样性检测失效(死代码路径) | | Bug 3 | `RUNTIME_MODEL_TYPE` 未注册到 `KeyStrategyFactory` | 跨节点丢失 | | Bug 4 | final-answer 路径不采集成功信号 | 仅 tool-call 路径采集,覆盖不全 | | Bug 5 | `latencyMs` 永远为 0 | 压力推断失效 | | 死代码 1-4 | 7 个 Properties 字段 / `actualModelStates` / `ModelFamily.contextProfile` / `MIN_OBSERVATION_HOURS` 均未使用 | 代码膨胀 | ### v1 架构问题 | 维度 | v1 问题 | v2 改进 | |------|---------|---------| | 通信模式 | 同步直调(ReasoningNode → Monitor → Tracker) | 事件驱动(ApplicationEventPublisher + @eventlistener) | | 读写模型 | 读写同路径,强一致 | CQRS 分离,最终一致 | | 信号覆盖 | 仅 tool-call 路径(遗漏 final-answer) | 每次 LLM 调用统一发事件,全覆盖 | | 主线程阻塞 | DB 落库同步阻塞推理线程 | 事件发布 O(1),DB 落库异步 | | 组件耦合 | ReasoningNode 直接依赖 3 层组件 | 仅依赖 `ApplicationEventPublisher` | | P10 clamp | 外部直接修改状态机 `effectiveWindow` 字段 | 移到预算规划器内作为读取天花板,状态机自治 | | 扩展方式 | 改 ReasoningNode 加调用点 | 监听器内加分支或新增 `@EventListener` | --- ## v2 机制 ### 架构:EDA + CQRS ``` ReasoningNode(推理主线程) │ ├─ 读路径(sync, lock-free, 每次 ReAct 循环) │ EnvSnapshotStore.get() → TokenBudgetPlanner.plan() → LoopBudgetConfig → LoopMessageBudgeter │ └─ 写路径(event publish, O(1) non-blocking) ApplicationEventPublisher.publishEvent(LlmSuccessSignal / LlmOverflowSignal) │ ▼ ContextSignalProcessor(@eventlistener) ├─ onSuccess: @async(signalExecutor 线程池) └─ onOverflow: sync(确保 PTL 重试前 confidenceUpper 已更新) ``` ### 核心设计原则 1. **EDA 事件驱动**:信号采集与状态更新通过 Spring 事件总线异步解耦,推理主线程零阻塞 2. **CQRS 读写分离**:写路径(事件消费 + 状态机更新)异步;读路径(snapshot 查询 + 预算规划)同步无锁 3. **调整机制与环境感知分离**:WindowProbe 状态机(上探激进/下探保守)与多样性检测/压力推断是独立关注点 4. **三层降级**:动态 snapshot → yml 静态配置 → 硬编码 fallback,任一层故障不阻断推理 5. **零侵入扩展**:新增感知维度只需监听器内加分支或新增 `@EventListener` ### 三层降级(§8.1) | 层级 | 触发条件 | 数据来源 | |------|----------|----------| | Tier 1 | EnvSnapshot 有效(`effectiveWindow > 0`) | 动态探测值 + 多因子预算规划 | | Tier 2 | EnvSnapshot 为空 | yml `getDefaultMaxInputTokens()` | | Tier 3 | yml 未配置 | 硬编码 `DEFAULT_LOOP_CONTEXT_WINDOW_TOKENS = 128_000` | ### 状态机(WindowProbe.Phase) ``` COLD → PROBING → BINARY_SEARCH → STABLE ↓ DEGRADED(压力升级时降级) ``` - **COLD**:冷启动,用 32K seed - **PROBING**:采样填满 SAMPLE_SIZE/2 时上探 - **BINARY_SEARCH**:二分收敛(收敛阈值 10%) - **STABLE**:稳定窗口,闲置 30 分钟后重探 - **DEGRADED**:压力升级时降级(连续成功 5 次恢复一级) --- ## 数据流 ### 6 条核心数据流 | 数据流 | 方向 | 同步/异步 | 触发频率 | |--------|------|-----------|----------| | 信号采集 | ReasoningNode → Event Bus | sync publish, O(1) | 每次 LLM 调用 | | 成功处理 | Event Bus → Processor → Probe/Diversity/Pressure/Snapshot/DB | async | 每次成功调用 | | 溢出处理 | Event Bus → Processor → Probe/Diversity/Pressure/Snapshot/DB | sync | 每次溢出 | | 快照刷新 | Processor → EnvSnapshotStore | 仅值变化时 | phase 切换/压力变化/多样性变化 | | 预算规划 | ReasoningNode → EnvSnapshotStore → TokenBudgetPlanner | sync, lock-free | 每次 ReAct 循环 | | DB 持久化 | Processor → Repository → DB | async | phase 切换/首次成功 | ### 成功处理流程(异步,7 步) 1. `WindowProbeRegistry.recordSuccess` → 返回 `ProbeUpdate(snapshot, previousPhase)` 2. `BackendDiversityRegistry.recordSuccess` — 多样性检测 3. `PressureInferencer.recordSuccess` — 压力推断(连续成功降级) 4. 计算 safeWindow(多样性 P10) 5. `EnvSnapshotStore.refreshIfChanged` — 条件 CAS(仅关键字段变化时刷新) 6. 条件 DB 持久化(phase 变化或首次成功时) 7. `PersistRetryQueue.retryOnce` — 重试之前失败的持久化 ### 溢出处理流程(同步,5 步) 1. `WindowProbeRegistry.recordOverflow` — 窗口收缩(`overflowShrinkRatio = 0.85`) 2. `BackendDiversityRegistry.recordOverflow` — 修复 v1 Bug 2 3. `PressureInferencer.recordOverflow` — 压力升级 4. `EnvSnapshotStore.refreshIfChanged` — 溢出必然导致窗口收缩,必刷新 5. `WindowStateRepository.persist` — 同步 DB 写入(溢出是低频事件) ### 预算规划流程(读路径,4 步) 1. 确定 base window:`EnvSnapshot.effectiveWindow` 2. 应用 P10 多样性天花板:`min(effectiveWindow, safeWindow)`(仅读取,不修改状态机) 3. 压力因子比例调整:NORMAL 0.60 / ELEVATED 0.70 / HIGH 0.80 / CRITICAL 0.85 4. 计算预算:`historyBudget = available × historyRatio`,`keepTail = historyBudget × keepTailRatio` --- ## v2 组件清单 ### 19 个组件,9 个子包 | 子包 | 组件 | 职责 | |------|------|------| | `event` | `LlmSuccessSignal` | 成功信号事件(异步消费) | | `event` | `LlmOverflowSignal` | 溢出信号事件(同步消费) | | `listener` | `ContextSignalProcessor` | 信号处理器(写路径入口,@eventlistener) | | `snapshot` | `EnvSnapshot` | 不可变环境快照(读路径数据源) | | `snapshot` | `EnvSnapshotStore` | 快照缓存(ConcurrentHashMap + AtomicReference) | | `probe` | `WindowProbe` | 窗口探测状态机(5 阶段) | | `probe` | `WindowProbeRegistry` | 多模型探测注册表(ConcurrentHashMap + compute 原子操作) | | `probe` | `WindowProbeSnapshot` | 状态快照 record(用于 DB 持久化和锁外读取) | | `probe` | `ProbeUpdate` | recordSuccess/recordOverflow 返回值 | | `probe` | `WindowStateLoader` | 启动时从 DB 恢复状态 | | `perception` | `BackendDiversityTracker` | 后端多样性检测(per-model 实例,P10 安全窗口) | | `perception` | `BackendDiversityRegistry` | 多模型 tracker 注册表 | | `perception` | `PressureInferencer` | 压力推断(连续成功/溢出/延迟 → ResourcePressure) | | `budget` | `TokenBudgetPlanner` | 多因子预算规划器(读路径出口) | | `budget` | `TokenBudget` | 预算结果 record | | `budget` | `TokenBudgetTrace` | 决策追踪 record(监控/调试用) | | `persist` | `WindowStateRepository` | DB 读写(JdbcTemplate) | | `persist` | `PersistRetryQueue` | 持久化失败重试队列(内存,单次重试) | | `config` | `ContextIntelligenceProperties` | 配置属性(@ConfigurationProperties) | | `config` | `SignalExecutorConfig` | 专用线程池配置 | | `metrics` | `ContextIntelMetrics` | 可观测指标网关(9 项 Micrometer 指标) | | `enums` | `ResourcePressure` | 压力等级枚举(NORMAL/ELEVATED/HIGH/CRITICAL) | | `enums` | `ContextProfile` | 模型上下文画像枚举 | | root | `ContextIntelligenceAutoConfiguration` | 自动配置 | ### v1 → v2 组件映射 | v1 组件 | v2 组件 | 说明 | |---------|---------|------| | `AdaptiveContextTracker` | `WindowProbeRegistry` + `ContextSignalProcessor` | 拆分为注册表 + 处理器 | | `ModelWindowState` | `WindowProbe` | 重新设计,移除 `gatewayMode` 字段 | | `GatewayDistribution` | `BackendDiversityTracker` | 重新设计,per-model 实例 | | `ContextPressureMonitor` | `ContextSignalProcessor` + `PressureInferencer` | 拆分为处理器 + 推断器 | | `DynamicBudgetAllocator` | `TokenBudgetPlanner` | 重新设计,必须读取压力 | | `AdaptiveContextProperties` | `ContextIntelligenceProperties` | 所有字段生效(v1 有 7 个死字段) | --- ## 改动清单 ### 新增文件(28 个) - **v2 模块**:24 个 Java 文件(`vip.mate.context.intelligence.**`) - **数据库迁移**:3 个 SQL(`V161__mate_model_context_state.sql`,h2/mysql/kingbase 三方言) - **设计文档**:`docs/context-intelligence-design -1.md` ### 修改文件(7 个) | 文件 | 改动 | |------|------| | `ReasoningNode.java` | 三层 fallback 预算配置 + LlmSuccessSignal/LlmOverflowSignal 信号发布 | | `AgentGraphBuilder.java` | 注入 v2 组件到 ReasoningNode | | `LoopBudgetConfig.java` | 新增 `fromBudget(TokenBudget)` 工厂方法 | | `MateClawStateKeys.java` | 新增 `RUNTIME_MODEL_TYPE` 状态键 | | `StateGraphReActAgent.java` | 注入 modelType 用于 ContextProfile 阈值判断 | | `StateGraphPlanExecuteAgent.java` | 注入 modelType 用于 ContextProfile 阈值判断 | | `application.yml` | 启用 v2 模块(`mateclaw.context.intelligence.enabled=true`) | --- ## 验证结果 | 验证项 | 结果 | |--------|------| | `mvn compile`(JDK 21) | BUILD SUCCESS,1242 源文件编译通过 | | Flyway migration V161 | Successfully applied,155 migrations 全部通过 | | @SpringBootTest | 3 tests passed | | 代码注释英文化 | 30 个文件,97 处翻译,0 中文残留 | --- ## 可观测指标(9 项) | 指标 | 类型 | Tags | 说明 | |------|------|-------|------| | `context.intel.signal.process.duration` | Timer | type=success\|overflow | 信号处理耗时 | | `context.intel.phase.transition` | Counter | provider,model,from,to | 状态机 phase 切换次数 | | `context.intel.snapshot.refresh` | Counter | provider,model | 快照刷新次数 | | `context.intel.db.persist.failure` | Counter | provider,model | DB 落库失败次数 | | `context.intel.signal.dropped` | Counter | reason | 信号丢弃次数 | | `context.intel.pressure.level` | Gauge | provider,model | 当前压力等级 0/1/2/3 | | `context.intel.effective.window` | Gauge | provider,model | 当前有效窗口 tokens | | `context.intel.phase` | Gauge | provider,model | 当前状态机阶段 0/1/2/3/4 | | `context.intel.diversity.detected` | Gauge | provider,model | 是否检测到多后端 0/1 | --- ## 配置说明 ```yaml mateclaw: context: intelligence: enabled: true # 模块总开关 metrics: enabled: true # 指标监控开关 # 以下为可选项(含默认值) budget: normal-history-ratio: 0.60 # 正常压力下历史消息占比 elevated-history-ratio: 0.70 high-history-ratio: 0.80 critical-history-ratio: 0.85 output-reserve-ratio: 0.15 # 输出预留比例 keep-tail-ratio: 0.60 # 尾部保留比例 probe: sample-size: 20 # 采样数 overflow-shrink-ratio: 0.85 # 溢出收缩比例 binary-convergence: 0.10 # 二分收敛阈值 stale-reprobe-ms: 1800000 # 闲置重探间隔(30 分钟) ``` --- ## 已知限制 1. **冷启动期**:首次调用时 snapshot 为空,走 legacy fallback,需几轮对话后动态预算才生效 2. **不调整 max_output_tokens**:v2 仅优化输入侧(历史消息裁剪),输出上限仍为静态 16384 3. **legacy 比例不可配置**:`TokenBudget.legacy()` 硬编码 0.60/0.40/0.60/0.36(有意设计,保证 v1 兼容和平滑预热) --- ## 后续待办 - [ ] P1:动态 max_output_tokens(接入 `buildChatOptions()`) - [ ] P2:冷启动优化(预填 EnvSnapshotStore from DB) - [ ] P4:硬编码值可配置化(minTailMessages、targetMaxMessages 等) - [ ] 长期:多模型差异化配置
|
感谢这个 PR,能看出你在上下文预算这块投入了大量思考 👏 —— 把硬编码的 128K 静态窗口换成按 不过坦诚地说,以目前的形态还不能直接合并,需要先做一些返工。下面按优先级列一下,方便你迭代: 1. 默认关闭(最重要)
2. 补测试模块有约 2900 行,包含事件采集、CQRS 读写分离、异步执行器、retry queue、窗口探测等并发逻辑,但目前没有任何测试。核心路径建议至少覆盖:
这样评审才能验证正确性,也能防止后续回归。 3. 拆分 PR2890 行单 commit 很难做严肃审查。如果能拆成几个递进的 PR(例如:① 基础设施 + 表结构 → ② 事件采集 + 持久化 → ③ 预算规划器 → ④ 4. Flyway 版本号 V161 撞号
5. 注释自洽,移除对外部设计文档的引用代码和 6. PR 描述里的 v1 对比描述用了较大篇幅复盘 架构出发点我很认可,期待你按上面调整后再提一版~ 有任何疑问欢迎继续讨论 🙏 |
目标
实现 Context Intelligence v2 模块(
vip.mate.context.intelligence),通过动态探测每个(provider, model)的真实上下文窗口,替代原先硬编码的 128K 静态配置,优化历史消息裁剪与 token 预算分配。核心目标:
@EventListener,ReasoningNode 不变v1 复盘 → v2 重构
v1 问题清单(附录 A)
v1
vip.mate.context.adaptive模块存在 5 个 Bug、4 个死代码、2 个风格问题:loopContextWindowTokens参数顺序反转actualModelStates/ModelFamily.contextProfile/MIN_OBSERVATION_HOURS均未使用v1 架构问题
ApplicationEventPublisherv2 机制
架构:EDA + CQRS
核心设计原则
@EventListener三层降级(§8.1)
effectiveWindow > 0)状态机(WindowProbe.Phase)
数据流
6 条核心数据流
成功处理流程(异步,7 步)
WindowProbeRegistry.recordSuccess→ 返回ProbeUpdate(snapshot, previousPhase)BackendDiversityRegistry.recordSuccess— 多样性检测PressureInferencer.recordSuccess— 压力推断(连续成功降级)EnvSnapshotStore.refreshIfChanged— 条件 CAS(仅关键字段变化时刷新)PersistRetryQueue.retryOnce— 重试之前失败的持久化溢出处理流程(同步,5 步)
WindowProbeRegistry.recordOverflow— 窗口收缩(overflowShrinkRatio = 0.85)BackendDiversityRegistry.recordOverflow— 修复 v1 Bug 2PressureInferencer.recordOverflow— 压力升级EnvSnapshotStore.refreshIfChanged— 溢出必然导致窗口收缩,必刷新WindowStateRepository.persist— 同步 DB 写入(溢出是低频事件)预算规划流程(读路径,4 步)
EnvSnapshot.effectiveWindowmin(effectiveWindow, safeWindow)(仅读取,不修改状态机)historyBudget = available × historyRatio,keepTail = historyBudget × keepTailRatiov2 组件清单
19 个组件,9 个子包
eventLlmSuccessSignaleventLlmOverflowSignallistenerContextSignalProcessorsnapshotEnvSnapshotStoreprobeWindowProbeRegistryperceptionBackendDiversityTrackerbudgetTokenBudgetTracemetricsContextIntelMetricsContextIntelligenceAutoConfigurationv1 → v2 组件映射
AdaptiveContextTrackerWindowProbeRegistry+ContextSignalProcessor改动清单
新增文件(28 个)
vip.mate.context.intelligence.**)V161__mate_model_context_state.sql,h2/mysql/kingbase 三方言)docs/context-intelligence-design -1.md修改文件(7 个)
ReasoningNode.java验证结果
mvn compile(JDK 21)可观测指标(9 项)
context.intel.signal.process.duration配置说明
已知限制
TokenBudget.legacy()硬编码 0.60/0.40/0.60/0.36(有意设计,保证 v1 兼容和平滑预热)后续待办
buildChatOptions())