mirror of
https://github.com/block/buzz.git
synced 2026-11-07 09:52:03 +00:00
feat(catalog): replace Databricks model-label tokenizer with models.dev registry lookup
The frontend heuristic in #3586 title-cased every word and converted adjacent digit pairs to decimals for any string starting with 'databricks-'. This invented false labels for custom workspace endpoints: 'databricks-team-2025-01' → 'Team 2025.01', 'databricks-finance-2025-01-30' → 'Finance 2025.01 30'. Replace with a lookup-and-pass-through approach drawn from the same design goose uses in production: - Generator script (scripts/generate-databricks-model-names.py) fetches https://models.dev/api.json and emits a sorted Rust static slice of (id, name) pairs under crates/buzz-agent/src/databricks_model_names.rs. Refresh by rerunning the script and committing the diff. - catalog.rs consults the table via databricks_model_name(id): known managed endpoints get curated names (e.g. 'databricks-gpt-5-5' → 'GPT-5.5'), unknown/custom endpoints return their raw ID unchanged. Applied to all ModelEntry construction paths: v1/v2 discovery, the empty-list fallback, and discovery_failure_fallback. - Frontend: a matching TS registry (desktop/src/features/agents/lib/ databricksModelNames.ts) covers persisted raw IDs on cards, rows, and popovers that render before discovery data is available. formatAgentModelLabel and formatDefaultModelLabel use the registry; no heuristic string mangling exists anywhere in the TS layer. - AGENTS.md documents the three-tier label precedence: API/runtime name first, table-backed fallback second, raw ID last. Supersedes #3586 (kennylopez-model-display-labels). Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
parent
324bd6b464
commit
144ef4d9d0
@@ -143,3 +143,10 @@ matches the code is worse than no rule; a new pattern that isn't written down
|
||||
here will be broken by the next agent that never learns it existed. Reviewers:
|
||||
treat a config-behavior diff without a matching AGENTS.md diff (or an explicit
|
||||
"no rules changed" note) as incomplete.
|
||||
|
||||
## Model display-name precedence
|
||||
|
||||
Labels shown in cards, pickers, and popovers follow a three-tier cascade:
|
||||
1. **API/runtime `name`** — `AgentModelInfo.name` from discovery (`AgentModelsResponse`). This is the authoritative source; all providers populate it at discover time (`openai_model_display_name`, Anthropic `display_name`, ACP runtime name, Databricks registry lookup).
|
||||
2. **Table-backed fallback** — for persisted raw Databricks endpoint IDs that render before discovery data is available, `databricksModelName(id)` in `desktop/src/features/agents/lib/databricksModelNames.ts` does a static lookup against the models.dev-seeded registry. Refresh by rerunning `scripts/generate-databricks-model-names.py`.
|
||||
3. **Raw ID** — any ID not covered by tiers 1 or 2 renders unchanged. No heuristic string mangling.
|
||||
|
||||
@@ -56,3 +56,59 @@ test("resolveAgentCardModelLabel — non-inherited agent with a blank resolved m
|
||||
});
|
||||
assert.equal(label, "Default model (claude-sonnet)");
|
||||
});
|
||||
|
||||
// Databricks registry integration
|
||||
import { formatAgentModelLabel } from "./formatAgentModelLabel.ts";
|
||||
|
||||
test("formatAgentModelLabel — known Databricks managed ID returns curated name", () => {
|
||||
assert.equal(formatAgentModelLabel("databricks-gpt-5-5"), "GPT-5.5");
|
||||
assert.equal(
|
||||
formatAgentModelLabel("databricks-claude-opus-4-7"),
|
||||
"Claude Opus 4.7",
|
||||
);
|
||||
});
|
||||
|
||||
test("formatAgentModelLabel — unknown custom Databricks ID returns raw ID unchanged", () => {
|
||||
assert.equal(
|
||||
formatAgentModelLabel("databricks-team-2025-01"),
|
||||
"databricks-team-2025-01",
|
||||
);
|
||||
});
|
||||
|
||||
test("formatAgentModelLabel — non-Databricks ID returns raw ID unchanged", () => {
|
||||
assert.equal(formatAgentModelLabel("claude-sonnet-4-7"), "claude-sonnet-4-7");
|
||||
assert.equal(formatAgentModelLabel("gpt-4o"), "gpt-4o");
|
||||
});
|
||||
|
||||
test("formatAgentModelLabel — null or empty returns Auto", () => {
|
||||
assert.equal(formatAgentModelLabel(null), "Auto");
|
||||
assert.equal(formatAgentModelLabel(""), "Auto");
|
||||
assert.equal(formatAgentModelLabel(" "), "Auto");
|
||||
});
|
||||
|
||||
test("resolveAgentCardModelLabel — known Databricks defaultModel renders curated name in default label", () => {
|
||||
const label = resolveAgentCardModelLabel({
|
||||
agent: undefined,
|
||||
personaModel: null,
|
||||
defaultModel: "databricks-gpt-5-5",
|
||||
});
|
||||
assert.equal(label, "Default model (GPT-5.5)");
|
||||
});
|
||||
|
||||
test("resolveAgentCardModelLabel — unknown custom Databricks defaultModel renders raw ID in default label", () => {
|
||||
const label = resolveAgentCardModelLabel({
|
||||
agent: undefined,
|
||||
personaModel: null,
|
||||
defaultModel: "databricks-team-2025-01",
|
||||
});
|
||||
assert.equal(label, "Default model (databricks-team-2025-01)");
|
||||
});
|
||||
|
||||
test("resolveAgentCardModelLabel — known Databricks agent model renders curated name", () => {
|
||||
const label = resolveAgentCardModelLabel({
|
||||
agent: { modelSource: "definition", model: "databricks-gpt-oss-120b" },
|
||||
personaModel: null,
|
||||
defaultModel: "something-else",
|
||||
});
|
||||
assert.equal(label, "GPT OSS 120B");
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { formatAgentModelLabel } from "./formatAgentModelLabel";
|
||||
import { databricksModelName } from "./databricksModelNames";
|
||||
import type { ManagedAgent } from "@/shared/api/types";
|
||||
|
||||
/**
|
||||
@@ -40,5 +41,7 @@ export function resolveAgentCardModelLabel(input: {
|
||||
|
||||
export function formatDefaultModelLabel(defaultModel: string) {
|
||||
const model = defaultModel.trim();
|
||||
return model ? `Default model (${model})` : "Default model";
|
||||
return model
|
||||
? `Default model (${databricksModelName(model)})`
|
||||
: "Default model";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
databricksModelName,
|
||||
DATABRICKS_MODEL_NAMES,
|
||||
} from "./databricksModelNames.ts";
|
||||
|
||||
test("databricksModelName — known managed endpoint returns curated name", () => {
|
||||
assert.equal(databricksModelName("databricks-gpt-5-5"), "GPT-5.5");
|
||||
assert.equal(
|
||||
databricksModelName("databricks-claude-opus-4-7"),
|
||||
"Claude Opus 4.7",
|
||||
);
|
||||
assert.equal(databricksModelName("databricks-gpt-oss-120b"), "GPT OSS 120B");
|
||||
});
|
||||
|
||||
test("databricksModelName — unknown custom endpoint returns raw ID unchanged", () => {
|
||||
// A workspace-specific or date-stamped endpoint that isn't in the registry
|
||||
// must never be guessed — return it verbatim.
|
||||
assert.equal(
|
||||
databricksModelName("databricks-team-2025-01"),
|
||||
"databricks-team-2025-01",
|
||||
);
|
||||
assert.equal(
|
||||
databricksModelName("databricks-finance-2025-01-30"),
|
||||
"databricks-finance-2025-01-30",
|
||||
);
|
||||
assert.equal(
|
||||
databricksModelName("some-custom-workspace-model"),
|
||||
"some-custom-workspace-model",
|
||||
);
|
||||
});
|
||||
|
||||
test("databricksModelName — empty string returns empty string", () => {
|
||||
assert.equal(databricksModelName(""), "");
|
||||
});
|
||||
|
||||
test("DATABRICKS_MODEL_NAMES — registry is non-empty and all values are non-empty strings", () => {
|
||||
const entries = Object.entries(DATABRICKS_MODEL_NAMES);
|
||||
assert.ok(entries.length > 0, "registry must not be empty");
|
||||
for (const [id, name] of entries) {
|
||||
assert.ok(
|
||||
id.startsWith("databricks-"),
|
||||
`ID ${id} must start with 'databricks-'`,
|
||||
);
|
||||
assert.ok(name.length > 0, `name for ${id} must be non-empty`);
|
||||
assert.notEqual(name, id, `curated name for ${id} must differ from raw ID`);
|
||||
}
|
||||
});
|
||||
54
desktop/src/features/agents/lib/databricksModelNames.ts
Normal file
54
desktop/src/features/agents/lib/databricksModelNames.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
// GENERATED by scripts/generate-databricks-model-names.py
|
||||
// Source: https://models.dev/api.json -- providers.databricks.models
|
||||
// Refresh: python3 scripts/generate-databricks-model-names.py (then port to TS)
|
||||
//
|
||||
// Do not hand-edit -- rerun the script to update.
|
||||
|
||||
/**
|
||||
* Curated display names for known Databricks AI Gateway endpoints.
|
||||
*
|
||||
* Keys are endpoint IDs returned verbatim by the discovery APIs.
|
||||
* Values are human-readable display names sourced from models.dev.
|
||||
*
|
||||
* Unknown endpoint IDs are displayed as their raw ID -- no guessing.
|
||||
*/
|
||||
export const DATABRICKS_MODEL_NAMES: Record<string, string> = {
|
||||
"databricks-claude-haiku-4-5": "Claude Haiku 4.5 (latest)",
|
||||
"databricks-claude-opus-4-1": "Claude Opus 4.1 (latest)",
|
||||
"databricks-claude-opus-4-5": "Claude Opus 4.5 (latest)",
|
||||
"databricks-claude-opus-4-6": "Claude Opus 4.6",
|
||||
"databricks-claude-opus-4-7": "Claude Opus 4.7",
|
||||
"databricks-claude-sonnet-4": "Claude Sonnet 4.5",
|
||||
"databricks-claude-sonnet-4-5": "Claude Sonnet 4.5 (latest)",
|
||||
"databricks-claude-sonnet-4-6": "Claude Sonnet 4.6",
|
||||
"databricks-gemini-2-5-flash": "Gemini 2.5 Flash",
|
||||
"databricks-gemini-2-5-pro": "Gemini 2.5 Pro",
|
||||
"databricks-gemini-3-1-flash-lite": "Gemini 3.1 Flash Lite Preview",
|
||||
"databricks-gemini-3-1-pro": "Gemini 3.1 Pro Preview Custom Tools",
|
||||
"databricks-gemini-3-flash": "Gemini 3 Flash Preview",
|
||||
"databricks-gemini-3-pro": "Gemini 3 Pro Preview",
|
||||
"databricks-glm-5-2": "GLM-5.2",
|
||||
"databricks-gpt-5": "GPT-5",
|
||||
"databricks-gpt-5-1": "GPT-5.1",
|
||||
"databricks-gpt-5-2": "GPT-5.2",
|
||||
"databricks-gpt-5-4": "GPT-5.4",
|
||||
"databricks-gpt-5-4-mini": "GPT-5.4 mini",
|
||||
"databricks-gpt-5-4-nano": "GPT-5.4 nano",
|
||||
"databricks-gpt-5-5": "GPT-5.5",
|
||||
"databricks-gpt-5-6-luna": "GPT-5.6 Luna",
|
||||
"databricks-gpt-5-6-sol": "GPT-5.6 Sol",
|
||||
"databricks-gpt-5-6-terra": "GPT-5.6 Terra",
|
||||
"databricks-gpt-5-mini": "GPT-5 Mini",
|
||||
"databricks-gpt-5-nano": "GPT-5 Nano",
|
||||
"databricks-gpt-oss-120b": "GPT OSS 120B",
|
||||
"databricks-gpt-oss-20b": "GPT OSS 20B",
|
||||
"databricks-kimi-k2-7-code": "Kimi K2.7 Code",
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the curated display name for a Databricks endpoint ID, or the raw
|
||||
* ID when no entry exists in the registry. No heuristic guessing.
|
||||
*/
|
||||
export function databricksModelName(id: string): string {
|
||||
return DATABRICKS_MODEL_NAMES[id] ?? id;
|
||||
}
|
||||
@@ -1,8 +1,15 @@
|
||||
import { databricksModelName } from "./databricksModelNames";
|
||||
|
||||
/**
|
||||
* Returns a human-readable model label for an agent or persona, falling back to
|
||||
* "Auto" when no model is set (empty or whitespace-only).
|
||||
*
|
||||
* For known Databricks managed endpoints the registry-curated name is returned
|
||||
* (e.g. "databricks-gpt-5-5" → "GPT-5.5"). Unknown or custom endpoint IDs are
|
||||
* returned unchanged — no heuristic string mangling.
|
||||
*/
|
||||
export function formatAgentModelLabel(model: string | null | undefined) {
|
||||
const trimmed = model?.trim();
|
||||
return trimmed && trimmed.length > 0 ? trimmed : "Auto";
|
||||
if (!trimmed) return "Auto";
|
||||
return databricksModelName(trimmed);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import { friendlyAgentLastError } from "@/features/agents/lib/friendlyAgentLastE
|
||||
import { ManagedAgentLogPanel } from "./ManagedAgentLogPanel";
|
||||
import { PubKey } from "@/shared/ui/PubKey";
|
||||
import { SubsectionLabel } from "@/shared/ui/PageHeader";
|
||||
import { databricksModelName } from "@/features/agents/lib/databricksModelNames";
|
||||
|
||||
export function ManagedAgentRow({
|
||||
agent,
|
||||
@@ -410,7 +411,7 @@ function RuntimeBlock({
|
||||
{runtimeSource || agent.model ? (
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
|
||||
{runtimeSource ? <span>{runtimeSource}</span> : null}
|
||||
{agent.model ? <span>{agent.model}</span> : null}
|
||||
{agent.model ? <span>{databricksModelName(agent.model)}</span> : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -51,6 +51,7 @@ import { BotIdenticon } from "@/features/messages/ui/BotIdenticon";
|
||||
import { useNow } from "@/shared/lib/useNow";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import { Spinner } from "@/shared/ui/spinner";
|
||||
import { databricksModelName } from "@/features/agents/lib/databricksModelNames";
|
||||
|
||||
type UserProfilePopoverProps = {
|
||||
children: React.ReactNode;
|
||||
@@ -611,7 +612,7 @@ export function UserProfilePopover({
|
||||
<InfoBadge>{runtimeLabel(relayAgent.agentType)}</InfoBadge>
|
||||
) : null}
|
||||
{managedAgent?.model ? (
|
||||
<InfoBadge>{managedAgent.model}</InfoBadge>
|
||||
<InfoBadge>{databricksModelName(managedAgent.model)}</InfoBadge>
|
||||
) : null}
|
||||
{managedAgent?.acpCommand ? (
|
||||
<InfoBadge>ACP: {managedAgent.acpCommand}</InfoBadge>
|
||||
|
||||
Reference in New Issue
Block a user