Skip to content

Commit 6009a73

Browse files
authored
fix(vllm): remove requirement for api key for vllm (#2380)
1 parent 0acd860 commit 6009a73

File tree

5 files changed

+89
-47
lines changed

5 files changed

+89
-47
lines changed

apps/sim/app/api/providers/ollama/models/route.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ export async function GET(request: NextRequest) {
4545
host: OLLAMA_HOST,
4646
})
4747

48-
// Return empty array instead of error to avoid breaking the UI
4948
return NextResponse.json({ models: [] })
5049
}
5150
}

apps/sim/app/api/providers/vllm/models/route.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,16 @@ export async function GET(request: NextRequest) {
2020
baseUrl,
2121
})
2222

23+
const headers: Record<string, string> = {
24+
'Content-Type': 'application/json',
25+
}
26+
27+
if (env.VLLM_API_KEY) {
28+
headers.Authorization = `Bearer ${env.VLLM_API_KEY}`
29+
}
30+
2331
const response = await fetch(`${baseUrl}/v1/models`, {
24-
headers: {
25-
'Content-Type': 'application/json',
26-
},
32+
headers,
2733
next: { revalidate: 60 },
2834
})
2935

@@ -50,7 +56,6 @@ export async function GET(request: NextRequest) {
5056
baseUrl,
5157
})
5258

53-
// Return empty array instead of error to avoid breaking the UI
5459
return NextResponse.json({ models: [] })
5560
}
5661
}

apps/sim/providers/utils.test.ts

Lines changed: 64 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -53,19 +53,20 @@ describe('getApiKey', () => {
5353
module.require = originalRequire
5454
})
5555

56-
it('should return user-provided key when not in hosted environment', () => {
56+
it.concurrent('should return user-provided key when not in hosted environment', () => {
5757
isHostedSpy.mockReturnValue(false)
5858

59-
// For OpenAI
6059
const key1 = getApiKey('openai', 'gpt-4', 'user-key-openai')
6160
expect(key1).toBe('user-key-openai')
6261

63-
// For Anthropic
6462
const key2 = getApiKey('anthropic', 'claude-3', 'user-key-anthropic')
6563
expect(key2).toBe('user-key-anthropic')
64+
65+
const key3 = getApiKey('google', 'gemini-2.5-flash', 'user-key-google')
66+
expect(key3).toBe('user-key-google')
6667
})
6768

68-
it('should throw error if no key provided in non-hosted environment', () => {
69+
it.concurrent('should throw error if no key provided in non-hosted environment', () => {
6970
isHostedSpy.mockReturnValue(false)
7071

7172
expect(() => getApiKey('openai', 'gpt-4')).toThrow('API key is required for openai gpt-4')
@@ -74,63 +75,87 @@ describe('getApiKey', () => {
7475
)
7576
})
7677

77-
it('should fall back to user key in hosted environment if rotation fails', () => {
78+
it.concurrent('should fall back to user key in hosted environment if rotation fails', () => {
7879
isHostedSpy.mockReturnValue(true)
7980

8081
module.require = vi.fn(() => {
8182
throw new Error('Rotation failed')
8283
})
8384

84-
// Use gpt-4o which IS in the hosted models list
8585
const key = getApiKey('openai', 'gpt-4o', 'user-fallback-key')
8686
expect(key).toBe('user-fallback-key')
8787
})
8888

89-
it('should throw error in hosted environment if rotation fails and no user key', () => {
90-
isHostedSpy.mockReturnValue(true)
89+
it.concurrent(
90+
'should throw error in hosted environment if rotation fails and no user key',
91+
() => {
92+
isHostedSpy.mockReturnValue(true)
9193

92-
module.require = vi.fn(() => {
93-
throw new Error('Rotation failed')
94-
})
94+
module.require = vi.fn(() => {
95+
throw new Error('Rotation failed')
96+
})
9597

96-
// Use gpt-4o which IS in the hosted models list
97-
expect(() => getApiKey('openai', 'gpt-4o')).toThrow('No API key available for openai gpt-4o')
98-
})
98+
expect(() => getApiKey('openai', 'gpt-4o')).toThrow('No API key available for openai gpt-4o')
99+
}
100+
)
99101

100-
it('should require user key for non-OpenAI/Anthropic providers even in hosted environment', () => {
101-
isHostedSpy.mockReturnValue(true)
102+
it.concurrent(
103+
'should require user key for non-OpenAI/Anthropic providers even in hosted environment',
104+
() => {
105+
isHostedSpy.mockReturnValue(true)
102106

103-
const key = getApiKey('other-provider', 'some-model', 'user-key')
104-
expect(key).toBe('user-key')
107+
const key = getApiKey('other-provider', 'some-model', 'user-key')
108+
expect(key).toBe('user-key')
105109

106-
expect(() => getApiKey('other-provider', 'some-model')).toThrow(
107-
'API key is required for other-provider some-model'
108-
)
109-
})
110+
expect(() => getApiKey('other-provider', 'some-model')).toThrow(
111+
'API key is required for other-provider some-model'
112+
)
113+
}
114+
)
110115

111-
it('should require user key for models NOT in hosted list even if provider matches', () => {
112-
isHostedSpy.mockReturnValue(true)
116+
it.concurrent(
117+
'should require user key for models NOT in hosted list even if provider matches',
118+
() => {
119+
isHostedSpy.mockReturnValue(true)
113120

114-
// Models with version suffixes that are NOT in the hosted list should require user API key
115-
// even though they're from anthropic/openai providers
121+
const key1 = getApiKey('anthropic', 'claude-sonnet-4-20250514', 'user-key-anthropic')
122+
expect(key1).toBe('user-key-anthropic')
116123

117-
// User provides their own key - should work
118-
const key1 = getApiKey('anthropic', 'claude-sonnet-4-20250514', 'user-key-anthropic')
119-
expect(key1).toBe('user-key-anthropic')
124+
expect(() => getApiKey('anthropic', 'claude-sonnet-4-20250514')).toThrow(
125+
'API key is required for anthropic claude-sonnet-4-20250514'
126+
)
120127

121-
// No user key - should throw, NOT use server key
122-
expect(() => getApiKey('anthropic', 'claude-sonnet-4-20250514')).toThrow(
123-
'API key is required for anthropic claude-sonnet-4-20250514'
124-
)
128+
const key2 = getApiKey('openai', 'gpt-4o-2024-08-06', 'user-key-openai')
129+
expect(key2).toBe('user-key-openai')
130+
131+
expect(() => getApiKey('openai', 'gpt-4o-2024-08-06')).toThrow(
132+
'API key is required for openai gpt-4o-2024-08-06'
133+
)
134+
}
135+
)
125136

126-
// Same for OpenAI versioned models not in list
127-
const key2 = getApiKey('openai', 'gpt-4o-2024-08-06', 'user-key-openai')
128-
expect(key2).toBe('user-key-openai')
137+
it.concurrent('should return empty for ollama provider without requiring API key', () => {
138+
isHostedSpy.mockReturnValue(false)
129139

130-
expect(() => getApiKey('openai', 'gpt-4o-2024-08-06')).toThrow(
131-
'API key is required for openai gpt-4o-2024-08-06'
132-
)
140+
const key = getApiKey('ollama', 'llama2')
141+
expect(key).toBe('empty')
142+
143+
const key2 = getApiKey('ollama', 'codellama', 'user-key')
144+
expect(key2).toBe('empty')
133145
})
146+
147+
it.concurrent(
148+
'should return empty or user-provided key for vllm provider without requiring API key',
149+
() => {
150+
isHostedSpy.mockReturnValue(false)
151+
152+
const key = getApiKey('vllm', 'vllm/qwen-3')
153+
expect(key).toBe('empty')
154+
155+
const key2 = getApiKey('vllm', 'vllm/llama', 'user-key')
156+
expect(key2).toBe('user-key')
157+
}
158+
)
134159
})
135160

136161
describe('Model Capabilities', () => {
@@ -202,7 +227,6 @@ describe('Model Capabilities', () => {
202227
it.concurrent(
203228
'should inherit temperature support from provider for dynamically fetched models',
204229
() => {
205-
// OpenRouter models should inherit temperature support from provider capabilities
206230
expect(supportsTemperature('openrouter/anthropic/claude-3.5-sonnet')).toBe(true)
207231
expect(supportsTemperature('openrouter/openai/gpt-4')).toBe(true)
208232
}

apps/sim/providers/utils.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -630,13 +630,19 @@ export function getApiKey(provider: string, model: string, userProvidedKey?: str
630630
// If user provided a key, use it as a fallback
631631
const hasUserKey = !!userProvidedKey
632632

633-
// Ollama models don't require API keys - they run locally
633+
// Ollama and vLLM models don't require API keys
634634
const isOllamaModel =
635635
provider === 'ollama' || useProvidersStore.getState().providers.ollama.models.includes(model)
636636
if (isOllamaModel) {
637637
return 'empty' // Ollama uses 'empty' as a placeholder API key
638638
}
639639

640+
const isVllmModel =
641+
provider === 'vllm' || useProvidersStore.getState().providers.vllm.models.includes(model)
642+
if (isVllmModel) {
643+
return userProvidedKey || 'empty' // vLLM uses 'empty' as a placeholder if no key provided
644+
}
645+
640646
// Use server key rotation for all OpenAI models, Anthropic's Claude models, and Google's Gemini models on the hosted platform
641647
const isOpenAIModel = provider === 'openai'
642648
const isClaudeModel = provider === 'anthropic'

apps/sim/providers/vllm/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,15 @@ export const vllmProvider: ProviderConfig = {
7979
}
8080

8181
try {
82-
const response = await fetch(`${baseUrl}/v1/models`)
82+
const headers: Record<string, string> = {
83+
'Content-Type': 'application/json',
84+
}
85+
86+
if (env.VLLM_API_KEY) {
87+
headers.Authorization = `Bearer ${env.VLLM_API_KEY}`
88+
}
89+
90+
const response = await fetch(`${baseUrl}/v1/models`, { headers })
8391
if (!response.ok) {
8492
useProvidersStore.getState().setProviderModels('vllm', [])
8593
logger.warn('vLLM service is not available. The provider will be disabled.')

0 commit comments

Comments
 (0)