mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-30 06:14:23 +00:00
fix(agents): prioritize per-model thinking defaults (#30439)
* fix(agents): honor per-model thinking defaults * fix(agents): preserve thinking fallback with model defaults --------- Co-authored-by: Mark L <73659136+markliuyuxiang@users.noreply.github.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
modelKey,
|
||||
resolveAllowedModelRef,
|
||||
resolveConfiguredModelRef,
|
||||
resolveThinkingDefault,
|
||||
resolveModelRefFromString,
|
||||
} from "./model-selection.js";
|
||||
|
||||
@@ -470,6 +471,39 @@ describe("model-selection", () => {
|
||||
expect(result).toEqual({ provider: "openai", model: "gpt-4" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveThinkingDefault", () => {
|
||||
it("prefers per-model params.thinking over global thinkingDefault", () => {
|
||||
const cfg = {
|
||||
agents: {
|
||||
defaults: {
|
||||
thinkingDefault: "low",
|
||||
models: {
|
||||
"anthropic/claude-opus-4-6": {
|
||||
params: { thinking: "high" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
expect(
|
||||
resolveThinkingDefault({
|
||||
cfg,
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-6",
|
||||
catalog: [
|
||||
{
|
||||
provider: "anthropic",
|
||||
id: "claude-opus-4-6",
|
||||
name: "Claude Opus 4.6",
|
||||
reasoning: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toBe("high");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeModelSelection", () => {
|
||||
|
||||
@@ -525,6 +525,19 @@ export function resolveThinkingDefault(params: {
|
||||
model: string;
|
||||
catalog?: ModelCatalogEntry[];
|
||||
}): ThinkLevel {
|
||||
const perModelThinking =
|
||||
params.cfg.agents?.defaults?.models?.[modelKey(params.provider, params.model)]?.params
|
||||
?.thinking;
|
||||
if (
|
||||
perModelThinking === "off" ||
|
||||
perModelThinking === "minimal" ||
|
||||
perModelThinking === "low" ||
|
||||
perModelThinking === "medium" ||
|
||||
perModelThinking === "high" ||
|
||||
perModelThinking === "xhigh"
|
||||
) {
|
||||
return perModelThinking;
|
||||
}
|
||||
const configured = params.cfg.agents?.defaults?.thinkingDefault;
|
||||
if (configured) {
|
||||
return configured;
|
||||
|
||||
36
src/auto-reply/reply/directive-handling.levels.test.ts
Normal file
36
src/auto-reply/reply/directive-handling.levels.test.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { resolveCurrentDirectiveLevels } from "./directive-handling.levels.js";
|
||||
|
||||
describe("resolveCurrentDirectiveLevels", () => {
|
||||
it("prefers resolved model default over agent thinkingDefault", async () => {
|
||||
const resolveDefaultThinkingLevel = vi.fn().mockResolvedValue("high");
|
||||
|
||||
const result = await resolveCurrentDirectiveLevels({
|
||||
sessionEntry: {},
|
||||
agentCfg: {
|
||||
thinkingDefault: "low",
|
||||
},
|
||||
resolveDefaultThinkingLevel,
|
||||
});
|
||||
|
||||
expect(result.currentThinkLevel).toBe("high");
|
||||
expect(resolveDefaultThinkingLevel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("keeps session thinking override without consulting defaults", async () => {
|
||||
const resolveDefaultThinkingLevel = vi.fn().mockResolvedValue("high");
|
||||
|
||||
const result = await resolveCurrentDirectiveLevels({
|
||||
sessionEntry: {
|
||||
thinkingLevel: "minimal",
|
||||
},
|
||||
agentCfg: {
|
||||
thinkingDefault: "low",
|
||||
},
|
||||
resolveDefaultThinkingLevel,
|
||||
});
|
||||
|
||||
expect(result.currentThinkLevel).toBe("minimal");
|
||||
expect(resolveDefaultThinkingLevel).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -21,8 +21,8 @@ export async function resolveCurrentDirectiveLevels(params: {
|
||||
}> {
|
||||
const resolvedDefaultThinkLevel =
|
||||
(params.sessionEntry?.thinkingLevel as ThinkLevel | undefined) ??
|
||||
(params.agentCfg?.thinkingDefault as ThinkLevel | undefined) ??
|
||||
(await params.resolveDefaultThinkingLevel());
|
||||
(await params.resolveDefaultThinkingLevel()) ??
|
||||
(params.agentCfg?.thinkingDefault as ThinkLevel | undefined);
|
||||
const currentThinkLevel = resolvedDefaultThinkLevel;
|
||||
const currentVerboseLevel =
|
||||
(params.sessionEntry?.verboseLevel as VerboseLevel | undefined) ??
|
||||
|
||||
@@ -339,9 +339,7 @@ export async function resolveReplyDirectives(params: {
|
||||
});
|
||||
const defaultActivation = defaultGroupActivation(requireMention);
|
||||
const resolvedThinkLevel =
|
||||
directives.thinkLevel ??
|
||||
(sessionEntry?.thinkingLevel as ThinkLevel | undefined) ??
|
||||
(agentCfg?.thinkingDefault as ThinkLevel | undefined);
|
||||
directives.thinkLevel ?? (sessionEntry?.thinkingLevel as ThinkLevel | undefined);
|
||||
|
||||
const resolvedVerboseLevel =
|
||||
directives.verboseLevel ??
|
||||
@@ -390,6 +388,10 @@ export async function resolveReplyDirectives(params: {
|
||||
});
|
||||
provider = modelState.provider;
|
||||
model = modelState.model;
|
||||
const resolvedThinkLevelWithDefault =
|
||||
resolvedThinkLevel ??
|
||||
(await modelState.resolveDefaultThinkingLevel()) ??
|
||||
(agentCfg?.thinkingDefault as ThinkLevel | undefined);
|
||||
|
||||
// When neither directive nor session set reasoning, default to model capability
|
||||
// (e.g. OpenRouter with reasoning: true). Skip auto-enabling when thinking is
|
||||
@@ -398,9 +400,7 @@ export async function resolveReplyDirectives(params: {
|
||||
const reasoningExplicitlySet =
|
||||
directives.reasoningLevel !== undefined ||
|
||||
(sessionEntry?.reasoningLevel !== undefined && sessionEntry?.reasoningLevel !== null);
|
||||
const effectiveThinkingForReasoning =
|
||||
resolvedThinkLevel ?? (await modelState.resolveDefaultThinkingLevel());
|
||||
const thinkingActive = effectiveThinkingForReasoning !== "off";
|
||||
const thinkingActive = resolvedThinkLevelWithDefault !== "off";
|
||||
if (!reasoningExplicitlySet && resolvedReasoningLevel === "off" && !thinkingActive) {
|
||||
resolvedReasoningLevel = await modelState.resolveDefaultReasoningLevel();
|
||||
}
|
||||
@@ -477,7 +477,7 @@ export async function resolveReplyDirectives(params: {
|
||||
elevatedAllowed,
|
||||
elevatedFailures,
|
||||
defaultActivation,
|
||||
resolvedThinkLevel,
|
||||
resolvedThinkLevel: resolvedThinkLevelWithDefault,
|
||||
resolvedVerboseLevel,
|
||||
resolvedReasoningLevel,
|
||||
resolvedElevatedLevel,
|
||||
|
||||
@@ -734,6 +734,25 @@ describe("agentCommand", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers per-model thinking over global thinkingDefault", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const store = path.join(home, "sessions.json");
|
||||
mockConfig(home, store, {
|
||||
thinkingDefault: "low",
|
||||
models: {
|
||||
"anthropic/claude-opus-4-5": {
|
||||
params: { thinking: "high" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await agentCommand({ message: "hi", to: "+1555" }, runtime);
|
||||
|
||||
const callArgs = vi.mocked(runEmbeddedPiAgent).mock.calls.at(-1)?.[0];
|
||||
expect(callArgs?.thinkLevel).toBe("high");
|
||||
});
|
||||
});
|
||||
|
||||
it("prints JSON payload when requested", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
|
||||
@@ -588,11 +588,7 @@ export async function agentCommand(
|
||||
});
|
||||
}
|
||||
|
||||
let resolvedThinkLevel =
|
||||
thinkOnce ??
|
||||
thinkOverride ??
|
||||
persistedThinking ??
|
||||
(agentCfg?.thinkingDefault as ThinkLevel | undefined);
|
||||
let resolvedThinkLevel = thinkOnce ?? thinkOverride ?? persistedThinking;
|
||||
const resolvedVerboseLevel =
|
||||
verboseOverride ?? persistedVerbose ?? (agentCfg?.verboseDefault as VerboseLevel | undefined);
|
||||
|
||||
|
||||
@@ -299,16 +299,15 @@ export async function runCronIsolatedAgentTurn(params: {
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve thinking level - job thinking > hooks.gmail.thinking > agent default
|
||||
// Resolve thinking level - job thinking > hooks.gmail.thinking > model/global defaults
|
||||
const hooksGmailThinking = isGmailHook
|
||||
? normalizeThinkLevel(params.cfg.hooks?.gmail?.thinking)
|
||||
: undefined;
|
||||
const thinkOverride = normalizeThinkLevel(agentCfg?.thinkingDefault);
|
||||
const jobThink = normalizeThinkLevel(
|
||||
(params.job.payload.kind === "agentTurn" ? params.job.payload.thinking : undefined) ??
|
||||
undefined,
|
||||
);
|
||||
let thinkLevel = jobThink ?? hooksGmailThinking ?? thinkOverride;
|
||||
let thinkLevel = jobThink ?? hooksGmailThinking;
|
||||
if (!thinkLevel) {
|
||||
thinkLevel = resolveThinkingDefault({
|
||||
cfg: cfgWithAgentDefaults,
|
||||
|
||||
@@ -574,20 +574,15 @@ export const chatHandlers: GatewayRequestHandlers = {
|
||||
}
|
||||
let thinkingLevel = entry?.thinkingLevel;
|
||||
if (!thinkingLevel) {
|
||||
const configured = cfg.agents?.defaults?.thinkingDefault;
|
||||
if (configured) {
|
||||
thinkingLevel = configured;
|
||||
} else {
|
||||
const sessionAgentId = resolveSessionAgentId({ sessionKey, config: cfg });
|
||||
const { provider, model } = resolveSessionModelRef(cfg, entry, sessionAgentId);
|
||||
const catalog = await context.loadGatewayModelCatalog();
|
||||
thinkingLevel = resolveThinkingDefault({
|
||||
cfg,
|
||||
provider,
|
||||
model,
|
||||
catalog,
|
||||
});
|
||||
}
|
||||
const sessionAgentId = resolveSessionAgentId({ sessionKey, config: cfg });
|
||||
const { provider, model } = resolveSessionModelRef(cfg, entry, sessionAgentId);
|
||||
const catalog = await context.loadGatewayModelCatalog();
|
||||
thinkingLevel = resolveThinkingDefault({
|
||||
cfg,
|
||||
provider,
|
||||
model,
|
||||
catalog,
|
||||
});
|
||||
}
|
||||
const verboseLevel = entry?.verboseLevel ?? cfg.agents?.defaults?.verboseDefault;
|
||||
respond(true, {
|
||||
|
||||
Reference in New Issue
Block a user