LangChain Tutorial (TypeScript): handling long documents for intermediate developers
This tutorial shows you how to ingest long documents in TypeScript, split them into manageable chunks, and answer questions over them with LangChain. You need this when a single prompt can’t hold the whole document, or when you want reliable retrieval instead of stuffing everything into context.
What You'll Need
- •Node.js 18+
- •A TypeScript project with
ts-nodeor a build step - •Packages:
- •
langchain - •
@langchain/openai - •
@langchain/community - •
dotenv
- •
- •An OpenAI API key in
.env - •A long text file to test with, for example
./data/policy.txt
Step-by-Step
- •Set up your project and load environment variables. Keep your API key out of source control and make sure TypeScript can run the script directly.
npm init -y
npm install langchain @langchain/openai @langchain/community dotenv
npm install -D typescript ts-node @types/node
// index.ts
import "dotenv/config";
- •Load the document from disk. For long documents, plain file loading is fine at first; the important part is to get the raw text into LangChain as a
Document.
import { TextLoader } from "@langchain/community/document_loaders/fs/text";
async function loadDocs() {
const loader = new TextLoader("./data/policy.txt");
const docs = await loader.load();
console.log(`Loaded ${docs.length} document(s)`);
return docs;
}
- •Split the document into chunks with overlap. This is the core move for long-document handling: keep chunks small enough for embeddings and retrieval, but overlap them so you don’t lose context at boundaries.
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
async function splitDocs() {
const docs = await loadDocs();
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 150,
});
const chunks = await splitter.splitDocuments(docs);
console.log(`Created ${chunks.length} chunks`);
return chunks;
}
- •Embed the chunks and store them in a vector database. For an intermediate setup, use an in-memory vector store first so you can validate the flow before moving to persistent storage.
import { OpenAIEmbeddings } from "@langchain/openai";
import { MemoryVectorStore } from "langchain/vectorstores/memory";
async function buildVectorStore() {
const chunks = await splitDocs();
const embeddings = new OpenAIEmbeddings({
apiKey: process.env.OPENAI_API_KEY,
model: "text-embedding-3-small",
});
return MemoryVectorStore.fromDocuments(chunks, embeddings);
}
- •Query the document with retrieval plus a chat model. This avoids sending the entire file to the model; instead, LangChain retrieves only the most relevant chunks and uses them as context.
import { ChatOpenAI } from "@langchain/openai";
import { createStuffDocumentsChain } from "langchain/chains/combine_documents";
import { createRetrievalChain } from "langchain/chains/retrieval";
import { ChatPromptTemplate } from "@langchain/core/prompts";
async function askQuestion(question: string) {
const vectorStore = await buildVectorStore();
const retriever = vectorStore.asRetriever(4);
const llm = new ChatOpenAI({
apiKey: process.env.OPENAI_API_KEY,
model: "gpt-4o-mini",
temperature: 0,
});
const prompt = ChatPromptTemplate.fromMessages([
["system", "Answer using only the provided context."],
["human", "{input}"],
]);
const combineDocsChain = await createStuffDocumentsChain({
llm,
prompt,
});
const retrievalChain = await createRetrievalChain({
retriever,
combineDocsChain,
});
const result = await retrievalChain.invoke({
input: question,
});
console.log(result.answer);
}
- •Run it end-to-end with a real question. Start with something specific so you can confirm retrieval is pulling the right sections instead of guessing across the whole file.
async function main() {
await askQuestion("What does this policy say about termination notice?");
}
main().catch((err) => {
console.error(err);
process.exit(1);
});
Testing It
Run the script against a long text file that contains clearly searchable sections, like policy terms or a legal memo. Ask a question that should map to one part of the document, then compare the answer against the source text.
If the answer is vague or wrong, reduce chunkSize, increase chunkOverlap, or raise k in asRetriever(4). If retrieval seems noisy, your chunks are probably too large or your document has poor structure for semantic search.
A good sanity check is to print out retrieved chunk metadata or content before calling the LLM. That tells you whether the problem is ingestion, splitting, retrieval, or generation.
Next Steps
- •Swap
MemoryVectorStorefor a persistent store like Pinecone, pgvector, or Chroma. - •Add metadata filters so you can query by section, page number, or document type.
- •Move from “stuff” chains to map-reduce or refine patterns when documents get too large for simple context assembly.
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
By Cyprian Aarons, AI Consultant at Topiax.
Want the complete 8-step roadmap?
Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.
Get the Starter Kit