Quickstart
On this page
This walkthrough builds a small but realistic router with three intent groups — RAG, tools, and chat — using the built-in group templates.
1. Define handlers
Handlers implement BaseHandler.handle(). See BaseHandler for the full contract.
from zerda.router import BaseHandler, HandlerRequest, HandlerResponse
class DocsQAHandler(BaseHandler):
def handle(self, request: HandlerRequest) -> HandlerResponse:
# Pretend this retrieves from a vector store and synthesises an answer
answer = f"[docs] Answer to: {request.query}"
return HandlerResponse.ok(answer, source="docs_index")
class WeatherHandler(BaseHandler):
def handle(self, request: HandlerRequest) -> HandlerResponse:
city = request.context.get("city", "unknown")
return HandlerResponse.ok(f"It's sunny in {city}.")
class ChatHandler(BaseHandler):
def handle(self, request: HandlerRequest) -> HandlerResponse:
return HandlerResponse.ok(f"You said: {request.query}")
2. Build route groups
router ships templates for the three most common LLM application intent domains: make_rag_group, make_tools_group, and make_chat_group. Each returns a pre-configured RouteGroup with sensible intent examples, keywords, and priority.
from zerda.router import make_rag_group, make_tools_group, make_chat_group, Route, RouteKeywords
rag_group = make_rag_group()
rag_group.add_route(Route(
name="docs_qa",
description="Answer questions from the internal documentation",
handler=DocsQAHandler(),
examples=[
"What does the refund policy say?",
"How do I reset my password according to the docs?",
],
))
tools_group = make_tools_group()
tools_group.add_route(Route(
name="weather",
description="Get the current weather for a city",
handler=WeatherHandler(),
examples=["What's the weather in Cairo?", "Is it raining in London?"],
keywords=RouteKeywords(any_of=["weather", "temperature", "forecast"], boost=0.15),
))
chat_group = make_chat_group()
chat_group.add_route(Route(
name="general_chat",
description="Handle general conversation",
handler=ChatHandler(),
examples=["how are you", "tell me a joke"],
))
3. Register groups with the router
from zerda.router import HierarchicalRouter
router = HierarchicalRouter()
router.register_group(rag_group)
router.register_group(tools_group)
router.register_group(chat_group)
register_group immediately computes embeddings for the group's intent examples and every route's examples (see HierarchicalRouter.register_group), so the first route_query call doesn't pay an encoding-on-first-use penalty for these routes.
4. Route a query
result = router.route_query("What is the weather in Cairo right now?")
print(result.matched) # True
print(result.route_name) # "weather"
print(result.group_name) # "tools"
print(result.confidence) # ConfidenceLevel.HIGH (or MEDIUM/LOW depending on embeddings)
print(result.content) # "It's sunny in unknown." (no "city" in context)
Pass context through to handlers via the context argument:
result = router.route_query(
"What is the weather in Cairo right now?",
context={"city": "Cairo"},
)
print(result.content) # "It's sunny in Cairo."
5. Inspect the decision
Every RoutingResult carries a full RoutingTrace so you can see why a route was chosen:
print(result.trace.to_dict())
This includes the ranked candidate list with per-signal scores (semantic, keyword, LLM), timing breakdowns, and any notes the pipeline attached (e.g. "Best score 0.412 is below fallback threshold 0.4").
6. Record feedback (optional)
If your application can later determine whether the routed response was actually useful (a thumbs-up, a follow-up question, etc.), feed that back in:
router.feedback(route_name="weather", success=True, score=0.9)
Over time this shifts score_bias on the weather route via the FeedbackEngine, making it more (or less) likely to win close scoring decisions.
Next steps
- Read Concepts to understand how scoring and confidence work in detail.
- Read Architecture for the full request lifecycle.
- See Configuration to tune thresholds, weights, caching, and execution mode.