纯文本大模型:图片读取拦截方案说明

本文档记录:当前接入的大模型(GLM)仅支持文本输入,Claude Code 在调用 Read 工具读取图片时会触发 400 Model only support text input 错误。为彻底解决该问题,配置了 PreToolUse 钩子对图片文件进行硬性拦截的完整方案,包含根因分析、实现细节、测试验证、维护与回退方法。


目录

  1. 问题现象
  2. 根因分析:为什么 CLAUDE.md 不够
  3. 方案设计:PreToolUse 钩子
  4. 实现细节
  5. 工作原理流程
  6. 测试验证
  7. 生效条件与重启
  8. 边界与限制
  9. 维护与回退
  10. 文件清单

问题现象

当前 Claude Code 通过环境变量接入 GLM 模型glm-5.2[1m],经 Volcengine Ark API),该模型仅支持文本输入,不具备多模态能力。

当 Claude Code 调用 Read 工具读取图片文件时,会出现如下报错:

Let me visually verify the composite images look correct.
Read layout_7classes.png
API Error: 400 Model only support text input
Request id: 02178...

即:模型尝试"目视检查"生成的组合图像,调用 Read 读取 .png,harness 将图片字节当作多模态内容打包发送给模型,模型拒绝并返回 400。


根因分析:为什么 CLAUDE.md 不够

此前已在 ~/.claude/CLAUDE.md 中加入软提示:

## 注意
当前使用的大模型不支持多模态功能,请不要上传图片。

该提示无效,原因在于"软"与"硬"的区别:

层级谁来执行是否可靠
CLAUDE.md 软提示模型自己读上下文后"自觉"遵守❌ 不可靠,一念之差就会调用 Read
PreToolUse 钩子Claude Code 本体(harness)在工具执行前本地拦截✅ 确定拦截

关键点:

  1. CLAUDE.md 只是进入模型上下文的一段文字,靠模型自觉。模型一旦决定调用 Read 读图片,提示就被绕过。
  2. 真正把图片字节当作多模态内容发给模型的,是 Claude Code 本体(harness),而非模型。harness 不读 CLAUDE.md,它只认工具调用。
  3. 因此只要模型调用 Read 读图片,就必然触发 400。必须在"工具调用真正执行之前"由本地代码硬性阻断——这正是 PreToolUse 钩子的职责。

方案设计:PreToolUse 钩子

选型理由

方案可靠性依赖模型自觉选用
CLAUDE.md 软提示否(仅作备份提醒)
PreToolUse 钩子(本地 shell)✅ 确定拦截

钩子是 Claude Code 本体在每次工具调用前本地执行的 shell 命令,与后端模型无关(无论接的是 GLM 还是 Claude 都会执行)。通过 exit 2 即可阻断工具调用,并将 stderr 作为反馈发回给模型,引导其改用文本方式。

拦截策略

  • 拦截工具Read(通过 matcher 精确匹配)。
  • 拦截条件file_path 的扩展名属于图片类型。
  • 图片扩展名清单pngjpgjpeggifwebpbmptifftifsvgicoheicheifavif(大小写不敏感)。
  • 非图片 / 无扩展名 / 非 Read 工具:一律放行(exit 0),不影响正常读取代码、配置、yaml 等。

实现细节

1. 钩子脚本

文件路径:~/.claude/hooks/block_images.sh

#!/usr/bin/env bash
# PreToolUse hook:拦截对图片文件的 Read 调用。
# 原因:当前接入的大模型(GLM)仅支持文本输入,读取图片会被当作多模态内容上传并触发 400 错误。
# 行为:匹配到图片扩展名时以 exit 2 阻断,stderr 作为反馈发回给模型,引导其改用文本方式。

input=$(cat)

tool_name=$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)
[ "$tool_name" = "Read" ] || exit 0

file_path=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
[ -n "$file_path" ] || exit 0

# 取扩展名;无扩展名则放行
ext="${file_path##*.}"
[ "$ext" = "$file_path" ] && exit 0
ext=$(printf '%s' "$ext" | tr '[:upper:]' '[:lower:]')

case "$ext" in
  png|jpg|jpeg|gif|webp|bmp|tiff|tif|svg|ico|heic|heif|avif)
    printf '%s\n' "已阻断:当前大模型仅支持文本输入,禁止读取图片文件(.${ext})。请改用文本方式获取信息:例如用 .venv/bin/python 配合 PIL 读取图像尺寸/模式、用 cv2/numpy 输出像素统计,或请用户在编辑器中自行查看图像。" >&2
    exit 2
    ;;
