fix commitments extractor model selection (#75347)

This commit is contained in:
Vignesh
2026-04-30 18:57:21 -07:00
committed by GitHub
parent 4b98f09529
commit 38da2ac6f8
3 changed files with 170 additions and 8 deletions

View File

@@ -1,6 +1,7 @@
import { randomUUID } from "node:crypto";
import path from "node:path";
import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
import { resolveDefaultModelForAgent } from "../agents/model-selection.js";
import { runEmbeddedPiAgent, type EmbeddedPiRunResult } from "../agents/pi-embedded.js";
import type { OpenClawConfig } from "../config/config.js";
import { resolveStateDir } from "../config/paths.js";
@@ -41,12 +42,14 @@ export type CommitmentExtractionRuntime = {
};
const log = createSubsystemLogger("commitments");
const TERMINAL_EXTRACTION_FAILURE_COOLDOWN_MS = 15 * 60_000;
let runtime: CommitmentExtractionRuntime = {};
let queue: Array<Omit<CommitmentExtractionItem, "existingPending"> & { cfg?: OpenClawConfig }> = [];
let timer: TimerHandle | null = null;
let draining = false;
let queueOverflowWarned = false;
let terminalFailureCooldownUntilByAgent = new Map<string, number>();
function shouldDisableBackgroundExtractionForTests(): boolean {
if (runtime.forceInTests) {
@@ -82,6 +85,7 @@ export function resetCommitmentExtractionRuntimeForTests(): void {
timer = null;
draining = false;
queueOverflowWarned = false;
terminalFailureCooldownUntilByAgent = new Map();
}
function buildItemId(params: CommitmentExtractionEnqueueInput, nowMs: number): string {
@@ -95,14 +99,19 @@ function isUsefulText(value: string | undefined): boolean {
export function enqueueCommitmentExtraction(input: CommitmentExtractionEnqueueInput): boolean {
const resolved = resolveCommitmentsConfig(input.cfg);
const nowMs = input.nowMs ?? Date.now();
const agentId = normalizeOptionalString(input.agentId) ?? "";
const sessionKey = normalizeOptionalString(input.sessionKey) ?? "";
const channel = normalizeOptionalString(input.channel) ?? "";
if (
!resolved.enabled ||
shouldDisableBackgroundExtractionForTests() ||
(agentId ? nowMs < (terminalFailureCooldownUntilByAgent.get(agentId) ?? 0) : false) ||
!isUsefulText(input.userText) ||
!isUsefulText(input.assistantText) ||
!input.agentId.trim() ||
!input.sessionKey.trim() ||
!input.channel.trim()
!agentId ||
!sessionKey ||
!channel
) {
return false;
}
@@ -116,14 +125,13 @@ export function enqueueCommitmentExtraction(input: CommitmentExtractionEnqueueIn
}
return false;
}
const nowMs = input.nowMs ?? Date.now();
queue.push({
itemId: buildItemId(input, nowMs),
nowMs,
timezone: resolveCommitmentTimezone(input.cfg),
agentId: input.agentId.trim(),
sessionKey: input.sessionKey.trim(),
channel: input.channel.trim(),
agentId,
sessionKey,
channel,
...(input.accountId?.trim() ? { accountId: input.accountId.trim() } : {}),
...(input.to?.trim() ? { to: input.to.trim() } : {}),
...(input.threadId?.trim() ? { threadId: input.threadId.trim() } : {}),
@@ -145,6 +153,33 @@ export function enqueueCommitmentExtraction(input: CommitmentExtractionEnqueueIn
return true;
}
function isTerminalExtractionError(error: unknown): boolean {
const message = error instanceof Error ? error.message : String(error);
return (
/\bNo API key found\b/i.test(message) ||
/\bUnknown model\b/i.test(message) ||
/\bAuth profile credentials are missing or expired\b/i.test(message) ||
/\bOAuth token refresh failed\b/i.test(message) ||
/\bmissing credential\b/i.test(message) ||
/\bmissing credentials\b/i.test(message) ||
/\bmissing_api_key\b/i.test(message) ||
/\binvalid_grant\b/i.test(message)
);
}
function openTerminalFailureCooldown(agentId: string, error: unknown): void {
terminalFailureCooldownUntilByAgent.set(
agentId,
Date.now() + TERMINAL_EXTRACTION_FAILURE_COOLDOWN_MS,
);
queue = queue.filter((item) => item.agentId !== agentId);
log.warn("commitment extraction disabled temporarily after terminal model/auth failure", {
agentId,
cooldownMs: TERMINAL_EXTRACTION_FAILURE_COOLDOWN_MS,
error: String(error),
});
}
function resolveExtractionSessionFile(agentId: string, runId: string): string {
return path.join(
resolveStateDir(),
@@ -176,6 +211,7 @@ async function defaultExtractBatch(params: {
}
const resolved = resolveCommitmentsConfig(cfg);
const runId = `commitments-${randomUUID()}`;
const modelRef = resolveDefaultModelForAgent({ cfg, agentId: first.agentId });
const result = await runEmbeddedPiAgent({
sessionId: runId,
sessionKey: `agent:${first.agentId}:commitments:${runId}`,
@@ -184,6 +220,8 @@ async function defaultExtractBatch(params: {
sessionFile: resolveExtractionSessionFile(first.agentId, runId),
workspaceDir: resolveAgentWorkspaceDir(cfg, first.agentId),
config: cfg,
provider: modelRef.provider,
model: modelRef.model,
prompt: buildCommitmentExtractionPrompt({ cfg, items: params.items }),
disableTools: true,
thinkLevel: "off",
@@ -225,7 +263,15 @@ export async function drainCommitmentExtractionQueue(): Promise<number> {
const batch = queue.splice(0, resolved.extraction.batchMaxItems);
const items = await hydrateBatch(batch);
const extractor = runtime.extractBatch ?? defaultExtractBatch;
const result = await extractor({ cfg: firstCfg, items });
let result: CommitmentExtractionBatchResult;
try {
result = await extractor({ cfg: firstCfg, items });
} catch (error) {
if (isTerminalExtractionError(error)) {
openTerminalFailureCooldown(items[0]?.agentId ?? "", error);
}
throw error;
}
await persistCommitmentExtractionResult({
cfg: firstCfg,
items,