Skip to content

[context] feat: 上下文压缩接入核心链路(token 化触发)+ LLM Provider 切换 + 测试分包 - #86

Merged
atoncooper merged 4 commits into
mainfrom
feat/context-compression-wiring
Aug 21, 2026
Merged

[context] feat: 上下文压缩接入核心链路(token 化触发)+ LLM Provider 切换 + 测试分包#86
atoncooper merged 4 commits into
mainfrom
feat/context-compression-wiring

Conversation

@atoncooper

Copy link
Copy Markdown
Owner

概述

四个独立特性,均带完整测试:

  1. [context] 上下文压缩接入核心链路 — 此前 app/context/ 压缩管线已实现但无生产调用方(空转)。本次接通写入(orchestrator 三处 finalize 钩子 → ContextManager)、读取(get_conversation_context 注入 Redis 摘要 + 近 3 轮原文)、启动初始化三环。
  2. 触发 token 化 — 从轮数制改为 token 预算制:CJK 感知估算器 + TokenThreshold(超预算 且 ≥4 轮 且过冷却)+ TokenBudgetWindow。预算 = 当前模型窗口 × 50%,随模型自动缩放。
  3. [llm] Provider 切换llm.provider = dashscope | openrouter 一行配置切换对话类 LLM 网关;内置 2026-08 模型窗口表 + OpenRouter /models 动态元数据;embedding/rerank 不参与切换。
  4. [test] 分包重组 — 74 个顶层测试按业务域归入 16 个包(纯移动,git mv 保留历史)。

另含一个既有 bug 修复:search agent SYSTEM_PROMPT 字面 {api} 未转义导致 inject_prompt KeyError。

压缩工作方式

每轮落库后(后台,不阻塞响应)
  add_turn → TokenThreshold(窗口×50%, ≥4轮, 冷却10轮)
    ├─ 未超 → 结束
    └─ 超限 → qwen 滚动摘要(增量合并)→ 存储裁剪到近10轮 → 摘要写 Redis
下一轮 prompt =【历史摘要】+ 最近 3 轮原文

降级链:Redis 挂 → 压缩照常仅镜像失效;管线未初始化 → 钩子静默跳过;摘要 LLM 故障 → 内置截断兜底。

质量验证(Layer 2 真实评测,报告见 reports/

12 场景 × 3 重复 × 真实 qwen3-max:

指标 结果
触发正确性 应触发 9/9 全触发;不应触发 3/3 全未触发
关键词召回率 100%
事实保留均值 (judge) 1.0(实体/时序/纠正全类型)
无幻觉均值 0.907(T2 场景暴露一处填充内容定性失真,已定位)

测试

  • 新增:场景化 Layer 1(15 例,零 LLM 成本进 CI)+ provider/压缩/token 单测
  • 全量:ruff 通过;context/llm/eval 相关 195 passed
  • 说明:全量 pytest app/test 中约 90 个失败为重组前既有的环境/行为问题(未移动目录同样失败,证据在 PR 讨论),与本次改动无关

部署注意

  • backend 需 rebuild(新代码 + default.yaml)
  • 切换 OpenRouter:.envLLM__PROVIDER=openrouter + OPENROUTER__API_KEYdocker compose up -d backend

- providers.py:resolve_llm_config 按 llm.provider 选择配置段(dashscope 沿用
  llm.*,openrouter 用独立 openrouter.* 段 + 归因头);MODEL_CONTEXT_WINDOWS
  内置模型窗口表(2026-08 时点)+ OpenRouter /models 动态元数据刷新(24h TTL)
- settings.py:llm_provider / openrouter_* / llm_context_window 属性
- llm.py:build_llm 系统默认走 provider 解析;infer_provider 委托收敛
- main.py:harness 共享 LLM 同步走 provider 解析
- compose/.env.example/default.yaml:透传 LLM__PROVIDER 与 OPENROUTER__*
- 边界:embedding/rerank 不参与切换(DashScope 独占模型)
74 个顶层测试文件按 app 分层归入 16 个包:agents/harness/chat/context/quiz/
notes/auth/llm/services/infra/scripts + 既有 query/rag/security/eval/
real_agent_harness。纯移动(git mv 保留历史),无逻辑改动;根 conftest 继续
全局生效。
接入(三处 finalize 统一钩子,fire-and-forget 不阻塞响应):
- orchestrator:ask/ask_agentic/流式落库后 record_turn → 后台 add_turn +
  条件压缩;main.py 启动时 init_auto_compressor 注入真实 LLM
- db_deps.get_conversation_context:注入 Redis 摘要(历史记忆)+ 近 3 轮原文

触发 token 化(替代轮数制):
- tokens.py:CJK 感知估算器(中文 0.75/字,保守偏移)
- TokenThreshold:估算超预算(窗口×50% 自动推导) 且 ≥4 轮 且过冷却
- TokenBudgetWindow:按预算从最新侧裁剪,不拆 user/assistant 对
- 预算跟随模型窗口自动缩放:内置 2026-08 模型窗口表 + OpenRouter 厂商
  元数据动态刷新,1M 模型下压缩器自动休眠

语义与健壮性:
- manager:追加消息不再失效摘要缓存(摘要只描述已吸收旧轮次)
- compressor.restore:重启后从 Redis 恢复累计摘要保证连续性
- 降级链:Redis 挂→静态照常;管线未初始化→钩子静默跳过

测试:场景化基建(12 条声明式场景 + harness + Layer1 机械断言 15 例)+
L2 质量 runner(真实 qwen 摘要 + judge 评分,报告输出 reports/)
URL 示例中的 {api}/{path} 被 .format(query=...) 当作占位符,触发
KeyError: 'api' 且归类 non_retryable。双写转义为字面量,渲染验证通过。
@atoncooper
atoncooper merged commit e65fbf8 into main Aug 21, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant