Skip to content
Open
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
6 changes: 6 additions & 0 deletions redisvl/extensions/cache/llm/semantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,12 @@ def __init__(
# Create the search index in Redis
self._index.create(overwrite=self.overwrite, drop=False)

def __repr__(self) -> str:
return (
f"SemanticCache(name={self.name!r}, "
f"distance_threshold={self.distance_threshold}, ttl={self.ttl})"
)

def _modify_schema(
self,
schema: SemanticCacheIndexSchema,
Expand Down
3 changes: 3 additions & 0 deletions redisvl/extensions/message_history/message_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ def __init__(

self._default_session_filter = Tag(SESSION_FIELD_NAME) == self._session_tag

def __repr__(self) -> str:
return f"MessageHistory(name={self._name!r}, session_tag={self._session_tag!r})"

def clear(self) -> None:
"""Clears the conversation message history."""
self._index.clear()
Expand Down
6 changes: 6 additions & 0 deletions redisvl/extensions/message_history/semantic_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ def __init__(

self._default_session_filter = Tag(SESSION_FIELD_NAME) == self._session_tag

def __repr__(self) -> str:
return (
f"SemanticMessageHistory(name={self._name!r}, "
f"session_tag={self._session_tag!r}, distance_threshold={self.distance_threshold})"
)

def clear(self) -> None:
"""Clears the message history."""
self._index.clear()
Expand Down
3 changes: 3 additions & 0 deletions redisvl/extensions/router/semantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ def _initialize_index(
# write the routes to Redis
self._add_routes(self.routes)

def __repr__(self) -> str:
return f"SemanticRouter(name={self.name!r}, routes={len(self.routes)})"

@property
def route_names(self) -> List[str]:
"""Get the list of route names.
Expand Down
6 changes: 6 additions & 0 deletions redisvl/index/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,12 @@ def key(self, id: str) -> str:
key_separator=self.schema.index.key_separator,
)

def __repr__(self) -> str:
return (
f"{type(self).__name__}(name={self.name!r}, prefixes={self.prefixes!r}, "
f"storage_type={self.storage_type.value!r})"
)
Comment on lines +403 to +407
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description/example output shows SearchIndex/AsyncSearchIndex repr using prefix=..., but the implementation prints prefixes=[...]. Consider updating the PR description/examples to match, or adjust __repr__ to emit prefix= when there is a single prefix (and only use prefixes= for multi-prefix schemas) to keep the stated intent and actual output aligned.

Copilot uses AI. Check for mistakes.


class SearchIndex(BaseSearchIndex):
"""A search index class for interacting with Redis as a vector database.
Expand Down
206 changes: 206 additions & 0 deletions tests/unit/test_repr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
"""Unit tests for __repr__ implementations on core RedisVL classes."""

from unittest.mock import MagicMock, patch

import pytest

from redisvl.index import AsyncSearchIndex, SearchIndex
from redisvl.schema import IndexSchema

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _make_schema(name="my-index", prefix="rvl", storage_type="hash"):
return IndexSchema.from_dict(
{"index": {"name": name, "prefix": prefix, "storage_type": storage_type}}
)


def _make_mock_vectorizer(dims=768, dtype="float32"):
mock_vec = MagicMock()
mock_vec.dims = dims
mock_vec.dtype = dtype
return mock_vec


# ---------------------------------------------------------------------------
# SearchIndex / AsyncSearchIndex
# ---------------------------------------------------------------------------


def test_search_index_repr_hash():
schema = _make_schema(name="test-index", prefix="rvl", storage_type="hash")
index = SearchIndex(schema=schema)
assert (
repr(index)
== "SearchIndex(name='test-index', prefixes=['rvl'], storage_type='hash')"
)


def test_search_index_repr_json():
schema = _make_schema(name="docs", prefix="doc", storage_type="json")
index = SearchIndex(schema=schema)
assert (
repr(index) == "SearchIndex(name='docs', prefixes=['doc'], storage_type='json')"
)


def test_async_search_index_repr():
schema = _make_schema(name="async-idx", prefix="data", storage_type="json")
index = AsyncSearchIndex(schema=schema)
assert (
repr(index)
== "AsyncSearchIndex(name='async-idx', prefixes=['data'], storage_type='json')"
)


# ---------------------------------------------------------------------------
# SemanticCache
# ---------------------------------------------------------------------------


@pytest.fixture()
def patched_semantic_cache():
"""Patch out Redis and HFTextVectorizer so SemanticCache can be instantiated
without a running Redis server or ML model download."""
mock_vec = _make_mock_vectorizer()
mock_idx = MagicMock()
mock_idx.exists.return_value = False

with patch(
"redisvl.extensions.cache.llm.semantic.HFTextVectorizer",
return_value=mock_vec,
):
with patch(
"redisvl.extensions.cache.llm.semantic.SearchIndex",
return_value=mock_idx,
):
yield


def test_semantic_cache_repr_defaults(patched_semantic_cache):
from redisvl.extensions.cache.llm.semantic import SemanticCache

cache = SemanticCache(name="llmcache", distance_threshold=0.1)
assert (
repr(cache)
== "SemanticCache(name='llmcache', distance_threshold=0.1, ttl=None)"
)


def test_semantic_cache_repr_with_ttl(patched_semantic_cache):
from redisvl.extensions.cache.llm.semantic import SemanticCache

cache = SemanticCache(name="my-cache", distance_threshold=0.2, ttl=300)
assert (
repr(cache) == "SemanticCache(name='my-cache', distance_threshold=0.2, ttl=300)"
)


# ---------------------------------------------------------------------------
# SemanticRouter
# ---------------------------------------------------------------------------


def test_semantic_router_repr():
from redisvl.extensions.router.schema import Route
from redisvl.extensions.router.semantic import SemanticRouter

routes = [
Route(name="greeting", references=["hello", "hi"]),
Route(name="farewell", references=["bye", "goodbye"]),
]
# model_construct bypasses __init__ (and therefore Redis/vectorizer setup)
# while still setting the Pydantic fields that __repr__ reads.
router = SemanticRouter.model_construct(name="my-router", routes=routes)
assert repr(router) == "SemanticRouter(name='my-router', routes=2)"


def test_semantic_router_repr_single_route():
from redisvl.extensions.router.schema import Route
from redisvl.extensions.router.semantic import SemanticRouter

routes = [Route(name="support", references=["help", "issue"])]
router = SemanticRouter.model_construct(name="support-router", routes=routes)
assert repr(router) == "SemanticRouter(name='support-router', routes=1)"


# ---------------------------------------------------------------------------
# MessageHistory
# ---------------------------------------------------------------------------


@pytest.fixture()
def patched_message_history():
"""Patch SearchIndex.create so MessageHistory init doesn't need Redis."""
with patch("redisvl.extensions.message_history.message_history.SearchIndex.create"):
yield


def test_message_history_repr(patched_message_history):
from redisvl.extensions.message_history.message_history import MessageHistory

mh = MessageHistory(name="chat", session_tag="abc123")
assert repr(mh) == "MessageHistory(name='chat', session_tag='abc123')"


def test_message_history_repr_custom_name(patched_message_history):
from redisvl.extensions.message_history.message_history import MessageHistory

mh = MessageHistory(name="support-chat", session_tag="sess-001")
assert repr(mh) == "MessageHistory(name='support-chat', session_tag='sess-001')"


# ---------------------------------------------------------------------------
# SemanticMessageHistory
# ---------------------------------------------------------------------------


@pytest.fixture()
def patched_semantic_message_history():
"""Patch out Redis and HFTextVectorizer for SemanticMessageHistory."""
mock_vec = _make_mock_vectorizer(dims=384)
mock_idx = MagicMock()
mock_idx.exists.return_value = False

with patch(
"redisvl.extensions.message_history.semantic_history.HFTextVectorizer",
return_value=mock_vec,
):
with patch(
"redisvl.extensions.message_history.semantic_history.SearchIndex",
return_value=mock_idx,
):
yield


def test_semantic_message_history_repr(patched_semantic_message_history):
from redisvl.extensions.message_history.semantic_history import (
SemanticMessageHistory,
)

smh = SemanticMessageHistory(
name="sem-chat", session_tag="sess-42", distance_threshold=0.3
)
assert (
repr(smh)
== "SemanticMessageHistory(name='sem-chat', session_tag='sess-42', distance_threshold=0.3)"
)


def test_semantic_message_history_repr_custom_threshold(
patched_semantic_message_history,
):
from redisvl.extensions.message_history.semantic_history import (
SemanticMessageHistory,
)

smh = SemanticMessageHistory(
name="history", session_tag="s1", distance_threshold=0.5
)
assert (
repr(smh)
== "SemanticMessageHistory(name='history', session_tag='s1', distance_threshold=0.5)"
)