0% found this document useful (0 votes)
7 views18 pages

RAG News Chatbot Full Stack Guide

The document outlines the implementation of a full-stack news chatbot using a Retrieval-Augmented Generation (RAG) pipeline. It details the tech stack, project structure, backend implementation, and various services including session management, vector storage, and chat functionalities. The chatbot is designed to answer user queries based on a corpus of news articles, utilizing technologies such as Node.js, Express, Redis, PostgreSQL, and Google Gemini for language processing.

Uploaded by

soham pawar
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views18 pages

RAG News Chatbot Full Stack Guide

The document outlines the implementation of a full-stack news chatbot using a Retrieval-Augmented Generation (RAG) pipeline. It details the tech stack, project structure, backend implementation, and various services including session management, vector storage, and chat functionalities. The chatbot is designed to answer user queries based on a corpus of news articles, utilizing technologies such as Node.js, Express, Redis, PostgreSQL, and Google Gemini for language processing.

Uploaded by

soham pawar
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd

# RAG-Powered News Chatbot - Full Stack Implementation

## Overview
A complete full-stack chatbot application that answers queries over a news corpus
using Retrieval-Augmented Generation (RAG) pipeline with session management.

## Tech Stack Justification

- **Embeddings**: Sentence Transformers (all-MiniLM-L6-v2) - Free, fast, and


efficient
- **Vector DB**: Chroma - Simple setup, good for development
- **LLM API**: Google Gemini - As specified
- **Backend**: [Link] + Express - Fast development, good ecosystem
- **Cache & Sessions**: Redis - Industry standard for session management
- **Database**: PostgreSQL - Reliable, good JSON support for chat history
- **Frontend**: React + SCSS - Modern, responsive UI

## Project Structure
```
rag-news-chatbot/
├── backend/
│ ├── src/
│ │ ├── controllers/
│ │ ├── services/
│ │ ├── models/
│ │ ├── routes/
│ │ └── utils/
│ ├── [Link]
│ └── .env
├── frontend/
│ ├── src/
│ │ ├── components/
│ │ ├── styles/
│ │ └── services/
│ ├── [Link]
│ └── public/
└── [Link]
```

## Backend Implementation

### 1. [Link] (Backend)

```json
{
"name": "rag-news-chatbot-backend",
"version": "1.0.0",
"description": "RAG-powered news chatbot backend",
"main": "src/[Link]",
"scripts": {
"start": "node src/[Link]",
"dev": "nodemon src/[Link]",
"setup-db": "node src/scripts/[Link]",
"ingest-news": "node src/scripts/[Link]"
},
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"redis": "^4.6.7",
"pg": "^8.11.1",
"chromadb": "^1.5.13",
"@google/generative-ai": "^0.2.1",
"@xenova/transformers": "^2.10.0",
"axios": "^1.5.0",
"cheerio": "^1.0.0-rc.12",
"rss-parser": "^3.13.0",
"uuid": "^9.0.0",
"[Link]": "^4.7.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
```

### 2. Server Setup (src/[Link])

```javascript
const express = require('express');
const cors = require('cors');
const http = require('http');
const socketIo = require('[Link]');
require('dotenv').config();

const chatRoutes = require('./routes/chat');


const sessionRoutes = require('./routes/session');
const redisClient = require('./utils/redis');
const { initializeChroma } = require('./services/vectorStore');

const app = express();


const server = [Link](app);
const io = socketIo(server, {
cors: {
origin: [Link].FRONTEND_URL || "[Link]
methods: ["GET", "POST"]
}
});

// Middleware
[Link](cors());
[Link]([Link]());

// Routes
[Link]('/api/chat', chatRoutes);
[Link]('/api/session', sessionRoutes);

// [Link] for real-time chat


[Link]('connection', (socket) => {
[Link]('User connected:', [Link]);

[Link]('join-session', (sessionId) => {


[Link](sessionId);
});

[Link]('disconnect', () => {
[Link]('User disconnected:', [Link]);
});
});

// Initialize services
async function startServer() {
try {
await [Link]();
await initializeChroma();

const PORT = [Link] || 5000;


[Link](PORT, () => {
[Link](`Server running on port ${PORT}`);
});
} catch (error) {
[Link]('Failed to start server:', error);
[Link](1);
}
}

// Export io for use in routes


[Link]('io', io);

startServer();
```

### 3. Redis Configuration (src/utils/[Link])

```javascript
const redis = require('redis');

const client = [Link]({


host: [Link].REDIS_HOST || 'localhost',
port: [Link].REDIS_PORT || 6379,
// Configure TTL for sessions (24 hours)
database: 0
});

[Link]('error', (err) => {


[Link]('Redis Client Error:', err);
});

[Link]('connect', () => {
[Link]('Connected to Redis');
});

[Link] = client;
```

### 4. Vector Store Service (src/services/[Link])

```javascript
const { ChromaClient } = require('chromadb');
const { pipeline } = require('@xenova/transformers');

class VectorStore {
constructor() {
[Link] = new ChromaClient();
[Link] = null;
[Link] = null;
}
async initialize() {
try {
// Initialize embedding model
[Link] = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-
v2');

// Get or create collection


try {
[Link] = await [Link]({ name:
'news_articles' });
} catch (error) {
[Link] = await [Link]({
name: 'news_articles',
metadata: { description: 'News articles embeddings' }
});
}

[Link]('Vector store initialized');


} catch (error) {
[Link]('Failed to initialize vector store:', error);
throw error;
}
}

async addDocuments(documents) {
const embeddings = [];
const ids = [];
const metadatas = [];
const texts = [];

for (let i = 0; i < [Link]; i++) {


const doc = documents[i];
const embedding = await [Link]([Link], { pooling: 'mean',
normalize: true });

[Link]([Link]([Link]));
[Link]([Link] || `doc_${i}`);
[Link]({
title: [Link],
url: [Link],
publishedDate: [Link],
source: [Link]
});
[Link]([Link]);
}

await [Link]({
ids,
embeddings,
metadatas,
documents: texts
});

[Link](`Added ${[Link]} documents to vector store`);


}

async search(query, topK = 5) {


const queryEmbedding = await [Link](query, { pooling: 'mean', normalize:
true });

const results = await [Link]({


queryEmbeddings: [[Link]([Link])],
nResults: topK,
include: ['documents', 'metadatas', 'distances']
});

return [Link][0].map((doc, idx) => ({


content: doc,
metadata: [Link][0][idx],
score: [Link][0][idx]
}));
}
}

const vectorStore = new VectorStore();

async function initializeChroma() {


await [Link]();
}

[Link] = { vectorStore, initializeChroma };


```

### 5. LLM Service (src/services/[Link])

```javascript
const { GoogleGenerativeAI } = require('@google/generative-ai');

class LLMService {
constructor() {
[Link] = new GoogleGenerativeAI([Link].GEMINI_API_KEY);
[Link] = [Link]({ model: 'gemini-pro' });
}

async generateResponse(query, context, chatHistory = []) {


const contextText = [Link](doc =>
`Title: ${[Link]}\nContent: ${[Link]}\nSource: $
{[Link]}\n---`
).join('\n\n');

const historyText = [Link](-4).map(msg =>


`${[Link]}: ${[Link]}`
).join('\n');

const prompt = `You are a helpful news assistant. Answer the user's question
based on the provided news articles context.
If the answer cannot be found in the context, say so politely.

Context from news articles:


${contextText}

${historyText ? `Recent conversation history:\n${historyText}\n` : ''}

User question: ${query}

Please provide a helpful and accurate answer based on the news context:`;
try {
const result = await [Link](prompt);
const response = await [Link];
return [Link]();
} catch (error) {
[Link]('LLM Error:', error);
throw new Error('Failed to generate response');
}
}
}

[Link] = new LLMService();


```

### 6. Session Service (src/services/[Link])

```javascript
const redisClient = require('../utils/redis');
const { v4: uuidv4 } = require('uuid');

class SessionService {
constructor() {
[Link] = 24 * 60 * 60; // 24 hours in seconds
}

generateSessionId() {
return uuidv4();
}

async getSessionHistory(sessionId) {
try {
const history = await [Link](`session:${sessionId}`);
return history ? [Link](history) : [];
} catch (error) {
[Link]('Error getting session history:', error);
return [];
}
}

async addMessageToSession(sessionId, message) {


try {
const history = await [Link](sessionId);
[Link]({
...message,
timestamp: new Date().toISOString()
});

await [Link](
`session:${sessionId}`,
[Link],
[Link](history)
);

return history;
} catch (error) {
[Link]('Error adding message to session:', error);
throw error;
}
}
async clearSession(sessionId) {
try {
await [Link](`session:${sessionId}`);
return true;
} catch (error) {
[Link]('Error clearing session:', error);
return false;
}
}

async extendSessionTTL(sessionId) {
try {
await [Link](`session:${sessionId}`, [Link]);
} catch (error) {
[Link]('Error extending session TTL:', error);
}
}
}

[Link] = new SessionService();


```

