From 9a2ba0943cb89760febdf54fc70b0f1584ffc809 Mon Sep 17 00:00:00 2001 From: Liujian <824010343@qq.com> Date: Sun, 29 Sep 2024 14:46:58 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ai-provider/model-runtime/loader.go | 9 ++-- ai-provider/model-runtime/model.go | 2 +- ai-provider/model-runtime/provider.go | 5 +- controller/service/iml.go | 65 +++++++++++++++++++++++-- module/ai-api/iml.go | 68 ++++++++++++++++++++++++++- module/ai-api/schema.go | 1 + module/ai/dto/output.go | 25 ++++++---- module/ai/iml.go | 60 ++++++++++++++--------- module/router/iml.go | 7 +++ service/api/model.go | 2 + 10 files changed, 200 insertions(+), 44 deletions(-) diff --git a/ai-provider/model-runtime/loader.go b/ai-provider/model-runtime/loader.go index ac0edd54..9892af35 100644 --- a/ai-provider/model-runtime/loader.go +++ b/ai-provider/model-runtime/loader.go @@ -2,15 +2,14 @@ package model_runtime import ( "embed" - "errors" "fmt" "github.com/eolinker/eosc" "strings" ) -var ( - ErrInvalidAPIKey = errors.New("invalid api key") -) +func init() { + Load() +} type IConfig interface { Check(cfg string) error @@ -87,7 +86,7 @@ func ReadFile(dir embed.FS, name string) (eosc.Untyped[string, string], error) { if file.IsDir() { continue } - if !strings.HasSuffix(file.Name(), ".yaml") { + if !strings.HasSuffix(file.Name(), ".yaml") && !strings.HasSuffix(file.Name(), ".svg") { continue } data, err := dir.ReadFile(fmt.Sprintf("%s/%s", name, file.Name())) diff --git a/ai-provider/model-runtime/model.go b/ai-provider/model-runtime/model.go index 5e5503cb..9247c596 100644 --- a/ai-provider/model-runtime/model.go +++ b/ai-provider/model-runtime/model.go @@ -107,7 +107,7 @@ func NewModel(data string, logo string) (IModel, error) { Required: p.Required, }) } - dCfg, err := json.Marshal(defaultConfig) + dCfg, err := json.MarshalIndent(defaultConfig, "", " ") if err != nil { return nil, err } diff --git a/ai-provider/model-runtime/provider.go b/ai-provider/model-runtime/provider.go index 89a7f273..aa463c5d 100644 --- a/ai-provider/model-runtime/provider.go +++ b/ai-provider/model-runtime/provider.go @@ -68,7 +68,7 @@ func NewProvider(providerData string, modelContents map[string]eosc.Untyped[stri } defaultCfg[v.Variable] = v.Label[entity.LanguageEnglish] } - defaultCfgByte, _ := json.Marshal(defaultCfg) + defaultCfgByte, _ := json.MarshalIndent(defaultCfg, "", " ") provider.defaultConfig = string(defaultCfgByte) provider.paramValidator = params for name, f := range modelContents { @@ -86,6 +86,7 @@ func NewProvider(providerData string, modelContents map[string]eosc.Untyped[stri if model.ID() == defaultModel { provider.SetDefaultModel(name, model) } + models = append(models, model) } provider.SetModelsByType(name, models) } @@ -158,7 +159,7 @@ func (p *Provider) MaskConfig(cfg string) string { } for _, key := range p.maskKeys { if v, ok := data[key]; ok { - data[key] = PartialMasking(v, 0, -1) + data[key] = PartialMasking(v, 4, -1) } } result, _ := json.Marshal(data) diff --git a/controller/service/iml.go b/controller/service/iml.go index 952f3235..65b4454f 100644 --- a/controller/service/iml.go +++ b/controller/service/iml.go @@ -1,9 +1,15 @@ package service import ( + "fmt" + "github.com/APIParkLab/APIPark/module/ai" + ai_api "github.com/APIParkLab/APIPark/module/ai-api" + ai_api_dto "github.com/APIParkLab/APIPark/module/ai-api/dto" "github.com/APIParkLab/APIPark/module/service" service_dto "github.com/APIParkLab/APIPark/module/service/dto" "github.com/gin-gonic/gin" + "github.com/google/uuid" + "strings" ) var ( @@ -13,14 +19,67 @@ var ( ) type imlServiceController struct { - module service.IServiceModule `autowired:""` - docModule service.IServiceDocModule `autowired:""` + module service.IServiceModule `autowired:""` + docModule service.IServiceDocModule `autowired:""` + aiAPIModule ai_api.IAPIModule `autowired:""` + providerModule ai.IProviderModule `autowired:""` } func (i *imlServiceController) CreateAIService(ctx *gin.Context, teamID string, input *service_dto.CreateService) (*service_dto.Service, error) { kind := "ai" input.Kind = &kind - return i.module.Create(ctx, teamID, input) + if input.Provider == nil { + return nil, fmt.Errorf("provider is required") + } + p, err := i.providerModule.Provider(ctx, *input.Provider) + if err != nil { + return nil, err + } + if input.Id == "" { + input.Id = uuid.New().String() + } + if input.Prefix == "" { + if len(input.Id) < 9 { + input.Prefix = input.Id + } else { + input.Prefix = input.Id[:8] + } + } + info, err := i.module.Create(ctx, teamID, input) + if err != nil { + return nil, err + } + _, err = i.aiAPIModule.Create( + ctx, + info.Id, + &ai_api_dto.CreateAPI{ + Name: "Default API", + Path: fmt.Sprintf("/%s", strings.Trim(input.Prefix, "/")), + Description: "Default API for service", + Disable: false, + AiPrompt: &ai_api_dto.AiPrompt{ + Variables: []*ai_api_dto.AiPromptVariable{ + { + Key: "Query", + Description: "", + Require: true, + }, + }, + Prompt: "{{Query}}", + }, + AiModel: &ai_api_dto.AiModel{ + Id: p.DefaultLLM, + Config: p.DefaultLLMConfig, + }, + Timeout: 300000, + Retry: 0, + }, + ) + if err != nil { + i.module.Delete(ctx, info.Id, "ai") + return nil, err + } + return info, nil } func (i *imlServiceController) DeleteAIService(ctx *gin.Context, id string) error { diff --git a/module/ai-api/iml.go b/module/ai-api/iml.go index cfeb389c..b442980e 100644 --- a/module/ai-api/iml.go +++ b/module/ai-api/iml.go @@ -8,6 +8,7 @@ import ( model_runtime "github.com/APIParkLab/APIPark/ai-provider/model-runtime" ai_api_dto "github.com/APIParkLab/APIPark/module/ai-api/dto" ai_api "github.com/APIParkLab/APIPark/service/ai-api" + "github.com/APIParkLab/APIPark/service/api" api_doc "github.com/APIParkLab/APIPark/service/api-doc" "github.com/APIParkLab/APIPark/service/service" "github.com/eolinker/go-common/auto" @@ -17,6 +18,7 @@ import ( "github.com/google/uuid" "gorm.io/gorm" "net/http" + "strings" ) var _ IAPIModule = (*imlAPIModule)(nil) @@ -29,6 +31,7 @@ type imlAPIModule struct { serviceService service.IServiceService `autowired:""` apiDocService api_doc.IAPIDocService `autowired:""` aiAPIService ai_api.IAPIService `autowired:""` + apiService api.IAPIService `autowired:""` transaction store.ITransaction `autowired:""` } @@ -95,7 +98,30 @@ func (i *imlAPIModule) Create(ctx context.Context, serviceId string, input *ai_a input.Id = uuid.New().String() } err = i.transaction.Transaction(ctx, func(txCtx context.Context) error { - err := i.updateAPIDoc(ctx, serviceId, input.Path, input.Description, input.AiPrompt) + err = i.apiService.Exist(ctx, "", &api.Exist{Path: input.Path, Methods: []string{http.MethodPost}}) + if err != nil { + return err + } + err = i.apiService.Create(ctx, &api.Create{ + UUID: input.Id, + Description: input.Description, + Service: serviceId, + Team: info.Team, + Methods: []string{ + http.MethodPost, + }, + Protocols: []string{ + "http", + "https", + }, + Disable: false, + Path: input.Path, + Match: "{}", + }) + if err != nil { + return err + } + err = i.updateAPIDoc(ctx, serviceId, input.Path, input.Description, input.AiPrompt) if err != nil { return err } @@ -137,6 +163,26 @@ func (i *imlAPIModule) Edit(ctx context.Context, serviceId string, apiId string, } if input.Path != nil { apiInfo.Path = *input.Path + prefix, err := i.Prefix(ctx, serviceId) + if err != nil { + return err + } + if !strings.HasSuffix(apiInfo.Path, prefix) { + if apiInfo.Path[0] != '/' { + apiInfo.Path = fmt.Sprintf("/%s", apiInfo.Path) + } + apiInfo.Path = fmt.Sprintf("%s%s", prefix, apiInfo.Path) + err := i.apiService.Exist(ctx, apiId, &api.Exist{Path: apiInfo.Path, Methods: []string{http.MethodPost}}) + if err != nil { + return err + } + err = i.apiService.Save(ctx, apiId, &api.Edit{ + Path: &apiInfo.Path, + }) + if err != nil { + return err + } + } } if input.Description != nil { apiInfo.Description = *input.Description @@ -181,7 +227,11 @@ func (i *imlAPIModule) Delete(ctx context.Context, serviceId string, apiId strin return fmt.Errorf("service kind is not ai service") } return i.transaction.Transaction(ctx, func(txCtx context.Context) error { - err = i.deleteAPIDoc(ctx, serviceId, apiId) + apiInfo, err := i.aiAPIService.Get(ctx, apiId) + if err != nil { + return err + } + err = i.deleteAPIDoc(ctx, serviceId, apiInfo.Path) if err != nil { return err } @@ -276,3 +326,17 @@ func ConvertStruct[T any](data interface{}) (*T, error) { } return &t, nil } + +func (i *imlAPIModule) Prefix(ctx context.Context, serviceId string) (string, error) { + pInfo, err := i.serviceService.Check(ctx, serviceId, map[string]bool{"as_server": true}) + if err != nil { + return "", err + } + + if pInfo.Prefix != "" { + if pInfo.Prefix[0] != '/' { + pInfo.Prefix = fmt.Sprintf("/%s", strings.TrimSuffix(pInfo.Prefix, "/")) + } + } + return strings.TrimSuffix(pInfo.Prefix, "/"), nil +} diff --git a/module/ai-api/schema.go b/module/ai-api/schema.go index 17df241c..945329d3 100644 --- a/module/ai-api/schema.go +++ b/module/ai-api/schema.go @@ -51,6 +51,7 @@ func genRequestBodySchema(variables []*ai_api_dto.AiPromptVariable) *openapi3.Sc required := make([]string, 0, len(variables)) for _, v := range variables { val := openapi3.NewStringSchema() + val.Example = "" val.Description = v.Description if v.Require { required = append(required, v.Key) diff --git a/module/ai/dto/output.go b/module/ai/dto/output.go index 2d148efc..9c37fcb5 100644 --- a/module/ai/dto/output.go +++ b/module/ai/dto/output.go @@ -1,19 +1,24 @@ package ai_dto +import "time" + type Provider struct { - Id string `json:"id"` - Name string `json:"name"` - Config string `json:"config"` - GetAPIKeyUrl string `json:"get_apikey_url"` + Id string `json:"id"` + Name string `json:"name"` + Config string `json:"config"` + GetAPIKeyUrl string `json:"get_apikey_url"` + DefaultLLM string `json:"-"` + DefaultLLMConfig string `json:"-"` } type ProviderItem struct { - Id string `json:"id"` - Name string `json:"name"` - DefaultLLM string `json:"default_llm"` - DefaultLLMLogo string `json:"default_llm_logo"` - Logo string `json:"logo"` - Configured bool `json:"configured"` + Id string `json:"id"` + Name string `json:"name"` + DefaultLLM string `json:"default_llm"` + DefaultLLMLogo string `json:"default_llm_logo"` + Logo string `json:"logo"` + Configured bool `json:"configured"` + UpdateTime time.Time `json:"-"` } type SimpleProviderItem struct { diff --git a/module/ai/iml.go b/module/ai/iml.go index 0dd683bb..3ddbf569 100644 --- a/module/ai/iml.go +++ b/module/ai/iml.go @@ -9,6 +9,7 @@ import ( "github.com/APIParkLab/APIPark/service/ai" "github.com/eolinker/go-common/utils" "gorm.io/gorm" + "sort" ) var _ IProviderModule = (*imlProviderModule)(nil) @@ -58,17 +59,22 @@ func (i *imlProviderModule) Providers(ctx context.Context) ([]*ai_dto.ProviderIt continue } item := &ai_dto.ProviderItem{ - Id: v.ID(), - Name: v.Name(), - Logo: v.Logo(), - DefaultLLM: defaultLLM.ID(), - DefaultLLMLogo: defaultLLM.Logo(), + Id: v.ID(), + Name: v.Name(), + Logo: v.Logo(), } - if _, has = providerMap[v.ID()]; has { + if info, has := providerMap[v.ID()]; has { item.Configured = true + item.DefaultLLM = defaultLLM.ID() + item.DefaultLLMLogo = defaultLLM.Logo() + item.UpdateTime = info.UpdateAt } items = append(items, item) } + sort.SliceIsSorted(items, func(i, j int) bool { + + return items[i].UpdateTime.After(items[j].UpdateTime) + }) return items, nil } @@ -82,19 +88,30 @@ func (i *imlProviderModule) Provider(ctx context.Context, id string) (*ai_dto.Pr if !errors.Is(err, gorm.ErrRecordNotFound) { return nil, err } + defaultLLM, has := p.DefaultModel(model_runtime.ModelTypeLLM) + if !has { + return nil, fmt.Errorf("ai provider llm not found") + } return &ai_dto.Provider{ - Id: p.ID(), - Name: p.Name(), - Config: p.DefaultConfig(), - GetAPIKeyUrl: p.HelpUrl(), + Id: p.ID(), + Name: p.Name(), + Config: p.DefaultConfig(), + GetAPIKeyUrl: p.HelpUrl(), + DefaultLLM: defaultLLM.ID(), + DefaultLLMConfig: defaultLLM.Logo(), }, nil } - + defaultLLM, has := p.GetModel(info.DefaultLLM) + if !has { + return nil, fmt.Errorf("ai provider llm not found") + } return &ai_dto.Provider{ - Id: info.Id, - Name: info.Name, - Config: p.MaskConfig(info.Config), - GetAPIKeyUrl: p.HelpUrl(), + Id: info.Id, + Name: info.Name, + Config: p.MaskConfig(info.Config), + GetAPIKeyUrl: p.HelpUrl(), + DefaultLLM: defaultLLM.ID(), + DefaultLLMConfig: defaultLLM.DefaultConfig(), }, nil } @@ -114,7 +131,7 @@ func (i *imlProviderModule) LLMs(ctx context.Context, driver string) ([]*ai_dto. items = append(items, &ai_dto.LLMItem{ Id: v.ID(), Logo: v.Logo(), - Config: p.DefaultConfig(), + Config: v.DefaultConfig(), Scopes: []string{ "chat", }, @@ -130,11 +147,12 @@ func (i *imlProviderModule) LLMs(ctx context.Context, driver string) ([]*ai_dto. return nil, nil, fmt.Errorf("ai provider default llm not found") } return items, &ai_dto.ProviderItem{ - Id: p.ID(), - Name: p.Name(), - DefaultLLM: defaultLLM.ID(), - Logo: p.Logo(), - Configured: false, + Id: p.ID(), + Name: p.Name(), + DefaultLLM: defaultLLM.ID(), + DefaultLLMLogo: defaultLLM.Logo(), + Logo: p.Logo(), + Configured: false, }, err } diff --git a/module/router/iml.go b/module/router/iml.go index 5479ac6a..46ff5752 100644 --- a/module/router/iml.go +++ b/module/router/iml.go @@ -261,6 +261,7 @@ func (i *imlRouterModule) Create(ctx context.Context, serviceId string, dto *rou Service: serviceId, Team: info.Team, Methods: dto.Methods, + Protocols: dto.Protocols, Path: path, Match: string(match), }) @@ -278,6 +279,12 @@ func (i *imlRouterModule) Edit(ctx context.Context, serviceId string, apiId stri } err = i.transaction.Transaction(ctx, func(ctx context.Context) error { + if dto.Path != nil { + err = i.apiService.Exist(ctx, "", &api.Exist{Path: *dto.Path, Methods: *dto.Methods}) + if err != nil { + return err + } + } if dto.Proxy != nil { err = i.apiService.SaveProxy(ctx, apiId, router_dto.ToServiceProxy(dto.Proxy)) if err != nil { diff --git a/service/api/model.go b/service/api/model.go index 60a0d572..5fa6265b 100644 --- a/service/api/model.go +++ b/service/api/model.go @@ -71,6 +71,8 @@ func FromEntityInfo(e *api.Info) *Info { } } +type Kind string + type Create struct { UUID string Description string