Ai Docs
Tutoriais

Tutorial - Desenvolvendo um Chat com RAG

Este tutorial estende a aplicação RAG anterior para criar uma experiência de chat completa.

Desenvolvendo um Chat com RAG

Este tutorial estende a aplicação RAG anterior para criar uma experiência de chat completa.

Pré-requisitos

  • Aplicação RAG básica (do tutorial anterior)
  • Conhecimento básico de React (para frontend)
  • Node.js e TypeScript

Etapa 1: Melhorando a Memória de Chat

Vamos aprimorar o serviço de chat memory para suportar mais recursos:

// src/services/enhancedChatMemory.ts
import { RedisClientType } from 'redis';
import { ChatMessage } from '../models/chat';
import { openai } from '../config/openai';

export class EnhancedChatMemory {
  private prefix: string = 'chat:';
  private summaryPrefix: string = 'summary:';
  
  constructor(
    private redis: RedisClientType,
    private maxMessagesPerContext = 10
  ) {}
  
  async addMessage(sessionId: string, message: ChatMessage): Promise<void> {
    const key = `${this.prefix}${sessionId}`;
    
    // Adicionar timestamp se não existir
    const messageWithTime: ChatMessage = {
      ...message,
      timestamp: message.timestamp || new Date()
    };
    
    await this.redis.rPush(key, JSON.stringify(messageWithTime));
    await this.redis.expire(key, 24 * 60 * 60); // 24h TTL
    
    // Atualizar contador de mensagens
    const count = await this.redis.lLen(key);
    
    // Se temos muitas mensagens, criar/atualizar resumo
    if (count % 20 === 0) {
      await this.updateSummary(sessionId);
    }
  }
  
  async getContextForPrompt(sessionId: string): Promise<string> {
    // Obter resumo, se existir
    const summaryKey = `${this.summaryPrefix}${sessionId}`;
    const summary = await this.redis.get(summaryKey);
    
    // Obter mensagens recentes
    const recentMessages = await this.getMessages(sessionId, this.maxMessagesPerContext);
    const messageText = recentMessages
      .map(m => `${m.role}: ${m.content}`)
      .join('\n');
    
    if (summary) {
      return `
        Conversation Summary:
        ${summary}
        
        Recent Messages:
        ${messageText}
      `;
    }
    
    return `Conversation History:\n${messageText}`;
  }
  
  private async updateSummary(sessionId: string): Promise<void> {
    // Implementação da sumarização...
    // (código completo disponível na documentação)
  }
}

Etapa 2: Aprimorando o Serviço RAG para Chat

Modificando o RAG para suportar experiência de chat:

// src/services/enhancedRagService.ts
import { VectorStore } from './vectorStoreService';
import { EnhancedChatMemory } from './enhancedChatMemory';
import { openai } from '../config/openai';
import { QueryResult } from '../models/query';
import { ChatMessage } from '../models/chat';

export class EnhancedRagService {
  constructor(
    private vectorStore: VectorStore,
    private chatMemory: EnhancedChatMemory,
    private systemPrompt: string = "You are a helpful assistant."
  ) {}
  
  async query(question: string, sessionId: string): Promise<QueryResult> {
    // 1. Buscar documentos relevantes
    const relevantDocs = await this.vectorStore.search(question, 3);
    
    // 2. Obter contexto da conversa (resumo + mensagens recentes)
    const conversationContext = await this.chatMemory.getContextForPrompt(sessionId);
    
    // 3. Gerar resposta
    const completion = await openai.chat.completions.create({
      model: "gpt-3.5-turbo",
      messages: [
        {
          role: "system",
          content: `${this.systemPrompt}
            
            When responding, use the following information:
            
            ${relevantDocs.map(doc => doc.content).join('\n\n')}
            
            ${conversationContext}
          `
        },
        { role: "user", content: question }
      ],
    });
    
    const answer = completion.choices[0].message.content;
    
    // 4. Salvar na memória
    await this.chatMemory.addMessage(sessionId, { role: 'user', content: question });
    await this.chatMemory.addMessage(sessionId, { role: 'assistant', content: answer });
    
    return {
      answer,
      sources: relevantDocs
    };
  }
}

Etapa 3: Frontend de Chat

Agora vamos criar um frontend simples usando HTML/JavaScript:

<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>RAG Chat Application</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
  <link rel="stylesheet" href="/css/styles.css">
</head>
<body>
  <div class="container mt-4">
    <div class="chat-container">
      <div class="chat-header">
        <h2>RAG Chat Application</h2>
        <button id="clearChat" class="clear-button">Clear Chat</button>
      </div>
      
      <div id="chatMessages" class="chat-messages">
        <div class="text-center text-muted p-4">
          Start a conversation by sending a message
        </div>
      </div>
      
      <div class="chat-input-container">
        <form id="chatForm" class="chat-input-form">
          <input
            type="text"
            id="messageInput"
            class="chat-input"
            placeholder="Type your message..."
          />
          <button type="submit" class="send-button">Send</button>
        </form>
      </div>
    </div>
  </div>

  <script src="/js/chat.js"></script>
