qwen3.7-max 报 400 context_length_exceeded 怎么办?删了聊天记录为什么还超限?

标题:qwen3.7-max 报 400 context_length_exceeded 怎么办?删了聊天记录为什么还超限?

正文:

qwen3.7-max 报 400 context_length_exceeded 怎么办?删了聊天记录为什么还超限?

2026 年 10 月 22 日,我在一次本地调试中给采购 Agent 配置了 6 个工具。删掉最近几轮聊天后,请求 qwen3.7-max 仍返回 HTTP 400,应用侧记录的往返耗时为 186 ms。

下面是只保留排障字段的脱敏日志:

json 复制代码
{
  "date": "2026-10-22",
  "environment": "local",
  "model": "qwen3.7-max",
  "tool_count": 6,
  "http_status": 400,
  "error_code": "context_length_exceeded",
  "elapsed_ms": 186,
  "response_has_generated_content": false,
  "request_id": "[REDACTED]"
}

186 ms 只能说明响应较早返回,不能单凭耗时证明模型"压根没开始生成"。更准确的说法是:响应中没有生成内容,结合错误码,推测请求在生成前被网关或服务端的上下文校验拒绝。

继续随手删除几条消息通常没有用。发送请求前,应分别计算系统消息、工具定义、历史消息、当前输入、本轮工具结果和预留输出,再按明确的优先级裁剪。

可以先用下面的本地预算关系进行检查:

text 复制代码
系统消息
+ 工具与请求 Schema
+ 历史完整回合
+ 当前输入
+ 本轮工具结果
+ 预留输出
≤ 本地配置的总预算

这是一种保守的客户端预算模型,不是所有接口都采用的固定校验公式。许多兼容接口会结合输入长度与请求的最大输出预算进行校验,但不同服务商也可能分别限制总上下文、最大输入、实际输出或最大输出上限。最终应以当前路由的文档、控制台限制和错误响应为准。

这里的 400 是 HTTP 客户端错误码,不是"上下文只有 400 token"。对于内容完全相同的超长请求,原样重试通常只会再次失败。

数据边界:按 2026 年 10 月 27 日随文模型目录核对,模型完整 ID 为 qwen3.7-max。现有资料没有给出该路由的上下文窗口、最大输出和价格,因此下文不填写未经证实的数值,也不把其他 Qwen 型号的参数直接套用到它身上。

为什么会出现这个问题

兼容接口的错误响应可能类似下面这样:

text 复制代码
HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": {
    "message": "The request exceeds the model's maximum context length. Please reduce the length of the messages or the maximum output tokens.",
    "type": "invalid_request_error",
    "param": "messages",
    "code": "context_length_exceeded"
  }
}

这只是示例。具体的 HTTP 状态码、message、type、param 和 code 可能被官方端点、API 网关或工作流平台重新封装,不能把上述结构当成固定格式。

我最初只统计了 messages 中的用户聊天,但实际请求里还可能包含:

  • system 或 developer 消息;
  • Function Calling、MCP 等工具定义;
  • 工具参数的 JSON Schema;
  • 结构化输出或响应格式 Schema;
  • 历史工具调用及其返回值;
  • 当前轮生成的工具结果;
  • RAG 检索片段;
  • 图片、文件或其他附件的文本化内容;
  • max_tokens 或 max_completion_tokens 对应的输出预算。

先按现象选择处理方式

现象 优先处理 原因
减少历史消息后仍然 400 检查输出预算、工具定义和固定提示 超限项可能不在聊天记录中
Agent 工具越加越容易报错 精简工具描述及参数 Schema 顶层工具定义通常会随每次请求重复发送
RAG 查询从第二轮开始失败 限制检索片段与工具结果 文档和工具返回值可能被带入后续上下文
长对话偶发超限 发送前执行 token 预算检查 字符数不能稳定换算成 token
同一请求反复返回 400 停止原样重试,先修改请求 确定性的请求错误不会因等待而自动消失

如果请求链路包含 OpenRouter 或 ofox.io 这类聚合网关/中转方案,还应分别记录网关返回的 model、code、request_id 与客户端 token 估算,避免只根据 HTTP 400 判断超限发生在哪一层。

整个请求可以按下面的关系理解:

graph LR A[系统消息与工具 Schema] --> F[输入占用] B[历史完整回合] --> F C[当前输入与附件] --> F D[本轮工具结果] --> F F --> G{结合输出预算后是否符合路由限制} G -->|否| H[压缩/裁剪/降低输出预算] G -->|是| I[发送请求]

qwen3.7-max 是否存在额外的推理 token,以及服务端如何计算这部分占用,现有资料无法确认。应查看当前服务商的文档、响应字段和控制台限制,不宜凭其他模型的行为推断。

把六本账分开算

不要只记录一个总的 prompt_tokens。请求发送前,可以统一拆成下面六类:

  1. system_tokens:system/developer 消息、角色规则和固定业务约束;
  2. tool_tokens:工具名称、描述、参数 Schema,以及请求级结构化输出 Schema;
  3. history_tokens:此前已经完成的用户、助手和工具交互回合,包括历史工具调用及其结果;
  4. current_tokens:当前用户输入、当前注入的 RAG 片段和附件内容;
  5. current_tool_result_tokens:当前尚未结束的回合中,新产生并准备回传给模型的工具结果;
  6. reserved_output_tokens:客户端计划留给本次回复的最大输出预算。

这里采用一种固定归属:

  • 历史完整回合中的工具结果只计入 history_tokens;
  • 当前进行中回合的工具结果只计入 current_tool_result_tokens;
  • 同一段工具结果不能在两类中重复计算。

如果当前路由按总上下文限制校验,可使用下面的保守预算模型。设总上下文上限为 C,安全缓冲为 S:

text 复制代码
可用输入预算 B = C - reserved_output_tokens - S

实际输入 I =
    system_tokens
  + tool_tokens
  + history_tokens
  + current_tokens
  + current_tool_result_tokens

仅当 I ≤ B 时才发送

但这不是所有服务端的统一规则。如果某个路由分别设置"最大输入"和"最大输出",就应分别校验:

text 复制代码
实际输入 ≤ 最大输入限制
请求的最大输出 ≤ 最大输出限制

如果服务端按其他方式计算,则应以该路由文档和实际错误响应为准。

C 也必须从当前服务商的模型文档、控制台或配置中读取。模型理论窗口与某个 API 路由实际开放的窗口未必相同。

计数器应尽量匹配服务端 tokenizer。不能简单用汉字数除以二,再把结果当成准确 token 数。代码、JSON、URL、自然语言和工具 Schema 的分词比例可能相差很大。拿不到准确 tokenizer 时可以保守估算,但日志中应明确标记为 estimated。

python 复制代码
parts = {
    "system": system_messages,
    "tools": {
        "tools": tools,
        "response_format": response_format,
    },
    "history": completed_history,
    "current": {
        "message": current_message,
        "retrieved_docs": retrieved_docs,
        "attachments": attachments,
    },
    "current_tool_results": current_tool_results,
}

usage = {
    name: count_serialized_tokens(value)
    for name, value in parts.items()
}
usage["reserved_output"] = max_completion_tokens
usage["estimated"] = True

count_serialized_tokens() 只是占位接口。计数时最好针对最终序列化后的请求内容,而不是只计算业务层的原始字符串;随便选一个不匹配的 tokenizer,也不能保证准确计算 qwen3.7-max 的实际占用。

按优先级裁剪,不要直接截取数组尾部

只执行下面这行代码存在明显风险:

python 复制代码
messages = messages[-8:]

在标准 Chat Completions 请求中,system 或 developer 消息通常就在 messages 数组头部,直接保留最后八条可能把它们误删。顶层 tools 通常仍然存在,因此请求可能同时出现"固定规则丢失"和"工具定义占用没有下降"两个问题。

此外,这种裁剪还可能保留孤立的 tool 返回值,却删除与之对应的 assistant tool call,导致消息结构非法。

更稳妥的做法是先分离固定消息,再按完整回合裁剪:

python 复制代码
fixed_messages = extract_system_and_developer_messages(messages)
complete_turns = group_into_complete_turns(messages)
recent_turns = select_recent_turns(complete_turns, token_budget)

trimmed_messages = fixed_messages + recent_turns

我通常采用下面的裁剪顺序:

  1. 降低不必要的最大输出预算;
  2. 压缩本轮和历史工具结果;
  3. 对 RAG 文档去重,只保留与当前问题相关的片段;
  4. 把较旧的完整回合压缩成摘要;
  5. 保留最近若干个完整回合,而不是若干条孤立消息;
  6. 精简重复的 system/developer 规则;
  7. 最后再压缩工具描述和参数 Schema。

工具结果尤其容易膨胀,但不应优先按字符从中间硬切。硬截可能破坏 JSON、表格和关键字段,使后续模型收到不可解析或语义残缺的数据。

优先使用以下方法:

  • 对对象执行字段白名单;
  • 对列表分页,只返回当前页;
  • 将大结果转换为结构化摘要;
  • 保留错误码、状态、标识符等关键字段;
  • 记录 truncated、原始长度、返回长度和下一页游标;
  • 只有无法结构化处理时,才使用保留头尾的字符级保险。

例如:

