diff --git a/apps/sim/app/api/copilot/chat/route.ts b/apps/sim/app/api/copilot/chat/route.ts index 9d31bf5c36..72c959d9a4 100644 --- a/apps/sim/app/api/copilot/chat/route.ts +++ b/apps/sim/app/api/copilot/chat/route.ts @@ -285,6 +285,14 @@ export async function POST(req: NextRequest) { apiVersion: 'preview', endpoint: env.AZURE_OPENAI_ENDPOINT, } + } else if (providerEnv === 'azure-anthropic') { + providerConfig = { + provider: 'azure-anthropic', + model: envModel, + apiKey: env.AZURE_ANTHROPIC_API_KEY, + apiVersion: env.AZURE_ANTHROPIC_API_VERSION, + endpoint: env.AZURE_ANTHROPIC_ENDPOINT, + } } else if (providerEnv === 'vertex') { providerConfig = { provider: 'vertex', diff --git a/apps/sim/app/api/guardrails/validate/route.ts b/apps/sim/app/api/guardrails/validate/route.ts index 5f47383390..6e1b65750c 100644 --- a/apps/sim/app/api/guardrails/validate/route.ts +++ b/apps/sim/app/api/guardrails/validate/route.ts @@ -23,7 +23,16 @@ export async function POST(request: NextRequest) { topK, model, apiKey, + azureEndpoint, + azureApiVersion, + vertexProject, + vertexLocation, + vertexCredential, + bedrockAccessKeyId, + bedrockSecretKey, + bedrockRegion, workflowId, + workspaceId, piiEntityTypes, piiMode, piiLanguage, @@ -110,7 +119,18 @@ export async function POST(request: NextRequest) { topK, model, apiKey, + { + azureEndpoint, + azureApiVersion, + vertexProject, + vertexLocation, + vertexCredential, + bedrockAccessKeyId, + bedrockSecretKey, + bedrockRegion, + }, workflowId, + workspaceId, piiEntityTypes, piiMode, piiLanguage, @@ -178,7 +198,18 @@ async function executeValidation( topK: string | undefined, model: string, apiKey: string | undefined, + providerCredentials: { + azureEndpoint?: string + azureApiVersion?: string + vertexProject?: string + vertexLocation?: string + vertexCredential?: string + bedrockAccessKeyId?: string + bedrockSecretKey?: string + bedrockRegion?: string + }, workflowId: string | undefined, + workspaceId: string | undefined, piiEntityTypes: string[] | undefined, piiMode: string | undefined, piiLanguage: string | undefined, @@ -219,7 +250,9 @@ async function executeValidation( topK: topK ? Number.parseInt(topK) : 10, // Default topK is 10 model: model, apiKey, + providerCredentials, workflowId, + workspaceId, requestId, }) } diff --git a/apps/sim/blocks/blocks/agent.ts b/apps/sim/blocks/blocks/agent.ts index 395efec87b..bf8ec0d669 100644 --- a/apps/sim/blocks/blocks/agent.ts +++ b/apps/sim/blocks/blocks/agent.ts @@ -333,11 +333,11 @@ Return ONLY the JSON array.`, id: 'azureApiVersion', title: 'Azure API Version', type: 'short-input', - placeholder: '2024-07-01-preview', + placeholder: 'Enter API version', connectionDroppable: false, condition: { field: 'model', - value: providers['azure-openai'].models, + value: [...providers['azure-openai'].models, ...providers['azure-anthropic'].models], }, }, { @@ -715,7 +715,7 @@ Example 3 (Array Input): }, model: { type: 'string', description: 'AI model to use' }, apiKey: { type: 'string', description: 'Provider API key' }, - azureEndpoint: { type: 'string', description: 'Azure OpenAI endpoint URL' }, + azureEndpoint: { type: 'string', description: 'Azure endpoint URL' }, azureApiVersion: { type: 'string', description: 'Azure API version' }, vertexProject: { type: 'string', description: 'Google Cloud project ID for Vertex AI' }, vertexLocation: { type: 'string', description: 'Google Cloud location for Vertex AI' }, diff --git a/apps/sim/blocks/blocks/translate.ts b/apps/sim/blocks/blocks/translate.ts index d0d6477651..1385075c73 100644 --- a/apps/sim/blocks/blocks/translate.ts +++ b/apps/sim/blocks/blocks/translate.ts @@ -76,8 +76,9 @@ export const TranslateBlock: BlockConfig = { vertexProject: params.vertexProject, vertexLocation: params.vertexLocation, vertexCredential: params.vertexCredential, - bedrockRegion: params.bedrockRegion, + bedrockAccessKeyId: params.bedrockAccessKeyId, bedrockSecretKey: params.bedrockSecretKey, + bedrockRegion: params.bedrockRegion, }), }, }, diff --git a/apps/sim/blocks/utils.ts b/apps/sim/blocks/utils.ts index 7de0b518af..eed4a5c376 100644 --- a/apps/sim/blocks/utils.ts +++ b/apps/sim/blocks/utils.ts @@ -80,7 +80,7 @@ export function getApiKeyCondition() { /** * Returns the standard provider credential subblocks used by LLM-based blocks. - * This includes: Vertex AI OAuth, API Key, Azure OpenAI, Vertex AI config, and Bedrock config. + * This includes: Vertex AI OAuth, API Key, Azure (OpenAI + Anthropic), Vertex AI config, and Bedrock config. * * Usage: Spread into your block's subBlocks array after block-specific fields */ @@ -111,25 +111,25 @@ export function getProviderCredentialSubBlocks(): SubBlockConfig[] { }, { id: 'azureEndpoint', - title: 'Azure OpenAI Endpoint', + title: 'Azure Endpoint', type: 'short-input', password: true, - placeholder: 'https://your-resource.openai.azure.com', + placeholder: 'https://your-resource.services.ai.azure.com', connectionDroppable: false, condition: { field: 'model', - value: providers['azure-openai'].models, + value: [...providers['azure-openai'].models, ...providers['azure-anthropic'].models], }, }, { id: 'azureApiVersion', title: 'Azure API Version', type: 'short-input', - placeholder: '2024-07-01-preview', + placeholder: 'Enter API version', connectionDroppable: false, condition: { field: 'model', - value: providers['azure-openai'].models, + value: [...providers['azure-openai'].models, ...providers['azure-anthropic'].models], }, }, { @@ -202,7 +202,7 @@ export function getProviderCredentialSubBlocks(): SubBlockConfig[] { */ export const PROVIDER_CREDENTIAL_INPUTS = { apiKey: { type: 'string', description: 'Provider API key' }, - azureEndpoint: { type: 'string', description: 'Azure OpenAI endpoint URL' }, + azureEndpoint: { type: 'string', description: 'Azure endpoint URL' }, azureApiVersion: { type: 'string', description: 'Azure API version' }, vertexProject: { type: 'string', description: 'Google Cloud project ID for Vertex AI' }, vertexLocation: { type: 'string', description: 'Google Cloud location for Vertex AI' }, diff --git a/apps/sim/executor/handlers/evaluator/evaluator-handler.ts b/apps/sim/executor/handlers/evaluator/evaluator-handler.ts index 3e95b2f856..8c432f1da9 100644 --- a/apps/sim/executor/handlers/evaluator/evaluator-handler.ts +++ b/apps/sim/executor/handlers/evaluator/evaluator-handler.ts @@ -121,26 +121,17 @@ export class EvaluatorBlockHandler implements BlockHandler { temperature: EVALUATOR.DEFAULT_TEMPERATURE, apiKey: finalApiKey, + azureEndpoint: inputs.azureEndpoint, + azureApiVersion: inputs.azureApiVersion, + vertexProject: evaluatorConfig.vertexProject, + vertexLocation: evaluatorConfig.vertexLocation, + bedrockAccessKeyId: evaluatorConfig.bedrockAccessKeyId, + bedrockSecretKey: evaluatorConfig.bedrockSecretKey, + bedrockRegion: evaluatorConfig.bedrockRegion, workflowId: ctx.workflowId, workspaceId: ctx.workspaceId, } - if (providerId === 'vertex') { - providerRequest.vertexProject = evaluatorConfig.vertexProject - providerRequest.vertexLocation = evaluatorConfig.vertexLocation - } - - if (providerId === 'azure-openai') { - providerRequest.azureEndpoint = inputs.azureEndpoint - providerRequest.azureApiVersion = inputs.azureApiVersion - } - - if (providerId === 'bedrock') { - providerRequest.bedrockAccessKeyId = evaluatorConfig.bedrockAccessKeyId - providerRequest.bedrockSecretKey = evaluatorConfig.bedrockSecretKey - providerRequest.bedrockRegion = evaluatorConfig.bedrockRegion - } - const response = await fetch(url.toString(), { method: 'POST', headers: await buildAuthHeaders(), diff --git a/apps/sim/executor/handlers/router/router-handler.ts b/apps/sim/executor/handlers/router/router-handler.ts index 766a4aac66..541cdcccad 100644 --- a/apps/sim/executor/handlers/router/router-handler.ts +++ b/apps/sim/executor/handlers/router/router-handler.ts @@ -96,26 +96,17 @@ export class RouterBlockHandler implements BlockHandler { context: JSON.stringify(messages), temperature: ROUTER.INFERENCE_TEMPERATURE, apiKey: finalApiKey, + azureEndpoint: inputs.azureEndpoint, + azureApiVersion: inputs.azureApiVersion, + vertexProject: routerConfig.vertexProject, + vertexLocation: routerConfig.vertexLocation, + bedrockAccessKeyId: routerConfig.bedrockAccessKeyId, + bedrockSecretKey: routerConfig.bedrockSecretKey, + bedrockRegion: routerConfig.bedrockRegion, workflowId: ctx.workflowId, workspaceId: ctx.workspaceId, } - if (providerId === 'vertex') { - providerRequest.vertexProject = routerConfig.vertexProject - providerRequest.vertexLocation = routerConfig.vertexLocation - } - - if (providerId === 'azure-openai') { - providerRequest.azureEndpoint = inputs.azureEndpoint - providerRequest.azureApiVersion = inputs.azureApiVersion - } - - if (providerId === 'bedrock') { - providerRequest.bedrockAccessKeyId = routerConfig.bedrockAccessKeyId - providerRequest.bedrockSecretKey = routerConfig.bedrockSecretKey - providerRequest.bedrockRegion = routerConfig.bedrockRegion - } - const response = await fetch(url.toString(), { method: 'POST', headers: await buildAuthHeaders(), @@ -234,6 +225,13 @@ export class RouterBlockHandler implements BlockHandler { context: JSON.stringify(messages), temperature: ROUTER.INFERENCE_TEMPERATURE, apiKey: finalApiKey, + azureEndpoint: inputs.azureEndpoint, + azureApiVersion: inputs.azureApiVersion, + vertexProject: routerConfig.vertexProject, + vertexLocation: routerConfig.vertexLocation, + bedrockAccessKeyId: routerConfig.bedrockAccessKeyId, + bedrockSecretKey: routerConfig.bedrockSecretKey, + bedrockRegion: routerConfig.bedrockRegion, workflowId: ctx.workflowId, workspaceId: ctx.workspaceId, responseFormat: { @@ -257,22 +255,6 @@ export class RouterBlockHandler implements BlockHandler { }, } - if (providerId === 'vertex') { - providerRequest.vertexProject = routerConfig.vertexProject - providerRequest.vertexLocation = routerConfig.vertexLocation - } - - if (providerId === 'azure-openai') { - providerRequest.azureEndpoint = inputs.azureEndpoint - providerRequest.azureApiVersion = inputs.azureApiVersion - } - - if (providerId === 'bedrock') { - providerRequest.bedrockAccessKeyId = routerConfig.bedrockAccessKeyId - providerRequest.bedrockSecretKey = routerConfig.bedrockSecretKey - providerRequest.bedrockRegion = routerConfig.bedrockRegion - } - const response = await fetch(url.toString(), { method: 'POST', headers: await buildAuthHeaders(), diff --git a/apps/sim/lib/copilot/config.ts b/apps/sim/lib/copilot/config.ts index 4b9c89274c..5700e99300 100644 --- a/apps/sim/lib/copilot/config.ts +++ b/apps/sim/lib/copilot/config.ts @@ -12,6 +12,7 @@ const VALID_PROVIDER_IDS: readonly ProviderId[] = [ 'openai', 'azure-openai', 'anthropic', + 'azure-anthropic', 'google', 'deepseek', 'xai', diff --git a/apps/sim/lib/copilot/types.ts b/apps/sim/lib/copilot/types.ts index 6ed8133082..68e0970397 100644 --- a/apps/sim/lib/copilot/types.ts +++ b/apps/sim/lib/copilot/types.ts @@ -147,6 +147,13 @@ export type CopilotProviderConfig = apiVersion?: string endpoint?: string } + | { + provider: 'azure-anthropic' + model: string + apiKey?: string + apiVersion?: string + endpoint?: string + } | { provider: 'vertex' model: string @@ -155,7 +162,7 @@ export type CopilotProviderConfig = vertexLocation?: string } | { - provider: Exclude + provider: Exclude model?: string apiKey?: string } diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index 8440de3bc4..48e1a630da 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -95,6 +95,9 @@ export const env = createEnv({ AZURE_OPENAI_ENDPOINT: z.string().url().optional(), // Shared Azure OpenAI service endpoint AZURE_OPENAI_API_VERSION: z.string().optional(), // Shared Azure OpenAI API version AZURE_OPENAI_API_KEY: z.string().min(1).optional(), // Shared Azure OpenAI API key + AZURE_ANTHROPIC_ENDPOINT: z.string().url().optional(), // Azure Anthropic service endpoint + AZURE_ANTHROPIC_API_KEY: z.string().min(1).optional(), // Azure Anthropic API key + AZURE_ANTHROPIC_API_VERSION: z.string().min(1).optional(), // Azure Anthropic API version (e.g. 2023-06-01) KB_OPENAI_MODEL_NAME: z.string().optional(), // Knowledge base OpenAI model name (works with both regular OpenAI and Azure OpenAI) WAND_OPENAI_MODEL_NAME: z.string().optional(), // Wand generation OpenAI model name (works with both regular OpenAI and Azure OpenAI) OCR_AZURE_ENDPOINT: z.string().url().optional(), // Azure Mistral OCR service endpoint diff --git a/apps/sim/lib/guardrails/validate_hallucination.ts b/apps/sim/lib/guardrails/validate_hallucination.ts index b2668f2488..48a91fb819 100644 --- a/apps/sim/lib/guardrails/validate_hallucination.ts +++ b/apps/sim/lib/guardrails/validate_hallucination.ts @@ -1,7 +1,11 @@ +import { db } from '@sim/db' +import { account } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { eq } from 'drizzle-orm' import { getBaseUrl } from '@/lib/core/utils/urls' +import { refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils' import { executeProviderRequest } from '@/providers' -import { getApiKey, getProviderFromModel } from '@/providers/utils' +import { getProviderFromModel } from '@/providers/utils' const logger = createLogger('HallucinationValidator') @@ -19,7 +23,18 @@ export interface HallucinationValidationInput { topK: number // Number of chunks to retrieve, default 10 model: string apiKey?: string + providerCredentials?: { + azureEndpoint?: string + azureApiVersion?: string + vertexProject?: string + vertexLocation?: string + vertexCredential?: string + bedrockAccessKeyId?: string + bedrockSecretKey?: string + bedrockRegion?: string + } workflowId?: string + workspaceId?: string requestId: string } @@ -89,7 +104,9 @@ async function scoreHallucinationWithLLM( userInput: string, ragContext: string[], model: string, - apiKey: string, + apiKey: string | undefined, + providerCredentials: HallucinationValidationInput['providerCredentials'], + workspaceId: string | undefined, requestId: string ): Promise<{ score: number; reasoning: string }> { try { @@ -127,6 +144,23 @@ Evaluate the consistency and provide your score and reasoning in JSON format.` const providerId = getProviderFromModel(model) + let finalApiKey: string | undefined = apiKey + if (providerId === 'vertex' && providerCredentials?.vertexCredential) { + const credential = await db.query.account.findFirst({ + where: eq(account.id, providerCredentials.vertexCredential), + }) + if (credential) { + const { accessToken } = await refreshTokenIfNeeded( + requestId, + credential, + providerCredentials.vertexCredential + ) + if (accessToken) { + finalApiKey = accessToken + } + } + } + const response = await executeProviderRequest(providerId, { model, systemPrompt, @@ -137,7 +171,15 @@ Evaluate the consistency and provide your score and reasoning in JSON format.` }, ], temperature: 0.1, // Low temperature for consistent scoring - apiKey, + apiKey: finalApiKey, + azureEndpoint: providerCredentials?.azureEndpoint, + azureApiVersion: providerCredentials?.azureApiVersion, + vertexProject: providerCredentials?.vertexProject, + vertexLocation: providerCredentials?.vertexLocation, + bedrockAccessKeyId: providerCredentials?.bedrockAccessKeyId, + bedrockSecretKey: providerCredentials?.bedrockSecretKey, + bedrockRegion: providerCredentials?.bedrockRegion, + workspaceId, }) if (response instanceof ReadableStream || ('stream' in response && 'execution' in response)) { @@ -184,8 +226,18 @@ Evaluate the consistency and provide your score and reasoning in JSON format.` export async function validateHallucination( input: HallucinationValidationInput ): Promise { - const { userInput, knowledgeBaseId, threshold, topK, model, apiKey, workflowId, requestId } = - input + const { + userInput, + knowledgeBaseId, + threshold, + topK, + model, + apiKey, + providerCredentials, + workflowId, + workspaceId, + requestId, + } = input try { if (!userInput || userInput.trim().length === 0) { @@ -202,17 +254,6 @@ export async function validateHallucination( } } - let finalApiKey: string - try { - const providerId = getProviderFromModel(model) - finalApiKey = getApiKey(providerId, model, apiKey) - } catch (error: any) { - return { - passed: false, - error: `API key error: ${error.message}`, - } - } - // Step 1: Query knowledge base with RAG const ragContext = await queryKnowledgeBase( knowledgeBaseId, @@ -234,7 +275,9 @@ export async function validateHallucination( userInput, ragContext, model, - finalApiKey, + apiKey, + providerCredentials, + workspaceId, requestId ) diff --git a/apps/sim/lib/tokenization/constants.ts b/apps/sim/lib/tokenization/constants.ts index 010ef47437..a10b1995da 100644 --- a/apps/sim/lib/tokenization/constants.ts +++ b/apps/sim/lib/tokenization/constants.ts @@ -21,6 +21,11 @@ export const TOKENIZATION_CONFIG = { confidence: 'high', supportedMethods: ['heuristic', 'fallback'], }, + 'azure-anthropic': { + avgCharsPerToken: 4.5, + confidence: 'high', + supportedMethods: ['heuristic', 'fallback'], + }, google: { avgCharsPerToken: 5, confidence: 'medium', diff --git a/apps/sim/lib/tokenization/estimators.ts b/apps/sim/lib/tokenization/estimators.ts index 53ce719658..01aed1c1e6 100644 --- a/apps/sim/lib/tokenization/estimators.ts +++ b/apps/sim/lib/tokenization/estimators.ts @@ -204,6 +204,7 @@ export function estimateTokenCount(text: string, providerId?: string): TokenEsti estimatedTokens = estimateOpenAITokens(text) break case 'anthropic': + case 'azure-anthropic': estimatedTokens = estimateAnthropicTokens(text) break case 'google': diff --git a/apps/sim/providers/azure-anthropic/index.ts b/apps/sim/providers/azure-anthropic/index.ts index efb131be14..721e363394 100644 --- a/apps/sim/providers/azure-anthropic/index.ts +++ b/apps/sim/providers/azure-anthropic/index.ts @@ -35,6 +35,8 @@ export const azureAnthropicProvider: ProviderConfig = { // The SDK appends /v1/messages automatically const baseURL = `${request.azureEndpoint.replace(/\/$/, '')}/anthropic` + const anthropicVersion = request.azureApiVersion || '2023-06-01' + return executeAnthropicProviderRequest( { ...request, @@ -49,7 +51,7 @@ export const azureAnthropicProvider: ProviderConfig = { apiKey, defaultHeaders: { 'api-key': apiKey, - 'anthropic-version': '2023-06-01', + 'anthropic-version': anthropicVersion, ...(useNativeStructuredOutputs ? { 'anthropic-beta': 'structured-outputs-2025-11-13' } : {}), diff --git a/apps/sim/tools/guardrails/validate.ts b/apps/sim/tools/guardrails/validate.ts index f791fa89c0..124795c44a 100644 --- a/apps/sim/tools/guardrails/validate.ts +++ b/apps/sim/tools/guardrails/validate.ts @@ -9,6 +9,14 @@ export interface GuardrailsValidateInput { topK?: string model?: string apiKey?: string + azureEndpoint?: string + azureApiVersion?: string + vertexProject?: string + vertexLocation?: string + vertexCredential?: string + bedrockAccessKeyId?: string + bedrockSecretKey?: string + bedrockRegion?: string piiEntityTypes?: string[] piiMode?: string piiLanguage?: string @@ -166,6 +174,14 @@ export const guardrailsValidateTool: ToolConfig