Skip to content

fix(router): apply per-route thresholds before max_k truncation - #766

Open
vishal-bala wants to merge 2 commits into
mainfrom
fix/router-filter-before-sortby
Open

vishal-bala wants to merge 2 commits into
mainfrom
fix/router-filter-before-sortby

Conversation

@vishal-bala

@vishal-bala vishal-bala commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

SemanticRouter._build_aggregate_request built its FT.AGGREGATE as GROUPBY → REDUCE → SORTBY @distance MAX max_k → FILTER. FT.AGGREGATE runs its steps in the order given, and redis-py emits them in call order, so the router cut the result down to the max_k closest routes before it checked each route's own distance_threshold. When a close route missed its threshold, it still took a slot, and a farther route that met its threshold never reached the FILTER.

route() (and calling the router directly) always aggregates with max_k=1, whatever RoutingConfig.max_k says. So the most common call was always exposed: whenever the closest route missed its own threshold, route() returned an empty RouteMatch, even if another route qualified. route_many() could likewise return fewer matches than it should.

For example, with the test suite's all-mpnet-base-v2 vectorizer, avg aggregation and max_k=1, the query "hello" sits about 0.07 from greeting (threshold 0.01) and about 0.60 from farewell (threshold 0.8). The old plan kept only greeting, the FILTER then dropped it, and nothing came back.

The fix calls .filter() before .sort_by(). build_args() now shows GROUPBY … REDUCE … FILTER … SORTBY 2 @distance ASC MAX 1. RedisVL has no async router, so nothing else needed changing.

Tests

  • New integration test test_threshold_filter_applies_before_max_k_truncation covers both route() and route_many(), with max_k taken from the routing config. It fails on main (assert None == 'farewell') and passes with the fix.
  • tests/integration/test_semantic_router.py and tests/unit/test_semantic_router_queries.py pass locally against Redis 8.2 (43 passed, 1 skipped).

Release Notes

Fixed SemanticRouter.route() and route_many() sometimes missing a route that met its distance_threshold. Each route's threshold is now applied before results are truncated to max_k, so a closer route that fails its own threshold no longer displaces a farther one that passes. Previously route() could return an empty RouteMatch and route_many() could return fewer matches than expected.


Note

Medium Risk
Changes Redis aggregate step order and can alter which route route() / route_many() return when the nearest route fails its threshold.

Overview
SemanticRouter was building FT.AGGREGATE with SORTBY … MAX max_k before the per-route distance_threshold FILTER, so the closest routes were truncated first and a nearer route that failed its threshold could block a farther route that passed—often yielding an empty route() result or too few route_many() matches.

_build_aggregate_request now chains .filter() before .sort_by(), so thresholds are applied before max_k limits results. An integration test covers the case where greeting is closer but below threshold and farewell is selected for "hello".

Reviewed by Cursor Bugbot for commit a433882. Bugbot is set up for automated code reviews on this repo. Configure here.

The FT.AGGREGATE plan ran SORTBY @distance MAX max_k before FILTER, so a
closer route that missed its own distance_threshold could crowd out a
farther route that met its threshold. route() then returned an empty
RouteMatch and route_many() returned fewer matches than it should.

Call .filter() before .sort_by() so the plan is GROUPBY -> REDUCE ->
FILTER -> SORTBY MAX.
Address review feedback: the test's premise relies on avg aggregation
(under min, greeting would sit at ~0 and pass its threshold), so pin it
rather than depend on the default. Exercise max_k via the routing config
in route_many(), and say in the builder comment that FT.AGGREGATE itself
runs steps in order.
@vishal-bala
vishal-bala marked this pull request as ready for review October 15, 2026 08:39
@vishal-bala vishal-bala added the auto:patch Increment the patch version when merged label Oct 15, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto:patch Increment the patch version when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant