update: 更新配置文件中的服务地址,修改模型管理相关代码,调整数据结构和逻辑,优化模型列表查询和会话模型设置,更新数据库表结构和索引,修改模块名称和依赖版本

This commit is contained in:
2026-05-15 14:56:26 +08:00
parent adf1d0ae6e
commit bac9d7713f
27 changed files with 286 additions and 292 deletions

View File

@@ -1,5 +1,5 @@
server:
address: ":8001"
address: ":3004"
name: "model-gateway"
workerId: 1 # 雪花算法worker ID用于 common/db/gfdb
@@ -29,14 +29,14 @@ database:
redis:
default:
address: 116.204.74.41:6379
address: 192.168.3.30:6379
db: 0
consul:
address: 116.204.74.41:8500
address: 192.168.3.30:8500
jaeger:
addr: 116.204.74.41:4318
addr: 192.168.3.30:4318
# 本地调试用:可选自动执行 worker/cleaner默认关闭
asynch:

View File

@@ -1,9 +1,8 @@
package public
const (
TableNameModel = "asynch_models" // 模型表
TableNameModelType = "asynch_models_type" // 模型类型
TableNameTask = "asynch_task" // 任务
TableNameOpLog = "logs_model_op" // 操作日志表
TableNameStat = "logs_model_stat" // 按天统计表(请求次数)
TableNameModel = "asynch_models" // 模型表
TableNameTask = "asynch_task" // 任务
TableNameOpLog = "logs_model_op" // 操作日志
TableNameStat = "logs_model_stat" // 按天统计表(请求次数)
)

View File

