agentscope-runtime 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. agentscope_runtime/__init__.py +4 -0
  2. agentscope_runtime/engine/__init__.py +9 -0
  3. agentscope_runtime/engine/agents/__init__.py +2 -0
  4. agentscope_runtime/engine/agents/agentscope_agent/__init__.py +6 -0
  5. agentscope_runtime/engine/agents/agentscope_agent/agent.py +342 -0
  6. agentscope_runtime/engine/agents/agentscope_agent/hooks.py +156 -0
  7. agentscope_runtime/engine/agents/agno_agent.py +220 -0
  8. agentscope_runtime/engine/agents/base_agent.py +29 -0
  9. agentscope_runtime/engine/agents/langgraph_agent.py +59 -0
  10. agentscope_runtime/engine/agents/llm_agent.py +51 -0
  11. agentscope_runtime/engine/deployers/__init__.py +3 -0
  12. agentscope_runtime/engine/deployers/adapter/__init__.py +0 -0
  13. agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +2 -0
  14. agentscope_runtime/engine/deployers/adapter/a2a/a2a_adapter_utils.py +425 -0
  15. agentscope_runtime/engine/deployers/adapter/a2a/a2a_agent_adapter.py +69 -0
  16. agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +60 -0
  17. agentscope_runtime/engine/deployers/adapter/protocol_adapter.py +24 -0
  18. agentscope_runtime/engine/deployers/base.py +17 -0
  19. agentscope_runtime/engine/deployers/local_deployer.py +586 -0
  20. agentscope_runtime/engine/helpers/helper.py +127 -0
  21. agentscope_runtime/engine/llms/__init__.py +3 -0
  22. agentscope_runtime/engine/llms/base_llm.py +60 -0
  23. agentscope_runtime/engine/llms/qwen_llm.py +47 -0
  24. agentscope_runtime/engine/misc/__init__.py +0 -0
  25. agentscope_runtime/engine/runner.py +186 -0
  26. agentscope_runtime/engine/schemas/__init__.py +0 -0
  27. agentscope_runtime/engine/schemas/agent_schemas.py +551 -0
  28. agentscope_runtime/engine/schemas/context.py +54 -0
  29. agentscope_runtime/engine/services/__init__.py +9 -0
  30. agentscope_runtime/engine/services/base.py +77 -0
  31. agentscope_runtime/engine/services/context_manager.py +129 -0
  32. agentscope_runtime/engine/services/environment_manager.py +50 -0
  33. agentscope_runtime/engine/services/manager.py +174 -0
  34. agentscope_runtime/engine/services/memory_service.py +270 -0
  35. agentscope_runtime/engine/services/sandbox_service.py +198 -0
  36. agentscope_runtime/engine/services/session_history_service.py +256 -0
  37. agentscope_runtime/engine/tracing/__init__.py +40 -0
  38. agentscope_runtime/engine/tracing/base.py +309 -0
  39. agentscope_runtime/engine/tracing/local_logging_handler.py +356 -0
  40. agentscope_runtime/engine/tracing/tracing_metric.py +69 -0
  41. agentscope_runtime/engine/tracing/wrapper.py +321 -0
  42. agentscope_runtime/sandbox/__init__.py +14 -0
  43. agentscope_runtime/sandbox/box/__init__.py +0 -0
  44. agentscope_runtime/sandbox/box/base/__init__.py +0 -0
  45. agentscope_runtime/sandbox/box/base/base_sandbox.py +37 -0
  46. agentscope_runtime/sandbox/box/base/box/__init__.py +0 -0
  47. agentscope_runtime/sandbox/box/browser/__init__.py +0 -0
  48. agentscope_runtime/sandbox/box/browser/box/__init__.py +0 -0
  49. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +176 -0
  50. agentscope_runtime/sandbox/box/dummy/__init__.py +0 -0
  51. agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +26 -0
  52. agentscope_runtime/sandbox/box/filesystem/__init__.py +0 -0
  53. agentscope_runtime/sandbox/box/filesystem/box/__init__.py +0 -0
  54. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +87 -0
  55. agentscope_runtime/sandbox/box/sandbox.py +115 -0
  56. agentscope_runtime/sandbox/box/shared/__init__.py +0 -0
  57. agentscope_runtime/sandbox/box/shared/app.py +44 -0
  58. agentscope_runtime/sandbox/box/shared/dependencies/__init__.py +5 -0
  59. agentscope_runtime/sandbox/box/shared/dependencies/deps.py +22 -0
  60. agentscope_runtime/sandbox/box/shared/routers/__init__.py +12 -0
  61. agentscope_runtime/sandbox/box/shared/routers/generic.py +173 -0
  62. agentscope_runtime/sandbox/box/shared/routers/mcp.py +207 -0
  63. agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +153 -0
  64. agentscope_runtime/sandbox/box/shared/routers/runtime_watcher.py +187 -0
  65. agentscope_runtime/sandbox/box/shared/routers/workspace.py +325 -0
  66. agentscope_runtime/sandbox/box/training_box/__init__.py +0 -0
  67. agentscope_runtime/sandbox/box/training_box/base.py +120 -0
  68. agentscope_runtime/sandbox/box/training_box/env_service.py +752 -0
  69. agentscope_runtime/sandbox/box/training_box/environments/__init__.py +0 -0
  70. agentscope_runtime/sandbox/box/training_box/environments/appworld/appworld_env.py +987 -0
  71. agentscope_runtime/sandbox/box/training_box/registry.py +54 -0
  72. agentscope_runtime/sandbox/box/training_box/src/trajectory.py +278 -0
  73. agentscope_runtime/sandbox/box/training_box/training_box.py +219 -0
  74. agentscope_runtime/sandbox/build.py +213 -0
  75. agentscope_runtime/sandbox/client/__init__.py +5 -0
  76. agentscope_runtime/sandbox/client/http_client.py +527 -0
  77. agentscope_runtime/sandbox/client/training_client.py +265 -0
  78. agentscope_runtime/sandbox/constant.py +5 -0
  79. agentscope_runtime/sandbox/custom/__init__.py +16 -0
  80. agentscope_runtime/sandbox/custom/custom_sandbox.py +40 -0
  81. agentscope_runtime/sandbox/custom/example.py +37 -0
  82. agentscope_runtime/sandbox/enums.py +68 -0
  83. agentscope_runtime/sandbox/manager/__init__.py +4 -0
  84. agentscope_runtime/sandbox/manager/collections/__init__.py +22 -0
  85. agentscope_runtime/sandbox/manager/collections/base_mapping.py +20 -0
  86. agentscope_runtime/sandbox/manager/collections/base_queue.py +25 -0
  87. agentscope_runtime/sandbox/manager/collections/base_set.py +25 -0
  88. agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +22 -0
  89. agentscope_runtime/sandbox/manager/collections/in_memory_queue.py +28 -0
  90. agentscope_runtime/sandbox/manager/collections/in_memory_set.py +27 -0
  91. agentscope_runtime/sandbox/manager/collections/redis_mapping.py +26 -0
  92. agentscope_runtime/sandbox/manager/collections/redis_queue.py +27 -0
  93. agentscope_runtime/sandbox/manager/collections/redis_set.py +23 -0
  94. agentscope_runtime/sandbox/manager/container_clients/__init__.py +8 -0
  95. agentscope_runtime/sandbox/manager/container_clients/base_client.py +39 -0
  96. agentscope_runtime/sandbox/manager/container_clients/docker_client.py +170 -0
  97. agentscope_runtime/sandbox/manager/sandbox_manager.py +694 -0
  98. agentscope_runtime/sandbox/manager/server/__init__.py +0 -0
  99. agentscope_runtime/sandbox/manager/server/app.py +194 -0
  100. agentscope_runtime/sandbox/manager/server/config.py +68 -0
  101. agentscope_runtime/sandbox/manager/server/models.py +17 -0
  102. agentscope_runtime/sandbox/manager/storage/__init__.py +10 -0
  103. agentscope_runtime/sandbox/manager/storage/data_storage.py +16 -0
  104. agentscope_runtime/sandbox/manager/storage/local_storage.py +44 -0
  105. agentscope_runtime/sandbox/manager/storage/oss_storage.py +89 -0
  106. agentscope_runtime/sandbox/manager/utils.py +78 -0
  107. agentscope_runtime/sandbox/mcp_server.py +192 -0
  108. agentscope_runtime/sandbox/model/__init__.py +12 -0
  109. agentscope_runtime/sandbox/model/api.py +16 -0
  110. agentscope_runtime/sandbox/model/container.py +72 -0
  111. agentscope_runtime/sandbox/model/manager_config.py +158 -0
  112. agentscope_runtime/sandbox/registry.py +129 -0
  113. agentscope_runtime/sandbox/tools/__init__.py +12 -0
  114. agentscope_runtime/sandbox/tools/base/__init__.py +8 -0
  115. agentscope_runtime/sandbox/tools/base/tool.py +52 -0
  116. agentscope_runtime/sandbox/tools/browser/__init__.py +57 -0
  117. agentscope_runtime/sandbox/tools/browser/tool.py +597 -0
  118. agentscope_runtime/sandbox/tools/filesystem/__init__.py +32 -0
  119. agentscope_runtime/sandbox/tools/filesystem/tool.py +319 -0
  120. agentscope_runtime/sandbox/tools/function_tool.py +321 -0
  121. agentscope_runtime/sandbox/tools/mcp_tool.py +191 -0
  122. agentscope_runtime/sandbox/tools/sandbox_tool.py +104 -0
  123. agentscope_runtime/sandbox/tools/tool.py +123 -0
  124. agentscope_runtime/sandbox/tools/utils.py +68 -0
  125. agentscope_runtime/version.py +2 -0
  126. agentscope_runtime-0.1.0.dist-info/METADATA +327 -0
  127. agentscope_runtime-0.1.0.dist-info/RECORD +131 -0
  128. agentscope_runtime-0.1.0.dist-info/WHEEL +5 -0
  129. agentscope_runtime-0.1.0.dist-info/entry_points.txt +4 -0
  130. agentscope_runtime-0.1.0.dist-info/licenses/LICENSE +202 -0
  131. agentscope_runtime-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,129 @@
