Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
205
backend/internal/redisqueue/plugin.go
Normal file
205
backend/internal/redisqueue/plugin.go
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
package redisqueue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
internallogging "github.com/router-for-me/CLIProxyAPI/v7/internal/logging"
|
||||
coreusage "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/usage"
|
||||
)
|
||||
|
||||
func init() {
|
||||
coreusage.RegisterPlugin(&usageQueuePlugin{})
|
||||
}
|
||||
|
||||
type usageQueuePlugin struct{}
|
||||
|
||||
func (p *usageQueuePlugin) HandleUsage(ctx context.Context, record coreusage.Record) {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
if !Enabled() || !UsageStatisticsEnabled() {
|
||||
return
|
||||
}
|
||||
|
||||
timestamp := record.RequestedAt
|
||||
if timestamp.IsZero() {
|
||||
timestamp = time.Now()
|
||||
}
|
||||
|
||||
modelName := strings.TrimSpace(record.Model)
|
||||
if modelName == "" {
|
||||
modelName = "unknown"
|
||||
}
|
||||
aliasName := strings.TrimSpace(record.Alias)
|
||||
if aliasName == "" {
|
||||
aliasName = modelName
|
||||
}
|
||||
provider := strings.TrimSpace(record.Provider)
|
||||
if provider == "" {
|
||||
provider = "unknown"
|
||||
}
|
||||
executorType := strings.TrimSpace(record.ExecutorType)
|
||||
if executorType == "" {
|
||||
executorType = "unknown"
|
||||
}
|
||||
authType := strings.TrimSpace(record.AuthType)
|
||||
if authType == "" {
|
||||
authType = "unknown"
|
||||
}
|
||||
apiKey := strings.TrimSpace(record.APIKey)
|
||||
requestID := strings.TrimSpace(internallogging.GetRequestID(ctx))
|
||||
reasoningEffort := strings.TrimSpace(record.ReasoningEffort)
|
||||
if reasoningEffort == "" {
|
||||
reasoningEffort = coreusage.ReasoningEffortFromContext(ctx)
|
||||
}
|
||||
serviceTier := strings.TrimSpace(record.ServiceTier)
|
||||
if serviceTier == "" {
|
||||
serviceTier = strings.TrimSpace(record.RequestServiceTier)
|
||||
}
|
||||
if serviceTier == "" {
|
||||
serviceTier = coreusage.ServiceTierFromContext(ctx)
|
||||
}
|
||||
responseServiceTier := strings.TrimSpace(record.ResponseServiceTier)
|
||||
clientRequestMetadata := internallogging.GetClientRequestMetadata(ctx)
|
||||
|
||||
usageDetail := coreusage.EnsureTokenBreakdownForProvider(record.Detail, record.Provider, record.ExecutorType)
|
||||
tokens := tokenStats{
|
||||
InputTokens: usageDetail.InputTokens,
|
||||
OutputTokens: usageDetail.OutputTokens,
|
||||
ReasoningTokens: usageDetail.ReasoningTokens,
|
||||
CachedTokens: usageDetail.CachedTokens,
|
||||
CacheReadTokens: usageDetail.CacheReadTokens,
|
||||
CacheReadTokensPresent: true,
|
||||
CacheCreationTokens: usageDetail.CacheCreationTokens,
|
||||
TotalTokens: usageDetail.TotalTokens,
|
||||
}
|
||||
|
||||
failed := record.Failed
|
||||
if !failed {
|
||||
failed = !resolveSuccess(ctx)
|
||||
}
|
||||
fail := resolveFail(ctx, record, failed)
|
||||
|
||||
detail := requestDetail{
|
||||
Timestamp: timestamp,
|
||||
LatencyMs: record.Latency.Milliseconds(),
|
||||
TTFTMs: record.TTFT.Milliseconds(),
|
||||
Source: record.Source,
|
||||
AuthIndex: record.AuthIndex,
|
||||
AccessTokenHash: record.AccessTokenSHA256,
|
||||
ClientIP: clientRequestMetadata.ClientIP,
|
||||
XForwardedFor: clientRequestMetadata.XForwardedFor,
|
||||
UserAgent: clientRequestMetadata.UserAgent,
|
||||
Tokens: tokens,
|
||||
Failed: failed,
|
||||
Generate: coreusage.GenerateEnabled(record.Generate),
|
||||
Fail: fail,
|
||||
ResponseHeaders: record.ResponseHeaders,
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(queuedUsageDetail{
|
||||
requestDetail: detail,
|
||||
AccountingVersion: coreusage.TokenAccountingSchemaVersion,
|
||||
TokenBreakdown: usageDetail.TokenBreakdown,
|
||||
Provider: provider,
|
||||
ExecutorType: executorType,
|
||||
Model: modelName,
|
||||
Alias: aliasName,
|
||||
Endpoint: resolveEndpoint(ctx),
|
||||
AuthType: authType,
|
||||
APIKey: apiKey,
|
||||
RequestID: requestID,
|
||||
ReasoningEffort: reasoningEffort,
|
||||
ServiceTier: serviceTier,
|
||||
ResponseServiceTier: responseServiceTier,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
Enqueue(payload)
|
||||
}
|
||||
|
||||
type queuedUsageDetail struct {
|
||||
requestDetail
|
||||
AccountingVersion int `json:"accounting_version"`
|
||||
TokenBreakdown coreusage.TokenBreakdown `json:"token_breakdown"`
|
||||
Provider string `json:"provider"`
|
||||
ExecutorType string `json:"executor_type"`
|
||||
Model string `json:"model"`
|
||||
Alias string `json:"alias"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
AuthType string `json:"auth_type"`
|
||||
APIKey string `json:"api_key"`
|
||||
RequestID string `json:"request_id"`
|
||||
ReasoningEffort string `json:"reasoning_effort"`
|
||||
ServiceTier string `json:"service_tier"`
|
||||
ResponseServiceTier string `json:"response_service_tier,omitempty"`
|
||||
}
|
||||
|
||||
type requestDetail struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
LatencyMs int64 `json:"latency_ms"`
|
||||
TTFTMs int64 `json:"ttft_ms"`
|
||||
Source string `json:"source"`
|
||||
AuthIndex string `json:"auth_index"`
|
||||
AccessTokenHash string `json:"access_token_sha256,omitempty"`
|
||||
ClientIP string `json:"client_ip"`
|
||||
XForwardedFor string `json:"x_forwarded_for"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
Tokens tokenStats `json:"tokens"`
|
||||
Failed bool `json:"failed"`
|
||||
Generate bool `json:"generate"`
|
||||
Fail failDetail `json:"fail"`
|
||||
ResponseHeaders http.Header `json:"response_headers,omitempty"`
|
||||
}
|
||||
|
||||
type tokenStats struct {
|
||||
InputTokens int64 `json:"input_tokens"`
|
||||
OutputTokens int64 `json:"output_tokens"`
|
||||
ReasoningTokens int64 `json:"reasoning_tokens"`
|
||||
CachedTokens int64 `json:"cached_tokens"`
|
||||
CacheReadTokens int64 `json:"cache_read_tokens"`
|
||||
CacheReadTokensPresent bool `json:"cache_read_tokens_present"`
|
||||
CacheCreationTokens int64 `json:"cache_creation_tokens"`
|
||||
TotalTokens int64 `json:"total_tokens"`
|
||||
}
|
||||
|
||||
type failDetail struct {
|
||||
StatusCode int `json:"status_code"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
func resolveFail(ctx context.Context, record coreusage.Record, failed bool) failDetail {
|
||||
fail := failDetail{
|
||||
StatusCode: record.Fail.StatusCode,
|
||||
Body: strings.TrimSpace(record.Fail.Body),
|
||||
}
|
||||
if !failed {
|
||||
return failDetail{StatusCode: 200}
|
||||
}
|
||||
if fail.StatusCode <= 0 {
|
||||
fail.StatusCode = internallogging.GetResponseStatus(ctx)
|
||||
}
|
||||
if fail.StatusCode <= 0 {
|
||||
fail.StatusCode = 500
|
||||
}
|
||||
return fail
|
||||
}
|
||||
|
||||
func resolveSuccess(ctx context.Context) bool {
|
||||
status := internallogging.GetResponseStatus(ctx)
|
||||
if status == 0 {
|
||||
return true
|
||||
}
|
||||
return status < httpStatusBadRequest
|
||||
}
|
||||
|
||||
func resolveEndpoint(ctx context.Context) string {
|
||||
return strings.TrimSpace(internallogging.GetEndpoint(ctx))
|
||||
}
|
||||
|
||||
const httpStatusBadRequest = 400
|
||||
Loading…
Reference in a new issue