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:3000Sucesso! Agora você tem uma aplicação de chat completa com RAG funcionando.
Próximos Passos
Para uma aplicação em produção, considere:
- Autenticação e Autorização
- Rate Limiting
- Monitoramento e Logging
- Frontend mais robusto com React/Vue
- Testes automatizados
- 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.