diff --git a/README.md b/README.md
index 2d673d5..96198e7 100644
--- a/README.md
+++ b/README.md
@@ -262,9 +262,58 @@ _Analysis with serve mode_
```
curl -X GET "http://localhost:8080/analyze?namespace=k8sgpt&explain=false"
```
+
+
+## Additional AI providers
+
+### Azure OpenAI
+Prerequisites: 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.
+
+To authenticate with k8sgpt, you will need the Azure OpenAI endpoint of your tenant `"https://your Azure OpenAI Endpoint"`, the api key to access your deployment, the deployment name of your model and the model name itself.
+
+
+### Run k8sgpt
+To run k8sgpt, run `k8sgpt auth` with the `azureopenai` backend:
+```
+k8sgpt auth --backend azureopenai --baseurl https:// --engine --model
+```
+Lastly, enter your Azure API key, after the prompt.
+
+Now you are ready to analyze with the azure openai backend:
+```
+k8sgpt analyze --explain --backend azureopenai
+```
+### Running local models
+
+To run local models, it is possible to use OpenAI compatible APIs, for instance [LocalAI](https://github.com/go-skynet/LocalAI) which uses [llama.cpp](https://github.com/ggerganov/llama.cpp) and [ggml](https://github.com/ggerganov/ggml) to run inference on consumer-grade hardware. Models supported by LocalAI for instance are Vicuna, Alpaca, LLaMA, Cerebras, GPT4ALL, GPT4ALL-J and koala.
+
+
+
+To run local inference, you need to download the models first, for instance you can find `ggml` compatible models in [huggingface.com](https://huggingface.co/models?search=ggml) (for example vicuna, alpaca and koala).
+
+### Start the API server
+
+To start the API server, follow the instruction in [LocalAI](https://github.com/go-skynet/LocalAI#example-use-gpt4all-j-model).
+
+### Run k8sgpt
+
+To run k8sgpt, run `k8sgpt auth` with the `localai` backend:
+
+```
+k8sgpt auth --backend localai --model --baseurl http://localhost:8080/v1
+```
+
+Now you can analyze with the `localai` backend:
+
+```
+k8sgpt analyze --explain --backend localai
+```
+
+
+
## How does anonymization work?
With this option, the data is anonymized before being sent to the AI Backend. During the analysis execution, `k8sgpt` retrieves sensitive data (Kubernetes object names, labels, etc.). This data is masked when sent to the AI backend and replaced by a key that can be used to de-anonymize the data when the solution is returned to the user.
@@ -295,37 +344,6 @@ The Kubernetes system is trying to scale a StatefulSet named fake-deployment usi
-
-## Running local models
-
-To run local models, it is possible to use OpenAI compatible APIs, for instance [LocalAI](https://github.com/go-skynet/LocalAI) which uses [llama.cpp](https://github.com/ggerganov/llama.cpp) and [ggml](https://github.com/ggerganov/ggml) to run inference on consumer-grade hardware. Models supported by LocalAI for instance are Vicuna, Alpaca, LLaMA, Cerebras, GPT4ALL, GPT4ALL-J and koala.
-
-
-
-To run local inference, you need to download the models first, for instance you can find `ggml` compatible models in [huggingface.co](https://huggingface.co/models?search=ggml) (for example vicuna, alpaca and koala).
-
-### Start the API server
-
-To start the API server, follow the instruction in [LocalAI](https://github.com/go-skynet/LocalAI#example-use-gpt4all-j-model).
-
-### Run k8sgpt
-
-To run k8sgpt, run `k8sgpt auth` with the `localai` backend:
-
-```
-k8sgpt auth --backend localai --model --baseurl http://localhost:8080/v1
-```
-
-When being asked for an API key, just press enter.
-
-Now you can analyze with the `localai` backend:
-
-```
-k8sgpt analyze --explain --backend localai
-```
-
-
-
## Configuration
diff --git a/cmd/auth/auth.go b/cmd/auth/auth.go
index cb077a1..3aa2975 100644
--- a/cmd/auth/auth.go
+++ b/cmd/auth/auth.go
@@ -31,6 +31,7 @@ var (
password string
baseURL string
model string
+ engine string
)
// authCmd represents the auth command
@@ -38,6 +39,13 @@ var AuthCmd = &cobra.Command{
Use: "auth",
Short: "Authenticate with your chosen backend",
Long: `Provide the necessary credentials to authenticate with your chosen backend.`,
+ PreRun: func(cmd *cobra.Command, args []string) {
+ backend, _ := cmd.Flags().GetString("backend")
+ if strings.ToLower(backend) == "azureopenai" {
+ cmd.MarkFlagRequired("engine")
+ cmd.MarkFlagRequired("baseurl")
+ }
+ },
Run: func(cmd *cobra.Command, args []string) {
// get ai configuration
@@ -57,9 +65,18 @@ var AuthCmd = &cobra.Command{
}
}
- // check if backend is not empty
- if backend == "" {
- color.Red("Error: Backend AI cannot be empty.")
+ validBackend := func(validBackends []string, backend string) bool {
+ for _, b := range validBackends {
+ if b == backend {
+ return true
+ }
+ }
+ return false
+ }
+
+ // check if backend is not empty and a valid value
+ if backend == "" || !validBackend(ai.Backends, backend) {
+ color.Red("Error: Backend AI cannot be empty and accepted values are '%v'", strings.Join(ai.Backends, ", "))
os.Exit(1)
}
@@ -88,6 +105,7 @@ var AuthCmd = &cobra.Command{
Model: model,
Password: password,
BaseURL: baseURL,
+ Engine: engine,
}
if providerIndex == -1 {
@@ -117,4 +135,6 @@ func init() {
AuthCmd.Flags().StringVarP(&password, "password", "p", "", "Backend AI password")
// add flag for url
AuthCmd.Flags().StringVarP(&baseURL, "baseurl", "u", "", "URL AI provider, (e.g `http://localhost:8080/v1`)")
+ // add flag for azure open ai engine/deployment name
+ AuthCmd.Flags().StringVarP(&engine, "engine", "e", "", "Azure AI deployment name")
}
diff --git a/cmd/serve/serve.go b/cmd/serve/serve.go
index 8a556a6..e00265b 100644
--- a/cmd/serve/serve.go
+++ b/cmd/serve/serve.go
@@ -47,17 +47,17 @@ var ServeCmd = &cobra.Command{
password := os.Getenv("K8SGPT_PASSWORD")
model := os.Getenv("K8SGPT_MODEL")
baseURL := os.Getenv("K8SGPT_BASEURL")
-
+ engine := os.Getenv("K8SGPT_ENGINE")
// If the envs are set, allocate in place to the aiProvider
// else exit with error
- envIsSet := backend != "" || password != "" || model != "" || baseURL != ""
-
+ envIsSet := backend != "" || password != "" || model != ""
if envIsSet {
aiProvider = &ai.AIProvider{
Name: backend,
Password: password,
Model: model,
BaseURL: baseURL,
+ Engine: engine,
}
configAI.Providers = append(configAI.Providers, *aiProvider)
@@ -75,10 +75,10 @@ var ServeCmd = &cobra.Command{
if aiProvider == nil {
for _, provider := range configAI.Providers {
if backend == provider.Name {
- // he pointer to the range variable is not really an issue here, as there
- // is a break right after, but to prevent potential future issues, a temp
- // variable is assigned
- p := provider
+ // the pointer to the range variable is not really an issue here, as there
+ // is a break right after, but to prevent potential future issues, a temp
+ // variable is assigned
+ p := provider
aiProvider = &p
break
}
diff --git a/pkg/ai/azureopenai.go b/pkg/ai/azureopenai.go
new file mode 100644
index 0000000..21593a2
--- /dev/null
+++ b/pkg/ai/azureopenai.go
@@ -0,0 +1,94 @@
+package ai
+
+import (
+ "context"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "strings"
+
+ "github.com/k8sgpt-ai/k8sgpt/pkg/cache"
+ "github.com/k8sgpt-ai/k8sgpt/pkg/util"
+
+ "github.com/fatih/color"
+
+ "github.com/sashabaranov/go-openai"
+)
+
+type AzureAIClient struct {
+ client *openai.Client
+ language string
+ model string
+}
+
+func (c *AzureAIClient) Configure(config IAIConfig, lang string) error {
+ token := config.GetPassword()
+ baseURL := config.GetBaseURL()
+ engine := config.GetEngine()
+ defaultConfig := openai.DefaultAzureConfig(token, baseURL, engine)
+ client := openai.NewClientWithConfig(defaultConfig)
+ if client == nil {
+ return errors.New("error creating Azure OpenAI client")
+ }
+ c.language = lang
+ c.client = client
+ c.model = config.GetModel()
+ return nil
+}
+
+func (c *AzureAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
+ // Create a completion request
+ resp, err := c.client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
+ Model: c.model,
+ Messages: []openai.ChatCompletionMessage{
+ {
+ Role: "user",
+ Content: fmt.Sprintf(default_prompt, c.language, prompt),
+ },
+ },
+ })
+ if err != nil {
+ return "", err
+ }
+ return resp.Choices[0].Message.Content, nil
+}
+
+func (a *AzureAIClient) Parse(ctx context.Context, prompt []string, cache cache.ICache) (string, error) {
+ inputKey := strings.Join(prompt, " ")
+ // Check for cached data
+ cacheKey := util.GetCacheKey(a.GetName(), a.language, inputKey)
+
+ if !cache.IsCacheDisabled() && cache.Exists(cacheKey) {
+ response, err := cache.Load(cacheKey)
+ if err != nil {
+ return "", err
+ }
+
+ if response != "" {
+ output, err := base64.StdEncoding.DecodeString(response)
+ if err != nil {
+ color.Red("error decoding cached data: %v", err)
+ return "", nil
+ }
+ return string(output), nil
+ }
+ }
+
+ response, err := a.GetCompletion(ctx, inputKey)
+ if err != nil {
+ return "", err
+ }
+
+ err = cache.Store(cacheKey, base64.StdEncoding.EncodeToString([]byte(response)))
+
+ if err != nil {
+ color.Red("error storing value to cache: %v", err)
+ return "", nil
+ }
+
+ return response, nil
+}
+
+func (a *AzureAIClient) GetName() string {
+ return "azureopenai"
+}
diff --git a/pkg/ai/iai.go b/pkg/ai/iai.go
index 39e66b3..767e1e5 100644
--- a/pkg/ai/iai.go
+++ b/pkg/ai/iai.go
@@ -19,6 +19,21 @@ import (
"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
)
+var (
+ clients = []IAI{
+ &OpenAIClient{},
+ &AzureAIClient{},
+ &LocalAIClient{},
+ &NoOpAIClient{},
+ }
+ Backends = []string{
+ "openai",
+ "localai",
+ "azureopenai",
+ "noopai",
+ }
+)
+
type IAI interface {
Configure(config IAIConfig, language string) error
GetCompletion(ctx context.Context, prompt string) (string, error)
@@ -30,19 +45,17 @@ type IAIConfig interface {
GetPassword() string
GetModel() string
GetBaseURL() string
+ GetEngine() string
}
func NewClient(provider string) IAI {
- switch provider {
- case "openai":
- return &OpenAIClient{}
- case "localai":
- return &LocalAIClient{}
- case "noopai":
- return &NoOpAIClient{}
- default:
- return &OpenAIClient{}
+ for _, c := range clients {
+ if provider == c.GetName() {
+ return c
+ }
}
+ // default client
+ return &OpenAIClient{}
}
type AIConfiguration struct {
@@ -52,8 +65,9 @@ type AIConfiguration struct {
type AIProvider struct {
Name string `mapstructure:"name"`
Model string `mapstructure:"model"`
- Password string `mapstructure:"password"`
- BaseURL string `mapstructure:"baseurl"`
+ Password string `mapstructure:"password" yaml:"password,omitempty"`
+ BaseURL string `mapstructure:"baseurl" yaml:"baseurl,omitempty"`
+ Engine string `mapstructure:"engine" yaml:"engine,omitempty"`
}
func (p *AIProvider) GetBaseURL() string {
@@ -68,6 +82,10 @@ func (p *AIProvider) GetModel() string {
return p.Model
}
+func (p *AIProvider) GetEngine() string {
+ return p.Engine
+}
+
func NeedPassword(backend string) bool {
return backend != "localai"
}