@@ -3,9 +3,9 @@ package controller
import (
"context"
"model-asynch/model/dto"
"model-asynch/model/entity"
"model-asynch/service"
"model-gateway/model/dto"
"model-gateway/model/entity"
"model-gateway/service"
"gitea.com/red-future/common/beans"
)
@@ -38,21 +38,15 @@ func (c *model) GetModel(ctx context.Context, req *dto.GetModelReq) (res *dto.Ge
if err != nil {
return nil, err
}
if model == nil {
return nil, nil
}
return &dto.GetModelRes{Model: model}, nil
}
// ListModel 配置列表
func (c *model) ListModel(ctx context.Context, req *dto.ListModelReq) (res *dto.ListModelRes, err error) {
pageNum, pageSize := 1, 10 //默认分页参数
if req != nil {
if req.PageNum > 0 {
pageNum = req.PageNum
}
if req.PageSize > 0 {
pageSize = req.PageSize
}
}
list, total, err := service.Model.List(ctx, pageNum, pageSize, req)
list, total, err := service.Model.List(ctx, req)
if err != nil {
return nil, err
}

View File

@@ -3,8 +3,8 @@ package controller
import (
"context"
"model-asynch/model/dto"
"model-asynch/service"
"model-gateway/model/dto"
"model-gateway/service"
)
type stat struct{}

View File

@@ -3,8 +3,8 @@ package controller
import (
"context"
"model-asynch/model/dto"
"model-asynch/service"
"model-gateway/model/dto"
"model-gateway/service"
)
type task struct{}

View File

@@ -4,11 +4,12 @@ import (
"context"
"fmt"
"model-asynch/consts/public"
"model-asynch/model/dto"
"model-asynch/model/entity"
"model-gateway/consts/public"
"model-gateway/model/dto"
"model-gateway/model/entity"
"gitea.com/red-future/common/db/gfdb"
"gitea.com/red-future/common/utils"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
@@ -17,8 +18,13 @@ var Model = &modelDao{}
type modelDao struct{}
func (d *modelDao) Insert(ctx context.Context, m *entity.AsynchModel) (id int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameModel).Data(m).Insert()
func (d *modelDao) Insert(ctx context.Context, req *dto.CreateModelReq) (id int64, err error) {
asyncModel := new(entity.AsynchModel)
err = gconv.Struct(req, &asyncModel)
if err != nil {
return
}
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameModel).Data(asyncModel).Insert()
if err != nil {
return 0, err
}
@@ -38,20 +44,6 @@ func (d *modelDao) Update(ctx context.Context, m *dto.UpdateModelReq) (rows int6
return r.RowsAffected()
}
func (d *modelDao) UpdateByID(ctx context.Context, m *dto.UpdateModelReq) (rows int64, err error) {
// 专用于切换会话模型,只更新 is_chat_model 字段
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameModel).
Where(entity.AsynchModelCol.Id, m.ID).
Data(g.Map{
"is_chat_model": m.IsChatModel,
}).
Update()
if err != nil {
return 0, err
}
return r.RowsAffected()
}
func (d *modelDao) DeleteByID(ctx context.Context, id string) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameModel).
Where(entity.AsynchModelCol.Id, id).
@@ -78,6 +70,7 @@ func (d *modelDao) GetByModelName(ctx context.Context, modelName string) (m *ent
func (d *modelDao) Get(ctx context.Context, id int64) (m *entity.AsynchModel, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameModel).
NoTenantId(ctx).
Where(entity.AsynchModelCol.Id, id).
One()
if err != nil {
@@ -90,6 +83,13 @@ func (d *modelDao) Get(ctx context.Context, id int64) (m *entity.AsynchModel, er
return
}
func (d *modelDao) Count(ctx context.Context, req *dto.GetModelReq) (count int, err error) {
count, err = gfdb.DB(ctx).Model(ctx, public.TableNameModel).OmitEmpty().
Where(entity.AsynchModelCol.Creator, req.Creator).
Where(entity.AsynchModelCol.Id, req.ID).Count()
return
}
func (d *modelDao) List(ctx context.Context, pageNum, pageSize int, modelNameLike string, modelType int, isPrivate int) (list []*entity.AsynchModel, total int64, err error) {
model := gfdb.DB(ctx).Model(ctx, public.TableNameModel).
OrderDesc(entity.AsynchModelCol.CreatedAt)
@@ -97,7 +97,7 @@ func (d *modelDao) List(ctx context.Context, pageNum, pageSize int, modelNameLik
model = model.WhereLike(entity.AsynchModelCol.ModelName, "%"+modelNameLike+"%")
}
if modelType != 0 {
model = model.Where(entity.AsynchModelCol.ModelsType, modelType)
model = model.Where(entity.AsynchModelCol.ModelType, modelType)
}
if isPrivate != 0 {
model = model.Where(entity.AsynchModelCol.IsPrivate, isPrivate)
@@ -150,39 +150,64 @@ func (d *modelDao) ListByCreatorAndPlatform(ctx context.Context, creator string,
err = r.Structs(&list)
return
}
func (d *modelDao) GetByCreatorAndPlatform(ctx context.Context, creator string, modelNameLike string, modelType int, isPrivate int) (list []*entity.AsynchModel, err error) {
whereSQL := "deleted_at IS NULL AND (tenant_id = 1 OR creator = ?)"
args := []any{creator}
if modelNameLike != "" {
whereSQL += " AND model_name LIKE ?"
args = append(args, "%"+modelNameLike+"%")
func (d *modelDao) GetByCreatorAndPlatform(ctx context.Context, req *dto.ListModelReq) (list []*entity.AsynchModel, total int, err error) {
// 基础 SQL
sql := `
SELECT DISTINCT ON (model_name) *
FROM asynch_models
WHERE deleted_at IS NULL
AND (? = '' OR model_name LIKE ?)
AND (? = 0 OR model_type = ?)
`
args := []any{
req.ModelName, "%" + req.ModelName + "%",
req.ModelType, req.ModelType,
}
if modelType != 0 {
whereSQL += " AND models_type = ?"
args = append(args, modelType)
if !g.IsEmpty(req.IsPrivate) {
sql += ` AND is_private = ? `
args = append(args, req.IsPrivate)
}
if isPrivate != 0 {
whereSQL += " AND is_private = ?"
args = append(args, isPrivate)
if req.IsOwner != nil && *req.IsOwner == 0 {
sql += ` AND creator = ? AND is_owner = ? `
args = append(args, req.Creator)
args = append(args, req.IsOwner)
} else if req.IsOwner != nil && *req.IsOwner == 1 {
if req.Enabled != nil && *req.Enabled == 1 {
sql += ` AND ((creator = ? AND is_owner = ? AND enabled=1) OR (is_owner = 0 AND enabled=1)) `
} else if req.Enabled != nil && *req.Enabled == 0 {
sql += ` AND ((creator = ? AND is_owner = ? AND enabled=0) OR (is_owner = 0 AND enabled=1)) `
} else {
sql += ` AND ((creator = ? AND is_owner = ?) OR (is_owner = 0 AND enabled=1)) `
}
args = append(args, req.Creator)
args = append(args, req.IsOwner)
}
querySQL := fmt.Sprintf("SELECT * FROM %s WHERE %s ORDER BY created_at DESC", public.TableNameModel, whereSQL)
// 最后拼接排序
sql += ` ORDER BY model_name, is_owner DESC, created_at DESC`
r, err := gfdb.DB(ctx).GetAll(ctx, querySQL, args...)
r, err := gfdb.DB(ctx).GetAll(ctx, sql, args...)
if err != nil {
return nil, err
return nil, 0, err
}
err = r.Structs(&list)
if err != nil {
return nil, 0, err
}
total = len(list)
return
}
func (d *modelDao) GetByIsChatModel(ctx context.Context, userName string) (m *entity.AsynchModel, err error) {
func (d *modelDao) GetByIsChatModel(ctx context.Context) (m *entity.AsynchModel, err error) {
userInfo, err := utils.GetUserInfo(ctx)
if err != nil {
return nil, err
}
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameModel).
Where(entity.AsynchModelCol.IsChatModel, 1).
Where(entity.AsynchModelCol.Creator, userName).
Where(entity.AsynchModelCol.Creator, userInfo.UserName).
One()
if err != nil {
return nil, err

View File

@@ -3,8 +3,8 @@ package dao
import (
"context"
"model-asynch/consts/public"
"model-asynch/model/entity"
"model-gateway/consts/public"
"model-gateway/model/entity"
"gitea.com/red-future/common/db/gfdb"
)

View File

@@ -3,8 +3,8 @@ package dao
import (
"context"
"model-asynch/consts/public"
"model-asynch/model/entity"
"model-gateway/consts/public"
"model-gateway/model/entity"
"gitea.com/red-future/common/db/gfdb"
)

View File

@@ -5,8 +5,8 @@ import (
"fmt"
"time"
"model-asynch/consts/public"
"model-asynch/model/entity"
"model-gateway/consts/public"
"model-gateway/model/entity"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/os/gtime"

View File

@@ -5,8 +5,8 @@ import (
"fmt"
"time"
"model-asynch/consts/public"
"model-asynch/model/entity"
"model-gateway/consts/public"
"model-gateway/model/entity"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"

View File

@@ -5,8 +5,8 @@ import (
"fmt"
"time"
"model-asynch/consts/public"
"model-asynch/model/entity"
"model-gateway/consts/public"
"model-gateway/model/entity"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"

4
go.mod
View File

@@ -1,9 +1,9 @@
module model-asynch
module model-gateway
go 1.26.0
require (
gitea.com/red-future/common v0.0.19 // indirect
gitea.com/red-future/common v0.0.19
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.0
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.0
github.com/gogf/gf/v2 v2.10.0

1
go.sum
View File

@@ -603,6 +603,7 @@ gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=

View File

@@ -7,8 +7,8 @@ import (
"syscall"
"time"
"model-asynch/controller"
"model-asynch/service"
"model-gateway/controller"
"model-gateway/service"
"gitea.com/red-future/common/http"
"gitea.com/red-future/common/jaeger"

View File

@@ -1,6 +1,7 @@
package dto
import (
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
@@ -8,14 +9,15 @@ import (
type CreateModelReq struct {
g.Meta `path:"/createModel" method:"post" tags:"模型管理" summary:"创建模型配置" dc:"添加新的模型配置"`
ModelName string `p:"modelName" json:"modelName" v:"required#modelName不能为空" dc:"模型名称(唯一标识)"`
ModelsType int `p:"modelsType" json:"modelsType" v:"required#modelsType不能为空" dc:"模型类型1-文本生成 2-图像生成 3-语音 4-视频 5-多模态"`
ModelType int `p:"modelType" json:"modelType" v:"required#modelType不能为空" dc:"模型类型1-文本生成 2-图像生成 3-语音 4-视频 5-多模态"`
BaseURL string `p:"baseUrl" json:"baseUrl" v:"required#baseUrl不能为空" dc:"模型服务基础地址(如 http(s)://host:port"`
HttpMethod string `p:"httpMethod" json:"httpMethod" dc:"请求方式GET/POST默认POST"`
HeadMsg string `p:"headMsg" json:"headMsg" dc:"请求头绑定支持多个逗号分隔示例Authorization:Bearer xxx,Content-Type:application/json"`
IsPrivate int `p:"isPrivate" json:"isPrivate" v:"in:0,1#私有化参数只能为0或1" dc:"是否私有化0-私有(默认) 1-公共"`
Enabled int `p:"enabled" json:"enabled" v:"in:0,1#启用参数只能为0或1" dc:"是否启用0-禁用1-启用默认1"`
IsChatModel int `p:"isChatModel" json:"isChatModel" v:"in:0,1#对话模型参数只能为0或1" dc:"是否为对话模型0-否1-是默认0"`
ApiKey string `p:"apiKey" json:"apiKey" v:"required-if:isPrivate,1#公共模型必须填写API密钥" dc:"调用凭证/密钥,用于模型认证"`
IsPrivate *int `p:"isPrivate" json:"isPrivate" v:"in:0,1#私有化参数只能为0或1" dc:"是否私有化0-私有(默认) 1-公共"`
Enabled *int `p:"enabled" json:"enabled" v:"in:0,1#启用参数只能为0或1" dc:"是否启用0-禁用1-启用默认1"`
IsChatModel *int `p:"isChatModel" json:"isChatModel" v:"in:0,1#对话模型参数只能为0或1" dc:"是否为对话模型0-否1-是默认0"`
IsOwner *int `p:"isOwner" json:"isOwner" v:"in:0,1#是否为所有者参数只能为0或1" dc:"是否为所有者0-否1-是默认0"`
ApiKey string `p:"apiKey" json:"apiKey" dc:"调用凭证/密钥,用于模型认证"`
Form any `p:"form" json:"form" dc:"动态表单配置JSON用于前端渲染配置项"`
RequestMapping any `p:"requestMapping" json:"requestMapping" dc:"请求映射"`
ResponseMapping any `p:"responseMapping" json:"responseMapping" dc:"返回映射"`
@@ -38,17 +40,21 @@ type CreateModelRes struct {
type UpdateModelReq struct {
g.Meta `path:"/updateModel" method:"put" tags:"模型管理" summary:"更新模型配置" dc:"更新指定ID的模型配置"`
ID int64 `p:"id" json:"id" v:"required#id不能为空" dc:"配置ID"`
ModelsType string `p:"modelsType" json:"modelsType" dc:"模型类型ID列表逗号分隔可选更新"`
ModelName string `p:"modelName" json:"modelName" dc:"模型名称(唯一标识"`
ModelType int `p:"modelType" json:"modelType" dc:"模型类型ID列表逗号分隔可选更新"`
BaseURL string `p:"baseUrl" json:"baseUrl" dc:"模型服务基础地址"`
HttpMethod string `p:"httpMethod" json:"httpMethod" dc:"请求方式GET/POST可选更新"`
HeadMsg string `p:"headMsg" json:"headMsg" dc:"请求头绑定(可选更新)"`
ApiKey string `p:"apiKey" json:"apiKey" dc:"调用凭证/密钥,用于模型认证(可选更新)"`
Form any `p:"form" json:"form" dc:"动态表单配置JSON可选更新"`
RequestMapping any `p:"requestMapping" json:"requestMapping" dc:"请求参数映射(可选更新)"`
ResponseMapping any `p:"responseMapping" json:"responseMapping" dc:"返回参数映射(可选更新)"`
ResponseBody any `p:"responseBody" json:"responseBody" dc:"返回主体(可选更新)"`
TokenMapping string `p:"tokenMapping" json:"tokenMapping" dc:"token映射可选更新"`
Enabled int `p:"enabled" json:"enabled" dc:"是否启用0-禁用1-启用(可选更新)"`
IsChatModel int `p:"isChatModel" json:"isChatModel" v:"in:0,1#对话模型参数只能为0或1" dc:"是否为对话模型0-否1-是默认0"`
Enabled *int `p:"enabled" json:"enabled" dc:"是否启用0-禁用1-启用(可选更新)"`
IsPrivate *int `p:"isPrivate" json:"isPrivate" v:"in:0,1#私有化参数只能为0或1" dc:"是否私有化0-私有(默认) 1-公共"`
IsChatModel *int `p:"isChatModel" json:"isChatModel" v:"in:0,1#对话模型参数只能为0或1" dc:"是否为对话模型0-否1-是默认0"`
IsOwner *int `p:"isOwner" json:"isOwner" v:"in:0,1#是否为所有者参数只能为0或1" dc:"是否为所有者0-否1-是默认0"`
MaxConcurrency int `p:"maxConcurrency" json:"maxConcurrency" dc:"最大并发数(可选更新)"`
QueueLimit int `p:"queueLimit" json:"queueLimit" dc:"排队队列上限(可选更新)"`
TimeoutSeconds int `p:"timeoutSeconds" json:"timeoutSeconds" dc:"请求超时时间(秒)(可选更新)"`
@@ -67,8 +73,9 @@ type DeleteModelReq struct {
// GetModelReq 获取模型配置详情
type GetModelReq struct {
g.Meta `path:"/getModel" method:"get" tags:"模型管理" summary:"获取模型配置" dc:"根据模型ID获取配置详情"`
ID int64 `p:"id" json:"id,string" v:"required#id不能为空" dc:"配置ID"`
g.Meta `path:"/getModel" method:"get" tags:"模型管理" summary:"获取模型配置" dc:"根据模型ID获取配置详情"`
ID int64 `p:"id" json:"id,string" v:"required#id不能为空" dc:"配置ID"`
Creator string `p:"creator" json:"creator" dc:"创建人"`
}
type GetModelRes struct {
@@ -78,16 +85,18 @@ type GetModelRes struct {
// ListModelReq 配置列表
type ListModelReq struct {
g.Meta `path:"/listModel" method:"get" tags:"模型管理" summary:"模型配置列表" dc:"分页获取模型配置列表"`
PageNum int `p:"pageNum" json:"pageNum" dc:"页码默认1"`
PageSize int `p:"pageSize" json:"pageSize" dc:"每页条数默认10"`
ModelName string `p:"modelName" json:"modelName" dc:"模型名称(模糊查询,可选)"`
ModelType int `p:"modelType" json:"modelType" dc:"模型类型"`
IsPrivate int `p:"isPrivate" json:"isPrivate" dc:"是否私有化 0-私有 1-公共"`
Page *beans.Page `json:"page"`
ModelName string `p:"modelName" json:"modelName" dc:"模型名称(模糊查询,可选"`
ModelType int `p:"modelType" json:"modelType" dc:"模型类型"`
Enabled *int `p:"enabled" json:"enabled" dc:"是否启用0-禁用1-启用"`
IsPrivate *int `p:"isPrivate" json:"isPrivate" dc:"是否私有化 0-私有 1-公共"`
IsOwner *int `p:"isOwner" json:"isOwner" dc:"是否为所有者 0-否 1-是"`
Creator string `p:"creator" json:"creator" dc:"创建人"`
}
type ListModelRes struct {
List any `json:"list" dc:"列表数据"`
Total int64 `json:"total" dc:"总数"`
List any `json:"list" dc:"列表数据"`
Total int `json:"total" dc:"总数"`
}
// AutoTuneReq 动态调参(由上层定时任务每小时触发一次)

View File

@@ -5,7 +5,7 @@ import "gitea.com/red-future/common/beans"
type asynchModelCol struct {
beans.SQLBaseCol
ModelName string
ModelsType string
ModelType string
BaseURL string
HttpMethod string
HeadMsg string
@@ -27,12 +27,13 @@ type asynchModelCol struct {
RetryQueueMaxSecs string
AutoCleanSeconds string
Remark string
IsOwner string
}
var AsynchModelCol = asynchModelCol{
SQLBaseCol: beans.DefSQLBaseCol,
ModelName: "model_name",
ModelsType: "models_type",
ModelType: "model_type",
BaseURL: "base_url",
HttpMethod: "http_method",
HeadMsg: "head_msg",
@@ -54,13 +55,14 @@ var AsynchModelCol = asynchModelCol{
RetryQueueMaxSecs: "retry_queue_max_seconds",
AutoCleanSeconds: "auto_clean_seconds",
Remark: "remark",
IsOwner: "is_owner",
}
// AsynchModel 异步模型配置
type AsynchModel struct {
beans.SQLBaseDO `orm:",inline"`
ModelName string `orm:"model_name" json:"modelName"`
ModelsType int `orm:"models_type" json:"modelsType"`
ModelType int `orm:"model_type" json:"modelType"`
BaseURL string `orm:"base_url" json:"baseUrl"`
HttpMethod string `orm:"http_method" json:"httpMethod"`
HeadMsg string `orm:"head_msg" json:"headMsg"`
@@ -70,10 +72,10 @@ type AsynchModel struct {
ResponseBody any `orm:"response_body" json:"responseBody"`
TokenMapping string `orm:"token_mapping" json:"tokenMapping"`
Prompt string `orm:"prompt" json:"prompt"`
IsPrivate int `orm:"is_private" json:"isPrivate"`
IsChatModel int `orm:"is_chat_model" json:"isChatModel"`
IsPrivate *int `orm:"is_private" json:"isPrivate"`
IsChatModel *int `orm:"is_chat_model" json:"isChatModel"`
ApiKey string `orm:"api_key" json:"apiKey"`
Enabled int `orm:"enabled" json:"enabled"`
Enabled *int `orm:"enabled" json:"enabled"`
MaxConcurrency int `orm:"max_concurrency" json:"maxConcurrency"`
QueueLimit int `orm:"queue_limit" json:"queueLimit"`
TimeoutSeconds int `orm:"timeout_seconds" json:"timeoutSeconds"`
@@ -82,4 +84,5 @@ type AsynchModel struct {
RetryQueueMaxSeconds int `orm:"retry_queue_max_seconds" json:"retryQueueMaxSeconds"`
AutoCleanSeconds int `orm:"auto_clean_seconds" json:"autoCleanSeconds"`
Remark string `orm:"remark" json:"remark"`
IsOwner *int `json:"isOwner" orm:"is_owner"` // 1=当前用户创建的0=超级管理员的
}

View File

@@ -5,8 +5,8 @@ import (
"fmt"
"math"
"model-asynch/consts/public"
"model-asynch/model/entity"
"model-gateway/consts/public"
"model-gateway/model/entity"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/frame/g"
@@ -14,9 +14,9 @@ import (
// AutoTuneResult 单次调参结果(按 model_name
type AutoTuneResult struct {
ModelName string `json:"modelName"` // 模型名称asynch_models.model_name
Samples int `json:"samples"` // 统计样本数(窗口内 state=2/3 且 started_at/finished_at 非空的任务数量)
P90Exec float64 `json:"p90ExecSeconds"` // 执行耗时 P90口径finished_at - started_at
ModelName string `json:"modelName"` // 模型名称asynch_models.model_name
Samples int `json:"samples"` // 统计样本数(窗口内 state=2/3 且 started_at/finished_at 非空的任务数量)
P90Exec float64 `json:"p90ExecSeconds"` // 执行耗时 P90口径finished_at - started_at
CapMaxConcurrency int `json:"capMaxConcurrency"` // 配置上限asynch_models.max_concurrencycap不会被动态调参覆盖
OldMaxConcurrency int `json:"oldMaxConcurrency"` // 调参前运行时值Redis若无则等于 cap

View File

@@ -4,7 +4,7 @@ import (
"context"
"encoding/json"
"model-asynch/model/entity"
"model-gateway/model/entity"
"gitea.com/red-future/common/http"
"github.com/gogf/gf/v2/frame/g"
@@ -65,23 +65,3 @@ func triggerPromptsCallback(ctx context.Context, t *entity.AsynchTask, epicycleI
}
g.Log().Infof(ctx, "[提示词回调] 发送成功 epicycleId=%d 回调地址=%s 消息体大小=%d字节", t.EpicycleId, callbackURL, len(jsonData))
}
// IsSuperAdmin 调用admin-go服务检查是否是超级管理员
func IsSuperAdmin(ctx context.Context) (res bool, err error) {
headers := forwardHeaders(ctx)
var r = make(map[string]bool)
if err = http.Get(ctx, "admin-go/api/v1/system/user/checkIsSuperAdmin", headers, &r); err != nil {
return false, err
}
return r["isSuperAdmin"], err
}
// IsAdmin 调用admin-go服务检查是否是管理员
func IsAdmin(ctx context.Context) (res bool, err error) {
headers := forwardHeaders(ctx)
var r = make(map[string]bool)
if err = http.Get(ctx, "admin-go/api/v1/system/user/checkIsSuperAdmin", headers, &r); err != nil {
return false, err
}
return r["isSuperAdmin"], err
}

View File

@@ -4,7 +4,7 @@ import (
"context"
"time"
"model-asynch/dao"
"model-gateway/dao"
"github.com/gogf/gf/v2/frame/g"
)

View File

@@ -11,7 +11,7 @@ import (
"strings"
"time"
"model-asynch/model/entity"
"model-gateway/model/entity"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/frame/g"

View File

@@ -3,13 +3,15 @@ package service
import (
"context"
"errors"
"sort"
"model-asynch/dao"
"model-asynch/model/dto"
"model-asynch/model/entity"
"model-gateway/dao"
"model-gateway/model/dto"
"model-gateway/model/entity"
"gitea.com/red-future/common/beans"
"gitea.com/red-future/common/db/gfdb"
"gitea.com/red-future/common/http"
"gitea.com/red-future/common/utils"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
@@ -18,32 +20,45 @@ var Model = &modelService{}
type modelService struct{}
func (s *modelService) Create(ctx context.Context, req *dto.CreateModelReq) (res *dto.CreateModelRes, err error) {
m := &entity.AsynchModel{
ModelName: req.ModelName,
ModelsType: req.ModelsType,
BaseURL: req.BaseURL,
HttpMethod: req.HttpMethod,
HeadMsg: req.HeadMsg,
IsPrivate: req.IsPrivate,
Enabled: req.Enabled,
IsChatModel: req.IsChatModel,
ApiKey: req.ApiKey,
Form: req.Form,
RequestMapping: req.RequestMapping,
ResponseMapping: req.ResponseMapping,
ResponseBody: req.ResponseBody,
TokenMapping: req.TokenMapping,
MaxConcurrency: req.MaxConcurrency,
QueueLimit: req.QueueLimit,
TimeoutSeconds: req.TimeoutSeconds,
ExpectedSeconds: req.ExpectedSeconds,
RetryTimes: req.RetryTimes,
RetryQueueMaxSeconds: req.RetryQueueMaxSeconds,
AutoCleanSeconds: req.AutoCleanSeconds,
Remark: req.Remark,
// IsSuperAdmin 调用admin-go服务检查是否是超级管理员
func (s *modelService) IsSuperAdmin(ctx context.Context) (res bool, err error) {
headers := forwardHeaders(ctx)
var r = make(map[string]bool)
if err = http.Get(ctx, "admin-go/api/v1/system/user/checkIsSuperAdmin", headers, &r); err != nil {
return false, err
}
id, err := dao.Model.Insert(ctx, m)
return r["isSuperAdmin"], err
}
func (s *modelService) Create(ctx context.Context, req *dto.CreateModelReq) (res *dto.CreateModelRes, err error) {
// 获取当前会话模型
if !g.IsEmpty(req.IsChatModel) && *req.IsChatModel == 1 {
var model *entity.AsynchModel
model, err = dao.Model.GetByIsChatModel(ctx)
if err != nil {
return nil, err
}
// 如果有会话模型,那就改变为 0
if model != nil {
_, err = dao.Model.Update(ctx, &dto.UpdateModelReq{
ID: model.Id,
IsChatModel: gconv.PtrInt(0),
})
if err != nil {
return nil, err
}
}
}
req.IsOwner = gconv.PtrInt(1)
admin, err := s.IsSuperAdmin(ctx)
if err != nil {
return
}
if admin {
req.IsOwner = gconv.PtrInt(0)
}
id, err := dao.Model.Insert(ctx, req)
if err != nil {
return nil, err
}
@@ -52,23 +67,55 @@ func (s *modelService) Create(ctx context.Context, req *dto.CreateModelReq) (res
func (s *modelService) Update(ctx context.Context, req *dto.UpdateModelReq) error {
//根据当前 isChatModel 来判断是否更新模型
if req.IsChatModel == 1 {
user, err := utils.GetUserInfo(ctx)
if err != nil {
return err
}
if req.IsChatModel == gconv.PtrInt(1) {
//判断当前用户是否有会话模型
model, err := dao.Model.GetByIsChatModel(ctx, user.UserName)
model, err := dao.Model.GetByIsChatModel(ctx)
if err != nil {
return err
}
if model != nil {
return errors.New("用户已存在会话模型,不能创建新的会话模型")
return errors.New("用户已存在会话模型,不能创建")
}
_, err = dao.Model.Update(ctx, req)
}
req.IsOwner = gconv.PtrInt(1)
admin, err := s.IsSuperAdmin(ctx)
if err != nil {
return err
}
_, err := dao.Model.Update(ctx, req)
if admin {
req.IsOwner = gconv.PtrInt(0)
_, err = dao.Model.Update(ctx, req)
if err != nil {
return err
}
return nil
}
var user *beans.User
user, err = utils.GetUserInfo(ctx)
if err != nil {
return err
}
// 判断当前传过来的模型id的模型是否是超级管理员的。如果是超管的进行创建否则更新
var count int
count, err = dao.Model.Count(ctx, &dto.GetModelReq{
ID: req.ID,
Creator: user.UserName,
})
if err != nil {
return err
}
if count == 0 {
insertDto := new(dto.CreateModelReq)
err = gconv.Struct(req, insertDto)
if err != nil {
return err
}
_, err = dao.Model.Insert(ctx, insertDto)
return err
}
_, err = dao.Model.Update(ctx, req)
return err
}
@@ -89,27 +136,29 @@ func (s *modelService) Get(ctx context.Context, id int64) (*entity.AsynchModel,
return model, nil
}
func (s *modelService) List(ctx context.Context, pageNum, pageSize int, req *dto.ListModelReq) (list []*entity.AsynchModel, total int64, err error) {
isSuperAdmin, err := IsSuperAdmin(ctx)
if err != nil {
return nil, 0, err
}
user, err := utils.GetUserInfo(ctx)
if err != nil {
return nil, 0, err
}
func (s *modelService) List(ctx context.Context, req *dto.ListModelReq) (list []*entity.AsynchModel, total int, err error) {
var models []*entity.AsynchModel
var count int64
if isSuperAdmin {
models, count, err = dao.Model.List(ctx, pageNum, pageSize, req.ModelName, req.ModelType, req.IsPrivate)
} else {
models, count, err = s.getModelsWithDedup(ctx, user.UserName, pageNum, pageSize, req.ModelName, req.ModelType, req.IsPrivate)
req.IsOwner = gconv.PtrInt(1)
admin, err := s.IsSuperAdmin(ctx)
if err != nil {
return
}
if admin {
req.IsOwner = gconv.PtrInt(0)
}
var user *beans.User
user, err = utils.GetUserInfo(ctx)
if err != nil {
return nil, 0, err
}
req.Creator = user.UserName
models, total, err = dao.Model.GetByCreatorAndPlatform(ctx, req)
if err != nil {
return
}
// 处理列表中每条记录的 JSONB 字段
for _, m := range models {
@@ -118,61 +167,7 @@ func (s *modelService) List(ctx context.Context, pageNum, pageSize int, req *dto
m.ResponseMapping = ParseJSONField(m.ResponseMapping)
m.ResponseBody = ParseJSONField(m.ResponseBody)
}
return models, count, nil
}
// getModelsWithDedup 获取普通用户的模型列表并去重
func (s *modelService) getModelsWithDedup(ctx context.Context, creator string, pageNum, pageSize int, modelNameLike string, modelType int, isPrivate int) (list []*entity.AsynchModel, total int64, err error) {
// 1. 查全量数据(不分页,便于去重)
allModels, err := dao.Model.GetByCreatorAndPlatform(ctx, creator, modelNameLike, modelType, isPrivate)
if err != nil {
return nil, 0, err
}
// 2. 按 modelName 去重,保留当前用户的
modelMap := make(map[string]*entity.AsynchModel)
for _, m := range allModels {
if m == nil {
continue
}
name := m.ModelName
_, ok := modelMap[name]
if !ok {
// 没有冲突,直接放进去
modelMap[name] = m
} else {
// 有冲突,保留当前用户创建的
if m.Creator == creator {
modelMap[name] = m
}
// 如果现有的就是当前用户的,不做任何替换
}
}
// 3. 转回切片并排序
deduped := make([]*entity.AsynchModel, 0, len(modelMap))
for _, m := range modelMap {
deduped = append(deduped, m)
}
sort.Slice(deduped, func(i, j int) bool {
return deduped[i].CreatedAt.After(deduped[j].CreatedAt)
})
// 4. 手动分页
total = int64(len(deduped))
if pageNum > 0 && pageSize > 0 {
start := (pageNum - 1) * pageSize
if start >= len(deduped) {
return []*entity.AsynchModel{}, total, nil
}
end := start + pageSize
if end > len(deduped) {
end = len(deduped)
}
deduped = deduped[start:end]
}
return deduped, total, nil
return models, total, nil
}
// GetModelTypesFromConfig 从配置文件读取模型类型
@@ -202,11 +197,6 @@ func GetModelTypesFromConfig(ctx context.Context) map[int]string {
}
func (s *modelService) UpdateChatModel(ctx context.Context, req *dto.UpdateChatModelReq) error {
user, err := utils.GetUserInfo(ctx)
if err != nil {
return err
}
// 校验新会话模型是否存在
newModel, err := dao.Model.Get(ctx, req.Id)
if err != nil {
@@ -217,48 +207,40 @@ func (s *modelService) UpdateChatModel(ctx context.Context, req *dto.UpdateChatM
}
// 获取当前用户会话模型
currentModel, err := dao.Model.GetByIsChatModel(ctx, user.UserName)
currentModel, err := dao.Model.GetByIsChatModel(ctx)
if err != nil {
return err
}
if currentModel.ModelsType != 1 {
return errors.New("当前模型为非推理模型,不能设置为会话模型")
}
err = gfdb.DB(ctx).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
if !g.IsEmpty(currentModel) {
if currentModel.ModelType != 1 {
return errors.New("当前模型为非推理模型,不能设置为会话模型")
}
// 如果点击的就是当前会话模型已经是1取消它设为0
if currentModel != nil && currentModel.Id == req.Id {
_, err = dao.Model.UpdateByID(ctx, &dto.UpdateModelReq{
// 如果点击的就是当前会话模型已经是1取消它设为0
if currentModel.Id != req.Id {
_, err = dao.Model.Update(ctx, &dto.UpdateModelReq{
ID: currentModel.Id,
IsChatModel: gconv.PtrInt(0),
})
if err != nil {
return err
}
}
}
// 设置当前为会话模型设为1
_, err = dao.Model.Update(ctx, &dto.UpdateModelReq{
ID: req.Id,
IsChatModel: 0,
IsChatModel: gconv.PtrInt(1),
})
return err
}
// 如果之前有会话模型取消它设为0
if currentModel != nil {
_, err = dao.Model.UpdateByID(ctx, &dto.UpdateModelReq{
ID: currentModel.Id,
IsChatModel: 0,
})
if err != nil {
return err
}
}
// 设置当前为会话模型设为1
_, err = dao.Model.UpdateByID(ctx, &dto.UpdateModelReq{
ID: req.Id,
IsChatModel: 1,
})
return err
}
func (s *modelService) GetIsChatModel(ctx context.Context) (*entity.AsynchModel, error) {
user, err := utils.GetUserInfo(ctx)
if err != nil {
return nil, err
}
model, err := dao.Model.GetByIsChatModel(ctx, user.UserName)
model, err := dao.Model.GetByIsChatModel(ctx)
if err != nil {
return nil, err
}

View File

@@ -3,8 +3,8 @@ package service
import (
"context"
"model-asynch/dao"
"model-asynch/model/dto"
"model-gateway/dao"
"model-gateway/model/dto"
)
type statService struct{}

View File

@@ -4,7 +4,7 @@ import (
"context"
"errors"
"model-asynch/model/entity"
"model-gateway/model/entity"
)
// StorageService 结果存储OSS/MinIO抽象

View File

@@ -7,7 +7,7 @@ import (
"mime/multipart"
"time"
"model-asynch/model/entity"
"model-gateway/model/entity"
commonHttp "gitea.com/red-future/common/http"
"github.com/gogf/gf/v2/frame/g"

View File

@@ -3,11 +3,12 @@ package service
import (
"context"
"errors"
"fmt"
"time"
"model-asynch/dao"
"model-asynch/model/dto"
"model-asynch/model/entity"
"model-gateway/dao"
"model-gateway/model/dto"
"model-gateway/model/entity"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
@@ -20,6 +21,7 @@ var Task = &taskService{}
type taskService struct{}
func (s *taskService) Create(ctx context.Context, req *dto.CreateTaskReq) (res *dto.CreateTaskRes, err error) {
fmt.Printf("打印请求:%+v", req)
startAt := time.Now()
// 固化 token/user 等信息
ctx = asyncCtx(ctx)
@@ -29,7 +31,7 @@ func (s *taskService) Create(ctx context.Context, req *dto.CreateTaskReq) (res *
if err != nil {
return nil, err
}
if m == nil || m.Enabled != 1 {
if m == nil || (m.Enabled != nil && *m.Enabled != 1) {
return nil, errors.New("模型不存在或未启用")
}

View File

@@ -7,8 +7,8 @@ import (
"time"
"unicode/utf8"
"model-asynch/dao"
"model-asynch/model/entity"
"model-gateway/dao"
"model-gateway/model/entity"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/grpool"
@@ -95,7 +95,7 @@ func (w *asyncWorker) handleOne(ctx context.Context, t *entity.AsynchTask, epicy
// ================================
return
}
if m == nil || m.Enabled != 1 {
if m == nil || (m.Enabled != nil && *m.Enabled != 1) {
errMsg := "模型不存在或未启用"
_ = dao.Task.UpdateFailedGlobal(ctx, t.Id, errMsg)
ReleaseQueueSlot(ctx, t.ModelName, t.TaskID)
@@ -172,9 +172,6 @@ func (w *asyncWorker) handleOne(ctx context.Context, t *entity.AsynchTask, epicy
contentType, ext = DetectFileType(data)
if utf8.Valid(data) && (strings.HasPrefix(contentType, "text/") || contentType == "application/json") {
textResult = string(data)
if len(textResult) > 20000 {
textResult = textResult[:20000]
}
}
tmpPath, err := saveTmpResult(t.TaskID, data, ext)
if err == nil && tmpPath != "" {

View File

@@ -18,13 +18,14 @@ CREATE TABLE IF NOT EXISTS asynch_models (
deleted_at TIMESTAMP(6), -- 删除时间(软删)
-- 业务字段
model_name VARCHAR(128) NOT NULL, -- 模型名称
models_type SMALLINT NOT NULL DEFAULT 0, -- 模型类型
model_type SMALLINT NOT NULL DEFAULT 0, -- 模型类型
base_url VARCHAR(256) NOT NULL, -- 模型地址
http_method VARCHAR(8) NOT NULL DEFAULT 'POST', -- 请求方式 GET/POST
head_msg VARCHAR(1024) DEFAULT '', -- 请求头绑定(支持多个,逗号分隔)示例 X-API:xxx,operation:true
is_private SMALLINT NOT NULL DEFAULT 0, -- 是否私有化 0-私有 1-公共
enabled SMALLINT NOT NULL DEFAULT 1, -- 是否启用 0停用 1-启用
is_chat_model SMALLINT NOT NULL DEFAULT 0, -- 是否为对话模型 0-否 1-是
is_owner SMALLINT NOT NULL DEFAULT 99, -- 1=当前用户创建的0=超级管理员的
api_key VARCHAR(256) NOT NULL DEFAULT '', -- 调用凭证,密钥
prompt TEXT NOT NULL DEFAULT '', -- 提示词内容(文本)
form_json JSONB NOT NULL DEFAULT '{}'::jsonb, -- 表单结构(用于前端渲染)
@@ -46,7 +47,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS uk_asynch_models_tenant_creator_chat ON asynch
CREATE UNIQUE INDEX IF NOT EXISTS uk_asynch_models_tenant_model_name ON asynch_models(tenant_id, creator, model_name);
CREATE INDEX IF NOT EXISTS idx_asynch_models_tenant_id ON asynch_models(tenant_id);
CREATE INDEX IF NOT EXISTS idx_asynch_models_model_name ON asynch_models(model_name);
CREATE INDEX IF NOT EXISTS idx_asynch_models_models_type ON asynch_models(models_type);
CREATE INDEX IF NOT EXISTS idx_asynch_models_model_type ON asynch_models(model_type);
CREATE INDEX IF NOT EXISTS idx_asynch_models_enabled ON asynch_models(enabled);
CREATE INDEX IF NOT EXISTS idx_asynch_models_deleted_at ON asynch_models(deleted_at);
@@ -60,13 +61,14 @@ COMMENT ON COLUMN asynch_models.updated_at IS '更新时间';
COMMENT ON COLUMN asynch_models.deleted_at IS '删除时间(软删)';
COMMENT ON COLUMN asynch_models.model_name IS '模型名称';
COMMENT ON COLUMN asynch_models.models_type IS '模型类型';
COMMENT ON COLUMN asynch_models.model_type IS '模型类型';
COMMENT ON COLUMN asynch_models.base_url IS '模型地址';
COMMENT ON COLUMN asynch_models.http_method IS '请求方式 GET/POST';
COMMENT ON COLUMN asynch_models.head_msg IS '请求头绑定(支持多个,逗号分隔)示例 X-API:xxx,operation:true';
COMMENT ON COLUMN asynch_models.is_private IS '是否私有化 0-私有 1-公共';
COMMENT ON COLUMN asynch_models.enabled IS '是否启用 0停用 1-启用';
COMMENT ON COLUMN asynch_models.is_chat_model IS '是否为对话模型 0-否 1-是';
COMMENT ON COLUMN asynch_models.is_owner IS '1=当前用户创建的0=超级管理员的';
COMMENT ON COLUMN asynch_models.api_key IS '调用凭证,密钥';
COMMENT ON COLUMN asynch_models.prompt IS '提示词内容(文本)';
COMMENT ON COLUMN asynch_models.form_json IS '表单结构(用于前端渲染,也用于后端校验)';
@@ -97,7 +99,7 @@ CREATE TABLE IF NOT EXISTS asynch_task (
updater VARCHAR(64) NOT NULL, -- 更新人
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 更新时间
deleted_at TIMESTAMP(6), -- 删除时间(软删)
-- 业务字段
model_name VARCHAR(128) NOT NULL, -- 模型名称
task_id VARCHAR(64) NOT NULL, -- 任务ID(对外返回)