HappyHorse 1.0 正式上线立即体验
LLM API 调用中的 Context Length Exceeded 错误:修复、权衡与模型选择
guide

LLM API 调用中的 Context Length Exceeded 错误:修复、权衡与模型选择

EvoLink Team
EvoLink Team
Product Team
2026年5月13日
13 分钟阅读

你的 LLM API 调用刚刚返回:

{
  "error": {
    "message": "This model's maximum context length is 128000 tokens. However, your messages resulted in 142837 tokens.",
    "type": "invalid_request_error",
    "code": "context_length_exceeded"
  }
}

这意味着你的输入(系统提示词 + 对话历史 + 用户消息)超过了模型的上下文窗口。请求在生成任何 token 之前就被拒绝了。

与速率限制错误(关于请求量)不同,上下文长度错误关于的是请求大小。修复方法不是放慢速度——而是减少输入、重构请求或切换到上下文窗口更大的模型。

要点速览

  • Context length exceeded = 你的输入对于模型的 token 窗口来说太大了。
  • 你有四个选项:截断、摘要、分块或切换模型。
  • 每个选项在成本、质量和延迟上有不同的权衡。
  • 使用下面的决策表为你的工作负载选择正确的方案。
  • 对于生产系统,在用户看到错误之前就处理它——而不是之后。

快速修复清单

在选择策略之前,先检查这些常见原因:

检查项要找什么快速修复
对话历史过长多轮聊天中 messages 数组无限增长裁剪最旧的消息或实现滑动窗口
系统提示词过大详细指令占用了上下文预算压缩系统提示词或将静态指令移到引用中
重复内容相同上下文被多次注入(RAG、工具结果)发送前去重
大型工具/函数结果工具调用返回了巨大的 JSON 或文本块在添加到上下文之前截断或摘要工具输出
不必要的元数据只需几个字段却传入了完整对象只提取相关字段

如果这些快速修复都不适用,你需要一个结构化的方案。

决策表:截断 vs 摘要 vs 分块 vs 切换模型

策略工作原理质量影响成本影响延迟影响最适合
截断丢弃最旧的消息或裁剪输入可能丢失重要上下文减少输入 token = 更低成本更快(更少输入)长历史的聊天应用;近期上下文最重要的场景
摘要使用更便宜的模型将先前上下文压缩为摘要有损——摘要可能遗漏细节额外一次摘要 API 调用,但主调用更小增加一次额外调用有累积状态的 Agent 工作流;知识密集型对话
分块 + 合并将输入分成块,分别处理,合并结果可能丢失跨块上下文的风险多次调用 = 更高总成本更慢(串行或并行分块)文档处理、长文本分析
切换模型使用上下文窗口更大的模型通常中性或更好大上下文模型通常每 token 更贵可能有差异当输入无法在不丢失关键信息的情况下缩减时

策略 1:截断——丢弃最不重要的部分

截断是最简单的方案。移除输入中最旧或最不相关的部分。

聊天的滑动窗口

def sliding_window(messages: list, max_tokens: int, system_prompt: str) -> list:
    """在 token 预算内保留系统提示词 + 最近的消息。"""
    # 始终保留系统提示词
    result = [{"role": "system", "content": system_prompt}]
    token_count = count_tokens(system_prompt)

    # 从最新到最旧添加消息
    for msg in reversed(messages):
        msg_tokens = count_tokens(msg["content"])
        if token_count + msg_tokens > max_tokens:
            break
        result.insert(1, msg)  # 插入到系统提示词之后
        token_count += msg_tokens

    return result

截断何时效果好

  • 近期上下文最重要的多轮聊天
  • 需要时可以重新检索的 RAG 管道
  • 可以按顺序处理的批处理

截断何时危险

  • 早期指令影响后续行为的 Agent 工作流
  • 完整性很重要的法律或合规场景
  • 丢弃步骤会改变答案的多步推理

策略 2:摘要——在不丢失含义的情况下压缩

摘要使用更便宜、更快的模型来压缩先前上下文:

async def summarize_context(messages: list, client) -> str:
    """将较旧的消息压缩为摘要。"""
    context_text = "\n".join(
        f"{m['role']}: {m['content']}" for m in messages
    )

    response = await client.chat.completions.create(
        model="gpt-4o-mini",  # 用更便宜的模型做摘要
        messages=[{
            "role": "user",
            "content": f"将这段对话上下文总结在 500 token 以内。"
                       f"保留关键决策、事实和待办事项:\n\n{context_text}"
        }],
        max_tokens=500
    )

    return response.choices[0].message.content

成本对比:摘要 vs 截断

方案主模型的输入 token额外 API 调用总成本
不管理(触发错误)不适用——请求被拒绝0浪费的延迟 + 重试成本
截断到 80K token80K080K 输入的基础成本
摘要旧上下文~10K(摘要)+ 40K(近期)= 50K1 次摘要调用(约 $0.01)更低的主调用成本 + 小额摘要成本

