Skip to content
Open
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
70 changes: 70 additions & 0 deletions apps/sim/app/api/providers/litellm/models/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { type NextRequest, NextResponse } from 'next/server'
import {
providerModelsResponseSchema,
vllmUpstreamResponseSchema,
} from '@/lib/api/contracts/providers'
import { env } from '@/lib/core/config/env'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
import { filterBlacklistedModels, isProviderBlacklisted } from '@/providers/utils'

const logger = createLogger('LiteLLMModelsAPI')

export const GET = withRouteHandler(async (_request: NextRequest) => {
if (isProviderBlacklisted('litellm')) {
logger.info('LiteLLM provider is blacklisted, returning empty models')
return NextResponse.json({ models: [] })
}

const baseUrl = (env.LITELLM_BASE_URL || '').replace(/\/$/, '')

if (!baseUrl) {
logger.info('LITELLM_BASE_URL not configured')
return NextResponse.json({ models: [] })
}

try {
logger.info('Fetching LiteLLM models', { baseUrl })

const headers: Record<string, string> = {
'Content-Type': 'application/json',
}

if (env.LITELLM_API_KEY) {
headers.Authorization = `Bearer ${env.LITELLM_API_KEY}`
}

const response = await fetch(`${baseUrl}/v1/models`, {
headers,
next: { revalidate: 60 },
})

if (!response.ok) {
logger.warn('LiteLLM service is not available', {
status: response.status,
statusText: response.statusText,
})
return NextResponse.json({ models: [] })
}

const data = vllmUpstreamResponseSchema.parse(await response.json())
const allModels = data.data.map((model) => `litellm/${model.id}`)
const models = filterBlacklistedModels(allModels)

logger.info('Successfully fetched LiteLLM models', {
count: models.length,
filtered: allModels.length - models.length,
models,
})

return NextResponse.json(providerModelsResponseSchema.parse({ models }))
} catch (error) {
logger.error('Failed to fetch LiteLLM models', {
error: getErrorMessage(error, 'Unknown error'),
baseUrl,
})

return NextResponse.json({ models: [] })
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useParams } from 'next/navigation'
import { useProviderModels } from '@/hooks/queries/providers'
import {
updateFireworksProviderModels,
updateLiteLLMProviderModels,
updateOllamaProviderModels,
updateOpenRouterProviderModels,
updateVLLMProviderModels,
Expand All @@ -32,6 +33,8 @@ function useSyncProvider(provider: ProviderName, workspaceId?: string) {
updateOllamaProviderModels(data.models)
} else if (provider === 'vllm') {
updateVLLMProviderModels(data.models)
} else if (provider === 'litellm') {
updateLiteLLMProviderModels(data.models)
} else if (provider === 'openrouter') {
void updateOpenRouterProviderModels(data.models)
if (data.modelInfo) {
Expand Down Expand Up @@ -61,6 +64,7 @@ export function ProviderModelsLoader() {
useSyncProvider('base')
useSyncProvider('ollama')
useSyncProvider('vllm')
useSyncProvider('litellm')
useSyncProvider('openrouter')
useSyncProvider('fireworks', workspaceId)
return null
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/blocks/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,12 @@ function shouldRequireApiKeyForModel(model: string): boolean {
) {
return false
}
if (normalizedModel.startsWith('vllm/')) {
if (normalizedModel.startsWith('vllm/') || normalizedModel.startsWith('litellm/')) {
return false
}

const storeProvider = getProviderFromStore(normalizedModel)
if (storeProvider === 'ollama' || storeProvider === 'vllm') return false
if (storeProvider === 'ollama' || storeProvider === 'vllm' || storeProvider === 'litellm') return false
Comment thread
cursor[bot] marked this conversation as resolved.
if (storeProvider) return true

if (isOllamaConfigured) {
Expand Down
13 changes: 13 additions & 0 deletions apps/sim/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4335,6 +4335,19 @@ export function VllmIcon(props: SVGProps<SVGSVGElement>) {
)
}

export function LitellmIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} fill='none' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'>
<title>LiteLLM</title>
<rect width='24' height='24' rx='4' fill='#1A56DB' />
<path
d='M6 7h2v10H6V7zm4 0h2v8h4v2h-6V7z'
fill='white'
/>
</svg>
)
}

export function PosthogIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
Expand Down
3 changes: 3 additions & 0 deletions apps/sim/hooks/queries/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { requestJson } from '@/lib/api/client/request'
import {
getBaseProviderModelsContract,
getFireworksProviderModelsContract,
getLitellmProviderModelsContract,
getOllamaProviderModelsContract,
getOpenRouterProviderModelsContract,
getVllmProviderModelsContract,
Expand Down Expand Up @@ -54,6 +55,8 @@ async function requestProviderModels(
return requestJson(getOllamaProviderModelsContract, { signal })
case 'vllm':
return requestJson(getVllmProviderModelsContract, { signal })
case 'litellm':
return requestJson(getLitellmProviderModelsContract, { signal })
case 'openrouter':
return requestJson(getOpenRouterProviderModelsContract, { signal })
case 'fireworks':
Expand Down
9 changes: 9 additions & 0 deletions apps/sim/lib/api/contracts/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,15 @@ export const getOpenRouterProviderModelsContract = defineRouteContract({
},
})

export const getLitellmProviderModelsContract = defineRouteContract({
method: 'GET',
path: '/api/providers/litellm/models',
response: {
mode: 'json',
schema: providerModelsResponseSchema,
},
})

export const getFireworksProviderModelsContract = defineRouteContract({
method: 'GET',
path: '/api/providers/fireworks/models',
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/lib/core/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ export const env = createEnv({
OLLAMA_URL: z.string().url().optional(), // Ollama local LLM server URL
VLLM_BASE_URL: z.string().url().optional(), // vLLM self-hosted base URL (OpenAI-compatible)
VLLM_API_KEY: z.string().optional(), // Optional bearer token for vLLM
LITELLM_BASE_URL: z.string().url().optional(), // LiteLLM proxy base URL (OpenAI-compatible)
LITELLM_API_KEY: z.string().optional(), // Optional bearer token for LiteLLM
FIREWORKS_API_KEY: z.string().optional(), // Optional Fireworks AI API key for model listing
COHERE_API_KEY: z.string().min(1).optional(), // Cohere API key for reranker (rerank-v4.0-pro, rerank-v4.0-fast, rerank-v3.5)
COHERE_API_KEY_1: z.string().min(1).optional(), // Primary Cohere API key for rotation
Expand Down
4 changes: 4 additions & 0 deletions apps/sim/providers/attachments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type AttachmentProvider =
| 'fireworks'
| 'ollama'
| 'vllm'
| 'litellm'
| 'xai'
| 'deepseek'
| 'cerebras'
Expand Down Expand Up @@ -93,6 +94,7 @@ const PROVIDER_SUPPORTED_LABELS: Record<AttachmentProvider, string> = {
fireworks: 'images through image_url message parts on vision models',
ollama: 'images through image_url message parts on vision models',
vllm: 'images through image_url message parts on multimodal models',
litellm: 'images through image_url message parts on multimodal models',
xai: 'images through image_url message parts on Grok vision models',
deepseek: 'no file attachments in the current API adapter',
cerebras: 'no file attachments in the current API adapter',
Expand All @@ -109,6 +111,7 @@ export function getAttachmentProvider(providerId: ProviderId | string): Attachme
if (providerId === 'fireworks') return 'fireworks'
if (providerId === 'ollama') return 'ollama'
if (providerId === 'vllm') return 'vllm'
if (providerId === 'litellm') return 'litellm'
if (providerId === 'xai') return 'xai'
if (providerId === 'deepseek') return 'deepseek'
if (providerId === 'cerebras') return 'cerebras'
Expand Down Expand Up @@ -247,6 +250,7 @@ function isMimeTypeSupportedByProvider(
case 'fireworks':
case 'ollama':
case 'vllm':
case 'litellm':
case 'xai':
return isImageMimeType(mimeType)
case 'deepseek':
Expand Down
Loading