Merge pull request #162 from APIParkLab/feature/ai-balance

Feature/ai balance
This commit is contained in:
Dot.L
2025-01-08 11:26:18 +08:00
committed by GitHub
46 changed files with 1101 additions and 346 deletions
+98
View File
@@ -0,0 +1,98 @@
variables:
PATH: /opt/go-1.21/go/bin/:/opt/node/node/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
GOROOT: /opt/go-1.21/go
GOPROXY: https://goproxy.cn
VERSION: $CI_COMMIT_SHORT_SHA
APP: apipark
APP_PRE: ${APP}_${VERSION}
BUILD_DIR: ${APP}-build
DEPLOY_DESC: "DEV 环境"
VIEW_ADDR: http://172.18.166.219:8288
SAVE_DIR: /opt/${APP}
NODE_OPTIONS: --max_old_space_size=8192
stages:
- notice
- prefix
- build
- deploy
- webhook
feishu-informer: # 飞书回调
stage: notice
variables:
DIFF_URL: "$CI_MERGE_REQUEST_PROJECT_URL/-/merge_requests/$CI_MERGE_REQUEST_IID/diffs"
rules:
- if: $CI_PIPELINE_SOURCE=="merge_request_event" && $CI_COMMIT_BRANCH =~ "main"
script:
- echo "merge request"
- |
curl -X POST -H "Content-Type: application/json" \
-d "{\"msg_type\":\"text\",\"content\":{\"text\":\"项目:${CI_PROJECT_NAME}\\n提交人:${GITLAB_USER_NAME}\\n提交信息:${CI_MERGE_REQUEST_TITLE}\\n合并分支信息:${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME} -> ${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}\\n差异性地址:${DIFF_URL}\\n请及时review代码\"}}" \
https://open.feishu.cn/open-apis/bot/v2/hook/1c334752-2874-41a1-8f1b-3060f2d46b6c
prebuild:
stage: prefix
rules:
- if: $CI_COMMIT_BRANCH == "main"
script:
- echo "prebuild"
- chmod +x ./scripts/prefix.sh
- ./scripts/prefix.sh
builder:
stage: build
rules:
- if: $CI_COMMIT_BRANCH == "main"
script:
- set -e
- |
if [ ! -d "../artifacts" ]; then
mkdir -p ../artifacts
fi
if [ -d "../artifacts/dist" ]; then
cp -r ../artifacts/dist frontend/dist
fi
- |
if [ -n "$(git diff --name-status HEAD~1 HEAD -- frontend)" ]; then
./scripts/build.sh $BUILD_DIR ${VERSION} all ""
else
./scripts/build.sh $BUILD_DIR ${VERSION}
fi
if [ -d "frontend/dist" ]; then
echo "copy frontend/dist to artifacts/dist"
rm -fr ../artifacts/dist
cp -r frontend/dist ../artifacts/dist
fi
cp $BUILD_DIR/${APP_PRE}_linux_amd64.tar.gz ${SAVE_DIR}
deployer:
stage: deploy
rules:
- if: $CI_COMMIT_BRANCH == "main"
variables:
APIPARK_GUEST_MODE: allow
APIPARK_GUEST_ID: dklejrfbhjqwdh
script:
- cd ${SAVE_DIR};mkdir -p ${APP_PRE};tar -zxvf ${APP_PRE}_linux_amd64.tar.gz -C ${APP_PRE};cd ${APP_PRE};./install.sh ${SAVE_DIR};./run.sh restart;cd ${SAVE_DIR} && ./clean.sh ${APP_PRE}
when: on_success
success:
stage: webhook
rules:
- if: $CI_COMMIT_BRANCH == "main"
script:
- |
curl -X POST -H "Content-Type: application/json" \
-d "{\"msg_type\":\"text\",\"content\":{\"text\":\"最近一次提交:${CI_COMMIT_TITLE}\\n提交人:${GITLAB_USER_NAME}\\n项目:${CI_PROJECT_NAME}\\n环境:${DEPLOY_DESC}\\n更新部署完成.\\n访问地址:${VIEW_ADDR}\\n工作流地址:${CI_PIPELINE_URL}\"}}" \
https://open.feishu.cn/open-apis/bot/v2/hook/c3672932-4dfa-4989-8023-0128bae59338
when: on_success
failure:
stage: webhook
rules:
- if: $CI_COMMIT_BRANCH == "main"
script:
- |
curl -X POST -H "Content-Type: application/json" \
-d "{\"msg_type\":\"text\",\"content\":{\"text\":\"最近一次提交:${CI_COMMIT_TITLE}\\n提交人:${GITLAB_USER_NAME}\\n项目:${CI_PROJECT_NAME}\\n环境:${DEPLOY_DESC}\\n更新部署失败,请及时到gitlab上查看\\n工作流地址:${CI_PIPELINE_URL}\"}}" \
https://open.feishu.cn/open-apis/bot/v2/hook/c3672932-4dfa-4989-8023-0128bae59338
when: on_failure
+1
View File
@@ -0,0 +1 @@
/config.yml
+77
View File
@@ -0,0 +1,77 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"github.com/eolinker/go-common/autowire"
nsq "github.com/nsqio/go-nsq"
"github.com/eolinker/go-common/cftool"
_ "github.com/eolinker/go-common/store/store_mysql"
_ "github.com/go-sql-driver/mysql"
)
var (
version string
confPath string
)
func init() {
flag.StringVar(&confPath, "c", "config.yml", "`config` file path for server ")
}
type ServerConfig struct {
Port int `yaml:"port"`
}
func main() {
// 1. 连接 MySQL 数据库
cftool.Register[ServerConfig](fmt.Sprintf("root:%s", confPath))
cftool.ReadFile(confPath)
handler := &NSQHandler{}
autowire.Autowired(handler)
err := autowire.CheckComplete()
if err != nil {
log.Fatal("check autowired:", err)
return
}
// 2. 创建 NSQ 消费者
config := nsq.NewConfig()
hostname, err := os.Hostname()
if err != nil {
log.Fatalf("Failed to get hostname: %v", err)
return
}
nsqConfig := handler.nsqConfig
consumer, err := nsq.NewConsumer(fmt.Sprintf("%s_ai_event", nsqConfig.TopicPrefix), hostname, config)
if err != nil {
log.Fatalf("Failed to create NSQ consumer: %v", err)
}
consumer.AddHandler(handler)
// 4. 连接到 NSQ
//nsqAddress := "172.18.166.219:9150" // NSQ 地址
err = consumer.ConnectToNSQD(nsqConfig.Addr)
if err != nil {
log.Fatalf("Failed to connect to NSQ: %v", err)
}
log.Println("Connected to NSQ")
// 5. 捕获系统信号,优雅关闭
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
// 优雅停止消费者
consumer.Stop()
<-consumer.StopChan
log.Println("NSQ Consumer stopped")
}
+161
View File
@@ -0,0 +1,161 @@
package main
import (
"context"
"encoding/json"
"log"
"strings"
"time"
"github.com/eolinker/go-common/cftool"
ai_dto "github.com/APIParkLab/APIPark/module/ai/dto"
"github.com/eolinker/go-common/store"
"github.com/APIParkLab/APIPark/service/ai"
ai_key "github.com/APIParkLab/APIPark/service/ai-key"
nsq "github.com/nsqio/go-nsq"
ai_api "github.com/APIParkLab/APIPark/service/ai-api"
)
func init() {
cftool.Register[NSQConfig]("nsq")
}
type NSQConfig struct {
Addr string `json:"addr"`
TopicPrefix string `json:"topic_prefix"`
}
// 定义 NSQ 消息结构
type AIProviderStatus struct {
Provider string `json:"provider"`
Model string `json:"model"`
Key string `json:"key"`
Status string `json:"status"`
}
type AIInfo struct {
Model string `json:"ai_model"`
Cost interface{} `json:"ai_model_cost"`
InputToken interface{} `json:"ai_model_input_token"`
OutputToken interface{} `json:"ai_model_output_token"`
TotalToken interface{} `json:"ai_model_total_token"`
Provider string `json:"ai_provider"`
ProviderStats []AIProviderStatus `json:"ai_provider_statuses"`
}
type NSQMessage struct {
AI AIInfo `json:"ai"`
API string `json:"api"`
Provider string `json:"provider"`
RequestID string `json:"request_id"`
TimeISO8601 string `json:"time_iso8601"`
}
// NSQHandler 处理 NSQ 消息并写入 MySQL
type NSQHandler struct {
apiUseService ai_api.IAPIUseService `autowired:""`
aiKeyService ai_key.IKeyService `autowired:""`
aiService ai.IProviderService `autowired:""`
transaction store.ITransaction `autowired:""`
nsqConfig *NSQConfig `autowired:""`
ctx context.Context
}
func convertInt(value interface{}) int {
switch v := value.(type) {
case int:
return v
case float64:
return int(v)
default:
return 0
}
}
// HandleMessage 处理从 NSQ 读取的消息
func (h *NSQHandler) HandleMessage(message *nsq.Message) error {
log.Printf("Received message: %s", string(message.Body))
// 解析消息为结构体
var data NSQMessage
err := json.Unmarshal(message.Body, &data)
if err != nil {
log.Printf("Failed to unmarshal message: %v", err)
return err
}
// 将时间字符串转换为 time.Time
timestamp, err := time.Parse(time.RFC3339, data.TimeISO8601)
if err != nil {
log.Printf("Failed to parse timestamp: %v", err)
return err
}
day := time.Date(timestamp.Year(), timestamp.Month(), timestamp.Day(), 0, 0, 0, 0, timestamp.Location())
hour := time.Date(timestamp.Year(), timestamp.Month(), timestamp.Day(), timestamp.Hour(), 0, 0, 0, timestamp.Location())
minute := time.Date(timestamp.Year(), timestamp.Month(), timestamp.Day(), timestamp.Hour(), timestamp.Minute(), 0, 0, timestamp.Location())
return h.transaction.Transaction(context.Background(), func(ctx context.Context) error {
finalStatus := &AIProviderStatus{}
for _, s := range data.AI.ProviderStats {
status := ToKeyStatus(s.Status).Int()
keys := strings.Split(s.Key, "@")
key := keys[0]
err = h.aiKeyService.Save(ctx, key, &ai_key.Edit{
Status: &status,
})
if err != nil {
log.Printf("Failed to save AI key: %v", err)
return err
}
if s.Provider != data.AI.Provider {
pStatus := ai_dto.ProviderAbnormal.Int()
err = h.aiService.Save(ctx, s.Provider, &ai.SetProvider{
Status: &pStatus,
})
} else {
pStatus := ai_dto.ProviderEnabled.Int()
err = h.aiService.Save(ctx, s.Provider, &ai.SetProvider{
Status: &pStatus,
})
}
finalStatus = &s
}
if finalStatus != nil {
keys := strings.Split(finalStatus.Key, "@")
err = h.aiKeyService.IncrUseToken(ctx, keys[0], convertInt(data.AI.TotalToken))
if err != nil {
log.Printf("Failed to increment AI key token: %v", err)
return err
}
}
// 调用 AI API 接口
err = h.apiUseService.Incr(context.Background(), &ai_api.IncrAPIUse{
API: data.API,
Service: data.Provider,
Provider: data.AI.Provider,
Model: data.AI.Model,
Day: day.Unix(),
Hour: hour.Unix(),
Minute: minute.Unix(),
InputToken: convertInt(data.AI.InputToken),
OutputToken: convertInt(data.AI.OutputToken),
TotalToken: convertInt(data.AI.TotalToken),
})
if err != nil {
log.Printf("Failed to call AI API: %v", err)
return err
}
log.Printf("Message processed and saved to MySQL: %+v", data)
return nil
})
}
+34
View File
@@ -0,0 +1,34 @@
package main
import ai_key_dto "github.com/APIParkLab/APIPark/module/ai-key/dto"
var (
StatusNormal = "normal"
StatusInvalidRequest = "invalid request"
StatusQuotaExhausted = "quota exhausted"
StatusExpired = "expired"
StatusExceeded = "exceeded"
StatusInvalid = "invalid"
StatusTimeout = "timeout"
)
func ToKeyStatus(status string) ai_key_dto.KeyStatus {
switch status {
case StatusNormal:
return ai_key_dto.KeyNormal
case StatusInvalidRequest:
return ai_key_dto.KeyNormal
case StatusQuotaExhausted:
return ai_key_dto.KeyExceed
case StatusExpired:
return ai_key_dto.KeyExpired
case StatusExceeded:
return ai_key_dto.KeyNormal
case StatusInvalid:
return ai_key_dto.KeyError
case StatusTimeout:
return ai_key_dto.KeyError
default:
return ai_key_dto.KeyNormal
}
}
+7 -8
View File
@@ -2,7 +2,6 @@ package ai_api
import (
"context"
"fmt"
"net/http"
"github.com/APIParkLab/APIPark/model/plugin_model"
@@ -52,7 +51,7 @@ func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_
plugins["ai_formatter"] = api.PluginSetting{
Config: plugin_model.ConfigType{
"model": input.AiModel.Id,
"provider": fmt.Sprintf("%s@ai-provider", input.AiModel.Provider),
"provider": input.AiModel.Provider,
"config": input.AiModel.Config,
},
}
@@ -73,8 +72,8 @@ func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_
Retry: input.Retry,
Plugins: plugins,
},
Upstream: input.AiModel.Provider,
Disable: false,
//Upstream: input.AiModel.Provider,
Disable: false,
})
return err
@@ -101,16 +100,16 @@ func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string
Retry: apiInfo.Proxy.Retry,
Plugins: apiInfo.Proxy.Plugins,
}
var upstream *string
//var upstream *string
if input.AiModel != nil {
proxy.Plugins["ai_formatter"] = api.PluginSetting{
Config: plugin_model.ConfigType{
"model": input.AiModel.Id,
"provider": fmt.Sprintf("%s@ai-provider", input.AiModel.Provider),
"provider": input.AiModel.Provider,
"config": input.AiModel.Config,
},
}
upstream = &input.AiModel.Provider
//upstream = &input.AiModel.Provider
}
if input.AiPrompt != nil {
@@ -128,7 +127,7 @@ func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string
Path: input.Path,
Disable: input.Disable,
Methods: &apiInfo.Methods,
Upstream: upstream,
//Upstream: upstream,
})
if err != nil {
return err
+1 -1
View File
@@ -13,7 +13,7 @@ type IKeyController interface {
Edit(ctx *gin.Context, providerId string, id string, input *ai_key_dto.Edit) error
Delete(ctx *gin.Context, providerId string, id string) error
Get(ctx *gin.Context, providerId string, id string) (*ai_key_dto.Key, error)
List(ctx *gin.Context, providerId string, keyword string, page string, pageSize string) ([]*ai_key_dto.Item, int64, error)
List(ctx *gin.Context, providerId string, keyword string, page string, pageSize string, statuses string) ([]*ai_key_dto.Item, int64, error)
Enable(ctx *gin.Context, providerId string, id string) error
Disable(ctx *gin.Context, providerId string, id string) error
Sort(ctx *gin.Context, providerId string, input *ai_key_dto.Sort) error
+11 -3
View File
@@ -1,6 +1,7 @@
package ai_key
import (
"encoding/json"
"strconv"
ai_key "github.com/APIParkLab/APIPark/module/ai-key"
@@ -38,7 +39,7 @@ func (i *imlAIKeyController) Get(ctx *gin.Context, providerId string, id string)
return i.module.Get(ctx, providerId, id)
}
func (i *imlAIKeyController) List(ctx *gin.Context, providerId string, keyword string, page string, pageSize string) ([]*ai_key_dto.Item, int64, error) {
func (i *imlAIKeyController) List(ctx *gin.Context, providerId string, keyword string, page string, pageSize string, statuses string) ([]*ai_key_dto.Item, int64, error) {
p, err := strconv.Atoi(page)
if err != nil {
if page != "" {
@@ -51,9 +52,16 @@ func (i *imlAIKeyController) List(ctx *gin.Context, providerId string, keyword s
if pageSize != "" {
return nil, 0, err
}
ps = 15
ps = 20
}
return i.module.List(ctx, providerId, keyword, p, ps)
ss := make([]string, 0)
if statuses != "" {
err = json.Unmarshal([]byte(statuses), &ss)
if err != nil {
return nil, 0, err
}
}
return i.module.List(ctx, providerId, keyword, p, ps, ss)
}
func (i *imlAIKeyController) Sort(ctx *gin.Context, providerId string, input *ai_key_dto.Sort) error {
+3 -4
View File
@@ -3,17 +3,16 @@ package ai
import (
"reflect"
"github.com/eolinker/go-common/auto"
ai_dto "github.com/APIParkLab/APIPark/module/ai/dto"
"github.com/eolinker/go-common/autowire"
"github.com/gin-gonic/gin"
)
type IProviderController interface {
ConfiguredProviders(ctx *gin.Context) ([]*ai_dto.ConfiguredProviderItem, *auto.Label, error)
ConfiguredProviders(ctx *gin.Context) ([]*ai_dto.ConfiguredProviderItem, *ai_dto.BackupProvider, error)
UnConfiguredProviders(ctx *gin.Context) ([]*ai_dto.ProviderItem, error)
SimpleProviders(ctx *gin.Context) ([]*ai_dto.SimpleProviderItem, error)
SimpleConfiguredProviders(ctx *gin.Context) ([]*ai_dto.SimpleProviderItem, *ai_dto.BackupProvider, error)
Provider(ctx *gin.Context, id string) (*ai_dto.Provider, error)
SimpleProvider(ctx *gin.Context, id string) (*ai_dto.SimpleProvider, error)
LLMs(ctx *gin.Context, driver string) ([]*ai_dto.LLMItem, *ai_dto.ProviderItem, error)
@@ -25,7 +24,7 @@ type IProviderController interface {
}
type IStatisticController interface {
APIs(ctx *gin.Context, keyword string, providerId string, start string, end string, page string, pageSize string, sortCondition string, asc string) ([]*ai_dto.APIItem, int64, error)
APIs(ctx *gin.Context, keyword string, providerId string, start string, end string, page string, pageSize string, sortCondition string, asc string, models string, services string) ([]*ai_dto.APIItem, *ai_dto.Condition, int64, error)
}
func init() {
+29 -12
View File
@@ -1,11 +1,11 @@
package ai
import (
"encoding/json"
"strconv"
"github.com/APIParkLab/APIPark/module/ai"
ai_dto "github.com/APIParkLab/APIPark/module/ai/dto"
"github.com/eolinker/go-common/auto"
"github.com/gin-gonic/gin"
)
@@ -21,7 +21,7 @@ func (i *imlProviderController) Sort(ctx *gin.Context, input *ai_dto.Sort) error
return i.module.Sort(ctx, input)
}
func (i *imlProviderController) ConfiguredProviders(ctx *gin.Context) ([]*ai_dto.ConfiguredProviderItem, *auto.Label, error) {
func (i *imlProviderController) ConfiguredProviders(ctx *gin.Context) ([]*ai_dto.ConfiguredProviderItem, *ai_dto.BackupProvider, error) {
return i.module.ConfiguredProviders(ctx)
}
@@ -33,6 +33,10 @@ func (i *imlProviderController) SimpleProviders(ctx *gin.Context) ([]*ai_dto.Sim
return i.module.SimpleProviders(ctx)
}
func (i *imlProviderController) SimpleConfiguredProviders(ctx *gin.Context) ([]*ai_dto.SimpleProviderItem, *ai_dto.BackupProvider, error) {
return i.module.SimpleConfiguredProviders(ctx)
}
func (i *imlProviderController) Provider(ctx *gin.Context, id string) (*ai_dto.Provider, error) {
return i.module.Provider(ctx, id)
}
@@ -46,11 +50,13 @@ func (i *imlProviderController) LLMs(ctx *gin.Context, driver string) ([]*ai_dto
}
func (i *imlProviderController) Enable(ctx *gin.Context, id string) error {
return i.module.UpdateProviderStatus(ctx, id, true)
//return i.module.UpdateProviderStatus(ctx, id, true)
return nil
}
func (i *imlProviderController) Disable(ctx *gin.Context, id string) error {
return i.module.UpdateProviderStatus(ctx, id, false)
//return i.module.UpdateProviderStatus(ctx, id, false)
return nil
}
func (i *imlProviderController) UpdateProviderConfig(ctx *gin.Context, id string, input *ai_dto.UpdateConfig) error {
@@ -58,7 +64,8 @@ func (i *imlProviderController) UpdateProviderConfig(ctx *gin.Context, id string
}
func (i *imlProviderController) UpdateProviderDefaultLLM(ctx *gin.Context, id string, input *ai_dto.UpdateLLM) error {
return i.module.UpdateProviderDefaultLLM(ctx, id, input)
//return i.module.UpdateProviderDefaultLLM(ctx, id, input)
return nil
}
var _ IStatisticController = (*imlStatisticController)(nil)
@@ -67,21 +74,21 @@ type imlStatisticController struct {
module ai.IAIAPIModule `autowired:""`
}
func (i *imlStatisticController) APIs(ctx *gin.Context, keyword string, providerId string, start string, end string, page string, pageSize string, sortCondition string, asc string) ([]*ai_dto.APIItem, int64, error) {
func (i *imlStatisticController) APIs(ctx *gin.Context, keyword string, providerId string, start string, end string, page string, pageSize string, sortCondition string, asc string, models string, services string) ([]*ai_dto.APIItem, *ai_dto.Condition, int64, error) {
s, err := strconv.ParseInt(start, 10, 64)
if err != nil {
return nil, 0, err
return nil, nil, 0, err
}
e, err := strconv.ParseInt(end, 10, 64)
if err != nil {
return nil, 0, err
return nil, nil, 0, err
}
p, err := strconv.Atoi(page)
if err != nil {
if page != "" {
return nil, 0, err
return nil, nil, 0, err
}
p = 1
}
@@ -89,9 +96,19 @@ func (i *imlStatisticController) APIs(ctx *gin.Context, keyword string, provider
ps, err := strconv.Atoi(pageSize)
if err != nil {
if pageSize != "" {
return nil, 0, err
return nil, nil, 0, err
}
ps = 15
ps = 20
}
return i.module.APIs(ctx, keyword, providerId, s, e, p, ps, sortCondition, asc == "true")
ms := make([]string, 0)
if models != "" {
json.Unmarshal([]byte(models), &ms)
ms = append(ms, models)
}
ss := make([]string, 0)
if services != "" {
json.Unmarshal([]byte(services), &ss)
ss = append(ss, services)
}
return i.module.APIs(ctx, keyword, providerId, s, e, p, ps, sortCondition, asc == "true", ms, ss)
}
+2 -2
View File
@@ -318,8 +318,8 @@ func (i *imlServiceController) Get(ctx *gin.Context, id string) (*service_dto.Se
return i.module.Get(ctx, id)
}
func (i *imlServiceController) Search(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error) {
return i.module.Search(ctx, teamID, keyword)
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) {
+1 -1
View File
@@ -15,7 +15,7 @@ type IServiceController interface {
Get(ctx *gin.Context, id string) (*service_dto.Service, error)
// SearchMyServices 搜索服务
SearchMyServices(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error)
Search(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error)
Search(ctx *gin.Context, teamIDs string, keyword string) ([]*service_dto.ServiceItem, error)
// Create 创建
Create(ctx *gin.Context, teamID string, input *service_dto.CreateService) (*service_dto.Service, error)
// Edit 编辑
+3 -3
View File
@@ -437,7 +437,7 @@ func (i *imlInitController) createAIService(ctx context.Context, teamID string,
plugins["ai_formatter"] = api.PluginSetting{
Config: plugin_model.ConfigType{
"model": aiModel.Id,
"provider": fmt.Sprintf("%s@ai-provider", info.Provider.Id),
"provider": info.Provider.Id,
"config": aiModel.Config,
},
}
@@ -457,8 +457,8 @@ func (i *imlInitController) createAIService(ctx context.Context, teamID string,
Retry: retry,
Plugins: plugins,
},
Disable: false,
Upstream: info.Provider.Id,
Disable: false,
//Upstream: info.Provider.Id,
})
if err != nil {
return err
+5 -2
View File
@@ -161,7 +161,7 @@ func ToRouter(r *gateway.ApiRelease, version string, matches map[string]string)
labels = r.Labels
}
return &Router{
router := &Router{
BasicInfo: &BasicInfo{
ID: fmt.Sprintf("%s@router", r.ID),
Name: r.ID,
@@ -174,13 +174,16 @@ func ToRouter(r *gateway.ApiRelease, version string, matches map[string]string)
Method: r.Methods,
Location: r.Path,
Rules: rules,
Service: fmt.Sprintf("%s@service", r.Service),
Plugins: plugin,
Retry: r.Retry,
TimeOut: r.Timeout,
Labels: labels,
Protocols: []string{"http", "https"},
}
if r.Service != "" {
router.Service = fmt.Sprintf("%s@service", r.Service)
}
return router
}
// formatProxyPath 格式化转发路径上,用于转发重写插件正则替换 比如 请求路径 /path/{A}/{B} 原转发路径:/path/{B} 格式化后 新转发路径: /path/$2
+9
View File
@@ -8,6 +8,7 @@ const (
ProfessionStrategy = "strategy"
ProfessionService = "service"
ProfessionAIProvider = "ai-provider"
ProfessionAIResource = "ai-resource"
)
func RegisterDynamicResourceDriver(key string, worker Worker) {
@@ -61,6 +62,14 @@ var dynamicResourceMap = map[string]Worker{
Profession: ProfessionOutput,
Driver: "loki",
},
"ai-provider": {
Profession: ProfessionAIResource,
Driver: "ai-provider",
},
"ai-key": {
Profession: ProfessionAIResource,
Driver: "ai-key",
},
}
type Worker struct {
+4 -2
View File
@@ -7,12 +7,14 @@ go 1.21
require (
github.com/eolinker/ap-account v1.0.15
github.com/eolinker/eosc v0.18.3
github.com/eolinker/go-common v1.1.3
github.com/eolinker/go-common v1.1.4
github.com/gabriel-vasile/mimetype v1.4.4
github.com/getkin/kin-openapi v0.127.0
github.com/gin-gonic/gin v1.10.0
github.com/go-sql-driver/mysql v1.7.0
github.com/google/uuid v1.6.0
github.com/influxdata/influxdb-client-go/v2 v2.14.0
github.com/nsqio/go-nsq v1.1.0
github.com/urfave/cli v1.22.16
golang.org/x/crypto v0.24.0
gopkg.in/yaml.v3 v3.0.1
@@ -37,8 +39,8 @@ require (
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
+7 -2
View File
@@ -32,8 +32,8 @@ github.com/eolinker/ap-account v1.0.15 h1:n6DJeL6RHZ8eLlZUcY2U3H4d/GPaA5oelAx3R0
github.com/eolinker/ap-account v1.0.15/go.mod h1:zm/Ivs6waJ/M/nEszhpPmM6g50y/MKO+5eABFAdeD0g=
github.com/eolinker/eosc v0.18.3 h1:3IK5HkAPnJRfLbQ0FR7kWsZr6Y/OiqqGazvN1q2BL5A=
github.com/eolinker/eosc v0.18.3/go.mod h1:O9PQQXFCpB6fjHf+oFt/LN6EOAv779ItbMixMKCfTfk=
github.com/eolinker/go-common v1.1.3 h1:Chb0x6sj0hZKpIN6Qo9IMdi9pX2KLNUPmqcaAD8mmWs=
github.com/eolinker/go-common v1.1.3/go.mod h1:Kb/jENMN1mApnodvRgV4YwO9FJby1Jkt2EUjrBjvSX4=
github.com/eolinker/go-common v1.1.4 h1:U5AtQMr3RCudgeV6jcX5TemUGrTz8WqLu//KrZm3BzA=
github.com/eolinker/go-common v1.1.4/go.mod h1:Kb/jENMN1mApnodvRgV4YwO9FJby1Jkt2EUjrBjvSX4=
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/getkin/kin-openapi v0.127.0 h1:Mghqi3Dhryf3F8vR370nN67pAERW+3a95vomb3MAREY=
@@ -64,6 +64,9 @@ github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -107,6 +110,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/nsqio/go-nsq v1.1.0 h1:PQg+xxiUjA7V+TLdXw7nVrJ5Jbl3sN86EhGCQj4+FYE=
github.com/nsqio/go-nsq v1.1.0/go.mod h1:vKq36oyeVXgsS5Q8YEO7WghqidAVXQlcFxzQbQTuDEY=
github.com/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo=
github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
+2 -2
View File
@@ -5,7 +5,7 @@ type CreateAPI struct {
Name string `json:"name"`
Path string `json:"path"`
Description string `json:"description"`
Disable bool `json:"disable"`
Disable bool `json:"disabled"`
AiPrompt *AiPrompt `json:"ai_prompt"`
AiModel *AiModel `json:"ai_model"`
Timeout int `json:"timeout"`
@@ -33,7 +33,7 @@ type EditAPI struct {
Name *string `json:"name"`
Path *string `json:"path"`
Description *string `json:"description"`
Disable *bool `json:"disable"`
Disable *bool `json:"disabled"`
AiPrompt *AiPrompt `json:"ai_prompt"`
AiModel *AiModel `json:"ai_model"`
Timeout *int `json:"timeout"`
+2 -15
View File
@@ -9,7 +9,7 @@ type API struct {
Name string `json:"name"`
Path string `json:"path"`
Description string `json:"description"`
Disable bool `json:"disable"`
Disable bool `json:"disabled"`
AiPrompt *AiPrompt `json:"ai_prompt"`
AiModel *AiModel `json:"ai_model"`
Timeout int `json:"timeout"`
@@ -21,7 +21,7 @@ type APIItem struct {
Name string `json:"name"`
RequestPath string `json:"request_path"`
Description string `json:"description"`
Disable bool `json:"disable"`
Disable bool `json:"disabled"`
Creator auto.Label `json:"creator" aolabel:"user"`
Updater auto.Label `json:"updater" aolabel:"user"`
CreateTime auto.TimeLabel `json:"create_time"`
@@ -30,19 +30,6 @@ type APIItem struct {
Model ModelItem `json:"model"`
}
type AIAPIItem struct {
Id string `json:"id"`
Name string `json:"name"`
Service auto.Label `json:"service" aolabel:"service"`
Method string `json:"method"`
RequestPath string `json:"request_path"`
Model ModelItem `json:"model"`
Provider ProviderItem `json:"provider"`
UpdateTime auto.TimeLabel `json:"update_time"`
UseToken int64 `json:"use_token"`
Disable bool `json:"disable"`
}
type ModelItem struct {
Id string `json:"id"`
Logo string `json:"logo"`
+168 -25
View File
@@ -6,6 +6,13 @@ import (
"fmt"
"time"
"github.com/APIParkLab/APIPark/service/cluster"
"github.com/eolinker/eosc/log"
"github.com/APIParkLab/APIPark/gateway"
"github.com/eolinker/go-common/utils"
"gorm.io/gorm"
"github.com/eolinker/go-common/auto"
@@ -25,9 +32,32 @@ import (
var _ IKeyModule = &imlKeyModule{}
type imlKeyModule struct {
providerService ai.IProviderService `autowired:""`
aiKeyService ai_key.IKeyService `autowired:""`
transaction store.ITransaction `autowired:""`
providerService ai.IProviderService `autowired:""`
aiKeyService ai_key.IKeyService `autowired:""`
clusterService cluster.IClusterService `autowired:""`
transaction store.ITransaction `autowired:""`
}
func newKey(key *ai_key.Key) *gateway.DynamicRelease {
return &gateway.DynamicRelease{
BasicItem: &gateway.BasicItem{
ID: fmt.Sprintf("%s-%s", key.Provider, key.ID),
Description: key.Name,
Resource: "ai-key",
Version: time.Now().Format("20060102150405"),
MatchLabels: map[string]string{
"module": "ai-key",
},
},
Attr: map[string]interface{}{
"expired": key.ExpireTime,
"config": key.Config,
"provider": key.Provider,
"priority": key.Priority,
"disabled": key.Status == 0,
},
}
}
func (i *imlKeyModule) Create(ctx context.Context, providerId string, input *ai_key_dto.Create) error {
@@ -39,6 +69,7 @@ func (i *imlKeyModule) Create(ctx context.Context, providerId string, input *ai_
if !has {
return fmt.Errorf("provider not found: %w", err)
}
p.URI()
err = p.Check(input.Config)
if err != nil {
return fmt.Errorf("config check failed: %w", err)
@@ -54,11 +85,9 @@ func (i *imlKeyModule) Create(ctx context.Context, providerId string, input *ai_
status := ai_key_dto.KeyNormal.Int()
if input.ExpireTime > 0 && time.Unix(int64(input.ExpireTime), 0).Before(time.Now()) {
status = ai_key_dto.KeyExpired.Int()
} else {
// TODO: 发布Key到网关
}
return i.aiKeyService.Create(ctx, &ai_key.Create{
err = i.aiKeyService.Create(ctx, &ai_key.Create{
ID: input.Id,
Name: input.Name,
Config: input.Config,
@@ -67,9 +96,43 @@ func (i *imlKeyModule) Create(ctx context.Context, providerId string, input *ai_
ExpireTime: input.ExpireTime,
Priority: priority + 1,
})
info, _ := i.aiKeyService.Get(ctx, input.Id)
releases := []*gateway.DynamicRelease{newKey(info)}
return i.syncGateway(ctx, cluster.DefaultClusterID, releases, true)
})
}
func (i *imlKeyModule) syncGateway(ctx context.Context, clusterId string, releases []*gateway.DynamicRelease, online bool) error {
client, err := i.clusterService.GatewayClient(ctx, clusterId)
if err != nil {
log.Errorf("get apinto client error: %v", err)
return nil
}
defer func() {
err := client.Close(ctx)
if err != nil {
log.Warn("close apinto client:", err)
}
}()
for _, releaseInfo := range releases {
dynamicClient, err := client.Dynamic(releaseInfo.Resource)
if err != nil {
return err
}
if online {
err = dynamicClient.Online(ctx, releaseInfo)
} else {
err = dynamicClient.Offline(ctx, releaseInfo)
}
if err != nil {
return err
}
}
return nil
}
func (i *imlKeyModule) Edit(ctx context.Context, providerId string, id string, input *ai_key_dto.Edit) error {
p, has := model_runtime.GetProvider(providerId)
if !has {
@@ -89,7 +152,7 @@ func (i *imlKeyModule) Edit(ctx context.Context, providerId string, id string, i
if err != nil {
return fmt.Errorf("config check failed: %w", err)
}
cfg, err := p.GenConfig(info.Config, *input.Config)
cfg, err := p.GenConfig(*input.Config, info.Config)
if err != nil {
return fmt.Errorf("config gen failed: %w", err)
}
@@ -121,16 +184,25 @@ func (i *imlKeyModule) Edit(ctx context.Context, providerId string, id string, i
// 停用、超额需要启用,所以维持原状态
status = orgStatus.Int()
}
if status == ai_key_dto.KeyNormal.Int() {
// TODO: 发布Key到网关
}
return i.aiKeyService.Save(ctx, id, &ai_key.Edit{
err = i.aiKeyService.Save(ctx, id, &ai_key.Edit{
Name: input.Name,
Config: input.Config,
ExpireTime: input.ExpireTime,
Status: &status,
})
if err != nil {
return err
}
if status == ai_key_dto.KeyNormal.Int() {
info, err = i.aiKeyService.Get(ctx, id)
if err != nil {
return err
}
releases := []*gateway.DynamicRelease{newKey(info)}
return i.syncGateway(ctx, cluster.DefaultClusterID, releases, true)
}
return nil
})
}
@@ -165,8 +237,18 @@ func (i *imlKeyModule) Delete(ctx context.Context, providerId string, id string)
}
}
// TODO: 操作网关下线Key
return i.aiKeyService.Delete(ctx, id)
err = i.aiKeyService.Delete(ctx, id)
if err != nil {
return err
}
return i.syncGateway(ctx, cluster.DefaultClusterID, []*gateway.DynamicRelease{{
BasicItem: &gateway.BasicItem{
ID: fmt.Sprintf("%s-%s", providerId, id),
Resource: "ai-key",
},
Attr: nil,
},
}, false)
})
}
@@ -192,7 +274,7 @@ func (i *imlKeyModule) Get(ctx context.Context, providerId string, id string) (*
}, nil
}
func (i *imlKeyModule) List(ctx context.Context, providerId string, keyword string, page, pageSize int) ([]*ai_key_dto.Item, int64, error) {
func (i *imlKeyModule) List(ctx context.Context, providerId string, keyword string, page, pageSize int, statuses []string) ([]*ai_key_dto.Item, int64, error) {
_, err := i.aiKeyService.DefaultKey(ctx, providerId)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
@@ -217,12 +299,49 @@ func (i *imlKeyModule) List(ctx context.Context, providerId string, keyword stri
return nil, 0, fmt.Errorf("create default key failed: %w", err)
}
}
list, total, err := i.aiKeyService.SearchByPage(ctx, keyword, map[string]interface{}{
w := map[string]interface{}{
"provider": providerId,
}, page, pageSize, "sort asc")
if err != nil {
return nil, 0, err
}
hasExpired := true
if len(statuses) > 0 {
hasExpired = false
statusConditions := make([]int, 0, len(statuses))
for _, s := range statuses {
status := ai_key_dto.KeyStatus(s)
if status == ai_key_dto.KeyExpired {
hasExpired = true
}
statusConditions = append(statusConditions, status.Int())
}
w["status"] = statusConditions
}
var list []*ai_key.Key
var total int64
if !hasExpired {
if keyword != "" {
list, err = i.aiKeyService.Search(ctx, keyword, w, "sort asc")
if err != nil {
return nil, 0, err
}
if len(list) == 0 {
return nil, 0, nil
}
uuids := utils.SliceToSlice(list, func(key *ai_key.Key) string {
return key.ID
})
w["uuid"] = uuids
}
list, total, err = i.aiKeyService.SearchUnExpiredByPage(ctx, w, page, pageSize, "sort asc")
if err != nil {
return nil, 0, err
}
} else {
list, total, err = i.aiKeyService.SearchByPage(ctx, keyword, w, page, pageSize, "sort asc")
if err != nil {
return nil, 0, err
}
}
var result []*ai_key_dto.Item
for _, item := range list {
status := ai_key_dto.ToKeyStatus(item.Status)
@@ -255,11 +374,21 @@ func (i *imlKeyModule) UpdateKeyStatus(ctx context.Context, providerId string, i
}
return i.transaction.Transaction(ctx, func(ctx context.Context) error {
if !enable {
// TODO:下线Key
status := ai_key_dto.KeyDisable.Int()
return i.aiKeyService.Save(ctx, id, &ai_key.Edit{
err = i.aiKeyService.Save(ctx, id, &ai_key.Edit{
Status: &status,
})
if err != nil {
return err
}
releases := []*gateway.DynamicRelease{{
BasicItem: &gateway.BasicItem{
ID: id,
Resource: "ai-key",
},
Attr: nil,
}}
return i.syncGateway(ctx, providerId, releases, false)
}
if info.Status == ai_key_dto.KeyDisable.Int() || info.Status == ai_key_dto.KeyExceed.Int() {
// 超额 或 停用状态,可启用
@@ -270,11 +399,19 @@ func (i *imlKeyModule) UpdateKeyStatus(ctx context.Context, providerId string, i
Status: &status,
})
}
// TODO:发布Key到网关
status := ai_key_dto.KeyNormal.Int()
return i.aiKeyService.Save(ctx, id, &ai_key.Edit{
err = i.aiKeyService.Save(ctx, id, &ai_key.Edit{
Status: &status,
})
if err != nil {
return err
}
info, err = i.aiKeyService.Get(ctx, id)
if err != nil {
return err
}
releases := []*gateway.DynamicRelease{newKey(info)}
return i.syncGateway(ctx, providerId, releases, true)
}
return nil
})
@@ -297,8 +434,14 @@ func (i *imlKeyModule) Sort(ctx context.Context, providerId string, input *ai_ke
if err != nil {
return err
}
// TODO: 全量更新key配置到网关
return nil
list, err := i.aiKeyService.KeysByProvider(ctx, providerId)
if err != nil {
return err
}
releases := make([]*gateway.DynamicRelease, 0, len(list))
for _, info := range list {
releases = append(releases, newKey(info))
}
return i.syncGateway(ctx, cluster.DefaultClusterID, releases, true)
})
}
+1 -1
View File
@@ -13,7 +13,7 @@ type IKeyModule interface {
Edit(ctx context.Context, providerId string, id string, input *ai_key_dto.Edit) error
Delete(ctx context.Context, providerId string, id string) error
Get(ctx context.Context, providerId string, id string) (*ai_key_dto.Key, error)
List(ctx context.Context, providerId string, keyword string, page, pageSize int) ([]*ai_key_dto.Item, int64, error)
List(ctx context.Context, providerId string, keyword string, page, pageSize int, statuses []string) ([]*ai_key_dto.Item, int64, error)
UpdateKeyStatus(ctx context.Context, providerId string, id string, enable bool) error
Sort(ctx context.Context, providerId string, input *ai_key_dto.Sort) error
}
+5 -5
View File
@@ -13,9 +13,9 @@ func (p ProviderStatus) Int() int {
case ProviderAbnormal:
return 2
case ProviderEnabled:
return 0
case ProviderDisabled:
return 1
case ProviderDisabled:
return 0
default:
return 1
}
@@ -30,10 +30,10 @@ func ToProviderStatus(status int) ProviderStatus {
case 2:
return ProviderAbnormal
case 0:
return ProviderEnabled
return ProviderDisabled
case 1:
return ProviderDisabled
return ProviderEnabled
default:
return ProviderDisabled
return ProviderEnabled
}
}
+37 -14
View File
@@ -5,10 +5,11 @@ import (
)
type SimpleProvider struct {
Id string `json:"id"`
Name string `json:"name"`
Logo string `json:"logo"`
GetAPIKeyUrl string `json:"get_apikey_url"`
Id string `json:"id"`
Name string `json:"name"`
DefaultConfig string `json:"default_config"`
Logo string `json:"logo"`
GetAPIKeyUrl string `json:"get_apikey_url"`
}
type Provider struct {
@@ -16,10 +17,11 @@ type Provider struct {
Name string `json:"name"`
Config string `json:"config"`
GetAPIKeyUrl string `json:"get_apikey_url"`
DefaultLLM string `json:"defaultLLM"`
DefaultLLM string `json:"default_llm"`
DefaultLLMConfig string `json:"-"`
Priority int `json:"priority"`
Status ProviderStatus `json:"status"`
Configured bool `json:"configured"`
}
type ConfiguredProviderItem struct {
@@ -30,14 +32,15 @@ type ConfiguredProviderItem struct {
Status ProviderStatus `json:"status"`
APICount int64 `json:"api_count"`
KeyCount int `json:"key_count"`
KeyStatus []*KeyStatus `json:"key_status"`
KeyStatus []*KeyStatus `json:"keys"`
Priority int `json:"priority"`
}
type KeyStatus struct {
Id string `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
Id string `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
Priority int `json:"-"`
}
type ProviderItem struct {
@@ -49,11 +52,20 @@ type ProviderItem struct {
}
type SimpleProviderItem struct {
Id string `json:"id"`
Name string `json:"name"`
Logo string `json:"logo"`
Configured bool `json:"configured"`
Status ProviderStatus `json:"status"`
Id string `json:"id"`
Name string `json:"name"`
Logo string `json:"logo"`
Configured bool `json:"configured"`
DefaultConfig string `json:"default_config"`
Status ProviderStatus `json:"status"`
Model *BasicInfo `json:"model,omitempty"`
Priority int `json:"-"`
}
type BackupProvider struct {
Id string `json:"id"`
Name string `json:"name"`
Model *BasicInfo `json:"model,omitempty"`
}
type LLMItem struct {
@@ -67,6 +79,7 @@ type APIItem struct {
Id string `json:"id"`
Name string `json:"name"`
Service auto.Label `json:"service" aolabel:"service"`
Team auto.Label `json:"team" aolabel:"team"`
Method string `json:"method"`
RequestPath string `json:"request_path"`
Model auto.Label `json:"model"`
@@ -74,3 +87,13 @@ type APIItem struct {
UseToken int `json:"use_token"`
Disable bool `json:"disable"`
}
type Condition struct {
Models []*BasicInfo `json:"models"`
Services []*BasicInfo `json:"services"`
}
type BasicInfo struct {
Id string `json:"id"`
Name string `json:"name"`
}
+247 -149
View File
@@ -2,13 +2,14 @@ package ai
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"sort"
"time"
"github.com/APIParkLab/APIPark/service/service"
ai_key_dto "github.com/APIParkLab/APIPark/module/ai-key/dto"
ai_key "github.com/APIParkLab/APIPark/service/ai-key"
@@ -28,24 +29,24 @@ import (
"gorm.io/gorm"
)
func newAIUpstream(provider string, uri model_runtime.IProviderURI) *gateway.DynamicRelease {
func newKey(key *ai_key.Key) *gateway.DynamicRelease {
return &gateway.DynamicRelease{
BasicItem: &gateway.BasicItem{
ID: provider,
Description: fmt.Sprintf("auto create by ai provider %s", provider),
Resource: "service",
ID: fmt.Sprintf("%s-%s", key.Provider, key.ID),
Description: key.Name,
Resource: "ai-key",
Version: time.Now().Format("20060102150405"),
MatchLabels: map[string]string{
"module": "service",
"module": "ai-key",
},
},
Attr: map[string]interface{}{
"driver": "http",
"balance": "round-robin",
"nodes": []string{fmt.Sprintf("%s weight=100", uri.Host())},
"pass_host": "node",
"scheme": uri.Scheme(),
"timeout": 300000,
"expired": key.ExpireTime,
"config": key.Config,
"provider": key.Provider,
"priority": key.Priority,
"disabled": key.Status == 0,
},
}
}
@@ -66,10 +67,11 @@ func (i *imlProviderModule) SimpleProvider(ctx context.Context, id string) (*ai_
return nil, fmt.Errorf("ai provider not found")
}
return &ai_dto.SimpleProvider{
Id: p.ID(),
Name: p.Name(),
Logo: p.Logo(),
GetAPIKeyUrl: p.HelpUrl(),
Id: p.ID(),
Name: p.Name(),
Logo: p.Logo(),
DefaultConfig: p.DefaultConfig(),
GetAPIKeyUrl: p.HelpUrl(),
}, nil
}
@@ -82,8 +84,19 @@ func (i *imlProviderModule) Sort(ctx context.Context, input *ai_dto.Sort) error
providerMap := utils.SliceToMap(list, func(e *ai.Provider) string {
return e.Id
})
releases := make([]*gateway.DynamicRelease, 0, len(list))
offlineReleases := make([]*gateway.DynamicRelease, 0, len(list))
for index, id := range input.Providers {
_, has := providerMap[id]
p, has := model_runtime.GetProvider(id)
if !has {
continue
}
l, has := providerMap[id]
if !has {
continue
}
model, has := p.GetModel(l.DefaultLLM)
if !has {
continue
}
@@ -94,12 +107,43 @@ func (i *imlProviderModule) Sort(ctx context.Context, input *ai_dto.Sort) error
if err != nil {
return err
}
if ai_dto.ToProviderStatus(l.Status) == ai_dto.ProviderDisabled {
offlineReleases = append(offlineReleases, &gateway.DynamicRelease{
BasicItem: &gateway.BasicItem{
ID: l.Id,
Resource: "ai-provider",
}})
} else {
cfg := make(map[string]interface{})
cfg["provider"] = l.Id
cfg["model"] = l.DefaultLLM
cfg["model_config"] = model.DefaultConfig()
cfg["priority"] = l.Priority
cfg["base"] = fmt.Sprintf("%s://%s", p.URI().Scheme(), p.URI().Host())
releases = append(releases, &gateway.DynamicRelease{
BasicItem: &gateway.BasicItem{
ID: l.Id,
Description: l.Name,
Resource: "ai-provider",
Version: l.UpdateAt.Format("20060102150405"),
MatchLabels: map[string]string{
"module": "ai-provider",
},
},
Attr: cfg,
})
}
}
return nil
err = i.syncGateway(ctx, cluster.DefaultClusterID, releases, true)
if err != nil {
return err
}
return i.syncGateway(ctx, cluster.DefaultClusterID, offlineReleases, false)
})
}
func (i *imlProviderModule) ConfiguredProviders(ctx context.Context) ([]*ai_dto.ConfiguredProviderItem, *auto.Label, error) {
func (i *imlProviderModule) ConfiguredProviders(ctx context.Context) ([]*ai_dto.ConfiguredProviderItem, *ai_dto.BackupProvider, error) {
// 获取已配置的AI服务商
list, err := i.providerService.List(ctx)
if err != nil {
@@ -151,11 +195,15 @@ func (i *imlProviderModule) ConfiguredProviders(ctx context.Context) ([]*ai_dto.
status = ai_key_dto.KeyError
}
keysStatus = append(keysStatus, &ai_dto.KeyStatus{
Id: k.ID,
Name: k.Name,
Status: status.String(),
Id: k.ID,
Name: k.Name,
Status: status.String(),
Priority: k.Priority,
})
}
sort.Slice(keysStatus, func(i, j int) bool {
return keysStatus[i].Priority < keysStatus[j].Priority
})
providers = append(providers, &ai_dto.ConfiguredProviderItem{
Id: l.Id,
@@ -181,10 +229,10 @@ func (i *imlProviderModule) ConfiguredProviders(ctx context.Context) ([]*ai_dto.
}
return providers[i].Name < providers[j].Name
})
var backup *auto.Label
var backup *ai_dto.BackupProvider
for _, p := range providers {
if p.Status == ai_dto.ProviderEnabled {
backup = &auto.Label{
backup = &ai_dto.BackupProvider{
Id: p.Id,
Name: p.Name,
}
@@ -207,20 +255,94 @@ func (i *imlProviderModule) SimpleProviders(ctx context.Context) ([]*ai_dto.Simp
items := make([]*ai_dto.SimpleProviderItem, 0, len(providers))
for _, v := range providers {
item := &ai_dto.SimpleProviderItem{
Id: v.ID(),
Name: v.Name(),
Logo: v.Logo(),
Status: ai_dto.ProviderDisabled,
Id: v.ID(),
Name: v.Name(),
Logo: v.Logo(),
DefaultConfig: v.DefaultConfig(),
Status: ai_dto.ProviderDisabled,
}
if info, has := providerMap[v.ID()]; has {
item.Configured = true
item.Status = ai_dto.ToProviderStatus(info.Status)
item.Priority = info.Priority
}
items = append(items, item)
}
sort.Slice(items, func(i, j int) bool {
if items[i].Priority != items[j].Priority {
if items[i].Priority == 0 {
return false
}
if items[j].Priority == 0 {
return true
}
return items[i].Priority < items[j].Priority
}
return items[i].Name < items[j].Name
})
return items, nil
}
func (i *imlProviderModule) SimpleConfiguredProviders(ctx context.Context) ([]*ai_dto.SimpleProviderItem, *ai_dto.BackupProvider, error) {
list, err := i.providerService.List(ctx)
if err != nil {
return nil, nil, err
}
items := make([]*ai_dto.SimpleProviderItem, 0, len(list))
var backup *ai_dto.BackupProvider
for _, l := range list {
p, has := model_runtime.GetProvider(l.Id)
if !has {
continue
}
model, has := p.GetModel(l.DefaultLLM)
if !has {
model, has = p.DefaultModel(model_runtime.ModelTypeLLM)
if !has {
continue
}
}
item := &ai_dto.SimpleProviderItem{
Id: l.Id,
Name: l.Name,
Logo: p.Logo(),
DefaultConfig: p.DefaultConfig(),
Status: ai_dto.ToProviderStatus(l.Status),
Priority: l.Priority,
Configured: true,
Model: &ai_dto.BasicInfo{
Id: model.ID(),
Name: model.ID(),
},
}
items = append(items, item)
}
sort.Slice(items, func(i, j int) bool {
if items[i].Priority != items[j].Priority {
if items[i].Priority == 0 {
return false
}
if items[j].Priority == 0 {
return true
}
return items[i].Priority < items[j].Priority
}
return items[i].Name < items[j].Name
})
for _, item := range items {
if item.Status == ai_dto.ProviderEnabled {
backup = &ai_dto.BackupProvider{
Id: item.Id,
Name: item.Name,
Model: item.Model,
}
break
}
}
return items, backup, nil
}
func (i *imlProviderModule) UnConfiguredProviders(ctx context.Context) ([]*ai_dto.ProviderItem, error) {
list, err := i.providerService.List(ctx)
if err != nil {
@@ -313,7 +435,8 @@ func (i *imlProviderModule) Provider(ctx context.Context, id string) (*ai_dto.Pr
DefaultLLM: defaultLLM.ID(),
DefaultLLMConfig: defaultLLM.DefaultConfig(),
Priority: info.Priority,
Status: ai_dto.ProviderEnabled,
Status: ai_dto.ToProviderStatus(info.Status),
Configured: true,
}, nil
}
@@ -364,72 +487,6 @@ func (i *imlProviderModule) LLMs(ctx context.Context, driver string) ([]*ai_dto.
}, nil
}
func (i *imlProviderModule) UpdateProviderStatus(ctx context.Context, id string, enable bool) error {
driver, has := model_runtime.GetProvider(id)
if !has {
return fmt.Errorf("ai provider not found")
}
info, err := i.providerService.Get(ctx, id)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
return fmt.Errorf("ai provider not found")
}
return i.transaction.Transaction(ctx, func(txCtx context.Context) error {
status := 0
if enable {
status = 1
}
err = i.providerService.Save(txCtx, id, &ai.SetProvider{
Status: &status,
})
if err != nil {
return err
}
if enable {
cfg := make(map[string]interface{})
err = json.Unmarshal([]byte(info.Config), &cfg)
if err != nil {
log.Errorf("unmarshal ai provider config error,id is %s,err is %v", info.Id, err)
return err
}
cfg["driver"] = info.Id
return i.syncGateway(txCtx, cluster.DefaultClusterID, []*gateway.DynamicRelease{{
BasicItem: &gateway.BasicItem{
ID: info.Id,
Description: info.Name,
Version: info.UpdateAt.Format("20060102150405"),
MatchLabels: map[string]string{
"module": "ai-provider",
},
},
Attr: cfg,
}, newAIUpstream(info.Id, driver.URI()),
}, enable)
} else {
return i.syncGateway(txCtx, cluster.DefaultClusterID, []*gateway.DynamicRelease{
{
BasicItem: &gateway.BasicItem{
ID: info.Id,
Resource: info.Id,
},
},
{
BasicItem: &gateway.BasicItem{
ID: info.Id,
Resource: "service",
},
},
}, enable)
}
})
}
func (i *imlProviderModule) UpdateProviderConfig(ctx context.Context, id string, input *ai_dto.UpdateConfig) error {
p, has := model_runtime.GetProvider(id)
if !has {
@@ -454,6 +511,10 @@ func (i *imlProviderModule) UpdateProviderConfig(ctx context.Context, id string,
Config: input.Config,
}
}
model, has := p.GetModel(input.DefaultLLM)
if !has {
return fmt.Errorf("ai provider model not found")
}
err = p.Check(input.Config)
if err != nil {
return err
@@ -486,6 +547,7 @@ func (i *imlProviderModule) UpdateProviderConfig(ctx context.Context, id string,
Status: 1,
ExpireTime: 0,
Default: true,
Priority: 1,
})
} else {
err = i.aiKeyService.Save(ctx, id, &ai_key.Edit{
@@ -495,6 +557,7 @@ func (i *imlProviderModule) UpdateProviderConfig(ctx context.Context, id string,
if err != nil {
return err
}
if input.Enable != nil {
status = 0
if *input.Enable {
@@ -506,62 +569,73 @@ func (i *imlProviderModule) UpdateProviderConfig(ctx context.Context, id string,
if err != nil {
return err
}
cfg := make(map[string]interface{})
err = json.Unmarshal([]byte(input.Config), &cfg)
if *pInfo.Status == 0 {
return i.syncGateway(ctx, cluster.DefaultClusterID, []*gateway.DynamicRelease{
{
BasicItem: &gateway.BasicItem{
ID: id,
Resource: "ai-provider",
},
},
}, false)
}
// 获取当前供应商所有Key信息
defaultKey, err := i.aiKeyService.DefaultKey(ctx, id)
if err != nil {
log.Errorf("unmarshal ai provider config error,id is %s,err is %v", id, err)
return err
}
cfg := make(map[string]interface{})
cfg["provider"] = info.Id
cfg["model"] = info.DefaultLLM
cfg["model_config"] = model.DefaultConfig()
cfg["priority"] = info.Priority
cfg["base"] = fmt.Sprintf("%s://%s", p.URI().Scheme(), p.URI().Host())
return i.syncGateway(ctx, cluster.DefaultClusterID, []*gateway.DynamicRelease{
{
BasicItem: &gateway.BasicItem{
ID: id,
Description: info.Name,
Resource: id,
Resource: "ai-provider",
Version: info.UpdateAt.Format("20060102150405"),
MatchLabels: map[string]string{
"module": "ai-provider",
},
},
Attr: cfg,
}, newAIUpstream(id, p.URI()),
}, newKey(defaultKey),
}, true)
})
}
func (i *imlProviderModule) UpdateProviderDefaultLLM(ctx context.Context, id string, input *ai_dto.UpdateLLM) error {
_, err := i.providerService.Get(ctx, id)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
return fmt.Errorf("ai provider not found")
}
return i.providerService.Save(ctx, id, &ai.SetProvider{
DefaultLLM: &input.LLM,
})
}
func (i *imlProviderModule) getAiProviders(ctx context.Context) ([]*gateway.DynamicRelease, error) {
list, err := i.providerService.List(ctx)
if err != nil {
return nil, err
}
providers := make([]*gateway.DynamicRelease, 0, len(list))
for _, p := range list {
cfg := make(map[string]interface{})
err = json.Unmarshal([]byte(p.Config), &cfg)
if err != nil {
log.Errorf("unmarshal ai provider config error,id is %s,err is %v", p.Id, err)
continue
for _, l := range list {
// 获取当前供应商所有Key信息
driver, has := model_runtime.GetProvider(l.Id)
if !has {
return nil, fmt.Errorf("provider not found: %s", l.Id)
}
model, has := driver.GetModel(l.DefaultLLM)
if !has {
return nil, fmt.Errorf("model not found: %s", l.DefaultLLM)
}
cfg := make(map[string]interface{})
cfg["provider"] = l.Id
cfg["model"] = l.DefaultLLM
cfg["model_config"] = model.DefaultConfig()
cfg["priority"] = l.Priority
providers = append(providers, &gateway.DynamicRelease{
BasicItem: &gateway.BasicItem{
ID: p.Id,
Description: p.Name,
Resource: p.Id,
Version: p.UpdateAt.Format("20060102150405"),
ID: l.Id,
Description: l.Name,
Resource: "ai-provider",
Version: l.UpdateAt.Format("20060102150405"),
MatchLabels: map[string]string{
"module": "ai-provider",
},
@@ -577,16 +651,9 @@ func (i *imlProviderModule) initGateway(ctx context.Context, clusterId string, c
if err != nil {
return err
}
serviceClient, err := clientDriver.Dynamic("service")
if err != nil {
return err
}
for _, p := range providers {
driver, has := model_runtime.GetProvider(p.ID)
if !has {
continue
}
client, err := clientDriver.Dynamic(p.ID)
client, err := clientDriver.Dynamic(p.Resource)
if err != nil {
return err
}
@@ -594,12 +661,6 @@ func (i *imlProviderModule) initGateway(ctx context.Context, clusterId string, c
if err != nil {
return err
}
err = serviceClient.Online(ctx, newAIUpstream(p.ID, driver.URI()))
if err != nil {
return err
}
}
return nil
@@ -625,7 +686,7 @@ func (i *imlProviderModule) syncGateway(ctx context.Context, clusterId string, r
if online {
err = dynamicClient.Online(ctx, releaseInfo)
} else {
err = dynamicClient.Offline(ctx, releaseInfo)
dynamicClient.Offline(ctx, releaseInfo)
}
if err != nil {
return err
@@ -638,26 +699,60 @@ func (i *imlProviderModule) syncGateway(ctx context.Context, clusterId string, r
var _ IAIAPIModule = (*imlAIApiModule)(nil)
type imlAIApiModule struct {
aiAPIService ai_api.IAPIService `autowired:""`
aiAPIUseService ai_api.IAPIUseService `autowired:""`
aiAPIService ai_api.IAPIService `autowired:""`
aiAPIUseService ai_api.IAPIUseService `autowired:""`
serviceService service.IServiceService `autowired:""`
}
func (i *imlAIApiModule) APIs(ctx context.Context, keyword string, providerId string, start int64, end int64, page int, pageSize int, sortCondition string, asc bool) ([]*ai_dto.APIItem, int64, error) {
func (i *imlAIApiModule) APIs(ctx context.Context, keyword string, providerId string, start int64, end int64, page int, pageSize int, sortCondition string, asc bool, models []string, serviceIds []string) ([]*ai_dto.APIItem, *ai_dto.Condition, int64, error) {
p, has := model_runtime.GetProvider(providerId)
if !has {
return nil, nil, 0, fmt.Errorf("ai provider not found")
}
sortRule := "desc"
if asc {
sortRule = "asc"
}
services, err := i.serviceService.ServiceListByKind(ctx, service.AIService)
if err != nil {
return nil, nil, 0, err
}
serviceItems := make([]*ai_dto.BasicInfo, 0, len(services))
serviceTeamMap := make(map[string]string)
for _, s := range services {
serviceItems = append(serviceItems, &ai_dto.BasicInfo{
Id: s.Id,
Name: s.Name,
})
serviceTeamMap[s.Id] = s.Team
}
modelItems := utils.SliceToSlice(p.Models(), func(e model_runtime.IModel) *ai_dto.BasicInfo {
return &ai_dto.BasicInfo{
Id: e.ID(),
Name: e.ID(),
}
})
condition := &ai_dto.Condition{Services: serviceItems, Models: modelItems}
switch sortCondition {
default:
apis, err := i.aiAPIService.Search(ctx, keyword, map[string]interface{}{
w := map[string]interface{}{
"provider": providerId,
}, "update_at desc")
}
if len(models) > 0 {
w["model"] = models
}
if len(serviceIds) > 0 {
w["service"] = serviceIds
}
apis, err := i.aiAPIService.Search(ctx, keyword, w, "update_at desc")
if err != nil {
return nil, 0, err
return nil, nil, 0, err
}
if len(apis) <= 0 {
return nil, 0, nil
return nil, condition, 0, nil
}
apiMap := make(map[string]*ai_api.API)
apiIds := make([]string, 0, len(apis))
@@ -668,7 +763,7 @@ func (i *imlAIApiModule) APIs(ctx context.Context, keyword string, providerId st
offset := (page - 1) * pageSize
results, _, err := i.aiAPIUseService.SumByApisPage(ctx, providerId, start, end, offset, pageSize, fmt.Sprintf("total_token %s", sortRule), apiIds...)
if err != nil {
return nil, 0, err
return nil, nil, 0, err
}
apiItems := utils.SliceToSlice(results, func(e *ai_api.APIUse) *ai_dto.APIItem {
@@ -679,6 +774,7 @@ func (i *imlAIApiModule) APIs(ctx context.Context, keyword string, providerId st
Id: e.API,
Name: info.Name,
Service: auto.UUID(info.Service),
Team: auto.UUID(serviceTeamMap[info.Service]),
Method: http.MethodPost,
RequestPath: info.Path,
Model: auto.Label{
@@ -696,6 +792,7 @@ func (i *imlAIApiModule) APIs(ctx context.Context, keyword string, providerId st
Id: a.ID,
Name: a.Name,
Service: auto.UUID(a.Service),
Team: auto.UUID(serviceTeamMap[a.Service]),
Method: http.MethodPost,
RequestPath: a.Path,
Model: auto.Label{
@@ -715,7 +812,8 @@ func (i *imlAIApiModule) APIs(ctx context.Context, keyword string, providerId st
for i := offset; i < offset+size && i < len(sortApis); i++ {
apiItems = append(apiItems, sortApis[i])
}
total := int64(len(apis))
return apiItems, total, nil
return apiItems, condition, total, nil
}
}
+5 -6
View File
@@ -4,28 +4,27 @@ import (
"context"
"reflect"
"github.com/eolinker/go-common/auto"
"github.com/APIParkLab/APIPark/gateway"
ai_dto "github.com/APIParkLab/APIPark/module/ai/dto"
"github.com/eolinker/go-common/autowire"
)
type IProviderModule interface {
ConfiguredProviders(ctx context.Context) ([]*ai_dto.ConfiguredProviderItem, *auto.Label, error)
ConfiguredProviders(ctx context.Context) ([]*ai_dto.ConfiguredProviderItem, *ai_dto.BackupProvider, error)
UnConfiguredProviders(ctx context.Context) ([]*ai_dto.ProviderItem, error)
SimpleProviders(ctx context.Context) ([]*ai_dto.SimpleProviderItem, error)
SimpleConfiguredProviders(ctx context.Context) ([]*ai_dto.SimpleProviderItem, *ai_dto.BackupProvider, error)
Provider(ctx context.Context, id string) (*ai_dto.Provider, error)
SimpleProvider(ctx context.Context, id string) (*ai_dto.SimpleProvider, error)
LLMs(ctx context.Context, driver string) ([]*ai_dto.LLMItem, *ai_dto.ProviderItem, error)
UpdateProviderStatus(ctx context.Context, id string, enable bool) error
//UpdateProviderStatus(ctx context.Context, id string, enable bool) error
UpdateProviderConfig(ctx context.Context, id string, input *ai_dto.UpdateConfig) error
UpdateProviderDefaultLLM(ctx context.Context, id string, input *ai_dto.UpdateLLM) error
//UpdateProviderDefaultLLM(ctx context.Context, id string, input *ai_dto.UpdateLLM) error
Sort(ctx context.Context, input *ai_dto.Sort) error
}
type IAIAPIModule interface {
APIs(ctx context.Context, keyword string, providerId string, start int64, end int64, page int, pageSize int, sortCondition string, asc bool) ([]*ai_dto.APIItem, int64, error)
APIs(ctx context.Context, keyword string, providerId string, start int64, end int64, page int, pageSize int, sortCondition string, asc bool, models []string, services []string) ([]*ai_dto.APIItem, *ai_dto.Condition, int64, error)
}
func init() {
-13
View File
@@ -176,19 +176,6 @@ func (i *imlCatalogueModule) Subscribe(ctx context.Context, subscribeInfo *catal
// 修改订阅表状态
subscribers, err := i.subscribeService.ListByApplication(ctx, subscribeInfo.Service, appId)
if err != nil {
//if !errors.Is(err, gorm.ErrRecordNotFound) {
// return err
//}
//err = i.subscribeService.Create(ctx, &subscribe.CreateSubscribe{
// Uuid: uuid.New().String(),
// Service: subscribeInfo.Service,
// Application: appId,
// ApplyStatus: status,
// From: subscribe.FromSubscribe,
//})
//if err != nil {
// return err
//}
return err
} else {
subscriberMap := utils.SliceToMap(subscribers, func(t *subscribe.Subscribe) string {
+2 -2
View File
@@ -17,12 +17,12 @@ package cluster_dto
//type SaveMonitorConfig struct {
// Driver string `json:"driver"`
// Config map[string]interface{} `json:"config"`
// DefaultConfig map[string]interface{} `json:"config"`
//}
//type MonitorConfig struct {
// Driver string `json:"driver"`
// Config map[string]interface{} `json:"config"`
// DefaultConfig map[string]interface{} `json:"config"`
//}
//type MonitorPartition struct {
+5 -1
View File
@@ -124,6 +124,7 @@ func (m *imlPublishModule) getProjectRelease(ctx context.Context, projectID stri
Version: version,
}
apis := make([]*gateway.ApiRelease, 0, len(apiInfos))
hasUpstream := len(upstreamCommitIds) > 0
for _, a := range apiInfos {
apiInfo := &gateway.ApiRelease{
BasicItem: &gateway.BasicItem{
@@ -133,7 +134,10 @@ func (m *imlPublishModule) getProjectRelease(ctx context.Context, projectID stri
},
Path: a.Path,
Methods: a.Methods,
Service: a.Upstream,
//Service: a.Upstream,
}
if hasUpstream {
apiInfo.Service = a.Upstream
}
proxy, ok := proxyCommitMap[a.UUID]
if ok {
+2 -2
View File
@@ -30,7 +30,7 @@ type Create struct {
MatchRules []Match `json:"match"`
Upstream string `json:"upstream"`
Proxy *InputProxy `json:"proxy"`
Disable bool `json:"disable"`
Disable bool `json:"disabled"`
}
type InputProxy struct {
@@ -69,7 +69,7 @@ type Edit struct {
Methods *[]string `json:"methods"`
Protocols *[]string `json:"protocols"`
MatchRules *[]Match `json:"match"`
Disable *bool `json:"disable"`
Disable *bool `json:"disabled"`
Upstream *string `json:"upstream"`
}
+2 -2
View File
@@ -15,7 +15,7 @@ type Item struct {
Protocols []string `json:"protocols"`
Path string `json:"request_path"`
Description string `json:"description"`
Disable bool `json:"disable"`
Disable bool `json:"disabled"`
Creator auto.Label `json:"creator" aolabel:"user"`
Updater auto.Label `json:"updater" aolabel:"user"`
CreateTime auto.TimeLabel `json:"create_time"`
@@ -34,7 +34,7 @@ type Detail struct {
SimpleDetail
Proxy *Proxy `json:"proxy"`
Protocols []string `json:"protocols"`
Disable bool `json:"disable"`
Disable bool `json:"disabled"`
//Doc map[string]interface{} `json:"doc"`
}
-23
View File
@@ -92,11 +92,6 @@ func (i *imlServiceModule) ExportAll(ctx context.Context) ([]*service_dto.Export
serviceTagMap[st.Sid] = append(serviceTagMap[st.Sid], tagMap[st.Tid].Name)
}
//docMap, err := i.serviceDocService.Map(ctx, serviceIds...)
//if err != nil {
// return nil, err
//}
items := make([]*service_dto.ExportService, 0, len(services))
for _, s := range services {
info := &service_dto.ExportService{
@@ -171,24 +166,6 @@ func (i *imlServiceModule) SearchMyServices(ctx context.Context, teamId string,
return items, nil
}
//func (i *imlServiceModule) SimpleAPPS(ctx context.Context, keyword string) ([]*service_dto.SimpleServiceItem, error) {
// w := make(map[string]interface{})
// w["as_app"] = true
// services, err := i.serviceService.SearchByDriver(ctx, keyword, w)
// if err != nil {
// return nil, err
// }
// return utils.SliceToSlice(services, func(p *service.Service) *service_dto.SimpleServiceItem {
// return &service_dto.SimpleServiceItem{
// Id: p.Id,
// Name: p.Name,
// Description: p.Description,
//
// Team: auto.UUID(p.Team),
// }
// }), nil
//}
func (i *imlServiceModule) Simple(ctx context.Context) ([]*service_dto.SimpleServiceItem, error) {
w := make(map[string]interface{})
w["as_server"] = true
+32 -19
View File
@@ -218,33 +218,46 @@ func (i *imlSubscribeModule) AddSubscriber(ctx context.Context, serviceId string
if err != nil {
return err
}
_, err = i.subscribeService.GetByServiceAndApplication(ctx, serviceId, input.Application)
if err == nil {
// 订阅方已存在
return fmt.Errorf("subscriber is already exists")
clusters, err := i.clusterService.List(ctx)
if err != nil {
return err
}
sub := &gateway.SubscribeRelease{
Service: serviceId,
Application: input.Application,
Expired: "0",
}
clusters, err := i.clusterService.List(ctx)
if err != nil {
return err
}
return i.transaction.Transaction(ctx, func(ctx context.Context) error {
err = i.subscribeService.Create(ctx, &subscribe.CreateSubscribe{
Uuid: uuid.New().String(),
Service: serviceId,
Application: input.Application,
ApplyStatus: subscribe.ApplyStatusSubscribe,
From: subscribe.FromUser,
})
if err != nil {
return err
info, err := i.subscribeService.GetByServiceAndApplication(ctx, serviceId, input.Application)
if err == nil {
// 订阅方已存在
if info.ApplyStatus != subscribe.ApplyStatusSubscribe {
// 更新订阅方状态
status := subscribe.ApplyStatusSubscribe
from := subscribe.FromUser
err = i.subscribeService.Save(ctx, info.Id, &subscribe.UpdateSubscribe{
ApplyStatus: &status,
From: &from,
})
if err != nil {
return err
}
} else {
return nil
}
} else {
err = i.subscribeService.Create(ctx, &subscribe.CreateSubscribe{
Uuid: uuid.New().String(),
Service: serviceId,
Application: input.Application,
ApplyStatus: subscribe.ApplyStatusSubscribe,
From: subscribe.FromUser,
})
if err != nil {
return err
}
}
for _, c := range clusters {
err = i.onlineSubscriber(ctx, c.Uuid, sub)
if err != nil {
+3 -2
View File
@@ -13,20 +13,21 @@ func (p *plugin) aiAPIs() []pm3.Api {
pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/providers/unconfigured", []string{"context"}, []string{"providers"}, p.aiProviderController.UnConfiguredProviders, access.SystemSettingsAiProviderView),
pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/providers/configured", []string{"context"}, []string{"providers", "backup"}, p.aiProviderController.ConfiguredProviders, access.SystemSettingsAiProviderView),
pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/simple/ai/providers", []string{"context"}, []string{"providers"}, p.aiProviderController.SimpleProviders),
pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/simple/ai/providers/configured", []string{"context"}, []string{"providers", "backup"}, p.aiProviderController.SimpleConfiguredProviders),
pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/provider/config", []string{"context", "query:provider"}, []string{"provider"}, p.aiProviderController.Provider, access.SystemSettingsAiProviderView),
pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/simple/ai/provider", []string{"context", "query:provider"}, []string{"provider"}, p.aiProviderController.SimpleProvider),
pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/provider/llms", []string{"context", "query:provider"}, []string{"llms", "provider"}, p.aiProviderController.LLMs),
pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/ai/provider/sort", []string{"context", "body"}, nil, p.aiProviderController.Sort),
pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/ai/provider/config", []string{"context", "query:provider", "body"}, nil, p.aiProviderController.UpdateProviderConfig, access.SystemSettingsAiProviderManager),
pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/apis", []string{"context", "query:keyword", "query:provider", "query:start", "query:end", "query:page", "query:page_size", "query:sort", "query:asc"}, []string{"apis", "total"}, p.aiStatisticController.APIs),
pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/apis", []string{"context", "query:keyword", "query:provider", "query:start", "query:end", "query:page", "query:page_size", "query:sort", "query:asc", "query:models", "query:services"}, []string{"apis", "condition", "total"}, p.aiStatisticController.APIs),
}
}
func (p *plugin) aiKeyApis() []pm3.Api {
return []pm3.Api{
pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/resource/key", []string{"context", "query:provider", "query:id"}, []string{"info"}, p.aiKeyController.Get),
pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/resource/keys", []string{"context", "query:provider", "query:keyword", "query:page", "query:page_size"}, []string{"keys", "total"}, p.aiKeyController.List),
pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/resource/keys", []string{"context", "query:provider", "query:keyword", "query:page", "query:page_size", "query:statuses"}, []string{"keys", "total"}, p.aiKeyController.List),
pm3.CreateApiWidthDoc(http.MethodPost, "/api/v1/ai/resource/key", []string{"context", "query:provider", "body"}, nil, p.aiKeyController.Create),
pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/ai/resource/key", []string{"context", "query:provider", "query:id", "body"}, nil, p.aiKeyController.Edit),
pm3.CreateApiWidthDoc(http.MethodDelete, "/api/v1/ai/resource/key", []string{"context", "query:provider", "query:id"}, nil, p.aiKeyController.Delete),
+3
View File
@@ -8,6 +8,9 @@ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ARG APP
ENV NSQ_ADDR=nsq:4150
ENV NSQ_TOPIC_PREFIX=apipark
RUN mkdir -p /${APP}
COPY cmd/* /${APP}/
+9
View File
@@ -8,6 +8,8 @@ source ./scripts/common.sh
OUTPUT_DIR=$(mkdir_output "$1")
APP="apipark"
OUTPUT_BIN="${OUTPUT_DIR}/${APP}"
AI_EVENT_LISTEN_APP="apipark_ai_event_listen"
AI_EVENT_LISTEN_BIN="${OUTPUT_DIR}/${AI_EVENT_LISTEN_APP}"
VERSION=$(gen_version "$2")
BUILD_TYPE=$3
ARCH=$4
@@ -104,6 +106,10 @@ build_backend() {
# -ldflags="-w -s" means omit DWARF symbol table and the symbol table and debug information
echo "GOOS=linux GOARCH=$ARCH CGO_ENABLED=0 go build $Tags -ldflags \"-w -s $flags\" -o \"${OUTPUT_BIN}\""
GOOS=linux GOARCH=$ARCH CGO_ENABLED=0 go build ${Tags} -ldflags "-w -s $flags" -o ${OUTPUT_BIN}
echo "Build backend successfully..."
echo "GOOS=linux GOARCH=$ARCH CGO_ENABLED=0 go build -ldflags \"-w -s\" -o \"${AI_EVENT_LISTEN_BIN}\" ./app/ai-event-handler"
GOOS=linux GOARCH=$ARCH CGO_ENABLED=0 go build -ldflags "-w -s" -o "${AI_EVENT_LISTEN_BIN}" ./app/ai-event-handler
return
}
@@ -123,6 +129,9 @@ package() {
cp "${OUTPUT_BIN}" "${PACKAGE_DIR}"
echo "cp ${AI_EVENT_LISTEN_BIN} ${PACKAGE_DIR}"
cp "${AI_EVENT_LISTEN_BIN}" "${PACKAGE_DIR}"
echo "tar -czvf ${PACKAGE_DIR}_linux_${ARCH}.tar.gz -C ${PACKAGE_DIR}/ ./"
tar -czvf "${PACKAGE_DIR}_linux_${ARCH}.tar.gz" -C "${PACKAGE_DIR}/" "./"
# rm -fr "${PACKAGE_DIR}"
+6 -1
View File
@@ -21,6 +21,9 @@ echo -e "redis:" >> config.yml
echo -e " user_name: ${REDIS_USER_NAME}" >> config.yml
echo -e " password: ${REDIS_PWD}" >> config.yml
echo -e " addr: " >> config.yml
echo -e "nsq:" >> config.yml
echo -e " addr: ${NSQ_ADDR}" >> config.yml
echo -e " topic: ${NSQ_TOPIC}" >> config.yml
for s in ${arr[@]}
do
echo -e " - $s" >> config.yml
@@ -34,4 +37,6 @@ echo -e " log_expire: ${ERROR_EXPIRE}" >> config.yml
echo -e " log_period: ${ERROR_PERIOD}" >> config.yml
cat config.yml
./apipark
nohup ./apipark >> run.log 2>&1 &
nohup ./apipark_ai_event_listen >> run.log 2>&1 &
tail -F run.log
+2 -1
View File
@@ -42,7 +42,8 @@ start() {
cat "$LOG_FILE"
exit 1
fi
# 启动ai事件监听程序
# nohup ./apipark_ai_event_listen >> "$LOG_FILE" 2>&1 &
}
# 停止函数
+50 -16
View File
@@ -3,9 +3,10 @@ package ai_api
import (
"context"
"encoding/json"
"errors"
"time"
"github.com/eolinker/go-common/utils"
"gorm.io/gorm"
"github.com/APIParkLab/APIPark/service/universally"
"github.com/APIParkLab/APIPark/stores/api"
@@ -34,7 +35,7 @@ func (i *imlAPIService) OnComplete() {
}
func labelHandler(e *api.AiAPIInfo) []string {
return []string{e.Name, e.Uuid}
return []string{e.Name, e.Path}
}
func uniquestHandler(i *Create) []map[string]interface{} {
return []map[string]interface{}{{"uuid": i.ID}}
@@ -87,7 +88,9 @@ func updateHandler(e *api.AiAPIInfo, i *Edit) {
if i.Disable != nil {
e.Disable = *i.Disable
}
if i.UseToken != nil {
e.UseToken = *i.UseToken
}
e.UpdateAt = time.Now()
}
@@ -97,6 +100,36 @@ type imlAPIUseService struct {
store api.IAiAPIUseStore `autowired:""`
}
func (i *imlAPIUseService) Incr(ctx context.Context, incr *IncrAPIUse) error {
info, err := i.store.First(ctx, map[string]interface{}{
"api": incr.API,
"service": incr.Service,
"provider": incr.Provider,
"model": incr.Model,
"day": incr.Day,
"hour": incr.Hour,
"minute": incr.Minute,
})
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
info = &api.AiAPIUse{
API: incr.API,
Service: incr.Service,
Provider: incr.Provider,
Model: incr.Model,
Day: incr.Day,
Hour: incr.Hour,
Minute: incr.Minute,
}
}
info.InputToken += incr.InputToken
info.OutputToken += incr.OutputToken
info.TotalToken += incr.TotalToken
return i.store.Save(ctx, info)
}
func (i *imlAPIUseService) SumByApisPage(ctx context.Context, providerId string, start, end int64, offset, limit int, order string, apiIds ...string) ([]*APIUse, int64, error) {
list, total, err := i.store.SumByGroupPage(ctx, "api", order, offset, limit, "api,sum(input_token) as input_token,sum(output_token) as output_token,sum(total_token) as total_token", "provider = ? and api in (?) and minute >= ? and minute <= ?", providerId, apiIds, start, end)
if err != nil {
@@ -116,17 +149,18 @@ func (i *imlAPIUseService) SumByApisPage(ctx context.Context, providerId string,
}
func (i *imlAPIUseService) SumByApis(ctx context.Context, providerId string, start, end int64, apiIds ...string) ([]*APIUse, error) {
list, err := i.store.SumByGroup(ctx, "api", "api,sum(input_token) as input_token,sum(output_token) as output_token,sum(total_token) as total_token", "provider = ? and api in (?) and minute >= ? and minute <= ?", providerId, apiIds, start, end)
if err != nil {
return nil, err
}
return utils.SliceToSlice(list, func(v *api.AiAPIUse) *APIUse {
return &APIUse{
API: v.API,
InputToken: v.InputToken,
OutputToken: v.OutputToken,
TotalToken: v.TotalToken,
}
}), nil
//list, err := i.store.SumByGroup(ctx, "api", "api,sum(input_token) as input_token,sum(output_token) as output_token,sum(total_token) as total_token", "provider = ? and api in (?) and minute >= ? and minute <= ?", providerId, apiIds, start, end)
//if err != nil {
// return nil, err
//}
//
//return utils.SliceToSlice(list, func(v *api.AiAPIUse) *APIUse {
// return &APIUse{
// API: v.API,
// InputToken: v.InputToken,
// OutputToken: v.OutputToken,
// TotalToken: v.TotalToken,
// }
//}), nil
return nil, nil
}
+16
View File
@@ -19,6 +19,7 @@ type API struct {
Provider string
CreateAt time.Time
UpdateAt time.Time
UseToken int
Creator string
Updater string
AdditionalConfig map[string]interface{}
@@ -48,6 +49,7 @@ type Edit struct {
Provider *string
Model *string
Disable *bool
UseToken *int
AdditionalConfig *map[string]interface{}
}
@@ -70,6 +72,7 @@ func FromEntity(e *api.AiAPIInfo) *API {
Creator: e.Creator,
Updater: e.Updater,
Disable: e.Disable,
UseToken: e.UseToken,
AdditionalConfig: cfg,
}
}
@@ -80,3 +83,16 @@ type APIUse struct {
OutputToken int
TotalToken int
}
type IncrAPIUse struct {
API string
Service string
Provider string
Model string
Day int64
Hour int64
Minute int64
InputToken int
OutputToken int
TotalToken int
}
+1
View File
@@ -19,6 +19,7 @@ type IAPIService interface {
type IAPIUseService interface {
SumByApis(ctx context.Context, providerId string, start, end int64, apiIds ...string) ([]*APIUse, error)
SumByApisPage(ctx context.Context, providerId string, start, end int64, page, pageSize int, order string, apiIds ...string) ([]*APIUse, int64, error)
Incr(ctx context.Context, incr *IncrAPIUse) error
}
func init() {
+35 -1
View File
@@ -23,6 +23,39 @@ type imlAIKeyService struct {
universally.IServiceDelete
}
func (i *imlAIKeyService) IncrUseToken(ctx context.Context, id string, useToken int) error {
info, err := i.store.GetByUUID(ctx, id)
if err != nil {
return err
}
info.UseToken += useToken
return i.store.Save(ctx, info)
}
func (i *imlAIKeyService) SearchUnExpiredByPage(ctx context.Context, w map[string]interface{}, page, pageSize int, order string) ([]*Key, int64, error) {
sql := "(expire_time = 0 || expire_time > ?)"
args := []interface{}{time.Now().Unix()}
for k, v := range w {
switch v.(type) {
case []int:
sql += fmt.Sprintf(" and `%s` in (?)", k)
default:
sql += fmt.Sprintf(" and `%s` = ?", k)
}
args = append(args, v)
}
list, total, err := i.store.ListPage(ctx, sql, page, pageSize, args, order)
if err != nil {
return nil, 0, err
}
var result []*Key
for _, item := range list {
result = append(result, FromEntity(item))
}
return result, total, nil
}
func (i *imlAIKeyService) KeysAfterPriority(ctx context.Context, providerId string, priority int) ([]*Key, error) {
list, err := i.store.ListQuery(ctx, "sort > ? and provider = ?", []interface{}{priority, providerId}, "sort asc")
if err != nil {
@@ -169,7 +202,7 @@ func (i *imlAIKeyService) OnComplete() {
}
func labelHandler(e *ai.Key) []string {
return []string{e.Name, e.Uuid}
return []string{e.Name}
}
func uniquestHandler(i *Create) []map[string]interface{} {
return []map[string]interface{}{{"uuid": i.ID}}
@@ -206,5 +239,6 @@ func updateHandler(e *ai.Key, i *Edit) {
if i.Priority != nil {
e.Sort = *i.Priority
}
e.UpdateAt = time.Now()
}
+2
View File
@@ -20,6 +20,8 @@ type IKeyService interface {
SortBefore(ctx context.Context, provider string, originID string, targetID string) ([]*Key, error)
SortAfter(ctx context.Context, provider string, originID string, targetID string) ([]*Key, error)
KeysAfterPriority(ctx context.Context, providerId string, priority int) ([]*Key, error)
SearchUnExpiredByPage(ctx context.Context, w map[string]interface{}, page, pageSize int, order string) ([]*Key, int64, error)
IncrUseToken(ctx context.Context, id string, useToken int) error
}
func init() {
+1 -1
View File
@@ -46,7 +46,7 @@ func (i *imlServiceService) ServiceListByKind(ctx context.Context, kind Kind, se
w["uuid"] = serviceIds
}
w["as_server"] = true
w["kind"] = kind
w["kind"] = kind.Int()
w["is_delete"] = false
list, err := i.store.List(ctx, w)
if err != nil {
+3
View File
@@ -200,6 +200,9 @@ func (i *imlSubscribeService) updateHandler(e *subscribe.Subscribe, t *UpdateSub
if t.ApplyStatus != nil {
e.ApplyStatus = *t.ApplyStatus
}
if t.From != nil {
e.From = *t.From
}
}
var (
+3 -2
View File
@@ -2,14 +2,14 @@ package subscribe
import (
"time"
"github.com/APIParkLab/APIPark/stores/subscribe"
)
type Subscribe struct {
Id string
Service string
// 订阅方相关
Application string
From int
@@ -28,6 +28,7 @@ type CreateSubscribe struct {
}
type UpdateSubscribe struct {
From *int
ApplyStatus *int
}
+4 -3
View File
@@ -103,9 +103,10 @@ type AiAPIUse struct {
API string `gorm:"size:36;not null;column:api;comment:API;index:api"`
Service string `gorm:"size:36;not null;column:service;comment:服务;index:service"`
Provider string `gorm:"size:36;not null;column:provider;comment:提供者;index:provider"`
Day int `gorm:"type:int(11);not null;column:day;comment:当前日期"`
Hour int `gorm:"type:int(11);not null;column:hour;comment:当前小时"`
Minute int `gorm:"type:int(11);not null;column:minute;comment:当前分钟"`
Model string `gorm:"size:255;not null;column:model;comment:模型"`
Day int64 `gorm:"type:int(11);not null;column:day;comment:当前日期"`
Hour int64 `gorm:"type:int(11);not null;column:hour;comment:当前小时"`
Minute int64 `gorm:"type:int(11);not null;column:minute;comment:当前分钟"`
InputToken int `gorm:"type:int(11);not null;column:input_token;comment:输入token"`
OutputToken int `gorm:"type:int(11);not null;column:output_token;comment:输出token"`
TotalToken int `gorm:"type:int(11);not null;column:total_token;comment:总token"`