ragflow-cli-myfbee-test 0.27.2.dev0__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.
- http_client.py +182 -0
- parser.py +940 -0
- ragflow_cli.py +330 -0
- ragflow_cli_myfbee_test-0.27.2.dev0.dist-info/METADATA +150 -0
- ragflow_cli_myfbee_test-0.27.2.dev0.dist-info/RECORD +10 -0
- ragflow_cli_myfbee_test-0.27.2.dev0.dist-info/WHEEL +5 -0
- ragflow_cli_myfbee_test-0.27.2.dev0.dist-info/entry_points.txt +2 -0
- ragflow_cli_myfbee_test-0.27.2.dev0.dist-info/top_level.txt +5 -0
- ragflow_client.py +2437 -0
- user.py +76 -0
ragflow_client.py
ADDED
|
@@ -0,0 +1,2437 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
import base64
|
|
17
|
+
import json
|
|
18
|
+
import multiprocessing as mp
|
|
19
|
+
import time
|
|
20
|
+
import urllib.parse
|
|
21
|
+
from concurrent.futures import ProcessPoolExecutor, as_completed
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from Cryptodome.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
|
|
26
|
+
from Cryptodome.PublicKey import RSA
|
|
27
|
+
from http_client import HttpClient
|
|
28
|
+
from lark import Tree
|
|
29
|
+
from user import encrypt_password, login_user
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
from requests_toolbelt import MultipartEncoder
|
|
33
|
+
except Exception as e: # pragma: no cover - fallback without toolbelt
|
|
34
|
+
print(f"Fallback without belt: {e}")
|
|
35
|
+
MultipartEncoder = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def encrypt(input_string):
|
|
39
|
+
pub = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArq9XTUSeYr2+N1h3Afl/z8Dse/2yD0ZGrKwx+EEEcdsBLca9Ynmx3nIB5obmLlSfmskLpBo0UACBmB5rEjBp2Q2f3AG3Hjd4B+gNCG6BDaawuDlgANIhGnaTLrIqWrrcm4EMzJOnAOI1fgzJRsOOUEfaS318Eq9OVO3apEyCCt0lOQK6PuksduOjVxtltDav+guVAA068NrPYmRNabVKRNLJpL8w4D44sfth5RvZ3q9t+6RTArpEtc5sh5ChzvqPOzKGMXW83C95TxmXqpbK6olN4RevSfVjEAgCydH6HN6OhtOQEcnrU97r9H0iZOWwbw3pVrZiUkuRD1R56Wzs2wIDAQAB\n-----END PUBLIC KEY-----"
|
|
40
|
+
pub_key = RSA.importKey(pub)
|
|
41
|
+
cipher = Cipher_pkcs1_v1_5.new(pub_key)
|
|
42
|
+
cipher_text = cipher.encrypt(base64.b64encode(input_string.encode("utf-8")))
|
|
43
|
+
return base64.b64encode(cipher_text).decode("utf-8")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _strip_tree_value(value):
|
|
47
|
+
if isinstance(value, Tree):
|
|
48
|
+
value = value.children[0]
|
|
49
|
+
return str(value).strip("'\"")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class RAGFlowClient:
|
|
53
|
+
def __init__(self, http_client: HttpClient, server_type: str):
|
|
54
|
+
self.http_client = http_client
|
|
55
|
+
self.server_type = server_type
|
|
56
|
+
|
|
57
|
+
def login_user(self, command):
|
|
58
|
+
try:
|
|
59
|
+
response = self.http_client.request("GET", "/system/ping", use_api_base=True, auth_kind="web")
|
|
60
|
+
if response.status_code == 200 and response.content == b"pong":
|
|
61
|
+
pass
|
|
62
|
+
else:
|
|
63
|
+
print("Server is down")
|
|
64
|
+
return
|
|
65
|
+
except Exception as e:
|
|
66
|
+
print(str(e))
|
|
67
|
+
print("Can't access server for login (connection failed)")
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
email: str = command["email"]
|
|
71
|
+
user_password: str = command.get("password")
|
|
72
|
+
if not user_password:
|
|
73
|
+
import getpass
|
|
74
|
+
|
|
75
|
+
user_password = getpass.getpass("Password: ")
|
|
76
|
+
try:
|
|
77
|
+
token = login_user(self.http_client, self.server_type, email, user_password)
|
|
78
|
+
self.http_client.login_token = token
|
|
79
|
+
# Also store as api_key for API endpoint authentication
|
|
80
|
+
if self.server_type == "user":
|
|
81
|
+
self.http_client.api_key = token
|
|
82
|
+
print(f"Login user {email} successfully")
|
|
83
|
+
except Exception as e:
|
|
84
|
+
print(str(e))
|
|
85
|
+
print("Can't access server for login (connection failed)")
|
|
86
|
+
|
|
87
|
+
def ping_server(self, command):
|
|
88
|
+
iterations = command.get("iterations", 1)
|
|
89
|
+
if iterations > 1:
|
|
90
|
+
response = self.http_client.request("GET", "/system/ping", use_api_base=True, auth_kind="web", iterations=iterations)
|
|
91
|
+
return response
|
|
92
|
+
else:
|
|
93
|
+
response = self.http_client.request("GET", "/system/ping", use_api_base=True, auth_kind="web")
|
|
94
|
+
if response.status_code == 200 and response.content == b"pong":
|
|
95
|
+
print("Server is alive")
|
|
96
|
+
else:
|
|
97
|
+
print("Server is down")
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
def register_user(self, command):
|
|
101
|
+
if self.server_type != "user":
|
|
102
|
+
print("This command is only allowed in USER mode")
|
|
103
|
+
username: str = command["user_name"]
|
|
104
|
+
nickname: str = command["nickname"]
|
|
105
|
+
password: str = command["password"]
|
|
106
|
+
enc_password = encrypt_password(password)
|
|
107
|
+
print(f"Register user: {nickname}, email: {username}, password: ******")
|
|
108
|
+
payload = {"email": username, "nickname": nickname, "password": enc_password}
|
|
109
|
+
response = self.http_client.request(method="POST", path="/users", json_body=payload, use_api_base=True, auth_kind="web")
|
|
110
|
+
if response.status_code == 200:
|
|
111
|
+
res_json = response.json()
|
|
112
|
+
if res_json["code"] == 0:
|
|
113
|
+
self._print_table_simple(res_json["data"])
|
|
114
|
+
else:
|
|
115
|
+
print(f"Fail to register user {username}, code: {res_json['code']}, message: {res_json['message']}")
|
|
116
|
+
else:
|
|
117
|
+
print(f"Fail to register user {username}, code: {response.status_code}, body: {response.text}")
|
|
118
|
+
|
|
119
|
+
def list_services(self):
|
|
120
|
+
if self.server_type != "admin":
|
|
121
|
+
print("This command is only allowed in ADMIN mode")
|
|
122
|
+
|
|
123
|
+
response = self.http_client.request("GET", "/admin/services", use_api_base=True, auth_kind="admin")
|
|
124
|
+
if response.status_code == 200:
|
|
125
|
+
res_json = response.json()
|
|
126
|
+
if res_json["code"] == 0:
|
|
127
|
+
self._print_table_simple(res_json["data"])
|
|
128
|
+
else:
|
|
129
|
+
print(f"Fail to get all services, code: {res_json['code']}, message: {res_json['message']}")
|
|
130
|
+
else:
|
|
131
|
+
print(f"Fail to get all services, code: {response.status_code}, body: {response.text}")
|
|
132
|
+
|
|
133
|
+
def show_service(self, command):
|
|
134
|
+
if self.server_type != "admin":
|
|
135
|
+
print("This command is only allowed in ADMIN mode")
|
|
136
|
+
|
|
137
|
+
service_id: int = command["number"]
|
|
138
|
+
|
|
139
|
+
response = self.http_client.request("GET", f"/admin/services/{service_id}", use_api_base=True, auth_kind="admin")
|
|
140
|
+
if response.status_code == 200:
|
|
141
|
+
res_json = response.json()
|
|
142
|
+
if res_json["code"] == 0:
|
|
143
|
+
res_data = res_json["data"]
|
|
144
|
+
if "status" in res_data and res_data["status"] == "alive":
|
|
145
|
+
print(f"Service {res_data['service_name']} is alive, ")
|
|
146
|
+
res_message = res_data["message"]
|
|
147
|
+
if res_message is None:
|
|
148
|
+
return
|
|
149
|
+
elif isinstance(res_message, str):
|
|
150
|
+
print(res_message)
|
|
151
|
+
else:
|
|
152
|
+
data = self._format_service_detail_table(res_message)
|
|
153
|
+
self._print_table_simple(data)
|
|
154
|
+
else:
|
|
155
|
+
print(f"Service {res_data['service_name']} is down, {res_data['message']}")
|
|
156
|
+
else:
|
|
157
|
+
print(f"Fail to show service, code: {res_json['code']}, message: {res_json['message']}")
|
|
158
|
+
else:
|
|
159
|
+
print(f"Fail to show service, code: {response.status_code}, body: {response.text}")
|
|
160
|
+
|
|
161
|
+
def restart_service(self, command):
|
|
162
|
+
if self.server_type != "admin":
|
|
163
|
+
print("This command is only allowed in ADMIN mode")
|
|
164
|
+
|
|
165
|
+
# service_id: int = command["number"]
|
|
166
|
+
print("Restart service isn't implemented")
|
|
167
|
+
|
|
168
|
+
def shutdown_service(self, command):
|
|
169
|
+
if self.server_type != "admin":
|
|
170
|
+
print("This command is only allowed in ADMIN mode")
|
|
171
|
+
|
|
172
|
+
# service_id: int = command["number"]
|
|
173
|
+
print("Shutdown service isn't implemented")
|
|
174
|
+
|
|
175
|
+
def startup_service(self, command):
|
|
176
|
+
if self.server_type != "admin":
|
|
177
|
+
print("This command is only allowed in ADMIN mode")
|
|
178
|
+
|
|
179
|
+
# service_id: int = command["number"]
|
|
180
|
+
print("Startup service isn't implemented")
|
|
181
|
+
|
|
182
|
+
def list_users(self, command):
|
|
183
|
+
if self.server_type != "admin":
|
|
184
|
+
print("This command is only allowed in ADMIN mode")
|
|
185
|
+
|
|
186
|
+
response = self.http_client.request("GET", "/admin/users", use_api_base=True, auth_kind="admin")
|
|
187
|
+
if response.status_code == 200:
|
|
188
|
+
res_json = response.json()
|
|
189
|
+
if res_json["code"] == 0:
|
|
190
|
+
self._print_table_simple(res_json["data"])
|
|
191
|
+
else:
|
|
192
|
+
print(f"Fail to get all users, code: {res_json['code']}, message: {res_json['message']}")
|
|
193
|
+
else:
|
|
194
|
+
print(f"Fail to get all users, code: {response.status_code}, body: {response.text}")
|
|
195
|
+
|
|
196
|
+
def show_user(self, command):
|
|
197
|
+
if self.server_type != "admin":
|
|
198
|
+
print("This command is only allowed in ADMIN mode")
|
|
199
|
+
|
|
200
|
+
username_tree: Tree = command["user_name"]
|
|
201
|
+
user_name: str = username_tree.children[0].strip("'\"")
|
|
202
|
+
print(f"Showing user: {user_name}")
|
|
203
|
+
response = self.http_client.request("GET", f"/admin/users/{user_name}", use_api_base=True, auth_kind="admin")
|
|
204
|
+
if response.status_code == 200:
|
|
205
|
+
res_json = response.json()
|
|
206
|
+
if res_json["code"] == 0:
|
|
207
|
+
table_data = res_json["data"][0]
|
|
208
|
+
table_data.pop("avatar")
|
|
209
|
+
self._print_table_simple(table_data)
|
|
210
|
+
else:
|
|
211
|
+
print(f"Fail to get user {user_name}, code: {res_json['code']}, message: {res_json['message']}")
|
|
212
|
+
else:
|
|
213
|
+
print(f"Fail to get user {user_name}, code: {response.status_code}, body: {response.text}")
|
|
214
|
+
|
|
215
|
+
def drop_user(self, command):
|
|
216
|
+
if self.server_type != "admin":
|
|
217
|
+
print("This command is only allowed in ADMIN mode")
|
|
218
|
+
|
|
219
|
+
username_tree: Tree = command["user_name"]
|
|
220
|
+
user_name: str = username_tree.children[0].strip("'\"")
|
|
221
|
+
print(f"Drop user: {user_name}")
|
|
222
|
+
response = self.http_client.request("DELETE", f"/admin/users/{user_name}", use_api_base=True, auth_kind="admin")
|
|
223
|
+
if response.status_code == 200:
|
|
224
|
+
res_json = response.json()
|
|
225
|
+
if res_json["code"] == 0:
|
|
226
|
+
print(res_json["message"])
|
|
227
|
+
else:
|
|
228
|
+
print(f"Fail to drop user, code: {res_json['code']}, message: {res_json['message']}")
|
|
229
|
+
else:
|
|
230
|
+
print(f"Fail to drop user, code: {response.status_code}, body: {response.text}")
|
|
231
|
+
|
|
232
|
+
def alter_user(self, command):
|
|
233
|
+
if self.server_type != "admin":
|
|
234
|
+
print("This command is only allowed in ADMIN mode")
|
|
235
|
+
|
|
236
|
+
user_name_tree: Tree = command["user_name"]
|
|
237
|
+
user_name: str = user_name_tree.children[0].strip("'\"")
|
|
238
|
+
password_tree: Tree = command["password"]
|
|
239
|
+
password: str = password_tree.children[0].strip("'\"")
|
|
240
|
+
print(f"Alter user: {user_name}, password: ******")
|
|
241
|
+
response = self.http_client.request("PUT", f"/admin/users/{user_name}/password", json_body={"new_password": encrypt_password(password)}, use_api_base=True, auth_kind="admin")
|
|
242
|
+
if response.status_code == 200:
|
|
243
|
+
res_json = response.json()
|
|
244
|
+
if res_json["code"] == 0:
|
|
245
|
+
print(res_json["message"])
|
|
246
|
+
else:
|
|
247
|
+
print(f"Fail to alter password, code: {res_json['code']}, message: {res_json['message']}")
|
|
248
|
+
else:
|
|
249
|
+
print(f"Fail to alter password, code: {response.status_code}, body: {response.text}")
|
|
250
|
+
|
|
251
|
+
def create_user(self, command):
|
|
252
|
+
if self.server_type != "admin":
|
|
253
|
+
print("This command is only allowed in ADMIN mode")
|
|
254
|
+
|
|
255
|
+
user_name_tree: Tree = command["user_name"]
|
|
256
|
+
user_name: str = user_name_tree.children[0].strip("'\"")
|
|
257
|
+
password_tree: Tree = command["password"]
|
|
258
|
+
password: str = password_tree.children[0].strip("'\"")
|
|
259
|
+
role: str = command["role"]
|
|
260
|
+
print(f"Create user: {user_name}, password: ******, role: {role}")
|
|
261
|
+
# enpass1 = encrypt(password)
|
|
262
|
+
enc_password = encrypt_password(password)
|
|
263
|
+
response = self.http_client.request(method="POST", path="/admin/users", json_body={"username": user_name, "password": enc_password, "role": role}, use_api_base=True, auth_kind="admin")
|
|
264
|
+
if response.status_code == 200:
|
|
265
|
+
res_json = response.json()
|
|
266
|
+
if res_json["code"] == 0:
|
|
267
|
+
self._print_table_simple(res_json["data"])
|
|
268
|
+
else:
|
|
269
|
+
print(f"Fail to create user {user_name}, code: {res_json['code']}, message: {res_json['message']}")
|
|
270
|
+
else:
|
|
271
|
+
print(f"Fail to create user {user_name}, code: {response.status_code}, body: {response.text}")
|
|
272
|
+
|
|
273
|
+
def activate_user(self, command):
|
|
274
|
+
if self.server_type != "admin":
|
|
275
|
+
print("This command is only allowed in ADMIN mode")
|
|
276
|
+
|
|
277
|
+
user_name_tree: Tree = command["user_name"]
|
|
278
|
+
user_name: str = user_name_tree.children[0].strip("'\"")
|
|
279
|
+
activate_tree: Tree = command["activate_status"]
|
|
280
|
+
activate_status: str = activate_tree.children[0].strip("'\"")
|
|
281
|
+
if activate_status.lower() in ["on", "off"]:
|
|
282
|
+
print(f"Alter user {user_name} activate status, turn {activate_status.lower()}.")
|
|
283
|
+
response = self.http_client.request("PUT", f"/admin/users/{user_name}/activate", json_body={"activate_status": activate_status}, use_api_base=True, auth_kind="admin")
|
|
284
|
+
if response.status_code == 200:
|
|
285
|
+
res_json = response.json()
|
|
286
|
+
if res_json["code"] == 0:
|
|
287
|
+
print(res_json["message"])
|
|
288
|
+
else:
|
|
289
|
+
print(f"Fail to alter activate status, code: {res_json['code']}, message: {res_json['message']}")
|
|
290
|
+
else:
|
|
291
|
+
print(f"Fail to alter activate status, code: {response.status_code}, body: {response.text}")
|
|
292
|
+
else:
|
|
293
|
+
print(f"Unknown activate status: {activate_status}.")
|
|
294
|
+
|
|
295
|
+
def grant_admin(self, command):
|
|
296
|
+
if self.server_type != "admin":
|
|
297
|
+
print("This command is only allowed in ADMIN mode")
|
|
298
|
+
|
|
299
|
+
user_name_tree: Tree = command["user_name"]
|
|
300
|
+
user_name: str = user_name_tree.children[0].strip("'\"")
|
|
301
|
+
response = self.http_client.request("PUT", f"/admin/users/{user_name}/admin", use_api_base=True, auth_kind="admin")
|
|
302
|
+
if response.status_code == 200:
|
|
303
|
+
res_json = response.json()
|
|
304
|
+
if res_json["code"] == 0:
|
|
305
|
+
print(res_json["message"])
|
|
306
|
+
else:
|
|
307
|
+
print(f"Fail to grant {user_name} admin authorization, code: {res_json['code']}, message: {res_json['message']}")
|
|
308
|
+
else:
|
|
309
|
+
print(f"Fail to grant {user_name} admin authorization, code: {response.status_code}, body: {response.text}")
|
|
310
|
+
|
|
311
|
+
def revoke_admin(self, command):
|
|
312
|
+
if self.server_type != "admin":
|
|
313
|
+
print("This command is only allowed in ADMIN mode")
|
|
314
|
+
|
|
315
|
+
user_name_tree: Tree = command["user_name"]
|
|
316
|
+
user_name: str = user_name_tree.children[0].strip("'\"")
|
|
317
|
+
response = self.http_client.request("DELETE", f"/admin/users/{user_name}/admin", use_api_base=True, auth_kind="admin")
|
|
318
|
+
if response.status_code == 200:
|
|
319
|
+
res_json = response.json()
|
|
320
|
+
if res_json["code"] == 0:
|
|
321
|
+
print(res_json["message"])
|
|
322
|
+
else:
|
|
323
|
+
print(f"Fail to revoke {user_name} admin authorization, code: {res_json['code']}, message: {res_json['message']}")
|
|
324
|
+
else:
|
|
325
|
+
print(f"Fail to revoke {user_name} admin authorization, code: {response.status_code}, body: {response.text}")
|
|
326
|
+
|
|
327
|
+
def create_role(self, command):
|
|
328
|
+
if self.server_type != "admin":
|
|
329
|
+
print("This command is only allowed in ADMIN mode")
|
|
330
|
+
|
|
331
|
+
role_name_tree: Tree = command["role_name"]
|
|
332
|
+
role_name: str = role_name_tree.children[0].strip("'\"")
|
|
333
|
+
desc_str: str = ""
|
|
334
|
+
if "description" in command and command["description"] is not None:
|
|
335
|
+
desc_tree: Tree = command["description"]
|
|
336
|
+
desc_str = desc_tree.children[0].strip("'\"")
|
|
337
|
+
|
|
338
|
+
print(f"create role name: {role_name}, description: {desc_str}")
|
|
339
|
+
response = self.http_client.request("POST", "/admin/roles", json_body={"role_name": role_name, "description": desc_str}, use_api_base=True, auth_kind="admin")
|
|
340
|
+
if response.status_code == 200:
|
|
341
|
+
res_json = response.json()
|
|
342
|
+
if res_json["code"] == 0:
|
|
343
|
+
self._print_table_simple(res_json["data"])
|
|
344
|
+
else:
|
|
345
|
+
print(f"Fail to create role {role_name}, code: {res_json['code']}, message: {res_json['message']}")
|
|
346
|
+
else:
|
|
347
|
+
print(f"Fail to create role {role_name}, code: {response.status_code}, body: {response.text}")
|
|
348
|
+
|
|
349
|
+
def drop_role(self, command):
|
|
350
|
+
if self.server_type != "admin":
|
|
351
|
+
print("This command is only allowed in ADMIN mode")
|
|
352
|
+
|
|
353
|
+
role_name_tree: Tree = command["role_name"]
|
|
354
|
+
role_name: str = role_name_tree.children[0].strip("'\"")
|
|
355
|
+
print(f"drop role name: {role_name}")
|
|
356
|
+
response = self.http_client.request("DELETE", f"/admin/roles/{role_name}", use_api_base=True, auth_kind="admin")
|
|
357
|
+
if response.status_code == 200:
|
|
358
|
+
res_json = response.json()
|
|
359
|
+
if res_json["code"] == 0:
|
|
360
|
+
self._print_table_simple(res_json["data"])
|
|
361
|
+
else:
|
|
362
|
+
print(f"Fail to drop role {role_name}, code: {res_json['code']}, message: {res_json['message']}")
|
|
363
|
+
else:
|
|
364
|
+
print(f"Fail to drop role {role_name}, code: {response.status_code}, body: {response.text}")
|
|
365
|
+
|
|
366
|
+
def alter_role(self, command):
|
|
367
|
+
if self.server_type != "admin":
|
|
368
|
+
print("This command is only allowed in ADMIN mode")
|
|
369
|
+
|
|
370
|
+
role_name_tree: Tree = command["role_name"]
|
|
371
|
+
role_name: str = role_name_tree.children[0].strip("'\"")
|
|
372
|
+
desc_tree: Tree = command["description"]
|
|
373
|
+
desc_str: str = desc_tree.children[0].strip("'\"")
|
|
374
|
+
|
|
375
|
+
print(f"alter role name: {role_name}, description: {desc_str}")
|
|
376
|
+
response = self.http_client.request("PUT", f"/admin/roles/{role_name}", json_body={"description": desc_str}, use_api_base=True, auth_kind="admin")
|
|
377
|
+
if response.status_code == 200:
|
|
378
|
+
res_json = response.json()
|
|
379
|
+
if res_json["code"] == 0:
|
|
380
|
+
self._print_table_simple(res_json["data"])
|
|
381
|
+
else:
|
|
382
|
+
print(f"Fail to update role {role_name} with description: {desc_str}, code: {res_json['code']}, message: {res_json['message']}")
|
|
383
|
+
else:
|
|
384
|
+
print(f"Fail to update role {role_name} with description: {desc_str}, code: {response.status_code}, body: {response.text}")
|
|
385
|
+
|
|
386
|
+
def list_roles(self, command):
|
|
387
|
+
if self.server_type != "admin":
|
|
388
|
+
print("This command is only allowed in ADMIN mode")
|
|
389
|
+
|
|
390
|
+
response = self.http_client.request("GET", "/admin/roles", use_api_base=True, auth_kind="admin")
|
|
391
|
+
if response.status_code == 200:
|
|
392
|
+
res_json = response.json()
|
|
393
|
+
if res_json["code"] == 0:
|
|
394
|
+
self._print_table_simple(res_json["data"])
|
|
395
|
+
else:
|
|
396
|
+
print(f"Fail to list roles, code: {res_json['code']}, message: {res_json['message']}")
|
|
397
|
+
else:
|
|
398
|
+
print(f"Fail to list roles, code: {response.status_code}, body: {response.text}")
|
|
399
|
+
|
|
400
|
+
def show_role(self, command):
|
|
401
|
+
if self.server_type != "admin":
|
|
402
|
+
print("This command is only allowed in ADMIN mode")
|
|
403
|
+
|
|
404
|
+
role_name_tree: Tree = command["role_name"]
|
|
405
|
+
role_name: str = role_name_tree.children[0].strip("'\"")
|
|
406
|
+
print(f"show role: {role_name}")
|
|
407
|
+
response = self.http_client.request("GET", f"/admin/roles/{role_name}/permission", use_api_base=True, auth_kind="admin")
|
|
408
|
+
if response.status_code == 200:
|
|
409
|
+
res_json = response.json()
|
|
410
|
+
if res_json["code"] == 0:
|
|
411
|
+
self._print_table_simple(res_json["data"])
|
|
412
|
+
else:
|
|
413
|
+
print(f"Fail to list roles, code: {res_json['code']}, message: {res_json['message']}")
|
|
414
|
+
else:
|
|
415
|
+
print(f"Fail to list roles, code: {response.status_code}, body: {response.text}")
|
|
416
|
+
|
|
417
|
+
def grant_permission(self, command):
|
|
418
|
+
if self.server_type != "admin":
|
|
419
|
+
print("This command is only allowed in ADMIN mode")
|
|
420
|
+
|
|
421
|
+
role_name_tree: Tree = command["role_name"]
|
|
422
|
+
role_name_str: str = role_name_tree.children[0].strip("'\"")
|
|
423
|
+
resource_tree: Tree = command["resource"]
|
|
424
|
+
resource_str: str = resource_tree.children[0].strip("'\"")
|
|
425
|
+
action_tree_list: list = command["actions"]
|
|
426
|
+
actions: list = []
|
|
427
|
+
for action_tree in action_tree_list:
|
|
428
|
+
action_str: str = action_tree.children[0].strip("'\"")
|
|
429
|
+
actions.append(action_str)
|
|
430
|
+
print(f"grant role_name: {role_name_str}, resource: {resource_str}, actions: {actions}")
|
|
431
|
+
response = self.http_client.request("POST", f"/admin/roles/{role_name_str}/permission", json_body={"actions": actions, "resource": resource_str}, use_api_base=True, auth_kind="admin")
|
|
432
|
+
if response.status_code == 200:
|
|
433
|
+
res_json = response.json()
|
|
434
|
+
if res_json["code"] == 0:
|
|
435
|
+
self._print_table_simple(res_json["data"])
|
|
436
|
+
else:
|
|
437
|
+
print(f"Fail to grant role {role_name_str} with {actions} on {resource_str}, code: {res_json['code']}, message: {res_json['message']}")
|
|
438
|
+
else:
|
|
439
|
+
print(f"Fail to grant role {role_name_str} with {actions} on {resource_str}, code: {response.status_code}, body: {response.text}")
|
|
440
|
+
|
|
441
|
+
def revoke_permission(self, command):
|
|
442
|
+
if self.server_type != "admin":
|
|
443
|
+
print("This command is only allowed in ADMIN mode")
|
|
444
|
+
|
|
445
|
+
role_name_tree: Tree = command["role_name"]
|
|
446
|
+
role_name_str: str = role_name_tree.children[0].strip("'\"")
|
|
447
|
+
resource_tree: Tree = command["resource"]
|
|
448
|
+
resource_str: str = resource_tree.children[0].strip("'\"")
|
|
449
|
+
action_tree_list: list = command["actions"]
|
|
450
|
+
actions: list = []
|
|
451
|
+
for action_tree in action_tree_list:
|
|
452
|
+
action_str: str = action_tree.children[0].strip("'\"")
|
|
453
|
+
actions.append(action_str)
|
|
454
|
+
print(f"revoke role_name: {role_name_str}, resource: {resource_str}, actions: {actions}")
|
|
455
|
+
response = self.http_client.request("DELETE", f"/admin/roles/{role_name_str}/permission", json_body={"actions": actions, "resource": resource_str}, use_api_base=True, auth_kind="admin")
|
|
456
|
+
if response.status_code == 200:
|
|
457
|
+
res_json = response.json()
|
|
458
|
+
if res_json["code"] == 0:
|
|
459
|
+
self._print_table_simple(res_json["data"])
|
|
460
|
+
else:
|
|
461
|
+
print(f"Fail to revoke role {role_name_str} with {actions} on {resource_str}, code: {res_json['code']}, message: {res_json['message']}")
|
|
462
|
+
else:
|
|
463
|
+
print(f"Fail to revoke role {role_name_str} with {actions} on {resource_str}, code: {response.status_code}, body: {response.text}")
|
|
464
|
+
|
|
465
|
+
def alter_user_role(self, command):
|
|
466
|
+
if self.server_type != "admin":
|
|
467
|
+
print("This command is only allowed in ADMIN mode")
|
|
468
|
+
|
|
469
|
+
role_name_tree: Tree = command["role_name"]
|
|
470
|
+
role_name_str: str = role_name_tree.children[0].strip("'\"")
|
|
471
|
+
user_name_tree: Tree = command["user_name"]
|
|
472
|
+
user_name_str: str = user_name_tree.children[0].strip("'\"")
|
|
473
|
+
print(f"alter_user_role user_name: {user_name_str}, role_name: {role_name_str}")
|
|
474
|
+
response = self.http_client.request("PUT", f"/admin/users/{user_name_str}/role", json_body={"role_name": role_name_str}, use_api_base=True, auth_kind="admin")
|
|
475
|
+
if response.status_code == 200:
|
|
476
|
+
res_json = response.json()
|
|
477
|
+
if res_json["code"] == 0:
|
|
478
|
+
self._print_table_simple(res_json["data"])
|
|
479
|
+
else:
|
|
480
|
+
print(f"Fail to alter user: {user_name_str} to role {role_name_str}, code: {res_json['code']}, message: {res_json['message']}")
|
|
481
|
+
else:
|
|
482
|
+
print(f"Fail to alter user: {user_name_str} to role {role_name_str}, code: {response.status_code}, body: {response.text}")
|
|
483
|
+
|
|
484
|
+
def show_user_permission(self, command):
|
|
485
|
+
if self.server_type != "admin":
|
|
486
|
+
print("This command is only allowed in ADMIN mode")
|
|
487
|
+
|
|
488
|
+
user_name_tree: Tree = command["user_name"]
|
|
489
|
+
user_name_str: str = user_name_tree.children[0].strip("'\"")
|
|
490
|
+
print(f"show_user_permission user_name: {user_name_str}")
|
|
491
|
+
response = self.http_client.request("GET", f"/admin/users/{user_name_str}/permission", use_api_base=True, auth_kind="admin")
|
|
492
|
+
if response.status_code == 200:
|
|
493
|
+
res_json = response.json()
|
|
494
|
+
if res_json["code"] == 0:
|
|
495
|
+
self._print_table_simple(res_json["data"])
|
|
496
|
+
else:
|
|
497
|
+
print(f"Fail to show user: {user_name_str} permission, code: {res_json['code']}, message: {res_json['message']}")
|
|
498
|
+
else:
|
|
499
|
+
print(f"Fail to show user: {user_name_str} permission, code: {response.status_code}, body: {response.text}")
|
|
500
|
+
|
|
501
|
+
def generate_key(self, command: dict[str, Any]) -> None:
|
|
502
|
+
if self.server_type != "admin":
|
|
503
|
+
print("This command is only allowed in ADMIN mode")
|
|
504
|
+
|
|
505
|
+
username_tree: Tree = command["user_name"]
|
|
506
|
+
user_name: str = username_tree.children[0].strip("'\"")
|
|
507
|
+
print(f"Generating API key for user: {user_name}")
|
|
508
|
+
response = self.http_client.request("POST", f"/admin/users/{user_name}/keys", use_api_base=True, auth_kind="admin")
|
|
509
|
+
if response.status_code == 200:
|
|
510
|
+
res_json: dict[str, Any] = response.json()
|
|
511
|
+
if res_json["code"] == 0:
|
|
512
|
+
self._print_table_simple(res_json["data"])
|
|
513
|
+
else:
|
|
514
|
+
print(f"Failed to generate key for user {user_name}, code: {res_json['code']}, message: {res_json['message']}")
|
|
515
|
+
else:
|
|
516
|
+
print(f"Failed to generate key for user {user_name}, code: {response.status_code}, body: {response.text}")
|
|
517
|
+
|
|
518
|
+
def list_keys(self, command: dict[str, Any]) -> None:
|
|
519
|
+
if self.server_type != "admin":
|
|
520
|
+
print("This command is only allowed in ADMIN mode")
|
|
521
|
+
|
|
522
|
+
username_tree: Tree = command["user_name"]
|
|
523
|
+
user_name: str = username_tree.children[0].strip("'\"")
|
|
524
|
+
print(f"Listing API keys for user: {user_name}")
|
|
525
|
+
response = self.http_client.request("GET", f"/admin/users/{user_name}/keys", use_api_base=True, auth_kind="admin")
|
|
526
|
+
if response.status_code == 200:
|
|
527
|
+
res_json: dict[str, Any] = response.json()
|
|
528
|
+
if res_json["code"] == 0:
|
|
529
|
+
self._print_table_simple(res_json["data"])
|
|
530
|
+
else:
|
|
531
|
+
print(f"Failed to list keys for user {user_name}, code: {res_json['code']}, message: {res_json['message']}")
|
|
532
|
+
else:
|
|
533
|
+
print(f"Failed to list keys for user {user_name}, code: {response.status_code}, body: {response.text}")
|
|
534
|
+
|
|
535
|
+
def drop_key(self, command: dict[str, Any]) -> None:
|
|
536
|
+
if self.server_type != "admin":
|
|
537
|
+
print("This command is only allowed in ADMIN mode")
|
|
538
|
+
|
|
539
|
+
key_tree: Tree = command["key"]
|
|
540
|
+
key: str = key_tree.children[0].strip("'\"")
|
|
541
|
+
username_tree: Tree = command["user_name"]
|
|
542
|
+
user_name: str = username_tree.children[0].strip("'\"")
|
|
543
|
+
print(f"Dropping API key for user: {user_name}")
|
|
544
|
+
# URL encode the key to handle special characters
|
|
545
|
+
encoded_key: str = urllib.parse.quote(key, safe="")
|
|
546
|
+
response = self.http_client.request("DELETE", f"/admin/users/{user_name}/keys/{encoded_key}", use_api_base=True, auth_kind="admin")
|
|
547
|
+
if response.status_code == 200:
|
|
548
|
+
res_json: dict[str, Any] = response.json()
|
|
549
|
+
if res_json["code"] == 0:
|
|
550
|
+
print(res_json["message"])
|
|
551
|
+
else:
|
|
552
|
+
print(f"Failed to drop key for user {user_name}, code: {res_json['code']}, message: {res_json['message']}")
|
|
553
|
+
else:
|
|
554
|
+
print(f"Failed to drop key for user {user_name}, code: {response.status_code}, body: {response.text}")
|
|
555
|
+
|
|
556
|
+
def set_variable(self, command):
|
|
557
|
+
if self.server_type != "admin":
|
|
558
|
+
print("This command is only allowed in ADMIN mode")
|
|
559
|
+
|
|
560
|
+
var_name = _strip_tree_value(command["var_name"])
|
|
561
|
+
var_value = _strip_tree_value(command["var_value"])
|
|
562
|
+
response = self.http_client.request("PUT", "/admin/variables", json_body={"var_name": var_name, "var_value": var_value}, use_api_base=True, auth_kind="admin")
|
|
563
|
+
if response.status_code == 200:
|
|
564
|
+
res_json = response.json()
|
|
565
|
+
if res_json["code"] == 0:
|
|
566
|
+
print(res_json["message"])
|
|
567
|
+
else:
|
|
568
|
+
print(f"Fail to set variable {var_name} to {var_value}, code: {res_json['code']}, message: {res_json['message']}")
|
|
569
|
+
else:
|
|
570
|
+
print(f"Fail to set variable {var_name} to {var_value}, code: {response.status_code}, body: {response.text}")
|
|
571
|
+
|
|
572
|
+
def show_variable(self, command):
|
|
573
|
+
if self.server_type != "admin":
|
|
574
|
+
print("This command is only allowed in ADMIN mode")
|
|
575
|
+
|
|
576
|
+
var_name = _strip_tree_value(command["var_name"])
|
|
577
|
+
response = self.http_client.request(method="GET", path="/admin/variables", json_body={"var_name": var_name}, use_api_base=True, auth_kind="admin")
|
|
578
|
+
if response.status_code == 200:
|
|
579
|
+
res_json = response.json()
|
|
580
|
+
if res_json["code"] == 0:
|
|
581
|
+
self._print_table_simple(res_json["data"])
|
|
582
|
+
else:
|
|
583
|
+
print(f"Fail to get variable {var_name}, code: {res_json['code']}, message: {res_json['message']}")
|
|
584
|
+
else:
|
|
585
|
+
print(f"Fail to get variable {var_name}, code: {response.status_code}, body: {response.text}")
|
|
586
|
+
|
|
587
|
+
def list_variables(self, command):
|
|
588
|
+
if self.server_type != "admin":
|
|
589
|
+
print("This command is only allowed in ADMIN mode")
|
|
590
|
+
|
|
591
|
+
response = self.http_client.request("GET", "/admin/variables", use_api_base=True, auth_kind="admin")
|
|
592
|
+
if response.status_code == 200:
|
|
593
|
+
res_json = response.json()
|
|
594
|
+
if res_json["code"] == 0:
|
|
595
|
+
self._print_table_simple(res_json["data"])
|
|
596
|
+
else:
|
|
597
|
+
print(f"Fail to list variables, code: {res_json['code']}, message: {res_json['message']}")
|
|
598
|
+
else:
|
|
599
|
+
print(f"Fail to list variables, code: {response.status_code}, body: {response.text}")
|
|
600
|
+
|
|
601
|
+
def list_configs(self, command):
|
|
602
|
+
if self.server_type != "admin":
|
|
603
|
+
print("This command is only allowed in ADMIN mode")
|
|
604
|
+
|
|
605
|
+
response = self.http_client.request("GET", "/admin/configs", use_api_base=True, auth_kind="admin")
|
|
606
|
+
if response.status_code == 200:
|
|
607
|
+
res_json = response.json()
|
|
608
|
+
if res_json["code"] == 0:
|
|
609
|
+
self._print_table_simple(res_json["data"])
|
|
610
|
+
else:
|
|
611
|
+
print(f"Fail to list variables, code: {res_json['code']}, message: {res_json['message']}")
|
|
612
|
+
else:
|
|
613
|
+
print(f"Fail to list variables, code: {response.status_code}, body: {response.text}")
|
|
614
|
+
|
|
615
|
+
def list_environments(self, command):
|
|
616
|
+
if self.server_type != "admin":
|
|
617
|
+
print("This command is only allowed in ADMIN mode")
|
|
618
|
+
|
|
619
|
+
response = self.http_client.request("GET", "/admin/environments", use_api_base=True, auth_kind="admin")
|
|
620
|
+
if response.status_code == 200:
|
|
621
|
+
res_json = response.json()
|
|
622
|
+
if res_json["code"] == 0:
|
|
623
|
+
self._print_table_simple(res_json["data"])
|
|
624
|
+
else:
|
|
625
|
+
print(f"Fail to list variables, code: {res_json['code']}, message: {res_json['message']}")
|
|
626
|
+
else:
|
|
627
|
+
print(f"Fail to list variables, code: {response.status_code}, body: {response.text}")
|
|
628
|
+
|
|
629
|
+
def show_fingerprint(self, command):
|
|
630
|
+
if self.server_type != "admin":
|
|
631
|
+
print("This command is only allowed in ADMIN mode")
|
|
632
|
+
response = self.http_client.request("GET", "/admin/fingerprint", use_api_base=True, auth_kind="admin")
|
|
633
|
+
if response.status_code == 200:
|
|
634
|
+
res_json = response.json()
|
|
635
|
+
if res_json["code"] == 0:
|
|
636
|
+
self._print_table_simple(res_json["data"])
|
|
637
|
+
else:
|
|
638
|
+
print(f"Fail to show fingerprint, code: {res_json['code']}, message: {res_json['message']}")
|
|
639
|
+
else:
|
|
640
|
+
print(f"Fail to show fingerprint, code: {response.status_code}, body: {response.text}")
|
|
641
|
+
|
|
642
|
+
def set_license(self, command):
|
|
643
|
+
if self.server_type != "admin":
|
|
644
|
+
print("This command is only allowed in ADMIN mode")
|
|
645
|
+
license = command["license"]
|
|
646
|
+
response = self.http_client.request("POST", "/admin/license", json_body={"license": license}, use_api_base=True, auth_kind="admin")
|
|
647
|
+
if response.status_code == 200:
|
|
648
|
+
res_json = response.json()
|
|
649
|
+
if res_json["code"] == 0:
|
|
650
|
+
print("Set license successfully")
|
|
651
|
+
else:
|
|
652
|
+
print(f"Fail to set license, code: {res_json['code']}, message: {res_json['message']}")
|
|
653
|
+
else:
|
|
654
|
+
print(f"Fail to set license, code: {response.status_code}, body: {response.text}")
|
|
655
|
+
|
|
656
|
+
def set_license_config(self, command):
|
|
657
|
+
if self.server_type != "admin":
|
|
658
|
+
print("This command is only allowed in ADMIN mode")
|
|
659
|
+
value1 = command["value1"]
|
|
660
|
+
value2 = command["value2"]
|
|
661
|
+
response = self.http_client.request("POST", "/admin/license/config", json_body={"value1": value1, "value2": value2}, use_api_base=True, auth_kind="admin")
|
|
662
|
+
if response.status_code == 200:
|
|
663
|
+
res_json = response.json()
|
|
664
|
+
if res_json["code"] == 0:
|
|
665
|
+
print("Set license successfully")
|
|
666
|
+
else:
|
|
667
|
+
print(f"Fail to set license, code: {res_json['code']}, message: {res_json['message']}")
|
|
668
|
+
else:
|
|
669
|
+
print(f"Fail to set license, code: {response.status_code}, body: {response.text}")
|
|
670
|
+
|
|
671
|
+
def show_license(self, command):
|
|
672
|
+
if self.server_type != "admin":
|
|
673
|
+
print("This command is only allowed in ADMIN mode")
|
|
674
|
+
response = self.http_client.request("GET", "/admin/license", use_api_base=True, auth_kind="admin")
|
|
675
|
+
if response.status_code == 200:
|
|
676
|
+
res_json = response.json()
|
|
677
|
+
if res_json["code"] == 0:
|
|
678
|
+
self._print_table_simple(res_json["data"])
|
|
679
|
+
else:
|
|
680
|
+
print(f"Fail to show license, code: {res_json['code']}, message: {res_json['message']}")
|
|
681
|
+
else:
|
|
682
|
+
print(f"Fail to show license, code: {response.status_code}, body: {response.text}")
|
|
683
|
+
|
|
684
|
+
def check_license(self, command):
|
|
685
|
+
if self.server_type != "admin":
|
|
686
|
+
print("This command is only allowed in ADMIN mode")
|
|
687
|
+
response = self.http_client.request("GET", "/admin/license?check=true", use_api_base=True, auth_kind="admin")
|
|
688
|
+
if response.status_code == 200:
|
|
689
|
+
res_json = response.json()
|
|
690
|
+
if res_json["code"] == 320:
|
|
691
|
+
print(res_json["data"])
|
|
692
|
+
else:
|
|
693
|
+
print(f"Invalid, code: {res_json['code']}, message: {res_json['data']}")
|
|
694
|
+
else:
|
|
695
|
+
print(f"Fail to check license, code: {response.status_code}, body: {response.text}")
|
|
696
|
+
|
|
697
|
+
def list_server_configs(self, command):
|
|
698
|
+
"""List server configs by calling /system/configs API and flattening the JSON response."""
|
|
699
|
+
response = self.http_client.request("GET", "/system/configs", use_api_base=False, auth_kind="web")
|
|
700
|
+
if response.status_code != 200:
|
|
701
|
+
print(f"Fail to list server configs, code: {response.status_code}, body: {response.text}")
|
|
702
|
+
return
|
|
703
|
+
res_json = response.json()
|
|
704
|
+
if res_json.get("code") != 0:
|
|
705
|
+
print(f"Fail to list server configs, code: {res_json.get('code')}, message: {res_json.get('message')}")
|
|
706
|
+
return
|
|
707
|
+
|
|
708
|
+
data = res_json.get("data", {})
|
|
709
|
+
if not data:
|
|
710
|
+
print("No server configs found")
|
|
711
|
+
return
|
|
712
|
+
|
|
713
|
+
# Flatten nested JSON with a.b.c notation
|
|
714
|
+
def flatten(obj, parent_key=""):
|
|
715
|
+
items = []
|
|
716
|
+
if isinstance(obj, dict):
|
|
717
|
+
for k, v in obj.items():
|
|
718
|
+
new_key = f"{parent_key}.{k}" if parent_key else k
|
|
719
|
+
if isinstance(v, (dict, list)) and v:
|
|
720
|
+
items.extend(flatten(v, new_key))
|
|
721
|
+
else:
|
|
722
|
+
items.append({"name": new_key, "value": v})
|
|
723
|
+
elif isinstance(obj, list):
|
|
724
|
+
for i, v in enumerate(obj):
|
|
725
|
+
new_key = f"{parent_key}[{i}]"
|
|
726
|
+
if isinstance(v, (dict, list)) and v:
|
|
727
|
+
items.extend(flatten(v, new_key))
|
|
728
|
+
else:
|
|
729
|
+
items.append({"name": new_key, "value": v})
|
|
730
|
+
return items
|
|
731
|
+
|
|
732
|
+
# Reconstruct flattened data and print using _print_table_simple
|
|
733
|
+
flattened = flatten(data)
|
|
734
|
+
self._print_table_simple(flattened)
|
|
735
|
+
|
|
736
|
+
def handle_list_datasets(self, command):
|
|
737
|
+
if self.server_type != "admin":
|
|
738
|
+
print("This command is only allowed in ADMIN mode")
|
|
739
|
+
|
|
740
|
+
username_tree: Tree = command["user_name"]
|
|
741
|
+
user_name: str = username_tree.children[0].strip("'\"")
|
|
742
|
+
print(f"Listing all datasets of user: {user_name}")
|
|
743
|
+
|
|
744
|
+
response = self.http_client.request("GET", f"/admin/users/{user_name}/datasets", use_api_base=True, auth_kind="admin")
|
|
745
|
+
if response.status_code == 200:
|
|
746
|
+
res_json = response.json()
|
|
747
|
+
if res_json["code"] == 0:
|
|
748
|
+
table_data = res_json["data"]
|
|
749
|
+
for t in table_data:
|
|
750
|
+
t.pop("avatar")
|
|
751
|
+
self._print_table_simple(table_data)
|
|
752
|
+
else:
|
|
753
|
+
print(f"Fail to get all datasets of {user_name}, code: {res_json['code']}, message: {res_json['message']}")
|
|
754
|
+
else:
|
|
755
|
+
print(f"Fail to get all datasets of {user_name}, code: {response.status_code}, body: {response.text}")
|
|
756
|
+
|
|
757
|
+
def handle_list_agents(self, command):
|
|
758
|
+
if self.server_type != "admin":
|
|
759
|
+
print("This command is only allowed in ADMIN mode")
|
|
760
|
+
|
|
761
|
+
username_tree: Tree = command["user_name"]
|
|
762
|
+
user_name: str = username_tree.children[0].strip("'\"")
|
|
763
|
+
print(f"Listing all agents of user: {user_name}")
|
|
764
|
+
response = self.http_client.request("GET", f"/admin/users/{user_name}/agents", use_api_base=True, auth_kind="admin")
|
|
765
|
+
if response.status_code == 200:
|
|
766
|
+
res_json = response.json()
|
|
767
|
+
if res_json["code"] == 0:
|
|
768
|
+
table_data = res_json["data"]
|
|
769
|
+
for t in table_data:
|
|
770
|
+
t.pop("avatar")
|
|
771
|
+
self._print_table_simple(table_data)
|
|
772
|
+
else:
|
|
773
|
+
print(f"Fail to get all agents of {user_name}, code: {res_json['code']}, message: {res_json['message']}")
|
|
774
|
+
else:
|
|
775
|
+
print(f"Fail to get all agents of {user_name}, code: {response.status_code}, body: {response.text}")
|
|
776
|
+
|
|
777
|
+
def show_current_user(self, command):
|
|
778
|
+
if self.server_type != "user":
|
|
779
|
+
print("This command is only allowed in USER mode")
|
|
780
|
+
print("show current user")
|
|
781
|
+
|
|
782
|
+
def create_model_provider(self, command):
|
|
783
|
+
if self.server_type != "user":
|
|
784
|
+
print("This command is only allowed in USER mode")
|
|
785
|
+
return
|
|
786
|
+
provider_name: str = command["provider_name"]
|
|
787
|
+
api_key: str = command["provider_key"]
|
|
788
|
+
|
|
789
|
+
# Step 1: Add provider
|
|
790
|
+
provider_payload = {"provider_name": provider_name}
|
|
791
|
+
provider_response = self.http_client.request("PUT", "/providers", json_body=provider_payload, use_api_base=True, auth_kind="web")
|
|
792
|
+
if provider_response.status_code == 200:
|
|
793
|
+
provider_res = provider_response.json()
|
|
794
|
+
if provider_res.get("code") == 0:
|
|
795
|
+
print(f"Success to add provider {provider_name}")
|
|
796
|
+
else:
|
|
797
|
+
msg = provider_res.get("message", "")
|
|
798
|
+
if "duplicated" in msg.lower() or "already exist" in msg.lower():
|
|
799
|
+
print(f"Note: provider {provider_name} already exists, continuing to add instance")
|
|
800
|
+
else:
|
|
801
|
+
print(f"Fail to add provider {provider_name}, code: {provider_res.get('code')}, message: {msg}")
|
|
802
|
+
return
|
|
803
|
+
|
|
804
|
+
else:
|
|
805
|
+
print(f"Fail to add provider {provider_name}, code: {provider_response.status_code}, body: {provider_response.text}")
|
|
806
|
+
return
|
|
807
|
+
# Step 2: Add instance
|
|
808
|
+
instance_payload = {"instance_name": "default", "api_key": api_key, "region": "default", "base_url": ""}
|
|
809
|
+
instance_response = self.http_client.request("POST", f"/providers/{provider_name}/instances", json_body=instance_payload, use_api_base=True, auth_kind="web")
|
|
810
|
+
if instance_response.status_code == 200:
|
|
811
|
+
instance_res = instance_response.json()
|
|
812
|
+
if instance_res.get("code") == 0:
|
|
813
|
+
print(f"Success to add instance for provider {provider_name}")
|
|
814
|
+
else:
|
|
815
|
+
msg = instance_res.get("message", "")
|
|
816
|
+
if "already exist" in msg.lower():
|
|
817
|
+
print(f"Note: instance for provider {provider_name} already exists, skipping")
|
|
818
|
+
else:
|
|
819
|
+
print(f"Fail to add instance for provider {provider_name}, code: {instance_res.get('code')}, message: {msg}")
|
|
820
|
+
|
|
821
|
+
else:
|
|
822
|
+
print(f"Fail to add instance for provider {provider_name}, code: {instance_response.status_code}, body: {instance_response.text}")
|
|
823
|
+
|
|
824
|
+
def drop_model_provider(self, command):
|
|
825
|
+
if self.server_type != "user":
|
|
826
|
+
print("This command is only allowed in USER mode")
|
|
827
|
+
return
|
|
828
|
+
provider_name: str = command["provider_name"]
|
|
829
|
+
response = self.http_client.request("DELETE", f"/providers/{provider_name}", use_api_base=True, auth_kind="web")
|
|
830
|
+
if response.status_code == 200:
|
|
831
|
+
res_json = response.json()
|
|
832
|
+
if res_json.get("code") == 0:
|
|
833
|
+
print(f"Success to drop model provider {provider_name}")
|
|
834
|
+
else:
|
|
835
|
+
print(f"Fail to drop model provider {provider_name}, code: {res_json.get('code')}, message: {res_json.get('message')}")
|
|
836
|
+
|
|
837
|
+
else:
|
|
838
|
+
print(f"Fail to drop model provider {provider_name}, code: {response.status_code}, body: {response.text}")
|
|
839
|
+
|
|
840
|
+
# Mapping from legacy model_type keys to API model_type values
|
|
841
|
+
_MODEL_TYPE_MAP = {
|
|
842
|
+
"llm_id": "chat",
|
|
843
|
+
"embd_id": "embedding",
|
|
844
|
+
"img2txt_id": "vision",
|
|
845
|
+
"reranker_id": "rerank",
|
|
846
|
+
"asr_id": "asr",
|
|
847
|
+
"tts_id": "tts",
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
def set_default_model(self, command):
|
|
851
|
+
if self.server_type != "user":
|
|
852
|
+
print("This command is only allowed in USER mode")
|
|
853
|
+
return
|
|
854
|
+
|
|
855
|
+
model_type_key: str = command["model_type"]
|
|
856
|
+
model_id: str = command["model_id"]
|
|
857
|
+
|
|
858
|
+
model_type = self._MODEL_TYPE_MAP.get(model_type_key)
|
|
859
|
+
if model_type is None:
|
|
860
|
+
print(f"Unknown model type: {model_type_key}")
|
|
861
|
+
return
|
|
862
|
+
|
|
863
|
+
model_name, model_instance, model_provider = self._parse_model_id(model_id)
|
|
864
|
+
|
|
865
|
+
payload = {
|
|
866
|
+
"model_provider": model_provider,
|
|
867
|
+
"model_instance": model_instance,
|
|
868
|
+
"model_type": model_type,
|
|
869
|
+
"model_name": model_name,
|
|
870
|
+
}
|
|
871
|
+
response = self.http_client.request("PATCH", "/models/default", json_body=payload, use_api_base=True, auth_kind="web")
|
|
872
|
+
if response.status_code == 200:
|
|
873
|
+
res_json = response.json()
|
|
874
|
+
if res_json.get("code") == 0:
|
|
875
|
+
print(f"Success to set default {model_type} to {model_id}")
|
|
876
|
+
else:
|
|
877
|
+
print(f"Fail to set default {model_type}, code: {res_json.get('code')}, message: {res_json.get('message')}")
|
|
878
|
+
|
|
879
|
+
else:
|
|
880
|
+
print(f"Fail to set default {model_type}, code: {response.status_code}, body: {response.text}")
|
|
881
|
+
|
|
882
|
+
def reset_default_model(self, command):
|
|
883
|
+
if self.server_type != "user":
|
|
884
|
+
print("This command is only allowed in USER mode")
|
|
885
|
+
return
|
|
886
|
+
|
|
887
|
+
model_type_key: str = command["model_type"]
|
|
888
|
+
model_type = self._MODEL_TYPE_MAP.get(model_type_key)
|
|
889
|
+
if model_type is None:
|
|
890
|
+
print(f"Unknown model type: {model_type_key}")
|
|
891
|
+
return
|
|
892
|
+
|
|
893
|
+
payload = {"model_type": model_type}
|
|
894
|
+
response = self.http_client.request("PATCH", "/models/default", json_body=payload, use_api_base=True, auth_kind="web")
|
|
895
|
+
if response.status_code == 200:
|
|
896
|
+
res_json = response.json()
|
|
897
|
+
if res_json.get("code") == 0:
|
|
898
|
+
print(f"Success to reset default {model_type}")
|
|
899
|
+
else:
|
|
900
|
+
print(f"Fail to reset default {model_type}, code: {res_json.get('code')}, message: {res_json.get('message')}")
|
|
901
|
+
|
|
902
|
+
else:
|
|
903
|
+
print(f"Fail to reset default {model_type}, code: {response.status_code}, body: {response.text}")
|
|
904
|
+
|
|
905
|
+
@staticmethod
|
|
906
|
+
def _parse_model_id(model_id: str):
|
|
907
|
+
"""Parse model_id into (model_name, model_instance, model_provider).
|
|
908
|
+
|
|
909
|
+
Accepted formats:
|
|
910
|
+
- model_name@instance@provider -> (model_name, instance, provider)
|
|
911
|
+
- model_name@provider -> (model_name, "default", provider)
|
|
912
|
+
- model_name -> (model_name, "default", "")
|
|
913
|
+
"""
|
|
914
|
+
parts = model_id.split("@")
|
|
915
|
+
if len(parts) >= 3:
|
|
916
|
+
return parts[0], parts[1], parts[-1]
|
|
917
|
+
elif len(parts) == 2:
|
|
918
|
+
return parts[0], "default", parts[1]
|
|
919
|
+
else:
|
|
920
|
+
return model_id, "default", ""
|
|
921
|
+
|
|
922
|
+
def list_user_datasets(self, command):
|
|
923
|
+
if self.server_type != "user":
|
|
924
|
+
print("This command is only allowed in USER mode")
|
|
925
|
+
|
|
926
|
+
iterations = command.get("iterations", 1)
|
|
927
|
+
if iterations > 1:
|
|
928
|
+
response = self.http_client.request("GET", "/datasets", use_api_base=True, auth_kind="web", iterations=iterations)
|
|
929
|
+
return response
|
|
930
|
+
else:
|
|
931
|
+
response = self.http_client.request("GET", "/datasets", use_api_base=True, auth_kind="web")
|
|
932
|
+
if response.status_code == 200:
|
|
933
|
+
res_json = response.json()
|
|
934
|
+
if res_json["code"] == 0:
|
|
935
|
+
self._print_table_simple(res_json["data"])
|
|
936
|
+
else:
|
|
937
|
+
print(f"Fail to list datasets, code: {res_json['code']}, message: {res_json['message']}")
|
|
938
|
+
else:
|
|
939
|
+
print(f"Fail to list datasets, code: {response.status_code}, body: {response.text}")
|
|
940
|
+
return None
|
|
941
|
+
|
|
942
|
+
def create_user_dataset(self, command):
|
|
943
|
+
if self.server_type != "user":
|
|
944
|
+
print("This command is only allowed in USER mode")
|
|
945
|
+
payload = {"name": command["dataset_name"], "embedding_model": command["embedding"]}
|
|
946
|
+
if "parser_id" in command:
|
|
947
|
+
payload["chunk_method"] = command["parser"]
|
|
948
|
+
if "pipeline" in command:
|
|
949
|
+
payload["pipeline_id"] = command["pipeline"]
|
|
950
|
+
response = self.http_client.request("POST", "/datasets", json_body=payload, use_api_base=True, auth_kind="web")
|
|
951
|
+
if response.status_code == 200:
|
|
952
|
+
res_json = response.json()
|
|
953
|
+
if res_json["code"] == 0:
|
|
954
|
+
self._print_table_simple(res_json["data"])
|
|
955
|
+
else:
|
|
956
|
+
print(f"Fail to create datasets, code: {res_json['code']}, message: {res_json['message']}")
|
|
957
|
+
else:
|
|
958
|
+
print(f"Fail to create datasets, code: {response.status_code}, body: {response.text}")
|
|
959
|
+
|
|
960
|
+
def drop_user_dataset(self, command):
|
|
961
|
+
if self.server_type != "user":
|
|
962
|
+
print("This command is only allowed in USER mode")
|
|
963
|
+
|
|
964
|
+
dataset_name = command["dataset_name"]
|
|
965
|
+
dataset_id = self._get_dataset_id(dataset_name)
|
|
966
|
+
if dataset_id is None:
|
|
967
|
+
return
|
|
968
|
+
payload = {"ids": [dataset_id]}
|
|
969
|
+
response = self.http_client.request("DELETE", "/datasets", json_body=payload, use_api_base=True, auth_kind="web")
|
|
970
|
+
if response.status_code == 200:
|
|
971
|
+
res_json = response.json()
|
|
972
|
+
if res_json["code"] == 0:
|
|
973
|
+
print(f"Drop dataset {dataset_name} successfully")
|
|
974
|
+
else:
|
|
975
|
+
print(f"Fail to drop datasets, code: {res_json['code']}, message: {res_json['message']}")
|
|
976
|
+
else:
|
|
977
|
+
print(f"Fail to drop datasets, code: {response.status_code}, body: {response.text}")
|
|
978
|
+
|
|
979
|
+
def list_user_dataset_files(self, command_dict):
|
|
980
|
+
if self.server_type != "user":
|
|
981
|
+
print("This command is only allowed in USER mode")
|
|
982
|
+
|
|
983
|
+
dataset_name = command_dict["dataset_name"]
|
|
984
|
+
dataset_id = self._get_dataset_id(dataset_name)
|
|
985
|
+
if dataset_id is None:
|
|
986
|
+
return
|
|
987
|
+
|
|
988
|
+
res_json = self._list_documents(dataset_name, dataset_id)
|
|
989
|
+
if res_json is None:
|
|
990
|
+
return
|
|
991
|
+
self._print_table_simple(res_json)
|
|
992
|
+
|
|
993
|
+
def list_user_dataset_documents(self, command_dict):
|
|
994
|
+
if self.server_type != "user":
|
|
995
|
+
print("This command is only allowed in USER mode")
|
|
996
|
+
|
|
997
|
+
dataset_name = command_dict["dataset_name"]
|
|
998
|
+
dataset_id = self._get_dataset_id(dataset_name)
|
|
999
|
+
if dataset_id is None:
|
|
1000
|
+
return
|
|
1001
|
+
|
|
1002
|
+
docs = self._list_documents(dataset_name, dataset_id)
|
|
1003
|
+
if docs is None:
|
|
1004
|
+
return
|
|
1005
|
+
|
|
1006
|
+
if not docs:
|
|
1007
|
+
print(f"No documents found in dataset {dataset_name}")
|
|
1008
|
+
return
|
|
1009
|
+
|
|
1010
|
+
print(f"Documents in dataset: {dataset_name}")
|
|
1011
|
+
print("-" * 60)
|
|
1012
|
+
# Select key fields for display
|
|
1013
|
+
display_docs = []
|
|
1014
|
+
for doc in docs:
|
|
1015
|
+
meta_fields = doc.get("meta_fields", {})
|
|
1016
|
+
# Convert meta_fields dict to string for display
|
|
1017
|
+
meta_fields_str = ""
|
|
1018
|
+
if meta_fields:
|
|
1019
|
+
meta_fields_str = str(meta_fields)
|
|
1020
|
+
display_doc = {
|
|
1021
|
+
"name": doc.get("name", ""),
|
|
1022
|
+
"id": doc.get("id", ""),
|
|
1023
|
+
"size": doc.get("size", 0),
|
|
1024
|
+
"status": doc.get("status", ""),
|
|
1025
|
+
"created_at": doc.get("created_at", ""),
|
|
1026
|
+
}
|
|
1027
|
+
if meta_fields_str:
|
|
1028
|
+
display_doc["meta_fields"] = meta_fields_str
|
|
1029
|
+
display_docs.append(display_doc)
|
|
1030
|
+
self._print_table_simple(display_docs)
|
|
1031
|
+
|
|
1032
|
+
def list_user_datasets_metadata(self, command_dict):
|
|
1033
|
+
if self.server_type != "user":
|
|
1034
|
+
print("This command is only allowed in USER mode")
|
|
1035
|
+
return
|
|
1036
|
+
|
|
1037
|
+
dataset_names = command_dict["dataset_names"]
|
|
1038
|
+
valid_datasets = []
|
|
1039
|
+
for dataset_name in dataset_names:
|
|
1040
|
+
dataset_id = self._get_dataset_id(dataset_name)
|
|
1041
|
+
if dataset_id is None:
|
|
1042
|
+
print(f"Dataset not found: {dataset_name}")
|
|
1043
|
+
continue
|
|
1044
|
+
valid_datasets.append((dataset_name, dataset_id))
|
|
1045
|
+
|
|
1046
|
+
if not valid_datasets:
|
|
1047
|
+
print("No valid datasets found")
|
|
1048
|
+
return
|
|
1049
|
+
|
|
1050
|
+
dataset_ids = [dataset_id for _, dataset_id in valid_datasets]
|
|
1051
|
+
kb_ids_param = ",".join(dataset_ids)
|
|
1052
|
+
response = self.http_client.request("GET", f"/kb/get_meta?kb_ids={kb_ids_param}", use_api_base=False, auth_kind="web")
|
|
1053
|
+
if response.status_code != 200:
|
|
1054
|
+
print(f"Fail to get metadata, code: {response.status_code}, body: {response.text}")
|
|
1055
|
+
return
|
|
1056
|
+
|
|
1057
|
+
res_json = response.json()
|
|
1058
|
+
if res_json.get("code") != 0:
|
|
1059
|
+
print(f"Fail to get metadata, code: {res_json.get('code')}, message: {res_json.get('message')}")
|
|
1060
|
+
return
|
|
1061
|
+
meta = res_json.get("data", {})
|
|
1062
|
+
if not meta:
|
|
1063
|
+
print("No metadata found")
|
|
1064
|
+
return
|
|
1065
|
+
|
|
1066
|
+
table_data = []
|
|
1067
|
+
for field_name, values_dict in meta.items():
|
|
1068
|
+
for value, docs in values_dict.items():
|
|
1069
|
+
table_data.append({"field": field_name, "value": value, "doc_ids": ", ".join(docs)})
|
|
1070
|
+
self._print_table_simple(table_data)
|
|
1071
|
+
|
|
1072
|
+
def list_user_documents_metadata_summary(self, command_dict):
|
|
1073
|
+
if self.server_type != "user":
|
|
1074
|
+
print("This command is only allowed in USER mode")
|
|
1075
|
+
return
|
|
1076
|
+
|
|
1077
|
+
dataset_name = command_dict["dataset_name"]
|
|
1078
|
+
doc_ids = command_dict.get("document_ids", [])
|
|
1079
|
+
|
|
1080
|
+
kb_id = self._get_dataset_id(dataset_name)
|
|
1081
|
+
if kb_id is None:
|
|
1082
|
+
return
|
|
1083
|
+
|
|
1084
|
+
payload = {"kb_id": kb_id}
|
|
1085
|
+
if doc_ids:
|
|
1086
|
+
payload["doc_ids"] = doc_ids
|
|
1087
|
+
response = self.http_client.request("POST", "/document/metadata/summary", json_body=payload, use_api_base=False, auth_kind="web")
|
|
1088
|
+
if response.status_code == 200:
|
|
1089
|
+
res_json = response.json()
|
|
1090
|
+
if res_json["code"] == 0:
|
|
1091
|
+
summary = res_json.get("data", {}).get("summary", {})
|
|
1092
|
+
if not summary:
|
|
1093
|
+
if doc_ids:
|
|
1094
|
+
print(f"No metadata summary found for documents: {', '.join(doc_ids)}")
|
|
1095
|
+
else:
|
|
1096
|
+
print(f"No metadata summary found in dataset {dataset_name}")
|
|
1097
|
+
return
|
|
1098
|
+
if doc_ids:
|
|
1099
|
+
print(f"Metadata summary for document(s): {', '.join(doc_ids)}")
|
|
1100
|
+
else:
|
|
1101
|
+
print(f"Metadata summary for all documents in dataset: {dataset_name}")
|
|
1102
|
+
print("-" * 60)
|
|
1103
|
+
for field_name, field_info in summary.items():
|
|
1104
|
+
field_type = field_info.get("type", "unknown")
|
|
1105
|
+
values = field_info.get("values", [])
|
|
1106
|
+
print(f"\nField: {field_name} (type: {field_type})")
|
|
1107
|
+
print(f" Total unique values: {len(values)}")
|
|
1108
|
+
if values:
|
|
1109
|
+
print(" Values:")
|
|
1110
|
+
for value, count in values:
|
|
1111
|
+
print(f" {value}: {count}")
|
|
1112
|
+
else:
|
|
1113
|
+
print(f"Fail to get metadata summary, code: {res_json.get('code')}, message: {res_json.get('message')}")
|
|
1114
|
+
else:
|
|
1115
|
+
print(f"Fail to get metadata summary, code: {response.status_code}, body: {response.text}")
|
|
1116
|
+
|
|
1117
|
+
def list_user_agents(self, command):
|
|
1118
|
+
if self.server_type != "user":
|
|
1119
|
+
print("This command is only allowed in USER mode")
|
|
1120
|
+
|
|
1121
|
+
response = self.http_client.request("GET", "/canvas/list", use_api_base=False, auth_kind="web")
|
|
1122
|
+
if response.status_code == 200:
|
|
1123
|
+
res_json = response.json()
|
|
1124
|
+
if res_json["code"] == 0:
|
|
1125
|
+
self._print_table_simple(res_json["data"])
|
|
1126
|
+
else:
|
|
1127
|
+
print(f"Fail to list datasets, code: {res_json['code']}, message: {res_json['message']}")
|
|
1128
|
+
else:
|
|
1129
|
+
print(f"Fail to list datasets, code: {response.status_code}, body: {response.text}")
|
|
1130
|
+
|
|
1131
|
+
def list_user_chats(self, command):
|
|
1132
|
+
if self.server_type != "user":
|
|
1133
|
+
print("This command is only allowed in USER mode")
|
|
1134
|
+
|
|
1135
|
+
res_json = self._list_chats(command)
|
|
1136
|
+
if res_json is None:
|
|
1137
|
+
return None
|
|
1138
|
+
if "iterations" in command:
|
|
1139
|
+
# for benchmark
|
|
1140
|
+
return res_json
|
|
1141
|
+
self._print_table_simple(res_json)
|
|
1142
|
+
|
|
1143
|
+
def create_user_chat(self, command):
|
|
1144
|
+
if self.server_type != "user":
|
|
1145
|
+
print("This command is only allowed in USER mode")
|
|
1146
|
+
chat_name = command["chat_name"]
|
|
1147
|
+
default_models = self._get_default_models() or {}
|
|
1148
|
+
payload = {
|
|
1149
|
+
"name": chat_name,
|
|
1150
|
+
"description": "",
|
|
1151
|
+
"icon": "",
|
|
1152
|
+
"dataset_ids": [],
|
|
1153
|
+
"llm_setting": {},
|
|
1154
|
+
"prompt_config": {
|
|
1155
|
+
"empty_response": "",
|
|
1156
|
+
"prologue": "Hi! I'm your assistant. What can I do for you?",
|
|
1157
|
+
"quote": True,
|
|
1158
|
+
"keyword": False,
|
|
1159
|
+
"tts": False,
|
|
1160
|
+
"system": 'You are an intelligent assistant. Your primary function is to answer questions based strictly on the provided knowledge base.\n\n **Essential Rules:**\n - Your answer must be derived **solely** from this knowledge base: `{knowledge}`.\n - **When information is available**: Summarize the content to give a detailed answer.\n - **When information is unavailable**: Your response must contain this exact sentence: "The answer you are looking for is not found in the knowledge base!"\n - **Always consider** the entire conversation history.',
|
|
1161
|
+
"refine_multiturn": False,
|
|
1162
|
+
"use_kg": False,
|
|
1163
|
+
"reasoning": False,
|
|
1164
|
+
"parameters": [{"key": "knowledge", "optional": False}],
|
|
1165
|
+
"toc_enhance": False,
|
|
1166
|
+
},
|
|
1167
|
+
"similarity_threshold": 0.2,
|
|
1168
|
+
"top_n": 8,
|
|
1169
|
+
"top_k": 1024,
|
|
1170
|
+
"vector_similarity_weight": 0.3,
|
|
1171
|
+
"rerank_id": default_models.get("rerank_id", ""),
|
|
1172
|
+
}
|
|
1173
|
+
if default_models.get("llm_id"):
|
|
1174
|
+
payload["llm_id"] = default_models["llm_id"]
|
|
1175
|
+
|
|
1176
|
+
response = self.http_client.request(
|
|
1177
|
+
"POST",
|
|
1178
|
+
"/chats",
|
|
1179
|
+
json_body=payload,
|
|
1180
|
+
use_api_base=True,
|
|
1181
|
+
auth_kind="web",
|
|
1182
|
+
)
|
|
1183
|
+
if response.status_code == 200:
|
|
1184
|
+
res_json = response.json()
|
|
1185
|
+
if res_json["code"] == 0:
|
|
1186
|
+
print(f"Success to create chat: {chat_name}")
|
|
1187
|
+
else:
|
|
1188
|
+
print(f"Fail to create chat {chat_name}, code: {res_json['code']}, message: {res_json['message']}")
|
|
1189
|
+
|
|
1190
|
+
else:
|
|
1191
|
+
print(f"Fail to create chat {chat_name}, code: {response.status_code}, body: {response.text}")
|
|
1192
|
+
|
|
1193
|
+
def create_dataset_table(self, command):
|
|
1194
|
+
if self.server_type != "user":
|
|
1195
|
+
print("This command is only allowed in USER mode")
|
|
1196
|
+
return
|
|
1197
|
+
dataset_name = command["dataset_name"]
|
|
1198
|
+
vector_size = command.get("vector_size")
|
|
1199
|
+
if not vector_size:
|
|
1200
|
+
print("vector_size is required")
|
|
1201
|
+
return
|
|
1202
|
+
# Get dataset ID by name
|
|
1203
|
+
dataset_id = self._get_dataset_id(dataset_name)
|
|
1204
|
+
if dataset_id is None:
|
|
1205
|
+
return
|
|
1206
|
+
# Build payload
|
|
1207
|
+
payload = {"kb_id": dataset_id, "vector_size": vector_size}
|
|
1208
|
+
# Call API
|
|
1209
|
+
response = self.http_client.request("POST", "/kb/doc_engine_table", json_body=payload, use_api_base=False, auth_kind="web")
|
|
1210
|
+
if response.status_code == 200:
|
|
1211
|
+
res_json = response.json()
|
|
1212
|
+
if res_json.get("code") == 0:
|
|
1213
|
+
print(f"Success to create table for dataset: {dataset_name}")
|
|
1214
|
+
else:
|
|
1215
|
+
print(f"Fail to create table for dataset {dataset_name}, code: {res_json.get('code')}, message: {res_json.get('message')}")
|
|
1216
|
+
|
|
1217
|
+
else:
|
|
1218
|
+
print(f"Fail to create table for dataset {dataset_name}, code: {response.status_code}, body: {response.text}")
|
|
1219
|
+
|
|
1220
|
+
def drop_dataset_table(self, command):
|
|
1221
|
+
if self.server_type != "user":
|
|
1222
|
+
print("This command is only allowed in USER mode")
|
|
1223
|
+
return
|
|
1224
|
+
dataset_name = command["dataset_name"]
|
|
1225
|
+
# Get dataset ID by name
|
|
1226
|
+
dataset_id = self._get_dataset_id(dataset_name)
|
|
1227
|
+
if dataset_id is None:
|
|
1228
|
+
return
|
|
1229
|
+
# Call API to delete table
|
|
1230
|
+
payload = {"kb_id": dataset_id}
|
|
1231
|
+
response = self.http_client.request("DELETE", "/kb/doc_engine_table", json_body=payload, use_api_base=False, auth_kind="web")
|
|
1232
|
+
if response.status_code == 200:
|
|
1233
|
+
res_json = response.json()
|
|
1234
|
+
if res_json.get("code") == 0:
|
|
1235
|
+
print(f"Success to drop table for dataset: {dataset_name}")
|
|
1236
|
+
else:
|
|
1237
|
+
print(f"Fail to drop table for dataset {dataset_name}, code: {res_json.get('code')}, message: {res_json.get('message')}")
|
|
1238
|
+
|
|
1239
|
+
else:
|
|
1240
|
+
print(f"Fail to drop table for dataset {dataset_name}, code: {response.status_code}, body: {response.text}")
|
|
1241
|
+
|
|
1242
|
+
def create_metadata_table(self, command):
|
|
1243
|
+
if self.server_type != "user":
|
|
1244
|
+
print("This command is only allowed in USER mode")
|
|
1245
|
+
return
|
|
1246
|
+
# Call API to create metadata table
|
|
1247
|
+
response = self.http_client.request("POST", "/tenant/doc_engine_metadata_table", use_api_base=False, auth_kind="web")
|
|
1248
|
+
if response.status_code == 200:
|
|
1249
|
+
res_json = response.json()
|
|
1250
|
+
if res_json.get("code") == 0:
|
|
1251
|
+
print("Success to create metadata table")
|
|
1252
|
+
else:
|
|
1253
|
+
print(f"Fail to create metadata table, code: {res_json.get('code')}, message: {res_json.get('message')}")
|
|
1254
|
+
|
|
1255
|
+
else:
|
|
1256
|
+
print(f"Fail to create metadata table, code: {response.status_code}, body: {response.text}")
|
|
1257
|
+
|
|
1258
|
+
def drop_metadata_table(self, command):
|
|
1259
|
+
if self.server_type != "user":
|
|
1260
|
+
print("This command is only allowed in USER mode")
|
|
1261
|
+
return
|
|
1262
|
+
# Call API to delete metadata table
|
|
1263
|
+
response = self.http_client.request("DELETE", "/tenant/doc_engine_metadata_table", use_api_base=False, auth_kind="web")
|
|
1264
|
+
if response.status_code == 200:
|
|
1265
|
+
res_json = response.json()
|
|
1266
|
+
if res_json.get("code") == 0:
|
|
1267
|
+
print("Success to drop metadata table")
|
|
1268
|
+
else:
|
|
1269
|
+
print(f"Fail to drop metadata table, code: {res_json.get('code')}, message: {res_json.get('message')}")
|
|
1270
|
+
|
|
1271
|
+
else:
|
|
1272
|
+
print(f"Fail to drop metadata table, code: {response.status_code}, body: {response.text}")
|
|
1273
|
+
|
|
1274
|
+
def drop_user_chat(self, command):
|
|
1275
|
+
if self.server_type != "user":
|
|
1276
|
+
print("This command is only allowed in USER mode")
|
|
1277
|
+
chat_name = command["chat_name"]
|
|
1278
|
+
res_json = self._list_chats(command)
|
|
1279
|
+
to_drop_chat_ids = []
|
|
1280
|
+
for elem in res_json:
|
|
1281
|
+
if elem["name"] == chat_name:
|
|
1282
|
+
to_drop_chat_ids.append(elem["id"])
|
|
1283
|
+
payload = {"ids": to_drop_chat_ids}
|
|
1284
|
+
response = self.http_client.request(
|
|
1285
|
+
"DELETE",
|
|
1286
|
+
"/chats",
|
|
1287
|
+
json_body=payload,
|
|
1288
|
+
use_api_base=True,
|
|
1289
|
+
auth_kind="web",
|
|
1290
|
+
)
|
|
1291
|
+
if response.status_code == 200:
|
|
1292
|
+
res_json = response.json()
|
|
1293
|
+
if res_json["code"] == 0:
|
|
1294
|
+
print(f"Success to drop chat: {chat_name}")
|
|
1295
|
+
else:
|
|
1296
|
+
print(f"Fail to drop chat {chat_name}, code: {res_json['code']}, message: {res_json['message']}")
|
|
1297
|
+
|
|
1298
|
+
else:
|
|
1299
|
+
print(f"Fail to drop chat {chat_name}, code: {response.status_code}, body: {response.text}")
|
|
1300
|
+
|
|
1301
|
+
def _get_chat_id_by_name(self, chat_name):
|
|
1302
|
+
"""Get chat (dialog) ID by name."""
|
|
1303
|
+
res_json = self._list_chats({})
|
|
1304
|
+
if res_json is None:
|
|
1305
|
+
return None
|
|
1306
|
+
for elem in res_json:
|
|
1307
|
+
if elem["name"] == chat_name:
|
|
1308
|
+
return elem["id"]
|
|
1309
|
+
print(f"Chat '{chat_name}' not found")
|
|
1310
|
+
return None
|
|
1311
|
+
|
|
1312
|
+
def _list_chat_sessions(self, dialog_id):
|
|
1313
|
+
"""List all sessions (conversations) for a given dialog."""
|
|
1314
|
+
response = self.http_client.request("GET", f"/chats/{dialog_id}/conversations", use_api_base=True, auth_kind="web")
|
|
1315
|
+
if response.status_code == 200:
|
|
1316
|
+
res_json = response.json()
|
|
1317
|
+
if res_json["code"] == 0:
|
|
1318
|
+
return res_json["data"]
|
|
1319
|
+
else:
|
|
1320
|
+
print(f"Fail to list chat sessions, code: {res_json['code']}, message: {res_json['message']}")
|
|
1321
|
+
return None
|
|
1322
|
+
|
|
1323
|
+
else:
|
|
1324
|
+
print(f"Fail to list chat sessions, code: {response.status_code}, body: {response.text}")
|
|
1325
|
+
|
|
1326
|
+
def create_chat_session(self, command):
|
|
1327
|
+
if self.server_type != "user":
|
|
1328
|
+
print("This command is only allowed in USER mode")
|
|
1329
|
+
chat_name = command["chat_name"]
|
|
1330
|
+
dialog_id = self._get_chat_id_by_name(chat_name)
|
|
1331
|
+
if dialog_id is None:
|
|
1332
|
+
return
|
|
1333
|
+
payload = {"name": "New conversation"}
|
|
1334
|
+
response = self.http_client.request("POST", f"/chats/{dialog_id}/conversations", json_body=payload, use_api_base=True, auth_kind="web")
|
|
1335
|
+
if response.status_code == 200:
|
|
1336
|
+
res_json = response.json()
|
|
1337
|
+
if res_json["code"] == 0:
|
|
1338
|
+
print(f"Success to create chat session for chat: {chat_name}")
|
|
1339
|
+
else:
|
|
1340
|
+
print(f"Fail to create chat session for chat {chat_name}, code: {res_json['code']}, message: {res_json['message']}")
|
|
1341
|
+
|
|
1342
|
+
else:
|
|
1343
|
+
print(f"Fail to create chat session for chat {chat_name}, code: {response.status_code}, body: {response.text}")
|
|
1344
|
+
|
|
1345
|
+
def drop_chat_session(self, command):
|
|
1346
|
+
if self.server_type != "user":
|
|
1347
|
+
print("This command is only allowed in USER mode")
|
|
1348
|
+
chat_name = command["chat_name"]
|
|
1349
|
+
session_id = command["session_id"]
|
|
1350
|
+
dialog_id = self._get_chat_id_by_name(chat_name)
|
|
1351
|
+
if dialog_id is None:
|
|
1352
|
+
return
|
|
1353
|
+
sessions = self._list_chat_sessions(dialog_id)
|
|
1354
|
+
if sessions is None:
|
|
1355
|
+
return
|
|
1356
|
+
to_drop_session_ids = []
|
|
1357
|
+
for session in sessions:
|
|
1358
|
+
if session["id"] == session_id:
|
|
1359
|
+
to_drop_session_ids.append(session["id"])
|
|
1360
|
+
if not to_drop_session_ids:
|
|
1361
|
+
print(f"Chat session '{session_id}' not found in chat '{chat_name}'")
|
|
1362
|
+
return
|
|
1363
|
+
payload = {"ids": to_drop_session_ids}
|
|
1364
|
+
response = self.http_client.request("DELETE", f"/chats/{dialog_id}/conversations", json_body=payload, use_api_base=True, auth_kind="web")
|
|
1365
|
+
if response.status_code == 200:
|
|
1366
|
+
res_json = response.json()
|
|
1367
|
+
if res_json["code"] == 0:
|
|
1368
|
+
print(f"Success to drop chat session '{session_id}' from chat: {chat_name}")
|
|
1369
|
+
else:
|
|
1370
|
+
print(f"Fail to drop chat session '{session_id}' from chat {chat_name}, code: {res_json['code']}, message: {res_json['message']}")
|
|
1371
|
+
|
|
1372
|
+
else:
|
|
1373
|
+
print(f"Fail to drop chat session '{session_id}' from chat {chat_name}, code: {response.status_code}, body: {response.text}")
|
|
1374
|
+
|
|
1375
|
+
def list_chat_sessions(self, command):
|
|
1376
|
+
if self.server_type != "user":
|
|
1377
|
+
print("This command is only allowed in USER mode")
|
|
1378
|
+
chat_name = command["chat_name"]
|
|
1379
|
+
dialog_id = self._get_chat_id_by_name(chat_name)
|
|
1380
|
+
if dialog_id is None:
|
|
1381
|
+
return
|
|
1382
|
+
sessions = self._list_chat_sessions(dialog_id)
|
|
1383
|
+
if sessions is None:
|
|
1384
|
+
return
|
|
1385
|
+
# Add chat_name to each session for display
|
|
1386
|
+
for session in sessions:
|
|
1387
|
+
session["chat_name"] = chat_name
|
|
1388
|
+
if "iterations" in command:
|
|
1389
|
+
# for benchmark
|
|
1390
|
+
return sessions
|
|
1391
|
+
self._print_table_simple(sessions)
|
|
1392
|
+
|
|
1393
|
+
def chat_on_session(self, command):
|
|
1394
|
+
if self.server_type != "user":
|
|
1395
|
+
print("This command is only allowed in USER mode")
|
|
1396
|
+
message = command["message"]
|
|
1397
|
+
session_id = command["session_id"]
|
|
1398
|
+
|
|
1399
|
+
# Prepare payload for completion API
|
|
1400
|
+
# Note: stream parameter is not sent, server defaults to stream=True
|
|
1401
|
+
payload = {"session_id": session_id, "messages": [{"role": "user", "content": message}]}
|
|
1402
|
+
|
|
1403
|
+
response = self.http_client.request("POST", "/chat/completions", json_body=payload, use_api_base=True, auth_kind="web", stream=True)
|
|
1404
|
+
|
|
1405
|
+
if response.status_code != 200:
|
|
1406
|
+
print(f"Fail to chat on session, status code: {response.status_code}")
|
|
1407
|
+
return
|
|
1408
|
+
|
|
1409
|
+
print("Assistant: ", end="", flush=True)
|
|
1410
|
+
full_answer = ""
|
|
1411
|
+
for line in response.iter_lines():
|
|
1412
|
+
if not line:
|
|
1413
|
+
continue
|
|
1414
|
+
line_str = line.decode("utf-8")
|
|
1415
|
+
if not line_str.startswith("data:"):
|
|
1416
|
+
continue
|
|
1417
|
+
data_str = line_str[5:].strip()
|
|
1418
|
+
if data_str == "[DONE]":
|
|
1419
|
+
break
|
|
1420
|
+
try:
|
|
1421
|
+
data_json = json.loads(data_str)
|
|
1422
|
+
if data_json.get("code") != 0:
|
|
1423
|
+
print(f"\nFail to chat on session, code: {data_json.get('code')}, message: {data_json.get('message', '')}")
|
|
1424
|
+
return
|
|
1425
|
+
# Check if it's the final message
|
|
1426
|
+
if data_json.get("data") is True:
|
|
1427
|
+
break
|
|
1428
|
+
answer = data_json.get("data", {}).get("answer", "")
|
|
1429
|
+
if answer:
|
|
1430
|
+
print(answer, end="", flush=True)
|
|
1431
|
+
full_answer += answer
|
|
1432
|
+
except json.JSONDecodeError:
|
|
1433
|
+
continue
|
|
1434
|
+
print() # Final newline
|
|
1435
|
+
|
|
1436
|
+
def list_user_model_providers(self, command):
|
|
1437
|
+
if self.server_type != "user":
|
|
1438
|
+
print("This command is only allowed in USER mode")
|
|
1439
|
+
|
|
1440
|
+
response = self.http_client.request("GET", "/llm/my_llms", use_api_base=False, auth_kind="web")
|
|
1441
|
+
if response.status_code == 200:
|
|
1442
|
+
res_json = response.json()
|
|
1443
|
+
if res_json["code"] == 0:
|
|
1444
|
+
new_input = []
|
|
1445
|
+
for key, value in res_json["data"].items():
|
|
1446
|
+
new_input.append({"model provider": key, "models": value})
|
|
1447
|
+
self._print_table_simple(new_input)
|
|
1448
|
+
else:
|
|
1449
|
+
print(f"Fail to list model provider, code: {res_json['code']}, message: {res_json['message']}")
|
|
1450
|
+
else:
|
|
1451
|
+
print(f"Fail to list model provider, code: {response.status_code}, body: {response.text}")
|
|
1452
|
+
|
|
1453
|
+
def list_user_default_models(self, command):
|
|
1454
|
+
if self.server_type != "user":
|
|
1455
|
+
print("This command is only allowed in USER mode")
|
|
1456
|
+
|
|
1457
|
+
res_json = self._get_default_models()
|
|
1458
|
+
if res_json is None:
|
|
1459
|
+
return
|
|
1460
|
+
else:
|
|
1461
|
+
new_input = []
|
|
1462
|
+
for key, value in res_json.items():
|
|
1463
|
+
if key == "asr_id" and value != "":
|
|
1464
|
+
new_input.append({"model_category": "ASR", "model_name": value})
|
|
1465
|
+
elif key == "embd_id" and value != "":
|
|
1466
|
+
new_input.append({"model_category": "Embedding", "model_name": value})
|
|
1467
|
+
elif key == "llm_id" and value != "":
|
|
1468
|
+
new_input.append({"model_category": "LLM", "model_name": value})
|
|
1469
|
+
elif key == "rerank_id" and value != "":
|
|
1470
|
+
new_input.append({"model_category": "Reranker", "model_name": value})
|
|
1471
|
+
elif key == "tts_id" and value != "":
|
|
1472
|
+
new_input.append({"model_category": "TTS", "model_name": value})
|
|
1473
|
+
elif key == "img2txt_id" and value != "":
|
|
1474
|
+
new_input.append({"model_category": "VLM", "model_name": value})
|
|
1475
|
+
else:
|
|
1476
|
+
continue
|
|
1477
|
+
self._print_table_simple(new_input)
|
|
1478
|
+
|
|
1479
|
+
def parse_dataset_docs(self, command_dict):
|
|
1480
|
+
if self.server_type != "user":
|
|
1481
|
+
print("This command is only allowed in USER mode")
|
|
1482
|
+
|
|
1483
|
+
dataset_name = command_dict["dataset_name"]
|
|
1484
|
+
dataset_id = self._get_dataset_id(dataset_name)
|
|
1485
|
+
if dataset_id is None:
|
|
1486
|
+
return
|
|
1487
|
+
|
|
1488
|
+
res_json = self._list_documents(dataset_name, dataset_id)
|
|
1489
|
+
if res_json is None:
|
|
1490
|
+
return
|
|
1491
|
+
|
|
1492
|
+
document_names = command_dict["document_names"]
|
|
1493
|
+
document_ids = []
|
|
1494
|
+
to_parse_doc_names = []
|
|
1495
|
+
for doc in res_json:
|
|
1496
|
+
doc_name = doc["name"]
|
|
1497
|
+
if doc_name in document_names:
|
|
1498
|
+
document_ids.append(doc["id"])
|
|
1499
|
+
document_names.remove(doc_name)
|
|
1500
|
+
to_parse_doc_names.append(doc_name)
|
|
1501
|
+
|
|
1502
|
+
if len(document_ids) == 0:
|
|
1503
|
+
print(f"No documents found in {dataset_name}")
|
|
1504
|
+
return
|
|
1505
|
+
|
|
1506
|
+
if len(document_names) != 0:
|
|
1507
|
+
print(f"Documents {document_names} not found in {dataset_name}")
|
|
1508
|
+
|
|
1509
|
+
payload = {"doc_ids": document_ids, "run": 1}
|
|
1510
|
+
response = self.http_client.request("POST", "/documents/ingest", json_body=payload, use_api_base=True, auth_kind="web")
|
|
1511
|
+
if response.status_code == 200:
|
|
1512
|
+
res_json = response.json()
|
|
1513
|
+
if res_json["code"] == 0:
|
|
1514
|
+
print(f"Success to parse {to_parse_doc_names} of {dataset_name}")
|
|
1515
|
+
else:
|
|
1516
|
+
print(f"Fail to parse documents {res_json.get('data', {}).get('docs')}, code: {res_json['code']}, message: {res_json['message']}")
|
|
1517
|
+
|
|
1518
|
+
else:
|
|
1519
|
+
print(f"Fail to parse documents, code: {response.status_code}, body: {response.text}")
|
|
1520
|
+
|
|
1521
|
+
def parse_dataset(self, command_dict):
|
|
1522
|
+
if self.server_type != "user":
|
|
1523
|
+
print("This command is only allowed in USER mode")
|
|
1524
|
+
|
|
1525
|
+
dataset_name = command_dict["dataset_name"]
|
|
1526
|
+
dataset_id = self._get_dataset_id(dataset_name)
|
|
1527
|
+
if dataset_id is None:
|
|
1528
|
+
return
|
|
1529
|
+
|
|
1530
|
+
res_json = self._list_documents(dataset_name, dataset_id)
|
|
1531
|
+
if res_json is None:
|
|
1532
|
+
return
|
|
1533
|
+
document_ids = []
|
|
1534
|
+
for doc in res_json:
|
|
1535
|
+
document_ids.append(doc["id"])
|
|
1536
|
+
|
|
1537
|
+
payload = {"doc_ids": document_ids, "run": 1}
|
|
1538
|
+
response = self.http_client.request("POST", "/documents/ingest", json_body=payload, use_api_base=True, auth_kind="web")
|
|
1539
|
+
if response.status_code == 200:
|
|
1540
|
+
res_json = response.json()
|
|
1541
|
+
if res_json["code"] == 0:
|
|
1542
|
+
pass
|
|
1543
|
+
else:
|
|
1544
|
+
print(f"Fail to parse dataset {dataset_name}, code: {res_json['code']}, message: {res_json['message']}")
|
|
1545
|
+
|
|
1546
|
+
else:
|
|
1547
|
+
print(f"Fail to parse dataset {dataset_name}, code: {response.status_code}, body: {response.text}")
|
|
1548
|
+
if command_dict["method"] == "async":
|
|
1549
|
+
print(f"Success to start parse dataset {dataset_name}")
|
|
1550
|
+
return
|
|
1551
|
+
else:
|
|
1552
|
+
print(f"Start to parse dataset {dataset_name}, please wait...")
|
|
1553
|
+
if self._wait_parse_done(dataset_name, dataset_id):
|
|
1554
|
+
print(f"Success to parse dataset {dataset_name}")
|
|
1555
|
+
else:
|
|
1556
|
+
print(f"Parse dataset {dataset_name} timeout")
|
|
1557
|
+
|
|
1558
|
+
def import_docs_into_dataset(self, command_dict):
|
|
1559
|
+
if self.server_type != "user":
|
|
1560
|
+
print("This command is only allowed in USER mode")
|
|
1561
|
+
|
|
1562
|
+
dataset_name = command_dict["dataset_name"]
|
|
1563
|
+
dataset_id = self._get_dataset_id(dataset_name)
|
|
1564
|
+
if dataset_id is None:
|
|
1565
|
+
return
|
|
1566
|
+
|
|
1567
|
+
document_paths = command_dict["document_paths"]
|
|
1568
|
+
paths = [Path(p) for p in document_paths]
|
|
1569
|
+
|
|
1570
|
+
fields = []
|
|
1571
|
+
file_handles = []
|
|
1572
|
+
try:
|
|
1573
|
+
for path in paths:
|
|
1574
|
+
fh = path.open("rb")
|
|
1575
|
+
fields.append(("file", (path.name, fh)))
|
|
1576
|
+
file_handles.append(fh)
|
|
1577
|
+
fields.append(("kb_id", dataset_id))
|
|
1578
|
+
encoder = MultipartEncoder(fields=fields)
|
|
1579
|
+
headers = {"Content-Type": encoder.content_type}
|
|
1580
|
+
response = self.http_client.request(
|
|
1581
|
+
"POST", f"/datasets/{dataset_id}/documents?return_raw_files=true", headers=headers, data=encoder, json_body=None, params=None, stream=False, auth_kind="web", use_api_base=True
|
|
1582
|
+
)
|
|
1583
|
+
if response.status_code == 200:
|
|
1584
|
+
res = response.json()
|
|
1585
|
+
if res["code"] == 0:
|
|
1586
|
+
print(f"Success to import documents into dataset {dataset_name}")
|
|
1587
|
+
else:
|
|
1588
|
+
print(f"Fail to import documents: code: {res['code']}, message: {res['message']}")
|
|
1589
|
+
else:
|
|
1590
|
+
print(f"Fail to import documents: code: {response.status_code}, body: {response.text}")
|
|
1591
|
+
except Exception as exc:
|
|
1592
|
+
print(f"Fail to import document into dataset: {dataset_name}, error: {exc}")
|
|
1593
|
+
finally:
|
|
1594
|
+
for fh in file_handles:
|
|
1595
|
+
fh.close()
|
|
1596
|
+
|
|
1597
|
+
def search_on_datasets(self, command_dict):
|
|
1598
|
+
if self.server_type != "user":
|
|
1599
|
+
print("This command is only allowed in USER mode")
|
|
1600
|
+
|
|
1601
|
+
dataset_names = command_dict["datasets"]
|
|
1602
|
+
dataset_ids = []
|
|
1603
|
+
for dataset_name in dataset_names:
|
|
1604
|
+
dataset_id = self._get_dataset_id(dataset_name)
|
|
1605
|
+
if dataset_id is None:
|
|
1606
|
+
return
|
|
1607
|
+
dataset_ids.append(dataset_id)
|
|
1608
|
+
|
|
1609
|
+
payload = {
|
|
1610
|
+
"question": command_dict["question"],
|
|
1611
|
+
"dataset_ids": dataset_ids,
|
|
1612
|
+
"similarity_threshold": 0.2,
|
|
1613
|
+
"vector_similarity_weight": 0.3,
|
|
1614
|
+
# "top_k": 1024,
|
|
1615
|
+
# "kb_id": command_dict["datasets"][0],
|
|
1616
|
+
}
|
|
1617
|
+
iterations = command_dict.get("iterations", 1)
|
|
1618
|
+
if iterations > 1:
|
|
1619
|
+
response = self.http_client.request("POST", "/retrieval", json_body=payload, use_api_base=True, auth_kind="web", iterations=iterations)
|
|
1620
|
+
return response
|
|
1621
|
+
else:
|
|
1622
|
+
response = self.http_client.request("POST", "/retrieval", json_body=payload, use_api_base=True, auth_kind="web")
|
|
1623
|
+
if response.status_code == 200:
|
|
1624
|
+
res_json = response.json()
|
|
1625
|
+
if res_json["code"] == 0:
|
|
1626
|
+
self._print_table_simple(res_json["data"]["chunks"])
|
|
1627
|
+
else:
|
|
1628
|
+
print(f"Fail to search datasets: {dataset_names}, code: {res_json['code']}, message: {res_json['message']}")
|
|
1629
|
+
else:
|
|
1630
|
+
print(f"Fail to search datasets: {dataset_names}, code: {response.status_code}, body: {response.text}")
|
|
1631
|
+
|
|
1632
|
+
def get_chunk(self, command_dict):
|
|
1633
|
+
if self.server_type != "user":
|
|
1634
|
+
print("This command is only allowed in USER mode")
|
|
1635
|
+
return
|
|
1636
|
+
|
|
1637
|
+
chunk_id = command_dict["chunk_id"]
|
|
1638
|
+
response = self.http_client.request("GET", f"/chunk/get?chunk_id={chunk_id}", use_api_base=False, auth_kind="web")
|
|
1639
|
+
if response.status_code == 200:
|
|
1640
|
+
res_json = response.json()
|
|
1641
|
+
if res_json["code"] == 0:
|
|
1642
|
+
self._print_key_value(res_json["data"])
|
|
1643
|
+
else:
|
|
1644
|
+
print(f"Fail to get chunk, code: {res_json['code']}, message: {res_json['message']}")
|
|
1645
|
+
else:
|
|
1646
|
+
print(f"Fail to get chunk, code: {response.status_code}, body: {response.text}")
|
|
1647
|
+
|
|
1648
|
+
# Internal
|
|
1649
|
+
def insert_dataset_from_file(self, command_dict):
|
|
1650
|
+
if self.server_type != "user":
|
|
1651
|
+
print("This command is only allowed in USER mode")
|
|
1652
|
+
return
|
|
1653
|
+
|
|
1654
|
+
file_path = command_dict["file_path"]
|
|
1655
|
+
payload = {"file_path": file_path}
|
|
1656
|
+
response = self.http_client.request("POST", "/kb/insert_from_file", json_body=payload, use_api_base=False, auth_kind="web")
|
|
1657
|
+
if response.status_code == 200:
|
|
1658
|
+
res_json = response.json()
|
|
1659
|
+
if res_json["code"] == 0:
|
|
1660
|
+
print(f"Success to insert dataset from file: {file_path}")
|
|
1661
|
+
if res_json.get("data"):
|
|
1662
|
+
self._print_key_value(res_json["data"])
|
|
1663
|
+
else:
|
|
1664
|
+
print(f"Fail to insert dataset from file, code: {res_json['code']}, message: {res_json['message']}")
|
|
1665
|
+
else:
|
|
1666
|
+
print(f"Fail to insert dataset from file, code: {response.status_code}, body: {response.text}")
|
|
1667
|
+
|
|
1668
|
+
# Internal
|
|
1669
|
+
def insert_metadata_from_file(self, command_dict):
|
|
1670
|
+
if self.server_type != "user":
|
|
1671
|
+
print("This command is only allowed in USER mode")
|
|
1672
|
+
return
|
|
1673
|
+
|
|
1674
|
+
file_path = command_dict["file_path"]
|
|
1675
|
+
payload = {"file_path": file_path}
|
|
1676
|
+
response = self.http_client.request("POST", "/tenant/insert_metadata_from_file", json_body=payload, use_api_base=False, auth_kind="web")
|
|
1677
|
+
if response.status_code == 200:
|
|
1678
|
+
res_json = response.json()
|
|
1679
|
+
if res_json["code"] == 0:
|
|
1680
|
+
print(f"Success to insert metadata from file: {file_path}")
|
|
1681
|
+
if res_json.get("data"):
|
|
1682
|
+
self._print_key_value(res_json["data"])
|
|
1683
|
+
else:
|
|
1684
|
+
print(f"Fail to insert metadata from file, code: {res_json['code']}, message: {res_json['message']}")
|
|
1685
|
+
else:
|
|
1686
|
+
print(f"Fail to insert metadata from file, code: {response.status_code}, body: {response.text}")
|
|
1687
|
+
|
|
1688
|
+
def update_chunk(self, command_dict):
|
|
1689
|
+
if self.server_type != "user":
|
|
1690
|
+
print("This command is only allowed in USER mode")
|
|
1691
|
+
return
|
|
1692
|
+
|
|
1693
|
+
chunk_id = command_dict["chunk_id"]
|
|
1694
|
+
dataset_name = command_dict["dataset_name"]
|
|
1695
|
+
json_body_str = command_dict["json_body"]
|
|
1696
|
+
|
|
1697
|
+
# Get dataset_id from dataset_name
|
|
1698
|
+
dataset_id = self._get_dataset_id(dataset_name)
|
|
1699
|
+
if dataset_id is None:
|
|
1700
|
+
return
|
|
1701
|
+
|
|
1702
|
+
# Get doc_id from chunk_id via GET /chunk/get
|
|
1703
|
+
response = self.http_client.request("GET", f"/chunk/get?chunk_id={chunk_id}", use_api_base=False, auth_kind="web")
|
|
1704
|
+
if response.status_code != 200:
|
|
1705
|
+
print(f"Fail to get chunk info, code: {response.status_code}, body: {response.text}")
|
|
1706
|
+
return
|
|
1707
|
+
|
|
1708
|
+
res_json = response.json()
|
|
1709
|
+
doc_id = None
|
|
1710
|
+
if res_json.get("code") == 0 and res_json.get("data"):
|
|
1711
|
+
doc_id = res_json["data"].get("doc_id")
|
|
1712
|
+
|
|
1713
|
+
if not doc_id:
|
|
1714
|
+
print(f"Could not find document_id for chunk {chunk_id}")
|
|
1715
|
+
return
|
|
1716
|
+
|
|
1717
|
+
# Parse json_body
|
|
1718
|
+
try:
|
|
1719
|
+
payload = json.loads(json_body_str)
|
|
1720
|
+
except json.JSONDecodeError as e:
|
|
1721
|
+
print(f"Invalid JSON body: {e}")
|
|
1722
|
+
return
|
|
1723
|
+
|
|
1724
|
+
# Add IDs to payload
|
|
1725
|
+
payload["dataset_id"] = dataset_id
|
|
1726
|
+
payload["document_id"] = doc_id
|
|
1727
|
+
payload["chunk_id"] = chunk_id
|
|
1728
|
+
|
|
1729
|
+
# Call POST /v1/chunk/update
|
|
1730
|
+
response = self.http_client.request("POST", "/chunk/update", json_body=payload, use_api_base=False, auth_kind="web")
|
|
1731
|
+
if response.status_code == 200:
|
|
1732
|
+
res_json = response.json()
|
|
1733
|
+
if res_json.get("code") == 0:
|
|
1734
|
+
print(f"Success to update chunk: {chunk_id}")
|
|
1735
|
+
else:
|
|
1736
|
+
print(f"Fail to update chunk, code: {res_json.get('code')}, message: {res_json.get('message')}")
|
|
1737
|
+
else:
|
|
1738
|
+
print(f"Fail to update chunk, code: {response.status_code}, body: {response.text}")
|
|
1739
|
+
|
|
1740
|
+
def _get_documents_by_ids(self, ids: list[str]):
|
|
1741
|
+
response = self.http_client.request("POST", "/document/infos", json_body={"doc_ids": ids}, use_api_base=False, auth_kind="web")
|
|
1742
|
+
|
|
1743
|
+
if response.status_code != 200:
|
|
1744
|
+
return f"Fail to get document info, HTTP {response.status_code}", None
|
|
1745
|
+
|
|
1746
|
+
res_json = response.json()
|
|
1747
|
+
if res_json.get("code") != 0:
|
|
1748
|
+
return f"Fail to get document info: {res_json.get('message')}", None
|
|
1749
|
+
|
|
1750
|
+
docs = res_json.get("data", [])
|
|
1751
|
+
if not docs:
|
|
1752
|
+
return f"Document not found: {ids}", None
|
|
1753
|
+
|
|
1754
|
+
return None, docs
|
|
1755
|
+
|
|
1756
|
+
def set_metadata(self, command_dict):
|
|
1757
|
+
if self.server_type != "user":
|
|
1758
|
+
print("This command is only allowed in USER mode")
|
|
1759
|
+
return
|
|
1760
|
+
|
|
1761
|
+
doc_id = command_dict["doc_id"]
|
|
1762
|
+
meta_json_str = command_dict["meta"]
|
|
1763
|
+
|
|
1764
|
+
# Parse JSON string to dict
|
|
1765
|
+
import json
|
|
1766
|
+
|
|
1767
|
+
try:
|
|
1768
|
+
meta_fields = json.loads(meta_json_str)
|
|
1769
|
+
except json.JSONDecodeError as e:
|
|
1770
|
+
print(f"Invalid JSON format: {e}")
|
|
1771
|
+
return
|
|
1772
|
+
|
|
1773
|
+
# Step 1: Get document info to find kb_id (dataset_id)
|
|
1774
|
+
doc_error_msg, docs = self._get_documents_by_ids([doc_id])
|
|
1775
|
+
if doc_error_msg:
|
|
1776
|
+
print(doc_error_msg)
|
|
1777
|
+
return
|
|
1778
|
+
|
|
1779
|
+
if len(docs) == 0:
|
|
1780
|
+
print(f"no document found for {doc_id}")
|
|
1781
|
+
return
|
|
1782
|
+
|
|
1783
|
+
dataset_id = docs[0].get("dataset_id") or docs[0].get("kb_id")
|
|
1784
|
+
if not dataset_id:
|
|
1785
|
+
print(f"Dataset ID not found for document: {doc_id}")
|
|
1786
|
+
return
|
|
1787
|
+
|
|
1788
|
+
# Send meta as JSON string
|
|
1789
|
+
payload = {
|
|
1790
|
+
"meta_fields": meta_fields,
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
response = self.http_client.request("PATCH", f"/datasets/{dataset_id}/documents/{doc_id}", json_body=payload, use_api_base=True, auth_kind="web")
|
|
1794
|
+
|
|
1795
|
+
if response.status_code == 200:
|
|
1796
|
+
res_json = response.json()
|
|
1797
|
+
if res_json.get("code") == 0:
|
|
1798
|
+
print(f"Success to set metadata for document: {doc_id}")
|
|
1799
|
+
else:
|
|
1800
|
+
print(f"Fail to set metadata, code: {res_json.get('code')}, message: {res_json.get('message')}")
|
|
1801
|
+
else:
|
|
1802
|
+
print(f"Fail to set metadata, code: {response.status_code}, body: {response.text}")
|
|
1803
|
+
|
|
1804
|
+
def generate_nav_for_dataset(self, command_dict):
|
|
1805
|
+
if self.server_type != "user":
|
|
1806
|
+
print("This command is only allowed in USER mode")
|
|
1807
|
+
return
|
|
1808
|
+
|
|
1809
|
+
dataset_name = command_dict["dataset_id"]
|
|
1810
|
+
dataset_id = self._get_dataset_id(dataset_name)
|
|
1811
|
+
if dataset_id is None:
|
|
1812
|
+
print(f"Dataset not found: {dataset_name}")
|
|
1813
|
+
return
|
|
1814
|
+
|
|
1815
|
+
response = self.http_client.request(
|
|
1816
|
+
"POST",
|
|
1817
|
+
f"/datasets/{dataset_id}/navigation",
|
|
1818
|
+
json_body={},
|
|
1819
|
+
use_api_base=True,
|
|
1820
|
+
auth_kind="web",
|
|
1821
|
+
)
|
|
1822
|
+
if response.status_code == 200:
|
|
1823
|
+
res_json = response.json()
|
|
1824
|
+
if res_json.get("code") == 0:
|
|
1825
|
+
data = res_json.get("data", {})
|
|
1826
|
+
print(f"Navigation tree created: deleted={data.get('deleted', 0)}, upserted={data.get('upserted', 0)}")
|
|
1827
|
+
else:
|
|
1828
|
+
print(f"Fail to generate navigation, code: {res_json.get('code')}, message: {res_json.get('message')}")
|
|
1829
|
+
else:
|
|
1830
|
+
print(f"Fail to generate navigation, code: {response.status_code}, body: {response.text}")
|
|
1831
|
+
|
|
1832
|
+
def navigation_search(self, command_dict):
|
|
1833
|
+
if self.server_type != "user":
|
|
1834
|
+
print("This command is only allowed in USER mode")
|
|
1835
|
+
return
|
|
1836
|
+
|
|
1837
|
+
query = command_dict["query"]
|
|
1838
|
+
dataset_name = command_dict["dataset_id"]
|
|
1839
|
+
mode = command_dict["mode"]
|
|
1840
|
+
topk = command_dict.get("topk", None)
|
|
1841
|
+
|
|
1842
|
+
# Try name lookup; fall back to using the value directly as an ID.
|
|
1843
|
+
dataset_id = self._get_dataset_id(dataset_name)
|
|
1844
|
+
if dataset_id is None:
|
|
1845
|
+
dataset_id = dataset_name
|
|
1846
|
+
|
|
1847
|
+
valid_modes = {"nav_doc", "nav_cluster", "navigation_tree", "chunk", "all"}
|
|
1848
|
+
if mode not in valid_modes:
|
|
1849
|
+
print(f"Invalid mode: {mode}, expected one of {valid_modes}")
|
|
1850
|
+
return
|
|
1851
|
+
|
|
1852
|
+
url = f"/datasets/{dataset_id}/navigation/search?q={query}&mode={mode}"
|
|
1853
|
+
if topk is not None:
|
|
1854
|
+
url += f"&top_k={topk}"
|
|
1855
|
+
response = self.http_client.request(
|
|
1856
|
+
"GET",
|
|
1857
|
+
url,
|
|
1858
|
+
use_api_base=True,
|
|
1859
|
+
auth_kind="web",
|
|
1860
|
+
)
|
|
1861
|
+
if response.status_code == 200:
|
|
1862
|
+
res_json = response.json()
|
|
1863
|
+
if res_json.get("code") == 0:
|
|
1864
|
+
data = res_json.get("data", {})
|
|
1865
|
+
items = data.get("items", [])
|
|
1866
|
+
print(f"Found {len(items)} result(s) for mode '{data.get('mode', mode)}':")
|
|
1867
|
+
for i, item in enumerate(items, 1):
|
|
1868
|
+
print(f" [{i}] doc_id: {item.get('doc_id', '')} score: {item.get('score', 0):.4f}")
|
|
1869
|
+
else:
|
|
1870
|
+
print(f"Search failed, code: {res_json.get('code')}, message: {res_json.get('message')}")
|
|
1871
|
+
else:
|
|
1872
|
+
print(f"Search failed, code: {response.status_code}, body: {response.text}")
|
|
1873
|
+
|
|
1874
|
+
def remove_tags(self, command_dict):
|
|
1875
|
+
if self.server_type != "user":
|
|
1876
|
+
print("This command is only allowed in USER mode")
|
|
1877
|
+
return
|
|
1878
|
+
|
|
1879
|
+
dataset_name = command_dict["dataset_name"]
|
|
1880
|
+
dataset_id = self._get_dataset_id(dataset_name)
|
|
1881
|
+
if dataset_id is None:
|
|
1882
|
+
print(f"Dataset not found: {dataset_name}")
|
|
1883
|
+
return
|
|
1884
|
+
|
|
1885
|
+
tags = command_dict["tags"]
|
|
1886
|
+
|
|
1887
|
+
payload = {
|
|
1888
|
+
"tags": tags,
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
response = self.http_client.request("POST", f"/kb/{dataset_id}/rm_tags", json_body=payload, use_api_base=False, auth_kind="web")
|
|
1892
|
+
if response.status_code == 200:
|
|
1893
|
+
res_json = response.json()
|
|
1894
|
+
if res_json.get("code") == 0:
|
|
1895
|
+
print(f"Success to remove tags from dataset: {dataset_name}")
|
|
1896
|
+
else:
|
|
1897
|
+
print(f"Fail to remove tags, code: {res_json.get('code')}, message: {res_json.get('message')}")
|
|
1898
|
+
else:
|
|
1899
|
+
print(f"Fail to remove tags, code: {response.status_code}, body: {response.text}")
|
|
1900
|
+
|
|
1901
|
+
def remove_chunks(self, command_dict):
|
|
1902
|
+
if self.server_type != "user":
|
|
1903
|
+
print("This command is only allowed in USER mode")
|
|
1904
|
+
return
|
|
1905
|
+
|
|
1906
|
+
doc_id = command_dict["doc_id"]
|
|
1907
|
+
payload = {"doc_id": doc_id}
|
|
1908
|
+
|
|
1909
|
+
if command_dict.get("delete_all"):
|
|
1910
|
+
payload["delete_all"] = True
|
|
1911
|
+
elif command_dict.get("chunk_ids"):
|
|
1912
|
+
payload["chunk_ids"] = command_dict["chunk_ids"]
|
|
1913
|
+
|
|
1914
|
+
response = self.http_client.request("POST", "/chunk/rm", json_body=payload, use_api_base=False, auth_kind="web")
|
|
1915
|
+
if response.status_code == 200:
|
|
1916
|
+
res_json = response.json()
|
|
1917
|
+
if res_json.get("code") == 0:
|
|
1918
|
+
deleted_count = res_json.get("data", 0)
|
|
1919
|
+
print(f"Success to remove chunks from document {doc_id}: {deleted_count} chunks deleted")
|
|
1920
|
+
else:
|
|
1921
|
+
print(f"Fail to remove chunks, code: {res_json.get('code')}, message: {res_json.get('message')}")
|
|
1922
|
+
else:
|
|
1923
|
+
print(f"Fail to remove chunks, code: {response.status_code}, body: {response.text}")
|
|
1924
|
+
|
|
1925
|
+
def list_chunks(self, command_dict):
|
|
1926
|
+
if self.server_type != "user":
|
|
1927
|
+
print("This command is only allowed in USER mode")
|
|
1928
|
+
return
|
|
1929
|
+
|
|
1930
|
+
doc_id = command_dict["doc_id"]
|
|
1931
|
+
payload = {
|
|
1932
|
+
"doc_id": doc_id,
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
# Add optional parameters (only if explicitly provided)
|
|
1936
|
+
if "page" in command_dict:
|
|
1937
|
+
payload["page"] = command_dict["page"]
|
|
1938
|
+
if "size" in command_dict:
|
|
1939
|
+
payload["size"] = command_dict["size"]
|
|
1940
|
+
if command_dict.get("keywords"):
|
|
1941
|
+
payload["keywords"] = command_dict["keywords"]
|
|
1942
|
+
if "available_int" in command_dict:
|
|
1943
|
+
payload["available_int"] = command_dict["available_int"]
|
|
1944
|
+
|
|
1945
|
+
response = self.http_client.request("POST", "/chunk/list", json_body=payload, use_api_base=False, auth_kind="web")
|
|
1946
|
+
if response.status_code == 200:
|
|
1947
|
+
res_json = response.json()
|
|
1948
|
+
if res_json["code"] == 0:
|
|
1949
|
+
chunks = res_json["data"]["chunks"]
|
|
1950
|
+
if chunks:
|
|
1951
|
+
for i, chunk in enumerate(chunks):
|
|
1952
|
+
print(f"\n--- Chunk {i + 1} ---")
|
|
1953
|
+
for key, value in chunk.items():
|
|
1954
|
+
print(f" {key}: {value}")
|
|
1955
|
+
else:
|
|
1956
|
+
print("No chunks found")
|
|
1957
|
+
else:
|
|
1958
|
+
print(f"Fail to list chunks, code: {res_json['code']}, message: {res_json['message']}")
|
|
1959
|
+
else:
|
|
1960
|
+
print(f"Fail to list chunks, code: {response.status_code}, body: {response.text}")
|
|
1961
|
+
|
|
1962
|
+
def show_version(self, command):
|
|
1963
|
+
if self.server_type == "admin":
|
|
1964
|
+
response = self.http_client.request("GET", "/admin/version", use_api_base=True, auth_kind="admin")
|
|
1965
|
+
else:
|
|
1966
|
+
response = self.http_client.request("GET", "/system/version", use_api_base=True, auth_kind="admin")
|
|
1967
|
+
|
|
1968
|
+
if response.status_code == 200:
|
|
1969
|
+
res_json = response.json()
|
|
1970
|
+
if res_json["code"] == 0:
|
|
1971
|
+
if self.server_type == "admin":
|
|
1972
|
+
self._print_table_simple(res_json["data"])
|
|
1973
|
+
else:
|
|
1974
|
+
self._print_table_simple({"version": res_json["data"]})
|
|
1975
|
+
else:
|
|
1976
|
+
print(f"Fail to show version, code: {res_json['code']}, message: {res_json['message']}")
|
|
1977
|
+
else:
|
|
1978
|
+
print(f"Fail to show version, code: {response.status_code}, body: {response.text}")
|
|
1979
|
+
|
|
1980
|
+
def _wait_parse_done(self, dataset_name: str, dataset_id: str):
|
|
1981
|
+
start = time.monotonic()
|
|
1982
|
+
while True:
|
|
1983
|
+
docs = self._list_documents(dataset_name, dataset_id)
|
|
1984
|
+
if docs is None:
|
|
1985
|
+
return False
|
|
1986
|
+
all_done = True
|
|
1987
|
+
for doc in docs:
|
|
1988
|
+
if doc.get("run") != "DONE":
|
|
1989
|
+
print(f"Document {doc['name']} is not done, status: {doc.get('run')}")
|
|
1990
|
+
all_done = False
|
|
1991
|
+
break
|
|
1992
|
+
if all_done:
|
|
1993
|
+
return True
|
|
1994
|
+
if time.monotonic() - start > 60:
|
|
1995
|
+
return False
|
|
1996
|
+
time.sleep(0.5)
|
|
1997
|
+
|
|
1998
|
+
def _list_documents(self, dataset_name: str, dataset_id: str):
|
|
1999
|
+
# Use the new RESTful API: GET /api/v1/datasets/<dataset_id>/documents
|
|
2000
|
+
response = self.http_client.request("GET", f"/datasets/{dataset_id}/documents", use_api_base=True, auth_kind="web")
|
|
2001
|
+
if response.status_code != 200:
|
|
2002
|
+
print(f"Fail to list files from dataset {dataset_name}, code: {response.status_code}, body: {response.text}")
|
|
2003
|
+
return None
|
|
2004
|
+
res_json = response.json()
|
|
2005
|
+
if res_json.get("code") != 0:
|
|
2006
|
+
print(f"Fail to list files from dataset {dataset_name}, code: {res_json.get('code')}, message: {res_json.get('message')}")
|
|
2007
|
+
return None
|
|
2008
|
+
return res_json["data"]["docs"]
|
|
2009
|
+
|
|
2010
|
+
def _get_dataset_id(self, dataset_name: str):
|
|
2011
|
+
response = self.http_client.request("GET", "/datasets", use_api_base=True, auth_kind="web")
|
|
2012
|
+
if response.status_code != 200:
|
|
2013
|
+
print(f"Fail to list datasets, code: {response.status_code}, body: {response.text}")
|
|
2014
|
+
return None
|
|
2015
|
+
|
|
2016
|
+
res_json = response.json()
|
|
2017
|
+
if res_json.get("code") != 0:
|
|
2018
|
+
print(f"Fail to list datasets, code: {res_json.get('code')}, message: {res_json.get('message')}")
|
|
2019
|
+
return None
|
|
2020
|
+
dataset_list = res_json["data"]
|
|
2021
|
+
dataset_id: str = ""
|
|
2022
|
+
for dataset in dataset_list:
|
|
2023
|
+
if dataset["name"] == dataset_name:
|
|
2024
|
+
dataset_id = dataset["id"]
|
|
2025
|
+
|
|
2026
|
+
if dataset_id == "":
|
|
2027
|
+
print(f"Dataset {dataset_name} not found")
|
|
2028
|
+
return None
|
|
2029
|
+
return dataset_id
|
|
2030
|
+
|
|
2031
|
+
def _list_chats(self, command):
|
|
2032
|
+
iterations = command.get("iterations", 1)
|
|
2033
|
+
if iterations > 1:
|
|
2034
|
+
response = self.http_client.request(
|
|
2035
|
+
"GET",
|
|
2036
|
+
"/chats",
|
|
2037
|
+
use_api_base=True,
|
|
2038
|
+
auth_kind="web",
|
|
2039
|
+
iterations=iterations,
|
|
2040
|
+
)
|
|
2041
|
+
return response
|
|
2042
|
+
else:
|
|
2043
|
+
response = self.http_client.request(
|
|
2044
|
+
"GET",
|
|
2045
|
+
"/chats",
|
|
2046
|
+
use_api_base=True,
|
|
2047
|
+
auth_kind="web",
|
|
2048
|
+
iterations=iterations,
|
|
2049
|
+
)
|
|
2050
|
+
if response.status_code == 200:
|
|
2051
|
+
res_json = response.json()
|
|
2052
|
+
if res_json["code"] == 0:
|
|
2053
|
+
return res_json["data"]["chats"]
|
|
2054
|
+
else:
|
|
2055
|
+
print(f"Fail to list chats, code: {res_json['code']}, message: {res_json['message']}")
|
|
2056
|
+
return None
|
|
2057
|
+
|
|
2058
|
+
else:
|
|
2059
|
+
print(f"Fail to list chats, code: {response.status_code}, body: {response.text}")
|
|
2060
|
+
|
|
2061
|
+
def _format_service_detail_table(self, data):
|
|
2062
|
+
if isinstance(data, list):
|
|
2063
|
+
return data
|
|
2064
|
+
if not all([isinstance(v, list) for v in data.values()]):
|
|
2065
|
+
# normal table
|
|
2066
|
+
return data
|
|
2067
|
+
# handle task_executor heartbeats map, for example {'name': [{'done': 2, 'now': timestamp1}, {'done': 3, 'now': timestamp2}]
|
|
2068
|
+
task_executor_list = []
|
|
2069
|
+
for k, v in data.items():
|
|
2070
|
+
# display latest status
|
|
2071
|
+
heartbeats = sorted(v, key=lambda x: x["now"], reverse=True)
|
|
2072
|
+
task_executor_list.append(
|
|
2073
|
+
{
|
|
2074
|
+
"task_executor_name": k,
|
|
2075
|
+
**heartbeats[0],
|
|
2076
|
+
}
|
|
2077
|
+
if heartbeats
|
|
2078
|
+
else {"task_executor_name": k}
|
|
2079
|
+
)
|
|
2080
|
+
return task_executor_list
|
|
2081
|
+
|
|
2082
|
+
def _print_table_simple(self, data):
|
|
2083
|
+
if not data:
|
|
2084
|
+
print("No data to print")
|
|
2085
|
+
return
|
|
2086
|
+
if isinstance(data, dict):
|
|
2087
|
+
# handle single row data
|
|
2088
|
+
data = [data]
|
|
2089
|
+
|
|
2090
|
+
columns = list(set().union(*(d.keys() for d in data)))
|
|
2091
|
+
columns.sort()
|
|
2092
|
+
col_widths = {}
|
|
2093
|
+
|
|
2094
|
+
def get_string_width(text):
|
|
2095
|
+
half_width_chars = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\t\n\r"
|
|
2096
|
+
width = 0
|
|
2097
|
+
for char in text:
|
|
2098
|
+
if char in half_width_chars:
|
|
2099
|
+
width += 1
|
|
2100
|
+
else:
|
|
2101
|
+
width += 2
|
|
2102
|
+
return width
|
|
2103
|
+
|
|
2104
|
+
for col in columns:
|
|
2105
|
+
max_width = get_string_width(str(col))
|
|
2106
|
+
for item in data:
|
|
2107
|
+
value_len = get_string_width(str(item.get(col, "")))
|
|
2108
|
+
max_width = max(max_width, value_len)
|
|
2109
|
+
col_widths[col] = max(2, max_width)
|
|
2110
|
+
|
|
2111
|
+
# Generate delimiter
|
|
2112
|
+
separator = "+" + "+".join(["-" * (col_widths[col] + 2) for col in columns]) + "+"
|
|
2113
|
+
|
|
2114
|
+
# Print header
|
|
2115
|
+
print(separator)
|
|
2116
|
+
header = "|" + "|".join([f" {col:<{col_widths[col]}} " for col in columns]) + "|"
|
|
2117
|
+
print(header)
|
|
2118
|
+
print(separator)
|
|
2119
|
+
|
|
2120
|
+
# Print data
|
|
2121
|
+
for item in data:
|
|
2122
|
+
row = "|"
|
|
2123
|
+
for col in columns:
|
|
2124
|
+
value = str(item.get(col, ""))
|
|
2125
|
+
if get_string_width(value) > col_widths[col]:
|
|
2126
|
+
value = value[: col_widths[col] - 3] + "..."
|
|
2127
|
+
row += f" {value:<{col_widths[col] - (get_string_width(value) - len(value))}} |"
|
|
2128
|
+
print(row)
|
|
2129
|
+
|
|
2130
|
+
print(separator)
|
|
2131
|
+
|
|
2132
|
+
def _print_key_value(self, data: dict):
|
|
2133
|
+
"""Print data as key-value pairs (one per line)"""
|
|
2134
|
+
if not data:
|
|
2135
|
+
print("No data to print")
|
|
2136
|
+
return
|
|
2137
|
+
for key, value in data.items():
|
|
2138
|
+
print(f"{key}: {value}")
|
|
2139
|
+
|
|
2140
|
+
|
|
2141
|
+
def run_command(client: RAGFlowClient, command_dict: dict):
|
|
2142
|
+
command_type = command_dict["type"]
|
|
2143
|
+
|
|
2144
|
+
match command_type:
|
|
2145
|
+
case "benchmark":
|
|
2146
|
+
run_benchmark(client, command_dict)
|
|
2147
|
+
case "login_user":
|
|
2148
|
+
client.login_user(command_dict)
|
|
2149
|
+
case "ping_server":
|
|
2150
|
+
return client.ping_server(command_dict)
|
|
2151
|
+
case "register_user":
|
|
2152
|
+
client.register_user(command_dict)
|
|
2153
|
+
case "list_services":
|
|
2154
|
+
client.list_services()
|
|
2155
|
+
case "show_service":
|
|
2156
|
+
client.show_service(command_dict)
|
|
2157
|
+
case "restart_service":
|
|
2158
|
+
client.restart_service(command_dict)
|
|
2159
|
+
case "shutdown_service":
|
|
2160
|
+
client.shutdown_service(command_dict)
|
|
2161
|
+
case "startup_service":
|
|
2162
|
+
client.startup_service(command_dict)
|
|
2163
|
+
case "list_users":
|
|
2164
|
+
client.list_users(command_dict)
|
|
2165
|
+
case "show_user":
|
|
2166
|
+
client.show_user(command_dict)
|
|
2167
|
+
case "drop_user":
|
|
2168
|
+
client.drop_user(command_dict)
|
|
2169
|
+
case "alter_user":
|
|
2170
|
+
client.alter_user(command_dict)
|
|
2171
|
+
case "create_user":
|
|
2172
|
+
client.create_user(command_dict)
|
|
2173
|
+
case "activate_user":
|
|
2174
|
+
client.activate_user(command_dict)
|
|
2175
|
+
case "list_datasets":
|
|
2176
|
+
client.handle_list_datasets(command_dict)
|
|
2177
|
+
case "list_agents":
|
|
2178
|
+
client.handle_list_agents(command_dict)
|
|
2179
|
+
case "create_role":
|
|
2180
|
+
client.create_role(command_dict)
|
|
2181
|
+
case "drop_role":
|
|
2182
|
+
client.drop_role(command_dict)
|
|
2183
|
+
case "alter_role":
|
|
2184
|
+
client.alter_role(command_dict)
|
|
2185
|
+
case "list_roles":
|
|
2186
|
+
client.list_roles(command_dict)
|
|
2187
|
+
case "show_role":
|
|
2188
|
+
client.show_role(command_dict)
|
|
2189
|
+
case "grant_permission":
|
|
2190
|
+
client.grant_permission(command_dict)
|
|
2191
|
+
case "revoke_permission":
|
|
2192
|
+
client.revoke_permission(command_dict)
|
|
2193
|
+
case "alter_user_role":
|
|
2194
|
+
client.alter_user_role(command_dict)
|
|
2195
|
+
case "show_user_permission":
|
|
2196
|
+
client.show_user_permission(command_dict)
|
|
2197
|
+
case "show_version":
|
|
2198
|
+
client.show_version(command_dict)
|
|
2199
|
+
case "grant_admin":
|
|
2200
|
+
client.grant_admin(command_dict)
|
|
2201
|
+
case "revoke_admin":
|
|
2202
|
+
client.revoke_admin(command_dict)
|
|
2203
|
+
case "generate_key":
|
|
2204
|
+
client.generate_key(command_dict)
|
|
2205
|
+
case "list_keys":
|
|
2206
|
+
client.list_keys(command_dict)
|
|
2207
|
+
case "drop_key":
|
|
2208
|
+
client.drop_key(command_dict)
|
|
2209
|
+
case "set_variable":
|
|
2210
|
+
client.set_variable(command_dict)
|
|
2211
|
+
case "show_variable":
|
|
2212
|
+
client.show_variable(command_dict)
|
|
2213
|
+
case "list_variables":
|
|
2214
|
+
client.list_variables(command_dict)
|
|
2215
|
+
case "list_configs":
|
|
2216
|
+
client.list_configs(command_dict)
|
|
2217
|
+
case "list_environments":
|
|
2218
|
+
client.list_environments(command_dict)
|
|
2219
|
+
case "show_fingerprint":
|
|
2220
|
+
client.show_fingerprint(command_dict)
|
|
2221
|
+
case "set_license":
|
|
2222
|
+
client.set_license(command_dict)
|
|
2223
|
+
case "set_license_config":
|
|
2224
|
+
client.set_license_config(command_dict)
|
|
2225
|
+
case "show_license":
|
|
2226
|
+
client.show_license(command_dict)
|
|
2227
|
+
case "check_license":
|
|
2228
|
+
client.check_license(command_dict)
|
|
2229
|
+
case "list_server_configs":
|
|
2230
|
+
client.list_server_configs(command_dict)
|
|
2231
|
+
case "create_model_provider":
|
|
2232
|
+
client.create_model_provider(command_dict)
|
|
2233
|
+
case "drop_model_provider":
|
|
2234
|
+
client.drop_model_provider(command_dict)
|
|
2235
|
+
case "show_current_user":
|
|
2236
|
+
client.show_current_user(command_dict)
|
|
2237
|
+
case "set_default_model":
|
|
2238
|
+
client.set_default_model(command_dict)
|
|
2239
|
+
case "reset_default_model":
|
|
2240
|
+
client.reset_default_model(command_dict)
|
|
2241
|
+
case "list_user_datasets":
|
|
2242
|
+
return client.list_user_datasets(command_dict)
|
|
2243
|
+
case "create_user_dataset":
|
|
2244
|
+
client.create_user_dataset(command_dict)
|
|
2245
|
+
case "drop_user_dataset":
|
|
2246
|
+
client.drop_user_dataset(command_dict)
|
|
2247
|
+
case "list_user_dataset_files":
|
|
2248
|
+
return client.list_user_dataset_files(command_dict)
|
|
2249
|
+
case "list_user_dataset_documents":
|
|
2250
|
+
return client.list_user_dataset_documents(command_dict)
|
|
2251
|
+
case "list_user_datasets_metadata":
|
|
2252
|
+
return client.list_user_datasets_metadata(command_dict)
|
|
2253
|
+
case "list_user_documents_metadata_summary":
|
|
2254
|
+
return client.list_user_documents_metadata_summary(command_dict)
|
|
2255
|
+
case "list_user_agents":
|
|
2256
|
+
return client.list_user_agents(command_dict)
|
|
2257
|
+
case "list_user_chats":
|
|
2258
|
+
return client.list_user_chats(command_dict)
|
|
2259
|
+
case "create_user_chat":
|
|
2260
|
+
client.create_user_chat(command_dict)
|
|
2261
|
+
case "drop_user_chat":
|
|
2262
|
+
client.drop_user_chat(command_dict)
|
|
2263
|
+
case "create_dataset_table":
|
|
2264
|
+
client.create_dataset_table(command_dict)
|
|
2265
|
+
case "drop_dataset_table":
|
|
2266
|
+
client.drop_dataset_table(command_dict)
|
|
2267
|
+
case "create_metadata_table":
|
|
2268
|
+
client.create_metadata_table(command_dict)
|
|
2269
|
+
case "drop_metadata_table":
|
|
2270
|
+
client.drop_metadata_table(command_dict)
|
|
2271
|
+
case "create_chat_session":
|
|
2272
|
+
client.create_chat_session(command_dict)
|
|
2273
|
+
case "drop_chat_session":
|
|
2274
|
+
client.drop_chat_session(command_dict)
|
|
2275
|
+
case "list_chat_sessions":
|
|
2276
|
+
return client.list_chat_sessions(command_dict)
|
|
2277
|
+
case "chat_on_session":
|
|
2278
|
+
client.chat_on_session(command_dict)
|
|
2279
|
+
case "list_user_model_providers":
|
|
2280
|
+
client.list_user_model_providers(command_dict)
|
|
2281
|
+
case "list_user_default_models":
|
|
2282
|
+
client.list_user_default_models(command_dict)
|
|
2283
|
+
case "parse_dataset_docs":
|
|
2284
|
+
client.parse_dataset_docs(command_dict)
|
|
2285
|
+
case "parse_dataset":
|
|
2286
|
+
client.parse_dataset(command_dict)
|
|
2287
|
+
case "import_docs_into_dataset":
|
|
2288
|
+
client.import_docs_into_dataset(command_dict)
|
|
2289
|
+
case "search_on_datasets":
|
|
2290
|
+
return client.search_on_datasets(command_dict)
|
|
2291
|
+
case "get_chunk":
|
|
2292
|
+
return client.get_chunk(command_dict)
|
|
2293
|
+
case "insert_dataset_from_file":
|
|
2294
|
+
return client.insert_dataset_from_file(command_dict)
|
|
2295
|
+
case "insert_metadata_from_file":
|
|
2296
|
+
return client.insert_metadata_from_file(command_dict)
|
|
2297
|
+
case "update_chunk":
|
|
2298
|
+
return client.update_chunk(command_dict)
|
|
2299
|
+
case "set_metadata":
|
|
2300
|
+
return client.set_metadata(command_dict)
|
|
2301
|
+
case "generate_nav_for_dataset":
|
|
2302
|
+
return client.generate_nav_for_dataset(command_dict)
|
|
2303
|
+
case "navigation_search":
|
|
2304
|
+
return client.navigation_search(command_dict)
|
|
2305
|
+
case "remove_tags":
|
|
2306
|
+
return client.remove_tags(command_dict)
|
|
2307
|
+
case "remove_chunks":
|
|
2308
|
+
return client.remove_chunks(command_dict)
|
|
2309
|
+
case "list_chunks":
|
|
2310
|
+
return client.list_chunks(command_dict)
|
|
2311
|
+
case "meta":
|
|
2312
|
+
_handle_meta_command(command_dict)
|
|
2313
|
+
case _:
|
|
2314
|
+
print(f"Command '{command_type}' would be executed with API")
|
|
2315
|
+
|
|
2316
|
+
|
|
2317
|
+
def _handle_meta_command(command: dict):
|
|
2318
|
+
meta_command = command["command"]
|
|
2319
|
+
args = command.get("args", [])
|
|
2320
|
+
|
|
2321
|
+
if meta_command in ["?", "h", "help"]:
|
|
2322
|
+
show_help()
|
|
2323
|
+
elif meta_command in ["q", "quit", "exit"]:
|
|
2324
|
+
print("Goodbye!")
|
|
2325
|
+
else:
|
|
2326
|
+
print(f"Meta command '{meta_command}' with args {args}")
|
|
2327
|
+
|
|
2328
|
+
|
|
2329
|
+
def show_help():
|
|
2330
|
+
"""Help info"""
|
|
2331
|
+
help_text = """
|
|
2332
|
+
Commands:
|
|
2333
|
+
LIST SERVICES
|
|
2334
|
+
SHOW SERVICE <service>
|
|
2335
|
+
STARTUP SERVICE <service>
|
|
2336
|
+
SHUTDOWN SERVICE <service>
|
|
2337
|
+
RESTART SERVICE <service>
|
|
2338
|
+
LIST USERS
|
|
2339
|
+
SHOW USER <user>
|
|
2340
|
+
DROP USER <user>
|
|
2341
|
+
CREATE USER <user> <password>
|
|
2342
|
+
ALTER USER PASSWORD <user> <new_password>
|
|
2343
|
+
ALTER USER ACTIVE <user> <on/off>
|
|
2344
|
+
LIST DATASETS OF <user>
|
|
2345
|
+
LIST AGENTS OF <user>
|
|
2346
|
+
CREATE ROLE <role>
|
|
2347
|
+
DROP ROLE <role>
|
|
2348
|
+
ALTER ROLE <role> SET DESCRIPTION <description>
|
|
2349
|
+
LIST ROLES
|
|
2350
|
+
SHOW ROLE <role>
|
|
2351
|
+
GRANT <action_list> ON <function> TO ROLE <role>
|
|
2352
|
+
REVOKE <action_list> ON <function> TO ROLE <role>
|
|
2353
|
+
ALTER USER <user> SET ROLE <role>
|
|
2354
|
+
SHOW USER PERMISSION <user>
|
|
2355
|
+
SHOW VERSION
|
|
2356
|
+
GRANT ADMIN <user>
|
|
2357
|
+
REVOKE ADMIN <user>
|
|
2358
|
+
GENERATE KEY FOR USER <user>
|
|
2359
|
+
LIST KEYS OF <user>
|
|
2360
|
+
DROP KEY <key> OF <user>
|
|
2361
|
+
|
|
2362
|
+
User Commands (use -t user):
|
|
2363
|
+
LIST DATASETS
|
|
2364
|
+
LIST DOCUMENTS OF DATASET <dataset>
|
|
2365
|
+
SEARCH <query> ON DATASETS <dataset>
|
|
2366
|
+
GENERATE NAVIGATION OF DATASET '<id>'
|
|
2367
|
+
NAVIGATION SEARCH '<query>' IN DATASET '<name or id>' MODE '<mode>' [TOP_K <n>]
|
|
2368
|
+
LIST METADATA OF DATASETS <dataset>[, <dataset>]*
|
|
2369
|
+
LIST METADATA SUMMARY OF DATASET <dataset> DOCUMENTS <doc_id>[, <doc_id>]*
|
|
2370
|
+
GET CHUNK <chunk_id>
|
|
2371
|
+
LIST CHUNKS OF DOCUMENT <doc_id> [PAGE <page>] [SIZE <size>] [KEYWORDS <keywords>] [AVAILABLE <0|1>]
|
|
2372
|
+
|
|
2373
|
+
Meta Commands:
|
|
2374
|
+
\\?, \\h, \\help Show this help
|
|
2375
|
+
\\q, \\quit, \\exit Quit the CLI
|
|
2376
|
+
"""
|
|
2377
|
+
print(help_text)
|
|
2378
|
+
|
|
2379
|
+
|
|
2380
|
+
def run_benchmark(client: RAGFlowClient, command_dict: dict):
|
|
2381
|
+
concurrency = command_dict.get("concurrency", 1)
|
|
2382
|
+
iterations = command_dict.get("iterations", 1)
|
|
2383
|
+
command: dict = command_dict["command"]
|
|
2384
|
+
command.update({"iterations": iterations})
|
|
2385
|
+
|
|
2386
|
+
command_type = command["type"]
|
|
2387
|
+
if concurrency < 1:
|
|
2388
|
+
print("Concurrency must be greater than 0")
|
|
2389
|
+
return
|
|
2390
|
+
elif concurrency == 1:
|
|
2391
|
+
result = run_command(client, command)
|
|
2392
|
+
success_count: int = 0
|
|
2393
|
+
response_list = result["response_list"]
|
|
2394
|
+
for response in response_list:
|
|
2395
|
+
match command_type:
|
|
2396
|
+
case "ping_server":
|
|
2397
|
+
if response.status_code == 200:
|
|
2398
|
+
success_count += 1
|
|
2399
|
+
case _:
|
|
2400
|
+
if response.status_code == 200:
|
|
2401
|
+
res_json = response.json()
|
|
2402
|
+
if res_json["code"] == 0:
|
|
2403
|
+
success_count += 1
|
|
2404
|
+
|
|
2405
|
+
total_duration = result["duration"]
|
|
2406
|
+
qps = iterations / total_duration if total_duration > 0 else None
|
|
2407
|
+
print(f"command: {command}, Concurrency: {concurrency}, iterations: {iterations}")
|
|
2408
|
+
print(f"total duration: {total_duration:.4f}s, QPS: {qps}, COMMAND_COUNT: {iterations}, SUCCESS: {success_count}, FAILURE: {iterations - success_count}")
|
|
2409
|
+
else:
|
|
2410
|
+
results: list[dict | None] = [None] * concurrency
|
|
2411
|
+
mp_context = mp.get_context("spawn")
|
|
2412
|
+
start_time = time.perf_counter()
|
|
2413
|
+
with ProcessPoolExecutor(max_workers=concurrency, mp_context=mp_context) as executor:
|
|
2414
|
+
future_map = {executor.submit(run_command, client, command): idx for idx in range(concurrency)}
|
|
2415
|
+
for future in as_completed(future_map):
|
|
2416
|
+
idx = future_map[future]
|
|
2417
|
+
results[idx] = future.result()
|
|
2418
|
+
end_time = time.perf_counter()
|
|
2419
|
+
success_count = 0
|
|
2420
|
+
for result in results:
|
|
2421
|
+
response_list = result["response_list"]
|
|
2422
|
+
for response in response_list:
|
|
2423
|
+
match command_type:
|
|
2424
|
+
case "ping_server":
|
|
2425
|
+
if response.status_code == 200:
|
|
2426
|
+
success_count += 1
|
|
2427
|
+
case _:
|
|
2428
|
+
if response.status_code == 200:
|
|
2429
|
+
res_json = response.json()
|
|
2430
|
+
if res_json["code"] == 0:
|
|
2431
|
+
success_count += 1
|
|
2432
|
+
|
|
2433
|
+
total_duration = end_time - start_time
|
|
2434
|
+
total_command_count = iterations * concurrency
|
|
2435
|
+
qps = total_command_count / total_duration if total_duration > 0 else None
|
|
2436
|
+
print(f"command: {command}, Concurrency: {concurrency} , iterations: {iterations}")
|
|
2437
|
+
print(f"total duration: {total_duration:.4f}s, QPS: {qps}, COMMAND_COUNT: {total_command_count}, SUCCESS: {success_count}, FAILURE: {total_command_count - success_count}")
|