Tutorial: Closing the Feedback Loop
On this page
This tutorial shows how to let real usage data adjust routing behavior over time via FeedbackEngine.
1. Enable feedback with a low sample threshold (for demonstration)
from zerda.router import HierarchicalRouter, RouterConfig, FeedbackConfig
config = RouterConfig(
feedback=FeedbackConfig(
enabled=True,
min_samples_to_adapt=5, # adapt quickly for this demo
persist_path="demo_feedback.json",
),
)
router = HierarchicalRouter(config=config)
# ... register groups/routes as usual ...
2. Simulate routing + outcome feedback
In production, "outcome" typically comes from a user signal (thumbs up/down, a follow-up clarifying question, task completion). For this tutorial, we simulate it:
import random
queries = ["what's the refund policy?"] * 20
for q in queries:
result = router.route_query(q)
# Simulate: this route is actually good 90% of the time
was_useful = random.random() < 0.9
router.feedback(route_name=result.route_name, success=was_useful, score=0.8)
3. Inspect the learned bias
summary = router.feedback_summary()
print(summary["states"]["docs_qa"])
# {'route_name': 'docs_qa', 'ema': 0.87, 'n_samples': 20, 'n_success': 18,
# 'n_failure': 2, 'success_rate': 0.9, 'bias': 0.222}
The bias value (clipped to [-0.3, +0.3]) is now live on router.get_route("docs_qa").score_bias, and will be added into every future scoring pass for that route — see Architecture → Feedback loop.
4. Persist and restore across restarts
router._feedback.save() # or use `with HierarchicalRouter(...) as router:` for auto-save
# On next process start, constructing FeedbackEngine with the same persist_path
# automatically restores prior learned biases:
router2 = HierarchicalRouter(config=config)
print(router2.get_route("docs_qa").score_bias) # reflects the persisted bias, once routes are registered
[!WARNING] Bias only applies once you register the route again Persisted feedback state lives in
FeedbackEngine, keyed by route name — it isn't automatically re-applied to aRouteobject untilapply_biases(route_map)runs again (which happens on the nextrouter.feedback(...)call or_post_routehook). If you need biases applied immediately after restoring persisted state and re-registering routes, callrouter._feedback.apply_biases(router._route_map)explicitly.
Best practices
- Start with a conservative
min_samples_to_adapt(the default is10) in production — the demo above lowers it only to make the effect visible quickly. - Remember
FeedbackConfig.score_decaydoes not control the EMA smoothing rate — see the caveat inFeedbackEngineif you need to tune adaptation speed. - Combine automatic feedback (from
_post_route'sresult.success) with manual, higher-quality feedback (router.feedback(...)after a user signal) — both write to the same underlying state.