mirror of
https://github.com/k8sgpt-ai/k8sgpt.git
synced 2025-07-17 00:41:18 +00:00
feat: add azure openai provider (#309)
* feat: add azure openai provider Signed-off-by: Aris Boutselis <aris.boutselis@senseon.io> * feat: validate backend name Signed-off-by: Aris Boutselis <aris.boutselis@senseon.io> * fix: remove BaseURL from the mandatory env variables Signed-off-by: Aris Boutselis <arisboutselis08@gmail.com> * fix: conflicts Signed-off-by: Aris Boutselis <aris.boutselis@senseon.io> * chore: updated logo (#365) Signed-off-by: Alex Jones <alexsimonjones@gmail.com> * chore: added changing banners (#367) Signed-off-by: Alex Jones <alexsimonjones@gmail.com> * feat: add additionalLabels to Service Monitor (#366) * feat: add additionalLabels to Service Monitor Signed-off-by: Brad McCoy <bradmccoydev@gmail.com> * feat: update additionalLabels Signed-off-by: Brad McCoy <bradmccoydev@gmail.com> --------- Signed-off-by: Brad McCoy <bradmccoydev@gmail.com> * fix: update README file's ai provider section. Signed-off-by: Aris Boutselis <aris.boutselis@senseon.io> --------- Signed-off-by: Aris Boutselis <aris.boutselis@senseon.io> Signed-off-by: Aris Boutselis <arisboutselis08@gmail.com> Signed-off-by: Alex Jones <alexsimonjones@gmail.com> Signed-off-by: Brad McCoy <bradmccoydev@gmail.com> Co-authored-by: Aris Boutselis <arisboutselis08@gmail.com> Co-authored-by: Alex Jones <alexsimonjones@gmail.com> Co-authored-by: Brad McCoy <bradmccoydev@gmail.com>
This commit is contained in:
parent
a89a5cfa2e
commit
d8357ceb94
80
README.md
80
README.md
@ -262,9 +262,58 @@ _Analysis with serve mode_
|
|||||||
```
|
```
|
||||||
curl -X GET "http://localhost:8080/analyze?namespace=k8sgpt&explain=false"
|
curl -X GET "http://localhost:8080/analyze?namespace=k8sgpt&explain=false"
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Additional AI providers
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
<details>
|
||||||
|
|
||||||
|
### 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>
|
||||||
|
```
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
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 <model_name> --baseurl http://localhost:8080/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can analyze with the `localai` backend:
|
||||||
|
|
||||||
|
```
|
||||||
|
k8sgpt analyze --explain --backend localai
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## How does anonymization work?
|
## 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.
|
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
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
<details>
|
|
||||||
|
|
||||||
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 <model_name> --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
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
@ -31,6 +31,7 @@ var (
|
|||||||
password string
|
password string
|
||||||
baseURL string
|
baseURL string
|
||||||
model string
|
model string
|
||||||
|
engine string
|
||||||
)
|
)
|
||||||
|
|
||||||
// authCmd represents the auth command
|
// authCmd represents the auth command
|
||||||
@ -38,6 +39,13 @@ var AuthCmd = &cobra.Command{
|
|||||||
Use: "auth",
|
Use: "auth",
|
||||||
Short: "Authenticate with your chosen backend",
|
Short: "Authenticate with your chosen backend",
|
||||||
Long: `Provide the necessary credentials to 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) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
// get ai configuration
|
// get ai configuration
|
||||||
@ -57,9 +65,18 @@ var AuthCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if backend is not empty
|
validBackend := func(validBackends []string, backend string) bool {
|
||||||
if backend == "" {
|
for _, b := range validBackends {
|
||||||
color.Red("Error: Backend AI cannot be empty.")
|
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)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,6 +105,7 @@ var AuthCmd = &cobra.Command{
|
|||||||
Model: model,
|
Model: model,
|
||||||
Password: password,
|
Password: password,
|
||||||
BaseURL: baseURL,
|
BaseURL: baseURL,
|
||||||
|
Engine: engine,
|
||||||
}
|
}
|
||||||
|
|
||||||
if providerIndex == -1 {
|
if providerIndex == -1 {
|
||||||
@ -117,4 +135,6 @@ func init() {
|
|||||||
AuthCmd.Flags().StringVarP(&password, "password", "p", "", "Backend AI password")
|
AuthCmd.Flags().StringVarP(&password, "password", "p", "", "Backend AI password")
|
||||||
// add flag for url
|
// add flag for url
|
||||||
AuthCmd.Flags().StringVarP(&baseURL, "baseurl", "u", "", "URL AI provider, (e.g `http://localhost:8080/v1`)")
|
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")
|
||||||
}
|
}
|
||||||
|
@ -47,17 +47,17 @@ var ServeCmd = &cobra.Command{
|
|||||||
password := os.Getenv("K8SGPT_PASSWORD")
|
password := os.Getenv("K8SGPT_PASSWORD")
|
||||||
model := os.Getenv("K8SGPT_MODEL")
|
model := os.Getenv("K8SGPT_MODEL")
|
||||||
baseURL := os.Getenv("K8SGPT_BASEURL")
|
baseURL := os.Getenv("K8SGPT_BASEURL")
|
||||||
|
engine := os.Getenv("K8SGPT_ENGINE")
|
||||||
// If the envs are set, allocate in place to the aiProvider
|
// If the envs are set, allocate in place to the aiProvider
|
||||||
// else exit with error
|
// else exit with error
|
||||||
envIsSet := backend != "" || password != "" || model != "" || baseURL != ""
|
envIsSet := backend != "" || password != "" || model != ""
|
||||||
|
|
||||||
if envIsSet {
|
if envIsSet {
|
||||||
aiProvider = &ai.AIProvider{
|
aiProvider = &ai.AIProvider{
|
||||||
Name: backend,
|
Name: backend,
|
||||||
Password: password,
|
Password: password,
|
||||||
Model: model,
|
Model: model,
|
||||||
BaseURL: baseURL,
|
BaseURL: baseURL,
|
||||||
|
Engine: engine,
|
||||||
}
|
}
|
||||||
|
|
||||||
configAI.Providers = append(configAI.Providers, *aiProvider)
|
configAI.Providers = append(configAI.Providers, *aiProvider)
|
||||||
@ -75,10 +75,10 @@ var ServeCmd = &cobra.Command{
|
|||||||
if aiProvider == nil {
|
if aiProvider == nil {
|
||||||
for _, provider := range configAI.Providers {
|
for _, provider := range configAI.Providers {
|
||||||
if backend == provider.Name {
|
if backend == provider.Name {
|
||||||
// he pointer to the range variable is not really an issue here, as there
|
// 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
|
// is a break right after, but to prevent potential future issues, a temp
|
||||||
// variable is assigned
|
// variable is assigned
|
||||||
p := provider
|
p := provider
|
||||||
aiProvider = &p
|
aiProvider = &p
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
94
pkg/ai/azureopenai.go
Normal file
94
pkg/ai/azureopenai.go
Normal file
@ -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"
|
||||||
|
}
|
@ -19,6 +19,21 @@ import (
|
|||||||
"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
|
"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
clients = []IAI{
|
||||||
|
&OpenAIClient{},
|
||||||
|
&AzureAIClient{},
|
||||||
|
&LocalAIClient{},
|
||||||
|
&NoOpAIClient{},
|
||||||
|
}
|
||||||
|
Backends = []string{
|
||||||
|
"openai",
|
||||||
|
"localai",
|
||||||
|
"azureopenai",
|
||||||
|
"noopai",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
type IAI interface {
|
type IAI interface {
|
||||||
Configure(config IAIConfig, language string) error
|
Configure(config IAIConfig, language string) error
|
||||||
GetCompletion(ctx context.Context, prompt string) (string, error)
|
GetCompletion(ctx context.Context, prompt string) (string, error)
|
||||||
@ -30,19 +45,17 @@ type IAIConfig interface {
|
|||||||
GetPassword() string
|
GetPassword() string
|
||||||
GetModel() string
|
GetModel() string
|
||||||
GetBaseURL() string
|
GetBaseURL() string
|
||||||
|
GetEngine() string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(provider string) IAI {
|
func NewClient(provider string) IAI {
|
||||||
switch provider {
|
for _, c := range clients {
|
||||||
case "openai":
|
if provider == c.GetName() {
|
||||||
return &OpenAIClient{}
|
return c
|
||||||
case "localai":
|
}
|
||||||
return &LocalAIClient{}
|
|
||||||
case "noopai":
|
|
||||||
return &NoOpAIClient{}
|
|
||||||
default:
|
|
||||||
return &OpenAIClient{}
|
|
||||||
}
|
}
|
||||||
|
// default client
|
||||||
|
return &OpenAIClient{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type AIConfiguration struct {
|
type AIConfiguration struct {
|
||||||
@ -52,8 +65,9 @@ type AIConfiguration struct {
|
|||||||
type AIProvider struct {
|
type AIProvider struct {
|
||||||
Name string `mapstructure:"name"`
|
Name string `mapstructure:"name"`
|
||||||
Model string `mapstructure:"model"`
|
Model string `mapstructure:"model"`
|
||||||
Password string `mapstructure:"password"`
|
Password string `mapstructure:"password" yaml:"password,omitempty"`
|
||||||
BaseURL string `mapstructure:"baseurl"`
|
BaseURL string `mapstructure:"baseurl" yaml:"baseurl,omitempty"`
|
||||||
|
Engine string `mapstructure:"engine" yaml:"engine,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *AIProvider) GetBaseURL() string {
|
func (p *AIProvider) GetBaseURL() string {
|
||||||
@ -68,6 +82,10 @@ func (p *AIProvider) GetModel() string {
|
|||||||
return p.Model
|
return p.Model
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *AIProvider) GetEngine() string {
|
||||||
|
return p.Engine
|
||||||
|
}
|
||||||
|
|
||||||
func NeedPassword(backend string) bool {
|
func NeedPassword(backend string) bool {
|
||||||
return backend != "localai"
|
return backend != "localai"
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user