feat: add wallet settlement audit flow
This commit is contained in:
@@ -21,6 +21,13 @@ func (s *Service) Estimate(ctx context.Context, kind string, model string, body
|
||||
return EstimateResult{}, err
|
||||
}
|
||||
candidate := candidates[0]
|
||||
return EstimateResult{
|
||||
Items: s.estimatedBillings(ctx, user, kind, body, candidate),
|
||||
Resolver: "effective-pricing-v1",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) estimatedBillings(ctx context.Context, user *auth.User, kind string, body map[string]any, candidate store.RuntimeModelCandidate) []any {
|
||||
usage := clients.Usage{InputTokens: estimateRequestTokens(body), OutputTokens: int(floatFromAny(body["max_tokens"]))}
|
||||
if usage.OutputTokens == 0 {
|
||||
usage.OutputTokens = 64
|
||||
@@ -31,10 +38,7 @@ func (s *Service) Estimate(ctx context.Context, kind string, model string, body
|
||||
"completion_tokens": usage.OutputTokens,
|
||||
"total_tokens": usage.TotalTokens,
|
||||
}}}
|
||||
return EstimateResult{
|
||||
Items: s.billings(ctx, user, kind, body, candidate, response, true),
|
||||
Resolver: "effective-pricing-v1",
|
||||
}, nil
|
||||
return s.billings(ctx, user, kind, body, candidate, response, true)
|
||||
}
|
||||
|
||||
func (s *Service) billings(ctx context.Context, user *auth.User, kind string, body map[string]any, candidate store.RuntimeModelCandidate, response clients.Response, simulated bool) []any {
|
||||
|
||||
@@ -67,6 +67,19 @@ func (s *Service) execute(ctx context.Context, task store.GatewayTask, user *aut
|
||||
}
|
||||
return Result{Task: failed, Output: failed.Result}, err
|
||||
}
|
||||
if len(candidates) > 0 {
|
||||
estimatedBillings := s.estimatedBillings(ctx, user, task.Kind, body, candidates[0])
|
||||
if err := s.ensureWalletBalance(ctx, user, estimatedBillings); err != nil {
|
||||
if errors.Is(err, store.ErrInsufficientWalletBalance) {
|
||||
failed, finishErr := s.failTask(ctx, task.ID, "insufficient_balance", err.Error(), task.RunMode == "simulation", err)
|
||||
if finishErr != nil {
|
||||
return Result{}, finishErr
|
||||
}
|
||||
return Result{Task: failed, Output: failed.Result}, err
|
||||
}
|
||||
return Result{}, err
|
||||
}
|
||||
}
|
||||
if err := s.store.MarkTaskRunning(ctx, task.ID, modelType, body); err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/auth"
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
|
||||
)
|
||||
|
||||
func (s *Service) ensureWalletBalance(ctx context.Context, user *auth.User, billings []any) error {
|
||||
amounts := map[string]float64{}
|
||||
for _, raw := range billings {
|
||||
line, _ := raw.(map[string]any)
|
||||
if line == nil {
|
||||
continue
|
||||
}
|
||||
currency := strings.TrimSpace(stringFromAny(line["currency"]))
|
||||
if currency == "" {
|
||||
currency = "resource"
|
||||
}
|
||||
amounts[currency] = roundPrice(amounts[currency] + floatFromAny(line["amount"]))
|
||||
}
|
||||
for currency, amount := range amounts {
|
||||
if amount <= 0 {
|
||||
continue
|
||||
}
|
||||
availability, err := s.store.WalletAvailability(ctx, user, currency, amount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !availability.Enough {
|
||||
return fmt.Errorf("%w: required %.6f %s, available %.6f", store.ErrInsufficientWalletBalance, amount, currency, availability.AvailableAmount)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user