agentscope-runtime 0.1.5b2__py3-none-any.whl → 0.2.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/common/__init__.py +0 -0
- agentscope_runtime/common/collections/in_memory_mapping.py +27 -0
- agentscope_runtime/common/collections/redis_mapping.py +42 -0
- agentscope_runtime/common/container_clients/__init__.py +0 -0
- agentscope_runtime/common/container_clients/agentrun_client.py +1098 -0
- agentscope_runtime/common/container_clients/docker_client.py +250 -0
- agentscope_runtime/{sandbox/manager → common}/container_clients/kubernetes_client.py +6 -13
- agentscope_runtime/engine/__init__.py +12 -0
- agentscope_runtime/engine/agents/agentscope_agent.py +567 -0
- agentscope_runtime/engine/agents/agno_agent.py +26 -27
- agentscope_runtime/engine/agents/autogen_agent.py +13 -8
- agentscope_runtime/engine/agents/langgraph_agent.py +52 -9
- agentscope_runtime/engine/agents/utils.py +53 -0
- agentscope_runtime/engine/app/__init__.py +6 -0
- agentscope_runtime/engine/app/agent_app.py +239 -0
- agentscope_runtime/engine/app/base_app.py +181 -0
- agentscope_runtime/engine/app/celery_mixin.py +92 -0
- agentscope_runtime/engine/deployers/adapter/responses/response_api_adapter_utils.py +5 -1
- agentscope_runtime/engine/deployers/base.py +1 -0
- agentscope_runtime/engine/deployers/cli_fc_deploy.py +39 -20
- agentscope_runtime/engine/deployers/kubernetes_deployer.py +12 -5
- agentscope_runtime/engine/deployers/local_deployer.py +61 -3
- agentscope_runtime/engine/deployers/modelstudio_deployer.py +201 -40
- agentscope_runtime/engine/deployers/utils/docker_image_utils/runner_image_factory.py +9 -0
- agentscope_runtime/engine/deployers/utils/package_project_utils.py +234 -3
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +567 -7
- agentscope_runtime/engine/deployers/utils/service_utils/standalone_main.py.j2 +211 -0
- agentscope_runtime/engine/deployers/utils/wheel_packager.py +1 -1
- agentscope_runtime/engine/helpers/helper.py +60 -41
- agentscope_runtime/engine/runner.py +40 -24
- agentscope_runtime/engine/schemas/agent_schemas.py +42 -0
- agentscope_runtime/engine/schemas/modelstudio_llm.py +14 -14
- agentscope_runtime/engine/services/sandbox_service.py +62 -70
- agentscope_runtime/engine/services/tablestore_memory_service.py +307 -0
- agentscope_runtime/engine/services/tablestore_rag_service.py +143 -0
- agentscope_runtime/engine/services/tablestore_session_history_service.py +293 -0
- agentscope_runtime/engine/services/utils/__init__.py +0 -0
- agentscope_runtime/engine/services/utils/tablestore_service_utils.py +352 -0
- agentscope_runtime/engine/tracing/__init__.py +9 -3
- agentscope_runtime/engine/tracing/asyncio_util.py +24 -0
- agentscope_runtime/engine/tracing/base.py +66 -34
- agentscope_runtime/engine/tracing/local_logging_handler.py +45 -31
- agentscope_runtime/engine/tracing/message_util.py +528 -0
- agentscope_runtime/engine/tracing/tracing_metric.py +20 -8
- agentscope_runtime/engine/tracing/tracing_util.py +130 -0
- agentscope_runtime/engine/tracing/wrapper.py +794 -169
- agentscope_runtime/sandbox/__init__.py +2 -0
- agentscope_runtime/sandbox/box/base/__init__.py +4 -0
- agentscope_runtime/sandbox/box/base/base_sandbox.py +6 -4
- agentscope_runtime/sandbox/box/browser/__init__.py +4 -0
- agentscope_runtime/sandbox/box/browser/browser_sandbox.py +10 -14
- agentscope_runtime/sandbox/box/dummy/__init__.py +4 -0
- agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +2 -1
- agentscope_runtime/sandbox/box/filesystem/__init__.py +4 -0
- agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +10 -7
- agentscope_runtime/sandbox/box/gui/__init__.py +4 -0
- agentscope_runtime/sandbox/box/gui/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/gui/gui_sandbox.py +81 -0
- agentscope_runtime/sandbox/box/sandbox.py +5 -2
- agentscope_runtime/sandbox/box/shared/routers/generic.py +20 -1
- agentscope_runtime/sandbox/box/training_box/__init__.py +4 -0
- agentscope_runtime/sandbox/box/training_box/training_box.py +7 -54
- agentscope_runtime/sandbox/build.py +143 -58
- agentscope_runtime/sandbox/client/http_client.py +87 -59
- agentscope_runtime/sandbox/client/training_client.py +0 -1
- agentscope_runtime/sandbox/constant.py +27 -1
- agentscope_runtime/sandbox/custom/custom_sandbox.py +7 -6
- agentscope_runtime/sandbox/custom/example.py +4 -3
- agentscope_runtime/sandbox/enums.py +1 -1
- agentscope_runtime/sandbox/manager/sandbox_manager.py +212 -106
- agentscope_runtime/sandbox/manager/server/app.py +82 -14
- agentscope_runtime/sandbox/manager/server/config.py +50 -3
- agentscope_runtime/sandbox/model/container.py +12 -23
- agentscope_runtime/sandbox/model/manager_config.py +93 -5
- agentscope_runtime/sandbox/registry.py +1 -1
- agentscope_runtime/sandbox/tools/gui/__init__.py +7 -0
- agentscope_runtime/sandbox/tools/gui/tool.py +77 -0
- agentscope_runtime/sandbox/tools/mcp_tool.py +6 -2
- agentscope_runtime/sandbox/tools/tool.py +4 -0
- agentscope_runtime/sandbox/utils.py +124 -0
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0.dist-info}/METADATA +246 -111
- {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0.dist-info}/RECORD +96 -80
- agentscope_runtime/engine/agents/agentscope_agent/__init__.py +0 -6
- agentscope_runtime/engine/agents/agentscope_agent/agent.py +0 -401
- agentscope_runtime/engine/agents/agentscope_agent/hooks.py +0 -169
- agentscope_runtime/engine/agents/llm_agent.py +0 -51
- agentscope_runtime/engine/llms/__init__.py +0 -3
- agentscope_runtime/engine/llms/base_llm.py +0 -60
- agentscope_runtime/engine/llms/qwen_llm.py +0 -47
- agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +0 -22
- agentscope_runtime/sandbox/manager/collections/redis_mapping.py +0 -26
- agentscope_runtime/sandbox/manager/container_clients/__init__.py +0 -10
- agentscope_runtime/sandbox/manager/container_clients/docker_client.py +0 -422
- /agentscope_runtime/{sandbox/manager → common}/collections/__init__.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/base_mapping.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/base_queue.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/base_set.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_queue.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_set.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/redis_queue.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/redis_set.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/container_clients/base_client.py +0 -0
- {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0.dist-info}/WHEEL +0 -0
- {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0.dist-info}/entry_points.txt +0 -0
- {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -2,27 +2,17 @@
|
|
|
2
2
|
# pylint: disable=too-many-branches
|
|
3
3
|
from typing import List
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from ...sandbox.tools.function_tool import FunctionTool
|
|
12
|
-
except ImportError:
|
|
13
|
-
pass
|
|
14
|
-
|
|
5
|
+
from ...sandbox.enums import SandboxType
|
|
6
|
+
from ...sandbox.manager import SandboxManager
|
|
7
|
+
from ...sandbox.registry import SandboxRegistry
|
|
8
|
+
from ...sandbox.tools.mcp_tool import MCPTool
|
|
9
|
+
from ...sandbox.tools.sandbox_tool import SandboxTool
|
|
10
|
+
from ...sandbox.tools.function_tool import FunctionTool
|
|
15
11
|
from ...engine.services.base import ServiceWithLifecycleManager
|
|
16
12
|
|
|
17
13
|
|
|
18
14
|
class SandboxService(ServiceWithLifecycleManager):
|
|
19
15
|
def __init__(self, base_url=None, bearer_token=None):
|
|
20
|
-
if SandboxManager is None:
|
|
21
|
-
raise ImportError(
|
|
22
|
-
"SandboxManager is not available. "
|
|
23
|
-
"Please install agentscope-runtime[sandbox]",
|
|
24
|
-
)
|
|
25
|
-
|
|
26
16
|
self.manager_api = SandboxManager(
|
|
27
17
|
base_url=base_url,
|
|
28
18
|
bearer_token=bearer_token,
|
|
@@ -30,18 +20,20 @@ class SandboxService(ServiceWithLifecycleManager):
|
|
|
30
20
|
|
|
31
21
|
self.base_url = base_url
|
|
32
22
|
self.bearer_token = bearer_token
|
|
33
|
-
self.sandbox_type_set = set(item.value for item in SandboxType)
|
|
34
|
-
|
|
35
|
-
self.session_mapping = {}
|
|
36
23
|
|
|
37
24
|
async def start(self) -> None:
|
|
38
25
|
pass
|
|
39
26
|
|
|
40
27
|
async def stop(self) -> None:
|
|
41
28
|
# Release all environments
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
29
|
+
session_keys = self.manager_api.list_session_keys()
|
|
30
|
+
|
|
31
|
+
if session_keys:
|
|
32
|
+
for session_ctx_id in session_keys:
|
|
33
|
+
env_ids = self.manager_api.get_session_mapping(session_ctx_id)
|
|
34
|
+
if env_ids:
|
|
35
|
+
for env_id in env_ids:
|
|
36
|
+
self.manager_api.release(env_id)
|
|
45
37
|
|
|
46
38
|
if self.base_url is None:
|
|
47
39
|
# Embedded mode
|
|
@@ -57,61 +49,52 @@ class SandboxService(ServiceWithLifecycleManager):
|
|
|
57
49
|
env_types=None,
|
|
58
50
|
tools=None,
|
|
59
51
|
) -> List:
|
|
60
|
-
for tool in tools:
|
|
61
|
-
if not isinstance(tool, (SandboxTool, FunctionTool, MCPTool)):
|
|
62
|
-
raise ValueError(
|
|
63
|
-
"tools must be instances of SandboxTool, FunctionTool, "
|
|
64
|
-
"or MCPTool",
|
|
65
|
-
)
|
|
66
|
-
|
|
67
52
|
# Create a composite key
|
|
68
|
-
|
|
53
|
+
session_ctx_id = self._create_session_ctx_id(session_id, user_id)
|
|
69
54
|
|
|
70
|
-
|
|
71
|
-
|
|
55
|
+
env_ids = self.manager_api.get_session_mapping(session_ctx_id)
|
|
56
|
+
|
|
57
|
+
# Check if the session_ctx_id already has an environment
|
|
58
|
+
if env_ids:
|
|
72
59
|
# Connect to existing environment
|
|
73
|
-
return self._connect_existing_environment(
|
|
60
|
+
return self._connect_existing_environment(env_ids)
|
|
74
61
|
else:
|
|
75
62
|
# Create a new environment
|
|
76
63
|
return self._create_new_environment(
|
|
77
|
-
|
|
64
|
+
session_ctx_id,
|
|
78
65
|
env_types,
|
|
79
66
|
tools,
|
|
80
67
|
)
|
|
81
68
|
|
|
82
69
|
def _create_new_environment(
|
|
83
70
|
self,
|
|
84
|
-
|
|
71
|
+
session_ctx_id: str,
|
|
85
72
|
env_types=None,
|
|
86
73
|
tools=None,
|
|
87
74
|
):
|
|
75
|
+
if tools:
|
|
76
|
+
for tool in tools:
|
|
77
|
+
if not isinstance(tool, (SandboxTool, FunctionTool, MCPTool)):
|
|
78
|
+
raise ValueError(
|
|
79
|
+
"tools must be instances of SandboxTool, "
|
|
80
|
+
"FunctionTool, or MCPTool",
|
|
81
|
+
)
|
|
82
|
+
|
|
88
83
|
if env_types is None:
|
|
89
84
|
assert (
|
|
90
85
|
tools is not None
|
|
91
86
|
), "tools must be specified when env_types is not set"
|
|
92
87
|
|
|
93
|
-
server_configs = None
|
|
94
88
|
if tools:
|
|
95
|
-
server_config_list = []
|
|
96
89
|
tool_env_types = set()
|
|
97
90
|
for tool in tools:
|
|
98
91
|
tool_env_types.add(tool.sandbox_type)
|
|
99
|
-
if isinstance(tool, MCPTool):
|
|
100
|
-
server_config_list.append(tool.server_configs)
|
|
101
|
-
|
|
102
92
|
if env_types is None:
|
|
103
93
|
env_types = []
|
|
104
94
|
|
|
105
|
-
if server_config_list:
|
|
106
|
-
server_configs = {"mcpServers": {}}
|
|
107
|
-
for server_config in server_config_list:
|
|
108
|
-
server_configs["mcpServers"].update(
|
|
109
|
-
server_config["mcpServers"],
|
|
110
|
-
)
|
|
111
|
-
|
|
112
95
|
env_types = set(env_types) | tool_env_types
|
|
113
96
|
|
|
114
|
-
|
|
97
|
+
sandboxes = []
|
|
115
98
|
for env_type in env_types:
|
|
116
99
|
if env_type is None:
|
|
117
100
|
continue
|
|
@@ -120,6 +103,7 @@ class SandboxService(ServiceWithLifecycleManager):
|
|
|
120
103
|
|
|
121
104
|
box_id = self.manager_api.create_from_pool(
|
|
122
105
|
sandbox_type=box_type.value,
|
|
106
|
+
meta={"session_ctx_id": session_ctx_id},
|
|
123
107
|
)
|
|
124
108
|
box_cls = SandboxRegistry.get_classes_by_type(box_type)
|
|
125
109
|
|
|
@@ -135,22 +119,30 @@ class SandboxService(ServiceWithLifecycleManager):
|
|
|
135
119
|
# Embedded mode
|
|
136
120
|
box.manager_api = self.manager_api
|
|
137
121
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
122
|
+
# Add MCP to the sandbox
|
|
123
|
+
server_config_list = []
|
|
124
|
+
if tools:
|
|
125
|
+
for tool in tools:
|
|
126
|
+
if isinstance(tool, MCPTool) and SandboxType(
|
|
127
|
+
tool.sandbox_type,
|
|
128
|
+
) == SandboxType(box_type):
|
|
129
|
+
server_config_list.append(tool.server_configs)
|
|
130
|
+
if server_config_list:
|
|
131
|
+
server_configs = {"mcpServers": {}}
|
|
132
|
+
for server_config in server_config_list:
|
|
133
|
+
if (
|
|
134
|
+
server_config is not None
|
|
135
|
+
and "mcpServers" in server_config
|
|
136
|
+
):
|
|
137
|
+
server_configs["mcpServers"].update(
|
|
138
|
+
server_config["mcpServers"],
|
|
139
|
+
)
|
|
140
|
+
box.add_mcp_servers(server_configs, overwrite=False)
|
|
141
|
+
|
|
142
|
+
sandboxes.append(box)
|
|
143
|
+
return sandboxes
|
|
144
|
+
|
|
145
|
+
def _connect_existing_environment(self, env_ids: List[str]):
|
|
154
146
|
boxes = []
|
|
155
147
|
for env_id in env_ids:
|
|
156
148
|
info = self.manager_api.get_info(env_id)
|
|
@@ -183,16 +175,16 @@ class SandboxService(ServiceWithLifecycleManager):
|
|
|
183
175
|
return boxes
|
|
184
176
|
|
|
185
177
|
def release(self, session_id, user_id):
|
|
186
|
-
|
|
178
|
+
session_ctx_id = self._create_session_ctx_id(session_id, user_id)
|
|
187
179
|
|
|
188
|
-
|
|
189
|
-
env_ids = self.session_mapping.pop(composite_key, [])
|
|
180
|
+
env_ids = self.manager_api.get_session_mapping(session_ctx_id)
|
|
190
181
|
|
|
191
|
-
|
|
192
|
-
|
|
182
|
+
if env_ids:
|
|
183
|
+
for env_id in env_ids:
|
|
184
|
+
self.manager_api.release(env_id)
|
|
193
185
|
|
|
194
186
|
return True
|
|
195
187
|
|
|
196
|
-
def
|
|
188
|
+
def _create_session_ctx_id(self, session_id, user_id):
|
|
197
189
|
# Create a composite key from session_id and user_id
|
|
198
190
|
return f"{session_id}_{user_id}"
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# pylint: disable=redefined-outer-name
|
|
3
|
+
import asyncio
|
|
4
|
+
import copy
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import tablestore
|
|
10
|
+
from langchain_community.embeddings import DashScopeEmbeddings
|
|
11
|
+
from langchain_core.embeddings import Embeddings
|
|
12
|
+
from tablestore import AsyncOTSClient as AsyncTablestoreClient
|
|
13
|
+
from tablestore import VectorMetricType
|
|
14
|
+
from tablestore_for_agent_memory.base.filter import Filters
|
|
15
|
+
from tablestore_for_agent_memory.knowledge.async_knowledge_store import (
|
|
16
|
+
AsyncKnowledgeStore,
|
|
17
|
+
)
|
|
18
|
+
except ImportError as e:
|
|
19
|
+
raise ImportError(
|
|
20
|
+
"aliyun_tablestore is not available. "
|
|
21
|
+
"Please run pip install agentscope-runtime[aliyun_tablestore_ext]",
|
|
22
|
+
) from e
|
|
23
|
+
|
|
24
|
+
from ..schemas.agent_schemas import Message, MessageType
|
|
25
|
+
from .memory_service import MemoryService
|
|
26
|
+
from .utils.tablestore_service_utils import (
|
|
27
|
+
convert_messages_to_tablestore_documents,
|
|
28
|
+
convert_tablestore_document_to_message,
|
|
29
|
+
get_message_metadata_names,
|
|
30
|
+
tablestore_log,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SearchStrategy(Enum):
|
|
35
|
+
FULL_TEXT = "full_text"
|
|
36
|
+
VECTOR = "vector"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TablestoreMemoryService(MemoryService):
|
|
40
|
+
"""
|
|
41
|
+
A Tablestore-based implementation of the memory service.
|
|
42
|
+
based on tablestore_for_agent_memory
|
|
43
|
+
(https://github.com/aliyun/
|
|
44
|
+
alibabacloud-tablestore-for-agent-memory/blob/main/python/docs/knowledge_store_tutorial.ipynb).
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
_SEARCH_INDEX_NAME = "agentscope_runtime_knowledge_search_index_name"
|
|
48
|
+
_DEFAULT_SESSION_ID = "default"
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
tablestore_client: AsyncTablestoreClient,
|
|
53
|
+
search_strategy: SearchStrategy = SearchStrategy.FULL_TEXT,
|
|
54
|
+
embedding_model: Optional[Embeddings] = None,
|
|
55
|
+
vector_dimension: int = 1536,
|
|
56
|
+
table_name: Optional[str] = "agentscope_runtime_memory",
|
|
57
|
+
search_index_schema: Optional[List[tablestore.FieldSchema]] = (
|
|
58
|
+
tablestore.FieldSchema("user_id", tablestore.FieldType.KEYWORD),
|
|
59
|
+
tablestore.FieldSchema("session_id", tablestore.FieldType.KEYWORD),
|
|
60
|
+
),
|
|
61
|
+
text_field: Optional[str] = "text",
|
|
62
|
+
embedding_field: Optional[str] = "embedding",
|
|
63
|
+
vector_metric_type: VectorMetricType = VectorMetricType.VM_COSINE,
|
|
64
|
+
**kwargs: Any,
|
|
65
|
+
):
|
|
66
|
+
if embedding_model is None:
|
|
67
|
+
embedding_model = DashScopeEmbeddings()
|
|
68
|
+
|
|
69
|
+
self._search_strategy = search_strategy
|
|
70
|
+
self._embedding_model = (
|
|
71
|
+
embedding_model # the parameter is None, don't store vector.
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
self._search_strategy == SearchStrategy.VECTOR
|
|
76
|
+
and self._embedding_model is None
|
|
77
|
+
):
|
|
78
|
+
raise ValueError(
|
|
79
|
+
"Embedding model is required when search strategy is VECTOR.",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
self._tablestore_client = tablestore_client
|
|
83
|
+
self._vector_dimension = vector_dimension
|
|
84
|
+
self._table_name = table_name
|
|
85
|
+
self._search_index_schema = (
|
|
86
|
+
list(search_index_schema)
|
|
87
|
+
if search_index_schema is not None
|
|
88
|
+
else None
|
|
89
|
+
)
|
|
90
|
+
self._text_field = text_field
|
|
91
|
+
self._embedding_field = embedding_field
|
|
92
|
+
self._vector_metric_type = vector_metric_type
|
|
93
|
+
self._knowledge_store: Optional[AsyncKnowledgeStore] = None
|
|
94
|
+
self._knowledge_store_init_parameter_kwargs = kwargs
|
|
95
|
+
|
|
96
|
+
async def _init_knowledge_store(self) -> None:
|
|
97
|
+
self._knowledge_store = AsyncKnowledgeStore(
|
|
98
|
+
tablestore_client=self._tablestore_client,
|
|
99
|
+
vector_dimension=self._vector_dimension,
|
|
100
|
+
enable_multi_tenant=False,
|
|
101
|
+
# enable multi tenant will make user be confused,
|
|
102
|
+
# we unify the usage of session id and user id,
|
|
103
|
+
# and allow users to configure the index themselves.
|
|
104
|
+
table_name=self._table_name,
|
|
105
|
+
search_index_name=TablestoreMemoryService._SEARCH_INDEX_NAME,
|
|
106
|
+
search_index_schema=copy.deepcopy(self._search_index_schema),
|
|
107
|
+
text_field=self._text_field,
|
|
108
|
+
embedding_field=self._embedding_field,
|
|
109
|
+
vector_metric_type=self._vector_metric_type,
|
|
110
|
+
**self._knowledge_store_init_parameter_kwargs,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
await self._knowledge_store.init_table()
|
|
114
|
+
|
|
115
|
+
async def start(self) -> None:
|
|
116
|
+
"""Start the tablestore service"""
|
|
117
|
+
if self._knowledge_store:
|
|
118
|
+
return
|
|
119
|
+
await self._init_knowledge_store()
|
|
120
|
+
|
|
121
|
+
async def stop(self) -> None:
|
|
122
|
+
"""Close the tablestore service"""
|
|
123
|
+
if self._knowledge_store is None:
|
|
124
|
+
return
|
|
125
|
+
knowledge_store = self._knowledge_store
|
|
126
|
+
self._knowledge_store = None
|
|
127
|
+
await knowledge_store.close()
|
|
128
|
+
|
|
129
|
+
async def health(self) -> bool:
|
|
130
|
+
"""Checks the health of the service."""
|
|
131
|
+
if self._knowledge_store is None:
|
|
132
|
+
tablestore_log("Tablestore memory service is not started.")
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
async for _ in await self._knowledge_store.get_all_documents():
|
|
137
|
+
return True
|
|
138
|
+
return True
|
|
139
|
+
except Exception as e:
|
|
140
|
+
tablestore_log(
|
|
141
|
+
f"Tablestore memory service "
|
|
142
|
+
f"cannot access Tablestore, error: {str(e)}.",
|
|
143
|
+
)
|
|
144
|
+
return False
|
|
145
|
+
|
|
146
|
+
async def add_memory(
|
|
147
|
+
self,
|
|
148
|
+
user_id: str,
|
|
149
|
+
messages: list,
|
|
150
|
+
session_id: Optional[str] = None,
|
|
151
|
+
) -> None:
|
|
152
|
+
if not session_id:
|
|
153
|
+
session_id = TablestoreMemoryService._DEFAULT_SESSION_ID
|
|
154
|
+
|
|
155
|
+
if not messages:
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
tablestore_documents = convert_messages_to_tablestore_documents(
|
|
159
|
+
messages,
|
|
160
|
+
user_id,
|
|
161
|
+
session_id,
|
|
162
|
+
self._embedding_model,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
put_tasks = [
|
|
166
|
+
self._knowledge_store.put_document(tablestore_document)
|
|
167
|
+
for tablestore_document in tablestore_documents
|
|
168
|
+
]
|
|
169
|
+
await asyncio.gather(*put_tasks)
|
|
170
|
+
|
|
171
|
+
@staticmethod
|
|
172
|
+
async def get_query_text(message: Message) -> str:
|
|
173
|
+
if not message or message.type != MessageType.MESSAGE:
|
|
174
|
+
return ""
|
|
175
|
+
|
|
176
|
+
for content in message.content:
|
|
177
|
+
if content.type == "text":
|
|
178
|
+
return content.text
|
|
179
|
+
|
|
180
|
+
return ""
|
|
181
|
+
|
|
182
|
+
async def search_memory(
|
|
183
|
+
self,
|
|
184
|
+
user_id: str,
|
|
185
|
+
messages: list,
|
|
186
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
187
|
+
) -> list:
|
|
188
|
+
if (
|
|
189
|
+
not messages
|
|
190
|
+
or not isinstance(messages, list)
|
|
191
|
+
or len(messages) == 0
|
|
192
|
+
):
|
|
193
|
+
return []
|
|
194
|
+
|
|
195
|
+
query = await TablestoreMemoryService.get_query_text(messages[-1])
|
|
196
|
+
if not query:
|
|
197
|
+
return []
|
|
198
|
+
|
|
199
|
+
top_k = 100
|
|
200
|
+
if (
|
|
201
|
+
filters
|
|
202
|
+
and "top_k" in filters
|
|
203
|
+
and isinstance(filters["top_k"], int)
|
|
204
|
+
):
|
|
205
|
+
top_k = filters["top_k"]
|
|
206
|
+
|
|
207
|
+
if self._search_strategy == SearchStrategy.FULL_TEXT:
|
|
208
|
+
matched_messages = [
|
|
209
|
+
convert_tablestore_document_to_message(hit.document)
|
|
210
|
+
for hit in (
|
|
211
|
+
await self._knowledge_store.full_text_search(
|
|
212
|
+
query=query,
|
|
213
|
+
metadata_filter=Filters.eq("user_id", user_id),
|
|
214
|
+
limit=top_k,
|
|
215
|
+
meta_data_to_get=get_message_metadata_names(),
|
|
216
|
+
)
|
|
217
|
+
).hits
|
|
218
|
+
]
|
|
219
|
+
elif self._search_strategy == SearchStrategy.VECTOR:
|
|
220
|
+
matched_messages = [
|
|
221
|
+
convert_tablestore_document_to_message(hit.document)
|
|
222
|
+
for hit in (
|
|
223
|
+
await self._knowledge_store.vector_search(
|
|
224
|
+
query_vector=self._embedding_model.embed_query(query),
|
|
225
|
+
metadata_filter=Filters.eq("user_id", user_id),
|
|
226
|
+
top_k=top_k,
|
|
227
|
+
meta_data_to_get=get_message_metadata_names(),
|
|
228
|
+
)
|
|
229
|
+
).hits
|
|
230
|
+
]
|
|
231
|
+
else:
|
|
232
|
+
raise ValueError(
|
|
233
|
+
f"Unsupported search strategy: {self._search_strategy}",
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
return matched_messages
|
|
237
|
+
|
|
238
|
+
async def list_memory(
|
|
239
|
+
self,
|
|
240
|
+
user_id: str,
|
|
241
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
242
|
+
) -> list:
|
|
243
|
+
page_num = filters.get("page_num", 1) if filters else 1
|
|
244
|
+
page_size = filters.get("page_size", 10) if filters else 10
|
|
245
|
+
|
|
246
|
+
if page_num < 1 or page_size < 1:
|
|
247
|
+
raise ValueError("page_num and page_size must be greater than 0.")
|
|
248
|
+
|
|
249
|
+
next_token = None
|
|
250
|
+
for _ in range(page_num - 1):
|
|
251
|
+
next_token = (
|
|
252
|
+
await self._knowledge_store.search_documents(
|
|
253
|
+
metadata_filter=Filters.eq("user_id", user_id),
|
|
254
|
+
limit=page_size,
|
|
255
|
+
next_token=next_token,
|
|
256
|
+
)
|
|
257
|
+
).next_token
|
|
258
|
+
if not next_token:
|
|
259
|
+
tablestore_log(
|
|
260
|
+
"Page number exceeds the total number of pages, "
|
|
261
|
+
"return empty list.",
|
|
262
|
+
)
|
|
263
|
+
return []
|
|
264
|
+
|
|
265
|
+
messages = [
|
|
266
|
+
convert_tablestore_document_to_message(hit.document)
|
|
267
|
+
for hit in (
|
|
268
|
+
await self._knowledge_store.search_documents(
|
|
269
|
+
metadata_filter=Filters.eq("user_id", user_id),
|
|
270
|
+
limit=page_size,
|
|
271
|
+
next_token=next_token,
|
|
272
|
+
meta_data_to_get=get_message_metadata_names(),
|
|
273
|
+
)
|
|
274
|
+
).hits
|
|
275
|
+
]
|
|
276
|
+
|
|
277
|
+
return messages
|
|
278
|
+
|
|
279
|
+
async def delete_memory(
|
|
280
|
+
self,
|
|
281
|
+
user_id: str,
|
|
282
|
+
session_id: Optional[str] = None,
|
|
283
|
+
) -> None:
|
|
284
|
+
delete_tablestore_documents = [
|
|
285
|
+
hit.document
|
|
286
|
+
for hit in (
|
|
287
|
+
await self._knowledge_store.search_documents(
|
|
288
|
+
metadata_filter=(
|
|
289
|
+
Filters.eq("user_id", user_id)
|
|
290
|
+
if not session_id
|
|
291
|
+
else Filters.logical_and(
|
|
292
|
+
[
|
|
293
|
+
Filters.eq("user_id", user_id),
|
|
294
|
+
Filters.eq("session_id", session_id),
|
|
295
|
+
],
|
|
296
|
+
)
|
|
297
|
+
),
|
|
298
|
+
)
|
|
299
|
+
).hits
|
|
300
|
+
]
|
|
301
|
+
delete_tasks = [
|
|
302
|
+
self._knowledge_store.delete_document(
|
|
303
|
+
tablestore_document.document_id,
|
|
304
|
+
)
|
|
305
|
+
for tablestore_document in delete_tablestore_documents
|
|
306
|
+
]
|
|
307
|
+
await asyncio.gather(*delete_tasks)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# pylint: disable=redefined-outer-name
|
|
3
|
+
import asyncio
|
|
4
|
+
import uuid
|
|
5
|
+
from typing import Any, List, Optional, Union
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from langchain_community.embeddings import DashScopeEmbeddings
|
|
9
|
+
from langchain_core.documents import Document
|
|
10
|
+
from langchain_core.embeddings import Embeddings
|
|
11
|
+
from tablestore import AsyncOTSClient as AsyncTablestoreClient
|
|
12
|
+
from tablestore import VectorMetricType
|
|
13
|
+
from tablestore_for_agent_memory.base.base_knowledge_store import (
|
|
14
|
+
Document as TablestoreDocument,
|
|
15
|
+
)
|
|
16
|
+
from tablestore_for_agent_memory.knowledge.async_knowledge_store import (
|
|
17
|
+
AsyncKnowledgeStore,
|
|
18
|
+
)
|
|
19
|
+
except ImportError as e:
|
|
20
|
+
raise ImportError(
|
|
21
|
+
"aliyun_tablestore is not available. "
|
|
22
|
+
"Please run pip install agentscope-runtime[aliyun_tablestore_ext]",
|
|
23
|
+
) from e
|
|
24
|
+
|
|
25
|
+
from .rag_service import RAGService
|
|
26
|
+
from .utils.tablestore_service_utils import tablestore_log
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TablestoreRAGService(RAGService):
|
|
30
|
+
"""
|
|
31
|
+
RAG Service using Tablestore(aliyun tablestore)
|
|
32
|
+
based on tablestore_for_agent_memory
|
|
33
|
+
(https://github.com/aliyun/
|
|
34
|
+
alibabacloud-tablestore-for-agent-memory/blob/main/python/docs/knowledge_store_tutorial.ipynb).
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
_SEARCH_INDEX_NAME = "agentscope_runtime_knowledge_search_index_name"
|
|
38
|
+
_DEFAULT_SESSION_ID = "default"
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
tablestore_client: AsyncTablestoreClient,
|
|
43
|
+
embedding_model: Optional[Embeddings] = None,
|
|
44
|
+
vector_dimension: int = 1536,
|
|
45
|
+
table_name: Optional[str] = "agentscope_runtime_rag",
|
|
46
|
+
text_field: Optional[str] = "text",
|
|
47
|
+
embedding_field: Optional[str] = "embedding",
|
|
48
|
+
vector_metric_type: VectorMetricType = VectorMetricType.VM_COSINE,
|
|
49
|
+
**kwargs: Any,
|
|
50
|
+
):
|
|
51
|
+
self._embedding_model = (
|
|
52
|
+
embedding_model if embedding_model else DashScopeEmbeddings()
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
self._tablestore_client = tablestore_client
|
|
56
|
+
self._vector_dimension = vector_dimension
|
|
57
|
+
self._table_name = table_name
|
|
58
|
+
self._text_field = text_field
|
|
59
|
+
self._embedding_field = embedding_field
|
|
60
|
+
self._vector_metric_type = vector_metric_type
|
|
61
|
+
self._knowledge_store: Optional[AsyncKnowledgeStore] = None
|
|
62
|
+
self._knowledge_store_init_parameter_kwargs = kwargs
|
|
63
|
+
|
|
64
|
+
async def _init_knowledge_store(self) -> None:
|
|
65
|
+
self._knowledge_store = AsyncKnowledgeStore(
|
|
66
|
+
tablestore_client=self._tablestore_client,
|
|
67
|
+
vector_dimension=self._vector_dimension,
|
|
68
|
+
enable_multi_tenant=False,
|
|
69
|
+
table_name=self._table_name,
|
|
70
|
+
search_index_name=TablestoreRAGService._SEARCH_INDEX_NAME,
|
|
71
|
+
text_field=self._text_field,
|
|
72
|
+
embedding_field=self._embedding_field,
|
|
73
|
+
vector_metric_type=self._vector_metric_type,
|
|
74
|
+
**self._knowledge_store_init_parameter_kwargs,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
await self._knowledge_store.init_table()
|
|
78
|
+
|
|
79
|
+
async def start(self) -> None:
|
|
80
|
+
"""Start the tablestore service"""
|
|
81
|
+
if self._knowledge_store:
|
|
82
|
+
return
|
|
83
|
+
await self._init_knowledge_store()
|
|
84
|
+
|
|
85
|
+
async def stop(self) -> None:
|
|
86
|
+
"""Close the tablestore service"""
|
|
87
|
+
if self._knowledge_store is None:
|
|
88
|
+
return
|
|
89
|
+
knowledge_store = self._knowledge_store
|
|
90
|
+
self._knowledge_store = None
|
|
91
|
+
await knowledge_store.close()
|
|
92
|
+
|
|
93
|
+
async def health(self) -> bool:
|
|
94
|
+
"""Checks the health of the service."""
|
|
95
|
+
if self._knowledge_store is None:
|
|
96
|
+
tablestore_log("Tablestore rag service is not started.")
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
async for _ in await self._knowledge_store.get_all_documents():
|
|
101
|
+
return True
|
|
102
|
+
return True
|
|
103
|
+
except Exception as e:
|
|
104
|
+
tablestore_log(
|
|
105
|
+
f"Tablestore rag service "
|
|
106
|
+
f"cannot access Tablestore, error: {str(e)}.",
|
|
107
|
+
)
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
async def add_docs(self, docs: Union[Document, List[Document]]):
|
|
111
|
+
if not isinstance(docs, List):
|
|
112
|
+
docs = [docs]
|
|
113
|
+
|
|
114
|
+
contents = [doc.page_content for doc in docs]
|
|
115
|
+
# Encode in batches to reduce time consumption.
|
|
116
|
+
embeddings = self._embedding_model.embed_documents(contents)
|
|
117
|
+
|
|
118
|
+
put_tasks = [
|
|
119
|
+
# The conversion logic here is simple,
|
|
120
|
+
# so no separate conversion function is defined.
|
|
121
|
+
self._knowledge_store.put_document(
|
|
122
|
+
document=TablestoreDocument(
|
|
123
|
+
document_id=f"document_{uuid.uuid4()}",
|
|
124
|
+
text=doc.page_content,
|
|
125
|
+
embedding=embedding,
|
|
126
|
+
metadata=doc.metadata,
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
for doc, embedding in zip(docs, embeddings)
|
|
130
|
+
]
|
|
131
|
+
await asyncio.gather(*put_tasks)
|
|
132
|
+
|
|
133
|
+
async def retrieve(self, query: str, k: int = 1) -> list[str]:
|
|
134
|
+
matched_text = [
|
|
135
|
+
hit.document.text
|
|
136
|
+
for hit in (
|
|
137
|
+
await self._knowledge_store.vector_search(
|
|
138
|
+
query_vector=self._embedding_model.embed_query(query),
|
|
139
|
+
top_k=k,
|
|
140
|
+
)
|
|
141
|
+
).hits
|
|
142
|
+
]
|
|
143
|
+
return matched_text
|