Compare commits

...

1 Commits

Author SHA1 Message Date
ImLukeF
ee42928807 Add adaptive thinking resolution 2026-04-24 11:25:36 +10:00
3 changed files with 140 additions and 2 deletions

View File

@@ -135,6 +135,7 @@ import type {
EmbeddedRunLivenessState,
} from "./types.js";
import { createUsageAccumulator, mergeUsageIntoAccumulator } from "./usage-accumulator.js";
import { resolveAdaptiveThinkingLevel } from "./utils.js";
type ApiKeyInfo = ResolvedProviderAuth;
@@ -667,7 +668,6 @@ export async function runEmbeddedPiAgent(
runLoopIterations += 1;
const runtimeAuthRetry = authRetryPending;
authRetryPending = false;
attemptedThinking.add(thinkLevel);
await fs.mkdir(resolvedWorkspace, { recursive: true });
const basePrompt =
@@ -684,6 +684,21 @@ export async function runEmbeddedPiAgent(
promptAdditions.length > 0
? `${basePrompt}\n\n${promptAdditions.join("\n\n")}`
: basePrompt;
const attemptThinkLevel = resolveAdaptiveThinkingLevel({
level: thinkLevel,
prompt,
trigger: params.trigger,
provider,
modelId,
hasImages: (params.images?.length ?? 0) > 0,
disableTools: params.disableTools,
});
if (thinkLevel === "adaptive" && attemptThinkLevel !== "adaptive") {
log.debug(
`adaptive thinking resolved for ${provider}/${modelId}: ${attemptThinkLevel} (trigger=${params.trigger ?? "user"})`,
);
}
attemptedThinking.add(attemptThinkLevel);
let resolvedStreamApiKey: string | undefined;
if (!runtimeAuthState && apiKeyInfo) {
resolvedStreamApiKey = (apiKeyInfo as ApiKeyInfo).apiKey;
@@ -749,7 +764,7 @@ export async function runEmbeddedPiAgent(
modelRegistry,
agentId: workspaceResolution.agentId,
legacyBeforeAgentStartResult,
thinkLevel,
thinkLevel: attemptThinkLevel,
fastMode: params.fastMode,
verboseLevel: params.verboseLevel,
reasoningLevel: params.reasoningLevel,

View File

@@ -0,0 +1,47 @@
import { describe, expect, it } from "vitest";
import { mapThinkingLevel, resolveAdaptiveThinkingLevel } from "./utils.js";
describe("resolveAdaptiveThinkingLevel", () => {
it("passes through fixed thinking levels", () => {
expect(resolveAdaptiveThinkingLevel({ level: "high", prompt: "fix the gateway" })).toBe("high");
});
it("keeps simple chat light", () => {
expect(resolveAdaptiveThinkingLevel({ level: "adaptive", prompt: "what model are you" })).toBe(
"low",
);
});
it("uses medium for ordinary tool-capable turns", () => {
expect(
resolveAdaptiveThinkingLevel({
level: "adaptive",
prompt: "Can you check my notes and summarize what changed today?",
}),
).toBe("medium");
});
it("uses high for investigation and debugging", () => {
expect(
resolveAdaptiveThinkingLevel({
level: "adaptive",
prompt: "Can you check the logs and debug why Telegram is timing out?",
}),
).toBe("high");
});
it("uses xhigh for risky production mutations", () => {
expect(
resolveAdaptiveThinkingLevel({
level: "adaptive",
prompt: "Fix the VPS gateway auth token issue and restart the production service.",
}),
).toBe("xhigh");
});
});
describe("mapThinkingLevel", () => {
it("retains the defensive adaptive fallback mapping", () => {
expect(mapThinkingLevel("adaptive")).toBe("medium");
});
});

View File

@@ -1,6 +1,82 @@
import type { ThinkingLevel } from "@mariozechner/pi-agent-core";
import type { ReasoningLevel, ThinkLevel } from "../../auto-reply/thinking.js";
type AdaptiveThinkingTrigger = "cron" | "heartbeat" | "manual" | "memory" | "overflow" | "user";
export type ResolveAdaptiveThinkingParams = {
level?: ThinkLevel;
prompt?: string;
trigger?: AdaptiveThinkingTrigger;
provider?: string;
modelId?: string;
hasImages?: boolean;
disableTools?: boolean;
};
const SIMPLE_TURN_RE =
/^\s*(\/?(status|help|start|menu|ping|model|models)|hi|hello|hey|yo|ok|okay|thanks|thank you|what model are you|what'?s your model)\b/i;
const HIGH_SIGNAL_RE =
/\b(debug|investigate|trace|logs?|root cause|why|broken|failing|failed|failure|error|exception|timeout|regression|smoke test|doctor|health ?check|review|audit|plan|architecture|design)\b/i;
const MUTATION_RE =
/\b(fix|patch|implement|edit|change|update|delete|remove|clean|deploy|migrate|restart|install|configure|setup|reauth|auth|wire|enable|disable)\b/i;
const RISK_SURFACE_RE =
/\b(vps|production|prod|gateway|systemd|service|oauth|auth|token|secret|api key|database|db|convex|graph|email|telegram|discord|journal|cron|provider|model|billing|security|ssh)\b/i;
const XHIGH_RISK_RE =
/\b(delete|remove|migrate|migration|production|prod|oauth|auth|token|secret|security|database|db|billing|gateway|systemd|restart|deploy)\b/i;
export function resolveAdaptiveThinkingLevel(params: ResolveAdaptiveThinkingParams): ThinkLevel {
const requested = params.level ?? "off";
if (requested !== "adaptive") {
return requested;
}
const prompt = params.prompt ?? "";
const trimmedPrompt = prompt.trim();
const promptChars = trimmedPrompt.length;
if (params.trigger === "memory" || params.trigger === "heartbeat") {
return "low";
}
if (params.trigger === "cron") {
return HIGH_SIGNAL_RE.test(prompt) || MUTATION_RE.test(prompt) ? "medium" : "low";
}
if (!params.hasImages && promptChars < 160 && SIMPLE_TURN_RE.test(trimmedPrompt)) {
return "low";
}
if (params.hasImages) {
return "high";
}
const highSignal = HIGH_SIGNAL_RE.test(prompt);
const mutation = MUTATION_RE.test(prompt);
const riskSurface = RISK_SURFACE_RE.test(prompt);
if (mutation && riskSurface && XHIGH_RISK_RE.test(prompt)) {
return "xhigh";
}
if ((mutation && riskSurface) || highSignal) {
return "high";
}
if (promptChars > 8_000) {
return "high";
}
if (promptChars > 2_500 || !params.disableTools) {
return "medium";
}
return "low";
}
export function mapThinkingLevel(level?: ThinkLevel): ThinkingLevel {
// pi-agent-core supports "xhigh"; OpenClaw enables it for specific models.
if (!level) {