Repository navigation
Conversation
RedisSemanticCache passed llm_string to RedisVL only as metadata. RedisVL keys entries by prompt and filters, so the same prompt cached for two llm_strings landed on the same key and the second write replaced the first. lookup() also asked RedisVL for a single nearest hit and compared llm_string afterwards, so a closer entry for another model hid a valid one. Store an MD5 of llm_string as a tag field via filterable_fields, pass it as filters on store and as a Tag filter on check. Indexes created by earlier versions lack the field, so the constructor now raises a ValueError pointing at the new RedisSemanticCache.migrate_index(). It adds the field with FT.ALTER and keeps the keys, leaving pre-upgrade entries as misses. With backfill=True it also moves those entries to their new keys, keeping their fields and expiration deadline. It is safe to rerun. Fixes langchain-ai#216
limjoobin
approved these changes
Nov 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #216.
limjoobin this has the migration utility you asked for on the issue: explicit, cold by default, with an optional backfill.
What
RedisSemanticCachepassedllm_stringto RedisVL only asmetadata, which isn't part of the entry key. The same prompt cached for twollm_strings landed on one key and the second write replaced the first.lookup()also fetched a single nearest hit and comparedllm_stringafterwards, so a closer entry from another model hid a valid one.Change
Entries now carry an
llm_string_hashtag field (an MD5, since rawllm_strings contain tag separators).update()passes it asfilters, which also puts it in the key, andlookup()filters on it in the search. Same for the async methods.An index created by an earlier version lacks that field, so the constructor now raises a
ValueErrorthat points tomigrate_index(). Any other schema mismatch raises the same error as before.It's a classmethod because the constructor raises on a legacy index, and it doesn't need embeddings: backfill reuses the stored vectors.
Index upgrade: I used
FT.ALTER ... SKIPINITIALSCANrather than theoverwrite=TrueI suggested on the issue. Keys are untouched, there's no moment without an index, and nothing gets re-indexed (legacy entries don't have the field anyway). A plainFT.ALTERre-indexes every document in the background; with 20k entries,max_doc_idwent from 20000 to 40000.Backfill: for each legacy entry, all fields are copied as raw bytes to the key the new write path uses, with
llm_string_hashadded andentry_idupdated. The write and thePEXPIREATof the original deadline share aMULTI, so a replacement never exists without its expiration. Entries with no expiration keep none. The source is deleted only after that.To rerun it safely, tagged entries are skipped. If the replacement already exists (interrupted run or newer write), it's kept and only the source is removed.
A few things you didn't specify, so please check them:
llm_stringcan't be read stay in place, since their new key can't be computed. They're logged and counted in the returned{"backfilled": n, "skipped": m}.foo:tenant:<id>when migratingfoo, which thename+prefixmapping can produce).Testing
test_semantic_cache_llm_string.py. The migration ones cover cold start, rerun, field and deadline preservation,decode_responsesclients, unreadable entries, and failures both before the write and before the source delete.pyproject.toml).main, 5,000 entries each. Every entry was a hit after backfill, and all deadlines were unchanged.test_chat_message_history.pyfails the same way on unmodifiedmainin my environment ('dict' object has no attribute 'index'when parsingFT.INFO), so I left it out of the runs above.ruff check,mypyandcodespellpass.ruff format --check .only flagsREADME.md, as it does onmain.Release note
RedisSemanticCachenow keeps entries separate perllm_string, so different models or parameter sets no longer overwrite each other's entries or hide each other's hits.Action required for existing caches: an index created by an earlier version now raises a
ValueError. Stop the applications using the cache, runRedisSemanticCache.migrate_index(), then deploy the new version. Entries cached before the upgrade are no longer returned unless you passbackfill=True. Old and new versions can't share a migrated index.