Files
APIPark/controller/service/iml.go
T
2025-03-11 19:44:55 +08:00

616 lines
17 KiB
Go

package service
import (
"context"
"fmt"
"io"
"net/http"
"strings"
"time"
ai_provider_local "github.com/APIParkLab/APIPark/ai-provider/local"
subscribe_dto "github.com/APIParkLab/APIPark/module/subscribe/dto"
"github.com/APIParkLab/APIPark/module/subscribe"
ai_local "github.com/APIParkLab/APIPark/module/ai-local"
ai_dto "github.com/APIParkLab/APIPark/module/ai/dto"
api_doc_dto "github.com/APIParkLab/APIPark/module/api-doc/dto"
"github.com/APIParkLab/APIPark/module/catalogue"
"github.com/APIParkLab/APIPark/module/team"
"github.com/eolinker/go-common/pm3"
"github.com/APIParkLab/APIPark/module/system"
"github.com/getkin/kin-openapi/openapi3"
api_doc "github.com/APIParkLab/APIPark/module/api-doc"
"github.com/eolinker/eosc/log"
application_authorization "github.com/APIParkLab/APIPark/module/application-authorization"
application_authorization_dto "github.com/APIParkLab/APIPark/module/application-authorization/dto"
"github.com/APIParkLab/APIPark/model/plugin_model"
"github.com/APIParkLab/APIPark/service/api"
router_dto "github.com/APIParkLab/APIPark/module/router/dto"
model_runtime "github.com/APIParkLab/APIPark/ai-provider/model-runtime"
"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/router"
"github.com/APIParkLab/APIPark/module/service"
service_dto "github.com/APIParkLab/APIPark/module/service/dto"
"github.com/APIParkLab/APIPark/module/upstream"
"github.com/eolinker/go-common/store"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
//var (
// ollamaConfig = "{\n \"mirostat\": 0,\n \"mirostat_eta\": 0.1,\n \"mirostat_tau\": 5.0,\n \"num_ctx\": 4096,\n \"repeat_last_n\":64,\n \"repeat_penalty\": 1.1,\n \"temperature\": 0.7,\n \"seed\": 42,\n \"num_predict\": 42,\n \"top_k\": 40,\n \"top_p\": 0.9,\n \"min_p\": 0.5\n}\n"
//)
var (
_ IServiceController = (*imlServiceController)(nil)
_ IAppController = (*imlAppController)(nil)
)
type imlServiceController struct {
module service.IServiceModule `autowired:""`
docModule service.IServiceDocModule `autowired:""`
subscribeModule subscribe.ISubscribeModule `autowired:""`
aiAPIModule ai_api.IAPIModule `autowired:""`
routerModule router.IRouterModule `autowired:""`
apiDocModule api_doc.IAPIDocModule `autowired:""`
providerModule ai.IProviderModule `autowired:""`
aiLocalModel ai_local.ILocalModelModule `autowired:""`
appModule service.IAppModule `autowired:""`
upstreamModule upstream.IUpstreamModule `autowired:""`
settingModule system.ISettingModule `autowired:""`
teamModule team.ITeamModule `autowired:""`
catalogueModule catalogue.ICatalogueModule `autowired:""`
transaction store.ITransaction `autowired:""`
}
func (i *imlServiceController) QuickCreateAIService(ctx *gin.Context, input *service_dto.QuickCreateAIService) error {
return i.transaction.Transaction(ctx, func(txCtx context.Context) error {
enable := true
err := i.providerModule.UpdateProviderConfig(ctx, input.Provider, &ai_dto.UpdateConfig{
Config: input.Config,
Enable: &enable,
})
if err != nil {
return err
}
p, err := i.providerModule.Provider(ctx, input.Provider)
if err != nil {
return err
}
id := uuid.NewString()
prefix := fmt.Sprintf("/%s", id[:8])
catalogueInfo, err := i.catalogueModule.DefaultCatalogue(ctx)
if err != nil {
return err
}
_, err = i.createAIService(ctx, input.Team, &service_dto.CreateService{
Id: uuid.NewString(),
Name: input.Provider + " AI Service",
Prefix: prefix,
Description: "Quick create by AI provider",
ServiceType: "public",
State: "normal",
Catalogue: catalogueInfo.Id,
ApprovalType: "auto",
Provider: &input.Provider,
Model: &p.DefaultLLM,
Kind: "ai",
})
return err
})
}
func (i *imlServiceController) QuickCreateRestfulService(ctx *gin.Context) error {
fileHeader, err := ctx.FormFile("file")
if err != nil {
return err
}
file, err := fileHeader.Open()
if err != nil {
return err
}
content, err := io.ReadAll(file)
if err != nil {
return err
}
typ := ctx.PostForm("type")
switch typ {
case "swagger", "":
default:
return fmt.Errorf("type %s not support", typ)
}
return i.transaction.Transaction(ctx, func(txCtx context.Context) error {
teamId := ctx.PostForm("team")
id := uuid.NewString()
prefix := fmt.Sprintf("/%s", id[:8])
catalogueInfo, err := i.catalogueModule.DefaultCatalogue(ctx)
if err != nil {
return err
}
s, err := i.module.Create(ctx, teamId, &service_dto.CreateService{
Id: uuid.NewString(),
Name: "Restful Service By Swagger",
Prefix: prefix,
Description: "Auto create by upload swagger",
ServiceType: "public",
State: "normal",
Catalogue: catalogueInfo.Id,
ApprovalType: "auto",
Kind: "rest",
})
if err != nil {
return err
}
_, err = i.apiDocModule.UpdateDoc(ctx, s.Id, &api_doc_dto.UpdateDoc{
Id: s.Id,
Content: string(content),
})
if err != nil {
return err
}
path := prefix + "/"
_, err = i.routerModule.Create(ctx, s.Id, &router_dto.Create{
Id: uuid.NewString(),
Name: "",
Path: path + "*",
Methods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodPatch, http.MethodOptions},
Description: "auto create by create service",
Protocols: []string{"http", "https"},
Proxy: &router_dto.InputProxy{
Path: path,
Timeout: 30000,
Retry: 0,
},
Disable: false,
})
if err != nil {
return err
}
apps, err := i.appModule.Search(ctx, teamId, "")
if err != nil {
return err
}
for _, app := range apps {
i.subscribeModule.AddSubscriber(ctx, id, &subscribe_dto.AddSubscriber{
Application: app.Id,
})
}
return nil
})
}
var (
loader = openapi3.NewLoader()
)
func (i *imlServiceController) swagger(ctx *gin.Context, id string) (*openapi3.T, error) {
doc, err := i.apiDocModule.GetDoc(ctx, id)
if err != nil {
return nil, err
}
tmp, err := loader.LoadFromData([]byte(doc.Content))
if err != nil {
return nil, err
}
cfg := i.settingModule.Get(ctx)
tmp.AddServer(&openapi3.Server{
URL: cfg.InvokeAddress,
})
return tmp, nil
}
func (i *imlServiceController) ExportSwagger(ctx *gin.Context) {
id, has := ctx.Params.Get("id")
if !has {
ctx.JSON(200, &pm3.Response{
Code: -1,
Success: "fail",
Message: fmt.Sprintf("id is required"),
})
return
}
s, err := i.module.Get(ctx, id)
if err != nil {
ctx.JSON(200, &pm3.Response{
Code: -1,
Success: "fail",
Message: err.Error(),
})
return
}
tmp, err := i.swagger(ctx, id)
if err != nil {
ctx.JSON(200, &pm3.Response{
Code: -1,
Success: "fail",
Message: err.Error(),
})
return
}
data, _ := tmp.MarshalJSON()
ctx.Status(200)
// 设置响应头
ctx.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s.json", strings.Replace(s.Name, " ", "_", -1)))
ctx.Header("Content-Type", "application/octet-stream")
ctx.Header("Content-Transfer-Encoding", "binary")
ctx.Writer.Write(data)
return
}
func (i *imlServiceController) Swagger(ctx *gin.Context) {
id, has := ctx.Params.Get("id")
if !has {
ctx.JSON(200, &pm3.Response{
Code: -1,
Success: "fail",
Message: fmt.Sprintf("id is required"),
})
return
}
tmp, err := i.swagger(ctx, id)
if err != nil {
ctx.JSON(200, &pm3.Response{
Code: -1,
Success: "fail",
Message: err.Error(),
})
return
}
ctx.JSON(200, tmp)
return
}
func (i *imlServiceController) Simple(ctx *gin.Context) ([]*service_dto.SimpleServiceItem, error) {
return i.module.Simple(ctx)
}
func (i *imlServiceController) MySimple(ctx *gin.Context) ([]*service_dto.SimpleServiceItem, error) {
return i.module.MySimple(ctx)
}
func (i *imlServiceController) editAIService(ctx *gin.Context, id string, input *service_dto.EditService) (*service_dto.Service, error) {
if input.Provider == nil {
return nil, fmt.Errorf("provider is required")
}
if *input.Provider != ai_provider_local.ProviderLocal {
_, has := model_runtime.GetProvider(*input.Provider)
if !has {
return nil, fmt.Errorf("provider not found")
}
}
info, err := i.module.Edit(ctx, id, input)
if err != nil {
return nil, err
}
//_, err = i.upstreamModule.Save(ctx, id, newAIUpstream(id, *input.Provider, p.URI()))
return info, nil
}
func (i *imlServiceController) createAIService(ctx *gin.Context, teamID string, input *service_dto.CreateService) (*service_dto.Service, error) {
if input.Provider == nil {
return nil, fmt.Errorf("provider is required")
}
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]
}
}
modelId := ""
modelCfg := ""
modelType := "online"
if *input.Provider == ai_provider_local.ProviderLocal {
modelType = "local"
list, err := i.aiLocalModel.SimpleList(ctx)
if err != nil {
return nil, err
}
if len(list) == 0 {
return nil, fmt.Errorf("no local model")
}
modelId = list[0].Id
modelCfg = ai_provider_local.LocalConfig
} else {
pv, err := i.providerModule.Provider(ctx, *input.Provider)
if err != nil {
return nil, err
}
p, has := model_runtime.GetProvider(*input.Provider)
if !has {
return nil, fmt.Errorf("provider not found")
}
m, has := p.GetModel(pv.DefaultLLM)
if !has {
return nil, fmt.Errorf("model %s not found", pv.DefaultLLM)
}
modelId = m.ID()
modelCfg = m.DefaultConfig()
}
var info *service_dto.Service
err := i.transaction.Transaction(ctx, func(txCtx context.Context) error {
var err error
info, err = i.module.Create(ctx, teamID, input)
if err != nil {
return err
}
prefix := strings.Replace(input.Prefix, ":", "_", -1)
path := fmt.Sprintf("/%s/chat/completions", strings.Trim(prefix, "/"))
timeout := 300000
retry := 0
aiPrompt := &ai_api_dto.AiPrompt{
Variables: []*ai_api_dto.AiPromptVariable{},
Prompt: "",
}
aiModel := &ai_api_dto.AiModel{
Id: modelId,
Config: modelCfg,
Provider: *input.Provider,
Type: modelType,
}
name := "Demo AI API "
description := "This is a demo that shows you how to use a Chat API."
apiId := uuid.New().String()
err = i.aiAPIModule.Create(
ctx,
info.Id,
&ai_api_dto.CreateAPI{
Id: apiId,
Name: name,
Path: path,
Description: description,
Disable: false,
AiPrompt: aiPrompt,
AiModel: aiModel,
Timeout: timeout,
Retry: retry,
},
)
if err != nil {
return err
}
plugins := make(map[string]api.PluginSetting)
plugins["ai_prompt"] = api.PluginSetting{
Config: plugin_model.ConfigType{
"prompt": aiPrompt.Prompt,
"variables": aiPrompt.Variables,
},
}
plugins["ai_formatter"] = api.PluginSetting{
Config: plugin_model.ConfigType{
"model": aiModel.Id,
"provider": fmt.Sprintf("%s@ai-provider", info.Provider.Id),
"config": aiModel.Config,
},
}
_, err = i.routerModule.Create(ctx, info.Id, &router_dto.Create{
Id: apiId,
Name: name,
Path: path,
Methods: []string{
http.MethodPost,
},
Description: description,
Protocols: []string{"http", "https"},
MatchRules: nil,
Proxy: &router_dto.InputProxy{
Path: path,
Timeout: timeout,
Retry: retry,
Plugins: plugins,
},
Disable: false,
Upstream: info.Provider.Id,
})
if err != nil {
return err
}
apps, err := i.appModule.Search(ctx, info.Team.Id, "")
if err != nil {
return err
}
for _, app := range apps {
i.subscribeModule.AddSubscriber(ctx, info.Id, &subscribe_dto.AddSubscriber{
Application: app.Id,
})
}
return i.docModule.SaveServiceDoc(ctx, info.Id, &service_dto.SaveServiceDoc{
Doc: "",
})
})
return info, err
}
func (i *imlServiceController) SearchMyServices(ctx *gin.Context, teamId string, keyword string) ([]*service_dto.ServiceItem, error) {
return i.module.SearchMyServices(ctx, teamId, keyword)
}
func (i *imlServiceController) Get(ctx *gin.Context, id string) (*service_dto.Service, error) {
now := time.Now()
defer func() {
log.Infof("get service %s cost %d ms", id, time.Since(now).Milliseconds())
}()
return i.module.Get(ctx, id)
}
func (i *imlServiceController) Search(ctx *gin.Context, teamIDs string, keyword string) ([]*service_dto.ServiceItem, error) {
return i.module.Search(ctx, teamIDs, keyword)
}
func (i *imlServiceController) Create(ctx *gin.Context, teamID string, input *service_dto.CreateService) (*service_dto.Service, error) {
if input.Kind == "ai" {
return i.createAIService(ctx, teamID, input)
}
var err error
var info *service_dto.Service
err = i.transaction.Transaction(ctx, func(ctx context.Context) error {
info, err = i.module.Create(ctx, teamID, input)
if err != nil {
return err
}
path := fmt.Sprintf("/%s/", strings.Trim(input.Prefix, "/"))
_, err = i.routerModule.Create(ctx, info.Id, &router_dto.Create{
Id: uuid.New().String(),
Name: "",
Path: path + "*",
Methods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodPatch, http.MethodOptions},
Description: "auto create by create service",
Protocols: []string{"http", "https"},
MatchRules: nil,
Upstream: "",
Proxy: &router_dto.InputProxy{
Path: path,
Timeout: 30000,
Retry: 0,
},
Disable: false,
})
apps, err := i.appModule.Search(ctx, teamID, "")
if err != nil {
return err
}
for _, app := range apps {
i.subscribeModule.AddSubscriber(ctx, info.Id, &subscribe_dto.AddSubscriber{
Application: app.Id,
})
}
return err
})
return info, err
}
func (i *imlServiceController) Edit(ctx *gin.Context, id string, input *service_dto.EditService) (*service_dto.Service, error) {
info, err := i.Get(ctx, id)
if err != nil {
return nil, err
}
if info.ServiceKind == "ai" {
return i.editAIService(ctx, id, input)
}
return i.module.Edit(ctx, id, input)
}
func (i *imlServiceController) Delete(ctx *gin.Context, id string) error {
return i.module.Delete(ctx, id)
}
func (i *imlServiceController) ServiceDoc(ctx *gin.Context, id string) (*service_dto.ServiceDoc, error) {
return i.docModule.ServiceDoc(ctx, id)
}
func (i *imlServiceController) SaveServiceDoc(ctx *gin.Context, id string, input *service_dto.SaveServiceDoc) error {
return i.docModule.SaveServiceDoc(ctx, id, input)
}
type imlAppController struct {
module service.IAppModule `autowired:""`
authModule application_authorization.IAuthorizationModule `autowired:""`
}
func (i *imlAppController) SearchCanSubscribe(ctx *gin.Context, serviceId string) ([]*service_dto.SimpleAppItem, error) {
return i.module.SearchCanSubscribe(ctx, serviceId)
}
func (i *imlAppController) Search(ctx *gin.Context, teamId string, keyword string) ([]*service_dto.AppItem, error) {
return i.module.Search(ctx, teamId, keyword)
}
func (i *imlAppController) CreateApp(ctx *gin.Context, teamID string, input *service_dto.CreateApp) (*service_dto.App, error) {
app, err := i.module.CreateApp(ctx, teamID, input)
if err != nil {
return nil, err
}
_, err = i.authModule.AddAuthorization(ctx, app.Id, &application_authorization_dto.CreateAuthorization{
Name: "Default API Key",
Driver: "apikey",
Position: "Header",
TokenName: "Authorization",
ExpireTime: 0,
Config: map[string]interface{}{
"apikey": uuid.New().String(),
},
})
if err != nil {
i.module.DeleteApp(ctx, app.Id)
return nil, err
}
return app, nil
}
func (i *imlAppController) UpdateApp(ctx *gin.Context, appId string, input *service_dto.UpdateApp) (*service_dto.App, error) {
return i.module.UpdateApp(ctx, appId, input)
}
func (i *imlAppController) SearchMyApps(ctx *gin.Context, teamId string, keyword string) ([]*service_dto.AppItem, error) {
return i.module.SearchMyApps(ctx, teamId, keyword)
}
func (i *imlAppController) SimpleApps(ctx *gin.Context, keyword string) ([]*service_dto.SimpleAppItem, error) {
return i.module.SimpleApps(ctx, keyword)
}
func (i *imlAppController) MySimpleApps(ctx *gin.Context, keyword string) ([]*service_dto.SimpleAppItem, error) {
return i.module.MySimpleApps(ctx, keyword)
}
func (i *imlAppController) GetApp(ctx *gin.Context, appId string) (*service_dto.App, error) {
return i.module.GetApp(ctx, appId)
}
func (i *imlAppController) DeleteApp(ctx *gin.Context, appId string) error {
return i.module.DeleteApp(ctx, appId)
}
//func newAIUpstream(id string, provider string, uri model_runtime.IProviderURI) *upstream_dto.Upstream {
// return &upstream_dto.Upstream{
// Type: "http",
// Balance: "round-robin",
// Timeout: 300000,
// Retry: 0,
// Remark: fmt.Sprintf("auto create by ai service %s,provider is %s", id, provider),
// LimitPeerSecond: 0,
// ProxyHeaders: nil,
// Scheme: uri.Scheme(),
// PassHost: "node",
// Nodes: []*upstream_dto.NodeConfig{
// {
// Address: uri.Host(),
// Weight: 100,
// },
// },
// }
//}