Repository navigation
RedisVL 0.32.0 adds OpenTelemetry metrics and spans for SemanticCache and vectorizers, drops Python 3.10, adds async methods to MessageHistory, and makes SearchIndex work with RESP3 clients. Before upgrading, read the behaviour change to exists() below if your code creates an index when exists() returns False.
Highlights
OpenTelemetry support for SemanticCache and vectorizers
RedisVL now emits OpenTelemetry metrics and spans, so you can see how often SemanticCache hits, how long each operation takes, and how close every miss came to matching.
- Metrics:
redisvl.semantic_cache.operation.durationcoverscheck,store,updateanddrop, split by hit and miss, which gives you the hit rate.redisvl.semantic_cache.distancerecords the nearest candidate's distance on every lookup, hit or miss. That distance is what tells you whetherdistance_thresholdis right: if most misses land just above it, loosening it slightly turns them into hits. - Spans: each cache operation produces a span tree that separates embedding the prompt from the vector search, so a slow lookup shows where its time went. Every vectorizer also emits
embeddingsspans and thegen_ai.client.operation.durationmetric.
To turn it on, install redisvl[otel] and configure an OpenTelemetry SDK in your application. RedisVL depends on the OpenTelemetry API only and records nothing until you configure an SDK, so applications that don't use OpenTelemetry are unaffected. Prompts are recorded only when OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT is SPAN_ONLY or SPAN_AND_EVENT, and REDISVL_OTEL_DISABLED=true switches all of it off. The new guide, "Observe the Semantic Cache with OpenTelemetry", walks through the setup. (#755, #776, #777, @vishal-bala)
Python 3.10 is no longer supported
RedisVL now requires Python 3.11 or later. On Python 3.10, pip install redisvl keeps installing 0.31.0, so move to 3.11 or later to get this release and later ones. (#810, @vishal-bala)
Async methods on MessageHistory
MessageHistory now has async versions of its methods: aget_recent, astore, aadd_messages, aadd_message, acount, adrop, aclear and adisconnect. If your agent runs on asyncio, reading and writing chat history no longer blocks the event loop. The async index is created on the first async call from the history's existing connection settings, the same way SemanticRouter.aroute works. A sync Sentinel or RedisCluster client can't be converted, so pass redis_url instead if you use one. (#816, @Yarmoluk)
SemanticCache TTL refresh controls
By default, every cache hit resets the entry's TTL, so an entry that keeps getting hit never expires. You can now turn that off with refresh_ttl_on_hit=False on the constructor, or for a single lookup with refresh_ttl=False on check() or acheck(). Use it when answers should expire on a fixed schedule however popular they are. With refresh off, a hit sends no EXPIRE, so a lookup makes no writes and works under an ACL user that can only read. (#618, @omribz156, @vishal-bala)
RESP3 client support in SearchIndex
query(), paginate(), search(), clear() and delete(drop=True) now work on SearchIndex and AsyncSearchIndex with a client created with protocol=3; before, they failed on such a client. On a RESP3 client, search(), cluster_search() and async_cluster_search() now return a redis-py Result rather than the raw reply dict, so update any code that reads that dict. HybridQuery and AggregateHybridQuery are still unsupported on RESP3. (#830, @hyeonsang010716)
Behaviour change: exists() raises when Redis can't answer
SearchIndex.exists() and AsyncSearchIndex.exists() now recognise a missing index by the reply type rather than by the wording of Redis Search's error, so create() keeps working if a future Redis release rewords it. As part of that change, two errors that used to read as "index missing" now raise RedisSearchError: BUSY, while a script or function is running on the server, and ERRCLUSTER, while a cluster's Redis Search coordinator is not yet initialised. Previously exists() returned False in both cases, so code that creates the index when exists() returns False would try to create one that was already there. If you call exists() at startup, catch RedisSearchError and retry. (#818, @Yarmoluk)
Other fixes
- Vector fields declared with
index_missing: truecan now be created, becauseINDEXMISSINGis emitted after the vector attribute block. You can then useismissing(@field)to find documents that don't have an embedding yet. (#771, @RaphaelFakhri) - Hybrid queries with an explicit KNN or RANGE vector clause no longer send default tuning parameters you didn't set, which the server rejected, and hybrid RANGE now accepts a radius of
0. (#828, @gezerui888) - SVS-VAMANA
compressionvalues are no longer case-sensitive:lvq4x8in a schema now means the same asLVQ4x8instead of failing validation, and the migration wizard accepts any case too. (#809, @Bhavy-Ranka, @servis) CohereReranker.aranknow creates Cohere'sAsyncClienton first use and recreates it when the event loop changes, so it keeps working in tests or scripts that callasyncio.run()more than once; before, every call after the first loop closed failed with "Event loop is closed". If you pass your ownhttpx_client, also passasync_httpx_client; with only a sync client,arankfalls back to the blockingrankand logs a warning. (#827, @Yarmoluk)
Contributors
Thanks to @Yarmoluk, @hyeonsang010716, @RaphaelFakhri, @gezerui888, @omribz156, @Bhavy-Ranka and @servis for their contributions to this release, including first contributions from @RaphaelFakhri, @gezerui888, @omribz156, @Bhavy-Ranka and @servis.
Full changelog: v0.31.0...v0.32.0