任务跑一半,API 突然报“对话超长“断流:我给 Agent 修的紧急逃生通道

简介: 本文是开源终端AI编程智能体`codeAgent`系列第二篇,详解其“紧急上下文压缩”机制:统一识别四家API超长报错、动态瘦身重试、双保险防死循环,并附60行核心源码。

本文是我开源项目 codeAgent(终端 AI 编程智能体)的系列第 2 篇,每天讲一个里面真实用到的技术点,全部附源码。
仓库:https://github.com/Harvil1/codeAgent

请添加图片描述

场景:最猝不及防的一种死法

长任务跑到第 30 轮,一个大工具结果塞进来,下一轮请求直接被 API 拒了:

Error: prompt_too_long / maximum context length exceeded ...

此时最不能做的就是把异常抛给用户——前面 30 轮的工作全卡在半空。我的做法是修一条"紧急逃生通道":识别它 → 立刻瘦身 → 马上重发,任务自己缓过来。

第一关:四家服务商,四种报错措辞

DeepSeek 说 prompt_too_long,OpenAI 说 maximum context length,还有 context_length、too long 各种变体。如果识别口径不统一,OpenAI 风格的错误会走普通失败重试——白烧一次熔断次数。

所以我全项目只认一个判定函数,源码在 agent/context_compressor.py:

# PTL 错误识别的统一口径(四家服务商措辞全认)。此前三处各写各的
# 子集(摘要重试认 2 种、主循环认 3 种、丢条数计算认 4 种)——OpenAI
# 风格的 "maximum context length ..." 在摘要重试路径会被当普通失败,
# 白计一次熔断还降级规则总结。全项目判定一律走 is_prompt_too_long_error
_PTL_MARKERS = (
    "prompt_too_long", "context_length", "too long", "maximum context",
)


def is_prompt_too_long_error(err_str: str) -> bool:
    """判断一段错误文本是不是「输入超长」(PTL)。四家措辞口径统一。"""
    s = (err_str or "").lower()
    return any(kw in s for kw in _PTL_MARKERS)

教训藏在注释里:错误分类这类"知识"必须收敛到一处,三个地方各写一个子集版本,迟早有人只改一处。

第二关:紧急压缩函数(核心源码)

识别出来之后立刻瘦身重发。做法简单粗暴:只留 system + 一条边界占位 + 最近 5 条消息,其余的靠落盘的 transcript 快照找回。源码在 agent/context_pipeline.py:

# 紧急压缩的两道保险默认值(config 可覆盖):冷却窗口秒数 / 单会话次数上限
REACTIVE_COOLDOWN_SECONDS = 60
REACTIVE_MAX_PER_SESSION = 5


def reactive_compact(
    messages: list,
    *,
    session_state: CompressionSessionState,
    keep_recent: int = 5,
    cooldown_seconds: float = REACTIVE_COOLDOWN_SECONDS,
    max_per_session: int = REACTIVE_MAX_PER_SESSION,
    now_fn=time.time,
) -> Tuple[list, bool]:
    """紧急通道:API 报「对话超长」(prompt_too_long)时立刻调用。

    做法简单粗暴:只留 system + 一条说明占位 + 最后 keep_recent 条消息,
    其余全靠 transcript 文件找回。
    **可多次触发**:每次报错都可以再压,但有两道保险:
      1. 冷却窗口:距上次触发不足 cooldown_seconds 秒(默认 60s)→ 跳过
      2. 单会话上限:本场已触发 max_per_session 次(默认 5)→ 跳过
    """
    # 保护 1:冷却窗口
    now = now_fn()
    elapsed = now - session_state.reactive_last_at
    if session_state.reactive_count > 0 and elapsed < cooldown_seconds:
        logger.info(
            "reactive_compact 冷却中(距上次 %ds < %ds),跳过",
            int(elapsed), int(cooldown_seconds),
        )
        return messages, False

    # 保护 2:单会话上限
    if session_state.reactive_count >= max_per_session:
        logger.warning(
            "reactive_compact 达到单会话上限 %d 次,跳过",
            session_state.reactive_count,
        )
        return messages, False

    system, conv = _split_system(messages)
    keep = conv[-keep_recent:] if len(conv) > keep_recent else conv[:]
    placeholder = {
   
        "role": "user",
        "content": (
            # 边界前缀和大写统一:恢复侧 _truncate_at_last_compact_boundary
            # 只认 [COMPACT_BOUNDARY] 开头,reactive 的占位也得能被裁剪定位
            "[COMPACT_BOUNDARY]\n"
            "[紧急上下文压缩:API 返回 prompt_too_long,"
            f"已只保留最近 {len(keep)} 条消息。"
            "如 .transcripts/ 下有快照(latest.txt 指向最新一份)可 "
            "read_file 分段找回;无快照则被裁原文已不可恢复]"
        ),
    }
    new_conv = [placeholder] + keep
    new_conv = _fix_tool_call_pairs(new_conv)
    new_messages = _reassemble(system, new_conv)

    session_state.reactive_last_at = now
    session_state.reactive_count += 1
    return new_messages, True

这 60 行里藏了四个细节

1. 为什么允许触发 5 次而不是 1 次? 砍到只剩 5 条后,下一轮又来一个巨型工具结果,还会再超——所以留了多次触发的余地。但必须有冷却窗口(60 秒)+ 次数上限(5 次)两道闸,否则会陷入"压了又超、超了又压"的死亡循环,日志和账单一起爆炸。

2. 占位消息以 [COMPACT_BOUNDARY] 开头。 会话恢复时只认这个前缀来定位"历史上次压到哪",紧急压缩和常规压缩共用同一套边界标记——逃生通道也得上户口,不然恢复逻辑找不到断点。

3. _fix_tool_call_pairs 不能省。 从中间砍消息,很容易把"模型要调工具"和"工具结果"切成两半——孤儿 tool_call 会让下一次请求直接被 API 拒掉(400)。砍完必须对账补配。

4. 被砍的内容给了一条活路。 压缩前完整对话已落盘 .transcripts/,占位消息里明确告诉模型:可以去读快照找回上下文。丢的是上下文窗口里的位置,不是信息本身。

小结

三句话带走:

  1. 错误识别统一口径:四家服务商措辞全收进一个函数,全项目只认它
  2. 紧急压缩 = 保 system + 边界占位 + 最近 5 条,立刻重发不断线
  3. 两道保险(冷却 60s / 上限 5 次)防死亡循环,砍完修 tool_call 配对

下一篇讲它的上游:四级上下文压缩怎么分层——目标是让任务压根走不到"报超长"这一步(照样附源码)。


仓库在这,注释全中文,欢迎 Star ⭐:https://github.com/Harvil1/codeAgent

目录
相关文章
|
2天前
|
人工智能 API 语音技术
阿里云百炼TokenPlan 个人版又上新!Standard 和 Pro 套餐新增 12 类 Harness 权益
阿里云百炼TokenPlan个人版升级:Standard/Pro套餐新增12类Harness权益(如网页解析、图像生成等),享每月免费额度及超量88折,独立计量不扣Credits。
|
3天前
|
缓存 人工智能 API
你的大模型 API 账单能打折:前缀缓存,我用一次踩坑换来的
Agent 每轮都要把全部历史重发给大模型,费用照付;但服务商对逐字节一致的前缀有缓存折扣,输入价最低一折。本文讲清前缀缓存的命中规则、四种"毒死缓存"的常见写法,并附我开源项目 codeAgent 的三段真实源码:临时消息走便签通道不进正式历史、_timestamp 等记账字段发送前统一剥离、system prompt 只构建一次。零依赖零成本,长会话能省数倍 token 费用,附完整代码。
62 1
|
Java 关系型数据库 中间件
分库分表(3)——ShardingJDBC实践
分库分表(3)——ShardingJDBC实践
1759 0
分库分表(3)——ShardingJDBC实践
|
2天前
|
缓存 C++
0.8MB 跑通 Qwen|第 3-1 篇:推理引擎的 mmap 直挂——为什么权重加载可以只要一秒多
本文实测RK3588板端冷启动仅1.0–1.2秒,核心在于mmap直挂VQF权重文件:不全量读取,仅映射+校验头部,权重页由内核按需缺页加载,配合预量化布局,较safetensors全量读快33倍。(239字)
 0.8MB 跑通 Qwen|第 3-1 篇:推理引擎的 mmap 直挂——为什么权重加载可以只要一秒多
|
3天前
|
编译器 C语言
0.8MB 跑通 Qwen|第 2-1 篇:推理引擎的 C11 `_Static_assert`——让编译器守卫你的内存布局
本文详解C11 `_Static_assert` 在内存布局守卫中的关键作用:针对mmap直挂场景,通过编译期断言钉死VQF格式三结构体(200/432/64字节),杜绝因对齐差异导致的静默错位。真机RK3588实测验证,实现错误前移。
0.8MB 跑通 Qwen|第 2-1 篇:推理引擎的 C11 `_Static_assert`——让编译器守卫你的内存布局
|
2天前
|
SQL 人工智能 数据库
好友申请已通过,为什么不等于仍是好友?事务与重复提交的边界
好友申请“已通过”仅表示历史操作完成,不等于当前仍是好友;关系删除后,旧申请重试不会恢复关系。本文通过双表模型与事务设计,厘清申请状态与好友关系的独立性,避免重复建联。(239字)
|
2天前
|
人工智能 编解码 算法
肝脏病理病变检测数据集 | 4000张YOLO数字病理数据集
本数据集含约4000张YOLO格式肝脏病理切片图像,精准标注肝细胞气球样变、纤维化、炎症、脂肪变性4类病变,覆盖NAS评分核心指标,专为数字病理辅助诊断、肝毒性评估及YOLO系列模型训练设计,支持自动化量化分析与科研教学。(239字)
|
1月前
|
弹性计算 人工智能 API
DeepSeek Harness:阿里云百炼支持按量计费、Coding Plan、Token Plan方式配置接入
阿里云百炼支持DeepSeek Harness接入,提供按量计费、Coding Plan及Token Plan(个人/团队版)四种灵活配置方式,兼容OpenAI协议,开箱即用。在阿里云百炼官网:https://t.aliyun.com/U/fPVHqY 免费领取千万Tokens
|
2月前
|
机器学习/深度学习 数据采集 人工智能
企业知识库搭建实战:RAG 从文档导入到检索调优全流程拆解
本文详解企业知识库搭建实战:以RAG为核心,覆盖文档导入、智能解析分段、语义/增强检索调优全流程。结合硅基边界平台案例,直击解析策略、分段长度、相似度阈值等关键参数调优要点,助技术/产品/运营团队两周内快速验证AI问答效果,让私域资料真正变成“会回答的AI”。
299 1
|
2月前
|
人工智能 中间件 定位技术
LangChain 入门教学:一张地图搞懂模型、链、RAG、图与智能体
本文是一份面向初学者的LangChain系统性入门指南,以“认知地图”为主线,清晰梳理LangChain 1.0、LangGraph、RAG、智能体(`create_agent`)等核心模块的定位与协作关系,摒弃过时API,聚焦2026年官方推荐架构,助开发者快速建立正确心智模型。(239字)
550 0

热门文章

最新文章