Router Group

On this page

Provides the "module" abstraction: RouteGroup is a self-contained namespace of routes with its own intent description, usable as a first-class routing target by the top-level router. Also provides three built-in group templates.

RouteGroup

Python · 11 lines
class RouteGroup:
    def __init__(
        self,
        name: str,
        description: str,
        intent_examples: Optional[List[str]] = None,
        intent_keywords: Optional[RouteKeywords] = None,
        priority: int = 0,
        enabled: bool = True,
        metadata: Optional[Dict[str, Any]] = None,
    ): ...

A named collection of Route objects sharing a common intent. The parent HierarchicalRouter first identifies the best group, then delegates route selection within it.

Constructor

Parameters:

NameTypeRequiredDefaultDescription
namestrYes—Unique name (e.g. "rag", "tools", "chat"). Stripped of whitespace.
descriptionstrYes—Intent description used for top-level (group-level) routing. Stripped of whitespace.
intent_examplesOptional[List[str]]NoNone → []Sample queries signaling this group's intent; deduplicated.
intent_keywordsOptional[RouteKeywords]NoNone → RouteKeywords()Keyword signals for group-level selection.
priorityintNo0Group-level tie-breaker (higher wins).
enabledboolNoTrueWhether this group participates in routing.
metadataOptional[Dict[str, Any]]NoNone → {}Arbitrary labels/config.

Raises: ValueError — if name is empty or whitespace-only.

Attributes

AttributeTypeDescription
name, description, priority, enabled, metadata—As passed to the constructor (post-processing noted above).
intent_examplesList[str]Deduplicated intent examples.
intent_keywordsRouteKeywordsGroup-level keyword rules.
embeddingsList[List[float]]Group-level intent embeddings, computed externally by EmbeddingProvider.encode_group.
created_at, updated_atfloatLifecycle timestamps.

(_routes: Dict[str, Route] is a private implementation detail backing route storage — accessed only through the methods below.)

Route management methods

add_route(self, route: Route) -> RouteGroup

Registers a route under this group. Sets route.group = self.name as a side effect.

Raises: ValueError — if a route with the same name already exists in this group ("Route '{name}' already exists in group '{group}'. Use update_route() to replace it.").

Returns: self — supports fluent chaining: group.add_route(r1).add_route(r2).

update_route(self, route: Route) -> RouteGroup

Adds a route, or replaces an existing one with the same name (no error on collision, unlike add_route). Sets route.group = self.name.

Returns: self.

remove_route(self, name: str) -> bool

Removes a route by name.

Returns: True if removed, False if no route with that name existed.

get_route(self, name: str) -> Optional[Route]

Direct lookup by name; None if not found.

get_routes(self, enabled_only: bool = True) -> List[Route]

Returns all routes (optionally filtered to enabled=True), sorted by priority descending.

get_by_tag(self, tag: str) -> List[Route]

Returns all routes (regardless of enabled state) whose tags set contains tag. Not priority-sorted.

Fluent builder / decorator

route(self, name, description, examples=None, keywords=None, priority=0, tags=None, tools=None)

Returns a decorator that registers the decorated class or function as a handler for a new Route, and returns the original decorated object unchanged.

Behavior by decorated type:

Decorated objectHandling
A type subclassing BaseHandlerInstantiated with no arguments: handler_or_cls().
A BaseHandler instanceUsed directly.
Any other callableWrapped in CallableHandler.
Anything elseTypeError(f"Cannot use {type(handler_or_cls)} as a handler.")

Example:

Python · 8 lines
@group.route(
    name="greet",
    description="Greet the user",
    examples=["hello", "hi"],
)
class GreetHandler(BaseHandler):
    def handle(self, request):
        return HandlerResponse.ok("Hi there!")

[!WARNING] No-argument instantiation only Because a decorated BaseHandler subclass is instantiated as handler_or_cls(), this decorator pattern only works for handlers whose __init__ takes no required arguments. Handlers needing configuration (API keys, DB connections) should be instantiated manually and passed to add_route(Route(..., handler=my_instance)) instead.

Lifecycle methods

enable(self) / disable(self)

Toggle self.enabled; refresh updated_at. Note this does not cascade to the group's individual routes — a disabled group's routes retain their own enabled state, they're simply not reachable because RoutingPipeline.run filters active_groups = [g for g in groups if g.enabled] before ever considering routes.

invalidate_embeddings(self)

Clears self.embeddings and calls route.invalidate_embeddings() on every contained route — a cascading invalidation, unlike enable/disable. Refreshes updated_at.

Introspection

summary(self) -> Dict[str, Any]

Returns {"name", "description", "enabled", "priority", "route_count", "routes" (list of names), "metadata"}.

Magic methods

  • __len__ — number of routes in the group.
  • __iter__ — iterates over Route objects (via self._routes.values()) — order is Python dict insertion order, not priority-sorted (unlike get_routes()).
  • __contains__(name) — name in group checks route names.
  • __repr__ — e.g. RouteGroup(name='rag', routes=3, status=enabled, priority=10).

