在 Opencode 接入 Deekseek 的基础上让 Deepseek 长出眼睛
·
在 Opencode 接入 Deekseek 的基础上让 Deepseek 长出眼睛
效果
原图:

效果:

安装 python 环境
python 需要的安装包如下:
pip install mcp httpx zhipuai
写入下面 mcp 服务器
这个位置随意,我放到:~/.config/opencode/mcp/unserstand_image_mcp.py
"""understand_image MCP —— 图片理解工具(兼容 Anthropic / OpenAI / 智谱 三种协议)。
功能:
- 图片来源: 本地文件路径 或 http(s) URL, 混用亦可。
- 协议模式: 通过环境变量 IMAGE_API_MODE 切换 anthropic / openai / zhipu。
- anthropic / openai: 走 HTTP 接口, 图片统一转 base64。
- zhipu: 走 zhipuai SDK (ZhipuAI), URL 直传, 本地文件转 base64 data URL。
- 本地文件限制大小防止误传大图。
环境变量:
IMAGE_API_MODE 协议模式, 可选 anthropic (默认) / openai / zhipu
IMAGE_API_KEY API Key (必填)
IMAGE_MODEL 模型名(必填)
IMAGE_BASE_URL 接口地址, 需为 OpenAI 风格 /v1 前缀
IMAGE_MAX_BYTES 单张图片最大字节数, 默认 10MB
IMAGE_ZHIPU_THINKING 智谱 thinking 开关, 默认 disabled, 设 enabled 开启
"""
import base64
import logging
import mimetypes
import os
import httpx
from mcp.server.mcpserver import MCPServer
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.WARNING)
# ---------------------------------------------------------------------------
# 配置
# ---------------------------------------------------------------------------
_MODE = os.environ.get("IMAGE_API_MODE", "anthropic").strip().lower()
if _MODE not in ("anthropic", "openai", "zhipu"):
raise RuntimeError(f"IMAGE_API_MODE 必须为 anthropic / openai / zhipu, 当前为: {_MODE!r}")
MODEL = os.environ.get("IMAGE_MODEL")
BASE_URL = os.environ.get("IMAGE_BASE_URL", " ").rstrip("/")
MAX_BYTES = int(os.environ.get("IMAGE_MAX_BYTES", 10 * 1024 * 1024))
HTTP_TIMEOUT = 600.0
# 常见图片扩展名兜底映射 (mimetypes 覆盖不到的 webp/avif 等)
_EXT_TYPES = {
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".webp": "image/webp",
".gif": "image/gif",
".bmp": "image/bmp",
".avif": "image/avif",
}
def _media_type(path: str) -> str:
"""由文件名推断图片 MIME, 无法识别时兜底为 image/png。"""
mt, _ = mimetypes.guess_type(path)
if mt:
return mt
ext = "." + path.rsplit(".", 1)[-1].lower() if "." in path else ""
return _EXT_TYPES.get(ext, "image/png")
# ---------------------------------------------------------------------------
# 图片加载: URL 或本地文件 -> (base64, mime)
# ---------------------------------------------------------------------------
def _load_image(source: str) -> tuple[str, str]:
"""把 url 或本地路径统一加载为 (base64_data, media_type)。失败抛明确异常。"""
source = source.strip()
if not source:
raise ValueError("图片路径不能为空")
if source.startswith(("http://", "https://")):
try:
resp = httpx.get(source, timeout=30)
resp.raise_for_status()
except httpx.HTTPError as e:
raise ValueError(f"下载图片失败: {source} ({e})") from e
raw, path = resp.content, source
elif os.path.isfile(source):
with open(source, "rb") as f:
raw = f.read()
path = source
else:
raise FileNotFoundError(f"图片既不是有效文件也不是 URL: {source}")
if not raw:
raise ValueError(f"图片内容为空: {source}")
if len(raw) > MAX_BYTES:
raise ValueError(f"图片超过大小限制 ({len(raw)} > {MAX_BYTES} 字节): {source}")
return base64.b64encode(raw).decode(), _media_type(path)
# ---------------------------------------------------------------------------
# 三种协议的消息构造与调用
# ---------------------------------------------------------------------------
def _build_messages(image_sources: list[str], prompt: str) -> tuple:
"""按当前模式构造请求体 (dict) 与请求头 (dict), 失败抛出明确异常。"""
if not image_sources:
raise ValueError("至少需要一张图片")
if not prompt.strip():
raise ValueError("prompt 不能为空")
payload = {"model": MODEL, "max_tokens": 1024}
if _MODE == "anthropic":
content = []
for src in image_sources:
data, mt = _load_image(src)
content.append({"type": "image", "source": {"type": "base64", "media_type": mt, "data": data}})
content.append({"type": "text", "text": prompt})
payload["messages"] = [{"role": "user", "content": content}]
headers = {
"x-api-key": os.environ["IMAGE_API_KEY"],
"anthropic-version": "2023-06-01",
"content-type": "application/json",
}
else: # openai
content = [{"type": "text", "text": prompt}]
for src in image_sources:
data, mt = _load_image(src)
content.append({"type": "image_url", "image_url": {"url": f"data:{mt};base64,{data}"}})
payload["messages"] = [{"role": "user", "content": content}]
headers = {"authorization": f"Bearer {os.environ['IMAGE_API_KEY']}", "content-type": "application/json"}
return payload, headers
def _extract_text(data: dict) -> str:
"""从不同协议的响应中抽取首个文本内容。"""
if _MODE == "anthropic":
blocks = data.get("content") or []
for block in blocks:
if block.get("type") == "text":
return block["text"]
raise RuntimeError(f"anthropic 响应中没有 text 内容块: {str(data)[:500]}")
choices = data.get("choices") or []
if not choices:
raise RuntimeError(f"openai 响应中没有 choices: {str(data)[:500]}")
msg = choices[0].get("message") or {}
return msg.get("content") or ""
def _call_zhipu(image_sources: list[str], prompt: str) -> str:
"""智谱模式: 通过 zhipuai SDK (ZhipuAI) 调用视觉模型。
URL 图片直接传给 SDK, 本地文件转为 data URL 后再传, 与 OpenAI 格式一致。
"""
if not image_sources:
raise ValueError("至少需要一张图片")
if not prompt.strip():
raise ValueError("prompt 不能为空")
try:
from zhipuai import ZhipuAI
except ImportError as e:
raise RuntimeError("智谱模式需要安装 zhipuai 包: pip install zhipuai") from e
client = ZhipuAI(api_key=os.environ["IMAGE_API_KEY"])
content = [{"type": "text", "text": prompt}]
for src in image_sources:
src = src.strip()
if src.startswith(("http://", "https://")):
url = src
else:
data, mt = _load_image(src)
url = f"data:{mt};base64,{data}"
content.append({"type": "image_url", "image_url": {"url": url}})
kwargs = {
"model": MODEL,
"messages": [{"role": "user", "content": content}],
}
if os.environ.get("IMAGE_ZHIPU_THINKING", "disabled") == "enabled":
kwargs["thinking"] = {"type": "enabled"}
try:
response = client.chat.completions.create(**kwargs)
except Exception as e:
raise RuntimeError(f"智谱接口调用失败: {e}") from e
if not getattr(response, "choices", None):
raise RuntimeError(f"智谱响应中没有 choices: {str(response)[:500]}")
message = response.choices[0].message
text = getattr(message, "content", None) or ""
return text if isinstance(text, str) else str(text)
def call_model(image_sources: list[str], prompt: str) -> str:
"""入口: 按模式路由 -> 调用模型 -> 返回文本结果。"""
if _MODE == "zhipu":
return _call_zhipu(image_sources, prompt)
payload, headers = _build_messages(image_sources, prompt)
try:
resp = httpx.post(f"{BASE_URL}/messages" if _MODE == "anthropic" else f"{BASE_URL}/chat/completions",
headers=headers, json=payload, timeout=HTTP_TIMEOUT)
except httpx.HTTPError as e:
raise RuntimeError(f"请求模型接口失败: {e}") from e
if resp.status_code != 200:
raise RuntimeError(f"模型接口返回 {resp.status_code}: {resp.text[:500]}")
return _extract_text(resp.json())
# ---------------------------------------------------------------------------
# MCP 服务注册
# ---------------------------------------------------------------------------
mcp = MCPServer("understand-image")
@mcp.tool()
def understand_image(image_paths: list[str], prompt: str) -> str:
"""理解一张或多张图片。
Args:
image_paths: 本地图片文件路径或 http(s) 图片 URL 的列表, 可混用。
prompt: 对图片的提问指令。
Returns:
模型的理解结果文本。
"""
return call_model(image_paths, prompt)
def main():
mcp.run()
if __name__ == "__main__":
main()
写入模型配置
这里只要接入有多模态识别能力就好了。
这个 MCP 服务进行了三个服务商的兼容,OPENAI,anthropic,zhipu
在 Opencode 的配置文件(~/.config/opencode/opencode.jsonc)中写入 MCP 配置
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
"superpowers@git+https://github.com/obra/superpowers.git",
"@dietrichgebert/ponytail"
],
// "mcp": {
// "understand-image": {
// "type": "local",
// "command": [
// "python.exe",
// "understand_image_mcp.py"
// ],
// "enabled": true,
// "timeout": 600000,
// "environment": {
// "IMAGE_API_MODE": "openai",
// "IMAGE_API_KEY": "sk-ws-H.ELXPMIH.qIiY.",
// "IMAGE_MODEL": "qwen3.8-max",
// "IMAGE_BASE_URL": "https://dashscope.aliyuncs.com/compatible-mode/v1"
// }
// }
// },
"mcp": {
"understand-image": {
"type": "local",
"command": [
"python.exe",
"understand_image_mcp.py"
],
"enabled": true,
"timeout": 600000,
"environment": {
"IMAGE_API_MODE": "zhipu",
"IMAGE_API_KEY": ".XJuvCbWEKRnRQxj3",
"IMAGE_MODEL": "glm-4.6v-flash"
}
}
},
"provider": {
}
}
}
主要注意的地方:
command: 其中python解释器的位置要填下载相关依赖的解释器位置,如果不知道在哪里,可以用where python找到在哪里,如果结果有多个选择第一个就好了,其中Python文件地方,你自己放在哪里,就写哪里。- 写入相关环境配置,这些配置,要自己去配,去官网申请
"IMAGE_API_MODE": "zhipu",
"IMAGE_API_KEY": ".XJuvCbWEKRnRQxj3",
"IMAGE_MODEL": "glm-4.6v-flash"
"IMAGE_BASE_URL": "https://dashscope.aliyuncs.com/compatible-mode/v1" // 这个智谱不需要填写
- 智谱是免费的多模态模型,然后 Qwen3.8Max 也有免费额度,可以尝试
- 智谱网址:https://bigmodel.cn/apikey/platform
- 千问网址:https://platform.qianwenai.com/home/analytics/request-logs
检测是否成功
在 Opencode 中写入上述配置以后,Opencode 输入 /status 会显示如下:

更多推荐
所有评论(0)