diff --git a/consts/model/model_supplier.go b/consts/model/model_supplier.go new file mode 100644 index 0000000..5dd2545 --- /dev/null +++ b/consts/model/model_supplier.go @@ -0,0 +1,108 @@ +package model + +import ( + "model-gateway/consts/public" + + "github.com/gogf/gf/v2/util/gconv" +) + +// 供应商编码常量 +const ( + SupplierAliyun = 1 + SupplierVolcengine = 2 + SupplierTencent = 3 + SupplierHuawei = 4 + SupplierBaidu = 5 + SupplierOpenAI = 6 + SupplierAzure = 7 + SupplierAWS = 8 + SupplierGoogle = 9 + SupplierDeepSeek = 10 + SupplierMoonshot = 11 + SupplierZhipu = 12 + SupplierBaichuan = 13 + SupplierMinimax = 14 + SupplierXunfei = 15 + SupplierOthers = 16 +) + +// SupplierType 供应商编码类型 +type SupplierType *int8 + +// SupplierItem 供应商项 +type SupplierItem struct { + Code SupplierType `json:"code"` + Desc string `json:"desc"` +} + +// 名称映射【唯一文案维护】 +var supplierNameMap = map[int]string{ + SupplierAliyun: "阿里云百炼", + SupplierVolcengine: "火山引擎", + SupplierTencent: "腾讯云", + SupplierHuawei: "华为云", + SupplierBaidu: "百度智能云", + SupplierOpenAI: "OpenAI", + SupplierAzure: "Azure OpenAI", + SupplierAWS: "AWS Bedrock", + SupplierGoogle: "Google Cloud", + SupplierDeepSeek: "DeepSeek", + SupplierMoonshot: "Moonshot", + SupplierZhipu: "智谱AI", + SupplierBaichuan: "百川智能", + SupplierMinimax: "MiniMax", + SupplierXunfei: "科大讯飞", + SupplierOthers: "其他", +} + +// 供应商展示顺序 +var supplierOrder = []int{ + SupplierAliyun, SupplierVolcengine, SupplierTencent, SupplierHuawei, SupplierBaidu, + SupplierOpenAI, SupplierAzure, SupplierAWS, SupplierGoogle, SupplierDeepSeek, + SupplierMoonshot, SupplierZhipu, SupplierBaichuan, SupplierMinimax, SupplierXunfei, SupplierOthers, +} + +// 全局供应商实例 +var ( + SupplierItemAliyun = newSupplierItem(gconv.PtrInt8(SupplierAliyun)) + SupplierItemVolcengine = newSupplierItem(gconv.PtrInt8(SupplierVolcengine)) + SupplierItemTencent = newSupplierItem(gconv.PtrInt8(SupplierTencent)) + SupplierItemHuawei = newSupplierItem(gconv.PtrInt8(SupplierHuawei)) + SupplierItemBaidu = newSupplierItem(gconv.PtrInt8(SupplierBaidu)) + SupplierItemOpenAI = newSupplierItem(gconv.PtrInt8(SupplierOpenAI)) + SupplierItemAzure = newSupplierItem(gconv.PtrInt8(SupplierAzure)) + SupplierItemAWS = newSupplierItem(gconv.PtrInt8(SupplierAWS)) + SupplierItemGoogle = newSupplierItem(gconv.PtrInt8(SupplierGoogle)) + SupplierItemDeepSeek = newSupplierItem(gconv.PtrInt8(SupplierDeepSeek)) + SupplierItemMoonshot = newSupplierItem(gconv.PtrInt8(SupplierMoonshot)) + SupplierItemZhipu = newSupplierItem(gconv.PtrInt8(SupplierZhipu)) + SupplierItemBaichuan = newSupplierItem(gconv.PtrInt8(SupplierBaichuan)) + SupplierItemMinimax = newSupplierItem(gconv.PtrInt8(SupplierMinimax)) + SupplierItemXunfei = newSupplierItem(gconv.PtrInt8(SupplierXunfei)) + SupplierItemOthers = newSupplierItem(gconv.PtrInt8(SupplierOthers)) +) + +func newSupplierItem(code SupplierType) SupplierItem { + val := int(*code) + return SupplierItem{ + Code: code, + Desc: supplierNameMap[val], + } +} + +// GetSupplierDescByCode 根据编码获取供应商名称 +func GetSupplierDescByCode(code int) string { + return supplierNameMap[code] +} + +// GetSupplierOptionList 获取供应商下拉列表 +func GetSupplierOptionList() []*public.Option { + var list []*public.Option + for _, code := range supplierOrder { + list = append(list, &public.Option{ + Value: code, + Label: supplierNameMap[code], + }) + } + return list +} diff --git a/consts/model/model_type.go b/consts/model/model_type.go new file mode 100644 index 0000000..8f0cfe9 --- /dev/null +++ b/consts/model/model_type.go @@ -0,0 +1,186 @@ +package model + +import ( + "model-gateway/consts/public" + + "github.com/gogf/gf/v2/util/gconv" +) + +// 模型类型编码常量 +const ( + TypeInference = 100 // 推理模型 + TypeImage = 200 // 图片模型 + TypeAudio = 300 // 音频模型 + TypeVector = 400 // 向量化模型 + TypeOmni = 500 // 全模态模型 + TypeVideo = 600 // 视频模型 + + // 图片子类型 + ImageSubTextToImage = 201 + ImageSubImageToImage = 202 + ImageSubImageEdit = 203 + ImageSubImageVariation = 204 + ImageSubImageTextToImage = 205 + + // 音频子类型 + AudioSubTextToSpeech = 301 + AudioSubSpeechToText = 302 + AudioSubSpeechToSpeech = 303 + + // 向量化子类型 + VectorSubEmbedding = 401 + VectorSubRerank = 402 + + // 全模态子类型 + OmniSubTextImageAudio = 501 + OmniSubVision = 502 + + // 视频子类型 + VideoSubTextToVideo = 601 + VideoSubImageToVideo = 602 + VideoSubImageTextToVideo = 603 + VideoSubVideoToVideo = 604 +) + +// ModelType 编码类型 +type ModelType *int + +// ModelTypeItem 模型类型项 +type ModelTypeItem struct { + Code ModelType `json:"code"` + Desc string `json:"desc"` +} + +// TypeTree 树形结构 +type TypeTree struct { + Value int `json:"value"` + Label string `json:"label"` + Children []*public.Option `json:"children"` +} + +// 名称映射表【唯一文案维护入口】 +var typeNameMap = map[int]string{ + TypeInference: "推理模型", + TypeImage: "图片模型", + TypeAudio: "音频模型", + TypeVector: "向量化模型", + TypeOmni: "全模态模型", + TypeVideo: "视频模型", + + ImageSubTextToImage: "文生图", + ImageSubImageToImage: "图生图", + ImageSubImageEdit: "图片编辑", + ImageSubImageVariation: "图片变体", + ImageSubImageTextToImage: "图文生图", + + AudioSubTextToSpeech: "文生音", + AudioSubSpeechToText: "音生文", + AudioSubSpeechToSpeech: "音生音", + + VectorSubEmbedding: "文本嵌入", + VectorSubRerank: "重排序", + + OmniSubTextImageAudio: "文图音", + OmniSubVision: "视觉理解", + + VideoSubTextToVideo: "文生视频", + VideoSubImageToVideo: "图生视频", + VideoSubImageTextToVideo: "图文生视频", + VideoSubVideoToVideo: "视频生视频", +} + +// 父子级映射(仅存有子项的分类) +var parentChildMap = map[int][]int{ + TypeImage: {ImageSubTextToImage, ImageSubImageToImage, ImageSubImageEdit, ImageSubImageVariation, ImageSubImageTextToImage}, + TypeAudio: {AudioSubTextToSpeech, AudioSubSpeechToText, AudioSubSpeechToSpeech}, + TypeVector: {VectorSubEmbedding, VectorSubRerank}, + TypeOmni: {OmniSubTextImageAudio, OmniSubVision}, + TypeVideo: {VideoSubTextToVideo, VideoSubImageToVideo, VideoSubImageTextToVideo, VideoSubVideoToVideo}, +} + +// 一级分类展示顺序 +var parentTypeOrder = []int{ + TypeInference, TypeImage, TypeAudio, TypeVector, TypeOmni, TypeVideo, +} + +// 全局实例:一级 + 全部二级子类型,统一通过 newItem 构造,文案仅维护在 typeNameMap +var ( + // 一级类型 + ModelTypeInference = newItem(gconv.PtrInt(TypeInference)) + ModelTypeImage = newItem(gconv.PtrInt(TypeImage)) + ModelTypeAudio = newItem(gconv.PtrInt(TypeAudio)) + ModelTypeVector = newItem(gconv.PtrInt(TypeVector)) + ModelTypeOmni = newItem(gconv.PtrInt(TypeOmni)) + ModelTypeVideo = newItem(gconv.PtrInt(TypeVideo)) + + // 图片二级子类型 + ModelImageSubTextToImage = newItem(gconv.PtrInt(ImageSubTextToImage)) + ModelImageSubImageToImage = newItem(gconv.PtrInt(ImageSubImageToImage)) + ModelImageSubImageEdit = newItem(gconv.PtrInt(ImageSubImageEdit)) + ModelImageSubImageVariation = newItem(gconv.PtrInt(ImageSubImageVariation)) + ModelImageSubImageTextToImage = newItem(gconv.PtrInt(ImageSubImageTextToImage)) + + // 音频二级子类型 + ModelAudioSubTextToSpeech = newItem(gconv.PtrInt(AudioSubTextToSpeech)) + ModelAudioSubSpeechToText = newItem(gconv.PtrInt(AudioSubSpeechToText)) + ModelAudioSubSpeechToSpeech = newItem(gconv.PtrInt(AudioSubSpeechToSpeech)) + + // 向量化二级子类型 + ModelVectorSubEmbedding = newItem(gconv.PtrInt(VectorSubEmbedding)) + ModelVectorSubRerank = newItem(gconv.PtrInt(VectorSubRerank)) + + // 全模态二级子类型 + ModelOmniSubTextImageAudio = newItem(gconv.PtrInt(OmniSubTextImageAudio)) + ModelOmniSubVision = newItem(gconv.PtrInt(OmniSubVision)) + + // 视频二级子类型 + ModelVideoSubTextToVideo = newItem(gconv.PtrInt(VideoSubTextToVideo)) + ModelVideoSubImageToVideo = newItem(gconv.PtrInt(VideoSubImageToVideo)) + ModelVideoSubImageTextToVideo = newItem(gconv.PtrInt(VideoSubImageTextToVideo)) + ModelVideoSubVideoToVideo = newItem(gconv.PtrInt(VideoSubVideoToVideo)) +) + +// newItem 构造方法:自动从 typeNameMap 读取描述 +func newItem(code ModelType) ModelTypeItem { + val := int(*code) + return ModelTypeItem{ + Code: code, + Desc: typeNameMap[val], + } +} + +// GetDescByCode 根据编码获取名称 +func GetDescByCode(code int) string { + return typeNameMap[code] +} + +// GetTypeTreeList 生成树形数据 +func GetTypeTreeList() []*TypeTree { + var list []*TypeTree + for _, parentCode := range parentTypeOrder { + tree := &TypeTree{ + Value: parentCode, + Label: typeNameMap[parentCode], + Children: make([]*public.Option, 0), + } + if childCodes, ok := parentChildMap[parentCode]; ok { + for _, c := range childCodes { + tree.Children = append(tree.Children, &public.Option{ + Value: c, + Label: typeNameMap[c], + }) + } + } + list = append(list, tree) + } + return list +} + +// GetAllTypeOption 全量平铺选项 +func GetAllTypeOption() []*public.Option { + var list []*public.Option + for code, label := range typeNameMap { + list = append(list, &public.Option{Value: code, Label: label}) + } + return list +} diff --git a/consts/model/response_type.go b/consts/model/response_type.go new file mode 100644 index 0000000..3e14c82 --- /dev/null +++ b/consts/model/response_type.go @@ -0,0 +1,27 @@ +package model + +import "github.com/gogf/gf/v2/util/gconv" + +var ( + ResponseTypeSync = newResponseType(gconv.PtrInt8(1), "sync") // 同步 + ResponseTypeAsync = newResponseType(gconv.PtrInt8(2), "async") // 异步 + ResponseTypeStream = newResponseType(gconv.PtrInt8(3), "stream") // 流 +) + +type ResponseType *int8 + +type responseType struct { + code ResponseType + desc string +} + +func (s responseType) Code() ResponseType { + return s.code +} +func (s responseType) Desc() string { + return s.desc +} + +func newResponseType(code ResponseType, desc string) responseType { + return responseType{code: code, desc: desc} +} diff --git a/consts/public/public.go b/consts/public/public.go index f20e1dc..08ef959 100644 --- a/consts/public/public.go +++ b/consts/public/public.go @@ -1,5 +1,11 @@ package public +// Option 通用下拉选项 +type Option struct { + Value int `json:"value"` + Label string `json:"label"` +} + const ( CallModeSync = 0 // 同步调用 CallModeAsync = 1 // 异步调用 diff --git a/consts/public/table_name.go b/consts/public/table_name.go index a7f89cb..2042ad2 100644 --- a/consts/public/table_name.go +++ b/consts/public/table_name.go @@ -5,8 +5,9 @@ const ( ) const ( - TableNameModel = "model_gateway_models" // 模型表 - TableNameTask = "model_gateway_task" // 任务表 - TableNameOpLog = "model_gateway_logs_op" // 操作日志表 - TableNameStat = "model_gateway_logs_stat" // 按天统计表 + TableNameModel = "model_gateway_models" // 模型表 + TableNameTask = "model_gateway_task" // 任务表 + TableNameOpLog = "model_gateway_logs_op" // 操作日志表 + TableNameStat = "model_gateway_logs_stat" // 按天统计表 + TableNameModelManage = "model_gateway_model_manage" ) diff --git a/controller/model_manage_controller.go b/controller/model_manage_controller.go new file mode 100644 index 0000000..5a5e9ee --- /dev/null +++ b/controller/model_manage_controller.go @@ -0,0 +1,56 @@ +package controller + +import ( + "context" + "model-gateway/model/dto" + "model-gateway/service" + + "gitea.redpowerfuture.com/red-future/common/beans" +) + +// ModelManage 模型配置控制器 +var ModelManage = new(modelManage) + +type modelManage struct{} + +// CreateModel 添加配置 +func (c *modelManage) CreateModel(ctx context.Context, req *dto.CreateModelManageReq) (res *dto.CreateModelManageRes, err error) { + return service.ModelManage.Create(ctx, req) +} + +// UpdateModel 更改配置 +func (c *modelManage) UpdateModel(ctx context.Context, req *dto.UpdateModelManageReq) (res *beans.ResponseEmpty, err error) { + err = service.ModelManage.Update(ctx, req) + return +} + +// DeleteModel 删除配置 +func (c *modelManage) DeleteModel(ctx context.Context, req *dto.DeleteModelManageReq) (res *beans.ResponseEmpty, err error) { + err = service.ModelManage.Delete(ctx, req) + return +} + +// GetModel 获取配置 +func (c *modelManage) GetModel(ctx context.Context, req *dto.GetModelManageReq) (res *dto.GetModelManageRes, err error) { + return service.ModelManage.Get(ctx, req) +} + +// ListModel 配置列表 +func (c *modelManage) ListModel(ctx context.Context, req *dto.ListModelManageReq) (res *dto.ListModelManageRes, err error) { + return service.ModelManage.List(ctx, req) +} + +// CheckChatModel 检查是否为聊天模型 +func (c *modelManage) CheckChatModel(ctx context.Context, req *dto.CheckChatModelReq) (res *dto.CheckChatModelRes, err error) { + return service.ModelManage.CheckChatModel(ctx, req) +} + +// ListType 模型类型列表 +func (c *modelManage) ListType(ctx context.Context, req *dto.ModelTypeReq) (res *dto.ModelTypeRes, err error) { + return service.ModelManage.GetModelType(ctx, req) +} + +// ListOperator 运营商列表 +func (c *modelManage) ListOperator(ctx context.Context, req *dto.ModelSupplierReq) (res *dto.ModelSupplierRes, err error) { + return service.ModelManage.GetModelSupplier(ctx, req) +} diff --git a/dao/model_manage_dao.go b/dao/model_manage_dao.go new file mode 100644 index 0000000..0052582 --- /dev/null +++ b/dao/model_manage_dao.go @@ -0,0 +1,193 @@ +package dao + +import ( + "context" + "fmt" + "model-gateway/consts/public" + "model-gateway/model/dto" + "model-gateway/model/entity" + + "gitea.redpowerfuture.com/red-future/common/db/gfdb" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +var ModelManage = &modelManageDao{} + +type modelManageDao struct{} + +// Insert 插入 +func (d *modelManageDao) Insert(ctx context.Context, req *dto.CreateModelManageReq) (id int64, err error) { + var e = new(entity.ModelManage) + err = gconv.Struct(req, &e) + if err != nil { + return + } + r, err := gfdb.DB(ctx, public.DbNameModelGateway).Model(ctx, public.TableNameModelManage).Insert(e) + if err != nil { + return + } + return r.LastInsertId() +} + +// Update 更新 +func (d *modelManageDao) Update(ctx context.Context, req *dto.UpdateModelManageReq) (rows int64, err error) { + r, err := gfdb.DB(ctx, public.DbNameModelGateway).Model(ctx, public.TableNameModelManage).OmitEmpty().Data(req).Where(entity.ModelManageCol.Id, req.Id).Update() + if err != nil { + return + } + return r.RowsAffected() +} + +// Delete 删除 +func (d *modelManageDao) Delete(ctx context.Context, req *dto.DeleteModelManageReq) (rows int64, err error) { + r, err := gfdb.DB(ctx, public.DbNameModelGateway).Model(ctx, public.TableNameModelManage).Where(entity.ModelManageCol.Id, req.Id).Delete() + if err != nil { + return + } + return r.RowsAffected() +} + +func (d *modelManageDao) Get(ctx context.Context, req *dto.GetModelManage, fields ...string) (res *entity.ModelManage, err error) { + r, err := gfdb.DB(ctx, public.DbNameModelGateway).Model(ctx, public.TableNameModelManage).Cache(ctx). + OmitEmpty(). + Where(entity.ModelManageCol.ModelName, req.ModelName). + Where(entity.ModelManageCol.ChatModel, req.ChatModel). + Where(entity.ModelManageCol.Creator, req.Creator). + Fields(fields).One() + if err != nil { + return + } + err = r.Struct(&res) + return +} + +func (d *modelManageDao) GetNotTenantId(ctx context.Context, req *dto.GetModelManageReq, fields ...string) (res *entity.ModelManage, err error) { + // 获取表前缀 + prefix := g.Cfg().MustGet(ctx, fmt.Sprintf("database.%s.0.prefix", public.DbNameModelGateway)).String() + table := prefix + public.TableNameModelManage + // 动态拼接 SELECT 列 + var field string + if !g.IsEmpty(fields) { + for k, v := range fields { + if k == len(fields)-1 { + field = field + v + } else { + field = field + v + "," + } + } + } else { + field = "*" + } + // 动态拼接 WHERE 条件 + var whereCondition string + var queryParams []interface{} + if !g.IsEmpty(req.Id) { + whereCondition = fmt.Sprintf(" AND %s=(?) ", entity.ModelManageCol.Id) + queryParams = append(queryParams, req.Id) + } + whereCondition = whereCondition + " AND " + entity.ModelManageCol.DeletedAt + " IS NULL " + + sql := `SELECT ` + field + ` FROM ` + table + ` WHERE 1=1 ` + whereCondition + `` + // 执行查询 + result, err := gfdb.DB(ctx, public.DbNameModelGateway).GetOne(ctx, sql, queryParams...) + if err != nil { + return nil, err + } + err = result.Struct(&res) + return +} +func (d *modelManageDao) ListNotTenantId(ctx context.Context, req *dto.ListModelManageReq, fields ...string) (res []*entity.ModelManage, total int, err error) { + // 获取表前缀 + prefix := g.Cfg().MustGet(ctx, fmt.Sprintf("database.%s.0.prefix", public.DbNameModelGateway)).String() + table := prefix + public.TableNameModelManage + + // 动态拼接 SELECT 列 + var field string + if !g.IsEmpty(fields) { + for k, v := range fields { + if k == len(fields)-1 { + field = field + v + } else { + field = field + v + "," + } + } + } else { + field = "*" + } + + // 动态拼接 WHERE 条件 + var whereCondition string + var queryParams []interface{} + + if !g.IsEmpty(req.ModelName) { + whereCondition += fmt.Sprintf(" AND %s=(?) ", entity.ModelManageCol.ModelName) + queryParams = append(queryParams, req.ModelName) + } + if !g.IsEmpty(req.ModelType) { + whereCondition += fmt.Sprintf(" AND %s=(?) ", entity.ModelManageCol.ModelType) + queryParams = append(queryParams, req.ModelType) + } + if !g.IsEmpty(req.Creator) { + whereCondition += fmt.Sprintf(" AND (%s=(?) OR %s=true) ", entity.ModelManageCol.Creator, entity.ModelManageCol.SystemModel) + queryParams = append(queryParams, req.Creator) + } + whereCondition = whereCondition + " AND " + entity.ModelManageCol.DeletedAt + " IS NULL " + + // 1. 统计去重后总条数 + countSql := fmt.Sprintf( + `SELECT COUNT(DISTINCT %s) FROM %s WHERE 1=1 %s`, + entity.ModelManageCol.ModelName, + table, + whereCondition, + ) + countResult, err := gfdb.DB(ctx, public.DbNameModelGateway).GetOne(ctx, countSql, queryParams...) + if err != nil { + return nil, 0, err + } + type crr struct { + Count int64 `db:"count"` + } + var cr crr + if err = countResult.Struct(&cr); err != nil { + return nil, 0, err + } + total = int(cr.Count) + // 2. 分页处理 + limitSql := "" + if req.Page != nil { + pageNum := int(req.Page.PageNum) + pageSize := int(req.Page.PageSize) + offset := (pageNum - 1) * pageSize + limitSql = fmt.Sprintf(" LIMIT ? OFFSET ? ") + // PG 语法 LIMIT 条数 OFFSET 偏移量 + queryParams = append(queryParams, pageSize, offset) + } + + // 排序优先级:1.分组字段ModelName 2.SystemModel升序(false在前,保留用户数据) 3.创建时间倒序 + orderSql := fmt.Sprintf( + " ORDER BY %s, %s ASC, %s DESC ", + entity.ModelManageCol.ModelName, + entity.ModelManageCol.SystemModel, + entity.ModelManageCol.CreatedAt, + ) + + // PG DISTINCT ON 按模型名去重,同名只取第一条(用户数据) + sql := fmt.Sprintf( + `SELECT DISTINCT ON (%s) %s FROM %s WHERE 1=1 %s %s %s`, + entity.ModelManageCol.ModelName, + field, + table, + whereCondition, + orderSql, + limitSql, + ) + + // 执行查询 + result, err := gfdb.DB(ctx, public.DbNameModelGateway).GetAll(ctx, sql, queryParams...) + if err != nil { + return nil, 0, err + } + err = result.Structs(&res) + return +} diff --git a/go.mod b/go.mod index ceebe44..8a7dc17 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module model-gateway go 1.26.1 require ( - gitea.redpowerfuture.com/red-future/common v0.0.29 + gitea.redpowerfuture.com/red-future/common v0.0.30 github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.2 github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.2 github.com/gogf/gf/v2 v2.10.2 diff --git a/go.sum b/go.sum index 9e4e87b..06dae1c 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= gitea.redpowerfuture.com/red-future/common v0.0.29 h1:5McaN5pSewvrLUHQzWMX6EaUvD+B5I5bMYoU+clHJk4= gitea.redpowerfuture.com/red-future/common v0.0.29/go.mod h1:50U1Xi+Ie56z09S5LQbZvaken0Mxv3OeS9LgR7U/ZRY= +gitea.redpowerfuture.com/red-future/common v0.0.30 h1:UkWYubUsLPJQUhEhc9Ca2UPg5iLC6jzURo3ngztINYg= +gitea.redpowerfuture.com/red-future/common v0.0.30/go.mod h1:zuhqbWHd/YICalYJnmecY8Vqo5j4dtZOD63P96fiFeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= diff --git a/main.go b/main.go index ff2821a..7f55c07 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,7 @@ func main() { // 注册路由 http.RouteRegister([]interface{}{ + controller.ModelManage, controller.ModelGatewayModels, controller.ModelGatewayTask, controller.ModelGatewayLogsStat, diff --git a/model/dto/model_manage_dto.go b/model/dto/model_manage_dto.go new file mode 100644 index 0000000..b66ae70 --- /dev/null +++ b/model/dto/model_manage_dto.go @@ -0,0 +1,127 @@ +package dto + +import ( + "model-gateway/consts/model" + "model-gateway/consts/public" + "model-gateway/model/entity" + + "gitea.redpowerfuture.com/red-future/common/beans" + "github.com/gogf/gf/v2/frame/g" +) + +// CreateModelManageReq 添加模型配置 +type CreateModelManageReq struct { + g.Meta `path:"/createModelManage" method:"post" tags:"new模型管理" summary:"new创建模型配置" dc:"new添加新的模型配置"` + ModelSupplier model.SupplierType `json:"modelSupplier" v:"required#模型供应商不能为空" dc:"模型供应商"` + ModelName string `json:"modelName" v:"required#模型名称不能为空" dc:"模型名称"` + ModelType model.ModelType `json:"modelType" v:"required#模型类型不能为空" dc:"模型类型"` + BaseURL string `json:"baseUrl" v:"required#模型服务地址不能为空" dc:"模型服务地址"` + SystemModel *bool `json:"systemModel" dc:"系统模型"` + HttpMethod string `json:"httpMethod" dc:"请求方式:GET/POST" d:"POST"` + ChatModel *bool `json:"chatModel" dc:"对话模型"` + ResponseType model.ResponseType `json:"responseType" v:"required#调用模式不能为空" dc:"调用模式:0-同步 1-异步 2-流式"` + ApiKey string `json:"apiKey" dc:"调用凭证/密钥"` + Enabled *bool `json:"enabled" dc:"启用"` + RequestHeadMapping map[string]string `json:"requestHeadMapping" dc:"请求头映射"` + RequestBodyMapping map[string]any `json:"requestBodyMapping" dc:"请求体映射"` + ResponseMapping map[string]any `json:"responseMapping" dc:"返回映射"` + ResponseBodyMapping map[string]string `json:"responseBodyMapping" dc:"返回体映射"` + MaxConcurrency int `json:"maxConcurrency" dc:"最大并发数(默认10)"` + TokenMapping *entity.TokenMapping `json:"tokenMapping" dc:"token映射"` + AsyncTaskMapping *entity.AsyncTaskMapping `json:"asyncTaskMapping" dc:"异步任务映射"` + TokenPredictPrice float64 `json:"tokenPredictPrice" dc:"模型Token预估价格"` + TokenPredictPriceUnit string `json:"tokenPredictPriceUnit" dc:"模型Token预估价格单位"` + MaxTokens int `json:"maxTokens" dc:"最大token数"` + MaxDuration int `json:"maxDuration" dc:"最大时长"` + LastFrame string `json:"lastFrame" dc:"视频的尾帧图像"` +} + +type CreateModelManageRes struct { + Id int64 `json:"id,string" dc:"配置ID"` +} + +type UpdateModelManageReq struct { + g.Meta `path:"/updateModelManage" method:"put" tags:"new模型管理" summary:"new更新模型配置" dc:"new更新指定ID的模型配置"` + Id int64 `json:"id" v:"required#id不能为空" dc:"配置ID"` + ModelSupplier model.SupplierType `json:"modelSupplier" dc:"模型供应商"` + ModelName string `json:"modelName" dc:"模型名称"` + ModelType model.ModelType `json:"modelType" dc:"模型类型"` + BaseURL string `json:"baseUrl" dc:"模型服务地址"` + SystemModel *bool `json:"systemModel" dc:"系统模型"` + HttpMethod string `json:"httpMethod" dc:"请求方式:GET/POST"` + ChatModel *bool `json:"chatModel" dc:"对话模型"` + ResponseType model.ResponseType `json:"responseType" dc:"调用模式:0-同步 1-异步 2-流式"` + ApiKey string `json:"apiKey" dc:"调用凭证/密钥"` + Enabled *bool `json:"enabled" dc:"启用"` + RequestHeadMapping map[string]string `json:"requestHeadMapping" dc:"请求头映射"` + RequestBodyMapping map[string]any `json:"requestBodyMapping" dc:"请求体映射"` + ResponseMapping map[string]any `json:"responseMapping" dc:"返回映射"` + ResponseBodyMapping map[string]string `json:"responseBodyMapping" dc:"返回主体映射"` + MaxConcurrency int `json:"maxConcurrency" dc:"最大并发数(默认10)"` + TokenMapping *entity.TokenMapping `json:"tokenMapping" dc:"token映射"` + AsyncTaskMapping *entity.AsyncTaskMapping `json:"asyncTaskMapping" dc:"异步任务映射"` + TokenPredictPrice float64 `json:"tokenPredictPrice" dc:"模型Token预估价格"` + TokenPredictPriceUnit string `json:"tokenPredictPriceUnit" dc:"模型Token预估价格单位"` + MaxTokens int `json:"maxTokens" dc:"最大token数"` + MaxDuration int `json:"maxDuration" dc:"最大时长"` + LastFrame string `json:"lastFrame" dc:"视频的尾帧图像"` +} + +type DeleteModelManageReq struct { + g.Meta `path:"/deleteModelManage" method:"delete" tags:"new模型管理" summary:"new删除模型配置" dc:"new删除指定ID的模型配置"` + Id int64 `p:"id" json:"id,string" v:"required#id不能为空" dc:"配置ID"` +} + +type GetModelManage struct { + ChatModel *bool `json:"chatModel" dc:"对话模型"` + Creator string `json:"creator" dc:"创建人"` + ModelName string `json:"modelName" dc:"模型名称"` +} + +type GetModelManageReq struct { + g.Meta `path:"/getModelManage" method:"get" tags:"new模型管理" summary:"new获取模型配置" dc:"new获取指定ID的模型配置"` + Id int64 `p:"id" json:"id,string" v:"required#id不能为空" dc:"配置ID"` +} + +type GetModelManageRes struct { + *entity.ModelManage `json:"modelManage"` +} + +// ListModelManageReq 配置列表 +type ListModelManageReq struct { + g.Meta `path:"/listModelManage" method:"get" tags:"new模型管理" summary:"new模型配置列表" dc:"new分页获取模型配置列表"` + *beans.Page `json:"page"` + ModelName string `p:"modelName" json:"modelName" dc:"模型名称(模糊查询,可选)"` + ModelType int `p:"modelType" json:"modelType" dc:"模型类型"` + Creator string `json:"creator" dc:"创建人"` +} + +type ListModelManageRes struct { + List []*entity.ModelManage `json:"list" dc:"列表数据"` + Total int `json:"total" dc:"总数"` +} + +type CheckChatModelReq struct { + g.Meta `path:"/checkChatModel" method:"get" tags:"new模型管理" summary:"new检查是否为聊天模型" dc:"new检查是否为聊天模型"` +} + +type CheckChatModelRes struct { + IsChatModel bool `json:"isChatModel" dc:"是否为聊天模型"` +} + +// ModelTypeReq 模型类型列表(分页) +type ModelTypeReq struct { + g.Meta `path:"/modelType" method:"get" tags:"new模型管理" summary:"new模型类型列表" dc:"new分页获取模型类型列表"` +} + +type ModelTypeRes struct { + List []*model.TypeTree `json:"list" dc:"模型类型ID到名称的映射"` +} + +type ModelSupplierReq struct { + g.Meta `path:"/modelSupplier" method:"get" tags:"new模型管理" summary:"new获取运营商列表" dc:"new获取运营商列表"` +} + +type ModelSupplierRes struct { + List []*public.Option `json:"list" dc:"运营商名称到ID的映射"` +} diff --git a/model/entity/model_manage.go b/model/entity/model_manage.go new file mode 100644 index 0000000..3004310 --- /dev/null +++ b/model/entity/model_manage.go @@ -0,0 +1,102 @@ +package entity + +import ( + "model-gateway/consts/model" + + "gitea.redpowerfuture.com/red-future/common/beans" +) + +type modelManageCol struct { + beans.SQLBaseCol + ModelSupplier string + ModelName string + ModelType string + BaseURL string + SystemModel string + HttpMethod string + ChatModel string + ResponseType string + ApiKey string + Enabled string + RequestHeadMapping string + RequestBodyMapping string + ResponseMapping string + ResponseBodyMapping string + MaxConcurrency string + TokenPredictPrice string + TokenPredictPriceUnit string + MaxTokens string + MaxDuration string + LastFrame string +} + +var ModelManageCol = modelManageCol{ + SQLBaseCol: beans.DefSQLBaseCol, + ModelSupplier: "model_supplier", + ModelName: "model_name", + ModelType: "model_type", + BaseURL: "base_url", + SystemModel: "system_model", + HttpMethod: "http_method", + ChatModel: "chat_model", + ResponseType: "response_type", + ApiKey: "api_key", + Enabled: "enabled", + RequestHeadMapping: "request_head_mapping", + RequestBodyMapping: "request_body_mapping", + ResponseMapping: "response_mapping", + ResponseBodyMapping: "response_body_mapping", + MaxConcurrency: "max_concurrency", + TokenPredictPrice: "token_predict_price", + TokenPredictPriceUnit: "token_predict_price_unit", + MaxTokens: "max_tokens", + MaxDuration: "max_duration", + LastFrame: "last_frame", +} + +type ModelManage struct { + beans.SQLBaseDO `orm:",inline"` + ModelSupplier model.SupplierType `orm:"model_supplier" json:"modelSupplier" description:"模型供应商"` + ModelName string `orm:"model_name" json:"modelName" description:"模型名称"` + ModelType model.ModelType `orm:"model_type" json:"modelType" description:"模型类型"` + BaseURL string `orm:"base_url" json:"baseUrl" description:"模型地址"` + SystemModel *bool `orm:"system_model" json:"systemModel" description:"系统模型"` + HttpMethod string `orm:"http_method" json:"httpMethod" description:"http方法"` + ChatModel *bool `orm:"chat_model" json:"ChatModel" description:"是否聊天模型"` + ResponseType model.ResponseType `orm:"response_type" json:"responseType" description:"返回类型:1同步,2异步,3流"` + ApiKey string `orm:"api_key" json:"apiKey" description:"api key"` + Enabled *bool `orm:"enabled" json:"enabled" description:"是否启用"` + RequestHeadMapping map[string]string `orm:"request_head_mapping" json:"requestHeadMapping" description:"请求头映射"` + RequestBodyMapping map[string]any `orm:"request_body_mapping" json:"requestBodyMapping" description:"请求体映射"` + ResponseMapping map[string]any `orm:"response_mapping" json:"responseMapping" description:"响应映射"` + ResponseBodyMapping map[string]string `orm:"response_body_mapping" json:"responseBodyMapping" description:"响应主体映射"` + MaxConcurrency int `orm:"max_concurrency" json:"maxConcurrency" description:"最大并发数"` + TokenMapping *TokenMapping `orm:"token_mapping" json:"tokenMapping" description:"token映射"` + AsyncTaskMapping *AsyncTaskMapping `orm:"async_task_mapping" json:"asyncTaskMapping" description:"异步任务映射"` + TokenPredictPrice float64 `orm:"token_predict_price" json:"tokenPredictPrice" description:"模型Token预估价格"` + TokenPredictPriceUnit string `orm:"token_predict_price_unit" json:"tokenPredictPriceUnit" description:"模型token预估价格单位(秒,百万Token,千Token,字数)"` + MaxTokens int `orm:"max_tokens" json:"maxTokens" description:"最大token数"` + MaxDuration int `orm:"max_duration" json:"maxDuration" description:"最大时长(秒)"` + LastFrame string `orm:"last_frame" json:"lastFrame" description:"视频的尾帧图像"` +} + +type TokenMapping struct { + PromptTokens string `json:"promptTokens" dc:"输入token"` + CompletionTokens string `json:"completionTokens" dc:"输出token"` + TotalTokens string `json:"totalTokens" dc:"总token"` +} + +type AsyncTaskMapping struct { + Url string `json:"url" dc:"url"` + HttpMethod string `json:"httpMethod" dc:"http方法" d:"POST"` + RequestHeadMapping map[string]string `json:"requestHeadMapping" description:"请求头映射"` + ResponseMapping map[string]any `json:"responseMapping" description:"响应映射"` + TaskId string `json:"taskId" dc:"任务id"` + TaskStatus string `json:"taskStatus" dc:"任务状态"` + TaskStatusPending string `json:"taskStatusPending" dc:"任务状态-待处理"` + TaskStatusRunning string `json:"taskStatusRunning" dc:"任务状态-运行中"` + TaskStatusSuccess string `json:"taskStatusSuccess" dc:"任务状态-成功"` + TaskStatusFailed string `json:"taskStatusFailed" dc:"任务状态-失败"` + TaskStatusCancel string `json:"taskStatusCancel" dc:"任务状态-取消"` + TaskStatusUnknown string `json:"taskStatusUnknown" dc:"任务状态-未知"` +} diff --git a/service/model_manage_service.go b/service/model_manage_service.go new file mode 100644 index 0000000..57483b3 --- /dev/null +++ b/service/model_manage_service.go @@ -0,0 +1,204 @@ +package service + +import ( + "context" + "fmt" + "model-gateway/consts/model" + "model-gateway/consts/public" + "model-gateway/dao" + "model-gateway/model/dto" + "model-gateway/model/entity" + "model-gateway/service/gateway" + + "gitea.redpowerfuture.com/red-future/common/beans" + "gitea.redpowerfuture.com/red-future/common/db/gfdb" + "gitea.redpowerfuture.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" +) + +var ModelManage = &modelManageService{} + +type modelManageService struct{} + +// Create 创建模型 +func (s *modelManageService) Create(ctx context.Context, req *dto.CreateModelManageReq) (res *dto.CreateModelManageRes, err error) { + err = gfdb.DB(ctx, public.DbNameModelGateway).Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) { + // 1)检查是否是超管 + var isSuperAdmin bool + isSuperAdmin, err = gateway.IsSuperAdmin(ctx) + if err != nil { + return + } + req.SystemModel = &isSuperAdmin + // 1)如果设为会话模型,先把该用户旧会话模型取消 + err = s.CancelChatModel(ctx, req.ModelType, req.ChatModel, isSuperAdmin) + if err != nil { + return + } + // 2)插入数据 + id, err := dao.ModelManage.Insert(ctx, req) + if err != nil { + return + } + res = &dto.CreateModelManageRes{Id: id} + return + }) + return +} + +// Update 更新模型配置 +func (s *modelManageService) Update(ctx context.Context, req *dto.UpdateModelManageReq) (err error) { + err = gfdb.DB(ctx, public.DbNameModelGateway).Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) { + var get *entity.ModelManage + get, err = dao.ModelManage.GetNotTenantId(ctx, &dto.GetModelManageReq{ + Id: req.Id, + }) + if err != nil { + return + } + var user *beans.User + user, err = utils.GetUserInfo(ctx) + if err != nil { + return + } + // 1)如果不是创建者,且是系统模型,则需要拷贝 + if get.Creator != user.UserName { + if get.SystemModel != nil && *get.SystemModel { + if g.IsEmpty(req.ApiKey) { + return fmt.Errorf("模型apiKey不能为空") + } + d := new(dto.CreateModelManageReq) + err = gconv.Struct(req, d) + if err != nil { + return + } + _, err = s.Create(ctx, d) + if err != nil { + return err + } + return + } + return fmt.Errorf("无权限操作") + } + + // 1)检查是否是超管 + var isSuperAdmin bool + isSuperAdmin, err = gateway.IsSuperAdmin(ctx) + if err != nil { + return + } + // 1)如果设为会话模型,先把该用户旧会话模型取消 + err = s.CancelChatModel(ctx, req.ModelType, req.ChatModel, isSuperAdmin) + if err != nil { + return + } + // 2)更新数据 + _, err = dao.ModelManage.Update(ctx, req) + return + }) + return +} + +func (s *modelManageService) CancelChatModel(ctx context.Context, modelType model.ModelType, chatModel *bool, isSuperAdmin bool) (err error) { + if !g.IsEmpty(chatModel) && *chatModel { + if *modelType == *model.ModelTypeInference.Code { + if isSuperAdmin { + return fmt.Errorf("超级管理员不能设置会话模型") + } + // 2)获取该用户信息 + var user *beans.User + user, err = utils.GetUserInfo(ctx) + if err != nil { + return + } + // 3)取消该用户之前的会话模型 + var get *entity.ModelManage + get, err = dao.ModelManage.Get(ctx, &dto.GetModelManage{ + Creator: user.UserName, + ChatModel: chatModel, + }) + if err != nil { + return + } + _, err = dao.ModelManage.Update(ctx, &dto.UpdateModelManageReq{ + Id: get.Id, + ChatModel: gconv.PtrBool(false), + }) + if err != nil { + return + } + } else { + return fmt.Errorf("只有推理模型可以设置成会话模型") + } + } + return +} + +// Delete 删除模型 +func (s *modelManageService) Delete(ctx context.Context, req *dto.DeleteModelManageReq) error { + _, err := dao.ModelManage.Delete(ctx, req) + return err +} + +func (s *modelManageService) Get(ctx context.Context, req *dto.GetModelManageReq) (res *dto.GetModelManageRes, err error) { + get, err := dao.ModelManage.GetNotTenantId(ctx, req) + if err != nil { + return + } + err = gconv.Struct(get, &res) + return +} + +// List 获取模型列表 +func (s *modelManageService) List(ctx context.Context, req *dto.ListModelManageReq) (res *dto.ListModelManageRes, err error) { + var user *beans.User + user, err = utils.GetUserInfo(ctx) + if err != nil { + return + } + req.Creator = user.UserName + list, total, err := dao.ModelManage.ListNotTenantId(ctx, req) + if err != nil { + return + } + res = &dto.ListModelManageRes{ + Total: total, + } + err = gconv.Struct(list, &res.List) + return +} + +func (s *modelManageService) CheckChatModel(ctx context.Context, req *dto.CheckChatModelReq) (res *dto.CheckChatModelRes, err error) { + user, err := utils.GetUserInfo(ctx) + if err != nil { + return + } + get, err := dao.ModelManage.Get(ctx, &dto.GetModelManage{ + Creator: user.UserName, + ChatModel: gconv.PtrBool(true), + }) + if err != nil { + return + } + res = &dto.CheckChatModelRes{ + IsChatModel: !g.IsEmpty(get), + } + return +} + +// GetModelType 获取模型类型 +func (s *modelManageService) GetModelType(ctx context.Context, req *dto.ModelTypeReq) (res *dto.ModelTypeRes, err error) { + res = &dto.ModelTypeRes{ + List: model.GetTypeTreeList(), + } + return res, nil +} + +// GetModelSupplier 获取运营商列表 +func (s *modelManageService) GetModelSupplier(ctx context.Context, req *dto.ModelSupplierReq) (res *dto.ModelSupplierRes, err error) { + return &dto.ModelSupplierRes{ + List: model.GetSupplierOptionList(), + }, nil +} diff --git a/update.sql b/update.sql index 248f060..df93383 100644 --- a/update.sql +++ b/update.sql @@ -230,4 +230,122 @@ COMMENT ON COLUMN model_gateway_logs_op.success IS '是否成功:1成功/0失 COMMENT ON COLUMN model_gateway_logs_op.error_msg IS '错误信息(失败时)'; COMMENT ON COLUMN model_gateway_logs_op.cost_ms IS '耗时(毫秒)'; COMMENT ON COLUMN model_gateway_logs_op.request_payload IS '请求 JSON'; -COMMENT ON COLUMN model_gateway_logs_op.response_payload IS '响应 JSON'; \ No newline at end of file +COMMENT ON COLUMN model_gateway_logs_op.response_payload IS '响应 JSON'; + +-- ========================================================================================================================= +CREATE TABLE "public"."model_gateway_model_manage" ( + "id" int8 NOT NULL, + "tenant_id" int8 NOT NULL DEFAULT 0, + "creator" varchar(64) COLLATE "pg_catalog"."default" NOT NULL, + "created_at" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) COLLATE "pg_catalog"."default" NOT NULL, + "updated_at" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted_at" timestamp(6), + "model_supplier" varchar(32) COLLATE "pg_catalog"."default" NOT NULL DEFAULT ''::character varying, + "model_name" varchar(128) COLLATE "pg_catalog"."default" NOT NULL DEFAULT ''::character varying, + "model_type" varchar(32) COLLATE "pg_catalog"."default" NOT NULL DEFAULT ''::character varying, + "base_url" varchar(512) COLLATE "pg_catalog"."default" NOT NULL DEFAULT ''::character varying, + "system_model" bool, + "http_method" varchar(32) COLLATE "pg_catalog"."default" NOT NULL DEFAULT ''::character varying, + "chat_model" bool, + "response_type" int2 NOT NULL DEFAULT 0, + "api_key" varchar(255) COLLATE "pg_catalog"."default" NOT NULL DEFAULT ''::character varying, + "enabled" bool, + "request_head_mapping" jsonb DEFAULT '{}'::jsonb, + "request_body_mapping" jsonb DEFAULT '{}'::jsonb, + "response_mapping" jsonb DEFAULT '{}'::jsonb, + "max_concurrency" int4 NOT NULL DEFAULT 0, + "token_mapping" jsonb, + "async_task_mapping" jsonb, + "token_predict_price" numeric(12,6) NOT NULL DEFAULT 0.000000, + "max_tokens" int4 NOT NULL DEFAULT 0, + "last_frame" varchar(512) COLLATE "pg_catalog"."default" NOT NULL DEFAULT ''::character varying, + "response_body_mapping" jsonb DEFAULT '{}'::jsonb, + "token_predict_price_unit" varchar(32) COLLATE "pg_catalog"."default", + "max_duration" int4, + CONSTRAINT "model_gateway_model_manage_pkey" PRIMARY KEY ("id") +) +; + +ALTER TABLE "public"."model_gateway_model_manage" + OWNER TO "postgres"; + +CREATE INDEX "idx_model_manage_deleted_at" ON "public"."model_gateway_model_manage" USING btree ( + "deleted_at" "pg_catalog"."timestamp_ops" ASC NULLS LAST + ); + +CREATE INDEX "idx_model_manage_model_type" ON "public"."model_gateway_model_manage" USING btree ( + "model_type" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST + ); + +CREATE INDEX "idx_model_manage_response_type" ON "public"."model_gateway_model_manage" USING btree ( + "response_type" "pg_catalog"."int2_ops" ASC NULLS LAST + ); + +CREATE INDEX "idx_model_manage_supplier" ON "public"."model_gateway_model_manage" USING btree ( + "model_supplier" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST + ); + +CREATE INDEX "idx_model_manage_tenant_id" ON "public"."model_gateway_model_manage" USING btree ( + "tenant_id" "pg_catalog"."int8_ops" ASC NULLS LAST + ); + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."id" IS '主键ID'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."tenant_id" IS '租户ID'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."creator" IS '创建人'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."created_at" IS '创建时间'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."updater" IS '更新人'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."updated_at" IS '更新时间'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."deleted_at" IS '删除时间(软删)'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."model_supplier" IS '模型供应商'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."model_name" IS '模型名称'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."model_type" IS '模型类型'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."base_url" IS '模型地址'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."system_model" IS '是否系统模型'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."http_method" IS 'http请求方法'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."chat_model" IS '是否聊天模型'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."response_type" IS '返回类型:1同步,2异步,3流'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."api_key" IS '接口密钥'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."enabled" IS '是否启用'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."request_head_mapping" IS '请求头映射'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."request_body_mapping" IS '请求体映射'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."response_mapping" IS '响应映射'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."max_concurrency" IS '最大并发数'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."token_mapping" IS 'token映射'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."async_task_mapping" IS '异步任务映射'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."token_predict_price" IS '模型Token预估价格'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."max_tokens" IS '最大token数'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."last_frame" IS '视频尾帧图像地址'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."response_body_mapping" IS '响应主体映射'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."token_predict_price_unit" IS '模型token预估价格单位(秒,百万Token,千Token,字数)'; + +COMMENT ON COLUMN "public"."model_gateway_model_manage"."max_duration" IS '最大时长(秒)'; + +COMMENT ON TABLE "public"."model_gateway_model_manage" IS '模型管理表'; \ No newline at end of file