diff --git a/plugins/wasm-go/extensions/ai-statistics/main.go b/plugins/wasm-go/extensions/ai-statistics/main.go index 57fa36563..349cfdd10 100644 --- a/plugins/wasm-go/extensions/ai-statistics/main.go +++ b/plugins/wasm-go/extensions/ai-statistics/main.go @@ -105,6 +105,7 @@ const ( BuiltinAnswerKey = "answer" BuiltinToolCallsKey = "tool_calls" BuiltinReasoningKey = "reasoning" + BuiltinSystemKey = "system" BuiltinReasoningTokens = "reasoning_tokens" BuiltinCachedTokens = "cached_tokens" BuiltinInputTokenDetails = "input_token_details" @@ -115,6 +116,9 @@ const ( QuestionPathOpenAI = "messages.@reverse.0.content" QuestionPathClaude = "messages.@reverse.0.content" // Claude uses same format + // System prompt paths (from request body) + SystemPathClaude = "system" // Claude /v1/messages has system as a top-level field + // Answer paths (from response body - non-streaming) AnswerPathOpenAINonStreaming = "choices.0.message.content" AnswerPathClaudeNonStreaming = "content.0.text" @@ -150,6 +154,10 @@ func getDefaultAttributes() []Attribute { Key: BuiltinQuestionKey, ApplyToLog: true, }, + { + Key: BuiltinSystemKey, + ApplyToLog: true, + }, { Key: BuiltinAnswerKey, ApplyToLog: true, @@ -871,7 +879,7 @@ func setAttributeBySource(ctx wrapper.HttpContext, config AIStatisticsConfig, so // isBuiltinAttribute checks if the given key is a built-in attribute func isBuiltinAttribute(key string) bool { - return key == BuiltinQuestionKey || key == BuiltinAnswerKey || key == BuiltinToolCallsKey || key == BuiltinReasoningKey || + return key == BuiltinQuestionKey || key == BuiltinAnswerKey || key == BuiltinToolCallsKey || key == BuiltinReasoningKey || key == BuiltinSystemKey || key == BuiltinReasoningTokens || key == BuiltinCachedTokens || key == BuiltinInputTokenDetails || key == BuiltinOutputTokenDetails } @@ -880,7 +888,7 @@ func isBuiltinAttribute(key string) bool { // Returns nil if the key is not a built-in attribute func getBuiltinAttributeDefaultSources(key string) []string { switch key { - case BuiltinQuestionKey: + case BuiltinQuestionKey, BuiltinSystemKey: return []string{RequestBody} case BuiltinAnswerKey, BuiltinToolCallsKey, BuiltinReasoningKey: return []string{ResponseStreamingBody, ResponseBody} @@ -918,6 +926,13 @@ func getBuiltinAttributeFallback(ctx wrapper.HttpContext, config AIStatisticsCon return value } } + case BuiltinSystemKey: + if source == RequestBody { + // Try Claude /v1/messages format (system is a top-level field) + if value := gjson.GetBytes(body, SystemPathClaude).Value(); value != nil && value != "" { + return value + } + } case BuiltinAnswerKey: if source == ResponseStreamingBody { // Try OpenAI format first @@ -1069,6 +1084,9 @@ func debugLogAiLog(ctx wrapper.HttpContext) { if question := ctx.GetUserAttribute("question"); question != nil { userAttrs["question"] = question } + if system := ctx.GetUserAttribute("system"); system != nil { + userAttrs["system"] = system + } if answer := ctx.GetUserAttribute("answer"); answer != nil { userAttrs["answer"] = answer }