DeepSeek V4 API 报错排查:429/超时/百万上下文切片,踩了 3 天坑的血泪记录

2 阅读1分钟

上周 DeepSeek V4 预览版刚开放 API 接入,我第一时间就上手了。百万级上下文窗口这个卖点确实让我兴奋了一把——手头正好有个长文档摘要的项目,之前用 DeepSeek V3 只能切到 128K,各种拼接逻辑写得我头秃。

结果呢?三天时间,429 报错、超时断连、上下文切片莫名截断,能踩的坑我全踩了一遍。这篇文章就是我这三天的排查笔记,所有日志都是真实环境跑出来的,希望能帮后来人少走点弯路。

先说环境

项目配置
DeepSeek V4 版本preview-0627(2026年6月预览版)
SDKopenai-python 1.82.0
Python3.12.4
测试场景长文档摘要(单次输入 200K~1M tokens)
并发量5~20 QPS
部署环境阿里云 ECS 2C4G + 本地 Mac M3

坑一:429 Too Many Requests,但我明明没超限

症状

刚上手第一天,跑了个简单的批量测试脚本,20 个请求并发出去,大概第 7、8 个开始疯狂 429:

httpx.HTTPStatusError: Error response 429 while requesting POST https://api.deepseek.com/v1/chat/completions
{
 "error": {
 "message": "Rate limit reached for deepseek-v4-preview on requests per minute (RPM): Limit 10, Used 10, Requested 1.",
 "type": "requests",
 "code": "rate_limit_exceeded"
 }
}

我看到这个日志第一反应是——10 RPM?这也太低了吧?我之前用 DeepSeek V3 的时候 RPM 限制是 60,V4 预览版直接砍到 10?

排查过程

去官方文档翻了一圈,预览版的 Rate Limit 页面写得很模糊,只说「预览阶段限制可能与正式版不同」。又去社区翻了翻,发现好几个人遇到同样的问题。

最后在 DeepSeek 的 Discord 频道里找到一个官方人员的回复:

V4 预览版目前 RPM 限制为 10(免费层)/ 30(付费层),TPM 限制为 500K(免费层)/ 2M(付费层)。正式版会放开。

关键信息:不光有 RPM 限制,还有 TPM(Tokens Per Minute)限制。我之前只关注了请求数,没注意到单次请求如果塞了 200K tokens 的上下文,两三个请求就把 TPM 打满了。

解决方案

写了个简单的令牌桶限流器,同时控制 RPM 和 TPM:

import asyncio
import time
from dataclasses import dataclass

@dataclass
class RateLimiter:
 rpm_limit: int = 25 # 留点余量,不要打满
 tpm_limit: int = 1_800_000
 _request_timestamps: list = None
 _token_usage: list = None
 
 def __post_init__(self):
 self._request_timestamps = []
 self._token_usage = []
 
 def _clean_old_entries(self):
 now = time.time()
 cutoff = now - 60
 self._request_timestamps = [t for t in self._request_timestamps if t > cutoff]
 self._token_usage = [(t, n) for t, n in self._token_usage if t > cutoff]
 
 async def wait_if_needed(self, estimated_tokens: int):
 while True:
 self._clean_old_entries()
 current_rpm = len(self._request_timestamps)
 current_tpm = sum(n for _, n in self._token_usage)
 
 if current_rpm < self.rpm_limit and (current_tpm + estimated_tokens) < self.tpm_limit:
 self._request_timestamps.append(time.time())
 self._token_usage.append((time.time(), estimated_tokens))
 return
 
 await asyncio.sleep(1)

# 使用
limiter = RateLimiter(rpm_limit=25, tpm_limit=1_800_000)

async def call_deepseek_v4(messages, estimated_tokens):
 await limiter.wait_if_needed(estimated_tokens)
 # 实际调用...

另一个偷懒但有效的方案是加指数退避重试:

import openai
import time

def call_with_retry(client, messages, max_retries=5):
 for attempt in range(max_retries):
 try:
 return client.chat.completions.create(
 model="deepseek-v4-preview",
 messages=messages,
 )
 except openai.RateLimitError as e:
 wait_time = min(2 ** attempt * 3, 60) # 3s, 6s, 12s, 24s, 48s
 print(f"429 hit, attempt {attempt+1}, waiting {wait_time}s...")
 print(f" Detail: {e.message}")
 time.sleep(wait_time)
 raise Exception("Max retries exceeded")

坑二:超时问题,百万上下文下的 TTFT 炸了

症状

