Skip to content

Commit

Permalink
feat: support many:1 auth:provider mapping
Browse files Browse the repository at this point in the history
Added the ability to add multiple configurations for the same backend
provider.

One to one auth:provider mapping had many bugs and users were requesting
many to one auth:provider mapping functionality for more flexibility
with backend provider configurations.

This commit also includes the addition of an optional `--config-name`
flag to allow users to specify the config-name while configuring backend
AI providers.

Changes exclusive to individual subcommands are:
1. default
  - `--config-name` flag has been added to allow users to update the
    default configuration for a backend AI provider.
  - Setting the `--config-name` flag will only update the default
    configuration for the specified backend, it will not update the
    `configAI.DefaultProvider`.
  - To update the `configAI.DefaultProvider`, user must leave the
    `--config-name` flag unset.

2. list
  - The list subcommand will now respect the value of `userInput` set
    when the user is asked to show password or not.
  - The command's output has been modified to display the backend
    providers list along with their configuration names.
  - Default configs for each backend AI provider are marked with the
    `(Default Config)` string in bright yellow color.

3. remove
  - Users can specify either single or multiple backends when
    removing the configurations.
  - If the `--config-name` flag is not set, the "default" configuration
    for the specified backend(s) will be removed(if it is present).
  - Setting the `--config-name` flag will specify the configuration name
    to delete for each of the specified backends.

4. Update
  - Now the user can only specify a single backend provider at a time.
    This has been done because updating multiple backend configs with
    same values of parameters doesn't make any sense.
  - Removal of the `Args` function because update subcommand can have
    multiple args to set different parameters for a backend config.
  - Users can also update the configuration name for an already existing
    config.

Addresses:
- #936
- #911
- #905
- #900
- #843

Signed-off-by: VaibhavMalik4187 <[email protected]>
  • Loading branch information
