LangChain LangGraph
Module 1 Module 2 Module 3
Module 2 · Tools, State and Multi-Agent
1 2.1 MCP
2 2.1 Travel Agent
3 2.2 Runtime Context
4 2.2 State
5 2.3 Multi Agent
6 2.4 Wedding Planners
7 Bonus: RAG
8 Bonus: SQL
SUM Module Summary
HomeLangChainModule 2 · Tools, State and Multi-AgentBonus: RAG
📓 Notebook 📖 Explained
LANGCHAIN MODULE 2 · BONUS RAG

Bonus RAG: letting an Agent query a PDF handbook
Load · Split · Embed · Retrieve

This section builds a minimal RAG process from the employee handbook PDF: loading the document, segmenting the text, generating embedding, writing to the vector library, and packaging the Put retrieval capability into an Agent tool.

1 The six-step RAG flow

The complete process is listed at the beginning of the Notebook. The essence of RAG is: first put external data into a searchable index, when users ask questions, first retrieve relevant fragments, and then let the model answer based on the fragments.

1. Upload

Prepare PDF file.

2. Split

Put long documents into chunks.

3. Embed

Put chunk into vector.

4. Store

Write to vector store.

5. Retrieve

Do a similarity search by question.

6. Answer

Agent makes Use search results answer.

2 Load the PDF

This lesson uses PyPDFLoader to read resources/acmecorp-employee-handbook.pdf. The loading result is a set of LangChain Documents, each Document containing text and metadata.

Python
from langchain_community.document_loaders import PyPDFLoader

loader = PyPDFLoader("resources/acmecorp-employee-handbook.pdf")
data = loader.load()

print(data)
Document

A LangChain Document usually contains page_content and metadata. RAG retrieval mainly uses page_content.

3 Chunk the document

LLMs and embedding models are not suited to processing the whole handbook at once, so the notebook first uses a recursive splitter to cut the text into chunks. chunk_overlap reduces the chance that cross-section information is cut off.

Python
from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    add_start_index=True,
)
all_splits = text_splitter.split_documents(data)

print(len(all_splits))
ParameterMeaning
chunk_size=1000Target length of about 1000 characters per text chunk
chunk_overlap=200Keep 200 overlapping characters between adjacent chunks
add_start_index=TrueRecord each chunk's start position in metadata

4 HashingEmbeddings in the course

The notebook defines a local HashingEmbeddings implementation. It does not depend on an external embedding API, so it suits course demos and offline environments, but its semantic quality is lower than a real embedding model.

Python
import hashlib
import math
import re
from langchain_core.embeddings import Embeddings

class HashingEmbeddings(Embeddings):
    def __init__(self, size: int = 384):
        self.size = size

    def _embed(self, text: str) -> list[float]:
        # Convert tokens into a fixed-size hashed vector
        ...

embeddings = HashingEmbeddings()
Demo implementation

Hashing embeddings can run the RAG flow end to end, but production systems usually switch to OpenAI, Voyage, Cohere or a local semantic embedding model.

5 Write to the vector store and test retrieval

This lesson uses InMemoryVectorStore. It is simple and needs no server, which suits a notebook, but data disappears when the process ends.

Python
from langchain_core.vectorstores import InMemoryVectorStore

vector_store = InMemoryVectorStore(embeddings)
ids = vector_store.add_documents(documents=all_splits)

results = vector_store.similarity_search(
    "How many days of vacation does an employee get in their first year?"
)

print(results[0])
Retrieval before generation

A RAG Agent's answer quality first depends on retrieving the right snippets. If results[0] is irrelevant, even a strong downstream LLM can answer incorrectly.

6 Wrap retrieval as an Agent tool

Finally, vector retrieval is wrapped as the search_handbook tool and given to the Agent. When the user asks about the handbook, the model can call the tool first, then answer from the returned snippet.

Python
@tool
def search_handbook(query: str) -> str:
    """Search the employee handbook for information"""
    results = vector_store.similarity_search(query)
    return results[0].page_content

agent = create_agent(
    model=deepseek_model(),
    tools=[search_handbook],
    system_prompt="You are a helpful agent that can search the employee handbook for information.",
)

response = agent.invoke(
    {"messages": [HumanMessage(content="How many days of vacation does an employee get in their first year?")]}
)

print(response["messages"][-1].content)
Next improvements

You can return multiple top-k snippets, include source page numbers, return an explicit error when there are no results, and replace InMemoryVectorStore with a persistent vector database.