Skip to content

Commit 172a16a

Browse files
EItanyavictordibia
andauthored
Memory component base (#5380)
<!-- Thank you for your contribution! Please review https://microsoft.github.io/autogen/docs/Contribute before opening a pull request. --> <!-- Please add a reviewer to the assignee section when you create a PR. If you don't have the access to it, we will shortly find a reviewer and assign them to your PR. --> ## Why are these changes needed? Currently the way to accomplish RAG behavior with agent chat, specifically assistant agents is with the memory interface, however there is no way to configure it via the declarative API. <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number <!-- For example: "Closes #1234" --> ## Checks - [ ] I've included any doc changes needed for https://microsoft.github.io/autogen/. See https://microsoft.github.io/autogen/docs/Contribute#documentation to build and test documentation locally. - [ ] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [ ] I've made sure all auto checks have passed. --------- Co-authored-by: Victor Dibia <[email protected]>
1 parent be3c60b commit 172a16a

File tree

5 files changed

+72
-8
lines changed

5 files changed

+72
-8
lines changed

python/packages/autogen-agentchat/src/autogen_agentchat/agents/_assistant_agent.py

+3
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class AssistantAgentConfig(BaseModel):
6262
tools: List[ComponentModel] | None
6363
handoffs: List[HandoffBase | str] | None = None
6464
model_context: ComponentModel | None = None
65+
memory: List[ComponentModel] | None = None
6566
description: str
6667
system_message: str | None = None
6768
model_client_stream: bool = False
@@ -591,6 +592,7 @@ def _to_config(self) -> AssistantAgentConfig:
591592
tools=[tool.dump_component() for tool in self._tools],
592593
handoffs=list(self._handoffs.values()),
593594
model_context=self._model_context.dump_component(),
595+
memory=[memory.dump_component() for memory in self._memory] if self._memory else None,
594596
description=self.description,
595597
system_message=self._system_messages[0].content
596598
if self._system_messages and isinstance(self._system_messages[0].content, str)
@@ -609,6 +611,7 @@ def _from_config(cls, config: AssistantAgentConfig) -> Self:
609611
tools=[BaseTool.load_component(tool) for tool in config.tools] if config.tools else None,
610612
handoffs=config.handoffs,
611613
model_context=None,
614+
memory=[Memory.load_component(memory) for memory in config.memory] if config.memory else None,
612615
description=config.description,
613616
system_message=config.system_message,
614617
model_client_stream=config.model_client_stream,

python/packages/autogen-agentchat/tests/test_assistant_agent.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
ToolCallRequestEvent,
1919
ToolCallSummaryMessage,
2020
)
21-
from autogen_core import FunctionCall, Image
21+
from autogen_core import ComponentModel, FunctionCall, Image
2222
from autogen_core.memory import ListMemory, Memory, MemoryContent, MemoryMimeType, MemoryQueryResult
2323
from autogen_core.model_context import BufferedChatCompletionContext
2424
from autogen_core.models import (
@@ -754,7 +754,12 @@ async def test_run_with_memory(monkeypatch: pytest.MonkeyPatch) -> None:
754754
"test_agent", model_client=OpenAIChatCompletionClient(model=model, api_key=""), memory=[memory2]
755755
)
756756

757-
result = await agent.run(task="test task")
757+
# Test dump and load component with memory
758+
agent_config: ComponentModel = agent.dump_component()
759+
assert agent_config.provider == "autogen_agentchat.agents.AssistantAgent"
760+
agent2 = AssistantAgent.load_component(agent_config)
761+
762+
result = await agent2.run(task="test task")
758763
assert len(result.messages) > 0
759764
memory_event = next((msg for msg in result.messages if isinstance(msg, MemoryQueryEvent)), None)
760765
assert memory_event is not None
@@ -795,9 +800,10 @@ async def test_assistant_agent_declarative(monkeypatch: pytest.MonkeyPatch) -> N
795800
"test_agent",
796801
model_client=OpenAIChatCompletionClient(model=model, api_key=""),
797802
model_context=model_context,
803+
memory=[ListMemory(name="test_memory")],
798804
)
799805

800-
agent_config = agent.dump_component()
806+
agent_config: ComponentModel = agent.dump_component()
801807
assert agent_config.provider == "autogen_agentchat.agents.AssistantAgent"
802808

803809
agent2 = AssistantAgent.load_component(agent_config)

python/packages/autogen-core/src/autogen_core/memory/_base_memory.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from pydantic import BaseModel, ConfigDict
66

77
from .._cancellation_token import CancellationToken
8+
from .._component_config import ComponentBase
89
from .._image import Image
910
from ..model_context import ChatCompletionContext
1011

@@ -49,7 +50,7 @@ class UpdateContextResult(BaseModel):
4950
memories: MemoryQueryResult
5051

5152

52-
class Memory(ABC):
53+
class Memory(ABC, ComponentBase[BaseModel]):
5354
"""Protocol defining the interface for memory implementations.
5455
5556
A memory is the storage for data that can be used to enrich or modify the model context.
@@ -64,6 +65,8 @@ class Memory(ABC):
6465
See :class:`~autogen_core.memory.ListMemory` for an example implementation.
6566
"""
6667

68+
component_type = "memory"
69+
6770
@abstractmethod
6871
async def update_context(
6972
self,

python/packages/autogen-core/src/autogen_core/memory/_list_memory.py

+27-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
11
from typing import Any, List
22

3+
from pydantic import BaseModel
4+
from typing_extensions import Self
5+
36
from .._cancellation_token import CancellationToken
7+
from .._component_config import Component
48
from ..model_context import ChatCompletionContext
59
from ..models import SystemMessage
610
from ._base_memory import Memory, MemoryContent, MemoryQueryResult, UpdateContextResult
711

812

9-
class ListMemory(Memory):
13+
class ListMemoryConfig(BaseModel):
14+
"""Configuration for ListMemory component."""
15+
16+
name: str | None = None
17+
"""Optional identifier for this memory instance."""
18+
memory_contents: List[MemoryContent] = []
19+
"""List of memory contents stored in this memory instance."""
20+
21+
22+
class ListMemory(Memory, Component[ListMemoryConfig]):
1023
"""Simple chronological list-based memory implementation.
1124
1225
This memory implementation stores contents in a list and retrieves them in
@@ -53,9 +66,13 @@ async def main() -> None:
5366
5467
"""
5568

56-
def __init__(self, name: str | None = None) -> None:
69+
component_type = "memory"
70+
component_provider_override = "autogen_core.memory.ListMemory"
71+
component_config_schema = ListMemoryConfig
72+
73+
def __init__(self, name: str | None = None, memory_contents: List[MemoryContent] | None = None) -> None:
5774
self._name = name or "default_list_memory"
58-
self._contents: List[MemoryContent] = []
75+
self._contents: List[MemoryContent] = memory_contents if memory_contents is not None else []
5976

6077
@property
6178
def name(self) -> str:
@@ -146,3 +163,10 @@ async def clear(self) -> None:
146163
async def close(self) -> None:
147164
"""Cleanup resources if needed."""
148165
pass
166+
167+
@classmethod
168+
def _from_config(cls, config: ListMemoryConfig) -> Self:
169+
return cls(name=config.name, memory_contents=config.memory_contents)
170+
171+
def _to_config(self) -> ListMemoryConfig:
172+
return ListMemoryConfig(name=self.name, memory_contents=self._contents)

python/packages/autogen-core/tests/test_memory.py

+29-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from typing import Any
22

33
import pytest
4-
from autogen_core import CancellationToken
4+
from autogen_core import CancellationToken, ComponentModel
55
from autogen_core.memory import (
66
ListMemory,
77
Memory,
@@ -23,6 +23,34 @@ def test_memory_protocol_attributes() -> None:
2323
assert hasattr(Memory, "close")
2424

2525

26+
def test_memory_component_load_config_from_base_model() -> None:
27+
"""Test that Memory component can be loaded from a BaseModel."""
28+
config = ComponentModel(
29+
provider="autogen_core.memory.ListMemory",
30+
config={
31+
"name": "test_memory",
32+
"memory_contents": [MemoryContent(content="test", mime_type=MemoryMimeType.TEXT)],
33+
},
34+
)
35+
memory = Memory.load_component(config)
36+
assert isinstance(memory, ListMemory)
37+
assert memory.name == "test_memory"
38+
assert len(memory.content) == 1
39+
40+
41+
def test_memory_component_dump_config_to_base_model() -> None:
42+
"""Test that Memory component can be dumped to a BaseModel."""
43+
memory = ListMemory(
44+
name="test_memory", memory_contents=[MemoryContent(content="test", mime_type=MemoryMimeType.TEXT)]
45+
)
46+
config = memory.dump_component()
47+
assert isinstance(config, ComponentModel)
48+
assert config.provider == "autogen_core.memory.ListMemory"
49+
assert config.component_type == "memory"
50+
assert config.config["name"] == "test_memory"
51+
assert len(config.config["memory_contents"]) == 1
52+
53+
2654
def test_memory_abc_implementation() -> None:
2755
"""Test that Memory ABC is properly implemented."""
2856

0 commit comments

Comments
 (0)