feat(home): implement count for home auth dispatch requests and enable usage statistics
- Added `count` attribute to `homeAuthCount` requests to improve home message batching. - Enabled usage statistics for home mode by default and added config-level enforcement. - Adjusted failure logging to include detailed metadata in `UsageReporter`. - Updated multiple executors to pass error details to `PublishFailure` for better debugging. - Enhanced unit tests to validate `count` behavior and usage statistics enforcement across components.
This commit is contained in:
@@ -66,6 +66,7 @@ func (p *usageQueuePlugin) HandleUsage(ctx context.Context, record coreusage.Rec
|
||||
if !failed {
|
||||
failed = !resolveSuccess(ctx)
|
||||
}
|
||||
fail := resolveFail(ctx, record, failed)
|
||||
|
||||
detail := requestDetail{
|
||||
Timestamp: timestamp,
|
||||
@@ -74,6 +75,7 @@ func (p *usageQueuePlugin) HandleUsage(ctx context.Context, record coreusage.Rec
|
||||
AuthIndex: record.AuthIndex,
|
||||
Tokens: tokens,
|
||||
Failed: failed,
|
||||
Fail: fail,
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(queuedUsageDetail{
|
||||
@@ -110,6 +112,7 @@ type requestDetail struct {
|
||||
AuthIndex string `json:"auth_index"`
|
||||
Tokens tokenStats `json:"tokens"`
|
||||
Failed bool `json:"failed"`
|
||||
Fail failDetail `json:"fail"`
|
||||
}
|
||||
|
||||
type tokenStats struct {
|
||||
@@ -120,6 +123,28 @@ type tokenStats struct {
|
||||
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 {
|
||||
|
||||
@@ -44,9 +44,10 @@ func TestUsageQueuePluginPayloadIncludesStableFieldsAndSuccess(t *testing.T) {
|
||||
requireStringField(t, payload, "alias", "client-gpt")
|
||||
requireStringField(t, payload, "endpoint", "POST /v1/chat/completions")
|
||||
requireStringField(t, payload, "auth_type", "apikey")
|
||||
requireStringField(t, payload, "user_api_key", "test-key")
|
||||
requireMissingField(t, payload, "user_api_key")
|
||||
requireStringField(t, payload, "request_id", "ctx-request-id")
|
||||
requireBoolField(t, payload, "failed", false)
|
||||
requireFailField(t, payload, http.StatusOK, "")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -68,6 +69,10 @@ func TestUsageQueuePluginPayloadIncludesStableFieldsAndFailureAndGinRequestID(t
|
||||
Source: "user@example.com",
|
||||
RequestedAt: time.Date(2026, 4, 25, 0, 0, 0, 0, time.UTC),
|
||||
Latency: 2500 * time.Millisecond,
|
||||
Fail: coreusage.Failure{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
Body: "upstream failed",
|
||||
},
|
||||
Detail: coreusage.Detail{
|
||||
InputTokens: 10,
|
||||
OutputTokens: 20,
|
||||
@@ -81,9 +86,10 @@ func TestUsageQueuePluginPayloadIncludesStableFieldsAndFailureAndGinRequestID(t
|
||||
requireStringField(t, payload, "alias", "client-mini")
|
||||
requireStringField(t, payload, "endpoint", "GET /v1/responses")
|
||||
requireStringField(t, payload, "auth_type", "apikey")
|
||||
requireStringField(t, payload, "user_api_key", "test-key")
|
||||
requireMissingField(t, payload, "user_api_key")
|
||||
requireStringField(t, payload, "request_id", "gin-request-id")
|
||||
requireBoolField(t, payload, "failed", true)
|
||||
requireFailField(t, payload, http.StatusInternalServerError, "upstream failed")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -115,6 +121,10 @@ func TestUsageQueuePluginAsyncIgnoresRecycledGinContext(t *testing.T) {
|
||||
Source: "user@example.com",
|
||||
RequestedAt: time.Date(2026, 4, 25, 0, 0, 0, 0, time.UTC),
|
||||
Latency: 1500 * time.Millisecond,
|
||||
Fail: coreusage.Failure{
|
||||
StatusCode: http.StatusBadGateway,
|
||||
Body: "bad gateway",
|
||||
},
|
||||
Detail: coreusage.Detail{
|
||||
InputTokens: 10,
|
||||
OutputTokens: 20,
|
||||
@@ -125,9 +135,10 @@ func TestUsageQueuePluginAsyncIgnoresRecycledGinContext(t *testing.T) {
|
||||
payload := waitForSinglePayload(t, 2*time.Second)
|
||||
requireStringField(t, payload, "endpoint", "POST /v1/chat/completions")
|
||||
requireStringField(t, payload, "alias", "client-gpt")
|
||||
requireStringField(t, payload, "user_api_key", "test-key")
|
||||
requireMissingField(t, payload, "user_api_key")
|
||||
requireStringField(t, payload, "request_id", "ctx-request-id")
|
||||
requireBoolField(t, payload, "failed", true)
|
||||
requireFailField(t, payload, http.StatusBadGateway, "bad gateway")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -217,6 +228,14 @@ func requireStringField(t *testing.T, payload map[string]json.RawMessage, key, w
|
||||
}
|
||||
}
|
||||
|
||||
func requireMissingField(t *testing.T, payload map[string]json.RawMessage, key string) {
|
||||
t.Helper()
|
||||
|
||||
if _, ok := payload[key]; ok {
|
||||
t.Fatalf("payload unexpectedly contains %q", key)
|
||||
}
|
||||
}
|
||||
|
||||
type pluginFunc func(context.Context, coreusage.Record)
|
||||
|
||||
func (fn pluginFunc) HandleUsage(ctx context.Context, record coreusage.Record) {
|
||||
@@ -238,3 +257,22 @@ func requireBoolField(t *testing.T, payload map[string]json.RawMessage, key stri
|
||||
t.Fatalf("%s = %t, want %t", key, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func requireFailField(t *testing.T, payload map[string]json.RawMessage, wantStatus int, wantBody string) {
|
||||
t.Helper()
|
||||
|
||||
raw, ok := payload["fail"]
|
||||
if !ok {
|
||||
t.Fatalf("payload missing %q", "fail")
|
||||
}
|
||||
var got struct {
|
||||
StatusCode int `json:"status_code"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &got); err != nil {
|
||||
t.Fatalf("unmarshal fail: %v", err)
|
||||
}
|
||||
if got.StatusCode != wantStatus || got.Body != wantBody {
|
||||
t.Fatalf("fail = {status_code:%d body:%q}, want {status_code:%d body:%q}", got.StatusCode, got.Body, wantStatus, wantBody)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user