mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
* feat(routing): add reasoning-based model and effort routing * refactor(routing): modularize reasoning and auto-routing pipeline * fix(routing): remove redundant DB re-export and prevent SQL scan false positives * fix(routing): resolve reasoning routing review blockers * fix(i18n): keep release ranking fallbacks outside reasoning * fix(db): renumber reasoning-routing migration past release tip (124→125) 124_generic_session_affinity_ttl.sql (#7274) has since landed on release/v3.8.49 at version 124, colliding with this PR's own 124_reasoning_routing_rules.sql. Renumbers to 125 (the next free slot past the current release tip) and updates the one filename reference in docs/routing/REASONING_ROUTING.md. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(db): renumber reasoning-routing migration 125→126 (slot taken by #7360) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(api): compact temp-path decls in exportAll GET (complexity-ratchet lines budget) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(api): single-statement auth guard in exportAll GET (function under 80-line cap) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
441 lines
13 KiB
TypeScript
441 lines
13 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { createChatPipelineHarness } from "./_chatPipelineHarness.ts";
|
|
|
|
const harness = await createChatPipelineHarness("reasoning-routing-pipeline");
|
|
const {
|
|
BaseExecutor,
|
|
buildOpenAIResponse,
|
|
buildRequest,
|
|
cleanup,
|
|
combosDb,
|
|
getLatestCallLog,
|
|
handleChat,
|
|
reasoningRulesDb,
|
|
resetStorage,
|
|
seedApiKey,
|
|
seedConnection,
|
|
toPlainHeaders,
|
|
waitFor,
|
|
} = harness;
|
|
|
|
test.beforeEach(async () => {
|
|
BaseExecutor.RETRY_CONFIG.delayMs = 0;
|
|
await resetStorage();
|
|
});
|
|
|
|
test.afterEach(async () => {
|
|
BaseExecutor.RETRY_CONFIG.delayMs = harness.originalRetryDelayMs;
|
|
await resetStorage();
|
|
});
|
|
|
|
test.after(async () => {
|
|
await cleanup();
|
|
});
|
|
|
|
test("chat pipeline applies a reasoning model/effort rule once and records route trace", async () => {
|
|
await seedConnection("openai", { apiKey: "sk-openai-reasoning-target" });
|
|
await reasoningRulesDb.createReasoningRoutingRule({
|
|
name: "Route low to high",
|
|
description: "integration test",
|
|
scope: "global",
|
|
apiKeyId: null,
|
|
comboId: null,
|
|
connectionId: null,
|
|
modelPattern: "openai/gpt-4o-mini",
|
|
sourceEffort: "low",
|
|
requestTags: ["coding"],
|
|
tagMatchMode: "all",
|
|
effortMode: "force",
|
|
targetEffort: "high",
|
|
targetKind: "model",
|
|
targetModel: "openai/gpt-4.1-mini",
|
|
targetComboId: null,
|
|
budgetAction: "preserve",
|
|
budgetTokens: null,
|
|
priority: 10,
|
|
enabled: true,
|
|
});
|
|
const fetchCalls: FetchCall[] = [];
|
|
globalThis.fetch = async (url, init: RequestInit = {}) => {
|
|
fetchCalls.push({
|
|
url: String(url),
|
|
method: init.method || "GET",
|
|
headers: toPlainHeaders(init.headers),
|
|
body: init.body ? JSON.parse(String(init.body)) : null,
|
|
});
|
|
return buildOpenAIResponse("reasoning route ok", "gpt-4.1-mini");
|
|
};
|
|
|
|
const response = await handleChat(
|
|
buildRequest({
|
|
body: {
|
|
model: "openai/gpt-4o-mini",
|
|
stream: false,
|
|
reasoning_effort: "low",
|
|
metadata: { tags: ["coding"] },
|
|
messages: [{ role: "user", content: "Apply the reasoning rule" }],
|
|
},
|
|
})
|
|
);
|
|
await response.json();
|
|
const callLog = await waitFor(() => getLatestCallLog());
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(fetchCalls.length, 1);
|
|
assert.equal(fetchCalls[0].body.model, "gpt-4.1-mini");
|
|
assert.equal(fetchCalls[0].body.reasoning_effort, "high");
|
|
assert.equal(fetchCalls[0].body._omnirouteReasoningRule, undefined);
|
|
assert.equal(fetchCalls[0].body._omnirouteReasoningRouteTrace, undefined);
|
|
assert.equal(callLog?.pipelinePayloads?.routeDecision?.sourceModel, "openai/gpt-4o-mini");
|
|
assert.equal(callLog?.pipelinePayloads?.routeDecision?.targetModel, "openai/gpt-4.1-mini");
|
|
assert.equal(callLog?.pipelinePayloads?.routeDecision?.targetEffort, "high");
|
|
});
|
|
|
|
test("chat pipeline stays unchanged when the reasoning rule table is empty", async () => {
|
|
await seedConnection("openai", { apiKey: "sk-openai-no-reasoning-rule" });
|
|
const fetchCalls: FetchCall[] = [];
|
|
globalThis.fetch = async (url, init: RequestInit = {}) => {
|
|
fetchCalls.push({
|
|
url: String(url),
|
|
method: init.method || "GET",
|
|
headers: toPlainHeaders(init.headers),
|
|
body: init.body ? JSON.parse(String(init.body)) : null,
|
|
});
|
|
return buildOpenAIResponse("unchanged");
|
|
};
|
|
|
|
const response = await handleChat(
|
|
buildRequest({
|
|
body: {
|
|
model: "openai/gpt-4o-mini",
|
|
stream: false,
|
|
reasoning_effort: "low",
|
|
messages: [{ role: "user", content: "Keep this request unchanged" }],
|
|
},
|
|
})
|
|
);
|
|
await response.json();
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(fetchCalls.length, 1);
|
|
assert.equal(fetchCalls[0].body.model, "gpt-4o-mini");
|
|
assert.equal(fetchCalls[0].body._omnirouteReasoningRule, undefined);
|
|
assert.equal(fetchCalls[0].body._omnirouteReasoningRouteTrace, undefined);
|
|
});
|
|
|
|
test("reasoning routing works through Responses and Anthropic Messages transports", async () => {
|
|
await seedConnection("openai", { apiKey: "sk-openai-reasoning-transports" });
|
|
await reasoningRulesDb.createReasoningRoutingRule({
|
|
name: "Transport effort rule",
|
|
description: "",
|
|
scope: "global",
|
|
apiKeyId: null,
|
|
comboId: null,
|
|
connectionId: null,
|
|
modelPattern: "openai/gpt-4o-mini",
|
|
sourceEffort: "missing",
|
|
requestTags: [],
|
|
tagMatchMode: "any",
|
|
effortMode: "force",
|
|
targetEffort: "high",
|
|
targetKind: "model",
|
|
targetModel: "openai/gpt-4.1-mini",
|
|
targetComboId: null,
|
|
budgetAction: "preserve",
|
|
budgetTokens: null,
|
|
priority: 1,
|
|
enabled: true,
|
|
});
|
|
const fetchCalls: FetchCall[] = [];
|
|
globalThis.fetch = async (url, init: RequestInit = {}) => {
|
|
fetchCalls.push({
|
|
url: String(url),
|
|
method: init.method || "GET",
|
|
headers: toPlainHeaders(init.headers),
|
|
body: init.body ? JSON.parse(String(init.body)) : null,
|
|
});
|
|
return buildOpenAIResponse("transport reasoning ok");
|
|
};
|
|
|
|
const responses = await handleChat(
|
|
buildRequest({
|
|
url: "http://localhost/v1/responses",
|
|
body: {
|
|
model: "openai/gpt-4o-mini",
|
|
stream: false,
|
|
input: "Responses reasoning rule",
|
|
},
|
|
})
|
|
);
|
|
await responses.json();
|
|
const messages = await handleChat(
|
|
buildRequest({
|
|
url: "http://localhost/v1/messages",
|
|
body: {
|
|
model: "openai/gpt-4o-mini",
|
|
stream: false,
|
|
max_tokens: 128,
|
|
messages: [{ role: "user", content: "Messages reasoning rule" }],
|
|
},
|
|
})
|
|
);
|
|
await messages.json();
|
|
|
|
assert.equal(responses.status, 200);
|
|
assert.equal(messages.status, 200);
|
|
assert.equal(fetchCalls.length, 2);
|
|
assert.equal(fetchCalls[0].body.reasoning_effort, "high");
|
|
assert.equal(fetchCalls[1].body.reasoning_effort, "high");
|
|
});
|
|
|
|
test("reasoning routing cannot widen an API key model allowlist", async () => {
|
|
await seedConnection("openai", { apiKey: "sk-openai-reasoning-policy" });
|
|
const apiKey = await seedApiKey({ allowedModels: ["openai/gpt-4o-mini"] });
|
|
await reasoningRulesDb.createReasoningRoutingRule({
|
|
name: "Disallowed target",
|
|
description: "",
|
|
scope: "global",
|
|
apiKeyId: null,
|
|
comboId: null,
|
|
connectionId: null,
|
|
modelPattern: "openai/gpt-4o-mini",
|
|
sourceEffort: "any",
|
|
requestTags: [],
|
|
tagMatchMode: "any",
|
|
effortMode: "inherit",
|
|
targetEffort: null,
|
|
targetKind: "model",
|
|
targetModel: "openai/gpt-4.1-mini",
|
|
targetComboId: null,
|
|
budgetAction: "preserve",
|
|
budgetTokens: null,
|
|
priority: 1,
|
|
enabled: true,
|
|
});
|
|
let upstreamCalls = 0;
|
|
globalThis.fetch = async () => {
|
|
upstreamCalls += 1;
|
|
return buildOpenAIResponse();
|
|
};
|
|
|
|
const response = await handleChat(
|
|
buildRequest({
|
|
authKey: apiKey.key,
|
|
body: {
|
|
model: "openai/gpt-4o-mini",
|
|
stream: false,
|
|
messages: [{ role: "user", content: "Do not widen my model access" }],
|
|
},
|
|
})
|
|
);
|
|
|
|
assert.equal(response.status, 403);
|
|
assert.equal(upstreamCalls, 0);
|
|
});
|
|
|
|
test("reasoning routing cannot target a combo outside the API key policy", async () => {
|
|
await seedConnection("openai", { apiKey: "sk-openai-reasoning-combo-policy" });
|
|
const allowedCombo = await combosDb.createCombo({
|
|
name: "allowed-reasoning-combo",
|
|
strategy: "priority",
|
|
models: ["openai/gpt-4o-mini"],
|
|
});
|
|
const disallowedCombo = await combosDb.createCombo({
|
|
name: "disallowed-reasoning-combo",
|
|
strategy: "priority",
|
|
models: ["openai/gpt-4.1-mini"],
|
|
});
|
|
const apiKey = await seedApiKey({ allowedCombos: [String(allowedCombo.name)] });
|
|
await reasoningRulesDb.createReasoningRoutingRule({
|
|
name: "Disallowed combo target",
|
|
description: "",
|
|
scope: "global",
|
|
apiKeyId: null,
|
|
comboId: null,
|
|
connectionId: null,
|
|
modelPattern: "openai/gpt-4o-mini",
|
|
sourceEffort: "any",
|
|
requestTags: [],
|
|
tagMatchMode: "any",
|
|
effortMode: "inherit",
|
|
targetEffort: null,
|
|
targetKind: "combo",
|
|
targetModel: null,
|
|
targetComboId: String(disallowedCombo.id),
|
|
budgetAction: "preserve",
|
|
budgetTokens: null,
|
|
priority: 1,
|
|
enabled: true,
|
|
});
|
|
let upstreamCalls = 0;
|
|
globalThis.fetch = async () => {
|
|
upstreamCalls += 1;
|
|
return buildOpenAIResponse();
|
|
};
|
|
|
|
const response = await handleChat(
|
|
buildRequest({
|
|
authKey: apiKey.key,
|
|
body: {
|
|
model: "openai/gpt-4o-mini",
|
|
stream: false,
|
|
messages: [{ role: "user", content: "Do not widen my combo access" }],
|
|
},
|
|
})
|
|
);
|
|
|
|
assert.equal(response.status, 403);
|
|
assert.equal(upstreamCalls, 0);
|
|
});
|
|
|
|
test("reasoning routing filters incompatible combo targets and rejects an empty result", async () => {
|
|
await seedConnection("openai", { apiKey: "sk-openai-reasoning-combo-filter" });
|
|
const mixedCombo = await combosDb.createCombo({
|
|
name: "mixed-reasoning-combo",
|
|
strategy: "priority",
|
|
models: ["antigravity/gemini-3-pro", "openai/gpt-4.1-mini"],
|
|
});
|
|
await reasoningRulesDb.createReasoningRoutingRule({
|
|
name: "Filtered combo target",
|
|
description: "",
|
|
scope: "global",
|
|
apiKeyId: null,
|
|
comboId: null,
|
|
connectionId: null,
|
|
modelPattern: "openai/gpt-4o-mini",
|
|
sourceEffort: "missing",
|
|
requestTags: [],
|
|
tagMatchMode: "any",
|
|
effortMode: "force",
|
|
targetEffort: "high",
|
|
targetKind: "combo",
|
|
targetModel: null,
|
|
targetComboId: String(mixedCombo.id),
|
|
budgetAction: "preserve",
|
|
budgetTokens: null,
|
|
priority: 1,
|
|
enabled: true,
|
|
});
|
|
const fetchCalls: FetchCall[] = [];
|
|
globalThis.fetch = async (url, init: RequestInit = {}) => {
|
|
fetchCalls.push({
|
|
url: String(url),
|
|
headers: toPlainHeaders(init.headers),
|
|
body: init.body ? JSON.parse(String(init.body)) : null,
|
|
});
|
|
return buildOpenAIResponse("filtered combo ok", "gpt-4.1-mini");
|
|
};
|
|
|
|
const mixedResponse = await handleChat(
|
|
buildRequest({
|
|
body: {
|
|
model: "openai/gpt-4o-mini",
|
|
stream: false,
|
|
messages: [{ role: "user", content: "Filter incompatible targets" }],
|
|
},
|
|
})
|
|
);
|
|
await mixedResponse.json();
|
|
|
|
assert.equal(mixedResponse.status, 200);
|
|
assert.equal(fetchCalls.length, 1);
|
|
assert.equal(fetchCalls[0].body.model, "gpt-4.1-mini");
|
|
assert.equal(fetchCalls[0].body.reasoning_effort, "high");
|
|
|
|
await reasoningRulesDb.deleteReasoningRoutingRule(
|
|
(await reasoningRulesDb.getReasoningRoutingRules())[0].id
|
|
);
|
|
const incompatibleCombo = await combosDb.createCombo({
|
|
name: "incompatible-reasoning-combo",
|
|
strategy: "priority",
|
|
models: ["antigravity/gemini-3-pro"],
|
|
});
|
|
await reasoningRulesDb.createReasoningRoutingRule({
|
|
name: "Empty combo target",
|
|
description: "",
|
|
scope: "global",
|
|
apiKeyId: null,
|
|
comboId: null,
|
|
connectionId: null,
|
|
modelPattern: "openai/gpt-4o-mini",
|
|
sourceEffort: "missing",
|
|
requestTags: [],
|
|
tagMatchMode: "any",
|
|
effortMode: "force",
|
|
targetEffort: "high",
|
|
targetKind: "combo",
|
|
targetModel: null,
|
|
targetComboId: String(incompatibleCombo.id),
|
|
budgetAction: "preserve",
|
|
budgetTokens: null,
|
|
priority: 1,
|
|
enabled: true,
|
|
});
|
|
|
|
const emptyResponse = await handleChat(
|
|
buildRequest({
|
|
body: {
|
|
model: "openai/gpt-4o-mini",
|
|
stream: false,
|
|
messages: [{ role: "user", content: "Reject an empty target set" }],
|
|
},
|
|
})
|
|
);
|
|
|
|
assert.equal(emptyResponse.status, 400);
|
|
assert.equal(fetchCalls.length, 1);
|
|
});
|
|
|
|
test("connection reasoning rules apply only after selecting their connection", async () => {
|
|
const connection = await seedConnection("openai", {
|
|
apiKey: "sk-openai-connection-reasoning",
|
|
});
|
|
await reasoningRulesDb.createReasoningRoutingRule({
|
|
name: "Connection effort",
|
|
description: "",
|
|
scope: "connection",
|
|
apiKeyId: null,
|
|
comboId: null,
|
|
connectionId: String(connection.id),
|
|
modelPattern: null,
|
|
sourceEffort: "missing",
|
|
requestTags: [],
|
|
tagMatchMode: "any",
|
|
effortMode: "force",
|
|
targetEffort: "high",
|
|
targetKind: "keep",
|
|
targetModel: null,
|
|
targetComboId: null,
|
|
budgetAction: "preserve",
|
|
budgetTokens: null,
|
|
priority: 1,
|
|
enabled: true,
|
|
});
|
|
const fetchCalls: FetchCall[] = [];
|
|
globalThis.fetch = async (url, init: RequestInit = {}) => {
|
|
fetchCalls.push({
|
|
url: String(url),
|
|
headers: toPlainHeaders(init.headers),
|
|
body: init.body ? JSON.parse(String(init.body)) : null,
|
|
});
|
|
return buildOpenAIResponse("connection rule ok");
|
|
};
|
|
|
|
const response = await handleChat(
|
|
buildRequest({
|
|
body: {
|
|
model: "openai/o3-mini",
|
|
stream: false,
|
|
messages: [{ role: "user", content: "Apply the selected connection rule" }],
|
|
},
|
|
})
|
|
);
|
|
await response.json();
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(fetchCalls.length, 1);
|
|
assert.equal(fetchCalls[0].body.model, "o3-mini");
|
|
assert.equal(fetchCalls[0].body.reasoning_effort, "high");
|
|
});
|