VaibhavMalik4187 committed Feb 18, 2024
1 parent a3cd7e6 commit 241a712
Show file tree
Hide file tree
Showing 9 changed files with 462 additions and 290 deletions.
214 changes: 129 additions & 85 deletions cmd/auth/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,120 +28,164 @@ import (

const (
defaultBackend = "openai"
defaultConfig = "default"
defaultModel = "gpt-3.5-turbo"
)

var addCmd = &cobra.Command{
Use: "add",
Short: "Add new provider",
Long: "The add command allows to configure a new backend AI provider",
PreRun: func(cmd *cobra.Command, args []string) {
backend, _ := cmd.Flags().GetString("backend")
if strings.ToLower(backend) == "azureopenai" {
_ = cmd.MarkFlagRequired("engine")
_ = cmd.MarkFlagRequired("baseurl")
}
if strings.ToLower(backend) == "amazonsagemaker" {
_ = cmd.MarkFlagRequired("endpointname")
_ = cmd.MarkFlagRequired("providerRegion")
func runAddCommand(cmd *cobra.Command, args []string) {
// 1. Get ai configuration
err := viper.UnmarshalKey("ai", &configAI)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}

// 2. Validate input values upfront and set default values if the inputs are empty
// check if backend is not empty and a valid value
validBackend := func(validBackends []string, backend string) bool {
for _, b := range validBackends {
if b == backend {
return true
}
}
},
Run: func(cmd *cobra.Command, args []string) {
return false
}

// get ai configuration
err := viper.UnmarshalKey("ai", &configAI)
if err != nil {
color.Red("Error: %v", err)
if backend == "" {
// Set the default value of the backend provider
color.Yellow(fmt.Sprintf("Warning: backend input is empty, will use the default value: %s", defaultBackend))
backend = defaultBackend
} else {
// Check if the given provider is valid or not.
if !validBackend(ai.Backends, backend) {
color.Red("Error: Backend AI accepted values are '%v'", strings.Join(ai.Backends, ", "))
os.Exit(1)
}
}

// search for provider with same name
providerIndex := -1
for i, provider := range configAI.Providers {
if backend == provider.Name {
providerIndex = i
break
}
}
// Set the value of config-name if it is not provided by the user.
if configName == "" {
color.Yellow(fmt.Sprintf("Warning: config-name input is empty, will use the default value: %s", defaultConfig))
configName = defaultConfig
}

// 3. Find existing provider index
// search for provider with same backend
providerIndex := -1
configIndex := -1
for i, provider := range configAI.Providers {
if backend == provider.Backend {
providerIndex = i

validBackend := func(validBackends []string, backend string) bool {
for _, b := range validBackends {
if b == backend {
return true
// Iterate over all the configs of this provider
// and check if a config with the same name already exists
for index, config := range provider.Configs {
if configName == config.Name {
configIndex = index
break
}
}
return false
}

// check if backend is not empty and a valid value
if backend == "" {
color.Yellow(fmt.Sprintf("Warning: backend input is empty, will use the default value: %s", defaultBackend))
backend = defaultBackend
} else {
if !validBackend(ai.Backends, backend) {
color.Red("Error: Backend AI accepted values are '%v'", strings.Join(ai.Backends, ", "))
os.Exit(1)
if configIndex != -1 {
break
}
}
}

// check if model is not empty
if model == "" {
model = defaultModel
color.Yellow(fmt.Sprintf("Warning: model input is empty, will use the default value: %s", defaultModel))
}
if temperature > 1.0 || temperature < 0.0 {
color.Red("Error: temperature ranges from 0 to 1.")
os.Exit(1)
}
if topP > 1.0 || topP < 0.0 {
color.Red("Error: topP ranges from 0 to 1.")
// Quit if the config already exists
if configIndex != -1 {
color.Red("Provider with same config already exists.")
os.Exit(1)
}

// Handle input sanitization for config.
if model == "" {
model = defaultModel
color.Yellow(fmt.Sprintf("Warning: model input is empty, will use the default value: %s", defaultModel))
}
if temperature > 1.0 || temperature < 0.0 {
color.Red("Error: temperature ranges from 0 to 1.")
os.Exit(1)
}
if topP > 1.0 || topP < 0.0 {
color.Red("Error: topP ranges from 0 to 1.")
os.Exit(1)
}

if ai.NeedPassword(backend) && password == "" {
fmt.Printf("Enter %s Key: ", backend)
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
color.Red("Error reading %s Key from stdin: %s", backend,
err.Error())
os.Exit(1)
}
password = strings.TrimSpace(string(bytePassword))
}

if ai.NeedPassword(backend) && password == "" {
fmt.Printf("Enter %s Key: ", backend)
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
color.Red("Error reading %s Key from stdin: %s", backend,
err.Error())
os.Exit(1)
}
password = strings.TrimSpace(string(bytePassword))
}
// Create a new provider config
config := ai.AIProviderConfig{
Name: configName,
Model: model,
Password: password,
BaseURL: baseURL,
EndpointName: endpointName,
Engine: engine,
Temperature: temperature,
ProviderRegion: providerRegion,
TopP: topP,
MaxTokens: maxTokens,
}

// create new provider object
// Create a new provider if the providerIndex is -1
if providerIndex == -1 {
// Instantiate a new provider if it is not already present.
newProvider := ai.AIProvider{
Name: backend,
Model: model,
Password: password,
BaseURL: baseURL,
EndpointName: endpointName,
Engine: engine,
Temperature: temperature,
ProviderRegion: providerRegion,
TopP: topP,
MaxTokens: maxTokens,
Backend: backend,
Configs: []ai.AIProviderConfig{
config,
},
DefaultConfig: 0,
}

if providerIndex == -1 {
// provider with same name does not exist, add new provider to list
configAI.Providers = append(configAI.Providers, newProvider)
viper.Set("ai", configAI)
if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error())
os.Exit(1)
}
color.Green("%s added to the AI backend provider list", backend)
} else {
// provider with same name exists, update provider info
color.Yellow("Provider with same name already exists.")
// provider with this backend name does not exist, add new provider to list
configAI.Providers = append(configAI.Providers, newProvider)
} else {
// Append this config in the configs of the ai provider
configAI.Providers[providerIndex].Configs = append(configAI.Providers[providerIndex].Configs, config)
}

viper.Set("ai", configAI)
if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error())
os.Exit(1)
}
color.Green("%s added to the AI backend provider list", backend)
}

var addCmd = &cobra.Command{
Use: "add",
Short: "Add new provider",
Long: "The add command allows to configure a new backend AI provider",
PreRun: func(cmd *cobra.Command, args []string) {
backend, _ := cmd.Flags().GetString("backend")
if strings.ToLower(backend) == "azureopenai" {
_ = cmd.MarkFlagRequired("engine")
_ = cmd.MarkFlagRequired("baseurl")
}
if strings.ToLower(backend) == "amazonsagemaker" {
_ = cmd.MarkFlagRequired("endpointname")
_ = cmd.MarkFlagRequired("providerRegion")
}
},
Run: runAddCommand,
}

func init() {
// add flag for backend
addCmd.Flags().StringVarP(&backend, "backend", "b", defaultBackend, "Backend AI provider")
// add flag for config-name
addCmd.Flags().StringVarP(&configName, "config-name", "", defaultConfig, "Backend AI provider")
// add flag for model
addCmd.Flags().StringVarP(&model, "model", "m", defaultModel, "Backend AI model")
// add flag for password
Expand Down
1 change: 1 addition & 0 deletions cmd/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (

var (
backend string
configName string
password string
baseURL string
endpointName string
Expand Down
114 changes: 77 additions & 37 deletions cmd/auth/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,54 +26,94 @@ var (
providerName string
)

var defaultCmd = &cobra.Command{
Use: "default",
Short: "Set your default AI backend provider",
Long: "The command to set your new default AI backend provider (default is openai)",
Run: func(cmd *cobra.Command, args []string) {
err := viper.UnmarshalKey("ai", &configAI)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
func runDefaultCommand(cmd *cobra.Command, args []string) {
// 1. Get the ai configurations
err := viper.UnmarshalKey("ai", &configAI)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}

// 2. Validate the input values and set defaults if necessary
if providerName == "" {
if configAI.DefaultProvider != "" {
color.Yellow("Your default provider is \"%s\"", configAI.DefaultProvider)
} else {
color.Yellow("Your default provider is openai")
}
if providerName == "" {
if configAI.DefaultProvider != "" {
color.Yellow("Your default provider is %s", configAI.DefaultProvider)
} else {
color.Yellow("Your default provider is openai")
os.Exit(0)
}

// lowercase the provider name
providerName = strings.ToLower(providerName)

// Check if the provider is in the provider list
providerIndex := -1
configIndex := -1
for i, provider := range configAI.Providers {
if providerName == provider.Backend {
providerIndex = i

// Iterate over all the configs of this provider
// and check if a config with the same name exists
if configName != "" {
for index, config := range provider.Configs {
if configName == config.Name {
configIndex = index
break
}
}
}
os.Exit(0)
}
// lowercase the provider name
providerName = strings.ToLower(providerName)

// Check if the provider is in the provider list
providerExists := false
for _, provider := range configAI.Providers {
if provider.Name == providerName {
providerExists = true

if configIndex != -1 {
break
}
}
if !providerExists {
color.Red("Error: Provider %s does not exist", providerName)
os.Exit(1)
}
}

if providerIndex == -1 {
color.Red("Error: Provider \"%s\" does not exist", providerName)
os.Exit(1)
}

if configIndex == -1 && configName != "" {
color.Red("Error: The backend provider \"%s\" does not have a configuration with the name \"%s\"", backend, configName)
os.Exit(1)
}

if configName != "" {
// Set the default config
configAI.Providers[providerIndex].DefaultConfig = configIndex
} else {
// Set the default provider
configAI.DefaultProvider = providerName
}

viper.Set("ai", configAI)
// Viper write config
err = viper.WriteConfig()
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
// Print acknowledgement
viper.Set("ai", configAI)
// Viper write config
err = viper.WriteConfig()
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}

// Print acknowledgement
if configName != "" {
color.Green("Default config for %s set to %s", providerName, configName)
} else {
color.Green("Default provider set to %s", providerName)
},
}
}

var defaultCmd = &cobra.Command{
Use: "default",
Short: "Set your default AI backend provider and provider config",
Long: "The command to set your new default AI backend provider (default is openai)",
Run: runDefaultCommand,
}

func init() {
// provider name flag
defaultCmd.Flags().StringVarP(&providerName, "provider", "p", "", "The name of the provider to set as default")
defaultCmd.Flags().StringVarP(&configName, "config-name", "", "", "The name of the config to set as default for a provider")
}
Loading

0 comments on commit 241a712

Please sign in to comment.