Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 133 additions & 8 deletions crates/buzz-agent/src/llm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1019,13 +1019,53 @@ fn is_responses_required_error(body: &str) -> bool {
|| b.contains("use the responses api")
}

/// OpenAI-family code names that appear as their own segment in a Databricks v2
/// endpoint name (the GPT-5 launch aliases). The `gpt` family itself is matched
/// separately by segment prefix so `gpt`, `gpt5`, and the `gpt` of a split
/// `gpt-5` all qualify.
const DATABRICKS_V2_OPENAI_CODE_NAMES: &[&str] = &["sol", "luna", "terra"];

/// Anthropic (Claude) family and release code names that appear as their own
/// segment in a Databricks v2 endpoint name — the `claude` prefix, the family
/// names (`opus`, `sonnet`, `haiku`), and the release code names (`mythos`,
/// `fable`). Getting a Claude model onto the Anthropic Messages route is what
/// lets it carry a `cache_control` breakpoint; an endpoint that matches none of
/// these falls through to the MLflow (OpenAI-wire) path, where Anthropic prompt
/// caching is structurally impossible and the discount is silently lost.
const DATABRICKS_V2_CLAUDE_NAMES: &[&str] =
&["claude", "opus", "sonnet", "haiku", "mythos", "fable"];

/// Split a Databricks v2 endpoint name into its lowercase alphanumeric segments,
/// breaking on any non-alphanumeric delimiter (`-`, `_`, `.`, `/`, …). E.g.
/// `Databricks-Claude-Opus-5` -> `["databricks", "claude", "opus", "5"]`.
fn model_name_segments(model: &str) -> Vec<String> {
model
.split(|c: char| !c.is_ascii_alphanumeric())
.filter(|s| !s.is_empty())
.map(str::to_ascii_lowercase)
.collect()
}

fn databricks_v2_route_for_model(model: &str) -> DatabricksV2Route {
// Databricks v2 catalog names currently identify OpenAI-shaped GPT-5
// models and Anthropic-shaped Claude models by these substrings.
let lower = model.to_ascii_lowercase();
if lower.contains("gpt-5") || lower.contains("gpt5") {
// The v2 catalog exposes no family field, so the wire format is inferred
// from the endpoint name. Discovery deliberately keeps arbitrary custom
// aliases, so we match whole name *segments* rather than raw substrings: a
// substring test would misroute unrelated names — `consolidated-llama`
// (`sol`), `terraform-coder` (`terra`), `corpus-reranker`/`octopus-model`
// (`opus`) — onto a wire whose request shape their backend can't parse,
// turning a caching optimization into a hard request/parse failure. Segment
// matching still accepts real prefixed names like `goose-opus-5`.
let segments = model_name_segments(model);
let has_named_segment =
|names: &[&str]| segments.iter().any(|seg| names.contains(&seg.as_str()));
// `gpt` family: any segment beginning with `gpt` — covers `gpt`, `gpt5`, and
// the `gpt` segment of a split `gpt-5`, without matching mid-word.
let is_gpt_family = segments.iter().any(|seg| seg.starts_with("gpt"));
// OpenAI is checked before Claude so a name carrying both markers resolves
// to the OpenAI wire (preserving the prior `gpt-5`-first precedence).
if is_gpt_family || has_named_segment(DATABRICKS_V2_OPENAI_CODE_NAMES) {
DatabricksV2Route::OpenAiResponses
} else if lower.contains("claude") {
} else if has_named_segment(DATABRICKS_V2_CLAUDE_NAMES) {
DatabricksV2Route::AnthropicMessages
} else {
DatabricksV2Route::MlflowChatCompletions
Expand Down Expand Up @@ -2579,20 +2619,105 @@ mod tests {

#[test]
fn databricks_v2_routes_by_model_family() {
use DatabricksV2Route::{AnthropicMessages, MlflowChatCompletions, OpenAiResponses};
for (model, route, path) in [
// OpenAI-shaped: the gpt family plus the GPT-5 code names.
(
"databricks-gpt-5-5",
DatabricksV2Route::OpenAiResponses,
OpenAiResponses,
"/ai-gateway/openai/v1/responses",
),
("gpt-4o", OpenAiResponses, "/ai-gateway/openai/v1/responses"),
// The intentional dashless `gpt5` spelling still routes to OpenAI.
("gpt5", OpenAiResponses, "/ai-gateway/openai/v1/responses"),
(
"databricks-gpt-5-6-luna",
OpenAiResponses,
"/ai-gateway/openai/v1/responses",
),
(
"databricks-gpt-5-6-sol",
OpenAiResponses,
"/ai-gateway/openai/v1/responses",
),
(
"databricks-terra",
OpenAiResponses,
"/ai-gateway/openai/v1/responses",
),
// Anthropic-shaped: the claude prefix, the family names, and the
// release code names — each must reach the cache-capable route even
// when the endpoint name omits the literal "claude".
(
"databricks-claude-opus-4-7",
DatabricksV2Route::AnthropicMessages,
AnthropicMessages,
"/ai-gateway/anthropic/v1/messages",
),
(
"goose-opus-5",
AnthropicMessages,
"/ai-gateway/anthropic/v1/messages",
),
(
"databricks-sonnet-5",
AnthropicMessages,
"/ai-gateway/anthropic/v1/messages",
),
(
"databricks-haiku-4-5",
AnthropicMessages,
"/ai-gateway/anthropic/v1/messages",
),
(
"databricks-mythos-5",
AnthropicMessages,
"/ai-gateway/anthropic/v1/messages",
),
(
"databricks-fable-5",
AnthropicMessages,
"/ai-gateway/anthropic/v1/messages",
),
// Case-insensitive.
(
"Databricks-Claude-Opus-5",
AnthropicMessages,
"/ai-gateway/anthropic/v1/messages",
),
// Unrecognised names still fall through to the MLflow chat route.
(
"custom-tool-model",
DatabricksV2Route::MlflowChatCompletions,
MlflowChatCompletions,
"/ai-gateway/mlflow/v1/chat/completions",
),
(
"databricks-gemini-3-pro",
MlflowChatCompletions,
"/ai-gateway/mlflow/v1/chat/completions",
),
// Collision guard: short code names must match only as whole
// segments, never as substrings of an unrelated custom alias.
// Each of these embeds a marker (`sol`, `terra`, `opus`) mid-word
// and must stay on the MLflow fallback, not adopt a wire its
// backend can't parse.
(
"consolidated-llama",
MlflowChatCompletions,
"/ai-gateway/mlflow/v1/chat/completions",
),
(
"terraform-coder",
MlflowChatCompletions,
"/ai-gateway/mlflow/v1/chat/completions",
),
(
"corpus-reranker",
MlflowChatCompletions,
"/ai-gateway/mlflow/v1/chat/completions",
),
(
"octopus-model",
MlflowChatCompletions,
"/ai-gateway/mlflow/v1/chat/completions",
),
] {
Expand Down