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.
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}")