fix(app): support anthropic models on azure cognitive services (#8335)
This commit is contained in:
committed by
GitHub
parent
6e028ec2dc
commit
b8e2895dfc
@@ -586,6 +586,13 @@ export namespace Provider {
|
|||||||
})
|
})
|
||||||
export type Info = z.infer<typeof Info>
|
export type Info = z.infer<typeof Info>
|
||||||
|
|
||||||
|
export function isAzureAnthropic(model: Model): boolean {
|
||||||
|
return (
|
||||||
|
model.providerID === "azure-cognitive-services" &&
|
||||||
|
(model.api.id.includes("claude") || model.api.id.includes("anthropic"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
|
function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
|
||||||
const m: Model = {
|
const m: Model = {
|
||||||
id: model.id,
|
id: model.id,
|
||||||
@@ -1006,9 +1013,16 @@ export namespace Provider {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special case: google-vertex-anthropic uses a subpath import
|
// Special cases for providers that use different npm packages
|
||||||
const bundledKey =
|
if (isAzureAnthropic(model)) {
|
||||||
model.providerID === "google-vertex-anthropic" ? "@ai-sdk/google-vertex/anthropic" : model.api.npm
|
const resourceName = Env.get("AZURE_COGNITIVE_SERVICES_RESOURCE_NAME")
|
||||||
|
if (resourceName) options["baseURL"] = `https://${resourceName}.services.ai.azure.com/anthropic/v1/`
|
||||||
|
}
|
||||||
|
const bundledKey = iife(() => {
|
||||||
|
if (model.providerID === "google-vertex-anthropic") return "@ai-sdk/google-vertex/anthropic"
|
||||||
|
if (isAzureAnthropic(model)) return "@ai-sdk/anthropic"
|
||||||
|
return model.api.npm
|
||||||
|
})
|
||||||
const bundledFn = BUNDLED_PROVIDERS[bundledKey]
|
const bundledFn = BUNDLED_PROVIDERS[bundledKey]
|
||||||
if (bundledFn) {
|
if (bundledFn) {
|
||||||
log.info("using bundled provider", { providerID: model.providerID, pkg: bundledKey })
|
log.info("using bundled provider", { providerID: model.providerID, pkg: bundledKey })
|
||||||
@@ -1074,8 +1088,11 @@ export namespace Provider {
|
|||||||
const provider = s.providers[model.providerID]
|
const provider = s.providers[model.providerID]
|
||||||
const sdk = await getSDK(model)
|
const sdk = await getSDK(model)
|
||||||
|
|
||||||
|
// Skip custom model loader for Azure Anthropic models since they use @ai-sdk/anthropic
|
||||||
|
const useCustomLoader = s.modelLoaders[model.providerID] && !isAzureAnthropic(model)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const language = s.modelLoaders[model.providerID]
|
const language = useCustomLoader
|
||||||
? await s.modelLoaders[model.providerID](sdk, model.api.id, provider.options)
|
? await s.modelLoaders[model.providerID](sdk, model.api.id, provider.options)
|
||||||
: sdk.languageModel(model.api.id)
|
: sdk.languageModel(model.api.id)
|
||||||
s.models.set(key, language)
|
s.models.set(key, language)
|
||||||
|
|||||||
@@ -16,6 +16,17 @@ function mimeToModality(mime: string): Modality | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export namespace ProviderTransform {
|
export namespace ProviderTransform {
|
||||||
|
function isAzureAnthropic(model: Provider.Model): boolean {
|
||||||
|
return (
|
||||||
|
model.providerID === "azure-cognitive-services" &&
|
||||||
|
(model.api.id.includes("claude") || model.api.id.includes("anthropic"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function usesAnthropicSDK(model: Provider.Model): boolean {
|
||||||
|
return model.api.npm === "@ai-sdk/anthropic" || isAzureAnthropic(model)
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeMessages(
|
function normalizeMessages(
|
||||||
msgs: ModelMessage[],
|
msgs: ModelMessage[],
|
||||||
model: Provider.Model,
|
model: Provider.Model,
|
||||||
@@ -50,7 +61,7 @@ export namespace ProviderTransform {
|
|||||||
|
|
||||||
// Anthropic rejects messages with empty content - filter out empty string messages
|
// Anthropic rejects messages with empty content - filter out empty string messages
|
||||||
// and remove empty text/reasoning parts from array content
|
// and remove empty text/reasoning parts from array content
|
||||||
if (model.api.npm === "@ai-sdk/anthropic") {
|
if (usesAnthropicSDK(model)) {
|
||||||
msgs = msgs
|
msgs = msgs
|
||||||
.map((msg) => {
|
.map((msg) => {
|
||||||
if (typeof msg.content === "string") {
|
if (typeof msg.content === "string") {
|
||||||
@@ -256,7 +267,7 @@ export namespace ProviderTransform {
|
|||||||
model.providerID === "anthropic" ||
|
model.providerID === "anthropic" ||
|
||||||
model.api.id.includes("anthropic") ||
|
model.api.id.includes("anthropic") ||
|
||||||
model.api.id.includes("claude") ||
|
model.api.id.includes("claude") ||
|
||||||
model.api.npm === "@ai-sdk/anthropic"
|
usesAnthropicSDK(model)
|
||||||
) {
|
) {
|
||||||
msgs = applyCaching(msgs, model.providerID)
|
msgs = applyCaching(msgs, model.providerID)
|
||||||
}
|
}
|
||||||
@@ -308,6 +319,23 @@ export namespace ProviderTransform {
|
|||||||
const id = model.id.toLowerCase()
|
const id = model.id.toLowerCase()
|
||||||
if (id.includes("deepseek") || id.includes("minimax") || id.includes("glm") || id.includes("mistral")) return {}
|
if (id.includes("deepseek") || id.includes("minimax") || id.includes("glm") || id.includes("mistral")) return {}
|
||||||
|
|
||||||
|
if (isAzureAnthropic(model)) {
|
||||||
|
return {
|
||||||
|
high: {
|
||||||
|
thinking: {
|
||||||
|
type: "enabled",
|
||||||
|
budgetTokens: 16000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
thinking: {
|
||||||
|
type: "enabled",
|
||||||
|
budgetTokens: 31999,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (model.api.npm) {
|
switch (model.api.npm) {
|
||||||
case "@openrouter/ai-sdk-provider":
|
case "@openrouter/ai-sdk-provider":
|
||||||
if (!model.id.includes("gpt") && !model.id.includes("gemini-3") && !model.id.includes("grok-4")) return {}
|
if (!model.id.includes("gpt") && !model.id.includes("gemini-3") && !model.id.includes("grok-4")) return {}
|
||||||
@@ -578,6 +606,9 @@ export namespace ProviderTransform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function providerOptions(model: Provider.Model, options: { [x: string]: any }) {
|
export function providerOptions(model: Provider.Model, options: { [x: string]: any }) {
|
||||||
|
if (isAzureAnthropic(model)) {
|
||||||
|
return { ["anthropic" as string]: options }
|
||||||
|
}
|
||||||
switch (model.api.npm) {
|
switch (model.api.npm) {
|
||||||
case "@ai-sdk/github-copilot":
|
case "@ai-sdk/github-copilot":
|
||||||
case "@ai-sdk/openai":
|
case "@ai-sdk/openai":
|
||||||
@@ -613,16 +644,27 @@ export namespace ProviderTransform {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function maxOutputTokens(model: Provider.Model, options: Record<string, any>, globalLimit: number): number
|
||||||
export function maxOutputTokens(
|
export function maxOutputTokens(
|
||||||
npm: string,
|
npm: string,
|
||||||
options: Record<string, any>,
|
options: Record<string, any>,
|
||||||
modelLimit: number,
|
modelLimit: number,
|
||||||
globalLimit: number,
|
globalLimit: number,
|
||||||
|
): number
|
||||||
|
export function maxOutputTokens(
|
||||||
|
arg1: Provider.Model | string,
|
||||||
|
options: Record<string, any>,
|
||||||
|
arg3: number,
|
||||||
|
arg4?: number,
|
||||||
): number {
|
): number {
|
||||||
|
const model = typeof arg1 === "object" ? arg1 : null
|
||||||
|
const npm = model ? model.api.npm : (arg1 as string)
|
||||||
|
const modelLimit = model ? model.limit.output : arg3
|
||||||
|
const globalLimit = model ? arg3 : arg4!
|
||||||
const modelCap = modelLimit || globalLimit
|
const modelCap = modelLimit || globalLimit
|
||||||
const standardLimit = Math.min(modelCap, globalLimit)
|
const standardLimit = Math.min(modelCap, globalLimit)
|
||||||
|
|
||||||
if (npm === "@ai-sdk/anthropic") {
|
if (model ? usesAnthropicSDK(model) : npm === "@ai-sdk/anthropic") {
|
||||||
const thinking = options?.["thinking"]
|
const thinking = options?.["thinking"]
|
||||||
const budgetTokens = typeof thinking?.["budgetTokens"] === "number" ? thinking["budgetTokens"] : 0
|
const budgetTokens = typeof thinking?.["budgetTokens"] === "number" ? thinking["budgetTokens"] : 0
|
||||||
const enabled = thinking?.["type"] === "enabled"
|
const enabled = thinking?.["type"] === "enabled"
|
||||||
|
|||||||
@@ -133,12 +133,7 @@ export namespace LLM {
|
|||||||
|
|
||||||
const maxOutputTokens = isCodex
|
const maxOutputTokens = isCodex
|
||||||
? undefined
|
? undefined
|
||||||
: ProviderTransform.maxOutputTokens(
|
: ProviderTransform.maxOutputTokens(input.model, params.options, OUTPUT_TOKEN_MAX)
|
||||||
input.model.api.npm,
|
|
||||||
params.options,
|
|
||||||
input.model.limit.output,
|
|
||||||
OUTPUT_TOKEN_MAX,
|
|
||||||
)
|
|
||||||
|
|
||||||
const tools = await resolveTools(input)
|
const tools = await resolveTools(input)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user