Skip to content

Commit 3631890

Browse files
authored
Merge branch 'release-1.11.0' into cz/hitl-feedbacks
2 parents 55f2328 + c487d31 commit 3631890

2 files changed

Lines changed: 106 additions & 22 deletions

File tree

src/lfx/src/lfx/base/models/models_dev_catalog.py

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@
8080
# models.dev. OpenRouter is omitted because it's live-fetched per-user.
8181
}
8282

83+
_GOOGLE_GENERATIVE_AI_PROVIDER = "Google Generative AI"
84+
_GOOGLE_MODEL_PREFIX = "models/"
85+
86+
87+
def _catalog_model_identity(provider: str, model: dict[str, Any]) -> tuple[str | None, str]:
88+
"""Return a provider-aware identity for matching static and models.dev rows."""
89+
name = model.get("name")
90+
if provider == _GOOGLE_GENERATIVE_AI_PROVIDER and isinstance(name, str):
91+
name = name.removeprefix(_GOOGLE_MODEL_PREFIX)
92+
return name, model.get("model_type") or "llm"
93+
8394

8495
def _snapshot_dir() -> Path:
8596
"""Return the directory where the disk snapshot lives.
@@ -352,18 +363,18 @@ def apply_models_dev_overrides(
352363
the fresher metadata (context windows, pricing, capabilities) is preserved.
353364
354365
models.dev exposes no ``deprecated`` field of its own, so this function
355-
preserves the static-list curation by name: any model that was already
356-
flagged deprecated in the bundled ``*_constants.py`` lists keeps that flag
357-
after the override. Dated-snapshot ids
366+
preserves the static-list curation by catalog identity: any model that was
367+
already flagged deprecated in the bundled ``*_constants.py`` lists keeps
368+
that flag after the override. Dated-snapshot ids
358369
(e.g. ``claude-opus-4-5-20251101``, ``gpt-4o-2024-05-13``) and language
359370
models whose most recent date is older than :data:`_AGE_DEPRECATION_DAYS` are also
360371
auto-flagged in :func:`_translate_model_entry`. ``now`` is forwarded for
361372
testability.
362373
"""
363374
now = now or datetime.now(tz=timezone.utc)
364-
# Build provider_name -> {model_name: deprecated} from the static lists so
365-
# we can preserve the static curation through the override.
366-
static_deprecated_by_provider: dict[str, set[str]] = {}
375+
# Build provider_name -> {model identity} from the static lists so we can
376+
# preserve the static curation through the override.
377+
static_deprecated_by_provider: dict[str, set[tuple[str | None, str]]] = {}
367378
for group in static_lists:
368379
for entry in group:
369380
if not isinstance(entry, dict):
@@ -373,7 +384,7 @@ def apply_models_dev_overrides(
373384
if not provider or not name:
374385
continue
375386
if entry.get("deprecated"):
376-
static_deprecated_by_provider.setdefault(provider, set()).add(name)
387+
static_deprecated_by_provider.setdefault(provider, set()).add(_catalog_model_identity(provider, entry))
377388

378389
# Build provider_name -> translated list once.
379390
overrides: dict[str, list[dict[str, Any]]] = {}
@@ -382,15 +393,12 @@ def apply_models_dev_overrides(
382393
if not isinstance(provider_block, dict):
383394
continue
384395
static_deprecated = static_deprecated_by_provider.get(provider_name, set())
385-
translated = [
386-
_translate_model_entry(
387-
provider_name,
388-
m,
389-
deprecated=(m.get("id") in static_deprecated),
390-
now=now,
391-
)
392-
for m in _provider_model_dicts(provider_block)
393-
]
396+
translated = []
397+
for model in _provider_model_dicts(provider_block):
398+
translated_model = _translate_model_entry(provider_name, model, now=now)
399+
if _catalog_model_identity(provider_name, translated_model) in static_deprecated:
400+
translated_model["deprecated"] = True
401+
translated.append(translated_model)
394402
if translated:
395403
overrides[provider_name] = translated
396404

@@ -403,13 +411,30 @@ def _fold_custom_static_entries(provider: str, group: list[dict[str, Any]]) -> N
403411
models.dev only knows the models it ships, so any model a user added to
404412
the bundled ``*_constants.py`` lists would otherwise be silently dropped
405413
when its provider's group is replaced. Carrying those rows over (matched
406-
by name) keeps user-added models selectable while models.dev still wins
407-
for every model it does cover. The override list is mutated in place so a
408-
later same-provider group (e.g. the OpenAI embeddings group) folds its
409-
custom rows into the same list already appended to ``replaced``.
414+
by provider-aware identity) keeps user-added models selectable while
415+
models.dev still wins for every model it does cover. The override list
416+
is mutated in place so a later same-provider group (e.g. the OpenAI
417+
embeddings group) folds its custom rows into the same list already
418+
appended to ``replaced``.
410419
"""
411-
override_names = {m.get("name") for m in overrides[provider] if isinstance(m, dict)}
412-
custom_entries = [m for m in group if isinstance(m, dict) and m.get("name") not in override_names]
420+
overrides_by_identity = {
421+
_catalog_model_identity(provider, model): model for model in overrides[provider] if isinstance(model, dict)
422+
}
423+
custom_entries = []
424+
for static_model in group:
425+
if not isinstance(static_model, dict):
426+
continue
427+
override_model = overrides_by_identity.get(_catalog_model_identity(provider, static_model))
428+
if override_model is None:
429+
custom_entries.append(static_model)
430+
continue
431+
432+
static_name = static_model.get("name")
433+
if provider == _GOOGLE_GENERATIVE_AI_PROVIDER and static_name != override_model.get("name"):
434+
override_model["name"] = static_name
435+
if static_icon := static_model.get("icon"):
436+
override_model["icon"] = static_icon
437+
413438
if custom_entries:
414439
overrides[provider].extend(custom_entries)
415440

src/lfx/tests/unit/base/models/test_models_dev_catalog.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,65 @@ def test_apply_overrides_preserves_custom_static_entries_for_covered_provider():
271271
assert old_one.get("created") is not None # came through _translate_model_entry
272272

273273

274+
def test_apply_overrides_deduplicates_google_embedding_prefix_aliases():
275+
"""Google ``models/`` aliases collapse to the established static identity."""
276+
from lfx.base.models.models_dev_catalog import apply_models_dev_overrides
277+
278+
static_google_embeddings = [
279+
{
280+
"provider": "Google Generative AI",
281+
"name": "models/gemini-embedding-001",
282+
"icon": "GoogleGenerativeAI",
283+
"model_type": "embeddings",
284+
"deprecated": False,
285+
},
286+
{
287+
"provider": "Google Generative AI",
288+
"name": "models/text-embedding-004",
289+
"icon": "GoogleGenerativeAI",
290+
"model_type": "embeddings",
291+
"deprecated": True,
292+
},
293+
{
294+
"provider": "Google Generative AI",
295+
"name": "models/custom-embedding",
296+
"icon": "GoogleGenerativeAI",
297+
"model_type": "embeddings",
298+
"deprecated": False,
299+
},
300+
]
301+
snapshot = {
302+
"google": {
303+
"id": "google",
304+
"models": {
305+
"gemini-embedding-001": {
306+
"id": "gemini-embedding-001",
307+
"family": "gemini",
308+
"limit": {"context": 2048},
309+
},
310+
"text-embedding-004": {
311+
"id": "text-embedding-004",
312+
"family": "text-embedding",
313+
},
314+
},
315+
}
316+
}
317+
318+
result = apply_models_dev_overrides([static_google_embeddings], snapshot)
319+
models = [model for group in result for model in group]
320+
matching_gemini = [model for model in models if model["name"].removeprefix("models/") == "gemini-embedding-001"]
321+
matching_text = [model for model in models if model["name"].removeprefix("models/") == "text-embedding-004"]
322+
323+
assert len(matching_gemini) == 1
324+
assert matching_gemini[0]["name"] == "models/gemini-embedding-001"
325+
assert matching_gemini[0]["icon"] == "GoogleGenerativeAI"
326+
assert matching_gemini[0]["context_window"] == 2048
327+
assert len(matching_text) == 1
328+
assert matching_text[0]["name"] == "models/text-embedding-004"
329+
assert matching_text[0]["deprecated"] is True
330+
assert any(model["name"] == "models/custom-embedding" for model in models)
331+
332+
274333
def test_apply_overrides_handles_missing_or_invalid_release_date():
275334
"""release_date is best-effort: ISO YYYY-MM-DD parses, anything else → 0.
276335

0 commit comments

Comments
 (0)