Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion redisvl/extensions/router/semantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,8 +577,9 @@ def remove_route(self, route_name: str) -> None:
self._update_router_state()

def delete(self) -> None:
"""Delete the semantic router index."""
"""Delete the semantic router index and its persisted config."""
self._index.delete(drop=True)
self._index.client.delete(f"{self.name}:route_config") # type: ignore

def clear(self) -> None:
"""Flush all routes from the semantic router index."""
Expand Down
77 changes: 45 additions & 32 deletions tests/integration/test_key_separator_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,21 +99,26 @@ def test_semantic_router_uses_index_separator(self, redis_url):
overwrite=True,
)

# Modify the index schema to use custom separator
router._index.schema.index.key_separator = "|"
router._index.schema.index.prefix = "router"
try:
# Modify the index schema to use custom separator
router._index.schema.index.key_separator = "|"
router._index.schema.index.prefix = "router"

# Check that route reference keys use the custom separator
route_key = router._route_ref_key(router._index, "test_route", "ref123")
# Check that route reference keys use the custom separator
route_key = router._route_ref_key(router._index, "test_route", "ref123")

# Should use custom separator
assert "|" in route_key, f"Route key doesn't use custom separator: {route_key}"
assert (
route_key.count(":") == 0
), f"Route key uses default separator: {route_key}"
assert (
route_key == "router|test_route|ref123"
), f"Unexpected route key: {route_key}"
# Should use custom separator
assert (
"|" in route_key
), f"Route key doesn't use custom separator: {route_key}"
assert (
route_key.count(":") == 0
), f"Route key uses default separator: {route_key}"
assert (
route_key == "router|test_route|ref123"
), f"Unexpected route key: {route_key}"
finally:
router.delete()

def test_prefix_with_separator_and_custom_separator(self):
"""Test handling when prefix contains old separator and we use a new one."""
Expand Down Expand Up @@ -204,19 +209,22 @@ def test_router_respects_modified_key_separator(self, redis_url):
overwrite=True,
)

# Test with different separators
for separator in [":", "-", "_", "|"]:
router._index.schema.index.key_separator = separator
router._index.schema.index.prefix = "routes"
try:
# Test with different separators
for separator in [":", "-", "_", "|"]:
router._index.schema.index.key_separator = separator
router._index.schema.index.prefix = "routes"

# Test internal key generation
route_key = router._route_ref_key(router._index, "route1", "ref1")
# Test internal key generation
route_key = router._route_ref_key(router._index, "route1", "ref1")

# Should use the configured separator
expected = f"routes{separator}route1{separator}ref1"
assert (
route_key == expected
), f"For sep '{separator}': Expected '{expected}' but got '{route_key}'"
# Should use the configured separator
expected = f"routes{separator}route1{separator}ref1"
assert (
route_key == expected
), f"For sep '{separator}': Expected '{expected}' but got '{route_key}'"
finally:
router.delete()

def test_router_with_prefix_ending_in_separator(self, redis_url):
"""Test SemanticRouter when prefix ends with separator."""
Expand All @@ -231,16 +239,21 @@ def test_router_with_prefix_ending_in_separator(self, redis_url):
overwrite=True,
)

# Modify to have prefix ending with separator
router._index.schema.index.prefix = "routes:"
router._index.schema.index.key_separator = ":"
try:
# Modify to have prefix ending with separator
router._index.schema.index.prefix = "routes:"
router._index.schema.index.key_separator = ":"

# Generate a route key
route_key = router._route_ref_key(router._index, "route1", "ref1")
# Generate a route key
route_key = router._route_ref_key(router._index, "route1", "ref1")

# Should not have double separator
assert "::" not in route_key, f"Route key has double separator: {route_key}"
assert route_key == "routes:route1:ref1", f"Unexpected route key: {route_key}"
# Should not have double separator
assert "::" not in route_key, f"Route key has double separator: {route_key}"
assert (
route_key == "routes:route1:ref1"
), f"Unexpected route key: {route_key}"
finally:
router.delete()


class TestSearchIndexKeyConstruction:
Expand Down
30 changes: 30 additions & 0 deletions tests/integration/test_semantic_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,36 @@ def test_add_route_survives_from_existing(
router.delete()


def test_delete_removes_route_config_key(
client, redis_url, routes, redis_test_name, hf_vectorizer
):
"""router.delete() must also remove the {name}:route_config JSON key,
not just drop the search index (see issue #634)."""
skip_if_no_redis_search(client)

router = SemanticRouter(
name=redis_test_name("test_delete_config"),
routes=routes,
routing_config=RoutingConfig(max_k=2),
redis_client=client,
overwrite=True,
vectorizer=hf_vectorizer,
)
config_key = f"{router.name}:route_config"

# Config key is persisted on init.
assert client.exists(config_key) == 1

router.delete()

# After delete(), no orphaned config key remains.
assert client.exists(config_key) == 0

# from_existing() on a deleted router must not read stale config.
with pytest.raises(Exception):
SemanticRouter.from_existing(name=router.name, redis_client=client)


def test_add_route_duplicate_raises(semantic_router):
duplicate = Route(
name="greeting",
Expand Down