Fix admin CLI system variable commands (#14956)

## What

Fixes #12409.

Implements admin CLI support for:

- `list vars;`
- `show var <name-or-prefix>;`
- `set var <name> <value>;`

## Changes

- Wire Go CLI variable commands to the admin API.
- Support integer and quoted string values in `SET VAR`.
- Return variable rows as `data_type`, `name`, `setting_type`, and
`value`.
- Add exact-name lookup with prefix fallback for `SHOW VAR`.
- Validate values by stored data type: `string`, `integer`, `bool`, and
`json`.
- Keep the legacy Python admin CLI/server behavior aligned.
- Update admin CLI docs and add focused tests.

## Verification

- `go test -count=1 ./internal/cli`
- `python3.12 -m py_compile admin/server/services.py
admin/server/routes.py api/db/services/system_settings_service.py
admin/client/parser.py admin/client/ragflow_client.py`
- Python admin CLI parser smoke test for `SET VAR`, quoted values, `SHOW
VAR`, and `LIST VARS`.
- Attempted `./run_go_tests.sh`; local environment is missing native
tokenizer/linker artifacts:
  - `internal/cpp/cmake-build-release/librag_tokenizer_c_api.a`
  - `-lstdc++`

Co-authored-by: Jin Hai <haijin.chn@gmail.com>
This commit is contained in:
Jake Armstrong
2026-05-18 01:08:45 -10:00
committed by GitHub
parent 732e4741c4
commit 93d3deb5e4
16 changed files with 646 additions and 84 deletions

View File

@@ -264,7 +264,7 @@ generate_key: GENERATE KEY FOR USER quoted_string ";"
list_keys: LIST KEYS OF quoted_string ";"
drop_key: DROP KEY quoted_string OF quoted_string ";"
set_variable: SET VAR identifier identifier ";"
set_variable: SET VAR identifier variable_value ";"
show_variable: SHOW VAR identifier ";"
list_variables: LIST VARS ";"
list_configs: LIST CONFIGS ";"
@@ -378,6 +378,7 @@ update_chunk: UPDATE CHUNK quoted_string OF DATASET quoted_string SET quoted_str
identifier_list: identifier (COMMA identifier)*
identifier: WORD
variable_value: WORD | NUMBER | QUOTED_STRING
quoted_string: QUOTED_STRING
status: ON | WORD

View File

@@ -43,6 +43,12 @@ def encrypt(input_string):
return base64.b64encode(cipher_text).decode("utf-8")
def _strip_tree_value(value):
if isinstance(value, Tree):
value = value.children[0]
return str(value).strip("'\"")
class RAGFlowClient:
def __init__(self, http_client: HttpClient, server_type: str):
self.http_client = http_client
@@ -526,10 +532,8 @@ class RAGFlowClient:
if self.server_type != "admin":
print("This command is only allowed in ADMIN mode")
var_name_tree: Tree = command["var_name"]
var_name = var_name_tree.children[0].strip("'\"")
var_value_tree: Tree = command["var_value"]
var_value = var_value_tree.children[0].strip("'\"")
var_name = _strip_tree_value(command["var_name"])
var_value = _strip_tree_value(command["var_value"])
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")
@@ -544,8 +548,7 @@ class RAGFlowClient:
if self.server_type != "admin":
print("This command is only allowed in ADMIN mode")
var_name_tree: Tree = command["var_name"]
var_name = var_name_tree.children[0].strip("'\"")
var_name = _strip_tree_value(command["var_name"])
response = self.http_client.request(method="GET", path="/admin/variables", json_body={"var_name": var_name},
use_api_base=True, auth_kind="admin")
res_json = response.json()

View File

@@ -421,7 +421,7 @@ def get_user_permission(user_name: str):
def set_variable():
try:
data = request.get_json()
if not data and "var_name" not in data:
if not data or "var_name" not in data:
return error_response("Var name is required", 400)
if "var_value" not in data:
@@ -449,7 +449,7 @@ def get_variable():
# get var
data = request.get_json()
if not data and "var_name" not in data:
if not data or "var_name" not in data:
return error_response("Var name is required", 400)
var_name: str = data["var_name"]
res = SettingsMgr.get_by_name(var_name)

View File

@@ -330,36 +330,65 @@ class ServiceMgr:
class SettingsMgr:
@staticmethod
def _format_setting(setting):
return {
"data_type": setting.data_type,
"name": setting.name,
"setting_type": "config",
"value": setting.value,
}
@staticmethod
def _validate_value(name: str, data_type: str, value: str):
data_type = data_type.lower()
value = str(value)
if data_type == "string":
return
if data_type == "integer":
try:
int(value)
except ValueError:
raise AdminException(f"Invalid integer value for {name}: {value}")
return
if data_type in {"bool", "boolean"}:
if value not in {"true", "false"}:
raise AdminException(f"Invalid bool value for {name}: expected true or false")
return
if data_type == "json":
try:
json.loads(value)
except json.JSONDecodeError:
raise AdminException(f"Invalid JSON value for {name}")
return
raise AdminException(f"Unsupported data type for {name}: {data_type}")
@staticmethod
def _infer_data_type(name: str):
if name.startswith("sandbox."):
return "json"
if name.endswith(".enabled"):
return "bool"
return "string"
@staticmethod
def get_all():
settings = SystemSettingsService.get_all()
settings = SystemSettingsService.get_all(reverse=False, order_by="name")
result = []
for setting in settings:
result.append(
{
"name": setting.name,
"source": setting.source,
"data_type": setting.data_type,
"value": setting.value,
}
)
result.append(SettingsMgr._format_setting(setting))
return result
@staticmethod
def get_by_name(name: str):
settings = SystemSettingsService.get_by_name(name)
if len(settings) == 0:
raise AdminException(f"Can't get setting: {name}")
settings = SystemSettingsService.get_by_name_prefix(name)
if len(settings) == 0:
raise AdminException(f"Can't get setting: {name}")
result = []
for setting in settings:
result.append(
{
"name": setting.name,
"source": setting.source,
"data_type": setting.data_type,
"value": setting.value,
}
)
result.append(SettingsMgr._format_setting(setting))
return result
@staticmethod
@@ -367,6 +396,7 @@ class SettingsMgr:
settings = SystemSettingsService.get_by_name(name)
if len(settings) == 1:
setting = settings[0]
SettingsMgr._validate_value(name, setting.data_type, value)
setting.value = value
setting_dict = setting.to_dict()
SystemSettingsService.update_by_name(name, setting_dict)
@@ -376,12 +406,8 @@ class SettingsMgr:
# Create new setting if it doesn't exist
# Determine data_type based on name and value
if name.startswith("sandbox."):
data_type = "json"
elif name.endswith(".enabled"):
data_type = "boolean"
else:
data_type = "string"
data_type = SettingsMgr._infer_data_type(name)
SettingsMgr._validate_value(name, data_type, value)
new_setting = {
"name": name,

View File

@@ -26,7 +26,13 @@ class SystemSettingsService(CommonService):
@classmethod
@DB.connection_context()
def get_by_name(cls, name):
objs = cls.model.select().where(cls.model.name == name)
objs = cls.model.select().where(cls.model.name == name).order_by(cls.model.name.asc())
return objs
@classmethod
@DB.connection_context()
def get_by_name_prefix(cls, name_prefix):
objs = cls.model.select().where(cls.model.name.startswith(name_prefix)).order_by(cls.model.name.asc())
return objs
@classmethod

View File

@@ -468,18 +468,18 @@ Revoke successfully!
```
ragflow> list vars;
+-----------+---------------------+--------------+-----------+
| data_type | name | source | value |
| data_type | name | setting_type | value |
+-----------+---------------------+--------------+-----------+
| string | default_role | variable | user |
| bool | enable_whitelist | variable | true |
| string | mail.default_sender | variable | |
| string | mail.password | variable | |
| integer | mail.port | variable | 15 |
| string | mail.server | variable | localhost |
| integer | mail.timeout | variable | 10 |
| bool | mail.use_ssl | variable | true |
| bool | mail.use_tls | variable | false |
| string | mail.username | variable | |
| string | default_role | config | user |
| bool | enable_whitelist | config | true |
| string | mail.default_sender | config | |
| string | mail.password | config | |
| integer | mail.port | config | 15 |
| string | mail.server | config | localhost |
| integer | mail.timeout | config | 10 |
| bool | mail.use_ssl | config | true |
| bool | mail.use_tls | config | false |
| string | mail.username | config | |
+-----------+---------------------+--------------+-----------+
```
@@ -490,9 +490,9 @@ ragflow> list vars;
```
ragflow> show var mail.server;
+-----------+-------------+--------------+-----------+
| data_type | name | source | value |
| data_type | name | setting_type | value |
+-----------+-------------+--------------+-----------+
| string | mail.server | variable | localhost |
| string | mail.server | config | localhost |
+-----------+-------------+--------------+-----------+
```

View File

@@ -21,6 +21,7 @@ import (
"crypto/tls"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"net/http"
@@ -1451,9 +1452,59 @@ func NewAdminException(message string) *AdminException {
}
}
func formatSystemSetting(setting entity.SystemSettings) map[string]interface{} {
return map[string]interface{}{
"data_type": setting.DataType,
"name": setting.Name,
"setting_type": "config",
"value": setting.Value,
}
}
func formatSystemSettings(settings []entity.SystemSettings) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(settings))
for _, setting := range settings {
result = append(result, formatSystemSetting(setting))
}
return result
}
func validateSystemSettingValue(setting entity.SystemSettings, value string) error {
dataType := strings.ToLower(setting.DataType)
switch dataType {
case "string":
return nil
case "integer", "int":
if _, err := strconv.Atoi(value); err != nil {
return NewAdminException(fmt.Sprintf("Invalid integer value for %s: %s", setting.Name, value))
}
case "bool", "boolean":
if value != "true" && value != "false" {
return NewAdminException(fmt.Sprintf("Invalid bool value for %s: expected true or false", setting.Name))
}
case "json":
if !json.Valid([]byte(value)) {
return NewAdminException(fmt.Sprintf("Invalid JSON value for %s", setting.Name))
}
default:
return NewAdminException(fmt.Sprintf("Unsupported data type for %s: %s", setting.Name, setting.DataType))
}
return nil
}
func inferSystemSettingDataType(name string) string {
if strings.HasPrefix(name, "sandbox.") {
return "json"
}
if strings.HasSuffix(name, ".enabled") {
return "bool"
}
return "string"
}
// GetVariable get variable by name
// Returns the system setting with the given name
// Returns AdminException if the setting is not found
// Returns the exact system setting with the given name, or settings matching the
// given name prefix when an exact setting does not exist.
func (s *Service) GetVariable(varName string) ([]map[string]interface{}, error) {
settings, err := s.systemSettingsDAO.GetByName(varName)
if err != nil {
@@ -1461,19 +1512,15 @@ func (s *Service) GetVariable(varName string) ([]map[string]interface{}, error)
}
if len(settings) == 0 {
return nil, NewAdminException("Can't get setting: " + varName)
settings, err = s.systemSettingsDAO.GetByNamePrefix(varName)
if err != nil {
return nil, err
}
if len(settings) == 0 {
return nil, NewAdminException("Can't get setting: " + varName)
}
}
result := make([]map[string]interface{}, 0, len(settings))
for _, setting := range settings {
result = append(result, map[string]interface{}{
"name": setting.Name,
"source": setting.Source,
"data_type": setting.DataType,
"value": setting.Value,
})
}
return result, nil
return formatSystemSettings(settings), nil
}
// GetAllVariables get all variables
@@ -1484,16 +1531,7 @@ func (s *Service) GetAllVariables() ([]map[string]interface{}, error) {
return nil, err
}
result := make([]map[string]interface{}, 0, len(settings))
for _, setting := range settings {
result = append(result, map[string]interface{}{
"name": setting.Name,
"source": setting.Source,
"data_type": setting.DataType,
"value": setting.Value,
})
}
return result, nil
return formatSystemSettings(settings), nil
}
// SetVariable set variable
@@ -1507,27 +1545,25 @@ func (s *Service) SetVariable(varName, varValue string) error {
if len(settings) == 1 {
setting := &settings[0]
if err := validateSystemSettingValue(*setting, varValue); err != nil {
return err
}
setting.Value = varValue
return s.systemSettingsDAO.UpdateByName(varName, setting)
} else if len(settings) > 1 {
return NewAdminException("Can't update more than 1 setting: " + varName)
}
// Create new setting if it doesn't exist
// Determine data_type based on name and value
dataType := "string"
if len(varName) >= 7 && varName[:7] == "sandbox" {
dataType = "json"
} else if len(varName) >= 9 && varName[len(varName)-9:] == ".enabled" {
dataType = "boolean"
}
dataType := inferSystemSettingDataType(varName)
newSetting := &entity.SystemSettings{
Name: varName,
Value: varValue,
Source: "admin",
DataType: dataType,
}
if err := validateSystemSettingValue(*newSetting, varValue); err != nil {
return err
}
return s.systemSettingsDAO.Create(newSetting)
}

