fix: handle provider factory failover
This commit is contained in:
@@ -59,6 +59,8 @@ export function validateFirecrawlSearchRequest(providerName: string, request: No
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function validateFirecrawlFetchRequest(providerName: string, request: NormalizedFetchRequest) {
|
export function validateFirecrawlFetchRequest(providerName: string, request: NormalizedFetchRequest) {
|
||||||
|
// Keep this defensive check here even though runtime validation also rejects it,
|
||||||
|
// so direct provider callers still get the same provider-specific error.
|
||||||
if (request.highlights) {
|
if (request.highlights) {
|
||||||
throw createProviderValidationError(providerName, 'does not support generic fetch option "highlights".');
|
throw createProviderValidationError(providerName, 'does not support generic fetch option "highlights".');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -398,3 +398,67 @@ test("search starts with the explicitly requested provider and still follows its
|
|||||||
assert.equal(result.execution.requestedProviderName, "firecrawl-main");
|
assert.equal(result.execution.requestedProviderName, "firecrawl-main");
|
||||||
assert.equal(result.execution.actualProviderName, "exa-fallback");
|
assert.equal(result.execution.actualProviderName, "exa-fallback");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("search records provider factory failures and follows fallbacks", async () => {
|
||||||
|
const calls: string[] = [];
|
||||||
|
|
||||||
|
const runtime = createWebSearchRuntime({
|
||||||
|
loadConfig: async () => ({
|
||||||
|
path: "test.json",
|
||||||
|
defaultProviderName: "firecrawl-main",
|
||||||
|
defaultProvider: {
|
||||||
|
name: "firecrawl-main",
|
||||||
|
type: "firecrawl",
|
||||||
|
apiKey: "fc",
|
||||||
|
fallbackProviders: ["exa-fallback"],
|
||||||
|
},
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
name: "firecrawl-main",
|
||||||
|
type: "firecrawl",
|
||||||
|
apiKey: "fc",
|
||||||
|
fallbackProviders: ["exa-fallback"],
|
||||||
|
},
|
||||||
|
{ name: "exa-fallback", type: "exa", apiKey: "exa" },
|
||||||
|
],
|
||||||
|
providersByName: new Map([
|
||||||
|
[
|
||||||
|
"firecrawl-main",
|
||||||
|
{ name: "firecrawl-main", type: "firecrawl", apiKey: "fc", fallbackProviders: ["exa-fallback"] },
|
||||||
|
],
|
||||||
|
["exa-fallback", { name: "exa-fallback", type: "exa", apiKey: "exa" }],
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
createProvider(providerConfig) {
|
||||||
|
if (providerConfig.name === "firecrawl-main") {
|
||||||
|
throw new Error("factory boom:firecrawl-main");
|
||||||
|
}
|
||||||
|
|
||||||
|
return createProvider(providerConfig.name, providerConfig.type, {
|
||||||
|
search: async () => {
|
||||||
|
calls.push(providerConfig.name);
|
||||||
|
return {
|
||||||
|
providerName: providerConfig.name,
|
||||||
|
results: [{ title: "Exa hit", url: "https://exa.ai" }],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await runtime.search({ query: "pi docs", provider: "firecrawl-main" });
|
||||||
|
|
||||||
|
assert.deepEqual(calls, ["exa-fallback"]);
|
||||||
|
assert.deepEqual(result.execution.attempts, [
|
||||||
|
{
|
||||||
|
providerName: "firecrawl-main",
|
||||||
|
status: "failed",
|
||||||
|
reason: "factory boom:firecrawl-main",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
providerName: "exa-fallback",
|
||||||
|
status: "succeeded",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
assert.equal(result.execution.actualProviderName, "exa-fallback");
|
||||||
|
});
|
||||||
|
|||||||
@@ -118,7 +118,24 @@ export function createWebSearchRuntime(
|
|||||||
validateFetchRequestForProvider(providerName, providerConfig, request as NormalizedFetchRequest);
|
validateFetchRequestForProvider(providerName, providerConfig, request as NormalizedFetchRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
const provider = createProvider(providerConfig);
|
let provider: WebProvider;
|
||||||
|
try {
|
||||||
|
provider = createProvider(providerConfig);
|
||||||
|
} catch (error) {
|
||||||
|
attempts.push({
|
||||||
|
providerName,
|
||||||
|
status: "failed",
|
||||||
|
reason: (error as Error).message,
|
||||||
|
});
|
||||||
|
lastError = error;
|
||||||
|
|
||||||
|
for (const fallbackProviderName of providerConfig.fallbackProviders ?? []) {
|
||||||
|
if (!visited.has(fallbackProviderName)) {
|
||||||
|
pendingProviderNames.push(fallbackProviderName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await provider[operation]({
|
const response = await provider[operation]({
|
||||||
|
|||||||
Reference in New Issue
Block a user