agentscope-runtime 0.1.5b2__py3-none-any.whl → 0.2.0b1__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/engine/__init__.py +12 -0
  8. agentscope_runtime/engine/agents/agentscope_agent.py +488 -0
  9. agentscope_runtime/engine/agents/agno_agent.py +19 -18
  10. agentscope_runtime/engine/agents/autogen_agent.py +13 -8
  11. agentscope_runtime/engine/agents/utils.py +53 -0
  12. agentscope_runtime/engine/app/__init__.py +6 -0
  13. agentscope_runtime/engine/app/agent_app.py +239 -0
  14. agentscope_runtime/engine/app/base_app.py +181 -0
  15. agentscope_runtime/engine/app/celery_mixin.py +92 -0
  16. agentscope_runtime/engine/deployers/base.py +1 -0
  17. agentscope_runtime/engine/deployers/cli_fc_deploy.py +39 -20
  18. agentscope_runtime/engine/deployers/kubernetes_deployer.py +12 -5
  19. agentscope_runtime/engine/deployers/local_deployer.py +61 -3
  20. agentscope_runtime/engine/deployers/modelstudio_deployer.py +10 -11
  21. agentscope_runtime/engine/deployers/utils/docker_image_utils/runner_image_factory.py +9 -0
  22. agentscope_runtime/engine/deployers/utils/package_project_utils.py +234 -3
  23. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +567 -7
  24. agentscope_runtime/engine/deployers/utils/service_utils/standalone_main.py.j2 +211 -0
  25. agentscope_runtime/engine/deployers/utils/wheel_packager.py +1 -1
  26. agentscope_runtime/engine/helpers/helper.py +60 -41
  27. agentscope_runtime/engine/runner.py +35 -24
  28. agentscope_runtime/engine/schemas/agent_schemas.py +42 -0
  29. agentscope_runtime/engine/schemas/modelstudio_llm.py +14 -14
  30. agentscope_runtime/engine/services/sandbox_service.py +62 -70
  31. agentscope_runtime/engine/services/tablestore_memory_service.py +304 -0
  32. agentscope_runtime/engine/services/tablestore_rag_service.py +143 -0
  33. agentscope_runtime/engine/services/tablestore_session_history_service.py +293 -0
  34. agentscope_runtime/engine/services/utils/__init__.py +0 -0
  35. agentscope_runtime/engine/services/utils/tablestore_service_utils.py +352 -0
  36. agentscope_runtime/engine/tracing/__init__.py +9 -3
  37. agentscope_runtime/engine/tracing/asyncio_util.py +24 -0
  38. agentscope_runtime/engine/tracing/base.py +66 -34
  39. agentscope_runtime/engine/tracing/local_logging_handler.py +45 -31
  40. agentscope_runtime/engine/tracing/message_util.py +528 -0
  41. agentscope_runtime/engine/tracing/tracing_metric.py +20 -8
  42. agentscope_runtime/engine/tracing/tracing_util.py +130 -0
  43. agentscope_runtime/engine/tracing/wrapper.py +794 -169
  44. agentscope_runtime/sandbox/__init__.py +2 -0
  45. agentscope_runtime/sandbox/box/base/__init__.py +4 -0
  46. agentscope_runtime/sandbox/box/base/base_sandbox.py +6 -4
  47. agentscope_runtime/sandbox/box/browser/__init__.py +4 -0
  48. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +10 -14
  49. agentscope_runtime/sandbox/box/dummy/__init__.py +4 -0
  50. agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +2 -1
  51. agentscope_runtime/sandbox/box/filesystem/__init__.py +4 -0
  52. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +10 -7
  53. agentscope_runtime/sandbox/box/gui/__init__.py +4 -0
  54. agentscope_runtime/sandbox/box/gui/box/__init__.py +0 -0
  55. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +81 -0
  56. agentscope_runtime/sandbox/box/sandbox.py +5 -2
  57. agentscope_runtime/sandbox/box/shared/routers/generic.py +20 -1
  58. agentscope_runtime/sandbox/box/training_box/__init__.py +4 -0
  59. agentscope_runtime/sandbox/box/training_box/training_box.py +10 -15
  60. agentscope_runtime/sandbox/build.py +143 -58
  61. agentscope_runtime/sandbox/client/http_client.py +87 -59
  62. agentscope_runtime/sandbox/client/training_client.py +0 -1
  63. agentscope_runtime/sandbox/constant.py +27 -1
  64. agentscope_runtime/sandbox/custom/custom_sandbox.py +7 -6
  65. agentscope_runtime/sandbox/custom/example.py +4 -3
  66. agentscope_runtime/sandbox/enums.py +1 -0
  67. agentscope_runtime/sandbox/manager/sandbox_manager.py +212 -106
  68. agentscope_runtime/sandbox/manager/server/app.py +82 -14
  69. agentscope_runtime/sandbox/manager/server/config.py +50 -3
  70. agentscope_runtime/sandbox/model/container.py +12 -23
  71. agentscope_runtime/sandbox/model/manager_config.py +93 -5
  72. agentscope_runtime/sandbox/registry.py +1 -1
  73. agentscope_runtime/sandbox/tools/gui/__init__.py +7 -0
  74. agentscope_runtime/sandbox/tools/gui/tool.py +77 -0
  75. agentscope_runtime/sandbox/tools/mcp_tool.py +6 -2
  76. agentscope_runtime/sandbox/tools/tool.py +4 -0
  77. agentscope_runtime/sandbox/utils.py +124 -0
  78. agentscope_runtime/version.py +1 -1
  79. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b1.dist-info}/METADATA +209 -101
  80. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b1.dist-info}/RECORD +94 -78
  81. agentscope_runtime/engine/agents/agentscope_agent/__init__.py +0 -6
  82. agentscope_runtime/engine/agents/agentscope_agent/agent.py +0 -401
  83. agentscope_runtime/engine/agents/agentscope_agent/hooks.py +0 -169
  84. agentscope_runtime/engine/agents/llm_agent.py +0 -51
  85. agentscope_runtime/engine/llms/__init__.py +0 -3
  86. agentscope_runtime/engine/llms/base_llm.py +0 -60
  87. agentscope_runtime/engine/llms/qwen_llm.py +0 -47
  88. agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +0 -22
  89. agentscope_runtime/sandbox/manager/collections/redis_mapping.py +0 -26
  90. agentscope_runtime/sandbox/manager/container_clients/__init__.py +0 -10
  91. agentscope_runtime/sandbox/manager/container_clients/docker_client.py +0 -422
  92. /agentscope_runtime/{sandbox/manager → common}/collections/__init__.py +0 -0
  93. /agentscope_runtime/{sandbox/manager → common}/collections/base_mapping.py +0 -0
  94. /agentscope_runtime/{sandbox/manager → common}/collections/base_queue.py +0 -0
  95. /agentscope_runtime/{sandbox/manager → common}/collections/base_set.py +0 -0
  96. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_queue.py +0 -0
  97. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_set.py +0 -0
  98. /agentscope_runtime/{sandbox/manager → common}/collections/redis_queue.py +0 -0
  99. /agentscope_runtime/{sandbox/manager → common}/collections/redis_set.py +0 -0
  100. /agentscope_runtime/{sandbox/manager → common}/container_clients/base_client.py +0 -0
  101. /agentscope_runtime/{sandbox/manager → common}/container_clients/kubernetes_client.py +0 -0
  102. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b1.dist-info}/WHEEL +0 -0
  103. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b1.dist-info}/entry_points.txt +0 -0
  104. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b1.dist-info}/licenses/LICENSE +0 -0
  105. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b1.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,304 @@
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] = DashScopeEmbeddings(),
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
+ self._search_strategy = search_strategy
67
+ self._embedding_model = (
68
+ embedding_model # the parameter is None, don't store vector.
69
+ )
70
+
71
+ if (
72
+ self._search_strategy == SearchStrategy.VECTOR
73
+ and self._embedding_model is None
74
+ ):
75
+ raise ValueError(
76
+ "Embedding model is required when search strategy is VECTOR.",
77
+ )
78
+
79
+ self._tablestore_client = tablestore_client
80
+ self._vector_dimension = vector_dimension
81
+ self._table_name = table_name
82
+ self._search_index_schema = (
83
+ list(search_index_schema)
84
+ if search_index_schema is not None
85
+ else None
86
+ )
87
+ self._text_field = text_field
88
+ self._embedding_field = embedding_field
89
+ self._vector_metric_type = vector_metric_type
90
+ self._knowledge_store: Optional[AsyncKnowledgeStore] = None
91
+ self._knowledge_store_init_parameter_kwargs = kwargs
92
+
93
+ async def _init_knowledge_store(self) -> None:
94
+ self._knowledge_store = AsyncKnowledgeStore(
95
+ tablestore_client=self._tablestore_client,
96
+ vector_dimension=self._vector_dimension,
97
+ enable_multi_tenant=False,
98
+ # enable multi tenant will make user be confused,
99
+ # we unify the usage of session id and user id,
100
+ # and allow users to configure the index themselves.
101
+ table_name=self._table_name,
102
+ search_index_name=TablestoreMemoryService._SEARCH_INDEX_NAME,
103
+ search_index_schema=copy.deepcopy(self._search_index_schema),
104
+ text_field=self._text_field,
105
+ embedding_field=self._embedding_field,
106
+ vector_metric_type=self._vector_metric_type,
107
+ **self._knowledge_store_init_parameter_kwargs,
108
+ )
109
+
110
+ await self._knowledge_store.init_table()
111
+
112
+ async def start(self) -> None:
113
+ """Start the tablestore service"""
114
+ if self._knowledge_store:
115
+ return
116
+ await self._init_knowledge_store()
117
+
118
+ async def stop(self) -> None:
119
+ """Close the tablestore service"""
120
+ if self._knowledge_store is None:
121
+ return
122
+ knowledge_store = self._knowledge_store
123
+ self._knowledge_store = None
124
+ await knowledge_store.close()
125
+
126
+ async def health(self) -> bool:
127
+ """Checks the health of the service."""
128
+ if self._knowledge_store is None:
129
+ tablestore_log("Tablestore memory service is not started.")
130
+ return False
131
+
132
+ try:
133
+ async for _ in await self._knowledge_store.get_all_documents():
134
+ return True
135
+ return True
136
+ except Exception as e:
137
+ tablestore_log(
138
+ f"Tablestore memory service "
139
+ f"cannot access Tablestore, error: {str(e)}.",
140
+ )
141
+ return False
142
+
143
+ async def add_memory(
144
+ self,
145
+ user_id: str,
146
+ messages: list,
147
+ session_id: Optional[str] = None,
148
+ ) -> None:
149
+ if not session_id:
150
+ session_id = TablestoreMemoryService._DEFAULT_SESSION_ID
151
+
152
+ if not messages:
153
+ return
154
+
155
+ tablestore_documents = convert_messages_to_tablestore_documents(
156
+ messages,
157
+ user_id,
158
+ session_id,
159
+ self._embedding_model,
160
+ )
161
+
162
+ put_tasks = [
163
+ self._knowledge_store.put_document(tablestore_document)
164
+ for tablestore_document in tablestore_documents
165
+ ]
166
+ await asyncio.gather(*put_tasks)
167
+
168
+ @staticmethod
169
+ async def get_query_text(message: Message) -> str:
170
+ if not message or message.type != MessageType.MESSAGE:
171
+ return ""
172
+
173
+ for content in message.content:
174
+ if content.type == "text":
175
+ return content.text
176
+
177
+ return ""
178
+
179
+ async def search_memory(
180
+ self,
181
+ user_id: str,
182
+ messages: list,
183
+ filters: Optional[Dict[str, Any]] = None,
184
+ ) -> list:
185
+ if (
186
+ not messages
187
+ or not isinstance(messages, list)
188
+ or len(messages) == 0
189
+ ):
190
+ return []
191
+
192
+ query = await TablestoreMemoryService.get_query_text(messages[-1])
193
+ if not query:
194
+ return []
195
+
196
+ top_k = 100
197
+ if (
198
+ filters
199
+ and "top_k" in filters
200
+ and isinstance(filters["top_k"], int)
201
+ ):
202
+ top_k = filters["top_k"]
203
+
204
+ if self._search_strategy == SearchStrategy.FULL_TEXT:
205
+ matched_messages = [
206
+ convert_tablestore_document_to_message(hit.document)
207
+ for hit in (
208
+ await self._knowledge_store.full_text_search(
209
+ query=query,
210
+ metadata_filter=Filters.eq("user_id", user_id),
211
+ limit=top_k,
212
+ meta_data_to_get=get_message_metadata_names(),
213
+ )
214
+ ).hits
215
+ ]
216
+ elif self._search_strategy == SearchStrategy.VECTOR:
217
+ matched_messages = [
218
+ convert_tablestore_document_to_message(hit.document)
219
+ for hit in (
220
+ await self._knowledge_store.vector_search(
221
+ query_vector=self._embedding_model.embed_query(query),
222
+ metadata_filter=Filters.eq("user_id", user_id),
223
+ top_k=top_k,
224
+ meta_data_to_get=get_message_metadata_names(),
225
+ )
226
+ ).hits
227
+ ]
228
+ else:
229
+ raise ValueError(
230
+ f"Unsupported search strategy: {self._search_strategy}",
231
+ )
232
+
233
+ return matched_messages
234
+
235
+ async def list_memory(
236
+ self,
237
+ user_id: str,
238
+ filters: Optional[Dict[str, Any]] = None,
239
+ ) -> list:
240
+ page_num = filters.get("page_num", 1) if filters else 1
241
+ page_size = filters.get("page_size", 10) if filters else 10
242
+
243
+ if page_num < 1 or page_size < 1:
244
+ raise ValueError("page_num and page_size must be greater than 0.")
245
+
246
+ next_token = None
247
+ for _ in range(page_num - 1):
248
+ next_token = (
249
+ await self._knowledge_store.search_documents(
250
+ metadata_filter=Filters.eq("user_id", user_id),
251
+ limit=page_size,
252
+ next_token=next_token,
253
+ )
254
+ ).next_token
255
+ if not next_token:
256
+ tablestore_log(
257
+ "Page number exceeds the total number of pages, "
258
+ "return empty list.",
259
+ )
260
+ return []
261
+
262
+ messages = [
263
+ convert_tablestore_document_to_message(hit.document)
264
+ for hit in (
265
+ await self._knowledge_store.search_documents(
266
+ metadata_filter=Filters.eq("user_id", user_id),
267
+ limit=page_size,
268
+ next_token=next_token,
269
+ meta_data_to_get=get_message_metadata_names(),
270
+ )
271
+ ).hits
272
+ ]
273
+
274
+ return messages
275
+
276
+ async def delete_memory(
277
+ self,
278
+ user_id: str,
279
+ session_id: Optional[str] = None,
280
+ ) -> None:
281
+ delete_tablestore_documents = [
282
+ hit.document
283
+ for hit in (
284
+ await self._knowledge_store.search_documents(
285
+ metadata_filter=(
286
+ Filters.eq("user_id", user_id)
287
+ if not session_id
288
+ else Filters.logical_and(
289
+ [
290
+ Filters.eq("user_id", user_id),
291
+ Filters.eq("session_id", session_id),
292
+ ],
293
+ )
294
+ ),
295
+ )
296
+ ).hits
297
+ ]
298
+ delete_tasks = [
299
+ self._knowledge_store.delete_document(
300
+ tablestore_document.document_id,
301
+ )
302
+ for tablestore_document in delete_tablestore_documents
303
+ ]
304
+ 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