From efd6ecc3e554851619c866cdbf82a32b270235ca Mon Sep 17 00:00:00 2001 From: Jin Hai Date: Tue, 31 Mar 2026 18:42:12 +0800 Subject: [PATCH] New provider and models API and CLI (#13865) ### What problem does this PR solve? As title. ### Type of change - [x] New Feature (non-breaking change which adds functionality) - [x] Refactoring --------- Signed-off-by: Jin Hai --- cmd/server_main.go | 3 +- internal/admin/handler.go | 111 ++++++++++++++++++++++ internal/admin/router.go | 19 ++-- internal/cli/admin_parser.go | 67 +++++++------ internal/cli/cli.go | 10 +- internal/cli/client.go | 37 +++++--- internal/cli/common_command.go | 12 +-- internal/cli/lexer.go | 6 +- internal/cli/types.go | 3 +- internal/cli/user_command.go | 113 ++++++++++++++++++++++ internal/cli/user_parser.go | 165 ++++++++++++++++++++++++++++++++- internal/handler/providers.go | 61 ++++++++---- internal/router/router.go | 20 ++-- 13 files changed, 528 insertions(+), 99 deletions(-) diff --git a/cmd/server_main.go b/cmd/server_main.go index fe69e50c7e..8b90664bb7 100644 --- a/cmd/server_main.go +++ b/cmd/server_main.go @@ -193,9 +193,10 @@ func startServer(config *server.Config) { searchHandler := handler.NewSearchHandler(searchService, userService) fileHandler := handler.NewFileHandler(fileService, userService) memoryHandler := handler.NewMemoryHandler(memoryService) + providerHandler := handler.NewProviderHandler(userService) // Initialize router - r := router.NewRouter(authHandler, userHandler, tenantHandler, documentHandler, datasetsHandler, systemHandler, kbHandler, chunkHandler, llmHandler, chatHandler, chatSessionHandler, connectorHandler, searchHandler, fileHandler, memoryHandler) + r := router.NewRouter(authHandler, userHandler, tenantHandler, documentHandler, datasetsHandler, systemHandler, kbHandler, chunkHandler, llmHandler, chatHandler, chatSessionHandler, connectorHandler, searchHandler, fileHandler, memoryHandler, providerHandler) // Create Gin engine ginEngine := gin.New() diff --git a/internal/admin/handler.go b/internal/admin/handler.go index 602092c5ce..f02bd02e53 100644 --- a/internal/admin/handler.go +++ b/internal/admin/handler.go @@ -21,11 +21,13 @@ import ( "fmt" "net/http" "ragflow/internal/common" + "ragflow/internal/dao" "ragflow/internal/logger" "ragflow/internal/server" "ragflow/internal/service" "ragflow/internal/utility" "strconv" + "strings" "time" "github.com/gin-gonic/gin" @@ -794,6 +796,115 @@ func (h *Handler) RestartService(c *gin.Context) { success(c, result, "") } +func (h *Handler) ListProviders(c *gin.Context) { + + keywords := "" + if queryKeywords := c.Query("available"); queryKeywords != "" { + keywords = queryKeywords + } + + // convert keywords to small case + keywords = strings.ToLower(keywords) + if keywords == "true" { + // list pool providers + providers, err := dao.GetModelProviderManager().ListProviders() + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "code": common.CodeNotFound, + "message": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "message": "success", + "data": providers, + }) + } +} + +func (h *Handler) ShowProvider(c *gin.Context) { + providerName := c.Param("provider_name") + if providerName == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 400, + "message": "Provider name is required", + }) + return + } + + provider, err := dao.GetModelProviderManager().GetProviderByName(providerName) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "code": common.CodeNotFound, + "message": err.Error(), + }) + return + } + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "message": "success", + "data": provider, + }) +} + +func (h *Handler) ListModels(c *gin.Context) { + providerName := c.Param("provider_name") + if providerName == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 400, + "message": "Provider name is required", + }) + return + } + models, err := dao.GetModelProviderManager().ListModels(providerName) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "code": common.CodeNotFound, + "message": err.Error(), + }) + return + } + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "message": "success", + "data": models, + }) +} + +func (h *Handler) ShowModel(c *gin.Context) { + providerName := c.Param("provider_name") + if providerName == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 400, + "message": "Provider name is required", + }) + return + } + modelName := c.Param("model_name") + if modelName == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 400, + "message": "Model name is required", + }) + return + } + model, err := dao.GetModelProviderManager().GetModelByName(providerName, modelName) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "code": common.CodeNotFound, + "message": err.Error(), + }) + return + } + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "message": "success", + "data": model, + }) +} + // GetVariables handle get variables // Python logic: if request body is empty, list all variables; otherwise get single variable by var_name from body func (h *Handler) GetVariables(c *gin.Context) { diff --git a/internal/admin/router.go b/internal/admin/router.go index 6895b07fbc..fe3e54d22a 100644 --- a/internal/admin/router.go +++ b/internal/admin/router.go @@ -17,8 +17,6 @@ package admin import ( - "ragflow/internal/handler" - "github.com/gin-gonic/gin" ) @@ -48,15 +46,6 @@ func (r *Router) Setup(engine *gin.Engine) { admin.POST("/reports", r.handler.Reports) - // provider pool route group - provider := admin.Group("providers") - { - provider.GET("/", handler.ListPoolProviders) - provider.GET("/:provider_name", handler.ShowPoolProvider) - provider.GET("/:provider_name/models", handler.ListPoolModels) - provider.GET("/:provider_name/models/:model_name", handler.ShowPoolModel) - } - // Protected routes protected := admin.Group("") protected.Use(r.handler.AuthMiddleware()) @@ -136,6 +125,14 @@ func (r *Router) Setup(engine *gin.Engine) { // Log level protected.GET("/log_level", r.handler.GetLogLevel) protected.PUT("/log_level", r.handler.SetLogLevel) + + provider := protected.Group("/providers") + { + provider.GET("/", r.handler.ListProviders) + provider.GET("/:provider_name", r.handler.ShowProvider) + provider.GET("/:provider_name/models", r.handler.ListModels) + provider.GET("/:provider_name/models/:model_name", r.handler.ShowModel) + } } } diff --git a/internal/cli/admin_parser.go b/internal/cli/admin_parser.go index 05b8569d83..17ecd68cc5 100644 --- a/internal/cli/admin_parser.go +++ b/internal/cli/admin_parser.go @@ -166,8 +166,10 @@ func (p *Parser) parseAdminListCommand() (*Command, error) { return p.parseAdminListModelProviders() case TokenDefault: return p.parseAdminListDefaultModels() - case TokenPool: - return p.parseCommonListPoolModels() + case TokenAvailable: + return p.parseCommonListProviders() + case TokenModels: + return p.parseListModelsOfProvider() case TokenChats: p.nextToken() // Semicolon is optional for SHOW TOKEN @@ -273,31 +275,38 @@ func (p *Parser) parseAdminListDefaultModels() (*Command, error) { return NewCommand("list_user_default_models"), nil } -func (p *Parser) parseCommonListPoolModels() (*Command, error) { - p.nextToken() // consume POOL - if p.curToken.Type == TokenProviders { - return NewCommand("list_pool_providers"), nil - } else if p.curToken.Type == TokenModels { - p.nextToken() - if p.curToken.Type != TokenFrom { - return nil, fmt.Errorf("expected FROM") - } - p.nextToken() - providerName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - cmd := NewCommand("list_pool_models") - cmd.Params["provider_name"] = providerName - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil - } else { - return nil, fmt.Errorf("expected PROVIDERS or MODELS") +func (p *Parser) parseListModelsOfProvider() (*Command, error) { + if p.curToken.Type != TokenModels { + return nil, fmt.Errorf("expected MODELS") } + + p.nextToken() + if p.curToken.Type != TokenFrom { + return nil, fmt.Errorf("expected FROM") + } + p.nextToken() + providerName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + cmd := NewCommand("list_provider_models") + cmd.Params["provider_name"] = providerName + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseCommonListProviders() (*Command, error) { + p.nextToken() // consume AVAILABLE + + if p.curToken.Type != TokenProviders { + return nil, fmt.Errorf("expected PROVIDERS") + } + + return NewCommand("list_available_providers"), nil } func (p *Parser) parseCommonShowPoolModel() (*Command, error) { @@ -409,8 +418,10 @@ func (p *Parser) parseAdminShowCommand() (*Command, error) { return p.parseShowVariable() case TokenService: return p.parseShowService() - case TokenPool: - return p.parseCommonShowPoolModel() + case TokenProvider: + return p.parseShowProvider() + case TokenModel: + return p.parseShowModel() default: return nil, fmt.Errorf("unknown SHOW target: %s", p.curToken.Value) } diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 40765fd2b4..fb48bb3e59 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -1006,15 +1006,17 @@ Commands (User Mode): LIST MODEL PROVIDERS; - List model providers LIST DEFAULT MODELS; - List default models LIST TOKENS; - List API tokens + LIST PROVIDERS; - List available LLM providers CREATE TOKEN; - Create new API token + CREATE PROVIDER 'name'; - Create a provider without API key + CREATE PROVIDER 'name' 'api_key'; - Create a provider with API key DROP TOKEN 'token_value'; - Delete an API token + DROP PROVIDER 'name'; - Delete a provider SET TOKEN 'token_value'; - Set and validate API token SHOW TOKEN; - Show current API token + SHOW PROVIDER 'name'; - Show provider details UNSET TOKEN; - Remove current API token - CREATE INDEX FOR DATASET 'name' VECTOR_SIZE N; - Create index for dataset - DROP INDEX FOR DATASET 'name'; - Drop index for dataset - CREATE INDEX DOC_META; - Create doc meta index - DROP INDEX DOC_META; - Drop doc meta index + ALTER PROVIDER 'name' NAME 'new_name'; - Rename a provider Context Engine Commands (no quotes): ls [path] - List resources diff --git a/internal/cli/client.go b/internal/cli/client.go index 18d7175603..28c0dc324f 100644 --- a/internal/cli/client.go +++ b/internal/cli/client.go @@ -152,14 +152,14 @@ func (c *RAGFlowClient) ExecuteAdminCommand(cmd *Command) (ResponseIf, error) { return c.ListAdminTokens(cmd) case "drop_token": return c.DropAdminToken(cmd) - case "list_pool_providers": - return c.ListPoolProviders(cmd) - case "show_pool_provider": - return c.ShowPoolProvider(cmd) - case "list_pool_models": - return c.ListPoolModels(cmd) - case "show_pool_model": - return c.ShowPoolModel(cmd) + case "list_available_providers": + return c.ListAvailableProviders(cmd) + case "show_provider": + return c.ShowProvider(cmd) + case "list_provider_models": + return c.ListModels(cmd) + case "show_model": + return c.ShowModel(cmd) // TODO: Implement other commands default: return nil, fmt.Errorf("command '%s' would be executed with API", cmd.Type) @@ -204,13 +204,20 @@ func (c *RAGFlowClient) ExecuteUserCommand(cmd *Command) (ResponseIf, error) { case "drop_doc_meta_index": return c.DropDocMetaIndex(cmd) case "list_pool_providers": - return c.ListPoolProviders(cmd) - case "show_pool_provider": - return c.ShowPoolProvider(cmd) - case "list_pool_models": - return c.ListPoolModels(cmd) - case "show_pool_model": - return c.ShowPoolModel(cmd) + return c.ListAvailableProviders(cmd) + case "show_provider": + return c.ShowProvider(cmd) + case "list_provider_models": + return c.ListModels(cmd) + case "show_model": + return c.ShowModel(cmd) + // Provider commands + case "create_provider": + return c.CreateProvider(cmd) + case "list_providers": + return c.ListProviders(cmd) + case "drop_provider": + return c.DropProvider(cmd) // ContextEngine commands case "ce_ls": return c.CEList(cmd) diff --git a/internal/cli/common_command.go b/internal/cli/common_command.go index 6bb4eb304f..2940b131c1 100644 --- a/internal/cli/common_command.go +++ b/internal/cli/common_command.go @@ -237,13 +237,13 @@ func (c *RAGFlowClient) Logout() (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) ListPoolProviders(cmd *Command) (ResponseIf, error) { +func (c *RAGFlowClient) ListAvailableProviders(cmd *Command) (ResponseIf, error) { var endPoint string if c.ServerType == "admin" { - endPoint = fmt.Sprintf("/admin/providers") + endPoint = fmt.Sprintf("/admin/providers?available=true") } else { - endPoint = fmt.Sprintf("/providers") + endPoint = fmt.Sprintf("/providers?available=true") } resp, err := c.HTTPClient.Request("GET", endPoint, true, "web", nil, nil) @@ -267,7 +267,7 @@ func (c *RAGFlowClient) ListPoolProviders(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) ShowPoolProvider(cmd *Command) (ResponseIf, error) { +func (c *RAGFlowClient) ShowProvider(cmd *Command) (ResponseIf, error) { providerName, ok := cmd.Params["provider_name"].(string) if !ok { return nil, fmt.Errorf("provider_name not provided") @@ -301,7 +301,7 @@ func (c *RAGFlowClient) ShowPoolProvider(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) ListPoolModels(cmd *Command) (ResponseIf, error) { +func (c *RAGFlowClient) ListModels(cmd *Command) (ResponseIf, error) { providerName, ok := cmd.Params["provider_name"].(string) if !ok { @@ -336,7 +336,7 @@ func (c *RAGFlowClient) ListPoolModels(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) ShowPoolModel(cmd *Command) (ResponseIf, error) { +func (c *RAGFlowClient) ShowModel(cmd *Command) (ResponseIf, error) { providerName, ok := cmd.Params["provider_name"].(string) if !ok { return nil, fmt.Errorf("provider_name not provided") diff --git a/internal/cli/lexer.go b/internal/cli/lexer.go index a6ef735362..8add64d6fd 100644 --- a/internal/cli/lexer.go +++ b/internal/cli/lexer.go @@ -301,8 +301,10 @@ func (l *Lexer) lookupIdent(ident string) Token { return Token{Type: TokenVectorSize, Value: ident} case "DOC_META": return Token{Type: TokenDocMeta, Value: ident} - case "POOL": - return Token{Type: TokenPool, Value: ident} + case "AVAILABLE": + return Token{Type: TokenAvailable, Value: ident} + case "NAME": + return Token{Type: TokenName, Value: ident} default: return Token{Type: TokenIdentifier, Value: ident} } diff --git a/internal/cli/types.go b/internal/cli/types.go index 4b2844a3ca..9726f5e8b3 100644 --- a/internal/cli/types.go +++ b/internal/cli/types.go @@ -69,7 +69,7 @@ const ( TokenKey TokenKeys TokenGenerate - TokenPool + TokenAvailable TokenModel TokenModels TokenProvider @@ -103,6 +103,7 @@ const ( TokenIndex TokenVectorSize TokenDocMeta + TokenName // For ALTER PROVIDER NAME // Literals TokenIdentifier diff --git a/internal/cli/user_command.go b/internal/cli/user_command.go index b53b7bfcd4..4614067960 100644 --- a/internal/cli/user_command.go +++ b/internal/cli/user_command.go @@ -751,6 +751,119 @@ func (c *RAGFlowClient) DropDocMetaIndex(cmd *Command) (ResponseIf, error) { return &result, nil } +// CreateProvider creates a new model provider +// CREATE PROVIDER +// CREATE PROVIDER +func (c *RAGFlowClient) CreateProvider(cmd *Command) (ResponseIf, error) { + if c.ServerType != "user" { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + providerName, ok := cmd.Params["provider_name"].(string) + if !ok { + return nil, fmt.Errorf("provider name not provided") + } + + // Get optional api_key + apiKey, _ := cmd.Params["api_key"].(string) + + // Build payload + payload := map[string]interface{}{ + "llm_factory": providerName, + "api_key": apiKey, + "verify": apiKey != "", // Only verify if api_key is provided + } + + resp, err := c.HTTPClient.Request("POST", "/llm/set_api_key", true, "web", nil, payload) + if err != nil { + return nil, fmt.Errorf("failed to create provider: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to create provider: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + + var result CommonDataResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("create provider failed: invalid JSON (%w)", err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + + result.Duration = resp.Duration + return &result, nil +} + +// ListProviders lists all providers +// LIST PROVIDERS +func (c *RAGFlowClient) ListProviders(cmd *Command) (ResponseIf, error) { + if c.ServerType != "user" { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + resp, err := c.HTTPClient.Request("GET", "/llm/factories", true, "web", nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to list providers: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to list providers: 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 providers failed: invalid JSON (%w)", err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + + result.Duration = resp.Duration + return &result, nil +} + +// DropProvider deletes a provider +// DROP PROVIDER +func (c *RAGFlowClient) DropProvider(cmd *Command) (ResponseIf, error) { + if c.ServerType != "user" { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + providerName, ok := cmd.Params["provider_name"].(string) + if !ok { + return nil, fmt.Errorf("provider name not provided") + } + + // Build payload + payload := map[string]interface{}{ + "llm_factory": providerName, + } + + resp, err := c.HTTPClient.Request("DELETE", "/llm/factory", true, "web", nil, payload) + if err != nil { + return nil, fmt.Errorf("failed to drop provider: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to drop 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("drop provider failed: invalid JSON (%w)", err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + + result.Duration = resp.Duration + return &result, nil +} + // Context related commands // CEList handles the ls command - lists nodes using Context Engine diff --git a/internal/cli/user_parser.go b/internal/cli/user_parser.go index 2b939d23ba..05456818bc 100644 --- a/internal/cli/user_parser.go +++ b/internal/cli/user_parser.go @@ -151,10 +151,14 @@ func (p *Parser) parseListCommand() (*Command, error) { return p.parseListTokens() case TokenModel: return p.parseListModelProviders() + case TokenModels: + return p.parseListModelsOfProvider() + case TokenProviders: + return p.parseListProviders() case TokenDefault: return p.parseListDefaultModels() - case TokenPool: - return p.parseCommonListPoolModels() + case TokenAvailable: + return p.parseCommonListProviders() case TokenChats: p.nextToken() // Semicolon is optional for SHOW TOKEN @@ -259,6 +263,16 @@ func (p *Parser) parseListModelProviders() (*Command, error) { return NewCommand("list_user_model_providers"), nil } +// parseListProviders parses LIST PROVIDERS command +func (p *Parser) parseListProviders() (*Command, error) { + p.nextToken() // consume PROVIDERS + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("list_providers"), nil +} + func (p *Parser) parseListDefaultModels() (*Command, error) { p.nextToken() // consume DEFAULT if p.curToken.Type != TokenModels { @@ -336,8 +350,10 @@ func (p *Parser) parseShowCommand() (*Command, error) { return p.parseShowVariable() case TokenService: return p.parseShowService() - case TokenPool: - return p.parseCommonShowPoolModel() + case TokenProvider: + return p.parseShowProvider() + case TokenModel: + return p.parseShowModel() default: return nil, fmt.Errorf("unknown SHOW target: %s", p.curToken.Value) } @@ -433,6 +449,56 @@ func (p *Parser) parseShowService() (*Command, error) { return cmd, nil } +func (p *Parser) parseShowModel() (*Command, error) { + p.nextToken() // consume model + + modelName, err := p.parseQuotedString() + if err != nil { + return nil, fmt.Errorf("expected model name: %w", err) + } + + cmd := NewCommand("show_model") + cmd.Params["model_name"] = modelName + + p.nextToken() // consume model_name + + if p.curToken.Type != TokenFrom { + return nil, fmt.Errorf("expected FROM") + } + p.nextToken() // consume from + providerName, err := p.parseQuotedString() + if err != nil { + return nil, fmt.Errorf("expected provider name: %w", err) + } + cmd.Params["provider_name"] = providerName + p.nextToken() // consume provider name + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +// parseShowProvider parses SHOW PROVIDER command +func (p *Parser) parseShowProvider() (*Command, error) { + p.nextToken() // consume PROVIDER + + providerName, err := p.parseQuotedString() + if err != nil { + return nil, fmt.Errorf("expected provider name: %w", err) + } + + cmd := NewCommand("show_provider") + cmd.Params["provider_name"] = providerName + + p.nextToken() + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + func (p *Parser) parseCreateCommand() (*Command, error) { p.nextToken() // consume CREATE @@ -443,6 +509,8 @@ func (p *Parser) parseCreateCommand() (*Command, error) { return p.parseCreateRole() case TokenModel: return p.parseCreateModelProvider() + case TokenProvider: + return p.parseCreateProvider() case TokenDataset: return p.parseCreateDataset() case TokenChat: @@ -606,6 +674,39 @@ func (p *Parser) parseCreateModelProvider() (*Command, error) { return cmd, nil } +// parseCreateProvider parses CREATE PROVIDER commands +// CREATE PROVIDER +// CREATE PROVIDER +func (p *Parser) parseCreateProvider() (*Command, error) { + p.nextToken() // consume PROVIDER + + providerName, err := p.parseQuotedString() + if err != nil { + return nil, fmt.Errorf("expected provider name: %w", err) + } + + cmd := NewCommand("create_provider") + cmd.Params["provider_name"] = providerName + + p.nextToken() + + // Check if api_key is provided (optional) + if p.curToken.Type == TokenQuotedString { + apiKey, err := p.parseQuotedString() + if err != nil { + return nil, fmt.Errorf("expected api key: %w", err) + } + cmd.Params["api_key"] = apiKey + p.nextToken() + } + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + func (p *Parser) parseCreateDataset() (*Command, error) { p.nextToken() // consume DATASET datasetName, err := p.parseQuotedString() @@ -688,6 +789,8 @@ func (p *Parser) parseDropCommand() (*Command, error) { return p.parseDropRole() case TokenModel: return p.parseDropModelProvider() + case TokenProvider: + return p.parseDropProvider() case TokenDataset: return p.parseDropDataset() case TokenChat: @@ -831,6 +934,26 @@ func (p *Parser) parseDropModelProvider() (*Command, error) { return cmd, nil } +// parseDropProvider parses DROP PROVIDER command +func (p *Parser) parseDropProvider() (*Command, error) { + p.nextToken() // consume PROVIDER + + providerName, err := p.parseQuotedString() + if err != nil { + return nil, fmt.Errorf("expected provider name: %w", err) + } + + cmd := NewCommand("drop_provider") + cmd.Params["provider_name"] = providerName + + p.nextToken() + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + func (p *Parser) parseDropDataset() (*Command, error) { p.nextToken() // consume DATASET datasetName, err := p.parseQuotedString() @@ -875,6 +998,8 @@ func (p *Parser) parseAlterCommand() (*Command, error) { return p.parseAlterUser() case TokenRole: return p.parseAlterRole() + case TokenProvider: + return p.parseAlterProvider() default: return nil, fmt.Errorf("unknown ALTER target: %s", p.curToken.Value) } @@ -1004,6 +1129,38 @@ func (p *Parser) parseAlterRole() (*Command, error) { return cmd, nil } +// parseAlterProvider parses ALTER PROVIDER NAME command +func (p *Parser) parseAlterProvider() (*Command, error) { + p.nextToken() // consume PROVIDER + + providerName, err := p.parseQuotedString() + if err != nil { + return nil, fmt.Errorf("expected provider name: %w", err) + } + + p.nextToken() + if p.curToken.Type != TokenName { + return nil, fmt.Errorf("expected NAME") + } + p.nextToken() + + newName, err := p.parseQuotedString() + if err != nil { + return nil, fmt.Errorf("expected new provider name: %w", err) + } + + cmd := NewCommand("alter_provider") + cmd.Params["provider_name"] = providerName + cmd.Params["new_name"] = newName + + p.nextToken() + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + func (p *Parser) parseGrantCommand() (*Command, error) { p.nextToken() // consume GRANT diff --git a/internal/handler/providers.go b/internal/handler/providers.go index 267aa70dd6..a936bcd450 100644 --- a/internal/handler/providers.go +++ b/internal/handler/providers.go @@ -20,28 +20,53 @@ import ( "net/http" "ragflow/internal/common" "ragflow/internal/dao" + "ragflow/internal/service" + "strings" "github.com/gin-gonic/gin" ) -func ListPoolProviders(c *gin.Context) { - providers, err := dao.GetModelProviderManager().ListProviders() - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "code": common.CodeNotFound, - "message": err.Error(), - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "code": 0, - "message": "success", - "data": providers, - }) +// ProviderHandler provider handler +type ProviderHandler struct { + userService *service.UserService } -func ShowPoolProvider(c *gin.Context) { +// NewProviderHandler create provider handler +func NewProviderHandler(userService *service.UserService) *ProviderHandler { + return &ProviderHandler{ + userService: userService, + } +} + +func (h *ProviderHandler) ListProviders(c *gin.Context) { + + keywords := "" + if queryKeywords := c.Query("available"); queryKeywords != "" { + keywords = queryKeywords + } + + // convert keywords to small case + keywords = strings.ToLower(keywords) + if keywords == "true" { + // list pool providers + providers, err := dao.GetModelProviderManager().ListProviders() + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "code": common.CodeNotFound, + "message": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "message": "success", + "data": providers, + }) + } +} + +func (h *ProviderHandler) ShowProvider(c *gin.Context) { providerName := c.Param("provider_name") if providerName == "" { c.JSON(http.StatusBadRequest, gin.H{ @@ -66,7 +91,7 @@ func ShowPoolProvider(c *gin.Context) { }) } -func ListPoolModels(c *gin.Context) { +func (h *ProviderHandler) ListModels(c *gin.Context) { providerName := c.Param("provider_name") if providerName == "" { c.JSON(http.StatusBadRequest, gin.H{ @@ -90,7 +115,7 @@ func ListPoolModels(c *gin.Context) { }) } -func ShowPoolModel(c *gin.Context) { +func (h *ProviderHandler) ShowModel(c *gin.Context) { providerName := c.Param("provider_name") if providerName == "" { c.JSON(http.StatusBadRequest, gin.H{ diff --git a/internal/router/router.go b/internal/router/router.go index 18767ad7ba..ea9305b261 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -39,6 +39,7 @@ type Router struct { searchHandler *handler.SearchHandler fileHandler *handler.FileHandler memoryHandler *handler.MemoryHandler + providerHandler *handler.ProviderHandler } // NewRouter create router @@ -58,6 +59,7 @@ func NewRouter( searchHandler *handler.SearchHandler, fileHandler *handler.FileHandler, memoryHandler *handler.MemoryHandler, + providerHandler *handler.ProviderHandler, ) *Router { return &Router{ authHandler: authHandler, @@ -75,6 +77,7 @@ func NewRouter( searchHandler: searchHandler, fileHandler: fileHandler, memoryHandler: memoryHandler, + providerHandler: providerHandler, } } @@ -100,15 +103,6 @@ func (r *Router) Setup(engine *gin.Engine) { // User logout endpoint engine.GET("/v1/user/logout", r.userHandler.Logout) - // provider pool route group - provider := engine.Group("/api/v1/providers") - { - provider.GET("/", handler.ListPoolProviders) - provider.GET("/:provider_name", handler.ShowPoolProvider) - provider.GET("/:provider_name/models", handler.ListPoolModels) - provider.GET("/:provider_name/models/:model_name", handler.ShowPoolModel) - } - // Protected routes authorized := engine.Group("") authorized.Use(r.authHandler.AuthMiddleware()) @@ -195,6 +189,14 @@ func (r *Router) Setup(engine *gin.Engine) { // message.GET("", r.memoryHandler.GetMessages) // message.GET("/:memory_id/:message_id/content", r.memoryHandler.GetMessageContent) // } + // provider pool route group + provider := v1.Group("/providers") + { + provider.GET("/", r.providerHandler.ListProviders) + provider.GET("/:provider_name", r.providerHandler.ShowProvider) + provider.GET("/:provider_name/models", r.providerHandler.ListModels) + provider.GET("/:provider_name/models/:model_name", r.providerHandler.ShowModel) + } } // Knowledge base routes