LlamaIndex Tutorial (TypeScript): building conditional routing for advanced developers

By Cyprian AaronsUpdated 2026-04-21
llamaindexbuilding-conditional-routing-for-advanced-developerstypescript

This tutorial shows how to build a conditional router in LlamaIndex TypeScript that sends each user query to the right retrieval path based on intent. You need this when one index is not enough and you want deterministic routing across domains like policy docs, claims, billing, or support.

What You'll Need

  • Node.js 18+ and npm
  • A TypeScript project with ts-node or tsx
  • llamaindex installed
  • An OpenAI API key set in your environment
  • A few source documents to route across
  • Basic familiarity with VectorStoreIndex, QueryEngine, and embeddings

Install the package:

npm install llamaindex

Set your API key:

export OPENAI_API_KEY="your-key-here"

Step-by-Step

  1. Create two separate indexes for two different document sets.
    In production, this is the cleanest pattern: keep each domain isolated, then route before retrieval instead of mixing everything into one giant index.
import { Document, VectorStoreIndex } from "llamaindex";

const hrDocs = [
  new Document({ text: "Vacation policy: employees get 20 days of PTO per year." }),
  new Document({ text: "Benefits policy: health coverage starts on day one." }),
];

const itDocs = [
  new Document({ text: "Laptop setup guide: install VPN, Slack, and Okta Verify." }),
  new Document({ text: "Password reset steps: use the self-service portal or contact IT." }),
];

async function buildIndexes() {
  const hrIndex = await VectorStoreIndex.fromDocuments(hrDocs);
  const itIndex = await VectorStoreIndex.fromDocuments(itDocs);

  return { hrIndex, itIndex };
}
  1. Define a routing function that inspects the query and chooses a destination.
    For advanced systems, keep this logic explicit and testable. Don’t hide routing decisions inside prompt magic if you need predictable behavior.
function routeQuery(query: string): "hr" | "it" {
  const q = query.toLowerCase();

  if (
    q.includes("pto") ||
    q.includes("vacation") ||
    q.includes("benefits") ||
    q.includes("health coverage")
  ) {
    return "hr";
  }

  if (
    q.includes("laptop") ||
    q.includes("vpn") ||
    q.includes("password") ||
    q.includes("okta")
  ) {
    return "it";
  }

  return "it";
}
  1. Turn each index into a query engine and wrap them behind a router.
    This keeps your application code simple: one entry point in, one answer out, with routing handled before retrieval.
import { QueryEngineTool } from "llamaindex";

async function buildTools() {
  const { hrIndex, itIndex } = await buildIndexes();

  const hrEngine = hrIndex.asQueryEngine();
  const itEngine = itIndex.asQueryEngine();

  const tools = [
    QueryEngineTool.fromDefaults({
      queryEngine: hrEngine,
      name: "hr_policy",
      description: "Answers questions about HR policies, PTO, and benefits.",
    }),
    QueryEngineTool.fromDefaults({
      queryEngine: itEngine,
      name: "it_support",
      description: "Answers questions about IT setup, passwords, VPN, and devices.",
    }),
  ];

  return tools;
}
  1. Build the conditional dispatcher that selects the tool and executes the query.
    This is the part you’ll actually call from your app layer or agent orchestration code.
import { OpenAI } from "@llamaindex/openai";

async function answer(query: string) {
  const tools = await buildTools();
  const selected = routeQuery(query);

  const tool =
    selected === "hr"
      ? tools.find((t) => t.metadata.name === "hr_policy")
      : tools.find((t) => t.metadata.name === "it_support");

  if (!tool) {
    throw new Error(`No tool found for route: ${selected}`);
  }

  const response = await tool.queryEngine.query({ query });
  return response.toString();
}
  1. Add an executable entry point so you can test real queries end to end.
    Keep this in a single file first. Once it works, split routing logic, index construction, and transport into separate modules.
async function main() {
  const queries = [
    "How many vacation days do employees get?",
    "How do I reset my password?",
  ];

  for (const query of queries) {
    const result = await answer(query);
    console.log(`\nQ: ${query}\nA: ${result}`);
  }
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

Testing It

Run the script with tsx or ts-node and confirm that HR questions hit the HR index while IT questions hit the IT index. Then try ambiguous inputs like “What do I need for onboarding?” and verify your fallback route behaves consistently.

Check logs around routeQuery() so you can see which branch was chosen before retrieval runs. If answers look wrong, inspect whether your keyword rules are too broad or whether your documents are too sparse for the selected domain.

For a stronger test suite, add unit tests for routeQuery() and integration tests that assert the selected tool name for a given input. That gives you deterministic coverage without depending on model behavior.

Next Steps

  • Replace keyword routing with an LLM-based selector using structured output when you need semantic routing.
  • Add confidence thresholds and fallback escalation to a human or broader search index.
  • Extend this pattern to more than two domains by using a routing table instead of nested conditionals

Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides