Sunday, 1 November 2026

Build an AI-Powered Blog Chatbot Using LangChain, RAG, OpenAI and ChromaDB – Complete Hands-on Project

 

1. Introduction

Generative AI and Large Language Models (LLMs) are changing how we access and interact with information.

Traditional chatbots generally depend on predefined questions, answers or information available within their training data.

However, what if we want to build an AI assistant that can understand our own technical articles, retrieve relevant information and answer questions using our personal knowledge base?

In this project, we will build an AI-powered chatbot that connects directly to a personal Blogger website and answers questions based on published blog articles.

We will use Retrieval-Augmented Generation (RAG), LangChain, OpenAI embeddings, GPT-4.1-mini and ChromaDB.

The application will automatically:

  1. Fetch published articles from a Blogger website using its RSS feed.

  2. Extract article titles and content.

  3. Convert articles into LangChain documents.

  4. Split documents into smaller text chunks.

  5. Generate vector embeddings using OpenAI.

  6. Store embeddings in ChromaDB.

  7. Retrieve relevant content based on user questions.

  8. Generate contextual answers using an LLM.

  9. Display the original article references.

Project results

During my implementation, the application successfully processed:

MetricResult
Blog posts retrieved276
Text chunks generated2,773
Vector databaseChromaDB
Embedding modeltext-embedding-3-small
LLMGPT-4.1-mini
FrameworkLangChain

2. Project Architecture

Before writing the code, let's understand the complete architecture.

High-level architecture

Blogger Website
|
v
Blogger RSS Feed
|
v
Python RSS Data Loader
|
v
BeautifulSoup HTML Content Extraction
|
v
LangChain Documents
|
v
Recursive Character Text Splitter
|
v
OpenAI Embeddings
|
v
ChromaDB Vector Database
|
v
Retriever
|
v
Relevant Document Chunks
|
v
GPT-4.1-mini
|
v
AI Generated Answer + Source References

Architecture components

1. Blogger RSS Feed

Provides access to published blog articles without manually downloading individual posts.

2. Python

Handles RSS fetching, content extraction, document processing and application execution.

3. LangChain

Provides the document loading, splitting, embedding, retrieval and LLM orchestration components.

4. OpenAI Embeddings

Converts text into numerical vector representations that can be searched semantically.

5. ChromaDB

Stores the vectors and associated document metadata.

6. Retriever

Finds the most relevant document chunks based on the user's question.

7. GPT-4.1-mini

Uses retrieved information to generate a contextual answer.

3. Technology Stack

We will use the following technologies.

TechnologyPurpose
Python 3.13Application development
LangChainRAG orchestration
OpenAI APILLM and embeddings
GPT-4.1-miniAnswer generation
text-embedding-3-smallVector embeddings
ChromaDBVector database
feedparserRSS feed parsing
BeautifulSoupHTML extraction
RequestsHTTP requests
python-dotenvAPI key management

4. Prerequisites

Before starting, ensure you have:

  • Windows 11 or Linux.

  • Python 3.10 or newer.

  • VS Code.

  • OpenAI API key with API access and billing configured.

  • Internet connectivity.

  • A public Blogger website.

For this project, we will use:

Blog URL:

https://rajguptaaws.blogspot.com/

5. Create the Project Directory

Create a project directory:

mkdir day6-rag
cd day6-rag

Create the following project structure:

day6-rag/
│
├── main.py
│
├── .env
│
├── requirements.txt
│
└── chroma_db/

Explanation:

  • main.py: Main Python application.

  • .env: Stores the OpenAI API key.

  • requirements.txt: Python dependencies.

  • chroma_db: Persistent vector database storage.

6. Create Python Virtual Environment

We will use an existing virtual environment called Old/.venv.

Run:

& "..\Old\.venv\Scripts\python.exe" --version

Install the required dependencies:

& "..\Old\.venv\Scripts\python.exe" -m pip install requests beautifulsoup4 python-dotenv feedparser langchain langchain-community langchain-core langchain-openai langchain-chroma langchain-text-splitters

Verify the installation:

& "..\Old\.venv\Scripts\python.exe" -m pip list

7. Configure OpenAI API Key

Create a file named .env in the project directory.

Add:

OPENAI_API_KEY=your_openai_api_key

Replace the placeholder with your actual OpenAI API key.

Important security precautions:

  • Never publish your API key.

  • Never upload .env to GitHub.

  • Never hardcode API keys inside Python source code.

  • Add .env to .gitignore.

8. Understand the Blogger RSS Feed

Instead of scraping the complete Blogger website, we will use its RSS feed.

Our feed URL:

https://rajguptaaws.blogspot.com/feeds/posts/default?alt=rss

The feed contains information about published blog posts.

Examples of information available:

  • Article title

  • Article URL

  • Published date

  • Article content or summary

We can access the RSS feed programmatically using Python.

We will use feedparser to read the feed and Requests to download it.

9. Complete Python Implementation

Create main.py.

We will implement the application step by step.

Step 1: Import required libraries

import os
import hashlib
import requests
import feedparser

from bs4 import BeautifulSoup
from dotenv import load_dotenv

from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter

from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_chroma import Chroma

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

Explanation:

  • requests: Downloads RSS feed content.

  • feedparser: Parses Blogger RSS entries.

  • BeautifulSoup: Extracts readable text from HTML.

  • Document: Creates LangChain document objects.

  • RecursiveCharacterTextSplitter: Splits articles into chunks.

  • OpenAIEmbeddings: Generates vector embeddings.

  • Chroma: Stores and retrieves vectors.

  • ChatOpenAI: Generates AI answers.

Step 2: Load environment variables

load_dotenv()

if not os.getenv("OPENAI_API_KEY"):
    raise ValueError("OPENAI_API_KEY is missing from .env")

This loads the OpenAI API key from the .env file.

Step 3: Define Blogger configuration

BLOG_URL = "https://rajguptaaws.blogspot.com"

FEED_URL = BLOG_URL + "/feeds/posts/default"

PAGE_SIZE = 100

We will retrieve articles in batches of 100.

Step 4: Load blog posts

Create a function to retrieve published blog posts.

def load_blog_posts():

    documents = []

    start_index = 1

    seen_urls = set()

    while True:

        params = {
            "alt": "rss",
            "start-index": start_index,
            "max-results": PAGE_SIZE
        }

        print(f"Fetching blog posts from index {start_index}...")

        response = requests.get(
            FEED_URL,
            params=params,
            timeout=30,
            headers={"User-Agent": "Mozilla/5.0"}
        )

        response.raise_for_status()

        feed = feedparser.parse(response.content)

        entries = feed.entries

        if not entries:
            break

        for entry in entries:

            title = entry.get("title", "Untitled")

            article_url = entry.get("link", BLOG_URL)

            if article_url in seen_urls:
                continue

            seen_urls.add(article_url)

            if entry.get("content"):

                html_content = entry.content[0].value

            else:

                html_content = entry.get("summary", "")

            soup = BeautifulSoup(
                html_content,
                "html.parser"
            )

            content = soup.get_text(
                separator="\n",
                strip=True
            )

            if not content:
                continue

            published = entry.get("published", "")

            document = Document(

                page_content=f"""
Title: {title}

Published: {published}

Content:

{content}
""",

                metadata={
                    "title": title,
                    "source": article_url,
                    "published": published
                }
            )

            documents.append(document)

        if len(entries) < PAGE_SIZE:
            break

        start_index += PAGE_SIZE

    return documents

Explanation:

The load_blog_posts() function performs the following operations:

  1. Connects to the Blogger RSS feed.

  2. Retrieves articles in batches.

  3. Extracts the article title.

  4. Retrieves the article URL.

  5. Extracts article HTML content.

  6. Converts HTML into plain text.

  7. Creates LangChain Document objects.

  8. Stores article metadata.

We use pagination because a blog may contain hundreds of articles.

Step 5: Load the blog documents

documents = load_blog_posts()

print(
    f"\nTotal blog posts loaded: {len(documents)}"
)

if not documents:
    raise RuntimeError(
        "No blog posts found. Check the RSS feed."
    )

Expected output from my implementation:

Fetching blog posts from index 1...
Fetching blog posts from index 101...
Fetching blog posts from index 201...

Total blog posts loaded: 276

The actual count depends on the number of published articles.

Step 6: Split documents into smaller chunks

Large articles should not be passed directly to the retrieval process as a single document.

We divide them into smaller chunks.

splitter = RecursiveCharacterTextSplitter(

    chunk_size=500,

    chunk_overlap=100
)

chunks = splitter.split_documents(documents)

print(
    f"Total chunks created: {len(chunks)}"
)

Explanation:

chunk_size=500

Each chunk contains approximately 500 characters.

chunk_overlap=100

Each consecutive chunk overlaps by approximately 100 characters.

Why do we need chunking?

  • Improves retrieval granularity.

  • Helps retrieve relevant sections.

  • Preserves some context between adjacent chunks.

  • Avoids sending entire articles to the LLM unnecessarily.

Expected output:

Total chunks created: 2773

Step 7: Initialize OpenAI embeddings

Embeddings convert text into numerical vector representations.