429 搞定之后,我开始测百万级上下文。塞了大概 800K tokens 的长文档进去,然后……等了快两分钟,连第一个 token 都没吐出来,最后直接超时:

httpx.ReadTimeout: timed out

openai-python 默认超时是 600 秒,我以为够了。结果用 streaming 模式的时候,问题更诡异——stream 连接建立了,但第一个 chunk 迟迟不来,然后连接被服务端断开:

openai.APIConnectionError: Connection error.
 [SSL: UNEXPECTED_EOF_RECEIVED] unexpected eof received (_ssl.c:1000)

排查过程

我用 time 命令粗略测了不同上下文长度下的 TTFT(Time To First Token):

输入 TokensTTFT(秒)是否超时
10K1.2
50K3.8
100K8.5
200K22.3
500K68.7
800K142.5❌(但很慢)
1M✅ 超时

TTFT 基本随上下文长度超线性增长。800K 的时候已经要等两分多钟才出第一个 token,1M 直接超时。

我猜预览版的 prefill 还没做好优化,或者我的请求被排到了低优先级队列。

解决方案

方案一:加大超时时间

最简单粗暴的办法:

from openai import OpenAI

client = OpenAI(
 api_key="your-key",
 base_url="https://api.deepseek.com/v1",
 timeout=900.0, # 15 分钟,百万上下文真的需要这么久
)

如果用 streaming,还要单独设置:

# httpx 的超时配置更细粒度
import httpx

client = OpenAI(
 api_key="your-key",
 base_url="https://api.deepseek.com/v1",
 http_client=httpx.Client(
 timeout=httpx.Timeout(
 connect=30.0,
 read=300.0, # 读超时拉长
 write=30.0,
 pool=30.0,
 )
 ),
)

方案二:分段处理,别一次性塞满

对大多数场景来说,一次性塞 1M tokens 进去并不是最优解。我后来改成了分段摘要 + 合并的策略,效果反而更好:

graph TD
 A[原始长文档 1M tokens] --> B[切片器]
 B --> C1[片段1: 100K tokens]
 B --> C2[片段2: 100K tokens]
 B --> C3[片段3: 100K tokens]
 B --> C4[...]
 C1 --> D1[V4 摘要]
 C2 --> D2[V4 摘要]
 C3 --> D3[V4 摘要]
 C4 --> D4[...]
 D1 --> E[合并所有摘要]
 D2 --> E
 D3 --> E
 D4 --> E
 E --> F[V4 最终摘要]

每段控制在 100K~200K tokens,TTFT 能控制在 20 秒以内,整体体验好很多。

坑三:百万上下文切片的隐藏 Bug

这个坑是最恶心的,花了我整整一天半才定位到。

症状

我把一份 600K tokens 的法律合同塞进去,让 V4 提取关键条款。返回结果看起来没问题,但仔细一对比发现——文档后半部分的内容完全没被模型"看到"

具体表现:我在文档末尾故意插了一段很明显的测试文本 [CANARY_TOKEN_XYZ_12345],然后问模型「文档中是否包含 CANARY_TOKEN_XYZ_12345?」,模型回答「未找到相关内容」。

但同样的文本放在文档开头 50K tokens 的位置,模型就能正确识别。

排查过程

一开始我以为是经典的「Lost in the Middle」问题,但这次不太一样——不是中间丢了,是尾部整个没了。

我写了个测试脚本,在不同位置插入 canary token,测试模型能否检索到:

import tiktoken

def create_test_document(total_tokens, canary_position_ratio):
 """在指定位置插入金丝雀标记"""
 enc = tiktoken.encoding_for_model("gpt-4") # V4 tokenizer 类似
 
 # 用重复文本填充
 filler = "这是一段用于填充的普通文本内容,不包含任何特殊标记。" * 1000
 filler_tokens = enc.encode(filler)
 
 canary = "\n\n[CANARY_MARKER_ALPHA_98765] 这是一个特殊的测试标记。\n\n"
 canary_tokens = enc.encode(canary)
 
 insert_pos = int(total_tokens * canary_position_ratio)
 
 before = enc.decode(filler_tokens[:insert_pos])
 after = enc.decode(filler_tokens[insert_pos:total_tokens - len(canary_tokens)])
 
 return before + canary + after

# 测试不同位置
for ratio in [0.1, 0.3, 0.5, 0.7, 0.9, 0.95]:
 doc = create_test_document(500_000, ratio)
 # 调用 API 测试...

测试结果:

Canary 位置(占总长比例)500K 文档300K 文档100K 文档
10%✅ 找到✅ 找到✅ 找到
30%✅ 找到✅ 找到✅ 找到
50%✅ 找到✅ 找到✅ 找到
70%⚠️ 偶尔找到✅ 找到✅ 找到
90%❌ 找不到⚠️ 偶尔找到✅ 找到
95%❌ 找不到❌ 找不到✅ 找到

规律很明显:上下文越长,尾部丢失越严重

我把这个结果发到 DeepSeek 的 GitHub Issues 里,过了大半天收到回复,大意是:

已知问题,预览版在超过 256K tokens 时存在 attention 精度下降的情况,正在修复中。建议预览阶段单次输入不超过 300K tokens。

好家伙,百万上下文的预览版,建议不超过 300K。

我的 Workaround

既然长上下文尾部不靠谱,那就把重要内容往前放。我写了个预处理函数,对超长文档做"重要性排序"后再拼接:

def reorder_chunks_for_long_context(chunks: list[str], query: str) -> list[str]:
 """
 把与 query 最相关的 chunk 放在文档开头和中间,
 不太相关的放在尾部(反正尾部可能丢)
 """
 from sentence_transformers import SentenceTransformer
 import numpy as np
 
 model = SentenceTransformer('BAAI/bge-large-zh-v1.5')
 
 query_emb = model.encode([query])[0]
 chunk_embs = model.encode(chunks)
 
 similarities = np.dot(chunk_embs, query_emb) / (
 np.linalg.norm(chunk_embs, axis=1) * np.linalg.norm(query_emb)
 )
 
 # 按相关性排序,最相关的放前面
 sorted_indices = np.argsort(-similarities)
 
 # 但完全按相关性排会破坏文档结构,所以用交替策略:
 # 高相关 -> 原始顺序 -> 高相关 -> 原始顺序...
 high_relevance = sorted_indices[:len(sorted_indices)//3]
 rest = sorted_indices[len(sorted_indices)//3:]
 
 reordered = []
 for idx in high_relevance:
 reordered.append(chunks[idx])
 for idx in sorted(rest): # 剩余的按原始顺序
 reordered.append(chunks[idx])
 
 return reordered

这个方案不完美,但在预览版修复之前,至少能保证关键信息不被"吃掉"。

其他零散的坑

JSON Mode 偶发格式错误

V4 预览版的 JSON Mode 在长输出时偶尔会吐出不合法的 JSON,具体表现是尾部截断——大括号没闭合:

{"summary": "这是一段很长的摘要...", "key_points": ["第一点", "第二点", "第三

我的处理方式是加一层修复:

import json

def safe_parse_json(text: str) -> dict:
 try:
 return json.loads(text)
 except json.JSONDecodeError:
 # 尝试补全常见的截断情况
 for suffix in ['"}', '"]}', '"]}' , '"]}}']: 
 try:
 return json.loads(text + suffix)
 except:
 continue
 # 实在不行就用 LLM 修复(套娃了属于是)
 raise

Streaming 模式下 usage 字段缺失

V4 预览版的 streaming 响应里,最后一个 chunk 的 usage 字段经常是 null。这导致没法准确统计 token 消耗。临时方案是用 tiktoken 本地估算,等正式版修复。

接入方案推荐

我目前用的是 ofox.ai 的聚合接口,一个 API Key 就能调用 GPT-5、Claude 4.6、Gemini 3、DeepSeek V3 等 50+ 模型,兼容 OpenAI SDK 格式,改个 base_url 就行:

from openai import OpenAI
client = OpenAI(
    api_key="your-key",
    base_url="https://api.ofox.ai/v1"  # 聚合接口,一个 Key 用所有模型
)

低延迟直连,支持支付宝/微信付款,按量计费免费版可起步。

小结

DeepSeek V4 预览版的百万上下文确实是个大卖点,但现阶段(2026 年 6 月底)毛刺不少:

  1. 429 报错:预览版 RPM/TPM 限制比 V3 低很多,要做好限流
  2. 超时问题:超过 500K tokens 的 TTFT 非常长,建议分段处理
  3. 尾部丢失:超过 256K tokens 后尾部内容可能被"忽略",重要内容往前放
  4. JSON Mode:长输出偶发截断,加一层容错

我的建议是:如果你的场景不是非百万上下文不可,先把单次输入控制在 200K 以内,等正式版出来再上强度。预览版拿来跑跑 demo、测测效果可以,生产环境还是再等等。

这三天虽然踩坑踩得够呛,但也算是对 V4 的能力边界摸了个底。等正式版发布我再来更新一波测试数据。有同样在折腾 V4 的兄弟,评论区交流~