DeepSeek Harness 长截图把会话拖死:image 3000 像素上限与后续 400

故障排查发布于 2026-10-30作者: DeepSeek Plugin 插件市场
DeepSeek HarnessDSH400图片长截图tokenAnthropic
DeepSeek Harness 里一张整页长截图,会让之后每个回合都报 400:图片边长超过 many-image 请求的 3000 像素上限。按发生前、发生时、发生后看投影与重编码链路,并给三条止损动作。

一张 390×6400 的整页截图进了会话之后,用户什么也没做,下一轮、再下一轮全是同一个 400:At least one of the image dimensions exceed max allowed size for many-image requests: 3000 pixels。 这不是图片损坏,也不是这一轮上传失败——是那张图已经被写进历史,此后每次请求都会重新编码它,于是每一轮都在同一个位置被拒(#11511)。按时间线拆开看最清楚。

DeepSeek Harness 发生前:一张竖图被投影成多长

投影分两步:先按 token 网格缩放,再把长边夹到 4096;宽图会被网格压到限制内,整页竖图则卡在 4096 上。

  1. 第一步是 token 网格缩放,上限是 MAX_IMAGE_TOKENS = 1024。
  2. 第二步把长边夹到 REQUEST_IMAGE_MAX_DIMENSION。
  3. 社区实测的三组数据:
源尺寸网格投影后实际发送(上限 4096)
1440×3900755×2044755×2044
520×6400336×4130333×4096
390×6400293×4816250×4096
  1. 1440×3900 这种宽图投影后远低于 3000,所以没事。
  2. 后两行都被夹到 4096——一个无论如何满足不了 3000 上限的数字。

这就解释了为什么故障「只在某些截图之后出现」:不是所有图都超,是整页竖图才超。

DSH插件 发生时:为什么整条请求被拒

Anthropic Messages 对多图请求的每条边限 3000 像素,且是整条请求级拒绝;只要有一张超,这一轮就没了。

  1. 看报错索引:messages.9.content.88.image.source.base64.data 指向的是一张历史图片,不是本轮新上传的。
  2. 看常量:packages/llm/llm-deepseek/src/request-pricing.ts 里 REQUEST_IMAGE_MAX_DIMENSION = 4096。
  3. 看注释:它把 4096 称作「携带 15 张以上图片时的提供方单边限制」,而端点的 400 明确写 3000——两个数字不可能同时成立。
  4. 结果:适配器投影出 4096 长边,端点按 3000 判,整轮 400,这一轮的所有内容都拿不到。

社区在 master ce7073d5(比 dsh-v0.2.2-alpha.2 多 351 个提交)上复算过,clamp 确实触发了,也确实仍发出 4096——这不是估算,是代码算出来的。

DeepSeek Harness 发生后:历史图片每轮重编码,所以每轮都挂

编码器和计费器共用 resolveRequestImageTarget 这一个目标,历史里每张保留的图片在每次请求时都会被重新投影成同一尺寸。

  1. images.ts 调用 resolveRequestImageTarget 生成实际发送的字节。
  2. 也就是说,不是「上传那一刻错了」,而是「每次请求都按 4096 再生成一遍」。
  3. 于是只要这张图还在历史里,每一轮都会带上一个 4096 长边的图片。
  4. 重试没用、换措辞没用,甚至这一轮完全不带新图片也没用。
  5. 想恢复只有两条:把那张图从历史里去掉,或者新开会话。

修复补丁把上限做成按模型声明的字段:能力条目可以写 imageMaxDimension,不写就保持 4096,所以 DeepSeek 路由与现有配置完全不受影响。声明 3000 后,390×6400 会被投影成 183×3000,请求成功。适配器包测试 176 项通过。补丁在社区分支上,未合入。

DeepSeek插件 另一条时间线:中转按 base64 计价

同一个「图片把会话拖死」的症状,还有一条完全不同的机制:网关把 base64 当普通文本计价,约 1 token/原始字节,而本地计量器只记 55~59 token/张。

  1. 看现象:报错是 Input exceeds the context limit (1048568 tokens),不是尺寸超限。
  2. 看成本差:同样 5 张图,按 Anthropic 公开公式应收约 6,860 token,该路由收了 1,001,062,差 146 倍。
  3. 看计量器:estimateStructuralBlock() 对 image 块只算附件引用 JSON 的长度,55~59 token/张;真实约 1,001,062,差约 3,500 倍。
  4. 看后果:仪表显示 surface 只有约 6 万 token,而真实已占上限 95.5%,压缩永远选不中图片。
  5. 再看触发范围:社区收窄后确认,膨胀只发生在「网关把 Anthropic 格式转成 OpenAI 格式、且图片在 tool_result 里」时,而 read_image 恰好把图放在 tool_result 里。
  6. 对照实验:同一张 106,973 B 的 PNG,放在 tool_result 里计 93,775 token;放在 user 消息里、走另一张图时只计 1,242。

这条时间线还有个附带发现:路由没声明窗口时会取 defaultContextWindow = 262144,而上游真实上限是 1,048,568,相差 4 倍。在 profile 里给该路由声明 defaultContextWindow: 1048576 后,正在运行的会话下一次请求就切换了,不需要重启。

DeepSeek Harness 一分钟自查:先分清两条机制

看报错正文的两个关键词就够:出现 3000 pixels 是尺寸问题,出现 context limit 是计价问题。

  1. 复制完整报错,搜 3000 pixels,命中就是尺寸那条。
  2. 搜 Input exceeds the context limit,命中就是计价那条。
  3. 再看历史里有没有 read_image 或整页截图,两类都可能出现。
  4. 两条同时命中的情况很少,但中转既转协议又有尺寸上限时可能叠加。

DSH插件 止损:三条按代价排序的动作

先判断是哪条机制,再按「换协议 → 换会话 → 等补丁」的顺序处理。

  1. 换协议或换模型:中转场景下切到 openai-responses 或 Claude 路由,图片不再按 base64 计价。
  2. 发一条普通消息再压缩:换完路由后先发一条短消息更新 request/header,再执行 /compact;直接压缩往往仍按旧 header 计算。
  3. 声明路由窗口:在 profile 里加 defaultContextWindow: 1048576,让仪表按真实上限判断,减少无效压缩。
  4. 删掉那张图:长截图场景下,把包含该图片的那条消息从历史里去掉,或新开会话。
  5. 等按模型声明尺寸的补丁:imageMaxDimension 字段合入后,Messages 路由声明 3000 即可根治。

注意事项

  1. 别只看这一轮的输入。报错索引指向历史里的第 N 张图,不是刚上传的那张。
  2. 别把两种 400 混为一谈。尺寸超限是 invalid_request_error,上下文超限是 Input exceeds the context limit。
  3. 别指望 image/offload 救场。它只在 base64 字节超过 20 MiB 时触发,而真实被烧掉的是 token。
  4. 别用全页竖图做证据。需要截图时优先裁切或用宽幅,减少触发概率。
  5. 换协议后要更新 header。旧 header 里的计价与窗口会让压缩继续失灵。

如果你的 400 里带的是 messages.N.content.M 索引且提到 3000,就是第一篇的机制;如果提到 context limit 且历史里有 read_image,去查第二篇的计价。相关排查见 DeepSeek Harness 图片超过 8 张必报 400:image count limit 怎么修。

常见问题

DeepSeek Harness 报 400 说 image dimensions exceed max allowed size for many-image requests: 3000 pixels,怎么解?

这是 DeepSeek Harness 命中的 Anthropic Messages 端点 many-image 限制:每条边长最多 3000 像素,超一张就拒整条请求。而适配器常量 REQUEST_IMAGE_MAX_DIMENSION 仍是 4096,长截图会被压到 4096 而不是 3000,必然被拒。

为什么删掉那张长截图后就好了,可下一张又让 DeepSeek Harness 复发?

因为 DeepSeek Harness 会在每次请求时把历史里保留的每张图片按同一目标尺寸重新编码。只要还有一张被投影到 3000 以上,之后每一轮都会带上它,所以表现为「一张图拖垮整个会话」。

哪些截图会触发 DeepSeek Harness 的 3000px 限制,哪些不会?

宽的截图通常没事,DeepSeek Harness 投影后 1440×3900 会变成 755×2044,低于限制。整页竖图会出事:520×6400 投影成 336×4130 再被压到 333×4096,390×6400 同理。手机全页截图最容易命中。

会话报 Input exceeds the context limit、历史里只多了几张图,这和 DeepSeek Harness 的尺寸问题是同一个吗?

不是同一个机制,DeepSeek Harness 这条 Input exceeds the context limit 是图片计价口径造成的。那是中转网关把图片的 base64 当普通文本计价(约 1 token/原始字节),而本地计量器按附件引用长度算,只记 55~59 token/张,两者差约 3500 倍,压缩永远选不中图片。

不想新开会话时,怎么让被长截图卡住的 DeepSeek Harness 会话继续用?

在 DeepSeek Harness 里换协议或换模型,先发一条普通消息更新 request/header,再执行 /compact。社区实测这样可以恢复;直接压缩往往失败,因为旧 header 还在按原来的计价与窗口计算。

相关术语

many-image 请求限制
many-image 请求限制是 Anthropic Messages 对携带多张图片的请求施加的边长上限,每条边最多 3000 像素。只要有一张超限,整个请求被拒绝,错误码为 invalid_request_error。— https://github.com/deepseek-ai/deepseek-harness/discussions/11511
REQUEST_IMAGE_MAX_DIMENSION
REQUEST_IMAGE_MAX_DIMENSION 是 DeepSeek 适配器里控制请求图片最长边的常量,当前值为 4096。它被注释描述为提供方的多图限制,但 Messages 端点实际只允许 3000。— https://github.com/deepseek-ai/deepseek-harness/discussions/11511
resolveRequestImageTarget
resolveRequestImageTarget 是把源图投影成请求目标尺寸的函数,先按 token 网格缩放,再把长边夹到上限。编码器与计费器共用同一个目标,所以历史图片每轮都会被重新投影。— https://github.com/deepseek-ai/deepseek-harness/discussions/11511
image/offload
image/offload 是图片卸载机制,只在累计 base64 载荷超过字节预算时触发。按 token 计价的溢出不会触发它,因此在这类路由上它从不生效。— https://github.com/deepseek-ai/deepseek-harness/discussions/11036

来源