diff --git a/controller/ai-api/controller.go b/controller/ai-api/controller.go new file mode 100644 index 00000000..cc906605 --- /dev/null +++ b/controller/ai-api/controller.go @@ -0,0 +1,23 @@ +package ai_api + +import ( + ai_api_dto "github.com/APIParkLab/APIPark/module/ai-api/dto" + "github.com/eolinker/go-common/autowire" + "github.com/gin-gonic/gin" + "reflect" +) + +type IAPIController interface { + Create(ctx *gin.Context, serviceId string, input *ai_api_dto.CreateAPI) (*ai_api_dto.API, error) + Edit(ctx *gin.Context, serviceId string, apiId string, input *ai_api_dto.EditAPI) (*ai_api_dto.API, error) + Delete(ctx *gin.Context, serviceId string, apiId string) error + List(ctx *gin.Context, keyword string, serviceId string) ([]*ai_api_dto.APIItem, error) + Get(ctx *gin.Context, serviceId string, apiId string) (*ai_api_dto.API, error) +} + +func init() { + autowire.Auto[IAPIController](func() reflect.Value { + m := new(imlAPIController) + return reflect.ValueOf(m) + }) +} diff --git a/controller/ai-api/iml.go b/controller/ai-api/iml.go new file mode 100644 index 00000000..9a8b7ff3 --- /dev/null +++ b/controller/ai-api/iml.go @@ -0,0 +1,33 @@ +package ai_api + +import ( + ai_api "github.com/APIParkLab/APIPark/module/ai-api" + ai_api_dto "github.com/APIParkLab/APIPark/module/ai-api/dto" + "github.com/gin-gonic/gin" +) + +var _ IAPIController = (*imlAPIController)(nil) + +type imlAPIController struct { + module ai_api.IAPIModule `autowired:""` +} + +func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_api_dto.CreateAPI) (*ai_api_dto.API, error) { + return i.module.Create(ctx, serviceId, input) +} + +func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string, input *ai_api_dto.EditAPI) (*ai_api_dto.API, error) { + return i.module.Edit(ctx, serviceId, apiId, input) +} + +func (i *imlAPIController) Delete(ctx *gin.Context, serviceId string, apiId string) error { + return i.module.Delete(ctx, serviceId, apiId) +} + +func (i *imlAPIController) List(ctx *gin.Context, keyword string, serviceId string) ([]*ai_api_dto.APIItem, error) { + return i.module.List(ctx, keyword, serviceId) +} + +func (i *imlAPIController) Get(ctx *gin.Context, serviceId string, apiId string) (*ai_api_dto.API, error) { + return i.module.Get(ctx, serviceId, apiId) +} diff --git a/module/ai-api/dto/input.go b/module/ai-api/dto/input.go new file mode 100644 index 00000000..f445f9cb --- /dev/null +++ b/module/ai-api/dto/input.go @@ -0,0 +1,40 @@ +package ai_api_dto + +type CreateAPI struct { + Id string `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + Description string `json:"description"` + Disable bool `json:"disable"` + AiPrompt *AiPrompt `json:"ai_prompt"` + AiModel *AiModel `json:"ai_model"` + Timeout int `json:"timeout"` + Retry int `json:"retry"` +} + +type AiPrompt struct { + Variables []*AiPromptVariable `json:"variables"` + Prompt string `json:"prompt"` +} + +type AiPromptVariable struct { + Key string `json:"key"` + Description string `json:"description"` + Require bool `json:"require"` +} + +type AiModel struct { + Id string `json:"id"` + Config string `json:"config"` +} + +type EditAPI struct { + Name *string `json:"name"` + Path *string `json:"path"` + Description *string `json:"description"` + Disable *bool `json:"disable"` + AiPrompt *AiPrompt `json:"ai_prompt"` + AiModel *AiModel `json:"ai_model"` + Timeout *int `json:"timeout"` + Retry *int `json:"retry"` +} diff --git a/module/ai-api/dto/output.go b/module/ai-api/dto/output.go new file mode 100644 index 00000000..1083f98f --- /dev/null +++ b/module/ai-api/dto/output.go @@ -0,0 +1,35 @@ +package ai_api_dto + +import ( + "github.com/eolinker/go-common/auto" +) + +type API struct { + Id string `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + Description string `json:"description"` + Disable bool `json:"disable"` + AiPrompt *AiPrompt `json:"ai_prompt"` + AiModel *AiModel `json:"ai_model"` + Timeout int `json:"timeout"` + Retry int `json:"retry"` +} + +type APIItem struct { + Id string `json:"id"` + Name string `json:"name"` + RequestPath string `json:"request_path"` + Description string `json:"description"` + Disable bool `json:"disable"` + Creator auto.Label `json:"creator" aolabel:"user"` + Updater auto.Label `json:"updater" aolabel:"user"` + CreateTime auto.TimeLabel `json:"create_time"` + UpdateTime auto.TimeLabel `json:"update_time"` + Model ModelItem `json:"model"` +} + +type ModelItem struct { + Id string `json:"id"` + Logo string `json:"logo"` +} diff --git a/module/ai-api/iml.go b/module/ai-api/iml.go new file mode 100644 index 00000000..d2927d58 --- /dev/null +++ b/module/ai-api/iml.go @@ -0,0 +1,278 @@ +package ai_api + +import ( + "context" + "encoding/json" + "errors" + "fmt" + ai_api_dto "github.com/APIParkLab/APIPark/module/ai-api/dto" + "github.com/APIParkLab/APIPark/module/ai/provider" + ai_api "github.com/APIParkLab/APIPark/service/ai-api" + api_doc "github.com/APIParkLab/APIPark/service/api-doc" + "github.com/APIParkLab/APIPark/service/service" + "github.com/eolinker/go-common/auto" + "github.com/eolinker/go-common/store" + "github.com/eolinker/go-common/utils" + "github.com/getkin/kin-openapi/openapi3" + "github.com/google/uuid" + "gorm.io/gorm" + "net/http" +) + +var _ IAPIModule = (*imlAPIModule)(nil) + +var ( + openapi3Loader = openapi3.NewLoader() +) + +type imlAPIModule struct { + serviceService service.IServiceService `autowired:""` + apiDocService api_doc.IAPIDocService `autowired:""` + aiAPIService ai_api.IAPIService `autowired:""` + transaction store.ITransaction `autowired:""` +} + +func (i *imlAPIModule) getAPIDoc(ctx context.Context, serviceId string) (*openapi3.T, error) { + doc, err := i.apiDocService.GetDoc(ctx, serviceId) + if err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, err + } + info, err := i.serviceService.Get(ctx, serviceId) + if err != nil { + return nil, fmt.Errorf("get service info error:%v", err) + } + return genOpenAPI3Template(info.Name, info.Description), nil + } + return openapi3Loader.LoadFromData([]byte(doc.Content)) +} + +func (i *imlAPIModule) updateAPIDoc(ctx context.Context, serviceId string, path string, description string, aiPrompt *ai_api_dto.AiPrompt) error { + doc, err := i.getAPIDoc(ctx, serviceId) + if err != nil { + return err + } + var variables []*ai_api_dto.AiPromptVariable + if aiPrompt != nil { + variables = aiPrompt.Variables + } + doc.AddOperation(path, http.MethodPost, genOperation(description, variables)) + result, err := doc.MarshalJSON() + if err != nil { + return err + } + return i.apiDocService.UpdateDoc(ctx, serviceId, &api_doc.UpdateDoc{ + ID: uuid.New().String(), + Content: string(result), + }) +} + +func (i *imlAPIModule) deleteAPIDoc(ctx context.Context, serviceId string, path string) error { + doc, err := i.getAPIDoc(ctx, serviceId) + if err != nil { + return err + } + doc.Paths.Delete(path) + result, err := doc.MarshalJSON() + if err != nil { + return err + } + return i.apiDocService.UpdateDoc(ctx, serviceId, &api_doc.UpdateDoc{ + ID: uuid.New().String(), + Content: string(result), + }) +} + +func (i *imlAPIModule) Create(ctx context.Context, serviceId string, input *ai_api_dto.CreateAPI) (*ai_api_dto.API, error) { + info, err := i.serviceService.Get(ctx, serviceId) + if err != nil { + return nil, err + } + if info.Kind != service.AIService { + return nil, fmt.Errorf("service kind is not ai service") + } + if input.Id == "" { + 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) + if err != nil { + return err + } + return i.aiAPIService.Create(ctx, &ai_api.Create{ + ID: input.Id, + Name: input.Name, + Service: serviceId, + Path: input.Path, + Description: input.Description, + Timeout: input.Timeout, + Retry: input.Retry, + Model: input.AiModel.Id, + AdditionalConfig: map[string]interface{}{ + "ai_prompt": input.AiPrompt, + "ai_model": input.AiModel, + }, + }) + }) + if err != nil { + return nil, err + } + + return i.Get(ctx, serviceId, input.Id) +} + +func (i *imlAPIModule) Edit(ctx context.Context, serviceId string, apiId string, input *ai_api_dto.EditAPI) (*ai_api_dto.API, error) { + info, err := i.serviceService.Get(ctx, serviceId) + if err != nil { + return nil, err + } + if info.Kind != service.AIService { + return nil, fmt.Errorf("service kind is not ai service") + } + + err = i.transaction.Transaction(ctx, func(txCtx context.Context) error { + apiInfo, err := i.aiAPIService.Get(ctx, apiId) + if err != nil { + return err + } + if input.Path != nil { + apiInfo.Path = *input.Path + } + if input.Description != nil { + apiInfo.Description = *input.Description + } + err = i.updateAPIDoc(ctx, serviceId, apiInfo.Path, apiInfo.Description, input.AiPrompt) + if err != nil { + return err + } + var modelId *string + if input.AiModel != nil { + modelId = &input.AiModel.Id + } + if input.AiPrompt != nil { + apiInfo.AdditionalConfig["ai_prompt"] = input.AiPrompt + } + if input.AiModel != nil { + apiInfo.AdditionalConfig["ai_model"] = input.AiModel + } + return i.aiAPIService.Save(ctx, apiId, &ai_api.Edit{ + Name: input.Name, + Path: input.Path, + Description: input.Description, + Timeout: input.Timeout, + Retry: input.Retry, + Model: modelId, + AdditionalConfig: &apiInfo.AdditionalConfig, + }) + }) + if err != nil { + return nil, err + } + + return i.Get(ctx, serviceId, apiId) +} + +func (i *imlAPIModule) Delete(ctx context.Context, serviceId string, apiId string) error { + info, err := i.serviceService.Get(ctx, serviceId) + if err != nil { + return err + } + if info.Kind != service.AIService { + 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) + if err != nil { + return err + } + return i.aiAPIService.Delete(ctx, apiId) + }) +} + +func (i *imlAPIModule) List(ctx context.Context, keyword string, serviceId string) ([]*ai_api_dto.APIItem, error) { + info, err := i.serviceService.Get(ctx, serviceId) + if err != nil { + return nil, err + } + if info.Kind != service.AIService { + return nil, fmt.Errorf("service kind is not ai service") + } + apis, err := i.aiAPIService.Search(ctx, keyword, map[string]interface{}{ + "service": serviceId, + }, "update_at desc") + if err != nil { + return nil, err + } + p, has := provider.GetProvider(info.AdditionalConfig["provider"]) + if !has { + return nil, fmt.Errorf("provider not found") + } + return utils.SliceToSlice(apis, func(t *ai_api.API) *ai_api_dto.APIItem { + modelItem := ai_api_dto.ModelItem{ + Id: t.Model, + } + model, has := p.LLM(t.Model) + if has { + modelItem.Logo = model.Logo + } + return &ai_api_dto.APIItem{ + Id: t.ID, + Name: t.Name, + RequestPath: t.Path, + Description: t.Description, + Disable: t.Disable, + Creator: auto.UUID(t.Creator), + Updater: auto.UUID(t.Updater), + CreateTime: auto.TimeLabel(t.CreateAt), + UpdateTime: auto.TimeLabel(t.UpdateAt), + Model: modelItem, + } + }), nil +} + +func (i *imlAPIModule) Get(ctx context.Context, serviceId string, apiId string) (*ai_api_dto.API, error) { + info, err := i.serviceService.Get(ctx, serviceId) + if err != nil { + return nil, err + } + if info.Kind != service.AIService { + return nil, fmt.Errorf("service kind is not ai service") + } + apiInfo, err := i.aiAPIService.Get(ctx, apiId) + if err != nil { + return nil, err + } + prompt, err := ConvertStruct[ai_api_dto.AiPrompt](apiInfo.AdditionalConfig["ai_prompt"]) + if err != nil { + return nil, err + } + aiModel, err := ConvertStruct[ai_api_dto.AiModel](apiInfo.AdditionalConfig["ai_model"]) + if err != nil { + return nil, err + } + + return &ai_api_dto.API{ + Id: apiInfo.ID, + Name: apiInfo.Name, + Path: apiInfo.Path, + Description: apiInfo.Description, + Disable: apiInfo.Disable, + AiPrompt: prompt, + AiModel: aiModel, + Timeout: apiInfo.Timeout, + Retry: apiInfo.Retry, + }, nil +} + +func ConvertStruct[T any](data interface{}) (*T, error) { + b, err := json.Marshal(data) + if err != nil { + return nil, err + } + var t T + err = json.Unmarshal(b, &t) + if err != nil { + return nil, err + } + return &t, nil +} diff --git a/module/ai-api/module.go b/module/ai-api/module.go new file mode 100644 index 00000000..fb9b250c --- /dev/null +++ b/module/ai-api/module.go @@ -0,0 +1,23 @@ +package ai_api + +import ( + "context" + ai_api_dto "github.com/APIParkLab/APIPark/module/ai-api/dto" + "github.com/eolinker/go-common/autowire" + "reflect" +) + +type IAPIModule interface { + Create(ctx context.Context, serviceId string, input *ai_api_dto.CreateAPI) (*ai_api_dto.API, error) + Edit(ctx context.Context, serviceId string, apiId string, input *ai_api_dto.EditAPI) (*ai_api_dto.API, error) + Delete(ctx context.Context, serviceId string, apiId string) error + List(ctx context.Context, keyword, serviceId string) ([]*ai_api_dto.APIItem, error) + Get(ctx context.Context, serviceId string, apiId string) (*ai_api_dto.API, error) +} + +func init() { + autowire.Auto[IAPIModule](func() reflect.Value { + m := new(imlAPIModule) + return reflect.ValueOf(m) + }) +} diff --git a/module/ai-api/schema.go b/module/ai-api/schema.go new file mode 100644 index 00000000..17df241c --- /dev/null +++ b/module/ai-api/schema.go @@ -0,0 +1,105 @@ +package ai_api + +import ( + ai_api_dto "github.com/APIParkLab/APIPark/module/ai-api/dto" + "github.com/getkin/kin-openapi/openapi3" +) + +func genOpenAPI3Template(title string, description string) *openapi3.T { + result := new(openapi3.T) + result.OpenAPI = "3.1.0" + result.Info = &openapi3.Info{ + Title: title, + Description: description, + Version: "beta", + } + result.Components = genComponents() + result.Paths = new(openapi3.Paths) + return result +} + +func genOperation(description string, variables []*ai_api_dto.AiPromptVariable) *openapi3.Operation { + operation := openapi3.NewOperation() + operation.RequestBody = genRequestBody(variables) + operation.Responses = &openapi3.Responses{} + operation.Responses.Set("200", genResponse()) + operation.Description = description + return operation +} + +func genRequestBody(variables []*ai_api_dto.AiPromptVariable) *openapi3.RequestBodyRef { + requestBody := openapi3.NewRequestBody() + requestBody.Content = openapi3.NewContentWithSchema(genRequestBodySchema(variables), []string{"application/json"}) + return &openapi3.RequestBodyRef{ + Value: requestBody, + } +} + +func genResponse() *openapi3.ResponseRef { + response := openapi3.NewResponse() + response.Content = openapi3.NewContentWithSchema(genResponseSchema(), []string{"application/json"}) + description := "Response from the server" + response.Description = &description + return &openapi3.ResponseRef{ + Value: response, + } +} + +func genRequestBodySchema(variables []*ai_api_dto.AiPromptVariable) *openapi3.Schema { + result := openapi3.NewObjectSchema() + variableSchema := openapi3.NewObjectSchema() + required := make([]string, 0, len(variables)) + for _, v := range variables { + val := openapi3.NewStringSchema() + val.Description = v.Description + if v.Require { + required = append(required, v.Key) + } + variableSchema.WithProperty(v.Key, val) + } + result.WithProperty("variables", variableSchema.WithRequired(required)) + result.WithProperty("messages", genMessageSchema()) + result.WithRequired([]string{"variables", "messages"}) + return result +} + +func genComponents() *openapi3.Components { + components := openapi3.NewComponents() + components.Schemas = make(openapi3.Schemas) + components.Schemas["Message"] = genMessageSchema().NewRef() + components.Schemas["Response"] = genResponseSchema().NewRef() + return &components +} + +func genMessageSchema() *openapi3.Schema { + messageSchema := openapi3.NewObjectSchema() + messageSchema.Title = "Message" + messageSchema.Description = "Chat Message" + roleSchema := openapi3.NewStringSchema() + roleSchema.Description = "Role of the message sender" + roleSchema.Example = "assistant" + contentSchema := openapi3.NewStringSchema() + contentSchema.Description = "The message content" + contentSchema.Example = "Hello, how can I help you?" + messageSchema.WithProperties(map[string]*openapi3.Schema{ + "role": roleSchema, + "content": contentSchema, + }) + return messageSchema +} + +func genResponseSchema() *openapi3.Schema { + responseSchema := openapi3.NewObjectSchema() + responseSchema.Description = "Response from the server" + responseSchema.WithPropertyRef("message", openapi3.NewSchemaRef("#/components/schemas/Message", genMessageSchema())) + responseSchema.WithProperty("code", openapi3.NewInt32Schema().WithMin(0)) + responseSchema.WithProperty("error", openapi3.NewStringSchema()) + responseSchema.WithProperty("finish_reason", openapi3.NewStringSchema().WithEnum([]string{ + "stop", + "length", + "function_call", + "content_filter", + "null", + })) + return responseSchema +} diff --git a/module/ai/dto/output.go b/module/ai/dto/output.go index b4930bb5..2d148efc 100644 --- a/module/ai/dto/output.go +++ b/module/ai/dto/output.go @@ -8,11 +8,12 @@ type Provider struct { } type ProviderItem struct { - Id string `json:"id"` - Name string `json:"name"` - DefaultLLM string `json:"default_llm"` - 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"` } type SimpleProviderItem struct { diff --git a/module/ai/iml.go b/module/ai/iml.go index 5d639c32..15665ee6 100644 --- a/module/ai/iml.go +++ b/module/ai/iml.go @@ -69,11 +69,16 @@ func (i *imlProviderModule) Providers(ctx context.Context) ([]*ai_dto.ProviderIt DefaultLLM: v.Info().DefaultLLM, } if info, has := providerMap[v.Info().Id]; has { + llm, has := v.LLM(info.DefaultLLM) + if !has { + continue + } err = v.GlobalConfig().CheckConfig(info.Config) if err == nil { item.Configured = true } item.DefaultLLM = info.DefaultLLM + item.DefaultLLMLogo = llm.Logo } items = append(items, item) } diff --git a/module/ai/provider/openAI/model.go b/module/ai/provider/openAI/model.go index 4d51ea2c..216982fb 100644 --- a/module/ai/provider/openAI/model.go +++ b/module/ai/provider/openAI/model.go @@ -4,6 +4,7 @@ import ( _ "embed" "encoding/json" "github.com/APIParkLab/APIPark/module/ai/provider" + "github.com/eolinker/eosc" "sync" ) @@ -27,19 +28,23 @@ type ModelData struct { type Model struct { globalConfig provider.IAIConfig invokeConfig provider.IAIConfig - models []*ModelData + models eosc.Untyped[string, *ModelData] locker sync.RWMutex } func NewModel() provider.IAIProvider { modelData := make([]*ModelData, 0) json.Unmarshal(modelJson, &modelData) - return &Model{ + m := &Model{ globalConfig: NewGlobalConfigDriver(), invokeConfig: NewInvokeConfigDriver(), - models: modelData, + models: eosc.BuildUntyped[string, *ModelData](), locker: sync.RWMutex{}, } + for _, v := range modelData { + m.models.Set(v.Id, v) + } + return m } func (m *Model) Index() int { @@ -83,11 +88,9 @@ func (m *Model) UpdateLLMs() error { } func (m *Model) LLMs() []*provider.LLM { - m.locker.RLock() models := m.models - m.locker.RUnlock() - result := make([]*provider.LLM, 0, len(models)) - for _, model := range models { + result := make([]*provider.LLM, 0, models.Count()) + for _, model := range models.List() { llm := &provider.LLM{ Id: model.Id, Logo: model.Logo, @@ -97,3 +100,15 @@ func (m *Model) LLMs() []*provider.LLM { } return result } + +func (m *Model) LLM(id string) (*provider.LLM, bool) { + model, has := m.models.Get(id) + if !has { + return nil, false + } + return &provider.LLM{ + Id: model.Id, + Logo: model.Logo, + Scopes: model.Scopes, + }, true +} diff --git a/module/ai/provider/provider.go b/module/ai/provider/provider.go index bdc4dd8a..43ec8b2b 100644 --- a/module/ai/provider/provider.go +++ b/module/ai/provider/provider.go @@ -19,6 +19,7 @@ type IAIProvider interface { InvokeConfig() IAIConfig UpdateLLMs() error LLMs() []*LLM + LLM(id string) (*LLM, bool) } type IAIConfig interface { diff --git a/module/api-doc/api_doc.go b/module/api-doc/api_doc.go index 69573516..022dd64f 100644 --- a/module/api-doc/api_doc.go +++ b/module/api-doc/api_doc.go @@ -29,7 +29,6 @@ func (i *imlAPIDocModule) UpdateDoc(ctx context.Context, serviceId string, input } err = i.apiDocService.UpdateDoc(ctx, serviceId, &api_doc.UpdateDoc{ ID: input.Id, - Service: serviceId, Content: input.Content, }) if err != nil { diff --git a/plugins/core/api.go b/plugins/core/api.go index 80a2c1ab..4655573f 100644 --- a/plugins/core/api.go +++ b/plugins/core/api.go @@ -19,5 +19,11 @@ func (p *plugin) apiApis() []pm3.Api { pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/service/api_doc", []string{"context", "query:service"}, []string{"doc"}, p.apiDocController.GetDoc), pm3.CreateApiWidthDoc(http.MethodPost, "/api/v1/service/api_doc/upload", []string{"context", "query:service"}, []string{"doc"}, p.apiDocController.UploadDoc), + + pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/service/ai-router", []string{"context", "query:service", "query:router"}, []string{"api"}, p.aiAPIController.Get), + pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/service/ai-routers", []string{"context", "query:keyword", "query:service"}, []string{"apis"}, p.aiAPIController.List), + pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/service/ai-router", []string{"context", "query:service", "query:router", "body"}, []string{"api"}, p.aiAPIController.Edit), + pm3.CreateApiWidthDoc(http.MethodPost, "/api/v1/service/ai-router", []string{"context", "query:service", "body"}, []string{"api"}, p.aiAPIController.Create), + pm3.CreateApiWidthDoc(http.MethodDelete, "/api/v1/service/ai-router", []string{"context", "query:service", "query:router"}, nil, p.aiAPIController.Delete), } } diff --git a/plugins/core/core.go b/plugins/core/core.go index 1ad01170..c116a2bb 100644 --- a/plugins/core/core.go +++ b/plugins/core/core.go @@ -2,6 +2,7 @@ package core import ( "github.com/APIParkLab/APIPark/controller/ai" + ai_api "github.com/APIParkLab/APIPark/controller/ai-api" "github.com/APIParkLab/APIPark/controller/monitor" "github.com/APIParkLab/APIPark/controller/router" "github.com/APIParkLab/APIPark/controller/system" @@ -67,6 +68,7 @@ type plugin struct { catalogueController catalogue.ICatalogueController `autowired:""` upstreamController upstream.IUpstreamController `autowired:""` routerController router.IRouterController `autowired:""` + aiAPIController ai_api.IAPIController `autowired:""` apiDocController router.IAPIDocController `autowired:""` subscribeController subscribe.ISubscribeController `autowired:""` appAuthorizationController application_authorization.IAuthorizationController `autowired:""` diff --git a/plugins/core/service.go b/plugins/core/service.go index a20364e6..ff0e7c4c 100644 --- a/plugins/core/service.go +++ b/plugins/core/service.go @@ -20,12 +20,12 @@ func (p *plugin) ServiceApis() []pm3.Api { //pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/simple/services", []string{"context", "query:keyword"}, []string{"services"}, p.serviceController.Simple), // AI服务 - pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai-services", []string{"context", "query:service", "query:keyword"}, []string{"service"}, p.serviceController.SearchAIServices), + pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai-services", []string{"context", "query:service", "query:keyword"}, []string{"services"}, p.serviceController.SearchAIServices), pm3.CreateApiWidthDoc(http.MethodPost, "/api/v1/team/ai-service", []string{"context", "query:team", "body"}, []string{"service"}, p.serviceController.CreateAIService), pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/ai-service/info", []string{"context", "query:service", "body"}, []string{"service"}, p.serviceController.Edit), pm3.CreateApiWidthDoc(http.MethodDelete, "/api/v1/team/ai-service", []string{"context", "query:service"}, nil, p.serviceController.DeleteAIService), pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/my_ai_services", []string{"context", "query:team", "query:keyword"}, []string{"services"}, p.serviceController.SearchMyAIServices), - pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai-service/info", []string{"context", "query:service"}, []string{"services"}, p.serviceController.Get), + pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai-service/info", []string{"context", "query:service"}, []string{"service"}, p.serviceController.Get), // 应用相关 pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/app/info", []string{"context", "query:app"}, []string{"app"}, p.appController.GetApp), diff --git a/service/ai-api/iml.go b/service/ai-api/iml.go new file mode 100644 index 00000000..4971dcbf --- /dev/null +++ b/service/ai-api/iml.go @@ -0,0 +1,75 @@ +package ai_api + +import ( + "encoding/json" + "github.com/APIParkLab/APIPark/service/universally" + "github.com/APIParkLab/APIPark/stores/api" + "time" +) + +var _ IAPIService = (*imlAPIService)(nil) + +type imlAPIService struct { + store api.IAiAPIInfoStore `autowired:""` + + universally.IServiceGet[API] + universally.IServiceCreate[Create] + universally.IServiceEdit[Edit] + universally.IServiceDelete +} + +func (i *imlAPIService) OnComplete() { + i.IServiceGet = universally.NewGetSoftDelete[API, api.AiAPIInfo](i.store, FromEntity) + i.IServiceCreate = universally.NewCreatorSoftDelete[Create, api.AiAPIInfo](i.store, "ai_api_info", createEntityHandler, uniquestHandler, labelHandler) + i.IServiceEdit = universally.NewEdit[Edit, api.AiAPIInfo](i.store, updateHandler) + i.IServiceDelete = universally.NewSoftDelete[api.AiAPIInfo](i.store) +} + +func labelHandler(e *api.AiAPIInfo) []string { + return []string{e.Name, e.Uuid} +} +func uniquestHandler(i *Create) []map[string]interface{} { + return []map[string]interface{}{{"uuid": i.ID}} +} +func createEntityHandler(i *Create) *api.AiAPIInfo { + now := time.Now() + cfg, _ := json.Marshal(i.AdditionalConfig) + return &api.AiAPIInfo{ + Uuid: i.ID, + Name: i.Name, + Service: i.Service, + Path: i.Path, + Description: i.Description, + Timeout: i.Timeout, + Retry: i.Retry, + Model: i.Model, + CreateAt: now, + UpdateAt: now, + AdditionalConfig: string(cfg), + } +} +func updateHandler(e *api.AiAPIInfo, i *Edit) { + if i.Name != nil { + e.Name = *i.Name + } + if i.Path != nil { + e.Path = *i.Path + } + if i.Description != nil { + e.Description = *i.Description + } + if i.Timeout != nil { + e.Timeout = *i.Timeout + } + if i.Retry != nil { + e.Retry = *i.Retry + } + if i.Model != nil { + e.Model = *i.Model + } + if i.AdditionalConfig != nil { + cfg, _ := json.Marshal(i.AdditionalConfig) + e.AdditionalConfig = string(cfg) + } + e.UpdateAt = time.Now() +} diff --git a/service/ai-api/model.go b/service/ai-api/model.go new file mode 100644 index 00000000..ed25fd12 --- /dev/null +++ b/service/ai-api/model.go @@ -0,0 +1,68 @@ +package ai_api + +import ( + "encoding/json" + "github.com/APIParkLab/APIPark/stores/api" + "time" +) + +type API struct { + ID string + Name string + Service string + Path string + Description string + Timeout int + Retry int + Model string + CreateAt time.Time + UpdateAt time.Time + Creator string + Updater string + AdditionalConfig map[string]interface{} + Disable bool +} + +type Create struct { + ID string + Name string + Service string + Path string + Description string + Timeout int + Retry int + Model string + AdditionalConfig map[string]interface{} +} + +type Edit struct { + Name *string + Path *string + Description *string + Timeout *int + Retry *int + Model *string + AdditionalConfig *map[string]interface{} +} + +func FromEntity(e *api.AiAPIInfo) *API { + cfg := make(map[string]interface{}) + if e.AdditionalConfig != "" { + _ = json.Unmarshal([]byte(e.AdditionalConfig), &cfg) + } + return &API{ + ID: e.Uuid, + Name: e.Name, + Service: e.Service, + Path: e.Path, + Description: e.Description, + Timeout: e.Timeout, + Retry: e.Retry, + Model: e.Model, + CreateAt: e.CreateAt, + UpdateAt: e.UpdateAt, + Creator: e.Creator, + Updater: e.Updater, + AdditionalConfig: cfg, + } +} diff --git a/service/ai-api/service.go b/service/ai-api/service.go new file mode 100644 index 00000000..6cf068ee --- /dev/null +++ b/service/ai-api/service.go @@ -0,0 +1,22 @@ +package ai_api + +import ( + "github.com/APIParkLab/APIPark/service/universally" + "github.com/eolinker/go-common/autowire" + "reflect" +) + +type IAPIService interface { + universally.IServiceGet[API] + universally.IServiceCreate[Create] + universally.IServiceEdit[Edit] + universally.IServiceDelete + + //ListByServices(ctx context.Context, serviceIds ...string) ([]*API, error) +} + +func init() { + autowire.Auto[IAPIService](func() reflect.Value { + return reflect.ValueOf(new(imlAPIService)) + }) +} diff --git a/service/api-doc/model.go b/service/api-doc/model.go index af57aba1..b9791f24 100644 --- a/service/api-doc/model.go +++ b/service/api-doc/model.go @@ -4,7 +4,6 @@ import "time" type UpdateDoc struct { ID string - Service string Content string } diff --git a/stores/api/api.go b/stores/api/api.go index c8cb4d7a..4eab2d83 100644 --- a/stores/api/api.go +++ b/stores/api/api.go @@ -23,15 +23,29 @@ type IAPIDocStore interface { store.IBaseStore[Doc] } +type IAiAPIInfoStore interface { + store.ISearchStore[AiAPIInfo] +} + +type imlAiAPIInfoStore struct { + store.SearchStoreSoftDelete[AiAPIInfo] +} + func init() { autowire.Auto[IApiBaseStore](func() reflect.Value { return reflect.ValueOf(new(imlApiBaseStore)) }) + autowire.Auto[IAPIInfoStore](func() reflect.Value { return reflect.ValueOf(new(store.Store[Info])) }) + autowire.Auto[IAPIDocStore](func() reflect.Value { return reflect.ValueOf(new(imlAPIDocStore)) }) + + autowire.Auto[IAiAPIInfoStore](func() reflect.Value { + return reflect.ValueOf(new(imlAiAPIInfoStore)) + }) } diff --git a/stores/api/model.go b/stores/api/model.go index ed1dca17..6adf58d1 100644 --- a/stores/api/model.go +++ b/stores/api/model.go @@ -66,3 +66,29 @@ func (i *Doc) TableName() string { func (i *Doc) IdValue() int64 { return i.Id } + +type AiAPIInfo struct { + Id int64 `gorm:"column:id;type:BIGINT(20);AUTO_INCREMENT;NOT NULL;comment:id;primary_key;comment:主键ID;"` + Uuid string `gorm:"type:varchar(36);not null;column:uuid;uniqueIndex:uuid;comment:UUID"` + Name string `gorm:"type:varchar(100);not null;column:name;comment:name"` + Service string `gorm:"size:36;not null;column:service;comment:服务;index:service"` + Path string `gorm:"size:512;not null;column:path;comment:请求路径"` + Description string `gorm:"size:255;not null;column:description;comment:description"` + Timeout int `gorm:"type:int(11);not null;column:timeout;comment:超时时间"` + Retry int `gorm:"type:int(11);not null;column:retry;comment:重试次数"` + Model string `gorm:"size:36;not null;column:model;comment:模型"` + Creator string `gorm:"size:36;not null;column:creator;comment:创建人;index:creator" aovalue:"creator"` + CreateAt time.Time `gorm:"type:timestamp;NOT NULL;DEFAULT:CURRENT_TIMESTAMP;column:create_at;comment:创建时间"` + Updater string `gorm:"size:36;not null;column:updater;comment:更新人;index:updater" aovalue:"updater"` + UpdateAt time.Time `gorm:"type:timestamp;NOT NULL;DEFAULT:CURRENT_TIMESTAMP;column:update_at;comment:更新时间"` + AdditionalConfig string `gorm:"type:text;null;column:additional_config;comment:额外配置"` + IsDelete bool `gorm:"type:tinyint(1);not null;column:is_delete;comment:是否删除 0:否 1:是"` +} + +func (a *AiAPIInfo) TableName() string { + return "ai_api_info" +} + +func (a *AiAPIInfo) IdValue() int64 { + return a.Id +}