View File

@@ -0,0 +1,65 @@
//
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package admin
import (
"ragflow/internal/entity"
"testing"
)
func TestValidateSystemSettingValue(t *testing.T) {
tests := []struct {
name string
dataType string
value string
wantError bool
}{
{name: "string accepts arbitrary text", dataType: "string", value: "local host"},
{name: "integer accepts digits", dataType: "integer", value: "15"},
{name: "integer rejects text", dataType: "integer", value: "localhost", wantError: true},
{name: "bool accepts true", dataType: "bool", value: "true"},
{name: "bool accepts false", dataType: "bool", value: "false"},
{name: "bool rejects non bool", dataType: "bool", value: "yes", wantError: true},
{name: "json accepts object", dataType: "json", value: `{"endpoint":"http://localhost:9385"}`},
{name: "json rejects invalid", dataType: "json", value: "{", wantError: true},
{name: "unknown type rejects", dataType: "float", value: "1.2", wantError: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setting := entity.SystemSettings{Name: "test.setting", DataType: tt.dataType}
err := validateSystemSettingValue(setting, tt.value)
if (err != nil) != tt.wantError {
t.Fatalf("validateSystemSettingValue() error = %v, wantError %v", err, tt.wantError)
}
})
}
}
func TestInferSystemSettingDataType(t *testing.T) {
tests := map[string]string{
"sandbox.self_managed": "json",
"mail.enabled": "bool",
"mail.server": "string",
}
for name, want := range tests {
if got := inferSystemSettingDataType(name); got != want {
t.Fatalf("inferSystemSettingDataType(%q) = %q, want %q", name, got, want)
}
}
}

