Intent router

Send each incoming message to the intent it means. You define intents, each with a name, a few example phrases and an optional target (the agent, workflow…

Intent router

Send each incoming message to the intent it means. You define intents, each with a name, a few example phrases and an optional target (the agent, workflow, tool or URL that should handle it). KiviDB embeds the examples into one of your databases. To route a message, KiviDB embeds it, finds the closest examples and returns the best intent with its score and target, or no match when nothing is close enough.

No language model is called, and the target is returned, never run: your application decides what to do with it.

Intent router is part of Kivi Synapse, KiviDB's building blocks for AI agents.

The data model

Routera name (unique in the organization), the database that keeps its examples, an embedding model, a threshold and top_k
Intenta name (unique in the router), a description, a target and 1 to 100 example phrases
Target{type, ref, args}: type is agent, workflow, tool, url or none; ref names it (a URL for url); args is any JSON object you want back with it

The router's settings and its intents' names, descriptions and targets are kept by KiviDB Cloud. The examples and their vectors are kept in your database, under keys that start with the router's prefix:

KeyHolds
kividb:intent:{<router id>}:ex:<intent id>:<n>one example: a hash with intent, text and vec
kividb:intent:{<router id>}:i:<intent id>the intent's example texts, in order (a list)
kividb:intent:<router id>:idxthe router's search index over the examples

They use the database's own memory: about 2 KB per example with the built-in model (384 dimensions). Deleting an intent deletes its examples; deleting the router deletes all of its keys and its index. Deleting the database deletes its routers with it.

Embedding models

Chosen when you create the router, and fixed once it has intents (their examples were embedded with it; make a new router to change it).

  • Built-in, all-MiniLM-L6-v2 (the default), run inside KiviDB Cloud: 384 dimensions, no key, default threshold 0.40.
  • Hosted, Titan Text Embeddings V2 (hosted models, billed per token): 256, 512 or 1024 dimensions, no key, default threshold 0.45.
  • OpenAI, or an OpenAI-compatible endpoint (https): the model's dimensions, your key (stored encrypted, never shown again), default threshold 0.60.

The models score on different scales, so each has its own default threshold. With the built-in model, rewordings of an example typically score 0.4 to 0.9 and unrelated text under 0.2.

How scoring works

  1. The message is embedded with the router's model.
  2. The nearest examples are found by cosine similarity, across all intents (an exact search, not an approximate one).
  3. They are grouped by intent. An intent's score is its closest example's similarity, from 0 to 1. Several close examples do not add up; one very close example is enough.
  4. Intents are ranked by score and the best top_k kept.
  5. If the best score reaches the threshold, it is the match and the rest are alternatives. Otherwise match is null and every ranked intent is an alternative.

The same message against the same examples always gets the same answer. A message that should go nowhere ("what is a good banana bread recipe" to a support router) comes back with match: null: treat that as an answer, by asking again or handing over to a person.

To tune a router, give each intent five to twenty varied phrasings, and use the console's Try it box: type messages, see every intent's score, and drag the threshold to see what each setting would do before saving it.

The API

Every call takes an API key or a console session. :id is the router's id or its name.

  • GET /intent-routers: every router in the organization, with its intent count
  • GET /instances/:id/intent-routers: the routers whose examples are in one database
  • POST /intent-routers: create a router
  • GET /intent-routers/:id: the router, its intents and their examples
  • GET /intent-routers/:id/stats: the router and each intent's example count, without the examples
  • PATCH /intent-routers/:id: change name, description, threshold or top_k
  • DELETE /intent-routers/:id: delete it, its examples and its index
  • PUT /intent-routers/:id/intents/:intent: create or replace an intent
  • DELETE /intent-routers/:id/intents/:intent: delete an intent and its examples
  • POST /intent-routers/:id/match: route one message
  • POST /intent-routers/:id/match-batch: route up to 32 messages

Create a router with the built-in model:

curl https://api.kividb.io/intent-routers \
  -H "Authorization: Bearer $KIVIDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"instance_id": "<database id>", "name": "support"}'

Optional fields: description, embed_provider (local, the default; kividb; openai; openai_compatible), embed_model, embed_base_url, embed_api_key, dimensions, threshold and top_k (default 5).

Add an intent. Saving the same examples again does not re-embed them; changing them replaces all of the intent's examples:

curl -X PUT https://api.kividb.io/intent-routers/support/intents/billing \
  -H "Authorization: Bearer $KIVIDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Charges, invoices and payment methods",
    "target": {"type": "agent", "ref": "billing-agent", "args": {"queue": "finance"}},
    "examples": [
      "Why was I charged twice this month?",
      "I need a copy of my invoice",
      "Can I update my credit card?",
      "What is this charge on my statement?",
      "How much will my next bill be?"
    ]
  }'

Route a message. top_k and threshold are optional and override the router's for this call:

curl https://api.kividb.io/intent-routers/support/match \
  -H "Authorization: Bearer $KIVIDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "there is a double payment on my card"}'
{
  "match": {
    "intent": "billing",
    "score": 0.5088,
    "target": { "type": "agent", "ref": "billing-agent", "args": { "queue": "finance" } }
  },
  "alternatives": [
    { "intent": "cancel", "score": 0.331, "target": { "type": "workflow", "ref": "retention-flow" } },
    { "intent": "tech support", "score": 0.1542, "target": null }
  ],
  "threshold": 0.4,
  "top_k": 5
}

A batch takes {"texts": [...]} (1 to 32) and returns {"results": [{"text", "match", "alternatives"}, ...]} in the same order.

Errors: 409 instance_not_running when the database is stopped, 409 examples_missing when the router's index is gone from the database (for example after a flush; save each intent again to restore it), 409 sharded_not_supported, 422 too_many_intents and 422 too_many_routers at the limits, and 503 embedder_busy when the built-in model is busy (retry after a moment).

MCP

The KiviDB MCP server has two tools for routers made in the console or the API:

  • list_intent_routers: the organization's routers.
  • route_intent: router (id or name) and text, with optional top_k and threshold. Returns the same answer as the match call.

Limits

Routers per organization20
Intents per router200
Examples per intent100, each up to 500 characters (duplicates are kept once)
Message to routeup to 2,000 characters
Messages per batch32
top_k1 to 20
Thresholdabove 0, up to 1

Intent routers are not available on sharded databases yet. A database's Intent router tab and the dashboard's Intent router section manage them in the console.

There is no extra charge: the examples use the database's own memory. Only the hosted embedding model, if you choose it, is billed per token.