Module 7.1 — Production RAG System with LangChain

Building a real, deployable RAG system using the LangChain API


Package Setup — Install Latest Versions


    mkdir langchain-production-rag
    cd langchain-production-rag
    npm init -y

Update package.json:


  {
    "name": "langchain-production-rag",
    "version": "1.0.0",
    "type": "module",
    "scripts": {
      "start": "node src/rag.js",
      "test": "echo \"Error: no test specified\" && exit 1"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "description": "",
    "dependencies": {
      "@langchain/community": "^1.1.29",
      "@langchain/core": "^1.2.3",
      "@langchain/openai": "^1.5.5",
      "@langchain/textsplitters": "^1.0.1",
      "dotenv": "^16.4.5",
      "langchain": "^1.5.3",
      "pdf-parse": "^2.4.5"
    }
  }

Install packages:

npm install

Create .env:

OPENAI_API_KEY=sk-proj-your-key-here

Project Structure

langchain-production-rag/
├── .env
├── package.json
├── sample.pdf          ← any PDF you want to chat with
└── src/
    ├── loader.js       ← load and chunk PDF
    ├── vectorstore.js  ← embed and store chunks
    ├── rag.js          ← complete RAG pipeline + chat
    └── utils.js        ← helper functions

File 1 — src/loader.js


    // ─────────────────────────────────────────
    // IMPORTS
    // ─────────────────────────────────────────

    import { PDFLoader } from "@langchain/community/document_loaders/fs/pdf";
    // PDFLoader = reads a PDF file and returns Document objects
    // Each Document has: { pageContent: "text...", metadata: { source, page } }
    // Comes from @langchain/community package

    import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
    // RecursiveCharacterTextSplitter = splits large text into smaller chunks
    // "Recursive" = tries multiple separators in order:
    //   1. "\n\n" (paragraph breaks) — cleanest split
    //   2. "\n"   (line breaks)
    //   3. ". "   (sentence ends)
    //   4. " "    (word boundaries)
    //   5. ""     (character by character — last resort)
    // From @langchain/textsplitters — separate package from core langchain

    // ─────────────────────────────────────────
    // FUNCTION: loadAndChunkPDF
    // Loads a PDF and splits it into chunks
    // Returns: array of Document objects (small chunks)
    // ─────────────────────────────────────────

    export async function loadAndChunkPDF(filePath, options = {}) {

        const {
            chunkSize = 800,
            // target characters per chunk
            // 800 chars ≈ 150-200 words
            // smaller = more precise retrieval
            // larger  = more context per chunk

            chunkOverlap = 150,
            // characters repeated between consecutive chunks
            // prevents losing context at chunk boundaries
            // example: end of chunk 1 = start of chunk 2 (150 chars)
        } = options;

        console.log(`\n📄 Loading PDF: ${filePath}`);

        // STEP 1 — Load PDF
        const loader = new PDFLoader(filePath, {
            splitPages: true,
            // true = each page becomes a separate Document
            // false = entire PDF is one Document
            // true is better — keeps page numbers in metadata
        });

        const rawDocs = await loader.load();
        // rawDocs = array of Documents, one per page
        // example for a 5-page PDF:
        // [
        //   { pageContent: "page 1 text...", metadata: { source: "file.pdf", page: 0 } },
        //   { pageContent: "page 2 text...", metadata: { source: "file.pdf", page: 1 } },
        //   ...
        // ]

        console.log(`   Loaded ${rawDocs.length} pages`);
        console.log(`   Total characters: ${rawDocs.reduce((sum, d) => sum + d.pageContent.length, 0)}`);

        // STEP 2 — Split into chunks
        const splitter = new RecursiveCharacterTextSplitter({
            chunkSize,
            chunkOverlap,
        });

        const chunks = await splitter.splitDocuments(rawDocs);
        // splitDocuments = takes array of Documents
        // returns array of SMALLER Documents
        // automatically preserves metadata (source, page) in each chunk
        // example result:
        // [
        //   { pageContent: "first 800 chars...", metadata: { source: "file.pdf", page: 0 } },
        //   { pageContent: "overlap + next 800 chars...", metadata: { source: "file.pdf", page: 0 } },
        //   { pageContent: "page 2 content...", metadata: { source: "file.pdf", page: 1 } },
        //   ...
        // ]

        console.log(`   Split into ${chunks.length} chunks`);
        console.log(`   Avg chunk size: ${Math.round(
            chunks.reduce((sum, c) => sum + c.pageContent.length, 0) / chunks.length
        )} chars\n`);

        return chunks;
        // returns array of chunk Documents ready to be embedded
    }


File 2 — src/vectorstore.js


    // ─────────────────────────────────────────
    // IMPORTS
    // ─────────────────────────────────────────

    import { OpenAIEmbeddings } from "@langchain/openai";
    // OpenAIEmbeddings = calls OpenAI API to convert text → vector
    // Model: text-embedding-3-small → 1536 numbers per text
    // Each number captures some aspect of meaning

    import { MemoryVectorStore } from "@langchain/classic/vectorstores/memory";
    // MemoryVectorStore = stores vectors in JavaScript array (RAM)
    // No server needed — perfect for development
    // Data lost when process stops (in-memory only)
    // For production: replace with Pinecone, Chroma, or pgvector

    import * as dotenv from "dotenv";
    dotenv.config();

    // ─────────────────────────────────────────
    // SETUP — Embeddings model
    // Created once, reused everywhere
    // ─────────────────────────────────────────

    const embeddings = new OpenAIEmbeddings({
        model: "text-embedding-3-small",
        // text-embedding-3-small:
        //   → 1536 dimensions
        //   → $0.02 per 1M tokens (very cheap)
        //   → good quality for most RAG use cases
        //
        // text-embedding-3-large (alternative):
        //   → 3072 dimensions
        //   → better quality, more expensive
        //   → use when retrieval quality needs improvement

        apiKey: process.env.OPENAI_API_KEY,
    });

    // ─────────────────────────────────────────
    // FUNCTION: buildVectorStore
    // Takes chunks, embeds them, stores them
    // Returns: searchable vector store
    // ─────────────────────────────────────────

    export async function buildVectorStore(chunks) {

        console.log(`💾 Embedding ${chunks.length} chunks...`);
        console.log(`   Calling OpenAI API ${chunks.length} times (one per chunk)`);
        console.log(`   Estimated cost: ~$${(chunks.length * 0.000002).toFixed(4)}\n`);
        // rough cost estimate: ~0.002 cents per chunk

        const vectorStore = await MemoryVectorStore.fromDocuments(
            chunks,
            // array of Document objects to embed and store
            // each Document's .pageContent gets converted to a vector

            embeddings,
            // the embedding model to use
            // MemoryVectorStore calls embeddings.embedDocuments(texts) internally
            // which batches all texts and calls OpenAI in one/few API calls
        );

        // What fromDocuments() does internally:
        // 1. Extracts .pageContent from each chunk
        // 2. Calls OpenAI embeddings API → gets 1536 numbers for each
        // 3. Stores { vector, pageContent, metadata } for each chunk
        // 4. Returns the complete searchable store

        console.log(`   ✅ Vector store built — ${chunks.length} chunks indexed\n`);

        return vectorStore;
        // returns MemoryVectorStore object
        // has .similaritySearch(), .similaritySearchWithScore(), .asRetriever()
    }

    // ─────────────────────────────────────────
    // FUNCTION: createRetriever
    // Converts vector store to a retriever
    // A retriever = object you call with a question → get relevant chunks
    // ─────────────────────────────────────────

    export function createRetriever(vectorStore, options = {}) {

        const {
            k = 5,
            // how many chunks to return per search
            // k=5 means top 5 most similar chunks
            // increase if answers are incomplete
            // decrease if answers are noisy

            searchType = "similarity",
            // "similarity" = cosine similarity (default, most common)
            // "mmr"        = Maximum Marginal Relevance
            //   MMR picks diverse results — avoids returning
            //   5 chunks that all say the same thing
            //   better for broad questions
        } = options;

        const retriever = vectorStore.asRetriever({
            k,
            searchType,
        });
        // asRetriever() = wraps vector store in a Retriever interface
        // Retriever has one job: .invoke(question) → returns Documents
        // Can be used in chains: retriever.pipe(formatDocs).pipe(llm)

        return retriever;
    }


File 3 — src/utils.js


    // ─────────────────────────────────────────
    // UTILITY FUNCTIONS
    // Small helper functions used across the app
    // ─────────────────────────────────────────

    // ─────────────────────────────────────────
    // FUNCTION: formatDocsForPrompt
    // Converts retrieved Document array into
    // a clean string for LLM prompt injection
    // ─────────────────────────────────────────

    export function formatDocsForPrompt(docs) {
        // docs = array of Document objects from retriever
        // example input:
        // [
        //   { pageContent: "Aspirin reduces fever...", metadata: { source: "handbook.pdf", page: 2 } },
        //   { pageContent: "Common side effects...",  metadata: { source: "handbook.pdf", page: 3 } },
        // ]

        return docs
            .map((doc, index) => {
                const page = doc.metadata.page !== undefined
                    ? `Page ${doc.metadata.page + 1}`
                    : "Unknown page";
                // page + 1 because PDFs are 0-indexed internally
                // "page: 0" in metadata = Page 1 in the actual document

                const source = doc.metadata.source
                    ? doc.metadata.source.split("/").pop()
                    : "Unknown source";
                // .split("/").pop() = get just filename from full path
                // "./documents/handbook.pdf" → "handbook.pdf"

                return `[Source ${index + 1}${source}, ${page}]\n${doc.pageContent}`;
                // example output for one chunk:
                // "[Source 1 — handbook.pdf, Page 3]
                //  Aspirin is commonly used to reduce fever and pain..."
            })
            .join("\n\n" + "".repeat(40) + "\n\n");
        // join chunks with a clear visual separator
        // makes it easy for LLM to distinguish between sources
    }


    // ─────────────────────────────────────────
    // FUNCTION: getLastAIMessage
    // Extracts final answer from agent result
    // ─────────────────────────────────────────

    export function getLastAIMessage(result) {
        // result = object returned by agent.invoke()
        // result.messages = full array of all messages in this run:
        //   HumanMessage  → user's question
        //   AIMessage     → LLM thinking / tool call decision
        //   ToolMessage   → result from calling a tool
        //   AIMessage     → final answer (last message)
        //
        // We want ONLY the last message — the final answer

        const messages = result.messages;
        // example messages array:
        // [
        //   HumanMessage { content: "What are aspirin side effects?" },
        //   AIMessage    { content: "", tool_calls: [{name:"search_docs",...}] },
        //   ToolMessage  { content: "Aspirin can cause stomach pain..." },
        //   AIMessage    { content: "According to Source 1, aspirin can cause..." }
        // ]

        const lastMessage = messages[messages.length - 1];
        // messages.length - 1 = index of last message
        // This is always the AI's final response to the user

        return lastMessage.content;
        // .content = the actual text string
        // example: "According to Source 1 (handbook.pdf, Page 3),
        //            aspirin commonly causes stomach irritation..."
    }


    // ─────────────────────────────────────────
    // FUNCTION: printDivider
    // Just prints a clean separator line
    // ─────────────────────────────────────────

    export function printDivider(char = "", length = 55) {
        console.log(char.repeat(length));
    }


File 4 — src/rag.js — The Complete Pipeline


    // ─────────────────────────────────────────
    // IMPORTS
    // ─────────────────────────────────────────

    import { ChatOpenAI } from "@langchain/openai";
    // ChatOpenAI = wrapper for OpenAI chat models
    // Used to generate the final answer

    import { createAgent, tool } from "langchain";
    // createAgent = API for building agents
    // tool = function to wrap JS functions into LangChain tools

    import { HumanMessage } from "@langchain/core/messages";
    // HumanMessage = represents a user message
    // Used when calling agent.invoke()

    import { z } from "zod";
    // z = zod schema library
    // Defines what inputs each tool accepts

    import { InMemoryChatMessageHistory } from "@langchain/core/chat_history";
    // InMemoryChatMessageHistory = stores conversation messages in RAM
    // Each conversation has its own history object
    // Lost when process stops — fine for this demo

    import * as readline from "readline";
    // readline = Node.js built-in module for reading terminal input
    // Used to create the interactive chat interface

    import * as dotenv from "dotenv";
    import { loadAndChunkPDF } from "./loader.js";
    import { buildVectorStore, createRetriever } from "./vectorstore.js";
    import { formatDocsForPrompt, getLastAIMessage, printDivider } from "./utils.js";

    dotenv.config();
    // loads OPENAI_API_KEY from .env into process.env


    // ─────────────────────────────────────────
    // STEP 1 — INDEX THE PDF
    // Load, chunk, embed, store
    // Done ONCE at startup
    // ─────────────────────────────────────────

    async function indexPDF(filePath) {
        console.log("\n" + "=".repeat(55));
        console.log("📚 INDEXING PDF");
        console.log("=".repeat(55));

        const chunks = await loadAndChunkPDF(filePath, {
            chunkSize: 800,
            chunkOverlap: 150,
        });
        // chunks = array of Document objects (small pieces of the PDF)
        // example: 38 chunks for a 12-page PDF

        const vectorStore = await buildVectorStore(chunks);
        // vectorStore = searchable store with all chunks embedded
        // Calling OpenAI API 38 times (one per chunk) internally

        const retriever = createRetriever(vectorStore, {
            k: 5,
            // return top 5 most relevant chunks per question
            searchType: "similarity",
            // use cosine similarity for search
        });
        // retriever = simplified interface to search the vector store
        // retriever.invoke("question") → top 5 matching chunks

        console.log("=".repeat(55));
        console.log("✅ PDF indexed and ready!\n");

        return retriever;
    }


    // ─────────────────────────────────────────
    // STEP 2 — CREATE THE RETRIEVER TOOL
    // Wraps the retriever as a tool so the agent can call it
    // ─────────────────────────────────────────

    function createRetrieverTool(retriever) {

        const searchDocumentsTool = tool(
            async ({ query }) => {
                // This function runs when the agent decides to search the document
                // query = the search query the agent generates
                // example: "side effects of aspirin" or "recommended dosage"

                console.log(`\n   🔍 Searching document for: "${query}"`);

                const docs = await retriever.invoke(query);
                // retriever.invoke() = embed the query + find similar chunks
                // docs = array of top 5 most relevant Document objects
                // example:
                // [
                //   { pageContent: "Aspirin side effects include...", metadata: {...} },
                //   { pageContent: "Common reactions to aspirin...", metadata: {...} },
                //   ...
                // ]

                if (docs.length === 0) {
                    return "No relevant information found in the document for this query.";
                    // return message that agent can use to tell user nothing was found
                }

                const formatted = formatDocsForPrompt(docs);
                // formatted = clean string with source numbers and page references
                // example:
                // "[Source 1 — handbook.pdf, Page 3]
                //  Aspirin is commonly used to reduce fever...
                //  ────────────────────────────────────────
                //  [Source 2 — handbook.pdf, Page 4]
                //  Common side effects include stomach pain..."

                console.log(`   Found ${docs.length} relevant sections`);

                return formatted;
                // agent receives this formatted text as the tool result
                // agent then uses it to compose the final answer
            },

            {
                name: "search_document",
                // name the agent uses to call this tool
                // should be descriptive and unique

                description: `Search the indexed PDF document for information relevant to a query.
                Use this tool EVERY TIME the user asks a question about the document.
                The tool returns relevant text sections with source and page references.
                Always base your answer on what this tool returns — never answer from your own knowledge.`,
                // description = how the agent decides WHEN to use this tool
                // Key: "EVERY TIME the user asks a question" ensures agent always retrieves
                // without this, agent might answer from its own knowledge (hallucination)

                schema: z.object({
                    query: z.string()
                        .describe("the search query to find relevant information in the document"),
                    // example query: "What are the main topics?" or "How does X work?"
                }),
            }
        );

        return searchDocumentsTool;
    }


    // ─────────────────────────────────────────
    // STEP 3 — BUILD THE RAG AGENT
    // Agent = LLM + retriever tool + system prompt
    // Uses 2025 new createAgent API
    // ─────────────────────────────────────────

    function buildRAGAgent(retrieverTool) {

        const agent = createAgent({
            model: new ChatOpenAI({
                model: "gpt-4o",
                temperature: 0.1,
                // low temperature = factual, consistent answers
                // 0.1 (not 0) = slight natural variation in phrasing
                // good for RAG — we want accuracy not creativity
            }),

            tools: [retrieverTool],
            // only one tool: the document search tool
            // agent will ALWAYS call this before answering

            systemPrompt: `You are a helpful assistant that answers questions about a specific document.

                STRICT RULES — follow these exactly:
                1. ALWAYS use the search_document tool to find information before answering
                2. Base your answer ONLY on what the tool returns — never use your own knowledge
                3. If the tool returns no relevant information — say "I couldn't find information about this in the document"
                4. Always mention which Source number your answer comes from (Source 1, Source 2, etc.)
                5. If the user asks something unrelated to the document — politely say this tool is for document Q&A only
                6. Keep answers clear, concise, and well-structured

                Format your answers like this:
                - Start with a direct answer
                - Support with evidence from the sources
                - End with the source reference: "Source: [Source N, filename, Page X]"`,
            // systemPrompt defines agent behavior
            // The ALWAYS rule prevents the agent from skipping retrieval
            // Source citation makes answers verifiable
        });

        return agent;
    }


    // ─────────────────────────────────────────
    // STEP 4 — CONVERSATION HISTORY
    // Each user session gets its own history
    // Messages stored in memory (in-memory only)
    // ─────────────────────────────────────────

    const conversationHistories = {};
    // conversationHistories = object storing history per session
    // key   = session ID (string)
    // value = InMemoryChatMessageHistory object
    //
    // Example after a few turns:
    // {
    //   "session_abc": InMemoryChatMessageHistory {
    //     messages: [
    //       HumanMessage { content: "What is this about?" },
    //       AIMessage    { content: "This document covers..." },
    //       HumanMessage { content: "Tell me more about chapter 2" },
    //       AIMessage    { content: "Chapter 2 discusses..." }
    //     ]
    //   }
    // }

    function getHistory(sessionId) {
        if (!conversationHistories[sessionId]) {
            conversationHistories[sessionId] = new InMemoryChatMessageHistory();
            // create fresh empty history for new session
        }
        return conversationHistories[sessionId];
    }


    // ─────────────────────────────────────────
    // STEP 5 — ASK ONE QUESTION
    // Takes user question, runs agent, returns answer
    // Also maintains conversation history
    // ─────────────────────────────────────────

    async function askQuestion(agent, question, sessionId) {

        // Get conversation history for this session
        const history = getHistory(sessionId);
        const pastMessages = await history.getMessages();
        // pastMessages = array of all previous HumanMessage + AIMessage objects
        // example for second question in conversation:
        // [
        //   HumanMessage { content: "What is this document about?" },
        //   AIMessage    { content: "This document is about..." }
        // ]

        // Build the messages array to send to agent
        // Include all past messages + current question
        const messages = [
            ...pastMessages,
            // spread past messages first (conversation history)

            new HumanMessage(question),
            // add current question as the last message
        ];
        // example combined messages for second question:
        // [
        //   HumanMessage { content: "What is this document about?" },  ← past
        //   AIMessage    { content: "This document is about..." },      ← past
        //   HumanMessage { content: "Can you give more details?" }      ← current
        // ]

        // Run the agent
        const result = await agent.invoke({ messages });
        // agent receives all messages
        // internally:
        //   1. LLM reads messages + system prompt
        //   2. LLM decides to call search_document tool
        //   3. Tool searches vector store, returns relevant chunks
        //   4. LLM reads chunks, composes grounded answer
        //   5. Returns final AIMessage

        const answer = getLastAIMessage(result);
        // extract just the text from the last AIMessage in result.messages

        // Save this exchange to history for next question
        await history.addMessage(new HumanMessage(question));
        // save user's question

        const { AIMessage } = await import("@langchain/core/messages");
        await history.addMessage(new AIMessage(answer));
        // save AI's answer
        // next question will include both of these in context

        return answer;
    }


    // ─────────────────────────────────────────
    // STEP 6 — INTERACTIVE CHAT LOOP
    // Terminal interface for asking questions
    // ─────────────────────────────────────────

    async function startChat(agent) {

        const rl = readline.createInterface({
            input: process.stdin,
            // read from keyboard
            output: process.stdout,
            // write to terminal screen
        });

        function prompt(question) {
            return new Promise(resolve => rl.question(question, resolve));
            // wraps rl.question in a Promise so we can use await
            // rl.question("You: ", callback) → await prompt("You: ")
        }

        const sessionId = `session_${Date.now()}`;
        // unique ID for this conversation session
        // Date.now() = milliseconds since epoch → guaranteed unique
        // example: "session_1752672000000"

        printDivider("=");
        console.log("🤖 PDF CHATBOT — Ready to answer your questions");
        console.log('   Type "quit" to exit | Type "clear" to reset history');
        printDivider("=");
        console.log();

        while (true) {
            const userInput = await prompt("You: ");
            // wait for user to type something and press Enter
            // userInput = exactly what they typed

            const trimmed = userInput.trim();
            // remove leading/trailing whitespace

            if (!trimmed) continue;
            // skip if user just pressed Enter without typing

            if (trimmed.toLowerCase() === "quit") {
                console.log("\n👋 Goodbye!\n");
                rl.close();
                break;
                // exit the while loop → end program
            }

            if (trimmed.toLowerCase() === "clear") {
                // reset conversation history
                if (conversationHistories[sessionId]) {
                    await conversationHistories[sessionId].clear();
                    // .clear() = removes all stored messages
                }
                console.log("🔄 Conversation history cleared\n");
                continue;
            }

            try {
                console.log("\n🤔 Searching document and generating answer...\n");

                const answer = await askQuestion(agent, trimmed, sessionId);
                // runs the full RAG pipeline for this question

                printDivider();
                console.log(`📝 Answer:\n\n${answer}`);
                printDivider();
                console.log();

            } catch (error) {
                console.log(`\n❌ Error: ${error.message}\n`);
                // handle errors gracefully — don't crash the chat loop
            }
        }

        process.exit(0);
    }


    // ─────────────────────────────────────────
    // MAIN — Entry point
    // Ties everything together
    // ─────────────────────────────────────────

    async function main() {

        const pdfPath = process.argv[2] || "./sample.pdf";
        // process.argv[2] = first command line argument
        // node src/rag.js ./my-document.pdf → pdfPath = "./my-document.pdf"
        // node src/rag.js                   → pdfPath = "./sample.pdf" (default)

        console.log("\n" + "=".repeat(55));
        console.log("🚀 PRODUCTION RAG SYSTEM");
        console.log("   Powered by LangChain 1.5.x + GPT-4o");
        console.log("=".repeat(55));

        try {
            // Step 1: Index the PDF (runs once at startup)
            const retriever = await indexPDF(pdfPath);

            // Step 2: Create the retriever tool
            const retrieverTool = createRetrieverTool(retriever);

            // Step 3: Build the RAG agent
            const agent = buildRAGAgent(retrieverTool);

            // Step 4: Start the interactive chat
            await startChat(agent);

        } catch (error) {
            if (error.message.includes("ENOENT")) {
                // ENOENT = file not found error
                console.log(`\n❌ PDF not found: ${pdfPath}`);
                console.log("Usage: node src/rag.js ./your-document.pdf\n");
            } else {
                console.log(`\n❌ Error: ${error.message}`);
            }
            process.exit(1);
        }
    }

    main().catch(console.error);


Run It


    # With default sample.pdf
    node src/rag.js


    # With a specific PDF
    node src/rag.js ./your-document.pdf


Expected Output

=======================================================
🚀 PRODUCTION RAG SYSTEM
   Powered by LangChain 1.5.x + GPT-4o
=======================================================

=======================================================
📚 INDEXING PDF
=======================================================

📄 Loading PDF: ./sample.pdf
   Loaded 8 pages
   Total characters: 24,832

   Splitting into chunks...
   Split into 32 chunks
   Avg chunk size: 776 chars

💾 Embedding 32 chunks...
   Calling OpenAI API 32 times (one per chunk)
   Estimated cost: ~$0.0001

   ✅ Vector store built — 32 chunks indexed

=======================================================
✅ PDF indexed and ready!

=======================================================
🤖 PDF CHATBOT — Ready to answer your questions
   Type "quit" to exit | Type "clear" to reset history
=======================================================

You: What is this document about?

🤔 Searching document and generating answer...

   🔍 Searching document for: "main topic overview"
   Found 5 relevant sections

───────────────────────────────────────────────────────
📝 Answer:

Based on the document, this appears to be a comprehensive 
guide covering AI engineering fundamentals. It discusses 
topics including machine learning concepts, neural networks, 
and practical applications.

Source: [Source 1 — sample.pdf, Page 1]
───────────────────────────────────────────────────────

You: What are the key concepts covered?

🤔 Searching document and generating answer...

   🔍 Searching document for: "key concepts topics covered"
   Found 5 relevant sections

───────────────────────────────────────────────────────
📝 Answer:

According to the document, the key concepts include:
1. Embeddings and vector representations (Source 1, Page 2)
2. Neural network architectures (Source 2, Page 3)
3. Training and fine-tuning methods (Source 3, Page 4)

Source: [Sources 1-3 — sample.pdf, Pages 2-4]
───────────────────────────────────────────────────────

You: quit

👋 Goodbye!

How the Full Pipeline Works

User question
      ↓
agent.invoke({ messages: [...history, HumanMessage] })
      ↓
GPT-4o reads system prompt + history + question
      ↓
GPT-4o decides: "I need to search the document"
      ↓
Calls search_document tool with a search query
      ↓
Tool embeds query → searches vector store
      ↓
Returns top 5 relevant chunks (formatted with sources)
      ↓
GPT-4o reads chunks → composes grounded answer
      ↓
Answer saved to history for next question
      ↓
User sees answer with source citations

3-Line Summary

  1. The LangChain API uses createAgent from "langchain" — it handles the agent loop internally and takes model, tools, and systemPrompt as config.
  2. The RAG pipeline wraps the vector store retriever as a tool — the agent automatically calls it for every user question, gets the relevant chunks back, and uses them to compose a grounded answer with source citations.
  3. Conversation history is stored per session using InMemoryChatMessageHistory — each question includes all past messages so the agent understands follow-up questions that reference previous answers.

Module 7.1 — Complete ✅

Coming up — Module 7.2 — Streaming, Error Handling & API Layer

We take this RAG system and make it production-ready — add streaming so responses appear word by word, proper error handling for every failure point, and wrap it in an Express API so it can be called from a Next.js frontend.

No comments:

Post a Comment

Module 7.1 — Production RAG System with LangChain

Building a real, deployable RAG system using the LangChain API Package Setup — Install Latest Versions     mkdir langchain-production-ra...