</body>
</html>

CSS para o chat:

/* public/css/styles.css */
.chat-container {
  max-width: 1000px;
  margin: 2rem auto;
  background: white;
  border-radius: 10px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  display: flex;
  flex-direction: column;
  height: 80vh;
}

.chat-messages {
  flex: 1;
  overflow-y: auto;
  padding: 1rem;
  display: flex;
  flex-direction: column;
}

.message {
  padding: 0.75rem 1rem;
  border-radius: 18px;
  margin-bottom: 0.5rem;
  max-width: 75%;
}

.user-message {
  background-color: #1da1f2;
  color: white;
  align-self: flex-end;
  border-bottom-right-radius: 4px;
}

.assistant-message {
  background-color: #e1e8ed;
  color: #14171a;
  align-self: flex-start;
  border-bottom-left-radius: 4px;
}

JavaScript para funcionalidade do chat:

// public/js/chat.js
class ChatApp {
  constructor() {
    this.sessionId = this.generateSessionId();
    this.isLoading = false;
    this.setupEventListeners();
  }

  generateSessionId() {
    return Math.random().toString(36).substring(2, 15) + 
           Math.random().toString(36).substring(2, 15);
  }

  setupEventListeners() {
    const form = document.getElementById('chatForm');
    const clearButton = document.getElementById('clearChat');

    form.addEventListener('submit', (e) => this.handleSubmit(e));
    clearButton.addEventListener('click', () => this.clearChat());
  }

  async handleSubmit(e) {
    e.preventDefault();
    
    const input = document.getElementById('messageInput');
    const message = input.value.trim();
    
    if (!message || this.isLoading) return;

    this.addMessage('user', message);
    input.value = '';
    this.setLoading(true);

    try {
      const response = await fetch('/api/chat/message', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ message, sessionId: this.sessionId })
      });

      const data = await response.json();
      this.addMessage('assistant', data.message, data.sources);
    } catch (error) {
      this.addMessage('assistant', 'Sorry, I encountered an error.');
    } finally {
      this.setLoading(false);
    }
  }

  addMessage(role, content, sources = []) {
    const messagesContainer = document.getElementById('chatMessages');
    
    // Remove welcome message if it exists
    const welcomeMsg = messagesContainer.querySelector('.text-center');
    if (welcomeMsg) welcomeMsg.remove();

    const messageDiv = document.createElement('div');
    messageDiv.className = `message ${role}-message`;
    
    let html = `<div>${content}</div>`;
    
    if (sources && sources.length > 0) {
      html += `
        <div class="sources-container">
          <strong>Sources:</strong>
          <ul class="ps-3 mb-0">
            ${sources.slice(0, 2).map(source => 
              `<li>${source.metadata?.title || 'Document'}</li>`
            ).join('')}
          </ul>
        </div>
      `;
    }
    
    messageDiv.innerHTML = html;
    messagesContainer.appendChild(messageDiv);
    messagesContainer.scrollTop = messagesContainer.scrollHeight;
  }

  setLoading(loading) {
    this.isLoading = loading;
    const submitButton = document.querySelector('.send-button');
    submitButton.disabled = loading;
    submitButton.textContent = loading ? 'Sending...' : 'Send';
  }

  async clearChat() {
    if (confirm('Are you sure you want to clear the chat?')) {
      try {
        await fetch(`/api/chat/history/${this.sessionId}`, {
          method: 'DELETE'
        });
        
        const messagesContainer = document.getElementById('chatMessages');
        messagesContainer.innerHTML = `
          <div class="text-center text-muted p-4">
            Start a conversation by sending a message
          </div>
        `;
        
        this.sessionId = this.generateSessionId();
      } catch (error) {
        console.error('Error clearing chat:', error);
      }
    }
  }
}

// Initialize the app when the page loads
document.addEventListener('DOMContentLoaded', () => {
  new ChatApp();
});

Etapa 4: Testando a Aplicação

# Executar o servidor
npm run dev

# Acessar no navegador
# http://localhost:3000

Sucesso! Agora você tem uma aplicação de chat completa com RAG funcionando.

Próximos Passos

Para uma aplicação em produção, considere:

  1. Autenticação e Autorização
  2. Rate Limiting
  3. Monitoramento e Logging
  4. Frontend mais robusto com React/Vue
  5. Testes automatizados
  6. Deploy em produção

Conclusão

Este tutorial criou uma aplicação de chat completa que combina:

  • RAG: Para respostas baseadas em conhecimento
  • Memória: Para contexto conversacional
  • Interface amigável: Para experiência do usuário
  • Escalabilidade: Preparada para produção

O resultado é um sistema conversacional poderoso que mantém contexto e fornece respostas precisas baseadas em sua base de conhecimento.