diff --git a/README.md b/README.md
index ed1e34558..fb9e2c6f7 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@
-
+
@@ -85,6 +85,7 @@ Try our demo at [https://demo.ragflow.io](https://demo.ragflow.io).
## 🔥 Latest Updates
+- 2025-12-26 Supports 'Memory' for AI agent.
- 2025-11-19 Supports Gemini 3 Pro.
- 2025-11-12 Supports data synchronization from Confluence, S3, Notion, Discord, Google Drive.
- 2025-10-23 Supports MinerU & Docling as document parsing methods.
@@ -187,12 +188,12 @@ releases! 🌟
> All Docker images are built for x86 platforms. We don't currently offer Docker images for ARM64.
> If you are on an ARM64 platform, follow [this guide](https://ragflow.io/docs/dev/build_docker_image) to build a Docker image compatible with your system.
-> The command below downloads the `v0.22.1` edition of the RAGFlow Docker image. See the following table for descriptions of different RAGFlow editions. To download a RAGFlow edition different from `v0.22.1`, update the `RAGFLOW_IMAGE` variable accordingly in **docker/.env** before using `docker compose` to start the server.
+> The command below downloads the `v0.23.0` edition of the RAGFlow Docker image. See the following table for descriptions of different RAGFlow editions. To download a RAGFlow edition different from `v0.23.0`, update the `RAGFLOW_IMAGE` variable accordingly in **docker/.env** before using `docker compose` to start the server.
```bash
$ cd ragflow/docker
- # git checkout v0.22.1
+ # git checkout v0.23.0
# Optional: use a stable tag (see releases: https://github.com/infiniflow/ragflow/releases)
# This step ensures the **entrypoint.sh** file in the code matches the Docker image version.
diff --git a/README_id.md b/README_id.md
index 4b6794040..88657cf3e 100644
--- a/README_id.md
+++ b/README_id.md
@@ -22,7 +22,7 @@
-
+
@@ -85,6 +85,7 @@ Coba demo kami di [https://demo.ragflow.io](https://demo.ragflow.io).
## 🔥 Pembaruan Terbaru
+- 2025-12-26 Mendukung 'Memori' untuk agen AI.
- 2025-11-19 Mendukung Gemini 3 Pro.
- 2025-11-12 Mendukung sinkronisasi data dari Confluence, S3, Notion, Discord, Google Drive.
- 2025-10-23 Mendukung MinerU & Docling sebagai metode penguraian dokumen.
@@ -187,12 +188,12 @@ Coba demo kami di [https://demo.ragflow.io](https://demo.ragflow.io).
> Semua gambar Docker dibangun untuk platform x86. Saat ini, kami tidak menawarkan gambar Docker untuk ARM64.
> Jika Anda menggunakan platform ARM64, [silakan gunakan panduan ini untuk membangun gambar Docker yang kompatibel dengan sistem Anda](https://ragflow.io/docs/dev/build_docker_image).
-> Perintah di bawah ini mengunduh edisi v0.22.1 dari gambar Docker RAGFlow. Silakan merujuk ke tabel berikut untuk deskripsi berbagai edisi RAGFlow. Untuk mengunduh edisi RAGFlow yang berbeda dari v0.22.1, perbarui variabel RAGFLOW_IMAGE di docker/.env sebelum menggunakan docker compose untuk memulai server.
+> Perintah di bawah ini mengunduh edisi v0.23.0 dari gambar Docker RAGFlow. Silakan merujuk ke tabel berikut untuk deskripsi berbagai edisi RAGFlow. Untuk mengunduh edisi RAGFlow yang berbeda dari v0.23.0, perbarui variabel RAGFLOW_IMAGE di docker/.env sebelum menggunakan docker compose untuk memulai server.
```bash
$ cd ragflow/docker
- # git checkout v0.22.1
+ # git checkout v0.23.0
# Opsional: gunakan tag stabil (lihat releases: https://github.com/infiniflow/ragflow/releases)
# This steps ensures the **entrypoint.sh** file in the code matches the Docker image version.
diff --git a/README_ja.md b/README_ja.md
index 58e2b7a52..d8c1cc264 100644
--- a/README_ja.md
+++ b/README_ja.md
@@ -22,7 +22,7 @@
-
+
@@ -66,7 +66,8 @@
## 🔥 最新情報
-- 2025-11-19 Gemini 3 Proをサポートしています
+- 2025-12-26 AIエージェントの「メモリ」機能をサポート。
+- 2025-11-19 Gemini 3 Proをサポートしています。
- 2025-11-12 Confluence、S3、Notion、Discord、Google Drive からのデータ同期をサポートします。
- 2025-10-23 ドキュメント解析方法として MinerU と Docling をサポートします。
- 2025-10-15 オーケストレーションされたデータパイプラインのサポート。
@@ -167,12 +168,12 @@
> 現在、公式に提供されているすべての Docker イメージは x86 アーキテクチャ向けにビルドされており、ARM64 用の Docker イメージは提供されていません。
> ARM64 アーキテクチャのオペレーティングシステムを使用している場合は、[このドキュメント](https://ragflow.io/docs/dev/build_docker_image)を参照して Docker イメージを自分でビルドしてください。
-> 以下のコマンドは、RAGFlow Docker イメージの v0.22.1 エディションをダウンロードします。異なる RAGFlow エディションの説明については、以下の表を参照してください。v0.22.1 とは異なるエディションをダウンロードするには、docker/.env ファイルの RAGFLOW_IMAGE 変数を適宜更新し、docker compose を使用してサーバーを起動してください。
+> 以下のコマンドは、RAGFlow Docker イメージの v0.23.0 エディションをダウンロードします。異なる RAGFlow エディションの説明については、以下の表を参照してください。v0.23.0 とは異なるエディションをダウンロードするには、docker/.env ファイルの RAGFLOW_IMAGE 変数を適宜更新し、docker compose を使用してサーバーを起動してください。
```bash
$ cd ragflow/docker
- # git checkout v0.22.1
+ # git checkout v0.23.0
# 任意: 安定版タグを利用 (一覧: https://github.com/infiniflow/ragflow/releases)
# この手順は、コード内の entrypoint.sh ファイルが Docker イメージのバージョンと一致していることを確認します。
diff --git a/README_ko.md b/README_ko.md
index f21d461b9..117bae8f9 100644
--- a/README_ko.md
+++ b/README_ko.md
@@ -22,7 +22,7 @@
-
+
@@ -67,6 +67,7 @@
## 🔥 업데이트
+- 2025-12-26 AI 에이전트의 '메모리' 기능 지원.
- 2025-11-19 Gemini 3 Pro를 지원합니다.
- 2025-11-12 Confluence, S3, Notion, Discord, Google Drive에서 데이터 동기화를 지원합니다.
- 2025-10-23 문서 파싱 방법으로 MinerU 및 Docling을 지원합니다.
@@ -169,12 +170,12 @@
> 모든 Docker 이미지는 x86 플랫폼을 위해 빌드되었습니다. 우리는 현재 ARM64 플랫폼을 위한 Docker 이미지를 제공하지 않습니다.
> ARM64 플랫폼을 사용 중이라면, [시스템과 호환되는 Docker 이미지를 빌드하려면 이 가이드를 사용해 주세요](https://ragflow.io/docs/dev/build_docker_image).
- > 아래 명령어는 RAGFlow Docker 이미지의 v0.22.1 버전을 다운로드합니다. 다양한 RAGFlow 버전에 대한 설명은 다음 표를 참조하십시오. v0.22.1과 다른 RAGFlow 버전을 다운로드하려면, docker/.env 파일에서 RAGFLOW_IMAGE 변수를 적절히 업데이트한 후 docker compose를 사용하여 서버를 시작하십시오.
+ > 아래 명령어는 RAGFlow Docker 이미지의 v0.23.0 버전을 다운로드합니다. 다양한 RAGFlow 버전에 대한 설명은 다음 표를 참조하십시오. v0.23.0과 다른 RAGFlow 버전을 다운로드하려면, docker/.env 파일에서 RAGFLOW_IMAGE 변수를 적절히 업데이트한 후 docker compose를 사용하여 서버를 시작하십시오.
```bash
$ cd ragflow/docker
- # git checkout v0.22.1
+ # git checkout v0.23.0
# Optional: use a stable tag (see releases: https://github.com/infiniflow/ragflow/releases)
# 이 단계는 코드의 entrypoint.sh 파일이 Docker 이미지 버전과 일치하도록 보장합니다.
diff --git a/README_pt_br.md b/README_pt_br.md
index 8d963b284..e6dfef5cf 100644
--- a/README_pt_br.md
+++ b/README_pt_br.md
@@ -22,7 +22,7 @@
-
+
@@ -86,6 +86,7 @@ Experimente nossa demo em [https://demo.ragflow.io](https://demo.ragflow.io).
## 🔥 Últimas Atualizações
+- 26-12-2025 Suporte à função 'Memória' para agentes de IA.
- 19-11-2025 Suporta Gemini 3 Pro.
- 12-11-2025 Suporta a sincronização de dados do Confluence, S3, Notion, Discord e Google Drive.
- 23-10-2025 Suporta MinerU e Docling como métodos de análise de documentos.
@@ -187,12 +188,12 @@ Experimente nossa demo em [https://demo.ragflow.io](https://demo.ragflow.io).
> Todas as imagens Docker são construídas para plataformas x86. Atualmente, não oferecemos imagens Docker para ARM64.
> Se você estiver usando uma plataforma ARM64, por favor, utilize [este guia](https://ragflow.io/docs/dev/build_docker_image) para construir uma imagem Docker compatível com o seu sistema.
- > O comando abaixo baixa a edição`v0.22.1` da imagem Docker do RAGFlow. Consulte a tabela a seguir para descrições de diferentes edições do RAGFlow. Para baixar uma edição do RAGFlow diferente da `v0.22.1`, atualize a variável `RAGFLOW_IMAGE` conforme necessário no **docker/.env** antes de usar `docker compose` para iniciar o servidor.
+ > O comando abaixo baixa a edição`v0.23.0` da imagem Docker do RAGFlow. Consulte a tabela a seguir para descrições de diferentes edições do RAGFlow. Para baixar uma edição do RAGFlow diferente da `v0.23.0`, atualize a variável `RAGFLOW_IMAGE` conforme necessário no **docker/.env** antes de usar `docker compose` para iniciar o servidor.
```bash
$ cd ragflow/docker
- # git checkout v0.22.1
+ # git checkout v0.23.0
# Opcional: use uma tag estável (veja releases: https://github.com/infiniflow/ragflow/releases)
# Esta etapa garante que o arquivo entrypoint.sh no código corresponda à versão da imagem do Docker.
diff --git a/README_tzh.md b/README_tzh.md
index 951bad319..3c55ca2e5 100644
--- a/README_tzh.md
+++ b/README_tzh.md
@@ -22,7 +22,7 @@
-
+
@@ -85,15 +85,16 @@
## 🔥 近期更新
-- 2025-11-19 支援 Gemini 3 Pro.
+- 2025-12-26 支援AI代理的「記憶」功能。
+- 2025-11-19 支援 Gemini 3 Pro。
- 2025-11-12 支援從 Confluence、S3、Notion、Discord、Google Drive 進行資料同步。
- 2025-10-23 支援 MinerU 和 Docling 作為文件解析方法。
- 2025-10-15 支援可編排的資料管道。
- 2025-08-08 支援 OpenAI 最新的 GPT-5 系列模型。
-- 2025-08-01 支援 agentic workflow 和 MCP
+- 2025-08-01 支援 agentic workflow 和 MCP。
- 2025-05-23 為 Agent 新增 Python/JS 程式碼執行器元件。
- 2025-05-05 支援跨語言查詢。
-- 2025-03-19 PDF和DOCX中的圖支持用多模態大模型去解析得到描述.
+- 2025-03-19 PDF和DOCX中的圖支持用多模態大模型去解析得到描述。
- 2024-12-18 升級了 DeepDoc 的文檔佈局分析模型。
- 2024-08-22 支援用 RAG 技術實現從自然語言到 SQL 語句的轉換。
@@ -186,12 +187,12 @@
> 所有 Docker 映像檔都是為 x86 平台建置的。目前,我們不提供 ARM64 平台的 Docker 映像檔。
> 如果您使用的是 ARM64 平台,請使用 [這份指南](https://ragflow.io/docs/dev/build_docker_image) 來建置適合您系統的 Docker 映像檔。
-> 執行以下指令會自動下載 RAGFlow Docker 映像 `v0.22.1`。請參考下表查看不同 Docker 發行版的說明。如需下載不同於 `v0.22.1` 的 Docker 映像,請在執行 `docker compose` 啟動服務之前先更新 **docker/.env** 檔案內的 `RAGFLOW_IMAGE` 變數。
+> 執行以下指令會自動下載 RAGFlow Docker 映像 `v0.23.0`。請參考下表查看不同 Docker 發行版的說明。如需下載不同於 `v0.23.0` 的 Docker 映像,請在執行 `docker compose` 啟動服務之前先更新 **docker/.env** 檔案內的 `RAGFLOW_IMAGE` 變數。
```bash
$ cd ragflow/docker
- # git checkout v0.22.1
+ # git checkout v0.23.0
# 可選:使用穩定版標籤(查看發佈:https://github.com/infiniflow/ragflow/releases)
# 此步驟確保程式碼中的 entrypoint.sh 檔案與 Docker 映像版本一致。
diff --git a/README_zh.md b/README_zh.md
index 2deb26818..1109f3bfe 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -22,7 +22,7 @@
-
+
@@ -85,7 +85,8 @@
## 🔥 近期更新
-- 2025-11-19 支持 Gemini 3 Pro.
+- 2025-12-26 支持AI代理的“记忆”功能。
+- 2025-11-19 支持 Gemini 3 Pro。
- 2025-11-12 支持从 Confluence、S3、Notion、Discord、Google Drive 进行数据同步。
- 2025-10-23 支持 MinerU 和 Docling 作为文档解析方法。
- 2025-10-15 支持可编排的数据管道。
@@ -93,7 +94,7 @@
- 2025-08-01 支持 agentic workflow 和 MCP。
- 2025-05-23 Agent 新增 Python/JS 代码执行器组件。
- 2025-05-05 支持跨语言查询。
-- 2025-03-19 PDF 和 DOCX 中的图支持用多模态大模型去解析得到描述.
+- 2025-03-19 PDF 和 DOCX 中的图支持用多模态大模型去解析得到描述。
- 2024-12-18 升级了 DeepDoc 的文档布局分析模型。
- 2024-08-22 支持用 RAG 技术实现从自然语言到 SQL 语句的转换。
@@ -187,12 +188,12 @@
> 请注意,目前官方提供的所有 Docker 镜像均基于 x86 架构构建,并不提供基于 ARM64 的 Docker 镜像。
> 如果你的操作系统是 ARM64 架构,请参考[这篇文档](https://ragflow.io/docs/dev/build_docker_image)自行构建 Docker 镜像。
- > 运行以下命令会自动下载 RAGFlow Docker 镜像 `v0.22.1`。请参考下表查看不同 Docker 发行版的描述。如需下载不同于 `v0.22.1` 的 Docker 镜像,请在运行 `docker compose` 启动服务之前先更新 **docker/.env** 文件内的 `RAGFLOW_IMAGE` 变量。
+ > 运行以下命令会自动下载 RAGFlow Docker 镜像 `v0.23.0`。请参考下表查看不同 Docker 发行版的描述。如需下载不同于 `v0.23.0` 的 Docker 镜像,请在运行 `docker compose` 启动服务之前先更新 **docker/.env** 文件内的 `RAGFLOW_IMAGE` 变量。
```bash
$ cd ragflow/docker
- # git checkout v0.22.1
+ # git checkout v0.23.0
# 可选:使用稳定版本标签(查看发布:https://github.com/infiniflow/ragflow/releases)
# 这一步确保代码中的 entrypoint.sh 文件与 Docker 镜像的版本保持一致。
diff --git a/admin/client/README.md b/admin/client/README.md
index 01a027839..bb6268134 100644
--- a/admin/client/README.md
+++ b/admin/client/README.md
@@ -48,7 +48,7 @@ It consists of a server-side Service and a command-line client (CLI), both imple
1. Ensure the Admin Service is running.
2. Install ragflow-cli.
```bash
- pip install ragflow-cli==0.22.1
+ pip install ragflow-cli==0.23.0
```
3. Launch the CLI client:
```bash
diff --git a/admin/client/admin_client.py b/admin/client/admin_client.py
index 8cad14bab..471dd109c 100644
--- a/admin/client/admin_client.py
+++ b/admin/client/admin_client.py
@@ -16,14 +16,14 @@
import argparse
import base64
-from cmd import Cmd
-
-from Cryptodome.PublicKey import RSA
-from Cryptodome.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
-from typing import Dict, List, Any
-from lark import Lark, Transformer, Tree
-import requests
import getpass
+from cmd import Cmd
+from typing import Any, Dict, List
+
+import requests
+from Cryptodome.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
+from Cryptodome.PublicKey import RSA
+from lark import Lark, Transformer, Tree
GRAMMAR = r"""
start: command
@@ -141,7 +141,6 @@ NUMBER: /[0-9]+/
class AdminTransformer(Transformer):
-
def start(self, items):
return items[0]
@@ -149,7 +148,7 @@ class AdminTransformer(Transformer):
return items[0]
def list_services(self, items):
- result = {'type': 'list_services'}
+ result = {"type": "list_services"}
return result
def show_service(self, items):
@@ -236,11 +235,7 @@ class AdminTransformer(Transformer):
action_list = items[1]
resource = items[3]
role_name = items[6]
- return {
- "type": "revoke_permission",
- "role_name": role_name,
- "resource": resource, "actions": action_list
- }
+ return {"type": "revoke_permission", "role_name": role_name, "resource": resource, "actions": action_list}
def alter_user_role(self, items):
user_name = items[2]
@@ -264,12 +259,12 @@ class AdminTransformer(Transformer):
# handle quoted parameter
parsed_args = []
for arg in args:
- if hasattr(arg, 'value'):
+ if hasattr(arg, "value"):
parsed_args.append(arg.value)
else:
parsed_args.append(str(arg))
- return {'type': 'meta', 'command': command_name, 'args': parsed_args}
+ return {"type": "meta", "command": command_name, "args": parsed_args}
def meta_command_name(self, items):
return items[0]
@@ -279,22 +274,22 @@ class AdminTransformer(Transformer):
def encrypt(input_string):
- pub = '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArq9XTUSeYr2+N1h3Afl/z8Dse/2yD0ZGrKwx+EEEcdsBLca9Ynmx3nIB5obmLlSfmskLpBo0UACBmB5rEjBp2Q2f3AG3Hjd4B+gNCG6BDaawuDlgANIhGnaTLrIqWrrcm4EMzJOnAOI1fgzJRsOOUEfaS318Eq9OVO3apEyCCt0lOQK6PuksduOjVxtltDav+guVAA068NrPYmRNabVKRNLJpL8w4D44sfth5RvZ3q9t+6RTArpEtc5sh5ChzvqPOzKGMXW83C95TxmXqpbK6olN4RevSfVjEAgCydH6HN6OhtOQEcnrU97r9H0iZOWwbw3pVrZiUkuRD1R56Wzs2wIDAQAB\n-----END PUBLIC KEY-----'
+ pub = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArq9XTUSeYr2+N1h3Afl/z8Dse/2yD0ZGrKwx+EEEcdsBLca9Ynmx3nIB5obmLlSfmskLpBo0UACBmB5rEjBp2Q2f3AG3Hjd4B+gNCG6BDaawuDlgANIhGnaTLrIqWrrcm4EMzJOnAOI1fgzJRsOOUEfaS318Eq9OVO3apEyCCt0lOQK6PuksduOjVxtltDav+guVAA068NrPYmRNabVKRNLJpL8w4D44sfth5RvZ3q9t+6RTArpEtc5sh5ChzvqPOzKGMXW83C95TxmXqpbK6olN4RevSfVjEAgCydH6HN6OhtOQEcnrU97r9H0iZOWwbw3pVrZiUkuRD1R56Wzs2wIDAQAB\n-----END PUBLIC KEY-----"
pub_key = RSA.importKey(pub)
cipher = Cipher_pkcs1_v1_5.new(pub_key)
- cipher_text = cipher.encrypt(base64.b64encode(input_string.encode('utf-8')))
+ cipher_text = cipher.encrypt(base64.b64encode(input_string.encode("utf-8")))
return base64.b64encode(cipher_text).decode("utf-8")
def encode_to_base64(input_string):
- base64_encoded = base64.b64encode(input_string.encode('utf-8'))
- return base64_encoded.decode('utf-8')
+ base64_encoded = base64.b64encode(input_string.encode("utf-8"))
+ return base64_encoded.decode("utf-8")
class AdminCLI(Cmd):
def __init__(self):
super().__init__()
- self.parser = Lark(GRAMMAR, start='start', parser='lalr', transformer=AdminTransformer())
+ self.parser = Lark(GRAMMAR, start="start", parser="lalr", transformer=AdminTransformer())
self.command_history = []
self.is_interactive = False
self.admin_account = "admin@ragflow.io"
@@ -312,7 +307,7 @@ class AdminCLI(Cmd):
result = self.parse_command(command)
if isinstance(result, dict):
- if 'type' in result and result.get('type') == 'empty':
+ if "type" in result and result.get("type") == "empty":
return False
self.execute_command(result)
@@ -320,7 +315,7 @@ class AdminCLI(Cmd):
if isinstance(result, Tree):
return False
- if result.get('type') == 'meta' and result.get('command') in ['q', 'quit', 'exit']:
+ if result.get("type") == "meta" and result.get("command") in ["q", "quit", "exit"]:
return True
except KeyboardInterrupt:
@@ -338,7 +333,7 @@ class AdminCLI(Cmd):
def parse_command(self, command_str: str) -> dict[str, str]:
if not command_str.strip():
- return {'type': 'empty'}
+ return {"type": "empty"}
self.command_history.append(command_str)
@@ -346,11 +341,11 @@ class AdminCLI(Cmd):
result = self.parser.parse(command_str)
return result
except Exception as e:
- return {'type': 'error', 'message': f'Parse error: {str(e)}'}
+ return {"type": "error", "message": f"Parse error: {str(e)}"}
def verify_admin(self, arguments: dict, single_command: bool):
- self.host = arguments['host']
- self.port = arguments['port']
+ self.host = arguments["host"]
+ self.port = arguments["port"]
print("Attempt to access server for admin login")
url = f"http://{self.host}:{self.port}/api/v1/admin/login"
@@ -365,25 +360,21 @@ class AdminCLI(Cmd):
return False
if single_command:
- admin_passwd = arguments['password']
+ admin_passwd = arguments["password"]
else:
admin_passwd = getpass.getpass(f"password for {self.admin_account}: ").strip()
try:
self.admin_password = encrypt(admin_passwd)
- response = self.session.post(url, json={'email': self.admin_account, 'password': self.admin_password})
+ response = self.session.post(url, json={"email": self.admin_account, "password": self.admin_password})
if response.status_code == 200:
res_json = response.json()
- error_code = res_json.get('code', -1)
+ error_code = res_json.get("code", -1)
if error_code == 0:
- self.session.headers.update({
- 'Content-Type': 'application/json',
- 'Authorization': response.headers['Authorization'],
- 'User-Agent': 'RAGFlow-CLI/0.22.1'
- })
+ self.session.headers.update({"Content-Type": "application/json", "Authorization": response.headers["Authorization"], "User-Agent": "RAGFlow-CLI/0.23.0"})
print("Authentication successful.")
return True
else:
- error_message = res_json.get('message', 'Unknown error')
+ error_message = res_json.get("message", "Unknown error")
print(f"Authentication failed: {error_message}, try again")
continue
else:
@@ -403,10 +394,14 @@ class AdminCLI(Cmd):
for k, v in data.items():
# display latest status
heartbeats = sorted(v, key=lambda x: x["now"], reverse=True)
- task_executor_list.append({
- "task_executor_name": k,
- **heartbeats[0],
- } if heartbeats else {"task_executor_name": k})
+ task_executor_list.append(
+ {
+ "task_executor_name": k,
+ **heartbeats[0],
+ }
+ if heartbeats
+ else {"task_executor_name": k}
+ )
return task_executor_list
def _print_table_simple(self, data):
@@ -422,12 +417,7 @@ class AdminCLI(Cmd):
col_widths = {}
def get_string_width(text):
- half_width_chars = (
- " !\"#$%&'()*+,-./0123456789:;<=>?@"
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"
- "abcdefghijklmnopqrstuvwxyz{|}~"
- "\t\n\r"
- )
+ half_width_chars = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\t\n\r"
width = 0
for char in text:
if char in half_width_chars:
@@ -439,7 +429,7 @@ class AdminCLI(Cmd):
for col in columns:
max_width = get_string_width(str(col))
for item in data:
- value_len = get_string_width(str(item.get(col, '')))
+ value_len = get_string_width(str(item.get(col, "")))
if value_len > max_width:
max_width = value_len
col_widths[col] = max(2, max_width)
@@ -457,16 +447,15 @@ class AdminCLI(Cmd):
for item in data:
row = "|"
for col in columns:
- value = str(item.get(col, ''))
+ value = str(item.get(col, ""))
if get_string_width(value) > col_widths[col]:
- value = value[:col_widths[col] - 3] + "..."
+ value = value[: col_widths[col] - 3] + "..."
row += f" {value:<{col_widths[col] - (get_string_width(value) - len(value))}} |"
print(row)
print(separator)
def run_interactive(self):
-
self.is_interactive = True
print("RAGFlow Admin command line interface - Type '\\?' for help, '\\q' to quit")
@@ -483,7 +472,7 @@ class AdminCLI(Cmd):
if isinstance(result, Tree):
continue
- if result.get('type') == 'meta' and result.get('command') in ['q', 'quit', 'exit']:
+ if result.get("type") == "meta" and result.get("command") in ["q", "quit", "exit"]:
break
except KeyboardInterrupt:
@@ -497,36 +486,30 @@ class AdminCLI(Cmd):
self.execute_command(result)
def parse_connection_args(self, args: List[str]) -> Dict[str, Any]:
- parser = argparse.ArgumentParser(description='Admin CLI Client', add_help=False)
- parser.add_argument('-h', '--host', default='localhost', help='Admin service host')
- parser.add_argument('-p', '--port', type=int, default=9381, help='Admin service port')
- parser.add_argument('-w', '--password', default='admin', type=str, help='Superuser password')
- parser.add_argument('command', nargs='?', help='Single command')
+ parser = argparse.ArgumentParser(description="Admin CLI Client", add_help=False)
+ parser.add_argument("-h", "--host", default="localhost", help="Admin service host")
+ parser.add_argument("-p", "--port", type=int, default=9381, help="Admin service port")
+ parser.add_argument("-w", "--password", default="admin", type=str, help="Superuser password")
+ parser.add_argument("command", nargs="?", help="Single command")
try:
parsed_args, remaining_args = parser.parse_known_args(args)
if remaining_args:
command = remaining_args[0]
- return {
- 'host': parsed_args.host,
- 'port': parsed_args.port,
- 'password': parsed_args.password,
- 'command': command
- }
+ return {"host": parsed_args.host, "port": parsed_args.port, "password": parsed_args.password, "command": command}
else:
return {
- 'host': parsed_args.host,
- 'port': parsed_args.port,
+ "host": parsed_args.host,
+ "port": parsed_args.port,
}
except SystemExit:
- return {'error': 'Invalid connection arguments'}
+ return {"error": "Invalid connection arguments"}
def execute_command(self, parsed_command: Dict[str, Any]):
-
command_dict: dict
if isinstance(parsed_command, Tree):
command_dict = parsed_command.children[0]
else:
- if parsed_command['type'] == 'error':
+ if parsed_command["type"] == "error":
print(f"Error: {parsed_command['message']}")
return
else:
@@ -534,56 +517,56 @@ class AdminCLI(Cmd):
# print(f"Parsed command: {command_dict}")
- command_type = command_dict['type']
+ command_type = command_dict["type"]
match command_type:
- case 'list_services':
+ case "list_services":
self._handle_list_services(command_dict)
- case 'show_service':
+ case "show_service":
self._handle_show_service(command_dict)
- case 'restart_service':
+ case "restart_service":
self._handle_restart_service(command_dict)
- case 'shutdown_service':
+ case "shutdown_service":
self._handle_shutdown_service(command_dict)
- case 'startup_service':
+ case "startup_service":
self._handle_startup_service(command_dict)
- case 'list_users':
+ case "list_users":
self._handle_list_users(command_dict)
- case 'show_user':
+ case "show_user":
self._handle_show_user(command_dict)
- case 'drop_user':
+ case "drop_user":
self._handle_drop_user(command_dict)
- case 'alter_user':
+ case "alter_user":
self._handle_alter_user(command_dict)
- case 'create_user':
+ case "create_user":
self._handle_create_user(command_dict)
- case 'activate_user':
+ case "activate_user":
self._handle_activate_user(command_dict)
- case 'list_datasets':
+ case "list_datasets":
self._handle_list_datasets(command_dict)
- case 'list_agents':
+ case "list_agents":
self._handle_list_agents(command_dict)
- case 'create_role':
+ case "create_role":
self._create_role(command_dict)
- case 'drop_role':
+ case "drop_role":
self._drop_role(command_dict)
- case 'alter_role':
+ case "alter_role":
self._alter_role(command_dict)
- case 'list_roles':
+ case "list_roles":
self._list_roles(command_dict)
- case 'show_role':
+ case "show_role":
self._show_role(command_dict)
- case 'grant_permission':
+ case "grant_permission":
self._grant_permission(command_dict)
- case 'revoke_permission':
+ case "revoke_permission":
self._revoke_permission(command_dict)
- case 'alter_user_role':
+ case "alter_user_role":
self._alter_user_role(command_dict)
- case 'show_user_permission':
+ case "show_user_permission":
self._show_user_permission(command_dict)
- case 'show_version':
+ case "show_version":
self._show_version(command_dict)
- case 'meta':
+ case "meta":
self._handle_meta_command(command_dict)
case _:
print(f"Command '{command_type}' would be executed with API")
@@ -591,29 +574,29 @@ class AdminCLI(Cmd):
def _handle_list_services(self, command):
print("Listing all services")
- url = f'http://{self.host}:{self.port}/api/v1/admin/services'
+ url = f"http://{self.host}:{self.port}/api/v1/admin/services"
response = self.session.get(url)
res_json = response.json()
if response.status_code == 200:
- self._print_table_simple(res_json['data'])
+ self._print_table_simple(res_json["data"])
else:
print(f"Fail to get all services, code: {res_json['code']}, message: {res_json['message']}")
def _handle_show_service(self, command):
- service_id: int = command['number']
+ service_id: int = command["number"]
print(f"Showing service: {service_id}")
- url = f'http://{self.host}:{self.port}/api/v1/admin/services/{service_id}'
+ url = f"http://{self.host}:{self.port}/api/v1/admin/services/{service_id}"
response = self.session.get(url)
res_json = response.json()
if response.status_code == 200:
- res_data = res_json['data']
- if 'status' in res_data and res_data['status'] == 'alive':
+ res_data = res_json["data"]
+ if "status" in res_data and res_data["status"] == "alive":
print(f"Service {res_data['service_name']} is alive, ")
- if isinstance(res_data['message'], str):
- print(res_data['message'])
+ if isinstance(res_data["message"], str):
+ print(res_data["message"])
else:
- data = self._format_service_detail_table(res_data['message'])
+ data = self._format_service_detail_table(res_data["message"])
self._print_table_simple(data)
else:
print(f"Service {res_data['service_name']} is down, {res_data['message']}")
@@ -621,47 +604,47 @@ class AdminCLI(Cmd):
print(f"Fail to show service, code: {res_json['code']}, message: {res_json['message']}")
def _handle_restart_service(self, command):
- service_id: int = command['number']
+ service_id: int = command["number"]
print(f"Restart service {service_id}")
def _handle_shutdown_service(self, command):
- service_id: int = command['number']
+ service_id: int = command["number"]
print(f"Shutdown service {service_id}")
def _handle_startup_service(self, command):
- service_id: int = command['number']
+ service_id: int = command["number"]
print(f"Startup service {service_id}")
def _handle_list_users(self, command):
print("Listing all users")
- url = f'http://{self.host}:{self.port}/api/v1/admin/users'
+ url = f"http://{self.host}:{self.port}/api/v1/admin/users"
response = self.session.get(url)
res_json = response.json()
if response.status_code == 200:
- self._print_table_simple(res_json['data'])
+ self._print_table_simple(res_json["data"])
else:
print(f"Fail to get all users, code: {res_json['code']}, message: {res_json['message']}")
def _handle_show_user(self, command):
- username_tree: Tree = command['user_name']
+ username_tree: Tree = command["user_name"]
user_name: str = username_tree.children[0].strip("'\"")
print(f"Showing user: {user_name}")
- url = f'http://{self.host}:{self.port}/api/v1/admin/users/{user_name}'
+ url = f"http://{self.host}:{self.port}/api/v1/admin/users/{user_name}"
response = self.session.get(url)
res_json = response.json()
if response.status_code == 200:
- table_data = res_json['data']
- table_data.pop('avatar')
+ table_data = res_json["data"]
+ table_data.pop("avatar")
self._print_table_simple(table_data)
else:
print(f"Fail to get user {user_name}, code: {res_json['code']}, message: {res_json['message']}")
def _handle_drop_user(self, command):
- username_tree: Tree = command['user_name']
+ username_tree: Tree = command["user_name"]
user_name: str = username_tree.children[0].strip("'\"")
print(f"Drop user: {user_name}")
- url = f'http://{self.host}:{self.port}/api/v1/admin/users/{user_name}'
+ url = f"http://{self.host}:{self.port}/api/v1/admin/users/{user_name}"
response = self.session.delete(url)
res_json = response.json()
if response.status_code == 200:
@@ -670,13 +653,13 @@ class AdminCLI(Cmd):
print(f"Fail to drop user, code: {res_json['code']}, message: {res_json['message']}")
def _handle_alter_user(self, command):
- user_name_tree: Tree = command['user_name']
+ user_name_tree: Tree = command["user_name"]
user_name: str = user_name_tree.children[0].strip("'\"")
- password_tree: Tree = command['password']
+ password_tree: Tree = command["password"]
password: str = password_tree.children[0].strip("'\"")
print(f"Alter user: {user_name}, password: ******")
- url = f'http://{self.host}:{self.port}/api/v1/admin/users/{user_name}/password'
- response = self.session.put(url, json={'new_password': encrypt(password)})
+ url = f"http://{self.host}:{self.port}/api/v1/admin/users/{user_name}/password"
+ response = self.session.put(url, json={"new_password": encrypt(password)})
res_json = response.json()
if response.status_code == 200:
print(res_json["message"])
@@ -684,32 +667,29 @@ class AdminCLI(Cmd):
print(f"Fail to alter password, code: {res_json['code']}, message: {res_json['message']}")
def _handle_create_user(self, command):
- user_name_tree: Tree = command['user_name']
+ user_name_tree: Tree = command["user_name"]
user_name: str = user_name_tree.children[0].strip("'\"")
- password_tree: Tree = command['password']
+ password_tree: Tree = command["password"]
password: str = password_tree.children[0].strip("'\"")
- role: str = command['role']
+ role: str = command["role"]
print(f"Create user: {user_name}, password: ******, role: {role}")
- url = f'http://{self.host}:{self.port}/api/v1/admin/users'
- response = self.session.post(
- url,
- json={'user_name': user_name, 'password': encrypt(password), 'role': role}
- )
+ url = f"http://{self.host}:{self.port}/api/v1/admin/users"
+ response = self.session.post(url, json={"user_name": user_name, "password": encrypt(password), "role": role})
res_json = response.json()
if response.status_code == 200:
- self._print_table_simple(res_json['data'])
+ self._print_table_simple(res_json["data"])
else:
print(f"Fail to create user {user_name}, code: {res_json['code']}, message: {res_json['message']}")
def _handle_activate_user(self, command):
- user_name_tree: Tree = command['user_name']
+ user_name_tree: Tree = command["user_name"]
user_name: str = user_name_tree.children[0].strip("'\"")
- activate_tree: Tree = command['activate_status']
+ activate_tree: Tree = command["activate_status"]
activate_status: str = activate_tree.children[0].strip("'\"")
- if activate_status.lower() in ['on', 'off']:
+ if activate_status.lower() in ["on", "off"]:
print(f"Alter user {user_name} activate status, turn {activate_status.lower()}.")
- url = f'http://{self.host}:{self.port}/api/v1/admin/users/{user_name}/activate'
- response = self.session.put(url, json={'activate_status': activate_status})
+ url = f"http://{self.host}:{self.port}/api/v1/admin/users/{user_name}/activate"
+ response = self.session.put(url, json={"activate_status": activate_status})
res_json = response.json()
if response.status_code == 200:
print(res_json["message"])
@@ -719,202 +699,182 @@ class AdminCLI(Cmd):
print(f"Unknown activate status: {activate_status}.")
def _handle_list_datasets(self, command):
- username_tree: Tree = command['user_name']
+ username_tree: Tree = command["user_name"]
user_name: str = username_tree.children[0].strip("'\"")
print(f"Listing all datasets of user: {user_name}")
- url = f'http://{self.host}:{self.port}/api/v1/admin/users/{user_name}/datasets'
+ url = f"http://{self.host}:{self.port}/api/v1/admin/users/{user_name}/datasets"
response = self.session.get(url)
res_json = response.json()
if response.status_code == 200:
- table_data = res_json['data']
+ table_data = res_json["data"]
for t in table_data:
- t.pop('avatar')
+ t.pop("avatar")
self._print_table_simple(table_data)
else:
print(f"Fail to get all datasets of {user_name}, code: {res_json['code']}, message: {res_json['message']}")
def _handle_list_agents(self, command):
- username_tree: Tree = command['user_name']
+ username_tree: Tree = command["user_name"]
user_name: str = username_tree.children[0].strip("'\"")
print(f"Listing all agents of user: {user_name}")
- url = f'http://{self.host}:{self.port}/api/v1/admin/users/{user_name}/agents'
+ url = f"http://{self.host}:{self.port}/api/v1/admin/users/{user_name}/agents"
response = self.session.get(url)
res_json = response.json()
if response.status_code == 200:
- table_data = res_json['data']
+ table_data = res_json["data"]
for t in table_data:
- t.pop('avatar')
+ t.pop("avatar")
self._print_table_simple(table_data)
else:
print(f"Fail to get all agents of {user_name}, code: {res_json['code']}, message: {res_json['message']}")
def _create_role(self, command):
- role_name_tree: Tree = command['role_name']
+ role_name_tree: Tree = command["role_name"]
role_name: str = role_name_tree.children[0].strip("'\"")
- desc_str: str = ''
- if 'description' in command:
- desc_tree: Tree = command['description']
+ desc_str: str = ""
+ if "description" in command:
+ desc_tree: Tree = command["description"]
desc_str = desc_tree.children[0].strip("'\"")
print(f"create role name: {role_name}, description: {desc_str}")
- url = f'http://{self.host}:{self.port}/api/v1/admin/roles'
- response = self.session.post(
- url,
- json={'role_name': role_name, 'description': desc_str}
- )
+ url = f"http://{self.host}:{self.port}/api/v1/admin/roles"
+ response = self.session.post(url, json={"role_name": role_name, "description": desc_str})
res_json = response.json()
if response.status_code == 200:
- self._print_table_simple(res_json['data'])
+ self._print_table_simple(res_json["data"])
else:
print(f"Fail to create role {role_name}, code: {res_json['code']}, message: {res_json['message']}")
def _drop_role(self, command):
- role_name_tree: Tree = command['role_name']
+ role_name_tree: Tree = command["role_name"]
role_name: str = role_name_tree.children[0].strip("'\"")
print(f"drop role name: {role_name}")
- url = f'http://{self.host}:{self.port}/api/v1/admin/roles/{role_name}'
+ url = f"http://{self.host}:{self.port}/api/v1/admin/roles/{role_name}"
response = self.session.delete(url)
res_json = response.json()
if response.status_code == 200:
- self._print_table_simple(res_json['data'])
+ self._print_table_simple(res_json["data"])
else:
print(f"Fail to drop role {role_name}, code: {res_json['code']}, message: {res_json['message']}")
def _alter_role(self, command):
- role_name_tree: Tree = command['role_name']
+ role_name_tree: Tree = command["role_name"]
role_name: str = role_name_tree.children[0].strip("'\"")
- desc_tree: Tree = command['description']
+ desc_tree: Tree = command["description"]
desc_str: str = desc_tree.children[0].strip("'\"")
print(f"alter role name: {role_name}, description: {desc_str}")
- url = f'http://{self.host}:{self.port}/api/v1/admin/roles/{role_name}'
- response = self.session.put(
- url,
- json={'description': desc_str}
- )
+ url = f"http://{self.host}:{self.port}/api/v1/admin/roles/{role_name}"
+ response = self.session.put(url, json={"description": desc_str})
res_json = response.json()
if response.status_code == 200:
- self._print_table_simple(res_json['data'])
+ self._print_table_simple(res_json["data"])
else:
- print(
- f"Fail to update role {role_name} with description: {desc_str}, code: {res_json['code']}, message: {res_json['message']}")
+ print(f"Fail to update role {role_name} with description: {desc_str}, code: {res_json['code']}, message: {res_json['message']}")
def _list_roles(self, command):
print("Listing all roles")
- url = f'http://{self.host}:{self.port}/api/v1/admin/roles'
+ url = f"http://{self.host}:{self.port}/api/v1/admin/roles"
response = self.session.get(url)
res_json = response.json()
if response.status_code == 200:
- self._print_table_simple(res_json['data'])
+ self._print_table_simple(res_json["data"])
else:
print(f"Fail to list roles, code: {res_json['code']}, message: {res_json['message']}")
def _show_role(self, command):
- role_name_tree: Tree = command['role_name']
+ role_name_tree: Tree = command["role_name"]
role_name: str = role_name_tree.children[0].strip("'\"")
print(f"show role: {role_name}")
- url = f'http://{self.host}:{self.port}/api/v1/admin/roles/{role_name}/permission'
+ url = f"http://{self.host}:{self.port}/api/v1/admin/roles/{role_name}/permission"
response = self.session.get(url)
res_json = response.json()
if response.status_code == 200:
- self._print_table_simple(res_json['data'])
+ self._print_table_simple(res_json["data"])
else:
print(f"Fail to list roles, code: {res_json['code']}, message: {res_json['message']}")
def _grant_permission(self, command):
- role_name_tree: Tree = command['role_name']
+ role_name_tree: Tree = command["role_name"]
role_name_str: str = role_name_tree.children[0].strip("'\"")
- resource_tree: Tree = command['resource']
+ resource_tree: Tree = command["resource"]
resource_str: str = resource_tree.children[0].strip("'\"")
- action_tree_list: list = command['actions']
+ action_tree_list: list = command["actions"]
actions: list = []
for action_tree in action_tree_list:
action_str: str = action_tree.children[0].strip("'\"")
actions.append(action_str)
print(f"grant role_name: {role_name_str}, resource: {resource_str}, actions: {actions}")
- url = f'http://{self.host}:{self.port}/api/v1/admin/roles/{role_name_str}/permission'
- response = self.session.post(
- url,
- json={'actions': actions, 'resource': resource_str}
- )
+ url = f"http://{self.host}:{self.port}/api/v1/admin/roles/{role_name_str}/permission"
+ response = self.session.post(url, json={"actions": actions, "resource": resource_str})
res_json = response.json()
if response.status_code == 200:
- self._print_table_simple(res_json['data'])
+ self._print_table_simple(res_json["data"])
else:
- print(
- f"Fail to grant role {role_name_str} with {actions} on {resource_str}, code: {res_json['code']}, message: {res_json['message']}")
+ print(f"Fail to grant role {role_name_str} with {actions} on {resource_str}, code: {res_json['code']}, message: {res_json['message']}")
def _revoke_permission(self, command):
- role_name_tree: Tree = command['role_name']
+ role_name_tree: Tree = command["role_name"]
role_name_str: str = role_name_tree.children[0].strip("'\"")
- resource_tree: Tree = command['resource']
+ resource_tree: Tree = command["resource"]
resource_str: str = resource_tree.children[0].strip("'\"")
- action_tree_list: list = command['actions']
+ action_tree_list: list = command["actions"]
actions: list = []
for action_tree in action_tree_list:
action_str: str = action_tree.children[0].strip("'\"")
actions.append(action_str)
print(f"revoke role_name: {role_name_str}, resource: {resource_str}, actions: {actions}")
- url = f'http://{self.host}:{self.port}/api/v1/admin/roles/{role_name_str}/permission'
- response = self.session.delete(
- url,
- json={'actions': actions, 'resource': resource_str}
- )
+ url = f"http://{self.host}:{self.port}/api/v1/admin/roles/{role_name_str}/permission"
+ response = self.session.delete(url, json={"actions": actions, "resource": resource_str})
res_json = response.json()
if response.status_code == 200:
- self._print_table_simple(res_json['data'])
+ self._print_table_simple(res_json["data"])
else:
- print(
- f"Fail to revoke role {role_name_str} with {actions} on {resource_str}, code: {res_json['code']}, message: {res_json['message']}")
+ print(f"Fail to revoke role {role_name_str} with {actions} on {resource_str}, code: {res_json['code']}, message: {res_json['message']}")
def _alter_user_role(self, command):
- role_name_tree: Tree = command['role_name']
+ role_name_tree: Tree = command["role_name"]
role_name_str: str = role_name_tree.children[0].strip("'\"")
- user_name_tree: Tree = command['user_name']
+ user_name_tree: Tree = command["user_name"]
user_name_str: str = user_name_tree.children[0].strip("'\"")
print(f"alter_user_role user_name: {user_name_str}, role_name: {role_name_str}")
- url = f'http://{self.host}:{self.port}/api/v1/admin/users/{user_name_str}/role'
- response = self.session.put(
- url,
- json={'role_name': role_name_str}
- )
+ url = f"http://{self.host}:{self.port}/api/v1/admin/users/{user_name_str}/role"
+ response = self.session.put(url, json={"role_name": role_name_str})
res_json = response.json()
if response.status_code == 200:
- self._print_table_simple(res_json['data'])
+ self._print_table_simple(res_json["data"])
else:
- print(
- f"Fail to alter user: {user_name_str} to role {role_name_str}, code: {res_json['code']}, message: {res_json['message']}")
+ print(f"Fail to alter user: {user_name_str} to role {role_name_str}, code: {res_json['code']}, message: {res_json['message']}")
def _show_user_permission(self, command):
- user_name_tree: Tree = command['user_name']
+ user_name_tree: Tree = command["user_name"]
user_name_str: str = user_name_tree.children[0].strip("'\"")
print(f"show_user_permission user_name: {user_name_str}")
- url = f'http://{self.host}:{self.port}/api/v1/admin/users/{user_name_str}/permission'
+ url = f"http://{self.host}:{self.port}/api/v1/admin/users/{user_name_str}/permission"
response = self.session.get(url)
res_json = response.json()
if response.status_code == 200:
- self._print_table_simple(res_json['data'])
+ self._print_table_simple(res_json["data"])
else:
- print(
- f"Fail to show user: {user_name_str} permission, code: {res_json['code']}, message: {res_json['message']}")
+ print(f"Fail to show user: {user_name_str} permission, code: {res_json['code']}, message: {res_json['message']}")
def _show_version(self, command):
print("show_version")
- url = f'http://{self.host}:{self.port}/api/v1/admin/version'
+ url = f"http://{self.host}:{self.port}/api/v1/admin/version"
response = self.session.get(url)
res_json = response.json()
if response.status_code == 200:
- self._print_table_simple(res_json['data'])
+ self._print_table_simple(res_json["data"])
else:
print(f"Fail to show version, code: {res_json['code']}, message: {res_json['message']}")
def _handle_meta_command(self, command):
- meta_command = command['command']
- args = command.get('args', [])
+ meta_command = command["command"]
+ args = command.get("args", [])
- if meta_command in ['?', 'h', 'help']:
+ if meta_command in ["?", "h", "help"]:
self.show_help()
- elif meta_command in ['q', 'quit', 'exit']:
+ elif meta_command in ["q", "quit", "exit"]:
print("Goodbye!")
else:
print(f"Meta command '{meta_command}' with args {args}")
@@ -950,16 +910,16 @@ def main():
cli = AdminCLI()
args = cli.parse_connection_args(sys.argv)
- if 'error' in args:
+ if "error" in args:
print("Error: Invalid connection arguments")
return
- if 'command' in args:
- if 'password' not in args:
+ if "command" in args:
+ if "password" not in args:
print("Error: password is missing")
return
if cli.verify_admin(args, single_command=True):
- command: str = args['command']
+ command: str = args["command"]
# print(f"Run single command: {command}")
cli.run_single_command(command)
else:
@@ -974,5 +934,5 @@ def main():
cli.cmdloop()
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/admin/client/pyproject.toml b/admin/client/pyproject.toml
index db99755ab..691a685e1 100644
--- a/admin/client/pyproject.toml
+++ b/admin/client/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "ragflow-cli"
-version = "0.22.1"
+version = "0.23.0"
description = "Admin Service's client of [RAGFlow](https://github.com/infiniflow/ragflow). The Admin Service provides user management and system monitoring. "
authors = [{ name = "Lynn", email = "lynn_inf@hotmail.com" }]
license = { text = "Apache License, Version 2.0" }
diff --git a/admin/client/uv.lock b/admin/client/uv.lock
index 5a76402a2..5b414495a 100644
--- a/admin/client/uv.lock
+++ b/admin/client/uv.lock
@@ -196,7 +196,7 @@ wheels = [
[[package]]
name = "ragflow-cli"
-version = "0.22.1"
+version = "0.23.0"
source = { virtual = "." }
dependencies = [
{ name = "beartype" },
diff --git a/docker/.env b/docker/.env
index 09944c013..79d0d3fc6 100644
--- a/docker/.env
+++ b/docker/.env
@@ -128,11 +128,11 @@ ADMIN_SVR_HTTP_PORT=9381
SVR_MCP_PORT=9382
# The RAGFlow Docker image to download. v0.22+ doesn't include embedding models.
-RAGFLOW_IMAGE=infiniflow/ragflow:v0.22.1
+RAGFLOW_IMAGE=infiniflow/ragflow:v0.23.0
# If you cannot download the RAGFlow Docker image:
-# RAGFLOW_IMAGE=swr.cn-north-4.myhuaweicloud.com/infiniflow/ragflow:v0.22.1
-# RAGFLOW_IMAGE=registry.cn-hangzhou.aliyuncs.com/infiniflow/ragflow:v0.22.1
+# RAGFLOW_IMAGE=swr.cn-north-4.myhuaweicloud.com/infiniflow/ragflow:v0.23.0
+# RAGFLOW_IMAGE=registry.cn-hangzhou.aliyuncs.com/infiniflow/ragflow:v0.23.0
#
# - For the `nightly` edition, uncomment either of the following:
# RAGFLOW_IMAGE=swr.cn-north-4.myhuaweicloud.com/infiniflow/ragflow:nightly
diff --git a/docker/README.md b/docker/README.md
index 5de0946b0..d37a28879 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -77,7 +77,7 @@ The [.env](./.env) file contains important environment variables for Docker.
- `SVR_HTTP_PORT`
The port used to expose RAGFlow's HTTP API service to the host machine, allowing **external** access to the service running inside the Docker container. Defaults to `9380`.
- `RAGFLOW-IMAGE`
- The Docker image edition. Defaults to `infiniflow/ragflow:v0.22.1`. The RAGFlow Docker image does not include embedding models.
+ The Docker image edition. Defaults to `infiniflow/ragflow:v0.23.0`. The RAGFlow Docker image does not include embedding models.
> [!TIP]
diff --git a/docs/configurations.md b/docs/configurations.md
index f2602767c..f8d2964ef 100644
--- a/docs/configurations.md
+++ b/docs/configurations.md
@@ -99,7 +99,7 @@ RAGFlow utilizes MinIO as its object storage solution, leveraging its scalabilit
- `SVR_HTTP_PORT`
The port used to expose RAGFlow's HTTP API service to the host machine, allowing **external** access to the service running inside the Docker container. Defaults to `9380`.
- `RAGFLOW-IMAGE`
- The Docker image edition. Defaults to `infiniflow/ragflow:v0.22.1` (the RAGFlow Docker image without embedding models).
+ The Docker image edition. Defaults to `infiniflow/ragflow:v0.23.0` (the RAGFlow Docker image without embedding models).
:::tip NOTE
If you cannot download the RAGFlow Docker image, try the following mirrors.
diff --git a/docs/develop/build_docker_image.mdx b/docs/develop/build_docker_image.mdx
index e2d2361e9..1e8fa942a 100644
--- a/docs/develop/build_docker_image.mdx
+++ b/docs/develop/build_docker_image.mdx
@@ -47,7 +47,7 @@ After building the infiniflow/ragflow:nightly image, you are ready to launch a f
1. Edit Docker Compose Configuration
-Open the `docker/.env` file. Find the `RAGFLOW_IMAGE` setting and change the image reference from `infiniflow/ragflow:v0.22.1` to `infiniflow/ragflow:nightly` to use the pre-built image.
+Open the `docker/.env` file. Find the `RAGFLOW_IMAGE` setting and change the image reference from `infiniflow/ragflow:v0.23.0` to `infiniflow/ragflow:nightly` to use the pre-built image.
2. Launch the Service
diff --git a/docs/guides/dataset/configure_knowledge_base.md b/docs/guides/dataset/configure_knowledge_base.md
index 649dba9cc..7985d3374 100644
--- a/docs/guides/dataset/configure_knowledge_base.md
+++ b/docs/guides/dataset/configure_knowledge_base.md
@@ -133,7 +133,7 @@ See [Run retrieval test](./run_retrieval_test.md) for details.
## Search for dataset
-As of RAGFlow v0.22.1, the search feature is still in a rudimentary form, supporting only dataset search by name.
+As of RAGFlow v0.23.0, the search feature is still in a rudimentary form, supporting only dataset search by name.

diff --git a/docs/guides/manage_files.md b/docs/guides/manage_files.md
index ecef0509c..474a010d4 100644
--- a/docs/guides/manage_files.md
+++ b/docs/guides/manage_files.md
@@ -87,4 +87,4 @@ RAGFlow's file management allows you to download an uploaded file:

-> As of RAGFlow v0.22.1, bulk download is not supported, nor can you download an entire folder.
+> As of RAGFlow v0.23.0, bulk download is not supported, nor can you download an entire folder.
diff --git a/docs/guides/manage_users_and_services.md b/docs/guides/manage_users_and_services.md
index 6c06c40f8..0784df909 100644
--- a/docs/guides/manage_users_and_services.md
+++ b/docs/guides/manage_users_and_services.md
@@ -46,7 +46,7 @@ The Admin CLI and Admin Service form a client-server architectural suite for RAG
2. Install ragflow-cli.
```bash
- pip install ragflow-cli==0.22.1
+ pip install ragflow-cli==0.23.0
```
3. Launch the CLI client:
diff --git a/docs/guides/upgrade_ragflow.mdx b/docs/guides/upgrade_ragflow.mdx
index eb213d079..6195ffd47 100644
--- a/docs/guides/upgrade_ragflow.mdx
+++ b/docs/guides/upgrade_ragflow.mdx
@@ -60,16 +60,16 @@ To upgrade RAGFlow, you must upgrade **both** your code **and** your Docker imag
git pull
```
-3. Switch to the latest, officially published release, e.g., `v0.22.1`:
+3. Switch to the latest, officially published release, e.g., `v0.23.0`:
```bash
- git checkout -f v0.22.1
+ git checkout -f v0.23.0
```
4. Update **ragflow/docker/.env**:
```bash
- RAGFLOW_IMAGE=infiniflow/ragflow:v0.22.1
+ RAGFLOW_IMAGE=infiniflow/ragflow:v0.23.0
```
5. Update the RAGFlow image and restart RAGFlow:
@@ -90,10 +90,10 @@ No, you do not need to. Upgrading RAGFlow in itself will *not* remove your uploa
1. From an environment with Internet access, pull the required Docker image.
2. Save the Docker image to a **.tar** file.
```bash
- docker save -o ragflow.v0.22.1.tar infiniflow/ragflow:v0.22.1
+ docker save -o ragflow.v0.23.0.tar infiniflow/ragflow:v0.23.0
```
3. Copy the **.tar** file to the target server.
4. Load the **.tar** file into Docker:
```bash
- docker load -i ragflow.v0.22.1.tar
+ docker load -i ragflow.v0.23.0.tar
```
diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx
index bafaa676b..fe4068614 100644
--- a/docs/quickstart.mdx
+++ b/docs/quickstart.mdx
@@ -46,7 +46,7 @@ This section provides instructions on setting up the RAGFlow server on Linux. If
`vm.max_map_count`. This value sets the maximum number of memory map areas a process may have. Its default value is 65530. While most applications require fewer than a thousand maps, reducing this value can result in abnormal behaviors, and the system will throw out-of-memory errors when a process reaches the limitation.
- RAGFlow v0.22.1 uses Elasticsearch or [Infinity](https://github.com/infiniflow/infinity) for multiple recall. Setting the value of `vm.max_map_count` correctly is crucial to the proper functioning of the Elasticsearch component.
+ RAGFlow v0.23.0 uses Elasticsearch or [Infinity](https://github.com/infiniflow/infinity) for multiple recall. Setting the value of `vm.max_map_count` correctly is crucial to the proper functioning of the Elasticsearch component.