-
Notifications
You must be signed in to change notification settings - Fork 76
[api][runtime] Introduce long-term memory in python #332
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| ################################################################################ | ||
| # Licensed to the Apache Software Foundation (ASF) under one | ||
| # or more contributor license agreements. See the NOTICE file | ||
| # distributed with this work for additional information | ||
| # regarding copyright ownership. The ASF licenses this file | ||
| # to you under the Apache License, Version 2.0 (the | ||
| # "License"); you may not use this file except in compliance | ||
| # with the License. You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| ################################################################################# |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,263 @@ | ||||||
| ################################################################################ | ||||||
| # Licensed to the Apache Software Foundation (ASF) under one | ||||||
| # or more contributor license agreements. See the NOTICE file | ||||||
| # distributed with this work for additional information | ||||||
| # regarding copyright ownership. The ASF licenses this file | ||||||
| # to you under the Apache License, Version 2.0 (the | ||||||
| # "License"); you may not use this file except in compliance | ||||||
| # with the License. You may obtain a copy of the License at | ||||||
| # | ||||||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||||||
| # | ||||||
| # Unless required by applicable law or agreed to in writing, software | ||||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
| # See the License for the specific language governing permissions and | ||||||
| # limitations under the License. | ||||||
| ################################################################################# | ||||||
| import importlib | ||||||
| from abc import ABC, abstractmethod | ||||||
| from datetime import datetime | ||||||
| from enum import Enum | ||||||
| from typing import Any, Dict, List, Type | ||||||
|
|
||||||
| from pydantic import ( | ||||||
| BaseModel, | ||||||
| Field, | ||||||
| field_serializer, | ||||||
| model_validator, | ||||||
| ) | ||||||
|
|
||||||
| from flink_agents.api.chat_message import ChatMessage | ||||||
| from flink_agents.api.prompts.prompt import Prompt | ||||||
|
|
||||||
|
|
||||||
| class ReduceStrategy(Enum): | ||||||
| """Strategy for reducing memory set size.""" | ||||||
|
|
||||||
| TRIM = "trim" | ||||||
| SUMMARIZE = "summarize" | ||||||
|
|
||||||
|
|
||||||
| class ReduceSetup(BaseModel): | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd suggest to name this
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think |
||||||
| """Reduce setup for managing the storge of memory set.""" | ||||||
|
|
||||||
| strategy: ReduceStrategy | ||||||
| arguments: Dict[str, Any] = Field(default_factory=dict) | ||||||
|
|
||||||
| @staticmethod | ||||||
| def trim_setup(n: int) -> "ReduceSetup": | ||||||
| """Create trim setup.""" | ||||||
| return ReduceSetup(strategy=ReduceStrategy.TRIM, arguments={"n": n}) | ||||||
|
|
||||||
| @staticmethod | ||||||
| def summarize_setup( | ||||||
| n: int, model: str, prompt: Prompt | None = None | ||||||
| ) -> "ReduceSetup": | ||||||
| """Create summarize setup.""" | ||||||
| return ReduceSetup( | ||||||
| strategy=ReduceStrategy.SUMMARIZE, | ||||||
| arguments={"n": n, "model": model, "prompt": prompt}, | ||||||
| ) | ||||||
|
|
||||||
|
|
||||||
| class LongTermMemoryBackend(Enum): | ||||||
| """Backend for Long-Term Memory.""" | ||||||
|
|
||||||
| CHROMA = "chroma" | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We probably should either support a embedded backend only, or support any external vector store. |
||||||
|
|
||||||
|
|
||||||
| class DatetimeRange(BaseModel): | ||||||
| """Represents a datetime range.""" | ||||||
|
|
||||||
| start: datetime | ||||||
| end: datetime | ||||||
|
|
||||||
|
|
||||||
| class MemorySetItem(BaseModel): | ||||||
| """Represents a long term memory item retrieved from vector store. | ||||||
|
|
||||||
| Attributes: | ||||||
| memory_set_name: The name of the memory set this item belongs to. | ||||||
| id: The id of this item. | ||||||
| value: The value of this item. | ||||||
| compacted: Whether this item has been compacted. | ||||||
| created_time: The timestamp this item was added to the memory set. | ||||||
| last_accessed_time: The timestamp this item was last accessed. | ||||||
| """ | ||||||
|
|
||||||
| memory_set_name: str | ||||||
| id: str | ||||||
| value: Any | ||||||
| compacted: bool = False | ||||||
| created_time: DatetimeRange | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| last_accessed_time: datetime | ||||||
|
|
||||||
| class MemorySet(BaseModel): | ||||||
| """Represents a long term memory set contains memory items. | ||||||
|
|
||||||
| Attributes: | ||||||
| name: The name of this memory set. | ||||||
| item_type: The type of items stored in this set. | ||||||
| size: Current items count stored in this set. | ||||||
| capacity: The capacity of this memory set. | ||||||
| reduce_setup: Reduce strategy and additional arguments used to reduce memory | ||||||
| set size. | ||||||
| item_ids: The indices of items stored in this set. | ||||||
| reduced: Whether this memory set has been reduced. | ||||||
| """ | ||||||
|
|
||||||
| name: str | ||||||
| item_type: Type[str] | Type[ChatMessage] | ||||||
| size: int = 0 | ||||||
| capacity: int | ||||||
| reduce_setup: ReduceSetup | ||||||
| item_ids: List[str] = Field(default_factory=list) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to store ids of all the items? |
||||||
| reduced: bool = False | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And why do we need to know whether a memory set has been reduced/compacted? |
||||||
| ltm: "BaseLongTermMemory" = Field(default=None, exclude=True) | ||||||
|
|
||||||
| @field_serializer("item_type") | ||||||
| def _serialize_item_type(self, item_type: Type) -> Dict[str, str]: | ||||||
| return {"module": item_type.__module__, "name": item_type.__name__} | ||||||
|
|
||||||
| @model_validator(mode="before") | ||||||
| def _deserialize_item_type(self) -> "MemorySet": | ||||||
| if isinstance(self["item_type"], Dict): | ||||||
| module = importlib.import_module(self["item_type"]["module"]) | ||||||
| self["item_type"] = getattr(module, self["item_type"]["name"]) | ||||||
| return self | ||||||
|
|
||||||
| def add(self, item: str | ChatMessage) -> None: | ||||||
| """Add a memory item to the set, currently only support item with | ||||||
| type str or ChatMessage. | ||||||
|
|
||||||
| If the capacity of this memory set is reached, will trigger reduce | ||||||
| operation to manage the memory set size. | ||||||
|
|
||||||
| Args: | ||||||
| item: The item to be inserted to this set. | ||||||
| """ | ||||||
| self.ltm.add(memory_set=self, memory_item=item) | ||||||
|
|
||||||
| def get(self) -> List[MemorySetItem]: | ||||||
| """Retrieve all memory items. | ||||||
|
|
||||||
| Returns: | ||||||
| All memory items in this set. | ||||||
| """ | ||||||
| return self.ltm.get(memory_set=self) | ||||||
|
|
||||||
| def get_recent(self, n: int) -> List[MemorySetItem]: | ||||||
| """Retrieve n most recent memory items. | ||||||
|
|
||||||
| Args: | ||||||
| n: The number of items to retrieve. | ||||||
|
|
||||||
| Returns: | ||||||
| List of memory items retrieved, sorted by creation timestamp. | ||||||
| """ | ||||||
| return self.ltm.get_recent(memory_set=self, n=n) | ||||||
|
|
||||||
| def search(self, query: str, limit: int, **kwargs: Any) -> List[MemorySetItem]: | ||||||
| """Retrieve n memory items related to the query. | ||||||
|
|
||||||
| Args: | ||||||
| query: The query to search for. | ||||||
| limit: The number of items to retrieve. | ||||||
| **kwargs: Additional arguments for search. | ||||||
| """ | ||||||
| return self.ltm.search(memory_set=self, query=query, limit=limit, **kwargs) | ||||||
|
|
||||||
|
|
||||||
| class BaseLongTermMemory(ABC, BaseModel): | ||||||
| """Base Abstract class for long term memory.""" | ||||||
|
|
||||||
| @abstractmethod | ||||||
| def create_memory_set( | ||||||
| self, | ||||||
| name: str, | ||||||
| item_type: str | Type[ChatMessage], | ||||||
| capacity: int, | ||||||
| reduce_setup: ReduceSetup, | ||||||
| ) -> MemorySet: | ||||||
| """Create a memory set, if the memory set already exists, return it. | ||||||
|
|
||||||
| Args: | ||||||
| name: The name of the memory set. | ||||||
| item_type: The type of the memory item. | ||||||
| capacity: The capacity of the memory set. | ||||||
| reduce_setup: The reduce strategy and arguments for storge management. | ||||||
|
|
||||||
| Returns: | ||||||
| The created memory set. | ||||||
| """ | ||||||
|
|
||||||
| @abstractmethod | ||||||
| def get_memory_set(self, name: str) -> MemorySet: | ||||||
| """Get the memory set. | ||||||
|
|
||||||
| Args: | ||||||
| name: The name of the memory set. | ||||||
|
|
||||||
| Returns: | ||||||
| The memory set. | ||||||
| """ | ||||||
|
|
||||||
| @abstractmethod | ||||||
| def delete_memory_set(self, name: str) -> None: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Usually, for a delete method, we return a boolean of the deleted element / null to indicate whether the element has been deleted. |
||||||
| """Delete the memory set. | ||||||
|
|
||||||
| Args: | ||||||
| name: The name of the memory set. | ||||||
| """ | ||||||
|
|
||||||
| @abstractmethod | ||||||
| def add(self, memory_set: MemorySet, memory_item: str | ChatMessage) -> None: | ||||||
| """Add a memory item to the named set, currently only support item with | ||||||
| type str or ChatMessage. | ||||||
|
|
||||||
| This method may trigger reduce operation to manage the memory set size. | ||||||
|
|
||||||
| Args: | ||||||
| memory_set: The memory set to be inserted. | ||||||
| memory_item: The item to be inserted to this set. | ||||||
| """ | ||||||
|
|
||||||
| @abstractmethod | ||||||
| def get(self, memory_set: MemorySet) -> List[MemorySetItem]: | ||||||
| """Retrieve all memory items. | ||||||
|
|
||||||
| Args: | ||||||
| memory_set: The set to be retrieved. | ||||||
|
|
||||||
| Returns: | ||||||
| All the memory items of this set. | ||||||
| """ | ||||||
|
|
||||||
| @abstractmethod | ||||||
| def get_recent(self, memory_set: MemorySet, n: int) -> List[MemorySetItem]: | ||||||
| """Retrieve n most recent memory items. | ||||||
|
|
||||||
| Args: | ||||||
| memory_set: The set to be retrieved. | ||||||
| n: The number of items to retrieve. | ||||||
|
|
||||||
| Returns: | ||||||
| List of memory items retrieved, sorted by creation timestamp. | ||||||
| """ | ||||||
|
|
||||||
| @abstractmethod | ||||||
| def search( | ||||||
| self, memory_set: MemorySet, query: str, limit: int, **kwargs: Any | ||||||
| ) -> List[MemorySetItem]: | ||||||
| """Retrieve n memory items related to the query. | ||||||
|
|
||||||
| Args: | ||||||
| memory_set: The set to be retrieved. | ||||||
| query: The query for sematic search. | ||||||
| limit: The number of items to retrieve. | ||||||
| **kwargs: Additional arguments for sematic search. | ||||||
|
|
||||||
| Returns: | ||||||
| Related memory items retrieved. | ||||||
| """ | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| ################################################################################ | ||
| # Licensed to the Apache Software Foundation (ASF) under one | ||
| # or more contributor license agreements. See the NOTICE file | ||
| # distributed with this work for additional information | ||
| # regarding copyright ownership. The ASF licenses this file | ||
| # to you under the Apache License, Version 2.0 (the | ||
| # "License"); you may not use this file except in compliance | ||
| # with the License. You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| ################################################################################# |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| ################################################################################ | ||
| # Licensed to the Apache Software Foundation (ASF) under one | ||
| # or more contributor license agreements. See the NOTICE file | ||
| # distributed with this work for additional information | ||
| # regarding copyright ownership. The ASF licenses this file | ||
| # to you under the Apache License, Version 2.0 (the | ||
| # "License"); you may not use this file except in compliance | ||
| # with the License. You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| ################################################################################# | ||
|
|
||
| from flink_agents.api.chat_message import ChatMessage | ||
| from flink_agents.api.memory.long_term_memory import MemorySet, ReduceSetup | ||
|
|
||
|
|
||
| def test_memory_set_serialization() -> None: #noqa:D103 | ||
| memory_set = MemorySet(name="chat_history", | ||
| item_type=ChatMessage, | ||
| capacity=100, | ||
| reduce_setup=ReduceSetup.trim_setup(10)) | ||
|
|
||
| json_data = memory_set.model_dump_json() | ||
|
|
||
| memory_set_deserialized = MemorySet.model_validate_json(json_data) | ||
|
|
||
| assert memory_set_deserialized == memory_set |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| ################################################################################ | ||
| # Licensed to the Apache Software Foundation (ASF) under one | ||
| # or more contributor license agreements. See the NOTICE file | ||
| # distributed with this work for additional information | ||
| # regarding copyright ownership. The ASF licenses this file | ||
| # to you under the Apache License, Version 2.0 (the | ||
| # "License"); you may not use this file except in compliance | ||
| # with the License. You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| ################################################################################# |
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.
Not sure about the name
Reduce. I thinkCompactmight be better.