Redis Intent Router quickstart

Define two intents, match queries against them, and update an intent with Python, TypeScript, HTTP, or the Iris CLI.

Intent Router classifies each incoming user message by intent, so your application can send it to the right agent, workflow, or handler. This quickstart shows the whole loop in a few minutes. You define a track_order intent and a refund intent with example phrases, match three queries against them, then add an example and change a threshold to see how the results change.

Before you begin

You need a Router in the READY state and an agent key with write access to it. write includes read. On Redis Cloud, create an Intent Router and an agent key in the console. On a self-managed deployment, see Manage Routers to create a Router yourself. Platform teams mint agent keys as shown in Create a Router and an agent key.

This walkthrough needs no agent framework or model-provider key.

Follow one tab throughout the page. The Python package and the command-line interface (CLI) require Python 3.11 or later. The TypeScript client requires Node.js 22 or later. The cURL and CLI examples use Bash and jq. To choose a client, see the developer guide.

Python and CLI

python -m venv .venv
source .venv/bin/activate
python -m pip install 'redis-iris>=1.0.0'

TypeScript

npm install @redis-iris/intent-router

cURL

curl --version
jq --version

Save connection values

Export these values in the shell where you run the examples. Use the data plane's base address, including the scheme, without the /iris/data/v1/... path.

export IRIS_INTENT_ROUTER_URL='<data-plane-endpoint>'
export IRIS_INTENT_ROUTER_ID='<router-id>'
export IRIS_INTENT_ROUTER_API_KEY='<agent-key>'

Keep keys out of source control and model prompts.

The Python examples use IntentRouter from the unified redis-iris package. IntentRouter.from_env() reads these variables, and its methods, such as define, match, add_examples, and update, return plain values such as Intent and Matches. The TypeScript examples pass the same values to the generated @redis-iris/intent-router client, whose methods take positional arguments with the request body after the path and header values, and return { headers, result }. The CLI tab runs iris intent-router commands, which read the same variables. CLI commands use --json when saving a response or processing it with jq.

Connect to the Router

Expand an example to copy the complete program. Save it as quickstart.py, quickstart.mts, quickstart.sh (cURL), or quickstart.cli.sh, then run python quickstart.py, npx tsx quickstart.mts, bash quickstart.sh, or bash quickstart.cli.sh.

from redis_iris.intent_router import IntentRouter, Target

router = IntentRouter.from_env()
print(router.health())
import { IntentRouter } 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 router = new IntentRouter({
  serverURL: requiredEnv("IRIS_INTENT_ROUTER_URL"),
  routerId: requiredEnv("IRIS_INTENT_ROUTER_ID"),
  apiKey: requiredEnv("IRIS_INTENT_ROUTER_API_KEY"),
  retryConfig: { strategy: "none" },
});
console.log((await router.getRouterHealth()).result);
set -euo pipefail
ROUTER_API="${IRIS_INTENT_ROUTER_URL%/}/iris/data/v1/intent/routers/$IRIS_INTENT_ROUTER_ID"
curl --fail-with-body --silent --show-error \
  "$ROUTER_API/health" \
  -H "Authorization: Bearer $IRIS_INTENT_ROUTER_API_KEY" | jq .
set -euo pipefail
iris intent-router health

The health check reports ok: true when the Router can serve. Each entry in checks covers one part: the Router's state, the embedding provider, the intent store, and the search index. When ok is false, the failing check's message says why, for example that the Router is still provisioning. Wait and retry.

Create intents

Writing an intent with PUT creates it or replaces the one with the same name, so you can rerun this step. In Python this is define, and in the CLI it is intents add. This example defines track_order with a description, a target, and three examples, and refund with examples only.

for intent in (
    router.define(
        "track_order",
        description="The customer wants to know where an order is.",
        target=Target("tool", "orders.track", {"region": "eu"}),
        examples=["where is my order", "track my package", "has my parcel shipped"],
    ),
    router.define(
        "refund",
        examples=["i want a refund", "refund my purchase", "i would like my money back"],
    ),
):
    print(intent.name, intent.example_count, intent.revision)
for (const intent of [
  await router.upsertIntent("track_order", undefined, undefined, {
    description: "The customer wants to know where an order is.",
    target: { type: "tool", ref: "orders.track", args: { region: "eu" } },
    examples: ["where is my order", "track my package", "has my parcel shipped"],
  }),
  await router.upsertIntent("refund", undefined, undefined, {
    examples: ["i want a refund", "refund my purchase", "i would like my money back"],
  }),
]) {
  console.log(intent.result.name, intent.result.exampleCount, intent.result.revision);
}
curl --fail-with-body --silent --show-error \
  -X PUT "$ROUTER_API/intents/track_order" \
  -H "Authorization: Bearer $IRIS_INTENT_ROUTER_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{
    "description": "The customer wants to know where an order is.",
    "target": {"type": "tool", "ref": "orders.track", "args": {"region": "eu"}},
    "examples": ["where is my order", "track my package", "has my parcel shipped"]
  }' | jq -c '{name, exampleCount, revision}'
