Files

12 KiB
Raw Permalink Blame History

上下文超限自动恢复与压测记录

验证日期:2026-09-11。

行为

  • 模型管理的 max_input_tokens 保持可空,不增加 32000 或任何默认模型窗口。明确配置或已有模型 profile 时做预裁剪;两者均无时,首个请求保持原样。
  • 主代理、子代理的最终 ContextWindowGuardMiddleware 在模型明确返回超限时,对实际发送的请求副本缩短后重试,不重新执行外层摘要、RAG 或记忆注入。原始会话历史不改写。
  • 最多首次调用加四次恢复调用。恢复预算来自失败请求的估算值,每次降到约 35%;最后尝试最小请求。30 秒用于限制继续发起恢复尝试,单次调用的超时仍由模型配置控制。
  • 不盲目缩减已较小的输出额度,避免推理模型耗尽额度却没有正文。恢复后仍只有思考内容或空白时,普通问答返回自然澄清。
  • 不发送空消息列表;不静默删除结构化输出格式和强制工具约束。必要约束无法满足时保留内部失败标记,供业务调用方识别,不能伪造成功 JSON 或已执行的操作。
  • 摘要限制针对最终渲染的请求(包括 XML 和摘要提示词)。使用已有摘要裁剪配置与摘要模型上限中较小者,不新增默认模型窗口;trim_tokens_to_summarize: null 现在会正确传递。
  • 摘要超限也缩短重试;摘要失败时保留可用旧摘要、最近消息和真实用户问题,不缓存失败文案、不阻断主问答。内部摘要调用隔离父级流式回调,避免摘要文字混入回答。
  • 上下文错误识别兼容实测 MiniMax 的 invalid params, context window exceeds limit (2013)。普通鉴权、额度、网络等错误仍由原错误处理流程负责。

自动回归

以下命令从 backend 目录运行:

.venv/Scripts/python.exe -m pytest tests/test_context_recovery.py tests/test_context_recovery_transport.py tests/test_llm_error_handling_middleware.py tests/test_summarization_human_message.py tests/test_summarization_multiturn.py tests/test_model_management.py tests/test_writing_mode_report_middleware.py tests/test_roundtable_role_boundary.py -q -p no:cacheprovider

首轮结果:135 passed;补充多轮问答后的最终结果:168 passed。

覆盖内容:

  • 未填写输入上限时不擅自预裁剪,显式上限和绑定模型 profile 生效。
  • 中英文、表情、单条超长输入;持续超限;输出预算;极小无效预算。
  • 50 组固定随机种子的历史消息、工具调用/结果、隐藏上下文和系统提示组合,验证长度、工具配对与原始历史不变。
  • 同步/异步恢复、取消、LangGraph 中断、重试时限、熔断半开探针释放。
  • 摘要输入超限、摘要不可用、失败不污染缓存、摘要消息流不泄漏。
  • 真实 PatchedChatOpenAI 客户端 + 模拟 HTTP 400/SSE + LangGraph 的端到端请求链,验证只返回一次正文、不重复上下文注入、不泄漏超限异常。

Python 改动与新增测试、探针脚本通过 Ruff 检查。

真实模型联调

使用当前配置中的 MiniMax-M2.7,仅发送自动生成的合成材料,不使用用户会话或业务文档。40000 行输入的本地估算从 1,840,102 降到 644,001;这些是保守估算值,不是供应商统计的实际 token 数。

模式 首次调用 第二次调用 可见正文 耗时
普通调用 真实上下文超限 2013 自动缩短后成功 验证通过 18.83 秒
SSE 流式 真实上下文超限 2013 自动缩短后成功 验证通过 21.26 秒

可重复运行的脚本(真实请求会消耗已配置模型额度):

.venv/Scripts/python.exe scripts/probe_context_recovery.py --live --model MiniMax-M2.7 --rows 40000
.venv/Scripts/python.exe scripts/probe_context_recovery.py --live --model MiniMax-M2.7 --rows 40000 --stream

探针只在进程内设置 90 秒请求超时与 1024 输出预算,不修改配置文件。初期以较小输出预算测试时发现只有思考、没有正文的问题;30 秒网络超时测试也曾超时,扩大探针超时后完成上述验证。生产调用仍使用自身超时配置。

多轮问答追加验证

当前配置的主动摘要开关为 summarization.enabled: false,前端默认 contextCompressionEnabled: false。前端开启“上下文压缩”后,通过 summarization_enabled 传到后端。触发条件为 120000 token 或 160 条消息,保留目标为 40000 token。未开启主动摘要时,不会因为对话轮次增加而调用摘要模型;上下文超限后的裁剪重试仍可独立工作。本次没有修改这些开关或生产阈值。

多轮测试发现并修复:

  1. 消息数达到阈值但历史尚未达到 token 保留目标时,提前返回导致不触发摘要。现在先执行保留窗口调整,再判断是否有历史可摘要。
  2. 反复从全量旧历史重新摘要,旧事实可能被摘要输入截断删除。现在使用“上次摘要 + 新增历史”增量合并,并保留完整原始消息。
  3. 摘要提示词与历史拼接后整体截断,可能保留提示词却删除历史开头。现在先计算提示词与已有摘要开销,再给历史分配预算。
  4. 历史回复的思考文本、历史 token 用量缩放、摘要输出的思考文本可能干扰事实提取。现在清理这些信息的临时副本,存储的原始聊天记录不改写。
  5. 摘要模型可能执行历史中的“只回复已记住”。现在明确区分摘要任务和历史数据,过滤纯确认回复;摘要失败时保留尚未成功摘要的事实,由最终 guard 按实际需要裁剪,不把确认文案当记忆。
  6. 补齐 fraction 阈值、复合条件和绑定模型 profile 的多轮处理。
测试 真正生成摘要的轮次 结果
当前阈值的 105 轮模拟,逐轮重建 agent 81、101 210 条消息完整,早期事实可回忆
小摘要预算的 24 轮模拟 5、8、11、14、17、20、23 多次合并后仍保留首轮事实
12 轮工具问答,同步/异步分别运行 多次触发 每轮工具恰好执行一次,48 条消息完整,调用与结果不拆散
真实 MiniMax 12 轮,第一次最终复测 5、8、11 正确回忆代号、更新后的预算和期限;59.02 秒
真实 MiniMax 12 轮,第二次独立复测 5、8、11 再次通过相同事实检查;68.16 秒

真实测试为加快验证,仅在探针进程内把摘要触发调为 8 条消息、保留 2 条消息,并使用 2048 输出预算;未设置默认输入上限,也未修改配置文件。第 9 轮清空进程摘要缓存,检查能否从会话检查点恢复。第 6、7、9、10、12 轮复用摘要,不额外调用摘要模型。每次真正摘要均检查 checkpoint 中的摘要覆盖范围确实更新,而不只检查触发事件。

首轮提供代号 ORBIT-42、预算 35 万元和期限 2026-12-20,第 6 轮把预算改为 48 万元,第 8 轮把期限改为 2026-12-28。两次最终复测第 12 轮均正确返回:

{"code":"ORBIT-42","budget":48,"deadline":"2026-12-28"}

复测过程中曾出现“摘要更新了,但答案事实错误”的失败,未计为通过;上述两次成功记录均来自清理历史思考及用量缩放后的版本。逐轮输入场景、正文、摘要正文和缓存更新记录保存在:

复现命令(使用合成材料,会调用已配置模型):

.venv/Scripts/python.exe scripts/probe_multiturn_compression.py --live --output docs/multiturn-probe-20260911.json

前端及验证边界

  • 模型管理页已注明输入上限可留空;该 TSX 文件单独转换通过。
  • pnpm typecheck 未通过:未改动的 message-list-item.tsx、core/blog(缺少 nextra)、GeneralQA.tsx 等存在类型或依赖错误。本次不把前端全量检查记为通过。
  • 真实供应商测试覆盖 MiniMax-M2.7,不能等同于所有供应商均已实测。主代理/子代理和摘要调用以外的独立模型调用不因该中间件自动获得保护;标题生成原本已有本地标题降级。
  • 极端情况下会丢失部分材料并请求用户补充;恢复机制不保证缺失信息时仍能完成原任务,也不掩盖鉴权失败、额度不足或服务不可用。

部署版本兼容修复(2026-09-11)

