mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-04 00:10:21 +00:00
refactor(providers): move defaults and error policy into plugins
This commit is contained in:
@@ -59,6 +59,12 @@ describe("classifyProviderSpecificError", () => {
|
||||
expect(classifyProviderSpecificError("concurrency limit reached")).toBe("rate_limit");
|
||||
});
|
||||
|
||||
it("classifies Cloudflare Workers AI quota errors as rate_limit", () => {
|
||||
expect(classifyProviderSpecificError("workers_ai gateway error: quota limit exceeded")).toBe(
|
||||
"rate_limit",
|
||||
);
|
||||
});
|
||||
|
||||
it("does not match generic 'model is not ready' without Bedrock prefix", () => {
|
||||
expect(classifyProviderSpecificError("model is not ready")).toBeNull();
|
||||
});
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
/**
|
||||
* Provider-specific error patterns that improve failover classification accuracy.
|
||||
* Provider-owned error-pattern dispatch plus legacy fallback patterns.
|
||||
*
|
||||
* Many providers return errors in non-standard formats. Without these patterns,
|
||||
* errors get misclassified (e.g., a context overflow classified as "format"),
|
||||
* causing the failover engine to choose wrong recovery strategies.
|
||||
* Most provider-specific failover classification now lives on provider-plugin
|
||||
* hooks. This module keeps only fallback patterns for providers that do not
|
||||
* yet ship a dedicated provider plugin hook surface.
|
||||
*/
|
||||
|
||||
import {
|
||||
classifyProviderFailoverReasonWithPlugin,
|
||||
matchesProviderContextOverflowWithPlugin,
|
||||
} from "../../plugins/provider-runtime.js";
|
||||
import type { FailoverReason } from "./types.js";
|
||||
|
||||
type ProviderErrorPattern = {
|
||||
@@ -21,30 +25,9 @@ type ProviderErrorPattern = {
|
||||
* to catch provider-specific wording that the generic regex misses.
|
||||
*/
|
||||
export const PROVIDER_CONTEXT_OVERFLOW_PATTERNS: readonly RegExp[] = [
|
||||
// AWS Bedrock
|
||||
/ValidationException.*(?:input is too long|max input token|input token.*exceed)/i,
|
||||
/ValidationException.*(?:exceeds? the (?:maximum|max) (?:number of )?(?:input )?tokens)/i,
|
||||
/ModelStreamErrorException.*(?:Input is too long|too many input tokens)/i,
|
||||
|
||||
// Azure OpenAI (sometimes wraps OpenAI errors differently)
|
||||
/content_filter.*(?:prompt|input).*(?:too long|exceed)/i,
|
||||
|
||||
// Ollama / local models
|
||||
/\bollama\b.*(?:context length|too many tokens|context window)/i,
|
||||
/\btruncating input\b.*\btoo long\b/i,
|
||||
|
||||
// Mistral
|
||||
/\bmistral\b.*(?:input.*too long|token limit.*exceeded)/i,
|
||||
|
||||
// Cohere
|
||||
// Cohere does not currently ship a bundled provider hook.
|
||||
/\btotal tokens?.*exceeds? (?:the )?(?:model(?:'s)? )?(?:max|maximum|limit)/i,
|
||||
|
||||
// DeepSeek
|
||||
/\bdeepseek\b.*(?:input.*too long|context.*exceed)/i,
|
||||
|
||||
// Google Vertex / Gemini: INVALID_ARGUMENT with token-related messages is context overflow.
|
||||
/INVALID_ARGUMENT.*(?:exceeds? the (?:maximum|max)|input.*too (?:long|large))/i,
|
||||
|
||||
// Generic "input too long" pattern that isn't covered by existing checks
|
||||
/\binput (?:is )?too long for (?:the )?model\b/i,
|
||||
];
|
||||
@@ -55,38 +38,11 @@ export const PROVIDER_CONTEXT_OVERFLOW_PATTERNS: readonly RegExp[] = [
|
||||
* produce wrong results for specific providers.
|
||||
*/
|
||||
export const PROVIDER_SPECIFIC_PATTERNS: readonly ProviderErrorPattern[] = [
|
||||
// AWS Bedrock: ThrottlingException is rate limit
|
||||
{
|
||||
test: /ThrottlingException|Too many concurrent requests/i,
|
||||
reason: "rate_limit",
|
||||
},
|
||||
|
||||
// AWS Bedrock: ModelNotReadyException (require class prefix to avoid false positives)
|
||||
{
|
||||
test: /ModelNotReadyException/i,
|
||||
reason: "overloaded",
|
||||
},
|
||||
|
||||
// Azure: content_policy_violation should not trigger failover
|
||||
// (it's a content moderation rejection, not a transient error)
|
||||
|
||||
// Groq: model_deactivated is permanent
|
||||
// Groq does not currently ship a bundled provider hook.
|
||||
{
|
||||
test: /model(?:_is)?_deactivated|model has been deactivated/i,
|
||||
reason: "model_not_found",
|
||||
},
|
||||
|
||||
// Together AI / Fireworks: specific rate limit messages
|
||||
{
|
||||
test: /\bconcurrency limit\b.*\breached\b/i,
|
||||
reason: "rate_limit",
|
||||
},
|
||||
|
||||
// Cloudflare Workers AI
|
||||
{
|
||||
test: /\bworkers?_ai\b.*\b(?:rate|limit|quota)\b/i,
|
||||
reason: "rate_limit",
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -94,7 +50,11 @@ export const PROVIDER_SPECIFIC_PATTERNS: readonly ProviderErrorPattern[] = [
|
||||
* Called from `isContextOverflowError()` to catch provider-specific wording.
|
||||
*/
|
||||
export function matchesProviderContextOverflow(errorMessage: string): boolean {
|
||||
return PROVIDER_CONTEXT_OVERFLOW_PATTERNS.some((pattern) => pattern.test(errorMessage));
|
||||
return (
|
||||
matchesProviderContextOverflowWithPlugin({
|
||||
context: { errorMessage },
|
||||
}) || PROVIDER_CONTEXT_OVERFLOW_PATTERNS.some((pattern) => pattern.test(errorMessage))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,6 +62,12 @@ export function matchesProviderContextOverflow(errorMessage: string): boolean {
|
||||
* Returns null if no provider-specific pattern matches (fall through to generic classification).
|
||||
*/
|
||||
export function classifyProviderSpecificError(errorMessage: string): FailoverReason | null {
|
||||
const pluginReason = classifyProviderFailoverReasonWithPlugin({
|
||||
context: { errorMessage },
|
||||
});
|
||||
if (pluginReason) {
|
||||
return pluginReason;
|
||||
}
|
||||
for (const pattern of PROVIDER_SPECIFIC_PATTERNS) {
|
||||
if (pattern.test.test(errorMessage)) {
|
||||
return pattern.reason;
|
||||
|
||||
Reference in New Issue
Block a user