agentscope-runtime 0.1.5b2__py3-none-any.whl → 0.2.0b2__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 (105) hide show
  1. agentscope_runtime/common/__init__.py +0 -0
  2. agentscope_runtime/common/collections/in_memory_mapping.py +27 -0
  3. agentscope_runtime/common/collections/redis_mapping.py +42 -0
  4. agentscope_runtime/common/container_clients/__init__.py +0 -0
  5. agentscope_runtime/common/container_clients/agentrun_client.py +1098 -0
  6. agentscope_runtime/common/container_clients/docker_client.py +250 -0
  7. agentscope_runtime/{sandbox/manager → common}/container_clients/kubernetes_client.py +6 -13
  8. agentscope_runtime/engine/__init__.py +12 -0
  9. agentscope_runtime/engine/agents/agentscope_agent.py +488 -0
  10. agentscope_runtime/engine/agents/agno_agent.py +26 -27
  11. agentscope_runtime/engine/agents/autogen_agent.py +13 -8
  12. agentscope_runtime/engine/agents/utils.py +53 -0
  13. agentscope_runtime/engine/app/__init__.py +6 -0
  14. agentscope_runtime/engine/app/agent_app.py +239 -0
  15. agentscope_runtime/engine/app/base_app.py +181 -0
  16. agentscope_runtime/engine/app/celery_mixin.py +92 -0
  17. agentscope_runtime/engine/deployers/base.py +1 -0
  18. agentscope_runtime/engine/deployers/cli_fc_deploy.py +39 -20
  19. agentscope_runtime/engine/deployers/kubernetes_deployer.py +12 -5
  20. agentscope_runtime/engine/deployers/local_deployer.py +61 -3
  21. agentscope_runtime/engine/deployers/modelstudio_deployer.py +10 -11
  22. agentscope_runtime/engine/deployers/utils/docker_image_utils/runner_image_factory.py +9 -0
  23. agentscope_runtime/engine/deployers/utils/package_project_utils.py +234 -3
  24. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +567 -7
  25. agentscope_runtime/engine/deployers/utils/service_utils/standalone_main.py.j2 +211 -0
  26. agentscope_runtime/engine/deployers/utils/wheel_packager.py +1 -1
  27. agentscope_runtime/engine/helpers/helper.py +60 -41
  28. agentscope_runtime/engine/runner.py +35 -24
  29. agentscope_runtime/engine/schemas/agent_schemas.py +42 -0
  30. agentscope_runtime/engine/schemas/modelstudio_llm.py +14 -14
  31. agentscope_runtime/engine/services/sandbox_service.py +62 -70
  32. agentscope_runtime/engine/services/tablestore_memory_service.py +307 -0
  33. agentscope_runtime/engine/services/tablestore_rag_service.py +143 -0
  34. agentscope_runtime/engine/services/tablestore_session_history_service.py +293 -0
  35. agentscope_runtime/engine/services/utils/__init__.py +0 -0
  36. agentscope_runtime/engine/services/utils/tablestore_service_utils.py +352 -0
  37. agentscope_runtime/engine/tracing/__init__.py +9 -3
  38. agentscope_runtime/engine/tracing/asyncio_util.py +24 -0
  39. agentscope_runtime/engine/tracing/base.py +66 -34
  40. agentscope_runtime/engine/tracing/local_logging_handler.py +45 -31
  41. agentscope_runtime/engine/tracing/message_util.py +528 -0
  42. agentscope_runtime/engine/tracing/tracing_metric.py +20 -8
  43. agentscope_runtime/engine/tracing/tracing_util.py +130 -0
  44. agentscope_runtime/engine/tracing/wrapper.py +794 -169
  45. agentscope_runtime/sandbox/__init__.py +2 -0
  46. agentscope_runtime/sandbox/box/base/__init__.py +4 -0
  47. agentscope_runtime/sandbox/box/base/base_sandbox.py +6 -4
  48. agentscope_runtime/sandbox/box/browser/__init__.py +4 -0
  49. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +10 -14
  50. agentscope_runtime/sandbox/box/dummy/__init__.py +4 -0
  51. agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +2 -1
  52. agentscope_runtime/sandbox/box/filesystem/__init__.py +4 -0
  53. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +10 -7
  54. agentscope_runtime/sandbox/box/gui/__init__.py +4 -0
  55. agentscope_runtime/sandbox/box/gui/box/__init__.py +0 -0
  56. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +81 -0
  57. agentscope_runtime/sandbox/box/sandbox.py +5 -2
  58. agentscope_runtime/sandbox/box/shared/routers/generic.py +20 -1
  59. agentscope_runtime/sandbox/box/training_box/__init__.py +4 -0
  60. agentscope_runtime/sandbox/box/training_box/training_box.py +7 -54
  61. agentscope_runtime/sandbox/build.py +143 -58
  62. agentscope_runtime/sandbox/client/http_client.py +87 -59
  63. agentscope_runtime/sandbox/client/training_client.py +0 -1
  64. agentscope_runtime/sandbox/constant.py +27 -1
  65. agentscope_runtime/sandbox/custom/custom_sandbox.py +7 -6
  66. agentscope_runtime/sandbox/custom/example.py +4 -3
  67. agentscope_runtime/sandbox/enums.py +1 -1
  68. agentscope_runtime/sandbox/manager/sandbox_manager.py +212 -106
  69. agentscope_runtime/sandbox/manager/server/app.py +82 -14
  70. agentscope_runtime/sandbox/manager/server/config.py +50 -3
  71. agentscope_runtime/sandbox/model/container.py +12 -23
  72. agentscope_runtime/sandbox/model/manager_config.py +93 -5
  73. agentscope_runtime/sandbox/registry.py +1 -1
  74. agentscope_runtime/sandbox/tools/gui/__init__.py +7 -0
  75. agentscope_runtime/sandbox/tools/gui/tool.py +77 -0
  76. agentscope_runtime/sandbox/tools/mcp_tool.py +6 -2
  77. agentscope_runtime/sandbox/tools/tool.py +4 -0
  78. agentscope_runtime/sandbox/utils.py +124 -0
  79. agentscope_runtime/version.py +1 -1
  80. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b2.dist-info}/METADATA +214 -102
  81. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b2.dist-info}/RECORD +94 -78
  82. agentscope_runtime/engine/agents/agentscope_agent/__init__.py +0 -6
  83. agentscope_runtime/engine/agents/agentscope_agent/agent.py +0 -401
  84. agentscope_runtime/engine/agents/agentscope_agent/hooks.py +0 -169
  85. agentscope_runtime/engine/agents/llm_agent.py +0 -51
  86. agentscope_runtime/engine/llms/__init__.py +0 -3
  87. agentscope_runtime/engine/llms/base_llm.py +0 -60
  88. agentscope_runtime/engine/llms/qwen_llm.py +0 -47
  89. agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +0 -22
  90. agentscope_runtime/sandbox/manager/collections/redis_mapping.py +0 -26
  91. agentscope_runtime/sandbox/manager/container_clients/__init__.py +0 -10
  92. agentscope_runtime/sandbox/manager/container_clients/docker_client.py +0 -422
  93. /agentscope_runtime/{sandbox/manager → common}/collections/__init__.py +0 -0
  94. /agentscope_runtime/{sandbox/manager → common}/collections/base_mapping.py +0 -0
  95. /agentscope_runtime/{sandbox/manager → common}/collections/base_queue.py +0 -0
  96. /agentscope_runtime/{sandbox/manager → common}/collections/base_set.py +0 -0
  97. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_queue.py +0 -0
  98. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_set.py +0 -0
  99. /agentscope_runtime/{sandbox/manager → common}/collections/redis_queue.py +0 -0
  100. /agentscope_runtime/{sandbox/manager → common}/collections/redis_set.py +0 -0
  101. /agentscope_runtime/{sandbox/manager → common}/container_clients/base_client.py +0 -0
  102. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b2.dist-info}/WHEEL +0 -0
  103. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b2.dist-info}/entry_points.txt +0 -0
  104. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b2.dist-info}/licenses/LICENSE +0 -0
  105. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b2.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
