> ## Documentation Index
> Fetch the complete documentation index at: https://ai.tharung.in/llms.txt
> Use this file to discover all available pages before exploring further.

# RAG Summary Code

# RAG Summarization Using LangChain, FAISS, and Hugging Face

```python theme={null}
from transformers import pipeline

from langchain_core.documents import Document
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser

from langchain_text_splitters import RecursiveCharacterTextSplitter

from langchain_community.vectorstores import FAISS

from langchain_huggingface import HuggingFaceEmbeddings, HuggingFacePipeline

# 1. Create a long article

article = """
Retrieval-Augmented Generation, commonly called RAG, is a technique
used to improve the responses generated by language models.

Traditional language models generate answers using information learned
during training. However, the information available during training may
be incomplete, outdated, or unrelated to a specific organization's data.

RAG addresses this limitation by retrieving relevant information from
an external knowledge source before generating a response.

A typical RAG system begins by collecting documents from sources such
as PDFs, websites, databases, documentation, or text files.

Large documents are usually divided into smaller pieces called chunks.
Chunking makes it easier to search for specific information and retrieve
only the most relevant parts of a document.

Each chunk is converted into an embedding. An embedding is a numerical
representation of the meaning of text.

The embeddings are stored inside a vector database or vector store.
FAISS is a popular library that can perform efficient similarity search
over vector embeddings.

When a user asks a question, the question is also converted into an
embedding. The system compares the query embedding with the embeddings
stored in the vector store.

The most similar chunks are retrieved and used as context for the
language model.

The language model receives the retrieved context together with the
user's question or instruction. It then generates a response based on
the provided information.

RAG can reduce hallucinations by grounding the model's response in
retrieved information. However, the quality of the final response still
depends heavily on the quality of document chunking, embeddings, and
retrieval.

Choosing an appropriate chunk size is important. Very large chunks may
contain unnecessary information, while very small chunks may lose
important context.

Chunk overlap can help preserve information that appears near the
boundary between two chunks.

RAG systems are commonly used for document question answering,
enterprise search, knowledge assistants, customer support, and
summarisation.
"""

# 2 convert the article into langchain doc

documents = [Document(page_content=article)]

# 3. create a text splitter

text_splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=50)

# 4. split the article into smaller chunks

chunks = text_splitter.split_documents(documents)

# 5. display the chunks

for index, chunk in enumerate(chunks, start=1):
    print(f"\nChunk {index}:")
    print(chunk.page_content)

# 6. Load the embedding model

embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")

# 7. Create the FAISS vector store

vector_store = FAISS.from_documents(chunks, embeddings)

# 8. create a retriever

retriever = vector_store.as_retriever(search_kwargs={"k": 4})

# 9. get a summarisation topic

query = input("\nEnter a topic to summarize: ")

# 10. retrieve relevant chunks

retrieved_chunks = retriever.invoke(query)

# 11. displaying retrieved chunks

print("\nRetrieved chunks: ")

for rank, chunk in enumerate(retrieved_chunks, start=1):
    print(f"\nRank {rank}:")
    print(chunk.page_content)

# 12. combine retrieved chunks into context

context = "\n\n".join(chunk.page_content for chunk in retrieved_chunks)

# 13. load a local hugging face lang model

hf_pipeline = pipeline(
    task="text-generation", model="distilgpt2", max_new_tokens=150, do_sample=False
)

# 14. convert the hugging face pipeline into a langchain llm

llm = HuggingFacePipeline(pipeline=hf_pipeline)

# 15. create the summarisation prompt

prompt = PromptTemplate.from_template("""
Summarise the provided context.

Focus specifically on the topic given below.

Use only information from the provided context.

Do not add information that is not present in the context.

Topic:
{query}

Context:
{context}

Summary:
""")

# 16. Create the output parser

output_parser = StrOutputParser()

# 17. Build the summarisation chain

chain = prompt | llm | output_parser

# 18. generate the summary

response = chain.invoke({"query": query, "context": context})

# 19. final summary

print("\nFinal Summary: ")
print(response)
```

### Output

