Skip to content

revert(feat): improve auto routing - #1969

Merged
steebchen merged 1 commit into
mainfrom
revert-1962-auto-routing-sonnet46
Apr 5, 2026
Merged

steebchen merged 1 commit into
mainfrom
revert-1962-auto-routing-sonnet46

Conversation

@steebchen

@steebchen steebchen commented Apr 5, 2026

Copy link
Copy Markdown
Member

Reverts #1962

Summary by CodeRabbit

  • Refactor
    • Simplified auto-routing logic to prioritize cost-optimized model selection from a defined set of models.
    • Updated model fallback behavior to consistently default to a specific model and provider when no suitable auto-routed option is available.
    • Removed deprecated default model resolution function and related test coverage.

Copilot AI review requested due to automatic review settings April 5, 2026 15:42
@steebchen
steebchen enabled auto-merge April 5, 2026 15:42
@coderabbitai

coderabbitai Bot commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This PR refactors the auto-routing logic by removing the Claude-preference-based getDefaultAutoModelId function and replacing it with a hardcoded list of allowed models. The fallback behavior now defaults to gpt-5-nano/openai. E2E tests are updated to reflect the new routing behavior and simplified test data IDs.

Changes

Cohort / File(s) Summary
Auto-routing logic refactor
apps/gateway/src/chat/chat.ts
Removed getDefaultAutoModelId dependency and replaced default-model-ID routing with inline single-pass selection over hardcoded allowedAutoModels (gpt-oss-120b, gpt-5-nano, gpt-4.1-nano). Changed fallback to force gpt-5-nano/openai instead of dynamic defaults. Simplified control flow by removing helper function and "preferred vs fallback" structure.
Deleted auto-routing utilities
apps/gateway/src/chat/tools/get-default-auto-model-id.ts, apps/gateway/src/chat/tools/get-default-auto-model-id.spec.ts
Removed exported function getDefaultAutoModelId that previously selected between Claude Sonnet 4.6 and Opus based on context length threshold. Deleted corresponding unit test suite.
E2E test updates
apps/gateway/src/api-individual.e2e.ts
Removed Claude Sonnet 4.6 presence checks and replaced time-suffixed test data IDs with fixed strings. Updated assertions to expect requestedModel === "auto" instead of "llmgateway/auto", and modified reasoning effort expectations to vary based on selected model ("minimal" for gpt-5, "low" otherwise). Deleted entire test for Claude Haiku 4.5 auto-routing.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

auto-merge

Suggested reviewers

  • smakosh
  • rcogal
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the changeset as a revert of a previous auto routing improvement feature, which aligns with the PR's objective to revert commit 9cf3d62.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch revert-1962-auto-routing-sonnet46

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR reverts prior “improve auto routing” work by removing the default auto-model helper + unit test, and adjusting the gateway’s auto routing logic and related e2e expectations.

Changes:

  • Removes getDefaultAutoModelId (and its spec) used for Claude-based default auto selection.
  • Reworks auto routing selection in chat.ts to use a hardcoded allowlist of candidate models and a simplified inlined selection loop.
  • Updates individual e2e tests for auto routing behavior and skip conditions.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
apps/gateway/src/chat/tools/get-default-auto-model-id.ts Deletes the helper for picking a default auto model based on context size.
apps/gateway/src/chat/tools/get-default-auto-model-id.spec.ts Removes unit tests for the deleted helper.
apps/gateway/src/chat/chat.ts Updates auto routing candidate selection and fallback behavior.
apps/gateway/src/api-individual.e2e.ts Updates e2e tests/skip logic and assertions for auto routing.
Comments suppressed due to low confidence (1)

apps/gateway/src/api-individual.e2e.ts:337

  • This test inserts an API key with id token-credits-auto, but createTestData("credits-auto") already inserts an API key with the same id (token-${testId}). This will violate the unique constraint and fail the test. Use a different id for the credits token (or update the existing row instead of inserting a second one).
			const { userId, orgId, projectId } = await createTestData("credits-auto");

			await db
				.update(tables.organization)
				.set({ credits: "1000" })
				.where(eq(tables.organization.id, orgId));

			await db
				.update(tables.project)
				.set({ mode: "credits" })
				.where(eq(tables.project.id, projectId));

			const creditsToken = "credits-token-auto";
			await db.insert(tables.apiKey).values({
				id: "token-credits-auto",
				token: creditsToken,
				projectId: projectId,
				description: "Test API Key for Credits",
				createdBy: userId,
			});

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1406 to +1408
// Default fallback if no suitable model is found - use cheapest allowed model
usedModel = "gpt-5-nano";
usedProvider = "openai";

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The auto-routing fallback hardcodes usedProvider = "openai". This can select a provider that is not in availableProviders (no env/db key) or is disallowed by IAM allow/deny provider rules, and it also bypasses the normal provider-selection path that would choose an eligible provider-region mapping. Prefer leaving usedProvider undefined (or selecting from candidateIam.allowedProvidersavailableProviders) so the generic routing logic can pick a permitted provider for the fallback model.

Suggested change
// Default fallback if no suitable model is found - use cheapest allowed model
usedModel = "gpt-5-nano";
usedProvider = "openai";
// Default fallback if no suitable model is found - use cheapest allowed model.
// Leave provider selection unset so the normal routing logic can choose
// an eligible provider/region mapping for the fallback model.
usedModel = "gpt-5-nano";

Copilot uses AI. Check for mistakes.
const allowedAutoModels = ["gpt-oss-120b", "gpt-5-nano", "gpt-4.1-nano"];

let selectedModel: ModelDefinition | undefined;
let selectedProviders: any[] = [];

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

selectedProviders is typed as any[], which defeats type-checking for the provider mapping fields used later (e.g., providerId, contextSize, deprecatedAt, inputPrice). Use the concrete mapping type (e.g., ProviderModelMapping[]) to keep the auto-routing logic safe during future refactors.

Suggested change
let selectedProviders: any[] = [];
let selectedProviders: ProviderModelMapping[] = [];

Copilot uses AI. Check for mistakes.
Comment on lines +306 to +315
// require all provider keys to be set
for (const provider of providers) {
const envVarName = getProviderEnvVar(provider.id);
const envVarValue = envVarName ? process.env[envVarName] : undefined;
if (!envVarValue) {
console.log(
`Skipping llmgateway/auto in credits mode test - no API key provided for ${provider.id}`,
);
return;
}

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The credits auto-routing test now requires all provider env vars to be set in order to run. This makes the test extremely likely to be skipped in most environments and reduces coverage of the auto-routing path. Consider only requiring the providers that can actually be selected by the auto-routing candidates under test (e.g., the providers backing allowedAutoModels), or at minimum the provider you expect the test to route to.

Suggested change
// require all provider keys to be set
for (const provider of providers) {
const envVarName = getProviderEnvVar(provider.id);
const envVarValue = envVarName ? process.env[envVarName] : undefined;
if (!envVarValue) {
console.log(
`Skipping llmgateway/auto in credits mode test - no API key provided for ${provider.id}`,
);
return;
}
// Require at least one provider key so the auto-routing path can run.
// Requiring every provider key makes this test skip in most environments.
const availableProviders = providers.filter((provider) => {
const envVarName = getProviderEnvVar(provider.id);
return envVarName ? Boolean(process.env[envVarName]) : false;
});
if (availableProviders.length === 0) {
console.log(
"Skipping llmgateway/auto in credits mode test - no provider API keys configured",
);
return;

Copilot uses AI. Check for mistakes.
const { userId, orgId, projectId } = await createTestData(
`credits-auto-${Date.now()}`,
);
const { userId, orgId, projectId } = await createTestData("credits-auto");

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createTestData does not clean up the DB between runs (only clearCache() runs in beforeEach), so using fixed ids like credits-auto can cause primary-key collisions when the suite is rerun against the same database. Reintroduce a per-run unique suffix (e.g., Date.now() / generateTestRequestId()) or add DB cleanup for this file to keep these e2e tests repeatable.

Copilot uses AI. Check for mistakes.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a4f91bdcd1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1407 to +1408
usedModel = "gpt-5-nano";
usedProvider = "openai";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove forced OpenAI fallback in auto routing

When auto cannot find a suitable candidate, this fallback pins routing to openai/gpt-5-nano instead of leaving provider selection dynamic. In credits/hybrid environments that do not configure an OpenAI key (for example, orgs using only Anthropic or other providers), getProviderEnv("openai") later throws a 500, so auto requests fail even though other providers are available. The previous behavior (usedProvider = undefined) allowed downstream routing to pick an allowed configured provider or return a proper availability error.

Useful? React with 👍 / 👎.

const creditsToken = "credits-token-auto";
await db.insert(tables.apiKey).values({
id: `token-credits-auto-${creditsSuffix}`,
id: "token-credits-auto",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use a unique API key id in credits auto e2e test

This insert reuses id: "token-credits-auto", but createTestData("credits-auto") already creates an API key with the same id (token-${testId}), so the second insert hits a primary-key/unique violation when the test reaches this path. As written, the test will fail in environments where its provider-key preconditions are satisfied.

Useful? React with 👍 / 👎.

@steebchen
steebchen added this pull request to the merge queue Apr 5, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/gateway/src/api-individual.e2e.ts`:
- Around line 306-315: The test currently requires every provider in providers
to have an API key, causing the auto-routing credits test to be skipped in most
environments; change the gate to require at least one provider with a valid API
key instead of all. Locate the provider check using providers,
getProviderEnvVar(provider.id) and envVarValue, replace the all-provider loop
with a check like providers.some(...) to detect any provider that has a
configured env var and only skip (with the existing log) when none are present;
ensure subsequent test code uses the available provider(s) accordingly.

In `@apps/gateway/src/chat/chat.ts`:
- Around line 1406-1408: The fallback sets usedModel="gpt-5-nano" and
usedProvider="openai" unconditionally which can bypass provider-level IAM
checks; update the fallback logic in the selection flow (references: usedModel,
usedProvider, allowedProviders and the IAM revalidation block) to choose a
provider from allowedProviders that supports the chosen model (or choose an
allowed model/provider pair), and if no allowed provider supports the fallback
model, pick the cheapest model-provider pair from allowedProviders or return a
clear authorization/error path instead of hardcoding "openai".
- Around line 1242-1252: The hybrid auto-routing path is expanding all regions
without applying the same key-availability filtering used for credits, which can
select regions lacking valid keys and cause routing failures; update the
construction of candidateProviders (the call to preferConcreteRegionalMappings)
so that when project.mode === "hybrid" you also run
expandAllProviderRegions(modelDef.providers as ProviderModelMapping[]) through
filterRegionsByAvailableKeys (the same filter used for "credits") before passing
to preferConcreteRegionalMappings, ensuring both "credits" and "hybrid" modes
only consider regions with available keys.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: eb35ef56-9ff3-4c9a-832e-4f8b339c2366

📥 Commits

Reviewing files that changed from the base of the PR and between f76e8a9 and a4f91bd.

📒 Files selected for processing (4)
  • apps/gateway/src/api-individual.e2e.ts
  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/chat/tools/get-default-auto-model-id.spec.ts
  • apps/gateway/src/chat/tools/get-default-auto-model-id.ts
💤 Files with no reviewable changes (2)
  • apps/gateway/src/chat/tools/get-default-auto-model-id.spec.ts
  • apps/gateway/src/chat/tools/get-default-auto-model-id.ts

Comment on lines +306 to +315
// require all provider keys to be set
for (const provider of providers) {
const envVarName = getProviderEnvVar(provider.id);
const envVarValue = envVarName ? process.env[envVarName] : undefined;
if (!envVarValue) {
console.log(
`Skipping llmgateway/auto in credits mode test - no API key provided for ${provider.id}`,
);
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This gate will skip the auto-routing credits test in most environments.

Requiring keys for every provider (providers) makes this test effectively non-running unless the environment is fully populated. That removes meaningful coverage for the auto-routing path.

🔧 Proposed fix
-			// require all provider keys to be set
-			for (const provider of providers) {
-				const envVarName = getProviderEnvVar(provider.id);
-				const envVarValue = envVarName ? process.env[envVarName] : undefined;
-				if (!envVarValue) {
-					console.log(
-						`Skipping llmgateway/auto in credits mode test - no API key provided for ${provider.id}`,
-					);
-					return;
-				}
-			}
+			// require at least one provider key eligible for auto-routing
+			const autoModelIds = new Set(["gpt-oss-120b", "gpt-5-nano", "gpt-4.1-nano"]);
+			const autoProviderIds = new Set(
+				models
+					.filter((m) => autoModelIds.has(m.id))
+					.flatMap((m) => m.providers.map((p) => p.providerId)),
+			);
+			const hasAnyAutoProviderKey = Array.from(autoProviderIds).some((providerId) => {
+				const envVarName = getProviderEnvVar(providerId);
+				return Boolean(envVarName && process.env[envVarName]);
+			});
+			if (!hasAnyAutoProviderKey) {
+				console.log(
+					"Skipping llmgateway/auto in credits mode test - no eligible auto-routing provider key configured",
+				);
+				return;
+			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/gateway/src/api-individual.e2e.ts` around lines 306 - 315, The test
currently requires every provider in providers to have an API key, causing the
auto-routing credits test to be skipped in most environments; change the gate to
require at least one provider with a valid API key instead of all. Locate the
provider check using providers, getProviderEnvVar(provider.id) and envVarValue,
replace the all-provider loop with a check like providers.some(...) to detect
any provider that has a configured env var and only skip (with the existing log)
when none are present; ensure subsequent test code uses the available
provider(s) accordingly.

Comment on lines +1242 to +1252
const candidateProviders = preferConcreteRegionalMappings(
project.mode === "credits"
? filterRegionsByAvailableKeys(
expandAllProviderRegions(
modelDef.providers as ProviderModelMapping[],
),
);
// Check if any of the model's providers are available
const availableModelProviders = candidateProviders.filter(
(provider) =>
availableProviders.includes(provider.providerId) &&
(!candidateAllowedProviders ||
candidateAllowedProviders.includes(provider.providerId)),
);

// Filter by context size requirement, reasoning capability, and deprecation status
const suitableProviders = availableModelProviders.filter((provider) => {
// Skip deprecated provider mappings
if (provider.deprecatedAt && now > provider.deprecatedAt!) {
return false;
}

// Use the provider's context size, defaulting to a reasonable value if not specified
const modelContextSize = provider.contextSize ?? 8192;
const contextSizeMet = modelContextSize >= requiredContextSize;
)
: expandAllProviderRegions(
modelDef.providers as ProviderModelMapping[],
),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Hybrid auto-routing can choose regions without valid key coverage.

Line 1243 applies region-key filtering only for credits. In hybrid, this path expands all regions, which can pick non-default regions that were intentionally filtered elsewhere (see the project-mode filtering contract above this block). That can cause avoidable routing failures.

🔧 Proposed fix
-			const candidateProviders = preferConcreteRegionalMappings(
-				project.mode === "credits"
-					? filterRegionsByAvailableKeys(
-							expandAllProviderRegions(
-								modelDef.providers as ProviderModelMapping[],
-							),
-						)
-					: expandAllProviderRegions(
-							modelDef.providers as ProviderModelMapping[],
-						),
-			);
+			const expandedCandidates = expandAllProviderRegions(
+				modelDef.providers as ProviderModelMapping[],
+			);
+			const candidateProviders = preferConcreteRegionalMappings(
+				project.mode === "credits"
+					? filterRegionsByAvailableKeys(expandedCandidates)
+					: project.mode === "hybrid"
+						? filterHybridRegions(expandedCandidates)
+						: expandedCandidates,
+			);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/gateway/src/chat/chat.ts` around lines 1242 - 1252, The hybrid
auto-routing path is expanding all regions without applying the same
key-availability filtering used for credits, which can select regions lacking
valid keys and cause routing failures; update the construction of
candidateProviders (the call to preferConcreteRegionalMappings) so that when
project.mode === "hybrid" you also run
expandAllProviderRegions(modelDef.providers as ProviderModelMapping[]) through
filterRegionsByAvailableKeys (the same filter used for "credits") before passing
to preferConcreteRegionalMappings, ensuring both "credits" and "hybrid" modes
only consider regions with available keys.

Comment on lines +1406 to +1408
// Default fallback if no suitable model is found - use cheapest allowed model
usedModel = "gpt-5-nano";
usedProvider = "openai";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Hardcoded auto fallback can bypass provider-level IAM restrictions.

At Line 1407-Line 1408, fallback pins openai, but after IAM revalidation (Line 1436+), usedProvider is not reconciled with allowedProviders. If IAM denies openai for gpt-5-nano but allows another provider for that model, this can still proceed with the denied provider.

🔧 Proposed fix
-			usedModel = "gpt-5-nano";
-			usedProvider = "openai";
+			usedModel = "gpt-5-nano";
+			usedProvider = "openai";
...
 		const allowedProviders = resolvedIamValidation.allowedProviders;
+		if (
+			usedProvider &&
+			allowedProviders &&
+			!allowedProviders.includes(usedProvider)
+		) {
+			const allowedMapping = modelInfo.providers.find((p) =>
+				allowedProviders.includes(p.providerId),
+			);
+			if (!allowedMapping) {
+				throwIamException(
+					`No providers are allowed for model ${modelInfo.id} after applying IAM rules`,
+				);
+			}
+			usedProvider = allowedMapping.providerId;
+			usedModel = allowedMapping.modelName;
+			usedRegion = allowedMapping.region;
+		}
 		iamFilteredModelProviders = allowedProviders

Also applies to: 1436-1450

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/gateway/src/chat/chat.ts` around lines 1406 - 1408, The fallback sets
usedModel="gpt-5-nano" and usedProvider="openai" unconditionally which can
bypass provider-level IAM checks; update the fallback logic in the selection
flow (references: usedModel, usedProvider, allowedProviders and the IAM
revalidation block) to choose a provider from allowedProviders that supports the
chosen model (or choose an allowed model/provider pair), and if no allowed
provider supports the fallback model, pick the cheapest model-provider pair from
allowedProviders or return a clear authorization/error path instead of
hardcoding "openai".

Merged via the queue into main with commit fca845e Apr 5, 2026
21 of 22 checks passed
@steebchen
steebchen deleted the revert-1962-auto-routing-sonnet46 branch April 5, 2026 15:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants