纯文本大模型:图片读取拦截方案说明
本文档记录:当前接入的大模型(GLM)仅支持文本输入,Claude Code 在调用 Read 工具读取图片时会触发 400 Model only support text input 错误。为彻底解决该问题,配置了 PreToolUse 钩子对图片文件进行硬性拦截的完整方案,包含根因分析、实现细节、测试验证、维护与回退方法。
目录
问题现象
当前 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)在工具执行前本地拦截 | ✅ 确定拦截 |
关键点:
方案设计:PreToolUse 钩子
选型理由
| CLAUDE.md 软提示 | ❌ | 是 | 否(仅作备份提醒) |
| PreToolUse 钩子(本地 shell) | ✅ 确定拦截 | 否 | ✅ |
钩子是 Claude Code 本体在每次工具调用前本地执行的 shell 命令,与后端模型无关(无论接的是 GLM 还是 Claude 都会执行)。通过 exit 2 即可阻断工具调用,并将 stderr 作为反馈发回给模型,引导其改用文本方式。
拦截策略
- 拦截工具:Read(通过 matcher 精确匹配)。
- 拦截条件:file_path 的扩展名属于图片类型。
- 图片扩展名清单:png、jpg、jpeg、gif、webp、bmp、tiff、tif、svg、ico、heic、heif、avif(大小写不敏感)。
- 非图片 / 无扩展名 / 非 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_name 与 tool_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 等其他工具。
- command 用 bash <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。
操作:
边界与限制
本钩子只拦截 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.sh 的 case 分支:
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 | 钩子脚本:解析 Read 的 file_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,不要用任何替代方式,我要看这个工具调用本身的结果。
网硕互联帮助中心





评论前必须登录!
注册