Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion apps/sim/blocks/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
getHostedModels,
getProviderIcon,
getProviderModels,
orderModelIdsByReleaseDate,
} from '@/providers/models'
import { useProvidersStore } from '@/stores/providers/store'

Expand Down Expand Up @@ -48,7 +49,7 @@ export const SERVICE_ACCOUNT_SUBBLOCKS: SubBlockConfig[] = [
*/
export function getModelOptions() {
const providersState = useProvidersStore.getState()
const baseModels = providersState.providers.base.models
const baseModels = orderModelIdsByReleaseDate(providersState.providers.base.models)
const ollamaModels = providersState.providers.ollama.models
const ollamaCloudModels = providersState.providers['ollama-cloud'].models
const vllmModels = providersState.providers.vllm.models
Expand Down
81 changes: 81 additions & 0 deletions apps/sim/providers/models.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* @vitest-environment node
*/
import { describe, expect, it } from 'vitest'
import {
getBaseModelProviders,
orderModelIdsByReleaseDate,
PROVIDER_DEFINITIONS,
} from '@/providers/models'

/** Maps a lowercased model ID to its provider's index in the catalog. */
const PROVIDER_INDEX_BY_MODEL = new Map<string, number>()
/** Maps a lowercased model ID to its release time (ms), or null when undated. */
const RELEASE_TIME_BY_MODEL = new Map<string, number | null>()
for (const [providerIndex, provider] of Object.values(PROVIDER_DEFINITIONS).entries()) {
for (const model of provider.models) {
const id = model.id.toLowerCase()
PROVIDER_INDEX_BY_MODEL.set(id, providerIndex)
RELEASE_TIME_BY_MODEL.set(id, model.releaseDate ? Date.parse(model.releaseDate) : null)
}
}

describe('orderModelIdsByReleaseDate', () => {
it('keeps provider grouping order intact', () => {
const ordered = orderModelIdsByReleaseDate(Object.keys(getBaseModelProviders()))
let lastProviderIndex = -1
const seenProviders = new Set<number>()
for (const id of ordered) {
const providerIndex = PROVIDER_INDEX_BY_MODEL.get(id.toLowerCase())
expect(providerIndex).toBeDefined()
// A provider's models must form one contiguous run: once we leave a provider
// we never return to it.
if (providerIndex !== lastProviderIndex) {
expect(seenProviders.has(providerIndex as number)).toBe(false)
seenProviders.add(providerIndex as number)
lastProviderIndex = providerIndex as number
}
}
})

it('sorts models within a provider newest-first by release date', () => {
const ordered = orderModelIdsByReleaseDate(Object.keys(getBaseModelProviders()))
for (let i = 1; i < ordered.length; i++) {
const prev = ordered[i - 1].toLowerCase()
const curr = ordered[i].toLowerCase()
if (PROVIDER_INDEX_BY_MODEL.get(prev) !== PROVIDER_INDEX_BY_MODEL.get(curr)) continue

const prevTime = RELEASE_TIME_BY_MODEL.get(prev)
const currTime = RELEASE_TIME_BY_MODEL.get(curr)
// Dated models precede undated ones; among dated models, newer precedes older.
if (prevTime == null) {
expect(currTime).toBeNull()
} else if (currTime != null) {
expect(prevTime).toBeGreaterThanOrEqual(currTime)
}
}
})

it('places unknown model IDs last, preserving their input order', () => {
const known = Object.keys(getBaseModelProviders())[0]
const ordered = orderModelIdsByReleaseDate(['mystery-a', known, 'mystery-b'])
expect(ordered[0]).toBe(known)
expect(ordered.slice(1)).toEqual(['mystery-a', 'mystery-b'])
})

it('is case-insensitive when matching catalog IDs', () => {
const id = Object.keys(getBaseModelProviders())[0]
const ordered = orderModelIdsByReleaseDate([id.toUpperCase()])
expect(ordered).toEqual([id.toUpperCase()])
})

it('returns an empty array for empty input', () => {
expect(orderModelIdsByReleaseDate([])).toEqual([])
})

it('does not add or drop any IDs', () => {
const input = Object.keys(getBaseModelProviders())
const ordered = orderModelIdsByReleaseDate(input)
expect([...ordered].sort()).toEqual([...input].sort())
})
})
42 changes: 42 additions & 0 deletions apps/sim/providers/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3047,6 +3047,48 @@ export function getProviderModels(providerId: string): string[] {
return PROVIDER_DEFINITIONS[providerId]?.models.map((m) => m.id) || []
}

/**
* Reorders catalog model IDs so that, within each provider, newer models (by
* release date) come first, while preserving the existing provider grouping order.
*
* Models without a known release date keep their declaration order and sort after
* dated models within the same provider. IDs not found in the catalog (e.g.
* dynamically-discovered provider models) are left in their original order at the end.
*/
export function orderModelIdsByReleaseDate(modelIds: string[]): string[] {
const catalogIndex = new Map<
string,
{ providerIndex: number; declIndex: number; releaseTime: number }
>()
Object.values(PROVIDER_DEFINITIONS).forEach((provider, providerIndex) => {
provider.models.forEach((model, declIndex) => {
const parsed = model.releaseDate ? Date.parse(model.releaseDate) : Number.NaN
catalogIndex.set(model.id.toLowerCase(), {
providerIndex,
declIndex,
releaseTime: Number.isNaN(parsed) ? Number.NEGATIVE_INFINITY : parsed,
})
})
})
Comment thread
waleedlatif1 marked this conversation as resolved.
Outdated

return modelIds
.map((id, inputIndex) => ({ id, inputIndex, meta: catalogIndex.get(id.toLowerCase()) }))
.sort((a, b) => {
if (!a.meta || !b.meta) {
if (!a.meta && !b.meta) return a.inputIndex - b.inputIndex
return a.meta ? -1 : 1
}
if (a.meta.providerIndex !== b.meta.providerIndex) {
return a.meta.providerIndex - b.meta.providerIndex
Comment thread
waleedlatif1 marked this conversation as resolved.
Outdated
}
if (a.meta.releaseTime !== b.meta.releaseTime) {
return b.meta.releaseTime - a.meta.releaseTime
}
return a.meta.declIndex - b.meta.declIndex
})
.map((entry) => entry.id)
}

export const DYNAMIC_MODEL_PROVIDERS = [
'ollama',
'ollama-cloud',
Expand Down
Loading