```text theme={null}
Final Summary: 

Summarise the provided context.

Focus specifically on the topic given below.

Use only information from the provided context.

Do not add information that is not present in the context.

Topic:
How does RAG work ?

Context:
RAG systems are commonly used for document question answering,
enterprise search, knowledge assistants, customer support, and
summarisation.

RAG addresses this limitation by retrieving relevant information from
an external knowledge source before generating a response.

A typical RAG system begins by collecting documents from sources such
as PDFs, websites, databases, documentation, or text files.

Retrieval-Augmented Generation, commonly called RAG, is a technique
used to improve the responses generated by language models.

RAG can reduce hallucinations by grounding the model's response in
retrieved information. However, the quality of the final response still
depends heavily on the quality of document chunking, embeddings, and
retrieval.

Summary:
RAG may help improve the response rate
for the RAG system, providing a better overall response rate.
RAG should provide more detail on the performance of the RAG process as well as the types of information that can be retrieved from
the web.
All RAG systems are often used for document question answering,
enterprise search, knowledge assistants, customer support, and
summarisation.
Summary:
RAG can reduce hallucinations by grounding the model's response in
retrieved information. However, the quality of the final response stilldepends heavily on the quality of document chunking, embeddings, andretrieval.
RAG should provide more detail on the performance of the RAG process as well
```

## Import 1: `pipeline`

```python theme={null}
from transformers import pipeline
```

This imports the `pipeline` function from the Hugging Face Transformers library.

A pipeline provides an easy way to use a pre-trained model.

In your code, it is used for:

```python theme={null}
pipeline(
    task="text-generation",
    model="distilgpt2"
)
```

Flow:

```text theme={null}
distilgpt2 model
      ↓
Hugging Face pipeline
      ↓
Generate text
```

***

## Import 2: `Document`

```python theme={null}
from langchain_core.documents import Document
```

`Document` is a LangChain object used to store text and metadata.

Example:

```python theme={null}
Document(
    page_content="RAG is used to retrieve relevant information."
)
```

Your article is converted into a `Document` object so that LangChain components can process it.

***

## Import 3: `PromptTemplate`

```python theme={null}
from langchain_core.prompts import PromptTemplate
```

`PromptTemplate` is used to create reusable prompts with variables.

For example:

```python theme={null}
prompt = PromptTemplate.from_template("""
Question: {query}

Context: {context}

Answer:
""")
```

The placeholders:

```text theme={null}
{query}
{context}
```

are replaced with actual values later.

Example:

```text theme={null}
{query}
↓
"Explain RAG"

{context}
↓
Retrieved document chunks
```

***

## Import 4: `StrOutputParser`

```python theme={null}
from langchain_core.output_parsers import StrOutputParser
```

The language model returns an output that needs to be processed.

`StrOutputParser()` converts the model's response into a normal Python string.

Example:

```python theme={null}
output_parser = StrOutputParser()
```

Flow:

```text theme={null}
LLM Output
    ↓
StrOutputParser
    ↓
Python String
```

***

## Import 5: `RecursiveCharacterTextSplitter`

```python theme={null}
from langchain_text_splitters import RecursiveCharacterTextSplitter
```

This is used to divide a large document into smaller pieces called **chunks**.

RAG systems usually do not store one large document as a single piece.

Instead:

```text theme={null}
Large Document
       ↓
Text Splitter
       ↓
Chunk 1
Chunk 2
Chunk 3
Chunk 4
```

`RecursiveCharacterTextSplitter` tries to split text intelligently using separators such as:

```text theme={null}
Paragraph
Sentence
Word
Character
```

This helps preserve meaningful text structure.

***

## Import 6: `FAISS`

```python theme={null}
from langchain_community.vectorstores import FAISS
```

FAISS is used as a **vector store**.

After converting text into embeddings, FAISS stores those embeddings and performs similarity searches.

Example:

```text theme={null}
Chunk 1 → [0.12, 0.45, ...]
Chunk 2 → [0.87, 0.21, ...]
Chunk 3 → [0.34, 0.92, ...]
```

When the user asks a question:

```text theme={null}
"What is chunking?"
```

The question is also converted into an embedding.

FAISS finds the chunks with the most similar meaning.

Flow:

```text theme={null}
User Query
    ↓
Embedding
    ↓
FAISS Similarity Search
    ↓
Most Relevant Chunks
```

***

## Import 7: `HuggingFaceEmbeddings` and `HuggingFacePipeline`

```python theme={null}
from langchain_huggingface import (
    HuggingFaceEmbeddings,
    HuggingFacePipeline
)
```

These classes connect Hugging Face models with LangChain.