摘要增加了一次便宜的调用,但通常能显著减少主调用的输入 token——对于昂贵的模型,这可以实现净成本下降。

策略 3:分块和合并——用于长文档

当输入是单个长文档(而非对话)时,分块通常是正确的方案:

async def process_long_document(
    document: str,
    question: str,
    client,
    chunk_size: int = 50000
) -> str:
    """通过分块处理长文档。"""
    chunks = split_into_chunks(document, chunk_size)
    chunk_results = []

    for i, chunk in enumerate(chunks):
        response = await client.chat.completions.create(
            model="gpt-4o",
            messages=[{
                "role": "user",
                "content": f"分析文档的第 {i+1}/{len(chunks)} 块。\n"
                           f"问题:{question}\n\n"
                           f"块内容:\n{chunk}"
            }]
        )
        chunk_results.append(response.choices[0].message.content)

    # 合并块结果
    merge_response = await client.chat.completions.create(
        model="gpt-4o",
        messages=[{
            "role": "user",
            "content": f"将这些部分分析合并为最终答案。\n"
                       f"问题:{question}\n\n"
                       f"部分结果:\n" + "\n---\n".join(chunk_results)
        }]
    )

    return merge_response.choices[0].message.content

分块的权衡

  • 优点:可处理任意长度的输入
  • 缺点:跨块上下文会丢失
  • 缺点:多次 API 调用 = 更高的总成本和延迟
  • 缓解:块之间重叠 10-20% 以保留边界上下文

策略 4:切换到上下文更大的模型

如果你的输入确实需要保持大体量,切换到上下文窗口更大的模型:

模型家族典型最大上下文何时使用
GPT-4o128K token大多数工作负载的默认选择
Claude Sonnet/Opus200K token当你需要超过 128K 时
Gemini Pro1M+ token非常长的文档、完整代码库
Gemini Flash1M+ token成本敏感的长上下文任务

使用路由网关进行模型选择

与其硬编码模型选择,你可以使用根据输入大小路由到合适模型的网关:

from openai import OpenAI

client = OpenAI(
    api_key="your-evolink-key",
    base_url="https://api.evolink.ai/v1"
)

# 让 Smart Router 根据你的工作负载选择
response = client.chat.completions.create(
    model="evolink/auto",
    messages=your_messages
)
EvoLink 的 Smart Router 可以路由到符合你输入大小和成本要求的模型,这样你就不需要硬编码上下文窗口阈值。

生产模式:发送前预检查

不要等 API 拒绝你的请求。在发送前检查输入大小:

import tiktoken

def check_context_length(messages: list, model: str, max_tokens: int) -> dict:
    """预检查消息是否在模型的上下文窗口内。"""
    encoder = tiktoken.encoding_for_model(model)
    total_tokens = sum(
        len(encoder.encode(m["content"])) for m in messages
    )

    if total_tokens > max_tokens:
        return {
            "fits": False,
            "total_tokens": total_tokens,
            "excess": total_tokens - max_tokens,
            "suggestion": "truncate" if total_tokens < max_tokens * 1.5
                          else "summarize_or_switch"
        }

    return {"fits": True, "total_tokens": total_tokens}

这避免了被拒绝请求浪费的延迟,让你在用户看到错误之前就应用正确的策略。

相关文章

探索 EvoLink Smart Router

常见问题

"Context length exceeded" 是什么意思?

它意味着你的输入(系统提示词 + 消息 + 任何注入的上下文)包含的 token 超过了模型最大上下文窗口允许的数量。请求在任何生成开始之前就被拒绝了。

Context length exceeded 和速率限制错误一样吗?

不一样。速率限制错误(429)关于请求量——在一个时间窗口内请求太多。上下文长度错误关于请求大小——单个请求太大。它们需要不同的修复方法。

截断和摘要哪个更好?

截断更简单、更便宜但会丢失信息。摘要保留含义但增加额外的 API 调用并引入压缩失真。对于近期最重要的聊天历史使用截断;对于累积上下文很重要的 Agent 工作流使用摘要。

使用更大的模型能避免 context length 错误吗?

可以,但有成本代价。上下文窗口更大的模型(Gemini 1M+、Claude 200K)可以接受更大的输入,但每 token 通常更贵。计算质量改进是否值得额外的花费。

如何在发送请求前计算 token 数?

对于 OpenAI 模型使用 tiktoken 库,或使用提供商的 token 计数端点。确切的 token 数取决于模型的分词器,所以请为你的目标模型使用正确的编码器。

应该在应用代码还是网关层面处理这个错误?

两者都要。应用代码应预检查输入大小并应用合适的策略(截断、摘要、分块或切换)。路由网关可以额外选择上下文窗口足够大的模型来处理你的请求。

准备好把 AI 成本降低 89% 吗?

现在就开始使用 EvoLink,体验智能 API 路由的强大能力。