Skip to content

Commit e9e335b

Browse files
authored
Merge pull request #121 from WordPress/fix/useless-default-models
Sort models per provider based on reasonable heuristics to avoid random default model
2 parents d674c82 + 57902b5 commit e9e335b

File tree

3 files changed

+268
-3
lines changed

3 files changed

+268
-3
lines changed

src/ProviderImplementations/Anthropic/AnthropicModelMetadataDirectory.php

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ protected function parseResponseToModelMetadataList(Response $response): array
109109

110110
$modelsData = (array) $responseData['data'];
111111

112-
return array_values(
112+
$models = array_values(
113113
array_map(
114114
static function (array $modelData) use (
115115
$anthropicCapabilities,
@@ -137,5 +137,102 @@ static function (array $modelData) use (
137137
$modelsData
138138
)
139139
);
140+
141+
usort($models, [$this, 'modelSortCallback']);
142+
143+
return $models;
144+
}
145+
146+
/**
147+
* Callback function for sorting models by ID, to be used with `usort()`.
148+
*
149+
* This method expresses preferences for certain models or model families within the provider by putting them
150+
* earlier in the sorted list. The objective is not to be opinionated about which models are better, but to ensure
151+
* that more commonly used, more recent, or flagship models are presented first to users.
152+
*
153+
* @since n.e.x.t
154+
*
155+
* @param ModelMetadata $a First model.
156+
* @param ModelMetadata $b Second model.
157+
* @return int Comparison result.
158+
*/
159+
protected function modelSortCallback(ModelMetadata $a, ModelMetadata $b): int
160+
{
161+
$aId = $a->getId();
162+
$bId = $b->getId();
163+
164+
// Prefer Claude models over non-Claude models.
165+
if (str_starts_with($aId, 'claude-') && !str_starts_with($bId, 'claude-')) {
166+
return -1;
167+
}
168+
if (str_starts_with($bId, 'claude-') && !str_starts_with($aId, 'claude-')) {
169+
return 1;
170+
}
171+
172+
/*
173+
* Prefer Claude models where the version number isn't the second segment (e.g. 'claude-sonnet-4')
174+
* over those where it is (e.g. 'claude-2', 'claude-3-5-sonnet'). The latter is only used for older models.
175+
*/
176+
if (!preg_match('/^claude-\d/', $aId) && preg_match('/^claude-\d/', $bId)) {
177+
return -1;
178+
}
179+
if (!preg_match('/^claude-\d/', $bId) && preg_match('/^claude-\d/', $aId)) {
180+
return 1;
181+
}
182+
183+
/*
184+
* Prefer Claude models with type and version number (e.g. 'claude-sonnet-4', 'claude-sonnet-4-5-20250929')
185+
* over those without. An optional date suffix may also be present.
186+
*/
187+
$aMatch = preg_match('/^claude-([a-z]+)-(\d(-\d)?)(-[0-9]+)?$/', $aId, $aMatches);
188+
$bMatch = preg_match('/^claude-([a-z]+)-(\d(-\d)?)(-[0-9]+)?$/', $bId, $bMatches);
189+
if ($aMatch && !$bMatch) {
190+
return -1;
191+
}
192+
if ($bMatch && !$aMatch) {
193+
return 1;
194+
}
195+
if ($aMatch && $bMatch) {
196+
// Prefer later model versions.
197+
$aVersion = str_replace('-', '.', $aMatches[2]);
198+
$bVersion = str_replace('-', '.', $bMatches[2]);
199+
if (version_compare($aVersion, $bVersion, '>')) {
200+
return -1;
201+
}
202+
if (version_compare($bVersion, $aVersion, '>')) {
203+
return 1;
204+
}
205+
206+
// Prefer models without a suffix (i.e. base models) over those with a suffix.
207+
if (!isset($aMatches[4]) && isset($bMatches[4])) {
208+
return -1;
209+
}
210+
if (!isset($bMatches[4]) && isset($aMatches[4])) {
211+
return 1;
212+
}
213+
214+
// Prefer 'sonnet' models over other types.
215+
if ($aMatches[1] === 'sonnet' && $bMatches[1] !== 'sonnet') {
216+
return -1;
217+
}
218+
if ($bMatches[1] === 'sonnet' && $aMatches[1] !== 'sonnet') {
219+
return 1;
220+
}
221+
222+
// Prefer later release dates.
223+
if (isset($aMatches[4]) && isset($bMatches[4])) {
224+
$aDate = (int) substr($aMatches[4], 1);
225+
$bDate = (int) substr($bMatches[4], 1);
226+
if ($aDate > $bDate) {
227+
return -1;
228+
}
229+
if ($bDate > $aDate) {
230+
return 1;
231+
}
232+
}
233+
}
234+
235+
// Fallback: Sort alphabetically.
236+
return strcmp($a->getId(), $b->getId());
140237
}
141238
}

src/ProviderImplementations/Google/GoogleModelMetadataDirectory.php

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ protected function parseResponseToModelMetadataList(Response $response): array
167167

168168
$modelsData = (array) $responseData['models'];
169169

170-
return array_values(
170+
$models = array_values(
171171
array_map(
172172
static function (array $modelData) use (
173173
$geminiCapabilities,
@@ -234,5 +234,92 @@ static function (array $modelData) use (
234234
$modelsData
235235
)
236236
);
237+
238+
usort($models, [$this, 'modelSortCallback']);
239+
240+
return $models;
241+
}
242+
243+
/**
244+
* Callback function for sorting models by ID, to be used with `usort()`.
245+
*
246+
* This method expresses preferences for certain models or model families within the provider by putting them
247+
* earlier in the sorted list. The objective is not to be opinionated about which models are better, but to ensure
248+
* that more commonly used, more recent, or flagship models are presented first to users.
249+
*
250+
* @since n.e.x.t
251+
*
252+
* @param ModelMetadata $a First model.
253+
* @param ModelMetadata $b Second model.
254+
* @return int Comparison result.
255+
*/
256+
protected function modelSortCallback(ModelMetadata $a, ModelMetadata $b): int
257+
{
258+
$aId = $a->getId();
259+
$bId = $b->getId();
260+
261+
// Prefer non-experimental models over experimental models.
262+
if (str_contains($aId, '-exp') && !str_contains($bId, '-exp')) {
263+
return 1;
264+
}
265+
if (str_contains($bId, '-exp') && !str_contains($aId, '-exp')) {
266+
return -1;
267+
}
268+
269+
// Prefer non-preview models over preview models.
270+
if (str_contains($aId, '-preview') && !str_contains($bId, '-preview')) {
271+
return 1;
272+
}
273+
if (str_contains($bId, '-preview') && !str_contains($aId, '-preview')) {
274+
return -1;
275+
}
276+
277+
// Prefer Gemini models over non-Gemini models.
278+
if (str_starts_with($aId, 'gemini-') && !str_starts_with($bId, 'gemini-')) {
279+
return -1;
280+
}
281+
if (str_starts_with($bId, 'gemini-') && !str_starts_with($aId, 'gemini-')) {
282+
return 1;
283+
}
284+
285+
// Prefer Gemini models with version numbers (e.g. 'gemini-2.5', 'gemini-2.0') over those without.
286+
$aMatch = preg_match('/^gemini-([0-9.]+)(-[a-z0-9-]+)$/', $aId, $aMatches);
287+
$bMatch = preg_match('/^gemini-([0-9.]+)(-[a-z0-9-]+)$/', $bId, $bMatches);
288+
if ($aMatch && !$bMatch) {
289+
return -1;
290+
}
291+
if ($bMatch && !$aMatch) {
292+
return 1;
293+
}
294+
if ($aMatch && $bMatch) {
295+
// Prefer later model versions.
296+
$aVersion = $aMatches[1];
297+
$bVersion = $bMatches[1];
298+
if (version_compare($aVersion, $bVersion, '>')) {
299+
return -1;
300+
}
301+
if (version_compare($bVersion, $aVersion, '>')) {
302+
return 1;
303+
}
304+
305+
// Prefer '-pro' models over other suffixes.
306+
if ($aMatches[2] === '-pro' && $bMatches[2] !== '-pro') {
307+
return -1;
308+
}
309+
if ($bMatches[2] === '-pro' && $aMatches[2] !== '-pro') {
310+
return 1;
311+
}
312+
313+
// Prefer '-flash' models over other suffixes.
314+
if ($aMatches[2] === '-flash' && $bMatches[2] !== '-flash') {
315+
return -1;
316+
}
317+
if ($bMatches[2] === '-flash' && $aMatches[2] !== '-flash') {
318+
return 1;
319+
}
320+
}
321+
322+
// Fallback: Sort alphabetically.
323+
return strcmp($a->getId(), $b->getId());
237324
}
238325
}

src/ProviderImplementations/OpenAi/OpenAiModelMetadataDirectory.php

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ protected function parseResponseToModelMetadataList(Response $response): array
167167

168168
$modelsData = (array) $responseData['data'];
169169

170-
return array_values(
170+
$models = array_values(
171171
array_map(
172172
static function (array $modelData) use (
173173
$gptCapabilities,
@@ -234,5 +234,86 @@ static function (array $modelData) use (
234234
$modelsData
235235
)
236236
);
237+
238+
usort($models, [$this, 'modelSortCallback']);
239+
240+
return $models;
241+
}
242+
243+
/**
244+
* Callback function for sorting models by ID, to be used with `usort()`.
245+
*
246+
* This method expresses preferences for certain models or model families within the provider by putting them
247+
* earlier in the sorted list. The objective is not to be opinionated about which models are better, but to ensure
248+
* that more commonly used, more recent, or flagship models are presented first to users.
249+
*
250+
* @since n.e.x.t
251+
*
252+
* @param ModelMetadata $a First model.
253+
* @param ModelMetadata $b Second model.
254+
* @return int Comparison result.
255+
*/
256+
protected function modelSortCallback(ModelMetadata $a, ModelMetadata $b): int
257+
{
258+
$aId = $a->getId();
259+
$bId = $b->getId();
260+
261+
// Prefer non-preview models over preview models.
262+
if (str_contains($aId, '-preview') && !str_contains($bId, '-preview')) {
263+
return 1;
264+
}
265+
if (str_contains($bId, '-preview') && !str_contains($aId, '-preview')) {
266+
return -1;
267+
}
268+
269+
// Prefer GPT models over non-GPT models.
270+
if (str_starts_with($aId, 'gpt-') && !str_starts_with($bId, 'gpt-')) {
271+
return -1;
272+
}
273+
if (str_starts_with($bId, 'gpt-') && !str_starts_with($aId, 'gpt-')) {
274+
return 1;
275+
}
276+
277+
// Prefer GPT models with version numbers (e.g. 'gpt-5.1', 'gpt-5') over those without.
278+
$aMatch = preg_match('/^gpt-([0-9.]+)(-[a-z0-9-]+)?$/', $aId, $aMatches);
279+
$bMatch = preg_match('/^gpt-([0-9.]+)(-[a-z0-9-]+)?$/', $bId, $bMatches);
280+
if ($aMatch && !$bMatch) {
281+
return -1;
282+
}
283+
if ($bMatch && !$aMatch) {
284+
return 1;
285+
}
286+
if ($aMatch && $bMatch) {
287+
// Prefer later model versions.
288+
$aVersion = $aMatches[1];
289+
$bVersion = $bMatches[1];
290+
if (version_compare($aVersion, $bVersion, '>')) {
291+
return -1;
292+
}
293+
if (version_compare($bVersion, $aVersion, '>')) {
294+
return 1;
295+
}
296+
297+
// Prefer models without a suffix (i.e. base models) over those with a suffix.
298+
if (!isset($aMatches[2]) && isset($bMatches[2])) {
299+
return -1;
300+
}
301+
if (!isset($bMatches[2]) && isset($aMatches[2])) {
302+
return 1;
303+
}
304+
305+
// Prefer '-mini' models over others with a suffix.
306+
if (isset($aMatches[2]) && isset($bMatches[2])) {
307+
if ($aMatches[2] === '-mini' && $bMatches[2] !== '-mini') {
308+
return -1;
309+
}
310+
if ($bMatches[2] === '-mini' && $aMatches[2] !== '-mini') {
311+
return 1;
312+
}
313+
}
314+
}
315+
316+
// Fallback: Sort alphabetically.
317+
return strcmp($a->getId(), $b->getId());
237318
}
238319
}

0 commit comments

Comments
 (0)