### `HuggingFaceEmbeddings`

```python theme={null}
HuggingFaceEmbeddings
```

Used to convert text into embeddings.

### `HuggingFacePipeline`

```python theme={null}
HuggingFacePipeline
```

Used to convert a Hugging Face text-generation pipeline into a LangChain-compatible LLM.

***

# Step 1: Create a long article

```python theme={null}
article = """
...
"""
```

This is your knowledge source.

The article contains information about:

* RAG
* Chunking
* Embeddings
* FAISS
* Retrieval
* Language models
* Hallucinations

In a real RAG application, this information could come from:

```text theme={null}
PDF files
Websites
Databases
Documentation
Text files
Company documents
```

Here, you are manually storing the content in a Python string.

***

# Step 2: Convert the article into a LangChain Document

```python theme={null}
documents = [Document(page_content=article)]
```

LangChain works with `Document` objects.

Your article:

```text theme={null}
RAG is a technique...
```

is converted into:

```text theme={null}
Document(
    page_content=article
)
```

The square brackets:

```python theme={null}
[ ... ]
```

create a list.

So the structure is:

```text theme={null}
documents
    │
    └── Document
          │
          └── article text
```

This is useful because real applications may contain multiple documents.

Example:

```python theme={null}
documents = [
    Document(page_content="Document 1"),
    Document(page_content="Document 2"),
    Document(page_content="Document 3")
]
```

***

# Step 3: Create a text splitter

```python theme={null}
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=300,
    chunk_overlap=50
)
```

This creates an object responsible for splitting the document.

## `chunk_size=300`

Each chunk contains approximately 300 characters.

Conceptually:

```text theme={null}
Original Article
────────────────────────────

Chunk 1
[ 300 characters ]

Chunk 2
[ 300 characters ]

Chunk 3
[ 300 characters ]
```

## `chunk_overlap=50`

The last part of one chunk is repeated in the next chunk.

Example:

```text theme={null}
Chunk 1:

RAG retrieves relevant information from external sources
before generating a response.
                              ↓ overlap

Chunk 2:

from external sources before generating a response.
The retrieved information is then...
```

The overlap helps prevent important context from being lost at chunk boundaries.

***

# Step 4: Split the article into chunks

```python theme={null}
chunks = text_splitter.split_documents(documents)
```

This takes the LangChain document:

```text theme={null}
Large Document
```

and converts it into:

```text theme={null}
Large Document
       ↓
RecursiveCharacterTextSplitter
       ↓
Chunk 1
Chunk 2
Chunk 3
Chunk 4
...
```

Each chunk is also a `Document` object.

For example:

```python theme={null}
Document(
    page_content="RAG is a technique used to..."
)
```

***

# Step 5: Display the chunks

```python theme={null}
for index, chunk in enumerate(chunks, start=1):
    print(f"\nChunk {index}:")
    print(chunk.page_content)
```

This loop displays every chunk.

## `enumerate(chunks, start=1)`

`enumerate()` provides two values:

```text theme={null}
index
chunk
```

Example:

```text theme={null}
index = 1
chunk = first document chunk

index = 2
chunk = second document chunk
```

Using:

```python theme={null}
start=1
```

starts numbering from 1 instead of 0.

Output:

```text theme={null}
Chunk 1:
RAG is a technique...

Chunk 2:
Large documents are divided...

Chunk 3:
Embeddings are numerical...
```

## `chunk.page_content`

Each chunk is a LangChain `Document`.

The actual text is stored inside:

```python theme={null}
chunk.page_content
```

***

# Step 6: Load the embedding model

```python theme={null}
embeddings = HuggingFaceEmbeddings(
    model_name="all-MiniLM-L6-v2"
)
```

This loads the embedding model:

```text theme={null}
all-MiniLM-L6-v2
```

The embedding model converts text into a numerical representation.

Example:

```text theme={null}
"RAG retrieves information"
```

becomes conceptually:

```text theme={null}
[0.12, -0.45, 0.87, 0.21, ...]
```

This numerical representation is called an **embedding**.

Similar meanings produce embeddings that are closer together.

Example:

```text theme={null}
"What is RAG?"
"Explain Retrieval-Augmented Generation"
```

These sentences have similar meanings, so their embeddings should be relatively close.

***

# Step 7: Create the FAISS vector store

```python theme={null}
vector_store = FAISS.from_documents(
    chunks,
    embeddings
)
```

This step does two main things.

### 1. Convert chunks into embeddings

```text theme={null}
Chunk 1
    ↓
Embedding Model
    ↓
Vector 1

Chunk 2
    ↓
Embedding Model
    ↓
Vector 2
```

### 2. Store the embeddings in FAISS

```text theme={null}
FAISS Vector Store

Chunk 1 → Vector
Chunk 2 → Vector
Chunk 3 → Vector
Chunk 4 → Vector
```

FAISS can now efficiently search for similar vectors.

***

# Step 8: Create a retriever

```python theme={null}
retriever = vector_store.as_retriever(
    search_kwargs={"k": 4}
)
```

The retriever provides a simple interface for searching the vector store.

The value:

```python theme={null}
"k": 4
```

means:

```text theme={null}
Retrieve the top 4 most relevant chunks.
```

Flow:

```text theme={null}
User Query
    ↓
Retriever
    ↓
FAISS Search
    ↓
Top 4 Relevant Chunks
```

***

# Step 9: Get a summarization topic

```python theme={null}
query = input(
    "\nEnter a topic to summarize: "
)
```

This asks the user to enter a topic.

Example:

```text theme={null}
Enter a topic to summarize: chunking
```

Then:

```python theme={null}
query
```

contains:

```text theme={null}
"chunking"
```

This query will be used to find relevant chunks.

***

# Step 10: Retrieve relevant chunks

```python theme={null}
retrieved_chunks = retriever.invoke(query)
```

The retriever receives the user's query.

Example:

```text theme={null}
chunking
```

The process is:

```text theme={null}
User Query
    ↓
Convert Query to Embedding
    ↓
Search FAISS
    ↓
Compare with Document Embeddings
    ↓
Return Top 4 Similar Chunks
```

The result is stored in:

```python theme={null}
retrieved_chunks
```

For example:

```text theme={null}
Retrieved Chunk 1:
Large documents are divided into smaller pieces called chunks.

Retrieved Chunk 2:
Choosing an appropriate chunk size is important.

Retrieved Chunk 3:
Chunk overlap helps preserve information.

Retrieved Chunk 4:
...
```

***

# Step 11: Display retrieved chunks

```python theme={null}
print("\nRetrieved chunks: ")
```

This prints a heading.

Then:

```python theme={null}
for rank, chunk in enumerate(
    retrieved_chunks,
    start=1
):
    print(f"\nRank {rank}:")
    print(chunk.page_content)
```

This displays the retrieved chunks.

Example:

```text theme={null}
Rank 1:
Large documents are divided into smaller pieces called chunks.

Rank 2:
Chunk overlap can help preserve information.

Rank 3:
Choosing an appropriate chunk size is important.
```

The `rank` represents the position in the retrieved results.

***

# Step 12: Combine retrieved chunks into context

```python theme={null}
context = "\n\n".join(
    chunk.page_content
    for chunk in retrieved_chunks
)
```

The retrieved chunks are currently separate documents.

Example:

```text theme={null}
Chunk 1

Chunk 2

Chunk 3

Chunk 4
```

The `.join()` operation combines them into one string.

```text theme={null}
Chunk 1

Chunk 2

Chunk 3

Chunk 4
```

The `\n\n` adds two line breaks between chunks.

The final combined result is stored in:

```python theme={null}
context
```

This context will be sent to the language model.

***

# Step 13: Load a local Hugging Face language model

```python theme={null}
hf_pipeline = pipeline(
    task="text-generation",
    model="distilgpt2",
    max_new_tokens=150,
    do_sample=False
)
```

This creates a Hugging Face text-generation pipeline.

## `task="text-generation"`

Tells the pipeline that the model should generate text.

## `model="distilgpt2"`

Loads the DistilGPT-2 model.

## `max_new_tokens=150`

Limits the generated response to approximately 150 new tokens.

## `do_sample=False`

Disables random sampling.

This makes generation more deterministic.

Flow:

```text theme={null}
Prompt
   ↓
DistilGPT-2
   ↓
Generated Text
```

***

# Step 14: Convert the Hugging Face pipeline into a LangChain LLM

```python theme={null}
llm = HuggingFacePipeline(
    pipeline=hf_pipeline
)
```

The Hugging Face pipeline alone is not directly part of the LangChain chain structure.