前面的本机测试使用 LangChain 1.3.15,未覆盖部署的 1.2.15,这是此前验证的遗漏。 已在真实 1.2.15 包上复现 _trigger_clauses AttributeError:旧基类只提供 _trigger_conditions。 现在应用自行保存和计算触发条件,不再读取这两个版本相关的私有属性;字典条件的 AND、条件列表的 OR 和旧参数映射保持一致。 此前新增的 internal_call_transformer 依赖已移除。没有要求升级部署依赖,没有引入默认输入上限。

本次将用户提供的全部 20 个包安装到隔离目录,以 PYTHONPATH 优先加载,未替换本机虚拟环境。 其余未提供版本的依赖沿用本机,测试主机为 Windows;不把该结果宣称为生产服务器完整环境的启动验证。

  • 用户版本组合(LangChain 1.2.15、Core 1.3.2、LangGraph 1.1.9 等):177 项通过,26.26 秒。
  • 本机新版组合:同样 177 项通过,24.64 秒。
  • 覆盖 Gateway / 主代理导入、105 轮问答、重复压缩、缓存恢复、同步/异步工具和消息流隔离。
  • 新增删除基类两个私有触发属性后仍能正常判断阈值的回归测试,涵盖 tuple、OR、AND、混合和未启用情况。
  • 用户版本组合下真实 MiniMax-M2.7 12 轮问答:第 5、8、11 轮摘要,第 9 轮清空进程缓存后正常恢复;最终准确返回代号 ORBIT-42、更新预算 48、更新期限 2026-12-28。54.53 秒,无属性错误。
  • 所有被调用的摘要中间件方法均已检查能在 1.2.15 下解析;Ruff 通过。

用户版本组合的真实多轮记录及 20 个版本号。

隐藏压缩提示与连续问答复测

后台仅记录真实摘要日志,不再向 custom 流发送 context_compacting 通知;前端也忽略旧后台的同名事件,不弹 toast。摘要开关和压缩功能保留。

新增模型每轮上报 150000 输入 token 的 12 轮测试,使用 2000 token 摘要触发阈值:同步和异步均仅在第 2 轮摘要,后续复用;历史用量数据未修改。该测试在用户的 1.2.15 依赖组合和本机新版均通过。部署组合相关回归共 118 项通过(22.28 秒)。

尚未拿到用户连续弹提示时的后台 cache / counted_tokens / msgs 日志,未复现该会话的每轮重复摘要,不能把隐藏提示当作已修复重复摘要根因。

修复压缩后每轮重新摘要

已用过长摘要复现另一条真实缺陷路径:摘要加保留内容在生成当时就超过阈值,旧逻辑之后每轮都会把同一个超阈值基础内容再次送摘要模型。12 轮测试中第 2~12 轮均调用摘要。此前的简短摘要测试没有覆盖此边界;尚无生产会话日志,不能据此断言这是用户会话唯一原因。

修复:缓存可用且上次压缩结果本身就超阈值时,要求压缩后新增内容累计达到原触发阈值的 25% 才再摘要。正常压缩后低于阈值的路径保持原判定。这个比例用于避免无效重复摘要,不是模型输入上限;最终 ContextWindowGuard 的裁剪及超限重试继续工作。子集计数移除原请求 usage_metadata 的缩放影响,只修改临时副本。日志新增 waiting_for_growth,并区分 unusable 与 stale 缓存。

用户提供的 1.2.15 等 20 个包组合最终回归 187 passed,26.59 秒:

  • 12 轮过长摘要 + 短追问:仅第 2 轮摘要,修复前为第 2~12 轮;同步/异步均通过。
  • 36 轮持续增加材料:第 2、19、36 轮摘要;每轮清空进程缓存,仍可由 checkpoint 恢复,不是直接禁用后续摘要。
  • 超长摘要 + 严格 800 token 测试模型 + 连续 8 轮:分别验证配置上限(预裁剪,无拒绝)和不配置(真实抛出 context_length_exceeded 后缩短重试),同步/异步四种组合均正常回答最新问题;原会话消息数完整。这里使用可控测试模型,未宣称所有供应商均实测。
  • 既有 105 轮、工具执行、首次阈值、缓存恢复、旧版导入及摘要流隔离回归继续通过。Ruff 通过。