Skip to content

Custom (non-system.ai) UC model services are never discovered: support opt-in schema locations for model discovery #234

Description

@IceRhymers

Summary

If you create an FMAPI-backed Unity Catalog model service in your own schema (e.g. my_catalog.anthropic.claude-opus-4-8), ucode can't see it. Both discovery paths exclude it. The UC model-services listing filters client-side to system.ai.*, and the AI Gateway /v1/models listing only returns databricks-claude-* ids. The gateway itself routes catalog-qualified model ids just fine, so the gap is purely in discovery and config.

The motivation is a governed catalog with schemas holding model services created via Catalog Explorer, either one schema per provider (<catalog>.anthropic, <catalog>.openai) or a single shared schema. That gives teams per-schema permissions, usage tracking, and policies instead of everyone sharing the system.ai namespace.

Reproduction

Against a workspace with model services in user schemas (verified 2026-07-22):

from ucode.databricks import list_model_services, discover_claude_models
ids, _ = list_model_services(ws, token)
[i for i in ids if not i.startswith("system.ai.")]   # -> [] (filtered out)
discover_claude_models(ws, token)                     # -> only databricks-claude-* families

Meanwhile the same model id works through the gateway:

curl https://<ws>/ai-gateway/anthropic/v1/messages -H "Authorization: Bearer $TOKEN" \
  -d '{"model": "my_catalog.anthropic.claude-opus-4-8", "max_tokens": 32, "messages": [...]}'
# -> HTTP 200, normal completion

Root cause in the current code

  • src/ucode/databricks.py:1097: _MODEL_SERVICE_REQUIRED_PREFIX = "system.ai." means _model_service_id returns None for every other schema, so list_model_services and discover_model_services never surface them.
  • src/ucode/databricks.py:1787 (discover_claude_models): the gateway /ai-gateway/anthropic/v1/models response doesn't include catalog-scoped services, and the function additionally filters to databricks-claude-<family>-*.

Relationship to existing PRs

Requested change

Opt-in discovery locations, mirroring the existing configure mcp --location <catalog>.<schema> precedent:

ucode configure models --location my_catalog.anthropic --location my_catalog.openai

Each location gets listed via parent=schemas/<catalog>.<schema>, and the discovered ids feed the existing family bucketing alongside the system.ai results: claude-<family>-* goes to the claude picker with ANTHROPIC_DEFAULT_*_MODEL set to the full UC name, gpt-* gets pinned verbatim for codex, and gemini-* plus the OSS families work the same way.

Design notes / known limitations

  1. Naming contract. Substring bucketing means discovered services need to be named like the models they route to (claude-opus-*, gpt-5-*). Free-form names (claude_haiku, cheap-coder) won't bucket. Those should be reported as skipped at configure time rather than silently dropped. Worth noting: the singular GET (/model-services/<full_name>) exposes supported_api_types and the routing destination's underlying FMAPI model, so metadata-driven bucketing (per databricks: bucket UC model-services by supported_api_types #205) is a viable follow-up for arbitrary names.
  2. Alias shadowing. ANTHROPIC_DEFAULT_OPUS_MODEL holds exactly one id. If a location and system.ai both contain the same model, precedence should be explicit (suggestion: opt-in location beats system.ai, later --location beats earlier) instead of the current reverse-string sort, which effectively picks alphabetically by catalog name.
  3. [1m] suffix. Verified empirically: the suffix is a Claude Code client-side convention, not a gateway model id. The gateway returns 404 for system.ai.claude-opus-4-8[1m] (the exact value ucode writes to ANTHROPIC_DEFAULT_OPUS_MODEL today), so Claude Code must be stripping the suffix and sending the context-1m beta header, and the gateway accepts that header with a catalog-qualified id (HTTP 200 on <catalog>.<schema>.claude-opus-4-8 with anthropic-beta: context-1m-2025-08-07). The gap is in ucode itself: _maybe_add_1m_suffix's regex only matches system.ai. or databricks- prefixed ids, so custom ids never get the suffix and would silently lose 1M context. Fix is to match the family on the trailing name segment instead of the full id.
  4. Traffic-split services. A service with multiple routing destinations (say an opus/sonnet split) has no single family. Those should stay pin-only via Add --model launch flag for custom UC model services #224's --model and never get family-aliased.
  5. Dialect mismatch. A gpt-*-named service backed by a non-Responses destination fails at inference time instead of configure time. A cheap supported_api_types check on just the bucketed subset would catch this early.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions