openclaw配置修改支持图片识别

2026-04-11 15:151阅读0评论SEO资源
  • 内容介绍
  • 文章标签
  • 相关推荐
问题描述:
  • 备份配置:读取目标配置(默认 ~/.openclaw/openclaw.json,也可传入路径),并立刻写出备份 openclaw.json.bak(覆盖旧备份)。
  • 补齐模型图像能力声明:遍历 models.providers.*.models[],确保每个模型条目都有 input 字段,且包含 textimage;缺什么补什么,并去重、规范化。
  • 修正默认图片模型指向:检查 agents.defaults.imageModel.primary 是否指向一个真实存在且 inputimageprovider/model;不合法就自动挑一个可用的(优先 agents.defaults.model.primary,再尝试 imageModel 的 fallback,最后选第一个支持 image 的 provider 模型)。
  • 处理 allowlist(如果你启用了):如果存在 agents.defaults.models(模型 allowlist),会确保上一步选中的 imageModel.primary 在 allowlist 里,避免“模型存在但被禁止使用”。
  • iMessage 附件开关(如果你配置了 iMessage):如果存在 channels.imessage,确保 includeAttachmentstrue(缺失就加,false 就改 true)。

执行结果:

  • 会把修正后的 JSON 写回原配置文件,并打印扫描了多少 provider/model、修改了多少模型 input、imageModel 是否被修正,以及 iMessage includeAttachments 的处理结果。
    测试结果: 在虚拟机中搭建测试用openclaw 直接调用脚本修改成功在openclaw网页识图
    image1206×2622 446 KB
    image1620×413 38.4 KB

#!/usr/bin/env python3 # -*- coding: utf-8 -*- import json import os import sys from typing import Any, Dict, List, Optional, Tuple DEFAULT_PATH = os.path.expanduser("~/.openclaw/openclaw.json") def ensure_list_of_strings(v: Any) -> List[str]: if isinstance(v, list): out: List[str] = [] for x in v: if isinstance(x, str): s = x.strip() if s: out.append(s) return out if isinstance(v, str): s = v.strip() return [s] if s else [] return [] def dedupe_case_insensitive(items: List[str]) -> List[str]: seen = set() out: List[str] = [] for x in items: k = x.lower() if k in seen: continue seen.add(k) out.append(x) return out def norm_ref(ref: str) -> str: return str(ref or "").strip().lower() def build_provider_model_index(cfg: Dict[str, Any]) -> Tuple[Dict[str, str], Dict[str, List[str]]]: """ Returns: - canonical_by_norm: map of "provider/model" (lowercased) -> canonical "Provider/model" - input_by_norm: map of norm ref -> list of input strings (lowercased) """ canonical_by_norm: Dict[str, str] = {} input_by_norm: Dict[str, List[str]] = {} models = cfg.get("models") if not isinstance(models, dict): return canonical_by_norm, input_by_norm providers = models.get("providers") if not isinstance(providers, dict): return canonical_by_norm, input_by_norm for provider_key, provider_cfg in providers.items(): if not isinstance(provider_cfg, dict): continue model_list = provider_cfg.get("models") if not isinstance(model_list, list): continue for m in model_list: if not isinstance(m, dict): continue mid = m.get("id") if not isinstance(mid, str) or not mid.strip(): continue canonical = f"{provider_key}/{mid.strip()}" n = norm_ref(canonical) if n not in canonical_by_norm: canonical_by_norm[n] = canonical inp = ensure_list_of_strings(m.get("input")) input_by_norm[n] = [x.lower() for x in inp] return canonical_by_norm, input_by_norm def patch_provider_model_inputs(cfg: Dict[str, Any]) -> Tuple[int, int, int]: """Ensures every models.providers.*.models[].input includes text + image. Returns: (providers_seen, models_seen, models_changed) """ providers_seen = 0 models_seen = 0 models_changed = 0 models = cfg.get("models") if not isinstance(models, dict): return providers_seen, models_seen, models_changed providers = models.get("providers") if not isinstance(providers, dict): return providers_seen, models_seen, models_changed for _provider_id, provider_cfg in providers.items(): if not isinstance(provider_cfg, dict): continue model_list = provider_cfg.get("models") if not isinstance(model_list, list): continue providers_seen += 1 for m in model_list: if not isinstance(m, dict): continue models_seen += 1 before_raw = m.get("input", None) had_input = ("input" in m) inputs = ensure_list_of_strings(before_raw) if had_input else [] lower = [x.lower() for x in inputs] changed = False if "text" not in lower: inputs.append("text") changed = True if "image" not in lower: inputs.append("image") changed = True inputs = dedupe_case_insensitive(inputs) if (not had_input) or (not isinstance(before_raw, list)) or changed: m["input"] = inputs models_changed += 1 return providers_seen, models_seen, models_changed def get_dict(cfg: Dict[str, Any], path: List[str]) -> Optional[Dict[str, Any]]: cur: Any = cfg for k in path: if not isinstance(cur, dict): return None cur = cur.get(k) return cur if isinstance(cur, dict) else None def ensure_allowlist_contains(cfg: Dict[str, Any], canonical_model_ref: str) -> bool: """If agents.defaults.models exists (allowlist), ensure it includes canonical_model_ref.""" defaults = get_dict(cfg, ["agents", "defaults"]) if not isinstance(defaults, dict): return False allowlist = defaults.get("models") if not isinstance(allowlist, dict): return False target_n = norm_ref(canonical_model_ref) for k in list(allowlist.keys()): if norm_ref(k) == target_n: return False allowlist[canonical_model_ref] = {} return True def pick_best_image_model( cfg: Dict[str, Any], canonical_by_norm: Dict[str, str], input_by_norm: Dict[str, List[str]], ) -> Optional[str]: """Pick a canonical provider/model ref that supports image.""" defaults = get_dict(cfg, ["agents", "defaults"]) or {} # 1) Prefer primary reply model (if it supports image) model_cfg = defaults.get("model") primary_ref = None if isinstance(model_cfg, dict): primary_ref = model_cfg.get("primary") elif isinstance(model_cfg, str): primary_ref = model_cfg if isinstance(primary_ref, str): n = norm_ref(primary_ref) canonical = canonical_by_norm.get(n) if canonical and ("image" in input_by_norm.get(n, [])): return canonical # 2) Try imageModel fallbacks (if any) image_cfg = defaults.get("imageModel") fallbacks: List[str] = [] if isinstance(image_cfg, dict): fallbacks = [x for x in ensure_list_of_strings(image_cfg.get("fallbacks"))] for fb in fallbacks: n = norm_ref(fb) canonical = canonical_by_norm.get(n) if canonical and ("image" in input_by_norm.get(n, [])): return canonical # 3) First provider model with image, preserving config order models = cfg.get("models") if isinstance(models, dict): providers = models.get("providers") if isinstance(providers, dict): for provider_key, provider_cfg in providers.items(): if not isinstance(provider_cfg, dict): continue model_list = provider_cfg.get("models") if not isinstance(model_list, list): continue for m in model_list: if not isinstance(m, dict): continue mid = m.get("id") if not isinstance(mid, str) or not mid.strip(): continue canonical = f"{provider_key}/{mid.strip()}" n = norm_ref(canonical) if "image" in input_by_norm.get(n, []): return canonical return None def fix_image_model_primary(cfg: Dict[str, Any]) -> Tuple[bool, str]: """Ensure agents.defaults.imageModel.primary points to an existing image-capable model.""" canonical_by_norm, input_by_norm = build_provider_model_index(cfg) defaults = get_dict(cfg, ["agents", "defaults"]) if not isinstance(defaults, dict): return False, "SKIP: agents.defaults missing" image_cfg = defaults.get("imageModel") current_primary = None if isinstance(image_cfg, dict): current_primary = image_cfg.get("primary") elif isinstance(image_cfg, str): current_primary = image_cfg image_cfg = {"primary": current_primary, "fallbacks": []} defaults["imageModel"] = image_cfg else: image_cfg = {"primary": None, "fallbacks": []} defaults["imageModel"] = image_cfg if isinstance(current_primary, str): n = norm_ref(current_primary) canonical = canonical_by_norm.get(n) if canonical and ("image" in input_by_norm.get(n, [])): allow_changed = ensure_allowlist_contains(cfg, canonical) if allow_changed: return True, f"OK imageModel.primary (kept) + allowlist add: {canonical}" return False, f"OK imageModel.primary: {canonical}" replacement = pick_best_image_model(cfg, canonical_by_norm, input_by_norm) if not replacement: return False, "WARN: no provider model found that supports image; imageModel.primary not changed" image_cfg["primary"] = replacement allow_changed = ensure_allowlist_contains(cfg, replacement) if allow_changed: return True, f"SET imageModel.primary + allowlist add: {replacement}" return True, f"SET imageModel.primary: {replacement}" def patch_imessage_include_attachments(cfg: Dict[str, Any]) -> Tuple[bool, str]: """Ensure channels.imessage.includeAttachments is true (if channels.imessage exists).""" channels = cfg.get("channels") if not isinstance(channels, dict): return False, "SKIP: channels missing" im = channels.get("imessage") if im is None: return False, "SKIP: channels.imessage missing" if not isinstance(im, dict): return False, "SKIP: channels.imessage not an object" cur = im.get("includeAttachments") if cur is True: return False, "OK: channels.imessage.includeAttachments already true" im["includeAttachments"] = True if cur is False: return True, "SET: channels.imessage.includeAttachments false -> true" if cur is None: return True, "ADD: channels.imessage.includeAttachments true" return True, f"SET: channels.imessage.includeAttachments ({type(cur).__name__}) -> true" def main() -> int: path = os.path.expanduser(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT_PATH if not os.path.exists(path): print(f"ERROR: file not found: {path}", file=sys.stderr) return 2 try: with open(path, "r", encoding="utf-8") as f: raw = f.read() cfg = json.loads(raw) except json.JSONDecodeError as e: print(f"ERROR: JSON parse failed: {path}", file=sys.stderr) print(f" {e}", file=sys.stderr) return 3 if not isinstance(cfg, dict): print("ERROR: root JSON is not an object", file=sys.stderr) return 4 # Step 1: backup (always, overwrite) bak_path = path + ".bak" try: with open(bak_path, "w", encoding="utf-8") as f: f.write(raw) except Exception as e: print(f"ERROR: failed to write backup: {bak_path}: {e}", file=sys.stderr) return 5 # Step 2: ensure provider models declare image input providers_seen, models_seen, models_changed = patch_provider_model_inputs(cfg) # Step 3: ensure imageModel.primary points to a real image-capable model img_changed, img_msg = fix_image_model_primary(cfg) # Step 4: ensure iMessage includes attachments (if configured) im_changed, im_msg = patch_imessage_include_attachments(cfg) try: with open(path, "w", encoding="utf-8") as f: json.dump(cfg, f, ensure_ascii=False, indent=2) f.write("\n") except Exception as e: print(f"ERROR: failed to write patched file: {path}: {e}", file=sys.stderr) return 6 print(f"Backup: {bak_path}") print(f"Patched: {path}") print(f"Providers scanned: {providers_seen}") print(f"Models scanned: {models_seen}") print(f"Models updated (input): {models_changed}") print(f"Image model fix: {img_msg}") print(f"iMessage attachments: {im_msg}") return 0 if __name__ == "__main__": raise SystemExit(main()) 网友解答:


--【壹】--:

感觉是中转站不支持图片


--【贰】--:

可以用cherry studio图片测试下如果可以那么openclaw也可以


--【叁】--:

Mark 一会试试


--【肆】--:

在那些llm providers验证过?好像纯文本的中转测试不行。


--【伍】--:

我还真是就默认 text


--【陆】--:

text会自动把图片base64然后识别的


--【柒】--:

可以直接运行脚本自动补全了,不过不支持图片识别的模型不知道加了会怎么样


--【捌】--:

是grok吗,那应该是上游设置了base64
image758×160 5.98 KB


--【玖】--:

gpt54

openclaw 的总结:

验证结果出来了,而且很关键:curl + base64 + xx/gpt-5.4 这条链路是成功的。

我刚刚实际跑的是:

  • 模型:gpt-5.4
  • 接口:POST https://claude.xx.vin/openai/responses
  • 图片:你刚发的 wechat-inbound-2286276.jpg
  • 图片传法:data:image/jpeg;base64,...
  • 结果:流式响应完整 completed

模型最终返回的原文是:
This is the Doubao AI chat page, showing an AI-generated Chinese answer about baby crib safety and vertical slat design.

这说明两件事:

  1. xx 的 gpt-5.4 本身能看图,没问题
  • 同一个 key
  • 同一个模型
  • 同一张图
  • 手工 curl 成功识别
  1. 所以 OpenClaw 里失败的根因,不是 xx/gpt-5.4 不支持图片
  • 而是 OpenClaw 发给 xx 的请求方式/headers/上下文,与我这条成功的 curl 不一致
  • 或者 OpenClaw 在运行时还混入了别的 profile / 旧配置 / fallback 逻辑,导致 provider 侧回了 403

现在问题被缩小到很具体的范围了:

  • curl -> xx/gpt-5.4 + image:成功
  • OpenClaw -> xx/gpt-5.4 + same image:403

所以接下来不该再怀疑模型能力,而应该查 OpenClaw 到 xx 的适配层。最可能是这几类差异:

  • baseUrl / 路径拼接和我们 curl 不完全一样
  • header 附带方式不同
  • OpenClaw 走的是它内部的 openai-responses 适配器,但加了额外字段,被 xx 拒绝
  • 仍然有旧会话/旧 provider profile 在用旧认证状态

如果你愿意,我下一步就直接去查 OpenClaw 源码里 openai-responses provider adapter,把它最终构造的请求格式对照我这条成功 curl,找出差异。这样就能把“为什么 OpenClaw 原生不行,而 curl 行”钉死。


--【拾】--:

openclaw还是得边修边补着用,离真正大伙真正的预想看似很近实则非常远~


--【拾壹】--:

好像是我写的插件的问题。AI帮我修好了,似乎能用了。坏消息是插件崩溃了,慢慢修吧


--【拾贰】--:

佬太猛了 我傻傻的自己手动写json


--【拾叁】--:

我用的3月3日的版本正常,然后就没更新过了,新版本这部分源码可能有改动过


--【拾肆】--:

写过几次老是宕机,不想写了


--【拾伍】--:

已经不想再聊了。得冷静冷静


--【拾陆】--:

Invalid type for 'input[2].content[1].image_url': expected an image URL, but got an object instead. cherry 报错,但是似乎转成base64是可以用。龙虾验证curl可行,现在是怎么让龙虾自动用的问题。论坛里没搜到研究报告

标签:纯水
问题描述:
  • 备份配置:读取目标配置(默认 ~/.openclaw/openclaw.json,也可传入路径),并立刻写出备份 openclaw.json.bak(覆盖旧备份)。
  • 补齐模型图像能力声明:遍历 models.providers.*.models[],确保每个模型条目都有 input 字段,且包含 textimage;缺什么补什么,并去重、规范化。
  • 修正默认图片模型指向:检查 agents.defaults.imageModel.primary 是否指向一个真实存在且 inputimageprovider/model;不合法就自动挑一个可用的(优先 agents.defaults.model.primary,再尝试 imageModel 的 fallback,最后选第一个支持 image 的 provider 模型)。
  • 处理 allowlist(如果你启用了):如果存在 agents.defaults.models(模型 allowlist),会确保上一步选中的 imageModel.primary 在 allowlist 里,避免“模型存在但被禁止使用”。
  • iMessage 附件开关(如果你配置了 iMessage):如果存在 channels.imessage,确保 includeAttachmentstrue(缺失就加,false 就改 true)。

执行结果:

  • 会把修正后的 JSON 写回原配置文件,并打印扫描了多少 provider/model、修改了多少模型 input、imageModel 是否被修正,以及 iMessage includeAttachments 的处理结果。
    测试结果: 在虚拟机中搭建测试用openclaw 直接调用脚本修改成功在openclaw网页识图
    image1206×2622 446 KB
    image1620×413 38.4 KB

