improve model provider selection algorithm

This commit is contained in:
miloschwartz
2026-08-11 16:10:34 -04:00
parent 75c6af3b11
commit c42df737b0
11 changed files with 408 additions and 200 deletions
+20 -4
View File
@@ -50,6 +50,11 @@ import {
isAllowedByLists,
mostSpecificMatchingAllow
} from "@server/lib/aiModelKeyMatch";
import {
catalogOwnershipScore,
keepBestScored,
providerClassRank
} from "@server/lib/aiProviderSelection";
import { aiGatewayUpstreamFetch } from "@server/lib/aiGatewayUpstreamFetch";
import { getModelPricing, calculateAiCost } from "@server/lib/aiModelPricing";
import {
@@ -502,17 +507,28 @@ async function selectProvider(
};
}
// 1) Prefer the most specific allow pattern that matched the request.
candidates.sort((a, b) =>
compareModelKeySpecificity(a.modelKey, b.modelKey)
);
const bestSpecificity = candidates[0].modelKey;
const topCandidates = candidates.filter(
let remaining = candidates.filter(
(c) => compareModelKeySpecificity(c.modelKey, bestSpecificity) === 0
);
// 2) Prefer providers whose catalog owns this model id. Aggregators only
// score when the model is known somewhere in the catalog.
remaining = keepBestScored(remaining, (c) =>
catalogOwnershipScore(c.provider.type as AiProviderType, requestedModel)
);
// 3) Prefer native typed providers over aggregators over custom.
remaining = keepBestScored(remaining, (c) =>
providerClassRank(c.provider.type as AiProviderType)
);
const uniqueProviders = new Map<number, AiProvider>();
for (const candidate of topCandidates) {
for (const candidate of remaining) {
uniqueProviders.set(candidate.provider.providerId, candidate.provider);
}
@@ -523,7 +539,7 @@ async function selectProvider(
return {
ok: false,
status: HttpCode.FORBIDDEN,
message: `Model "${requestedModel}" is ambiguous across multiple AI providers on this resource`
message: `Model "${requestedModel}" is ambiguous across multiple AI providers on this resource. Ask your administrator to configure a more specific allow pattern for this model.`
};
}
@@ -131,8 +131,7 @@ export async function addAiProviderToResource(
const attachments = await resolveProviderAttachments({
orgId: resource.orgId,
attachments: nextAttachments,
requireAtLeastOne: true,
resourceId
requireAtLeastOne: true
});
if (isInferenceFieldsError(attachments)) {
return next(
@@ -134,8 +134,7 @@ export async function removeAiProviderFromResource(
const attachments = await resolveProviderAttachments({
orgId: resource.orgId,
attachments: remaining,
requireAtLeastOne: false,
resourceId
requireAtLeastOne: false
});
if (isInferenceFieldsError(attachments)) {
return next(
@@ -113,8 +113,7 @@ export async function setResourceAiProviders(
const attachments = await resolveProviderAttachments({
orgId: resource.orgId,
attachments: providers,
requireAtLeastOne: false,
resourceId
requireAtLeastOne: false
});
if (isInferenceFieldsError(attachments)) {
return next(
@@ -131,8 +131,7 @@ export async function addAiProviderToSiteResource(
const attachments = await resolveProviderAttachments({
orgId: siteResource.orgId,
attachments: nextAttachments,
requireAtLeastOne: true,
siteResourceId
requireAtLeastOne: true
});
if (isInferenceFieldsError(attachments)) {
return next(
@@ -133,8 +133,7 @@ export async function removeAiProviderFromSiteResource(
const attachments = await resolveProviderAttachments({
orgId: siteResource.orgId,
attachments: remaining,
requireAtLeastOne: false,
siteResourceId
requireAtLeastOne: false
});
if (isInferenceFieldsError(attachments)) {
return next(
@@ -115,8 +115,7 @@ export async function setSiteResourceAiProviders(
const attachments = await resolveProviderAttachments({
orgId: siteResource.orgId,
attachments: providers,
requireAtLeastOne: false,
siteResourceId
requireAtLeastOne: false
});
if (isInferenceFieldsError(attachments)) {
return next(