View File

@@ -596,6 +596,142 @@ func (c *RAGFlowClient) ShowService(cmd *Command) (ResponseIf, error) {
return &result, nil
}
func normalizeVariableRows(rows []map[string]interface{}) {
for _, row := range rows {
if _, ok := row["setting_type"]; ok {
delete(row, "source")
continue
}
if _, ok := row["source"]; ok {
row["setting_type"] = "config"
delete(row, "source")
}
}
}
// ListVariables lists all system variables (admin mode only).
func (c *RAGFlowClient) ListVariables(cmd *Command) (ResponseIf, error) {
if c.ServerType != "admin" {
return nil, fmt.Errorf("this command is only allowed in ADMIN mode")
}
iterations := 1
if val, ok := cmd.Params["iterations"].(int); ok && val > 1 {
iterations = val
}
if iterations > 1 {
return c.HTTPClient.RequestWithIterations("GET", "/admin/variables", "admin", nil, nil, iterations)
}
resp, err := c.HTTPClient.Request("GET", "/admin/variables", "admin", nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to list variables: %w", err)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("failed to list variables: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
}
var result CommonResponse
if err = json.Unmarshal(resp.Body, &result); err != nil {
return nil, fmt.Errorf("list variables failed: invalid JSON (%w)", err)
}
if result.Code != 0 {
return nil, fmt.Errorf("%s", result.Message)
}
normalizeVariableRows(result.Data)
result.Duration = resp.Duration
return &result, nil
}
// ShowVariable shows system variables by exact name or name prefix (admin mode only).
func (c *RAGFlowClient) ShowVariable(cmd *Command) (ResponseIf, error) {
if c.ServerType != "admin" {
return nil, fmt.Errorf("this command is only allowed in ADMIN mode")
}
varName, ok := cmd.Params["var_name"].(string)
if !ok {
return nil, fmt.Errorf("var_name not provided")
}
iterations := 1
if val, ok := cmd.Params["iterations"].(int); ok && val > 1 {
iterations = val
}
payload := map[string]interface{}{"var_name": varName}
if iterations > 1 {
return c.HTTPClient.RequestWithIterations("GET", "/admin/variables", "admin", nil, payload, iterations)
}
resp, err := c.HTTPClient.Request("GET", "/admin/variables", "admin", nil, payload)
if err != nil {
return nil, fmt.Errorf("failed to show variable: %w", err)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("failed to show variable: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
}
var result CommonResponse
if err = json.Unmarshal(resp.Body, &result); err != nil {
return nil, fmt.Errorf("show variable failed: invalid JSON (%w)", err)
}
if result.Code != 0 {
return nil, fmt.Errorf("%s", result.Message)
}
normalizeVariableRows(result.Data)
result.Duration = resp.Duration
return &result, nil
}
// SetVariable updates a system variable (admin mode only).
func (c *RAGFlowClient) SetVariable(cmd *Command) (ResponseIf, error) {
if c.ServerType != "admin" {
return nil, fmt.Errorf("this command is only allowed in ADMIN mode")
}
varName, ok := cmd.Params["var_name"].(string)
if !ok {
return nil, fmt.Errorf("var_name not provided")
}
varValue, ok := cmd.Params["var_value"].(string)
if !ok {
return nil, fmt.Errorf("var_value not provided")
}
payload := map[string]interface{}{
"var_name": varName,
"var_value": varValue,
}
resp, err := c.HTTPClient.Request("PUT", "/admin/variables", "admin", nil, payload)
if err != nil {
return nil, fmt.Errorf("failed to set variable: %w", err)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("failed to set variable: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
}
var result MessageResponse
if err = json.Unmarshal(resp.Body, &result); err != nil {
return nil, fmt.Errorf("set variable failed: invalid JSON (%w)", err)
}
if result.Code != 0 {
return nil, fmt.Errorf("%s", result.Message)
}
result.Duration = resp.Duration
return &result, nil
}
// ListUsers lists all users (admin mode only)
// Returns (result_map, error) - result_map is non-nil for benchmark mode
func (c *RAGFlowClient) ListUsers(cmd *Command) (ResponseIf, error) {

View File

@@ -1173,7 +1173,7 @@ func (p *Parser) parseAdminSetVariable() (*Command, error) {
}
p.nextToken()
varValue, err := p.parseIdentifier()
varValue, err := p.parseVariableValue()
if err != nil {
return nil, err
}

View File

@@ -0,0 +1,235 @@
//
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package cli
import (
"encoding/json"
"io"
"net"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"testing"
)
func TestParseAdminVariableCommands(t *testing.T) {
tests := []struct {
name string
input string
command string
varName string
varValue string
hasValue bool
adminMode bool
}{
{
name: "list variables",
input: "list vars;",
command: "list_variables",
adminMode: true,
},
{
name: "show variables by prefix",
input: "show var mail;",
command: "show_variable",
varName: "mail",
adminMode: true,
},
{
name: "set integer variable",
input: "set var mail.port 15;",
command: "set_variable",
varName: "mail.port",
varValue: "15",
hasValue: true,
adminMode: true,
},
{
name: "set quoted string variable",
input: `set var mail.server "local host";`,
command: "set_variable",
varName: "mail.server",
varValue: "local host",
hasValue: true,
adminMode: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd, err := NewParser(tt.input).Parse(tt.adminMode)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if cmd.Type != tt.command {
t.Fatalf("command type = %q, want %q", cmd.Type, tt.command)
}
if tt.varName != "" && cmd.Params["var_name"] != tt.varName {
t.Fatalf("var_name = %v, want %q", cmd.Params["var_name"], tt.varName)
}
if tt.hasValue && cmd.Params["var_value"] != tt.varValue {
t.Fatalf("var_value = %v, want %q", cmd.Params["var_value"], tt.varValue)
}
})
}
}
func newAdminTestClient(t *testing.T, handler http.HandlerFunc) (*RAGFlowClient, func()) {
t.Helper()
server := httptest.NewServer(handler)
serverURL, err := url.Parse(server.URL)
if err != nil {
t.Fatalf("parse test server URL: %v", err)
}
host, portText, err := net.SplitHostPort(serverURL.Host)
if err != nil {
t.Fatalf("split host port: %v", err)
}
port, err := strconv.Atoi(portText)
if err != nil {
t.Fatalf("parse port: %v", err)
}
client := NewRAGFlowClient("admin")
client.HTTPClient.Host = host
client.HTTPClient.Port = port
client.HTTPClient.client = server.Client()
client.HTTPClient.LoginToken = "test-token"
return client, server.Close
}
func TestListVariablesUsesAdminVariablesEndpoint(t *testing.T) {
client, closeServer := newAdminTestClient(t, func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
t.Errorf("method = %s, want GET", r.Method)
return
}
if r.URL.Path != "/api/v1/admin/variables" {
t.Errorf("path = %s, want /api/v1/admin/variables", r.URL.Path)
return
}
if r.Header.Get("Authorization") != "test-token" {
t.Errorf("Authorization header = %q", r.Header.Get("Authorization"))
return
}
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"code": 0,
"message": "",
"data": []map[string]interface{}{
{"data_type": "string", "name": "mail.server", "source": "variable", "value": "localhost"},
},
})
})
defer closeServer()
resp, err := client.ListVariables(NewCommand("list_variables"))
if err != nil {
t.Fatalf("ListVariables() error = %v", err)
}
result := resp.(*CommonResponse)
if got := result.Data[0]["setting_type"]; got != "config" {
t.Fatalf("setting_type = %v, want config", got)
}
if _, ok := result.Data[0]["source"]; ok {
t.Fatalf("source column should be normalized away: %#v", result.Data[0])
}
}
func TestShowVariableSendsRequestedName(t *testing.T) {
client, closeServer := newAdminTestClient(t, func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
t.Errorf("method = %s, want GET", r.Method)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
t.Errorf("read body: %v", err)
return
}
var request map[string]string
if err := json.Unmarshal(body, &request); err != nil {
t.Errorf("request body is not JSON: %v", err)
return
}
if request["var_name"] != "mail" {
t.Errorf("var_name = %q, want mail", request["var_name"])
return
}
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"code": 0,
"message": "",
"data": []map[string]interface{}{
{"data_type": "string", "name": "mail.server", "setting_type": "config", "value": "localhost"},
},
})
})
defer closeServer()
cmd := NewCommand("show_variable")
cmd.Params["var_name"] = "mail"
resp, err := client.ShowVariable(cmd)
if err != nil {
t.Fatalf("ShowVariable() error = %v", err)
}
result := resp.(*CommonResponse)
if got := result.Data[0]["name"]; got != "mail.server" {
t.Fatalf("name = %v, want mail.server", got)
}
}
func TestSetVariableReturnsServerConfirmation(t *testing.T) {
client, closeServer := newAdminTestClient(t, func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPut {
t.Errorf("method = %s, want PUT", r.Method)
return
}
var request map[string]string
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
t.Errorf("request body is not JSON: %v", err)
return
}
if request["var_name"] != "mail.server" {
t.Errorf("var_name = %q, want mail.server", request["var_name"])
return
}
if request["var_value"] != "localhost" {
t.Errorf("var_value = %q, want localhost", request["var_value"])
return
}
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"code": 0,
"message": "Set variable successfully",
"data": nil,
})
})
defer closeServer()
cmd := NewCommand("set_variable")
cmd.Params["var_name"] = "mail.server"
cmd.Params["var_value"] = "localhost"
resp, err := client.SetVariable(cmd)
if err != nil {
t.Fatalf("SetVariable() error = %v", err)
}
result := resp.(*MessageResponse)
if result.Message != "Set variable successfully" {
t.Fatalf("message = %q, want Set variable successfully", result.Message)
}
}

