-
Notifications
You must be signed in to change notification settings - Fork 44.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Local Cache and Redis Memory backend #372
Merged
Torantulino
merged 18 commits into
Significant-Gravitas:master
from
BillSchumacher:redis-backend
Apr 9, 2023
Merged
Changes from 17 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
6819799
Create an abstract MemoryProviderSingleton class. Pass config instead…
BillSchumacher 5a1d9e6
Implement redis memory backend.
BillSchumacher cce7969
Save redis memory state, with the default being to wipe on start still.
BillSchumacher 43746b1
Update README with WIPE_REDIS_ON_START setting.
BillSchumacher df28193
Merge branch 'Torantulino:master' into redis-backend
BillSchumacher f016203
Fix README
BillSchumacher 5d13fb2
Remove unused function.
BillSchumacher 14e10c9
Add configurable index key for redis.
BillSchumacher ea6b970
Update README
BillSchumacher cb14c8d
Implement local memory.
BillSchumacher 503b58b
Refactor memory into factory.
BillSchumacher d7ccaf4
Merge branch 'master' of github.com:Torantulino/Auto-GPT into redis-b…
BillSchumacher a34c51b
Update scripts/config.py
BillSchumacher d1777e3
Fixes incorrect class names in __all__
Torantulino b63238f
Merge branch 'master' into redis-backend
Torantulino 2db7f08
Update main.py
BillSchumacher 9e139fb
Wipe local memory on load
Torantulino a861dec
Memory fixes.
BillSchumacher File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
from memory.local import LocalCache | ||
try: | ||
from memory.redismem import RedisMemory | ||
except ImportError: | ||
print("Redis not installed. Skipping import.") | ||
RedisMemory = None | ||
|
||
try: | ||
from memory.pinecone import PineconeMemory | ||
except ImportError: | ||
print("Pinecone not installed. Skipping import.") | ||
PineconeMemory = None | ||
|
||
|
||
def get_memory(cfg, init=False): | ||
memory = None | ||
if cfg.memory_backend == "pinecone": | ||
if not PineconeMemory: | ||
print("Error: Pinecone is not installed. Please install pinecone" | ||
" to use Pinecone as a memory backend.") | ||
else: | ||
memory = PineconeMemory(cfg) | ||
if init: | ||
memory.clear() | ||
elif cfg.memory_backend == "redis": | ||
if not RedisMemory: | ||
print("Error: Redis is not installed. Please install redis-py to" | ||
" use Redis as a memory backend.") | ||
else: | ||
memory = RedisMemory(cfg) | ||
|
||
if memory is None: | ||
memory = LocalCache(cfg) | ||
Torantulino marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if init: | ||
memory.clear() | ||
return memory | ||
|
||
|
||
__all__ = [ | ||
"get_memory", | ||
"LocalCache", | ||
"RedisMemory", | ||
"PineconeMemory", | ||
] |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
"""Base class for memory providers.""" | ||
import abc | ||
from config import AbstractSingleton | ||
import openai | ||
|
||
|
||
def get_ada_embedding(text): | ||
text = text.replace("\n", " ") | ||
return openai.Embedding.create(input=[text], model="text-embedding-ada-002")["data"][0]["embedding"] | ||
|
||
|
||
class MemoryProviderSingleton(AbstractSingleton): | ||
@abc.abstractmethod | ||
def add(self, data): | ||
pass | ||
|
||
@abc.abstractmethod | ||
def get(self, data): | ||
pass | ||
|
||
@abc.abstractmethod | ||
def clear(self): | ||
pass | ||
|
||
@abc.abstractmethod | ||
def get_relevant(self, data, num_relevant=5): | ||
pass | ||
|
||
@abc.abstractmethod | ||
def get_stats(self): | ||
pass |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import dataclasses | ||
import orjson | ||
from typing import Any, List, Optional | ||
import numpy as np | ||
import os | ||
from memory.base import MemoryProviderSingleton, get_ada_embedding | ||
|
||
|
||
EMBED_DIM = 1536 | ||
SAVE_OPTIONS = orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_SERIALIZE_DATACLASS | ||
|
||
|
||
def create_default_embeddings(): | ||
return np.zeros((0, EMBED_DIM)).astype(np.float32) | ||
|
||
|
||
@dataclasses.dataclass | ||
class CacheContent: | ||
texts: List[str] = dataclasses.field(default_factory=list) | ||
embeddings: np.ndarray = dataclasses.field( | ||
default_factory=create_default_embeddings | ||
) | ||
|
||
|
||
class LocalCache(MemoryProviderSingleton): | ||
|
||
# on load, load our database | ||
def __init__(self, cfg) -> None: | ||
self.filename = f"{cfg.memory_index}.json" | ||
if os.path.exists(self.filename): | ||
with open(self.filename, 'rb') as f: | ||
loaded = orjson.loads(f.read()) | ||
self.data = CacheContent(**loaded) | ||
else: | ||
self.data = CacheContent() | ||
|
||
def add(self, text: str): | ||
""" | ||
Add text to our list of texts, add embedding as row to our | ||
embeddings-matrix | ||
|
||
Args: | ||
text: str | ||
|
||
Returns: None | ||
""" | ||
self.data.texts.append(text) | ||
|
||
embedding = get_ada_embedding(text) | ||
|
||
vector = np.array(embedding).astype(np.float32) | ||
vector = vector[np.newaxis, :] | ||
self.data.embeddings = np.concatenate( | ||
[ | ||
vector, | ||
self.data.embeddings, | ||
], | ||
axis=0, | ||
) | ||
|
||
with open(self.filename, 'wb') as f: | ||
out = orjson.dumps( | ||
self.data, | ||
option=SAVE_OPTIONS | ||
) | ||
f.write(out) | ||
|
||
def clear(self) -> str: | ||
""" | ||
Clears the redis server. | ||
|
||
Returns: A message indicating that the memory has been cleared. | ||
""" | ||
self.data = CacheContent() | ||
return "Obliviated" | ||
|
||
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 get_relevant(self, text: str, k: int) -> List[Any]: | ||
"""" | ||
matrix-vector mult to find score-for-each-row-of-matrix | ||
get indices for top-k winning scores | ||
return texts for those indices | ||
Args: | ||
text: str | ||
k: int | ||
|
||
Returns: List[str] | ||
""" | ||
embedding = get_ada_embedding(text) | ||
|
||
scores = np.dot(self.data.embeddings, embedding) | ||
|
||
top_k_indices = np.argsort(scores)[-k:][::-1] | ||
|
||
return [self.data.texts[i] for i in top_k_indices] | ||
|
||
def get_stats(self): | ||
""" | ||
Returns: The stats of the local cache. | ||
""" | ||
return len(self.data.texts), self.data.embeddings.shape |
This file contains 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
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We appear to be missing a call to memory.clear() here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the LocalCache
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, there was a lot of people that wanted the memory to persist by default and I just made Pi's implementation work.
I guess a direction one way or the other needs to be picked.
Deleting the memory is easy enough for local, use just delete the json file.
But there's also a memory index if you wanted to start fresh but keep memory from a different session.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting, okay.
Perhaps default persistent memory is a good idea in the near future, however now I think it has the side effect of encouraging the AI to repeat it's errors.
Through my experimenting with this PR, if the AI is reminded of events from it's past were it didn't respond in JSON, it's more likely to do so again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be prudent to not even save those in memory.