1111
1212try :
1313 from redis import Redis
14- from redisvl .extensions .message_history import SemanticMessageHistory
14+ from redisvl .extensions .message_history import MessageHistory , SemanticMessageHistory
1515 from redisvl .utils .utils import deserialize , serialize
16+ from redisvl .utils .vectorize import HFTextVectorizer
1617except ImportError as e :
1718 raise ImportError ("To use Redis Memory RedisVL must be installed. Run `pip install autogen-ext[redisvl]`" ) from e
1819
@@ -29,24 +30,25 @@ class RedisMemoryConfig(BaseModel):
2930 redis_url : str = Field (default = "redis://localhost:6379" , description = "url of the Redis instance" )
3031 index_name : str = Field (default = "chat_history" , description = "Name of the Redis collection" )
3132 prefix : str = Field (default = "memory" , description = "prefix of the Redis collection" )
33+ sequential : bool = Field (
34+ default = False , description = "ignore semantic similarity and simply return memories in sequential order"
35+ )
3236 distance_metric : Literal ["cosine" , "ip" , "l2" ] = "cosine"
3337 algorithm : Literal ["flat" , "hnsw" ] = "flat"
3438 top_k : int = Field (default = 10 , description = "Number of results to return in queries" )
3539 datatype : Literal ["uint8" , "int8" , "float16" , "float32" , "float64" , "bfloat16" ] = "float32"
3640 distance_threshold : float = Field (default = 0.7 , description = "Minimum similarity score threshold" )
37- model_name : str | None = Field (
38- default = "sentence-transformers/all-mpnet-base-v2" , description = "Embedding model name"
39- )
41+ model_name : str = Field (default = "sentence-transformers/all-mpnet-base-v2" , description = "Embedding model name" )
4042
4143
4244class RedisMemory (Memory , Component [RedisMemoryConfig ]):
4345 """
4446 Store and retrieve memory using vector similarity search powered by RedisVL.
4547
4648 `RedisMemory` provides a vector-based memory implementation that uses RedisVL for storing and
47- retrieving content based on semantic similarity. It enhances agents with the ability to recall
48- contextually relevant information during conversations by leveraging vector embeddings to find
49- similar content.
49+ retrieving content based on semantic similarity or sequential order . It enhances agents with the
50+ ability to recall relevant information during conversations by leveraging vector embeddings to
51+ find similar content.
5052
5153 This implementation requires the RedisVL extra to be installed. Install with:
5254
@@ -175,7 +177,19 @@ def __init__(self, config: RedisMemoryConfig | None = None) -> None:
175177 self .config = config or RedisMemoryConfig ()
176178 client = Redis .from_url (url = self .config .redis_url ) # type: ignore[reportUknownMemberType]
177179
178- self .message_history = SemanticMessageHistory (name = self .config .index_name , redis_client = client )
180+ if self .config .sequential :
181+ self .message_history = MessageHistory (
182+ name = self .config .index_name , prefix = self .config .prefix , redis_client = client
183+ )
184+ else :
185+ vectorizer = HFTextVectorizer (model = self .config .model_name , dtype = self .config .datatype )
186+ self .message_history = SemanticMessageHistory (
187+ name = self .config .index_name ,
188+ prefix = self .config .prefix ,
189+ vectorizer = vectorizer ,
190+ distance_threshold = self .config .distance_threshold ,
191+ redis_client = client ,
192+ )
179193
180194 async def update_context (
181195 self ,
@@ -203,7 +217,7 @@ async def update_context(
203217 else :
204218 last_message = ""
205219
206- query_results = await self .query (last_message )
220+ query_results = await self .query (last_message , sequential = self . config . sequential )
207221
208222 stringified_messages = "\n \n " .join ([str (m .content ) for m in query_results .results ])
209223
@@ -216,10 +230,10 @@ async def add(self, content: MemoryContent, cancellation_token: CancellationToke
216230
217231 .. note::
218232
219- To perform semantic search over stored memories RedisMemory creates a vector embedding
220- from the content field of a MemoryContent object. This content is assumed to be text,
221- JSON, or Markdown, and is passed to the vector embedding model specified in
222- RedisMemoryConfig.
233+ If RedisMemoryConfig is not set to 'sequential', to perform semantic search over stored
234+ memories RedisMemory creates a vector embedding from the content field of a
235+ MemoryContent object. This content is assumed to be text, JSON, or Markdown, and is
236+ passed to the vector embedding model specified in RedisMemoryConfig.
223237
224238 Args:
225239 content (MemoryContent): The memory content to store within Redis.
@@ -241,7 +255,7 @@ async def add(self, content: MemoryContent, cancellation_token: CancellationToke
241255 metadata = {"mime_type" : mime_type }
242256 metadata .update (content .metadata if content .metadata else {})
243257 self .message_history .add_message (
244- {"role" : "user" , "content" : memory_content , "tool_call_id " : serialize (metadata )} # type: ignore[reportArgumentType]
258+ {"role" : "user" , "content" : memory_content , "metadata " : serialize (metadata )} # type: ignore[reportArgumentType]
245259 )
246260
247261 async def query (
@@ -258,6 +272,7 @@ async def query(
258272 top_k (int): The maximum number of relevant memories to include. Defaults to 10.
259273 distance_threshold (float): The maximum distance in vector space to consider a memory
260274 semantically similar when performining cosine similarity search. Defaults to 0.7.
275+ sequential (bool): Ignore semantic similarity and return the top_k most recent memories.
261276
262277 Args:
263278 query (str | MemoryContent): query to perform vector similarity search with. If a
@@ -270,34 +285,46 @@ async def query(
270285 Returns:
271286 memoryQueryResult: Object containing memories relevant to the provided query.
272287 """
273- # get the query string, or raise an error for unsupported MemoryContent types
274- if isinstance (query , str ):
275- prompt = query
276- elif isinstance (query , MemoryContent ):
277- if query .mime_type in (MemoryMimeType .TEXT , MemoryMimeType .MARKDOWN ):
278- prompt = str (query .content )
279- elif query .mime_type == MemoryMimeType .JSON :
280- prompt = serialize (query .content )
281- else :
282- raise NotImplementedError (
283- f"Error: { query .mime_type } is not supported. Only MemoryMimeType.TEXT, MemoryMimeType.JSON, MemoryMimeType.MARKDOWN are currently supported."
284- )
285- else :
286- raise TypeError ("'query' must be either a string or MemoryContent" )
287-
288288 top_k = kwargs .pop ("top_k" , self .config .top_k )
289289 distance_threshold = kwargs .pop ("distance_threshold" , self .config .distance_threshold )
290290
291- results = self .message_history .get_relevant (
292- prompt = prompt , # type: ignore[reportArgumentType]
293- top_k = top_k ,
294- distance_threshold = distance_threshold ,
295- raw = False ,
296- )
291+ # if sequential memory is requested skip prompt creation
292+ sequential = bool (kwargs .pop ("sequential" , self .config .sequential ))
293+ if self .config .sequential and not sequential :
294+ raise ValueError (
295+ "Non-sequential queries cannot be run with an underlying sequential RedisMemory. Set sequential=False in RedisMemoryConfig to enable semantic memory querying."
296+ )
297+ elif sequential or self .config .sequential :
298+ results = self .message_history .get_recent (
299+ top_k = top_k ,
300+ raw = False ,
301+ )
302+ else :
303+ # get the query string, or raise an error for unsupported MemoryContent types
304+ if isinstance (query , str ):
305+ prompt = query
306+ elif isinstance (query , MemoryContent ):
307+ if query .mime_type in (MemoryMimeType .TEXT , MemoryMimeType .MARKDOWN ):
308+ prompt = str (query .content )
309+ elif query .mime_type == MemoryMimeType .JSON :
310+ prompt = serialize (query .content )
311+ else :
312+ raise NotImplementedError (
313+ f"Error: { query .mime_type } is not supported. Only MemoryMimeType.TEXT, MemoryMimeType.JSON, MemoryMimeType.MARKDOWN are currently supported."
314+ )
315+ else :
316+ raise TypeError ("'query' must be either a string or MemoryContent" )
317+
318+ results = self .message_history .get_relevant ( # type: ignore
319+ prompt = prompt , # type: ignore[reportArgumentType]
320+ top_k = top_k ,
321+ distance_threshold = distance_threshold ,
322+ raw = False ,
323+ )
297324
298325 memories : List [MemoryContent ] = []
299- for result in results :
300- metadata = deserialize (result ["tool_call_id " ]) # type: ignore[reportArgumentType]
326+ for result in results : # type: ignore[reportUnkownVariableType]
327+ metadata = deserialize (result ["metadata " ]) # type: ignore[reportArgumentType]
301328 mime_type = MemoryMimeType (metadata .pop ("mime_type" ))
302329 if mime_type in (MemoryMimeType .TEXT , MemoryMimeType .MARKDOWN ):
303330 memory_content = result ["content" ] # type: ignore[reportArgumentType]
0 commit comments