Routing
client.routing controls how the gateway selects a model and provider for each request. Artemis Task decides
every model="auto" request, by model capability; a request that names a model uses no router. See
Artemis Task for how it decides.
The router
There is no router to choose, per request or in a setting. The chat call's router= keyword (RouterChoice) is
optional: it takes only "artemis" or "v2", which change nothing, and any other value raises NuRouteError with
status 400. When Artemis Task cannot decide a request, fallback-router:apollo decides it, and the request's
routerVersion and routerFallback say so and why (see Which router decided). Your
governance policy applies either way. More in Routing concepts.
Get the current config
config = client.routing.get_config()
print(config["strategy"], config.get("speed"))
Update routing config
update_config() replaces the whole config. A key you leave out goes back to its default: strategy to
"balanced", the weights to 40/30/30 and the policy to its defaults. speed and the model aliases are kept when you
leave them out; send None for speed off. Aliases in the config you send are ignored: change them with the
alias methods. If the stored config cannot be read at that moment, the call fails with 503 and
nothing is saved. Changing the config needs an owner's or admin's session: sign the client in with auth.login()
(see Authentication). With an API key, get_config() works and update_config() and the alias
changes fail with 403. Read the config, change what you need and send all of it back:
config = client.routing.get_config()
config["strategy"] = "balanced" # "balanced" | "cheapest" | "fastest" | "highest_quality" | "manual"
config["policy"]["fallbackBehavior"] = "allow_commercial"
client.routing.update_config(config)
See Fallback for what fallbackBehavior actually
controls. It decides what happens when no provider passes your policy constraints, not
whether the gateway retries on a provider error.
What the strategy means
Artemis Task reads the strategy as a preset:
strategy | Artemis Task preset |
|---|---|
balanced | Balanced |
highest_quality | Quality-first |
cheapest | Cost-first |
fastest | Balanced at speed Fastest on a capability version with a speed table, else Fastest |
manual | None: your pinned provider serves it, recorded as routerFallback "manual_mode" |
For a request that names a model, the strategy ranks that model's providers. The balanced weights rank the
providers of a model you name, and the choice the fallback router makes inside its tier; Artemis Task does not
read them. Cost, latency and quality controls sets out what the strategy, the
weights, speed and the cost and latency caps in policy do, with what to change to make routing cheaper or
faster.
Speed
speed is Artemis Task's Speed setting: how much a quicker answer is worth, at the
quality target the strategy's preset sets. "off", "faster", "fastest", or None for off; any other value
raises NuRouteError with status 400. No chat keyword or header sets it, and a project's router_speed overrides
the organization's for the project's requests. fallback-router:apollo ignores it.
from nuroute.types import RouterSpeed # Literal["off", "faster", "fastest"]
speed: RouterSpeed = "faster"
config = client.routing.get_config()
config["speed"] = speed # None: off
client.routing.update_config(config)
# A project: the wire's snake_case key; "" follows the organization
proj = client.project("proj_abc123")
cfg = proj.get_routing_config()
cfg.update(router_speed="fastest")
proj.set_routing_config(**cfg)
Model aliases
Aliases map a name of your choice to a model. They are stored per organization (and per project), but the request
path does not resolve them today: a chat request has to name the real model, or auto.
# Create or update an alias
client.routing.set_alias("smart", "gpt-4o")
client.routing.set_alias("fast", "gpt-4o-mini")
client.routing.set_alias("coding", "claude-sonnet-4-6")
# List all aliases
aliases = client.routing.list_aliases()
# Delete an alias
client.routing.delete_alias("coding")
Routing history
result = client.routing.list_history(limit=20, provider="openai", strategy="balanced")
decision = client.routing.get_decision(result["decisions"][0]["id"])
print(decision["selectedProvider"], decision.get("routerVersion"), decision["candidates"])
Which router decided
client.chat.complete() returns the response body only, so read the router from the request log. The completion
id is chatcmpl- followed by the request id, which client.requests.list() can search for:
res = client.chat.complete(model="auto", messages=messages)
request_id = res["id"].removeprefix("chatcmpl-")
page = client.requests.list(search=request_id, limit=1)
detail = client.requests.get(page["requests"][0]["id"])
print(detail.get("routerVersion"), detail.get("capabilityVersion"), detail.get("routerFallback"))
# v2 cap-6 None, or v1 None no_estimates when fallback-router:apollo decided
RequestDetail (from client.requests.get()) and RoutingDecision (from client.routing.get_decision()) both
include the RouterDecision keys:
| Key | Type | Meaning |
|---|---|---|
routerVersion | RouterVersion | None | The router that decided: "v2" (Artemis Task) or "v1" (fallback-router:apollo); None for a request that named a model |
capabilityVersion | str | None | The capability version Artemis Task decided with, e.g. "cap-6"; None when the fallback router decided |
routerFallback | str | None | Why the fallback router decided instead of Artemis Task; None otherwise |
RequestDetail also breaks down the request's time: latencyMs (the provider call), routingDurationMs (routing),
totalMs (end to end, as the gateway measured it; 0 for requests recorded before it was tracked) and overheadMs
(the rest; None when totalMs is 0). There is no separate timeline call: requests.get_timeline() was removed,
since the gateway has no such endpoint.
When the fallback router decides, the request is still served, and your governance policy applies to its choice too. The fallback reasons:
routerFallback | Meaning |
|---|---|
no_estimates | Artemis Task had no estimates: router-model failed, was busy or ran out of time, or no capability version is active |
unknown_capability_version | Artemis Task's estimates came from a capability version the deployment does not know |
manual_mode | Routing is manual (mode or strategy manual), or the request pins a provider |
preset_unavailable | The capability version has no preset for the strategy (Fastest on cap-1) |
no_servable_model | None of Artemis Task's models could be served after your policy and enabled providers, or your cost and latency caps ruled them all out and your fallbackBehavior is not "fail" (reasons then include "policy_caps_exhausted"; under "fail" the request is refused with 422 instead) |
v2_disabled | Artemis Task is switched off by the operator. Self-hosted NuRoute does not include Artemis Task yet, so its auto requests all report this |
invalid_estimates | Artemis Task's estimates failed a sanity check |
v2_error | Artemis Task hit an error while deciding |
Candidates
candidates lists what was weighed, each with provider, model, costUsd, latencyMs, score (0–100) and
winner. On an Artemis Task decision, each Candidate adds Artemis Task's figures. Under Quality-first, Balanced
and Cost-first there is one row per pool model that can be served, on the provider it would be served by; under
Fastest, one row per model and provider.
| Key | Type | Meaning |
|---|---|---|
quality | float | The model's expected score on this prompt (0–1) |
utility | float | None | The expected score less the preset's price for extra cost; the highest wins. None under Fastest |
inputTokens | int | The prompt tokens it priced |
outputTokens | float | The answer length, in tokens, it expected |
expectedLatencyMs | float | The answer time it expected on this provider; Fastest picks by it |
When the fallback router decided, candidates lists the models it weighed in its tier, score being each one's
rank under the strategy (winner highest), and the keys above are absent.