Skip to content

关于PDF文件直接上传Base64二进制编码的bug #236

Description

@Timmy0307

上传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:    };

核心问题

  1. readFileSync 将 PDF 文件的全部二进制内容读到内存。
  2. buffer.toString("base64") 把约 2MB 的二进制转换为约 2,664,537 个字符 的 Base64 纯文本。
  3. 这段 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 消息)打包发送(createChatCompletionStreamlistSessionMessages 拼接 → 发送给 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) followUpMessagescontentParams(结构化附件)
对 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 数一样会爆。


Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions