mirror of https://github.com/openclaw/openclaw.git
94 lines
3.4 KiB
TypeScript
94 lines
3.4 KiB
TypeScript
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
import { sanitizeForLog } from "../terminal/ansi.js";
|
|
import type { FallbackAttempt, ModelCandidate } from "./model-fallback.types.js";
|
|
import { buildTextObservationFields } from "./pi-embedded-error-observation.js";
|
|
import type { FailoverReason } from "./pi-embedded-helpers.js";
|
|
|
|
const decisionLog = createSubsystemLogger("model-fallback").child("decision");
|
|
|
|
function buildErrorObservationFields(error?: string): {
|
|
errorPreview?: string;
|
|
errorHash?: string;
|
|
errorFingerprint?: string;
|
|
httpCode?: string;
|
|
providerErrorType?: string;
|
|
providerErrorMessagePreview?: string;
|
|
requestIdHash?: string;
|
|
} {
|
|
const observed = buildTextObservationFields(error);
|
|
return {
|
|
errorPreview: observed.textPreview,
|
|
errorHash: observed.textHash,
|
|
errorFingerprint: observed.textFingerprint,
|
|
httpCode: observed.httpCode,
|
|
providerErrorType: observed.providerErrorType,
|
|
providerErrorMessagePreview: observed.providerErrorMessagePreview,
|
|
requestIdHash: observed.requestIdHash,
|
|
};
|
|
}
|
|
|
|
export function logModelFallbackDecision(params: {
|
|
decision:
|
|
| "skip_candidate"
|
|
| "probe_cooldown_candidate"
|
|
| "candidate_failed"
|
|
| "candidate_succeeded";
|
|
runId?: string;
|
|
requestedProvider: string;
|
|
requestedModel: string;
|
|
candidate: ModelCandidate;
|
|
attempt?: number;
|
|
total?: number;
|
|
reason?: FailoverReason | null;
|
|
status?: number;
|
|
code?: string;
|
|
error?: string;
|
|
nextCandidate?: ModelCandidate;
|
|
isPrimary?: boolean;
|
|
requestedModelMatched?: boolean;
|
|
fallbackConfigured?: boolean;
|
|
allowTransientCooldownProbe?: boolean;
|
|
profileCount?: number;
|
|
previousAttempts?: FallbackAttempt[];
|
|
}): void {
|
|
const nextText = params.nextCandidate
|
|
? `${sanitizeForLog(params.nextCandidate.provider)}/${sanitizeForLog(params.nextCandidate.model)}`
|
|
: "none";
|
|
const reasonText = params.reason ?? "unknown";
|
|
const observedError = buildErrorObservationFields(params.error);
|
|
decisionLog.warn("model fallback decision", {
|
|
event: "model_fallback_decision",
|
|
tags: ["error_handling", "model_fallback", params.decision],
|
|
runId: params.runId,
|
|
decision: params.decision,
|
|
requestedProvider: params.requestedProvider,
|
|
requestedModel: params.requestedModel,
|
|
candidateProvider: params.candidate.provider,
|
|
candidateModel: params.candidate.model,
|
|
attempt: params.attempt,
|
|
total: params.total,
|
|
reason: params.reason,
|
|
status: params.status,
|
|
code: params.code,
|
|
...observedError,
|
|
nextCandidateProvider: params.nextCandidate?.provider,
|
|
nextCandidateModel: params.nextCandidate?.model,
|
|
isPrimary: params.isPrimary,
|
|
requestedModelMatched: params.requestedModelMatched,
|
|
fallbackConfigured: params.fallbackConfigured,
|
|
allowTransientCooldownProbe: params.allowTransientCooldownProbe,
|
|
profileCount: params.profileCount,
|
|
previousAttempts: params.previousAttempts?.map((attempt) => ({
|
|
provider: attempt.provider,
|
|
model: attempt.model,
|
|
reason: attempt.reason,
|
|
status: attempt.status,
|
|
code: attempt.code,
|
|
...buildErrorObservationFields(attempt.error),
|
|
})),
|
|
consoleMessage:
|
|
`model fallback decision: decision=${params.decision} requested=${sanitizeForLog(params.requestedProvider)}/${sanitizeForLog(params.requestedModel)} ` +
|
|
`candidate=${sanitizeForLog(params.candidate.provider)}/${sanitizeForLog(params.candidate.model)} reason=${reasonText} next=${nextText}`,
|
|
});
|
|
}
|