Skip to content

Commit

Permalink
feat: support ai backends (#551)
Browse files Browse the repository at this point in the history
/kind feature

support an AI backend service that other modules can use to access AI
language models.
  • Loading branch information
jueli12 authored and elliotxx committed Aug 14, 2024
1 parent 59bbd29 commit 1c7ae79
Show file tree
Hide file tree
Showing 13 changed files with 445 additions and 5 deletions.
69 changes: 69 additions & 0 deletions cmd/karpor/app/options/ai.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright The Karpor 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 options

import (
"github.com/KusionStack/karpor/pkg/kubernetes/registry"
"github.com/spf13/pflag"
)

type AIOptions struct {
Backend string
AuthToken string
BaseURL string
Model string
Temperature float32
TopP float32
}

const (
defaultBackend = "openai"
defaultModel = "gpt-3.5-turbo"
defaultTemperature = 1
defaultTopP = 1
)

func NewAIOptions() *AIOptions {
return &AIOptions{}
}

func (o *AIOptions) Validate() []error {
return nil
}

func (o *AIOptions) ApplyTo(config *registry.ExtraConfig) error {
// Apply the AIOptions to the provided config
config.Backend = o.Backend
config.AuthToken = o.AuthToken
config.BaseURL = o.BaseURL
config.Model = o.Model
config.Temperature = o.Temperature
config.TopP = o.TopP
return nil
}

// AddFlags adds flags for a specific Option to the specified FlagSet
func (o *AIOptions) AddFlags(fs *pflag.FlagSet) {
if o == nil {
return
}

fs.StringVar(&o.Backend, "ai-backend", defaultBackend, "The ai backend")
fs.StringVar(&o.AuthToken, "ai-auth-token", "", "The ai auth token")
fs.StringVar(&o.BaseURL, "ai-base-url", "", "The ai base url")
fs.StringVar(&o.Model, "ai-model", defaultModel, "The ai model")
fs.Float32Var(&o.Temperature, "ai-temperature", defaultTemperature, "The ai temperature")
fs.Float32Var(&o.TopP, "ai-top-p", defaultTopP, "The ai top-p")
}
10 changes: 10 additions & 0 deletions cmd/karpor/app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ type Options struct {
RecommendedOptions *options.RecommendedOptions
SearchStorageOptions *options.SearchStorageOptions
CoreOptions *options.CoreOptions
AIOptions *options.AIOptions

StdOut io.Writer
StdErr io.Writer
Expand All @@ -66,6 +67,7 @@ func NewOptions(out, errOut io.Writer) (*Options, error) {
),
SearchStorageOptions: options.NewSearchStorageOptions(),
CoreOptions: options.NewCoreOptions(),
AIOptions: options.NewAIOptions(),
StdOut: out,
StdErr: errOut,
}
Expand Down Expand Up @@ -97,6 +99,9 @@ func NewServerCommand(ctx context.Context) *cobra.Command {
expvar.Publish("StorageOptions", expvar.Func(func() interface{} {
return o.SearchStorageOptions
}))
expvar.Publish("AIOptions", expvar.Func(func() interface{} {
return o.AIOptions
}))
expvar.Publish("Version", expvar.Func(func() interface{} {
return version.GetVersion()
}))
Expand Down Expand Up @@ -132,13 +137,15 @@ func (o *Options) AddFlags(fs *pflag.FlagSet) {
o.RecommendedOptions.AddFlags(fs)
o.SearchStorageOptions.AddFlags(fs)
o.CoreOptions.AddFlags(fs)
o.AIOptions.AddFlags(fs)
}

// Validate validates Options
func (o *Options) Validate(args []string) error {
errors := []error{}
errors = append(errors, o.RecommendedOptions.Validate()...)
errors = append(errors, o.SearchStorageOptions.Validate()...)
errors = append(errors, o.AIOptions.Validate()...)
return utilerrors.NewAggregate(errors)
}

Expand All @@ -162,6 +169,9 @@ func (o *Options) Config() (*server.Config, error) {
if err := o.CoreOptions.ApplyTo(config.ExtraConfig); err != nil {
return nil, err
}
if err := o.AIOptions.ApplyTo(config.ExtraConfig); err != nil {
return nil, err
}

config.GenericConfig.BuildHandlerChainFunc = func(handler http.Handler, c *genericapiserver.Config) http.Handler {
handler = genericapiserver.DefaultBuildHandlerChain(handler, c)
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ require (
github.com/go-chi/render v1.0.3
github.com/go-logr/logr v1.2.3
github.com/google/gofuzz v1.2.0
github.com/google/uuid v1.3.0
github.com/google/uuid v1.4.0
github.com/hupe1980/go-huggingface v0.0.15
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852
github.com/pkg/errors v0.9.1
github.com/sashabaranov/go-openai v1.27.0
github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.4
Expand Down
8 changes: 6 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,8 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
Expand All @@ -268,6 +268,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/hupe1980/go-huggingface v0.0.15 h1:tTWmUGGunC/BYz4hrwS8SSVtMYVYjceG2uhL8HxeXvw=
github.com/hupe1980/go-huggingface v0.0.15/go.mod h1:IRvsik3+b9BJyw9hCfw1arI6gDObcVto1UA8f3kt8mM=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
Expand Down Expand Up @@ -384,6 +386,8 @@ github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBO
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sashabaranov/go-openai v1.27.0 h1:L3hO6650YUbKrbGUC6yCjsUluhKZ9h1/jcgbTItI8Mo=
github.com/sashabaranov/go-openai v1.27.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
Expand Down
5 changes: 4 additions & 1 deletion pkg/core/handler/search/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package search

import (
"github.com/KusionStack/karpor/pkg/core/manager/ai"
"net/http"
"strconv"

Expand Down Expand Up @@ -45,12 +46,14 @@ import (
// @Failure 429 {string} string "Too Many Requests"
// @Failure 500 {string} string "Internal Server Error"
// @Router /rest-api/v1/search [get]
func SearchForResource(searchMgr *search.SearchManager, searchStorage storage.SearchStorage) http.HandlerFunc {
func SearchForResource(searchMgr *search.SearchManager, aiMgr *ai.AIManager, searchStorage storage.SearchStorage) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Extract the context and logger from the request.
ctx := r.Context()
logger := ctxutil.GetLogger(ctx)

//res, nil := aiMgr.ConvertTextToSQL("搜索集群cluster中kind为namespace的")

// Extract URL query parameters with default value
searchQuery := r.URL.Query().Get("query")
searchPattern := r.URL.Query().Get("pattern")
Expand Down
36 changes: 36 additions & 0 deletions pkg/core/manager/ai/manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright The Karpor 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 ai

import (
"github.com/KusionStack/karpor/pkg/infra/ai"
"github.com/KusionStack/karpor/pkg/kubernetes/registry"
)

type AIManager struct {
client ai.AIProvider
}

// NewAIManager returns a new AIManager object
func NewAIManager(c registry.ExtraConfig) (*AIManager, error) {
aiClient := ai.NewClient(c.Backend)
if err := aiClient.Configure(ai.ConvertToAIConfig(c)); err != nil {
return nil, err
}

return &AIManager{
client: aiClient,
}, nil
}
10 changes: 9 additions & 1 deletion pkg/core/route/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package route

import (
"expvar"

docs "github.com/KusionStack/karpor/api/openapispec"
clusterhandler "github.com/KusionStack/karpor/pkg/core/handler/cluster"
detailhandler "github.com/KusionStack/karpor/pkg/core/handler/detail"
Expand All @@ -29,6 +30,7 @@ import (
summaryhandler "github.com/KusionStack/karpor/pkg/core/handler/summary"
topologyhandler "github.com/KusionStack/karpor/pkg/core/handler/topology"
healthhandler "github.com/KusionStack/karpor/pkg/core/health"
aimanager "github.com/KusionStack/karpor/pkg/core/manager/ai"
clustermanager "github.com/KusionStack/karpor/pkg/core/manager/cluster"
insightmanager "github.com/KusionStack/karpor/pkg/core/manager/insight"
resourcegroupmanager "github.com/KusionStack/karpor/pkg/core/manager/resourcegroup"
Expand Down Expand Up @@ -87,13 +89,18 @@ func NewCoreRoute(
if err != nil {
return nil, err
}
aiMgr, err := aimanager.NewAIManager(*extraConfig)
if err != nil {
return nil, err
}

clusterMgr := clustermanager.NewClusterManager()
searchMgr := searchmanager.NewSearchManager()

// Set up the API routes for version 1 of the API.
router.Route("/rest-api/v1", func(r chi.Router) {
setupRestAPIV1(r,
aiMgr,
clusterMgr,
insightMgr,
resourceGroupMgr,
Expand All @@ -120,6 +127,7 @@ func NewCoreRoute(
// resource type and setting up proper handlers.
func setupRestAPIV1(
r chi.Router,
aiMgr *aimanager.AIManager,
clusterMgr *clustermanager.ClusterManager,
insightMgr *insightmanager.InsightManager,
resourceGroupMgr *resourcegroupmanager.ResourceGroupManager,
Expand All @@ -144,7 +152,7 @@ func setupRestAPIV1(
})

r.Route("/search", func(r chi.Router) {
r.Get("/", searchhandler.SearchForResource(searchMgr, searchStorage))
r.Get("/", searchhandler.SearchForResource(searchMgr, aiMgr, searchStorage))
})

r.Route("/insight", func(r chi.Router) {
Expand Down
75 changes: 75 additions & 0 deletions pkg/infra/ai/azureopenai.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright The Karpor 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 ai

import (
"context"
"errors"
"github.com/sashabaranov/go-openai"
)

type AzureAIClient struct {
client *openai.Client
model string
temperature float32
}

func (c *AzureAIClient) Configure(cfg AIConfig) error {
if cfg.AuthToken == "" {
return errors.New("auth token was not provided")
}
if cfg.BaseURL == "" {
return errors.New("base url was not provided")
}

defaultConfig := openai.DefaultAzureConfig(cfg.AuthToken, cfg.BaseURL)

client := openai.NewClientWithConfig(defaultConfig)
if client == nil {
return errors.New("error creating Azure OpenAI client")
}

c.client = client
c.model = cfg.Model
c.temperature = cfg.Temperature
return nil
}

func (c *AzureAIClient) Generate(ctx context.Context, prompt string, serviceType string) (string, error) {
servicePrompt := ServicePromptMap[serviceType]

resp, err := c.client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
Model: c.model,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleSystem,
Content: servicePrompt,
},
{
Role: openai.ChatMessageRoleUser,
Content: prompt,
},
},
Temperature: c.temperature,
})
if err != nil {
return "", err
}

if len(resp.Choices) == 0 {
return "", errors.New("no completion choices returned from response")
}
return resp.Choices[0].Message.Content, nil
}
Loading

0 comments on commit 1c7ae79

Please sign in to comment.