veadk-python 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of veadk-python might be problematic. Click here for more details.
- veadk/__init__.py +31 -0
- veadk/a2a/__init__.py +13 -0
- veadk/a2a/agent_card.py +45 -0
- veadk/a2a/remote_ve_agent.py +19 -0
- veadk/a2a/ve_a2a_server.py +77 -0
- veadk/a2a/ve_agent_executor.py +78 -0
- veadk/a2a/ve_task_store.py +37 -0
- veadk/agent.py +253 -0
- veadk/cli/__init__.py +13 -0
- veadk/cli/main.py +278 -0
- veadk/cli/services/agentpilot/__init__.py +17 -0
- veadk/cli/services/agentpilot/agentpilot.py +77 -0
- veadk/cli/services/veapig/__init__.py +17 -0
- veadk/cli/services/veapig/apig.py +224 -0
- veadk/cli/services/veapig/apig_utils.py +332 -0
- veadk/cli/services/vefaas/__init__.py +17 -0
- veadk/cli/services/vefaas/template/deploy.py +44 -0
- veadk/cli/services/vefaas/template/src/app.py +30 -0
- veadk/cli/services/vefaas/template/src/config.py +58 -0
- veadk/cli/services/vefaas/vefaas.py +346 -0
- veadk/cli/services/vefaas/vefaas_utils.py +408 -0
- veadk/cli/services/vetls/__init__.py +17 -0
- veadk/cli/services/vetls/vetls.py +87 -0
- veadk/cli/studio/__init__.py +13 -0
- veadk/cli/studio/agent_processor.py +247 -0
- veadk/cli/studio/fast_api.py +232 -0
- veadk/cli/studio/model.py +116 -0
- veadk/cloud/__init__.py +13 -0
- veadk/cloud/cloud_agent_engine.py +144 -0
- veadk/cloud/cloud_app.py +123 -0
- veadk/cloud/template/app.py +30 -0
- veadk/cloud/template/config.py +55 -0
- veadk/config.py +131 -0
- veadk/consts.py +17 -0
- veadk/database/__init__.py +17 -0
- veadk/database/base_database.py +45 -0
- veadk/database/database_factory.py +80 -0
- veadk/database/kv/__init__.py +13 -0
- veadk/database/kv/redis_database.py +109 -0
- veadk/database/local_database.py +43 -0
- veadk/database/relational/__init__.py +13 -0
- veadk/database/relational/mysql_database.py +114 -0
- veadk/database/vector/__init__.py +13 -0
- veadk/database/vector/opensearch_vector_database.py +205 -0
- veadk/database/vector/type.py +50 -0
- veadk/database/viking/__init__.py +13 -0
- veadk/database/viking/viking_database.py +378 -0
- veadk/database/viking/viking_memory_db.py +521 -0
- veadk/evaluation/__init__.py +17 -0
- veadk/evaluation/adk_evaluator/__init__.py +13 -0
- veadk/evaluation/adk_evaluator/adk_evaluator.py +291 -0
- veadk/evaluation/base_evaluator.py +242 -0
- veadk/evaluation/deepeval_evaluator/__init__.py +17 -0
- veadk/evaluation/deepeval_evaluator/deepeval_evaluator.py +223 -0
- veadk/evaluation/eval_set_file_loader.py +28 -0
- veadk/evaluation/eval_set_recorder.py +91 -0
- veadk/evaluation/utils/prometheus.py +142 -0
- veadk/knowledgebase/__init__.py +17 -0
- veadk/knowledgebase/knowledgebase.py +83 -0
- veadk/knowledgebase/knowledgebase_database_adapter.py +259 -0
- veadk/memory/__init__.py +13 -0
- veadk/memory/long_term_memory.py +119 -0
- veadk/memory/memory_database_adapter.py +235 -0
- veadk/memory/short_term_memory.py +124 -0
- veadk/memory/short_term_memory_processor.py +90 -0
- veadk/prompts/__init__.py +13 -0
- veadk/prompts/agent_default_prompt.py +30 -0
- veadk/prompts/prompt_evaluator.py +20 -0
- veadk/prompts/prompt_memory_processor.py +55 -0
- veadk/prompts/prompt_optimization.py +158 -0
- veadk/runner.py +252 -0
- veadk/tools/__init__.py +13 -0
- veadk/tools/builtin_tools/__init__.py +13 -0
- veadk/tools/builtin_tools/lark.py +67 -0
- veadk/tools/builtin_tools/las.py +23 -0
- veadk/tools/builtin_tools/vesearch.py +49 -0
- veadk/tools/builtin_tools/web_scraper.py +76 -0
- veadk/tools/builtin_tools/web_search.py +192 -0
- veadk/tools/demo_tools.py +58 -0
- veadk/tools/load_knowledgebase_tool.py +144 -0
- veadk/tools/sandbox/__init__.py +13 -0
- veadk/tools/sandbox/browser_sandbox.py +27 -0
- veadk/tools/sandbox/code_sandbox.py +30 -0
- veadk/tools/sandbox/computer_sandbox.py +27 -0
- veadk/tracing/__init__.py +13 -0
- veadk/tracing/base_tracer.py +172 -0
- veadk/tracing/telemetry/__init__.py +13 -0
- veadk/tracing/telemetry/exporters/__init__.py +13 -0
- veadk/tracing/telemetry/exporters/apiserver_exporter.py +60 -0
- veadk/tracing/telemetry/exporters/apmplus_exporter.py +101 -0
- veadk/tracing/telemetry/exporters/base_exporter.py +28 -0
- veadk/tracing/telemetry/exporters/cozeloop_exporter.py +69 -0
- veadk/tracing/telemetry/exporters/inmemory_exporter.py +88 -0
- veadk/tracing/telemetry/exporters/tls_exporter.py +78 -0
- veadk/tracing/telemetry/metrics/__init__.py +13 -0
- veadk/tracing/telemetry/metrics/opentelemetry_metrics.py +73 -0
- veadk/tracing/telemetry/opentelemetry_tracer.py +167 -0
- veadk/types.py +23 -0
- veadk/utils/__init__.py +13 -0
- veadk/utils/logger.py +59 -0
- veadk/utils/misc.py +33 -0
- veadk/utils/patches.py +85 -0
- veadk/utils/volcengine_sign.py +199 -0
- veadk/version.py +15 -0
- veadk_python-0.1.0.dist-info/METADATA +124 -0
- veadk_python-0.1.0.dist-info/RECORD +110 -0
- veadk_python-0.1.0.dist-info/WHEEL +5 -0
- veadk_python-0.1.0.dist-info/entry_points.txt +2 -0
- veadk_python-0.1.0.dist-info/licenses/LICENSE +201 -0
- veadk_python-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
Knowledgebase may use different databases, so we need to create
|
|
17
|
+
an adapter to abstract the database operations.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import re
|
|
21
|
+
import time
|
|
22
|
+
from typing import BinaryIO, TextIO
|
|
23
|
+
|
|
24
|
+
from pydantic import BaseModel, ConfigDict
|
|
25
|
+
|
|
26
|
+
from veadk.database.base_database import BaseDatabase
|
|
27
|
+
from veadk.database.database_factory import DatabaseBackend
|
|
28
|
+
from veadk.utils.logger import get_logger
|
|
29
|
+
|
|
30
|
+
logger = get_logger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def format_collection_name(collection_name: str) -> str:
|
|
34
|
+
replaced_str = re.sub(r"[- ]", "_", collection_name)
|
|
35
|
+
return re.sub(r"[^a-z0-9_]", "", replaced_str).lower()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_knowledgebase_adapter(backend: str):
|
|
39
|
+
if backend == DatabaseBackend.REDIS:
|
|
40
|
+
return KnowledgebaseKVDatabaseAdapter
|
|
41
|
+
elif backend == DatabaseBackend.MYSQL:
|
|
42
|
+
return KnowledgebaseRelationalDatabaseAdapter
|
|
43
|
+
elif backend == DatabaseBackend.OPENSEARCH:
|
|
44
|
+
return KnowledgebaseVectorDatabaseAdapter
|
|
45
|
+
elif backend == DatabaseBackend.LOCAL:
|
|
46
|
+
return KnowledgebaseLocalDatabaseAdapter
|
|
47
|
+
elif backend == DatabaseBackend.VIKING:
|
|
48
|
+
return KnowledgebaseVikingDatabaseAdapter
|
|
49
|
+
else:
|
|
50
|
+
raise ValueError(f"Unknown backend: {backend}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class KnowledgebaseKVDatabaseAdapter(BaseModel):
|
|
54
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
55
|
+
|
|
56
|
+
database_client: BaseDatabase
|
|
57
|
+
|
|
58
|
+
def add(self, content: list[str], app_name: str, user_id: str, session_id: str):
|
|
59
|
+
"""Add texts to Redis.
|
|
60
|
+
|
|
61
|
+
Key: app_name
|
|
62
|
+
Field: app_name:user_id
|
|
63
|
+
Value: text in List
|
|
64
|
+
"""
|
|
65
|
+
# key = f"{app_name}:{user_id}"
|
|
66
|
+
key = f"{app_name}"
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
for _content in content:
|
|
70
|
+
self.database_client.add(key, _content)
|
|
71
|
+
logger.debug(
|
|
72
|
+
f"Successfully added {len(content)} texts to Redis list key `{key}`."
|
|
73
|
+
)
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logger.error(f"Failed to add texts to Redis list key `{key}`: {e}")
|
|
76
|
+
raise e
|
|
77
|
+
|
|
78
|
+
def query(self, query: str, app_name: str, user_id: str, **kwargs):
|
|
79
|
+
# key = f"{app_name}:{user_id}"
|
|
80
|
+
key = f"{app_name}"
|
|
81
|
+
top_k = 10
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
result = self.database_client.query(key, query)
|
|
85
|
+
return result[-top_k:]
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.error(f"Failed to search from Redis list key '{key}': {e}")
|
|
88
|
+
raise e
|
|
89
|
+
|
|
90
|
+
def delete(self, app_name: str, user_id: str, session_id: str):
|
|
91
|
+
try:
|
|
92
|
+
# key = f"{app_name}:{user_id}:{session_id}"
|
|
93
|
+
key = f"{app_name}"
|
|
94
|
+
self.database_client.delete(key=key)
|
|
95
|
+
logger.info(f"Successfully deleted data for app {app_name}")
|
|
96
|
+
except Exception as e:
|
|
97
|
+
logger.error(f"Failed to delete data: {e}")
|
|
98
|
+
raise e
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class KnowledgebaseRelationalDatabaseAdapter(BaseModel):
|
|
102
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
103
|
+
|
|
104
|
+
database_client: BaseDatabase
|
|
105
|
+
|
|
106
|
+
def create_table(self, table_name: str):
|
|
107
|
+
sql = f"""
|
|
108
|
+
CREATE TABLE `{table_name}` (
|
|
109
|
+
`id` BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
110
|
+
`data` TEXT NOT NULL,
|
|
111
|
+
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
112
|
+
) ENGINE=InnoDB DEFAULT CHARSET={self.database_client.config.charset};
|
|
113
|
+
"""
|
|
114
|
+
self.database_client.add(sql)
|
|
115
|
+
|
|
116
|
+
def add(self, content: list[str], app_name: str, user_id: str, session_id: str):
|
|
117
|
+
table = app_name
|
|
118
|
+
|
|
119
|
+
if not self.database_client.table_exists(table):
|
|
120
|
+
logger.warning(f"Table {table} does not exist, creating...")
|
|
121
|
+
self.create_table(table)
|
|
122
|
+
|
|
123
|
+
for _content in content:
|
|
124
|
+
sql = f"""
|
|
125
|
+
INSERT INTO `{table}` (`data`)
|
|
126
|
+
VALUES (%s);
|
|
127
|
+
"""
|
|
128
|
+
self.database_client.add(sql, params=(_content,))
|
|
129
|
+
logger.info(f"Successfully added {len(content)} texts to table {table}.")
|
|
130
|
+
|
|
131
|
+
def query(self, query: str, app_name: str, user_id: str, **kwargs):
|
|
132
|
+
"""Search content from table app_name."""
|
|
133
|
+
table = app_name
|
|
134
|
+
top_k = 10
|
|
135
|
+
|
|
136
|
+
if not self.database_client.table_exists(table):
|
|
137
|
+
logger.warning(
|
|
138
|
+
f"querying {query}, but table `{table}` does not exist, returning empty list."
|
|
139
|
+
)
|
|
140
|
+
return []
|
|
141
|
+
|
|
142
|
+
sql = f"""
|
|
143
|
+
SELECT `data` FROM `{table}` ORDER BY `created_at` DESC LIMIT {top_k};
|
|
144
|
+
"""
|
|
145
|
+
results = self.database_client.query(sql)
|
|
146
|
+
return [item["data"] for item in results]
|
|
147
|
+
|
|
148
|
+
def delete(self, app_name: str, user_id: str, session_id: str):
|
|
149
|
+
table = app_name
|
|
150
|
+
try:
|
|
151
|
+
self.database_client.delete(table=table)
|
|
152
|
+
logger.info(f"Successfully deleted data from table {app_name}")
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.error(f"Failed to delete data: {e}")
|
|
155
|
+
raise e
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class KnowledgebaseVectorDatabaseAdapter(BaseModel):
|
|
159
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
160
|
+
|
|
161
|
+
database_client: BaseDatabase
|
|
162
|
+
|
|
163
|
+
def add(self, content: list[str], app_name: str, user_id: str, session_id: str):
|
|
164
|
+
# collection_name = format_collection_name(f"{app_name}_{user_id}")
|
|
165
|
+
# knowledgebase is application specific
|
|
166
|
+
collection_name = format_collection_name(f"{app_name}")
|
|
167
|
+
self.database_client.add(content, collection_name=collection_name)
|
|
168
|
+
|
|
169
|
+
def query(self, query: str, app_name: str, user_id: str, **kwargs):
|
|
170
|
+
# collection_name = format_collection_name(f"{app_name}_{user_id}")
|
|
171
|
+
# knowledgebase is application specific
|
|
172
|
+
collection_name = format_collection_name(f"{app_name}")
|
|
173
|
+
return self.database_client.query(
|
|
174
|
+
query, collection_name=collection_name, **kwargs
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def delete(self, app_name: str, user_id: str, session_id: str):
|
|
178
|
+
# collection_name = format_collection_name(f"{app_name}_{user_id}")
|
|
179
|
+
# knowledgebase is application specific
|
|
180
|
+
collection_name = format_collection_name(f"{app_name}")
|
|
181
|
+
try:
|
|
182
|
+
self.database_client.delete(collection_name=collection_name)
|
|
183
|
+
logger.info(
|
|
184
|
+
f"Successfully deleted vector database collection for app {app_name}"
|
|
185
|
+
)
|
|
186
|
+
except Exception as e:
|
|
187
|
+
logger.error(f"Failed to delete vector database collection: {e}")
|
|
188
|
+
raise e
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class KnowledgebaseLocalDatabaseAdapter(BaseModel):
|
|
192
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
193
|
+
|
|
194
|
+
database_client: BaseDatabase
|
|
195
|
+
|
|
196
|
+
def add(self, content: list[str], app_name: str, user_id: str, session_id: str):
|
|
197
|
+
self.database_client.add(content)
|
|
198
|
+
|
|
199
|
+
def query(self, query: str, app_name: str, user_id: str, **kwargs):
|
|
200
|
+
return self.database_client.query(query, **kwargs)
|
|
201
|
+
|
|
202
|
+
def delete(self, app_name: str, user_id: str, session_id: str):
|
|
203
|
+
try:
|
|
204
|
+
self.database_client.delete()
|
|
205
|
+
logger.info(f"Successfully cleared local database for app {app_name}")
|
|
206
|
+
except Exception as e:
|
|
207
|
+
logger.error(f"Failed to clear local database: {e}")
|
|
208
|
+
raise e
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class KnowledgebaseVikingDatabaseAdapter(BaseModel):
|
|
212
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
213
|
+
|
|
214
|
+
database_client: BaseDatabase
|
|
215
|
+
|
|
216
|
+
def get_or_create_collection(self, collection_name: str):
|
|
217
|
+
if not self.database_client.collection_exists(collection_name):
|
|
218
|
+
self.database_client.create_collection(collection_name)
|
|
219
|
+
count = 0
|
|
220
|
+
while not self.database_client.collection_exists(collection_name):
|
|
221
|
+
time.sleep(1)
|
|
222
|
+
count += 1
|
|
223
|
+
if count > 50:
|
|
224
|
+
raise TimeoutError(
|
|
225
|
+
f"Collection {collection_name} not created after 50 seconds"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
def add(
|
|
229
|
+
self,
|
|
230
|
+
content: str | list[str] | TextIO | BinaryIO | bytes,
|
|
231
|
+
app_name: str,
|
|
232
|
+
user_id: str,
|
|
233
|
+
session_id: str,
|
|
234
|
+
**kwargs,
|
|
235
|
+
):
|
|
236
|
+
# collection_name = format_collection_name(f"{app_name}_{user_id}")
|
|
237
|
+
collection_name = format_collection_name(f"{app_name}")
|
|
238
|
+
self.get_or_create_collection(collection_name)
|
|
239
|
+
self.database_client.add(content, collection_name=collection_name, **kwargs)
|
|
240
|
+
|
|
241
|
+
def query(self, query: str, app_name: str, user_id: str, **kwargs):
|
|
242
|
+
collection_name = format_collection_name(f"{app_name}")
|
|
243
|
+
if not self.database_client.collection_exists(collection_name):
|
|
244
|
+
raise ValueError(f"Collection {collection_name} does not exist")
|
|
245
|
+
return self.database_client.query(
|
|
246
|
+
query, collection_name=collection_name, **kwargs
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
def delete(self, app_name: str, user_id: str, session_id: str):
|
|
250
|
+
# collection_name = format_collection_name(f"{app_name}_{user_id}")
|
|
251
|
+
collection_name = format_collection_name(f"{app_name}")
|
|
252
|
+
try:
|
|
253
|
+
self.database_client.delete(collection_name=collection_name)
|
|
254
|
+
logger.info(
|
|
255
|
+
f"Successfully deleted vector database collection for app {app_name}"
|
|
256
|
+
)
|
|
257
|
+
except Exception as e:
|
|
258
|
+
logger.error(f"Failed to delete vector database collection: {e}")
|
|
259
|
+
raise e
|
veadk/memory/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# adapted from Google ADK memory service adk-python/src/google/adk/memory/vertex_ai_memory_bank_service.py at 0a9e67dbca67789247e882d16b139dbdc76a329a · google/adk-python
|
|
16
|
+
import json
|
|
17
|
+
from typing import Literal
|
|
18
|
+
|
|
19
|
+
from google.adk.memory.base_memory_service import (
|
|
20
|
+
BaseMemoryService,
|
|
21
|
+
SearchMemoryResponse,
|
|
22
|
+
)
|
|
23
|
+
from google.adk.memory.memory_entry import MemoryEntry
|
|
24
|
+
from google.adk.sessions import Session
|
|
25
|
+
from google.genai import types
|
|
26
|
+
from typing_extensions import override
|
|
27
|
+
|
|
28
|
+
from veadk.config import getenv
|
|
29
|
+
from veadk.database import DatabaseFactory
|
|
30
|
+
from veadk.utils.logger import get_logger
|
|
31
|
+
|
|
32
|
+
from .memory_database_adapter import get_memory_adapter
|
|
33
|
+
|
|
34
|
+
logger = get_logger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class LongTermMemory(BaseMemoryService):
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
backend: Literal[
|
|
41
|
+
"local", "opensearch", "redis", "mysql", "viking"
|
|
42
|
+
] = "opensearch",
|
|
43
|
+
top_k: int = getenv("LONGTERM_MEMORY_TOP_K", 3),
|
|
44
|
+
):
|
|
45
|
+
if backend == "viking":
|
|
46
|
+
backend = "viking_mem"
|
|
47
|
+
self.top_k = top_k
|
|
48
|
+
self.backend = backend
|
|
49
|
+
|
|
50
|
+
self.db_client = DatabaseFactory.create(
|
|
51
|
+
backend=backend,
|
|
52
|
+
)
|
|
53
|
+
logger.info(f"Long term memory backend is `{backend}`.")
|
|
54
|
+
|
|
55
|
+
self.adapter = get_memory_adapter(backend)(database_client=self.db_client)
|
|
56
|
+
|
|
57
|
+
@override
|
|
58
|
+
async def add_session_to_memory(
|
|
59
|
+
self,
|
|
60
|
+
session: Session,
|
|
61
|
+
):
|
|
62
|
+
event_list = []
|
|
63
|
+
for event in session.events:
|
|
64
|
+
if not event.content or not event.content.parts:
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
message = event.content.model_dump(exclude_none=True, mode="json")
|
|
68
|
+
if (
|
|
69
|
+
"text" not in message["parts"][0]
|
|
70
|
+
): # remove function_call & function_resp
|
|
71
|
+
continue
|
|
72
|
+
event_list.append(json.dumps(message))
|
|
73
|
+
self.adapter.add(
|
|
74
|
+
event_list,
|
|
75
|
+
app_name=session.app_name,
|
|
76
|
+
user_id=session.user_id,
|
|
77
|
+
session_id=session.id,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
@override
|
|
81
|
+
async def search_memory(self, *, app_name: str, user_id: str, query: str):
|
|
82
|
+
memory_chunks = self.adapter.query(
|
|
83
|
+
query=query,
|
|
84
|
+
app_name=app_name,
|
|
85
|
+
user_id=user_id,
|
|
86
|
+
)
|
|
87
|
+
if len(memory_chunks) == 0:
|
|
88
|
+
return SearchMemoryResponse()
|
|
89
|
+
|
|
90
|
+
memory_events = []
|
|
91
|
+
for memory in memory_chunks:
|
|
92
|
+
try:
|
|
93
|
+
memory_dict = json.loads(memory)
|
|
94
|
+
try:
|
|
95
|
+
text = memory_dict["parts"][0]["text"]
|
|
96
|
+
role = memory_dict["role"]
|
|
97
|
+
except KeyError as e:
|
|
98
|
+
logger.error(
|
|
99
|
+
f"Memory content: {memory_dict}. Error parsing memory: {e}"
|
|
100
|
+
)
|
|
101
|
+
continue
|
|
102
|
+
except json.JSONDecodeError:
|
|
103
|
+
text = memory
|
|
104
|
+
role = "user"
|
|
105
|
+
memory_events.append(
|
|
106
|
+
MemoryEntry(
|
|
107
|
+
author="user",
|
|
108
|
+
content=types.Content(parts=[types.Part(text=text)], role=role),
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
return SearchMemoryResponse(memories=memory_events)
|
|
112
|
+
|
|
113
|
+
@override
|
|
114
|
+
async def delete_memory(self, *, app_name: str, user_id: str):
|
|
115
|
+
self.adapter.delete(
|
|
116
|
+
app_name=app_name,
|
|
117
|
+
user_id=user_id,
|
|
118
|
+
session_id="", # session_id is not used in the adapter delete method
|
|
119
|
+
)
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
Longterm memory may use different databases, so we need to create
|
|
17
|
+
an adapter to abstract the database operations.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import re
|
|
21
|
+
|
|
22
|
+
from pydantic import BaseModel, ConfigDict
|
|
23
|
+
|
|
24
|
+
from veadk.database.base_database import BaseDatabase
|
|
25
|
+
from veadk.database.database_factory import DatabaseBackend
|
|
26
|
+
from veadk.utils.logger import get_logger
|
|
27
|
+
|
|
28
|
+
logger = get_logger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def format_collection_name(collection_name: str) -> str:
|
|
32
|
+
replaced_str = re.sub(r"[- ]", "_", collection_name)
|
|
33
|
+
return re.sub(r"[^a-z0-9_]", "", replaced_str).lower()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_memory_adapter(backend: str):
|
|
37
|
+
if backend == DatabaseBackend.REDIS:
|
|
38
|
+
return MemoryKVDatabaseAdapter
|
|
39
|
+
elif backend == DatabaseBackend.MYSQL:
|
|
40
|
+
return MemoryRelationalDatabaseAdapter
|
|
41
|
+
elif backend == DatabaseBackend.OPENSEARCH:
|
|
42
|
+
return MemoryVectorDatabaseAdapter
|
|
43
|
+
elif backend == DatabaseBackend.LOCAL:
|
|
44
|
+
return MemoryLocalDatabaseAdapter
|
|
45
|
+
elif backend == DatabaseBackend.VIKING_MEM:
|
|
46
|
+
return MemoryVikingDBAdapter
|
|
47
|
+
else:
|
|
48
|
+
raise ValueError(f"Unknown backend: {backend}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class MemoryKVDatabaseAdapter(BaseModel):
|
|
52
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
53
|
+
|
|
54
|
+
database_client: BaseDatabase
|
|
55
|
+
|
|
56
|
+
def add(self, content: list[str], app_name: str, user_id: str, session_id: str):
|
|
57
|
+
"""Add texts to Redis.
|
|
58
|
+
|
|
59
|
+
Key: app_name
|
|
60
|
+
Field: app_name:user_id
|
|
61
|
+
Value: text in List
|
|
62
|
+
"""
|
|
63
|
+
key = f"{app_name}:{user_id}"
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
for _content in content:
|
|
67
|
+
self.database_client.add(key, _content)
|
|
68
|
+
logger.debug(
|
|
69
|
+
f"Successfully added {len(content)} texts to Redis list key `{key}`."
|
|
70
|
+
)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.error(f"Failed to add texts to Redis list key `{key}`: {e}")
|
|
73
|
+
raise e
|
|
74
|
+
|
|
75
|
+
def query(self, query: str, app_name: str, user_id: str):
|
|
76
|
+
key = f"{app_name}:{user_id}"
|
|
77
|
+
top_k = 10
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
result = self.database_client.query(key, query)
|
|
81
|
+
# Get latest top_k records.
|
|
82
|
+
# The data is stored in a Redis list, and the latest data is at the end of the list.
|
|
83
|
+
return result[-top_k:]
|
|
84
|
+
except Exception as e:
|
|
85
|
+
logger.error(f"Failed to search from Redis list key '{key}': {e}")
|
|
86
|
+
raise e
|
|
87
|
+
|
|
88
|
+
def delete(self, app_name: str, user_id: str, session_id: str):
|
|
89
|
+
try:
|
|
90
|
+
self.database_client.delete(
|
|
91
|
+
app_name=app_name, user_id=user_id, session_id=session_id
|
|
92
|
+
)
|
|
93
|
+
logger.info(
|
|
94
|
+
f"Successfully deleted memory data for app {app_name}, user {user_id}, session {session_id}"
|
|
95
|
+
)
|
|
96
|
+
except Exception as e:
|
|
97
|
+
logger.error(f"Failed to delete memory data: {e}")
|
|
98
|
+
raise e
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class MemoryRelationalDatabaseAdapter(BaseModel):
|
|
102
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
103
|
+
|
|
104
|
+
database_client: BaseDatabase
|
|
105
|
+
|
|
106
|
+
def create_table(self, table_name: str):
|
|
107
|
+
sql = f"""
|
|
108
|
+
CREATE TABLE `{table_name}` (
|
|
109
|
+
`id` BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
110
|
+
`data` TEXT NOT NULL,
|
|
111
|
+
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
112
|
+
) ENGINE=InnoDB DEFAULT CHARSET={self.database_client.config.charset};
|
|
113
|
+
"""
|
|
114
|
+
self.database_client.add(sql)
|
|
115
|
+
|
|
116
|
+
def add(self, content: list[str], app_name: str, user_id: str, session_id: str):
|
|
117
|
+
table = f"{app_name}_{user_id}"
|
|
118
|
+
|
|
119
|
+
if not self.database_client.table_exists(table):
|
|
120
|
+
logger.warning(f"Table {table} does not exist, creating...")
|
|
121
|
+
self.create_table(table)
|
|
122
|
+
|
|
123
|
+
for _content in content:
|
|
124
|
+
sql = f"""
|
|
125
|
+
INSERT INTO `{table}` (`data`)
|
|
126
|
+
VALUES (%s);
|
|
127
|
+
"""
|
|
128
|
+
self.database_client.add(sql, params=(_content,))
|
|
129
|
+
logger.info(f"Successfully added {len(content)} texts to table {table}.")
|
|
130
|
+
|
|
131
|
+
def query(self, query: str, app_name: str, user_id: str):
|
|
132
|
+
"""Search content from table app_name_user_id."""
|
|
133
|
+
top_k = 10
|
|
134
|
+
table = f"{app_name}_{user_id}"
|
|
135
|
+
|
|
136
|
+
if not self.database_client.table_exists(table):
|
|
137
|
+
logger.warning(
|
|
138
|
+
f"querying {query}, but table `{table}` does not exist, returning empty list."
|
|
139
|
+
)
|
|
140
|
+
return []
|
|
141
|
+
|
|
142
|
+
sql = f"""
|
|
143
|
+
SELECT `data` FROM `{table}` ORDER BY `created_at` DESC LIMIT {top_k};
|
|
144
|
+
"""
|
|
145
|
+
results = self.database_client.query(sql)
|
|
146
|
+
return [item["data"] for item in results]
|
|
147
|
+
|
|
148
|
+
def delete(self, app_name: str, user_id: str, session_id: str):
|
|
149
|
+
table = f"{app_name}_{user_id}"
|
|
150
|
+
try:
|
|
151
|
+
self.database_client.delete(table=table)
|
|
152
|
+
logger.info(f"Successfully deleted memory data from table {table}")
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.error(f"Failed to delete memory data: {e}")
|
|
155
|
+
raise e
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class MemoryVectorDatabaseAdapter(BaseModel):
|
|
159
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
160
|
+
|
|
161
|
+
database_client: BaseDatabase
|
|
162
|
+
|
|
163
|
+
def add(self, content: list[str], app_name: str, user_id: str, session_id: str):
|
|
164
|
+
collection_name = format_collection_name(f"{app_name}_{user_id}")
|
|
165
|
+
self.database_client.add(content, collection_name=collection_name)
|
|
166
|
+
|
|
167
|
+
def query(self, query: str, app_name: str, user_id: str):
|
|
168
|
+
collection_name = format_collection_name(f"{app_name}_{user_id}")
|
|
169
|
+
top_k = 10
|
|
170
|
+
return self.database_client.query(
|
|
171
|
+
query, collection_name=collection_name, top_k=top_k
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def delete(self, app_name: str, user_id: str, session_id: str):
|
|
175
|
+
collection_name = format_collection_name(f"{app_name}_{user_id}")
|
|
176
|
+
try:
|
|
177
|
+
self.database_client.delete(collection_name=collection_name)
|
|
178
|
+
logger.info(
|
|
179
|
+
f"Successfully deleted vector memory database collection for app {app_name}"
|
|
180
|
+
)
|
|
181
|
+
except Exception as e:
|
|
182
|
+
logger.error(f"Failed to delete vector memory database collection: {e}")
|
|
183
|
+
raise e
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class MemoryLocalDatabaseAdapter(BaseModel):
|
|
187
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
188
|
+
|
|
189
|
+
database_client: BaseDatabase
|
|
190
|
+
|
|
191
|
+
def add(self, content: list[str], app_name: str, user_id: str, session_id: str):
|
|
192
|
+
self.database_client.add(content)
|
|
193
|
+
|
|
194
|
+
def query(self, query: str, app_name: str, user_id: str):
|
|
195
|
+
return self.database_client.query(query)
|
|
196
|
+
|
|
197
|
+
def delete(self, app_name: str, user_id: str, session_id: str):
|
|
198
|
+
try:
|
|
199
|
+
self.database_client.delete()
|
|
200
|
+
logger.info(
|
|
201
|
+
f"Successfully cleared local memory database for app {app_name}"
|
|
202
|
+
)
|
|
203
|
+
except Exception as e:
|
|
204
|
+
logger.error(f"Failed to clear local memory database: {e}")
|
|
205
|
+
raise e
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class MemoryVikingDBAdapter(BaseModel):
|
|
209
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
210
|
+
|
|
211
|
+
database_client: BaseDatabase
|
|
212
|
+
|
|
213
|
+
def add(
|
|
214
|
+
self, content: list[str], app_name: str, user_id: str, session_id: str, **kwargs
|
|
215
|
+
):
|
|
216
|
+
kwargs.pop("user_id", None)
|
|
217
|
+
|
|
218
|
+
collection_name = format_collection_name(f"{app_name}_{user_id}")
|
|
219
|
+
self.database_client.add(
|
|
220
|
+
content, collection_name=collection_name, user_id=user_id, **kwargs
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def query(self, query: str, app_name: str, user_id: str, **kwargs):
|
|
224
|
+
kwargs.pop("user_id", None)
|
|
225
|
+
|
|
226
|
+
collection_name = format_collection_name(f"{app_name}_{user_id}")
|
|
227
|
+
result = self.database_client.query(
|
|
228
|
+
query, collection_name=collection_name, user_id=user_id, **kwargs
|
|
229
|
+
)
|
|
230
|
+
return result
|
|
231
|
+
|
|
232
|
+
def delete(self, app_name: str, user_id: str, session_id: str):
|
|
233
|
+
# collection_name = format_collection_name(f"{app_name}_{user_id}")
|
|
234
|
+
# todo: delete viking memory db
|
|
235
|
+
...
|