feat: add detailed logging for RAG chat and rerank attempts, including durations and error handling
Build and Release / release (push) Successful in 1m26s
Build and Release / release (push) Successful in 1m26s
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
|||||||
"history-api/pkg/constants"
|
"history-api/pkg/constants"
|
||||||
"history-api/pkg/convert"
|
"history-api/pkg/convert"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
@@ -39,28 +40,74 @@ func NewChatbotService(repo repositories.RagRepository, usageRepo repositories.U
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *chatbotService) Chat(ctx context.Context, userID string, projectID *string, question string) (string, error) {
|
func (s *chatbotService) Chat(ctx context.Context, userID string, projectID *string, question string) (string, error) {
|
||||||
|
totalStart := time.Now()
|
||||||
|
projectIDLog := ""
|
||||||
|
if projectID != nil {
|
||||||
|
projectIDLog = *projectID
|
||||||
|
}
|
||||||
|
questionLen := len([]rune(question))
|
||||||
|
|
||||||
|
log.Info().
|
||||||
|
Str("userID", userID).
|
||||||
|
Str("projectID", projectIDLog).
|
||||||
|
Int("question_len", questionLen).
|
||||||
|
Msg("rag chat started")
|
||||||
|
|
||||||
|
usageStart := time.Now()
|
||||||
usage, err := s.usageRepo.GetAIUsage(ctx, userID)
|
usage, err := s.usageRepo.GetAIUsage(ctx, userID)
|
||||||
|
usageDuration := time.Since(usageStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Warn().
|
||||||
|
Err(err).
|
||||||
|
Str("userID", userID).
|
||||||
|
Dur("usage_check_duration", usageDuration).
|
||||||
|
Dur("total_duration", time.Since(totalStart)).
|
||||||
|
Msg("rag chat failed while checking usage")
|
||||||
return "", fmt.Errorf("failed to check usage: %w", err)
|
return "", fmt.Errorf("failed to check usage: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if usage >= constants.MaxDailyAIUsage {
|
if usage >= constants.MaxDailyAIUsage {
|
||||||
|
log.Warn().
|
||||||
|
Str("userID", userID).
|
||||||
|
Int("usage", usage).
|
||||||
|
Dur("usage_check_duration", usageDuration).
|
||||||
|
Dur("total_duration", time.Since(totalStart)).
|
||||||
|
Msg("rag chat rejected by daily usage limit")
|
||||||
return "", fmt.Errorf("you have reached your daily limit of %d questions. Please come back tomorrow", constants.MaxDailyAIUsage)
|
return "", fmt.Errorf("you have reached your daily limit of %d questions. Please come back tomorrow", constants.MaxDailyAIUsage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
convertStart := time.Now()
|
||||||
pgUserID, err := convert.StringToUUID(userID)
|
pgUserID, err := convert.StringToUUID(userID)
|
||||||
|
convertDuration := time.Since(convertStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Warn().
|
||||||
|
Err(err).
|
||||||
|
Str("userID", userID).
|
||||||
|
Dur("uuid_convert_duration", convertDuration).
|
||||||
|
Dur("total_duration", time.Since(totalStart)).
|
||||||
|
Msg("rag chat failed while converting user id")
|
||||||
return "", fmt.Errorf("invalid user id: %w", err)
|
return "", fmt.Errorf("invalid user id: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
searchQuery := strings.TrimSpace(question)
|
searchQuery := strings.TrimSpace(question)
|
||||||
|
historyStart := time.Now()
|
||||||
rewriteHistory := s.getRewriteHistory(ctx, pgUserID)
|
rewriteHistory := s.getRewriteHistory(ctx, pgUserID)
|
||||||
|
historyDuration := time.Since(historyStart)
|
||||||
|
|
||||||
|
rewriteStart := time.Now()
|
||||||
rewrittenQuery, err := s.ragUtils.RewriteSearchQuery(ctx, question, rewriteHistory)
|
rewrittenQuery, err := s.ragUtils.RewriteSearchQuery(ctx, question, rewriteHistory)
|
||||||
|
rewriteDuration := time.Since(rewriteStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Msg("failed to rewrite RAG search query")
|
log.Warn().
|
||||||
|
Err(err).
|
||||||
|
Str("userID", userID).
|
||||||
|
Int("history_turns", len(rewriteHistory)).
|
||||||
|
Dur("rewrite_duration", rewriteDuration).
|
||||||
|
Msg("failed to rewrite RAG search query")
|
||||||
} else if strings.TrimSpace(rewrittenQuery) != "" {
|
} else if strings.TrimSpace(rewrittenQuery) != "" {
|
||||||
searchQuery = strings.TrimSpace(rewrittenQuery)
|
searchQuery = strings.TrimSpace(rewrittenQuery)
|
||||||
}
|
}
|
||||||
|
queryRewritten := searchQuery != strings.TrimSpace(question)
|
||||||
|
|
||||||
candidateLimit := config.GetIntConfigWithDefault("RAG_RETRIEVAL_CANDIDATES", 30)
|
candidateLimit := config.GetIntConfigWithDefault("RAG_RETRIEVAL_CANDIDATES", 30)
|
||||||
if candidateLimit < 8 {
|
if candidateLimit < 8 {
|
||||||
@@ -75,25 +122,62 @@ func (s *chatbotService) Chat(ctx context.Context, userID string, projectID *str
|
|||||||
contextLimit = candidateLimit
|
contextLimit = candidateLimit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
embedStart := time.Now()
|
||||||
qVector, err := s.ragUtils.EmbedQuery(ctx, searchQuery)
|
qVector, err := s.ragUtils.EmbedQuery(ctx, searchQuery)
|
||||||
|
embedDuration := time.Since(embedStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Warn().
|
||||||
|
Err(err).
|
||||||
|
Str("userID", userID).
|
||||||
|
Bool("query_rewritten", queryRewritten).
|
||||||
|
Dur("embed_duration", embedDuration).
|
||||||
|
Dur("total_duration", time.Since(totalStart)).
|
||||||
|
Msg("rag chat failed while embedding query")
|
||||||
return "", fmt.Errorf("failed to embed question: %w", err)
|
return "", fmt.Errorf("failed to embed question: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
searchStart := time.Now()
|
||||||
results, err := s.repo.SearchSimilar(ctx, projectID, qVector, candidateLimit, 0.45)
|
results, err := s.repo.SearchSimilar(ctx, projectID, qVector, candidateLimit, 0.45)
|
||||||
|
searchDuration := time.Since(searchStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Warn().
|
||||||
|
Err(err).
|
||||||
|
Str("userID", userID).
|
||||||
|
Str("projectID", projectIDLog).
|
||||||
|
Int("candidate_limit", candidateLimit).
|
||||||
|
Dur("vector_search_duration", searchDuration).
|
||||||
|
Dur("total_duration", time.Since(totalStart)).
|
||||||
|
Msg("rag chat failed while searching similar content")
|
||||||
return "", fmt.Errorf("failed to search similar content: %w", err)
|
return "", fmt.Errorf("failed to search similar content: %w", err)
|
||||||
}
|
}
|
||||||
|
initialResultCount := len(results)
|
||||||
|
|
||||||
|
var broadSearchDuration time.Duration
|
||||||
|
broadResultCount := 0
|
||||||
if len(results) < contextLimit {
|
if len(results) < contextLimit {
|
||||||
|
broadSearchStart := time.Now()
|
||||||
broadResults, err := s.repo.SearchSimilar(ctx, projectID, qVector, candidateLimit, 0.30)
|
broadResults, err := s.repo.SearchSimilar(ctx, projectID, qVector, candidateLimit, 0.30)
|
||||||
|
broadSearchDuration = time.Since(broadSearchStart)
|
||||||
if err == nil && len(broadResults) > len(results) {
|
if err == nil && len(broadResults) > len(results) {
|
||||||
results = broadResults
|
results = broadResults
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().
|
||||||
|
Err(err).
|
||||||
|
Str("userID", userID).
|
||||||
|
Str("projectID", projectIDLog).
|
||||||
|
Int("candidate_limit", candidateLimit).
|
||||||
|
Dur("broad_search_duration", broadSearchDuration).
|
||||||
|
Msg("rag broad vector search failed")
|
||||||
|
}
|
||||||
|
broadResultCount = len(broadResults)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rerankStart := time.Now()
|
||||||
results = s.rerankResults(ctx, searchQuery, results, contextLimit)
|
results = s.rerankResults(ctx, searchQuery, results, contextLimit)
|
||||||
|
rerankDuration := time.Since(rerankStart)
|
||||||
|
|
||||||
|
promptBuildStart := time.Now()
|
||||||
var contextBuilder strings.Builder
|
var contextBuilder strings.Builder
|
||||||
contextBuilder.Grow(len(results) * 128)
|
contextBuilder.Grow(len(results) * 128)
|
||||||
for i, res := range results {
|
for i, res := range results {
|
||||||
@@ -144,27 +228,72 @@ Rules:
|
|||||||
- Keep the answer focused on the user's question. Do not add background information that is not needed.
|
- Keep the answer focused on the user's question. Do not add background information that is not needed.
|
||||||
- Answer in complete, natural, grammatically correct sentences.`, contextStr, question)
|
- Answer in complete, natural, grammatically correct sentences.`, contextStr, question)
|
||||||
}
|
}
|
||||||
|
promptBuildDuration := time.Since(promptBuildStart)
|
||||||
|
|
||||||
|
generateStart := time.Now()
|
||||||
response, err := s.ragUtils.GenerateResponse(ctx, prompt)
|
response, err := s.ragUtils.GenerateResponse(ctx, prompt)
|
||||||
|
generateDuration := time.Since(generateStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Warn().
|
||||||
|
Err(err).
|
||||||
|
Str("userID", userID).
|
||||||
|
Str("projectID", projectIDLog).
|
||||||
|
Int("prompt_chars", len(prompt)).
|
||||||
|
Dur("generate_duration", generateDuration).
|
||||||
|
Dur("total_duration", time.Since(totalStart)).
|
||||||
|
Msg("rag chat failed while generating response")
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
response = normalizeAnswer(response)
|
response = normalizeAnswer(response)
|
||||||
|
|
||||||
|
usageIncrementStart := time.Now()
|
||||||
if _, err := s.usageRepo.IncrementAIUsage(ctx, userID); err != nil {
|
if _, err := s.usageRepo.IncrementAIUsage(ctx, userID); err != nil {
|
||||||
log.Warn().Err(err).Str("userID", userID).Msg("failed to increment AI usage")
|
log.Warn().Err(err).Str("userID", userID).Msg("failed to increment AI usage")
|
||||||
}
|
}
|
||||||
|
usageIncrementDuration := time.Since(usageIncrementStart)
|
||||||
|
|
||||||
|
historySaveStart := time.Now()
|
||||||
_, err = s.chatRepo.CreateChatbotHistory(ctx, sqlc.CreateChatbotHistoryParams{
|
_, err = s.chatRepo.CreateChatbotHistory(ctx, sqlc.CreateChatbotHistoryParams{
|
||||||
UserID: pgUserID,
|
UserID: pgUserID,
|
||||||
Question: question,
|
Question: question,
|
||||||
Answer: response,
|
Answer: response,
|
||||||
})
|
})
|
||||||
|
historySaveDuration := time.Since(historySaveStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Msg("failed to save chatbot history")
|
log.Warn().Err(err).Msg("failed to save chatbot history")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Info().
|
||||||
|
Str("userID", userID).
|
||||||
|
Str("projectID", projectIDLog).
|
||||||
|
Int("question_len", questionLen).
|
||||||
|
Int("search_query_len", len([]rune(searchQuery))).
|
||||||
|
Bool("query_rewritten", queryRewritten).
|
||||||
|
Int("history_turns", len(rewriteHistory)).
|
||||||
|
Int("candidate_limit", candidateLimit).
|
||||||
|
Int("context_limit", contextLimit).
|
||||||
|
Int("initial_results", initialResultCount).
|
||||||
|
Int("broad_results", broadResultCount).
|
||||||
|
Int("final_results", len(results)).
|
||||||
|
Int("context_chars", len(contextStr)).
|
||||||
|
Int("prompt_chars", len(prompt)).
|
||||||
|
Int("answer_chars", len(response)).
|
||||||
|
Dur("usage_check_duration", usageDuration).
|
||||||
|
Dur("uuid_convert_duration", convertDuration).
|
||||||
|
Dur("history_load_duration", historyDuration).
|
||||||
|
Dur("rewrite_duration", rewriteDuration).
|
||||||
|
Dur("embed_duration", embedDuration).
|
||||||
|
Dur("vector_search_duration", searchDuration).
|
||||||
|
Dur("broad_search_duration", broadSearchDuration).
|
||||||
|
Dur("rerank_duration", rerankDuration).
|
||||||
|
Dur("prompt_build_duration", promptBuildDuration).
|
||||||
|
Dur("generate_duration", generateDuration).
|
||||||
|
Dur("usage_increment_duration", usageIncrementDuration).
|
||||||
|
Dur("history_save_duration", historySaveDuration).
|
||||||
|
Dur("total_duration", time.Since(totalStart)).
|
||||||
|
Msg("rag chat completed")
|
||||||
|
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/tmc/langchaingo/embeddings"
|
"github.com/tmc/langchaingo/embeddings"
|
||||||
"github.com/tmc/langchaingo/llms"
|
"github.com/tmc/langchaingo/llms"
|
||||||
"github.com/tmc/langchaingo/llms/openai"
|
"github.com/tmc/langchaingo/llms/openai"
|
||||||
@@ -260,11 +261,30 @@ func (u *RagUtils) rerankDocumentsWithModel(ctx context.Context, model, query st
|
|||||||
|
|
||||||
var lastErr error
|
var lastErr error
|
||||||
for attempt := 0; attempt <= u.rerankMaxRetries; attempt++ {
|
for attempt := 0; attempt <= u.rerankMaxRetries; attempt++ {
|
||||||
|
attemptStart := time.Now()
|
||||||
results, retryable, err := u.sendRerankRequest(ctx, payload, documents)
|
results, retryable, err := u.sendRerankRequest(ctx, payload, documents)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
log.Info().
|
||||||
|
Str("model", model).
|
||||||
|
Int("attempt", attempt+1).
|
||||||
|
Int("documents", len(documents)).
|
||||||
|
Int("top_n", topN).
|
||||||
|
Int("results", len(results)).
|
||||||
|
Dur("duration", time.Since(attemptStart)).
|
||||||
|
Msg("rag rerank attempt succeeded")
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Warn().
|
||||||
|
Err(err).
|
||||||
|
Str("model", model).
|
||||||
|
Int("attempt", attempt+1).
|
||||||
|
Int("documents", len(documents)).
|
||||||
|
Int("top_n", topN).
|
||||||
|
Bool("retryable", retryable).
|
||||||
|
Dur("duration", time.Since(attemptStart)).
|
||||||
|
Msg("rag rerank attempt failed")
|
||||||
|
|
||||||
lastErr = err
|
lastErr = err
|
||||||
if !retryable || attempt == u.rerankMaxRetries {
|
if !retryable || attempt == u.rerankMaxRetries {
|
||||||
break
|
break
|
||||||
@@ -347,15 +367,38 @@ func (u *RagUtils) generateSinglePromptWithRetry(ctx context.Context, prompt, mo
|
|||||||
}
|
}
|
||||||
|
|
||||||
for attempt := 0; attempt <= u.generationMaxRetries; attempt++ {
|
for attempt := 0; attempt <= u.generationMaxRetries; attempt++ {
|
||||||
|
attemptStart := time.Now()
|
||||||
raw, err := llms.GenerateFromSinglePrompt(ctx, u.llm, prompt, options...)
|
raw, err := llms.GenerateFromSinglePrompt(ctx, u.llm, prompt, options...)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
log.Info().
|
||||||
|
Str("model", model).
|
||||||
|
Int("attempt", attempt+1).
|
||||||
|
Int("prompt_chars", len(prompt)).
|
||||||
|
Int("response_chars", len(raw)).
|
||||||
|
Dur("duration", time.Since(attemptStart)).
|
||||||
|
Msg("rag generation attempt succeeded")
|
||||||
return raw, nil
|
return raw, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
|
log.Warn().
|
||||||
|
Err(ctx.Err()).
|
||||||
|
Str("model", model).
|
||||||
|
Int("attempt", attempt+1).
|
||||||
|
Int("prompt_chars", len(prompt)).
|
||||||
|
Dur("duration", time.Since(attemptStart)).
|
||||||
|
Msg("rag generation attempt canceled")
|
||||||
return "", ctx.Err()
|
return "", ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Warn().
|
||||||
|
Err(err).
|
||||||
|
Str("model", model).
|
||||||
|
Int("attempt", attempt+1).
|
||||||
|
Int("prompt_chars", len(prompt)).
|
||||||
|
Dur("duration", time.Since(attemptStart)).
|
||||||
|
Msg("rag generation attempt failed")
|
||||||
|
|
||||||
lastErr = err
|
lastErr = err
|
||||||
if attempt == u.generationMaxRetries {
|
if attempt == u.generationMaxRetries {
|
||||||
break
|
break
|
||||||
|
|||||||
Reference in New Issue
Block a user