Skip to content

Commit 444067c

Browse files
authored
Merge branch 'main' into fix-message-id
2 parents 3b54706 + 8e2c5f0 commit 444067c

5 files changed

Lines changed: 323 additions & 48 deletions

File tree

python/packages/autogen-ext/src/autogen_ext/memory/redis/_redis_memory.py

Lines changed: 64 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111

1212
try:
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
1617
except 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

4244
class 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]

python/packages/autogen-ext/src/autogen_ext/models/anthropic/_anthropic_client.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -881,7 +881,8 @@ async def create_stream(
881881
tool_calls[current_tool_id] = {
882882
"id": chunk.content_block.id,
883883
"name": chunk.content_block.name,
884-
"input": "", # Will be populated from deltas
884+
"input": json.dumps(chunk.content_block.input),
885+
"partial_json": "", # May be populated from deltas
885886
}
886887

887888
elif chunk.type == "content_block_delta":
@@ -896,10 +897,15 @@ async def create_stream(
896897
elif hasattr(chunk.delta, "type") and chunk.delta.type == "input_json_delta":
897898
if current_tool_id is not None and hasattr(chunk.delta, "partial_json"):
898899
# Accumulate partial JSON for the current tool
899-
tool_calls[current_tool_id]["input"] += chunk.delta.partial_json
900+
tool_calls[current_tool_id]["partial_json"] += chunk.delta.partial_json
900901

901902
elif chunk.type == "content_block_stop":
902903
# End of a content block (could be text or tool)
904+
if current_tool_id is not None:
905+
# If there was partial JSON accumulated, use it as the input
906+
if len(tool_calls[current_tool_id]["partial_json"]) > 0:
907+
tool_calls[current_tool_id]["input"] = tool_calls[current_tool_id]["partial_json"]
908+
del tool_calls[current_tool_id]["partial_json"]
903909
current_tool_id = None
904910

905911
elif chunk.type == "message_delta":

0 commit comments

Comments
 (0)