Skip to content

Commit 6805029

Browse files
committed
improvement(models): sort model dropdown by latest release date within each provider
1 parent d14bc78 commit 6805029

3 files changed

Lines changed: 125 additions & 1 deletion

File tree

apps/sim/blocks/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
getHostedModels,
1414
getProviderIcon,
1515
getProviderModels,
16+
orderModelIdsByReleaseDate,
1617
} from '@/providers/models'
1718
import { useProvidersStore } from '@/stores/providers/store'
1819

@@ -48,7 +49,7 @@ export const SERVICE_ACCOUNT_SUBBLOCKS: SubBlockConfig[] = [
4849
*/
4950
export function getModelOptions() {
5051
const providersState = useProvidersStore.getState()
51-
const baseModels = providersState.providers.base.models
52+
const baseModels = orderModelIdsByReleaseDate(providersState.providers.base.models)
5253
const ollamaModels = providersState.providers.ollama.models
5354
const ollamaCloudModels = providersState.providers['ollama-cloud'].models
5455
const vllmModels = providersState.providers.vllm.models

apps/sim/providers/models.test.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { describe, expect, it } from 'vitest'
5+
import {
6+
getBaseModelProviders,
7+
orderModelIdsByReleaseDate,
8+
PROVIDER_DEFINITIONS,
9+
} from '@/providers/models'
10+
11+
/** Maps a lowercased model ID to its provider's index in the catalog. */
12+
const PROVIDER_INDEX_BY_MODEL = new Map<string, number>()
13+
/** Maps a lowercased model ID to its release time (ms), or null when undated. */
14+
const RELEASE_TIME_BY_MODEL = new Map<string, number | null>()
15+
for (const [providerIndex, provider] of Object.values(PROVIDER_DEFINITIONS).entries()) {
16+
for (const model of provider.models) {
17+
const id = model.id.toLowerCase()
18+
PROVIDER_INDEX_BY_MODEL.set(id, providerIndex)
19+
RELEASE_TIME_BY_MODEL.set(id, model.releaseDate ? Date.parse(model.releaseDate) : null)
20+
}
21+
}
22+
23+
describe('orderModelIdsByReleaseDate', () => {
24+
it('keeps provider grouping order intact', () => {
25+
const ordered = orderModelIdsByReleaseDate(Object.keys(getBaseModelProviders()))
26+
let lastProviderIndex = -1
27+
const seenProviders = new Set<number>()
28+
for (const id of ordered) {
29+
const providerIndex = PROVIDER_INDEX_BY_MODEL.get(id.toLowerCase())
30+
expect(providerIndex).toBeDefined()
31+
// A provider's models must form one contiguous run: once we leave a provider
32+
// we never return to it.
33+
if (providerIndex !== lastProviderIndex) {
34+
expect(seenProviders.has(providerIndex as number)).toBe(false)
35+
seenProviders.add(providerIndex as number)
36+
lastProviderIndex = providerIndex as number
37+
}
38+
}
39+
})
40+
41+
it('sorts models within a provider newest-first by release date', () => {
42+
const ordered = orderModelIdsByReleaseDate(Object.keys(getBaseModelProviders()))
43+
for (let i = 1; i < ordered.length; i++) {
44+
const prev = ordered[i - 1].toLowerCase()
45+
const curr = ordered[i].toLowerCase()
46+
if (PROVIDER_INDEX_BY_MODEL.get(prev) !== PROVIDER_INDEX_BY_MODEL.get(curr)) continue
47+
48+
const prevTime = RELEASE_TIME_BY_MODEL.get(prev)
49+
const currTime = RELEASE_TIME_BY_MODEL.get(curr)
50+
// Dated models precede undated ones; among dated models, newer precedes older.
51+
if (prevTime == null) {
52+
expect(currTime).toBeNull()
53+
} else if (currTime != null) {
54+
expect(prevTime).toBeGreaterThanOrEqual(currTime)
55+
}
56+
}
57+
})
58+
59+
it('places unknown model IDs last, preserving their input order', () => {
60+
const known = Object.keys(getBaseModelProviders())[0]
61+
const ordered = orderModelIdsByReleaseDate(['mystery-a', known, 'mystery-b'])
62+
expect(ordered[0]).toBe(known)
63+
expect(ordered.slice(1)).toEqual(['mystery-a', 'mystery-b'])
64+
})
65+
66+
it('is case-insensitive when matching catalog IDs', () => {
67+
const id = Object.keys(getBaseModelProviders())[0]
68+
const ordered = orderModelIdsByReleaseDate([id.toUpperCase()])
69+
expect(ordered).toEqual([id.toUpperCase()])
70+
})
71+
72+
it('returns an empty array for empty input', () => {
73+
expect(orderModelIdsByReleaseDate([])).toEqual([])
74+
})
75+
76+
it('does not add or drop any IDs', () => {
77+
const input = Object.keys(getBaseModelProviders())
78+
const ordered = orderModelIdsByReleaseDate(input)
79+
expect([...ordered].sort()).toEqual([...input].sort())
80+
})
81+
})

apps/sim/providers/models.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3047,6 +3047,48 @@ export function getProviderModels(providerId: string): string[] {
30473047
return PROVIDER_DEFINITIONS[providerId]?.models.map((m) => m.id) || []
30483048
}
30493049

3050+
/**
3051+
* Reorders catalog model IDs so that, within each provider, newer models (by
3052+
* release date) come first, while preserving the existing provider grouping order.
3053+
*
3054+
* Models without a known release date keep their declaration order and sort after
3055+
* dated models within the same provider. IDs not found in the catalog (e.g.
3056+
* dynamically-discovered provider models) are left in their original order at the end.
3057+
*/
3058+
export function orderModelIdsByReleaseDate(modelIds: string[]): string[] {
3059+
const catalogIndex = new Map<
3060+
string,
3061+
{ providerIndex: number; declIndex: number; releaseTime: number }
3062+
>()
3063+
Object.values(PROVIDER_DEFINITIONS).forEach((provider, providerIndex) => {
3064+
provider.models.forEach((model, declIndex) => {
3065+
const parsed = model.releaseDate ? Date.parse(model.releaseDate) : Number.NaN
3066+
catalogIndex.set(model.id.toLowerCase(), {
3067+
providerIndex,
3068+
declIndex,
3069+
releaseTime: Number.isNaN(parsed) ? Number.NEGATIVE_INFINITY : parsed,
3070+
})
3071+
})
3072+
})
3073+
3074+
return modelIds
3075+
.map((id, inputIndex) => ({ id, inputIndex, meta: catalogIndex.get(id.toLowerCase()) }))
3076+
.sort((a, b) => {
3077+
if (!a.meta || !b.meta) {
3078+
if (!a.meta && !b.meta) return a.inputIndex - b.inputIndex
3079+
return a.meta ? -1 : 1
3080+
}
3081+
if (a.meta.providerIndex !== b.meta.providerIndex) {
3082+
return a.meta.providerIndex - b.meta.providerIndex
3083+
}
3084+
if (a.meta.releaseTime !== b.meta.releaseTime) {
3085+
return b.meta.releaseTime - a.meta.releaseTime
3086+
}
3087+
return a.meta.declIndex - b.meta.declIndex
3088+
})
3089+
.map((entry) => entry.id)
3090+
}
3091+
30503092
export const DYNAMIC_MODEL_PROVIDERS = [
30513093
'ollama',
30523094
'ollama-cloud',

0 commit comments

Comments
 (0)