sdk(0.1.4): accept bare cm_<id> in chat.completions, drop qualified_name#10
Conversation
…1.4)
Customers were having to address custom models two different ways:
model.id (cm_abc123) for control-plane calls, model.qualified_name
(custom:cm_abc123) for chat. The custom: prefix is a wire-level
routing detail of the model gateway — it shouldn't have leaked into
public ergonomics.
This PR collapses both paths to model.id:
- chat.completions.create (sync + async) now normalizes the model
param: bare cm_<hex> ids get the gateway's "custom:" prefix
prepended before delegating to the openai client. Pre-prefixed
strings ("custom:cm_...") and first-party catalog ids
("meta-llama/...", "Qwen/...") are passed through unchanged. Any
non-string is also passed through so we don't shadow the openai
client's own TypeError messaging.
- _extract_custom_model_id (used by auto-wake to find which model
to wake) is unchanged in behavior — it now sees the
post-normalization value, so the cold-start retry loop continues
to fire on bare-cm calls.
- CustomModel.qualified_name property removed. Migration is
model.qualified_name -> model.id; the rare caller that needed
the wire-level form can write f"custom:{model.id}".
No backend change, no protocol change, no latency cost — the wire
request is byte-identical to the prefixed form. Pre-1.0 patch
release because (a) qualified_name was undocumented outside one
example and shipped two weeks ago in 0.1.0, (b) the new behavior
is purely additive on the input side.
README, docs/cold-starts.md, scripts/e2e.py, and every example
under examples/ updated to use model.id consistently.
examples/openai_compat.py keeps the explicit custom:cm_... form
with a note explaining the prefix is the cost of bypassing
graphn.Client.
Tests: added a parametrized test covering bare cm, prefixed cm,
HuggingFace-style, and non-string inputs through the normalizer;
updated the auto-wake test to feed the bare id and assert the
prefixed form goes out on the wire; removed the qualified_name
property test. 43/43 pass, ruff clean.
Pre-merge e2e against beta — passedRan a focused live test against The wire-payload check used a one-off httpx Auto-wake (cold-start retry) was not exercised live because both models in the workspace were warm. That path is untouched by this PR — it consumes the post-normalization model string via |
Why
We were making customers address the same custom model two different ways:
The
custom:prefix is a wire-level routing detail of the model gateway — it shouldn't have leaked into the public API surface.What
Collapse both paths to
model.id. Resolved entirely inside the SDK — no backend change, no protocol change, no latency hit (the wire request is byte-identical to the prefixed form).chat.completions.create(sync + async) normalizes themodelparam: barecm_<hex>ids get the gateway'scustom:prefix prepended before delegating to the openai client.custom:cm_...) and first-party catalog ids (meta-llama/...,Qwen/...) pass through unchanged. Old code keeps working._extract_custom_model_idis unchanged in behavior — it sees the post-normalization value, so cold-start retry continues to fire on bare-cm calls.CustomModel.qualified_nameproperty removed. Migration ismodel.qualified_name→model.id; the rare caller who needs the wire-level form can writef"custom:{model.id}".Versioning —
0.1.4(patch)Pre-1.0, and
qualified_name(a) was undocumented outside one example block, (b) shipped two weeks ago in0.1.0, (c) is being removed because the surface that justified its existence (passing it to chat) is now redundant. The new normalization on the input side is purely additive. If anyone hits this in the wild we'll restorequalified_nameas a deprecated alias.Docs
README.md,docs/cold-starts.md,scripts/e2e.py, and every example underexamples/updated to usemodel.idconsistently.examples/openai_compat.pydeliberately keepscustom:cm_...because that example is about bypassinggraphn.Client— it's now annotated to make that contrast explicit.Companion docs PR for the BYOM tutorial on agent-foundry: voltagepark/agent-foundry#818.
Test plan
ruff check src testsclean.pytest -ra— 43/43 pass.test_chat_normalizes_model_param_for_each_namespaceparametrized test covers bare cm, prefixed cm, HuggingFace-style, and non-string inputs.test_chat_auto_wakes_cold_custom_model_and_retriesnow feeds the bare id and asserts the prefixed form goes out on the wire.cm_<id>import → wait_until_ready → chat completion (cold-start auto-wake exercised).auto-tagjob createsv0.1.4,build+publishship to PyPI in the same workflow run,pip install graphn==0.1.4works ~1 min later.