Skip to content

Commit cb8b9c5

Browse files
authored
fix(router): update router to handle azure creds the same way the agent block does (#2572)
* fix(router): update router to handle azure creds the same way the agent block does * cleanup
1 parent b1cd8d1 commit cb8b9c5

File tree

9 files changed

+577
-18
lines changed

9 files changed

+577
-18
lines changed

apps/sim/blocks/blocks/evaluator.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,12 +187,16 @@ export const EvaluatorBlock: BlockConfig<EvaluatorResponse> = {
187187
type: 'combobox',
188188
placeholder: 'Type or select a model...',
189189
required: true,
190+
defaultValue: 'claude-sonnet-4-5',
190191
options: () => {
191192
const providersState = useProvidersStore.getState()
192193
const baseModels = providersState.providers.base.models
193194
const ollamaModels = providersState.providers.ollama.models
195+
const vllmModels = providersState.providers.vllm.models
194196
const openrouterModels = providersState.providers.openrouter.models
195-
const allModels = Array.from(new Set([...baseModels, ...ollamaModels, ...openrouterModels]))
197+
const allModels = Array.from(
198+
new Set([...baseModels, ...ollamaModels, ...vllmModels, ...openrouterModels])
199+
)
196200

197201
return allModels.map((model) => {
198202
const icon = getProviderIcon(model)

apps/sim/blocks/blocks/router.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,16 @@ export const RouterBlock: BlockConfig<RouterResponse> = {
135135
type: 'combobox',
136136
placeholder: 'Type or select a model...',
137137
required: true,
138+
defaultValue: 'claude-sonnet-4-5',
138139
options: () => {
139140
const providersState = useProvidersStore.getState()
140141
const baseModels = providersState.providers.base.models
141142
const ollamaModels = providersState.providers.ollama.models
143+
const vllmModels = providersState.providers.vllm.models
142144
const openrouterModels = providersState.providers.openrouter.models
143-
const allModels = Array.from(new Set([...baseModels, ...ollamaModels, ...openrouterModels]))
145+
const allModels = Array.from(
146+
new Set([...baseModels, ...ollamaModels, ...vllmModels, ...openrouterModels])
147+
)
144148

145149
return allModels.map((model) => {
146150
const icon = getProviderIcon(model)

apps/sim/executor/constants.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,13 @@ export const MEMORY = {
178178
} as const
179179

180180
export const ROUTER = {
181-
DEFAULT_MODEL: 'gpt-4o',
181+
DEFAULT_MODEL: 'claude-sonnet-4-5',
182182
DEFAULT_TEMPERATURE: 0,
183183
INFERENCE_TEMPERATURE: 0.1,
184184
} as const
185185

186186
export const EVALUATOR = {
187-
DEFAULT_MODEL: 'gpt-4o',
187+
DEFAULT_MODEL: 'claude-sonnet-4-5',
188188
DEFAULT_TEMPERATURE: 0.1,
189189
RESPONSE_SCHEMA_NAME: 'evaluation_response',
190190
JSON_INDENT: 2,

apps/sim/executor/handlers/evaluator/evaluator-handler.test.ts

Lines changed: 144 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ describe('EvaluatorBlockHandler', () => {
8282
{ name: 'score2', description: 'Second score', range: { min: 0, max: 10 } },
8383
],
8484
model: 'gpt-4o',
85+
apiKey: 'test-api-key',
8586
temperature: 0.1,
8687
}
8788

@@ -97,7 +98,6 @@ describe('EvaluatorBlockHandler', () => {
9798
})
9899
)
99100

100-
// Verify the request body contains the expected data
101101
const fetchCallArgs = mockFetch.mock.calls[0]
102102
const requestBody = JSON.parse(fetchCallArgs[1].body)
103103
expect(requestBody).toMatchObject({
@@ -137,6 +137,7 @@ describe('EvaluatorBlockHandler', () => {
137137
const inputs = {
138138
content: JSON.stringify(contentObj),
139139
metrics: [{ name: 'clarity', description: 'Clarity score', range: { min: 1, max: 5 } }],
140+
apiKey: 'test-api-key',
140141
}
141142

142143
mockFetch.mockImplementationOnce(() => {
@@ -169,6 +170,7 @@ describe('EvaluatorBlockHandler', () => {
169170
metrics: [
170171
{ name: 'completeness', description: 'Data completeness', range: { min: 0, max: 1 } },
171172
],
173+
apiKey: 'test-api-key',
172174
}
173175

174176
mockFetch.mockImplementationOnce(() => {
@@ -198,6 +200,7 @@ describe('EvaluatorBlockHandler', () => {
198200
const inputs = {
199201
content: 'Test content',
200202
metrics: [{ name: 'quality', description: 'Quality score', range: { min: 1, max: 10 } }],
203+
apiKey: 'test-api-key',
201204
}
202205

203206
mockFetch.mockImplementationOnce(() => {
@@ -223,6 +226,7 @@ describe('EvaluatorBlockHandler', () => {
223226
const inputs = {
224227
content: 'Test content',
225228
metrics: [{ name: 'score', description: 'Score', range: { min: 0, max: 5 } }],
229+
apiKey: 'test-api-key',
226230
}
227231

228232
mockFetch.mockImplementationOnce(() => {
@@ -251,6 +255,7 @@ describe('EvaluatorBlockHandler', () => {
251255
{ name: 'accuracy', description: 'Acc', range: { min: 0, max: 1 } },
252256
{ name: 'fluency', description: 'Flu', range: { min: 0, max: 1 } },
253257
],
258+
apiKey: 'test-api-key',
254259
}
255260

256261
mockFetch.mockImplementationOnce(() => {
@@ -276,6 +281,7 @@ describe('EvaluatorBlockHandler', () => {
276281
const inputs = {
277282
content: 'Test',
278283
metrics: [{ name: 'CamelCaseScore', description: 'Desc', range: { min: 0, max: 10 } }],
284+
apiKey: 'test-api-key',
279285
}
280286

281287
mockFetch.mockImplementationOnce(() => {
@@ -304,6 +310,7 @@ describe('EvaluatorBlockHandler', () => {
304310
{ name: 'presentScore', description: 'Desc1', range: { min: 0, max: 5 } },
305311
{ name: 'missingScore', description: 'Desc2', range: { min: 0, max: 5 } },
306312
],
313+
apiKey: 'test-api-key',
307314
}
308315

309316
mockFetch.mockImplementationOnce(() => {
@@ -327,7 +334,7 @@ describe('EvaluatorBlockHandler', () => {
327334
})
328335

329336
it('should handle server error responses', async () => {
330-
const inputs = { content: 'Test error handling.' }
337+
const inputs = { content: 'Test error handling.', apiKey: 'test-api-key' }
331338

332339
// Override fetch mock to return an error
333340
mockFetch.mockImplementationOnce(() => {
@@ -340,4 +347,139 @@ describe('EvaluatorBlockHandler', () => {
340347

341348
await expect(handler.execute(mockContext, mockBlock, inputs)).rejects.toThrow('Server error')
342349
})
350+
351+
it('should handle Azure OpenAI models with endpoint and API version', async () => {
352+
const inputs = {
353+
content: 'Test content to evaluate',
354+
metrics: [{ name: 'quality', description: 'Quality score', range: { min: 1, max: 10 } }],
355+
model: 'gpt-4o',
356+
apiKey: 'test-azure-key',
357+
azureEndpoint: 'https://test.openai.azure.com',
358+
azureApiVersion: '2024-07-01-preview',
359+
}
360+
361+
mockGetProviderFromModel.mockReturnValue('azure-openai')
362+
363+
mockFetch.mockImplementationOnce(() => {
364+
return Promise.resolve({
365+
ok: true,
366+
json: () =>
367+
Promise.resolve({
368+
content: JSON.stringify({ quality: 8 }),
369+
model: 'gpt-4o',
370+
tokens: {},
371+
cost: 0,
372+
timing: {},
373+
}),
374+
})
375+
})
376+
377+
await handler.execute(mockContext, mockBlock, inputs)
378+
379+
const fetchCallArgs = mockFetch.mock.calls[0]
380+
const requestBody = JSON.parse(fetchCallArgs[1].body)
381+
382+
expect(requestBody).toMatchObject({
383+
provider: 'azure-openai',
384+
model: 'gpt-4o',
385+
apiKey: 'test-azure-key',
386+
azureEndpoint: 'https://test.openai.azure.com',
387+
azureApiVersion: '2024-07-01-preview',
388+
})
389+
})
390+
391+
it('should throw error when API key is missing for non-hosted models', async () => {
392+
const inputs = {
393+
content: 'Test content',
394+
metrics: [{ name: 'score', description: 'Score', range: { min: 0, max: 10 } }],
395+
model: 'gpt-4o',
396+
// No apiKey provided
397+
}
398+
399+
mockGetProviderFromModel.mockReturnValue('openai')
400+
401+
await expect(handler.execute(mockContext, mockBlock, inputs)).rejects.toThrow(
402+
/API key is required/
403+
)
404+
})
405+
406+
it('should handle Vertex AI models with OAuth credential', async () => {
407+
const inputs = {
408+
content: 'Test content to evaluate',
409+
metrics: [{ name: 'quality', description: 'Quality score', range: { min: 1, max: 10 } }],
410+
model: 'gemini-2.0-flash-exp',
411+
vertexCredential: 'test-vertex-credential-id',
412+
vertexProject: 'test-gcp-project',
413+
vertexLocation: 'us-central1',
414+
}
415+
416+
mockGetProviderFromModel.mockReturnValue('vertex')
417+
418+
// Mock the database query for Vertex credential
419+
const mockDb = await import('@sim/db')
420+
const mockAccount = {
421+
id: 'test-vertex-credential-id',
422+
accessToken: 'mock-access-token',
423+
refreshToken: 'mock-refresh-token',
424+
expiresAt: new Date(Date.now() + 3600000), // 1 hour from now
425+
}
426+
vi.spyOn(mockDb.db.query.account, 'findFirst').mockResolvedValue(mockAccount as any)
427+
428+
mockFetch.mockImplementationOnce(() => {
429+
return Promise.resolve({
430+
ok: true,
431+
json: () =>
432+
Promise.resolve({
433+
content: JSON.stringify({ quality: 9 }),
434+
model: 'gemini-2.0-flash-exp',
435+
tokens: {},
436+
cost: 0,
437+
timing: {},
438+
}),
439+
})
440+
})
441+
442+
await handler.execute(mockContext, mockBlock, inputs)
443+
444+
const fetchCallArgs = mockFetch.mock.calls[0]
445+
const requestBody = JSON.parse(fetchCallArgs[1].body)
446+
447+
expect(requestBody).toMatchObject({
448+
provider: 'vertex',
449+
model: 'gemini-2.0-flash-exp',
450+
vertexProject: 'test-gcp-project',
451+
vertexLocation: 'us-central1',
452+
})
453+
expect(requestBody.apiKey).toBe('mock-access-token')
454+
})
455+
456+
it('should use default model when not provided', async () => {
457+
const inputs = {
458+
content: 'Test content',
459+
metrics: [{ name: 'score', description: 'Score', range: { min: 0, max: 10 } }],
460+
apiKey: 'test-api-key',
461+
// No model provided - should use default
462+
}
463+
464+
mockFetch.mockImplementationOnce(() => {
465+
return Promise.resolve({
466+
ok: true,
467+
json: () =>
468+
Promise.resolve({
469+
content: JSON.stringify({ score: 7 }),
470+
model: 'claude-sonnet-4-5',
471+
tokens: {},
472+
cost: 0,
473+
timing: {},
474+
}),
475+
})
476+
})
477+
478+
await handler.execute(mockContext, mockBlock, inputs)
479+
480+
const fetchCallArgs = mockFetch.mock.calls[0]
481+
const requestBody = JSON.parse(fetchCallArgs[1].body)
482+
483+
expect(requestBody.model).toBe('claude-sonnet-4-5')
484+
})
343485
})

apps/sim/executor/handlers/evaluator/evaluator-handler.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { BlockType, DEFAULTS, EVALUATOR, HTTP } from '@/executor/constants'
88
import type { BlockHandler, ExecutionContext } from '@/executor/types'
99
import { buildAPIUrl, extractAPIErrorMessage } from '@/executor/utils/http'
1010
import { isJSONString, parseJSON, stringifyJSON } from '@/executor/utils/json'
11-
import { calculateCost, getProviderFromModel } from '@/providers/utils'
11+
import { calculateCost, getApiKey, getProviderFromModel } from '@/providers/utils'
1212
import type { SerializedBlock } from '@/serializer/types'
1313

1414
const logger = createLogger('EvaluatorBlockHandler')
@@ -35,9 +35,11 @@ export class EvaluatorBlockHandler implements BlockHandler {
3535
}
3636
const providerId = getProviderFromModel(evaluatorConfig.model)
3737

38-
let finalApiKey = evaluatorConfig.apiKey
38+
let finalApiKey: string
3939
if (providerId === 'vertex' && evaluatorConfig.vertexCredential) {
4040
finalApiKey = await this.resolveVertexCredential(evaluatorConfig.vertexCredential)
41+
} else {
42+
finalApiKey = this.getApiKey(providerId, evaluatorConfig.model, evaluatorConfig.apiKey)
4143
}
4244

4345
const processedContent = this.processContent(inputs.content)
@@ -122,6 +124,11 @@ export class EvaluatorBlockHandler implements BlockHandler {
122124
providerRequest.vertexLocation = evaluatorConfig.vertexLocation
123125
}
124126

127+
if (providerId === 'azure-openai') {
128+
providerRequest.azureEndpoint = inputs.azureEndpoint
129+
providerRequest.azureApiVersion = inputs.azureApiVersion
130+
}
131+
125132
const response = await fetch(url.toString(), {
126133
method: 'POST',
127134
headers: {
@@ -268,6 +275,20 @@ export class EvaluatorBlockHandler implements BlockHandler {
268275
return DEFAULTS.EXECUTION_TIME
269276
}
270277

278+
private getApiKey(providerId: string, model: string, inputApiKey: string): string {
279+
try {
280+
return getApiKey(providerId, model, inputApiKey)
281+
} catch (error) {
282+
logger.error('Failed to get API key:', {
283+
provider: providerId,
284+
model,
285+
error: error instanceof Error ? error.message : String(error),
286+
hasProvidedApiKey: !!inputApiKey,
287+
})
288+
throw new Error(error instanceof Error ? error.message : 'API key error')
289+
}
290+
}
291+
271292
/**
272293
* Resolves a Vertex AI OAuth credential to an access token
273294
*/

0 commit comments

Comments
 (0)