From e482da66827948a940782967fced675e089edbba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E5=81=9A=E4=BA=86=E7=9D=A1=E5=A4=A7=E8=A7=89?= <64798754+stakeswky@users.noreply.github.com> Date: Mon, 2 Mar 2026 10:38:42 +0800 Subject: [PATCH] fix(ollama): prioritize provider baseUrl for embedded runner (#30964) * fix(ollama): honor provider baseUrl in embedded runner * Embedded Ollama: clarify provider baseUrl precedence comment * Changelog: note embedded Ollama baseUrl precedence fix * Telegram: apply required formatter update in accounts config merge * Revert "Telegram: apply required formatter update in accounts config merge" This reverts commit d372b26975ce1ab8245f6f7c2aa242f58dfc6a6d. * Update CHANGELOG.md --------- Co-authored-by: User Co-authored-by: Vincent Koc --- CHANGELOG.md | 1 + .../pi-embedded-runner/run/attempt.test.ts | 24 +++++++++++++++++ src/agents/pi-embedded-runner/run/attempt.ts | 26 ++++++++++++++++--- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18d794a9be7..51f1ddec2d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Docs: https://docs.openclaw.ai - Control UI/Debug log layout: render Debug Event Log payloads at full width to prevent payload JSON from being squeezed into a narrow side column. Landed from contributor PR #30978 by @stozo04. Thanks @stozo04. - Cron editor viewport: make the sticky cron edit form independently scrollable with viewport-bounded height so lower fields/actions are reachable on shorter screens. Landed from contributor PR #31133 by @Sid-Qin. Thanks @Sid-Qin. - Agents/Thinking fallback: when providers reject unsupported thinking levels without enumerating alternatives, retry with `think=off` to avoid hard failure during model/provider fallback chains. Landed from contributor PR #31002 by @yfge. Thanks @yfge. +- Ollama/Embedded runner base URL precedence: prioritize configured provider `baseUrl` over model defaults for embedded Ollama runs so Docker and remote-host setups avoid localhost fetch failures. (#30964) Thanks @stakeswky. - Agents/Failover reason classification: avoid false rate-limit classification from incidental `tpm` substrings by matching TPM as a standalone token/phrase and keeping auth-context errors on the auth path. Landed from contributor PR #31007 by @HOYALIM. Thanks @HOYALIM. - Slack/Announce target account routing: enable session-backed announce-target lookup for Slack so multi-account announces resolve the correct `accountId` instead of defaulting to bot-token context. Landed from contributor PR #31028 by @taw0002. Thanks @taw0002. - Tools/Edit workspace boundary errors: preserve the real `Path escapes workspace root` failure path instead of surfacing a misleading access/file-not-found error when editing outside workspace roots. Landed from contributor PR #31015 by @haosenwang1018. Thanks @haosenwang1018. diff --git a/src/agents/pi-embedded-runner/run/attempt.test.ts b/src/agents/pi-embedded-runner/run/attempt.test.ts index c7ef97c4133..705025eaf5a 100644 --- a/src/agents/pi-embedded-runner/run/attempt.test.ts +++ b/src/agents/pi-embedded-runner/run/attempt.test.ts @@ -3,6 +3,7 @@ import type { OpenClawConfig } from "../../../config/config.js"; import { isOllamaCompatProvider, resolveAttemptFsWorkspaceOnly, + resolveOllamaBaseUrlForRun, resolveOllamaCompatNumCtxEnabled, resolvePromptBuildHookResult, resolvePromptModeForSession, @@ -285,6 +286,29 @@ describe("isOllamaCompatProvider", () => { }); }); +describe("resolveOllamaBaseUrlForRun", () => { + it("prefers provider baseUrl over model baseUrl", () => { + expect( + resolveOllamaBaseUrlForRun({ + modelBaseUrl: "http://model-host:11434", + providerBaseUrl: "http://provider-host:11434", + }), + ).toBe("http://provider-host:11434"); + }); + + it("falls back to model baseUrl when provider baseUrl is missing", () => { + expect( + resolveOllamaBaseUrlForRun({ + modelBaseUrl: "http://model-host:11434", + }), + ).toBe("http://model-host:11434"); + }); + + it("falls back to native default when neither baseUrl is configured", () => { + expect(resolveOllamaBaseUrlForRun({})).toBe("http://127.0.0.1:11434"); + }); +}); + describe("wrapOllamaCompatNumCtx", () => { it("injects num_ctx and preserves downstream onPayload hooks", () => { let payloadSeen: Record | undefined; diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index 5a0aa1d0a72..45b98fc03c5 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -258,6 +258,21 @@ function normalizeToolCallNameForDispatch(rawName: string, allowedToolNames?: Se return caseInsensitiveMatch ?? trimmed; } +export function resolveOllamaBaseUrlForRun(params: { + modelBaseUrl?: string; + providerBaseUrl?: string; +}): string { + const providerBaseUrl = params.providerBaseUrl?.trim() ?? ""; + if (providerBaseUrl) { + return providerBaseUrl; + } + const modelBaseUrl = params.modelBaseUrl?.trim() ?? ""; + if (modelBaseUrl) { + return modelBaseUrl; + } + return OLLAMA_NATIVE_BASE_URL; +} + function trimWhitespaceFromToolCallNamesInMessage( message: unknown, allowedToolNames?: Set, @@ -902,13 +917,16 @@ export async function runEmbeddedAttempt( // Ollama native API: bypass SDK's streamSimple and use direct /api/chat calls // for reliable streaming + tool calling support (#11828). if (params.model.api === "ollama") { - // Use the resolved model baseUrl first so custom provider aliases work. + // Prioritize configured provider baseUrl so Docker/remote Ollama hosts work reliably. const providerConfig = params.config?.models?.providers?.[params.model.provider]; const modelBaseUrl = - typeof params.model.baseUrl === "string" ? params.model.baseUrl.trim() : ""; + typeof params.model.baseUrl === "string" ? params.model.baseUrl : undefined; const providerBaseUrl = - typeof providerConfig?.baseUrl === "string" ? providerConfig.baseUrl.trim() : ""; - const ollamaBaseUrl = modelBaseUrl || providerBaseUrl || OLLAMA_NATIVE_BASE_URL; + typeof providerConfig?.baseUrl === "string" ? providerConfig.baseUrl : undefined; + const ollamaBaseUrl = resolveOllamaBaseUrlForRun({ + modelBaseUrl, + providerBaseUrl, + }); activeSession.agent.streamFn = createOllamaStreamFn(ollamaBaseUrl); } else if (params.model.api === "openai-responses" && params.provider === "openai") { const wsApiKey = await params.authStorage.getApiKey(params.provider);