Implement redis memory backend.

This commit is contained in:
BillSchumacher
2023-04-07 00:08:25 -05:00
parent 6819799ebe
commit 5a1d9e6d0a
7 changed files with 175 additions and 4 deletions

View File

@@ -149,6 +149,27 @@ are loaded for the agent at any given time.
2. Choose the `Starter` plan to avoid being charged.
3. Find your API key and region under the default project in the left sidebar.
## Redis Setup
Install docker desktop.
Run:
```
docker run -d --name redis-stack-server -p 6379:6379 redis/redis-stack-server:latest
```
Set the following environment variables:
```
MEMORY_BACKEND=redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
```
Note that this is not intended to be run facing the internet and is not secure, do not expose redis to the internet without a password or at all really.
### Setting up environment variables
For Windows Users:
```

View File

@@ -12,3 +12,4 @@ docker
duckduckgo-search
google-api-python-client #(https://developers.google.com/custom-search/v1/overview)
pinecone-client==2.2.1
redis

View File

@@ -1,6 +1,7 @@
import browse
import json
from memory.pinecone import PineconeMemory
from memory.redismem import RedisMemory
import datetime
import agent_manager as agents
import speak
@@ -52,7 +53,10 @@ def get_command(response):
def execute_command(command_name, arguments):
memory = PineconeMemory(cfg=cfg)
if cfg.memory_backend == "pinecone":
memory = PineconeMemory(cfg=cfg)
else:
memory = RedisMemory(cfg=cfg)
try:
if command_name == "google":

View File

@@ -61,7 +61,12 @@ class Config(metaclass=Singleton):
# User agent headers to use when browsing web
# Some websites might just completely deny request with an error code if no user agent was found.
self.user_agent_header = {"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"}
self.redis_host = os.getenv("REDIS_HOST")
self.redis_port = os.getenv("REDIS_PORT")
self.redis_password = os.getenv("REDIS_PASSWORD")
# Note that indexes must be created on db 0 in redis, this is not configureable.
self.memory_backend = os.getenv("MEMORY_BACKEND", 'pinecone')
# Initialize the OpenAI API client
openai.api_key = self.openai_api_key

View File

@@ -2,6 +2,7 @@ import json
import random
import commands as cmd
from memory.pinecone import PineconeMemory
from memory.redismem import RedisMemory
import data
import chat
from colorama import Fore, Style
@@ -283,8 +284,11 @@ user_input = "Determine which next command to use, and respond using the format
# Initialize memory and make sure it is empty.
# this is particularly important for indexing and referencing pinecone memory
memory = PineconeMemory(cfg)
memory.clear()
if cfg.memory_backend == "pinecone":
memory = PineconeMemory(cfg)
memory.clear()
else:
memory = RedisMemory(cfg)
print('Using memory of type: ' + memory.__class__.__name__)

View File

@@ -1,3 +1,4 @@
"""Base class for memory providers."""
import abc
from config import AbstractSingleton
import openai

View File

@@ -0,0 +1,135 @@
"""Redis memory provider."""
from typing import Any, List, Optional
import redis
from redis.commands.search.field import VectorField, TextField
from redis.commands.search.query import Query
from redis.commands.search.indexDefinition import IndexDefinition, IndexType
import traceback
import numpy as np
from memory.base import MemoryProviderSingleton, get_ada_embedding
SCHEMA = [
TextField("data"),
VectorField(
"embedding",
"HNSW",
{
"TYPE": "FLOAT32",
"DIM": 1536,
"DISTANCE_METRIC": "COSINE"
}
),
]
class RedisMemory(MemoryProviderSingleton):
def __init__(self, cfg):
"""
Initializes the Redis memory provider.
Args:
cfg: The config object.
Returns: None
"""
redis_host = cfg.redis_host
redis_port = cfg.redis_port
redis_password = cfg.redis_password
self.dimension = 1536
self.redis = redis.Redis(
host=redis_host,
port=redis_port,
password=redis_password,
db=0 # Cannot be changed
)
self.redis.flushall()
try:
self.redis.ft("gpt").create_index(
fields=SCHEMA,
definition=IndexDefinition(
prefix=["gpt:"],
index_type=IndexType.HASH
)
)
except Exception as e:
print("Error creating Redis search index: ", e)
self.vec_num = 0
def add(self, data: str) -> str:
"""
Adds a data point to the memory.
Args:
data: The data to add.
Returns: Message indicating that the data has been added.
"""
vector = get_ada_embedding(data)
vector = np.array(vector).astype(np.float32).tobytes()
data_dict = {
b"data": data,
"embedding": vector
}
self.redis.hset(f"gpt:{self.vec_num}", mapping=data_dict)
_text = f"Inserting data into memory at index: {self.vec_num}:\n"\
f"data: {data}"
self.vec_num += 1
return _text
def get(self, data: str) -> Optional[List[Any]]:
"""
Gets the data from the memory that is most relevant to the given data.
Args:
data: The data to compare to.
Returns: The most relevant data.
"""
return self.get_relevant(data, 1)
def clear(self) -> str:
"""
Clears the redis server.
Returns: A message indicating that the memory has been cleared.
"""
self.redis.flushall()
return "Obliviated"
def get_relevant(
self,
data: str,
num_relevant: int = 5
) -> Optional[List[Any]]:
"""
Returns all the data in the memory that is relevant to the given data.
Args:
data: The data to compare to.
num_relevant: The number of relevant data to return.
Returns: A list of the most relevant data.
"""
query_embedding = get_ada_embedding(data)
base_query = f"*=>[KNN {num_relevant} @embedding $vector AS vector_score]"
query = Query(base_query).return_fields(
"data",
"vector_score"
).sort_by("vector_score").dialect(2)
query_vector = np.array(query_embedding).astype(np.float32).tobytes()
try:
results = self.redis.ft("gpt").search(
query, query_params={"vector": query_vector}
)
except Exception as e:
print("Error calling Redis search: ", e)
return None
return list(results.docs)
def get_stats(self):
"""
Returns: The stats of the memory index.
"""
return self.redis.ft("mem").info()