mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-12 08:42:18 +00:00
fix(cost-map): keep alias_of rows out of alias lists and add them to the registry schema
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
42e31fcc08
commit
91e6cf2c27
6 changed files with 55 additions and 15 deletions
|
|
@ -189,13 +189,6 @@ impl Catalog {
|
|||
}
|
||||
|
||||
let mut aliases: IndexMap<String, String> = IndexMap::new();
|
||||
for (alias, target) in pointer_rows {
|
||||
if entries.contains_key(&target) {
|
||||
aliases.insert(alias, target);
|
||||
} else {
|
||||
problems.push(Error::from_key(&alias, Repr::AliasTargetMissing { target }));
|
||||
}
|
||||
}
|
||||
for (model, names) in alias_lists {
|
||||
for alias in names {
|
||||
if entries.contains_key(&alias) {
|
||||
|
|
@ -213,6 +206,21 @@ impl Catalog {
|
|||
}
|
||||
}
|
||||
}
|
||||
for (alias, target) in pointer_rows {
|
||||
if !entries.contains_key(&target) {
|
||||
problems.push(Error::from_key(&alias, Repr::AliasTargetMissing { target }));
|
||||
} else if let Some(taken_by) = aliases.get(&alias) {
|
||||
problems.push(Error::from_key(
|
||||
&alias,
|
||||
Repr::AliasTaken {
|
||||
alias: alias.clone(),
|
||||
taken_by: taken_by.clone(),
|
||||
},
|
||||
));
|
||||
} else {
|
||||
aliases.insert(alias, target);
|
||||
}
|
||||
}
|
||||
|
||||
let fallback_generalizations = sections
|
||||
.iter()
|
||||
|
|
@ -362,8 +370,7 @@ impl Catalog {
|
|||
let mut catalog = self;
|
||||
catalog.store.edit(|rows| {
|
||||
rows.entries.retain(|name, _| !rejected.contains(name));
|
||||
rows.aliases
|
||||
.retain(|alias, owner| !rejected.contains(owner) && !rejected.contains(alias));
|
||||
rows.aliases.retain(|_, owner| !rejected.contains(owner));
|
||||
});
|
||||
catalog
|
||||
.sections
|
||||
|
|
@ -436,7 +443,6 @@ impl Catalog {
|
|||
}
|
||||
}
|
||||
|
||||
/// One row as read: a model, or an `alias_of` pointer at one
|
||||
struct RowRead {
|
||||
model: Option<Model>,
|
||||
aliases: Option<Vec<String>>,
|
||||
|
|
|
|||
|
|
@ -104,9 +104,18 @@ pub fn registry_json_schema() -> Schema {
|
|||
"title".into(),
|
||||
"LiteLLM model prices and context window registry".into(),
|
||||
);
|
||||
definitions.insert(
|
||||
"aliasRow".into(),
|
||||
json!({
|
||||
"type": "object",
|
||||
"required": ["alias_of"],
|
||||
"properties": {"alias_of": {"type": "string"}},
|
||||
"additionalProperties": false,
|
||||
}),
|
||||
);
|
||||
root.insert(
|
||||
"additionalProperties".into(),
|
||||
json!({"$ref": "#/$defs/modelEntry"}),
|
||||
json!({"anyOf": [{"$ref": "#/$defs/modelEntry"}, {"$ref": "#/$defs/aliasRow"}]}),
|
||||
);
|
||||
root.insert("$defs".into(), Value::Object(definitions));
|
||||
Schema::from(root.clone())
|
||||
|
|
@ -153,6 +162,7 @@ fn decorate_model_entry(schema: &mut Value) {
|
|||
let rates = json!({FLAT_KEY_PATTERN: {"type": "number", "minimum": 0}});
|
||||
let object = schema.as_object_mut().unwrap();
|
||||
object.insert("required".into(), json!(["litellm_provider"]));
|
||||
object.insert("not".into(), json!({"required": ["alias_of"]}));
|
||||
object.insert("additionalProperties".into(), Value::Bool(true));
|
||||
object.insert(
|
||||
"patternProperties".into(),
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ fn a_bad_entry_is_reported_by_model_and_key_and_kept_raw() {
|
|||
)]
|
||||
#[case::alias_of_claimed_by_a_list(
|
||||
json!({"Real": {"litellm_provider": "t"}, "real-latest": {"alias_of": "Real"}, "Other": {"litellm_provider": "t", "aliases": ["real-latest"]}}),
|
||||
r#"Other: alias "real-latest" already belongs to Real"#
|
||||
r#"real-latest: alias "real-latest" already belongs to Other"#
|
||||
)]
|
||||
#[case::aliases_not_a_list(
|
||||
json!({"a": {"litellm_provider": "t", "aliases": "bad"}}),
|
||||
|
|
@ -450,3 +450,18 @@ fn a_dangling_alias_of_row_is_rejected_and_dropped_from_the_written_document() {
|
|||
assert!(catalog.lookup("ghost-latest").is_none());
|
||||
assert_eq!(written(&catalog), r#"{"Real":{"litellm_provider":"t"}}"#);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn a_pointer_row_that_repeats_a_listed_alias_is_dropped_and_the_model_is_kept() {
|
||||
let parsed = loaded(json!({
|
||||
"Real": {"litellm_provider": "t", "aliases": ["real-latest"]},
|
||||
"real-latest": {"alias_of": "Real"}
|
||||
}));
|
||||
let (catalog, removed) = parsed.value.without_rejected(&parsed.errors);
|
||||
assert_eq!(removed.len(), 1);
|
||||
assert_eq!(catalog.lookup("real-latest").unwrap().canonical_key, "Real");
|
||||
assert_eq!(
|
||||
written(&catalog),
|
||||
r#"{"Real":{"litellm_provider":"t","aliases":["real-latest"]}}"#
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ fn generated_registry_schema_validates_checked_in_catalog() {
|
|||
#[case(json!({"example": {"litellm_provider": "test"}}))]
|
||||
#[case(json!({"example": {"litellm_provider": "test", "future_field": true}}))]
|
||||
#[case(json!({"sample_spec": {"litellm_provider": "placeholder"}}))]
|
||||
#[case(json!({"example": {"litellm_provider": "test"}, "example-latest": {"alias_of": "example"}}))]
|
||||
fn generated_registry_schema_keeps_reader_compatibility(#[case] document: Value) {
|
||||
assert!(registry_validator().is_valid(&document));
|
||||
}
|
||||
|
|
@ -54,6 +55,9 @@ fn generated_registry_schema_keeps_reader_compatibility(#[case] document: Value)
|
|||
#[case::empty_windows(json!({"litellm_provider": "test", "off_peak_pricing": {"windows": []}}))]
|
||||
#[case::invalid_weekday(json!({"litellm_provider": "test", "off_peak_pricing": {"windows": [{"hours_utc": "00:00-01:00", "weekdays": [0]}]}}))]
|
||||
#[case::invalid_aliases(json!({"litellm_provider": "test", "aliases": "wrong"}))]
|
||||
#[case::alias_of_on_a_model(json!({"litellm_provider": "test", "alias_of": "other"}))]
|
||||
#[case::alias_row_with_other_fields(json!({"alias_of": "other", "mode": "chat"}))]
|
||||
#[case::alias_of_not_a_string(json!({"alias_of": 4}))]
|
||||
#[case::non_object_model(json!(4))]
|
||||
fn generated_registry_schema_rejects_invalid_entries(#[case] entry: Value) {
|
||||
assert!(!registry_validator().is_valid(&json!({"example": entry})));
|
||||
|
|
|
|||
|
|
@ -606,7 +606,7 @@ def _expand_model_aliases(model_cost: dict[str, dict[str, object]]) -> dict[str,
|
|||
|
||||
for model_name, model_info in model_cost.items():
|
||||
aliases: object = model_info.get("aliases")
|
||||
if aliases is None:
|
||||
if aliases is None or model_name in pointer_rows:
|
||||
continue
|
||||
keys_with_aliases.append(model_name)
|
||||
if not isinstance(aliases, list):
|
||||
|
|
|
|||
|
|
@ -130,12 +130,15 @@ def test_finalize_pops_key_and_installs_rules():
|
|||
set_fallback_generalizations(previous)
|
||||
|
||||
|
||||
def test_alias_of_rows_are_served_as_their_target_and_dangling_ones_are_dropped(caplog) -> None:
|
||||
def test_alias_of_rows_are_served_as_their_target_and_dangling_ones_are_dropped(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
raw: Final = {
|
||||
"real": {"litellm_provider": "openai", "mode": "chat", "input_cost_per_token": 1e-6},
|
||||
"real-latest": {"alias_of": "real"},
|
||||
"ghost-latest": {"alias_of": "ghost"},
|
||||
"chain-latest": {"alias_of": "real-latest"},
|
||||
"listed-latest": {"alias_of": "missing", "aliases": ["extra"]},
|
||||
}
|
||||
with caplog.at_level("WARNING"):
|
||||
finalized: Final = _finalize_model_cost_map(raw)
|
||||
|
|
@ -143,7 +146,9 @@ def test_alias_of_rows_are_served_as_their_target_and_dangling_ones_are_dropped(
|
|||
assert "alias_of" not in finalized["real"]
|
||||
assert set(finalized) == {"real", "real-latest"}
|
||||
assert finalized["real-latest"]["litellm_provider"] == "openai"
|
||||
assert {"ghost-latest", "chain-latest"} <= {record.getMessage().split("'")[1] for record in caplog.records}
|
||||
assert {"ghost-latest", "chain-latest", "listed-latest"} == {
|
||||
record.getMessage().split("'")[1] for record in caplog.records
|
||||
}
|
||||
|
||||
|
||||
def test_finalize_with_no_block_clears_rules():
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue