agentscope-runtime 1.0.5.post1__py3-none-any.whl → 1.1.0b3__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 +3 -0
- agentscope_runtime/adapters/agentscope/message.py +85 -295
- agentscope_runtime/adapters/agentscope/stream.py +133 -3
- agentscope_runtime/adapters/agno/message.py +11 -2
- agentscope_runtime/adapters/agno/stream.py +1 -0
- agentscope_runtime/adapters/langgraph/__init__.py +1 -3
- agentscope_runtime/adapters/langgraph/message.py +11 -106
- agentscope_runtime/adapters/langgraph/stream.py +1 -0
- agentscope_runtime/adapters/ms_agent_framework/message.py +11 -1
- agentscope_runtime/adapters/ms_agent_framework/stream.py +1 -0
- agentscope_runtime/adapters/text/stream.py +1 -0
- agentscope_runtime/common/container_clients/agentrun_client.py +0 -3
- agentscope_runtime/common/container_clients/boxlite_client.py +26 -15
- agentscope_runtime/common/container_clients/fc_client.py +0 -11
- agentscope_runtime/common/utils/deprecation.py +14 -17
- agentscope_runtime/common/utils/logging.py +44 -0
- agentscope_runtime/engine/app/agent_app.py +5 -5
- agentscope_runtime/engine/app/celery_mixin.py +43 -4
- agentscope_runtime/engine/deployers/adapter/agui/__init__.py +8 -1
- agentscope_runtime/engine/deployers/adapter/agui/agui_adapter_utils.py +6 -1
- agentscope_runtime/engine/deployers/adapter/agui/agui_protocol_adapter.py +2 -2
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +13 -0
- agentscope_runtime/engine/runner.py +31 -6
- agentscope_runtime/engine/schemas/agent_schemas.py +28 -0
- agentscope_runtime/engine/services/sandbox/sandbox_service.py +41 -9
- agentscope_runtime/sandbox/box/base/base_sandbox.py +4 -0
- agentscope_runtime/sandbox/box/browser/browser_sandbox.py +4 -0
- agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +9 -2
- agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +4 -0
- agentscope_runtime/sandbox/box/gui/gui_sandbox.py +5 -1
- agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +4 -0
- agentscope_runtime/sandbox/box/sandbox.py +122 -13
- agentscope_runtime/sandbox/client/async_http_client.py +1 -0
- agentscope_runtime/sandbox/client/base.py +0 -1
- agentscope_runtime/sandbox/client/http_client.py +0 -2
- agentscope_runtime/sandbox/manager/heartbeat_mixin.py +486 -0
- agentscope_runtime/sandbox/manager/sandbox_manager.py +740 -153
- agentscope_runtime/sandbox/manager/server/app.py +18 -11
- agentscope_runtime/sandbox/manager/server/config.py +10 -2
- agentscope_runtime/sandbox/mcp_server.py +0 -1
- agentscope_runtime/sandbox/model/__init__.py +2 -1
- agentscope_runtime/sandbox/model/container.py +90 -3
- agentscope_runtime/sandbox/model/manager_config.py +45 -1
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-1.0.5.post1.dist-info → agentscope_runtime-1.1.0b3.dist-info}/METADATA +37 -54
- {agentscope_runtime-1.0.5.post1.dist-info → agentscope_runtime-1.1.0b3.dist-info}/RECORD +50 -69
- agentscope_runtime/adapters/agentscope/long_term_memory/__init__.py +0 -6
- agentscope_runtime/adapters/agentscope/long_term_memory/_long_term_memory_adapter.py +0 -258
- agentscope_runtime/adapters/agentscope/memory/__init__.py +0 -6
- agentscope_runtime/adapters/agentscope/memory/_memory_adapter.py +0 -152
- agentscope_runtime/engine/services/agent_state/__init__.py +0 -25
- agentscope_runtime/engine/services/agent_state/redis_state_service.py +0 -166
- agentscope_runtime/engine/services/agent_state/state_service.py +0 -179
- agentscope_runtime/engine/services/agent_state/state_service_factory.py +0 -52
- agentscope_runtime/engine/services/memory/__init__.py +0 -33
- agentscope_runtime/engine/services/memory/mem0_memory_service.py +0 -128
- agentscope_runtime/engine/services/memory/memory_service.py +0 -292
- agentscope_runtime/engine/services/memory/memory_service_factory.py +0 -126
- agentscope_runtime/engine/services/memory/redis_memory_service.py +0 -290
- agentscope_runtime/engine/services/memory/reme_personal_memory_service.py +0 -109
- agentscope_runtime/engine/services/memory/reme_task_memory_service.py +0 -11
- agentscope_runtime/engine/services/memory/tablestore_memory_service.py +0 -301
- agentscope_runtime/engine/services/session_history/__init__.py +0 -32
- agentscope_runtime/engine/services/session_history/redis_session_history_service.py +0 -283
- agentscope_runtime/engine/services/session_history/session_history_service.py +0 -267
- agentscope_runtime/engine/services/session_history/session_history_service_factory.py +0 -73
- agentscope_runtime/engine/services/session_history/tablestore_session_history_service.py +0 -288
- {agentscope_runtime-1.0.5.post1.dist-info → agentscope_runtime-1.1.0b3.dist-info}/WHEEL +0 -0
- {agentscope_runtime-1.0.5.post1.dist-info → agentscope_runtime-1.1.0b3.dist-info}/entry_points.txt +0 -0
- {agentscope_runtime-1.0.5.post1.dist-info → agentscope_runtime-1.1.0b3.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-1.0.5.post1.dist-info → agentscope_runtime-1.1.0b3.dist-info}/top_level.txt +0 -0
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
# pylint:disable=protected-access
|
|
3
|
-
"""AgentScope Memory implementation based on SessionHistoryService."""
|
|
4
|
-
import functools
|
|
5
|
-
|
|
6
|
-
from typing import Union
|
|
7
|
-
|
|
8
|
-
from agentscope.memory import MemoryBase
|
|
9
|
-
from agentscope.message import Msg
|
|
10
|
-
|
|
11
|
-
from ..message import agentscope_msg_to_message, message_to_agentscope_msg
|
|
12
|
-
from ....engine.services.session_history import SessionHistoryService
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def ensure_session(func):
|
|
16
|
-
"""
|
|
17
|
-
Async decorator that ensures the AgentScope session exists before
|
|
18
|
-
method execution.
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
@functools.wraps(func)
|
|
22
|
-
async def wrapper(self, *args, **kwargs):
|
|
23
|
-
await self._check_session()
|
|
24
|
-
return await func(self, *args, **kwargs)
|
|
25
|
-
|
|
26
|
-
return wrapper
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class AgentScopeSessionHistoryMemory(MemoryBase):
|
|
30
|
-
"""
|
|
31
|
-
AgentScope Memory subclass based on MemoryBase.
|
|
32
|
-
|
|
33
|
-
This class stores messages in an underlying SessionHistoryService instance.
|
|
34
|
-
|
|
35
|
-
Args:
|
|
36
|
-
service (SessionHistoryService): The backend session history service.
|
|
37
|
-
user_id (str): The user ID linked to this memory.
|
|
38
|
-
session_id (str): The session ID linked to this memory.
|
|
39
|
-
"""
|
|
40
|
-
|
|
41
|
-
def __init__(
|
|
42
|
-
self,
|
|
43
|
-
service: SessionHistoryService,
|
|
44
|
-
user_id: str,
|
|
45
|
-
session_id: str,
|
|
46
|
-
):
|
|
47
|
-
super().__init__()
|
|
48
|
-
self._service = service
|
|
49
|
-
self.user_id = user_id
|
|
50
|
-
self.session_id = session_id
|
|
51
|
-
self._session = None
|
|
52
|
-
|
|
53
|
-
async def _check_session(self) -> None:
|
|
54
|
-
"""
|
|
55
|
-
Check if the session exists in the backend.
|
|
56
|
-
"""
|
|
57
|
-
# Always check for session to stay in sync with backend
|
|
58
|
-
self._session = await self._service.get_session(
|
|
59
|
-
self.user_id,
|
|
60
|
-
self.session_id,
|
|
61
|
-
)
|
|
62
|
-
if self._session is None:
|
|
63
|
-
self._session = await self._service.create_session(
|
|
64
|
-
self.user_id,
|
|
65
|
-
self.session_id,
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
def state_dict(self):
|
|
69
|
-
"""
|
|
70
|
-
Get current memory state as a dictionary.
|
|
71
|
-
Always fetch from backend to ensure consistency.
|
|
72
|
-
"""
|
|
73
|
-
|
|
74
|
-
def load_state_dict(
|
|
75
|
-
self,
|
|
76
|
-
state_dict: dict,
|
|
77
|
-
strict: bool = True,
|
|
78
|
-
) -> None:
|
|
79
|
-
"""
|
|
80
|
-
Load memory state from a dictionary into backend storage.
|
|
81
|
-
"""
|
|
82
|
-
|
|
83
|
-
@ensure_session
|
|
84
|
-
async def size(self) -> int:
|
|
85
|
-
"""The size of the memory."""
|
|
86
|
-
current_message = self._session.messages
|
|
87
|
-
agentscope_msg = message_to_agentscope_msg(current_message)
|
|
88
|
-
return len(agentscope_msg)
|
|
89
|
-
|
|
90
|
-
@ensure_session
|
|
91
|
-
async def add(
|
|
92
|
-
self,
|
|
93
|
-
memories: Union[list[Msg], Msg, None],
|
|
94
|
-
allow_duplicates: bool = False, # pylint: disable=unused-argument
|
|
95
|
-
) -> None:
|
|
96
|
-
"""Add messages to backend service."""
|
|
97
|
-
if memories is None:
|
|
98
|
-
return
|
|
99
|
-
if isinstance(memories, Msg):
|
|
100
|
-
memories = [memories]
|
|
101
|
-
if not isinstance(memories, list):
|
|
102
|
-
raise TypeError(f"Expected Msg or list[Msg], got {type(memories)}")
|
|
103
|
-
|
|
104
|
-
# Convert Msg -> backend Message
|
|
105
|
-
backend_messages = agentscope_msg_to_message(memories)
|
|
106
|
-
|
|
107
|
-
if self._session:
|
|
108
|
-
await self._service.append_message(self._session, backend_messages)
|
|
109
|
-
|
|
110
|
-
@ensure_session
|
|
111
|
-
async def delete(self, index: Union[list[int], int]) -> None:
|
|
112
|
-
"""
|
|
113
|
-
Delete messages by index
|
|
114
|
-
"""
|
|
115
|
-
# TODO: add method to delete messages instead of replacement
|
|
116
|
-
if isinstance(index, int):
|
|
117
|
-
index = [index]
|
|
118
|
-
|
|
119
|
-
current_message = self._session.messages
|
|
120
|
-
agentscope_msg = message_to_agentscope_msg(current_message)
|
|
121
|
-
|
|
122
|
-
invalid_index = [_ for _ in index if _ < 0 or _ >= len(agentscope_msg)]
|
|
123
|
-
|
|
124
|
-
if invalid_index:
|
|
125
|
-
raise IndexError(
|
|
126
|
-
f"The index {invalid_index} does not exist.",
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
agentscope_msg = [
|
|
130
|
-
_ for idx, _ in enumerate(agentscope_msg) if idx not in index
|
|
131
|
-
]
|
|
132
|
-
|
|
133
|
-
await self.clear()
|
|
134
|
-
await self.add(agentscope_msg)
|
|
135
|
-
|
|
136
|
-
@ensure_session
|
|
137
|
-
async def clear(self) -> None:
|
|
138
|
-
"""Clear backend session memory."""
|
|
139
|
-
await self._service.delete_session(
|
|
140
|
-
user_id=self.user_id,
|
|
141
|
-
session_id=self.session_id,
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
@ensure_session
|
|
145
|
-
async def get_memory(self) -> list[Msg]:
|
|
146
|
-
"""
|
|
147
|
-
Retrieve memory content.
|
|
148
|
-
For sync purposes, we reload from backend before returning.
|
|
149
|
-
"""
|
|
150
|
-
current_message = self._session.messages
|
|
151
|
-
agentscope_msg = message_to_agentscope_msg(current_message)
|
|
152
|
-
return agentscope_msg
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
from typing import TYPE_CHECKING
|
|
3
|
-
from ....common.utils.lazy_loader import install_lazy_loader
|
|
4
|
-
from ....common.utils.deprecation import deprecated_module
|
|
5
|
-
|
|
6
|
-
deprecated_module(
|
|
7
|
-
module_name=__name__,
|
|
8
|
-
removed_in="v1.1",
|
|
9
|
-
alternative="agentscope.session",
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from .state_service import StateService, InMemoryStateService
|
|
14
|
-
from .redis_state_service import RedisStateService
|
|
15
|
-
from .state_service_factory import StateServiceFactory
|
|
16
|
-
|
|
17
|
-
install_lazy_loader(
|
|
18
|
-
globals(),
|
|
19
|
-
{
|
|
20
|
-
"StateService": ".state_service",
|
|
21
|
-
"InMemoryStateService": ".state_service",
|
|
22
|
-
"RedisStateService": ".redis_state_service",
|
|
23
|
-
"StateServiceFactory": ".state_service_factory",
|
|
24
|
-
},
|
|
25
|
-
)
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
import json
|
|
3
|
-
from typing import Optional, Dict, Any
|
|
4
|
-
|
|
5
|
-
import redis.asyncio as aioredis
|
|
6
|
-
|
|
7
|
-
from .state_service import StateService
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class RedisStateService(StateService):
|
|
11
|
-
"""
|
|
12
|
-
Redis-based implementation of StateService.
|
|
13
|
-
|
|
14
|
-
Stores agent states in Redis using a hash per (user_id, session_id),
|
|
15
|
-
with round_id as the hash field and serialized state as the value.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
_DEFAULT_SESSION_ID = "default"
|
|
19
|
-
|
|
20
|
-
def __init__(
|
|
21
|
-
self,
|
|
22
|
-
redis_url: str = "redis://localhost:6379/0",
|
|
23
|
-
redis_client: Optional[aioredis.Redis] = None,
|
|
24
|
-
socket_timeout: Optional[float] = 5.0,
|
|
25
|
-
socket_connect_timeout: Optional[float] = 5.0,
|
|
26
|
-
max_connections: Optional[int] = None,
|
|
27
|
-
retry_on_timeout: bool = True,
|
|
28
|
-
ttl_seconds: Optional[int] = 3600, # 1 hour in seconds
|
|
29
|
-
health_check_interval: Optional[float] = 30.0,
|
|
30
|
-
socket_keepalive: bool = True,
|
|
31
|
-
):
|
|
32
|
-
"""
|
|
33
|
-
Initialize RedisStateService.
|
|
34
|
-
|
|
35
|
-
Args:
|
|
36
|
-
redis_url: Redis connection URL
|
|
37
|
-
redis_client: Optional pre-configured Redis client
|
|
38
|
-
socket_timeout: Socket timeout in seconds (default: 5.0)
|
|
39
|
-
socket_connect_timeout: Socket connect timeout in seconds
|
|
40
|
-
(default: 5.0)
|
|
41
|
-
max_connections: Maximum number of connections in the pool
|
|
42
|
-
(default: None)
|
|
43
|
-
retry_on_timeout: Whether to retry on timeout (default: True)
|
|
44
|
-
ttl_seconds: Time-to-live in seconds for state data. If None,
|
|
45
|
-
data never expires (default: 3600, i.e., 1 hour)
|
|
46
|
-
health_check_interval: Interval in seconds for health checks on
|
|
47
|
-
idle connections (default: 30.0).
|
|
48
|
-
Connections idle longer than this will be checked before reuse.
|
|
49
|
-
Set to 0 to disable.
|
|
50
|
-
socket_keepalive: Enable TCP keepalive to prevent
|
|
51
|
-
silent disconnections (default: True)
|
|
52
|
-
"""
|
|
53
|
-
self._redis_url = redis_url
|
|
54
|
-
self._redis = redis_client
|
|
55
|
-
self._socket_timeout = socket_timeout
|
|
56
|
-
self._socket_connect_timeout = socket_connect_timeout
|
|
57
|
-
self._max_connections = max_connections
|
|
58
|
-
self._retry_on_timeout = retry_on_timeout
|
|
59
|
-
self._ttl_seconds = ttl_seconds
|
|
60
|
-
self._health_check_interval = health_check_interval
|
|
61
|
-
self._socket_keepalive = socket_keepalive
|
|
62
|
-
|
|
63
|
-
async def start(self) -> None:
|
|
64
|
-
"""Starts the Redis connection with proper timeout and connection
|
|
65
|
-
pool settings."""
|
|
66
|
-
if self._redis is None:
|
|
67
|
-
self._redis = aioredis.from_url(
|
|
68
|
-
self._redis_url,
|
|
69
|
-
decode_responses=True,
|
|
70
|
-
socket_timeout=self._socket_timeout,
|
|
71
|
-
socket_connect_timeout=self._socket_connect_timeout,
|
|
72
|
-
max_connections=self._max_connections,
|
|
73
|
-
retry_on_timeout=self._retry_on_timeout,
|
|
74
|
-
health_check_interval=self._health_check_interval,
|
|
75
|
-
socket_keepalive=self._socket_keepalive,
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
async def stop(self) -> None:
|
|
79
|
-
"""Closes the Redis connection."""
|
|
80
|
-
if self._redis:
|
|
81
|
-
await self._redis.aclose()
|
|
82
|
-
self._redis = None
|
|
83
|
-
|
|
84
|
-
async def health(self) -> bool:
|
|
85
|
-
"""Checks the health of the service."""
|
|
86
|
-
if not self._redis:
|
|
87
|
-
return False
|
|
88
|
-
try:
|
|
89
|
-
pong = await self._redis.ping()
|
|
90
|
-
return pong is True or pong == "PONG"
|
|
91
|
-
except Exception:
|
|
92
|
-
return False
|
|
93
|
-
|
|
94
|
-
def _session_key(self, user_id: str, session_id: str) -> str:
|
|
95
|
-
"""Generate the Redis key for a user's session."""
|
|
96
|
-
return f"user_state:{user_id}:{session_id}"
|
|
97
|
-
|
|
98
|
-
async def save_state(
|
|
99
|
-
self,
|
|
100
|
-
user_id: str,
|
|
101
|
-
state: Dict[str, Any],
|
|
102
|
-
session_id: Optional[str] = None,
|
|
103
|
-
round_id: Optional[int] = None,
|
|
104
|
-
) -> int:
|
|
105
|
-
if not self._redis:
|
|
106
|
-
raise RuntimeError("Redis connection is not available")
|
|
107
|
-
|
|
108
|
-
sid = session_id or self._DEFAULT_SESSION_ID
|
|
109
|
-
key = self._session_key(user_id, sid)
|
|
110
|
-
|
|
111
|
-
existing_fields = await self._redis.hkeys(key)
|
|
112
|
-
existing_rounds = sorted(
|
|
113
|
-
int(f) for f in existing_fields if f.isdigit()
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
if round_id is None:
|
|
117
|
-
if existing_rounds:
|
|
118
|
-
round_id = max(existing_rounds) + 1
|
|
119
|
-
else:
|
|
120
|
-
round_id = 1
|
|
121
|
-
|
|
122
|
-
await self._redis.hset(key, round_id, json.dumps(state))
|
|
123
|
-
|
|
124
|
-
# Set TTL for the state key if configured
|
|
125
|
-
if self._ttl_seconds is not None:
|
|
126
|
-
await self._redis.expire(key, self._ttl_seconds)
|
|
127
|
-
|
|
128
|
-
return round_id
|
|
129
|
-
|
|
130
|
-
async def export_state(
|
|
131
|
-
self,
|
|
132
|
-
user_id: str,
|
|
133
|
-
session_id: Optional[str] = None,
|
|
134
|
-
round_id: Optional[int] = None,
|
|
135
|
-
) -> Optional[Dict[str, Any]]:
|
|
136
|
-
if not self._redis:
|
|
137
|
-
raise RuntimeError("Redis connection is not available")
|
|
138
|
-
|
|
139
|
-
sid = session_id or self._DEFAULT_SESSION_ID
|
|
140
|
-
key = self._session_key(user_id, sid)
|
|
141
|
-
|
|
142
|
-
existing_fields = await self._redis.hkeys(key)
|
|
143
|
-
if not existing_fields:
|
|
144
|
-
return None
|
|
145
|
-
|
|
146
|
-
if round_id is None:
|
|
147
|
-
numeric_fields = [int(f) for f in existing_fields if f.isdigit()]
|
|
148
|
-
if not numeric_fields:
|
|
149
|
-
return None
|
|
150
|
-
latest_round_id = max(numeric_fields)
|
|
151
|
-
state_json = await self._redis.hget(key, latest_round_id)
|
|
152
|
-
else:
|
|
153
|
-
state_json = await self._redis.hget(key, round_id)
|
|
154
|
-
|
|
155
|
-
if state_json is None:
|
|
156
|
-
return None
|
|
157
|
-
|
|
158
|
-
# Refresh TTL when accessing the state
|
|
159
|
-
if self._ttl_seconds is not None:
|
|
160
|
-
await self._redis.expire(key, self._ttl_seconds)
|
|
161
|
-
|
|
162
|
-
try:
|
|
163
|
-
return json.loads(state_json)
|
|
164
|
-
except json.JSONDecodeError:
|
|
165
|
-
# Return None for corrupted state data instead of raising exception
|
|
166
|
-
return None
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
import copy
|
|
3
|
-
|
|
4
|
-
from abc import abstractmethod
|
|
5
|
-
from typing import Dict, Any, Optional
|
|
6
|
-
|
|
7
|
-
from ..base import ServiceWithLifecycleManager
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class StateService(ServiceWithLifecycleManager):
|
|
11
|
-
"""
|
|
12
|
-
Abstract base class for agent state management services.
|
|
13
|
-
|
|
14
|
-
Stores and manages agent states organized by user_id, session_id,
|
|
15
|
-
and round_id. Supports saving, retrieving, listing, and deleting states.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
async def start(self) -> None:
|
|
19
|
-
pass
|
|
20
|
-
|
|
21
|
-
async def stop(self) -> None:
|
|
22
|
-
pass
|
|
23
|
-
|
|
24
|
-
@abstractmethod
|
|
25
|
-
async def save_state(
|
|
26
|
-
self,
|
|
27
|
-
user_id: str,
|
|
28
|
-
state: Dict[str, Any],
|
|
29
|
-
session_id: Optional[str] = None,
|
|
30
|
-
round_id: Optional[int] = None,
|
|
31
|
-
) -> int:
|
|
32
|
-
"""
|
|
33
|
-
Save serialized state data for a specific user/session.
|
|
34
|
-
|
|
35
|
-
If round_id is provided, store the state in that round.
|
|
36
|
-
If round_id is None, append as a new round with automatically
|
|
37
|
-
assigned round_id.
|
|
38
|
-
|
|
39
|
-
Args:
|
|
40
|
-
user_id: The unique ID of the user.
|
|
41
|
-
state: A dictionary representing serialized agent state.
|
|
42
|
-
session_id: Optional session/conversation ID. Defaults to
|
|
43
|
-
"default".
|
|
44
|
-
round_id: Optional conversation round number.
|
|
45
|
-
|
|
46
|
-
Returns:
|
|
47
|
-
The round_id in which the state was saved.
|
|
48
|
-
"""
|
|
49
|
-
|
|
50
|
-
@abstractmethod
|
|
51
|
-
async def export_state(
|
|
52
|
-
self,
|
|
53
|
-
user_id: str,
|
|
54
|
-
session_id: Optional[str] = None,
|
|
55
|
-
round_id: Optional[int] = None,
|
|
56
|
-
) -> Optional[Dict[str, Any]]:
|
|
57
|
-
"""
|
|
58
|
-
Retrieve serialized state data for a user/session.
|
|
59
|
-
|
|
60
|
-
If round_id is provided, return that round's state.
|
|
61
|
-
If round_id is None, return the latest round's state.
|
|
62
|
-
|
|
63
|
-
Args:
|
|
64
|
-
user_id: The unique ID of the user.
|
|
65
|
-
session_id: Optional session/conversation ID.
|
|
66
|
-
round_id: Optional round number.
|
|
67
|
-
|
|
68
|
-
Returns:
|
|
69
|
-
A dictionary representing the agent state, or None if not found.
|
|
70
|
-
"""
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
class InMemoryStateService(StateService):
|
|
74
|
-
"""
|
|
75
|
-
In-memory implementation of StateService using dictionaries
|
|
76
|
-
for sparse round storage.
|
|
77
|
-
|
|
78
|
-
- Multiple users, sessions, and non-contiguous round IDs are supported.
|
|
79
|
-
- If round_id is None when saving, a new round is appended automatically.
|
|
80
|
-
- If round_id is None when exporting, the latest round is returned.
|
|
81
|
-
"""
|
|
82
|
-
|
|
83
|
-
_DEFAULT_SESSION_ID = "default"
|
|
84
|
-
|
|
85
|
-
def __init__(self) -> None:
|
|
86
|
-
# Structure:
|
|
87
|
-
# { user_id: { session_id: { round_id: state_dict } } }
|
|
88
|
-
self._store: Optional[
|
|
89
|
-
Dict[str, Dict[str, Dict[int, Dict[str, Any]]]]
|
|
90
|
-
] = None
|
|
91
|
-
self._health = False
|
|
92
|
-
|
|
93
|
-
async def start(self) -> None:
|
|
94
|
-
"""Initialize the in-memory store."""
|
|
95
|
-
if self._store is None:
|
|
96
|
-
self._store = {}
|
|
97
|
-
self._health = True
|
|
98
|
-
|
|
99
|
-
async def stop(self) -> None:
|
|
100
|
-
"""Clear all in-memory state data."""
|
|
101
|
-
if self._store is not None:
|
|
102
|
-
self._store.clear()
|
|
103
|
-
self._store = None
|
|
104
|
-
self._health = False
|
|
105
|
-
|
|
106
|
-
async def health(self) -> bool:
|
|
107
|
-
"""Service health check."""
|
|
108
|
-
return self._health
|
|
109
|
-
|
|
110
|
-
async def save_state(
|
|
111
|
-
self,
|
|
112
|
-
user_id: str,
|
|
113
|
-
state: Dict[str, Any],
|
|
114
|
-
session_id: Optional[str] = None,
|
|
115
|
-
round_id: Optional[int] = None,
|
|
116
|
-
) -> int:
|
|
117
|
-
"""
|
|
118
|
-
Save serialized state in sparse dict storage.
|
|
119
|
-
|
|
120
|
-
If round_id is None, a new round_id will be assigned
|
|
121
|
-
as (max existing round_id + 1) or 1 if none exist.
|
|
122
|
-
Otherwise, the given round_id will be overwritten.
|
|
123
|
-
|
|
124
|
-
Returns:
|
|
125
|
-
The round_id where the state was saved.
|
|
126
|
-
"""
|
|
127
|
-
if self._store is None:
|
|
128
|
-
raise RuntimeError("Service not started")
|
|
129
|
-
|
|
130
|
-
sid = session_id or self._DEFAULT_SESSION_ID
|
|
131
|
-
|
|
132
|
-
self._store.setdefault(user_id, {})
|
|
133
|
-
self._store[user_id].setdefault(sid, {})
|
|
134
|
-
|
|
135
|
-
rounds_dict = self._store[user_id][sid]
|
|
136
|
-
|
|
137
|
-
# Auto-generate round_id if not provided
|
|
138
|
-
if round_id is None:
|
|
139
|
-
if rounds_dict:
|
|
140
|
-
round_id = max(rounds_dict.keys()) + 1
|
|
141
|
-
else:
|
|
142
|
-
round_id = 1
|
|
143
|
-
|
|
144
|
-
# Store a deep copy so caller modifications don't affect saved state
|
|
145
|
-
rounds_dict[round_id] = copy.deepcopy(state)
|
|
146
|
-
|
|
147
|
-
return round_id
|
|
148
|
-
|
|
149
|
-
async def export_state(
|
|
150
|
-
self,
|
|
151
|
-
user_id: str,
|
|
152
|
-
session_id: Optional[str] = None,
|
|
153
|
-
round_id: Optional[int] = None,
|
|
154
|
-
) -> Optional[Dict[str, Any]]:
|
|
155
|
-
"""
|
|
156
|
-
Retrieve state data for given user/session/round.
|
|
157
|
-
|
|
158
|
-
If round_id is None: return the latest round.
|
|
159
|
-
If round_id is provided: return that round's state.
|
|
160
|
-
|
|
161
|
-
Returns:
|
|
162
|
-
Dictionary representing the agent state, or None if not found.
|
|
163
|
-
"""
|
|
164
|
-
if self._store is None:
|
|
165
|
-
raise RuntimeError("Service not started")
|
|
166
|
-
|
|
167
|
-
sid = session_id or self._DEFAULT_SESSION_ID
|
|
168
|
-
sessions = self._store.get(user_id, {})
|
|
169
|
-
rounds_dict = sessions.get(sid, {})
|
|
170
|
-
|
|
171
|
-
if not rounds_dict:
|
|
172
|
-
return None
|
|
173
|
-
|
|
174
|
-
if round_id is None:
|
|
175
|
-
# Get the latest round_id
|
|
176
|
-
latest_round_id = max(rounds_dict.keys())
|
|
177
|
-
return rounds_dict[latest_round_id]
|
|
178
|
-
|
|
179
|
-
return rounds_dict.get(round_id)
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
|
|
3
|
-
from typing import Callable, Dict
|
|
4
|
-
|
|
5
|
-
from ..service_factory import ServiceFactory
|
|
6
|
-
from .state_service import StateService, InMemoryStateService
|
|
7
|
-
from .redis_state_service import RedisStateService
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class StateServiceFactory(ServiceFactory[StateService]):
|
|
11
|
-
"""
|
|
12
|
-
Factory for StateService, supports both environment variables and kwargs
|
|
13
|
-
parameters.
|
|
14
|
-
|
|
15
|
-
Usage examples:
|
|
16
|
-
1. Start with environment variables only:
|
|
17
|
-
export STATE_BACKEND=redis
|
|
18
|
-
export STATE_REDIS_REDIS_URL="redis://localhost:6379/5"
|
|
19
|
-
service = await StateServiceFactory.create()
|
|
20
|
-
|
|
21
|
-
2. Override environment variables with arguments:
|
|
22
|
-
export STATE_BACKEND=redis
|
|
23
|
-
export STATE_REDIS_REDIS_URL="redis://localhost:6379/5"
|
|
24
|
-
service = await StateServiceFactory.create(
|
|
25
|
-
redis_url="redis://otherhost:6379/1"
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
3. User-defined backend:
|
|
29
|
-
from my_backend import PostgresStateService
|
|
30
|
-
StateServiceFactory.register_backend(
|
|
31
|
-
"postgres",
|
|
32
|
-
PostgresStateService,
|
|
33
|
-
)
|
|
34
|
-
export STATE_BACKEND=postgres
|
|
35
|
-
export STATE_POSTGRES_DSN="postgresql://user:pass@localhost/db"
|
|
36
|
-
service = await StateServiceFactory.create()
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
_registry: Dict[str, Callable[..., StateService]] = {}
|
|
40
|
-
_env_prefix = "STATE_"
|
|
41
|
-
_default_backend = "in_memory"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
StateServiceFactory.register_backend(
|
|
45
|
-
"in_memory",
|
|
46
|
-
InMemoryStateService,
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
StateServiceFactory.register_backend(
|
|
50
|
-
"redis",
|
|
51
|
-
RedisStateService,
|
|
52
|
-
)
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
from typing import TYPE_CHECKING
|
|
3
|
-
from ....common.utils.lazy_loader import install_lazy_loader
|
|
4
|
-
from ....common.utils.deprecation import deprecated_module
|
|
5
|
-
|
|
6
|
-
deprecated_module(
|
|
7
|
-
module_name=__name__,
|
|
8
|
-
removed_in="v1.1",
|
|
9
|
-
alternative="agentscope.memory",
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from .memory_service import MemoryService, InMemoryMemoryService
|
|
14
|
-
from .redis_memory_service import RedisMemoryService
|
|
15
|
-
from .reme_task_memory_service import ReMeTaskMemoryService
|
|
16
|
-
from .reme_personal_memory_service import ReMePersonalMemoryService
|
|
17
|
-
from .mem0_memory_service import Mem0MemoryService
|
|
18
|
-
from .tablestore_memory_service import TablestoreMemoryService
|
|
19
|
-
from .memory_service_factory import MemoryServiceFactory
|
|
20
|
-
|
|
21
|
-
install_lazy_loader(
|
|
22
|
-
globals(),
|
|
23
|
-
{
|
|
24
|
-
"MemoryService": ".memory_service",
|
|
25
|
-
"InMemoryMemoryService": ".memory_service",
|
|
26
|
-
"RedisMemoryService": ".redis_memory_service",
|
|
27
|
-
"ReMeTaskMemoryService": ".reme_task_memory_service",
|
|
28
|
-
"ReMePersonalMemoryService": ".reme_personal_memory_service",
|
|
29
|
-
"Mem0MemoryService": ".mem0_memory_service",
|
|
30
|
-
"TablestoreMemoryService": ".tablestore_memory_service",
|
|
31
|
-
"MemoryServiceFactory": ".memory_service_factory",
|
|
32
|
-
},
|
|
33
|
-
)
|