View File

@@ -155,6 +155,12 @@ func (c *RAGFlowClient) ExecuteAdminCommand(cmd *Command) (ResponseIf, error) {
return c.ShowAdminVersion(cmd)
case "show_user":
return c.ShowUser(cmd)
case "list_variables":
return c.ListVariables(cmd)
case "show_variable":
return c.ShowVariable(cmd)
case "set_variable":
return c.SetVariable(cmd)
case "list_user_datasets":
return c.ListUserDatasets(cmd)
case "list_agents":

View File

@@ -285,6 +285,15 @@ func (p *Parser) parseIdentifier() (string, error) {
return p.curToken.Value, nil
}
func (p *Parser) parseVariableValue() (string, error) {
switch p.curToken.Type {
case TokenIdentifier, TokenQuotedString, TokenInteger, TokenFloat:
return p.curToken.Value, nil
default:
return "", fmt.Errorf("expected variable value, got %s", p.curToken.Value)
}
}
func (p *Parser) parseNumber() (int, error) {
if p.curToken.Type != TokenInteger {
return 0, fmt.Errorf("expected number, got %s", p.curToken.Value)

View File

@@ -149,6 +149,34 @@ func (r *SimpleResponse) PrintOut() {
}
}
type MessageResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Duration float64
OutputFormat OutputFormat
}
func (r *MessageResponse) Type() string {
return "message"
}
func (r *MessageResponse) TimeCost() float64 {
return r.Duration
}
func (r *MessageResponse) SetOutputFormat(format OutputFormat) {
r.OutputFormat = format
}
func (r *MessageResponse) PrintOut() {
if r.Code == 0 {
fmt.Println(r.Message)
} else {
fmt.Println("ERROR")
fmt.Printf("%d, %s\n", r.Code, r.Message)
}
}
type NonStreamResponse struct {
Code int `json:"code"`
ReasoningContent string `json:"reasoning_content"`

View File

@@ -1875,7 +1875,7 @@ func (p *Parser) parseSetVariable() (*Command, error) {
}
p.nextToken()
varValue, err := p.parseIdentifier()
varValue, err := p.parseVariableValue()
if err != nil {
return nil, err
}

View File

@@ -35,7 +35,7 @@ func NewSystemSettingsDAO() *SystemSettingsDAO {
// Returns all system settings records from database
func (d *SystemSettingsDAO) GetAll() ([]entity.SystemSettings, error) {
var settings []entity.SystemSettings
err := DB.Find(&settings).Error
err := DB.Order("name ASC").Find(&settings).Error
if err != nil {
return nil, err
}
@@ -46,7 +46,18 @@ func (d *SystemSettingsDAO) GetAll() ([]entity.SystemSettings, error) {
// Returns settings records that match the given name
func (d *SystemSettingsDAO) GetByName(name string) ([]entity.SystemSettings, error) {
var settings []entity.SystemSettings
err := DB.Where("name = ?", name).Find(&settings).Error
err := DB.Where("name = ?", name).Order("name ASC").Find(&settings).Error
if err != nil {
return nil, err
}
return settings, nil
}
// GetByNamePrefix get system settings by name prefix
// Returns settings records whose names start with the given prefix.
func (d *SystemSettingsDAO) GetByNamePrefix(namePrefix string) ([]entity.SystemSettings, error) {
var settings []entity.SystemSettings
err := DB.Where("name LIKE ?", namePrefix+"%").Order("name ASC").Find(&settings).Error
if err != nil {
return nil, err
}