Model or dataset
aurelio-labs/semantic-router avatar
aurelio-labs/semantic-router

semantic-router: routing decisions without an LLM call

Superfast AI decision making and intelligent processing of multi-modal data.

3,897 stars372 forksPythonMIT

At a glance

What is it?
Aurelio Labs' semantic-router replaces LLM-based tool selection with vector similarity against labelled utterances. The library is small and the idea is clean, but the quality of your routes depends entirely on the utterances you write.
Who is it for?
Adopt semantic-router if you have a bounded set of intents and you want to stop paying an LLM round trip to pick between them. Do not adopt it if your routing logic needs multi-step reasoning, because the library decides by nearest-neighbour similarity over utterances you supply by hand.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The decision you are paying an LLM to make

Most agent stacks route by asking a model. You send the user query plus a list of tools, the model returns a tool name, and you act on it. That works, and it costs one generation per turn. semantic-router takes the position that most of these decisions do not need generation at all. They need comparison. If a user says "how's the weather today?" and you already have a route whose utterances are weather-shaped sentences, the nearest neighbour in embedding space is enough to pick it. The README frames this as a "superfast decision-making layer" and the mechanism is exactly that: embed the query, compare it to embedded utterances, return the route attached to the closest match. The audience is anyone building a chatbot or agent where the branching is known in advance and the latency of a classifier call is annoying. It is a poor fit for open-ended planning, where the set of possible next actions is not enumerable.

Routes, utterances, and what the router actually compares

The unit of configuration is a Route, constructed with a name and a list of utterances. In the README example, a politics route holds five sentences including "isn't politics the best thing ever" and "they're going to destroy this country!", and a chitchat route holds five weather-and-smalltalk sentences. Routes are collected into a plain Python list and passed to SemanticRouter along with an encoder. At query time the encoder produces a vector for the incoming text, and the router scores it against the utterance vectors belonging to each route. The route with the best score wins, subject to a threshold. If nothing clears the threshold the call returns None, which the README demonstrates with the query "I'm interested in learning about llama 2" producing no output at all. That None is the most important behaviour in the library. It is the difference between a router that guesses and a router that abstains, and any integration has to handle it explicitly rather than assuming a route always comes back.

Encoders decide your latency and your data boundary

The encoder is a separate object and the README offers CohereEncoder and OpenAIEncoder, initialized from COHERE_API_KEY and OPENAI_API_KEY environment variables respectively. Both send your utterances and your queries to a hosted embedding API. That means the default setup has the same data-egress property as the LLM routing you were trying to avoid: user text leaves your infrastructure. The library also documents HuggingFaceEncoder and FastEmbed encoders, and the README points at a local execution notebook that pairs HuggingFaceEncoder with LlamaCppLLM for a fully local setup. Local execution requires the extra install, pip install -qU "semantic-router[local]", and the hybrid route layer needs its own extra, pip install -qU "semantic-router[hybrid]". The README notes that local models such as Mistral 7B outperform GPT-3.5 in most tests on the project's own evaluation. That claim comes from the project's documentation, not from independent measurement, and the notebook is where you would check it against your own utterance sets.

Getting a router running: the actual sequence

Install with pip install -qU semantic-router. Define your Route objects, then pick an encoder. The README shows the environment-variable pattern: set COHERE_API_KEY and call CohereEncoder(), or set OPENAI_API_KEY and call OpenAIEncoder(). Build the router with SemanticRouter(encoder=encoder, routes=routes, auto_sync="local"). The auto_sync argument appears in the README example without further explanation in the material available here, so treat the value "local" as the documented starting point and read the save/load notebook before assuming what it controls. Invoke the router as a callable and read the .name attribute off the result: rl("don't you love politics?").name returns 'politics'. Because the unmatched case returns None, the safe call site is a check on the result before attribute access. For persistence, the repository includes a save/load notebook covering RouteLayer serialization to file, which matters if you want to avoid re-embedding utterances on every process start. The utterance vector space also integrates with Pinecone and Qdrant according to the README, which is the path to take if your route set outgrows an in-process index.

Where the approach breaks down

The router has no notion of intent hierarchy, negation, or context. It compares one query string to a flat bag of utterances. Two routes that share vocabulary will collide, and the README's own examples hint at this: "they're going to destroy this country!" and "they will save the country!" are both politics utterances despite opposite sentiment, which is fine for topic routing and useless for stance routing. A single query that legitimately spans two routes cannot be split; you get one name or None. Multi-turn state is not part of the mechanism, so a follow-up like "and tomorrow?" carries no memory of the previous route. The threshold is the other sharp edge. Too high and the router abstains constantly, pushing you back to an LLM fallback; too low and unrelated queries get absorbed into whichever route sits nearest. The repository includes a threshold optimization notebook, which tells you the maintainers expect this tuning step rather than treating it as automatic. If your routing decisions require reading a tool schema, resolving arguments, or chaining two steps, this library is the wrong layer and a function-calling model is the right one.

How this differs from embedding-based intent classifiers

The obvious alternative is a trained intent classifier, the kind you would build with scikit-learn or a small transformer fine-tuned on labelled examples. The difference is in the update path. A classifier learns a decision boundary from training data and you retrain to change it; semantic-router holds your utterances as data and compares at inference time, so adding a route means appending sentences, not running a training job. That is a real operational advantage for teams whose intents change weekly. It is also a real disadvantage: a classifier can learn from thousands of labelled examples and generalize to phrasings you never wrote down, while semantic-router only knows the sentences you gave it plus whatever the encoder generalizes for free. A second alternative is simply keeping the LLM call and accepting the latency. That is the honest comparison, and the README's framing is that the vector comparison is faster than a generation. Whether the speedup matters depends on your budget, and the library does not remove the LLM from your stack, only from this particular decision.

Maintenance, releases, and the MIT licence

The project is MIT licensed, which permits commercial use, modification, and redistribution provided the copyright notice and permission notice are retained. That is permissive and imposes no copyleft obligation on your own code. It also means no warranty and no liability from the authors, so the correctness of a routing decision that sends a user down the wrong path is yours. On maintenance, the release history shows v0.1.15 in May 2026, v0.1.16 in July 2026, and v0.2.0.dev1 in August 2026. The presence of a dev pre-release as the most recent tag means the version you pin matters: pip install semantic-router will not pull a dev build by default, but a requirements file that pins loosely could land you on a moving target. Pin an explicit version. The 0.x version numbers also mean the public API is not yet promised as stable, and the README already shows SemanticRouter imported from semantic_router.routers while the prose still refers to a RouteLayer, which suggests the naming has shifted across versions. Check the import path against your pinned version rather than copying examples from the current docs.

Editorial conclusion

Adopt semantic-router if you have a bounded set of intents and you want to stop paying an LLM round trip to pick between them. Do not adopt it if your routing logic needs multi-step reasoning, because the library decides by nearest-neighbour similarity over utterances you supply by hand. Before wiring it into anything, run your own queries through a RouteLayer with auto_sync="local" and check how often it returns None, then look at the threshold optimization notebook to see whether tuning recovers the misses.

Official sources

  1. aurelio-labs/semantic-router on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Community notes

Community notes