Manage Routers
Choose an embedding model, create a Router, wait for it to be ready, and update, suspend, or delete it.
Use the control plane to create the Routers that applications author intents in and match against. A Router is the configuration and authorization boundary for a set of intents. See Redis Intent Router concepts.
Before you begin
These tasks need the control-plane URL and its admin token, which a platform team holds. See Authentication and authorization. The admin token manages every Router in the deployment, so keep it out of agent and application configuration.
Export these values in the shell where you run the examples:
export IRIS_INTENT_ROUTER_SERVICE_URL='<control-plane-url>'
export IRIS_INTENT_ROUTER_SERVICE_TOKEN='<admin-token>'
The examples use Python (IntentRouter.from_env().service), TypeScript (IntentRouterService), cURL, and the iris intent-router service commands, which all read these variables. The Python and TypeScript clients return each response as result, next to its headers.
iris intent-router service create also needs the data-plane URL in IRIS_INTENT_ROUTER_URL, because it prints the IRIS_INTENT_ROUTER_* values an application needs. With --write-env PATH or --json, it also mints an agent key through the Identity Service, which needs IRIS_IDENTITY_URL and IRIS_IDENTITY_TOKEN. The CLI example creates the Router without a key. Follow one tab throughout the page. To choose a client, see the developer guide.
Choose an embedding model
A Router's embedding model decides how well matching understands wording, and it can't change after you create the Router. List the models your deployment offers. Embedding models have the purpose EMBEDDING, and you can filter the list with purpose=EMBEDDING.
models = service.list_models(purpose="EMBEDDING").result.items
for model in models:
print(model.provider, model.model, model.dimensions, model.authorization)
from redis_iris.intent_router import IntentRouter
service = IntentRouter.from_env().service
models = service.list_models(purpose="EMBEDDING").result.items
for model in models:
print(model.provider, model.model, model.dimensions, model.authorization)
created = service.create_router(
name="support-router",
description="Routes support requests to tools and agents.",
template="AGENT",
min_score=0.7,
vector_space={
"provider": models[0].provider,
"model": models[0].model,
"dimensions": models[0].dimensions,
},
).result
router_id = created.router_id
print(router_id, created.state, created.revision)
import time
router = service.get_router(router_id=router_id).result
while router.state == "PROVISIONING":
time.sleep(1)
router = service.get_router(router_id=router_id).result
print(router.router_id, router.state, router.failure_reason)
if router.state != "READY":
raise SystemExit(1)
for item in service.list_routers().result.items:
print(item.router_id, item.name, item.state)
updated = service.update_router(
router_id=router_id, min_score=0.75, expected_revision=router.revision
).result
print(updated.router_id, updated.min_score, updated.revision)
for state in ("SUSPENDED", "READY"):
changed = service.update_router(router_id=router_id, state=state).result
print(changed.router_id, changed.state)
deleted = service.delete_router(router_id=router_id).result
if deleted:
print(deleted.router_id, deleted.state)
const { result: models } = await service.listModels("EMBEDDING");
for (const model of models.items) {
console.log(model.provider, model.model, model.dimensions, model.authorization);
}
import { IntentRouterService } from "@redis-iris/intent-router";
function requiredEnv(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`Set ${name}`);
return value;
}
const service = new IntentRouterService({
serverURL: requiredEnv("IRIS_INTENT_ROUTER_SERVICE_URL"),
token: requiredEnv("IRIS_INTENT_ROUTER_SERVICE_TOKEN"),
retryConfig: { strategy: "none" },
});
const { result: models } = await service.listModels("EMBEDDING");
for (const model of models.items) {
console.log(model.provider, model.model, model.dimensions, model.authorization);
}
const [embedding] = models.items;
if (!embedding?.dimensions) throw new Error("No embedding model is offered");
const { result: created } = await service.createRouter({
name: "support-router",
description: "Routes support requests to tools and agents.",
template: "AGENT",
minScore: 0.7,
vectorSpace: {
provider: embedding.provider,
model: embedding.model,
dimensions: embedding.dimensions,
},
});
const routerId = created.routerId;
console.log(routerId, created.state, created.revision);
let router = (await service.getRouter(routerId)).result;
while (router.state === "PROVISIONING") {
await new Promise((resolve) => setTimeout(resolve, 1000));
router = (await service.getRouter(routerId)).result;
}
console.log(router.routerId, router.state, router.failureReason);
if (router.state !== "READY") process.exit(1);
const { result: routers } = await service.listRouters();
for (const item of routers.items) console.log(item.routerId, item.name, item.state);
const { result: updated } = await service.updateRouter(routerId, undefined, {
minScore: 0.75,
expectedRevision: router.revision,
});
console.log(updated.routerId, updated.minScore, updated.revision);
for (const state of ["SUSPENDED", "READY"] as const) {
const { result: changed } = await service.updateRouter(routerId, undefined, { state });
console.log(changed.routerId, changed.state);
}
const deleted = (await service.deleteRouter(routerId))?.result;
if (deleted) console.log(deleted.routerId, deleted.state);
SERVICE_API="${IRIS_INTENT_ROUTER_SERVICE_URL%/}/iris/v1/intent"
curl --fail-with-body --silent --show-error \
"$SERVICE_API/models?purpose=EMBEDDING" \
-H "Authorization: Bearer $IRIS_INTENT_ROUTER_SERVICE_TOKEN" > models.json
jq -c '.items[] | {provider, model, dimensions, authorization}' models.json
set -euo pipefail
SERVICE_API="${IRIS_INTENT_ROUTER_SERVICE_URL%/}/iris/v1/intent"
curl --fail-with-body --silent --show-error \
"$SERVICE_API/models?purpose=EMBEDDING" \
-H "Authorization: Bearer $IRIS_INTENT_ROUTER_SERVICE_TOKEN" > models.json
jq -c '.items[] | {provider, model, dimensions, authorization}' models.json
curl --fail-with-body --silent --show-error \
-X POST "$SERVICE_API/routers" \
-H "Authorization: Bearer $IRIS_INTENT_ROUTER_SERVICE_TOKEN" \
-H 'Content-Type: application/json' \
--data "$(jq '{
name: "support-router",
description: "Routes support requests to tools and agents.",
template: "AGENT",
minScore: 0.7,
vectorSpace: (.items[0] | {provider, model, dimensions})
}' models.json)" > router.json
ROUTER_ID=$(jq -er .routerId router.json)
jq -c '{routerId, state, revision}' router.json
until [ "$(curl --fail-with-body --silent --show-error \
"$SERVICE_API/routers/$ROUTER_ID" \
-H "Authorization: Bearer $IRIS_INTENT_ROUTER_SERVICE_TOKEN" | tee router.json | jq -r .state)" != "PROVISIONING" ]; do
sleep 1
done
jq -c '{routerId, state, failureReason}' router.json
jq -e '.state == "READY"' router.json > /dev/null
curl --fail-with-body --silent --show-error \
"$SERVICE_API/routers" \
-H "Authorization: Bearer $IRIS_INTENT_ROUTER_SERVICE_TOKEN" \
| jq -c '.items[] | {routerId, name, state}'
curl --fail-with-body --silent --show-error \
-X PATCH "$SERVICE_API/routers/$ROUTER_ID" \
-H "Authorization: Bearer $IRIS_INTENT_ROUTER_SERVICE_TOKEN" \
-H 'Content-Type: application/json' \
--data "$(jq -n --arg rev "$(jq -er .revision router.json)" '{minScore: 0.75, expectedRevision: $rev}')" \
| jq -c '{routerId, minScore, revision}'
for state in SUSPENDED READY; do
curl --fail-with-body --silent --show-error \
-X PATCH "$SERVICE_API/routers/$ROUTER_ID" \
-H "Authorization: Bearer $IRIS_INTENT_ROUTER_SERVICE_TOKEN" \
-H 'Content-Type: application/json' \
--data "$(jq -n --arg state "$state" '{state: $state}')" | jq -c '{routerId, state}'
done
curl --fail-with-body --silent --show-error \
-X DELETE "$SERVICE_API/routers/$ROUTER_ID" \
-H "Authorization: Bearer $IRIS_INTENT_ROUTER_SERVICE_TOKEN" | jq -c '{routerId, state}'
iris --json intent-router service models --purpose embedding > models.json
jq -c '.items[] | {provider, model, dimensions, authorization}' models.json
set -euo pipefail
iris --json intent-router service models --purpose embedding > models.json
jq -c '.items[] | {provider, model, dimensions, authorization}' models.json
iris intent-router service create --name support-router \
--description "Routes support requests to tools and agents." \
--template agent --min-score 0.7 \
--provider "$(jq -er '.items[0].provider' models.json)" \
--model "$(jq -er '.items[0].model' models.json)" \
--dimensions "$(jq -er '.items[0].dimensions' models.json)" \
--wait | tee router.env
ROUTER_ID=$(sed -n 's/^IRIS_INTENT_ROUTER_ID=//p' router.env)
iris --json intent-router service show "$ROUTER_ID" > router.json
jq -c '{routerId, state, failureReason}' router.json
jq -e '.state == "READY"' router.json > /dev/null
iris intent-router service list
iris intent-router service update "$ROUTER_ID" --min-score 0.75 \
--expected-revision "$(jq -er .revision router.json)"
iris intent-router service update "$ROUTER_ID" --state suspended
iris intent-router service update "$ROUTER_ID" --state ready
iris intent-router service delete "$ROUTER_ID" --yes
Each model reports its provider, model, and dimensions. Pass the same three values as the Router's vectorSpace. A self-managed deployment offers one embedding model, configured by your administrator, and holds its provider credentials itself, so authorization is NONE. Creating a Router with a model that isn't listed fails with 400.
The model also sets the Router's memory use. The Router's vector index takes about the number of examples multiplied by dimensions, multiplied by 4 bytes, plus the index graph. See Size memory.
Create a Router
Create a Router with a name and a vectorSpace. Optional fields set a description, a template, and a default minScore.
created = service.create_router(
name="support-router",
description="Routes support requests to tools and agents.",
template="AGENT",
min_score=0.7,
vector_space={
"provider": models[0].provider,
"model": models[0].model,
"dimensions": models[0].dimensions,
},
).result
router_id = created.router_id
print(router_id, created.state, created.revision)
const [embedding] = models.items;
if (!embedding?.dimensions) throw new Error("No embedding model is offered");
const { result: created } = await service.createRouter({
name: "support-router",
description: "Routes support requests to tools and agents.",
template: "AGENT",
minScore: 0.7,
vectorSpace: {
provider: embedding.provider,
model: embedding.model,
dimensions: embedding.dimensions,
},
});
const routerId = created.routerId;
console.log(routerId, created.state, created.revision);
curl --fail-with-body --silent --show-error \
-X POST "$SERVICE_API/routers" \
-H "Authorization: Bearer $IRIS_INTENT_ROUTER_SERVICE_TOKEN" \
-H 'Content-Type: application/json' \
--data "$(jq '{
name: "support-router",
description: "Routes support requests to tools and agents.",
template: "AGENT",
minScore: 0.7,
vectorSpace: (.items[0] | {provider, model, dimensions})
}' models.json)" > router.json
ROUTER_ID=$(jq -er .routerId router.json)
jq -c '{routerId, state, revision}' router.json
iris intent-router service create --name support-router \
--description "Routes support requests to tools and agents." \
--template agent --min-score 0.7 \
--provider "$(jq -er '.items[0].provider' models.json)" \
--model "$(jq -er '.items[0].model' models.json)" \
--dimensions "$(jq -er '.items[0].dimensions' models.json)" \
--wait | tee router.env
ROUTER_ID=$(sed -n 's/^IRIS_INTENT_ROUTER_ID=//p' router.env)
Save the routerId. The CLI prints it as IRIS_INTENT_ROUTER_ID. The new Router starts in PROVISIONING while the data plane builds its search index. The response carries a Retry-After header that suggests when to check again.
A tenant can hold up to 128 Routers by default. Router names don't have to be unique: the routerId identifies a Router.
| Field | Meaning |
|---|---|
name |
A display name, 1 to 128 characters. |
vectorSpace |
The embedding model: provider, model, and dimensions. Required and immutable. |
description |
What the Router is for, up to 1,024 characters. |
template |
A label for people and tooling: CUSTOM, MODEL, GUARDRAILS, or AGENT. Every template serves the same way. |
minScore |
The Router's minimum score, above 0 and up to 1. Omit it, or send 0, to use the default, 0.7. |
databaseId |
The customer database that holds the Router's intents, examples, and index. Omit it to use the deployment's default database. Your administrator names the available databases, and a deployment can require one. It can't change after creation. |
To retry a create safely, send an Idempotency-Key header of the form <client-id>:<hex SHA-256 of the request body>, with the body's object keys sorted and no extra whitespace. A retry with the same key and body returns the first response with Idempotent-Replayed: true, for 24 hours. Reusing the key with a different body fails with 422. Only a successful response is kept, so a retry after a failure runs the create again. A retry that arrives while the first request is still running gets 409.
Wait for the Router to be ready
Poll the Router until it leaves PROVISIONING. Data requests to a Router that is still PROVISIONING, or has FAILED, return 409. In the CLI, service create --wait already waited for READY, so this step reads the Router once with service show.
import time
router = service.get_router(router_id=router_id).result
while router.state == "PROVISIONING":
time.sleep(1)
router = service.get_router(router_id=router_id).result
print(router.router_id, router.state, router.failure_reason)
if router.state != "READY":
raise SystemExit(1)
let router = (await service.getRouter(routerId)).result;
while (router.state === "PROVISIONING") {
await new Promise((resolve) => setTimeout(resolve, 1000));
router = (await service.getRouter(routerId)).result;
}
console.log(router.routerId, router.state, router.failureReason);
if (router.state !== "READY") process.exit(1);
until [ "$(curl --fail-with-body --silent --show-error \
"$SERVICE_API/routers/$ROUTER_ID" \
-H "Authorization: Bearer $IRIS_INTENT_ROUTER_SERVICE_TOKEN" | tee router.json | jq -r .state)" != "PROVISIONING" ]; do
sleep 1
done
jq -c '{routerId, state, failureReason}' router.json
jq -e '.state == "READY"' router.json > /dev/null
iris --json intent-router service show "$ROUTER_ID" > router.json
jq -c '{routerId, state, failureReason}' router.json
jq -e '.state == "READY"' router.json > /dev/null
A FAILED Router reports why in failureReason, for example a database without vector search. A Router that isn't ready within the deployment's provisioning deadline, 10 minutes by default, also fails. A failed Router can't be resumed: delete it and create it again after you fix the cause.
List and inspect Routers
List the deployment's Routers, or read one by ID. Every read returns the current revision, which conditional updates need. Lists return nextCursor when more Routers follow.
for item in service.list_routers().result.items:
print(item.router_id, item.name, item.state)
const { result: routers } = await service.listRouters();
for (const item of routers.items) console.log(item.routerId, item.name, item.state);
curl --fail-with-body --silent --show-error \
"$SERVICE_API/routers" \
-H "Authorization: Bearer $IRIS_INTENT_ROUTER_SERVICE_TOKEN" \
| jq -c '.items[] | {routerId, name, state}'
iris intent-router service list
Update a Router
Change a Router's name, description, template, or default minScore as a merge patch. An omitted field is unchanged, a null description clears it, and a null minScore resets it to the default. The embedding model and databaseId can't change. Changes apply after the data plane has applied the Router's new configuration, usually within seconds.
Send expectedRevision, or the revision as a quoted If-Match header, to make the update conditional: it fails with 412 if the Router changed since you read it. iris intent-router service update reads the current revision and sends it for you unless you pass --expected-revision.
updated = service.update_router(
router_id=router_id, min_score=0.75, expected_revision=router.revision
).result
print(updated.router_id, updated.min_score, updated.revision)
const { result: updated } = await service.updateRouter(routerId, undefined, {
minScore: 0.75,
expectedRevision: router.revision,
});
console.log(updated.routerId, updated.minScore, updated.revision);
curl --fail-with-body --silent --show-error \
-X PATCH "$SERVICE_API/routers/$ROUTER_ID" \
-H "Authorization: Bearer $IRIS_INTENT_ROUTER_SERVICE_TOKEN" \
-H 'Content-Type: application/json' \
--data "$(jq -n --arg rev "$(jq -er .revision router.json)" '{minScore: 0.75, expectedRevision: $rev}')" \
| jq -c '{routerId, minScore, revision}'
iris intent-router service update "$ROUTER_ID" --min-score 0.75 \
--expected-revision "$(jq -er .revision router.json)"
Suspend and resume a Router
Suspend a Router to stop it serving without deleting its intents. Data requests return 423 while it is suspended. Setting the state back to READY resumes it. In the CLI, run service update ROUTER_ID --state suspended or --state ready.
for state in ("SUSPENDED", "READY"):
changed = service.update_router(router_id=router_id, state=state).result
print(changed.router_id, changed.state)
for (const state of ["SUSPENDED", "READY"] as const) {
const { result: changed } = await service.updateRouter(routerId, undefined, { state });
console.log(changed.routerId, changed.state);
}
for state in SUSPENDED READY; do
curl --fail-with-body --silent --show-error \
-X PATCH "$SERVICE_API/routers/$ROUTER_ID" \
-H "Authorization: Bearer $IRIS_INTENT_ROUTER_SERVICE_TOKEN" \
-H 'Content-Type: application/json' \
--data "$(jq -n --arg state "$state" '{state: $state}')" | jq -c '{routerId, state}'
done
iris intent-router service update "$ROUTER_ID" --state suspended
iris intent-router service update "$ROUTER_ID" --state ready
SUSPENDED and READY are the only states you can request, and only a READY or SUSPENDED Router can change between these states. Any other change fails with 409. The control plane sets the other states.
Delete a Router
Deleting a Router permanently removes it, its intents, its examples, and its search index. You can delete a Router in any state. Deleting a Router doesn't revoke its agent keys, because the keys live in the Identity Service, not the control plane. A key whose grant names a deleted Router stays in the Identity Service until you revoke it. Revoke the Router's keys before you delete it, as shown in Rotate or revoke agent keys.
deleted = service.delete_router(router_id=router_id).result
if deleted:
print(deleted.router_id, deleted.state)
const deleted = (await service.deleteRouter(routerId))?.result;
if (deleted) console.log(deleted.routerId, deleted.state);
curl --fail-with-body --silent --show-error \
-X DELETE "$SERVICE_API/routers/$ROUTER_ID" \
-H "Authorization: Bearer $IRIS_INTENT_ROUTER_SERVICE_TOKEN" | jq -c '{routerId, state}'
iris intent-router service delete "$ROUTER_ID" --yes
The delete returns 202 with the Router in DELETING, and the data plane finishes the removal. Repeating the delete returns 202 while the removal runs and 204 once the Router is gone, so you can repeat a delete that timed out.
Issue agent keys
The control plane doesn't issue agent keys. Your platform team issues them with the Redis Iris Identity Service, with a grant on the Router. The CLI mints one with iris intent-router service key ROUTER_ID --role write --write PATH, and iris identity keys lists, rotates, and revokes them. See Create a Router and an agent key.
| Action | What it allows |
|---|---|
read |
Match queries, read intents and examples, and check the Router's health. |
write |
Create, change, and delete intents and examples, and clear the Router. Includes read. |
full |
Includes write. No operation needs more than write today. |
No agent-key action can create, change, or delete the Router itself. Router creation, change, or deletion requires the admin token. Give an application that only classifies queries a read key. Give a curation tool or an authoring pipeline a write key.
Next steps
- Manage intents and examples to author the Router's intents.
- Match queries to classify text.