Examples

1. Basic:

Python · 4 lines
from zerda.router import RouteGroup, Route

support = RouteGroup("support", "Handle customer support requests")
support.add_route(Route(name="refunds", description="Process refunds", handler=RefundHandler()))

2. Intermediate — fluent chaining + decorator style mixed:

Python · 8 lines
support = (
    RouteGroup("support", "Handle customer support requests")
    .add_route(Route(name="refunds", description="Process refunds", handler=RefundHandler()))
)

@support.route(name="shipping", description="Track shipments", examples=["where's my order"])
def shipping_handler(query: str):
    return {"status": "in transit"}

3. Advanced — tag-based filtering:

Python · 3 lines
group.add_route(Route(name="a", description="...", handler=h1, tags={"beta"}))
group.add_route(Route(name="b", description="...", handler=h2, tags={"beta", "internal"}))
beta_routes = group.get_by_tag("beta")  # [route a, route b]

4. Error handling — duplicate route name:

Python · 4 lines
try:
    group.add_route(Route(name="refunds", description="dup", handler=h))
except ValueError as e:
    print(e)  # Route 'refunds' already exists in group 'support'. Use update_route() to replace it.

5. Production-ready — bulk registration with router:

Python · 4 lines
support = RouteGroup("support", "Customer support")
for name, desc, handler, examples in load_route_definitions():
    support.add_route(Route(name=name, description=desc, handler=handler, examples=examples))
router.register_group(support)  # encodes all embeddings in one call

Best practices

  • Prefer get_routes() over iterating the group directly (for r in group) when route order matters — only get_routes() guarantees priority ordering.
  • Call invalidate_embeddings() (not individual route.invalidate_embeddings() calls) after bulk-editing many routes' examples at once, then re-encode via router.re_encode_group(name).

Common mistakes

  • Relying on for route in group: to yield routes in priority order — it yields insertion order.
  • Calling group.disable() and expecting individual routes to report enabled=False — they don't; only the group-level gate changes.

Related APIs: Route, HierarchicalRouter.register_group


Built-in group templates

Three factory functions return pre-configured RouteGroup instances covering the most common LLM application intent domains. Each returns an empty group (no routes) — you still need to add_route(...) your own routes; the templates only provide the group-level description, intent_examples, intent_keywords, and priority.

make_rag_group

Python
def make_rag_group(priority: int = 10) -> RouteGroup

Template for a Retrieval-Augmented Generation intent group — "answer questions by retrieving and synthesising from documents, knowledge bases, or vector stores."

PropertyValue
name"rag"
intent_examples6 examples, e.g. "What does the policy say about refunds?", "Retrieve the latest quarterly report"
intent_keywords.any_of["according", "find", "search", "retrieve", "document", "manual", "policy", "report"]
intent_keywords.excluded["write", "create", "generate", "code"]
intent_keywords.boost0.08
priority10 (default; overridable)
metadata{"type": "rag"}

make_tools_group

Python
def make_tools_group(priority: int = 20) -> RouteGroup

Template for a tool/function-calling intent group — "execute actions using tools: call APIs, run code, query databases, send emails, manage calendars."

PropertyValue
name"tools"
intent_examples7 examples, e.g. "Send an email to John about the meeting", "Run the data pipeline"
intent_keywords.any_of["send", "run", "execute", "call", "query", "schedule", "create", "delete", "update", "weather", "search web"]
intent_keywords.boost0.10
priority20 (default — highest of the three templates)
metadata{"type": "tools"}

make_chat_group

Python
def make_chat_group(priority: int = 0) -> RouteGroup

Template for a general conversational intent group — "handle general conversation, creative writing, coding help, and open-ended questions." Intended as the lowest-priority catch-all.

PropertyValue
name"chat"
intent_examples6 examples, e.g. "How are you?", "Write a Python function to sort a list"
intent_keywordsNo any_of/required/excluded; boost=0.02 only
priority0 (default — lowest)
metadata{"type": "chat"}

[!TIP] Why tools outranks rag outranks chat by default The default priorities (tools=20 > rag=10 > chat=0) reflect that action-taking intents are usually more specific/urgent signals than document lookups, which are in turn more specific than general chat — this ordering only matters as a scoring tie-breaker (via Route.priority/group priority), not as a hard override of the semantic/keyword score.

Example: assembling the three templates

Python · 15 lines
from zerda.router import HierarchicalRouter, make_rag_group, make_tools_group, make_chat_group, Route

router = HierarchicalRouter()

rag = make_rag_group()
rag.add_route(Route(name="docs_qa", description="...", handler=DocsQAHandler(), examples=[...]))

tools = make_tools_group()
tools.add_route(Route(name="weather", description="...", handler=WeatherHandler(), examples=[...]))

chat = make_chat_group()
chat.add_route(Route(name="general", description="...", handler=ChatHandler(), examples=[...]))

for g in (rag, tools, chat):
    router.register_group(g)

Related APIs: RouteGroup, RouteKeywords, Quickstart

Related pages