diff --git a/internal/sdk/ga4.go b/internal/sdk/ga4.go index c7f72a89..6a2995dd 100644 --- a/internal/sdk/ga4.go +++ b/internal/sdk/ga4.go @@ -206,6 +206,6 @@ func SendGA4Event(name string, params map[string]interface{}) { } err := ga4Client.SendEvent(event) if err != nil { - log.Error().Err(err).Msg("send GA4 event failed") + log.Warn().Err(err).Msg("send GA4 event failed") } } diff --git a/internal/version/VERSION b/internal/version/VERSION index f82d6beb..e3cc677a 100644 --- a/internal/version/VERSION +++ b/internal/version/VERSION @@ -1 +1 @@ -v5.0.0-beta-2506181659 +v5.0.0-beta-2506181717 diff --git a/report.go b/report.go index b2aa9b1c..0a42c048 100644 --- a/report.go +++ b/report.go @@ -14,6 +14,7 @@ import ( "github.com/httprunner/httprunner/v5/internal/builtin" "github.com/httprunner/httprunner/v5/uixt" + "github.com/httprunner/httprunner/v5/uixt/ai" "github.com/pkg/errors" "github.com/rs/zerolog/log" ) @@ -262,11 +263,11 @@ func (g *HTMLReportGenerator) getStepLogs(stepName string, startTime int64, elap } // Track AI conversation patterns - if strings.Contains(logEntry.Message, "log request messages") { + if strings.Contains(logEntry.Message, ai.LOG_REQUEST_MESSAGES) { if logTime >= actualStartTime && logTime <= actualEndTime+30000 { // 30s buffer for AI requests inAIConversation = true } - } else if strings.Contains(logEntry.Message, "log response message") && inAIConversation { + } else if strings.Contains(logEntry.Message, ai.LOG_RESPONSE_MESSAGE) && inAIConversation { // Extend end time to include complete AI conversation, but respect next step boundary extendedEndTime := logTime if nextStepStartTime > 0 && extendedEndTime >= nextStepStartTime { diff --git a/uixt/ai/session.go b/uixt/ai/session.go index d5707fe3..ced09e09 100644 --- a/uixt/ai/session.go +++ b/uixt/ai/session.go @@ -73,6 +73,11 @@ func (h *ConversationHistory) Clear() { log.Warn().Msg("conversation history cleared completely") } +const ( + LOG_REQUEST_MESSAGES = "log request messages" + LOG_RESPONSE_MESSAGE = "log response message" +) + func logRequest(messages ConversationHistory) { msgs := make(ConversationHistory, 0, len(messages)) for _, message := range messages { @@ -99,7 +104,7 @@ func logRequest(messages ConversationHistory) { } msgs = append(msgs, msg) } - log.Debug().Interface("messages", msgs).Msg("log request messages") + log.Debug().Interface("messages", msgs).Msg(LOG_REQUEST_MESSAGES) } func logResponse(message *schema.Message) { @@ -126,5 +131,5 @@ func logResponse(message *schema.Message) { if message.Extra != nil { logger = logger.Interface("extra", message.Extra) } - logger.Msg("log response message") + logger.Msg(LOG_RESPONSE_MESSAGE) } diff --git a/uixt/ai/utils.go b/uixt/ai/utils.go index ddc0b014..1a6b1748 100644 --- a/uixt/ai/utils.go +++ b/uixt/ai/utils.go @@ -24,6 +24,45 @@ type PlanningJSONResponse struct { Error string `json:"error"` } +// parseStructuredResponse parses model response into structured format with error recovery +func parseStructuredResponse(content string, result interface{}) error { + // Clean and validate UTF-8 content first + cleanContent := sanitizeUTF8Content(content) + + // Extract JSON content from response + jsonContent := extractJSONFromContent(cleanContent) + if jsonContent == "" { + // If JSON extraction failed, try parsing the content directly as a fallback + jsonContent = cleanContent + } + + // Parse JSON response with error recovery + return parseJSONWithFallback(jsonContent, result) +} + +// sanitizeUTF8Content cleans invalid UTF-8 characters from content +func sanitizeUTF8Content(content string) string { + if utf8.ValidString(content) { + return content + } + + // Convert to bytes and filter out invalid UTF-8 sequences + bytes := []byte(content) + var validBytes []byte + + for len(bytes) > 0 { + r, size := utf8.DecodeRune(bytes) + if r != utf8.RuneError { + // Valid rune, keep it + validBytes = append(validBytes, bytes[:size]...) + } + // Skip invalid bytes (including RuneError) + bytes = bytes[size:] + } + + return string(validBytes) +} + // extractJSONFromContent extracts JSON content from various formats in the response // This function handles multiple formats: // 1. ```json ... ``` markdown code blocks @@ -121,29 +160,6 @@ func extractJSONFromContent(content string) string { return "" } -// sanitizeUTF8Content cleans invalid UTF-8 characters from content -func sanitizeUTF8Content(content string) string { - if utf8.ValidString(content) { - return content - } - - // Convert to bytes and filter out invalid UTF-8 sequences - bytes := []byte(content) - var validBytes []byte - - for len(bytes) > 0 { - r, size := utf8.DecodeRune(bytes) - if r != utf8.RuneError { - // Valid rune, keep it - validBytes = append(validBytes, bytes[:size]...) - } - // Skip invalid bytes (including RuneError) - bytes = bytes[size:] - } - - return string(validBytes) -} - // parseJSONWithFallback attempts to parse JSON with multiple strategies for any struct type func parseJSONWithFallback(jsonContent string, result interface{}) error { // Strategy 1: Direct JSON unmarshaling @@ -432,22 +448,6 @@ func extractPlanningFieldsManually(content string) (*PlanningJSONResponse, error return result, nil } -// parseStructuredResponse parses model response into structured format with error recovery -func parseStructuredResponse(content string, result interface{}) error { - // Clean and validate UTF-8 content first - cleanContent := sanitizeUTF8Content(content) - - // Extract JSON content from response - jsonContent := extractJSONFromContent(cleanContent) - if jsonContent == "" { - // If JSON extraction failed, try parsing the content directly as a fallback - jsonContent = cleanContent - } - - // Parse JSON response with error recovery - return parseJSONWithFallback(jsonContent, result) -} - // callModelWithLogging is a common function to call model with logging and timing // It handles the common pattern of: // 1. Log request