ultisuite-backend/internal/ai/quota.go
R3D347HR4Y 3978622050
Some checks are pending
CI / Go tests (push) Waiting to run
CI / Integration tests (push) Waiting to run
CI / DB migrations (push) Waiting to run
refactor(ai): update AI gateway and cost management features
- Refactored AI gateway to utilize new cost management structures for usage tracking.
- Replaced deprecated token extraction methods with a unified cost parsing approach.
- Enhanced usage fallback mechanisms and introduced detailed usage metrics in responses.
- Added new metering functionality to record AI usage and costs effectively.
- Updated tests to reflect changes in usage parsing and cost calculations.
- Introduced new API endpoints for retrieving AI usage summaries and pricing information.
2026-06-16 10:46:33 +02:00

97 lines
2.8 KiB
Go

package ai
import (
"context"
"strings"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/ultisuite/ulti-backend/internal/ai/cost"
"github.com/ultisuite/ulti-backend/internal/llm"
)
// QuotaService wraps cost policy and metering for backward compatibility.
type QuotaService struct {
db *pgxpool.Pool
policy *cost.PolicyService
meter *cost.Meter
}
func NewQuotaService(db *pgxpool.Pool) *QuotaService {
return &QuotaService{
db: db,
policy: cost.NewPolicyService(db),
meter: cost.NewMeter(db),
}
}
func (s *QuotaService) Check(ctx context.Context, externalUserID string) (SpendStatus, error) {
orgBilling, _ := s.usesOrgBilling(ctx, externalUserID)
status, err := s.policy.GetStatus(ctx, externalUserID, orgBilling)
if err != nil {
return SpendStatus{}, err
}
return status, nil
}
func (s *QuotaService) AssertAvailable(ctx context.Context, externalUserID string, provider llm.Provider, useOrgSettings bool) error {
scope := ResolveBillingScope(ctx, s.db, externalUserID, provider, useOrgSettings)
return s.policy.AssertAvailable(ctx, externalUserID, scope)
}
func (s *QuotaService) RecordUsage(ctx context.Context, in cost.RecordInput) error {
return s.meter.RecordUsage(ctx, in)
}
func (s *QuotaService) usesOrgBilling(ctx context.Context, externalUserID string) (bool, error) {
settings, err := LoadEffectiveLLMSettings(ctx, s.db, externalUserID)
if err != nil {
return true, err
}
org, err := loadOrgLLMPolicy(ctx, s.db)
if err != nil {
return true, err
}
if org.EnforceOrgProviders || len(settings.Providers) == 0 {
return true, nil
}
user, err := loadUserLLMSettings(ctx, s.db, externalUserID)
if err != nil {
return true, err
}
return len(user.Providers) == 0, nil
}
// ResolveBillingScope determines whether usage is billed to org or user's own key.
func ResolveBillingScope(ctx context.Context, db *pgxpool.Pool, externalUserID string, provider llm.Provider, useOrgSettings bool) string {
if useOrgSettings {
return cost.BillingScopeOrg
}
org, err := loadOrgLLMPolicy(ctx, db)
if err != nil || org.EnforceOrgProviders {
return cost.BillingScopeOrg
}
user, err := loadUserLLMSettings(ctx, db, externalUserID)
if err != nil || len(user.Providers) == 0 {
return cost.BillingScopeOrg
}
apiKey := strings.TrimSpace(provider.APIKey)
for _, op := range org.Providers {
if op.ID == provider.ID && strings.TrimSpace(op.APIKey) == apiKey && apiKey != "" {
return cost.BillingScopeOrg
}
}
for _, up := range user.Providers {
if up.ID == provider.ID && strings.TrimSpace(up.APIKey) != "" {
return cost.BillingScopeUser
}
}
return cost.BillingScopeOrg
}
// SpendStatus is the user-facing quota/spend response.
type SpendStatus = cost.SpendStatus
// ErrQuotaExceeded aliases cost limit error for backward compatibility.
var ErrQuotaExceeded = cost.ErrCostLimitExceeded