#!/usr/bin/env python3 # -*- coding: utf-8 -*- import json import os import sys from typing import Any, Dict, List, Optional, Tuple DEFAULT_PATH = os.path.expanduser("~/.openclaw/openclaw.json") def ensure_list_of_strings(v: Any) -> List[str]: if isinstance(v, list): out: List[str] = [] for x in v: if isinstance(x, str): s = x.strip() if s: out.append(s) return out if isinstance(v, str): s = v.strip() return [s] if s else [] return [] def dedupe_case_insensitive(items: List[str]) -> List[str]: seen = set() out: List[str] = [] for x in items: k = x.lower() if k in seen: continue seen.add(k) out.append(x) return out def norm_ref(ref: str) -> str: return str(ref or "").strip().lower() def build_provider_model_index(cfg: Dict[str, Any]) -> Tuple[Dict[str, str], Dict[str, List[str]]]: """ Returns: - canonical_by_norm: map of "provider/model" (lowercased) -> canonical "Provider/model" - input_by_norm: map of norm ref -> list of input strings (lowercased) """ canonical_by_norm: Dict[str, str] = {} input_by_norm: Dict[str, List[str]] = {} models = cfg.get("models") if not isinstance(models, dict): return canonical_by_norm, input_by_norm providers = models.get("providers") if not isinstance(providers, dict): return canonical_by_norm, input_by_norm for provider_key, provider_cfg in providers.items(): if not isinstance(provider_cfg, dict): continue model_list = provider_cfg.get("models") if not isinstance(model_list, list): continue for m in model_list: if not isinstance(m, dict): continue mid = m.get("id") if not isinstance(mid, str) or not mid.strip(): continue canonical = f"{provider_key}/{mid.strip()}" n = norm_ref(canonical) if n not in canonical_by_norm: canonical_by_norm[n] = canonical inp = ensure_list_of_strings(m.get("input")) input_by_norm[n] = [x.lower() for x in inp] return canonical_by_norm, input_by_norm def patch_provider_model_inputs(cfg: Dict[str, Any]) -> Tuple[int, int, int]: """Ensures every models.providers.*.models[].input includes text + image. Returns: (providers_seen, models_seen, models_changed) """ providers_seen = 0 models_seen = 0 models_changed = 0 models = cfg.get("models") if not isinstance(models, dict): return providers_seen, models_seen, models_changed providers = models.get("providers") if not isinstance(providers, dict): return providers_seen, models_seen, models_changed for _provider_id, provider_cfg in providers.items(): if not isinstance(provider_cfg, dict): continue model_list = provider_cfg.get("models") if not isinstance(model_list, list): continue providers_seen += 1 for m in model_list: if not isinstance(m, dict): continue models_seen += 1 before_raw = m.get("input", None) had_input = ("input" in m) inputs = ensure_list_of_strings(before_raw) if had_input else [] lower = [x.lower() for x in inputs] changed = False if "text" not in lower: inputs.append("text") changed = True if "image" not in lower: inputs.append("image") changed = True inputs = dedupe_case_insensitive(inputs) if (not had_input) or (not isinstance(before_raw, list)) or changed: m["input"] = inputs models_changed += 1 return providers_seen, models_seen, models_changed def get_dict(cfg: Dict[str, Any], path: List[str]) -> Optional[Dict[str, Any]]: cur: Any = cfg for k in path: if not isinstance(cur, dict): return None cur = cur.get(k) return cur if isinstance(cur, dict) else None def ensure_allowlist_contains(cfg: Dict[str, Any], canonical_model_ref: str) -> bool: """If agents.defaults.models exists (allowlist), ensure it includes canonical_model_ref.""" defaults = get_dict(cfg, ["agents", "defaults"]) if not isinstance(defaults, dict): return False allowlist = defaults.get("models") if not isinstance(allowlist, dict): return False target_n = norm_ref(canonical_model_ref) for k in list(allowlist.keys()): if norm_ref(k) == target_n: return False allowlist[canonical_model_ref] = {} return True def pick_best_image_model( cfg: Dict[str, Any], canonical_by_norm: Dict[str, str], input_by_norm: Dict[str, List[str]], ) -> Optional[str]: """Pick a canonical provider/model ref that supports image.""" defaults = get_dict(cfg, ["agents", "defaults"]) or {} # 1) Prefer primary reply model (if it supports image) model_cfg = defaults.get("model") primary_ref = None if isinstance(model_cfg, dict): primary_ref = model_cfg.get("primary") elif isinstance(model_cfg, str): primary_ref = model_cfg if isinstance(primary_ref, str): n = norm_ref(primary_ref) canonical = canonical_by_norm.get(n) if canonical and ("image" in input_by_norm.get(n, [])): return canonical # 2) Try imageModel fallbacks (if any) image_cfg = defaults.get("imageModel") fallbacks: List[str] = [] if isinstance(image_cfg, dict): fallbacks = [x for x in ensure_list_of_strings(image_cfg.get("fallbacks"))] for fb in fallbacks: n = norm_ref(fb) canonical = canonical_by_norm.get(n) if canonical and ("image" in input_by_norm.get(n, [])): return canonical # 3) First provider model with image, preserving config order models = cfg.get("models") if isinstance(models, dict): providers = models.get("providers") if isinstance(providers, dict): for provider_key, provider_cfg in providers.items(): if not isinstance(provider_cfg, dict): continue model_list = provider_cfg.get("models") if not isinstance(model_list, list): continue for m in model_list: if not isinstance(m, dict): continue mid = m.get("id") if not isinstance(mid, str) or not mid.strip(): continue canonical = f"{provider_key}/{mid.strip()}" n = norm_ref(canonical) if "image" in input_by_norm.get(n, []): return canonical return None def fix_image_model_primary(cfg: Dict[str, Any]) -> Tuple[bool, str]: """Ensure agents.defaults.imageModel.primary points to an existing image-capable model.""" canonical_by_norm, input_by_norm = build_provider_model_index(cfg) defaults = get_dict(cfg, ["agents", "defaults"]) if not isinstance(defaults, dict): return False, "SKIP: agents.defaults missing" image_cfg = defaults.get("imageModel") current_primary = None if isinstance(image_cfg, dict): current_primary = image_cfg.get("primary") elif isinstance(image_cfg, str): current_primary = image_cfg image_cfg = {"primary": current_primary, "fallbacks": []} defaults["imageModel"] = image_cfg else: image_cfg = {"primary": None, "fallbacks": []} defaults["imageModel"] = image_cfg if isinstance(current_primary, str): n = norm_ref(current_primary) canonical = canonical_by_norm.get(n) if canonical and ("image" in input_by_norm.get(n, [])): allow_changed = ensure_allowlist_contains(cfg, canonical) if allow_changed: return True, f"OK imageModel.primary (kept) + allowlist add: {canonical}" return False, f"OK imageModel.primary: {canonical}" replacement = pick_best_image_model(cfg, canonical_by_norm, input_by_norm) if not replacement: return False, "WARN: no provider model found that supports image; imageModel.primary not changed" image_cfg["primary"] = replacement allow_changed = ensure_allowlist_contains(cfg, replacement) if allow_changed: return True, f"SET imageModel.primary + allowlist add: {replacement}" return True, f"SET imageModel.primary: {replacement}" def patch_imessage_include_attachments(cfg: Dict[str, Any]) -> Tuple[bool, str]: """Ensure channels.imessage.includeAttachments is true (if channels.imessage exists).""" channels = cfg.get("channels") if not isinstance(channels, dict): return False, "SKIP: channels missing" im = channels.get("imessage") if im is None: return False, "SKIP: channels.imessage missing" if not isinstance(im, dict): return False, "SKIP: channels.imessage not an object" cur = im.get("includeAttachments") if cur is True: return False, "OK: channels.imessage.includeAttachments already true" im["includeAttachments"] = True if cur is False: return True, "SET: channels.imessage.includeAttachments false -> true" if cur is None: return True, "ADD: channels.imessage.includeAttachments true" return True, f"SET: channels.imessage.includeAttachments ({type(cur).__name__}) -> true" def main() -> int: path = os.path.expanduser(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT_PATH if not os.path.exists(path): print(f"ERROR: file not found: {path}", file=sys.stderr) return 2 try: with open(path, "r", encoding="utf-8") as f: raw = f.read() cfg = json.loads(raw) except json.JSONDecodeError as e: print(f"ERROR: JSON parse failed: {path}", file=sys.stderr) print(f" {e}", file=sys.stderr) return 3 if not isinstance(cfg, dict): print("ERROR: root JSON is not an object", file=sys.stderr) return 4 # Step 1: backup (always, overwrite) bak_path = path + ".bak" try: with open(bak_path, "w", encoding="utf-8") as f: f.write(raw) except Exception as e: print(f"ERROR: failed to write backup: {bak_path}: {e}", file=sys.stderr) return 5 # Step 2: ensure provider models declare image input providers_seen, models_seen, models_changed = patch_provider_model_inputs(cfg) # Step 3: ensure imageModel.primary points to a real image-capable model img_changed, img_msg = fix_image_model_primary(cfg) # Step 4: ensure iMessage includes attachments (if configured) im_changed, im_msg = patch_imessage_include_attachments(cfg) try: with open(path, "w", encoding="utf-8") as f: json.dump(cfg, f, ensure_ascii=False, indent=2) f.write("\n") except Exception as e: print(f"ERROR: failed to write patched file: {path}: {e}", file=sys.stderr) return 6 print(f"Backup: {bak_path}") print(f"Patched: {path}") print(f"Providers scanned: {providers_seen}") print(f"Models scanned: {models_seen}") print(f"Models updated (input): {models_changed}") print(f"Image model fix: {img_msg}") print(f"iMessage attachments: {im_msg}") return 0 if __name__ == "__main__": raise SystemExit(main()) 网友解答:


--【壹】--:

感觉是中转站不支持图片


--【贰】--:

可以用cherry studio图片测试下如果可以那么openclaw也可以


--【叁】--:

Mark 一会试试


--【肆】--:

在那些llm providers验证过?好像纯文本的中转测试不行。


--【伍】--:

我还真是就默认 text


--【陆】--:

text会自动把图片base64然后识别的


--【柒】--:

可以直接运行脚本自动补全了,不过不支持图片识别的模型不知道加了会怎么样


--【捌】--:

是grok吗,那应该是上游设置了base64
image758×160 5.98 KB


--【玖】--:

gpt54

openclaw 的总结:

验证结果出来了,而且很关键:curl + base64 + xx/gpt-5.4 这条链路是成功的。

我刚刚实际跑的是:

  • 模型:gpt-5.4
  • 接口:POST https://claude.xx.vin/openai/responses
  • 图片:你刚发的 wechat-inbound-2286276.jpg
  • 图片传法:data:image/jpeg;base64,...
  • 结果:流式响应完整 completed

模型最终返回的原文是:
This is the Doubao AI chat page, showing an AI-generated Chinese answer about baby crib safety and vertical slat design.

这说明两件事:

  1. xx 的 gpt-5.4 本身能看图,没问题
  • 同一个 key
  • 同一个模型
  • 同一张图
  • 手工 curl 成功识别
  1. 所以 OpenClaw 里失败的根因,不是 xx/gpt-5.4 不支持图片
  • 而是 OpenClaw 发给 xx 的请求方式/headers/上下文,与我这条成功的 curl 不一致
  • 或者 OpenClaw 在运行时还混入了别的 profile / 旧配置 / fallback 逻辑,导致 provider 侧回了 403

现在问题被缩小到很具体的范围了:

  • curl -> xx/gpt-5.4 + image:成功
  • OpenClaw -> xx/gpt-5.4 + same image:403

所以接下来不该再怀疑模型能力,而应该查 OpenClaw 到 xx 的适配层。最可能是这几类差异:

  • baseUrl / 路径拼接和我们 curl 不完全一样
  • header 附带方式不同
  • OpenClaw 走的是它内部的 openai-responses 适配器,但加了额外字段,被 xx 拒绝
  • 仍然有旧会话/旧 provider profile 在用旧认证状态

如果你愿意,我下一步就直接去查 OpenClaw 源码里 openai-responses provider adapter,把它最终构造的请求格式对照我这条成功 curl,找出差异。这样就能把“为什么 OpenClaw 原生不行,而 curl 行”钉死。


--【拾】--:

openclaw还是得边修边补着用,离真正大伙真正的预想看似很近实则非常远~


--【拾壹】--:

好像是我写的插件的问题。AI帮我修好了,似乎能用了。坏消息是插件崩溃了,慢慢修吧


--【拾贰】--:

佬太猛了 我傻傻的自己手动写json


--【拾叁】--:

我用的3月3日的版本正常,然后就没更新过了,新版本这部分源码可能有改动过


--【拾肆】--:

写过几次老是宕机,不想写了


--【拾伍】--:

已经不想再聊了。得冷静冷静


--【拾陆】--:

Invalid type for 'input[2].content[1].image_url': expected an image URL, but got an object instead. cherry 报错,但是似乎转成base64是可以用。龙虾验证curl可行,现在是怎么让龙虾自动用的问题。论坛里没搜到研究报告

标签:纯水