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.
- agentscope_runtime/__init__.py +4 -0
- agentscope_runtime/engine/__init__.py +9 -0
- agentscope_runtime/engine/agents/__init__.py +2 -0
- agentscope_runtime/engine/agents/agentscope_agent/__init__.py +6 -0
- agentscope_runtime/engine/agents/agentscope_agent/agent.py +342 -0
- agentscope_runtime/engine/agents/agentscope_agent/hooks.py +156 -0
- agentscope_runtime/engine/agents/agno_agent.py +220 -0
- agentscope_runtime/engine/agents/base_agent.py +29 -0
- agentscope_runtime/engine/agents/langgraph_agent.py +59 -0
- agentscope_runtime/engine/agents/llm_agent.py +51 -0
- agentscope_runtime/engine/deployers/__init__.py +3 -0
- agentscope_runtime/engine/deployers/adapter/__init__.py +0 -0
- agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +2 -0
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_adapter_utils.py +425 -0
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_agent_adapter.py +69 -0
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +60 -0
- agentscope_runtime/engine/deployers/adapter/protocol_adapter.py +24 -0
- agentscope_runtime/engine/deployers/base.py +17 -0
- agentscope_runtime/engine/deployers/local_deployer.py +586 -0
- agentscope_runtime/engine/helpers/helper.py +127 -0
- agentscope_runtime/engine/llms/__init__.py +3 -0
- agentscope_runtime/engine/llms/base_llm.py +60 -0
- agentscope_runtime/engine/llms/qwen_llm.py +47 -0
- agentscope_runtime/engine/misc/__init__.py +0 -0
- agentscope_runtime/engine/runner.py +186 -0
- agentscope_runtime/engine/schemas/__init__.py +0 -0
- agentscope_runtime/engine/schemas/agent_schemas.py +551 -0
- agentscope_runtime/engine/schemas/context.py +54 -0
- agentscope_runtime/engine/services/__init__.py +9 -0
- agentscope_runtime/engine/services/base.py +77 -0
- agentscope_runtime/engine/services/context_manager.py +129 -0
- agentscope_runtime/engine/services/environment_manager.py +50 -0
- agentscope_runtime/engine/services/manager.py +174 -0
- agentscope_runtime/engine/services/memory_service.py +270 -0
- agentscope_runtime/engine/services/sandbox_service.py +198 -0
- agentscope_runtime/engine/services/session_history_service.py +256 -0
- agentscope_runtime/engine/tracing/__init__.py +40 -0
- agentscope_runtime/engine/tracing/base.py +309 -0
- agentscope_runtime/engine/tracing/local_logging_handler.py +356 -0
- agentscope_runtime/engine/tracing/tracing_metric.py +69 -0
- agentscope_runtime/engine/tracing/wrapper.py +321 -0
- agentscope_runtime/sandbox/__init__.py +14 -0
- agentscope_runtime/sandbox/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/base/__init__.py +0 -0
- agentscope_runtime/sandbox/box/base/base_sandbox.py +37 -0
- agentscope_runtime/sandbox/box/base/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/browser/__init__.py +0 -0
- agentscope_runtime/sandbox/box/browser/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/browser/browser_sandbox.py +176 -0
- agentscope_runtime/sandbox/box/dummy/__init__.py +0 -0
- agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +26 -0
- agentscope_runtime/sandbox/box/filesystem/__init__.py +0 -0
- agentscope_runtime/sandbox/box/filesystem/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +87 -0
- agentscope_runtime/sandbox/box/sandbox.py +115 -0
- agentscope_runtime/sandbox/box/shared/__init__.py +0 -0
- agentscope_runtime/sandbox/box/shared/app.py +44 -0
- agentscope_runtime/sandbox/box/shared/dependencies/__init__.py +5 -0
- agentscope_runtime/sandbox/box/shared/dependencies/deps.py +22 -0
- agentscope_runtime/sandbox/box/shared/routers/__init__.py +12 -0
- agentscope_runtime/sandbox/box/shared/routers/generic.py +173 -0
- agentscope_runtime/sandbox/box/shared/routers/mcp.py +207 -0
- agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +153 -0
- agentscope_runtime/sandbox/box/shared/routers/runtime_watcher.py +187 -0
- agentscope_runtime/sandbox/box/shared/routers/workspace.py +325 -0
- agentscope_runtime/sandbox/box/training_box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/training_box/base.py +120 -0
- agentscope_runtime/sandbox/box/training_box/env_service.py +752 -0
- agentscope_runtime/sandbox/box/training_box/environments/__init__.py +0 -0
- agentscope_runtime/sandbox/box/training_box/environments/appworld/appworld_env.py +987 -0
- agentscope_runtime/sandbox/box/training_box/registry.py +54 -0
- agentscope_runtime/sandbox/box/training_box/src/trajectory.py +278 -0
- agentscope_runtime/sandbox/box/training_box/training_box.py +219 -0
- agentscope_runtime/sandbox/build.py +213 -0
- agentscope_runtime/sandbox/client/__init__.py +5 -0
- agentscope_runtime/sandbox/client/http_client.py +527 -0
- agentscope_runtime/sandbox/client/training_client.py +265 -0
- agentscope_runtime/sandbox/constant.py +5 -0
- agentscope_runtime/sandbox/custom/__init__.py +16 -0
- agentscope_runtime/sandbox/custom/custom_sandbox.py +40 -0
- agentscope_runtime/sandbox/custom/example.py +37 -0
- agentscope_runtime/sandbox/enums.py +68 -0
- agentscope_runtime/sandbox/manager/__init__.py +4 -0
- agentscope_runtime/sandbox/manager/collections/__init__.py +22 -0
- agentscope_runtime/sandbox/manager/collections/base_mapping.py +20 -0
- agentscope_runtime/sandbox/manager/collections/base_queue.py +25 -0
- agentscope_runtime/sandbox/manager/collections/base_set.py +25 -0
- agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +22 -0
- agentscope_runtime/sandbox/manager/collections/in_memory_queue.py +28 -0
- agentscope_runtime/sandbox/manager/collections/in_memory_set.py +27 -0
- agentscope_runtime/sandbox/manager/collections/redis_mapping.py +26 -0
- agentscope_runtime/sandbox/manager/collections/redis_queue.py +27 -0
- agentscope_runtime/sandbox/manager/collections/redis_set.py +23 -0
- agentscope_runtime/sandbox/manager/container_clients/__init__.py +8 -0
- agentscope_runtime/sandbox/manager/container_clients/base_client.py +39 -0
- agentscope_runtime/sandbox/manager/container_clients/docker_client.py +170 -0
- agentscope_runtime/sandbox/manager/sandbox_manager.py +694 -0
- agentscope_runtime/sandbox/manager/server/__init__.py +0 -0
- agentscope_runtime/sandbox/manager/server/app.py +194 -0
- agentscope_runtime/sandbox/manager/server/config.py +68 -0
- agentscope_runtime/sandbox/manager/server/models.py +17 -0
- agentscope_runtime/sandbox/manager/storage/__init__.py +10 -0
- agentscope_runtime/sandbox/manager/storage/data_storage.py +16 -0
- agentscope_runtime/sandbox/manager/storage/local_storage.py +44 -0
- agentscope_runtime/sandbox/manager/storage/oss_storage.py +89 -0
- agentscope_runtime/sandbox/manager/utils.py +78 -0
- agentscope_runtime/sandbox/mcp_server.py +192 -0
- agentscope_runtime/sandbox/model/__init__.py +12 -0
- agentscope_runtime/sandbox/model/api.py +16 -0
- agentscope_runtime/sandbox/model/container.py +72 -0
- agentscope_runtime/sandbox/model/manager_config.py +158 -0
- agentscope_runtime/sandbox/registry.py +129 -0
- agentscope_runtime/sandbox/tools/__init__.py +12 -0
- agentscope_runtime/sandbox/tools/base/__init__.py +8 -0
- agentscope_runtime/sandbox/tools/base/tool.py +52 -0
- agentscope_runtime/sandbox/tools/browser/__init__.py +57 -0
- agentscope_runtime/sandbox/tools/browser/tool.py +597 -0
- agentscope_runtime/sandbox/tools/filesystem/__init__.py +32 -0
- agentscope_runtime/sandbox/tools/filesystem/tool.py +319 -0
- agentscope_runtime/sandbox/tools/function_tool.py +321 -0
- agentscope_runtime/sandbox/tools/mcp_tool.py +191 -0
- agentscope_runtime/sandbox/tools/sandbox_tool.py +104 -0
- agentscope_runtime/sandbox/tools/tool.py +123 -0
- agentscope_runtime/sandbox/tools/utils.py +68 -0
- agentscope_runtime/version.py +2 -0
- agentscope_runtime-0.1.0.dist-info/METADATA +327 -0
- agentscope_runtime-0.1.0.dist-info/RECORD +131 -0
- agentscope_runtime-0.1.0.dist-info/WHEEL +5 -0
- agentscope_runtime-0.1.0.dist-info/entry_points.txt +4 -0
- agentscope_runtime-0.1.0.dist-info/licenses/LICENSE +202 -0
- 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]
|