python 复制代码
def compact_tool_result(result, allowed_fields=None, page_size=100):
    if isinstance(result, dict):
        fields = allowed_fields or result.keys()
        data = {k: result[k] for k in fields if k in result}

        return {
            "data": data,
            "_meta": {
                "truncated": len(data) < len(result),
                "original_field_count": len(result),
            },
        }

    if isinstance(result, list):
        page = result[:page_size]
        return {
            "data": page,
            "_meta": {
                "truncated": len(result) > page_size,
                "original_item_count": len(result),
                "returned_item_count": len(page),
                "next_cursor": page_size if len(result) > page_size else None,
            },
        }

    text = str(result)
    limit = 12000

    if len(text) <= limit:
        return {
            "data": text,
            "_meta": {
                "truncated": False,
                "original_chars": len(text),
            },
        }

    # 最后的字符级保险:保留头尾,并放进结构化容器中。
    head_size = limit * 3 // 4
    tail_size = limit - head_size

    return {
        "data": {
            "head": text[:head_size],
            "tail": text[-tail_size:],
        },
        "_meta": {
            "truncated": True,
            "original_chars": len(text),
            "returned_chars": limit,
        },
    }

这里的 12000 只是字符级保险示例,不是 qwen3.7-max 的官方限制。实际阈值应根据序列化后的 token 计数和业务数据结构调整。

历史上下文可以采用"有限摘要记忆 + 最近完整回合":

python 复制代码
context = []
context += fixed_messages
context.append(memory_summary)
context += recent_complete_turns
context += current_turn_messages

RAG 文档和本轮工具结果应放在前文定义的 current_tokens 与 current_tool_result_tokens 中单独计数,不要再重复算入历史。

摘要本身也不能无限增长。可以为摘要设置独立预算,将原始记录存入数据库,需要时再通过检索取回。对于合同、金额、日期、订单号和代码变更等内容,最好同时保留结构化事实,避免摘要丢失关键数字。

在 API 网关前增加预算检查

同一套 Agent 经过不同路由时,错误字段、模型限制和计费统计方式可能不同,但路由层不能凭空扩大模型或上游接口允许的上下文窗口。

这些产品也不宜全部归为同一类别:

  • OpenRouter 与 ofox.io 可以作为聚合网关/中转方案进行比较,但两者支持的模型、错误字段、usage 统计和路由限制应分别以当前文档与控制台为准;
  • Together AI 主要提供模型推理和 AI 云服务,不宜简单等同于聚合 API;
  • 即使请求参数都使用 messages、tools 和 max_completion_tokens,不同路由对上下文窗口与输出预算的校验方式也可能不同。

无论使用哪一层,真正防止超长请求的关键仍是客户端预检、结构化压缩与裁剪。网关日志可以协助定位问题,但不能代替请求预算控制。

兼容客户端配置示例

在 OpenRouter 与 ofox.io 等聚合网关/中转方案之间配置兼容客户端时,建议把 base_url、API Key 和模型 ID 分开注入,并在日志中记录实际生效的路由配置。

下面是兼容客户端的配置示例,模型 ID 按 2026 年 10 月 27 日目录填写:

python 复制代码
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["LLM_API_KEY"],
    base_url=os.environ["LLM_BASE_URL"],
)

MODEL = "qwen3.7-max"

预算检查最好放在最后一次请求组装和序列化之后。工具 Schema、默认 system/developer 消息、响应格式或工作流字段,可能要等业务代码执行完才被注入。

python 复制代码
payload = build_final_payload()

while request_tokens(payload) > input_budget:
    action = shrink_once(payload)

    if action is None:
        raise ValueError("context budget cannot be satisfied")

    log_shrink_action(action, payload)

send(payload)

shrink_once() 每次应只执行一个可解释的动作,并记录:

  • 裁剪对象;
  • 裁剪原因;
  • 裁剪前后的 token;
  • 是否为估算值;
  • 是否影响历史、工具结果或输出预算。

不要在循环里无条件删除消息。否则线上只能看到"请求成功了",却无法解释模型为什么突然丢失规则或上下文。

400 和 429 不要共用一套重试策略

这两个状态码经常被放进同一个重试中间件,但通常需要不同的处理方式:

  • 429 常见于速率或配额限制,可结合 Retry-After 和服务商文档执行退避;
  • 400 context_length_exceeded 表示当前请求不符合上下文限制,等待不会使请求自动变短;
  • 如果网关使用其他状态码或错误码,还要结合 message、usage、响应头和服务商文档判断;
  • 经过网关时,应同时保存 HTTP 状态码、错误字段、模型 ID、请求的输出上限和各部分 token 估算;
  • 日志应移除 API Key、业务文档正文和用户隐私,但不能只留一句"请求失败"。

建议至少记录以下字段:

