mirror of https://github.com/hwchase17/langchain
Harrison/redis cache (#3766)
Co-authored-by: Tyler Hutcherson <tyler.hutcherson@redis.com>pull/3768/head
parent
b588446bf9
commit
be7a8e0824
@ -0,0 +1,79 @@
|
|||||||
|
# Redis
|
||||||
|
|
||||||
|
This page covers how to use the [Redis](https://redis.com) ecosystem within LangChain.
|
||||||
|
It is broken into two parts: installation and setup, and then references to specific Redis wrappers.
|
||||||
|
|
||||||
|
## Installation and Setup
|
||||||
|
- Install the Redis Python SDK with `pip install redis`
|
||||||
|
|
||||||
|
## Wrappers
|
||||||
|
|
||||||
|
### Cache
|
||||||
|
|
||||||
|
The Cache wrapper allows for [Redis](https://redis.io) to be used as a remote, low-latency, in-memory cache for LLM prompts and responses.
|
||||||
|
|
||||||
|
#### Standard Cache
|
||||||
|
The standard cache is the Redis bread & butter of use case in production for both [open source](https://redis.io) and [enterprise](https://redis.com) users globally.
|
||||||
|
|
||||||
|
To import this cache:
|
||||||
|
```python
|
||||||
|
from langchain.cache import RedisCache
|
||||||
|
```
|
||||||
|
|
||||||
|
To use this cache with your LLMs:
|
||||||
|
```python
|
||||||
|
import langchain
|
||||||
|
import redis
|
||||||
|
|
||||||
|
redis_client = redis.Redis.from_url(...)
|
||||||
|
langchain.llm_cache = RedisCache(redis_client)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Semantic Cache
|
||||||
|
Semantic caching allows users to retrieve cached prompts based on semantic similarity between the user input and previously cached results. Under the hood it blends Redis as both a cache and a vectorstore.
|
||||||
|
|
||||||
|
To import this cache:
|
||||||
|
```python
|
||||||
|
from langchain.cache import RedisSemanticCache
|
||||||
|
```
|
||||||
|
|
||||||
|
To use this cache with your LLMs:
|
||||||
|
```python
|
||||||
|
import langchain
|
||||||
|
import redis
|
||||||
|
|
||||||
|
# use any embedding provider...
|
||||||
|
from tests.integration_tests.vectorstores.fake_embeddings import FakeEmbeddings
|
||||||
|
|
||||||
|
redis_url = "redis://localhost:6379"
|
||||||
|
|
||||||
|
langchain.llm_cache = RedisSemanticCache(
|
||||||
|
embedding=FakeEmbeddings(),
|
||||||
|
redis_url=redis_url
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### VectorStore
|
||||||
|
|
||||||
|
The vectorstore wrapper turns Redis into a low-latency [vector database](https://redis.com/solutions/use-cases/vector-database/) for semantic search or LLM content retrieval.
|
||||||
|
|
||||||
|
To import this vectorstore:
|
||||||
|
```python
|
||||||
|
from langchain.vectorstores import Redis
|
||||||
|
```
|
||||||
|
|
||||||
|
For a more detailed walkthrough of the Redis vectorstore wrapper, see [this notebook](../modules/indexes/vectorstores/examples/redis.ipynb).
|
||||||
|
|
||||||
|
### Retriever
|
||||||
|
|
||||||
|
The Redis vector store retriever wrapper generalizes the vectorstore class to perform low-latency document retrieval. To create the retriever, simply call `.as_retriever()` on the base vectorstore class.
|
||||||
|
|
||||||
|
### Memory
|
||||||
|
Redis can be used to persist LLM conversations.
|
||||||
|
|
||||||
|
#### Vector Store Retriever Memory
|
||||||
|
|
||||||
|
For a more detailed walkthrough of the `VectorStoreRetrieverMemory` wrapper, see [this notebook](../modules/memory/types/vectorstore_retriever_memory.ipynb).
|
||||||
|
|
||||||
|
#### Chat Message History Memory
|
||||||
|
For a detailed example of Redis to cache conversation message history, see [this notebook](../modules/memory/examples/redis_chat_message_history.ipynb).
|
@ -0,0 +1,55 @@
|
|||||||
|
"""Test Redis cache functionality."""
|
||||||
|
import redis
|
||||||
|
|
||||||
|
import langchain
|
||||||
|
from langchain.cache import RedisCache, RedisSemanticCache
|
||||||
|
from langchain.schema import Generation, LLMResult
|
||||||
|
from tests.integration_tests.vectorstores.fake_embeddings import FakeEmbeddings
|
||||||
|
from tests.unit_tests.llms.fake_llm import FakeLLM
|
||||||
|
|
||||||
|
REDIS_TEST_URL = "redis://localhost:6379"
|
||||||
|
|
||||||
|
|
||||||
|
def test_redis_cache() -> None:
|
||||||
|
langchain.llm_cache = RedisCache(redis_=redis.Redis.from_url(REDIS_TEST_URL))
|
||||||
|
llm = FakeLLM()
|
||||||
|
params = llm.dict()
|
||||||
|
params["stop"] = None
|
||||||
|
llm_string = str(sorted([(k, v) for k, v in params.items()]))
|
||||||
|
langchain.llm_cache.update("foo", llm_string, [Generation(text="fizz")])
|
||||||
|
output = llm.generate(["foo"])
|
||||||
|
print(output)
|
||||||
|
expected_output = LLMResult(
|
||||||
|
generations=[[Generation(text="fizz")]],
|
||||||
|
llm_output={},
|
||||||
|
)
|
||||||
|
print(expected_output)
|
||||||
|
assert output == expected_output
|
||||||
|
langchain.llm_cache.redis.flushall()
|
||||||
|
|
||||||
|
|
||||||
|
def test_redis_semantic_cache() -> None:
|
||||||
|
langchain.llm_cache = RedisSemanticCache(
|
||||||
|
embedding=FakeEmbeddings(), redis_url=REDIS_TEST_URL, score_threshold=0.1
|
||||||
|
)
|
||||||
|
llm = FakeLLM()
|
||||||
|
params = llm.dict()
|
||||||
|
params["stop"] = None
|
||||||
|
llm_string = str(sorted([(k, v) for k, v in params.items()]))
|
||||||
|
langchain.llm_cache.update("foo", llm_string, [Generation(text="fizz")])
|
||||||
|
output = llm.generate(
|
||||||
|
["bar"]
|
||||||
|
) # foo and bar will have the same embedding produced by FakeEmbeddings
|
||||||
|
expected_output = LLMResult(
|
||||||
|
generations=[[Generation(text="fizz")]],
|
||||||
|
llm_output={},
|
||||||
|
)
|
||||||
|
assert output == expected_output
|
||||||
|
# clear the cache
|
||||||
|
langchain.llm_cache.clear(llm_string=llm_string)
|
||||||
|
output = llm.generate(
|
||||||
|
["bar"]
|
||||||
|
) # foo and bar will have the same embedding produced by FakeEmbeddings
|
||||||
|
# expect different output now without cached result
|
||||||
|
assert output != expected_output
|
||||||
|
langchain.llm_cache.clear(llm_string=llm_string)
|
@ -1,26 +1,60 @@
|
|||||||
"""Test Redis functionality."""
|
"""Test Redis functionality."""
|
||||||
|
|
||||||
from langchain.docstore.document import Document
|
from langchain.docstore.document import Document
|
||||||
from langchain.vectorstores.redis import Redis
|
from langchain.vectorstores.redis import Redis
|
||||||
from tests.integration_tests.vectorstores.fake_embeddings import FakeEmbeddings
|
from tests.integration_tests.vectorstores.fake_embeddings import FakeEmbeddings
|
||||||
|
|
||||||
|
TEST_INDEX_NAME = "test"
|
||||||
|
TEST_REDIS_URL = "redis://localhost:6379"
|
||||||
|
TEST_SINGLE_RESULT = [Document(page_content="foo")]
|
||||||
|
TEST_RESULT = [Document(page_content="foo"), Document(page_content="foo")]
|
||||||
|
|
||||||
|
|
||||||
|
def drop(index_name: str) -> bool:
|
||||||
|
return Redis.drop_index(
|
||||||
|
index_name=index_name, delete_documents=True, redis_url=TEST_REDIS_URL
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_redis() -> None:
|
def test_redis() -> None:
|
||||||
"""Test end to end construction and search."""
|
"""Test end to end construction and search."""
|
||||||
texts = ["foo", "bar", "baz"]
|
texts = ["foo", "bar", "baz"]
|
||||||
docsearch = Redis.from_texts(
|
docsearch = Redis.from_texts(texts, FakeEmbeddings(), redis_url=TEST_REDIS_URL)
|
||||||
texts, FakeEmbeddings(), redis_url="redis://localhost:6379"
|
|
||||||
)
|
|
||||||
output = docsearch.similarity_search("foo", k=1)
|
output = docsearch.similarity_search("foo", k=1)
|
||||||
assert output == [Document(page_content="foo")]
|
assert output == TEST_SINGLE_RESULT
|
||||||
|
assert drop(docsearch.index_name)
|
||||||
|
|
||||||
|
|
||||||
def test_redis_new_vector() -> None:
|
def test_redis_new_vector() -> None:
|
||||||
"""Test adding a new document"""
|
"""Test adding a new document"""
|
||||||
texts = ["foo", "bar", "baz"]
|
texts = ["foo", "bar", "baz"]
|
||||||
docsearch = Redis.from_texts(
|
docsearch = Redis.from_texts(texts, FakeEmbeddings(), redis_url=TEST_REDIS_URL)
|
||||||
texts, FakeEmbeddings(), redis_url="redis://localhost:6379"
|
docsearch.add_texts(["foo"])
|
||||||
|
output = docsearch.similarity_search("foo", k=2)
|
||||||
|
assert output == TEST_RESULT
|
||||||
|
assert drop(docsearch.index_name)
|
||||||
|
|
||||||
|
|
||||||
|
def test_redis_from_existing() -> None:
|
||||||
|
"""Test adding a new document"""
|
||||||
|
texts = ["foo", "bar", "baz"]
|
||||||
|
Redis.from_texts(
|
||||||
|
texts, FakeEmbeddings(), index_name=TEST_INDEX_NAME, redis_url=TEST_REDIS_URL
|
||||||
|
)
|
||||||
|
# Test creating from an existing
|
||||||
|
docsearch2 = Redis.from_existing_index(
|
||||||
|
FakeEmbeddings(), index_name=TEST_INDEX_NAME, redis_url=TEST_REDIS_URL
|
||||||
|
)
|
||||||
|
output = docsearch2.similarity_search("foo", k=1)
|
||||||
|
assert output == TEST_SINGLE_RESULT
|
||||||
|
|
||||||
|
|
||||||
|
def test_redis_add_texts_to_existing() -> None:
|
||||||
|
"""Test adding a new document"""
|
||||||
|
# Test creating from an existing
|
||||||
|
docsearch = Redis.from_existing_index(
|
||||||
|
FakeEmbeddings(), index_name=TEST_INDEX_NAME, redis_url=TEST_REDIS_URL
|
||||||
)
|
)
|
||||||
docsearch.add_texts(["foo"])
|
docsearch.add_texts(["foo"])
|
||||||
output = docsearch.similarity_search("foo", k=2)
|
output = docsearch.similarity_search("foo", k=2)
|
||||||
assert output == [Document(page_content="foo"), Document(page_content="foo")]
|
assert output == TEST_RESULT
|
||||||
|
assert drop(TEST_INDEX_NAME)
|
||||||
|
Loading…
Reference in New Issue