mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 12:52:11 +03:00
Merge pull request #932 from oyi77/feat/lkgp-strategy-919
feat(routing): implement Last Known Good Provider (LKGP) strategy (#919)
This commit is contained in:
@@ -16,6 +16,7 @@ export interface RoutingContext {
|
||||
requestHasTools?: boolean;
|
||||
requestHasVision?: boolean;
|
||||
estimatedInputTokens?: number;
|
||||
lastKnownGoodProvider?: string;
|
||||
}
|
||||
|
||||
export interface RoutingDecision {
|
||||
@@ -116,6 +117,34 @@ class LatencyStrategyImpl implements RouterStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
// ── LKGPStrategy: tries last known good provider first ───────────────────────
|
||||
|
||||
class LKGPStrategyImpl implements RouterStrategy {
|
||||
readonly name = "lkgp";
|
||||
readonly description = "Tries last known good provider first, then falls back to rules";
|
||||
|
||||
select(pool: ProviderCandidate[], context: RoutingContext): RoutingDecision {
|
||||
if (context.lastKnownGoodProvider) {
|
||||
const best = pool.find(
|
||||
(c) => c.provider === context.lastKnownGoodProvider && c.circuitBreakerState !== "OPEN"
|
||||
);
|
||||
if (best) {
|
||||
return {
|
||||
provider: best.provider,
|
||||
model: best.model,
|
||||
strategy: this.name,
|
||||
reason: `LKGP: using last known good provider ${best.provider}`,
|
||||
candidatesConsidered: 1,
|
||||
finalScore: 1.0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to rules strategy
|
||||
return getStrategy("rules").select(pool, context);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Registry ──────────────────────────────────────────────────────────────────
|
||||
|
||||
const strategyRegistry = new Map<string, RouterStrategy>();
|
||||
@@ -123,12 +152,14 @@ const strategyRegistry = new Map<string, RouterStrategy>();
|
||||
const rulesStrategy = new RulesStrategyImpl();
|
||||
const costStrategy = new CostStrategyImpl();
|
||||
const latencyStrategy = new LatencyStrategyImpl();
|
||||
const lkgpStrategy = new LKGPStrategyImpl();
|
||||
|
||||
strategyRegistry.set("rules", rulesStrategy);
|
||||
strategyRegistry.set("cost", costStrategy);
|
||||
strategyRegistry.set("eco", costStrategy); // alias
|
||||
strategyRegistry.set("latency", latencyStrategy);
|
||||
strategyRegistry.set("fast", latencyStrategy); // alias
|
||||
strategyRegistry.set("lkgp", lkgpStrategy);
|
||||
|
||||
export function getStrategy(name: string): RouterStrategy {
|
||||
const strategy = strategyRegistry.get(name);
|
||||
|
||||
@@ -99,7 +99,10 @@ async function validateResponseQuality(
|
||||
if (json?.output || json?.result || json?.data || json?.response) return { valid: true };
|
||||
if (json?.error) {
|
||||
const err = json.error as Record<string, unknown>;
|
||||
return { valid: false, reason: `upstream error in 200 body: ${err?.message || JSON.stringify(json.error).substring(0, 200)}` };
|
||||
return {
|
||||
valid: false,
|
||||
reason: `upstream error in 200 body: ${err?.message || JSON.stringify(json.error).substring(0, 200)}`,
|
||||
};
|
||||
}
|
||||
return { valid: true };
|
||||
}
|
||||
@@ -809,6 +812,16 @@ export async function handleComboChat({
|
||||
const modePack =
|
||||
typeof autoConfigSource.modePack === "string" ? autoConfigSource.modePack : undefined;
|
||||
|
||||
// Retrieve last known good provider (LKGP) for this combo/model (#919)
|
||||
let lastKnownGoodProvider: string | undefined;
|
||||
try {
|
||||
const { getLKGP } = await import("../../src/lib/localDb");
|
||||
const lkgp = await getLKGP(combo.name, combo.id || combo.name);
|
||||
if (lkgp) lastKnownGoodProvider = lkgp;
|
||||
} catch {
|
||||
/* ignore db errors */
|
||||
}
|
||||
|
||||
const candidates = await buildAutoCandidates(eligibleModels, combo.name);
|
||||
if (candidates.length > 0) {
|
||||
let selectedProvider = null;
|
||||
@@ -819,7 +832,7 @@ export async function handleComboChat({
|
||||
try {
|
||||
const decision = selectWithStrategy(
|
||||
candidates,
|
||||
{ taskType, requestHasTools },
|
||||
{ taskType, requestHasTools, lastKnownGoodProvider },
|
||||
routingStrategy
|
||||
);
|
||||
selectedProvider = decision.provider;
|
||||
@@ -980,6 +993,14 @@ export async function handleComboChat({
|
||||
fallbackCount,
|
||||
strategy,
|
||||
});
|
||||
|
||||
// Record last known good provider (LKGP) for this combo/model (#919)
|
||||
if (provider) {
|
||||
import("../../src/lib/localDb")
|
||||
.then(({ setLKGP }) => setLKGP(combo.name, combo.id || combo.name, provider))
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -248,6 +248,31 @@ export async function resetAllPricing() {
|
||||
return {};
|
||||
}
|
||||
|
||||
// ──────────────── LKGP (Last Known Good Provider) ────────────────
|
||||
|
||||
export async function getLKGP(comboName: string, modelId: string): Promise<string | null> {
|
||||
const db = getDbInstance();
|
||||
const key = `${comboName}:${modelId}`;
|
||||
const row = db
|
||||
.prepare("SELECT value FROM key_value WHERE namespace = 'lkgp' AND key = ?")
|
||||
.get(key) as { value?: string } | undefined;
|
||||
if (!row?.value) return null;
|
||||
try {
|
||||
return JSON.parse(row.value);
|
||||
} catch {
|
||||
return row.value;
|
||||
}
|
||||
}
|
||||
|
||||
export async function setLKGP(comboName: string, modelId: string, providerId: string) {
|
||||
const db = getDbInstance();
|
||||
const key = `${comboName}:${modelId}`;
|
||||
db.prepare("INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES ('lkgp', ?, ?)").run(
|
||||
key,
|
||||
JSON.stringify(providerId)
|
||||
);
|
||||
}
|
||||
|
||||
// ──────────────── Proxy Config ────────────────
|
||||
|
||||
const DEFAULT_PROXY_CONFIG: ProxyConfig = { global: null, providers: {}, combos: {}, keys: {} };
|
||||
|
||||
@@ -98,6 +98,10 @@ export {
|
||||
updateSettings,
|
||||
isCloudEnabled,
|
||||
|
||||
// LKGP (Last Known Good Provider) (#919)
|
||||
getLKGP,
|
||||
setLKGP,
|
||||
|
||||
// Pricing
|
||||
getPricing,
|
||||
getPricingForModel,
|
||||
|
||||
Reference in New Issue
Block a user