feat: publish sanitized custom.97 source

This commit is contained in:
cneicy committed 2026-10-27 20:43:39 +08:00
1 parent 8e6ed0317b
commit 86c0276156
1022 files changed
+69903 -19207

No files matched your search

+70 -19
View File
@@ -13,7 +13,9 @@ import (
"github.com/QuantumNous/new-api/logger"
"github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/pkg/billingexpr"
"github.com/QuantumNous/new-api/pkg/jsplugin"
relaycommon "github.com/QuantumNous/new-api/relay/common"
"github.com/QuantumNous/new-api/relaykit/dto"
"github.com/QuantumNous/new-api/setting/ratio_setting"
"github.com/QuantumNous/new-api/types"
"github.com/gin-gonic/gin"
@@ -23,10 +25,10 @@ import (
// 实际扣费已由 BillingSession(PreConsumeBilling + SettleBilling)完成。
func LogTaskConsumption(c *gin.Context, info *relaycommon.RelayInfo, task *model.Task) {
tokenName := c.GetString("token_name")
logContent := fmt.Sprintf("操作 %s", info.Action)
logContent := []*common.Message{common.NewMessage("Action {{action}}", map[string]any{"action": info.Action})}
// 支持任务仅按次计费
if common.StringsContains(constant.TaskPricePatches, info.OriginModelName) {
logContent = fmt.Sprintf("%s,按次计费", logContent)
logContent = append(logContent, common.NewMessage("Billed per request"))
} else {
var contents []string
if otherRatios := info.PriceData.OtherRatios(); len(otherRatios) > 0 {
@@ -42,12 +44,15 @@ func LogTaskConsumption(c *gin.Context, info *relaycommon.RelayInfo, task *model
}
}
if len(contents) > 0 {
logContent = fmt.Sprintf("%s, 计算参数:%s", logContent, strings.Join(contents, ", "))
logContent = append(logContent, common.NewMessage("Billing parameters: {{params}}", map[string]any{"params": strings.Join(contents, ", ")}))
}
}
other := model.NewLogOther()
other.SetPublic("is_task", true)
other.SetPublic("request_path", c.Request.URL.Path)
if taskDeliveredInline(c, task) {
other.SetPublic("task_sync", true)
}
other.SetPublic("model_price", info.PriceData.ModelPrice)
if info.PriceData.ModelRatio > 0 {
other.SetPublic("model_ratio", info.PriceData.ModelRatio)
@@ -67,6 +72,9 @@ func LogTaskConsumption(c *gin.Context, info *relaycommon.RelayInfo, task *model
if len(snap.UsageFacts) > 0 {
other.SetPublic("usage_facts", snap.UsageFacts)
}
setTaskImageCount(other, snap.UsageFacts["image_count"])
} else {
setTaskImageCount(other, info.PriceData.OtherRatios()["image_count"])
}
appendTaskLogInfo(task, other)
attachQuotaSaturation(c, info, other)
@@ -93,7 +101,7 @@ func LogTaskConsumption(c *gin.Context, info *relaycommon.RelayInfo, task *model
func resolveTokenKey(ctx context.Context, tokenId int, taskID string) string {
token, err := model.GetTokenById(tokenId)
if err != nil {
logger.LogWarn(ctx, fmt.Sprintf("获取令牌 key 失败 (tokenId=%d, task=%s): %s", tokenId, taskID, err.Error()))
logger.LogWarn(ctx, common.LogText("failed to get token key (tokenId=%d, task=%s): %s", tokenId, taskID, err.Error()))
return ""
}
return token.Key
@@ -132,13 +140,15 @@ func taskAdjustTokenQuota(ctx context.Context, task *model.Task, delta int) {
err = model.IncreaseTokenQuota(task.PrivateData.TokenId, tokenKey, -delta)
}
if err != nil {
logger.LogWarn(ctx, fmt.Sprintf("调整令牌额度失败 (delta=%d, task=%s): %s", delta, task.TaskID, err.Error()))
logger.LogWarn(ctx, common.LogText("failed to adjust token quota (delta=%d, task=%s): %s", delta, task.TaskID, err.Error()))
}
}
// taskBillingOther 从 task 的 BillingContext 构建日志 Other 字段。
func taskBillingOther(task *model.Task) *model.LogOther {
other := model.NewLogOther()
// 前端列表按 is_task 选择任务档位渲染分支,缺失会把差额/退款日志误判成普通 relay 动态计费
other.SetPublic("is_task", true)
if bc := task.PrivateData.BillingContext; bc != nil {
other.SetPublic("model_price", bc.ModelPrice)
if bc.ModelRatio > 0 {
@@ -148,7 +158,7 @@ func taskBillingOther(task *model.Task) *model.LogOther {
if priceData := taskBillingContextPriceData(bc); priceData != nil {
for k, v := range priceData.OtherRatios() {
if !other.SetPublic(k, v) {
common.SysError("task billing other ratio key rejected: " + k)
common.SysError(common.LogText("task billing other ratio key rejected: %s", k))
}
}
}
@@ -159,6 +169,9 @@ func taskBillingOther(task *model.Task) *model.LogOther {
if len(snap.UsageFacts) > 0 {
other.SetPublic("usage_facts", snap.UsageFacts)
}
setTaskImageCount(other, snap.UsageFacts["image_count"])
} else if priceData := taskBillingContextPriceData(bc); priceData != nil {
setTaskImageCount(other, priceData.OtherRatios()["image_count"])
}
}
props := task.Properties
@@ -170,6 +183,35 @@ func taskBillingOther(task *model.Task) *model.LogOther {
return other
}
// setTaskImageCount publishes the billed image quantity of an image task as
// other.image_count, the same field the HTTP image relay writes, so the log
// detail shows one "billable image count" regardless of the serving path. The
// value is a host-validated count fact (at most dto.MaxImageN), never a quota.
func setTaskImageCount(other *model.LogOther, value any) {
count, ok := value.(float64)
if !ok || count < 0 || count > float64(dto.MaxImageN) {
return
}
other.SetPublic("image_count", common.QuotaRound(count))
}
// taskDeliveredInline reports whether the submitting HTTP request itself
// returns the task deliverable: the upstream completed immediately, or the
// request came through the synchronous OpenAI Images protocol, where the host
// waits for an asynchronous upstream task before answering. The usage log
// presents such requests as synchronous instead of as asynchronous jobs.
func taskDeliveredInline(c *gin.Context, task *model.Task) bool {
if task != nil && (task.Status == model.TaskStatusSuccess || task.Status == model.TaskStatusFailure) {
return true
}
pinnedValue, exists := c.Get(jsplugin.ContextKeyPinnedEndpoint)
if !exists {
return false
}
pinned, ok := pinnedValue.(jsplugin.PinnedEndpoint)
return ok && pinned.Protocol == jsplugin.ProtocolOpenAIImage
}
func appendTaskLogInfo(task *model.Task, other *model.LogOther) {
if task == nil || other == nil {
return
@@ -177,6 +219,11 @@ func appendTaskLogInfo(task *model.Task, other *model.LogOther) {
if task.TaskID != "" {
other.SetPublic("task_id", task.TaskID)
}
if task.PrivateData.ResultDiscarded {
// The result was delivered inline and the upstream snapshot was not
// persisted, so no artifact can be retrieved for this task.
other.SetPublic("result_discarded", true)
}
if task.PrivateData.Execution != nil {
AppendTaskPluginAuditInfo(other, task.PrivateData.Execution.TaskPlugin)
}
@@ -221,7 +268,7 @@ func RefundTaskQuota(ctx context.Context, task *model.Task, reason string) bool
// 1. 退还资金来源(钱包或订阅)
if err := taskAdjustFunding(task, -quota); err != nil {
logger.LogWarn(ctx, fmt.Sprintf("退还资金来源失败 task %s: %s", task.TaskID, err.Error()))
logger.LogWarn(ctx, common.LogText("failed to refund the funding source, task %s: %s", task.TaskID, err.Error()))
return false
}
@@ -239,7 +286,6 @@ func RefundTaskQuota(ctx context.Context, task *model.Task, reason string) bool
model.RecordTaskBillingLog(model.RecordTaskBillingLogParams{
UserId: task.UserId,
LogType: model.LogTypeRefund,
Content: "",
ChannelId: task.ChannelId,
ModelName: taskModelName(task),
Quota: quota,
@@ -252,16 +298,16 @@ func RefundTaskQuota(ctx context.Context, task *model.Task, reason string) bool
// 回写失败必须显式告警,避免漏掉潜在的重复退款风险。
task.Quota = 0
if err := task.UpdateQuota(); err != nil {
logger.LogError(ctx, fmt.Sprintf("退款成功但清除 task quota 失败 task %s: %s", task.TaskID, err.Error()))
logger.LogError(ctx, common.LogText("refund succeeded but clearing the task quota failed, task %s: %s", task.TaskID, err.Error()))
}
return true
}
// RecalculateTaskQuota 通用的异步差额结算。
// actualQuota 是任务完成后的实际应扣额度,与预扣额度 (task.Quota) 做差额结算。
// reason 用于日志记录(例如 "token重算" 或 "adaptor调整")。
// reason 写入日志内容(例如按 token 重算或 adaptor 调整)。
// clamps 可选:若计算 actualQuota 时发生额度饱和,将其记入日志 admin_info(仅管理员可见)。
func RecalculateTaskQuota(ctx context.Context, task *model.Task, actualQuota int, reason string, clamps ...*common.QuotaClamp) {
func RecalculateTaskQuota(ctx context.Context, task *model.Task, actualQuota int, reason *common.Message, clamps ...*common.QuotaClamp) {
if actualQuota < 0 {
return
}
@@ -269,22 +315,22 @@ func RecalculateTaskQuota(ctx context.Context, task *model.Task, actualQuota int
quotaDelta := actualQuota - preConsumedQuota
if quotaDelta == 0 {
logger.LogInfo(ctx, fmt.Sprintf("任务 %s 预扣费准确(%s,%s)",
task.TaskID, logger.LogQuota(actualQuota), reason))
logger.LogInfo(ctx, common.LogText("task %s pre-consumed quota matches the actual quota (%s, %s)",
task.TaskID, logger.LogQuota(actualQuota), reason.Error()))
return
}
logger.LogInfo(ctx, fmt.Sprintf("任务 %s 差额结算:delta=%s(实际:%s,预扣:%s,%s)",
logger.LogInfo(ctx, common.LogText("task %s settles the difference: delta=%s (actual %s, pre-consumed %s, %s)",
task.TaskID,
logger.LogQuota(quotaDelta),
logger.LogQuota(actualQuota),
logger.LogQuota(preConsumedQuota),
reason,
reason.Error(),
))
// 调整资金来源
if err := taskAdjustFunding(task, quotaDelta); err != nil {
logger.LogError(ctx, fmt.Sprintf("差额结算资金调整失败 task %s: %s", task.TaskID, err.Error()))
logger.LogError(ctx, common.LogText("failed to adjust funding for the difference, task %s: %s", task.TaskID, err.Error()))
return
}
@@ -293,7 +339,7 @@ func RecalculateTaskQuota(ctx context.Context, task *model.Task, actualQuota int
task.Quota = actualQuota
if err := task.UpdateQuota(); err != nil {
logger.LogError(ctx, fmt.Sprintf("差额结算回写 quota 失败 task %s: %s", task.TaskID, err.Error()))
logger.LogError(ctx, common.LogText("failed to write back the settled task quota, task %s: %s", task.TaskID, err.Error()))
}
// 提交阶段已经累计过一次请求;结算阶段只调整最终用量。
@@ -319,7 +365,7 @@ func RecalculateTaskQuota(ctx context.Context, task *model.Task, actualQuota int
model.RecordTaskBillingLog(model.RecordTaskBillingLogParams{
UserId: task.UserId,
LogType: logType,
Content: reason,
Content: []*common.Message{reason},
ChannelId: task.ChannelId,
ModelName: taskModelName(task),
Quota: logQuota,
@@ -383,7 +429,12 @@ func RecalculateTaskQuotaByTokens(ctx context.Context, task *model.Task, totalTo
// 计算实际应扣费额度: totalTokens * modelRatio * groupRatio * otherMultiplier(饱和转换,防止溢出成负数)
actualQuota, clamp := common.QuotaFromFloatChecked(float64(totalTokens) * modelRatio * finalGroupRatio * otherMultiplier)
reason := fmt.Sprintf("token重算:tokens=%d, modelRatio=%.2f, groupRatio=%.2f, otherMultiplier=%.4f", totalTokens, modelRatio, finalGroupRatio, otherMultiplier)
reason := common.NewMessage("Recalculated from tokens: tokens {{tokens}}, model ratio {{model_ratio}}, group ratio {{group_ratio}}, other multiplier {{other_multiplier}}", map[string]any{
"tokens": totalTokens,
"model_ratio": fmt.Sprintf("%.2f", modelRatio),
"group_ratio": fmt.Sprintf("%.2f", finalGroupRatio),
"other_multiplier": fmt.Sprintf("%.4f", otherMultiplier),
})
RecalculateTaskQuota(ctx, task, actualQuota, reason, clamp)
return true
}