Initialize the embedding model:

embeddings = OpenAIEmbeddings(

    model="text-embedding-3-small"

)

Example:

Input:

"What is Kubernetes?"

The embedding model converts the text into a vector representation.

The resulting vector represents semantic information about the text.

We can use these vectors to find semantically related content.

Step 8: Initialize ChromaDB

ChromaDB is our vector database.

We will store the generated vectors and their corresponding document content.

vector_store = Chroma(

    collection_name="raj_blog_rag",

    embedding_function=embeddings,

    persist_directory="./chroma_db"

)

Explanation:

  • collection_name: Logical collection for our blog knowledge.

  • embedding_function: Converts documents into vectors.

  • persist_directory: Stores the database on local disk.

This allows us to reuse the vector database between application executions.

Step 9: Store documents in ChromaDB

We will create deterministic IDs for each chunk.

This prevents the same chunks from being repeatedly added with new random IDs.

chunk_ids = []

for chunk in chunks:

    unique_string = (
        chunk.metadata["source"]
        + "_"
        + chunk.page_content
    )

    chunk_id = hashlib.sha256(
        unique_string.encode("utf-8")
    ).hexdigest()

    chunk_ids.append(chunk_id)

Now insert the documents:

vector_store.add_documents(

    documents=chunks,

    ids=chunk_ids

)

print(
    "Blog data stored in ChromaDB successfully!"
)

Note: For this initial tutorial, the code demonstrates ingestion into a persistent collection. For repeated production ingestion, implement collection synchronization, updating changed articles and deleting outdated chunks. Do not assume that deterministic IDs alone automatically synchronize existing records.

Step 10: Create the retriever

The retriever searches ChromaDB for relevant document chunks.

retriever = vector_store.as_retriever(

    search_kwargs={
        "k": 4
    }

)

The k=4 parameter tells the retriever to return four relevant chunks for each question.

Step 11: Initialize the LLM

We will use GPT-4.1-mini.

llm = ChatOpenAI(

    model="gpt-4.1-mini",

    temperature=0

)

Temperature 0 helps make responses more consistent.

Step 12: Create the RAG prompt

The prompt instructs the LLM to answer using the retrieved blog content.

prompt = ChatPromptTemplate.from_template("""

You are an expert AI assistant for Raj Gupta's
technical blog.

Answer the user's question using the provided context.

Rules:

1. Use the retrieved blog content.
2. Do not invent technical information.
3. If the answer is unavailable, say you do not know.
4. Explain technical concepts clearly.
5. Include relevant source URLs when available.

Context:

{context}

Question:

{question}

Answer:

""")

Step 13: Format retrieved documents

We need to combine retrieved document chunks into a single context string.

def format_docs(docs):

    formatted_docs = []

    for doc in docs:

        content = f"""

Title: {doc.metadata.get('title', '')}

Source: {doc.metadata.get('source', '')}

Content:

{doc.page_content}

"""

        formatted_docs.append(content)

    return "\n\n".join(formatted_docs)

This function also preserves the source URL so the LLM can reference the original article.

Step 14: Implement the RAG question-answering function

We will retrieve relevant documents first and then send them to the LLM.

def ask_question(question):

    # Retrieve relevant documents

    retrieved_docs = retriever.invoke(question)

    if not retrieved_docs:

        return "I could not find relevant information."

    # Format retrieved documents

    context = format_docs(retrieved_docs)

    # Create prompt

    messages = prompt.invoke({

        "context": context,

        "question": question

    })

    # Generate answer

    response = llm.invoke(messages)

    answer = StrOutputParser().invoke(response)

    # Display source references

    print("\nAI Answer:\n")

    print(answer)

    print("\nSources:")

    sources = set()

    for doc in retrieved_docs:

        source = doc.metadata.get("source")

        if source:

            sources.add(source)

    for source in sources:

        print(source)

This approach prints sources directly from the retrieved documents instead of relying entirely on the LLM to reproduce URLs accurately.

Step 15: Create the interactive chatbot

Finally, create an interactive terminal interface.

print("\n====================================")

print("Raj Gupta Blog AI Assistant")

print("====================================")


while True:

    question = input(
        "\nAsk a question (exit to stop): "
    )

    if question.lower().strip() == "exit":

        print("Exiting chatbot...")

        break

    if not question.strip():

        continue

    try:

        ask_question(question)

    except Exception as error:

        print(f"\nError: {error}")

10. Complete main.py

All the previous steps are combined into one Python application.

The complete file contains:

  • RSS feed ingestion

  • Document extraction

  • Text chunking

  • Embedding generation

  • ChromaDB storage

  • Semantic retrieval

  • LLM integration

  • Interactive question answering

  • Source reference display

Use the complete code in the accompanying implementation section of this article.

11. Run the Application

Open PowerShell inside the day6-rag directory.

Execute:

& "..\Old\.venv\Scripts\python.exe" main.py

The application will start fetching blog posts.

Expected output:

Fetching blog posts from index 1...
Fetching blog posts from index 101...
Fetching blog posts from index 201...

Total blog posts loaded: 276

Total chunks created: 2773

Blog data stored in ChromaDB successfully!

====================================
Raj Gupta Blog AI Assistant
====================================

Ask a question (exit to stop):

12. Test the AI Chatbot

Let's test the application with a real question.

Question:

What is RAG?

The chatbot retrieves relevant information from the blog and passes it to GPT-4.1-mini.

Example answer:

RAG stands for Retrieval-Augmented Generation.

It is a method used to improve the responses of a language model by incorporating relevant external information retrieved from a knowledge base or documents.

A typical RAG process is:

User Question → Document Retrieval → Context → LLM → Answer

The chatbot also displays the original blog article reference.

Example source:

https://rajguptaaws.blogspot.com/2026/10/part-2-building-production-style-rag.html

13. How RAG Works Internally

Let's understand the two important phases.

Phase 1: Data Ingestion

This happens before users ask questions.

  1. Fetch blog articles.

  2. Extract article content.

  3. Create document objects.

  4. Split articles into chunks.

  5. Generate embeddings.

  6. Store vectors in ChromaDB.

Phase 2: Query Execution

This happens whenever a user asks a question.

  1. Receive the user question.

  2. Convert the question into an embedding.

  3. Search ChromaDB for similar vectors.

  4. Retrieve the most relevant document chunks.

  5. Construct the prompt with retrieved context.

  6. Send the prompt to GPT-4.1-mini.

  7. Generate the final answer.

  8. Display relevant source URLs.

14. Common Errors and Troubleshooting

Error 1: ModuleNotFoundError: No module named 'feedparser'

Solution:

python -m pip install feedparser

Error 2: ModuleNotFoundError: No module named 'bs4'

Solution:

python -m pip install beautifulsoup4

Error 3: ModuleNotFoundError: No module named 'langchain_chroma'

Solution:

python -m pip install langchain-chroma

Error 4: OpenAI API authentication error

Check whether the API key is correctly configured in the .env file.

Also verify that your OpenAI API account has access and available billing.

Error 5: Blogger RSS feed returns no articles

Verify:

  • Blog is publicly accessible.

  • RSS feed URL is correct.

  • Blogger feed settings permit access.

  • The feed is returning article entries.

Error 6: ChromaDB retrieval returns irrelevant answers

Possible improvements:

  • Experiment with chunk size.

  • Experiment with chunk overlap.

  • Increase the number of retrieved chunks.

  • Improve document metadata.

  • Use a reranker.

  • Evaluate retrieval quality.

15. Production Improvements

The current implementation is a learning-level RAG application.

For a production-grade implementation, consider:

  1. Incremental ingestion instead of processing all articles every time.

  2. Detecting modified and deleted articles.

  3. Metadata filtering.

  4. Hybrid search using keyword and vector retrieval.

  5. Reranking retrieved documents.

  6. Query rewriting.

  7. RAG evaluation using Ragas.

  8. FastAPI integration.

  9. Authentication and authorization.

  10. Logging, monitoring and error handling.

  11. Rate limiting and API cost management.

  12. Deployment using Docker and Kubernetes.

16. Future Enhancements

The next version of this project can include:

  • FastAPI backend.

  • Web-based chatbot UI.

  • LangGraph workflow orchestration.

  • Conversation memory.

  • Multi-user support.

  • Automated blog synchronization.

  • Advanced retrieval techniques.

  • MCP server integration.

  • AI agents for technical troubleshooting.

17. Conclusion

In this project, we successfully built an AI-powered chatbot that uses our own technical blog as its knowledge base.

We explored how Blogger RSS feeds, Python, LangChain, OpenAI embeddings, ChromaDB and GPT-4.1-mini work together to implement a complete Retrieval-Augmented Generation pipeline.

The project demonstrates how an LLM can access custom knowledge beyond its pretrained information and generate answers grounded in retrieved documents.

This is a practical foundation for building enterprise knowledge assistants, internal documentation chatbots, technical support agents and AI-powered information retrieval systems.

Author: Raj Gupta

Technologies: Python | LangChain | RAG | OpenAI | ChromaDB | Generative AI | Vector Database | LLM

Project: AI-Powered Blog Chatbot


No comments:

Post a Comment