Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3315f6cf17 | ||
|
|
c69aed717d | ||
|
|
8ac032c12f | ||
|
|
f152c23a7e | ||
|
|
84102121c5 | ||
|
|
8bdae52d4d | ||
|
|
6166c24412 | ||
|
|
a7f30d1640 | ||
|
|
b8006329d1 | ||
|
|
d46f163cfd | ||
|
|
dcbdc462bd | ||
|
|
947a158a52 | ||
|
|
4033a2ba69 | ||
|
|
b9dff27e33 | ||
|
|
fe64d178ad | ||
|
|
dc6375707f | ||
|
|
518f666ac6 | ||
|
|
260d6547f2 | ||
|
|
313bb692c7 | ||
|
|
4d95ff75d2 | ||
|
|
a332aa438b | ||
|
|
d18016e698 | ||
|
|
7e849b1c0f | ||
|
|
84421b4ee5 | ||
|
|
62003c25a0 | ||
|
|
2edbe54fdc | ||
|
|
35e955a617 | ||
|
|
bdd5f93e2c | ||
|
|
326a8acac9 | ||
|
|
3ca8f93778 | ||
|
|
8ef6a180f7 | ||
|
|
29d3f7ffb2 | ||
|
|
2bc9d76e18 | ||
|
|
b00e5d34f4 | ||
|
|
85dd4e8e84 | ||
|
|
3feb912d86 | ||
|
|
63092be679 | ||
|
|
e11155b604 | ||
|
|
c22a38da9a | ||
|
|
aee243d4eb | ||
|
|
16f2967569 | ||
|
|
73f630636d | ||
|
|
eaa2942957 | ||
|
|
525b391f09 | ||
|
|
610481effd | ||
|
|
39b61f1867 | ||
|
|
4cc44bf57c | ||
|
|
dc06d1bb9a | ||
|
|
ecaaa5bdbc | ||
|
|
b21d7a8dbf | ||
|
|
fddaf36f48 |
@@ -0,0 +1 @@
|
||||
.git
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
# 阶段1: 构建
|
||||
FROM golang:alpine AS builder
|
||||
|
||||
RUN apk add --no-cache git ca-certificates tzdata
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
|
||||
apk add --no-cache git ca-certificates tzdata
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// ======================== 计费入口 ========================
|
||||
|
||||
func CalculateBilling(config map[string]any, billingData map[string]any) map[string]any {
|
||||
if len(config) == 0 {
|
||||
return nil
|
||||
}
|
||||
switch config["type"] {
|
||||
case "inference_tier": //推理模型计费
|
||||
return calculateInferenceTierBilling(config, billingData)
|
||||
case "video_resolution": //视频模型计费
|
||||
return calculateVideoResolutionBilling(config, billingData)
|
||||
case "tts":
|
||||
return calculateTTSBilling(config, billingData)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ======================== 推理模型计费 ========================
|
||||
|
||||
func calculateInferenceTierBilling(config map[string]any, data map[string]any) map[string]any {
|
||||
promptTokens := gconv.Int64(data["prompt_tokens"])
|
||||
completionTokens := gconv.Int64(data["completion_tokens"])
|
||||
hasAudio := gconv.Bool(data["has_audio"])
|
||||
inputK := promptTokens / 1000
|
||||
tiers := config["pricing"].(map[string]any)["tiers"].([]any)
|
||||
var matched map[string]any
|
||||
for _, t := range tiers {
|
||||
tier := t.(map[string]any)
|
||||
if inputK >= gconv.Int64(tier["input_min"]) && inputK <= gconv.Int64(tier["input_max"]) {
|
||||
matched = tier
|
||||
break
|
||||
}
|
||||
}
|
||||
if matched == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var inputPrice float64
|
||||
if hasAudio && matched["audio_input_price"] != nil {
|
||||
inputPrice = gconv.Float64(matched["audio_input_price"])
|
||||
} else {
|
||||
inputPrice = gconv.Float64(matched["input_price"])
|
||||
}
|
||||
outputPrice := gconv.Float64(matched["output_price"])
|
||||
inputCost := float64(promptTokens) * inputPrice / 1000000
|
||||
outputCost := float64(completionTokens) * outputPrice / 1000000
|
||||
|
||||
// 推理模型
|
||||
return map[string]any{
|
||||
"model_name": data["model_name"],
|
||||
"total_tokens": promptTokens + completionTokens,
|
||||
"total_fee": inputCost + outputCost,
|
||||
// 明细
|
||||
"prompt_tokens": promptTokens,
|
||||
"completion_tokens": completionTokens,
|
||||
"has_audio": hasAudio,
|
||||
"input_tier": fmt.Sprintf("[%v, %v]", matched["input_min"], matched["input_max"]),
|
||||
"input_unit_price": inputPrice,
|
||||
"output_unit_price": outputPrice,
|
||||
"input_cost": inputCost,
|
||||
"output_cost": outputCost,
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 视频模型计费 ========================
|
||||
|
||||
func calculateVideoResolutionBilling(config map[string]any, data map[string]any) map[string]any {
|
||||
pricingPath := buildPricingPath(config, data)
|
||||
|
||||
pricing := config["pricing"].(map[string]any)
|
||||
unitPrice := gconv.Float64(pricing[pricingPath])
|
||||
|
||||
var effectiveMinToken float64
|
||||
if gconv.Bool(data["input_has_video"]) {
|
||||
effectiveMinToken = matchMinToken(config, data)
|
||||
}
|
||||
|
||||
completionTokens := gconv.Float64(data["actual_tokens"])
|
||||
realChargeTokens := int64(math.Max(completionTokens, effectiveMinToken))
|
||||
totalFee := float64(realChargeTokens) * unitPrice / 1000000
|
||||
|
||||
if gconv.Bool(config["enable_audio_charge"]) {
|
||||
totalFee += gconv.Float64(data["audio_word_cnt"]) * gconv.Float64(config["audio_unit_price"])
|
||||
}
|
||||
|
||||
// 视频模型
|
||||
return map[string]any{
|
||||
"model_name": data["model_name"],
|
||||
"total_tokens": realChargeTokens,
|
||||
"total_fee": totalFee,
|
||||
// 明细
|
||||
"prompt_tokens": 0,
|
||||
"completion_tokens": int64(completionTokens),
|
||||
"is_online": data["is_online"],
|
||||
"video_resolution": data["video_resolution"],
|
||||
"input_has_video": data["input_has_video"],
|
||||
"output_duration_sec": data["output_duration_sec"],
|
||||
"aspect_ratio": data["aspect_ratio"],
|
||||
"resolution": data["resolution"],
|
||||
"matched_path": pricingPath,
|
||||
"token_unit_price": unitPrice,
|
||||
"effective_min_token": effectiveMinToken,
|
||||
}
|
||||
}
|
||||
|
||||
func calculateTTSBilling(config map[string]any, data map[string]any) map[string]any {
|
||||
usage := gconv.Float64(data["synthesize_text_length"])
|
||||
unitPrice := gconv.Float64(config["pricing"])
|
||||
totalFee := usage * unitPrice
|
||||
|
||||
return map[string]any{
|
||||
"model_name": data["model_name"],
|
||||
"total_tokens": int64(usage),
|
||||
"total_fee": totalFee,
|
||||
// 明细
|
||||
"prompt_tokens": 0,
|
||||
"completion_tokens": int64(usage),
|
||||
"unit_price": unitPrice,
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 数据提取 ========================
|
||||
|
||||
func ExtractRequestBilling(ctx context.Context, config map[string]any, requestPayload map[string]any) map[string]any {
|
||||
dimensions := config["dimensions"].(map[string]any)
|
||||
fields := dimensions["request"].(map[string]any)
|
||||
data := make(map[string]any)
|
||||
|
||||
for key, path := range fields {
|
||||
val := extractValue(requestPayload, gconv.String(path))
|
||||
// TTS 模型:input_chars 自动计算字符数
|
||||
if key == "input_chars" {
|
||||
if s, ok := val.(string); ok {
|
||||
data[key] = len([]rune(s))
|
||||
continue
|
||||
}
|
||||
}
|
||||
data[key] = val
|
||||
}
|
||||
|
||||
if defaults, ok := config["defaults"].(map[string]any); ok {
|
||||
for k, v := range defaults {
|
||||
if _, exists := data[k]; !exists || data[k] == nil {
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if compute, ok := config["compute"].(map[string]any); ok {
|
||||
// for targetField, rule := range compute {
|
||||
// r := rule.(map[string]any)
|
||||
// dependsOn := gconv.String(r["depends_on"])
|
||||
// dependsValue := gconv.Bool(r["depends_value"])
|
||||
//
|
||||
// if gconv.Bool(data[dependsOn]) != dependsValue {
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// switch r["service"] {
|
||||
// case "video_duration":
|
||||
// urls := extractVideoUrls(requestPayload)
|
||||
// if len(urls) > 0 {
|
||||
// resp, err := gateway.GetVideoDuration(ctx, urls)
|
||||
// if err == nil {
|
||||
// data[targetField] = resp.TotalDuration
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
return data
|
||||
}
|
||||
|
||||
func ExtractResponseBilling(config map[string]any, response map[string]any) map[string]any {
|
||||
dimensions := config["dimensions"].(map[string]any)
|
||||
fields := dimensions["response"].(map[string]any)
|
||||
data := make(map[string]any)
|
||||
|
||||
for key, path := range fields {
|
||||
data[key] = gjson.New(response).Get(gconv.String(path)).Val()
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// ======================== 内部辅助 ========================
|
||||
|
||||
func buildPricingPath(config map[string]any, data map[string]any) string {
|
||||
pathConfig := config["pricing_path"].(map[string]any)
|
||||
segments := pathConfig["segments"].([]any)
|
||||
mapping := pathConfig["mapping"].(map[string]any)
|
||||
|
||||
var parts []string
|
||||
for _, seg := range segments {
|
||||
segName := gconv.String(seg)
|
||||
val := gconv.String(data[segName])
|
||||
if m, ok := mapping[segName].(map[string]any); ok {
|
||||
if mapped, exists := m[val]; exists {
|
||||
val = gconv.String(mapped)
|
||||
}
|
||||
}
|
||||
parts = append(parts, val)
|
||||
}
|
||||
return strings.Join(parts, ".")
|
||||
}
|
||||
|
||||
func matchMinToken(config map[string]any, data map[string]any) float64 {
|
||||
rule, ok := config["min_token_rule"].(map[string]any)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
conditionDefs := rule["conditions"].([]any)
|
||||
rows := rule["rows"].([]any)
|
||||
|
||||
for _, row := range rows {
|
||||
r := row.(map[string]any)
|
||||
conditions := r["conditions"].([]any)
|
||||
matched := true
|
||||
|
||||
for i, cond := range conditions {
|
||||
condStr := gconv.String(cond)
|
||||
condDef := conditionDefs[i].(map[string]any)
|
||||
condName := gconv.String(condDef["name"])
|
||||
|
||||
switch condDef["type"] {
|
||||
case "range":
|
||||
if !matchRange(condStr, gconv.Float64(data[condName])) {
|
||||
matched = false
|
||||
}
|
||||
case "enum":
|
||||
if gconv.String(data[condName]) != condStr {
|
||||
matched = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if matched {
|
||||
return gconv.Float64(r["min_token"])
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func matchRange(rangeStr string, val float64) bool {
|
||||
rangeStr = strings.Trim(rangeStr, "[]()")
|
||||
parts := strings.Split(rangeStr, ",")
|
||||
if len(parts) != 2 {
|
||||
return false
|
||||
}
|
||||
return val >= gconv.Float64(strings.TrimSpace(parts[0])) && val < gconv.Float64(strings.TrimSpace(parts[1]))
|
||||
}
|
||||
|
||||
func extractValue(source map[string]any, path string) any {
|
||||
// 数组求和:rounds.#.duration
|
||||
if strings.HasPrefix(path, "rounds.#.") && !strings.Contains(path, "==") {
|
||||
field := strings.TrimPrefix(path, "rounds.#.")
|
||||
rounds := gjson.New(source).Get("rounds").Array()
|
||||
var sum float64
|
||||
for _, r := range rounds {
|
||||
if strings.Contains(field, "#") {
|
||||
parts := strings.SplitN(field, ".#.", 2)
|
||||
arr := gjson.New(r).Get(parts[0]).Array()
|
||||
for _, item := range arr {
|
||||
sum += gconv.Float64(gjson.New(item).Get(parts[1]).Val())
|
||||
}
|
||||
} else {
|
||||
sum += gconv.Float64(gjson.New(r).Get(field).Val())
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// 条件判断:xxx.#.type==yyy 或 rounds.#.content.#.type==yyy
|
||||
if strings.Contains(path, "==video_url") || strings.Contains(path, "==input_audio") {
|
||||
parts := strings.Split(path, "==")
|
||||
basePath := parts[0]
|
||||
typ := parts[1]
|
||||
segments := strings.Split(basePath, ".#.")
|
||||
|
||||
// 单层:content.#.type → segments = ["content", "type"]
|
||||
if len(segments) == 2 && !strings.Contains(segments[0], ".") {
|
||||
arr := gjson.New(source).Get(segments[0]).Array()
|
||||
for _, item := range arr {
|
||||
if gconv.String(gjson.New(item).Get(segments[1]).Val()) == typ {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 嵌套:rounds.#.content.#.type → segments = ["rounds", "content", "type"]
|
||||
topArr := gjson.New(source).Get(segments[0]).Array()
|
||||
for _, item := range topArr {
|
||||
subArr := gjson.New(item).Get(segments[1]).Array()
|
||||
for _, sub := range subArr {
|
||||
if gconv.String(gjson.New(sub).Get("type").Val()) == typ {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return gjson.New(source).Get(path).Val()
|
||||
}
|
||||
|
||||
func extractVideoUrls(body map[string]any) []string {
|
||||
var urls []string
|
||||
content := gjson.New(body).Get("content").Array()
|
||||
for _, c := range content {
|
||||
item := c.(map[string]any)
|
||||
if item["type"] == "video_url" {
|
||||
if v, ok := item["video_url"].(map[string]any); ok {
|
||||
if url, ok := v["url"].(string); ok {
|
||||
urls = append(urls, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return urls
|
||||
}
|
||||
+22
-55
@@ -1,7 +1,6 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -9,107 +8,75 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DetectFileType 根据返回的二进制内容推断 contentType + 扩展名(尽量稳定)
|
||||
// DetectFileType 根据返回的二进制内容推断 contentType + 扩展名
|
||||
func DetectFileType(data []byte) (contentType string, ext string) {
|
||||
if len(data) == 0 {
|
||||
return "application/octet-stream", ""
|
||||
return "application/octet-stream", ".bin"
|
||||
}
|
||||
|
||||
ct := http.DetectContentType(data)
|
||||
// gateway.DetectContentType 可能带 charset 等参数:text/plain; charset=utf-8
|
||||
if idx := strings.Index(ct, ";"); idx > 0 {
|
||||
ct = strings.TrimSpace(ct[:idx])
|
||||
}
|
||||
|
||||
switch ct {
|
||||
case "audio/mpeg":
|
||||
return ct, ".mp3"
|
||||
case "audio/wave", "audio/wav", "audio/x-wav":
|
||||
return ct, ".wav"
|
||||
case "audio/mp4", "audio/x-m4a":
|
||||
return ct, ".m4a"
|
||||
case "video/mp4":
|
||||
return ct, ".mp4"
|
||||
case "video/webm":
|
||||
return ct, ".webm"
|
||||
case "image/png":
|
||||
return ct, ".png"
|
||||
case "image/jpeg":
|
||||
return ct, ".jpg"
|
||||
case "image/gif":
|
||||
return ct, ".gif"
|
||||
case "image/webp":
|
||||
return ct, ".webp"
|
||||
case "application/pdf":
|
||||
return ct, ".pdf"
|
||||
case "text/plain":
|
||||
return ct, ".txt"
|
||||
case "application/json":
|
||||
return ct, ".json"
|
||||
case "application/zip":
|
||||
return ct, ".zip"
|
||||
case "application/octet-stream":
|
||||
return ct, ".bin"
|
||||
default:
|
||||
// 兜底:尝试从 ct 截取 subtype 作为后缀(例如 application/json)
|
||||
if parts := strings.Split(ct, "/"); len(parts) == 2 {
|
||||
sub := parts[1]
|
||||
// 避免出现 "plain; charset=utf-8" 之类的后缀
|
||||
if idx := strings.Index(sub, ";"); idx > 0 {
|
||||
sub = strings.TrimSpace(sub[:idx])
|
||||
}
|
||||
return ct, "." + sub
|
||||
}
|
||||
return ct, ""
|
||||
return ct, ".bin"
|
||||
}
|
||||
}
|
||||
|
||||
// SaveTmpResult 将模型输出写入临时文件,用于 OSS 上传失败后的“仅重试 OSS”。
|
||||
// SaveTmpResult 将二进制数据写入临时文件
|
||||
func SaveTmpResult(taskID string, data []byte, ext string) (string, error) {
|
||||
dir := filepath.Join(os.TempDir(), "model-asynch")
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return "", err
|
||||
return "", fmt.Errorf("创建临时目录失败: %w", err)
|
||||
}
|
||||
|
||||
if ext == "" {
|
||||
ext = ".bin"
|
||||
}
|
||||
if ext[0] != '.' {
|
||||
ext = "." + ext
|
||||
}
|
||||
|
||||
path := filepath.Join(dir, fmt.Sprintf("%s%s", taskID, ext))
|
||||
if err := os.WriteFile(path, data, 0o644); err != nil {
|
||||
return "", err
|
||||
return "", fmt.Errorf("写入临时文件失败: %w", err)
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// SaveTempFileByType
|
||||
// 根据传入的数据自动判断:
|
||||
// 若是 []byte 且后缀为 .mp3 → 保存二进制音频
|
||||
// 若是任意结构体/map → 自动转 JSON 保存
|
||||
// 返回:新临时文件路径、错误
|
||||
func SaveTempFileByType(taskID string, data any, oldTmpFile string) (string, error) {
|
||||
// 1. 先清理旧临时文件(统一逻辑)
|
||||
if oldTmpFile != "" {
|
||||
_ = os.Remove(oldTmpFile)
|
||||
}
|
||||
|
||||
var tmpPath string
|
||||
var tmpErr error
|
||||
|
||||
// 2. 判断是否是二进制音频([]byte + .mp3)
|
||||
if audioData, ok := data.([]byte); ok {
|
||||
tmpPath, tmpErr = saveTmpResult(taskID, audioData, ".mp3")
|
||||
} else {
|
||||
// 3. 其他类型 → 序列化为 JSON 保存
|
||||
mappedBytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(mappedBytes) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
tmpPath, tmpErr = saveTmpResult(taskID, mappedBytes, ".json")
|
||||
}
|
||||
|
||||
if tmpErr != nil || tmpPath == "" {
|
||||
return "", tmpErr
|
||||
}
|
||||
|
||||
return tmpPath, nil
|
||||
}
|
||||
|
||||
// saveTmpResult 你原有的底层保存文件方法(保留不动)
|
||||
func saveTmpResult(taskID string, data []byte, ext string) (string, error) {
|
||||
// 你原来实现,比如:
|
||||
filename := taskID + ext
|
||||
tmpPath := filepath.Join(os.TempDir(), filename)
|
||||
err := os.WriteFile(tmpPath, data, 0644)
|
||||
return tmpPath, err
|
||||
}
|
||||
|
||||
+55
-30
@@ -19,41 +19,59 @@ import (
|
||||
tgjson "github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// ParseAndValidate 解析并校验结果
|
||||
func ParseAndValidate(raw map[string]any, model *entity.ModelGatewayModel) (map[string]any, error) {
|
||||
// 1) 解析 content 字符串为 rounds 数组
|
||||
contentVal, ok := raw[model.ResponseBody]
|
||||
if !ok {
|
||||
return raw, fmt.Errorf("字段 %s 不存在", model.ResponseBody)
|
||||
}
|
||||
contentStr, ok := contentVal.(string)
|
||||
if !ok || strings.TrimSpace(contentStr) == "" {
|
||||
return raw, fmt.Errorf("字段 %s 为空或不是字符串", model.ResponseBody)
|
||||
}
|
||||
var arr []any
|
||||
if err := json.Unmarshal([]byte(contentStr), &arr); err != nil {
|
||||
return raw, fmt.Errorf("JSON解析失败: %w", err)
|
||||
}
|
||||
if len(arr) == 0 {
|
||||
return raw, fmt.Errorf("解析后数组为空")
|
||||
// ParseAndValidate 解析模型响应,并返回标准格式
|
||||
func ParseAndValidate(raw map[string]any, requiredFields []string) (map[string]any, error) {
|
||||
contentStr := gconv.String(raw[entity.ResponseBody])
|
||||
if strings.TrimSpace(contentStr) == "" {
|
||||
return raw, fmt.Errorf("字段 %s 为空", entity.ResponseBody)
|
||||
}
|
||||
|
||||
// 2) 校验必填字段
|
||||
if len(model.RequiredFields) > 0 {
|
||||
for i, r := range arr {
|
||||
round, ok := r.(map[string]any)
|
||||
contentStr = strings.Map(func(r rune) rune {
|
||||
if r < 32 && r != ' ' {
|
||||
return -1
|
||||
}
|
||||
return r
|
||||
}, contentStr)
|
||||
|
||||
// 第一步:先解析为通用 interface{},判断是对象还是数组
|
||||
var data any
|
||||
if err := json.Unmarshal([]byte(contentStr), &data); err != nil {
|
||||
return raw, fmt.Errorf("JSON解析失败: %w", err)
|
||||
}
|
||||
|
||||
var arr []any
|
||||
switch val := data.(type) {
|
||||
case []any:
|
||||
// 本身就是数组,直接赋值
|
||||
arr = val
|
||||
case map[string]any:
|
||||
// 单个对象,包装成单元素数组,统一后续逻辑
|
||||
arr = []any{val}
|
||||
default:
|
||||
return raw, fmt.Errorf("不支持的JSON类型,仅允许对象/数组")
|
||||
}
|
||||
|
||||
if len(arr) == 0 {
|
||||
return raw, fmt.Errorf("解析后数据数组为空")
|
||||
}
|
||||
|
||||
// 校验每一项的必填字段
|
||||
for _, field := range requiredFields {
|
||||
for i, item := range arr {
|
||||
itemMap, ok := item.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
return raw, fmt.Errorf("rounds[%d] 不是合法JSON对象", i)
|
||||
}
|
||||
for _, field := range model.RequiredFields {
|
||||
if gjson.New(round).Get(field).IsNil() {
|
||||
return raw, fmt.Errorf("rounds[%d] 缺少必填字段: %s", i, field)
|
||||
}
|
||||
if gjson.New(itemMap).Get(field).IsNil() {
|
||||
return raw, fmt.Errorf("rounds[%d] 缺少必填字段: %s", i, field)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return map[string]any{"total_rounds": len(arr), "rounds": arr}, nil
|
||||
return map[string]any{
|
||||
"total_rounds": len(arr),
|
||||
"rounds": arr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseStructResult 解析结构结果
|
||||
@@ -266,10 +284,17 @@ func PullTaskResult(ctx context.Context, body map[string]any, queryConfig map[st
|
||||
}
|
||||
|
||||
if matchStatus(statusStr, statusValues["failed"]) {
|
||||
g.Log().Errorf(ctx, "[PullTaskResult] 任务失败 taskID=%s", taskID)
|
||||
return result, fmt.Errorf("任务失败")
|
||||
errMsg := gconv.String(gjson.New(result).Get("error.message").Val())
|
||||
if errMsg == "" {
|
||||
errMsg = gconv.String(gjson.New(result).Get("error").Val())
|
||||
}
|
||||
if errMsg == "" {
|
||||
rawBytes, _ := json.Marshal(result)
|
||||
errMsg = string(rawBytes)
|
||||
}
|
||||
g.Log().Errorf(ctx, "[PullTaskResult] 任务失败 taskID=%s err=%s", taskID, errMsg)
|
||||
return result, fmt.Errorf("任务失败: %s", errMsg)
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(interval) * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
+7
-7
@@ -7,7 +7,7 @@ server:
|
||||
database:
|
||||
default:
|
||||
- type: "pgsql"
|
||||
host: "116.204.74.41"
|
||||
host: "192.168.0.83"
|
||||
port: "15432"
|
||||
user: "postgres"
|
||||
pass: "Bjang09@686^*^"
|
||||
@@ -28,7 +28,7 @@ database:
|
||||
timeMaintainDisabled: false # (可选)是否完全关闭时间更新特性,为true时CreatedAt/UpdatedAt/DeletedAt都将失效
|
||||
model_gateway:
|
||||
- type: "pgsql"
|
||||
host: "116.204.74.41"
|
||||
host: "192.168.0.83"
|
||||
port: "15432"
|
||||
user: "postgres"
|
||||
pass: "Bjang09@686^*^"
|
||||
@@ -39,8 +39,8 @@ database:
|
||||
dryRun: false
|
||||
charset: "utf8"
|
||||
timezone: "Asia/Shanghai"
|
||||
maxIdle: 5
|
||||
maxOpen: 20
|
||||
maxIdle: 15
|
||||
maxOpen: 60
|
||||
maxLifetime: "30s"
|
||||
maxIdleConnTime: "30s"
|
||||
createdAt: "created_at"
|
||||
@@ -50,14 +50,14 @@ database:
|
||||
|
||||
redis:
|
||||
default:
|
||||
address: 192.168.3.30:6379
|
||||
address: 192.168.0.83:6379
|
||||
db: 0
|
||||
|
||||
consul:
|
||||
address: 192.168.3.30:8500
|
||||
address: 192.168.0.83:8500
|
||||
|
||||
jaeger:
|
||||
addr: 192.168.3.30:4318
|
||||
addr: 192.168.0.83:4318
|
||||
|
||||
# 本地调试用:可选自动执行 worker/cleaner(默认关闭)
|
||||
asynch:
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
package public
|
||||
|
||||
// Option 通用下拉选项
|
||||
type Option struct {
|
||||
Value int `json:"value"`
|
||||
Label string `json:"label"`
|
||||
}
|
||||
|
||||
const (
|
||||
CallModeSync = 0 // 同步调用
|
||||
CallModeAsync = 1 // 异步调用
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -56,6 +56,7 @@ func (d *modelGatewayModelsDao) Get(ctx context.Context, req *entity.ModelGatewa
|
||||
Where(entity.ModelGatewayModelCol.Id, req.Id).
|
||||
Where(entity.ModelGatewayModelCol.Creator, req.Creator).
|
||||
Where(entity.ModelGatewayModelCol.ModelName, req.ModelName).
|
||||
Where(entity.ModelGatewayModelCol.IsChatModel, req.IsChatModel).
|
||||
Fields(fields).One()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -122,7 +123,7 @@ func (d *modelGatewayModelsDao) GetByAcrossTenant(ctx context.Context, req *enti
|
||||
func (d *modelGatewayModelsDao) GetByCreatorAndPlatform(ctx context.Context, req *dto.ListModelReq) (list []*entity.ModelGatewayModel, total int, err error) {
|
||||
sql := `
|
||||
SELECT DISTINCT ON (model_name) *
|
||||
FROM asynch_models
|
||||
FROM ` + public.TableNameModel + `
|
||||
WHERE deleted_at IS NULL
|
||||
AND (? = '' OR model_name LIKE ?)
|
||||
`
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"model-gateway/model/entity"
|
||||
|
||||
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
@@ -128,32 +127,32 @@ func (d *modelGatewayTaskDao) GetPendingAsyncTasks(ctx context.Context, limit in
|
||||
|
||||
// ClaimByID 按主键抢占,返回抢占后的任务
|
||||
func (d *modelGatewayTaskDao) ClaimByID(ctx context.Context, id int64) (*entity.ModelGatewayTask, error) {
|
||||
// 1) 先查任务
|
||||
var task entity.ModelGatewayTask
|
||||
err := gfdb.DB(ctx, public.DbNameModelGateway).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
r, err := tx.Model(public.TableNameTask).
|
||||
Where(entity.ModelGatewayTaskCol.Id, id).
|
||||
Where(entity.ModelGatewayTaskCol.State, public.TaskStatusPending).
|
||||
Limit(1).
|
||||
LockUpdate().
|
||||
One()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.IsEmpty() {
|
||||
return fmt.Errorf("任务已被抢占或不存在: id=%d", id)
|
||||
}
|
||||
if err := r.Struct(&task); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.Model(public.TableNameTask).
|
||||
Data(&entity.ModelGatewayTask{State: public.TaskStatusRunning}).
|
||||
Where(entity.ModelGatewayTaskCol.Id, id).
|
||||
OmitEmpty().
|
||||
Update()
|
||||
return err
|
||||
})
|
||||
r, err := gfdb.DB(ctx, public.DbNameModelGateway).Model(ctx, public.TableNameTask).
|
||||
Where(entity.ModelGatewayTaskCol.Id, id).
|
||||
Where(entity.ModelGatewayTaskCol.State, public.TaskStatusPending).
|
||||
One()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.IsEmpty() {
|
||||
return nil, fmt.Errorf("任务已被抢占或不存在: id=%d", id)
|
||||
}
|
||||
if err = r.Struct(&task); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2) 改为执行中
|
||||
_, err = gfdb.DB(ctx, public.DbNameModelGateway).Model(ctx, public.TableNameTask).
|
||||
Data(&entity.ModelGatewayTask{State: public.TaskStatusRunning}).
|
||||
Where(entity.ModelGatewayTaskCol.Id, id).
|
||||
Where(entity.ModelGatewayTaskCol.State, public.TaskStatusPending). // 防并发
|
||||
OmitEmpty().
|
||||
Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &task, nil
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -3,7 +3,7 @@ module model-gateway
|
||||
go 1.26.1
|
||||
|
||||
require (
|
||||
gitea.redpowerfuture.com/red-future/common v0.0.23
|
||||
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
|
||||
@@ -12,22 +12,25 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/BurntSushi/toml v1.6.0 // indirect
|
||||
github.com/armon/go-metrics v0.4.1 // indirect
|
||||
github.com/bwmarrin/snowflake v0.3.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
github.com/dgraph-io/badger/v4 v4.2.0 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fatih/color v1.19.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.10.1 // indirect
|
||||
github.com/go-ego/gse v1.0.2 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/goccy/go-json v0.10.6 // indirect
|
||||
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5 // indirect
|
||||
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
@@ -36,57 +39,62 @@ require (
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/google/flatbuffers v1.12.1 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/flatbuffers v25.12.19+incompatible // indirect
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
||||
github.com/hashicorp/consul/api v1.26.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
|
||||
github.com/hashicorp/consul/api v1.33.5 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-hclog v1.5.0 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/go-version v1.9.0 // indirect
|
||||
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
||||
github.com/hashicorp/serf v0.10.1 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/klauspost/compress v1.18.6 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/lib/pq v1.12.3 // indirect
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mattn/go-colorable v0.1.15 // indirect
|
||||
github.com/mattn/go-isatty v0.0.22 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.24 // indirect
|
||||
github.com/miekg/dns v1.1.72 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/olekukonko/errors v1.1.0 // indirect
|
||||
github.com/olekukonko/ll v0.0.9 // indirect
|
||||
github.com/olekukonko/tablewriter v1.1.0 // indirect
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
|
||||
github.com/olekukonko/errors v1.3.0 // indirect
|
||||
github.com/olekukonko/ll v0.1.8 // indirect
|
||||
github.com/olekukonko/tablewriter v1.1.4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/r3labs/diff/v2 v2.15.1 // indirect
|
||||
github.com/redis/go-redis/v9 v9.12.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/redis/go-redis/v9 v9.21.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/tidwall/match v1.2.0 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tiger1103/gfast-token v1.0.10 // indirect
|
||||
github.com/vcaesar/cedar v0.30.0 // indirect
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
||||
go.mongodb.org/mongo-driver/v2 v2.4.0 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
go.mongodb.org/mongo-driver/v2 v2.6.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.44.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect
|
||||
golang.org/x/net v0.56.0 // indirect
|
||||
golang.org/x/sys v0.46.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
|
||||
google.golang.org/grpc v1.75.0 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||
google.golang.org/grpc v1.79.3 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
gitea.redpowerfuture.com/red-future/common v0.0.23 h1:xieoA00iKOCDm5SO9iXn+cSyMKBAlZwI0fuEVPWrHLg=
|
||||
gitea.redpowerfuture.com/red-future/common v0.0.23/go.mod h1:50U1Xi+Ie56z09S5LQbZvaken0Mxv3OeS9LgR7U/ZRY=
|
||||
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.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
@@ -36,6 +38,10 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp
|
||||
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
|
||||
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
|
||||
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -47,8 +53,6 @@ github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWa
|
||||
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
@@ -61,10 +65,10 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/go-ego/gse v1.0.2 h1:+27lYFPhQEhA9igtdOsJPRKYL/k3TwYsxBF5jr6KFv4=
|
||||
github.com/go-ego/gse v1.0.2/go.mod h1:Fy35G+q7VV7Et1zIKO8o/sW1kkugV3znXap/lF/11zc=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
@@ -77,6 +81,10 @@ github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
||||
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.2 h1:u8EpP24GkprogROnJ7htMov9Fc66pTP1eVYrWxiCYOs=
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.2/go.mod h1:GmvM3r8GVByVMi4RD2+MCs5+CfxVXPMeT8mVDkAaAXE=
|
||||
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.2 h1:iTQegT+lEg/wDKvj2mi3W1wrdrwFarjokf88EXVVgu4=
|
||||
@@ -114,10 +122,10 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
|
||||
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
|
||||
github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@@ -134,12 +142,12 @@ github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5T
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
|
||||
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
||||
github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM=
|
||||
github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A=
|
||||
github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU=
|
||||
github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
||||
github.com/hashicorp/consul/api v1.33.5 h1:Nn6q87zudRU1rLBTJEgaWxz9STCNadilLCD7B8OA5aI=
|
||||
github.com/hashicorp/consul/api v1.33.5/go.mod h1:pa6fJOSHKLOzNHpUVeqLDtxA5+J1D7NNzLasuk8eRXA=
|
||||
github.com/hashicorp/consul/sdk v0.17.3 h1:oZMMxzQGSsiT+ToOH50y3Qcs0nc9Ud+7L5lRx+EmMU0=
|
||||
github.com/hashicorp/consul/sdk v0.17.3/go.mod h1:jnOmYjiNfVRpBaujQ1DFFVs0N6g3S1y6wygSjLTzYfc=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@@ -169,8 +177,8 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
|
||||
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA=
|
||||
github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
|
||||
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
@@ -185,8 +193,10 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
||||
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
@@ -196,8 +206,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
|
||||
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
@@ -205,39 +215,39 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
|
||||
github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
|
||||
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
||||
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
|
||||
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
|
||||
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
|
||||
github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY=
|
||||
github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
|
||||
github.com/olekukonko/errors v1.3.0 h1:teJvgLGUEqMzBUms+Dj3/3szNqCG/Jdw9iDbum8fR6U=
|
||||
github.com/olekukonko/errors v1.3.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||
github.com/olekukonko/ll v0.1.8 h1:ysHCJRGHYKzmBSdz9w5AySztx7lG8SQY+naTGYUbsz8=
|
||||
github.com/olekukonko/ll v0.1.8/go.mod h1:RPRC6UcscfFZgjo1nulkfMH5IM0QAYim0LfnMvUuozw=
|
||||
github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I=
|
||||
github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
@@ -264,13 +274,10 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg=
|
||||
github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc=
|
||||
github.com/redis/go-redis/v9 v9.12.1 h1:k5iquqv27aBtnTm2tIkROUDp8JBXhXZIVu1InSgvovg=
|
||||
github.com/redis/go-redis/v9 v9.12.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/redis/go-redis/v9 v9.21.0 h1:FPBE4hhbAke+TLmcY3WkpbDffJEomdqPn3HYiqAtL9E=
|
||||
github.com/redis/go-redis/v9 v9.21.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
@@ -278,22 +285,26 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU=
|
||||
github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
||||
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tiger1103/gfast-token v1.0.10 h1:fNiBE/Dq5iTHvTGlCx3DmXa2o4hr0NtumFpffZ39k6s=
|
||||
github.com/tiger1103/gfast-token v1.0.10/go.mod h1:a/21mxmj7zFeNvjhZSC0XpEAFHfb1aT2k6DXnufFU1s=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
@@ -305,28 +316,32 @@ github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaU
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.mongodb.org/mongo-driver/v2 v2.4.0 h1:Oq6BmUAAFTzMeh6AonuDlgZMuAuEiUxoAD1koK5MuFo=
|
||||
go.mongodb.org/mongo-driver/v2 v2.4.0/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI=
|
||||
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||
go.mongodb.org/mongo-driver/v2 v2.6.0 h1:b9sJOYrkmt4l8bY43ZenFBcPlhYIjaOfYHLtbB/5qi8=
|
||||
go.mongodb.org/mongo-driver/v2 v2.6.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
|
||||
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
|
||||
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -335,15 +350,15 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M=
|
||||
golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -359,8 +374,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -369,8 +384,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -394,16 +409,15 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@@ -413,8 +427,8 @@ golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk=
|
||||
golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -429,17 +443,17 @@ google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
|
||||
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -449,8 +463,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -26,6 +26,7 @@ func main() {
|
||||
|
||||
// 注册路由
|
||||
http.RouteRegister([]interface{}{
|
||||
controller.ModelManage,
|
||||
controller.ModelGatewayModels,
|
||||
controller.ModelGatewayTask,
|
||||
controller.ModelGatewayLogsStat,
|
||||
@@ -40,9 +41,10 @@ func main() {
|
||||
<-quit
|
||||
|
||||
g.Log().Infof(ctx, "[main] 收到退出信号,开始优雅退出...")
|
||||
cancel()
|
||||
// 关闭 gateway server(RouteRegister 内部是 go Httpserver.Run() 启动的)
|
||||
// 先关闭 gateway server,等待 in-flight 请求处理完成
|
||||
_ = http.Httpserver.Shutdown()
|
||||
// 再取消上下文,避免活跃请求被中断
|
||||
cancel()
|
||||
}
|
||||
|
||||
func startAutoRunner(ctx context.Context) {
|
||||
|
||||
@@ -13,6 +13,8 @@ type CreateTaskReq struct {
|
||||
RequestPayload map[string]any `p:"requestPayload" json:"requestPayload" dc:"请求负载(透传给模型服务)"`
|
||||
EpicycleId int64 `json:"epicycleId" dc:"轮次ID"`
|
||||
BuildType int64 `json:"buildType" dc:"构建类型:1-提示词构建 2-节点构建"`
|
||||
BuildModelName string `json:"buildModelName" json:"buildModelName" dc:"构建模型名称"`
|
||||
TaskId string `json:"taskId" dc:"任务ID"`
|
||||
}
|
||||
|
||||
type CreateTaskRes struct {
|
||||
|
||||
@@ -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的映射"`
|
||||
}
|
||||
@@ -4,99 +4,98 @@ import "gitea.redpowerfuture.com/red-future/common/beans"
|
||||
|
||||
type modelGatewayModelCol struct {
|
||||
beans.SQLBaseCol
|
||||
ModelName string
|
||||
ModelType string
|
||||
BaseURL string
|
||||
HttpMethod string
|
||||
HeadMsg string
|
||||
FormJSON string
|
||||
RequestMapping string
|
||||
ResponseMapping string
|
||||
ResponseBody string
|
||||
ResponseTokenField string
|
||||
RequiredFields string
|
||||
IsPrivate string
|
||||
IsChatModel string
|
||||
CallMode string
|
||||
ApiKey string
|
||||
Enabled string
|
||||
MaxConcurrency string
|
||||
TimeoutSeconds string
|
||||
RetryTimes string
|
||||
AutoCleanSeconds string
|
||||
IsOwner string
|
||||
OperatorName string
|
||||
TokenConfig string
|
||||
ExtendMapping string
|
||||
QueryConfig string
|
||||
StreamConfig string
|
||||
FirstFrame string
|
||||
LastFrame string
|
||||
MaxTokens string
|
||||
ModelName string
|
||||
ModelType string
|
||||
BaseURL string
|
||||
HttpMethod string
|
||||
HeadMsg string
|
||||
FormJSON string
|
||||
RequestMapping string
|
||||
ResponseMapping string
|
||||
RequiredFields string
|
||||
IsPrivate string
|
||||
IsChatModel string
|
||||
CallMode string
|
||||
ApiKey string
|
||||
Enabled string
|
||||
MaxConcurrency string
|
||||
TimeoutSeconds string
|
||||
RetryTimes string
|
||||
AutoCleanSeconds string
|
||||
IsOwner string
|
||||
OperatorName string
|
||||
TokenConfig string
|
||||
ExtendMapping string
|
||||
QueryConfig string
|
||||
StreamConfig string
|
||||
FirstFrame string
|
||||
LastFrame string
|
||||
BillingConfig string
|
||||
}
|
||||
|
||||
var ModelGatewayModelCol = modelGatewayModelCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
ModelName: "model_name",
|
||||
ModelType: "model_type",
|
||||
BaseURL: "base_url",
|
||||
HttpMethod: "http_method",
|
||||
HeadMsg: "head_msg",
|
||||
FormJSON: "form_json",
|
||||
RequestMapping: "request_mapping",
|
||||
ResponseMapping: "response_mapping",
|
||||
ResponseBody: "response_body",
|
||||
ResponseTokenField: "response_token_field",
|
||||
RequiredFields: "required_fields",
|
||||
IsPrivate: "is_private",
|
||||
IsChatModel: "is_chat_model",
|
||||
CallMode: "call_mode",
|
||||
ApiKey: "api_key",
|
||||
Enabled: "enabled",
|
||||
MaxConcurrency: "max_concurrency",
|
||||
TimeoutSeconds: "timeout_seconds",
|
||||
RetryTimes: "retry_times",
|
||||
AutoCleanSeconds: "auto_clean_seconds",
|
||||
IsOwner: "is_owner",
|
||||
OperatorName: "operator_name",
|
||||
TokenConfig: "token_config",
|
||||
ExtendMapping: "extend_mapping",
|
||||
QueryConfig: "query_config",
|
||||
StreamConfig: "stream_config",
|
||||
FirstFrame: "first_frame",
|
||||
LastFrame: "last_frame",
|
||||
MaxTokens: "max_tokens",
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
ModelName: "model_name",
|
||||
ModelType: "model_type",
|
||||
BaseURL: "base_url",
|
||||
HttpMethod: "http_method",
|
||||
HeadMsg: "head_msg",
|
||||
FormJSON: "form_json",
|
||||
RequestMapping: "request_mapping",
|
||||
ResponseMapping: "response_mapping",
|
||||
RequiredFields: "required_fields",
|
||||
IsPrivate: "is_private",
|
||||
IsChatModel: "is_chat_model",
|
||||
CallMode: "call_mode",
|
||||
ApiKey: "api_key",
|
||||
Enabled: "enabled",
|
||||
MaxConcurrency: "max_concurrency",
|
||||
TimeoutSeconds: "timeout_seconds",
|
||||
RetryTimes: "retry_times",
|
||||
AutoCleanSeconds: "auto_clean_seconds",
|
||||
IsOwner: "is_owner",
|
||||
OperatorName: "operator_name",
|
||||
TokenConfig: "token_config",
|
||||
ExtendMapping: "extend_mapping",
|
||||
QueryConfig: "query_config",
|
||||
StreamConfig: "stream_config",
|
||||
FirstFrame: "first_frame",
|
||||
LastFrame: "last_frame",
|
||||
BillingConfig: "billing_config",
|
||||
}
|
||||
|
||||
type ModelGatewayModel struct {
|
||||
beans.SQLBaseDO `orm:",inline"`
|
||||
ModelName string `orm:"model_name" json:"modelName"`
|
||||
ModelType int `orm:"model_type" json:"modelType"`
|
||||
BaseURL string `orm:"base_url" json:"baseUrl"`
|
||||
HttpMethod string `orm:"http_method" json:"httpMethod"`
|
||||
HeadMsg map[string]any `orm:"head_msg" json:"headMsg"`
|
||||
Form []map[string]any `orm:"form_json" json:"form"`
|
||||
RequestMapping map[string]any `orm:"request_mapping" json:"requestMapping"`
|
||||
ResponseMapping map[string]any `orm:"response_mapping" json:"responseMapping"`
|
||||
ResponseBody string `orm:"response_body" json:"responseBody"`
|
||||
ResponseTokenField string `orm:"response_token_field" json:"tokenField"`
|
||||
RequiredFields []string `orm:"required_fields" json:"requiredFields"`
|
||||
IsPrivate *int `orm:"is_private" json:"isPrivate"`
|
||||
IsChatModel *int `orm:"is_chat_model" json:"isChatModel"`
|
||||
CallMode *int `orm:"call_mode" json:"callMode"`
|
||||
ApiKey string `orm:"api_key" json:"apiKey"`
|
||||
Enabled *int `orm:"enabled" json:"enabled"`
|
||||
MaxConcurrency int `orm:"max_concurrency" json:"maxConcurrency"`
|
||||
TimeoutSeconds int `orm:"timeout_seconds" json:"timeoutSeconds"`
|
||||
RetryTimes int `orm:"retry_times" json:"retryTimes"`
|
||||
AutoCleanSeconds int `orm:"auto_clean_seconds" json:"autoCleanSeconds"`
|
||||
IsOwner *int `orm:"is_owner" json:"isOwner"`
|
||||
OperatorName string `orm:"operator_name" json:"operatorName"`
|
||||
TokenConfig map[string]any `orm:"token_config" json:"tokenConfig"`
|
||||
ExtendMapping map[string]any `orm:"extend_mapping" json:"extendMapping"`
|
||||
QueryConfig map[string]any `orm:"query_config" json:"queryConfig"`
|
||||
StreamConfig map[string]any `orm:"stream_config" json:"streamConfig"`
|
||||
FirstFrame string `orm:"first_frame" json:"firstFrame"`
|
||||
LastFrame string `orm:"last_frame" json:"lastFrame"`
|
||||
MaxTokens int `orm:"max_tokens" json:"maxTokens"`
|
||||
beans.SQLBaseDO `orm:",inline"`
|
||||
ModelName string `orm:"model_name" json:"modelName"`
|
||||
ModelType int `orm:"model_type" json:"modelType"`
|
||||
BaseURL string `orm:"base_url" json:"baseUrl"`
|
||||
HttpMethod string `orm:"http_method" json:"httpMethod"`
|
||||
HeadMsg map[string]any `orm:"head_msg" json:"headMsg"`
|
||||
Form []map[string]any `orm:"form_json" json:"form"`
|
||||
RequestMapping map[string]any `orm:"request_mapping" json:"requestMapping"`
|
||||
ResponseMapping map[string]any `orm:"response_mapping" json:"responseMapping"`
|
||||
RequiredFields []string `orm:"required_fields" json:"requiredFields"`
|
||||
IsPrivate *int `orm:"is_private" json:"isPrivate"`
|
||||
IsChatModel *int `orm:"is_chat_model" json:"isChatModel"`
|
||||
CallMode *int `orm:"call_mode" json:"callMode"`
|
||||
ApiKey string `orm:"api_key" json:"apiKey"`
|
||||
Enabled *int `orm:"enabled" json:"enabled"`
|
||||
MaxConcurrency int `orm:"max_concurrency" json:"maxConcurrency"`
|
||||
TimeoutSeconds int `orm:"timeout_seconds" json:"timeoutSeconds"`
|
||||
RetryTimes int `orm:"retry_times" json:"retryTimes"`
|
||||
AutoCleanSeconds int `orm:"auto_clean_seconds" json:"autoCleanSeconds"`
|
||||
IsOwner *int `orm:"is_owner" json:"isOwner"`
|
||||
OperatorName string `orm:"operator_name" json:"operatorName"`
|
||||
TokenConfig map[string]any `orm:"token_config" json:"tokenConfig"`
|
||||
ExtendMapping map[string]any `orm:"extend_mapping" json:"extendMapping"`
|
||||
QueryConfig map[string]any `orm:"query_config" json:"queryConfig"`
|
||||
StreamConfig map[string]any `orm:"stream_config" json:"streamConfig"`
|
||||
FirstFrame string `orm:"first_frame" json:"firstFrame"`
|
||||
LastFrame string `orm:"last_frame" json:"lastFrame"`
|
||||
BillingConfig map[string]any `orm:"billing_config" json:"billingConfig"`
|
||||
}
|
||||
|
||||
const (
|
||||
ResponseBody = "content" //返回主体(必填)
|
||||
TotalTokens = "total_tokens" //总token数
|
||||
)
|
||||
|
||||
@@ -21,6 +21,8 @@ type modelGatewayTaskCol struct {
|
||||
TmpFile string
|
||||
RequestPayload string
|
||||
EpicycleId string
|
||||
BuildModelName string
|
||||
BillingData string
|
||||
}
|
||||
|
||||
var ModelGatewayTaskCol = modelGatewayTaskCol{
|
||||
@@ -40,26 +42,30 @@ var ModelGatewayTaskCol = modelGatewayTaskCol{
|
||||
TmpFile: "tmp_file",
|
||||
RequestPayload: "request_payload",
|
||||
EpicycleId: "epicycle_id",
|
||||
BuildModelName: "build_model_name",
|
||||
BillingData: "billing_data",
|
||||
}
|
||||
|
||||
// ModelGatewayTask 模型网关任务
|
||||
type ModelGatewayTask struct {
|
||||
beans.SQLBaseDO `orm:",inline"`
|
||||
ModelName string `orm:"model_name" json:"modelName"`
|
||||
TaskID string `orm:"task_id" json:"taskId"`
|
||||
BizName string `orm:"biz_name" json:"bizName"`
|
||||
CallbackURL string `orm:"callback_url" json:"callbackUrl"`
|
||||
State int `orm:"state" json:"state"`
|
||||
Phase int `orm:"phase" json:"phase"`
|
||||
ErrorMsg string `orm:"error_msg" json:"errorMsg"`
|
||||
ResultFile *ResultFile `orm:"result_file" json:"resultFile"`
|
||||
TextResult map[string]any `orm:"text_result" json:"text"`
|
||||
ExpendTokens int64 `orm:"expend_tokens" json:"expendTokens"`
|
||||
DurationSeconds int64 `orm:"duration_seconds" json:"durationSeconds"`
|
||||
RetryCount int `orm:"retry_count" json:"retryCount"`
|
||||
TmpFile string `orm:"tmp_file" json:"tmpFile"`
|
||||
RequestPayload *RequestPayload `orm:"request_payload" json:"requestPayload"`
|
||||
EpicycleId int64 `orm:"epicycle_id" json:"epicycleId"`
|
||||
ModelName string `orm:"model_name" json:"modelName"`
|
||||
TaskID string `orm:"task_id" json:"taskId"`
|
||||
BizName string `orm:"biz_name" json:"bizName"`
|
||||
CallbackURL string `orm:"callback_url" json:"callbackUrl"`
|
||||
State int `orm:"state" json:"state"`
|
||||
Phase int `orm:"phase" json:"phase"`
|
||||
ErrorMsg string `orm:"error_msg" json:"errorMsg"`
|
||||
ResultFile *ResultFile `orm:"result_file" json:"resultFile"`
|
||||
TextResult map[string]any `orm:"text_result" json:"text"`
|
||||
ExpendTokens int64 `orm:"expend_tokens" json:"expendTokens"`
|
||||
DurationSeconds int64 `orm:"duration_seconds" json:"durationSeconds"`
|
||||
RetryCount int `orm:"retry_count" json:"retryCount"`
|
||||
TmpFile string `orm:"tmp_file" json:"tmpFile"`
|
||||
RequestPayload *RequestPayload `orm:"request_payload" json:"requestPayload"`
|
||||
EpicycleId int64 `orm:"epicycle_id" json:"epicycleId"`
|
||||
BuildModelName string `orm:"build_model_name" json:"buildModelName"`
|
||||
BillingData []map[string]any `orm:"billing_data" json:"billingData"`
|
||||
}
|
||||
|
||||
// ResultFile OSS 结果文件
|
||||
|
||||
@@ -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:"任务状态-未知"`
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"model-gateway/common/util"
|
||||
"model-gateway/model/entity"
|
||||
"time"
|
||||
|
||||
@@ -43,16 +42,25 @@ func UploadByTask(ctx context.Context, data []byte, fileExt string) (oss *Upload
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := part.Write(data); err != nil {
|
||||
if _, err = part.Write(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contentType := writer.FormDataContentType()
|
||||
//contentType := writer.FormDataContentType()
|
||||
if err = writer.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
headers := util.ForwardHeaders(ctx)
|
||||
headers["Content-Type"] = contentType
|
||||
//headers := util.ForwardHeaders(ctx)
|
||||
//headers["Content-Type"] = contentType
|
||||
|
||||
headers := make(map[string]string)
|
||||
headers["Content-Type"] = writer.FormDataContentType()
|
||||
if r := g.RequestFromCtx(ctx); r != nil {
|
||||
if auth := r.Header.Get("Authorization"); auth != "" {
|
||||
headers["Authorization"] = auth
|
||||
}
|
||||
}
|
||||
|
||||
fullURL := "oss/file/uploadFile"
|
||||
g.Log().Infof(ctx, "[OSS] upload start url=%s filename=%s size=%d", fullURL, filename, len(data))
|
||||
|
||||
@@ -69,23 +77,35 @@ func UploadByTask(ctx context.Context, data []byte, fileExt string) (oss *Upload
|
||||
|
||||
// CallbackPayload 回调请求体
|
||||
type CallbackPayload struct {
|
||||
TaskId string `json:"task_id"`
|
||||
State int `json:"state"`
|
||||
OssFile string `json:"oss_file"`
|
||||
FileType string `json:"file_type"`
|
||||
ErrorMsg string `json:"error_msg"`
|
||||
TaskId string `json:"task_id"`
|
||||
State int `json:"state"`
|
||||
OssFile string `json:"oss_file"`
|
||||
FileType string `json:"file_type"`
|
||||
ErrorMsg string `json:"error_msg"`
|
||||
BillingDate []map[string]any `json:"billing_data"`
|
||||
}
|
||||
|
||||
// TriggerCallback 任务的回调
|
||||
func TriggerCallback(ctx context.Context, t *entity.ModelGatewayTask) {
|
||||
headers := util.ForwardHeaders(ctx)
|
||||
//headers := util.ForwardHeaders(ctx)
|
||||
headers := make(map[string]string)
|
||||
if r := g.RequestFromCtx(ctx); r != nil {
|
||||
for k, v := range r.Request.Header {
|
||||
if len(v) > 0 {
|
||||
headers[k] = v[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
var resp struct{}
|
||||
payload := CallbackPayload{
|
||||
TaskId: t.TaskID,
|
||||
State: t.State,
|
||||
OssFile: t.ResultFile.OssFile,
|
||||
FileType: t.ResultFile.FileType,
|
||||
ErrorMsg: t.ErrorMsg,
|
||||
TaskId: t.TaskID,
|
||||
State: t.State,
|
||||
ErrorMsg: t.ErrorMsg,
|
||||
BillingDate: t.BillingData,
|
||||
}
|
||||
if !g.IsEmpty(t.ResultFile) {
|
||||
payload.OssFile = t.ResultFile.OssFile
|
||||
payload.FileType = t.ResultFile.FileType
|
||||
}
|
||||
jsonData, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
@@ -112,7 +132,15 @@ type PromptsCallbackPayload struct {
|
||||
// TriggerPromptsCallback 任务成功后的提示词回调
|
||||
func TriggerPromptsCallback(ctx context.Context, t *entity.ModelGatewayTask, epicycleId int64) {
|
||||
callbackURL := "prompts-core/session/callback"
|
||||
headers := util.ForwardHeaders(ctx)
|
||||
//headers := util.ForwardHeaders(ctx)
|
||||
headers := make(map[string]string)
|
||||
if r := g.RequestFromCtx(ctx); r != nil {
|
||||
for k, v := range r.Request.Header {
|
||||
if len(v) > 0 {
|
||||
headers[k] = v[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
var resp struct{}
|
||||
payload := PromptsCallbackPayload{
|
||||
EpicycleId: epicycleId,
|
||||
@@ -136,7 +164,15 @@ func TriggerPromptsCallback(ctx context.Context, t *entity.ModelGatewayTask, epi
|
||||
|
||||
// IsSuperAdmin 调用admin-go服务检查是否是超级管理员
|
||||
func IsSuperAdmin(ctx context.Context) (res bool, err error) {
|
||||
headers := util.ForwardHeaders(ctx)
|
||||
//headers := util.ForwardHeaders(ctx)
|
||||
headers := make(map[string]string)
|
||||
if r := g.RequestFromCtx(ctx); r != nil {
|
||||
for k, v := range r.Request.Header {
|
||||
if len(v) > 0 {
|
||||
headers[k] = v[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
var r = make(map[string]bool)
|
||||
if err = commonHttp.Get(ctx, "admin-go/api/v1/system/user/checkIsSuperAdmin", headers, &r); err != nil {
|
||||
return false, err
|
||||
@@ -144,6 +180,109 @@ func IsSuperAdmin(ctx context.Context) (res bool, err error) {
|
||||
return r["isSuperAdmin"], err
|
||||
}
|
||||
|
||||
// VideoDurationResp 视频时长接口返回
|
||||
type VideoDurationResp struct {
|
||||
Videos []VideoInfo `json:"videos"`
|
||||
Count int `json:"count"`
|
||||
TotalDuration float64 `json:"totalDuration"`
|
||||
TotalDurationStr string `json:"totalDurationStr"`
|
||||
}
|
||||
|
||||
type VideoInfo struct {
|
||||
Index int `json:"index"`
|
||||
VideoUrl string `json:"videoUrl"`
|
||||
Duration float64 `json:"duration"`
|
||||
DurationStr string `json:"durationStr"`
|
||||
}
|
||||
|
||||
// GetVideoDuration 获取视频时长
|
||||
func GetVideoDuration(ctx context.Context, urls []string) (VideoDurationResp, error) {
|
||||
apiURL := "media/video/duration"
|
||||
headers := make(map[string]string)
|
||||
if r := g.RequestFromCtx(ctx); r != nil {
|
||||
for k, v := range r.Request.Header {
|
||||
if len(v) > 0 {
|
||||
headers[k] = v[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body := map[string]any{"video_urls": urls}
|
||||
jsonData, _ := json.Marshal(body)
|
||||
|
||||
var resp VideoDurationResp
|
||||
err := commonHttp.Post(ctx, apiURL, headers, &resp, jsonData)
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "[视频时长] 获取失败 err=%v", err)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
g.Log().Infof(ctx, "[视频时长] 获取成功 count=%d totalDuration=%.2f", resp.Count, resp.TotalDuration)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DeductBalanceReq 扣减余额请求
|
||||
type DeductBalanceReq struct {
|
||||
Id uint64 `json:"id"`
|
||||
Surplus float64 `json:"surplus"`
|
||||
}
|
||||
|
||||
// DeductBalance 扣减租户余额
|
||||
func DeductBalance(ctx context.Context, tenantId uint64, amount float64) error {
|
||||
apiURL := "admin-go/api/v1/system/tenant/edit"
|
||||
headers := make(map[string]string)
|
||||
if r := g.RequestFromCtx(ctx); r != nil {
|
||||
for k, v := range r.Request.Header {
|
||||
if len(v) > 0 {
|
||||
headers[k] = v[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body := DeductBalanceReq{
|
||||
Id: tenantId,
|
||||
Surplus: amount,
|
||||
}
|
||||
jsonData, _ := json.Marshal(body)
|
||||
|
||||
var resp struct{}
|
||||
err := commonHttp.Put(ctx, apiURL, headers, &resp, jsonData)
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "[扣减余额] 失败 tenantId=%d amount=%.6f err=%v", tenantId, amount, err)
|
||||
return err
|
||||
}
|
||||
g.Log().Infof(ctx, "[扣减余额] 成功 tenantId=%d amount=%.6f", tenantId, amount)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TenantSurplusResp 租户余额返回
|
||||
type TenantSurplusResp struct {
|
||||
Tenant struct {
|
||||
Surplus float64 `json:"surplus"`
|
||||
} `json:"tenant"`
|
||||
}
|
||||
|
||||
// GetTenantSurplus 获取租户余额
|
||||
func GetTenantSurplus(ctx context.Context, tenantId uint64) (float64, error) {
|
||||
apiURL := fmt.Sprintf("admin-go/api/v1/system/tenant/getTenantDetails?tenantId=%d", tenantId)
|
||||
headers := make(map[string]string)
|
||||
if r := g.RequestFromCtx(ctx); r != nil {
|
||||
for k, v := range r.Request.Header {
|
||||
if len(v) > 0 {
|
||||
headers[k] = v[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var resp TenantSurplusResp
|
||||
err := commonHttp.Get(ctx, apiURL, headers, &resp, nil)
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "[获取余额] 失败 tenantId=%d err=%v", tenantId, err)
|
||||
return 0, err
|
||||
}
|
||||
return resp.Tenant.Surplus, nil
|
||||
}
|
||||
|
||||
//// callback 向回调地址 POST 任务结果(与查询接口 GetTaskRes 出参一致)
|
||||
//func (s *audioTaskService) callback(ctx context.Context, taskID, status, errMsg, callbackURL string) {
|
||||
// if callbackURL == "" {
|
||||
|
||||
@@ -99,7 +99,7 @@ func (s *modelService) Get(ctx context.Context, req *dto.GetModelReq) (*dto.GetM
|
||||
ModelName: req.ModelName,
|
||||
IsChatModel: req.IsChatModel,
|
||||
})
|
||||
if err != nil {
|
||||
if err != nil || model == nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.GetModelRes{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+194
-65
@@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"model-gateway/common/util"
|
||||
"model-gateway/consts/public"
|
||||
"model-gateway/service/queue"
|
||||
"time"
|
||||
|
||||
"model-gateway/dao"
|
||||
@@ -16,7 +15,10 @@ import (
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
"gitea.redpowerfuture.com/red-future/common/utils"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/database/gredis"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
@@ -27,13 +29,19 @@ type taskService struct{}
|
||||
|
||||
// Create 创建任务
|
||||
func (s *taskService) Create(ctx context.Context, req *dto.CreateTaskReq) (res *dto.CreateTaskRes, err error) {
|
||||
taskID := uuid.NewString()
|
||||
taskID := req.TaskId
|
||||
if taskID == "" {
|
||||
taskID = uuid.NewString()
|
||||
}
|
||||
startAt := time.Now()
|
||||
|
||||
// 1) 检查模型配置,并且获取模型
|
||||
// 1) 获取用户信息
|
||||
userInfo, err := utils.GetUserInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2) 检查模型配置
|
||||
model, err := dao.ModelGatewayModels.Get(ctx, &entity.ModelGatewayModel{
|
||||
SQLBaseDO: beans.SQLBaseDO{
|
||||
TenantId: userInfo.TenantId,
|
||||
@@ -47,78 +55,196 @@ func (s *taskService) Create(ctx context.Context, req *dto.CreateTaskReq) (res *
|
||||
if model == nil || (model.Enabled != nil && *model.Enabled != 1) {
|
||||
return nil, errors.New("模型不存在或未启用")
|
||||
}
|
||||
lockKey := fmt.Sprintf("lock:tenantId-%s:model-%s", gconv.String(userInfo.TenantId), req.ModelName)
|
||||
success, e := Lock(ctx, lockKey, -1, int64(time.Minute.Seconds()*5), func(ctx context.Context) error {
|
||||
const (
|
||||
keyExpireSec = 600 // 计数Key兜底过期时间 10min
|
||||
waitInterval = 10 * time.Second // 轮询等待间隔
|
||||
)
|
||||
// Redis 操作统一使用独立上下文,避免外部 ctx canceled
|
||||
redisCtx := context.WithoutCancel(ctx)
|
||||
// 模型并发计数Key
|
||||
concurrencyKey := fmt.Sprintf("model:concurrency:%s", req.ModelName)
|
||||
maxCon := gconv.Int64(model.MaxConcurrency)
|
||||
|
||||
// 2) 排队上限(严格控制:Redis 原子闸门)
|
||||
limit := queue.GetRuntimeQueueLimit(ctx, req.ModelName, model.MaxConcurrency*2)
|
||||
if limit > 0 {
|
||||
ok, err := queue.AcquireQueueSlot(ctx, req.ModelName, taskID, limit, model.TimeoutSeconds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// 循环尝试获取并发名额,超限则等待重试
|
||||
var held bool // 标记当前是否持有未释放的计数
|
||||
for {
|
||||
// 检测全局上下文取消
|
||||
if ctx.Err() != nil {
|
||||
if held {
|
||||
g.Redis().Decr(redisCtx, concurrencyKey)
|
||||
}
|
||||
return ctx.Err()
|
||||
}
|
||||
// 计数自增
|
||||
currentCon, e := g.Redis().Incr(redisCtx, concurrencyKey)
|
||||
if e != nil {
|
||||
if held {
|
||||
g.Redis().Decr(redisCtx, concurrencyKey)
|
||||
}
|
||||
glog.Errorf(ctx, "redis incr concurrency key err: %v", e)
|
||||
return e
|
||||
}
|
||||
held = true
|
||||
// 首次创建Key时设置过期时间(避免重复执行EXPIRE)
|
||||
exists, errr := g.Redis().Exists(redisCtx, concurrencyKey)
|
||||
if errr == nil && exists == 1 {
|
||||
g.Redis().Expire(redisCtx, concurrencyKey, keyExpireSec)
|
||||
}
|
||||
|
||||
// 未超限:跳出循环,执行业务
|
||||
if currentCon <= maxCon {
|
||||
glog.Infof(ctx, "并发数: %s %d/%d", concurrencyKey, currentCon, maxCon)
|
||||
break
|
||||
}
|
||||
// 超限立刻回减,撤销本次计数
|
||||
g.Redis().Decr(redisCtx, concurrencyKey)
|
||||
held = false
|
||||
glog.Infof(ctx, "并发超限等待: %s %d/%d", concurrencyKey, currentCon, maxCon)
|
||||
time.Sleep(waitInterval)
|
||||
}
|
||||
if !ok {
|
||||
return nil, errors.New("任务排队已满,请稍后再试")
|
||||
|
||||
// 3) 构建任务实体
|
||||
task := &entity.ModelGatewayTask{
|
||||
ModelName: model.ModelName,
|
||||
TaskID: taskID,
|
||||
State: public.TaskStatusRunning,
|
||||
BizName: req.BizName,
|
||||
CallbackURL: req.CallbackUrl,
|
||||
RequestPayload: &entity.RequestPayload{
|
||||
Body: req.RequestPayload,
|
||||
Headers: util.ParseHeadMsgHeaders(model.HeadMsg),
|
||||
},
|
||||
EpicycleId: req.EpicycleId,
|
||||
BuildModelName: req.BuildModelName,
|
||||
}
|
||||
}
|
||||
|
||||
// 3) 插入任务记录
|
||||
requestPayload := entity.RequestPayload{
|
||||
Body: req.RequestPayload,
|
||||
Headers: util.ParseHeadMsgHeaders(model.HeadMsg),
|
||||
}
|
||||
id, err := dao.ModelGatewayTask.Insert(ctx, &entity.ModelGatewayTask{
|
||||
ModelName: req.ModelName,
|
||||
TaskID: taskID,
|
||||
State: public.TaskStatusPending,
|
||||
BizName: req.BizName,
|
||||
CallbackURL: req.CallbackUrl,
|
||||
RequestPayload: &requestPayload,
|
||||
EpicycleId: req.EpicycleId,
|
||||
// 4) 插入任务记录
|
||||
id, errr := dao.ModelGatewayTask.Insert(ctx, task)
|
||||
if errr != nil {
|
||||
g.Redis().Decr(redisCtx, concurrencyKey)
|
||||
// TODO: 恢复排队逻辑后,此处需要回滚排队占位
|
||||
//queue.ReleaseQueueSlot(ctx, req.ModelName, taskID)
|
||||
return errr
|
||||
}
|
||||
task.Id = id
|
||||
|
||||
// 5) 记录操作日志(非关键路径,失败不影响主流程)
|
||||
ip, ua := "", ""
|
||||
if r := g.RequestFromCtx(ctx); r != nil {
|
||||
ip = utils.GetLocalIP()
|
||||
ua = r.UserAgent()
|
||||
}
|
||||
_, _ = dao.ModelGatewayLogsOp.Insert(ctx, &entity.ModelGatewayLogsOp{
|
||||
IP: ip,
|
||||
UserAgent: ua,
|
||||
APIPath: "/task/createTask",
|
||||
HttpMethod: "POST",
|
||||
BizName: req.BizName,
|
||||
ModelName: req.ModelName,
|
||||
TaskID: taskID,
|
||||
OpType: "createTask",
|
||||
Success: 1,
|
||||
CostMs: time.Since(startAt).Milliseconds(),
|
||||
RequestPayload: task.RequestPayload,
|
||||
ResponsePayload: gdb.Map{"taskId": taskID},
|
||||
})
|
||||
|
||||
// 6) 模型计费
|
||||
if len(model.BillingConfig) > 0 {
|
||||
requestData := util.ExtractRequestBilling(ctx, model.BillingConfig, req.RequestPayload)
|
||||
// 请求数据作为计费记录的基础字段,先存入数组
|
||||
task.BillingData = append(task.BillingData, requestData)
|
||||
_, _ = dao.ModelGatewayTask.Update(ctx, &entity.ModelGatewayTask{
|
||||
SQLBaseDO: beans.SQLBaseDO{Id: task.Id},
|
||||
BillingData: task.BillingData,
|
||||
})
|
||||
}
|
||||
|
||||
// 7) 异步执行任务
|
||||
go AsyncWorker.handleOne(util.AsyncCtx(ctx), task, model, req)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil { // 入库失败:回滚闸门占位
|
||||
queue.ReleaseQueueSlot(ctx, req.ModelName, taskID)
|
||||
return nil, err
|
||||
if e != nil {
|
||||
err = e
|
||||
return
|
||||
}
|
||||
|
||||
// 4) 写操作日志(不影响主流程,失败忽略)
|
||||
ip := ""
|
||||
ua := ""
|
||||
apiPath := "/task/createTask"
|
||||
httpMethod := "POST"
|
||||
if r := g.RequestFromCtx(ctx); r != nil {
|
||||
ip = utils.GetLocalIP()
|
||||
ua = r.UserAgent()
|
||||
apiPath = r.URL.Path
|
||||
httpMethod = r.Method
|
||||
if !success {
|
||||
err = gerror.New("任务排队已满,请稍后再试")
|
||||
return
|
||||
}
|
||||
_, _ = dao.ModelGatewayLogsOp.Insert(ctx, &entity.ModelGatewayLogsOp{
|
||||
IP: ip,
|
||||
UserAgent: ua,
|
||||
APIPath: apiPath,
|
||||
HttpMethod: httpMethod,
|
||||
BizName: req.BizName,
|
||||
ModelName: req.ModelName,
|
||||
TaskID: taskID,
|
||||
OpType: "createTask",
|
||||
Success: 1,
|
||||
CostMs: time.Since(time.Now()).Milliseconds(),
|
||||
RequestPayload: &requestPayload,
|
||||
ResponsePayload: gdb.Map{
|
||||
"taskId": taskID,
|
||||
},
|
||||
})
|
||||
|
||||
// 5) 获取任务信息
|
||||
task, err := dao.ModelGatewayTask.ClaimByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 5) 创建成功后立即异步尝试执行当前任务
|
||||
go AsyncWorker.handleOne(util.AsyncCtx(ctx), task, model, req)
|
||||
|
||||
return &dto.CreateTaskRes{TaskID: taskID}, nil
|
||||
}
|
||||
|
||||
// Lock 分布式锁 纯原生命令、无Lua、隔离上下文防 context canceled
|
||||
func Lock(ctx context.Context, key string, limit, expireSeconds int64, fn func(ctx context.Context) error) (success bool, err error) {
|
||||
if limit <= 0 {
|
||||
limit = -1
|
||||
}
|
||||
|
||||
// 过期时间合法校验(单位:秒)
|
||||
const maxExpireSec = 86400 * 7
|
||||
if expireSeconds < 1 || expireSeconds > maxExpireSec {
|
||||
glog.Warningf(ctx, "锁过期时间非法,原值:%d,兜底为60秒", expireSeconds)
|
||||
expireSeconds = 60
|
||||
}
|
||||
|
||||
lockVal := "1"
|
||||
|
||||
LOOP:
|
||||
// 检测父级上下文取消,防止无限重试阻塞 goroutine
|
||||
if ctx.Err() != nil {
|
||||
return false, ctx.Err()
|
||||
}
|
||||
if limit != -1 {
|
||||
if limit < 0 {
|
||||
return false, errors.New("锁重试次数耗尽,获取锁失败")
|
||||
}
|
||||
limit--
|
||||
}
|
||||
|
||||
// 核心:创建独立上下文,不受外部 ctx 取消影响
|
||||
redisCtx := context.WithoutCancel(ctx)
|
||||
|
||||
// 加锁
|
||||
val, err := g.Redis().Set(redisCtx, key, lockVal, gredis.SetOption{
|
||||
TTLOption: gredis.TTLOption{
|
||||
EX: &expireSeconds,
|
||||
},
|
||||
NX: true,
|
||||
})
|
||||
if err != nil {
|
||||
glog.Errorf(ctx, "redis set lock failed: %v", err)
|
||||
time.Sleep(time.Second)
|
||||
goto LOOP
|
||||
}
|
||||
|
||||
if val.Bool() {
|
||||
// 执行业务逻辑(使用原上下文)
|
||||
runErr := fn(ctx)
|
||||
|
||||
// 释放锁:同样使用独立上下文 + 先GET再DEL防误删
|
||||
getRes, err := g.Redis().Get(redisCtx, key)
|
||||
if err != nil {
|
||||
glog.Errorf(ctx, "redis get lock value failed: %v", err)
|
||||
} else if getRes.String() == lockVal {
|
||||
_, delErr := g.Redis().Del(redisCtx, key)
|
||||
if delErr != nil {
|
||||
glog.Errorf(ctx, "redis del lock failed: %v", delErr)
|
||||
}
|
||||
}
|
||||
|
||||
return true, runErr
|
||||
}
|
||||
|
||||
// 抢锁失败,休眠重试
|
||||
time.Sleep(time.Second)
|
||||
goto LOOP
|
||||
}
|
||||
|
||||
// GetResult 获取任务结果
|
||||
func (s *taskService) GetResult(ctx context.Context, taskID string) (res *dto.GetTaskResultRes, err error) {
|
||||
t, err := dao.ModelGatewayTask.Get(ctx, &entity.ModelGatewayTask{
|
||||
@@ -255,7 +381,10 @@ func (s *taskService) QueryPendingTasks(ctx context.Context, req *dto.QueryPendi
|
||||
if err != nil || model == nil || model.QueryConfig == nil {
|
||||
continue
|
||||
}
|
||||
result, err := util.PullTaskResult(ctx, nil, model.QueryConfig, model.HeadMsg)
|
||||
// 每个任务使用独立的超时上下文,防止单个任务阻塞整个轮询
|
||||
pullCtx, pullCancel := context.WithTimeout(ctx, 30*time.Second)
|
||||
result, err := util.PullTaskResult(pullCtx, nil, model.QueryConfig, model.HeadMsg)
|
||||
pullCancel()
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "[轮询] 查询失败 taskID=%s err=%v", t.TaskID, err)
|
||||
continue
|
||||
|
||||
+264
-206
@@ -6,22 +6,19 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"model-gateway/common/util"
|
||||
"model-gateway/consts/public"
|
||||
"model-gateway/dao"
|
||||
"model-gateway/model/dto"
|
||||
"model-gateway/model/entity"
|
||||
"model-gateway/service/gateway"
|
||||
"model-gateway/service/queue"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
"gitea.redpowerfuture.com/red-future/common/utils"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
@@ -38,73 +35,125 @@ func (w *asyncWorker) handleOne(ctx context.Context, task *entity.ModelGatewayTa
|
||||
body = task.RequestPayload.Body
|
||||
maxRetry = model.RetryTimes
|
||||
startTime = time.Now()
|
||||
rawData []byte
|
||||
result map[string]any
|
||||
err error
|
||||
surplus float64
|
||||
)
|
||||
g.Log().Infof(ctx, "[执行任务][开始] taskId=%s model=%s", task.TaskID, task.ModelName)
|
||||
g.Log().Infof(ctx, "[handleOne] 开始 taskId=%s model=%s", task.TaskID, task.ModelName)
|
||||
|
||||
// ============================================
|
||||
// 1) 分布式并发控制
|
||||
// 1) 查询余额
|
||||
// ============================================
|
||||
semKey := fmt.Sprintf("asynch:sem:%s", task.ModelName)
|
||||
maxC := queue.GetRuntimeMaxConcurrency(ctx, task.ModelName, model.MaxConcurrency)
|
||||
acquired, err := queue.AcquireSemaphore(ctx, semKey, maxC, 3600)
|
||||
if err != nil {
|
||||
task.DurationSeconds = int64(time.Since(startTime).Seconds())
|
||||
w.failTask(ctx, task, startTime, err.Error())
|
||||
surplus, _ = gateway.GetTenantSurplus(ctx, model.TenantId)
|
||||
if surplus <= 200 {
|
||||
w.failTask(ctx, task, startTime, "租户余额不足")
|
||||
return
|
||||
}
|
||||
if !acquired {
|
||||
_, _ = dao.ModelGatewayTask.Update(ctx, &entity.ModelGatewayTask{
|
||||
SQLBaseDO: beans.SQLBaseDO{Id: task.Id},
|
||||
State: public.TaskStatusPending,
|
||||
})
|
||||
g.Log().Infof(ctx, "[执行任务][排队] 并发已满,放回队列 taskId=%s", task.TaskID)
|
||||
return
|
||||
}
|
||||
defer func() { _ = queue.ReleaseSemaphore(ctx, semKey) }()
|
||||
g.Log().Infof(ctx, "[handleOne] 当前余额 tenantId=%d surplus=%.2f", model.TenantId, surplus)
|
||||
|
||||
// ============================================
|
||||
// 2) 调用模型
|
||||
// ============================================
|
||||
switch {
|
||||
case model.CallMode != nil && *model.CallMode == public.CallModeStream:
|
||||
rawBytes, streamErr := w.callModelStream(ctx, task, model, body)
|
||||
if streamErr != nil {
|
||||
w.failTask(ctx, task, startTime, streamErr.Error())
|
||||
for attempt := 0; ; attempt++ {
|
||||
if attempt > 0 {
|
||||
g.Log().Infof(ctx, "[handleOne] 调模型重试 第%d次 taskId=%s", attempt, task.TaskID)
|
||||
time.Sleep(time.Duration(attempt) * 5 * time.Second)
|
||||
}
|
||||
|
||||
rawData, err = InvokeModel(ctx, model, body)
|
||||
switch {
|
||||
case model.CallMode != nil && *model.CallMode == public.CallModeStream:
|
||||
if err == nil {
|
||||
result, err = util.ParseStreamResponse(rawData, model.StreamConfig)
|
||||
}
|
||||
case model.CallMode != nil && *model.CallMode == public.CallModeAsync:
|
||||
if err == nil {
|
||||
result = gjson.New(string(rawData)).Map()
|
||||
result, err = util.PullTaskResult(ctx, result, model.QueryConfig, model.HeadMsg)
|
||||
}
|
||||
default:
|
||||
if err == nil {
|
||||
result = gjson.New(string(rawData)).Map()
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
// 模型调用失败
|
||||
if !strings.Contains(err.Error(), "Timeout") &&
|
||||
!strings.Contains(err.Error(), "RequestCanceled") &&
|
||||
!strings.Contains(err.Error(), "InternalServiceError") &&
|
||||
!strings.Contains(err.Error(), "Invalid video_url") &&
|
||||
!strings.Contains(err.Error(), "Invalid audio track") &&
|
||||
!strings.Contains(err.Error(), "Error while downloading") &&
|
||||
!strings.Contains(err.Error(), "Error while connecting") &&
|
||||
!strings.Contains(err.Error(), "download failed") {
|
||||
w.failTask(ctx, task, startTime, err.Error())
|
||||
return
|
||||
}
|
||||
result, err = util.ParseStreamResponse(rawBytes, model.StreamConfig)
|
||||
case model.CallMode != nil && *model.CallMode == public.CallModeAsync:
|
||||
result, err = w.callModel(ctx, task, model, body)
|
||||
if err == nil {
|
||||
result, err = util.PullTaskResult(ctx, result, model.QueryConfig, model.HeadMsg)
|
||||
|
||||
g.Log().Warningf(ctx, "[handleOne] 调模型失败 taskId=%s attempt=%d err=%v", task.TaskID, attempt, err)
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 3) 解析返回映射 + 存储 token 相关信息
|
||||
// ============================================
|
||||
mapped, err := util.MapResponsePayload(model.ResponseMapping, result)
|
||||
if err != nil {
|
||||
w.failTask(ctx, task, startTime, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 计费处理
|
||||
if len(model.BillingConfig) > 0 && len(task.BillingData) > 0 {
|
||||
// 取请求阶段数据作为基础
|
||||
billingInput := make(map[string]any)
|
||||
for k, v := range task.BillingData[0] {
|
||||
billingInput[k] = v
|
||||
}
|
||||
// 补充返回数据
|
||||
responseData := util.ExtractResponseBilling(model.BillingConfig, mapped)
|
||||
for k, v := range responseData {
|
||||
billingInput[k] = v
|
||||
}
|
||||
// 计算费用,替换数组第一个元素
|
||||
billingResult := util.CalculateBilling(model.BillingConfig, billingInput)
|
||||
if billingResult != nil {
|
||||
task.BillingData[0] = billingResult
|
||||
}
|
||||
if billingResult != nil {
|
||||
task.BillingData[0] = billingResult
|
||||
totalFee := gconv.Float64(billingResult["total_fee"])
|
||||
if totalFee > 0 {
|
||||
for attempt := 0; attempt <= maxRetry; attempt++ {
|
||||
err = gateway.DeductBalance(util.AsyncCtx(ctx), model.TenantId, -totalFee)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
g.Log().Warningf(ctx, "[handleOne] 扣除余额失败 taskId=%s attempt=%d/%d err=%v", task.TaskID, attempt, maxRetry, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
result, err = w.callModel(ctx, task, model, body)
|
||||
}
|
||||
if err != nil {
|
||||
w.failTask(ctx, task, startTime, err.Error())
|
||||
|
||||
task.ExpendTokens = gconv.Int64(mapped[entity.TotalTokens])
|
||||
if _, err = dao.ModelGatewayTask.Update(ctx, task); err != nil {
|
||||
g.Log().Errorf(ctx, "[handleOne] 更新DB失败 taskId=%s err=%v", task.TaskID, err)
|
||||
return
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 3) 缓存临时文件
|
||||
// 4) 处理提示词相关数据解析涵盖重试
|
||||
// ============================================
|
||||
if tmpPath, tmpErr := util.SaveTempFileByType(task.TaskID, result, task.TmpFile); tmpErr == nil && tmpPath != "" {
|
||||
task.TmpFile = tmpPath
|
||||
task.Phase = 1
|
||||
_, _ = dao.ModelGatewayTask.Update(ctx, task)
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 4) 解析校验 + 响应映射(可重试)
|
||||
// ============================================
|
||||
result, err = w.parseAndRetry(ctx, result, task, model, req, maxRetry, startTime)
|
||||
if err != nil {
|
||||
task.TextResult = result
|
||||
w.failTask(ctx, task, startTime, err.Error())
|
||||
return
|
||||
if req.BuildType == public.BuildTypePrompt {
|
||||
mapped, err = w.parseAndRetry(ctx, mapped, model, task, maxRetry)
|
||||
if err != nil {
|
||||
task.TextResult = mapped
|
||||
w.failTask(ctx, task, startTime, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
@@ -113,18 +162,14 @@ func (w *asyncWorker) handleOne(ctx context.Context, task *entity.ModelGatewayTa
|
||||
var oss *gateway.UploadFileResponse
|
||||
for attempt := 0; attempt <= maxRetry; attempt++ {
|
||||
if attempt > 0 {
|
||||
g.Log().Infof(ctx, "[执行任务][重试] OSS上传 第%d/%d次 taskId=%s", attempt, maxRetry, task.TaskID)
|
||||
g.Log().Infof(ctx, "[handleOne] OSS上传重试 第%d/%d次 taskId=%s", attempt, maxRetry, task.TaskID)
|
||||
}
|
||||
oss, err = gateway.UploadByTask(ctx, gjson.New(result).MustToJson(), "json")
|
||||
oss, err = gateway.UploadByTask(ctx, gjson.New(mapped).MustToJson(), "json")
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
g.Log().Errorf(ctx, "[执行任务][失败] OSS上传失败 taskId=%s attempt=%d/%d err=%v", task.TaskID, attempt, maxRetry, err)
|
||||
g.Log().Errorf(ctx, "[handleOne] OSS上传失败 taskId=%s attempt=%d/%d err=%v", task.TaskID, attempt, maxRetry, err)
|
||||
if attempt == maxRetry {
|
||||
task.State = public.TaskStatusFailed
|
||||
task.ErrorMsg = err.Error()
|
||||
task.Phase = 1
|
||||
_, _ = dao.ModelGatewayTask.Update(ctx, task)
|
||||
w.failTask(ctx, task, startTime, fmt.Sprintf("OSS上传重试耗尽: %v", err))
|
||||
return
|
||||
}
|
||||
@@ -140,53 +185,20 @@ func (w *asyncWorker) handleOne(ctx context.Context, task *entity.ModelGatewayTa
|
||||
FileType: oss.FileFormat,
|
||||
FileSize: int64(oss.FileSize),
|
||||
}
|
||||
task.TextResult = result
|
||||
task.TextResult = mapped
|
||||
if _, err = dao.ModelGatewayTask.Update(ctx, task); err != nil {
|
||||
g.Log().Errorf(ctx, "[执行任务][失败] 更新数据库失败 taskId=%s err=%v", task.TaskID, err)
|
||||
g.Log().Errorf(ctx, "[handleOne] 更新DB失败 taskId=%s err=%v", task.TaskID, err)
|
||||
return
|
||||
}
|
||||
|
||||
queue.ReleaseQueueSlot(ctx, task.ModelName, task.TaskID)
|
||||
go gateway.TriggerCallback(context.WithoutCancel(ctx), task)
|
||||
concurrencyKey := fmt.Sprintf("model:concurrency:%s", req.ModelName)
|
||||
g.Redis().Decr(ctx, concurrencyKey)
|
||||
gateway.TriggerCallback(ctx, task)
|
||||
if req.EpicycleId != 0 {
|
||||
go gateway.TriggerPromptsCallback(context.WithoutCancel(ctx), task, req.EpicycleId)
|
||||
gateway.TriggerPromptsCallback(ctx, task, req.EpicycleId)
|
||||
}
|
||||
|
||||
g.Log().Infof(ctx, "[执行任务][成功] taskId=%s duration=%ds fileType=%s",
|
||||
g.Log().Infof(ctx, "[handleOne] 成功 taskId=%s duration=%ds fileType=%s",
|
||||
task.TaskID, task.DurationSeconds, oss.FileFormat)
|
||||
|
||||
_ = os.Remove(task.TmpFile)
|
||||
}
|
||||
|
||||
// callModelStream 调用模型,返回原始字节(不做响应映射,用于流式输出)
|
||||
func (w *asyncWorker) callModelStream(ctx context.Context, task *entity.ModelGatewayTask, model *entity.ModelGatewayModel, body map[string]any) ([]byte, error) {
|
||||
var data []byte
|
||||
var err error
|
||||
|
||||
if task.Phase == 1 && strings.TrimSpace(task.TmpFile) != "" {
|
||||
data, err = os.ReadFile(task.TmpFile)
|
||||
if err != nil || len(data) == 0 {
|
||||
data = nil
|
||||
}
|
||||
}
|
||||
|
||||
if data == nil {
|
||||
data, err = InvokeModel(ctx, model, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmpPath, tmpErr := util.SaveTmpResult(task.TaskID, data, "")
|
||||
if tmpErr == nil && tmpPath != "" {
|
||||
task.TmpFile = tmpPath
|
||||
task.Phase = 1
|
||||
_, err = dao.ModelGatewayTask.Update(ctx, task)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "[执行任务][失败] 临时文件保存失败 taskId=%s err=%v", task.TaskID, tmpErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// asyncResult 异步任务结果
|
||||
@@ -200,12 +212,13 @@ var asyncTaskChan = sync.Map{} // taskID → chan asyncResult
|
||||
|
||||
func (w *asyncWorker) callModelAsync(ctx context.Context, task *entity.ModelGatewayTask, model *entity.ModelGatewayModel, body map[string]any) (map[string]any, error) {
|
||||
// 1. 提交异步任务
|
||||
body, err := w.callModel(ctx, task, model, body)
|
||||
rawData, err := InvokeModel(ctx, model, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body = gjson.New(string(rawData)).Map()
|
||||
// 2. 拿到 task_id
|
||||
taskID := gjson.New(body).Get(model.ResponseBody).String()
|
||||
taskID := gjson.New(body).Get(entity.ResponseBody).String()
|
||||
|
||||
// 3. 创建等待通道
|
||||
ch := make(chan asyncResult, 1)
|
||||
@@ -241,135 +254,170 @@ func NotifyAsyncResult(taskID string, result map[string]any, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// callModel 调用模型 + 检测文件类型 + 保存临时文件
|
||||
// 返回: 解析后的响应体, error
|
||||
func (w *asyncWorker) callModel(ctx context.Context, task *entity.ModelGatewayTask, model *entity.ModelGatewayModel, body map[string]any) (map[string]any, error) {
|
||||
var data []byte
|
||||
var err error
|
||||
|
||||
// 1) 如果已有临时文件且 phase=1,直接读取
|
||||
if task.Phase == 1 && strings.TrimSpace(task.TmpFile) != "" {
|
||||
data, err = os.ReadFile(task.TmpFile)
|
||||
if err != nil || len(data) == 0 {
|
||||
g.Log().Warningf(ctx, "[callModel] 读取临时文件失败,重新调用模型 taskId=%s err=%v", task.TaskID, err)
|
||||
data = nil
|
||||
}
|
||||
}
|
||||
|
||||
// 2) 没有可用数据,调用模型
|
||||
if data == nil {
|
||||
data, err = InvokeModel(ctx, model, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 3) 检测文件类型,保存临时文件
|
||||
_, ext := util.DetectFileType(data)
|
||||
tmpPath, tmpErr := util.SaveTmpResult(task.TaskID, data, ext)
|
||||
if tmpErr == nil && tmpPath != "" {
|
||||
task.TmpFile = tmpPath
|
||||
task.Phase = 1
|
||||
_, err = dao.ModelGatewayTask.Update(ctx, task)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "[执行任务][失败] 临时文件保存失败 taskId=%s err=%v", task.TaskID, tmpErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4) 检测文件类型,提取文本结果
|
||||
contentType, _ := util.DetectFileType(data)
|
||||
var textResult string
|
||||
if utf8.Valid(data) && (strings.HasPrefix(contentType, "text/") || contentType == "application/json") {
|
||||
textResult = string(data)
|
||||
}
|
||||
|
||||
// 5) 非文本内容,返回错误
|
||||
if textResult == "" {
|
||||
return nil, fmt.Errorf("模型返回非文本内容,contentType=%s", contentType)
|
||||
}
|
||||
|
||||
// 6) 解析并返回
|
||||
return gjson.New(textResult).Map(), nil
|
||||
}
|
||||
//// callModel 调用模型 + 提取文本结果
|
||||
//func (w *asyncWorker) callModel(ctx context.Context, task *entity.ModelGatewayTask, model *entity.ModelGatewayModel, body map[string]any) (map[string]any, error) {
|
||||
// data, err := InvokeModel(ctx, model, body)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// contentType, _ := util.DetectFileType(data)
|
||||
// var textResult string
|
||||
// if utf8.Valid(data) && (strings.HasPrefix(contentType, "text/") || contentType == "application/json") {
|
||||
// textResult = string(data)
|
||||
// }
|
||||
//
|
||||
// if textResult == "" {
|
||||
// return nil, fmt.Errorf("模型返回非文本内容,contentType=%s", contentType)
|
||||
// }
|
||||
//
|
||||
// return gjson.New(textResult).Map(), nil
|
||||
//}
|
||||
|
||||
// parseAndRetry 解析模型返回结果,并重试
|
||||
func (w *asyncWorker) parseAndRetry(ctx context.Context, body map[string]any, task *entity.ModelGatewayTask, model *entity.ModelGatewayModel, req *dto.CreateTaskReq, maxRetry int, startTime time.Time) (map[string]any, error) {
|
||||
func (w *asyncWorker) parseAndRetry(ctx context.Context, body map[string]any, model *entity.ModelGatewayModel, task *entity.ModelGatewayTask, maxRetry int) (map[string]any, error) {
|
||||
// 获取构建模型的必填字段
|
||||
user, err := utils.GetUserInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buildModel, err := dao.ModelGatewayModels.Get(ctx, &entity.ModelGatewayModel{
|
||||
SQLBaseDO: beans.SQLBaseDO{TenantId: user.TenantId, Creator: user.UserName},
|
||||
ModelName: task.BuildModelName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for attempt := 0; attempt <= maxRetry; attempt++ {
|
||||
if attempt > 0 {
|
||||
g.Log().Infof(ctx, "[执行任务][重试] JSON解析 第%d/%d次 taskId=%s", attempt, maxRetry, task.TaskID)
|
||||
}
|
||||
|
||||
// 1) 响应映射
|
||||
mapped, err := util.MapResponsePayload(model.ResponseMapping, body)
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "[执行任务][映射失败] taskId=%s attempt=%d/%d err=%v", task.TaskID, attempt, maxRetry, err)
|
||||
if attempt == maxRetry {
|
||||
return nil, fmt.Errorf("响应映射重试耗尽: %w", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 2) 先存 token 到数据库,防止后续失败丢失
|
||||
if _, ok := mapped[model.ResponseTokenField]; ok {
|
||||
task.ExpendTokens = gconv.Int64(mapped[model.ResponseTokenField])
|
||||
_, err = dao.ModelGatewayTask.Update(ctx, &entity.ModelGatewayTask{
|
||||
SQLBaseDO: beans.SQLBaseDO{Id: task.Id},
|
||||
ExpendTokens: task.ExpendTokens,
|
||||
})
|
||||
}
|
||||
|
||||
// 3) 解析 + 校验
|
||||
var parsed map[string]any
|
||||
switch req.BuildType {
|
||||
case public.BuildTypePrompt, public.BuildTypeNode:
|
||||
parsed, err = util.ParseAndValidate(mapped, model)
|
||||
if err == nil {
|
||||
return parsed, nil
|
||||
}
|
||||
case public.BuildTypeStruct:
|
||||
parsed = util.ParseStructResult(mapped, model.ResponseBody)
|
||||
// 解析 + 校验(用构建模型的 RequiredFields)
|
||||
parsed, err := util.ParseAndValidate(body, buildModel.RequiredFields)
|
||||
if err == nil {
|
||||
return parsed, nil
|
||||
default:
|
||||
return mapped, nil
|
||||
}
|
||||
lastErr = err
|
||||
|
||||
g.Log().Warningf(ctx, "[执行任务][解析失败] taskId=%s attempt=%d/%d err=%v", task.TaskID, attempt, maxRetry, err)
|
||||
|
||||
if attempt == maxRetry {
|
||||
return nil, fmt.Errorf("JSON解析重试耗尽: %w", err)
|
||||
return nil, fmt.Errorf("JSON解析重试耗尽: %w", lastErr)
|
||||
}
|
||||
|
||||
// 4) 重新调模型(直接调,不走缓存)
|
||||
// 重试:重新调模型
|
||||
task.RetryCount++
|
||||
_, _ = dao.ModelGatewayTask.Update(ctx, task)
|
||||
rawData, callErr := InvokeModel(ctx, model, task.RequestPayload.Body)
|
||||
|
||||
reqBody := injectErrorMessage(task.RequestPayload.Body, lastErr)
|
||||
rawData, callErr := InvokeModel(ctx, model, reqBody)
|
||||
if callErr != nil {
|
||||
g.Log().Warningf(ctx, "[执行任务][重调模型失败] taskId=%s attempt=%d/%d err=%v", task.TaskID, attempt, maxRetry, callErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// 5) 解析原始响应,覆盖 body 进入下一轮
|
||||
var rawResp map[string]any
|
||||
if err = json.Unmarshal(rawData, &rawResp); err != nil {
|
||||
if err := json.Unmarshal(rawData, &rawResp); err != nil {
|
||||
g.Log().Warningf(ctx, "[执行任务][Unmarshal失败] taskId=%s err=%v", task.TaskID, err)
|
||||
continue
|
||||
}
|
||||
body = rawResp
|
||||
mapped, mapErr := util.MapResponsePayload(model.ResponseMapping, rawResp)
|
||||
if mapErr != nil {
|
||||
g.Log().Warningf(ctx, "[执行任务][映射失败] taskId=%s err=%v", task.TaskID, mapErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// 计费
|
||||
if len(model.BillingConfig) > 0 && len(task.BillingData) > 0 {
|
||||
requestData := task.BillingData[0]
|
||||
retryData := make(map[string]any)
|
||||
for k, v := range requestData {
|
||||
retryData[k] = v
|
||||
}
|
||||
responseData := util.ExtractResponseBilling(model.BillingConfig, mapped)
|
||||
for k, v := range responseData {
|
||||
retryData[k] = v
|
||||
}
|
||||
billingResult := util.CalculateBilling(model.BillingConfig, retryData)
|
||||
if billingResult != nil {
|
||||
task.BillingData = append(task.BillingData, billingResult)
|
||||
totalFee := gconv.Float64(billingResult["total_fee"])
|
||||
if totalFee > 0 {
|
||||
for a := 0; a <= maxRetry; a++ {
|
||||
errr := gateway.DeductBalance(util.AsyncCtx(ctx), task.TenantId, -totalFee)
|
||||
if errr == nil {
|
||||
break
|
||||
}
|
||||
g.Log().Warningf(ctx, "[handleOne] 扣除余额失败 taskId=%s attempt=%d/%d err=%v", task.TaskID, a, maxRetry, errr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task.ExpendTokens += gconv.Int64(mapped[entity.TotalTokens])
|
||||
_, _ = dao.ModelGatewayTask.Update(ctx, &entity.ModelGatewayTask{
|
||||
SQLBaseDO: beans.SQLBaseDO{Id: task.Id},
|
||||
BillingData: task.BillingData,
|
||||
ExpendTokens: task.ExpendTokens,
|
||||
})
|
||||
}
|
||||
|
||||
body = mapped
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// injectErrorMessage 将错误信息插入到最后一个 user 消息之前
|
||||
func injectErrorMessage(payload map[string]any, err error) map[string]any {
|
||||
if err == nil {
|
||||
return payload
|
||||
}
|
||||
|
||||
messages, _ := payload["messages"].([]any)
|
||||
if len(messages) == 0 {
|
||||
return payload
|
||||
}
|
||||
|
||||
errMsg := fmt.Sprintf("【上一轮输出错误,请修正】%s", err.Error())
|
||||
|
||||
// 找到最后一个 user 的位置
|
||||
lastUserIdx := -1
|
||||
for i := len(messages) - 1; i >= 0; i-- {
|
||||
msg, ok := messages[i].(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if gconv.String(msg["role"]) == "user" {
|
||||
lastUserIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if lastUserIdx == -1 {
|
||||
return payload
|
||||
}
|
||||
|
||||
// 在最后一个 user 之前插入错误消息
|
||||
errMsgObj := map[string]any{
|
||||
"role": "user",
|
||||
"content": []map[string]any{{"type": "text", "text": errMsg}},
|
||||
}
|
||||
|
||||
// 切片插入
|
||||
messages = append(messages[:lastUserIdx], append([]any{errMsgObj}, messages[lastUserIdx:]...)...)
|
||||
payload["messages"] = messages
|
||||
return payload
|
||||
}
|
||||
|
||||
// InvokeModel 调用模型服务,返回二进制结果
|
||||
// modelKey 用于覆盖/补充模型配置 head_msg(例如每次请求携带不同的 X-API-Key)
|
||||
func InvokeModel(ctx context.Context, model *entity.ModelGatewayModel, body map[string]any) ([]byte, error) {
|
||||
// 1) 记录模型调用次数
|
||||
_ = dao.ModelGatewayLogsStat.IncRequestCount(ctx, time.Now(), model.TenantId, model.Creator, model.ModelName)
|
||||
|
||||
// 2)请求参数映射:将标准 payload 按模型配置的 requestMapping 转为模型需要的格式
|
||||
//—— 请求映射实际处理为提示词构建请求,因为有附加字段及其他字段的拼接。这里不方便做请求映射
|
||||
//mappedPayload := util.ReverseMap(model.RequestMapping, payload)
|
||||
//surplus, _ := gateway.GetTenantSurplus(ctx, model.TenantId)
|
||||
//if surplus <= 0 {
|
||||
// return nil, fmt.Errorf("租户余额不足")
|
||||
//}
|
||||
|
||||
// 3)构建请求 URL 和超时
|
||||
baseURL := strings.TrimRight(model.BaseURL, "/")
|
||||
@@ -392,13 +440,20 @@ func InvokeModel(ctx context.Context, model *entity.ModelGatewayModel, body map[
|
||||
baseURL = baseURL + "?" + q.Encode()
|
||||
}
|
||||
}
|
||||
req, err = http.NewRequestWithContext(ctx, http.MethodGet, baseURL, nil)
|
||||
// 改用独立超时ctx,隔绝外层截止
|
||||
reqCtx, reqCancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer reqCancel()
|
||||
req, err = http.NewRequestWithContext(reqCtx, http.MethodGet, baseURL, nil)
|
||||
//req, err = http.NewRequestWithContext(ctx, http.MethodGet, baseURL, nil)
|
||||
default:
|
||||
bodyBytes, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err = http.NewRequestWithContext(ctx, http.MethodPost, baseURL, bytes.NewReader(bodyBytes))
|
||||
reqCtx, reqCancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer reqCancel()
|
||||
req, err = http.NewRequestWithContext(reqCtx, http.MethodPost, baseURL, bytes.NewReader(bodyBytes))
|
||||
//req, err = http.NewRequestWithContext(ctx, http.MethodPost, baseURL, bytes.NewReader(bodyBytes))
|
||||
}
|
||||
|
||||
// 5)注入请求头:先模型静态配置,再动态 modelKey(后者可覆盖前者)
|
||||
@@ -430,6 +485,11 @@ func InvokeModel(ctx context.Context, model *entity.ModelGatewayModel, body map[
|
||||
msg := string(b)
|
||||
return nil, fmt.Errorf("模型服务返回非2xx: %d, body=%s", resp.StatusCode, msg)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
//
|
||||
g.Log().Debugf(ctx, "[执行任务][模型调用成功] StatusCode=%v", resp.StatusCode)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
@@ -487,15 +547,13 @@ func InvokeModel(ctx context.Context, model *entity.ModelGatewayModel, body map[
|
||||
// return mappedResponse, nil
|
||||
// }
|
||||
|
||||
// failTask 任务失败统一处理:更新数据库 + 释放排队 + 回调
|
||||
// failTask 任务失败统一处理
|
||||
func (w *asyncWorker) failTask(ctx context.Context, t *entity.ModelGatewayTask, startTime time.Time, errMsg string) {
|
||||
t.State = 3
|
||||
t.ErrorMsg = errMsg
|
||||
t.DurationSeconds = int64(time.Since(startTime).Seconds())
|
||||
_, err := dao.ModelGatewayTask.Update(ctx, t)
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "[执行任务][更新数据库失败] taskId=%s err=%v", t.TaskID, err)
|
||||
}
|
||||
queue.ReleaseQueueSlot(ctx, t.ModelName, t.TaskID)
|
||||
go gateway.TriggerCallback(context.WithoutCancel(ctx), t)
|
||||
concurrencyKey := fmt.Sprintf("model:concurrency:%s", t.ModelName)
|
||||
g.Redis().Decr(ctx, concurrencyKey)
|
||||
_, _ = dao.ModelGatewayTask.Update(ctx, t) // 更新任务状态
|
||||
go gateway.TriggerCallback(util.AsyncCtx(ctx), t) // 触发回调
|
||||
}
|
||||
|
||||
+119
-1
@@ -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';
|
||||
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 '模型管理表';
|
||||
Reference in New Issue
Block a user