diff --git a/internal/services/chatbotService.go b/internal/services/chatbotService.go index 042652a..d60a13d 100644 --- a/internal/services/chatbotService.go +++ b/internal/services/chatbotService.go @@ -47,6 +47,11 @@ func (s *chatbotService) Chat(ctx context.Context, userID string, projectID *str return "", fmt.Errorf("you have reached your daily limit of %d questions. Please come back tomorrow", constants.MaxDailyAIUsage) } + pgUserID, err := convert.StringToUUID(userID) + if err != nil { + return "", fmt.Errorf("invalid user id: %w", err) + } + qVector, err := s.ragUtils.EmbedQuery(ctx, question) if err != nil { return "", fmt.Errorf("failed to embed question: %w", err) @@ -66,66 +71,61 @@ func (s *chatbotService) Chat(ctx context.Context, userID string, projectID *str var contextBuilder strings.Builder for i, res := range results { - contextBuilder.WriteString(fmt.Sprintf("[Document %d (score: %.2f)]: %s\n", i+1, res.Similarity, res.Content)) + contextBuilder.WriteString(fmt.Sprintf("\n%s\n\n\n", i+1, res.Similarity, res.Content)) } - contextStr := contextBuilder.String() - - pgUserID, err := convert.StringToUUID(userID) - if err != nil { - return "", fmt.Errorf("invalid user id: %w", err) - } - - histories, err := s.chatRepo.GetChatbotHistory(ctx, sqlc.GetChatbotHistoryParams{ - UserID: pgUserID, - Limit: 10, - }) - if err != nil { - log.Warn().Err(err).Msg("failed to get chatbot history") - } - - var historyBuilder strings.Builder - for _, h := range histories { - historyBuilder.WriteString(fmt.Sprintf("User: %s\nAssistant: %s\n\n", h.Question, h.Answer)) - } - historyStr := historyBuilder.String() + contextStr := strings.TrimSpace(contextBuilder.String()) var prompt string if contextStr == "" { prompt = fmt.Sprintf(`You are a friendly history assistant chatbot. -Recent Chat History: +User Question: %s -The user said: "%s" - Rules: -- You MUST reply in the same language as the user's question (e.g., if the user greets or asks in Vietnamese, reply in Vietnamese). -- If it is a greeting (like "hello", "hi", "xin chào"), respond with a friendly greeting and briefly introduce yourself. -- If it is a history question, say that you don't have relevant documents to answer. -- You MUST wrap your final response inside tags. Example: Hello! -- Do NOT show your reasoning outside or inside the tags if possible, but the final answer MUST be in tags.`, historyStr, question) +- Reply in the same language as the user's question. +- If the user is greeting, respond with a friendly greeting and briefly introduce yourself. +- If the user asks a history-related question, respond exactly: +I don't have enough historical context to answer that. +- Do not answer historical questions from memory. +- Do not use your own knowledge, assumptions, memory, or external facts. +- Do not guess, infer, assume, or invent missing information. +- Your final response MUST be wrapped inside tags. +- Do not output anything outside tags.`, question) } else { - prompt = fmt.Sprintf(`You are a helpful history assistant. Answer the question using ONLY the provided context. - -Rules: -- You MUST reply in the same language as the user's question (e.g., if the question is in Vietnamese, reply in Vietnamese). -- If the answer is not in the context, say "I don't have enough historical context to answer that." -- You MUST wrap your final response inside tags. Example: The capital is... -- Be concise and direct. + prompt = fmt.Sprintf(`You are a retrieval-augmented history assistant. Context: %s -Recent Chat History: +Question: %s -Question: %s`, contextStr, historyStr, question) +Rules: +- Reply in the same language as the user's question. +- Use ONLY the information explicitly stated in Context. +- Treat Context as the only source of truth. +- Never use your own knowledge, assumptions, memory, chat history, or external facts. +- Never infer information that is not explicitly stated in Context. +- Never create names, dates, places, events, causes, results, or explanations that are not in Context. +- Every factual sentence must be directly supported by Context. +- If Context does not contain enough information to answer, respond exactly: +I don't have enough historical context to answer that. +- If Context only partially answers the question, answer only the supported part and clearly say the remaining information is not available in the provided context. +- Do not mention document scores. +- Do not cite documents unless the user asks. +- Your final response MUST be wrapped inside tags. +- Do not output anything outside tags. +- Answer in complete, natural, grammatically correct sentences.`, contextStr, question) } response, err := s.ragUtils.GenerateResponse(ctx, prompt) if err != nil { return "", err } + + response = normalizeAnswer(response) + if _, err := s.usageRepo.IncrementAIUsage(ctx, userID); err != nil { log.Warn().Err(err).Str("userID", userID).Msg("failed to increment AI usage") } @@ -142,6 +142,20 @@ Question: %s`, contextStr, historyStr, question) return response, nil } +func normalizeAnswer(s string) string { + s = strings.TrimSpace(s) + + start := strings.Index(s, "") + end := strings.LastIndex(s, "") + + if start >= 0 && end > start { + return strings.TrimSpace(s[start : end+len("")]) + } + + s = strings.TrimSpace(strings.TrimPrefix(s, "Answer:")) + + return fmt.Sprintf("%s", s) +} func (s *chatbotService) GetHistory(ctx context.Context, userID string, dto *request.GetChatbotHistoryDto) ([]*models.ChatbotHistoryEntity, error) { pgUserID, err := convert.StringToUUID(userID) if err != nil {