So this wraps it inside:

```python theme={null}
HuggingFacePipeline
```

Now it can be connected using LangChain's pipe operator:

```python theme={null}
prompt | llm | output_parser
```

***

# Step 15: Create the summarization prompt

```python theme={null}
prompt = PromptTemplate.from_template("""
Summarise the provided context.

Focus specifically on the topic given below.

Use only information from the provided context.

Do not add information that is not present in the context.

Topic:

{query}

Context:

{context}

Summary:

""")
```

This creates a reusable prompt template.

It contains two variables:

```text theme={null}
{query}
{context}
```

Later, this:

```python theme={null}
{
    "query": query,
    "context": context
}
```

replaces the placeholders.

For example:

```text theme={null}
Topic:

chunking

Context:

Large documents are divided into smaller pieces called chunks.

Choosing an appropriate chunk size is important.

Chunk overlap can preserve information.

Summary:
```

This complete prompt is then sent to the language model.

***

# Step 16: Create the output parser

```python theme={null}
output_parser = StrOutputParser()
```

This creates an output parser.

Its purpose is to convert the model output into a clean string.

Flow:

```text theme={null}
LLM Response
      ↓
StrOutputParser
      ↓
Python String
```

The result can then be printed easily.

***

# Step 17: Build the summarization chain

```python theme={null}
chain = prompt | llm | output_parser
```

This is called an **LCEL chain**, where the pipe operator `|` connects components together.

The complete flow is:

```text theme={null}
Input
  ↓
PromptTemplate
  ↓
Hugging Face LLM
  ↓
StrOutputParser
  ↓
Final Response
```

Or:

```text theme={null}
query + context
        ↓
Prompt
        ↓
distilgpt2
        ↓
Output Parser
        ↓
Summary
```

***

# Step 18: Generate the summary

```python theme={null}
response = chain.invoke(
    {
        "query": query,
        "context": context
    }
)
```

The chain receives two inputs.

```python theme={null}
"query": query
```

Example:

```text theme={null}
chunking
```

And:

```python theme={null}
"context": context
```

Example:

```text theme={null}
Large documents are divided into smaller pieces called chunks.

Choosing an appropriate chunk size is important.

Chunk overlap helps preserve context.
```

The values replace:

```text theme={null}
{query}
{context}
```

inside the prompt.

The complete process is:

```text theme={null}
User enters topic
        ↓
Retriever finds relevant chunks
        ↓
Chunks are combined into context
        ↓
Query + Context enter PromptTemplate
        ↓
Prompt sent to DistilGPT-2
        ↓
Model generates summary
        ↓
StrOutputParser processes output
        ↓
Response stored in `response`
```

***

# Step 19: Display the final summary

```python theme={null}
print("\nFinal Summary: ")

print(response)
```

This displays the generated output.

Example:

```text theme={null}
Final Summary:

Chunking is the process of dividing large documents into
smaller pieces. It improves retrieval efficiency and allows
the system to find relevant information. Choosing an appropriate
chunk size and overlap helps preserve context.
```

***

# Complete RAG Flow

Your entire application works like this:

```text theme={null}
                 ARTICLE
                    │
                    ▼
           LangChain Document
                    │
                    ▼
             Text Splitter
                    │
                    ▼
       ┌────────────────────┐
       │       CHUNKS       │
       └────────────────────┘
                    │
                    ▼
            Embedding Model
       all-MiniLM-L6-v2
                    │
                    ▼
              FAISS Store
                    │
                    │
User enters topic ──┘
        │
        ▼
   Query Embedding
        │
        ▼
 Similarity Search
        │
        ▼
Top 4 Relevant Chunks
        │
        ▼
 Combine into Context
        │
        ▼
     PromptTemplate
        │
        ▼
      DistilGPT-2
        │
        ▼
    StrOutputParser
        │
        ▼
    Final Summary
```

This program demonstrates the core **RAG pipeline**:

```text theme={null}
Documents
    ↓
Chunking
    ↓
Embeddings
    ↓
Vector Store
    ↓
Retriever
    ↓
Relevant Context
    ↓
LLM
    ↓
Generated Response
```

One important note: retrieval part is a proper basic RAG workflow, but `distilgpt2` is not specifically trained for instruction-following or summarization. The code can run, but the final summary may sometimes repeat the prompt or produce poor-quality output.
