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
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
redis-py-version: ["5.x", "6.x", "7.x"]
redis-image: ["redis/redis-stack-server:latest", "redis:latest"]
steps:
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "redisvl"
version = "0.13.2"
description = "Python client library and CLI for using Redis as a vector database"
authors = [{ name = "Redis Inc.", email = "applied.ai@redis.com" }]
requires-python = ">=3.9.2,<3.14"
requires-python = ">=3.9.2,<3.15"
readme = "README.md"
license = "MIT"
keywords = [
Expand All @@ -19,6 +19,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"License :: OSI Approved :: MIT License",
]
dependencies = [
Expand Down
23 changes: 22 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
import os
import subprocess
import sys
from datetime import datetime, timezone

import pytest
Expand All @@ -9,7 +10,12 @@
from redisvl.index.index import AsyncSearchIndex, SearchIndex
from redisvl.redis.connection import RedisConnectionFactory, is_version_gte
from redisvl.redis.utils import array_to_buffer
from redisvl.utils.vectorize import HFTextVectorizer

# Check if we're on Python 3.14+ where sentence-transformers may not work
SKIP_HF = sys.version_info >= (3, 14)

if not SKIP_HF:
from redisvl.utils.vectorize import HFTextVectorizer

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -206,6 +212,8 @@ def cluster_client(redis_cluster_url):

@pytest.fixture(scope="session")
def hf_vectorizer():
if SKIP_HF:
pytest.skip("HFTextVectorizer not supported on Python 3.14+")
return HFTextVectorizer(
model="sentence-transformers/all-mpnet-base-v2",
token=os.getenv("HF_TOKEN"),
Expand All @@ -215,11 +223,15 @@ def hf_vectorizer():

@pytest.fixture(scope="session")
def hf_vectorizer_float16():
if SKIP_HF:
pytest.skip("HFTextVectorizer not supported on Python 3.14+")
return HFTextVectorizer(dtype="float16")


@pytest.fixture(scope="session")
def hf_vectorizer_with_model():
if SKIP_HF:
pytest.skip("HFTextVectorizer not supported on Python 3.14+")
return HFTextVectorizer("sentence-transformers/all-mpnet-base-v2")


Expand Down Expand Up @@ -420,6 +432,10 @@ def pytest_configure(config: pytest.Config) -> None:
config.addinivalue_line(
"markers", "requires_cluster: mark test as requiring a Redis cluster"
)
config.addinivalue_line(
"markers",
"requires_hf: mark test as requiring HuggingFace/sentence-transformers",
)


def pytest_collection_modifyitems(
Expand All @@ -436,13 +452,18 @@ def pytest_collection_modifyitems(
skip_cluster = pytest.mark.skip(
reason="Skipping test because Redis cluster is not available. Use --run-cluster-tests to run these tests."
)
skip_hf = pytest.mark.skip(
reason="Skipping test because sentence-transformers is not supported on Python 3.14+"
)

# Apply skip markers independently based on flags
for item in items:
if item.get_closest_marker("requires_api_keys") and not run_api_tests:
item.add_marker(skip_api)
if item.get_closest_marker("requires_cluster") and not run_cluster_tests:
item.add_marker(skip_cluster)
if item.get_closest_marker("requires_hf") and SKIP_HF:
item.add_marker(skip_hf)


@pytest.fixture
Expand Down
9 changes: 8 additions & 1 deletion tests/integration/test_cross_encoder_reranker.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import pytest

from redisvl.utils.rerank.hf_cross_encoder import HFCrossEncoderReranker
from tests.conftest import SKIP_HF

if not SKIP_HF:
from redisvl.utils.rerank.hf_cross_encoder import HFCrossEncoderReranker

pytestmark = pytest.mark.skipif(
SKIP_HF, reason="sentence-transformers not supported on Python 3.14+"
)


@pytest.fixture(scope="session")
Expand Down
11 changes: 9 additions & 2 deletions tests/integration/test_llmcache.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import sys
import warnings
from collections import namedtuple
from time import sleep, time
Expand All @@ -10,8 +11,14 @@
from redisvl.extensions.cache.llm import SemanticCache
from redisvl.index.index import AsyncSearchIndex, SearchIndex
from redisvl.query.filter import Num, Tag, Text
from redisvl.utils.vectorize import HFTextVectorizer
from tests.conftest import skip_if_no_redisearch, skip_if_no_redisearch_async
from tests.conftest import SKIP_HF, skip_if_no_redisearch, skip_if_no_redisearch_async

if not SKIP_HF:
from redisvl.utils.vectorize import HFTextVectorizer

pytestmark = pytest.mark.skipif(
SKIP_HF, reason="sentence-transformers not supported on Python 3.14+"
)


@pytest.fixture(scope="session")
Expand Down
15 changes: 14 additions & 1 deletion tests/integration/test_message_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@

from redisvl.extensions.constants import ID_FIELD_NAME
from redisvl.extensions.message_history import MessageHistory, SemanticMessageHistory
from tests.conftest import skip_if_no_redisearch
from tests.conftest import SKIP_HF, skip_if_no_redisearch

requires_hf = pytest.mark.skipif(
SKIP_HF, reason="sentence-transformers not supported on Python 3.14+"
)


@pytest.fixture
Expand Down Expand Up @@ -327,6 +331,7 @@ def test_standard_clear(standard_history):


# test semantic message history
@requires_hf
def test_semantic_specify_client(client, hf_vectorizer):
skip_if_no_redisearch(client)
history = SemanticMessageHistory(
Expand All @@ -339,6 +344,7 @@ def test_semantic_specify_client(client, hf_vectorizer):
assert isinstance(history._index.client, type(client))


@requires_hf
def test_semantic_bad_connection_info(hf_vectorizer):
with pytest.raises(ConnectionError):
SemanticMessageHistory(
Expand All @@ -349,6 +355,7 @@ def test_semantic_bad_connection_info(hf_vectorizer):
)


@requires_hf
def test_semantic_scope(semantic_history):
# store entries under default session tag
semantic_history.store("some prompt", "some response")
Expand Down Expand Up @@ -376,6 +383,7 @@ def test_semantic_scope(semantic_history):
assert no_context == []


@requires_hf
def test_semantic_store_and_get_recent(semantic_history):
context = semantic_history.get_recent()
assert len(context) == 0
Expand Down Expand Up @@ -461,6 +469,7 @@ def test_semantic_store_and_get_recent(semantic_history):
bad_context = semantic_history.get_recent(top_k="3")


@requires_hf
def test_semantic_messages_property(semantic_history):
semantic_history.add_messages(
[
Expand Down Expand Up @@ -505,6 +514,7 @@ def test_semantic_messages_property(semantic_history):
]


@requires_hf
def test_semantic_add_and_get_relevant(semantic_history):
semantic_history.add_message(
{"role": "system", "content": "discussing common fruits and vegetables"}
Expand Down Expand Up @@ -580,6 +590,7 @@ def test_semantic_add_and_get_relevant(semantic_history):
bad_context = semantic_history.get_relevant("test prompt", top_k="3")


@requires_hf
def test_semantic_get_raw(semantic_history):
semantic_history.store("first prompt", "first response")
semantic_history.store("second prompt", "second response")
Expand All @@ -591,6 +602,7 @@ def test_semantic_get_raw(semantic_history):
assert raw[1]["content"] == "first response"


@requires_hf
def test_semantic_drop(semantic_history):
semantic_history.store("first prompt", "first response")
semantic_history.store("second prompt", "second response")
Expand Down Expand Up @@ -679,6 +691,7 @@ def create_same_type():
)


@requires_hf
def test_vectorizer_dtype_mismatch(client, redis_url, hf_vectorizer_float16):
skip_if_no_redisearch(client)
with pytest.raises(ValueError):
Expand Down
2 changes: 2 additions & 0 deletions tests/integration/test_redis_cluster_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def test_search_index_cluster_info(redis_cluster_url):
finally:
index.delete(drop=True)


@pytest.mark.requires_cluster
@pytest.mark.asyncio
async def test_async_search_index_cluster_info(redis_cluster_url):
Expand All @@ -110,6 +111,7 @@ async def test_async_search_index_cluster_info(redis_cluster_url):
await index.delete(drop=True)
await client.aclose()


@pytest.mark.requires_cluster
@pytest.mark.asyncio
async def test_async_search_index_client(redis_cluster_url):
Expand Down
19 changes: 15 additions & 4 deletions tests/integration/test_rerankers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

import pytest

from redisvl.utils.rerank import (
CohereReranker,
HFCrossEncoderReranker,
VoyageAIReranker,
from redisvl.utils.rerank import CohereReranker, VoyageAIReranker
from tests.conftest import SKIP_HF

if not SKIP_HF:
from redisvl.utils.rerank import HFCrossEncoderReranker

requires_hf = pytest.mark.skipif(
SKIP_HF, reason="sentence-transformers not supported on Python 3.14+"
)


Expand All @@ -25,11 +29,15 @@ def reranker(request):

@pytest.fixture
def hfCrossEncoderReranker():
if SKIP_HF:
pytest.skip("HFCrossEncoderReranker not supported on Python 3.14+")
return HFCrossEncoderReranker()


@pytest.fixture
def hfCrossEncoderRerankerWithCustomModel():
if SKIP_HF:
pytest.skip("HFCrossEncoderReranker not supported on Python 3.14+")
return HFCrossEncoderReranker("cross-encoder/stsb-distilroberta-base")


Expand Down Expand Up @@ -76,6 +84,7 @@ def test_bad_input(reranker):
) # Invalid rank_by field


@requires_hf
def test_rank_documents_cross_encoder(hfCrossEncoderReranker):
query = "I love you"
texts = ["I love you", "I like you", "I don't like you", "I hate you"]
Expand All @@ -85,6 +94,7 @@ def test_rank_documents_cross_encoder(hfCrossEncoderReranker):
assert scores[i] > scores[i + 1]


@requires_hf
def test_rank_documents_cross_encoder_custom_model(
hfCrossEncoderRerankerWithCustomModel,
):
Expand All @@ -96,6 +106,7 @@ def test_rank_documents_cross_encoder_custom_model(
assert scores[i] > scores[i + 1]


@requires_hf
@pytest.mark.asyncio
async def test_async_rank_cross_encoder(hfCrossEncoderReranker):
docs = ["document one", "document two", "document three"]
Expand Down
1 change: 1 addition & 0 deletions tests/integration/test_search_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ def test_search_index_delete(index):
assert not index.exists()
assert index.name not in convert_bytes(index.client.execute_command("FT._LIST"))


@pytest.mark.parametrize("num_docs", [0, 1, 5, 10, 2042])
def test_search_index_clear(index, num_docs):
index.create(overwrite=True, drop=True)
Expand Down
6 changes: 5 additions & 1 deletion tests/integration/test_semantic_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
RoutingConfig,
)
from redisvl.redis.connection import is_version_gte
from tests.conftest import skip_if_no_redisearch, skip_if_redis_version_below
from tests.conftest import SKIP_HF, skip_if_no_redisearch, skip_if_redis_version_below

pytestmark = pytest.mark.skipif(
SKIP_HF, reason="sentence-transformers not supported on Python 3.14+"
)


def get_base_path():
Expand Down
Loading