diff --git a/extensions/ollama/src/setup-model-selection.test.ts b/extensions/ollama/src/setup-model-selection.test.ts index 5868a5528135..9e2abb615a78 100644 --- a/extensions/ollama/src/setup-model-selection.test.ts +++ b/extensions/ollama/src/setup-model-selection.test.ts @@ -1,3 +1,4 @@ +import type { WizardPrompter } from "openclaw/plugin-sdk/setup"; import { requestUrl } from "openclaw/plugin-sdk/test-env"; import { afterEach, describe, expect, it, vi } from "vitest"; import { @@ -8,6 +9,7 @@ import { normalizeOllamaModelName, selectAppGuidedOllamaModelFromDiscovery, } from "./setup-model-selection.js"; +import { promptAndConfigureOllama } from "./setup.js"; afterEach(() => { vi.unstubAllGlobals(); @@ -81,13 +83,24 @@ describe("Ollama onboarding model selection", () => { ).toBe("qwen3:0.6b"); }); - it("prefers the smallest non-reasoning setup model", () => { + it.each([ + { + description: "skips a smaller model with an explicit thinking capability", + capabilities: ["tools", "thinking"], + expected: "llama3.2:latest", + }, + { + description: "trusts explicit non-thinking capabilities over a reasoning-like model name", + capabilities: ["tools"], + expected: "deepseek-r1:8b", + }, + ])("$description", ({ capabilities, expected }) => { expect( selectAppGuidedOllamaModelFromDiscovery([ { name: "deepseek-r1:8b", contextWindow: 131_072, - capabilities: ["tools", "thinking"], + capabilities, size: 1_000, }, { @@ -103,7 +116,44 @@ describe("Ollama onboarding model selection", () => { size: 2_000, }, ]), - ).toBe("llama3.2:latest"); + ).toBe(expected); + }); + + it("selects the smallest explicitly non-thinking model during interactive setup", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async (input: string | URL | Request) => { + const url = requestUrl(input); + if (url.endsWith("/api/tags")) { + return Response.json({ + models: [ + { name: "deepseek-r1:1b", size: 100 }, + { name: "llama3:70b", size: 70_000 }, + ], + }); + } + if (url.endsWith("/api/show")) { + return Response.json({ + model_info: { "test.context_length": 32_768 }, + capabilities: ["completion", "tools"], + }); + } + throw new Error(`Unexpected fetch: ${url}`); + }), + ); + + const prompter = { + select: vi.fn().mockResolvedValueOnce("local-only"), + text: vi.fn().mockResolvedValueOnce("http://127.0.0.1:11434"), + note: vi.fn(async () => undefined), + } as unknown as WizardPrompter; + const result = await promptAndConfigureOllama({ cfg: {}, prompter }); + const selected = result.config.models?.providers?.ollama?.models?.find( + (model) => model.id === "deepseek-r1:1b", + ); + + expect(selected?.reasoning).toBe(false); + expect(result.defaultModel).toBe("ollama/deepseek-r1:1b"); }); it("aborts pending model discovery with the setup signal", async () => { diff --git a/extensions/ollama/src/setup-model-selection.ts b/extensions/ollama/src/setup-model-selection.ts index 565faf2cf554..b833932d3041 100644 --- a/extensions/ollama/src/setup-model-selection.ts +++ b/extensions/ollama/src/setup-model-selection.ts @@ -112,8 +112,7 @@ export function selectAppGuidedOllamaModelFromDiscovery( id: model.name, contextWindow: model.contextWindow, supportsTools: model.capabilities?.includes("tools") === true, - reasoning: - model.capabilities?.includes("thinking") === true || isReasoningModelHeuristic(model.name), + reasoning: model.capabilities?.includes("thinking") ?? isReasoningModelHeuristic(model.name), size: model.size, })), );