Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added the ability to set a user default AI provider #427

Merged
merged 3 commits into from
May 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,38 @@ curl -X GET "http://localhost:8080/analyze?namespace=k8sgpt&explain=false"

## Additional AI providers

### Setting a new default AI provider

<details>

There may be scenarios where you wish to have K8sGPT plugged into several default AI providers. In this case you may wish to use one as a new default, other than OpenAI which is the project default.

_To view available providers_

```
k8sgpt auth list
Default:
> openai
Active:
> openai
> azureopenai
Unused:
> localai
> noopai

```

_To set a new default provider_

```
k8sgpt auth default -p azureopenai
Default provider set to azureopenai
```


</details>


### Azure OpenAI
<em>Prerequisites:</em> an Azure OpenAI deployment is needed, please visit MS official [documentation](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/create-resource?pivots=web-portal#create-a-resource) to create your own.

Expand All @@ -286,7 +318,7 @@ To authenticate with k8sgpt, you will need the Azure OpenAI endpoint of your ten
### Run k8sgpt
To run k8sgpt, run `k8sgpt auth` with the `azureopenai` backend:
```
k8sgpt auth --backend azureopenai --baseurl https://<your Azure OpenAI endpoint> --engine <deployment_name> --model <model_name>
k8sgpt auth new --backend azureopenai --baseurl https://<your Azure OpenAI endpoint> --engine <deployment_name> --model <model_name>
```
Lastly, enter your Azure API key, after the prompt.

Expand Down
2 changes: 2 additions & 0 deletions cmd/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,6 @@ func init() {
AuthCmd.AddCommand(newCmd)
// add subcommand to remove new backend provider
AuthCmd.AddCommand(removeCmd)
// add subcommand to set default backend provider
AuthCmd.AddCommand(defaultCmd)
}
79 changes: 79 additions & 0 deletions cmd/auth/default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package auth

import (
"os"
"strings"

"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

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)
}
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
providerExists := false
for _, provider := range configAI.Providers {
if provider.Name == providerName {
providerExists = true
}
}
if !providerExists {
color.Red("Error: Provider %s does not exist", providerName)
os.Exit(1)
}
// 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
color.Green("Default provider set to %s", providerName)
},
}

func init() {
// provider name flag
defaultCmd.Flags().StringVarP(&providerName, "provider", "p", "", "The name of the provider to set as default")
}
38 changes: 32 additions & 6 deletions cmd/auth/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"os"

"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
Expand All @@ -35,12 +36,37 @@ var listCmd = &cobra.Command{
os.Exit(1)
}

// iterate over the provider list and prints each provider name
for _, provider := range configAI.Providers {
if len(configAI.Providers) == 0 {
color.Red("Provider list is currently empty.")
} else {
fmt.Printf("> %s\n", provider.Name)
// Print the default if it is set
fmt.Print(color.YellowString("Default: \n"))
if configAI.DefaultProvider != "" {
fmt.Printf("> %s\n", color.BlueString(configAI.DefaultProvider))
} else {
fmt.Printf("> %s\n", color.BlueString("openai"))
}

// Get list of all AI Backends and only print htem if they are not in the provider list
fmt.Print(color.YellowString("Active: \n"))
for _, aiBackend := range ai.Backends {
providerExists := false
for _, provider := range configAI.Providers {
if provider.Name == aiBackend {
providerExists = true
}
}
if providerExists {
fmt.Printf("> %s\n", color.GreenString(aiBackend))
}
}
fmt.Print(color.YellowString("Unused: \n"))
for _, aiBackend := range ai.Backends {
providerExists := false
for _, provider := range configAI.Providers {
if provider.Name == aiBackend {
providerExists = true
}
}
if !providerExists {
fmt.Printf("> %s\n", color.RedString(aiBackend))
}
}
},
Expand Down
3 changes: 2 additions & 1 deletion pkg/ai/iai.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ func NewClient(provider string) IAI {
}

type AIConfiguration struct {
Providers []AIProvider `mapstructure:"providers"`
Providers []AIProvider `mapstructure:"providers"`
DefaultProvider string `mapstructure:"defaultprovider"`
}

type AIProvider struct {
Expand Down
45 changes: 27 additions & 18 deletions pkg/analysis/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,17 @@ import (
)

type Analysis struct {
Context context.Context
Filters []string
Client *kubernetes.Client
AIClient ai.IAI
Results []common.Result
Errors []string
Namespace string
Cache cache.ICache
Explain bool
MaxConcurrency int
Context context.Context
Filters []string
Client *kubernetes.Client
AIClient ai.IAI
Results []common.Result
Errors []string
Namespace string
Cache cache.ICache
Explain bool
MaxConcurrency int
AnalysisAIProvider string // The name of the AI Provider used for this analysis
}

type AnalysisStatus string
Expand All @@ -55,6 +56,7 @@ const (
)

type JsonOutput struct {
Provider string `json:"provider"`
Errors AnalysisErrors `json:"errors"`
Status AnalysisStatus `json:"status"`
Problems int `json:"problems"`
Expand All @@ -74,6 +76,12 @@ func NewAnalysis(backend string, language string, filters []string, namespace st
os.Exit(1)
}

// Backend string will have high priority than a default provider
// Backend as "openai" represents the default CLI argument passed through
if configAI.DefaultProvider != "" && backend == "openai" {
matthisholleville marked this conversation as resolved.
Show resolved Hide resolved
backend = configAI.DefaultProvider
}

var aiProvider ai.AIProvider
for _, provider := range configAI.Providers {
if backend == provider.Name {
Expand Down Expand Up @@ -105,14 +113,15 @@ func NewAnalysis(backend string, language string, filters []string, namespace st
}

return &Analysis{
Context: ctx,
Filters: filters,
Client: client,
AIClient: aiClient,
Namespace: namespace,
Cache: cache.New(noCache),
Explain: explain,
MaxConcurrency: maxConcurrency,
Context: ctx,
Filters: filters,
Client: client,
AIClient: aiClient,
Namespace: namespace,
Cache: cache.New(noCache),
Explain: explain,
MaxConcurrency: maxConcurrency,
AnalysisAIProvider: backend,
}, nil
}

Expand Down
5 changes: 5 additions & 0 deletions pkg/analysis/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func (a *Analysis) jsonOutput() ([]byte, error) {
}

result := JsonOutput{
Provider: a.AnalysisAIProvider,
Problems: problems,
Results: a.Results,
Errors: a.Errors,
Expand All @@ -56,6 +57,10 @@ func (a *Analysis) jsonOutput() ([]byte, error) {

func (a *Analysis) textOutput() ([]byte, error) {
var output strings.Builder

// Print the AI provider used for this analysis
output.WriteString(fmt.Sprintf("AI Provider: %s\n", color.YellowString(a.AnalysisAIProvider)))
AlexsJones marked this conversation as resolved.
Show resolved Hide resolved

if len(a.Errors) != 0 {
output.WriteString("\n")
output.WriteString(color.YellowString("Warnings : \n"))
Expand Down