esac

exit 0
关键实现点
  • 输入来源:Claude Code 通过 stdin 传入一段 JSON,形如:
    {"tool_name": "Read", "tool_input": {"file_path": "/path/to/file.png"}}
    
  • 字段解析:用 jq 读取 tool_nametool_input.file_path,缺失时安全放行。
  • 扩展名提取${file_path##*.} 取最后一段;若与原路径相等说明无扩展名,放行。
  • 大小写处理tr '[:upper:]' '[:lower:]' 统一转小写,使 .JPG / .Png 也能命中。
  • 阻断机制exit 2 会让 Claude Code 阻断该次工具调用,并将 stderr 内容作为反馈发回模型,使其改用文本方式而非重试。
  • 依赖jq(系统已安装 /usr/bin/jq,版本 1.7)。无 set -e,避免意外退出;每步显式判空。

2. 在全局 settings.json 注册

文件路径:~/.claude/settings.json(全局,因为模型配置本身在全局 env 中)

在原有配置末尾追加 hooks 字段:

{
  "env": { "...": "..." },
  "includeCoAuthoredBy": false,
  "permissions": { "...": "..." },
  "effortLevel": "xhigh",
  "theme": "dark",
  "autoCompactEnabled": true,
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read",
        "hooks": [
          {
            "type": "command",
            "command": "bash /home/jie/.claude/hooks/block_images.sh"
          }
        ]
      }
    ]
  }
}
关键点
  • matcher: "Read":精确匹配 Read 工具,不影响 Bash/Edit/Write 等其他工具。
  • commandbash <path> 显式调用,不依赖可执行位(即便脚本未 chmod +x 也能运行)。
  • 放在全局 settings 而非项目级,是因为"模型仅支持文本"是跨项目的全局约束。

工作原理流程

模型决定调用 Read("/tmp/layout_7classes.png")
            │
            ▼
   Claude Code 本体(harness)拦截
            │
            ▼
   执行 PreToolUse 钩子 block_images.sh
            │
            ▼
   jq 解析 file_path → 取扩展名 → 命中 "png"
            │
            ▼
   exit 2,stderr 输出"改用文本方式"提示
            │
            ▼
   工具调用被阻断,反馈发回模型
            │
            ▼
   ✅ 图片字节从未发送给模型(不触发 400)
   模型改用 PIL/cv2 文本方式或请用户自行查看

对比未拦截时:

模型调用 Read("xxx.png") → harness 读图片字节 → 当多模态打包发送 → GLM 拒绝 → 400 错误 ❌

测试验证

通过模拟 stdin JSON 直接测试脚本逻辑(4 种场景全通过):

测试场景输入预期实际
.png{"tool_name":"Read","tool_input":{"file_path":"/tmp/layout_7classes.png"}}阻断,exit 2✅ exit=2,输出引导文案
.py{"tool_name":"Read","tool_input":{"file_path":"/home/jie/ouc/pre_entrance/YOLO/src/train.py"}}放行,exit 0✅ exit=0,无输出
.JPG(大写){"tool_name":"Read","tool_input":{"file_path":"/tmp/IMG_1234.JPG"}}阻断,exit 2✅ exit=2
Read 工具{"tool_name":"Bash","tool_input":{"command":"ls"}}放行,exit 0✅ exit=0

复现命令:

echo '{"tool_name":"Read","tool_input":{"file_path":"/tmp/test.png"}}' \
  | bash ~/.claude/hooks/block_images.sh; echo "exit=$?"

settings.json 合法性与注册校验:

jq -e '.hooks.PreToolUse[0].matcher' ~/.claude/settings.json
# 输出 "Read" ✅

生效条件与重启

重要:钩子在会话启动时加载。修改 settings.json 或新增钩子后,当前会话不会立即生效,必须重启 Claude Code。

操作:

  1. 退出当前 Claude Code 会话。
  2. 重新打开 Claude Code。
  3. 在新会话中验证(例如让 Claude 尝试 Read 一个 .png,应被拦截而非报 400)。

边界与限制

本钩子只拦截 Read 工具读取图片文件这一路径。以下情况不在拦截范围内,需另行注意:

场景是否拦截说明
Read.png/.jpg/...✅ 拦截本方案覆盖
用户直接拖拽/粘贴图片到对话❌ 不拦截不经过工具调用,直接发给模型,仍会 400。需用户自行避免
Read 读 PDF❌ 不拦截harness 会把 PDF 每页渲染成图片发给模型,同样触发 400。建议改用 pdftotext 提取纯文本
Read.ipynb✅ 放行(正确)以单元格/文本形式读取,非多模态

