Skip to content

Commit c80d570

Browse files
committed
[api][runtime][python] Introduce long-term memory interface and provide a vector store based implementation.
1 parent dcac415 commit c80d570

File tree

10 files changed

+1055
-0
lines changed

10 files changed

+1055
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
################################################################################
2+
# Licensed to the Apache Software Foundation (ASF) under one
3+
# or more contributor license agreements. See the NOTICE file
4+
# distributed with this work for additional information
5+
# regarding copyright ownership. The ASF licenses this file
6+
# to you under the Apache License, Version 2.0 (the
7+
# "License"); you may not use this file except in compliance
8+
# with the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#################################################################################
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
################################################################################
2+
# Licensed to the Apache Software Foundation (ASF) under one
3+
# or more contributor license agreements. See the NOTICE file
4+
# distributed with this work for additional information
5+
# regarding copyright ownership. The ASF licenses this file
6+
# to you under the Apache License, Version 2.0 (the
7+
# "License"); you may not use this file except in compliance
8+
# with the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#################################################################################
18+
import importlib
19+
from abc import ABC, abstractmethod
20+
from datetime import datetime
21+
from enum import Enum
22+
from typing import Any, Dict, List, Type
23+
24+
from pydantic import (
25+
BaseModel,
26+
Field,
27+
field_serializer,
28+
model_validator,
29+
)
30+
from typing_extensions import override
31+
32+
from flink_agents.api.chat_message import ChatMessage
33+
from flink_agents.api.prompts.prompt import Prompt
34+
35+
ItemType = str | ChatMessage
36+
37+
38+
class CompactionStrategyType(Enum):
39+
"""Strategy for compact memory set."""
40+
41+
SUMMARIZATION = "summarization"
42+
43+
44+
class CompactionStrategy(BaseModel, ABC):
45+
"""Strategy for compact memory set."""
46+
47+
@property
48+
@abstractmethod
49+
def type(self) -> CompactionStrategyType:
50+
"""Return type of this strategy."""
51+
52+
53+
class SummarizationStrategy(CompactionStrategy):
54+
"""Summarization strategy."""
55+
56+
model: str
57+
prompt: str | Prompt | None = None
58+
59+
@property
60+
@override
61+
def type(self) -> CompactionStrategyType:
62+
return CompactionStrategyType.SUMMARIZATION
63+
64+
65+
class LongTermMemoryBackend(Enum):
66+
"""Backend for Long-Term Memory."""
67+
68+
VectorStore = "vectorstore"
69+
70+
71+
class DatetimeRange(BaseModel):
72+
"""Represents a datetime range."""
73+
74+
start: datetime
75+
end: datetime
76+
77+
78+
class MemorySetItem(BaseModel):
79+
"""Represents a long term memory item retrieved from vector store.
80+
81+
Attributes:
82+
memory_set_name: The name of the memory set this item belongs to.
83+
id: The id of this item.
84+
value: The value of this item.
85+
compacted: Whether this item has been compacted.
86+
created_time: The timestamp this item was added to the memory set.
87+
last_accessed_time: The timestamp this item was last accessed.
88+
additional_metadata: Additional metadata for this item.
89+
"""
90+
91+
memory_set_name: str
92+
id: str
93+
value: Any
94+
compacted: bool = False
95+
created_time: datetime | DatetimeRange = None
96+
last_accessed_time: datetime
97+
additional_metadata: Dict[str, Any] | None = None
98+
99+
100+
class MemorySet(BaseModel):
101+
"""Represents a long term memory set contains memory items.
102+
103+
Attributes:
104+
name: The name of this memory set.
105+
item_type: The type of items stored in this set.
106+
capacity: The capacity of this memory set.
107+
compaction_strategy: Compaction strategy and additional arguments used
108+
to compact memory set.
109+
size: The size of this memory set.
110+
"""
111+
112+
name: str
113+
item_type: Type[str] | Type[ChatMessage]
114+
capacity: int
115+
compaction_strategy: CompactionStrategy
116+
size: int = Field(default=0, exclude=True)
117+
ltm: "BaseLongTermMemory" = Field(default=None, exclude=True)
118+
119+
@field_serializer("item_type")
120+
def _serialize_item_type(self, item_type: Type) -> Dict[str, str]:
121+
return {"module": item_type.__module__, "name": item_type.__name__}
122+
123+
@field_serializer("compaction_strategy")
124+
def _serialize_compaction_strategy(
125+
self, compaction_strategy: CompactionStrategy
126+
) -> Dict[str, str]:
127+
data = compaction_strategy.model_dump()
128+
data.update(
129+
{
130+
"module": compaction_strategy.__class__.__module__,
131+
"name": compaction_strategy.__class__.__name__,
132+
}
133+
)
134+
return data
135+
136+
@model_validator(mode="before")
137+
def _deserialize_item_type(self) -> "MemorySet":
138+
if isinstance(self["item_type"], Dict):
139+
module = importlib.import_module(self["item_type"]["module"])
140+
self["item_type"] = getattr(module, self["item_type"]["name"])
141+
if isinstance(self["compaction_strategy"], Dict):
142+
module = importlib.import_module(self["compaction_strategy"].pop("module"))
143+
clazz = getattr(module, self["compaction_strategy"].pop("name"))
144+
self["compaction_strategy"] = clazz.model_validate(
145+
self["compaction_strategy"]
146+
)
147+
return self
148+
149+
def add(
150+
self, items: ItemType | List[ItemType], ids: str | List[str] | None = None
151+
) -> None:
152+
"""Add a memory item to the set, currently only support item with
153+
type str or ChatMessage.
154+
155+
If the capacity of this memory set is reached, will trigger reduce
156+
operation to manage the memory set size.
157+
158+
Args:
159+
items: The items to be inserted to this set.
160+
ids: The ids of the items to be inserted. Optional.
161+
"""
162+
self.ltm.add(memory_set=self, memory_items=items, ids=ids)
163+
164+
def get(
165+
self, ids: str | List[str] | None = None
166+
) -> MemorySetItem | List[MemorySetItem]:
167+
"""Retrieve memory items. If no item id provided, will return all items.
168+
169+
Args:
170+
ids: The ids of the items to retrieve.
171+
172+
Returns:
173+
The memory items retrieved.
174+
"""
175+
return self.ltm.get(memory_set=self, ids=ids)
176+
177+
def search(self, query: str, limit: int, **kwargs: Any) -> List[MemorySetItem]:
178+
"""Retrieve n memory items related to the query.
179+
180+
Args:
181+
query: The query to search for.
182+
limit: The number of items to retrieve.
183+
**kwargs: Additional arguments for search.
184+
"""
185+
return self.ltm.search(memory_set=self, query=query, limit=limit, **kwargs)
186+
187+
188+
class BaseLongTermMemory(ABC, BaseModel):
189+
"""Base Abstract class for long term memory."""
190+
191+
@abstractmethod
192+
def get_or_create_memory_set(
193+
self,
194+
name: str,
195+
item_type: str | Type[ChatMessage],
196+
capacity: int,
197+
compaction_strategy: CompactionStrategy,
198+
) -> MemorySet:
199+
"""Create a memory set, if the memory set already exists, return it.
200+
201+
Args:
202+
name: The name of the memory set.
203+
item_type: The type of the memory item.
204+
capacity: The capacity of the memory set.
205+
compaction_strategy: The compaction strategy and arguments for
206+
storge management.
207+
208+
Returns:
209+
The created memory set.
210+
"""
211+
212+
@abstractmethod
213+
def get_memory_set(self, name: str) -> MemorySet:
214+
"""Get the memory set.
215+
216+
Args:
217+
name: The name of the memory set.
218+
219+
Returns:
220+
The memory set.
221+
"""
222+
223+
@abstractmethod
224+
def delete_memory_set(self, name: str) -> MemorySet:
225+
"""Delete the memory set.
226+
227+
Args:
228+
name: The name of the memory set.
229+
230+
Returns:
231+
The deleted memory set.
232+
"""
233+
234+
@abstractmethod
235+
def add(
236+
self,
237+
memory_set: MemorySet,
238+
memory_items: ItemType | List[ItemType],
239+
ids: str | List[str] | None = None,
240+
metadatas: Dict[str, Any] | List[Dict[str, Any]] | None = None,
241+
) -> None:
242+
"""Add items to the memory set, currently only support items with
243+
type str or ChatMessage.
244+
245+
This method may trigger compaction to manage the memory set size.
246+
247+
Args:
248+
memory_set: The memory set to add to.
249+
memory_items: The items to be added to this set.
250+
ids: The IDs of items. Will be automatically generated if not provided.
251+
Optional.
252+
metadatas: The metadata for items. Optional.
253+
"""
254+
255+
@abstractmethod
256+
def get(
257+
self, memory_set: MemorySet, ids: str | List[str] | None = None
258+
) -> MemorySetItem | List[MemorySetItem]:
259+
"""Retrieve memory items. If no item id provided, return all items.
260+
261+
Args:
262+
memory_set: The set to be retrieved.
263+
ids: The ids of the items to retrieve. If not provided, all items will
264+
be retrieved. Optional.
265+
266+
Returns:
267+
The memory items retrieved.
268+
"""
269+
270+
@abstractmethod
271+
def delete(self, memory_set: MemorySet, ids: str | List[str] | None = None) -> None:
272+
"""Delete memory items. If no item id provided, delete all items.
273+
274+
Args:
275+
memory_set: The memory set to delete from.
276+
ids: The ids of items to be deleted, If not provided, all items will be
277+
deleted. Optional.
278+
"""
279+
280+
@abstractmethod
281+
def search(
282+
self, memory_set: MemorySet, query: str, limit: int, **kwargs: Any
283+
) -> List[MemorySetItem]:
284+
"""Retrieve n memory items related to the query.
285+
286+
Args:
287+
memory_set: The set to be retrieved.
288+
query: The query for sematic search.
289+
limit: The number of items to retrieve.
290+
**kwargs: Additional arguments for sematic search.
291+
292+
Returns:
293+
Related memory items retrieved.
294+
"""
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
################################################################################
2+
# Licensed to the Apache Software Foundation (ASF) under one
3+
# or more contributor license agreements. See the NOTICE file
4+
# distributed with this work for additional information
5+
# regarding copyright ownership. The ASF licenses this file
6+
# to you under the Apache License, Version 2.0 (the
7+
# "License"); you may not use this file except in compliance
8+
# with the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#################################################################################
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
################################################################################
2+
# Licensed to the Apache Software Foundation (ASF) under one
3+
# or more contributor license agreements. See the NOTICE file
4+
# distributed with this work for additional information
5+
# regarding copyright ownership. The ASF licenses this file
6+
# to you under the Apache License, Version 2.0 (the
7+
# "License"); you may not use this file except in compliance
8+
# with the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#################################################################################
18+
19+
from flink_agents.api.chat_message import ChatMessage
20+
from flink_agents.api.memory.long_term_memory import MemorySet, SummarizationStrategy
21+
22+
23+
def test_memory_set_serialization() -> None: # noqa:D103
24+
memory_set = MemorySet(
25+
name="chat_history",
26+
item_type=ChatMessage,
27+
capacity=100,
28+
compaction_strategy=SummarizationStrategy(model="llm"),
29+
)
30+
31+
json_data = memory_set.model_dump_json()
32+
33+
memory_set_deserialized = MemorySet.model_validate_json(json_data)
34+
35+
assert memory_set_deserialized == memory_set
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
################################################################################
2+
# Licensed to the Apache Software Foundation (ASF) under one
3+
# or more contributor license agreements. See the NOTICE file
4+
# distributed with this work for additional information
5+
# regarding copyright ownership. The ASF licenses this file
6+
# to you under the Apache License, Version 2.0 (the
7+
# "License"); you may not use this file except in compliance
8+
# with the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#################################################################################

0 commit comments

Comments
 (0)