curl --fail-with-body --silent --show-error \
  -X PUT "$ROUTER_API/intents/refund" \
  -H "Authorization: Bearer $IRIS_INTENT_ROUTER_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{"examples": ["i want a refund", "refund my purchase", "i would like my money back"]}' \
  | jq -c '{name, exampleCount, revision}'
iris intent-router intents add track_order \
  --description "The customer wants to know where an order is." \
  --target '{"type": "tool", "ref": "orders.track", "args": {"region": "eu"}}' \
  --example "where is my order" --example "track my package" --example "has my parcel shipped"
iris intent-router intents add refund \
  --example "i want a refund" --example "refund my purchase" --example "i would like my money back"

Each intent reports its exampleCount and revision. The first request returns 201 because the intent is new. Repeating it returns 200 with the same intent and a new revision.

Match a query

Send three queries. The response lists the closest intents, best first.

for query in ("where is my order please", "i would like my money back", "zzz qqq"):
    best = router.match(query).best
    if best:
        print(query, "->", best.name, round(best.score, 2), best.target)
    else:
        print(query, "-> no match")
for (const query of ["where is my order please", "i would like my money back", "zzz qqq"]) {
  const { items } = (await router.match({ query })).result;
  if (items.length > 0) {
    console.log(query, "->", items[0].name, items[0].score.toFixed(2), items[0].target);
  } else {
    console.log(query, "-> no match");
  }
}
for query in "where is my order please" "i would like my money back" "zzz qqq"; do
  curl --fail-with-body --silent --show-error \
    -X POST "$ROUTER_API/match" \
    -H "Authorization: Bearer $IRIS_INTENT_ROUTER_API_KEY" \
    -H 'Content-Type: application/json' \
    --data "$(jq -n --arg query "$query" '{query: $query}')" \
    | jq -r --arg query "$query" 'if (.items | length) > 0
        then "\($query) -> \(.items[0].name) \(.items[0].score * 100 | round / 100) \(.items[0].target | tojson)"
        else "\($query) -> no match" end'
done
for query in "where is my order please" "i would like my money back" "zzz qqq"; do
  iris intent-router match "$query"
done

The first query matches track_order and returns the target you stored. Your application reads target.type and target.ref to decide what to call. The second matches refund. The third, nonsense text, returns an empty list because no intent is close enough. An empty list is a successful 200 response.

Scores vary with the Router's embedding model. If a query you expect to match returns nothing, add an example rather than lowering the threshold.

Add an example

Add a phrasing to an existing intent.

for example in router.add_examples("track_order", ["when will my delivery arrive"]):
    print(example.example_id, example.text)
const added = await router.addExamples("track_order", {
  examples: ["when will my delivery arrive"],
});
for (const example of added.result.items) console.log(example.exampleId, example.text);
curl --fail-with-body --silent --show-error \
  -X POST "$ROUTER_API/intents/track_order/examples" \
  -H "Authorization: Bearer $IRIS_INTENT_ROUTER_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{"examples": ["when will my delivery arrive"]}' | jq -c '.items[] | {exampleId, text}'
iris intent-router examples add track_order "when will my delivery arrive"

The next match uses the new example. Adding a phrasing that already exists is harmless.

Change an intent's threshold

Updating an intent with PATCH changes its configuration without touching or re-embedding its examples. An omitted field is unchanged, and null clears a field. In Python, update sends only the fields you pass, and None clears one.

updated = router.update("track_order", min_score=0.8)
print(updated.name, updated.min_score, updated.revision)
const { result: updated } = await router.updateIntent("track_order", undefined, { minScore: 0.8 });
console.log(updated.name, updated.minScore, updated.revision);
curl --fail-with-body --silent --show-error \
  -X PATCH "$ROUTER_API/intents/track_order" \
  -H "Authorization: Bearer $IRIS_INTENT_ROUTER_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{"minScore": 0.8}' | jq -c '{name, minScore, revision}'
iris intent-router intents update track_order --min-score 0.8

The intent now matches only queries that score at least 0.8. Higher scores are stricter.

Clean up

Delete the intents when you finish. Deleting a missing intent succeeds. To remove every intent at once and keep the Router, clear the Router.

for name in ("track_order", "refund"):
    router.remove(name)
for (const name of ["track_order", "refund"]) {
  await router.deleteIntent(name);
}
ROUTER_API="${IRIS_INTENT_ROUTER_URL%/}/iris/data/v1/intent/routers/$IRIS_INTENT_ROUTER_ID"
for name in track_order refund; do
  curl --fail-with-body --silent --show-error \
    -X DELETE "$ROUTER_API/intents/$name" \
    -H "Authorization: Bearer $IRIS_INTENT_ROUTER_API_KEY"
done
for name in track_order refund; do
  iris intent-router intents delete "$name" --yes
done

Next steps

RATE THIS PAGE
Back to top ↑