Files
2026-08-22 16:00:02 +08:00

129 lines
5.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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()` 中有一行逻辑:
```python
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` 是否包含视觉块:
```python
@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
```
2. 在 `_sanitize_messages()` 的强制转字符串条件里,增加"含视觉块则跳过"的判断:
```python
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` 下的文件会被**覆盖**,补丁随之丢失。
**重新打补丁的方法**(升级后执行):
```bash
# 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` 块,并能返回图片描述:
```bash
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 升级会覆盖、需重新打"的记录与此文档一致。