### 7. Chat Routes (src/routes/[Link])

```javascript
const express = require('express');
const router = [Link]();
const { vectorStore } = require('../services/vectorStore');
const llmService = require('../services/llm');
const sessionService = require('../services/sessionService');

// Chat endpoint
[Link]('/', async (req, res) => {
try {
const { query, sessionId } = [Link];

if (!query || !sessionId) {
return [Link](400).json({ error: 'Query and sessionId are required' });
}

// Get chat history


const chatHistory = await [Link](sessionId);

// Add user message to session


await [Link](sessionId, {
role: 'user',
content: query
});

// Retrieve relevant documents


const relevantDocs = await [Link](query, 5);

// Generate response
const response = await [Link](query, relevantDocs,
chatHistory);

// Add bot response to session


await [Link](sessionId, {
role: 'assistant',
content: response,
sources: [Link](doc => ({
title: [Link],
url: [Link],
source: [Link]
}))
});

// Extend session TTL


await [Link](sessionId);

// Emit response via socket if available


const io = [Link]('io');
[Link](sessionId).emit('bot-response', {
response,
sources: [Link](doc => ({
title: [Link],
url: [Link],
source: [Link]
}))
});

[Link]({
response,
sources: [Link](doc => ({
title: [Link],
url: [Link],
source: [Link]
}))
});

} catch (error) {
[Link]('Chat error:', error);
[Link](500).json({ error: 'Internal server error' });
}
});

[Link] = router;
```

### 8. Session Routes (src/routes/[Link])

```javascript
const express = require('express');
const router = [Link]();
const sessionService = require('../services/sessionService');

// Create new session


[Link]('/create', (req, res) => {
const sessionId = [Link]();
[Link]({ sessionId });
});

// Get session history


[Link]('/:sessionId/history', async (req, res) => {
try {
const { sessionId } = [Link];
const history = await [Link](sessionId);
[Link]({ history });
} catch (error) {
[Link]('Error getting session history:', error);
[Link](500).json({ error: 'Internal server error' });
}
});

// Clear session
[Link]('/:sessionId', async (req, res) => {
try {
const { sessionId } = [Link];
const success = await [Link](sessionId);
[Link]({ success });
} catch (error) {
[Link]('Error clearing session:', error);
[Link](500).json({ error: 'Internal server error' });
}
});

[Link] = router;
```

### 9. News Ingestion Script (src/scripts/[Link])

```javascript
const RSSParser = require('rss-parser');
const axios = require('axios');
const cheerio = require('cheerio');
const { vectorStore } = require('../services/vectorStore');

class NewsIngestion {
constructor() {
[Link] = new RSSParser();
[Link] = [
'[Link]
'[Link]
'[Link]
'[Link]
// Add more RSS feeds as needed
];
}

async fetchRSSFeed(url) {
try {
const feed = await [Link](url);
return [Link](0, 15); // Limit per source
} catch (error) {
[Link](`Error fetching RSS from ${url}:`, [Link]);
return [];
}
}

async extractArticleContent(url) {
try {
const response = await [Link](url, {
timeout: 10000,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36'
}
});

const $ = [Link]([Link]);

// Remove unnecessary elements


$('script, style, nav, header, footer, aside, .advertisement').remove();

// Extract main content (adapt selectors as needed)


let content = $('article, main, .content, .story-body, .entry-
content').text();

if (!content) {
content = $('p').text();
}

return [Link]().substring(0, 2000); // Limit content length


} catch (error) {
[Link](`Error extracting content from ${url}:`, [Link]);
return '';
}
}

async ingestNews() {
[Link]('Starting news ingestion...');

const allArticles = [];

for (const sourceUrl of [Link]) {


[Link](`Fetching from ${sourceUrl}...`);
const articles = await [Link](sourceUrl);

for (const article of articles) {


const content = await [Link]([Link]);

if (content && [Link] > 100) {


[Link]({
id: [Link]([Link]).toString('base64'),
title: [Link],
content: content,
url: [Link],
publishedDate: [Link],
source: new URL(sourceUrl).hostname
});
}

// Rate limiting
await new Promise(resolve => setTimeout(resolve, 1000));
}
}

[Link](`Ingested ${[Link]} articles`);

// Add to vector store


if ([Link] > 0) {
await [Link](allArticles);
[Link]('Articles added to vector store');
}
return [Link];
}
}

// Run ingestion if called directly


if ([Link] === module) {
const ingestion = new NewsIngestion();
[Link]().then(count => {
[Link](`Successfully ingested ${count} articles`);
[Link](0);
}).catch(error => {
[Link]('Ingestion failed:', error);
[Link](1);
});
}

[Link] = NewsIngestion;
```

