--- name: "vision" description: "Agent 识图工具 - Gemini 2.5 Flash Lite(首选)或本地 Qwen2.5-VL(降级)分析图像内容" metadata: author: Val version: 2.3.0 tags: [vision, image, multimodal, gemini, local-model] trigger: automatic trigger_priority: high --- # Vision 识图技能 ## 目标 当 agent 收到图片消息时,自动调用视觉模型识别图片内容,并将描述纳入对话上下文,让 agent 能够"看见"图片。 ## 触发条件(自动) agent 在以下任一情况**必须自动触发**本技能,优先于内置 `image` 工具: - 用户通过任何渠道(Telegram、微信、QQ 等)发送的图片消息 - 消息中包含图片附件(`` 标签、`image_url`、文件附件等) - 用户直接要求 agent 看图 - agent 需要理解图片内容才能继续对话 **自动执行流程:** 1. 检测到消息中有图片 → 优先使用本技能(走 Gemini curl 流程) 2. 不要依赖内置 `image` 工具,除非 Gemini curl 流程失败 3. 识图结果作为辅助信息写入对话上下文,让 agent 能基于图片内容回复用户 ## 引擎策略 按优先级顺序: 1. **Gemini 2.5 Flash Lite**(首选)— 快(1-3 秒),准确,可用性高,通过环境变量 `GEMINI_API_KEY` 调用 2. **Gemini 2.5 Flash**(二选)— 更高精度,但免费层有时 503,仅在 Lite 失败时降级尝试 3. **本地 Qwen2.5-VL**(最终降级)— 纯本地,但 CPU 推理非常慢(10-90 秒),仅在前两者都不可用时回退 ## 调用方式 ### 第一步:定位图片文件 图片文件存放在 `/Users/guchen/.openclaw/media/inbound/` 目录下,文件名通过消息中的 UUID 标识获取。 例如消息中的 media 路径或 filename 中包含 UUID,对应的文件路径为 `/Users/guchen/.openclaw/media/inbound/{UUID}.jpg`。 如果图片以 URL 形式传入,先下载到临时文件: ```bash curl -sL -x http://127.0.0.1:7897 -o /tmp/vision_input.jpg "{图片URL}" ``` ### 第二步:Gemini API(首选,通过 exec + curl) ```bash # 加载 GEMINI_API_KEY(注意:~/.zshrc 里 export 的值带双引号, # 用 eval 展开确保去掉外层引号,否则 API 调用会报 key invalid) eval "$(grep '^export GEMINI_API_KEY=' ~/.zshrc 2>/dev/null)" export HTTPS_PROXY=http://127.0.0.1:7897 VISION_B64=$(base64 -i /tmp/vision_input.jpg | tr -d '\n') # 或者从 inbound 目录读取 VISION_B64=$(base64 -i /Users/guchen/.openclaw/media/inbound/{UUID}.jpg | tr -d '\n') # 用 python3 构建 JSON 避免 shell 转义问题 curl -s --max-time 30 -x http://127.0.0.1:7897 \ "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:generateContent?key=***" \ -H "Content-Type: application/json" \ -d "$(python3 -c " import json b64 = '''$VISION_B64''' payload = { 'contents': [{ 'parts': [ {'text': '请用中文详细描述这张图片的内容'}, {'inline_data': {'mime_type': 'image/jpeg', 'data': b64}} ] }] } print(json.dumps(payload)) ")" ``` 从响应中提取:`.candidates[0].content.parts[0].text` 如果 gemini-2.5-flash-lite 失败(429/503),尝试降级到 gemini-2.5-flash: - 将 URL 中的 `gemini-2.5-flash-lite` 替换为 `gemini-2.5-flash` ### 第三步:降级到本地 Qwen API 如果 Gemini 调用失败(超时、API key 未设置、网络不通、额度用完等),降级到本地模型: ```bash VISION_B64=$(base64 -i /tmp/vision_input.jpg | tr -d '\n') curl -s --max-time 120 http://127.0.0.1:8081/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "guff", "messages": [ { "role": "user", "content": [ {"type": "text", "text": "请用中文详细描述这张图片的内容"}, {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,"'$VISION_B64'"}} ] } ], "max_tokens": 500 }' 2>&1 ``` 从响应中提取:`.choices[0].message.content` ### 第四步:纳入上下文 将识图得到的文字描述以辅助信息形式写入对话上下文,格式参考: ``` [Vision: 图片描述内容...] ``` 然后根据图片内容回复用户。 ## 速度参考 | 引擎 | 延迟 | 备注 | |---|---|---| | Gemini 2.5 Flash Lite | 1-3 秒 | 首选,可用性高 | | Gemini 2.5 Flash | 1-3 秒 | 二选,精度更高但有时 503 | | 本地 Qwen2.5-VL | 10-90 秒 | CPU 推理,无网络依赖 | ## 注意事项 - 每次识图调用串行处理,不要并发多张图片 - 图片大小建议不超过 5MB,过大的图片应先压缩 - 识别结果不一定 100% 准确,在回复中适当表达不确定性 - 如果 Gemini 和本地模型都不可用,给出友好提示而非报错 - API key 存放于 `~/.zshrc` 中的 `GEMINI_API_KEY` 环境变量 - **密钥加载必须用 `eval "$(grep '^export GEMINI_API_KEY=' ~/.zshrc)"` 而不是 `source ~/.zshrc`,因为后者引入的变量值可能因 shell 引号上下文不一致而出错** - 代理地址:`127.0.0.1:7897`