Skip to main content

Module context_provider

Module context_provider 

Source
Expand description

A ContextProvider that stores and retrieves conversation memories in Redis, scoped by application/agent/user/thread id.

Mirrors the scoping and prompt-injection behavior of the Python agent_framework_redis.RedisProvider: after_run() persists the request/response exchange as JSON entries tagged with the provider’s scope, and before_run() retrieves matching entries and injects them into the conversation as a single user-role Message prefixed by DEFAULT_CONTEXT_PROMPT (same default text as Python’s ContextProvider.DEFAULT_CONTEXT_PROMPT).

Python’s RedisProvider is built on redisvl and RediSearch: it maintains a FT.CREATEd index with TAG/TEXT/VECTOR fields, and before_run() runs a server-side TextQuery (BM25 full-text) or HybridQuery (BM25 + KNN vector similarity, when a vectorizer is configured) against that index.

RediSearch is a separate Redis module — bundled with Redis Stack but not with plain open-source redis-server — so RedisContextProvider detects it at runtime (once per provider instance, cached — see FT._LIST/with_force_scan_fallback) and switches behavior accordingly:

  • RediSearch available (Redis Stack): each memory is written with JSON.SET {key_prefix}:entry:{uuid} $ <entry> instead of a plain SET, so it’s visible to an FT.CREATE ... ON JSON PREFIX 1 {key_prefix}:entry: SCHEMA ... index (created lazily, once, and tolerant of a concurrent creator via Redis’s own “Index already exists” error) covering every MemoryEntry field: content is TEXT (BM25-scored full-text); application_id/agent_id/user_id/ thread_id/role/message_id/author_name are TAG (exact-match scope filtering); rank is NUMERIC SORTABLE. before_run() runs a single FT.SEARCH combining ANDed TAG filters for the configured scope with an ORed @content:(...) clause over the same lowercased, stopword-filtered tokens the SCAN path uses (tokenize_query), LIMITed to the configured RedisContextProvider::with_limit, and ranked by RediSearch’s own default scorer (a BM25 variant — no explicit SCORER argument is sent, since the exact literal scorer name has changed across RediSearch versions — BM25 was renamed BM25STD in Redis Open Source 8.4 — while the default scorer has been BM25-family since RediSearch 1.x, so omitting SCORER gets BM25 scoring portably). All caller-supplied scope values and query tokens are backslash-escaped against RediSearch’s reserved query-syntax characters before being interpolated into the query string (escape_redisearch), so they can never be misinterpreted as query operators.
  • RediSearch unavailable (plain redis-server), or when RedisContextProvider::with_force_scan_fallback is set: the original SCAN-based behavior, documented in detail below, is used unchanged.

Vector/hybrid (KNN) search is still not ported — that remains this crate’s one substantive gap versus Python, since it requires an embedding model/vectorizer this crate has no opinion on. Bring your own vector store (or embed client-side and extend this provider) if you need semantic recall; BM25 full-text (when Redis Stack is available) or token-match (otherwise) is what you get here.

Because RediSearch requires documents to be stored as native JSON (JSON.SET) rather than opaque strings (SET) to be indexable, the two storage encodings are not cross-readable: entries written while RediSearch was available are invisible to the plain SCAN+MGET fallback (which only sees string-typed keys), and vice versa. This only matters if a deployment’s effective RediSearch usage changes after memories already exist (e.g. migrating from OSS Redis to Redis Stack, or flipping RedisContextProvider::with_force_scan_fallback mid-flight) — capability is detected once per provider instance and does not retroactively reconcile entries written under the other encoding.

§The SCAN fallback (RediSearch unavailable)

  1. Stores each memory as its own key {key_prefix}:entry:{uuid} holding a JSON blob (content, role, scope fields, a client-assigned recency rank) — no index, no schema, no FT.CREATE/overwrite_index.
  2. On before_run(), enumerates candidates with SCAN MATCH {key_prefix}:entry:* (cursor-based, non-blocking — never KEYS), MGETs their values, and filters entirely client-side:
    • scope filter: exact-match AND over whichever of application/agent/user/thread id are configured (same semantics as Python’s Tag(k) == v conjunction), then
    • optional naive full-text filter: if the query text is non-empty, split it into lowercased alphanumeric tokens, drop a small built-in stopword list (the, is, of, …), and keep only entries whose content contains (substring, not word-boundary) at least one remaining token — a keyword check, not BM25 ranking or embeddings, then
    • sort by recency (most recent first) and take the configured limit (default 10, matching Python’s num_results default).

There is no relevance ranking beyond “did a token match” plus recency, no stemming, and — because every fallback before_run() call does a full SCAN over the prefix — retrieval is O(entries under key_prefix) rather than O(log n)/index-accelerated. This is adequate for modest memory volumes (demos, tests, small deployments); reach for Redis Stack (giving you the FT.SEARCH path above) for anything larger.

application_id participates in the scope filter here — unlike in the sibling agent-framework-mem0 crate’s Mem0Provider, where it is write-only metadata — matching the Python RedisProvider, which includes application_id in its combined Tag filter for both reads and writes.

Structs§

RedisContextProvider
Redis-backed ContextProvider: recency-based memory storage/retrieval scoped by application/agent/user/thread id, upgraded to a real FT.SEARCH BM25 full-text index when the connected server supports RediSearch (Redis Stack). See the module docs for the RediSearch-vs-SCAN behavior and the remaining vector-search divergence from Python’s RedisProvider.

Constants§

DEFAULT_CONTEXT_PROMPT
Default context-injection header, byte-for-byte identical to Python’s agent_framework.ContextProvider.DEFAULT_CONTEXT_PROMPT.
DEFAULT_KEY_PREFIX
Default Redis key prefix, matching the Python provider’s default prefix.
DEFAULT_LIMIT
Default number of memories returned by before_run(), matching Python’s RedisProvider._redis_search(..., num_results=10) default.