## Frontend Implementation

### 1. [Link] (Frontend)

```json
{
"name": "rag-news-chatbot-frontend",
"version": "1.0.0",
"description": "RAG-powered news chatbot frontend",
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"axios": "^1.5.0",
"[Link]-client": "^4.7.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/fontawesome-svg-core": "^6.4.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"devDependencies": {
"react-scripts": "^5.0.1",
"sass": "^1.66.1"
},
"browserslist": {
"production": [">0.2%", "not dead", "not op_mini all"],
"development": ["last 1 chrome version", "last 1 firefox version", "last 1
safari version"]
}
}
```

### 2. Main App Component (src/[Link])

```jsx
import React, { useState, useEffect } from 'react';
import ChatInterface from './components/ChatInterface';
import { createSession } from './services/api';
import './styles/[Link]';

function App() {
const [sessionId, setSessionId] = useState(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
initializeSession();
}, []);

const initializeSession = async () => {


try {
const response = await createSession();
setSessionId([Link]);
} catch (error) {
[Link]('Failed to create session:', error);
} finally {
setLoading(false);
}
};

const handleNewSession = async () => {


setLoading(true);
await initializeSession();
};

if (loading) {
return (
<div className="app-loading">
<div className="loading-spinner"></div>
<p>Initializing chatbot...</p>
</div>
);
}

return (
<div className="app">
<header className="app-header">
<div className="header-content">
<h1>📰 News AI Assistant</h1>
<p>Ask questions about recent news and get AI-powered answers</p>
<button
className="new-session-btn"
onClick={handleNewSession}
>
🔄 New Session
</button>
</div>
</header>

<main className="app-main">
{sessionId && (
<ChatInterface
sessionId={sessionId}
onNewSession={handleNewSession}
/>
)}
</main>
</div>
);
}

export default App;


```

### 3. Chat Interface Component (src/components/[Link])

```jsx
import React, { useState, useEffect, useRef } from 'react';
import { sendMessage, getSessionHistory, clearSession } from '../services/api';
import { connectSocket, disconnectSocket } from '../services/socket';
import MessageList from './MessageList';
import MessageInput from './MessageInput';
import TypingIndicator from './TypingIndicator';
import '../styles/[Link]';

const ChatInterface = ({ sessionId, onNewSession }) => {


const [messages, setMessages] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [isTyping, setIsTyping] = useState(false);
const chatContainerRef = useRef(null);

useEffect(() => {
if (sessionId) {
loadSessionHistory();
setupSocket();
}

return () => {
disconnectSocket();
};
}, [sessionId]);

useEffect(() => {
scrollToBottom();
}, [messages]);

const loadSessionHistory = async () => {


try {
const response = await getSessionHistory(sessionId);
setMessages([Link] || []);
} catch (error) {
[Link]('Failed to load session history:', error);
}
};

const setupSocket = () => {


connectSocket(sessionId, (response) => {
setIsTyping(false);
// Socket response is handled by the API response
});
};

const scrollToBottom = () => {


if ([Link]) {
[Link] = [Link];
}
};

const handleSendMessage = async (messageText) => {


if (![Link]()) return;

const userMessage = {
role: 'user',
content: messageText,
timestamp: new Date().toISOString()
};

setMessages(prev => [...prev, userMessage]);


setIsLoading(true);
setIsTyping(true);

try {
const response = await sendMessage(messageText, sessionId);

const botMessage = {
role: 'assistant',
content: [Link],
sources: [Link],
timestamp: new Date().toISOString()
};

setMessages(prev => [...prev, botMessage]);


} catch (error) {
[Link]('Failed to send message:', error);

const errorMessage = {
role: 'assistant',
content: 'Sorry, I encountered an error processing your request. Please try
again.',
timestamp: new Date().toISOString(),
isError: true
};

setMessages(prev => [...prev, errorMessage]);


} finally {
setIsLoading(false);
setIsTyping(false);
}
};

const handleClearSession = async () => {


if ([Link]('Are you sure you want to clear this session?')) {
try {
await clearSession(sessionId);
setMessages([]);
} catch (error) {
[Link]('Failed to clear session:', error);
}
}
};

return (
<div className="chat-interface">
<div className="chat-header">
<div className="session-info">
<span className="session-id">Session: {[Link](0,
8)}...</span>
<div className="session-actions">
<button
className="clear-btn"
onClick={handleClearSession}
title="Clear chat history"
>
Clear
</button>
</div>
</div>
</div>

<div className="chat-container" ref={chatContainerRef}>


<MessageList messages={messages} />
{isTyping && <TypingIndicator />}
</div>

<MessageInput
onSendMessage={handleSendMessage}
disabled={isLoading}
/>
</div>
);
};

export default ChatInterface;


```