1
+ # -*- coding: utf-8 -*-
2
+ from contextlib import asynccontextmanager
3
+ from typing import List
4
+
5
+ from .manager import ServiceManager
6
+ from .memory_service import MemoryService, InMemoryMemoryService
7
+ from .session_history_service import (
8
+ SessionHistoryService,
9
+ Session,
10
+ InMemorySessionHistoryService,
11
+ )
12
+ from ..schemas.agent_schemas import Message
13
+
14
+
15
+ class ContextComposer:
16
+ @staticmethod
17
+ async def compose(
18
+ request_input: List[Message], # current input
19
+ session: Session, # session
20
+ memory_service: MemoryService = None,
21
+ session_history_service: SessionHistoryService = None,
22
+ ):
23
+ # session
24
+ if session_history_service:
25
+ await session_history_service.append_message(
26
+ session=session,
27
+ message=request_input,
28
+ )
29
+ else:
30
+ session.messages += request_input
31
+ # memory
32
+ if memory_service:
33
+ memories: List[Message] = await memory_service.search_memory(
34
+ user_id=session.user_id,
35
+ messages=request_input,
36
+ filters={"top_k": 5},
37
+ )
38
+ await memory_service.add_memory(
39
+ user_id=session.user_id,
40
+ messages=request_input,
41
+ session_id=session.id,
42
+ )
43
+ session.messages = memories + session.messages
44
+
45
+
46
+ class ContextManager(ServiceManager):
47
+ """
48
+ The contextManager class
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ context_composer_cls=ContextComposer,
54
+ session_history_service: SessionHistoryService = None,
55
+ memory_service: MemoryService = None,
56
+ ):
57
+ self._context_composer_cls = context_composer_cls
58
+ self._session_history_service = session_history_service
59
+ self._memory_service = memory_service
60
+ super().__init__()
61
+
62
+ def _register_default_services(self):
63
+ """Register default services for context management."""
64
+ self._session_history_service = (
65
+ self._session_history_service or InMemorySessionHistoryService()
66
+ )
67
+ self._memory_service = self._memory_service or InMemoryMemoryService()
68
+
69
+ self.register_service("session", self._session_history_service)
70
+ self.register_service("memory", self._memory_service)
71
+
72
+ async def compose_context(
73
+ self,
74
+ session: Session,
75
+ request_input: List[Message],
76
+ ):
77
+ await self._context_composer_cls.compose(
78
+ memory_service=self._memory_service,
79
+ session_history_service=self._session_history_service,
80
+ session=session,
81
+ request_input=request_input,
82
+ )
83
+
84
+ async def compose_session(
85
+ self,
86
+ user_id: str,
87
+ session_id: str,
88
+ ):
89
+ if self._session_history_service:
90
+ session = await self._session_history_service.get_session(
91
+ user_id=user_id,
92
+ session_id=session_id,
93
+ )
94
+ if not session:
95
+ raise RuntimeError(f"Session {session_id} not found")
96
+ else:
97
+ session = Session(
98
+ user_id=user_id,
99
+ id=session_id,
100
+ messages=[],
101
+ )
102
+ return session
103
+
104
+ async def append(self, session: Session, event_output: List[Message]):
105
+ if self._session_history_service:
106
+ await self._session_history_service.append_message(
107
+ session=session,
108
+ message=event_output,
109
+ )
110
+ if self._memory_service:
111
+ await self._memory_service.add_memory(
112
+ user_id=session.user_id,
113
+ session_id=session.id,
114
+ messages=event_output,
115
+ )
116
+
117
+
118
+ @asynccontextmanager
119
+ async def create_context_manager(
120
+ memory_service: MemoryService = None,
121
+ session_history_service: SessionHistoryService = None,
122
+ ):
123
+ manager = ContextManager(
124
+ memory_service=memory_service,
125
+ session_history_service=session_history_service,
126
+ )
127
+
128
+ async with manager:
129
+ yield manager
@@ -0,0 +1,50 @@
1
+ # -*- coding: utf-8 -*-
2
+ from typing import List
3
+ from contextlib import asynccontextmanager
4
+
5
+ from .manager import ServiceManager
6
+ from .sandbox_service import SandboxService
7
+
8
+
9
+ class EnvironmentManager(ServiceManager):
10
+ """
11
+ The EnvironmentManager class for managing environment-related services.
12
+ """
13
+
14
+ def __init__(self, sandbox_service: SandboxService = None):
15
+ self._sandbox_service = sandbox_service
16
+ super().__init__()
17
+
18
+ def _register_default_services(self):
19
+ """Register default services for environment management."""
20
+ self._sandbox_service = self._sandbox_service or SandboxService()
21
+ self.register_service("sandbox", self._sandbox_service)
22
+
23
+ def connect_sandbox(
24
+ self,
25
+ session_id,
26
+ user_id,
27
+ env_types=None,
28
+ tools=None,
29
+ ) -> List:
30
+ return self._sandbox_service.connect(
31
+ session_id,
32
+ user_id,
33
+ env_types=env_types,
34
+ tools=tools,
35
+ )
36
+
37
+ def release_sandbox(self, session_id, user_id):
38
+ return self._sandbox_service.release(session_id, user_id)
39
+
40
+
41
+ @asynccontextmanager
42
+ async def create_environment_manager(
43
+ sandbox_service: SandboxService = None,
44
+ ):
45
+ manager = EnvironmentManager(
46
+ sandbox_service=sandbox_service,
47
+ )
48
+
49
+ async with manager:
50
+ yield manager
@@ -0,0 +1,174 @@
1
+ # -*- coding: utf-8 -*-
2
+ import logging
3
+ from contextlib import AsyncExitStack
4
+ from typing import Dict, Any, Type, List
5
+ from abc import ABC, abstractmethod
6
+
7
+ from agentscope_runtime.engine import Service
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class ServiceManager(ABC):
13
+ """
14
+ Abstract base class for service managers.
15
+ Provides common functionality for service registration and lifecycle
16
+ management.
17
+ """
18
+
19
+ def __init__(self):
20
+ self.services = []
21
+ self.service_instances = {}
22
+ self._exit_stack = AsyncExitStack()
23
+
24
+ # Initialize default services
25
+ self._register_default_services()
26
+
27
+ @abstractmethod
28
+ def _register_default_services(self):
29
+ """
30
+ Register default services for this manager. Override in
31
+ subclasses.
32
+ """
33
+
34
+ def register(self, service_class: Type, *args, name: str = None, **kwargs):
35
+ """
36
+ Register a service.
37
+
38
+ Args:
39
+ service_class: The class of the service to register.
40
+ *args: Positional arguments for service initialization.
41
+ name: Optional service name. Defaults to class name without
42
+ 'Service' suffix and converted to lowercase.
43
+ **kwargs: Keyword arguments for service initialization.
44
+
45
+ Returns:
46
+ self: For method chaining
47
+ """
48
+ if name is None:
49
+ name = service_class.__name__.replace("Service", "").lower()
50
+
51
+ # Check if service name already exists
52
+ if any(service[3] == name for service in self.services):
53
+ raise ValueError(
54
+ f"Service with name '{name}' is already registered",
55
+ )
56
+
57
+ self.services.append((service_class, args, kwargs, name))
58
+ logger.debug(f"Registered service: {name} ({service_class.__name__})")
59
+ return self
60
+
61
+ def register_service(self, name: str, service: Service):
62
+ """Register an already instantiated service.
63
+
64
+ Args:
65
+ name: Service name
66
+ service: Service instance
67
+
68
+ Returns:
69
+ self: For method chaining
70
+ """
71
+ if name in self.service_instances:
72
+ raise ValueError(
73
+ f"Service with name '{name}' is already registered",
74
+ )
75
+
76
+ self.service_instances[name] = service
77
+ logger.debug(f"Registered service instance: {name}")
78
+ return self
79
+
80
+ async def __aenter__(self):
81
+ """Start all registered services using AsyncExitStack."""
82
+ try:
83
+ # Track services that were registered with register() to avoid
84
+ # duplicate processing
85
+ registered_names = set()
86
+
87
+ # Start services that were registered with register()
88
+ for service_class, args, kwargs, name in self.services:
89
+ logger.debug(f"Starting service: {name}")
90
+ instance = service_class(*args, **kwargs)
91
+
92
+ # Use AsyncExitStack to manage the context
93
+ await self._exit_stack.enter_async_context(instance)
94
+ self.service_instances[name] = instance
95
+ registered_names.add(name) # Track this service as processed
96
+ logger.debug(f"Successfully started service: {name}")
97
+
98
+ # Start services that were registered with register_service()
99
+ # These services are already instantiated, just need to enter
100
+ # their context
101
+ for name, service in list(self.service_instances.items()):
102
+ if (
103
+ name not in registered_names
104
+ ): # Only process services not from register() method
105
+ logger.debug(f"Starting pre-instantiated service: {name}")
106
+ await self._exit_stack.enter_async_context(service)
107
+ logger.debug(
108
+ f"Successfully started pre-instantiated service:"
109
+ f" {name}",
110
+ )
111
+
112
+ return self
113
+
114
+ except Exception as e:
115
+ logger.error(f"Failed to start services: {e}")
116
+ # Ensure proper cleanup if initialization fails
117
+ await self._exit_stack.aclose()
118
+ raise
119
+
120
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
121
+ """Close all services using AsyncExitStack."""
122
+ logger.debug("Stopping all services")
123
+ await self._exit_stack.aclose()
124
+ self.service_instances.clear()
125
+ logger.debug("All services stopped")
126
+ return False
127
+
128
+ def __getattr__(self, name: str):
129
+ """
130
+ Enable attribute access for services, e.g., manager.env,
131
+ manager.session.
132
+ """
133
+ if name in self.service_instances:
134
+ return self.service_instances[name]
135
+ raise AttributeError(f"Service '{name}' not found")
136
+
137
+ def __getitem__(self, name: str):
138
+ """Enable dictionary-style access for services."""
139
+ if name in self.service_instances:
140
+ return self.service_instances[name]
141
+ raise KeyError(f"Service '{name}' not found")
142
+
143
+ def get(self, name: str, default=None):
144
+ """Explicitly retrieve a service instance with optional default."""
145
+ return self.service_instances.get(name, default)
146
+
147
+ def has_service(self, name: str) -> bool:
148
+ """Check if a service exists."""
149
+ return name in self.service_instances
150
+
151
+ def list_services(self) -> List[str]:
152
+ """List all registered service names."""
153
+ return list(self.service_instances.keys())
154
+
155
+ @property
156
+ def all_services(self) -> Dict[str, Any]:
157
+ """Retrieve all service instances."""
158
+ return self.service_instances.copy()
159
+
160
+ async def health_check(self) -> Dict[str, bool]:
161
+ """Check health of all services."""
162
+ health_status = {}
163
+ for name, service in self.service_instances.items():
164
+ try:
165
+ if hasattr(service, "health"):
166
+ health_status[name] = await service.health()
167
+ else:
168
+ health_status[
169
+ name
170
+ ] = True # Assume healthy if no health method
171
+ except Exception as e:
172
+ logger.error(f"Health check failed for service {name}: {e}")
173
+ health_status[name] = False
174
+ return health_status
@@ -0,0 +1,270 @@
1
+ # -*- coding: utf-8 -*-
2
+ from abc import abstractmethod
3
+ from typing import Optional, Dict, Any
4
+
5
+
6
+ from pydantic import Field
7
+
8
+ from .base import ServiceWithLifecycleManager
9
+ from ..schemas.agent_schemas import MessageType, Message
10
+
11
+
12
+ class MemoryService(ServiceWithLifecycleManager):
13
+ """
14
+ Used to store and retrieve long memory from the database or in-memory.
15
+ The memory is organized by the user id at top level, under which there are
16
+ two different memory manage strategies,
17
+ - one is the message grouped by the session id, the session id is under
18
+ the user id,
19
+ - the other is the message grouped by the user id only
20
+ """
21
+
22
+ @abstractmethod
23
+ async def add_memory(
24
+ self,
25
+ user_id: str,
26
+ messages: list,
27
+ session_id: Optional[str] = None,
28
+ ) -> None:
29
+ """
30
+ Adds messages to the memory service.
31
+
32
+ Args:
33
+ user_id: The user id.
34
+ messages: The messages to add.
35
+ session_id: The session id, which is optional.
36
+ """
37
+
38
+ async def stop(self):
39
+ raise NotImplementedError
40
+
41
+ async def start(self):
42
+ raise NotImplementedError
43
+
44
+ @abstractmethod
45
+ async def search_memory(
46
+ self,
47
+ user_id: str,
48
+ messages: list,
49
+ filters: Optional[Dict[str, Any]] = Field(
50
+ description="Associated filters for the messages, "
51
+ "such as top_k, score etc.",
52
+ default=None,
53
+ ),
54
+ ) -> list:
55
+ """
56
+ Searches messages from the memory service.
57
+
58
+ Args:
59
+ user_id: The user id.
60
+ messages: The user query or the query with history messages,
61
+ both in the format of list of messages. If messages is a list,
62
+ the search will be based on the content of the last message.
63
+ filters: The filters used to search memory
64
+ """
65
+
66
+ @abstractmethod
67
+ async def list_memory(
68
+ self,
69
+ user_id: str,
70
+ filters: Optional[Dict[str, Any]] = Field(
71
+ description="Associated filters for the messages, "
72
+ "such as top_k, score etc.",
73
+ default=None,
74
+ ),
75
+ ) -> list:
76
+ """
77
+ Lists the memory items for a given user with filters, such as
78
+ page_num, page_size, etc.
79
+
80
+ Args:
81
+ user_id: The user id.
82
+ filters: The filters for the memory items.
83
+ """
84
+
85
+ @abstractmethod
86
+ async def delete_memory(
87
+ self,
88
+ user_id: str,
89
+ session_id: Optional[str] = None,
90
+ ) -> None:
91
+ """
92
+ Deletes the memory items for a given user with certain session id,
93
+ or all the memory items for a given user.
94
+ """
95
+
96
+
97
+ class InMemoryMemoryService(MemoryService):
98
+ """
99
+ An in-memory implementation of the memory service.
100
+ """
101
+
102
+ _store: Dict[str, Dict[str, list]] = {}
103
+ _DEFAULT_SESSION_ID = "default"
104
+
105
+ async def start(self) -> None:
106
+ """Starts the service."""
107
+ self._store = {}
108
+
109
+ async def stop(self) -> None:
110
+ """Stops the service."""
111
+ self._store = {}
112
+
113
+ async def health(self) -> bool:
114
+ """Checks the health of the service."""
115
+ return True
116
+
117
+ async def add_memory(
118
+ self,
119
+ user_id: str,
120
+ messages: list,
121
+ session_id: Optional[str] = None,
122
+ ) -> None:
123
+ """
124
+ Adds messages to the in-memory store.
125
+
126
+ Args:
127
+ user_id: The user's unique identifier.
128
+ messages: A list of messages to be added.
129
+ session_id: An optional session identifier. If not provided,
130
+ a default session is used.
131
+ """
132
+ if user_id not in self._store:
133
+ self._store[user_id] = {}
134
+
135
+ storage_key = session_id if session_id else self._DEFAULT_SESSION_ID
136
+
137
+ if storage_key not in self._store[user_id]:
138
+ self._store[user_id][storage_key] = []
139
+
140
+ self._store[user_id][storage_key].extend(messages)
141
+
142
+ async def search_memory(
143
+ self,
144
+ user_id: str,
145
+ messages: list,
146
+ filters: Optional[Dict[str, Any]] = None,
147
+ ) -> list:
148
+ """
149
+ Searches messages from the in-memory store for a specific user
150
+ based on keywords.
151
+
152
+ Args:
153
+ user_id: The user's unique identifier.
154
+ messages: A list of messages, where the last message's content
155
+ is used as the search query.
156
+ filters: Optional filters to apply, such as 'top_k' to limit the
157
+ number of returned messages.
158
+
159
+ Returns:
160
+ A list of matching messages from the store.
161
+ """
162
+ if user_id not in self._store:
163
+ return []
164
+
165
+ if (
166
+ not messages
167
+ or not isinstance(messages, list)
168
+ or len(messages) == 0
169
+ ):
170
+ return []
171
+
172
+ message = messages[-1]
173
+ query = await self.get_query_text(message)
174
+ if not query:
175
+ return []
176
+
177
+ keywords = set(query.lower().split())
178
+
179
+ all_messages = []
180
+ for session_messages in self._store[user_id].values():
181
+ all_messages.extend(session_messages)
182
+
183
+ matched_messages = []
184
+ for msg in all_messages:
185
+ candidate_content = await self.get_query_text(msg)
186
+ if candidate_content:
187
+ msg_content_lower = candidate_content.lower()
188
+ if any(keyword in msg_content_lower for keyword in keywords):
189
+ matched_messages.append(msg)
190
+
191
+ if (
192
+ filters
193
+ and "top_k" in filters
194
+ and isinstance(filters["top_k"], int)
195
+ ):
196
+ return matched_messages[-filters["top_k"] :]
197
+
198
+ return matched_messages
199
+
200
+ async def get_query_text(self, message: Message) -> str:
201
+ """
202
+ Gets the query text from the messages.
203
+
204
+ Args:
205
+ message: A list of messages.
206
+
207
+ Returns:
208
+ The query text.
209
+ """
210
+ if message:
211
+ if message.type == MessageType.MESSAGE:
212
+ for content in message.content:
213
+ if content.type == "text":
214
+ return content.text
215
+ return ""
216
+
217
+ async def list_memory(
218
+ self,
219
+ user_id: str,
220
+ filters: Optional[Dict[str, Any]] = None,
221
+ ) -> list:
222
+ """
223
+ Lists messages from the in-memory store with pagination support.
224
+
225
+ Args:
226
+ user_id: The user's unique identifier.
227
+ filters: Optional filters for pagination, including 'page_num'
228
+ and 'page_size'.
229
+
230
+ Returns:
231
+ A paginated list of messages.
232
+ """
233
+ if user_id not in self._store:
234
+ return []
235
+
236
+ all_messages = []
237
+ # Sort by session id to have a consistent order for pagination
238
+ for session_id in sorted(self._store[user_id].keys()):
239
+ all_messages.extend(self._store[user_id][session_id])
240
+
241
+ page_num = filters.get("page_num", 1) if filters else 1
242
+ page_size = filters.get("page_size", 10) if filters else 10
243
+
244
+ start_index = (page_num - 1) * page_size
245
+ end_index = start_index + page_size
246
+
247
+ return all_messages[start_index:end_index]
248
+
249
+ async def delete_memory(
250
+ self,
251
+ user_id: str,
252
+ session_id: Optional[str] = None,
253
+ ) -> None:
254
+ """
255
+ Deletes messages from the in-memory store.
256
+
257
+ Args:
258
+ user_id: The user's unique identifier.
259
+ session_id: If provided, only deletes the messages for that
260
+ session. Otherwise, deletes all messages for the user.
261
+ """
262
+ if user_id not in self._store:
263
+ return
264
+
265
+ if session_id:
266
+ if session_id in self._store[user_id]:
267
+ del self._store[user_id][session_id]
268
+ else:
269
+ if user_id in self._store:
270
+ del self._store[user_id]