Files

nanobot 图片识别修复补丁

适用对象:nanobot(uv 工具安装,位于 ~/.local/share/uv/tools/nanobot-ai/) 修复日期:2026-08-21 备份目录:本目录 /home/ubuntu/nanocode/nanobot-patches/


一、问题现象

使用支持视觉的模型(如 deepseek-v4-flash-vision-exp)时,通过 read_file 读取图片后,模型看不到图片,只收到类似 (Image file: xxx.png) 的占位符文本,导致无法识别图片内容。

二、根因

nanobot/providers/openai_compat_provider.py 的 _sanitize_messages() 中有一行逻辑:

force_string_content = bool(self._spec and self._spec.name == "deepseek")

只要 provider 名字是 deepseek,它就把所有消息的 content 强制转为纯字符串。而 content 一旦是字符串,里面 image_url / input_image / image / file 等视觉块就会被剥离,图片信息丢失。

该逻辑本意是兼容 DeepSeek 纯文本模型的"只收字符串 content",但一刀切地也作用到了视觉模型上,导致视觉内容被丢弃。

三、修复方法

在 openai_compat_provider.py 中:

  1. 新增一个静态方法,判断 content 是否包含视觉块:
@staticmethod
def _content_has_vision_blocks(content: Any) -> bool:
    """Return True when content is a block list containing image/file blocks.

    Used to keep vision content intact for multimodal models (e.g.
    ``deepseek-v4-flash-vision-exp``) instead of coercing it to plain text.
    """
    if not isinstance(content, list):
        return False
    for item in content:
        mapping = OpenAICompatProvider._maybe_mapping(item)
        if not mapping:
            continue
        if mapping.get("type") in ("image_url", "input_image", "image", "file"):
            return True
    return False
  1. 在 _sanitize_messages() 的强制转字符串条件里,增加"含视觉块则跳过"的判断:
if (
    force_string_content
    and not (clean.get("role") == "assistant" and clean.get("tool_calls"))
    and not OpenAICompatProvider._content_has_vision_blocks(clean.get("content"))
):
    clean["content"] = self._coerce_content_to_string(clean.get("content"))

关键点:_content_has_vision_blocks() 判断在 _coerce_content_to_string() 之前生效,视觉块因此被保留。纯文本消息仍会走原字符串转换逻辑,不受影响。

四、涉及文件与路径

  • 被修改文件: /home/ubuntu/.local/share/uv/tools/nanobot-ai/lib/python3.11/site-packages/nanobot/providers/openai_compat_provider.py

  • 本目录备份:

    • openai_compat_provider.py.before_patch —— 修复前(旧)文件
    • openai_compat_provider.py.after_patch —— 修复后(当前)文件

五、⚠️ 重要:uv 升级会覆盖补丁

nanobot-ai 是通过 uv tool 安装的。一旦执行升级(例如 uv tool upgrade nanobot-ai 或重新安装),site-packages 下的文件会被覆盖,补丁随之丢失。

重新打补丁的方法(升级后执行):

# 1. 确认当前文件与 before_patch 是否一致(若一致说明补丁已丢失)
diff /home/ubuntu/nanocode/nanobot-patches/openai_compat_provider.py.before_patch \
     /home/ubuntu/.local/share/uv/tools/nanobot-ai/lib/python3.11/site-packages/nanobot/providers/openai_compat_provider.py

# 2. 若补丁丢失,用 after_patch 覆盖回去
cp /home/ubuntu/nanocode/nanobot-patches/openai_compat_provider.py.after_patch \
   /home/ubuntu/.local/share/uv/tools/nanobot-ai/lib/python3.11/site-packages/nanobot/providers/openai_compat_provider.py

# 3. 重启 nanobot 服务使补丁生效
sudo systemctl restart nanobot

六、验证方法

用 nanobot 自身的 venv Python 跑一次完整链路(构造图片块 → _sanitize_messages → chat),确认 content 仍为 list 且保留 image_url 块,并能返回图片描述:

PY=/home/ubuntu/.local/share/uv/tools/nanobot-ai/bin/python
$PY -c "
import asyncio, base64
from nanobot.providers.registry import PROVIDERS
from nanobot.providers.openai_compat_provider import OpenAICompatProvider

spec = next(s for s in PROVIDERS if s.name=='deepseek')
key = 'sk-xxxx'  # 替换为 DeepSeek API Key
model = 'deepseek-v4-flash-vision-exp'
b64 = base64.b64encode(open('/path/to/img.png','rb').read()).decode()
messages = [{'role':'user','content':[
    {'type':'image_url','image_url':{'url':'data:image/png;base64,'+b64}},
    {'type':'text','text':'请描述这张图片'}
]}]
prov = OpenAICompatProvider(api_key=key, spec=spec, default_model=model)
async def main():
    s = prov._sanitize_messages(messages)
    print('blocks:', [b.get('type') for b in s[0]['content']])  # 期望 ['image_url','text']
    resp = await prov.chat(messages, model=model)
    print(resp.text)
asyncio.run(main())
"

七、关于"识别能力"的说明

  • 图片识别能力本身是模型自带的(如 deepseek-v4-flash-vision-exp、Kimi 等视觉模型都支持),不需要记忆到 nanobot 的长期记忆里。
  • 需要防丢失的是"代码补丁"这一工程事实:它属于 site-packages 的源码改动,会随 uv 升级被覆盖。因此用文档 + 前后备份来保护,而不是当作模型能力去记忆。

八、关联历史

  • 修改前备份文件:/home/ubuntu/backup_openai_compat_provider.py.1787316174
  • 历史记忆(memory)中的补丁位置与"uv 升级会覆盖、需重新打"的记录与此文档一致。