openclaw - ✅(Solved) Fix [Bug] Bailian provider image model lookup fails due to double provider prefix [2 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#68895Fetched 2026-04-19 15:06:31
View on GitHub
Comments
0
Participants
1
Timeline
4
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×2referenced ×2

Error Message

Image model service fails for Bailian (DashScope) provider with error:

Root Cause

CRITICAL UPDATE: Text models work correctly, but image models fail. The root cause is different resolution paths between text and image models.

Fix Action

Fixed

PR fix notes

PR #68906: fix(media): avoid double-prefixing qualified image models

Description (problem / solution / changelog)

Summary

  • Tolerate image model IDs that arrive already qualified with the same provider, e.g. provider=bailian and model=bailian/qwen3.5-plus.
  • Keep the existing normalized lookup first, then retry the parsed provider/model ref only when it is the same provider. This avoids changing global model normalization behavior.
  • Add a Bailian regression test for the double-prefix lookup failure.

Closes #68895.

Test plan

  • pnpm vitest run src/media-understanding/image.test.ts (6 passed)
  • pnpm vitest run src/media-understanding/image.test.ts src/agents/tools/media-tool-shared.model-resolution.test.ts (8 passed)
  • pnpm exec oxfmt --check src/media-understanding/image.ts src/media-understanding/image.test.ts
  • git diff --check

Notes

NODE_OPTIONS=--max-old-space-size=8192 pnpm exec tsc --noEmit --pretty false --project tsconfig.test.src.json is currently blocked by an unrelated upstream type error in src/tasks/detached-task-runtime.test.ts:60, where the mock startTaskRunByRunId returns undefined but the interface expects TaskRecord[].

Changed files

  • src/media-understanding/image.test.ts (modified, +43/-0)
  • src/media-understanding/image.ts (modified, +30/-3)

PR #68919: fix(tools): remove provider prefix from vision model config lookup

Description (problem / solution / changelog)

Problem

#68895 — Image model lookup fails for Bailian (and likely other providers) with error:

Unknown model: bailian/qwen3.5-plus

Root Cause

resolveProviderVisionModelFromConfig() in image-tool.helpers.ts:78 returns "${provider}/${modelId}" (e.g. "bailian/qwen3.5-plus"). But the downstream resolveImageRuntime()normalizeModelRef() re-applies the provider prefix, resulting in a double-prefix lookup ("bailian/bailian/qwen3.5-plus").

Text models work because they go through a different resolution path that returns { provider, model } (pure ID).

Fix

One line: return pure model ID instead of \${provider}/${id}``. Callers that need the prefixed form add it themselves.

-  return id ? `${params.provider}/${id}` : null;
+  return id || null;

Test

  • Configure Bailian with image model → should resolve correctly now
  • No behavior change for other providers (prefix added downstream as before)

Changed files

  • src/media-understanding/image.ts (modified, +4/-1)

Code Example

All image models failed (2): bailian/qwen3.5-plus: Unknown model: bailian/qwen3.5-plus | bailian/kimi-k2.5: Unknown model: bailian/kimi-k2.5

---

Config: "bailian/qwen3.5-plus"
resolveConfiguredModelRef()
parseModelRef("bailian/qwen3.5-plus", defaultProvider)
Split at "/" → provider="bailian", model="qwen3.5-plus"normalizeModelRef("bailian", "qwen3.5-plus")
normalizeStaticProviderModelId("bailian", "qwen3.5-plus")
Returns "qwen3.5-plus" (unchanged, bailian not in hardcoded list)
Returns { provider: "bailian", model: "qwen3.5-plus" }
modelRegistry.find("bailian", "qwen3.5-plus")SUCCESS

---

Config: "bailian/qwen3.5-plus"
resolveProviderVisionModelFromConfig()
Returns "bailian/qwen3.5-plus"  (already prefixed)
resolveImageRuntime(provider="bailian", model="bailian/qwen3.5-plus")
normalizeModelRef("bailian", "bailian/qwen3.5-plus")parseModelRef("bailian/bailian/qwen3.5-plus")
Split at "/" → provider="bailian", model="bailian/qwen3.5-plus"  (still prefixed!)
normalizeStaticProviderModelId("bailian", "bailian/qwen3.5-plus")
Returns "bailian/qwen3.5-plus" (bailian not in hardcoded list, no processing)
Returns { provider: "bailian", model: "bailian/qwen3.5-plus" }
modelRegistry.find("bailian", "bailian/qwen3.5-plus")FAILS
    (registry only has "qwen3.5-plus")

---

export function resolveProviderVisionModelFromConfig(params: {
  cfg?: OpenClawConfig;
  provider: string;
}): string | null {
  // ...
  return id ? `${params.provider}/${id}` : null;  // Returns "bailian/qwen3.5-plus"
}

---

{
  "models": {
    "providers": {
      "bailian": {
        "baseUrl": "https://coding.dashscope.aliyuncs.com/v1",
        "api": "openai-completions",
        "models": [
          {
            "id": "qwen3.5-plus",
            "input": ["text", "image"]
          }
        ]
      }
    }
  },
  "imageModel": {
    "primary": "bailian/qwen3.5-plus"
  }
}

---

export function resolveProviderVisionModelFromConfig(params: {
  cfg?: OpenClawConfig;
  provider: string;
}): string | null {
  const providerCfg = findNormalizedProviderValue(
    params.cfg?.models?.providers,
    params.provider,
  ) as unknown as { models?: Array<{ id?: string; input?: string[] }> } | undefined;
  const models = providerCfg?.models ?? [];
  const picked = models.find((m) => Boolean((m?.id ?? "").trim()) && m.input?.includes("image"));
  const id = (picked?.id ?? "").trim();
  return id || null;  // Return "qwen3.5-plus" instead of "bailian/qwen3.5-plus"
}

---

// src/agents/model-ref-shared.ts
export function normalizeStaticProviderModelId(provider: string, model: string): string {
  if (provider === "anthropic") {
    return normalizeAnthropicModelId(model);
  }
  if (provider === "huggingface") {
    return normalizeHuggingfaceModelId(model);
  }
  if (provider === "google" || provider === "google-vertex") {
    return normalizeGooglePreviewModelId(model);
  }
  if (provider === "openrouter" && !model.includes("/")) {
    return `openrouter/${model}`;
  }
  if (provider === "xai") {
    return normalizeNativeXaiModelId(model);
  }
  if (provider === "vercel-ai-gateway" && !model.includes("/")) {
    const normalizedAnthropicModel = normalizeAnthropicModelId(model);
    if (normalizedAnthropicModel.startsWith("claude-")) {
      return `anthropic/${normalizedAnthropicModel}`;
    }
  }
  // NEW: Add bailian handling to strip existing prefix
  if (provider === "bailian") {
    const prefix = "bailian/";
    return model.startsWith(prefix) ? model.slice(prefix.length) : model;
  }
  return model;
}
RAW_BUFFERClick to expand / collapse

[Bug] Bailian provider image model lookup fails due to double provider prefix

Describe the bug

Image model service fails for Bailian (DashScope) provider with error:

All image models failed (2): bailian/qwen3.5-plus: Unknown model: bailian/qwen3.5-plus | bailian/kimi-k2.5: Unknown model: bailian/kimi-k2.5

The issue was introduced in commit e59f5ecac3 (fix(tools): normalize media model lookups #66422) between v2026.4.12 (working) and v2026.4.19-beta.2 (broken).

Root Cause

CRITICAL UPDATE: Text models work correctly, but image models fail. The root cause is different resolution paths between text and image models.

Why Text Models Work

Text model resolution path:

Config: "bailian/qwen3.5-plus"
resolveConfiguredModelRef()
parseModelRef("bailian/qwen3.5-plus", defaultProvider)
Split at "/" → provider="bailian", model="qwen3.5-plus" ✓
normalizeModelRef("bailian", "qwen3.5-plus")
normalizeStaticProviderModelId("bailian", "qwen3.5-plus")
    → Returns "qwen3.5-plus" (unchanged, bailian not in hardcoded list)
Returns { provider: "bailian", model: "qwen3.5-plus" }
modelRegistry.find("bailian", "qwen3.5-plus") ✓ SUCCESS

Why Image Models Fail

Image model resolution path:

Config: "bailian/qwen3.5-plus"
resolveProviderVisionModelFromConfig()
Returns "bailian/qwen3.5-plus" ✗ (already prefixed)
resolveImageRuntime(provider="bailian", model="bailian/qwen3.5-plus")
normalizeModelRef("bailian", "bailian/qwen3.5-plus") ✗
parseModelRef("bailian/bailian/qwen3.5-plus")
Split at "/" → provider="bailian", model="bailian/qwen3.5-plus" ✗ (still prefixed!)
normalizeStaticProviderModelId("bailian", "bailian/qwen3.5-plus")
    → Returns "bailian/qwen3.5-plus" (bailian not in hardcoded list, no processing)
Returns { provider: "bailian", model: "bailian/qwen3.5-plus" }
modelRegistry.find("bailian", "bailian/qwen3.5-plus") ✗ FAILS
    (registry only has "qwen3.5-plus")

Key Difference

AspectText ModelImage Model
Resolution FunctionparseModelRefresolveProviderVisionModelFromConfig
Return Format{ provider, model } (model is pure ID)"${provider}/${model}" (prefixed string)
Secondary normalize❌ No✅ Yes (resolveImageRuntime calls normalizeModelRef)
Final model value"qwen3.5-plus""bailian/qwen3.5-plus"

Original Root Cause (Still Valid)

The function resolveProviderVisionModelFromConfig in src/agents/tools/image-tool.helpers.ts returns the model ID with provider prefix already included:

export function resolveProviderVisionModelFromConfig(params: {
  cfg?: OpenClawConfig;
  provider: string;
}): string | null {
  // ...
  return id ? `${params.provider}/${id}` : null;  // Returns "bailian/qwen3.5-plus"
}

This causes a double-prefixing issue in the model resolution chain:

  1. resolveProviderVisionModelFromConfig returns "bailian/qwen3.5-plus"
  2. buildToolModelConfigFromCandidates parses this as provider="bailian", but keeps modelId="bailian/qwen3.5-plus"
  3. runWithImageModelFallback calls run("bailian", "bailian/qwen3.5-plus")
  4. resolveImageRuntime calls normalizeModelRef("bailian", "bailian/qwen3.5-plus")
  5. parseModelRef("bailian/bailian/qwen3.5-plus") parses to provider="bailian", model="bailian/qwen3.5-plus"
  6. modelRegistry.find("bailian", "bailian/qwen3.5-plus") fails because the configured model ID is "qwen3.5-plus" (without prefix)

Steps to Reproduce

  1. Configure Bailian provider in openclaw.json:
{
  "models": {
    "providers": {
      "bailian": {
        "baseUrl": "https://coding.dashscope.aliyuncs.com/v1",
        "api": "openai-completions",
        "models": [
          {
            "id": "qwen3.5-plus",
            "input": ["text", "image"]
          }
        ]
      }
    }
  },
  "imageModel": {
    "primary": "bailian/qwen3.5-plus"
  }
}
  1. Send an image to the agent with a question (e.g., "这是什么")
  2. Image analysis fails with "Unknown model: bailian/qwen3.5-plus"

Expected Behavior

The model lookup should succeed with provider="bailian" and model="qwen3.5-plus".

Proposed Fix

Option 1 (Recommended): Modify resolveProviderVisionModelFromConfig to return pure model ID:

export function resolveProviderVisionModelFromConfig(params: {
  cfg?: OpenClawConfig;
  provider: string;
}): string | null {
  const providerCfg = findNormalizedProviderValue(
    params.cfg?.models?.providers,
    params.provider,
  ) as unknown as { models?: Array<{ id?: string; input?: string[] }> } | undefined;
  const models = providerCfg?.models ?? [];
  const picked = models.find((m) => Boolean((m?.id ?? "").trim()) && m.input?.includes("image"));
  const id = (picked?.id ?? "").trim();
  return id || null;  // Return "qwen3.5-plus" instead of "bailian/qwen3.5-plus"
}

This ensures downstream code receives the pure model ID, and normalizeModelRef("bailian", "qwen3.5-plus") correctly resolves to { provider: "bailian", model: "qwen3.5-plus" }.

Option 2 (Alternative): Add bailian-specific handling to normalizeStaticProviderModelId:

// src/agents/model-ref-shared.ts
export function normalizeStaticProviderModelId(provider: string, model: string): string {
  if (provider === "anthropic") {
    return normalizeAnthropicModelId(model);
  }
  if (provider === "huggingface") {
    return normalizeHuggingfaceModelId(model);
  }
  if (provider === "google" || provider === "google-vertex") {
    return normalizeGooglePreviewModelId(model);
  }
  if (provider === "openrouter" && !model.includes("/")) {
    return `openrouter/${model}`;
  }
  if (provider === "xai") {
    return normalizeNativeXaiModelId(model);
  }
  if (provider === "vercel-ai-gateway" && !model.includes("/")) {
    const normalizedAnthropicModel = normalizeAnthropicModelId(model);
    if (normalizedAnthropicModel.startsWith("claude-")) {
      return `anthropic/${normalizedAnthropicModel}`;
    }
  }
  // NEW: Add bailian handling to strip existing prefix
  if (provider === "bailian") {
    const prefix = "bailian/";
    return model.startsWith(prefix) ? model.slice(prefix.length) : model;
  }
  return model;
}

Option 1 is preferred because it fixes the issue at the source without requiring provider-specific hacks in the normalization layer.

Environment

  • OpenClaw version: v2026.4.19-beta.2
  • Last working version: v2026.4.12
  • Provider: Bailian (DashScope)
  • Affected models: All image-capable models (qwen3.5-plus, kimi-k2.5, etc.)

Additional Context

This affects any provider using the resolveProviderVisionModelFromConfig function. The issue may also impact other providers beyond Bailian.

Relevant files:

  • src/agents/tools/image-tool.helpers.ts (line 67-78)
  • src/agents/tools/media-tool-shared.ts (line 400-410)
  • src/agents/model-selection-normalize.ts (normalizeModelRef function)

extent analysis

TL;DR

Modify the resolveProviderVisionModelFromConfig function to return the pure model ID without the provider prefix.

Guidance

  1. Identify the root cause: The issue is caused by the resolveProviderVisionModelFromConfig function returning the model ID with the provider prefix already included, leading to double-prefixing in the model resolution chain.
  2. Apply the proposed fix: Update the resolveProviderVisionModelFromConfig function to return the pure model ID, as shown in the proposed fix (Option 1).
  3. Verify the fix: Test the image model lookup with the updated function to ensure it succeeds with the correct provider and model IDs.
  4. Consider alternative solutions: If the proposed fix is not feasible, consider adding provider-specific handling to the normalizeStaticProviderModelId function, as shown in Option 2.

Example

The updated resolveProviderVisionModelFromConfig function should return the pure model ID, like this:

export function resolveProviderVisionModelFromConfig(params: {
  cfg?: OpenClawConfig;
  provider: string;
}): string | null {
  // ...
  return id || null;  // Return "qwen3.5-plus" instead of "bailian/qwen3.5-plus"
}

Notes

The proposed fix assumes that the resolveProviderVisionModelFromConfig function is the root cause of the issue. If this is not the case, additional debugging may be required to identify the correct root cause.

Recommendation

Apply the proposed fix (Option 1) to update the resolveProviderVisionModelFromConfig function, as it is the most straightforward and efficient solution. This fix addresses the root cause of the issue and ensures that the model lookup works correctly for the Bailian provider.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix [Bug] Bailian provider image model lookup fails due to double provider prefix [2 pull requests, 1 participants]