json 复制代码
{
  "model": "qwen3.7-max",
  "status": 400,
  "code": "context_length_exceeded",
  "request_id": "[REDACTED]",
  "system_tokens": 1200,
  "tool_tokens": 3500,
  "history_tokens": 9200,
  "current_tokens": 1800,
  "current_tool_result_tokens": 2600,
  "token_counts_estimated": true,
  "max_completion_tokens": 4096
}

这些数值仅用于演示日志结构,不代表 qwen3.7-max 的官方限制或某次真实请求的 token 用量。

常见问题 FAQ

Q:qwen3.7-max 返回 400,是不是只能输入 400 token?

不是。400 是 HTTP 客户端错误状态码,与允许输入的 token 数没有这种对应关系。

如果响应中明确出现 context_length_exceeded,可以直接判断为上下文长度相关错误。若接口使用其他 code、文案或 HTTP 状态码,则还需要结合 message、usage、响应头和服务商文档判断。

Q:为什么删除 10 条聊天记录后仍然超限?

聊天记录只是请求上下文的一部分。system/developer 消息、工具 Schema、结构化输出 Schema、RAG 文档、附件、工具返回值和输出预算都可能占据大量空间。

应先按六类分项计数,再决定压缩哪一项,不要只按消息条数判断。

Q:把 max_tokens 调小能解决超限吗?

有可能,但取决于具体路由的校验规则。

如果服务端按"输入占用 + 请求的最大输出预算"进行校验,而且请求只是轻微越界,调小输出上限可能有效。如果输入本身已经超过该路由的最大输入限制,仍然必须裁剪输入。

参数名可能是 max_tokens,也可能是 max_completion_tokens,应以接口文档为准。

Q:context_length_exceeded 可以自动重试吗?

不能原样重试。程序可以在修改请求后自动重试,例如:

  1. 降低不必要的输出预算;
  2. 对工具结果执行字段筛选或分页;
  3. 减少 RAG 片段;
  4. 摘要旧的完整回合;
  5. 重新计算最终序列化请求的 token。

原样重试适合部分瞬时故障,不适合确定性的超长请求。

Q:字符数可以直接换算成 token 吗?

不能精确换算。汉字、英文、代码、JSON、URL 和工具 Schema 的分词比例不同。拿不到匹配的 tokenizer 时只能进行保守估算,并留出安全缓冲,同时在日志中标记计数结果为 estimated。

Q:为什么不能直接保留最后八条消息?

因为 system/developer 消息可能位于数组头部,直接取末尾可能把固定规则删除;同时还可能拆散 assistant tool call 与对应的 tool result。

应先分离固定消息,再按完整回合进行裁剪。

我的最终选择

我现在会把上下文当成有限资源,而不是把 messages 当成可以无限增长的数组。

具体做法是:

  • 单独统计 system/developer 消息;
  • 对工具定义和响应 Schema 做版本化管理;
  • 历史对话使用有限摘要加最近完整回合;
  • 历史工具结果统一归入历史回合;
  • 本轮工具结果单独设置预算;
  • 对 RAG、附件和工具输出执行字段筛选、分页与结构化摘要;
  • 在最终序列化之后执行一次预算检查;
  • 根据当前路由的文档和错误响应选择校验规则。

核心原则很简单:先把六本账算清楚,再决定压缩什么。

随手删除几条聊天记录只是碰运气。只有统一系统消息、工具定义、历史回合、当前输入、本轮工具结果和预留输出的归属与计数口径,服务才能长期稳定运行。

相关推荐
警醒与鞭策13 小时前
Codex桌面版与Claude桌面版使用设置
android·unity·ai·性能优化
奇牙coding15 小时前
GPT-5.5 报 401 invalid_api_key 怎么办?换密钥前按 4 层排查请求头、项目权限和端点
数据库·gpt·ios·ai
西索斯coding17 小时前
Qwen3-Max 与 Qwen-Plus:60 个真实修复任务评测方案
大数据·网络·算法·ai
骑着蜗牛撵大象32718 小时前
让 Codex 值夜班:把告警排查与巡检接成可回滚的自动化链路
android·java·运维·ai·自动化·codex·智能体
玩泥巴的18 小时前
一份声明,多份产物:飞书 SDK 的 AI 工具面是怎么生成的
ai·.net·飞书·agent
king135987455019 小时前
AI Agent 端云协同选型:Node.js 任务成本算例与权限检查清单(2027—2028 展望)
人工智能·ai·node.js·ai agent
林伽一19 小时前
修复流程被开源成智能体技能,产业侧开始分工|2026年10月26日
人工智能·科技·ai·开源
小七-七牛开发者20 小时前
从 GLM 到 GPT,HarnessPro 如何用 RSI 降低 Agent 成本
ai·大模型·agent·ai coding·harnesspro
看浪的路人20 小时前
第8讲:分布式链路追踪
ai