From d4ef3d21d12224ed989bb786c6c4b55e3aa06ad8 Mon Sep 17 00:00:00 2001 From: Jin Hai Date: Mon, 29 Jun 2026 11:13:14 +0800 Subject: [PATCH] Go CLI: Add create and drop commands (#16430) ### What problem does this PR solve? 1. Add CREATE and DROP DATASET / MEMORY / AGENT / SEARCH / CHAT. 2. Add option to build.sh to strip RAGFlow binary. ### Type of change - [x] Refactoring --------- Signed-off-by: Jin Hai --- build.sh | 27 ++- internal/cli/admin_parser.go | 36 ---- internal/cli/cli_http.go | 12 ++ internal/cli/common_command.go | 76 +++++++ internal/cli/lexer.go | 2 + internal/cli/response.go | 50 +++++ internal/cli/types.go | 1 + internal/cli/user_command.go | 380 +++++++++++++++++++++++---------- internal/cli/user_parser.go | 139 +++++++----- internal/development.md | 8 + internal/service/dataset.go | 2 +- 11 files changed, 530 insertions(+), 203 deletions(-) diff --git a/build.sh b/build.sh index 5d4350b107..d483c4be37 100755 --- a/build.sh +++ b/build.sh @@ -19,6 +19,9 @@ ADMIN_SERVER_BINARY="$PROJECT_ROOT/bin/admin_server" INGESTOR_BINARY="$PROJECT_ROOT/bin/ingestor" RAGFLOW_CLI_BINARY="$PROJECT_ROOT/bin/ragflow-cli" +# Strip symbols from Go binaries (set via --strip / -s) +STRIP_SYMBOLS="" + # office_oxide native library settings OFFICE_OXIDE_PREFIX="${HOME}/.office_oxide" OFFICE_OXIDE_VERSION="0.1.2" @@ -255,19 +258,22 @@ build_go() { setup_cgo_env + local strip_flags=() + [ -n "$STRIP_SYMBOLS" ] && strip_flags=(-ldflags="-s -w") + echo "Building RAGFlow binary: $RAGFLOW_SERVER_BINARY, $ADMIN_SERVER_BINARY, $INGESTOR_BINARY, and $RAGFLOW_CLI_BINARY" GOPROXY=${GOPROXY:-https://goproxy.cn,https://proxy.golang.org,direct} CGO_ENABLED=1 \ CGO_CFLAGS="$CGO_CFLAGS" CGO_LDFLAGS="$CGO_LDFLAGS" \ - go build -o "$RAGFLOW_SERVER_BINARY" cmd/server_main.go + go build "${strip_flags[@]}" -o "$RAGFLOW_SERVER_BINARY" cmd/server_main.go GOPROXY=${GOPROXY:-https://goproxy.cn,https://proxy.golang.org,direct} CGO_ENABLED=1 \ CGO_CFLAGS="$CGO_CFLAGS" CGO_LDFLAGS="$CGO_LDFLAGS" \ - go build -o "$ADMIN_SERVER_BINARY" cmd/admin_server.go + go build "${strip_flags[@]}" -o "$ADMIN_SERVER_BINARY" cmd/admin_server.go GOPROXY=${GOPROXY:-https://goproxy.cn,https://proxy.golang.org,direct} CGO_ENABLED=1 \ CGO_CFLAGS="$CGO_CFLAGS" CGO_LDFLAGS="$CGO_LDFLAGS" \ - go build -o "$INGESTOR_BINARY" cmd/ingestor.go + go build "${strip_flags[@]}" -o "$INGESTOR_BINARY" cmd/ingestor.go GOPROXY=${GOPROXY:-https://goproxy.cn,https://proxy.golang.org,direct} CGO_ENABLED=1 \ CGO_CFLAGS="$CGO_CFLAGS" CGO_LDFLAGS="$CGO_LDFLAGS" \ - go build -o "$RAGFLOW_CLI_BINARY" cmd/ragflow-cli.go + go build "${strip_flags[@]}" -o "$RAGFLOW_CLI_BINARY" cmd/ragflow-cli.go if [ ! -f "$RAGFLOW_SERVER_BINARY" ]; then echo -e "${RED}Error: Failed to build RAGFlow server binary${NC}" @@ -389,6 +395,8 @@ OPTIONS: `$0 --test -- -run TestFoo ./internal/admin/...` --clean, -C Clean all build artifacts --run, -r Build and run the server + --strip, -s Strip debug symbols from Go binaries (-ldflags="-s -w") + (disabled by default, useful for smaller production binaries) --help, -h Show this help message EXAMPLES: @@ -415,7 +423,16 @@ EOF # Main function main() { - case "${1:-}" in + # Parse --strip / -s before other arguments + local args=() + for arg in "$@"; do + case "$arg" in + --strip|-s) STRIP_SYMBOLS="1" ;; + *) args+=("$arg") ;; + esac + done + + case "${args[0]:-}" in --cpp|-c) check_cpp_deps build_cpp diff --git a/internal/cli/admin_parser.go b/internal/cli/admin_parser.go index f0a7b66f50..739e6eb2cc 100644 --- a/internal/cli/admin_parser.go +++ b/internal/cli/admin_parser.go @@ -1269,42 +1269,6 @@ func (p *Parser) parseAdminDropRole() (*Command, error) { return cmd, nil } -func (p *Parser) parseAdminDropDataset() (*Command, error) { - p.nextToken() // consume DATASET - datasetName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("drop_user_dataset") - cmd.Params["dataset_name"] = datasetName - - p.nextToken() - // Semicolon is optional - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseAdminDropChat() (*Command, error) { - p.nextToken() // consume CHAT - chatName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("drop_user_chat") - cmd.Params["chat_name"] = chatName - - p.nextToken() - // Semicolon is optional - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - // endregion DROP commands // region ALTER commands diff --git a/internal/cli/cli_http.go b/internal/cli/cli_http.go index dc256ad586..b1b42a589e 100644 --- a/internal/cli/cli_http.go +++ b/internal/cli/cli_http.go @@ -312,6 +312,8 @@ func (c *CLI) ExecuteUserCommand(cmd *Command) (ResponseIf, error) { return c.APIListChatsCommand(cmd) case "api_list_searches": return c.APIListSearchesCommand(cmd) + case "api_list_memories": + return c.APIListMemoriesCommand(cmd) case "list_dataset_documents": return c.ListDatasetDocumentUserCommand(cmd) case "search_on_datasets": @@ -380,6 +382,16 @@ func (c *CLI) ExecuteUserCommand(cmd *Command) (ResponseIf, error) { return c.APIListProviders(cmd) case "delete_provider": return c.DeleteProvider(cmd) + case "api_drop_dataset": + return c.APIDropDatasetCommand(cmd) + case "api_drop_chat": + return c.APIDropChatCommand(cmd) + case "api_drop_search": + return c.APIDropSearchCommand(cmd) + case "api_drop_memory": + return c.APIDropMemoryCommand(cmd) + case "api_drop_agent": + return c.APIDropAgentCommand(cmd) // Provider instance commands case "api_create_provider_instance": return c.APICreateProviderInstanceCommand(cmd) diff --git a/internal/cli/common_command.go b/internal/cli/common_command.go index e58ba1051a..3a427d71d1 100644 --- a/internal/cli/common_command.go +++ b/internal/cli/common_command.go @@ -1613,3 +1613,79 @@ func (c *CLI) getDatasetIDByName(datasetName string) (string, error) { } return "", fmt.Errorf("dataset %s not found", datasetName) } + +func (c *CLI) getAgentIDByName(agentName string) (string, error) { + response, err := c.APIListAgentsCommand(nil) + if err != nil { + return "", err + } + commonResponse, ok := response.(*CommonResponse) + if !ok { + return "", fmt.Errorf("invalid response") + } + for _, agent := range commonResponse.Data { + if agent["name"] == agentName { + return agent["id"].(string), nil + } + } + return "", fmt.Errorf("agent %s not found", agentName) +} + +func (c *CLI) getSearchIDByName(searchName string) (string, error) { + response, err := c.APIListSearchesCommand(nil) + if err != nil { + return "", err + } + searchesResponse, ok := response.(*ListSearchesResponse) + if !ok { + return "", fmt.Errorf("invalid response") + } + searches := searchesResponse.Data["search_apps"].([]interface{}) + for _, search := range searches { + searchMap := search.(map[string]interface{}) + if searchMap["name"] == searchName { + return searchMap["id"].(string), nil + } + } + return "", fmt.Errorf("search %s not found", searchName) +} + +func (c *CLI) getChatIDByName(chatName string) (string, error) { + response, err := c.APIListChatsCommand(nil) + if err != nil { + return "", err + } + commonResponse, ok := response.(*CommonResponse) + if !ok { + return "", fmt.Errorf("invalid response") + } + for _, chat := range commonResponse.Data { + if chat["name"] == chatName { + return chat["id"].(string), nil + } + } + return "", fmt.Errorf("chat %s not found", chatName) +} + +func (c *CLI) getMemoryIDByName(memoryName string) (string, error) { + response, err := c.APIListMemoriesCommand(nil) + if err != nil { + return "", err + } + listMemoriesResponse, ok := response.(*ListMemoriesResponse) + memories := listMemoriesResponse.Data["memory_list"].([]interface{}) + if !ok { + return "", fmt.Errorf("invalid response") + } + for _, memory := range memories { + var memoryMap map[string]interface{} + memoryMap, ok = memory.(map[string]interface{}) + if !ok { + continue + } + if memoryMap["name"] == memoryName { + return memoryMap["id"].(string), nil + } + } + return "", fmt.Errorf("memory %s not found", memoryName) +} diff --git a/internal/cli/lexer.go b/internal/cli/lexer.go index eb86169699..767939bf23 100644 --- a/internal/cli/lexer.go +++ b/internal/cli/lexer.go @@ -365,6 +365,8 @@ func (l *Lexer) lookupIdent(ident string) Token { return Token{Type: TokenAgent, Value: ident} case "MEMORY": return Token{Type: TokenMemory, Value: ident} + case "MEMORIES": + return Token{Type: TokenMemories, Value: ident} case "RETRIEVE": return Token{Type: TokenRetrieve, Value: ident} case "CURRENT": diff --git a/internal/cli/response.go b/internal/cli/response.go index d9dafd254b..7f629a4292 100644 --- a/internal/cli/response.go +++ b/internal/cli/response.go @@ -288,6 +288,42 @@ func (r *ListSearchesResponse) PrintOut() { } } +type ListMemoriesResponse struct { + Code int `json:"code"` + Data map[string]interface{} `json:"data"` + Message string `json:"message"` + Duration float64 + OutputFormat OutputFormat +} + +func (r *ListMemoriesResponse) Type() string { + return "list_memories" +} + +func (r *ListMemoriesResponse) TimeCost() float64 { + return r.Duration +} + +func (r *ListMemoriesResponse) SetOutputFormat(format OutputFormat) { + r.OutputFormat = format +} + +func (r *ListMemoriesResponse) PrintOut() { + if r.Code == 0 { + total := r.Data["total_count"].(float64) + fmt.Printf("Total: %0.0f\n", total) + docs := r.Data["memory_list"].([]interface{}) + table := make([]map[string]interface{}, 0) + for _, doc := range docs { + table = append(table, doc.(map[string]interface{})) + } + PrintTableSimpleByFormat(table, r.OutputFormat) + } else { + fmt.Println("ERROR") + fmt.Printf("%d, %s\n", r.Code, r.Message) + } +} + type ChunkResponse struct { Code int `json:"code"` Data map[string]interface{} `json:"data"` @@ -414,6 +450,20 @@ func (r *SimpleResponse) PrintOut() { } } +func HandleSimpleResponse(response *Response, command string) (ResponseIf, error) { + var result SimpleResponse + if err := json.Unmarshal(response.Body, &result); err != nil { + return nil, fmt.Errorf("%s failed: invalid JSON (%w)", command, err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + + result.Duration = response.Duration + return &result, nil +} + type MessageResponse struct { Code int `json:"code"` Message string `json:"message"` diff --git a/internal/cli/types.go b/internal/cli/types.go index e2d8bc79d0..46a6a9c310 100644 --- a/internal/cli/types.go +++ b/internal/cli/types.go @@ -102,6 +102,7 @@ const ( TokenPipeline TokenSearch TokenAgent + TokenMemories TokenMemory TokenRetrieve TokenCurrent diff --git a/internal/cli/user_command.go b/internal/cli/user_command.go index 96cc6766d1..cc78abbe96 100644 --- a/internal/cli/user_command.go +++ b/internal/cli/user_command.go @@ -248,16 +248,10 @@ func (c *CLI) APISetLogLevelCommand(cmd *Command) (ResponseIf, error) { } if resp.StatusCode != 200 { - return nil, fmt.Errorf("failed to register user: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + return nil, fmt.Errorf("failed to change log level: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) } - var result SimpleResponse - if err = json.Unmarshal(resp.Body, &result); err != nil { - return nil, fmt.Errorf("change log level failed: invalid JSON (%w)", err) - } - result.Code = 0 - result.Duration = resp.Duration - return &result, nil + return HandleSimpleResponse(resp, "change log level") } func (c *CLI) RegisterUser(cmd *Command) (ResponseIf, error) { @@ -602,6 +596,50 @@ func (c *CLI) APIListSearchesCommand(cmd *Command) (ResponseIf, error) { return &result, nil } +// APIListMemoriesCommand lists memories +func (c *CLI) APIListMemoriesCommand(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + + if httpClient.LoginToken == nil && !c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].useAPIKey { + return nil, fmt.Errorf("no authorization") + } + + authKind := "web" + if httpClient.useAPIKey { + authKind = "api" + } + + if httpClient.LoginToken != nil { + authKind = "web" + } + + // Normal mode + resp, err := httpClient.Request("GET", "/memories", authKind, nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to list memories: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to list memories: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + + var result ListMemoriesResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("list memories failed: invalid JSON (%w)", err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + result.Duration = resp.Duration + + return &result, nil +} + // ListDatasetDocumentUserCommand lists dataset documents func (c *CLI) ListDatasetDocumentUserCommand(cmd *Command) (ResponseIf, error) { if c.Config.CLIMode != APIMode { @@ -1000,7 +1038,16 @@ func (c *CLI) APICreateDatasetCommand(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("no authorization") } - resp, err := httpClient.Request("POST", "/datasets", "web", nil, nil) + datasetName, ok := cmd.Params["dataset_name"].(string) + if !ok { + return nil, fmt.Errorf("dataset_name parameter is required") + } + + payload := map[string]interface{}{ + "name": datasetName, + } + + resp, err := httpClient.Request("POST", "/datasets", "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to create dataset: %w", err) } @@ -1009,20 +1056,7 @@ func (c *CLI) APICreateDatasetCommand(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("failed to create dataset: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) } - var createResult CommonDataResponse - if err = json.Unmarshal(resp.Body, &createResult); err != nil { - return nil, fmt.Errorf("create dataset failed: invalid JSON (%w)", err) - } - - if createResult.Code != 0 { - return nil, fmt.Errorf("error code: %d, message: %s", createResult.Code, createResult.Message) - } - - var result SimpleResponse - result.Code = 0 - result.Message = "Dataset created successfully" - result.Duration = resp.Duration - return &result, nil + return HandleSimpleResponse(resp, "create dataset") } func (c *CLI) APICreateAgentCommand(cmd *Command) (ResponseIf, error) { @@ -1111,7 +1145,16 @@ func (c *CLI) APICreateSearchCommand(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("no authorization") } - resp, err := httpClient.Request("POST", "/searches", "web", nil, nil) + searchName, ok := cmd.Params["search_name"].(string) + if !ok { + return nil, fmt.Errorf("search_name parameter is required") + } + + payload := map[string]interface{}{ + "name": searchName, + } + + resp, err := httpClient.Request("POST", "/searches", "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to create search: %w", err) } @@ -1148,7 +1191,16 @@ func (c *CLI) APICreateMemoryCommand(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("no authorization") } - resp, err := httpClient.Request("POST", "/memories", "web", nil, nil) + memoryName, ok := cmd.Params["memory_name"].(string) + if !ok { + return nil, fmt.Errorf("memory_name parameter is required") + } + + payload := map[string]interface{}{ + "name": memoryName, + } + + resp, err := httpClient.Request("POST", "/memories", "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to create memory: %w", err) } @@ -1221,16 +1273,7 @@ func (c *CLI) APIDeleteAPIKeyCommand(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("failed to delete key: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) } - var result SimpleResponse - if err = json.Unmarshal(resp.Body, &result); err != nil { - return nil, fmt.Errorf("delete key failed: invalid JSON (%w)", err) - } - - if result.Code != 0 { - return nil, fmt.Errorf("%s", result.Message) - } - result.Duration = resp.Duration - return &result, nil + return HandleSimpleResponse(resp, "delete key") } // APISetAPIKeyCommand sets the API key after validating it @@ -1628,17 +1671,7 @@ func (c *CLI) AddProvider(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("failed to add provider: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) } - var result SimpleResponse - if err = json.Unmarshal(resp.Body, &result); err != nil { - return nil, fmt.Errorf("add provider failed: invalid JSON (%w)", err) - } - - if result.Code != 0 { - return nil, fmt.Errorf("%s", result.Message) - } - - result.Duration = resp.Duration - return &result, nil + return HandleSimpleResponse(resp, "add provider") } // APIListProviders lists added providers @@ -1702,17 +1735,196 @@ func (c *CLI) DeleteProvider(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("failed to delete provider: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) } - var result SimpleResponse - if err = json.Unmarshal(resp.Body, &result); err != nil { - return nil, fmt.Errorf("delete provider failed: invalid JSON (%w)", err) + return HandleSimpleResponse(resp, "delete provider") +} + +// APIDropDatasetCommand DROP DATASET 'dataset_name' +func (c *CLI) APIDropDatasetCommand(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { + return nil, fmt.Errorf("this command is only allowed in USER mode") } - if result.Code != 0 { - return nil, fmt.Errorf("%s", result.Message) + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + + if httpClient.LoginToken == nil && !c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].useAPIKey { + return nil, fmt.Errorf("no authorization") } - result.Duration = resp.Duration - return &result, nil + datasetName, ok := cmd.Params["dataset_name"].(string) + if !ok { + return nil, fmt.Errorf("dataset_name parameter is required") + } + + datasetID, err := c.getDatasetIDByName(datasetName) + if err != nil { + return nil, fmt.Errorf("failed to get dataset ID: %w by dataset name: %s", err, datasetName) + } + + payload := map[string]interface{}{ + "ids": []string{datasetID}, + "delete_all": true, + } + + resp, err := httpClient.Request("DELETE", "/datasets", "web", nil, payload) + if err != nil { + return nil, fmt.Errorf("failed to create dataset: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to create dataset: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + + return HandleSimpleResponse(resp, "create provider instance") +} + +// APIDropAgentCommand DROP AGENT 'agent_name' +func (c *CLI) APIDropAgentCommand(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + + if httpClient.LoginToken == nil && !c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].useAPIKey { + return nil, fmt.Errorf("no authorization") + } + + agentName, ok := cmd.Params["agent_name"].(string) + if !ok { + return nil, fmt.Errorf("agent_name parameter is required") + } + + agentID, err := c.getAgentIDByName(agentName) + if err != nil { + return nil, fmt.Errorf("failed to get agent ID: %w by agent name: %s", err, agentName) + } + + payload := map[string]interface{}{ + "ids": []string{agentID}, + "delete_all": true, + } + + resp, err := httpClient.Request("DELETE", "/agents", "web", nil, payload) + if err != nil { + return nil, fmt.Errorf("failed to create agent: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to create agent: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + + return HandleSimpleResponse(resp, "delete agent") +} + +// APIDropChatCommand DROP CHAT 'chat_name' +func (c *CLI) APIDropChatCommand(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + + if httpClient.LoginToken == nil && !c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].useAPIKey { + return nil, fmt.Errorf("no authorization") + } + + chatName, ok := cmd.Params["chat_name"].(string) + if !ok { + return nil, fmt.Errorf("chat_name parameter is required") + } + + chatID, err := c.getChatIDByName(chatName) + if err != nil { + return nil, fmt.Errorf("failed to get chat ID: %w by chat name: %s", err, chatName) + } + + payload := map[string]interface{}{ + "ids": []string{chatID}, + "delete_all": true, + } + + resp, err := httpClient.Request("DELETE", "/chats", "web", nil, payload) + if err != nil { + return nil, fmt.Errorf("failed to create chat: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to create chat: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + + return HandleSimpleResponse(resp, "delete chat") +} + +// APIDropSearchCommand DROP SEARCH 'search_name' +func (c *CLI) APIDropSearchCommand(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + + if httpClient.LoginToken == nil && !c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].useAPIKey { + return nil, fmt.Errorf("no authorization") + } + + searchName, ok := cmd.Params["search_name"].(string) + if !ok { + return nil, fmt.Errorf("search_name parameter is required") + } + + searchID, err := c.getSearchIDByName(searchName) + if err != nil { + return nil, fmt.Errorf("failed to get search ID: %w by search name: %s", err, searchName) + } + + endPoint := fmt.Sprintf("/searches/%s", searchID) + + resp, err := httpClient.Request("DELETE", endPoint, "web", nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to delete search: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to delete search: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + + return HandleSimpleResponse(resp, "delete search") +} + +// APIDropMemoryCommand DROP MEMORY 'memory_name' +func (c *CLI) APIDropMemoryCommand(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + + if httpClient.LoginToken == nil && !c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].useAPIKey { + return nil, fmt.Errorf("no authorization") + } + + memoryName, ok := cmd.Params["memory_name"].(string) + if !ok { + return nil, fmt.Errorf("memory_name parameter is required") + } + + memoryID, err := c.getMemoryIDByName(memoryName) + if err != nil { + return nil, fmt.Errorf("failed to get memory ID: %w by memory name: %s", err, memoryName) + } + + endPoint := fmt.Sprintf("/memories/%s", memoryID) + + resp, err := httpClient.Request("DELETE", endPoint, "web", nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to delete memory: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to delete memory: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + + return HandleSimpleResponse(resp, "delete memory") } // APICreateProviderInstanceCommand creates a new provider instance @@ -1770,17 +1982,7 @@ func (c *CLI) APICreateProviderInstanceCommand(cmd *Command) (ResponseIf, error) return nil, fmt.Errorf("failed to create provider instance: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) } - var result SimpleResponse - if err = json.Unmarshal(resp.Body, &result); err != nil { - return nil, fmt.Errorf("create provider instance failed: invalid JSON (%w)", err) - } - - if result.Code != 0 { - return nil, fmt.Errorf("%s", result.Message) - } - - result.Duration = resp.Duration - return &result, nil + return HandleSimpleResponse(resp, "create provider instance") } // ShowInstanceBalance shows balance of a specific instance @@ -1856,17 +2058,7 @@ func (c *CLI) DropProviderInstance(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("failed to drop instance: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) } - var result SimpleResponse - if err = json.Unmarshal(resp.Body, &result); err != nil { - return nil, fmt.Errorf("drop instance failed: invalid JSON (%w)", err) - } - - if result.Code != 0 { - return nil, fmt.Errorf("%s", result.Message) - } - - result.Duration = resp.Duration - return &result, nil + return HandleSimpleResponse(resp, "drop instance") } // DROP MODEL FROM @@ -1899,24 +2091,14 @@ func (c *CLI) DropInstanceModel(cmd *Command) (ResponseIf, error) { resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("DELETE", url, "web", nil, payload) if err != nil { - return nil, fmt.Errorf("failed to drop instance: %w", err) + return nil, fmt.Errorf("failed to drop model: %w", err) } if resp.StatusCode != 200 { - return nil, fmt.Errorf("failed to drop instance: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + return nil, fmt.Errorf("failed to drop model: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) } - var result SimpleResponse - if err = json.Unmarshal(resp.Body, &result); err != nil { - return nil, fmt.Errorf("drop instance failed: invalid JSON (%w)", err) - } - - if result.Code != 0 { - return nil, fmt.Errorf("%s", result.Message) - } - - result.Duration = resp.Duration - return &result, nil + return HandleSimpleResponse(resp, "drop model") } func isValidURL(str string) bool { @@ -3043,16 +3225,8 @@ func (c *CLI) AddCustomModel(cmd *Command) (ResponseIf, error) { if resp.StatusCode != 200 { return nil, fmt.Errorf("failed to add custom model: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) } - var result SimpleResponse - if err = json.Unmarshal(resp.Body, &result); err != nil { - return nil, fmt.Errorf("add custom model failed: invalid JSON (%w)", err) - } - if result.Code != 0 { - return nil, fmt.Errorf("%s", result.Message) - } - result.Duration = resp.Duration - return &result, nil + return HandleSimpleResponse(resp, "add custom model") } // InsertChunksFromFile inserts chunks from a JSON file @@ -3522,17 +3696,7 @@ func (c *CLI) ParseDocumentsUserCommand(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("failed to list documents: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) } - var result SimpleResponse - if err = json.Unmarshal(resp.Body, &result); err != nil { - return nil, fmt.Errorf("list documents failed: invalid JSON (%w)", err) - } - - if result.Code != 0 { - return nil, fmt.Errorf("%s", result.Message) - } - result.Duration = resp.Duration - - return &result, nil + return HandleSimpleResponse(resp, "list documents") } func (c *CLI) UserParseLocalFile(cmd *Command) (ResponseIf, error) { diff --git a/internal/cli/user_parser.go b/internal/cli/user_parser.go index 478895b65e..60f45d07ef 100644 --- a/internal/cli/user_parser.go +++ b/internal/cli/user_parser.go @@ -140,6 +140,8 @@ func (p *Parser) parseAPIListCommands() (*Command, error) { return p.parseAPIListChats() case TokenSearches: return p.parseAPIListSearches() + case TokenMemories: + return p.parseAPIListMemories() case TokenKeys: return p.parseAPIListAPIKeys() case TokenProviders: @@ -283,6 +285,17 @@ func (p *Parser) parseAPIListSearches() (*Command, error) { return NewCommand("api_list_searches"), nil } +func (p *Parser) parseAPIListMemories() (*Command, error) { + p.nextToken() // consume MEMORIES + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return NewCommand("api_list_memories"), nil +} + func (p *Parser) parseGetMetadata() (*Command, error) { p.nextToken() // consume METADATA @@ -1247,8 +1260,7 @@ func (p *Parser) parseAPISaveConfig() (*Command, error) { return cmd, nil } -// CREATE DATASET 'abc' WITH EMBEDDING 'modelName@instanceName@providerName' PARSER 'parserType' -// CREATE DATASET 'abc' WITH EMBEDDING 'modelName@instanceName@providerName' PIPELINE 'pipelineName' +// CREATE DATASET 'abc'; func (p *Parser) parseAPICreateDataset() (*Command, error) { p.nextToken() // consume DATASET datasetName, err := p.parseQuotedString() @@ -1256,52 +1268,13 @@ func (p *Parser) parseAPICreateDataset() (*Command, error) { return nil, err } - p.nextToken() - if p.curToken.Type != TokenWith { - return nil, fmt.Errorf("expected WITH") - } - p.nextToken() - if p.curToken.Type != TokenEmbedding { - return nil, fmt.Errorf("expected EMBEDDING") - } - p.nextToken() - - embedding, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - p.nextToken() - cmd := NewCommand("api_create_dataset") - cmd.Params["dataset_name"] = datasetName - cmd.Params["embedding"] = embedding - - if p.curToken.Type == TokenParser { - p.nextToken() - var parserType string - parserType, err = p.parseQuotedString() - if err != nil { - return nil, err - } - cmd.Params["parser_type"] = parserType - p.nextToken() - } else if p.curToken.Type == TokenPipeline { - p.nextToken() - var pipeline string - pipeline, err = p.parseQuotedString() - if err != nil { - return nil, err - } - cmd.Params["pipeline"] = pipeline - p.nextToken() - } else { - return nil, fmt.Errorf("expected PARSER or PIPELINE") - } - // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } + + cmd := NewCommand("api_create_dataset") + cmd.Params["dataset_name"] = datasetName return cmd, nil } @@ -1389,9 +1362,15 @@ func (p *Parser) parseAPIDropCommands() (*Command, error) { switch p.curToken.Type { case TokenDataset: - return p.parseDropDataset() + return p.parseAPIDropDataset() case TokenChat: - return p.parseDropChat() + return p.parseAPIDropChat() + case TokenSearch: + return p.parseAPIDropSearch() + case TokenMemory: + return p.parseAPIDropMemory() + case TokenAgent: + return p.parseAPIDropAgent() case TokenKey: return p.parseAPIDeleteAPIKey() case TokenChunkStore: @@ -1529,16 +1508,52 @@ func (p *Parser) parseDeleteProvider() (*Command, error) { return cmd, nil } -func (p *Parser) parseDropDataset() (*Command, error) { +func (p *Parser) parseAPIDropDataset() (*Command, error) { p.nextToken() // consume DATASET datasetName, err := p.parseQuotedString() if err != nil { return nil, err } + p.nextToken() - cmd := NewCommand("drop_user_dataset") + cmd := NewCommand("api_drop_dataset") cmd.Params["dataset_name"] = datasetName + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAPIDropSearch() (*Command, error) { + p.nextToken() // consume SEARCH + searchName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + p.nextToken() + + cmd := NewCommand("api_drop_search") + cmd.Params["search_name"] = searchName + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAPIDropChat() (*Command, error) { + p.nextToken() // consume CHAT + chatName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("api_drop_chat") + cmd.Params["chat_name"] = chatName + p.nextToken() // Semicolon is optional if p.curToken.Type == TokenSemicolon { @@ -1547,15 +1562,33 @@ func (p *Parser) parseDropDataset() (*Command, error) { return cmd, nil } -func (p *Parser) parseDropChat() (*Command, error) { - p.nextToken() // consume CHAT - chatName, err := p.parseQuotedString() +func (p *Parser) parseAPIDropMemory() (*Command, error) { + p.nextToken() // consume MEMORY + memoryName, err := p.parseQuotedString() if err != nil { return nil, err } - cmd := NewCommand("drop_user_chat") - cmd.Params["chat_name"] = chatName + cmd := NewCommand("api_drop_memory") + cmd.Params["memory_name"] = memoryName + + p.nextToken() + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAPIDropAgent() (*Command, error) { + p.nextToken() // consume AGENT + agentName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("api_drop_agent") + cmd.Params["agent_name"] = agentName p.nextToken() // Semicolon is optional diff --git a/internal/development.md b/internal/development.md index e4fc25e376..85c50eb190 100644 --- a/internal/development.md +++ b/internal/development.md @@ -19,6 +19,14 @@ docker compose -f docker/docker-compose-base.yml up -d ./build.sh --go ``` +- Production builds (strip debug symbols for smaller binaries): + +```bash +./build.sh --strip --all +# or +./build.sh -s --go +``` + > **Note**: If you use IDEs like GoLand to run/debug directly (via Run/Debug buttons), or run `go build` / `go run` from command line, you must set the following two CGO environment variables in your run configuration or shell: > > ```bash diff --git a/internal/service/dataset.go b/internal/service/dataset.go index a83f56bad6..c680c5fd07 100644 --- a/internal/service/dataset.go +++ b/internal/service/dataset.go @@ -2488,7 +2488,7 @@ func (s *DatasetService) CreateDataset(req *CreateDatasetRequest, tenantID strin kb.Language = language } - if err := s.kbDAO.Create(kb); err != nil { + if err = s.kbDAO.Create(kb); err != nil { return nil, common.CodeServerError, errors.New("Failed to save dataset") }