adaptive-memory-multi-model-router 2.15.2 → 2.15.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT_COUNCIL_FINDINGS.md +123 -98
- package/ARCHITECTURE.md +1 -1
- package/CAMPAIGN_SUMMARY.md +87 -0
- package/ENTERPRISE_INTEGRATIONS.md +94 -0
- package/INTEGRATION_PROGRESS.md +96 -0
- package/OPPORTUNITIES_100.md +277 -0
- package/POPULARITY_BOOSTERS.md +1 -1
- package/README.md +143 -173
- package/README_ja.md +16 -20
- package/README_zh.md +13 -17
- package/TODO_BROWSER_AUTOMATION.md +87 -0
- package/a3m-integrations-summary.md +73 -0
- package/articles/CHINESE_DIRECTORIES.md +2 -2
- package/articles/CHINESE_SUBMISSIONS_READY.md +5 -5
- package/articles/HN_POST_READY.md +16 -20
- package/articles/HN_SHOW_SEO.md +49 -0
- package/articles/INDIEHACKERS_READY.md +19 -34
- package/articles/PRODUCTHUNT_READY.md +47 -67
- package/articles/REDDIT_SEO_2026.md +50 -0
- package/demo/IMPROVED_PROMPTS.md +3 -3
- package/demo/VEO3_PROMPTS.md +2 -2
- package/demo/VIDEO_PRODUCTION_GUIDE.md +3 -3
- package/dist/analytics/costAnalytics.d.ts +0 -1
- package/dist/benchmark/reproducible.d.ts +0 -1
- package/dist/cache/semanticCache.d.ts +0 -1
- package/dist/cost/budgetEnforcer.d.ts +0 -1
- package/dist/cost/costTracker.d.ts +0 -1
- package/dist/ensemble/multiRoundDialog.d.ts +0 -1
- package/dist/ensemble/shapleyValue.d.ts +0 -1
- package/dist/ensemble.d.ts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/integrations/oauth.d.ts +0 -1
- package/dist/integrations/scienceAdapter.d.ts +0 -1
- package/dist/memory/autoFetch.d.ts +0 -1
- package/dist/memory/hybridMemory.d.ts +0 -1
- package/dist/memory/memoryTree.d.ts +0 -1
- package/dist/memory/obsidianVault.d.ts +0 -1
- package/dist/memory/reasoningBank.d.ts +0 -1
- package/dist/observability/changeWatch.d.ts +0 -1
- package/dist/observability/fatigueDetector.d.ts +0 -1
- package/dist/observability/index.d.ts +0 -1
- package/dist/observability/metrics.d.ts +0 -1
- package/dist/observability/middleware.d.ts +0 -1
- package/dist/observability/tracer.d.ts +0 -1
- package/dist/observability/types.d.ts +0 -1
- package/dist/providers/providerConfig.d.ts +0 -1
- package/dist/routing/advancedRouter.d.ts +1 -2
- package/dist/routing/advancedRouter.js +84 -0
- package/dist/routing/advancedRouter.js.map +1 -1
- package/dist/routing/crossModelValidation.d.ts +0 -1
- package/dist/routing/providerHealth.d.ts +77 -2
- package/dist/routing/providerHealth.js +190 -4
- package/dist/routing/providerHealth.js.map +1 -1
- package/dist/routing/providerRetry.d.ts +0 -1
- package/dist/routing/shadowSampler.d.ts +187 -0
- package/dist/routing/shadowSampler.js +306 -0
- package/dist/routing/shadowSampler.js.map +1 -0
- package/dist/sdk.d.ts +0 -1
- package/dist/security/guardrails.d.ts +0 -1
- package/dist/server/dashboard.d.ts +0 -1
- package/dist/server/handlers/chatHandler.d.ts +0 -1
- package/dist/server/handlers/completionsHandler.d.ts +0 -1
- package/dist/server/handlers/embeddingsHandler.d.ts +0 -1
- package/dist/server/handlers/healthHandler.d.ts +0 -1
- package/dist/server/handlers/metricsHandler.d.ts +0 -1
- package/dist/server/handlers/modelsHandler.d.ts +0 -1
- package/dist/server/metrics.d.ts +0 -1
- package/dist/server/modelMapper.d.ts +0 -1
- package/dist/server/proxyServer.d.ts +0 -1
- package/dist/server/router.d.ts +0 -1
- package/dist/server/state.d.ts +0 -1
- package/dist/tui/dashboard.d.ts +0 -1
- package/dist/utils/costUtils.d.ts +0 -1
- package/dist/utils/sorting.d.ts +0 -1
- package/dist/utils/tokenUtils.d.ts +0 -1
- package/docs/comparison-litellm.md +10 -18
- package/docs/index.html +29 -34
- package/docs/llms-full.txt +6 -6
- package/docs/llms.txt +21 -23
- package/docs-site/index.html +19 -19
- package/integrations/browser-use/README.md +155 -0
- package/integrations/browser-use/examples/form-filling.ts +179 -0
- package/integrations/browser-use/index.ts +145 -0
- package/integrations/browser-use/package.json +34 -0
- package/integrations/browser-use/tsconfig.json +17 -0
- package/integrations/mcp-browser/README.md +177 -0
- package/integrations/mcp-browser/package.json +38 -0
- package/integrations/mcp-browser/src/index.ts +297 -0
- package/integrations/sota-browser/README.md +191 -0
- package/integrations/sota-browser/package.json +34 -0
- package/llms-full.txt +6 -6
- package/llms.txt +22 -33
- package/mcp-server/package.json +0 -6
- package/mcp-server/tsconfig.json +2 -3
- package/package.json +3 -3
- package/sitemap.xml +15 -9
- package/src/routing/advancedRouter.ts +101 -0
- package/src/routing/providerHealth.ts +234 -4
- package/src/routing/shadowSampler.ts +427 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -94
- package/.github/ISSUE_TEMPLATE/checklist.md +0 -35
- package/.github/ISSUE_TEMPLATE/config.yml +0 -17
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -71
- package/.well-known/ai-plugin.json +0 -16
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShadowSampler — Value-Proportional Shadow Verification
|
|
3
|
+
*
|
|
4
|
+
* Implements Optimal Defense Theory (Rhoades 1979; McKey 1974; Zangerl & Bazzaz 1992)
|
|
5
|
+
* from plant ecological economics:
|
|
6
|
+
*
|
|
7
|
+
* "A plant allocates defensive compounds (defense cost) in proportion to
|
|
8
|
+
* tissue value, attack probability, and the marginal cost of defense."
|
|
9
|
+
*
|
|
10
|
+
* Mapped to A3M routing:
|
|
11
|
+
* - Tissue value → query stakes / expected cost of wrong answer
|
|
12
|
+
* - Attack probability → probability that the primary provider fails/misbehaves
|
|
13
|
+
* - Defense cost → cost of the shadow verification call
|
|
14
|
+
* - Marginal return → expected reduction in error rate from verification
|
|
15
|
+
*
|
|
16
|
+
* Instead of binary always-on (wasteful) or always-off (risky), ODT says:
|
|
17
|
+
* Sample shadow verification PROPORTIONALLY to expected value of verification.
|
|
18
|
+
*
|
|
19
|
+
* The sampling probability is:
|
|
20
|
+
* P(shadow) = f(expected_error_cost_reduction, query_risk)
|
|
21
|
+
*
|
|
22
|
+
* This is NOT the Zahavi "shared expert" (which always runs on 100% of queries).
|
|
23
|
+
* ODT shadow runs probabilistically — only on queries where the expected
|
|
24
|
+
* verification benefit exceeds the verification cost.
|
|
25
|
+
*
|
|
26
|
+
* Usage:
|
|
27
|
+
* const sampler = new ShadowSampler();
|
|
28
|
+
* const decision = await sampler.routeWithShadow(query, { strategy: 'auto' });
|
|
29
|
+
* // decision.hasShadow === true iff we sampled a shadow provider
|
|
30
|
+
* // decision.primaryResult and decision.shadowResult are compared automatically
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { routeQuery, extractQueryFeatures, RouteDecision } from './advancedRouter';
|
|
34
|
+
import { getAvailableProviders, ProviderDefinition } from '../providers/providerConfig';
|
|
35
|
+
import { estimateCost } from '../utils/tokenUtils';
|
|
36
|
+
|
|
37
|
+
export interface ShadowSamplerConfig {
|
|
38
|
+
/**
|
|
39
|
+
* Minimum query stakes (estimated cost of wrong answer) to consider shadow.
|
|
40
|
+
* Below this, P(shadow) = 0 regardless of other factors.
|
|
41
|
+
* In dollars — what a wrong answer costs you.
|
|
42
|
+
* Default: $0.01 (1 cent — roughly equivalent to a simple API call cost)
|
|
43
|
+
*/
|
|
44
|
+
minQueryStake?: number;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Maximum probability of shadow verification.
|
|
48
|
+
* Set to 1.0 for always-on (Zahavi-style, expensive).
|
|
49
|
+
* Default: 0.15 (15% — ODT-proportional sampling)
|
|
50
|
+
*/
|
|
51
|
+
maxShadowProbability?: number;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Risk threshold (from QueryFeatures.risk_profile) above which
|
|
55
|
+
* shadow probability scales up linearly.
|
|
56
|
+
* 0.0 = no risk scaling (all queries get same P(shadow))
|
|
57
|
+
* 0.5 = medium risk scales P(shadow) by 50%
|
|
58
|
+
* 1.0 = high risk scales P(shadow) by 100%
|
|
59
|
+
*/
|
|
60
|
+
riskScalingFactor?: number;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Cost scaling factor — if query is expensive (large output),
|
|
64
|
+
* shadow is more worthwhile (higher P(shadow)).
|
|
65
|
+
* 0.0 = no cost scaling.
|
|
66
|
+
* Default: 0.3 (expensive queries get moderate boost in P(shadow))
|
|
67
|
+
*/
|
|
68
|
+
costScalingFactor?: number;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Provider to use as shadow (cheapest reliable provider).
|
|
72
|
+
* If not specified, auto-selected from available providers.
|
|
73
|
+
*/
|
|
74
|
+
shadowProvider?: string;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Always use this shadow provider (overrides auto-selection).
|
|
78
|
+
*/
|
|
79
|
+
forceShadowProvider?: boolean;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* If true, use complexity as a signal for verification value.
|
|
83
|
+
* Complex queries (code, math, reasoning) benefit more from verification.
|
|
84
|
+
* Default: true
|
|
85
|
+
*/
|
|
86
|
+
useComplexitySignal?: boolean;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Debug mode: always shadow regardless of sampling.
|
|
90
|
+
* Default: false
|
|
91
|
+
*/
|
|
92
|
+
forceShadow?: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface ShadowDecision extends RouteDecision {
|
|
96
|
+
/** Whether a shadow provider was sampled for this query */
|
|
97
|
+
hasShadow: boolean;
|
|
98
|
+
/** The sampled shadow provider (null if no shadow) */
|
|
99
|
+
shadowProvider: string | null;
|
|
100
|
+
/** Shadow sampling probability that was used */
|
|
101
|
+
shadowProbability: number;
|
|
102
|
+
/** The query features that triggered shadow decision */
|
|
103
|
+
features: ReturnType<typeof extractQueryFeatures>;
|
|
104
|
+
/** ODT rationale for the shadow decision */
|
|
105
|
+
shadowReasoning: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface ShadowedResponse {
|
|
109
|
+
primary: string;
|
|
110
|
+
shadow: string | null;
|
|
111
|
+
winner: 'primary' | 'shadow' | 'tie' | 'no-shadow';
|
|
112
|
+
confidenceDelta: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Optimal Defense Theory Shadow Sampler
|
|
117
|
+
*
|
|
118
|
+
* Allocates shadow verification proportionally to query value and risk.
|
|
119
|
+
*
|
|
120
|
+
* The ODT sampling probability:
|
|
121
|
+
* P_shadow = min(maxP, baseP + risk_adj + cost_adj + complexity_adj)
|
|
122
|
+
*
|
|
123
|
+
* where:
|
|
124
|
+
* baseP = baseline verification rate (ODT " constitutive defense")
|
|
125
|
+
* risk_adj = risk_profile scaling (ODT "induced defense")
|
|
126
|
+
* cost_adj = output cost scaling (value of correct answer)
|
|
127
|
+
* complexity_adj = query complexity signal
|
|
128
|
+
*/
|
|
129
|
+
export class ShadowSampler {
|
|
130
|
+
private config: Required<Omit<ShadowSamplerConfig, 'forceShadowProvider'>> & { forceShadowProvider: boolean };
|
|
131
|
+
|
|
132
|
+
// Cached shadow provider (auto-selected on first call)
|
|
133
|
+
private _shadowProvider: string | null = null;
|
|
134
|
+
|
|
135
|
+
// Counters for sampling statistics
|
|
136
|
+
private _shadowCount = 0;
|
|
137
|
+
private _totalCount = 0;
|
|
138
|
+
|
|
139
|
+
constructor(config: ShadowSamplerConfig = {}) {
|
|
140
|
+
this.config = {
|
|
141
|
+
minQueryStake: config.minQueryStake ?? 0.01,
|
|
142
|
+
maxShadowProbability: config.maxShadowProbability ?? 0.15,
|
|
143
|
+
riskScalingFactor: config.riskScalingFactor ?? 0.5,
|
|
144
|
+
costScalingFactor: config.costScalingFactor ?? 0.3,
|
|
145
|
+
shadowProvider: config.shadowProvider ?? null,
|
|
146
|
+
forceShadowProvider: config.forceShadowProvider ?? false,
|
|
147
|
+
useComplexitySignal: config.useComplexitySignal ?? true,
|
|
148
|
+
forceShadow: config.forceShadow ?? false,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Auto-select the cheapest available provider as the shadow.
|
|
154
|
+
* Excludes the primary provider to ensure diversity.
|
|
155
|
+
*/
|
|
156
|
+
private selectShadowProvider(primaryProvider: string): string {
|
|
157
|
+
if (this.config.forceShadowProvider && this.config.shadowProvider) {
|
|
158
|
+
return this.config.shadowProvider;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (this._shadowProvider) return this._shadowProvider;
|
|
162
|
+
|
|
163
|
+
const profiles = getAvailableProviders();
|
|
164
|
+
const candidates = Object.entries(profiles)
|
|
165
|
+
.filter(([name, p]: [string, ProviderDefinition]) => {
|
|
166
|
+
// Exclude primary
|
|
167
|
+
if (name === primaryProvider) return false;
|
|
168
|
+
// Must be available (has API key) — cost must be finite
|
|
169
|
+
const cost = (p.costPerK.input + p.costPerK.output) / 2;
|
|
170
|
+
return cost < Infinity;
|
|
171
|
+
})
|
|
172
|
+
.sort((a, b) => {
|
|
173
|
+
const costA = (a[1].costPerK.input + a[1].costPerK.output) / 2;
|
|
174
|
+
const costB = (b[1].costPerK.input + b[1].costPerK.output) / 2;
|
|
175
|
+
return costA - costB;
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
if (candidates.length === 0) {
|
|
179
|
+
// Fallback: pick any non-primary
|
|
180
|
+
const fallback = Object.keys(profiles).find(n => n !== primaryProvider);
|
|
181
|
+
this._shadowProvider = fallback || primaryProvider;
|
|
182
|
+
} else {
|
|
183
|
+
this._shadowProvider = candidates[0][0];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return this._shadowProvider;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Estimate the query "stakes" — the expected cost of a wrong answer.
|
|
191
|
+
*
|
|
192
|
+
* ODT maps this to "tissue value": how much is this asset worth protecting?
|
|
193
|
+
* In routing terms: if this query fails, how much does it cost?
|
|
194
|
+
*
|
|
195
|
+
* We approximate this as:
|
|
196
|
+
* stake ≈ estimated_output_tokens × cost_per_token × criticality_multiplier
|
|
197
|
+
*
|
|
198
|
+
* where criticality is derived from query complexity and risk_profile.
|
|
199
|
+
*/
|
|
200
|
+
private estimateQueryStake(features: ReturnType<typeof extractQueryFeatures>): number {
|
|
201
|
+
const estimatedTokens = features.length * 1.5; // rough estimate
|
|
202
|
+
const avgCostPerToken = 0.0001; // roughly $0.10/1K tokens
|
|
203
|
+
const stake = estimatedTokens * avgCostPerToken;
|
|
204
|
+
|
|
205
|
+
// Criticality multiplier from risk profile
|
|
206
|
+
const riskMultiplier: Record<string, number> = {
|
|
207
|
+
high: 10.0, // Wrong answer could cause real harm — worth verifying
|
|
208
|
+
medium: 2.0, // Some cost to wrong answer
|
|
209
|
+
low: 0.5, // Low stakes — skip verification
|
|
210
|
+
};
|
|
211
|
+
const riskMult = riskMultiplier[features.risk_profile || 'medium'] ?? 1.0;
|
|
212
|
+
|
|
213
|
+
// Complexity multiplier — complex queries are harder to verify but more valuable
|
|
214
|
+
// We use complexity as a proxy for "correctness is harder to judge"
|
|
215
|
+
const complexityMultiplier = 1 + (features.complexity || 0) * 2;
|
|
216
|
+
|
|
217
|
+
return stake * riskMult * complexityMultiplier;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Compute the ODT sampling probability for this query.
|
|
222
|
+
*
|
|
223
|
+
* ODT principle: defense (shadow) allocation ∝ expected benefit of defense.
|
|
224
|
+
* Expected benefit = P(failure) × cost_of_failure
|
|
225
|
+
*
|
|
226
|
+
* So P(shadow) scales with:
|
|
227
|
+
* 1. Query stake (expected cost of wrong answer)
|
|
228
|
+
* 2. Risk profile (probability of primary failure)
|
|
229
|
+
* 3. Output cost (verification ROI — cheaper outputs need less verification)
|
|
230
|
+
* 4. Complexity (complex queries benefit more from verification)
|
|
231
|
+
*/
|
|
232
|
+
private computeShadowProbability(
|
|
233
|
+
features: ReturnType<typeof extractQueryFeatures>,
|
|
234
|
+
stake: number
|
|
235
|
+
): number {
|
|
236
|
+
// Base probability (ODT "constitutive defense" — baseline verification rate)
|
|
237
|
+
let p = 0.02; // 2% baseline
|
|
238
|
+
|
|
239
|
+
// === STAKES ADJUSTMENT (ODT "tissue value") ===
|
|
240
|
+
// Higher stake → proportionally higher verification probability
|
|
241
|
+
// Scale from 2% to maxP as stake goes from minStake to $1.00
|
|
242
|
+
if (stake >= this.config.minQueryStake) {
|
|
243
|
+
const stakeAdj = Math.min(
|
|
244
|
+
(stake - this.config.minQueryStake) / (1.0 - this.config.minQueryStake),
|
|
245
|
+
1.0
|
|
246
|
+
) * (this.config.maxShadowProbability - 0.02);
|
|
247
|
+
p += stakeAdj;
|
|
248
|
+
} else {
|
|
249
|
+
// Below minimum stake — no verification regardless
|
|
250
|
+
return 0;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// === RISK ADJUSTMENT (ODT "attack probability") ===
|
|
254
|
+
// High-risk queries are more likely to have primary failures
|
|
255
|
+
// Scale risk_adj by riskScalingFactor (0.5 = 50% boost for high-risk)
|
|
256
|
+
const riskAdj: Record<string, number> = {
|
|
257
|
+
high: 0.10 * this.config.riskScalingFactor,
|
|
258
|
+
medium: 0.03 * this.config.riskScalingFactor,
|
|
259
|
+
low: 0.0,
|
|
260
|
+
};
|
|
261
|
+
p += riskAdj[features.risk_profile || 'medium'] ?? 0;
|
|
262
|
+
|
|
263
|
+
// === COMPLEXITY ADJUSTMENT (ODT "defense efficacy") ===
|
|
264
|
+
// Complex queries benefit more from verification (more errors to catch)
|
|
265
|
+
// But also harder to verify (requires domain knowledge to judge)
|
|
266
|
+
if (this.config.useComplexitySignal) {
|
|
267
|
+
if (features.has_code) {
|
|
268
|
+
p += 0.05; // Code verification has high ROI (bugs are costly)
|
|
269
|
+
}
|
|
270
|
+
if (features.requires_reasoning) {
|
|
271
|
+
p += 0.03; // Reasoning errors are subtle but costly
|
|
272
|
+
}
|
|
273
|
+
if (features.has_math) {
|
|
274
|
+
p += 0.04; // Math has objective ground truth — easy to verify (note: field is has_math)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// === COST SCALING (ODT "marginal defense cost") ===
|
|
279
|
+
// Expensive outputs are harder to produce — verify to avoid waste
|
|
280
|
+
if (this.config.costScalingFactor > 0 && features.length > 500) {
|
|
281
|
+
const costAdj = Math.min(
|
|
282
|
+
(features.length - 500) / 10000, // scale up for very long outputs
|
|
283
|
+
0.05
|
|
284
|
+
) * this.config.costScalingFactor;
|
|
285
|
+
p += costAdj;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Clamp to [0, maxShadowProbability]
|
|
289
|
+
return Math.min(Math.max(p, 0), this.config.maxShadowProbability);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Route a query, with optional ODT-proportional shadow verification.
|
|
294
|
+
*
|
|
295
|
+
* @param prompt - The user query
|
|
296
|
+
* @param options - Routing options (same as routeQuery)
|
|
297
|
+
* @returns ShadowDecision with shadow metadata
|
|
298
|
+
*/
|
|
299
|
+
routeWithShadow(
|
|
300
|
+
prompt: string,
|
|
301
|
+
options?: { available_models?: string[]; budget_multiplier?: number }
|
|
302
|
+
): ShadowDecision {
|
|
303
|
+
const features = extractQueryFeatures(prompt);
|
|
304
|
+
const primaryDecision = routeQuery(prompt, options?.available_models, options?.budget_multiplier);
|
|
305
|
+
|
|
306
|
+
// Compute query stakes and shadow probability
|
|
307
|
+
const stake = this.estimateQueryStake(features);
|
|
308
|
+
const shadowProb = this.computeShadowProbability(features, stake);
|
|
309
|
+
|
|
310
|
+
// ODT sampling decision
|
|
311
|
+
const shouldShadow = this.config.forceShadow || Math.random() < shadowProb;
|
|
312
|
+
const shadowProvider = shouldShadow
|
|
313
|
+
? this.selectShadowProvider(primaryDecision.primary_model || '')
|
|
314
|
+
: null;
|
|
315
|
+
|
|
316
|
+
if (shouldShadow) {
|
|
317
|
+
this._shadowCount++;
|
|
318
|
+
}
|
|
319
|
+
this._totalCount++;
|
|
320
|
+
|
|
321
|
+
const reasoning = shouldShadow
|
|
322
|
+
? `ODT shadow: stake=${stake.toFixed(4)}, risk=${features.risk_profile}, P=${shadowProb.toFixed(3)}, complexity=${features.complexity.toFixed(2)}`
|
|
323
|
+
: `ODT no-shadow: stake=${stake.toFixed(4)} below threshold`;
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
...primaryDecision,
|
|
327
|
+
hasShadow: shouldShadow,
|
|
328
|
+
shadowProvider,
|
|
329
|
+
shadowProbability: shadowProb,
|
|
330
|
+
features,
|
|
331
|
+
shadowReasoning: reasoning,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Compare primary and shadow outputs.
|
|
337
|
+
* Returns the "better" answer and confidence delta.
|
|
338
|
+
*
|
|
339
|
+
* For production use: this would call both providers in parallel
|
|
340
|
+
* and compare outputs. For now, returns a stub that signals
|
|
341
|
+
* the caller should handle comparison.
|
|
342
|
+
*/
|
|
343
|
+
async compareOutputs(
|
|
344
|
+
primaryAnswer: string,
|
|
345
|
+
shadowAnswer: string | null
|
|
346
|
+
): Promise<ShadowedResponse> {
|
|
347
|
+
if (!shadowAnswer) {
|
|
348
|
+
return {
|
|
349
|
+
primary: primaryAnswer,
|
|
350
|
+
shadow: null,
|
|
351
|
+
winner: 'no-shadow',
|
|
352
|
+
confidenceDelta: 0,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Simple comparison: length + character overlap as proxy for agreement
|
|
357
|
+
// In production, this would use a proper semantic similarity check
|
|
358
|
+
const primaryLen = primaryAnswer.length;
|
|
359
|
+
const shadowLen = shadowAnswer.length;
|
|
360
|
+
const lengthRatio = Math.min(primaryLen, shadowLen) / Math.max(primaryLen, shadowLen);
|
|
361
|
+
|
|
362
|
+
// Count common bigrams as a simple similarity proxy
|
|
363
|
+
const primaryBigrams = new Set<string>();
|
|
364
|
+
const shadowBigrams = new Set<string>();
|
|
365
|
+
for (let i = 0; i < primaryAnswer.length - 1; i++) {
|
|
366
|
+
primaryBigrams.add(primaryAnswer.slice(i, i + 2));
|
|
367
|
+
}
|
|
368
|
+
for (let i = 0; i < shadowAnswer.length - 1; i++) {
|
|
369
|
+
shadowBigrams.add(shadowAnswer.slice(i, i + 2));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
let intersection = 0;
|
|
373
|
+
for (const bg of primaryBigrams) {
|
|
374
|
+
if (shadowBigrams.has(bg)) intersection++;
|
|
375
|
+
}
|
|
376
|
+
const union = primaryBigrams.size + shadowBigrams.size - intersection;
|
|
377
|
+
const jaccard = union > 0 ? intersection / union : 0;
|
|
378
|
+
|
|
379
|
+
// High agreement (jaccard > 0.8) → trust primary
|
|
380
|
+
// Low agreement → flag for review or prefer primary
|
|
381
|
+
const winner: ShadowedResponse['winner'] =
|
|
382
|
+
jaccard > 0.8 ? 'primary'
|
|
383
|
+
: jaccard > 0.5 ? 'tie'
|
|
384
|
+
: 'shadow'; // Low agreement: shadow might have caught something
|
|
385
|
+
|
|
386
|
+
const confidenceDelta = jaccard > 0.8 ? 0 : -0.1; // Reduce confidence if they differ
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
primary: primaryAnswer,
|
|
390
|
+
shadow: shadowAnswer,
|
|
391
|
+
winner,
|
|
392
|
+
confidenceDelta,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Get sampling statistics for monitoring.
|
|
398
|
+
*/
|
|
399
|
+
getStats(): { shadowCount: number; totalCount: number; shadowRate: number } {
|
|
400
|
+
return {
|
|
401
|
+
shadowCount: this._shadowCount,
|
|
402
|
+
totalCount: this._totalCount,
|
|
403
|
+
shadowRate: this._totalCount > 0 ? this._shadowCount / this._totalCount : 0,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Reset statistics counters.
|
|
409
|
+
*/
|
|
410
|
+
resetStats(): void {
|
|
411
|
+
this._shadowCount = 0;
|
|
412
|
+
this._totalCount = 0;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Update configuration at runtime.
|
|
417
|
+
*/
|
|
418
|
+
configure(config: Partial<ShadowSamplerConfig>): void {
|
|
419
|
+
this.config = { ...this.config, ...config };
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// ============================================================
|
|
424
|
+
// NOTE: Named exports are at declaration level above.
|
|
425
|
+
// ShadowSampler, ShadowSamplerConfig, ShadowDecision, ShadowedResponse
|
|
426
|
+
// are all exported via 'export interface' / 'export class'
|
|
427
|
+
// ============================================================
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: Bug Report
|
|
3
|
-
about: Report a bug or unexpected behavior to help us improve A3M Router
|
|
4
|
-
title: "[Bug] "
|
|
5
|
-
labels: bug, needs-triage
|
|
6
|
-
assignees: ""
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Description
|
|
10
|
-
|
|
11
|
-
A clear and concise description of the bug.
|
|
12
|
-
|
|
13
|
-
## Reproduction Steps
|
|
14
|
-
|
|
15
|
-
Steps to reproduce the behavior:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
# 1. Set up (if applicable)
|
|
19
|
-
export PROVIDER_API_KEY=sk-...
|
|
20
|
-
|
|
21
|
-
# 2. Run
|
|
22
|
-
npx a3m-router route "Your query here"
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
If using the SDK, provide a minimal code snippet:
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
import { A3MRouter } from "adaptive-memory-multi-model-router";
|
|
29
|
-
|
|
30
|
-
const router = new A3MRouter({ /* your config */ });
|
|
31
|
-
const result = await router.route("Your query");
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Expected Behavior
|
|
35
|
-
|
|
36
|
-
What did you expect to happen?
|
|
37
|
-
|
|
38
|
-
## Actual Behavior
|
|
39
|
-
|
|
40
|
-
What actually happened? Include error messages, stack traces, or unexpected output.
|
|
41
|
-
|
|
42
|
-
```
|
|
43
|
-
Paste error output or logs here
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## A3M Router Version
|
|
47
|
-
|
|
48
|
-
- Package: `adaptive-memory-multi-model-router@<version>` (run `npm list adaptive-memory-multi-model-router`)
|
|
49
|
-
- CLI version (if applicable): `npx a3m-router --version`
|
|
50
|
-
|
|
51
|
-
## Environment
|
|
52
|
-
|
|
53
|
-
- **OS:** macOS / Linux / Windows
|
|
54
|
-
- **Node.js version:** (run `node --version`)
|
|
55
|
-
- **npm version:** (run `npm --version`)
|
|
56
|
-
- **Python version (if using Python SDK):** (run `python --version`)
|
|
57
|
-
|
|
58
|
-
## Providers Used
|
|
59
|
-
|
|
60
|
-
Which provider(s) were involved? (e.g., Groq, OpenAI, Anthropic, NVIDIA, DeepSeek, custom)
|
|
61
|
-
|
|
62
|
-
## Configuration
|
|
63
|
-
|
|
64
|
-
Attach or describe relevant config (redact API keys):
|
|
65
|
-
|
|
66
|
-
```json
|
|
67
|
-
{
|
|
68
|
-
"providers": { ... },
|
|
69
|
-
"routing": { ... },
|
|
70
|
-
"budgets": { ... }
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## Logs
|
|
75
|
-
|
|
76
|
-
If you ran with `DEBUG=*` or `LOG_LEVEL=debug`, include relevant log lines:
|
|
77
|
-
|
|
78
|
-
```
|
|
79
|
-
[DEBUG] Routing query...
|
|
80
|
-
[ERROR] Provider groq returned 503
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
## Additional Context
|
|
84
|
-
|
|
85
|
-
- Does this happen consistently or intermittently?
|
|
86
|
-
- Does it affect all providers or a specific one?
|
|
87
|
-
- Did it work in a previous version? If so, which version?
|
|
88
|
-
- Any recent changes to your setup?
|
|
89
|
-
|
|
90
|
-
## Checklist
|
|
91
|
-
|
|
92
|
-
- [ ] I have searched existing issues for duplicates
|
|
93
|
-
- [ ] I have redacted all API keys and secrets from the above
|
|
94
|
-
- [ ] I can reliably reproduce this bug
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
GitHub Pages Checker Checklist
|
|
2
|
-
|
|
3
|
-
## Phase 1: Content Ready
|
|
4
|
-
- [x] GitHub Pages workflow configured
|
|
5
|
-
- [x] README.md updated with TL;DR and latest stats
|
|
6
|
-
- [x] popular-boosters.md ready with quick wins
|
|
7
|
-
- [ ] Anim workshops for sponsors
|
|
8
|
-
- [ ] PI-assistant memory setup
|
|
9
|
-
- [ ] Orion assistant memory setup
|
|
10
|
-
|
|
11
|
-
## Phase 2: Deploy: GitHub Pages
|
|
12
|
-
- [x] /docs/index.html exists (activated and deployed)
|
|
13
|
-
- [ ] Primary landing page URL
|
|
14
|
-
- [ ] Right domain (das-rebel.github.io/a3m-router)
|
|
15
|
-
- [ ] CNAME file if needed
|
|
16
|
-
- [ ] Deployed? -> https://das-rebel.github.io/a3m-router/docs/index.html
|
|
17
|
-
|
|
18
|
-
## Phase 3: Traffic Greasing
|
|
19
|
-
- [x] Twitter thread
|
|
20
|
-
- [x] Reddit post
|
|
21
|
-
- [x] HackerNews
|
|
22
|
-
- [x] Lint checklist files
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
You need to set up a nitro django project (or similar) for tracking usage.
|
|
27
|
-
|
|
28
|
-
**Key status:**
|
|
29
|
-
- GitHub Pages is working (deployed from /docs)
|
|
30
|
-
- Latest commit was July 7, 2026
|
|
31
|
-
- Repo has 10 stars, 1 fork
|
|
32
|
-
- If you need advanced ORM/batch processing, maybe try something else (like Data Stewards)
|
|
33
|
-
- If you need standard server-side script support, this should work.
|
|
34
|
-
|
|
35
|
-
Want me to: 1) Create deployment instructions, or 2) Set up script environment?
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
blank_issues_enabled: false
|
|
2
|
-
contact_links:
|
|
3
|
-
- name: "\U0001F4D6 Documentation & Quick Start"
|
|
4
|
-
url: https://github.com/Das-rebel/a3m-router#readme
|
|
5
|
-
about: Check the README for setup, SDK usage, API reference, and configuration examples
|
|
6
|
-
- name: "\U0001F4AC Discussions & Community"
|
|
7
|
-
url: https://github.com/Das-rebel/a3m-router/discussions
|
|
8
|
-
about: Ask questions, share ideas, show what you've built, and get help from the community
|
|
9
|
-
- name: "\U0001F680 NPM Package"
|
|
10
|
-
url: https://www.npmjs.com/package/adaptive-memory-multi-model-router
|
|
11
|
-
about: View package details, version history, and download statistics
|
|
12
|
-
- name: "\U0001F4DC Changelog"
|
|
13
|
-
url: https://github.com/Das-rebel/a3m-router/blob/main/CHANGELOG.md
|
|
14
|
-
about: See what's changed in recent releases
|
|
15
|
-
- name: "\U0001F6A8 Security"
|
|
16
|
-
url: https://github.com/Das-rebel/a3m-router/security/policy
|
|
17
|
-
about: Report security vulnerabilities privately
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: Feature Request
|
|
3
|
-
about: Suggest an idea or enhancement for A3M Router
|
|
4
|
-
title: "[Feature] "
|
|
5
|
-
labels: enhancement
|
|
6
|
-
assignees: ""
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Problem Statement
|
|
10
|
-
|
|
11
|
-
A clear description of the problem you're trying to solve. What gap or pain point does this feature address?
|
|
12
|
-
|
|
13
|
-
**Example:** "Currently, A3M Router doesn't support routing based on response language. When I send multilingual queries, I want them routed to providers that perform best in that language."
|
|
14
|
-
|
|
15
|
-
## Proposed Solution
|
|
16
|
-
|
|
17
|
-
Describe the feature you'd like to see. Be as specific as possible about behavior, configuration, and API.
|
|
18
|
-
|
|
19
|
-
```typescript
|
|
20
|
-
// If applicable, sketch the API you envision
|
|
21
|
-
const router = new A3MRouter({
|
|
22
|
-
languageRouting: {
|
|
23
|
-
enabled: true,
|
|
24
|
-
defaultProvider: "openai",
|
|
25
|
-
languageOverrides: {
|
|
26
|
-
ja: "anthropic",
|
|
27
|
-
zh: "deepseek",
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Use Case
|
|
34
|
-
|
|
35
|
-
Describe the real-world scenario that would benefit from this feature.
|
|
36
|
-
|
|
37
|
-
- Who is the target user? (e.g., solo developer, enterprise team, researcher)
|
|
38
|
-
- What workflow does it enable or simplify?
|
|
39
|
-
- How frequently would this be used?
|
|
40
|
-
|
|
41
|
-
## Alternatives Considered
|
|
42
|
-
|
|
43
|
-
List any workarounds or alternative approaches you've explored:
|
|
44
|
-
|
|
45
|
-
1. Manual provider selection per query
|
|
46
|
-
2. Custom wrapper script
|
|
47
|
-
3. Forking and modifying the router
|
|
48
|
-
4. Using a different tool altogether
|
|
49
|
-
|
|
50
|
-
Explain why these are insufficient.
|
|
51
|
-
|
|
52
|
-
## Priority
|
|
53
|
-
|
|
54
|
-
How important is this to you?
|
|
55
|
-
|
|
56
|
-
- [ ] **Blocking** — Cannot proceed without this feature
|
|
57
|
-
- [ ] **High** — Important for my workflow
|
|
58
|
-
- [ ] **Medium** — Nice to have
|
|
59
|
-
- [ ] **Low** — Interesting idea, not urgent
|
|
60
|
-
|
|
61
|
-
## Would You Implement It?
|
|
62
|
-
|
|
63
|
-
- [ ] **Yes** — I can submit a PR (with guidance)
|
|
64
|
-
- [ ] **Maybe** — Willing to help test or provide requirements
|
|
65
|
-
- [ ] **No** — Just suggesting
|
|
66
|
-
|
|
67
|
-
## Additional Context
|
|
68
|
-
|
|
69
|
-
- Links to related discussions, issues, or external references
|
|
70
|
-
- Screenshots or mockups (if applicable)
|
|
71
|
-
- Any constraints or requirements (e.g., must work offline, must not increase bundle size)
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schema_version": "v1",
|
|
3
|
-
"name_for_human": "A3M Router",
|
|
4
|
-
"name_for_model": "a3m_router",
|
|
5
|
-
"description_for_human": "LLM routing proxy — #1 on RouterArena (0.9404 / 96.77%) at $0.0768/1K. Rule-based, no ML, 47+ providers.",
|
|
6
|
-
"description_for_model": "A3M Router is an LLM routing service. Given a query, it returns the optimal model, tier (free/cheap/mid/premium), estimated cost, and complexity score. Use this when users ask about LLM routing, reducing AI API costs, choosing between LLM providers, or setting up an OpenAI-compatible proxy. Available as TypeScript SDK, Python SDK, CLI, and REST API. RouterArena rank #1 with 0.9404 / 96.77% at $0.0768 per 1K queries (arXiv:2510.00202).",
|
|
7
|
-
"api": {
|
|
8
|
-
"type": "openapi",
|
|
9
|
-
"url": "https://das-rebel.github.io/a3m-router/docs/openapi.json"
|
|
10
|
-
},
|
|
11
|
-
"auth": {
|
|
12
|
-
"type": "none"
|
|
13
|
-
},
|
|
14
|
-
"contact_email": "Sdas22@gmail.com",
|
|
15
|
-
"legal_info_url": "https://github.com/Das-rebel/a3m-router/blob/main/LICENSE"
|
|
16
|
-
}
|