上周 DeepSeek V4 预览版刚开放 API 接入,我第一时间就上手了。百万级上下文窗口这个卖点确实让我兴奋了一把——手头正好有个长文档摘要的项目,之前用 DeepSeek V3 只能切到 128K,各种拼接逻辑写得我头秃。
结果呢?三天时间,429 报错、超时断连、上下文切片莫名截断,能踩的坑我全踩了一遍。这篇文章就是我这三天的排查笔记,所有日志都是真实环境跑出来的,希望能帮后来人少走点弯路。
先说环境
| 项目 | 配置 |
|---|---|
| DeepSeek V4 版本 | preview-0627(2026年6月预览版) |
| SDK | openai-python 1.82.0 |
| Python | 3.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):
| 输入 Tokens | TTFT(秒) | 是否超时 |
|---|---|---|
| 10K | 1.2 | ❌ |
| 50K | 3.8 | ❌ |
| 100K | 8.5 | ❌ |
| 200K | 22.3 | ❌ |
| 500K | 68.7 | ❌ |
| 800K | 142.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 月底)毛刺不少:
- 429 报错:预览版 RPM/TPM 限制比 V3 低很多,要做好限流
- 超时问题:超过 500K tokens 的 TTFT 非常长,建议分段处理
- 尾部丢失:超过 256K tokens 后尾部内容可能被"忽略",重要内容往前放
- JSON Mode:长输出偶发截断,加一层容错
我的建议是:如果你的场景不是非百万上下文不可,先把单次输入控制在 200K 以内,等正式版出来再上强度。预览版拿来跑跑 demo、测测效果可以,生产环境还是再等等。
这三天虽然踩坑踩得够呛,但也算是对 V4 的能力边界摸了个底。等正式版发布我再来更新一波测试数据。有同样在折腾 V4 的兄弟,评论区交流~