Files
Harry 00d76a24c9 feat: add Multiple field to ProviderConfig for enhanced configuration
- Introduced a new Multiple field in the ProviderConfig struct to allow for multiple values in provider configurations.
- This addition improves the flexibility of provider options within the plugin system.

These changes enhance the capability of provider configurations, enabling more complex setups.
2025-09-17 22:08:13 +08:00

445 lines
14 KiB
Go

package plugin_entities
import (
"errors"
"strings"
"github.com/go-playground/locales/en"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
"github.com/langgenius/dify-plugin-daemon/pkg/validators"
)
type ConfigType string
const (
CONFIG_TYPE_SECRET_INPUT ConfigType = SECRET_INPUT
CONFIG_TYPE_TEXT_INPUT ConfigType = TEXT_INPUT
CONFIG_TYPE_SELECT ConfigType = SELECT
CONFIG_TYPE_BOOLEAN ConfigType = BOOLEAN
CONFIG_TYPE_MODEL_SELECTOR ConfigType = MODEL_SELECTOR
CONFIG_TYPE_APP_SELECTOR ConfigType = APP_SELECTOR
// CONFIG_TYPE_TOOL_SELECTOR ConfigType = TOOL_SELECTOR
CONFIG_TYPE_TOOLS_SELECTOR ConfigType = TOOLS_SELECTOR
CONFIG_TYPE_ANY ConfigType = ANY
)
type ModelConfigScope string
const (
MODEL_CONFIG_SCOPE_ALL ModelConfigScope = "all"
MODEL_CONFIG_SCOPE_LLM ModelConfigScope = "llm"
MODEL_CONFIG_SCOPE_TEXT_EMBEDDING ModelConfigScope = "text-embedding"
MODEL_CONFIG_SCOPE_RERANK ModelConfigScope = "rerank"
MODEL_CONFIG_SCOPE_TTS ModelConfigScope = "tts"
MODEL_CONFIG_SCOPE_SPEECH2TEXT ModelConfigScope = "speech2text"
MODEL_CONFIG_SCOPE_MODERATION ModelConfigScope = "moderation"
MODEL_CONFIG_SCOPE_VISION ModelConfigScope = "vision"
MODEL_CONFIG_SCOPE_DOCUMENT ModelConfigScope = "document"
MODEL_CONFIG_SCOPE_TOOL_CALL ModelConfigScope = "tool-call"
)
type AppSelectorScope string
const (
APP_SELECTOR_SCOPE_ALL AppSelectorScope = "all"
APP_SELECTOR_SCOPE_CHAT AppSelectorScope = "chat"
APP_SELECTOR_SCOPE_WORKFLOW AppSelectorScope = "workflow"
APP_SELECTOR_SCOPE_COMPLETION AppSelectorScope = "completion"
)
type ToolSelectorScope string
const (
TOOL_SELECTOR_SCOPE_ALL ToolSelectorScope = "all"
TOOL_SELECTOR_SCOPE_PLUGIN ToolSelectorScope = "plugin"
TOOL_SELECTOR_SCOPE_API ToolSelectorScope = "api"
TOOL_SELECTOR_SCOPE_WORKFLOW ToolSelectorScope = "workflow"
)
type AnyScope string
const (
ANY_SCOPE_STRING AnyScope = "string"
ANY_SCOPE_NUMBER AnyScope = "number"
ANY_SCOPE_OBJECT AnyScope = "object"
ANY_SCOPE_ARRAY_NUMBER AnyScope = "array[number]"
ANY_SCOPE_ARRAY_STRING AnyScope = "array[string]"
ANY_SCOPE_ARRAY_OBJECT AnyScope = "array[object]"
ANY_SCOPE_ARRAY_FILES AnyScope = "array[file]"
)
func isCredentialType(fl validator.FieldLevel) bool {
value := fl.Field().String()
switch value {
case string(CONFIG_TYPE_SECRET_INPUT),
string(CONFIG_TYPE_TEXT_INPUT),
string(CONFIG_TYPE_SELECT),
string(CONFIG_TYPE_BOOLEAN),
string(CONFIG_TYPE_APP_SELECTOR),
string(CONFIG_TYPE_MODEL_SELECTOR),
string(CONFIG_TYPE_TOOLS_SELECTOR):
return true
}
return false
}
type ConfigOption struct {
Value string `json:"value" validate:"required,lt=128"`
Label I18nObject `json:"label" validate:"required"`
}
func isModelConfigScope(fl validator.FieldLevel) bool {
value := fl.Field().String()
// split by and symbol
scopes := strings.Split(value, "&")
for _, scope := range scopes {
// trim space
scope = strings.TrimSpace(scope)
switch scope {
case string(MODEL_CONFIG_SCOPE_LLM),
string(MODEL_CONFIG_SCOPE_TEXT_EMBEDDING),
string(MODEL_CONFIG_SCOPE_RERANK),
string(MODEL_CONFIG_SCOPE_TTS),
string(MODEL_CONFIG_SCOPE_SPEECH2TEXT),
string(MODEL_CONFIG_SCOPE_MODERATION),
string(MODEL_CONFIG_SCOPE_VISION),
string(MODEL_CONFIG_SCOPE_DOCUMENT),
string(MODEL_CONFIG_SCOPE_TOOL_CALL):
return true
}
}
return false
}
func isAppSelectorScope(fl validator.FieldLevel) bool {
value := fl.Field().String()
// split by and symbol
scopes := strings.Split(value, "&")
for _, scope := range scopes {
// trim space
scope = strings.TrimSpace(scope)
switch scope {
case string(APP_SELECTOR_SCOPE_ALL),
string(APP_SELECTOR_SCOPE_CHAT),
string(APP_SELECTOR_SCOPE_WORKFLOW),
string(APP_SELECTOR_SCOPE_COMPLETION):
return true
}
}
return false
}
func isToolSelectorScope(fl validator.FieldLevel) bool {
value := fl.Field().String()
// split by and symbol
scopes := strings.Split(value, "&")
for _, scope := range scopes {
// trim space
scope = strings.TrimSpace(scope)
switch scope {
case string(TOOL_SELECTOR_SCOPE_ALL),
string(TOOL_SELECTOR_SCOPE_PLUGIN),
string(TOOL_SELECTOR_SCOPE_API),
string(TOOL_SELECTOR_SCOPE_WORKFLOW):
return true
}
}
return false
}
func isVarSelectorScope(fl validator.FieldLevel) bool {
value := fl.Field().String()
// split by and symbol
scopes := strings.Split(value, "&")
for _, scope := range scopes {
// trim space
scope = strings.TrimSpace(scope)
switch scope {
case string(ANY_SCOPE_STRING),
string(ANY_SCOPE_NUMBER),
string(ANY_SCOPE_OBJECT),
string(ANY_SCOPE_ARRAY_NUMBER),
string(ANY_SCOPE_ARRAY_STRING),
string(ANY_SCOPE_ARRAY_OBJECT),
string(ANY_SCOPE_ARRAY_FILES):
return true
}
}
return false
}
func isScope(fl validator.FieldLevel) bool {
// get parent and check if it's a provider config
parent := fl.Parent().Interface()
if providerConfig, ok := parent.(ProviderConfig); ok {
// check config type
if providerConfig.Type == CONFIG_TYPE_APP_SELECTOR {
return isAppSelectorScope(fl)
} else if providerConfig.Type == CONFIG_TYPE_MODEL_SELECTOR {
return isModelConfigScope(fl)
} else if providerConfig.Type == CONFIG_TYPE_ANY {
return isVarSelectorScope(fl)
} else {
return false
}
//else if providerConfig.Type == CONFIG_TYPE_TOOL_SELECTOR {
//return isToolSelectorScope(fl)
//}
}
if toolParameter, ok := parent.(ToolParameter); ok {
if toolParameter.Type == TOOL_PARAMETER_TYPE_APP_SELECTOR {
return isAppSelectorScope(fl)
} else if toolParameter.Type == TOOL_PARAMETER_TYPE_MODEL_SELECTOR {
return isModelConfigScope(fl)
} else if toolParameter.Type == TOOL_PARAMETER_TYPE_ANY {
return isVarSelectorScope(fl)
} else {
return false
}
// else if toolParameter.Type == TOOL_PARAMETER_TYPE_TOOL_SELECTOR {
// return isToolSelectorScope(fl)
// }
}
if agentStrategyParameter, ok := parent.(AgentStrategyParameter); ok {
if agentStrategyParameter.Type == AGENT_STRATEGY_PARAMETER_TYPE_APP_SELECTOR {
return isAppSelectorScope(fl)
} else if agentStrategyParameter.Type == AGENT_STRATEGY_PARAMETER_TYPE_MODEL_SELECTOR {
return isModelConfigScope(fl)
} else if agentStrategyParameter.Type == AGENT_STRATEGY_PARAMETER_TYPE_ANY {
return isVarSelectorScope(fl)
} else {
return false
}
//else if agentStrategyParameter.Type == AGENT_STRATEGY_PARAMETER_TYPE_TOOLS_SELECTOR {
// return isToolSelectorScope(fl)
//}
}
return false
}
func init() {
en := en.New()
uni := ut.New(en, en)
translator, _ := uni.GetTranslator("en")
validators.GlobalEntitiesValidator.RegisterValidation("is_scope", isScope)
validators.GlobalEntitiesValidator.RegisterTranslation(
"is_scope",
translator,
func(ut ut.Translator) error {
return ut.Add("is_scope", "{0} is not a valid scope", true)
},
func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("is_scope", fe.Field())
return t
},
)
validators.GlobalEntitiesValidator.RegisterValidation("is_app_selector_scope", isAppSelectorScope)
validators.GlobalEntitiesValidator.RegisterTranslation(
"is_app_selector_scope",
translator,
func(ut ut.Translator) error {
return ut.Add("is_app_selector_scope", "{0} is not a valid app selector scope", true)
},
func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("is_app_selector_scope", fe.Field())
return t
},
)
validators.GlobalEntitiesValidator.RegisterValidation("is_model_config_scope", isModelConfigScope)
validators.GlobalEntitiesValidator.RegisterTranslation(
"is_model_config_scope",
translator,
func(ut ut.Translator) error {
return ut.Add("is_model_config_scope", "{0} is not a valid model config scope", true)
},
func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("is_model_config_scope", fe.Field())
return t
},
)
validators.GlobalEntitiesValidator.RegisterValidation("is_tool_selector_scope", isToolSelectorScope)
validators.GlobalEntitiesValidator.RegisterTranslation(
"is_tool_selector_scope",
translator,
func(ut ut.Translator) error {
return ut.Add("is_tool_selector_scope", "{0} is not a valid tool selector scope", true)
},
func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("is_tool_selector_scope", fe.Field())
return t
},
)
}
type ProviderConfig struct {
Name string `json:"name" validate:"omitempty,gt=0,lt=1024"`
Type ConfigType `json:"type" validate:"required,credential_type"`
Scope *string `json:"scope" validate:"omitempty,is_scope"`
Required bool `json:"required"`
Default any `json:"default" validate:"omitempty,is_basic_type"`
Options []ConfigOption `json:"options" validate:"omitempty,lt=128,dive"`
Multiple bool `json:"multiple" validate:"omitempty"`
Label I18nObject `json:"label" validate:"required"`
Help *I18nObject `json:"help" validate:"omitempty"`
URL *string `json:"url" validate:"omitempty"`
Placeholder *I18nObject `json:"placeholder" validate:"omitempty"`
}
func init() {
en := en.New()
uni := ut.New(en, en)
translator, _ := uni.GetTranslator("en")
validators.GlobalEntitiesValidator.RegisterValidation("credential_type", isCredentialType)
validators.GlobalEntitiesValidator.RegisterTranslation(
"credential_type",
translator,
func(ut ut.Translator) error {
return ut.Add("credential_type", "{0} is not a valid credential type", true)
},
func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("credential_type", fe.Field())
return t
},
)
}
// ValidateProviderConfigs validates the provider configs
func ValidateProviderConfigs(settings map[string]any, configs []ProviderConfig) error {
if len(settings) > 64 {
return errors.New("too many setting fields")
}
configsMap := make(map[string]ProviderConfig)
for _, config := range configs {
configsMap[config.Name] = config
}
for config_name, config := range configsMap {
v, ok := settings[config_name]
if (!ok || v == nil) && config.Required {
return errors.New("missing required setting: " + config_name)
}
if !ok || v == nil {
continue
}
// check type
switch config.Type {
case CONFIG_TYPE_TEXT_INPUT:
if _, ok := v.(string); !ok {
return errors.New("setting " + config_name + " is not a string")
}
case CONFIG_TYPE_SECRET_INPUT:
if _, ok := v.(string); !ok {
return errors.New("setting " + config_name + " is not a string")
}
case CONFIG_TYPE_SELECT:
if _, ok := v.(string); !ok {
return errors.New("setting " + config_name + " is not a string")
}
// check if value is in options
found := false
for _, option := range config.Options {
if v == option.Value {
found = true
break
}
}
if !found {
return errors.New("setting " + config_name + " is not a valid option")
}
case CONFIG_TYPE_BOOLEAN:
if _, ok := v.(bool); !ok {
return errors.New("setting " + config_name + " is not a boolean")
}
case CONFIG_TYPE_APP_SELECTOR:
m, ok := v.(map[string]any)
if !ok {
return errors.New("setting " + config_name + " is not a map")
}
// check keys
if _, ok := m["app_id"]; !ok {
return errors.New("setting " + config_name + " is missing app_id")
}
case CONFIG_TYPE_MODEL_SELECTOR:
m, ok := v.(map[string]any)
if !ok {
return errors.New("setting " + config_name + " is not a map")
}
// check keys
if _, ok := m["provider"]; !ok {
return errors.New("setting " + config_name + " is missing provider")
}
if _, ok := m["model"]; !ok {
return errors.New("setting " + config_name + " is missing model")
}
if _, ok := m["model_type"]; !ok {
return errors.New("setting " + config_name + " is missing model_type")
}
// check scope
if config.Scope != nil {
switch *config.Scope {
case string(MODEL_CONFIG_SCOPE_ALL):
// do nothing
case string(MODEL_CONFIG_SCOPE_LLM):
// do nothing
case string(MODEL_CONFIG_SCOPE_TEXT_EMBEDDING):
// do nothing
case string(MODEL_CONFIG_SCOPE_RERANK):
// score_threshold, top_n
if _, ok := m["score_threshold"]; !ok {
return errors.New("setting " + config_name + " is missing score_threshold")
}
if _, ok := m["top_n"]; !ok {
return errors.New("setting " + config_name + " is missing top_n")
}
case string(MODEL_CONFIG_SCOPE_TTS):
// voice
if _, ok := m["voice"]; !ok {
return errors.New("setting " + config_name + " is missing voice")
}
case string(MODEL_CONFIG_SCOPE_SPEECH2TEXT):
// do nothing
case string(MODEL_CONFIG_SCOPE_MODERATION):
// do nothing
case string(MODEL_CONFIG_SCOPE_VISION):
// the same as llm
if _, ok := m["completion_params"]; !ok {
return errors.New("setting " + config_name + " is missing completion_params")
}
default:
return errors.New("setting " + config_name + " is not a valid model config scope")
}
}
// case CONFIG_TYPE_TOOL_SELECTOR:
// m, ok := v.(map[string]any)
// if !ok {
// return errors.New("setting " + config_name + " is not a map")
// }
// // check keys
// if _, ok := m["provider"]; !ok {
// return errors.New("setting " + config_name + " is missing provider")
// }
// if _, ok := m["tool"]; !ok {
// return errors.New("setting " + config_name + " is missing tool")
// }
// if _, ok := m["tool_type"]; !ok {
// return errors.New("setting " + config_name + " is missing tool_type")
// }
}
}
return nil
}