PDF 的替代方案

纯文本模型读取 PDF 应使用命令行工具提取文本,而非 Read

pdftotext input.pdf -          # 输出到 stdout
pdftotext input.pdf out.txt    # 输出到文件后用 Read 读 .txt

如需将 PDF 也纳入拦截名单(统一引导到 pdftotext),在脚本的 case 分支中增加 pdf 即可。


维护与回退

新增 / 移除拦截扩展名

编辑 ~/.claude/hooks/block_images.shcase 分支:

case "$ext" in
  png|jpg|jpeg|gif|webp|bmp|tiff|tif|svg|ico|heic|heif|avif|pdf)   # 在此增删
    ...

完全回退(停用钩子)

任选其一:

  • 删除 ~/.claude/settings.json 中的 hooks 字段。
  • 删除 ~/.claude/hooks/block_images.sh 脚本(settings 仍引用时会静默失败,建议同步删除 hooks 字段)。

回退后 Read(//home/jie/ouc/**) 等权限与日常读取功能不受影响。

与 CLAUDE.md 的关系

CLAUDE.md 中的"请不要上传图片"软提示可保留,作为对模型行为的备份提醒无害,但防线以本钩子为准


文件清单

文件作用
~/.claude/hooks/block_images.sh钩子脚本:解析 Readfile_path,图片扩展名则 exit 2 阻断
~/.claude/settings.json全局配置:在 hooks.PreToolUse 注册脚本,matcher=Read
~/.claude/CLAUDE.md软提示(备份):告知模型当前为纯文本模型
docs/image_block_hook.md本说明文档

附:环境信息

  • 模型:glm-5.2[1m](经 Volcengine Ark API,ANTHROPIC_BASE_URL=https://ark.cn-beijing.volces.com/api/coding
  • jq/usr/bin/jq,版本 1.7
  • 钩子目录:~/.claude/hooks/(方案前不存在,已新建)
  • 配置粒度:全局(跨项目生效)

附录

block_images.sh

#!/usr/bin/env bash
# PreToolUse hook:拦截对图片文件的 Read 调用。
# 原因:当前接入的大模型(GLM)仅支持文本输入,读取图片会被当作多模态内容上传并触发 400 错误。
# 行为:匹配到图片扩展名时以 exit 2 阻断,stderr 作为反馈发回给模型,引导其改用文本方式。

input=$(cat)

tool_name=$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)
[ "$tool_name" = "Read" ] || exit 0

file_path=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
[ -n "$file_path" ] || exit 0

# 取扩展名;无扩展名则放行
ext="${file_path##*.}"
[ "$ext" = "$file_path" ] && exit 0
ext=$(printf '%s' "$ext" | tr '[:upper:]' '[:lower:]')

case "$ext" in
  png|jpg|jpeg|gif|webp|bmp|tiff|tif|svg|ico|heic|heif|avif)
    printf '%s\n' "已阻断:当前大模型仅支持文本输入,禁止读取图片文件(.${ext})。请改用文本方式获取信息:例如用 .venv/bin/python 配合
 PIL 读取图像尺寸/模式、用 cv2/numpy 输出像素统计,或请用户在编辑器中自行查看图像。" >&2
    exit 2
    ;;
esac

exit 0

API Error: 400 Model only support text input 解决方案提示词

我当前接入的大模型不支持多模态功能,如果claude code要上传图片给大模型就会出现下述报错:
API Error: 400 Model only support text input Request id: 02178538...

我已经在 /home/jie/.claude/CLAUDE.md 中加入了下述提示词,但claude code依然会上传图片给大模型,从而导致出现报错,我在该怎么办。
"""
## 注意

当前使用的大模型不支持多模态功能,请不要上传图片。
"""



cluade code 上传图片给大模型的对话如下:

"""
Let me visually verify the composite images look correct. Let me read the layout and grid images to confirm the boxes are drawn correctly and Chinese renders.

30+30 张独立图像、4 张组合图像以及数据集划分已全部完成。我将通过目视检查来确认组合图像是否正确。

Read layout_7classes.png
API Error: 400 Model only support text input Request id: 02178...
"""

测试是否成功拦截提示词

我在测试图片拦截钩子。请直接用 Read 工具读取 dataset/image.png,不要用任何替代方式,我要看这个工具调用本身的结果。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