Files
buzz/crates/buzz-agent/src/databricks_label_grammar.rs
Will Pfleger d01e5f8205 feat(agents): humanize uncurated Databricks model ids with a label grammar (#7844)
New Databricks endpoints (Claude Opus 5.5, GPT-6 Astra/Sol, etc.) showed
as raw FQNs in the model picker until someone added a curated corpus
record for each. This adds a strict token-class grammar as a third label
tier inside the Databricks label helpers (Rust `model_capabilities.rs` +
TS `modelCapabilities.ts`): exact record → unique alias → **generated
label** → raw id. In TS it fires only for an explicit `databricks_v2`
provider; providerless ids stay raw.

## Grammar

Takes the last component of a UC FQN (or strips one wrapper:
`databricks-`, `goose-`, `kgoose-`, `builderbot-`; an id that is neither
stays raw), splits on `-`, and classifies each token as version,
letter+digit version, size, trailing date (dropped), or word. Labels are
built only from the id's own text, and the grammar never reinterprets a
version. When in doubt it returns raw:

- **Known families only.** The family (leading letters of the first
token) must be in the manifest's existing `label_family_tokens`. Custom
endpoints such as `prod.ai.rag-agent-3` or `builderbot-pr-reviews-v2`
stay raw. Supporting a new vendor means adding one entry to that list,
not a record per model.
- **Attached digits stay whole** (`gpt10` → `GPT10`). If the family
already carries digits and the next token is a version
(`goose-qwen3-5-122b-a10b`, `system.ai.llama3-1-8b`,
`system.ai.claude3-5-sonnet`), the id stays raw; this is checked before
any reorder.
- A third consecutive number (`gemini-3-7-1-flash`) returns raw rather
than splitting off a patch version.
- Any non-ASCII input returns raw, checked before trimming or case
folding.
- Any unclassifiable token, or no version-bearing token
(`builderbot-pr-reviews`), returns raw. `(latest)`/`Preview` never
appear unless an exact record supplies them.

Special cases, each justified by an unseen-id shape rather than the
corpus score:

- `meta-llama-` → `Llama`: a namespace convention.
- Claude numeric-first reorder: Goose names Claude endpoints
`claude-4-7-opus`. Only 1–2 version tokens followed by
`opus`/`sonnet`/`haiku` reorder (`claude-5-6-opus` → `Claude Opus 5.6`);
anything else keeps its order (`claude-5-6-thinking` → `Claude 5.6
Thinking`). Both current numeric-first ids also have exact records.
- Qwen compact decimal: Qwen's own naming `qwen35` → `Qwen3.5` when the
second digit is nonzero.

## Colliding labels in the picker

The same model is often served from several sources, so humanized labels
collide (four rows are all `GPT-5.5`). `disambiguateModelLabels` in
`formatAgentModelLabel.ts` suffixes only colliding Databricks labels
with their source:

- UC FQN: its `catalog.schema`, e.g. `GPT-6 Astra (system.ai)` / `GPT-6
Astra (sandbox.public)`.
- Workspace endpoint: its wrapper, e.g. `GPT-5.5 (databricks)` /
`GPT-5.5 (goose)` / `GPT-5.5 (kgoose)`.
- Still colliding after that (e.g. a date variant in the same schema):
the full id, e.g. `Gemini 3.7 Flash (system.ai.gemini-3-7-flash-0925)`.

Unique labels, raw-id rows, the default row, and other providers are
unchanged. It runs in `getDiscoveredPersonaModelOptions` (covering
`AgentDropdownSelect`, `PersonaModelCombobox`, `PersonaDropdownField`,
whose closed triggers read the option label) and in `ModelPicker`. Model
search in `AgentDropdownSelect` and `PersonaModelCombobox` also matches
the raw id, so searching `system.ai` finds those rows. Rust
`ModelEntry.name` is unchanged.

## What labels drive

Picker values, persistence, routing, catalog dedup, `resolve()` and
capabilities all stay keyed on the raw id; two ids sharing a label both
remain selectable. Labels do drive picker search (alongside the raw id),
the workspace catalog's equal-time sort, and `TeamIdentityCard`'s "Mixed
models" summary, which compares lowercased base labels, so `goose-` and
`system.ai` GPT-6 Astra read as one model there (correct, since they are
the same model).

## Tests

Shared fixtures in `scripts/databricks-label-fixtures.json` are replayed
by both Rust and TS. Each row asserts its tier: `exact` rows match a
record, `alias` rows resolve with generation off, `generated` rows
return nothing with generation off (so a future alias or record cannot
mask them), and `raw` rows return nothing either way. A curator guard
requires that across all 57 exact records, the ids the grammar does not
reproduce equal exactly the 10 curator-only ids.
`disambiguateModelLabels` has unit tests for namespace, wrapper, and
full-id-fallback collisions, untouched rows, other providers, and
case-insensitive grouping. The defaults-picker e2e checks the suffixed
rows and trigger, raw-id search, and that choosing the second uncurated
FQN by its visible name persists its raw id and restores its label. An
edit-agent e2e covers the existing-agent model dropdown
(`#edit-agent-model`): both suffixed rows render, the `system.ai` row is
chosen by name, and after save and reopen the agent stores the
`system.ai` id and shows `GPT-6 Astra (system.ai)`.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-09-23 17:12:02 -04:00

282 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Deterministic display-label grammar for Databricks endpoint ids.
//!
//! The last tier of [`crate::model_capabilities::databricks_registry_label`],
//! reached only after an exact-record miss and a unique-alias miss. The result
//! is either a label built solely from the id's own tokens or `None`, in which
//! case callers show the raw id. It is presentation-only: capabilities, wire
//! routing, and the saved model id never depend on it. Ids from families not
//! in the manifest's `label_family_tokens` stay raw; supporting a new vendor
//! means adding one entry to that list, not a record per model.
//!
//! Mirrored by `desktop/src/features/agents/ui/modelCapabilities.ts`; both
//! replay `scripts/databricks-label-fixtures.json`.
use crate::model_capabilities::is_databricks_model_service_fqn;
/// Endpoint-name wrappers that precede the model name in Databricks ids.
const WRAPPERS: [&str; 4] = ["databricks-", "goose-", "kgoose-", "builderbot-"];
enum Part {
/// Numeric version (`5`, `5.5`) — the one part GPT/GLM join with a hyphen.
Version(String),
Word(String),
}
/// Parse a Databricks endpoint id into a display label, or `None` when any part
/// of the id falls outside the grammar. Only families listed in the manifest's
/// `label_family_tokens` are named, so custom endpoints (`rag-agent-3`) stay raw.
pub(crate) fn generate_databricks_label(
raw_model_id: &str,
family_tokens: &[String],
) -> Option<String> {
// Refuse non-ASCII before trimming or folding, so no Unicode case or
// whitespace rule can turn an unsupported id into an ASCII-looking one.
if !raw_model_id.is_ascii() {
return None;
}
let id = raw_model_id.trim().to_ascii_lowercase();
let is_fqn = is_databricks_model_service_fqn(&id);
let service = if is_fqn {
id.rsplit('.').next()?
} else {
id.as_str()
};
// Only a UC FQN or a wrapped endpoint name is attributable to Databricks; a
// bare id such as `gpt-5` stays raw.
let body = match WRAPPERS.iter().find_map(|w| service.strip_prefix(w)) {
Some(body) => body,
None if is_fqn => service,
None => return None,
};
let body = body
.strip_prefix("meta-")
.filter(|rest| rest.starts_with("llama-"))
.unwrap_or(body);
let mut tokens = body.split('-');
let (family, stem) = split_family(tokens.next()?)?;
if !family_tokens
.iter()
.any(|t| t.strip_suffix('-').unwrap_or(t) == family)
{
return None;
}
let mut rest: Vec<&str> = tokens.collect();
// Attached digits followed by a version (`qwen3-5`, `llama3-1`) cannot be
// read without guessing; checked before any reorder.
if stem.is_some() && rest.first().is_some_and(|t| is_version(t)) {
return None;
}
if family == "claude" {
// Goose's numeric-first Claude ids (`claude-4-7-opus`) name the tier
// after the version. Reorder only that exact shape; any other token
// after the version keeps its position.
let digits = rest.iter().take_while(|t| is_version(t)).count();
if (1..=2).contains(&digits)
&& rest
.get(digits)
.is_some_and(|t| matches!(*t, "opus" | "sonnet" | "haiku"))
{
rest[..=digits].rotate_right(1);
}
}
let mut parts = Vec::new();
let mut has_number = stem.is_some();
let mut after_minor = false;
let mut rest = rest.into_iter().peekable();
while let Some(tok) = rest.next() {
if is_date(tok) && rest.peek().is_none() {
break;
}
let part = if is_version(tok) {
let minor = rest.next_if(|t| is_version(t));
after_minor = minor.is_some();
if after_minor && rest.peek().is_some_and(|t| is_version(t)) {
// A third number (`3-7-1`) would read as a separate version.
return None;
}
Part::Version(minor.map_or_else(|| tok.to_string(), |m| format!("{tok}.{m}")))
} else if is_letter_version(tok) {
let minor = rest.next_if(|t| is_version(t));
if minor.is_some() && rest.peek().is_some_and(|t| is_version(t)) {
return None;
}
let major = capitalize(tok);
Part::Word(minor.map_or_else(|| major.clone(), |m| format!("{major}.{m}")))
} else if is_size(tok) {
Part::Word(tok.to_ascii_uppercase())
} else if !tok.is_empty() && tok.bytes().all(|b| b.is_ascii_lowercase()) {
Part::Word(match tok {
"oss" => "OSS".to_string(),
"mini" | "nano" if family == "gpt" && after_minor => tok.to_string(),
_ => capitalize(tok),
})
} else {
return None;
};
has_number |= match &part {
Part::Version(_) => true,
Part::Word(word) => word.bytes().any(|b| b.is_ascii_digit()),
};
parts.push(part);
}
// A versionless id (`builderbot-pr-reviews`) is a named endpoint, not a model.
if !has_number {
return None;
}
let (brand, hyphenated) = match family {
"gpt" => ("GPT".to_string(), true),
"glm" => ("GLM".to_string(), true),
"deepseek" => ("DeepSeek".to_string(), false),
_ => (capitalize(family), false),
};
let mut label = brand + &stem.unwrap_or_default();
for (index, part) in parts.iter().enumerate() {
let (sep, text) = match part {
Part::Version(v) if index == 0 && hyphenated => ('-', v),
Part::Version(text) | Part::Word(text) => (' ', text),
};
label.push(sep);
label.push_str(text);
}
Some(label)
}
/// Split the leading token into its family name and an optional in-name
/// version, kept whole (`nova12` → `12`). The one exception is Qwen's own
/// compact-decimal naming: `qwen35` → `3.5` when the second digit is nonzero.
fn split_family(tok: &str) -> Option<(&str, Option<String>)> {
let alpha = tok.bytes().take_while(u8::is_ascii_lowercase).count();
let (family, digits) = tok.split_at(alpha);
if family.is_empty() || digits.len() > 2 || !digits.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
let stem = match digits.as_bytes() {
[] => None,
[major, minor] if family == "qwen" && *minor != b'0' => {
Some(format!("{}.{}", *major as char, *minor as char))
}
_ => Some(digits.to_string()),
};
Some((family, stem))
}
/// `0` or a 1–2-digit number without a leading zero.
fn is_version(tok: &str) -> bool {
match tok.as_bytes() {
[d] => d.is_ascii_digit(),
[a, b] => (b'1'..=b'9').contains(a) && b.is_ascii_digit(),
_ => false,
}
}
/// A 4- or 8-digit date stamp (`0731`, `20260101`).
fn is_date(tok: &str) -> bool {
matches!(tok.len(), 4 | 8) && tok.bytes().all(|b| b.is_ascii_digit())
}
/// A letter-prefixed version such as `v4` or `k2`.
fn is_letter_version(tok: &str) -> bool {
matches!(tok.as_bytes(), [l, digits @ ..] if l.is_ascii_lowercase()
&& !digits.is_empty()
&& digits.iter().all(u8::is_ascii_digit))
}
/// A parameter-size token: `120b`, or letter-prefixed `a3b`.
fn is_size(tok: &str) -> bool {
let Some(body) = tok.strip_suffix('b') else {
return false;
};
let digits = body
.strip_prefix(|c: char| c.is_ascii_lowercase())
.unwrap_or(body);
!digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit())
}
fn capitalize(word: &str) -> String {
let mut chars = word.chars();
chars.next().map_or_else(String::new, |first| {
first.to_ascii_uppercase().to_string() + chars.as_str()
})
}
#[cfg(test)]
mod tests {
use crate::model_capabilities::{databricks_curated_label, databricks_registry_label};
use serde_json::Value;
const FIXTURES_JSON: &str = include_str!("../../../scripts/databricks-label-fixtures.json");
const MANIFEST_JSON: &str = include_str!("../../../scripts/model-capabilities.json");
fn generate(id: &str) -> Option<String> {
let family_tokens: Vec<String> =
serde_json::from_value(json(MANIFEST_JSON)["label_family_tokens"].clone())
.expect("label_family_tokens");
super::generate_databricks_label(id, &family_tokens)
}
fn json(source: &str) -> Value {
serde_json::from_str(source).expect("fixture JSON must parse")
}
/// `(raw_model_id, registry_label)` for every `databricks_v2` exact record.
fn exact_records() -> Vec<(String, String)> {
json(MANIFEST_JSON)["exact_records"]
.as_array()
.expect("exact_records")
.iter()
.filter(|rec| rec["provider"] == "databricks_v2")
.map(|rec| {
let field = |key: &str| rec[key].as_str().expect(key).to_string();
(field("raw_model_id"), field("registry_label"))
})
.collect()
}
#[test]
fn shared_fixtures_replay_through_the_label_helper() {
let fixtures = json(FIXTURES_JSON);
let records = exact_records();
for row in fixtures["lookup"].as_array().expect("lookup") {
let id = row["id"].as_str().expect("id");
let label = row["label"].as_str();
assert_eq!(databricks_registry_label(id).as_deref(), label, "id={id:?}");
let has_exact = records.iter().any(|(raw, _)| raw.eq_ignore_ascii_case(id));
let curated = databricks_curated_label(id);
let tier = row["tier"].as_str().expect("tier");
assert_eq!(has_exact, tier == "exact", "tier={tier} id={id:?}");
match tier {
"exact" | "alias" => assert_eq!(curated.as_deref(), label, "id={id:?}"),
"generated" => {
assert_eq!(curated, None, "generated fixture is masked: {id:?}");
assert_eq!(generate(id).as_deref(), label, "id={id:?}");
}
"raw" => {
assert_eq!(label, None, "raw fixture has a label: {id:?}");
assert_eq!(curated, None, "raw fixture is curated: {id:?}");
}
tier => panic!("unknown tier {tier:?} for {id}"),
}
}
}
#[test]
fn grammar_reproduces_every_curated_label_except_curator_only_ones() {
let fixtures = json(FIXTURES_JSON);
let curator_only: Vec<&str> = fixtures["curator_only_labels"]
.as_array()
.expect("curator_only_labels")
.iter()
.map(|id| id.as_str().expect("id"))
.collect();
let misses: Vec<String> = exact_records()
.into_iter()
.filter(|(raw, label)| generate(raw).as_deref() != Some(label))
.map(|(raw, _)| raw)
.collect();
assert_eq!(misses, curator_only);
}
}