Spaces:
Runtime error
Runtime error
File size: 3,087 Bytes
cd8bd0a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | /**
* A2A Routing Decision Logger
*
* Records every routing decision to the `routing_decisions` SQLite table.
* Used by `omniroute_explain_route` (T03) and future learning router.
* Retention: 7 days default.
*/
import { randomUUID } from "crypto";
export interface RoutingFactor {
name: string; // "quota", "health", "cost", "latency", "task_fit"
value: number;
weight: number;
contribution: number;
}
export interface FallbackEntry {
provider: string;
reason: string; // "circuit_breaker_open", "quota_exceeded", "timeout"
}
export interface RoutingDecision {
requestId: string;
taskType: string;
comboId: string;
providerSelected: string;
modelUsed: string;
score: number;
factors: RoutingFactor[];
fallbacksTriggered: FallbackEntry[];
success: boolean;
latencyMs: number;
cost: number;
timestamp: string;
}
// In-memory log (production would use SQLite via routing_decisions table)
const decisions: RoutingDecision[] = [];
const MAX_DECISIONS = 1000;
const RETENTION_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
/**
* Log a routing decision.
*/
export function logRoutingDecision(
params: Omit<RoutingDecision, "requestId" | "timestamp">
): RoutingDecision {
const decision: RoutingDecision = {
...params,
requestId: randomUUID(),
timestamp: new Date().toISOString(),
};
decisions.push(decision);
// Cleanup: cap + TTL
if (decisions.length > MAX_DECISIONS) {
const cutoff = new Date(Date.now() - RETENTION_MS);
const validIdx = decisions.findIndex((d) => new Date(d.timestamp) > cutoff);
if (validIdx > 0) decisions.splice(0, validIdx);
else if (decisions.length > MAX_DECISIONS)
decisions.splice(0, decisions.length - MAX_DECISIONS);
}
return decision;
}
/**
* Get a specific routing decision by request ID.
*/
export function getRoutingDecision(requestId: string): RoutingDecision | undefined {
return decisions.find((d) => d.requestId === requestId);
}
/**
* Get recent routing decisions.
*/
export function getRecentDecisions(limit: number = 20): RoutingDecision[] {
return decisions.slice(-limit).reverse();
}
/**
* Get routing decision stats.
*/
export function getDecisionStats(): {
total: number;
successRate: number;
avgLatencyMs: number;
topProviders: Array<{ provider: string; count: number }>;
} {
if (decisions.length === 0) {
return { total: 0, successRate: 1, avgLatencyMs: 0, topProviders: [] };
}
const successful = decisions.filter((d) => d.success);
const providerCounts = new Map<string, number>();
let totalLatency = 0;
for (const d of decisions) {
totalLatency += d.latencyMs;
providerCounts.set(d.providerSelected, (providerCounts.get(d.providerSelected) || 0) + 1);
}
const topProviders = [...providerCounts.entries()]
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
.map(([provider, count]) => ({ provider, count }));
return {
total: decisions.length,
successRate: successful.length / decisions.length,
avgLatencyMs: Math.round(totalLatency / decisions.length),
topProviders,
};
}
|