上传PDF文件时,PDF 的完整 Base64 被直接塞入 tool output,从而导致输入tokens爆仓。
以下是DeepSeek给出的详细报错分析:
分析日期:2026-07-13
涉及版本:@vegamo/deepcode-cli(全局安装,路径 C:\Users\admin\AppData\Roaming\npm\node_modules\@vegamo\deepcode-cli\cli.js)
使用模型:deepseek-v4-pro(上下文硬上限:1,048,565 tokens)
一、报错现象
400 This model's maximum context length is 1048565 tokens.
However, you requested 1798018 tokens (1798018 in the messages, 0 in the completion).
Please reduce the length of the messages or completion.
每次传入 PDF 文件后,请求 token 数飙升至约 ~1,798,000 tokens,超出模型 1,048,565 上限,API 返回 400。
二、根因定位
2.1 直接原因:PDF 的完整 Base64 被直接塞入 tool output
源码位置:cli.js 第 75182 ~ 75225 行(read-handler.ts 编译产物)
关键代码路径:
75182: if (ext === ".pdf") {
75183: const pagesParam = typeof args.pages === "string" ? args.pages.trim() : "";
75184: const buffer = fs11.readFileSync(filePath); // ← 读入整个 PDF 文件
75185: const pageCount = countPdfPages(buffer);
75186: const pageRange = pagesParam ? parsePageRange(pagesParam) : null;
// ... 仅对 pageRange 做校验(line 75187-75207)...
75208: const base643 = buffer.toString("base64"); // ← 将整个 PDF 编成 Base64
75209: markFileRead(context.sessionId, filePath, { ... });
75214: return {
75215: ok: true,
75216: name: "read",
75217: output: `data:application/pdf;base64,${base643}`, // ← 全部塞进 output!
75218: metadata: {
75219: mime: "application/pdf",
75220: encoding: "base64",
75221: bytes: buffer.length,
75222: pageCount,
75223: pages: pageRange ? `${pageRange.start}-${pageRange.end}` : null
75224: }
75225: };
核心问题:
readFileSync 将 PDF 文件的全部二进制内容读到内存。
buffer.toString("base64") 把约 2MB 的二进制转换为约 2,664,537 个字符 的 Base64 纯文本。
- 这段 260 万字符的字符串被直接放入
output 字段,作为 tool role 消息的 content 由 _addMessage 写入对话历史。
2.2 放大效应:tool output 成为对话历史的永久部分
Tool 执行结果注入会话的链路(cli.js 第 79882 ~ 79898 行):
79886: const toolFunction = this.messageConverter.findToolFunction(toolCalls, execution.toolCallId);
79887: const toolMessage = this.buildToolMessage(
sessionId, execution.toolCallId, execution.content, toolFunction
);
79888: this.appendSessionMessage(sessionId, toolMessage); // ← 写入会话历史
79899: for (const followUpMessage of followUpMessages) {
79900: this.appendSessionMessage(sessionId, followUpMessage);
79901: }
而每次 API 调用都会将完整的会话历史(含所有 tool 消息)打包发送(createChatCompletionStream → listSessionMessages 拼接 → 发送给 DeepSeek API)。
这就形成了"利滚利"效应:
- 首次调用:System Prompt + Skill 文档 + 用户问题 + PDF Base64 tool output → ~179 万 tokens(爆表)
- 后续追问:上一轮的完整上下文(含 260 万字符的 Base64)再次被携带进去 → 永远卡死
日志中 5 条错误记录的 token 数验证了这一模式:
| 时间 |
SessionId |
请求 tokens |
| 06:06 |
65a80203-... (新) |
1,798,018 |
| 06:07 |
65a80203-... (追问) |
1,798,085 |
| 06:08 |
5a4b6230-... (新) |
1,798,005 |
| 06:10 |
5a4b6230-... (追问) |
1,798,082 |
| 06:23 |
5a5696b8-... (新) |
1,801,153 |
三、与图片处理的对比——设计不一致的"罪证"
同样位于 read-handler.ts,图片(image)的处理方式完全不同(cli.js 第 75227 ~ 75243 行):
if (isImageExtension(ext)) {
const buffer = fs11.readFileSync(filePath);
const mime = getImageMimeType(ext);
return {
ok: true,
name: "read",
output: "File loaded.", // ← 只返回简短文本!
metadata: { mime, bytes: buffer.length },
followUpMessages: [ // ← 图片数据走 followUpMessages
buildImageFollowUpMessage(filePath, mime, buffer)
]
};
}
buildImageFollowUpMessage(第 75489 行)将图片 Base64 放入 contentParams 字段,以结构化多模态附件的形式注入上下文——这与普通文本 token 计算的代价完全不同,DeepSeek 对多模态 content 数组中的图片 URL 有特殊处理。
关键差异:
|
PDF 处理 |
图片处理 |
tool output |
整个 Base64 字符串(260 万+字符) |
"File loaded."(极短) |
| 实际数据传递方式 |
纯文本字符串(tool role content) |
followUpMessages → contentParams(结构化附件) |
| 对 token 的影响 |
每个字符 1~1.5 token(灾难级) |
模型对 image_url 类型有独立处理 |
pages 参数作用 |
仅校验 → metadata,不参与编码 |
N/A |
结论:PDF 的处理逻辑是未完整实现的"半成品"——它借鉴了图片的分支结构(有 metadata 字段),但没有正确使用 followUpMessages 机制,导致大量二进制编码文本被当作对话内容直接注入上下文。
四、额外发现:pages 参数形同虚设
注意第 75208 行:
const base643 = buffer.toString("base64"); // ← 永远是整个 buffer!
即使用户传入 pages: "1-5",代码也没有任何页面提取逻辑。pageRange 仅在第 75187~75207 行做校验(检查页码范围是否合法),并在第 75223 行写入 metadata,但对实际的 Base64 编码范围毫无影响。
这意味着:目前指定 pages 参数也无法解决问题,请求 token 数一样会爆。
上传PDF文件时,PDF 的完整 Base64 被直接塞入 tool output,从而导致输入tokens爆仓。
以下是DeepSeek给出的详细报错分析:
一、报错现象
每次传入 PDF 文件后,请求 token 数飙升至约 ~1,798,000 tokens,超出模型 1,048,565 上限,API 返回 400。
二、根因定位
2.1 直接原因:PDF 的完整 Base64 被直接塞入 tool output
源码位置:
cli.js第 75182 ~ 75225 行(read-handler.ts编译产物)关键代码路径:
核心问题:
readFileSync将 PDF 文件的全部二进制内容读到内存。buffer.toString("base64")把约 2MB 的二进制转换为约 2,664,537 个字符 的 Base64 纯文本。output字段,作为 tool role 消息的content由_addMessage写入对话历史。2.2 放大效应:tool output 成为对话历史的永久部分
Tool 执行结果注入会话的链路(
cli.js第 79882 ~ 79898 行):而每次 API 调用都会将完整的会话历史(含所有 tool 消息)打包发送(
createChatCompletionStream→listSessionMessages拼接 → 发送给 DeepSeek API)。这就形成了"利滚利"效应:
日志中 5 条错误记录的 token 数验证了这一模式:
65a80203-...(新)65a80203-...(追问)5a4b6230-...(新)5a4b6230-...(追问)5a5696b8-...(新)三、与图片处理的对比——设计不一致的"罪证"
同样位于
read-handler.ts,图片(image)的处理方式完全不同(cli.js第 75227 ~ 75243 行):buildImageFollowUpMessage(第 75489 行)将图片 Base64 放入contentParams字段,以结构化多模态附件的形式注入上下文——这与普通文本 token 计算的代价完全不同,DeepSeek 对多模态 content 数组中的图片 URL 有特殊处理。关键差异:
output"File loaded."(极短)followUpMessages→contentParams(结构化附件)image_url类型有独立处理pages参数作用结论:PDF 的处理逻辑是未完整实现的"半成品"——它借鉴了图片的分支结构(有 metadata 字段),但没有正确使用
followUpMessages机制,导致大量二进制编码文本被当作对话内容直接注入上下文。四、额外发现:
pages参数形同虚设注意第 75208 行:
即使用户传入
pages: "1-5",代码也没有任何页面提取逻辑。pageRange仅在第 75187~75207 行做校验(检查页码范围是否合法),并在第 75223 行写入 metadata,但对实际的 Base64 编码范围毫无影响。这意味着:目前指定
pages参数也无法解决问题,请求 token 数一样会爆。