- try:
6
- from ...sandbox.enums import SandboxType
7
- from ...sandbox.manager import SandboxManager
8
- from ...sandbox.registry import SandboxRegistry
9
- from ...sandbox.tools.mcp_tool import MCPTool
10
- from ...sandbox.tools.sandbox_tool import SandboxTool
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
- for _, env_ids in self.session_mapping.items():
43
- for env_id in env_ids:
44
- self.manager_api.release(env_id)
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
- composite_key = self._create_composite_key(session_id, user_id)
53
+ session_ctx_id = self._create_session_ctx_id(session_id, user_id)
69
54
 
70
- # Check if the composite_key already has an environment
71
- if composite_key in self.session_mapping:
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(composite_key)
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
- composite_key,
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
- composite_key,
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
- wbs = []
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
- if box_type == SandboxType.BASE:
139
- if server_configs:
140
- box.add_mcp_servers(server_configs, overwrite=False)
141
-
142
- # Store mapping from composite_key to env_id
143
- if composite_key not in self.session_mapping:
144
- self.session_mapping[composite_key] = []
145
-
146
- self.session_mapping[composite_key].append(box_id)
147
-
148
- wbs.append(box)
149
- return wbs
150
-
151
- def _connect_existing_environment(self, composite_key):
152
- env_ids = self.session_mapping.get(composite_key)
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
- composite_key = self._create_composite_key(session_id, user_id)
178
+ session_ctx_id = self._create_session_ctx_id(session_id, user_id)
187
179
 
188
- # Retrieve and remove env_id using composite_key
189
- env_ids = self.session_mapping.pop(composite_key, [])
180
+ env_ids = self.manager_api.get_session_mapping(session_ctx_id)
190
181
 
191
- for env_id in env_ids:
192
- self.manager_api.release(env_id)
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 _create_composite_key(self, session_id, user_id):
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