Cookbook: RAG + Tools + Chat Support Bot

A complete, production-shaped recipe combining everything documented on this site: three intent groups, keyword boosts, a fallback handler, feedback, and observability.

Python · 92 lines
from zerda.router import (
    HierarchicalRouter, RouterConfig, FeedbackConfig, ObservabilityConfig, LogLevel,
    make_rag_group, make_tools_group, make_chat_group,
    Route, RouteKeywords,
    BaseHandler, HandlerRequest, HandlerResponse,
)

# --- Handlers -----------------------------------------------------------

class DocsQAHandler(BaseHandler):
    def __init__(self, retriever):
        self.retriever = retriever

    async def handle_async(self, request: HandlerRequest) -> HandlerResponse:
        docs = await self.retriever.aretrieve(request.query)
        if not docs:
            return HandlerResponse.fail("No relevant documents found.")
        return HandlerResponse.ok(docs[0].text, source=docs[0].source)

    def handle(self, request: HandlerRequest) -> HandlerResponse:
        docs = self.retriever.retrieve(request.query)
        if not docs:
            return HandlerResponse.fail("No relevant documents found.")
        return HandlerResponse.ok(docs[0].text, source=docs[0].source)


class RefundToolHandler(BaseHandler):
    def handle(self, request: HandlerRequest) -> HandlerResponse:
        order_id = request.context.get("order_id")
        if not order_id:
            return HandlerResponse.fail("Missing order_id — cannot process refund.")
        # ... call your real refund API here ...
        return HandlerResponse.ok(f"Refund initiated for order {order_id}.")


class ChitChatHandler(BaseHandler):
    def handle(self, request: HandlerRequest) -> HandlerResponse:
        return HandlerResponse.ok("Happy to chat! How can I help with your order today?")


class ClarifyHandler(BaseHandler):
    def handle(self, request: HandlerRequest) -> HandlerResponse:
        return HandlerResponse.ok("I'm not sure I understood — could you rephrase that?")


# --- Assembly -------------------------------------------------------------

config = RouterConfig(
    feedback=FeedbackConfig(enabled=True, min_samples_to_adapt=20, persist_path="feedback.json"),
    observability=ObservabilityConfig(log_level=LogLevel.INFO, structured_logging=True),
)

with HierarchicalRouter(config=config) as router:
    router.set_fallback(ClarifyHandler())

    rag = make_rag_group()
    rag.add_route(Route(
        name="docs_qa",
        description="Answer from product documentation",
        handler=DocsQAHandler(my_retriever),
        examples=["What's the return policy?", "How do I reset my password?"],
    ))
    router.register_group(rag)

    tools = make_tools_group()
    tools.add_route(Route(
        name="refund",
        description="Process a refund for an order",
        handler=RefundToolHandler(),
        examples=["I want a refund", "please refund my order"],
        keywords=RouteKeywords(any_of=["refund", "money back", "cancel order"], boost=0.2),
    ))
    router.register_group(tools)

    chat = make_chat_group()
    chat.add_route(Route(
        name="chitchat",
        description="Casual conversation",
        handler=ChitChatHandler(),
        examples=["hi", "how are you"],
    ))
    router.register_group(chat)

    # --- Handle a request ---------------------------------------------
    result = router.route_query(
        "I'd like a refund for my last order",
        context={"order_id": "ORD-5521"},
    )
    print(result.content)

    # --- Record feedback once outcome is known -------------------------
    router.feedback(route_name=result.route_name, success=True, score=0.95)

Related pages: Quickstart, Concepts, RouteGroup templates

Related pages