### 4. Message List Component (src/components/[Link])

```jsx
import React from 'react';
import MessageBubble from './MessageBubble';
import '../styles/[Link]';

const MessageList = ({ messages }) => {


if ([Link] === 0) {
return (
<div className="message-list empty">
<div className="welcome-message">
<h3>👋 Welcome to News AI Assistant!</h3>
<p>Ask me anything about recent news and current events.</p>
<div className="example-questions">
<p>Try asking:</p>
<ul>
<li>"What's the latest news about technology?"</li>
<li>"Tell me about recent political developments"</li>
<li>"What are the top business stories today?"</li>
</ul>
</div>
</div>
</div>
);
}

return (
<div className="message-list">
{[Link]((message, index) => (
<MessageBubble
key={`${[Link]}-${index}`}
message={message}
/>
))}
</div>
);
};

export default MessageList;


```

### 5. Message Bubble Component (src/components/[Link])

```jsx
import React, { useState, useEffect } from 'react';
import '../styles/[Link]';

const MessageBubble = ({ message }) => {


const [displayedContent, setDisplayedContent] = useState('');
const [isComplete, setIsComplete] = useState(false);

useEffect(() => {
if ([Link] === 'assistant' && ![Link]) {
// Simulate typing effect for bot messages
let index = 0;
const content = [Link];

const typeWriter = () => {


if (index < [Link]) {
setDisplayedContent([Link](0, index + 1));
index++;
setTimeout(typeWriter, 20);
} else {
setIsComplete(true);
}
};

typeWriter();
} else {
setDisplayedContent([Link]);
setIsComplete(true);
}
}, [message]);

const formatTimestamp = (timestamp) => {


return new Date(timestamp).toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit'
});
};

return (
<div className={`message-bubble ${[Link]}`}>
<div className="message-avatar">
{[Link] === 'user' ? '👤' : '🤖'}
</div>
<div className="message-content">
<div className={`message-text ${[Link] ? 'error' : ''}`}>
{displayedContent}
{!isComplete && [Link] === 'assistant' && (
<span className="typing-cursor">|</span>
)}
</div>

{[Link] && [Link] > 0 && isComplete && (


<div className="message-sources">
<h4>📚 Sources:</h4>
<ul>
{[Link]((source, index) => (
<li key={index}>
<a href={[Link]} target="_blank" rel="noopener noreferrer">
{[Link]}
</a>
<span className="source-name">- {[Link]}</span>
</li>
))}
</ul>
</div>
)}

<div className="message-timestamp">
{formatTimestamp([Link])}
</div>
</div>
</div>
);
};

export default MessageBubble;


```

### 6. Message Input Component (src/components/[Link])

```jsx
import React, { useState } from 'react';
import '../styles/[Link]';

const MessageInput = ({ onSendMessage, disabled }) => {


const [message, setMessage] = useState('');

const handleSubmit = (e) => {


[Link]();
if ([Link]() && !disabled) {
onSendMessage(message);
setMessage('');
}
};

const handleKeyPress = (e) => {


if ([Link] === 'Enter' && ![Link]) {
[Link]();
handleSubmit(e);
}
};
return (
<form className="message-input" onSubmit={handleSubmit}>
<div className="input-container">
<textarea
value={message}
onChange={(e) => setMessage([Link])}
onKeyPress={handleKeyPress}
placeholder="Ask me about recent news..."
disabled={disabled}
rows="1"
/>
<button
type="submit"
disabled={disabled || ![Link]()}
className="send-button"
>
{disabled ? '⏳' : '📤'}
</button>
</div>
</form>
);
};

export default MessageInput;


```

### 7. Typing Indicator Component (src/components/[Link])

```jsx
import React from 'react';
import '../styles/[Link]';

const TypingIndicator = ()

You might also like