veadk-python 0.2.5__py3-none-any.whl → 0.2.7__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/agent.py +29 -22
- veadk/agent_builder.py +94 -0
- veadk/auth/__init__.py +13 -0
- veadk/auth/base_auth.py +22 -0
- veadk/auth/veauth/__init__.py +13 -0
- veadk/auth/veauth/apmplus_veauth.py +65 -0
- veadk/auth/veauth/ark_veauth.py +77 -0
- veadk/auth/veauth/base_veauth.py +50 -0
- veadk/auth/veauth/cozeloop_veauth.py +13 -0
- veadk/auth/veauth/prompt_pilot_veauth.py +60 -0
- veadk/auth/veauth/vesearch_veauth.py +62 -0
- veadk/cli/cli.py +2 -0
- veadk/cli/cli_deploy.py +5 -2
- veadk/cli/cli_init.py +25 -6
- veadk/cli/cli_pipeline.py +220 -0
- veadk/cli/cli_prompt.py +4 -4
- veadk/config.py +45 -81
- veadk/configs/__init__.py +13 -0
- veadk/configs/database_configs.py +83 -0
- veadk/configs/model_configs.py +42 -0
- veadk/configs/tool_configs.py +42 -0
- veadk/configs/tracing_configs.py +110 -0
- veadk/consts.py +32 -1
- veadk/database/database_adapter.py +256 -3
- veadk/database/kv/redis_database.py +47 -0
- veadk/database/local_database.py +23 -4
- veadk/database/relational/mysql_database.py +58 -0
- veadk/database/vector/opensearch_vector_database.py +6 -3
- veadk/database/viking/viking_database.py +272 -36
- veadk/integrations/ve_code_pipeline/__init__.py +13 -0
- veadk/integrations/ve_code_pipeline/ve_code_pipeline.py +431 -0
- veadk/integrations/ve_cozeloop/__init__.py +13 -0
- veadk/integrations/ve_cozeloop/ve_cozeloop.py +96 -0
- veadk/integrations/ve_cr/__init__.py +13 -0
- veadk/integrations/ve_cr/ve_cr.py +220 -0
- veadk/integrations/ve_faas/template/cookiecutter.json +3 -2
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/deploy.py +2 -2
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/agent.py +1 -1
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/app.py +24 -1
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/requirements.txt +3 -1
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/run.sh +1 -12
- veadk/integrations/ve_faas/ve_faas.py +352 -35
- veadk/integrations/ve_faas/web_template/cookiecutter.json +17 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/__init__.py +13 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/clean.py +23 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/config.yaml.example +2 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/deploy.py +41 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/Dockerfile +23 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/app.py +123 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/init_db.py +46 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/models.py +36 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/requirements.txt +4 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/run.sh +21 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/css/style.css +368 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/js/admin.js +0 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/dashboard.html +21 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/edit_post.html +24 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/login.html +21 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/posts.html +53 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/base.html +45 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/index.html +29 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/post.html +14 -0
- veadk/integrations/ve_prompt_pilot/ve_prompt_pilot.py +6 -3
- veadk/integrations/ve_tls/__init__.py +13 -0
- veadk/integrations/ve_tls/utils.py +117 -0
- veadk/integrations/ve_tls/ve_tls.py +208 -0
- veadk/integrations/ve_tos/ve_tos.py +128 -73
- veadk/knowledgebase/knowledgebase.py +116 -20
- veadk/memory/long_term_memory.py +20 -21
- veadk/memory/short_term_memory_processor.py +9 -4
- veadk/runner.py +213 -223
- veadk/tools/builtin_tools/vesearch.py +2 -2
- veadk/tools/builtin_tools/video_generate.py +27 -20
- veadk/tracing/telemetry/attributes/extractors/common_attributes_extractors.py +5 -0
- veadk/tracing/telemetry/attributes/extractors/llm_attributes_extractors.py +253 -129
- veadk/tracing/telemetry/attributes/extractors/types.py +15 -4
- veadk/tracing/telemetry/exporters/apmplus_exporter.py +158 -12
- veadk/tracing/telemetry/exporters/cozeloop_exporter.py +4 -9
- veadk/tracing/telemetry/exporters/tls_exporter.py +4 -10
- veadk/tracing/telemetry/opentelemetry_tracer.py +11 -5
- veadk/tracing/telemetry/telemetry.py +23 -5
- veadk/utils/logger.py +1 -1
- veadk/utils/misc.py +48 -0
- veadk/utils/volcengine_sign.py +6 -2
- veadk/version.py +1 -1
- {veadk_python-0.2.5.dist-info → veadk_python-0.2.7.dist-info}/METADATA +2 -1
- veadk_python-0.2.7.dist-info/RECORD +172 -0
- veadk_python-0.2.5.dist-info/RECORD +0 -127
- /veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/{{{ cookiecutter.app_name|replace('-', '_') }} → {{ cookiecutter.app_name }}}/__init__.py +0 -0
- /veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/{{{ cookiecutter.app_name|replace('-', '_') }} → {{ cookiecutter.app_name }}}/agent.py +0 -0
- {veadk_python-0.2.5.dist-info → veadk_python-0.2.7.dist-info}/WHEEL +0 -0
- {veadk_python-0.2.5.dist-info → veadk_python-0.2.7.dist-info}/entry_points.txt +0 -0
- {veadk_python-0.2.5.dist-info → veadk_python-0.2.7.dist-info}/licenses/LICENSE +0 -0
- {veadk_python-0.2.5.dist-info → veadk_python-0.2.7.dist-info}/top_level.txt +0 -0
|
@@ -12,72 +12,125 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
import os
|
|
16
|
-
from veadk.config import getenv
|
|
17
|
-
from veadk.utils.logger import get_logger
|
|
18
|
-
import tos
|
|
19
15
|
import asyncio
|
|
20
|
-
|
|
21
|
-
from pydantic import BaseModel, Field
|
|
22
|
-
from typing import Any
|
|
23
|
-
from urllib.parse import urlparse
|
|
16
|
+
import os
|
|
24
17
|
from datetime import datetime
|
|
18
|
+
from typing import TYPE_CHECKING, Union
|
|
19
|
+
from urllib.parse import urlparse
|
|
20
|
+
|
|
21
|
+
from veadk.consts import DEFAULT_TOS_BUCKET_NAME
|
|
22
|
+
from veadk.utils.logger import get_logger
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
pass
|
|
26
|
+
|
|
25
27
|
|
|
28
|
+
# Initialize logger before using it
|
|
26
29
|
logger = get_logger(__name__)
|
|
27
30
|
|
|
28
31
|
|
|
29
|
-
class
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
default_factory=lambda: getenv("DATABASE_TOS_BUCKET"),
|
|
44
|
-
description="TOS bucket name",
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
class VeTOS(BaseModel):
|
|
49
|
-
config: TOSConfig = Field(default_factory=TOSConfig)
|
|
50
|
-
|
|
51
|
-
def model_post_init(self, __context: Any) -> None:
|
|
32
|
+
class VeTOS:
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
ak: str = "",
|
|
36
|
+
sk: str = "",
|
|
37
|
+
region: str = "cn-beijing",
|
|
38
|
+
bucket_name: str = DEFAULT_TOS_BUCKET_NAME,
|
|
39
|
+
) -> None:
|
|
40
|
+
self.ak = ak if ak else os.getenv("VOLCENGINE_ACCESS_KEY", "")
|
|
41
|
+
self.sk = sk if sk else os.getenv("VOLCENGINE_SECRET_KEY", "")
|
|
42
|
+
self.region = region
|
|
43
|
+
self.bucket_name = bucket_name
|
|
44
|
+
self._tos_module = None
|
|
45
|
+
|
|
52
46
|
try:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
47
|
+
import tos
|
|
48
|
+
|
|
49
|
+
self._tos_module = tos
|
|
50
|
+
except ImportError as e:
|
|
51
|
+
logger.error(
|
|
52
|
+
"Failed to import 'tos' module. Please install it using: pip install tos\n"
|
|
58
53
|
)
|
|
59
|
-
|
|
54
|
+
raise ImportError(
|
|
55
|
+
"Missing 'tos' module. Please install it using: pip install tos\n"
|
|
56
|
+
) from e
|
|
57
|
+
|
|
58
|
+
self._client = None
|
|
59
|
+
try:
|
|
60
|
+
self._client = self._tos_module.TosClientV2(
|
|
61
|
+
ak=self.ak,
|
|
62
|
+
sk=self.sk,
|
|
63
|
+
endpoint=f"tos-{self.region}.volces.com",
|
|
64
|
+
region=self.region,
|
|
65
|
+
)
|
|
66
|
+
logger.info("Init TOS client.")
|
|
60
67
|
except Exception as e:
|
|
61
68
|
logger.error(f"Client initialization failed:{e}")
|
|
62
|
-
|
|
69
|
+
|
|
70
|
+
def _refresh_client(self):
|
|
71
|
+
try:
|
|
72
|
+
if self._client:
|
|
73
|
+
self._client.close()
|
|
74
|
+
self._client = self._tos_module.TosClientV2(
|
|
75
|
+
self.ak,
|
|
76
|
+
self.sk,
|
|
77
|
+
endpoint=f"tos-{self.region}.volces.com",
|
|
78
|
+
region=self.region,
|
|
79
|
+
)
|
|
80
|
+
logger.info("refreshed client successfully.")
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.error(f"Failed to refresh client: {str(e)}")
|
|
83
|
+
self._client = None
|
|
63
84
|
|
|
64
85
|
def create_bucket(self) -> bool:
|
|
65
|
-
"""If the bucket does not exist, create it"""
|
|
86
|
+
"""If the bucket does not exist, create it and set CORS rules"""
|
|
87
|
+
if not self._client:
|
|
88
|
+
logger.error("TOS client is not initialized")
|
|
89
|
+
return False
|
|
66
90
|
try:
|
|
67
|
-
self._client.head_bucket(self.
|
|
68
|
-
logger.info(f"Bucket {self.
|
|
69
|
-
|
|
70
|
-
except tos.exceptions.TosServerError as e:
|
|
91
|
+
self._client.head_bucket(self.bucket_name)
|
|
92
|
+
logger.info(f"Bucket {self.bucket_name} already exists")
|
|
93
|
+
except self._tos_module.exceptions.TosServerError as e:
|
|
71
94
|
if e.status_code == 404:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
95
|
+
try:
|
|
96
|
+
self._client.create_bucket(
|
|
97
|
+
bucket=self.bucket_name,
|
|
98
|
+
storage_class=self._tos_module.StorageClassType.Storage_Class_Standard,
|
|
99
|
+
acl=self._tos_module.ACLType.ACL_Public_Read,
|
|
100
|
+
)
|
|
101
|
+
logger.info(f"Bucket {self.bucket_name} created successfully")
|
|
102
|
+
self._refresh_client()
|
|
103
|
+
except Exception as create_error:
|
|
104
|
+
logger.error(f"Bucket creation failed: {str(create_error)}")
|
|
105
|
+
return False
|
|
106
|
+
else:
|
|
107
|
+
logger.error(f"Bucket check failed: {str(e)}")
|
|
108
|
+
return False
|
|
109
|
+
except Exception as e:
|
|
110
|
+
logger.error(f"Bucket check failed: {str(e)}")
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
# ensure return bool type
|
|
114
|
+
return self._set_cors_rules()
|
|
115
|
+
|
|
116
|
+
def _set_cors_rules(self) -> bool:
|
|
117
|
+
if not self._client:
|
|
118
|
+
logger.error("TOS client is not initialized")
|
|
119
|
+
return False
|
|
120
|
+
try:
|
|
121
|
+
rule = self._tos_module.models2.CORSRule(
|
|
122
|
+
allowed_origins=["*"],
|
|
123
|
+
allowed_methods=["GET", "HEAD"],
|
|
124
|
+
allowed_headers=["*"],
|
|
125
|
+
max_age_seconds=1000,
|
|
126
|
+
)
|
|
127
|
+
self._client.put_bucket_cors(self.bucket_name, [rule])
|
|
128
|
+
logger.info(f"CORS rules for bucket {self.bucket_name} set successfully")
|
|
129
|
+
return True
|
|
79
130
|
except Exception as e:
|
|
80
|
-
logger.error(
|
|
131
|
+
logger.error(
|
|
132
|
+
f"Failed to set CORS rules for bucket {self.bucket_name}: {str(e)}"
|
|
133
|
+
)
|
|
81
134
|
return False
|
|
82
135
|
|
|
83
136
|
def build_tos_url(
|
|
@@ -93,7 +146,9 @@ class VeTOS(BaseModel):
|
|
|
93
146
|
|
|
94
147
|
timestamp: str = datetime.now().strftime("%Y%m%d%H%M%S%f")[:-3]
|
|
95
148
|
object_key: str = f"{app_name}-{user_id}-{session_id}/{timestamp}-{file_name}"
|
|
96
|
-
tos_url: str =
|
|
149
|
+
tos_url: str = (
|
|
150
|
+
f"https://{self.bucket_name}.tos-{self.region}.volces.com/{object_key}"
|
|
151
|
+
)
|
|
97
152
|
|
|
98
153
|
return object_key, tos_url
|
|
99
154
|
|
|
@@ -103,57 +158,57 @@ class VeTOS(BaseModel):
|
|
|
103
158
|
data: Union[str, bytes],
|
|
104
159
|
):
|
|
105
160
|
if isinstance(data, str):
|
|
106
|
-
|
|
161
|
+
# data is a file path
|
|
162
|
+
return asyncio.to_thread(self._do_upload_file, object_key, data)
|
|
107
163
|
elif isinstance(data, bytes):
|
|
108
|
-
|
|
164
|
+
# data is bytes content
|
|
165
|
+
return asyncio.to_thread(self._do_upload_bytes, object_key, data)
|
|
109
166
|
else:
|
|
110
167
|
error_msg = f"Upload failed: data type error. Only str (file path) and bytes are supported, got {type(data)}"
|
|
111
168
|
logger.error(error_msg)
|
|
112
169
|
raise ValueError(error_msg)
|
|
113
|
-
if data_type == "file":
|
|
114
|
-
return asyncio.to_thread(self._do_upload_file, object_key, data)
|
|
115
|
-
elif data_type == "bytes":
|
|
116
|
-
return asyncio.to_thread(self._do_upload_bytes, object_key, data)
|
|
117
170
|
|
|
118
|
-
def _do_upload_bytes(self, object_key: str,
|
|
171
|
+
def _do_upload_bytes(self, object_key: str, data: bytes) -> None:
|
|
119
172
|
try:
|
|
120
173
|
if not self._client:
|
|
121
|
-
return
|
|
174
|
+
return
|
|
122
175
|
if not self.create_bucket():
|
|
123
|
-
return
|
|
176
|
+
return
|
|
124
177
|
self._client.put_object(
|
|
125
|
-
bucket=self.
|
|
178
|
+
bucket=self.bucket_name, key=object_key, content=data
|
|
126
179
|
)
|
|
127
|
-
logger.debug(f"Upload success,
|
|
180
|
+
logger.debug(f"Upload success, url: {object_key}")
|
|
128
181
|
self._close()
|
|
129
|
-
return
|
|
182
|
+
return
|
|
130
183
|
except Exception as e:
|
|
131
184
|
logger.error(f"Upload failed: {e}")
|
|
132
185
|
self._close()
|
|
133
|
-
return
|
|
186
|
+
return
|
|
134
187
|
|
|
135
|
-
def _do_upload_file(self, object_key: str, file_path: str) ->
|
|
188
|
+
def _do_upload_file(self, object_key: str, file_path: str) -> None:
|
|
136
189
|
try:
|
|
137
190
|
if not self._client:
|
|
138
|
-
return
|
|
191
|
+
return
|
|
139
192
|
if not self.create_bucket():
|
|
140
|
-
return
|
|
141
|
-
|
|
193
|
+
return
|
|
142
194
|
self._client.put_object_from_file(
|
|
143
|
-
bucket=self.
|
|
195
|
+
bucket=self.bucket_name, key=object_key, file_path=file_path
|
|
144
196
|
)
|
|
145
197
|
self._close()
|
|
146
198
|
logger.debug(f"Upload success, object_key: {object_key}")
|
|
147
|
-
return
|
|
199
|
+
return
|
|
148
200
|
except Exception as e:
|
|
149
201
|
logger.error(f"Upload failed: {e}")
|
|
150
202
|
self._close()
|
|
151
|
-
return
|
|
203
|
+
return
|
|
152
204
|
|
|
153
205
|
def download(self, object_key: str, save_path: str) -> bool:
|
|
154
206
|
"""download image from TOS"""
|
|
207
|
+
if not self._client:
|
|
208
|
+
logger.error("TOS client is not initialized")
|
|
209
|
+
return False
|
|
155
210
|
try:
|
|
156
|
-
object_stream = self._client.get_object(self.
|
|
211
|
+
object_stream = self._client.get_object(self.bucket_name, object_key)
|
|
157
212
|
|
|
158
213
|
save_dir = os.path.dirname(save_path)
|
|
159
214
|
if save_dir and not os.path.exists(save_dir):
|
|
@@ -11,11 +11,15 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
+
import io
|
|
15
|
+
import os.path
|
|
16
|
+
from typing import Any, BinaryIO, Literal, TextIO
|
|
14
17
|
|
|
15
|
-
from
|
|
18
|
+
from pydantic import BaseModel
|
|
16
19
|
|
|
17
20
|
from veadk.database.database_adapter import get_knowledgebase_database_adapter
|
|
18
21
|
from veadk.database.database_factory import DatabaseFactory
|
|
22
|
+
from veadk.utils.misc import formatted_timestamp
|
|
19
23
|
from veadk.utils.logger import get_logger
|
|
20
24
|
|
|
21
25
|
logger = get_logger(__name__)
|
|
@@ -25,23 +29,23 @@ def build_knowledgebase_index(app_name: str):
|
|
|
25
29
|
return f"veadk_kb_{app_name}"
|
|
26
30
|
|
|
27
31
|
|
|
28
|
-
class KnowledgeBase:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
top_k: int = 10,
|
|
33
|
-
db_config=None,
|
|
34
|
-
):
|
|
35
|
-
logger.info(f"Initializing knowledgebase: backend={backend} top_k={top_k}")
|
|
32
|
+
class KnowledgeBase(BaseModel):
|
|
33
|
+
backend: Literal["local", "opensearch", "viking", "redis", "mysql"] = "local"
|
|
34
|
+
top_k: int = 10
|
|
35
|
+
db_config: Any | None = None
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
def model_post_init(self, __context: Any) -> None:
|
|
38
|
+
logger.info(
|
|
39
|
+
f"Initializing knowledgebase: backend={self.backend} top_k={self.top_k}"
|
|
40
|
+
)
|
|
39
41
|
|
|
40
|
-
self.
|
|
41
|
-
|
|
42
|
+
self._db_client = DatabaseFactory.create(
|
|
43
|
+
backend=self.backend, config=self.db_config
|
|
44
|
+
)
|
|
45
|
+
self._adapter = get_knowledgebase_database_adapter(self._db_client)
|
|
42
46
|
|
|
43
47
|
logger.info(
|
|
44
|
-
f"Initialized knowledgebase: db_client={self.
|
|
48
|
+
f"Initialized knowledgebase: db_client={self._db_client.__class__.__name__} adapter={self._adapter}"
|
|
45
49
|
)
|
|
46
50
|
|
|
47
51
|
def add(
|
|
@@ -52,9 +56,16 @@ class KnowledgeBase:
|
|
|
52
56
|
):
|
|
53
57
|
"""
|
|
54
58
|
Add documents to the vector database.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
Args:
|
|
60
|
+
data (str | list[str] | TextIO | BinaryIO | bytes): The data to be added.
|
|
61
|
+
- str: A single file path. (viking only)
|
|
62
|
+
- list[str]: A list of file paths.
|
|
63
|
+
- TextIO: A file object (TextIO). (viking only) file descriptor
|
|
64
|
+
- BinaryIO: A file object (BinaryIO). (viking only) file descriptor
|
|
65
|
+
- bytes: Binary data. (viking only) binary data (f.read())
|
|
66
|
+
app_name: index name
|
|
67
|
+
**kwargs: Additional keyword arguments.
|
|
68
|
+
- file_name (str | list[str]): The file name or a list of file names (including suffix). (viking only)
|
|
58
69
|
"""
|
|
59
70
|
if self.backend != "viking" and not (
|
|
60
71
|
isinstance(data, str) or isinstance(data, list)
|
|
@@ -64,10 +75,68 @@ class KnowledgeBase:
|
|
|
64
75
|
)
|
|
65
76
|
|
|
66
77
|
index = build_knowledgebase_index(app_name)
|
|
67
|
-
|
|
68
78
|
logger.info(f"Adding documents to knowledgebase: index={index}")
|
|
69
79
|
|
|
70
|
-
self.
|
|
80
|
+
if self.backend == "viking":
|
|
81
|
+
# Case 1: Handling file paths or lists of file paths (str)
|
|
82
|
+
if isinstance(data, str) and os.path.isfile(data):
|
|
83
|
+
# Get the file name (including the suffix)
|
|
84
|
+
if "file_name" not in kwargs or not kwargs["file_name"]:
|
|
85
|
+
kwargs["file_name"] = os.path.basename(data)
|
|
86
|
+
return self._adapter.add(data=data, index=index, **kwargs)
|
|
87
|
+
# Case 2: Handling when list[str] is a full path (list[str])
|
|
88
|
+
if isinstance(data, list):
|
|
89
|
+
if all(isinstance(item, str) for item in data):
|
|
90
|
+
all_paths = all(os.path.isfile(item) for item in data)
|
|
91
|
+
all_not_paths = all(not os.path.isfile(item) for item in data)
|
|
92
|
+
if all_paths:
|
|
93
|
+
if "file_name" not in kwargs or not kwargs["file_name"]:
|
|
94
|
+
kwargs["file_name"] = [
|
|
95
|
+
os.path.basename(item) for item in data
|
|
96
|
+
]
|
|
97
|
+
return self._adapter.add(data=data, index=index, **kwargs)
|
|
98
|
+
elif (
|
|
99
|
+
not all_not_paths
|
|
100
|
+
): # Prevent the occurrence of non-existent paths
|
|
101
|
+
# There is a mixture of paths and non-paths
|
|
102
|
+
raise ValueError(
|
|
103
|
+
"Mixed file paths and content strings in list are not allowed"
|
|
104
|
+
)
|
|
105
|
+
# Case 3: Handling strings or string arrays (content) (str or list[str])
|
|
106
|
+
if isinstance(data, str) or (
|
|
107
|
+
isinstance(data, list) and all(isinstance(item, str) for item in data)
|
|
108
|
+
):
|
|
109
|
+
if "file_name" not in kwargs or not kwargs["file_name"]:
|
|
110
|
+
if isinstance(data, str):
|
|
111
|
+
kwargs["file_name"] = f"{formatted_timestamp()}.txt"
|
|
112
|
+
else: # list[str] without file_names
|
|
113
|
+
prefix_file_name = formatted_timestamp()
|
|
114
|
+
kwargs["file_name"] = [
|
|
115
|
+
f"{prefix_file_name}_{i}.txt" for i in range(len(data))
|
|
116
|
+
]
|
|
117
|
+
return self._adapter.add(data=data, index=index, **kwargs)
|
|
118
|
+
|
|
119
|
+
# Case 4: Handling binary data (bytes)
|
|
120
|
+
if isinstance(data, bytes):
|
|
121
|
+
# user must give file_name
|
|
122
|
+
if "file_name" not in kwargs:
|
|
123
|
+
raise ValueError("file_name must be provided for binary data")
|
|
124
|
+
return self._adapter.add(data=data, index=index, **kwargs)
|
|
125
|
+
|
|
126
|
+
# Case 5: Handling file objects TextIO or BinaryIO
|
|
127
|
+
if isinstance(data, (io.TextIOWrapper, io.BufferedReader)):
|
|
128
|
+
if not kwargs.get("file_name") and hasattr(data, "name"):
|
|
129
|
+
kwargs["file_name"] = os.path.basename(data.name)
|
|
130
|
+
return self._adapter.add(data=data, index=index, **kwargs)
|
|
131
|
+
# Case6: Unsupported data type
|
|
132
|
+
raise TypeError(f"Unsupported data type: {type(data)}")
|
|
133
|
+
|
|
134
|
+
if not isinstance(data, list):
|
|
135
|
+
raise TypeError(
|
|
136
|
+
f"Unsupported data type: {type(data)}. Only viking support file_path and file bytes"
|
|
137
|
+
)
|
|
138
|
+
# not viking
|
|
139
|
+
return self._adapter.add(data=data, index=index, **kwargs)
|
|
71
140
|
|
|
72
141
|
def search(self, query: str, app_name: str, top_k: int | None = None) -> list[str]:
|
|
73
142
|
top_k = self.top_k if top_k is None else top_k
|
|
@@ -76,7 +145,34 @@ class KnowledgeBase:
|
|
|
76
145
|
f"Searching knowledgebase: app_name={app_name} query={query} top_k={top_k}"
|
|
77
146
|
)
|
|
78
147
|
index = build_knowledgebase_index(app_name)
|
|
79
|
-
result = self.
|
|
148
|
+
result = self._adapter.query(query=query, index=index, top_k=top_k)
|
|
80
149
|
if len(result) == 0:
|
|
81
150
|
logger.warning(f"No documents found in knowledgebase. Query: {query}")
|
|
82
151
|
return result
|
|
152
|
+
|
|
153
|
+
def delete(self, app_name: str) -> bool:
|
|
154
|
+
index = build_knowledgebase_index(app_name)
|
|
155
|
+
return self._adapter.delete(index=index)
|
|
156
|
+
|
|
157
|
+
def delete_doc(self, app_name: str, id: str) -> bool:
|
|
158
|
+
index = build_knowledgebase_index(app_name)
|
|
159
|
+
return self._adapter.delete_doc(index=index, id=id)
|
|
160
|
+
|
|
161
|
+
def list_chunks(
|
|
162
|
+
self, app_name: str, offset: int = 0, limit: int = 100
|
|
163
|
+
) -> list[dict]:
|
|
164
|
+
index = build_knowledgebase_index(app_name)
|
|
165
|
+
return self._adapter.list_chunks(index=index, offset=offset, limit=limit)
|
|
166
|
+
|
|
167
|
+
def list_docs(self, app_name: str, offset: int = 0, limit: int = 100) -> list[dict]:
|
|
168
|
+
if self.backend == "viking":
|
|
169
|
+
index = build_knowledgebase_index(app_name)
|
|
170
|
+
return self._adapter.list_docs(index=index, offset=offset, limit=limit)
|
|
171
|
+
else:
|
|
172
|
+
raise NotImplementedError(
|
|
173
|
+
f"list_docs not supported for {self.backend}, only viking support list_docs"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
def exists(self, app_name: str) -> bool:
|
|
177
|
+
index = build_knowledgebase_index(app_name)
|
|
178
|
+
return self._adapter.index_exists(index=index)
|
veadk/memory/long_term_memory.py
CHANGED
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
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
|
+
|
|
16
17
|
import json
|
|
17
|
-
from typing import Literal
|
|
18
|
+
from typing import Any, Literal
|
|
18
19
|
|
|
19
20
|
from google.adk.events.event import Event
|
|
20
21
|
from google.adk.memory.base_memory_service import (
|
|
@@ -24,6 +25,7 @@ from google.adk.memory.base_memory_service import (
|
|
|
24
25
|
from google.adk.memory.memory_entry import MemoryEntry
|
|
25
26
|
from google.adk.sessions import Session
|
|
26
27
|
from google.genai import types
|
|
28
|
+
from pydantic import BaseModel
|
|
27
29
|
from typing_extensions import override
|
|
28
30
|
|
|
29
31
|
from veadk.database import DatabaseFactory
|
|
@@ -37,33 +39,30 @@ def build_long_term_memory_index(app_name: str, user_id: str):
|
|
|
37
39
|
return f"{app_name}_{user_id}"
|
|
38
40
|
|
|
39
41
|
|
|
40
|
-
class LongTermMemory(BaseMemoryService):
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if backend == "viking":
|
|
42
|
+
class LongTermMemory(BaseMemoryService, BaseModel):
|
|
43
|
+
backend: Literal[
|
|
44
|
+
"local", "opensearch", "redis", "mysql", "viking", "viking_mem"
|
|
45
|
+
] = "opensearch"
|
|
46
|
+
top_k: int = 5
|
|
47
|
+
|
|
48
|
+
def model_post_init(self, __context: Any) -> None:
|
|
49
|
+
if self.backend == "viking":
|
|
49
50
|
logger.warning(
|
|
50
51
|
"`viking` backend is deprecated, switching to `viking_mem` backend."
|
|
51
52
|
)
|
|
52
|
-
backend = "viking_mem"
|
|
53
|
-
self.top_k = top_k
|
|
54
|
-
self.backend = backend
|
|
53
|
+
self.backend = "viking_mem"
|
|
55
54
|
|
|
56
55
|
logger.info(
|
|
57
56
|
f"Initializing long term memory: backend={self.backend} top_k={self.top_k}"
|
|
58
57
|
)
|
|
59
58
|
|
|
60
|
-
self.
|
|
61
|
-
backend=backend,
|
|
59
|
+
self._db_client = DatabaseFactory.create(
|
|
60
|
+
backend=self.backend,
|
|
62
61
|
)
|
|
63
|
-
self.
|
|
62
|
+
self._adapter = get_long_term_memory_database_adapter(self._db_client)
|
|
64
63
|
|
|
65
64
|
logger.info(
|
|
66
|
-
f"Initialized long term memory: db_client={self.
|
|
65
|
+
f"Initialized long term memory: db_client={self._db_client.__class__.__name__} adapter={self._adapter}"
|
|
67
66
|
)
|
|
68
67
|
|
|
69
68
|
def _filter_and_convert_events(self, events: list[Event]) -> list[str]:
|
|
@@ -101,9 +100,9 @@ class LongTermMemory(BaseMemoryService):
|
|
|
101
100
|
|
|
102
101
|
# check if viking memory database, should give a user id: if/else
|
|
103
102
|
if self.backend == "viking_mem":
|
|
104
|
-
self.
|
|
103
|
+
self._adapter.add(data=event_strings, index=index, user_id=session.user_id)
|
|
105
104
|
else:
|
|
106
|
-
self.
|
|
105
|
+
self._adapter.add(data=event_strings, index=index)
|
|
107
106
|
|
|
108
107
|
logger.info(
|
|
109
108
|
f"Added {len(event_strings)} events to long term memory: index={index}"
|
|
@@ -119,11 +118,11 @@ class LongTermMemory(BaseMemoryService):
|
|
|
119
118
|
|
|
120
119
|
# user id if viking memory db
|
|
121
120
|
if self.backend == "viking_mem":
|
|
122
|
-
memory_chunks = self.
|
|
121
|
+
memory_chunks = self._adapter.query(
|
|
123
122
|
query=query, index=index, top_k=self.top_k, user_id=user_id
|
|
124
123
|
)
|
|
125
124
|
else:
|
|
126
|
-
memory_chunks = self.
|
|
125
|
+
memory_chunks = self._adapter.query(
|
|
127
126
|
query=query, index=index, top_k=self.top_k
|
|
128
127
|
)
|
|
129
128
|
|
|
@@ -20,7 +20,12 @@ from google.adk.sessions import Session
|
|
|
20
20
|
from google.genai.types import Content, Part
|
|
21
21
|
from litellm import completion
|
|
22
22
|
|
|
23
|
-
from veadk.config import
|
|
23
|
+
from veadk.config import settings
|
|
24
|
+
from veadk.consts import (
|
|
25
|
+
DEFAULT_MODEL_AGENT_API_BASE,
|
|
26
|
+
DEFAULT_MODEL_AGENT_NAME,
|
|
27
|
+
DEFAULT_MODEL_AGENT_PROVIDER,
|
|
28
|
+
)
|
|
24
29
|
from veadk.prompts.prompt_memory_processor import render_prompt
|
|
25
30
|
from veadk.utils.logger import get_logger
|
|
26
31
|
|
|
@@ -62,9 +67,9 @@ class ShortTermMemoryProcessor:
|
|
|
62
67
|
prompt = render_prompt(messages=messages)
|
|
63
68
|
|
|
64
69
|
res = completion(
|
|
65
|
-
model=
|
|
66
|
-
base_url=
|
|
67
|
-
api_key=
|
|
70
|
+
model=DEFAULT_MODEL_AGENT_PROVIDER + "/" + DEFAULT_MODEL_AGENT_NAME,
|
|
71
|
+
base_url=DEFAULT_MODEL_AGENT_API_BASE,
|
|
72
|
+
api_key=settings.model.api_key,
|
|
68
73
|
messages=[
|
|
69
74
|
{
|
|
70
75
|
"role": "user",
|