Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions apps/web/src/lib/ai-gateway/providers/gateway-models-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,17 @@ export function extractVercelInferenceProviderIdsFromModel(model: StoredModel):
}

const VercelInferenceProvidersSchema = z.array(z.string());
const vercelInferenceProviderFetchers = new Map<string, () => Promise<string[] | null>>();
const vercelInferenceProviderFetchers = new Map<
string,
() => Promise<ReadonlyArray<string> | null>
>();

export function getCachedVercelInferenceProviderIdsForModel(
modelId: string
): Promise<string[] | null> {
): Promise<ReadonlyArray<string> | null> {
let fetchProviders = vercelInferenceProviderFetchers.get(modelId);
if (!fetchProviders) {
fetchProviders = createCachedFetch<string[] | null>(
fetchProviders = createCachedFetch<ReadonlyArray<string> | null>(
async () => {
const raw = await redisClient.get<string>(vercelInferenceProvidersRedisKey(modelId));
if (raw === null) {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/lib/ai-gateway/providers/get-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ export async function getProvider(input: GetProviderInput): Promise<GetProviderR

if (
eligibleForVercelRouting &&
(await shouldRouteToVercel(requestedModel, kiloExclusiveModel, request, taskId || user.id))
(await shouldRouteToVercel(requestedModel, request, taskId || user.id))
) {
return {
kind: 'provider',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export default {
apiKey: getEnvVariable('VERCEL_AI_GATEWAY_API_KEY'),
supportedChatApis: ['chat_completions', 'messages', 'responses'],
async transformRequest(context) {
applyVercelSettings(context.model, context.request, context.userByok);
await applyVercelSettings(context.model, context.request, context.userByok);
},
},
} as const satisfies Record<string, Provider>;
56 changes: 41 additions & 15 deletions apps/web/src/lib/ai-gateway/providers/vercel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
} from '@/lib/ai-gateway/providers/gateway-models-cache';
import type { AnthropicProviderOptions } from '@ai-sdk/anthropic';
import { isDeepseekModel } from '@/lib/ai-gateway/providers/deepseek';
import type { KiloExclusiveModel } from '@/lib/ai-gateway/providers/kilo-exclusive-model';

const getVercelRoutingPercentage = createCachedFetch(
async () => {
Expand All @@ -41,7 +40,7 @@ const getVercelRoutingPercentage = createCachedFetch(

export function hasCompatibleVercelInferenceProvider(
openRouterInferenceProviders: string[],
vercelInferenceProviders: string[] | null
vercelInferenceProviders: ReadonlyArray<string> | null
) {
if (!vercelInferenceProviders) {
return true;
Expand All @@ -60,9 +59,23 @@ export function passesVercelRoutingPercentage(randomSeed: string, routingPercent
return wholePercentageBucket + fractionalPercentageBucket / 1_000 < routingPercentage;
}

async function getAcceptableDeepSeekProviders(
vercelDeepseekModelId: string,
only: string[] | undefined
) {
const availableProviders =
await getCachedVercelInferenceProviderIdsForModel(vercelDeepseekModelId);
const acceptableProviders = only
? new Set(only.map(p => openRouterToVercelInferenceProviderId(p))).intersection(
new Set(availableProviders)
)
: new Set(availableProviders);
acceptableProviders.delete(VercelUserByokInferenceProviderIdSchema.enum.novita); // https://kilo-code.slack.com/archives/C0A4SA041DE/p1781743079721409
return acceptableProviders;
}

export async function shouldRouteToVercel(
requestedModel: string,
kiloExclusiveModel: KiloExclusiveModel | null,
request: GatewayRequest,
randomSeed: string
) {
Expand All @@ -73,14 +86,6 @@ export async function shouldRouteToVercel(
return false;
}

if (!kiloExclusiveModel?.flags.includes('vercel-routing') && isDeepseekModel(requestedModel)) {
// https://kilo-code.slack.com/archives/C0A4SA041DE/p1781743079721409
console.debug(
'[shouldRouteToVercel] not routing to Vercel because some of its DeepSeek providers have tool call issues'
);
return false;
}

console.debug('[shouldRouteToVercel] randomizing user to either OpenRouter or Vercel');
const routingPercentage = await getVercelRoutingPercentage();

Expand All @@ -98,6 +103,17 @@ export async function shouldRouteToVercel(
}

const only = request.body.provider?.only;

if (
isDeepseekModel(requestedModel) &&
(await getAcceptableDeepSeekProviders(requestedModel, only)).size == 0
) {
console.debug(
'[shouldRouteToVercel] not routing to Vercel because some of its DeepSeek providers have tool call issues'
);
return false;
}

if (only) {
const vercelInferenceProviders =
await getCachedVercelInferenceProviderIdsForModel(vercelModelId);
Expand All @@ -112,11 +128,21 @@ export async function shouldRouteToVercel(
return true;
}

function convertProviderOptions(requestToMutate: GatewayRequest): VercelProviderConfig {
async function convertProviderOptions(
requestToMutate: GatewayRequest
): Promise<VercelProviderConfig> {
const vercelModelId = requestToMutate.body.model;
const provider = requestToMutate.body.provider;
const acceptableDeepSeekProviders =
vercelModelId && isDeepseekModel(vercelModelId)
? await getAcceptableDeepSeekProviders(vercelModelId, provider?.only)
: undefined;
return {
gateway: {
only: provider?.only?.map(p => openRouterToVercelInferenceProviderId(p)),
only:
acceptableDeepSeekProviders && acceptableDeepSeekProviders.size > 0
? [...acceptableDeepSeekProviders]
: provider?.only?.map(p => openRouterToVercelInferenceProviderId(p)),
order: provider?.order?.map(p => openRouterToVercelInferenceProviderId(p)),
zeroDataRetention: provider?.zdr,
disallowPromptTraining: provider?.data_collection === 'deny' || undefined,
Expand Down Expand Up @@ -180,7 +206,7 @@ export function getVercelInferenceProviderConfigForUserByok(
return [key, list];
}

export function applyVercelSettings(
export async function applyVercelSettings(
requestedModel: string,
requestToMutate: GatewayRequest,
userByok: BYOKResult[] | null
Expand All @@ -207,7 +233,7 @@ export function applyVercelSettings(
},
};
} else {
requestToMutate.body.providerOptions = convertProviderOptions(requestToMutate);
requestToMutate.body.providerOptions = await convertProviderOptions(requestToMutate);
}

if (requestToMutate.body.providerOptions) {
Expand Down