Asking Questions with Retrieved Context and Templates

Welcome to the final lesson of this course! In this lesson, we will integrate context retrieval with a chat model using LangChain in TypeScript. Building on your previous experience with document embeddings and similarity search, you’ll now learn how to use prompt templates to format messages with additional context. This enables you to ask questions and receive answers based on the content retrieved from your documents. By the end of this lesson, you’ll have a comprehensive understanding of document processing and retrieval workflows using LangChain in TypeScript.

Quick Reminder: Preparing Documents and Creating a Vector Store

Let’s quickly review the process of preparing documents and creating a vector store in TypeScript. We’ll load and process our document, "The Adventure of the Blue Carbuncle," generate embeddings, and store them in a FAISS vector database. This setup is essential for efficient context retrieval.

import { PDFLoader } from "@langchain/community/document_loaders/fs/pdf";
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
import { OpenAIEmbeddings } from "@langchain/openai";
import { FaissStore } from "@langchain/community/vectorstores/faiss";
import { Document } from "@langchain/core/documents";

// Define the file path with a type annotation
const filePath: string = "data/the_adventure_of_the_blue_carbuncle.pdf";

// Create a loader for our document
const loader = new PDFLoader(filePath);

// Load the document and ensure type safety
const docs: Document[] = await loader.load();

// Split the document into manageable chunks
const textSplitter = new RecursiveCharacterTextSplitter({
  chunkSize: 1000,
  chunkOverlap: 100
});
const splitDocs: Document[] = await textSplitter.splitDocuments(docs);

// Create a vector store for all the document chunks
const embeddingModel = new OpenAIEmbeddings();
const vectorstore = await FaissStore.fromDocuments(splitDocs, embeddingModel);

In this code, we load a document, split it into chunks, generate embeddings, and create a vector store, setting the stage for efficient context retrieval in our question-answering tasks.

Combining Retrieved Context

With our vector store ready, we can now retrieve relevant context for a given question. We’ll define a question, perform a similarity search, and combine the content of the most relevant document chunks to form a context string.

// Define a question with a type annotation
const question: string = "From whom was the stone stolen?";

// Retrieve the top three most relevant document chunks
const retrievedDocs: Document[] = await vectorstore.similaritySearch(question, 3);

// Combine the content of the retrieved documents into a single context string
const context: string = retrievedDocs.map((doc: Document) => doc.pageContent).join("\n\n");

Here, we use TypeScript’s type annotations to clarify the types of our variables. The context variable now contains the combined content of the most relevant document chunks, ready to be used in our prompt.

Formatting Messages with Templates

To interact effectively with the chat model, we need to format our messages using prompt templates. In TypeScript, we use the ChatPromptTemplate class to define a structured prompt with placeholders for the context and question. This approach ensures type safety and clarity in how we construct our prompts.

import { ChatPromptTemplate } from "@langchain/core/prompts";

// Create a prompt template with placeholders for context and question
const promptTemplate = ChatPromptTemplate.fromTemplate(
  "Answer the following question based on the provided context.\n\n" +
  "Context:\n{context}\n\n" +
  "Question: {question}"
);

In this code, we define a prompt template as a string with {context} and {question} placeholders. The ChatPromptTemplate class manages the structure and ensures that the correct values are inserted.

To fill in the template, we use the format method, passing an object with the required properties:

// Format the prompt with our context and question
const prompt: string = await promptTemplate.format({
  context,
  question
});

This approach leverages TypeScript’s type checking to ensure that the correct data is provided to the template, resulting in a well-structured prompt for the chat model.

To verify the structure of the formatted prompt, you can print it:

// Print the formatted prompt
console.log(prompt);

The output will look similar to:

Answer the following question based on the provided context.

Context:
the back yard and smoked a pipe and wondered
what it would be best to do.
“I had a friend once called Maudsley, who went
to the bad, and has just been serving his time in
Pentonville. One day he had met me...

Question: From whom was the stone stolen?

This confirms that the context and question are correctly inserted into the template.

Asking a Question with Retrieved Context to a Chat Model

With our prompt ready, we can now interact with the chat model. We’ll instantiate the chat model, invoke it with our formatted prompt, and display the response.

import { ChatOpenAI } from "@langchain/openai";
import { AIMessage } from "@langchain/core/messages";

// Initialize the chat model
const chat = new ChatOpenAI();

// Get the response from the model
const response: AIMessage = await chat.invoke(prompt);

// Print the question and the AI's answer
console.log(`Question: ${question}`);
console.log(`Answer: ${response.content}`);

In this section, we use TypeScript’s type annotations to ensure that the response from the chat model is of type AIMessage. The model processes the prompt and generates an answer based on the provided context and question. By printing both the question and the answer, you can see how the integration of context retrieval and prompt templates leads to accurate and relevant responses.

A typical output might be:

Question: From whom was the stone stolen?
Answer: The stone was stolen from the Countess of Morcar.

This demonstrates how the chat model leverages the retrieved context to provide a precise answer.

Summary and Next Steps

You've successfully completed this lesson, where you learned how to integrate context retrieval with a chat model using LangChain in TypeScript. We explored the use of templates to format messages with extra context, allowing you to ask questions and receive answers based on the retrieved document content. This lesson consolidated all the skills you've acquired so far, equipping you with a solid understanding of document processing and retrieval with LangChain in TypeScript. As you continue your learning journey, consider experimenting with different queries and document types to deepen your understanding. Stay tuned for the next course, where we'll build on these concepts and explore even more advanced techniques.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal