lynkr 9.7.2 → 9.9.0
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/README.md +29 -19
- package/bin/cli.js +11 -0
- package/bin/lynkr-init.js +14 -1
- package/bin/lynkr-usage.js +78 -0
- package/bin/wrap.js +60 -35
- package/config/difficulty-anchors.json +22 -0
- package/package.json +24 -3
- package/scripts/audit-log-reader.js +399 -0
- package/scripts/calibrate-thresholds.js +38 -157
- package/scripts/compact-dictionary.js +204 -0
- package/scripts/test-deduplication.js +448 -0
- package/scripts/ws7-anchor-replay.js +108 -0
- package/skills/lynkr/SKILL.md +195 -0
- package/src/agents/context-manager.js +18 -2
- package/src/agents/definitions/loader.js +90 -0
- package/src/agents/executor.js +24 -2
- package/src/agents/index.js +9 -1
- package/src/agents/parallel-coordinator.js +2 -2
- package/src/agents/reflector.js +11 -1
- package/src/api/middleware/loop-guard.js +87 -0
- package/src/api/middleware/request-logging.js +5 -64
- package/src/api/middleware/session.js +0 -0
- package/src/api/openai-router.js +120 -101
- package/src/api/providers-handler.js +27 -2
- package/src/api/router.js +450 -125
- package/src/budget/index.js +2 -19
- package/src/cache/semantic.js +9 -0
- package/src/clients/databricks.js +459 -146
- package/src/clients/gpt-utils.js +11 -105
- package/src/clients/openai-format.js +10 -3
- package/src/clients/openrouter-utils.js +49 -24
- package/src/clients/prompt-cache-injection.js +1 -0
- package/src/clients/provider-capabilities.js +1 -1
- package/src/clients/responses-format.js +34 -3
- package/src/clients/routing.js +15 -0
- package/src/config/index.js +36 -2
- package/src/context/gcf.js +275 -0
- package/src/context/tool-result-compressor.js +51 -9
- package/src/dashboard/api.js +1 -0
- package/src/logger/index.js +14 -1
- package/src/memory/search.js +12 -40
- package/src/memory/tools.js +3 -24
- package/src/orchestrator/bypass.js +4 -2
- package/src/orchestrator/index.js +144 -88
- package/src/routing/affinity-store.js +194 -0
- package/src/routing/agentic-detector.js +36 -6
- package/src/routing/bandit.js +25 -6
- package/src/routing/calibration.js +212 -0
- package/src/routing/client-profiles.js +292 -0
- package/src/routing/complexity-analyzer.js +48 -11
- package/src/routing/deescalator.js +148 -0
- package/src/routing/degradation.js +109 -0
- package/src/routing/feedback.js +157 -0
- package/src/routing/index.js +897 -87
- package/src/routing/intent-score.js +339 -0
- package/src/routing/interaction.js +3 -0
- package/src/routing/knn-router.js +70 -21
- package/src/routing/model-registry.js +28 -7
- package/src/routing/model-tiers.js +25 -2
- package/src/routing/reward-pipeline.js +68 -2
- package/src/routing/risk-analyzer.js +30 -1
- package/src/routing/risk-classifier.js +6 -2
- package/src/routing/session-affinity.js +162 -34
- package/src/routing/telemetry.js +298 -10
- package/src/routing/verifier.js +267 -0
- package/src/server.js +66 -21
- package/src/sessions/cleanup.js +17 -0
- package/src/tools/index.js +1 -15
- package/src/tools/smart-selection.js +10 -0
- package/src/tools/web-client.js +3 -3
- package/.eslintrc.cjs +0 -12
- package/benchmark-configs/litellm_config.yaml +0 -86
- package/benchmark-configs/lynkr.env +0 -48
- package/benchmark-configs/portkey-config.json +0 -60
- package/benchmark-configs/portkey-docker.sh +0 -23
- package/benchmark-tier-routing.js +0 -449
- package/funding.json +0 -110
- package/src/api/middleware/validation.js +0 -261
- package/src/routing/drift-monitor.js +0 -113
- package/src/workers/helpers.js +0 -185
package/src/server.js
CHANGED
|
@@ -37,13 +37,10 @@ const lazyLoader = require("./tools/lazy-loader");
|
|
|
37
37
|
const { setLazyLoader } = require("./tools");
|
|
38
38
|
const { waitForOllama } = require("./clients/ollama-startup");
|
|
39
39
|
|
|
40
|
-
// Initialize MCP
|
|
41
40
|
initialiseMcp();
|
|
42
41
|
|
|
43
|
-
// Set up lazy tool loading
|
|
44
42
|
setLazyLoader(lazyLoader);
|
|
45
43
|
|
|
46
|
-
// Check if lazy loading is enabled (default: true)
|
|
47
44
|
const LAZY_TOOLS_ENABLED = process.env.LAZY_TOOLS_ENABLED !== "false";
|
|
48
45
|
|
|
49
46
|
if (LAZY_TOOLS_ENABLED) {
|
|
@@ -88,16 +85,13 @@ function createApp() {
|
|
|
88
85
|
app.get('/dashboard/api/routing', require('./dashboard/api').routing);
|
|
89
86
|
app.get('/dashboard/api/logs', require('./dashboard/api').logs);
|
|
90
87
|
|
|
91
|
-
// Initialize load shedder (log configuration)
|
|
92
88
|
initializeLoadShedder();
|
|
93
89
|
|
|
94
|
-
// Load shedding (protect against overload)
|
|
95
90
|
app.use(loadSheddingMiddleware);
|
|
96
91
|
|
|
97
92
|
// Request logging (add request IDs, structured logs)
|
|
98
93
|
app.use(requestLoggingMiddleware);
|
|
99
94
|
|
|
100
|
-
// Metrics collection
|
|
101
95
|
app.use(metricsMiddleware);
|
|
102
96
|
|
|
103
97
|
// Note: If using a tunnel (ngrok, Cloudflare Tunnel) and seeing BrotliDecompressionError,
|
|
@@ -107,9 +101,21 @@ function createApp() {
|
|
|
107
101
|
app.use(sessionMiddleware);
|
|
108
102
|
app.use(loggingMiddleware);
|
|
109
103
|
|
|
104
|
+
// Agent-framework endpoints: LangGraph/CrewAI/AutoGen talk to the
|
|
105
|
+
// OpenAI-compat surface, Claude Code to /v1/messages. Guards that protect
|
|
106
|
+
// against runaway agents must cover BOTH — mounting on /v1/messages only
|
|
107
|
+
// silently exempts every OpenAI-compat client (this bit us with client
|
|
108
|
+
// profiles already).
|
|
109
|
+
const AGENT_ENDPOINTS = ['/v1/messages', '/v1/chat/completions', '/v1/responses'];
|
|
110
|
+
|
|
111
|
+
// Loop guard — stateless runaway-agent circuit breaker
|
|
112
|
+
// (LYNKR_MAX_SESSION_TURNS / LYNKR_MAX_TOOL_TURNS; off unless set).
|
|
113
|
+
const { loopGuard } = require('./api/middleware/loop-guard');
|
|
114
|
+
for (const p of AGENT_ENDPOINTS) app.use(p, loopGuard);
|
|
115
|
+
|
|
110
116
|
// Budget and rate limiting (can be disabled via config)
|
|
111
117
|
if (config.budget?.enabled !== false) {
|
|
112
|
-
app.use(
|
|
118
|
+
for (const p of AGENT_ENDPOINTS) app.use(p, budgetMiddleware);
|
|
113
119
|
}
|
|
114
120
|
|
|
115
121
|
// Phase 6.1 — per-tenant routing policies (LYNKR-Tenant-Id header).
|
|
@@ -117,13 +123,11 @@ function createApp() {
|
|
|
117
123
|
app.use('/v1/messages', tenantMiddleware);
|
|
118
124
|
|
|
119
125
|
// Phase 6.2 — hierarchical budget enforcement (LYNKR_BUDGET_ENFORCER=false to disable).
|
|
120
|
-
app.use(
|
|
126
|
+
for (const p of AGENT_ENDPOINTS) app.use(p, budgetEnforcer);
|
|
121
127
|
|
|
122
|
-
// Health check endpoints
|
|
123
128
|
app.get("/health/live", livenessCheck);
|
|
124
129
|
app.get("/health/ready", readinessCheck);
|
|
125
130
|
|
|
126
|
-
// Metrics endpoints
|
|
127
131
|
app.get("/metrics", (req, res) => {
|
|
128
132
|
res.json(metrics.snapshot());
|
|
129
133
|
});
|
|
@@ -176,10 +180,8 @@ function createApp() {
|
|
|
176
180
|
|
|
177
181
|
app.use(router);
|
|
178
182
|
|
|
179
|
-
// Dashboard UI
|
|
180
183
|
app.use('/dashboard', require('./dashboard/router'));
|
|
181
184
|
|
|
182
|
-
// Files API
|
|
183
185
|
const filesRouter = require("./api/files-router");
|
|
184
186
|
app.use("/v1", filesRouter);
|
|
185
187
|
|
|
@@ -193,8 +195,7 @@ function createApp() {
|
|
|
193
195
|
}
|
|
194
196
|
|
|
195
197
|
async function start() {
|
|
196
|
-
//
|
|
197
|
-
// This pre-warms worker threads for CPU-intensive tasks
|
|
198
|
+
// Pre-warms worker threads for CPU-intensive tasks
|
|
198
199
|
if (config.workerPool?.enabled !== false) {
|
|
199
200
|
try {
|
|
200
201
|
const poolOptions = {
|
|
@@ -245,17 +246,65 @@ async function start() {
|
|
|
245
246
|
console.log(`Claude→Databricks proxy listening on http://localhost:${config.port}`);
|
|
246
247
|
});
|
|
247
248
|
|
|
248
|
-
// Start session cleanup manager
|
|
249
|
+
// Start session cleanup manager. It also drives routing-side maintenance
|
|
250
|
+
// (telemetry retention + session-pin TTL) via its runCleanup tick — see
|
|
251
|
+
// src/sessions/cleanup.js.
|
|
249
252
|
const { getSessionCleanupManager } = require("./sessions/cleanup");
|
|
250
253
|
const sessionCleanup = getSessionCleanupManager();
|
|
251
254
|
sessionCleanup.start();
|
|
252
255
|
|
|
253
|
-
//
|
|
256
|
+
// WS5.7 — auto-calibration scheduler.
|
|
257
|
+
//
|
|
258
|
+
// Recomputes `data/calibrated-thresholds.json` from live telemetry every
|
|
259
|
+
// 24 h and hot-reloads the model-tier selector so the update takes
|
|
260
|
+
// effect without a restart. First run is jittered 30–90 min into the
|
|
261
|
+
// process lifetime so a cluster of proxies won't all recalibrate at the
|
|
262
|
+
// same moment. When telemetry is sparse (<100 rows in-window), the
|
|
263
|
+
// calibration step no-ops itself via `runCalibration`'s
|
|
264
|
+
// `insufficient_samples` skip path. Unconditionally armed — the pre-B
|
|
265
|
+
// `LYNKR_AUTO_CALIBRATE` env flag was removed because the "off" state
|
|
266
|
+
// was never useful in prod (calibration is idempotent and self-gating).
|
|
267
|
+
{
|
|
268
|
+
const { runCalibration } = require('./routing/calibration');
|
|
269
|
+
const { reloadCalibratedThresholds } = require('./routing/model-tiers');
|
|
270
|
+
const degradation = require('./routing/degradation');
|
|
271
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
272
|
+
const FIRST_RUN_MIN_MS = 30 * 60 * 1000;
|
|
273
|
+
const FIRST_RUN_JITTER_MS = 60 * 60 * 1000;
|
|
274
|
+
const firstDelay = FIRST_RUN_MIN_MS + Math.floor(Math.random() * FIRST_RUN_JITTER_MS);
|
|
275
|
+
|
|
276
|
+
const runOnce = () => {
|
|
277
|
+
try {
|
|
278
|
+
const result = runCalibration({ days: 7 });
|
|
279
|
+
if (result.skipped) {
|
|
280
|
+
logger.info({ reason: result.reason, count: result.count }, '[Calibration] Skipped');
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const before = reloadCalibratedThresholds();
|
|
284
|
+
logger.info({
|
|
285
|
+
calibratedAt: result.calibratedAt,
|
|
286
|
+
sampleCount: result.sampleCount,
|
|
287
|
+
ranges: result.ranges,
|
|
288
|
+
previousRanges: before,
|
|
289
|
+
}, '[Calibration] Ranges refreshed');
|
|
290
|
+
} catch (err) {
|
|
291
|
+
degradation.record('calibration', err);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const firstTimer = setTimeout(() => {
|
|
296
|
+
runOnce();
|
|
297
|
+
const interval = setInterval(runOnce, DAY_MS);
|
|
298
|
+
interval.unref();
|
|
299
|
+
}, firstDelay);
|
|
300
|
+
firstTimer.unref();
|
|
301
|
+
logger.info({ firstDelayMs: firstDelay, intervalMs: DAY_MS }, '[Calibration] Scheduler armed');
|
|
302
|
+
}
|
|
303
|
+
|
|
254
304
|
const shutdownManager = getShutdownManager();
|
|
255
305
|
shutdownManager.registerServer(server);
|
|
256
306
|
shutdownManager.setupSignalHandlers();
|
|
257
307
|
|
|
258
|
-
// Register Headroom shutdown callback
|
|
259
308
|
if (config.headroom?.enabled) {
|
|
260
309
|
shutdownManager.onShutdown(async () => {
|
|
261
310
|
logger.info("Stopping Headroom sidecar on shutdown");
|
|
@@ -263,7 +312,6 @@ async function start() {
|
|
|
263
312
|
});
|
|
264
313
|
}
|
|
265
314
|
|
|
266
|
-
// Register Worker Pool shutdown callback
|
|
267
315
|
if (config.workerPool?.enabled !== false && isWorkerPoolReady()) {
|
|
268
316
|
shutdownManager.onShutdown(async () => {
|
|
269
317
|
logger.info("Stopping worker thread pool on shutdown");
|
|
@@ -272,7 +320,6 @@ async function start() {
|
|
|
272
320
|
});
|
|
273
321
|
}
|
|
274
322
|
|
|
275
|
-
// Register Codex process shutdown callback
|
|
276
323
|
shutdownManager.onShutdown(async () => {
|
|
277
324
|
try {
|
|
278
325
|
const { getCodexProcess } = require("./clients/codex-process");
|
|
@@ -283,7 +330,6 @@ async function start() {
|
|
|
283
330
|
} catch { /* ignore if codex never started */ }
|
|
284
331
|
});
|
|
285
332
|
|
|
286
|
-
// Initialize hot reload config watcher
|
|
287
333
|
if (config.hotReload?.enabled !== false) {
|
|
288
334
|
const watcher = initConfigWatcher({
|
|
289
335
|
paths: [".env"],
|
|
@@ -300,7 +346,6 @@ async function start() {
|
|
|
300
346
|
}
|
|
301
347
|
});
|
|
302
348
|
|
|
303
|
-
// Stop watcher on shutdown
|
|
304
349
|
shutdownManager.onShutdown(() => {
|
|
305
350
|
const w = getConfigWatcher();
|
|
306
351
|
if (w) w.stop();
|
package/src/sessions/cleanup.js
CHANGED
|
@@ -32,6 +32,23 @@ class SessionCleanupManager {
|
|
|
32
32
|
} catch (error) {
|
|
33
33
|
logger.error({ error }, "Session cleanup failed");
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
// Routing-side maintenance piggybacks on the same tick — both live in
|
|
37
|
+
// .lynkr/telemetry.db, both are unbounded without a scheduled prune,
|
|
38
|
+
// and neither had a caller before this hook. WS5 auto-calibration will
|
|
39
|
+
// hang off this same manager.
|
|
40
|
+
try {
|
|
41
|
+
const telemetry = require("../routing/telemetry");
|
|
42
|
+
const affinityStore = require("../routing/affinity-store");
|
|
43
|
+
const pinTtlMs = Number(process.env.LYNKR_STICKY_TTL_MS) || 6 * 60 * 60 * 1000;
|
|
44
|
+
const telemetryDeleted = telemetry.cleanup();
|
|
45
|
+
const pinsDeleted = affinityStore.cleanup(pinTtlMs);
|
|
46
|
+
if (telemetryDeleted || pinsDeleted) {
|
|
47
|
+
logger.debug({ telemetryDeleted, pinsDeleted }, "Routing telemetry/pin cleanup completed");
|
|
48
|
+
}
|
|
49
|
+
} catch (err) {
|
|
50
|
+
logger.debug({ err: err.message }, "Routing cleanup tick failed (non-fatal)");
|
|
51
|
+
}
|
|
35
52
|
}
|
|
36
53
|
|
|
37
54
|
stop() {
|
package/src/tools/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const logger = require("../logger");
|
|
2
2
|
const { truncateToolOutput } = require("./truncate");
|
|
3
|
-
const { isGPTProvider, formatToolResultForGPT } = require("../clients/gpt-utils");
|
|
4
3
|
|
|
5
4
|
const registry = new Map();
|
|
6
5
|
const registryLowercase = new Map();
|
|
@@ -258,19 +257,7 @@ async function executeToolCall(call, context = {}) {
|
|
|
258
257
|
);
|
|
259
258
|
const formatted = normalizeHandlerResult(result);
|
|
260
259
|
|
|
261
|
-
|
|
262
|
-
let truncatedContent = truncateToolOutput(normalisedCall.name, formatted.content);
|
|
263
|
-
|
|
264
|
-
// GPT-specific formatting temporarily disabled for testing
|
|
265
|
-
// const isGPT = context?.provider && isGPTProvider(context.provider);
|
|
266
|
-
// if (isGPT) {
|
|
267
|
-
// truncatedContent = formatToolResultForGPT(
|
|
268
|
-
// normalisedCall.name,
|
|
269
|
-
// truncatedContent,
|
|
270
|
-
// normalisedCall.arguments
|
|
271
|
-
// );
|
|
272
|
-
// }
|
|
273
|
-
const isGPT = false; // Disabled for testing
|
|
260
|
+
const truncatedContent = truncateToolOutput(normalisedCall.name, formatted.content);
|
|
274
261
|
|
|
275
262
|
return {
|
|
276
263
|
id: normalisedCall.id,
|
|
@@ -284,7 +271,6 @@ async function executeToolCall(call, context = {}) {
|
|
|
284
271
|
truncated: truncatedContent !== formatted.content,
|
|
285
272
|
originalLength: formatted.content?.length,
|
|
286
273
|
truncatedLength: truncatedContent?.length,
|
|
287
|
-
gptFormatted: isGPT,
|
|
288
274
|
},
|
|
289
275
|
};
|
|
290
276
|
} catch (err) {
|
|
@@ -122,6 +122,14 @@ function estimateToolTokens(tools) {
|
|
|
122
122
|
/**
|
|
123
123
|
* Apply conservative stripping to the tool list.
|
|
124
124
|
*/
|
|
125
|
+
function recordStrippingSavings(before, after) {
|
|
126
|
+
if (after >= before) return;
|
|
127
|
+
try {
|
|
128
|
+
const telemetry = require('../routing/telemetry');
|
|
129
|
+
telemetry.recordSavings('tool_stripping', (before - after) * 175);
|
|
130
|
+
} catch { /* telemetry is best-effort */ }
|
|
131
|
+
}
|
|
132
|
+
|
|
125
133
|
function selectToolsSmartly(tools, classification, options = {}) {
|
|
126
134
|
if (!Array.isArray(tools) || tools.length === 0) return tools;
|
|
127
135
|
|
|
@@ -130,6 +138,7 @@ function selectToolsSmartly(tools, classification, options = {}) {
|
|
|
130
138
|
|
|
131
139
|
// Greeting: strip everything
|
|
132
140
|
if (classification.type === 'conversational') {
|
|
141
|
+
recordStrippingSavings(tools.length, 0);
|
|
133
142
|
return [];
|
|
134
143
|
}
|
|
135
144
|
|
|
@@ -159,6 +168,7 @@ function selectToolsSmartly(tools, classification, options = {}) {
|
|
|
159
168
|
selected = selected.slice(0, 10);
|
|
160
169
|
}
|
|
161
170
|
|
|
171
|
+
recordStrippingSavings(tools.length, selected.length);
|
|
162
172
|
return selected;
|
|
163
173
|
}
|
|
164
174
|
|
package/src/tools/web-client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { Agent, setGlobalDispatcher } = require("undici");
|
|
1
|
+
const { Agent, fetch: undiciFetch, setGlobalDispatcher } = require("undici");
|
|
2
2
|
const logger = require("../logger");
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -42,10 +42,10 @@ function createWebAgent() {
|
|
|
42
42
|
const webAgent = createWebAgent();
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* Fetch with the optimized agent
|
|
45
|
+
* Fetch with the optimized agent (uses undici.fetch for dispatcher support)
|
|
46
46
|
*/
|
|
47
47
|
async function fetchWithAgent(url, options = {}) {
|
|
48
|
-
return
|
|
48
|
+
return undiciFetch(url, {
|
|
49
49
|
...options,
|
|
50
50
|
dispatcher: webAgent,
|
|
51
51
|
});
|
package/.eslintrc.cjs
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
# ─── LiteLLM Benchmark Config ─────────────────────────────────────────────────
|
|
2
|
-
# Multi-provider tier routing via LiteLLM Complexity Router.
|
|
3
|
-
#
|
|
4
|
-
# Start: litellm --config benchmark-configs/litellm_config.yaml --port 8082
|
|
5
|
-
#
|
|
6
|
-
# Required env vars:
|
|
7
|
-
# AZURE_OPENAI_API_KEY
|
|
8
|
-
# AZURE_OPENAI_ENDPOINT (https://YOUR-RESOURCE.openai.azure.com)
|
|
9
|
-
# MOONSHOT_API_KEY
|
|
10
|
-
# (Ollama needs no key — running locally on :11434)
|
|
11
|
-
#
|
|
12
|
-
# Tier mapping (matches Lynkr benchmark config):
|
|
13
|
-
# SIMPLE → ollama:minimax-m2.5:cloud
|
|
14
|
-
# MEDIUM → ollama:minimax-m2.5:cloud
|
|
15
|
-
# COMPLEX → moonshot:moonshot-v1-auto
|
|
16
|
-
# REASONING → azure-openai:gpt-5.2-chat
|
|
17
|
-
|
|
18
|
-
model_list:
|
|
19
|
-
|
|
20
|
-
# ── SIMPLE + MEDIUM → Ollama minimax-m2.5:cloud ───────────────────────────
|
|
21
|
-
# Note: the model tag is "minimax-m2.5:cloud" — the colon is part of the
|
|
22
|
-
# Ollama model name, NOT a provider separator here.
|
|
23
|
-
- model_name: smart-router
|
|
24
|
-
litellm_params:
|
|
25
|
-
model: "ollama/minimax-m2.5:cloud"
|
|
26
|
-
api_base: http://localhost:11434
|
|
27
|
-
- model_name: smart-router
|
|
28
|
-
litellm_params:
|
|
29
|
-
model: "ollama/minimax-m2.5:cloud"
|
|
30
|
-
api_base: http://localhost:11434
|
|
31
|
-
|
|
32
|
-
# ── COMPLEX → Moonshot moonshot-v1-auto (matches Lynkr TIER_COMPLEX) ────────
|
|
33
|
-
- model_name: smart-router
|
|
34
|
-
litellm_params:
|
|
35
|
-
model: openai/moonshot-v1-auto
|
|
36
|
-
api_base: https://api.moonshot.ai/v1
|
|
37
|
-
api_key: os.environ/MOONSHOT_API_KEY
|
|
38
|
-
|
|
39
|
-
# ── REASONING → Azure OpenAI gpt-5.2-chat ─────────────────────────────────
|
|
40
|
-
- model_name: smart-router
|
|
41
|
-
litellm_params:
|
|
42
|
-
model: azure/gpt-5.2-chat
|
|
43
|
-
api_base: os.environ/AZURE_OPENAI_ENDPOINT
|
|
44
|
-
api_key: os.environ/AZURE_OPENAI_API_KEY
|
|
45
|
-
api_version: "2024-12-01-preview"
|
|
46
|
-
|
|
47
|
-
# ── Direct aliases (for targeted calls outside the benchmark) ─────────────
|
|
48
|
-
- model_name: ollama-minimax
|
|
49
|
-
litellm_params:
|
|
50
|
-
model: "ollama/minimax-m2.5:cloud"
|
|
51
|
-
api_base: http://localhost:11434
|
|
52
|
-
|
|
53
|
-
- model_name: moonshot-kimi-k2
|
|
54
|
-
litellm_params:
|
|
55
|
-
model: openai/moonshot-v1-auto
|
|
56
|
-
api_base: https://api.moonshot.ai/v1
|
|
57
|
-
api_key: os.environ/MOONSHOT_API_KEY
|
|
58
|
-
|
|
59
|
-
- model_name: azure-gpt5
|
|
60
|
-
litellm_params:
|
|
61
|
-
model: azure/gpt-5.2-chat
|
|
62
|
-
api_base: os.environ/AZURE_OPENAI_ENDPOINT
|
|
63
|
-
api_key: os.environ/AZURE_OPENAI_API_KEY
|
|
64
|
-
api_version: "2024-12-01-preview"
|
|
65
|
-
|
|
66
|
-
router_settings:
|
|
67
|
-
routing_strategy: cost-based-routing
|
|
68
|
-
# Fallback: if smart-router fails on one deployment, try the next
|
|
69
|
-
fallbacks:
|
|
70
|
-
- smart-router:
|
|
71
|
-
- ollama-minimax
|
|
72
|
-
- moonshot-kimi-k2
|
|
73
|
-
- azure-gpt5
|
|
74
|
-
num_retries: 2
|
|
75
|
-
timeout: 90
|
|
76
|
-
|
|
77
|
-
litellm_settings:
|
|
78
|
-
drop_params: true
|
|
79
|
-
use_responses_api: false
|
|
80
|
-
return_response_headers: true
|
|
81
|
-
success_callback: []
|
|
82
|
-
failure_callback: []
|
|
83
|
-
|
|
84
|
-
general_settings:
|
|
85
|
-
master_key: sk-1234 # change this
|
|
86
|
-
port: 8082
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# ─── Lynkr Benchmark Config ───────────────────────────────────────────────────
|
|
2
|
-
# Multi-provider tier routing: Ollama → Moonshot → Azure OpenAI
|
|
3
|
-
# Copy to .env and fill in your credentials.
|
|
4
|
-
|
|
5
|
-
PORT=8081
|
|
6
|
-
|
|
7
|
-
# ── Ollama (local, free) ───────────────────────────────────────────────────────
|
|
8
|
-
OLLAMA_ENDPOINT=http://localhost:11434
|
|
9
|
-
OLLAMA_MODEL=qwen2.5-coder:7b
|
|
10
|
-
OLLAMA_TIMEOUT_MS=120000
|
|
11
|
-
OLLAMA_EMBEDDINGS_MODEL=nomic-embed-text
|
|
12
|
-
OLLAMA_EMBEDDINGS_ENDPOINT=http://localhost:11434/api/embeddings
|
|
13
|
-
|
|
14
|
-
# ── Azure OpenAI ───────────────────────────────────────────────────────────────
|
|
15
|
-
AZURE_OPENAI_ENDPOINT=https://YOUR-RESOURCE.openai.azure.com
|
|
16
|
-
AZURE_OPENAI_API_KEY=your-azure-openai-key
|
|
17
|
-
AZURE_OPENAI_DEPLOYMENT=gpt-4o
|
|
18
|
-
AZURE_OPENAI_API_VERSION=2024-08-01-preview
|
|
19
|
-
|
|
20
|
-
# ── Moonshot (Kimi) ────────────────────────────────────────────────────────────
|
|
21
|
-
MOONSHOT_API_KEY=your-moonshot-api-key
|
|
22
|
-
MOONSHOT_ENDPOINT=https://api.moonshot.ai/v1/chat/completions
|
|
23
|
-
MOONSHOT_MODEL=kimi-k2-turbo-preview
|
|
24
|
-
|
|
25
|
-
# ── Primary provider (Lynkr uses this when no tier matches) ───────────────────
|
|
26
|
-
# Set to whichever you want as the default fallback
|
|
27
|
-
MODEL_PROVIDER=azure-openai
|
|
28
|
-
|
|
29
|
-
# ── Tier Routing ───────────────────────────────────────────────────────────────
|
|
30
|
-
# SIMPLE → Ollama local (free)
|
|
31
|
-
# MEDIUM → Moonshot Kimi (cheap, fast)
|
|
32
|
-
# COMPLEX → Azure OpenAI GPT-4o (powerful)
|
|
33
|
-
# REASONING→ Azure OpenAI o3-mini (best reasoning)
|
|
34
|
-
TIER_SIMPLE=ollama:minimax-m2.5:cloud
|
|
35
|
-
TIER_MEDIUM=ollama:minimax-m2.5:cloud
|
|
36
|
-
TIER_COMPLEX=moonshot:kimi-k2.6
|
|
37
|
-
TIER_REASONING=azure-openai:gpt-5.2-chat
|
|
38
|
-
|
|
39
|
-
# ── Token Optimisations (these are what LiteLLM/Portkey don't have) ────────────
|
|
40
|
-
SMART_TOOL_SELECTION=true
|
|
41
|
-
PROMPT_CACHE_ENABLED=true
|
|
42
|
-
SEMANTIC_CACHE_ENABLED=true
|
|
43
|
-
SEMANTIC_CACHE_THRESHOLD=0.95
|
|
44
|
-
HISTORY_COMPRESSION_ENABLED=true
|
|
45
|
-
TOOL_INJECTION_ENABLED=false
|
|
46
|
-
|
|
47
|
-
# ── Optional: make routing decisions visible in responses ──────────────────────
|
|
48
|
-
LYNKR_VISIBLE_ROUTING=true
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"_comment": "Portkey Gateway Config — multi-provider conditional routing",
|
|
3
|
-
"_note": "Portkey has NO automatic complexity detection. This config uses max_tokens as a proxy for complexity. For real tier routing pass x-portkey-metadata: { 'tier': 'simple|medium|complex|reasoning' } from your client.",
|
|
4
|
-
|
|
5
|
-
"strategy": {
|
|
6
|
-
"mode": "conditional"
|
|
7
|
-
},
|
|
8
|
-
|
|
9
|
-
"conditions": [
|
|
10
|
-
{
|
|
11
|
-
"_comment": "SIMPLE — short requests (max_tokens <= 256) → Ollama",
|
|
12
|
-
"condition": {
|
|
13
|
-
"query.max_tokens": { "$lte": 256 }
|
|
14
|
-
},
|
|
15
|
-
"target": {
|
|
16
|
-
"provider": "ollama",
|
|
17
|
-
"customHost": "http://localhost:11434",
|
|
18
|
-
"override_params": {
|
|
19
|
-
"model": "qwen2.5-coder:7b"
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"_comment": "MEDIUM — metadata tier=medium → Moonshot",
|
|
25
|
-
"condition": {
|
|
26
|
-
"metadata.tier": { "$eq": "medium" }
|
|
27
|
-
},
|
|
28
|
-
"target": {
|
|
29
|
-
"provider": "openai",
|
|
30
|
-
"apiKey": "{{MOONSHOT_API_KEY}}",
|
|
31
|
-
"baseURL": "https://api.moonshot.ai/v1",
|
|
32
|
-
"override_params": {
|
|
33
|
-
"model": "moonshot-v1-8k"
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"_comment": "REASONING — metadata tier=reasoning → Azure OpenAI o3-mini",
|
|
39
|
-
"condition": {
|
|
40
|
-
"metadata.tier": { "$eq": "reasoning" }
|
|
41
|
-
},
|
|
42
|
-
"target": {
|
|
43
|
-
"provider": "azure-openai",
|
|
44
|
-
"apiKey": "{{AZURE_OPENAI_API_KEY}}",
|
|
45
|
-
"resourceName": "YOUR-RESOURCE",
|
|
46
|
-
"deploymentId": "o3-mini",
|
|
47
|
-
"apiVersion": "2024-12-01-preview"
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
],
|
|
51
|
-
|
|
52
|
-
"default": {
|
|
53
|
-
"_comment": "COMPLEX — everything else → Azure OpenAI GPT-4o",
|
|
54
|
-
"provider": "azure-openai",
|
|
55
|
-
"apiKey": "{{AZURE_OPENAI_API_KEY}}",
|
|
56
|
-
"resourceName": "YOUR-RESOURCE",
|
|
57
|
-
"deploymentId": "gpt-4o",
|
|
58
|
-
"apiVersion": "2024-08-01-preview"
|
|
59
|
-
}
|
|
60
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Run Portkey local AI Gateway with provider credentials injected
|
|
3
|
-
|
|
4
|
-
docker run -d \
|
|
5
|
-
--name portkey-gateway \
|
|
6
|
-
-p 8083:8787 \
|
|
7
|
-
-e AZURE_OPENAI_API_KEY="${AZURE_OPENAI_API_KEY}" \
|
|
8
|
-
-e MOONSHOT_API_KEY="${MOONSHOT_API_KEY}" \
|
|
9
|
-
portkeyai/gateway:latest
|
|
10
|
-
|
|
11
|
-
echo "Portkey gateway running on http://localhost:8083"
|
|
12
|
-
echo ""
|
|
13
|
-
echo "To use Azure OpenAI directly (no tier routing):"
|
|
14
|
-
echo " curl http://localhost:8083/v1/chat/completions \\"
|
|
15
|
-
echo " -H 'x-portkey-provider: azure-openai' \\"
|
|
16
|
-
echo " -H 'x-portkey-api-key: \$PORTKEY_API_KEY' \\"
|
|
17
|
-
echo " -H 'x-portkey-azure-resource-name: YOUR-RESOURCE' \\"
|
|
18
|
-
echo " -H 'x-portkey-azure-deployment-id: gpt-4o' \\"
|
|
19
|
-
echo " -H 'x-portkey-azure-api-version: 2024-08-01-preview' \\"
|
|
20
|
-
echo " -d '{\"model\": \"gpt-4o\", \"messages\": [...]}'"
|
|
21
|
-
echo ""
|
|
22
|
-
echo "To use conditional routing config, pass:"
|
|
23
|
-
echo " -H 'x-portkey-config: <base64-encoded portkey-config.json>'"
|