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 <haijin.chn@gmail.com>
This commit is contained in:
Jin Hai
2026-03-31 18:42:12 +08:00
committed by GitHub
parent 68b4287892
commit efd6ecc3e5
13 changed files with 528 additions and 99 deletions

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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")

View File

@@ -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}
}

View File

@@ -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> NAME <new_name>
// Literals
TokenIdentifier

View File

@@ -751,6 +751,119 @@ func (c *RAGFlowClient) DropDocMetaIndex(cmd *Command) (ResponseIf, error) {
return &result, nil
}
// CreateProvider creates a new model provider
// CREATE PROVIDER <name>
// CREATE PROVIDER <name> <api_key>
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 <name>
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

View File

@@ -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 <name> 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 <name>
// CREATE PROVIDER <name> <api_key>
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 <name> 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> NAME <new_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