<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Varunabishek</title><link href="https://varunabishek.github.io/" rel="alternate"/><link href="https://varunabishek.github.io/feeds/all.atom.xml" rel="self"/><id>https://varunabishek.github.io/</id><updated>2026-08-19T00:00:00+05:30</updated><entry><title>Building Your First RAG (Retrieval-Augmented Generation) App</title><link href="https://varunabishek.github.io/building-your-first-rag-app.html" rel="alternate"/><published>2026-08-19T00:00:00+05:30</published><updated>2026-08-19T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-08-19:/building-your-first-rag-app.html</id><summary type="html">&lt;p&gt;LLMs are trained on static data. Ask one about your internal docs, last week's meeting notes, or a product spec — it has no idea. RAG solves this by fetching relevant content at query time and injecting it into the prompt. The model still does the reasoning; you just give it …&lt;/p&gt;</summary><content type="html">&lt;p&gt;LLMs are trained on static data. Ask one about your internal docs, last week's meeting notes, or a product spec — it has no idea. RAG solves this by fetching relevant content at query time and injecting it into the prompt. The model still does the reasoning; you just give it the right context first.&lt;/p&gt;
&lt;h2&gt;How RAG works&lt;/h2&gt;
&lt;p&gt;The pipeline has two phases:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Indexing (offline)&lt;/strong&gt; — Load your documents, split them into chunks, convert each chunk into a vector embedding, and store those vectors in a vector database.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Retrieval + generation (online)&lt;/strong&gt; — When a user asks a question, convert the question into an embedding, find the closest-matching chunks in the vector DB, shove those chunks into the prompt alongside the question, and let the LLM answer.&lt;/p&gt;
&lt;p&gt;That's it. The LLM never "learns" your docs — it reads them fresh on every query.&lt;/p&gt;
&lt;h2&gt;Dependencies&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;langchain&lt;span class="w"&gt; &lt;/span&gt;langchain-community&lt;span class="w"&gt; &lt;/span&gt;langchain-openai&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;chromadb&lt;span class="w"&gt; &lt;/span&gt;tiktoken
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Step 1 — Load your documents&lt;/h2&gt;
&lt;p&gt;LangChain ships with loaders for almost every format. For a plain text file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.document_loaders&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TextLoader&lt;/span&gt;

&lt;span class="n"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TextLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;docs/handbook.txt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For a folder of files:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.document_loaders&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DirectoryLoader&lt;/span&gt;

&lt;span class="n"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DirectoryLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;docs/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;**/*.txt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Each loaded item is a &lt;code&gt;Document&lt;/code&gt; with &lt;code&gt;.page_content&lt;/code&gt; (the text) and &lt;code&gt;.metadata&lt;/code&gt; (source path, page number, etc.).&lt;/p&gt;
&lt;h2&gt;Step 2 — Split into chunks&lt;/h2&gt;
&lt;p&gt;Embeddings work best on short, focused passages. Long documents get split up:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.text_splitter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RecursiveCharacterTextSplitter&lt;/span&gt;

&lt;span class="n"&gt;splitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RecursiveCharacterTextSplitter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;chunk_overlap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;splitter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;chunk_overlap&lt;/code&gt; keeps a window of context at chunk boundaries. Tune &lt;code&gt;chunk_size&lt;/code&gt; based on what you're embedding — 500 characters is a reasonable starting point for dense technical text.&lt;/p&gt;
&lt;h2&gt;Step 3 — Embed and store&lt;/h2&gt;
&lt;p&gt;Convert chunks to vectors and store them in Chroma (a local vector DB):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbeddings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.vectorstores&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Chroma&lt;/span&gt;

&lt;span class="n"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbeddings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;vectorstore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Chroma&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;persist_directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./chroma_db&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;persist_directory&lt;/code&gt; saves the index to disk so you don't re-embed on every run. Drop it if you want an in-memory store that resets each session.&lt;/p&gt;
&lt;h2&gt;Step 4 — Build the retrieval chain&lt;/h2&gt;
&lt;p&gt;LangChain's &lt;code&gt;RetrievalQA&lt;/code&gt; chain wraps the retriever and the LLM together:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.chains&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RetrievalQA&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;gpt-3.5-turbo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;retriever&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vectorstore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_retriever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search_kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;k&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;qa_chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RetrievalQA&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_chain_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;chain_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;stuff&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;return_source_documents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;k=4&lt;/code&gt; means retrieve the 4 most similar chunks per query. &lt;code&gt;chain_type="stuff"&lt;/code&gt; concatenates them into the prompt. For larger doc sets, &lt;code&gt;map_reduce&lt;/code&gt; or &lt;code&gt;refine&lt;/code&gt; handle overflow better.&lt;/p&gt;
&lt;h2&gt;Step 5 — Query it&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qa_chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;query&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;What is the refund policy?&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;result&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Sources:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;source_documents&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;source&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;source_documents&lt;/code&gt; field shows exactly which chunks the model drew from — useful for debugging and for building citation UIs.&lt;/p&gt;
&lt;h2&gt;Full pipeline&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.document_loaders&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DirectoryLoader&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.text_splitter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RecursiveCharacterTextSplitter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbeddings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.vectorstores&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Chroma&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.chains&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RetrievalQA&lt;/span&gt;

&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;OPENAI_API_KEY&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;your-key-here&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Load&lt;/span&gt;
&lt;span class="n"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DirectoryLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;docs/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;**/*.txt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Split&lt;/span&gt;
&lt;span class="n"&gt;splitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RecursiveCharacterTextSplitter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk_overlap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;splitter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Embed + store&lt;/span&gt;
&lt;span class="n"&gt;vectorstore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Chroma&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OpenAIEmbeddings&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;persist_directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./chroma_db&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Chain&lt;/span&gt;
&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;gpt-3.5-turbo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;retriever&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vectorstore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_retriever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search_kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;k&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;qa_chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RetrievalQA&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_chain_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;chain_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;stuff&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;return_source_documents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Query&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qa_chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;query&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Summarize the onboarding process&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;result&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Common failure modes&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Bad retrieval&lt;/strong&gt; — The right answer exists in your docs but the LLM gives a wrong or vague response. Usually means your chunks are too large or your embeddings aren't granular enough. Shrink &lt;code&gt;chunk_size&lt;/code&gt; and re-index.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hallucination despite good retrieval&lt;/strong&gt; — The LLM drifts from the retrieved context. Add a system prompt that says: "Answer only using the provided context. If the context doesn't contain the answer, say so."&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Slow indexing&lt;/strong&gt; — Embedding thousands of chunks hits API rate limits fast. Batch your calls, add retry logic, or switch to a local embedding model like &lt;code&gt;sentence-transformers&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Going further&lt;/h2&gt;
&lt;p&gt;Once the basic pipeline works, two things make the biggest difference in real apps:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Metadata filtering&lt;/strong&gt; — Tag chunks with source, date, or category at index time, then filter by those fields at retrieval time. Keeps queries scoped and cuts noise.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hybrid search&lt;/strong&gt; — Combine vector similarity with keyword search (BM25). Pure vector search misses exact-match queries; hybrid search handles both.&lt;/p&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="LLM"/><category term="RAG"/><category term="LangChain"/><category term="Python"/><category term="Vector Store"/></entry><entry><title>Document Loaders — Pulling in PDFs, Websites, CSVs, and More</title><link href="https://varunabishek.github.io/document-loaders-langchain.html" rel="alternate"/><published>2026-08-19T00:00:00+05:30</published><updated>2026-08-19T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-08-19:/document-loaders-langchain.html</id><summary type="html">&lt;p&gt;Before any LangChain pipeline does anything useful, it needs data. Document loaders are how that data gets in — they fetch content from a source, parse it, and return a list of &lt;code&gt;Document&lt;/code&gt; objects with &lt;code&gt;.page_content&lt;/code&gt; and &lt;code&gt;.metadata&lt;/code&gt;. The rest of the pipeline (splitting, embedding, retrieval) doesn't care where the content …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Before any LangChain pipeline does anything useful, it needs data. Document loaders are how that data gets in — they fetch content from a source, parse it, and return a list of &lt;code&gt;Document&lt;/code&gt; objects with &lt;code&gt;.page_content&lt;/code&gt; and &lt;code&gt;.metadata&lt;/code&gt;. The rest of the pipeline (splitting, embedding, retrieval) doesn't care where the content came from. That's the point.&lt;/p&gt;
&lt;h2&gt;The Document object&lt;/h2&gt;
&lt;p&gt;Every loader returns the same structure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.schema&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Document&lt;/span&gt;

&lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;page_content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;This is the text content&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;source&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;handbook.pdf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;page&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;.page_content&lt;/code&gt; is what gets embedded and fed to the LLM. &lt;code&gt;.metadata&lt;/code&gt; is free-form — source file, URL, page number, timestamps — whatever you want to carry through for filtering or citation.&lt;/p&gt;
&lt;h2&gt;PDFs&lt;/h2&gt;
&lt;p&gt;The most common use case. Two loaders worth knowing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# PyPDF — lightweight, good for most PDFs&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.document_loaders&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PyPDFLoader&lt;/span&gt;

&lt;span class="n"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyPDFLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;report.pdf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# One Document per page&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# PDFPlumber — better for tables and layout-heavy docs&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.document_loaders&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PDFPlumberLoader&lt;/span&gt;

&lt;span class="n"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PDFPlumberLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;report.pdf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;PyPDFLoader&lt;/code&gt; is faster. &lt;code&gt;PDFPlumberLoader&lt;/code&gt; preserves layout better and handles tables more reliably. For scanned PDFs (image-based, no text layer), neither works — you need OCR. &lt;code&gt;UnstructuredPDFLoader&lt;/code&gt; with &lt;code&gt;mode="elements"&lt;/code&gt; can handle these via Tesseract.&lt;/p&gt;
&lt;h2&gt;Websites&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.document_loaders&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;WebBaseLoader&lt;/span&gt;

&lt;span class="n"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebBaseLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://docs.python.org/3/library/os.html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This fetches static HTML and strips the tags. Works fine for documentation sites, Wikipedia, blogs. For JavaScript-rendered pages (React/Next.js SPAs), the content won't be there — use &lt;code&gt;SeleniumURLLoader&lt;/code&gt; or &lt;code&gt;PlaywrightURLLoader&lt;/code&gt; instead, which spin up a real browser.&lt;/p&gt;
&lt;p&gt;Loading multiple URLs at once:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;https://example.com/page1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;https://example.com/page2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebBaseLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;CSVs&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.document_loaders&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CSVLoader&lt;/span&gt;

&lt;span class="n"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CSVLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;employees.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;source_column&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;employee_id&amp;quot;&lt;/span&gt;   &lt;span class="c1"&gt;# used as the metadata source field&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Each row becomes its own &lt;code&gt;Document&lt;/code&gt;. The &lt;code&gt;source_column&lt;/code&gt; parameter sets which column gets used as the source identifier in metadata — useful when you want to trace an answer back to a specific record.&lt;/p&gt;
&lt;p&gt;For large CSVs with many columns, consider specifying which columns matter:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CSVLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;csv_args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;fieldnames&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;date&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;JSON and JSONL&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.document_loaders&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;JSONLoader&lt;/span&gt;

&lt;span class="n"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JSONLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data.json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;jq_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.messages[].content&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# jq syntax to extract the right field&lt;/span&gt;
    &lt;span class="n"&gt;text_content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;JSONLoader&lt;/code&gt; uses &lt;code&gt;jq&lt;/code&gt; syntax to navigate nested JSON. &lt;code&gt;jq_schema&lt;/code&gt; tells it exactly which field to pull as the page content. For JSONL (one JSON object per line), set &lt;code&gt;json_lines=True&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Directories (bulk loading)&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.document_loaders&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DirectoryLoader&lt;/span&gt;

&lt;span class="n"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DirectoryLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;docs/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;**/*.pdf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;# glob pattern for file types&lt;/span&gt;
    &lt;span class="n"&gt;loader_cls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PyPDFLoader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# which loader to use per file&lt;/span&gt;
    &lt;span class="n"&gt;show_progress&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;DirectoryLoader&lt;/code&gt; walks a folder and applies the specified loader to each matching file. Mix it with different &lt;code&gt;glob&lt;/code&gt; patterns and &lt;code&gt;loader_cls&lt;/code&gt; values if you have a folder with mixed file types.&lt;/p&gt;
&lt;h2&gt;Notion, Google Drive, and databases&lt;/h2&gt;
&lt;p&gt;LangChain has loaders for these too, though they need extra auth setup:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Notion&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.document_loaders&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;NotionDBLoader&lt;/span&gt;

&lt;span class="n"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NotionDBLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;integration_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;your-token&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;database_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;your-db-id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;request_timeout_sec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Google Drive&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.document_loaders&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GoogleDriveLoader&lt;/span&gt;

&lt;span class="n"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GoogleDriveLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;folder_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;your-folder-id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;recursive&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;SQL databases have their own path — &lt;code&gt;SQLDatabaseLoader&lt;/code&gt; runs a query and turns rows into documents. Useful for pipelines that need to answer questions against live relational data.&lt;/p&gt;
&lt;h2&gt;Custom loaders&lt;/h2&gt;
&lt;p&gt;If nothing built-in fits, subclass &lt;code&gt;BaseLoader&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.document_loaders.base&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseLoader&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.schema&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Document&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyAPILoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseLoader&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;page_content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;source&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;items&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The interface is just one method: &lt;code&gt;load()&lt;/code&gt; returning a list of &lt;code&gt;Document&lt;/code&gt; objects. That's all the rest of the pipeline needs.&lt;/p&gt;
&lt;h2&gt;What to watch for&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Encoding issues&lt;/strong&gt; — PDFs and older text files sometimes come with encoding problems. &lt;code&gt;PyPDFLoader&lt;/code&gt; occasionally returns garbled text from scanned or password-protected files. Check &lt;code&gt;doc.page_content&lt;/code&gt; before embedding — garbage in means garbage retrieved.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Large files&lt;/strong&gt; — Loading a 500-page PDF returns 500 documents at once. If you're loading many large files, stream them in batches rather than calling &lt;code&gt;.load()&lt;/code&gt; on everything at once.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Metadata hygiene&lt;/strong&gt; — The metadata your loaders attach is what you'll use later for filtering. Set it deliberately. A chunk with &lt;code&gt;metadata={"source": "file.pdf"}&lt;/code&gt; is much harder to filter than one with &lt;code&gt;metadata={"source": "file.pdf", "section": "terms", "date": "2026-01"}&lt;/code&gt;.&lt;/p&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="LangChain"/><category term="Python"/><category term="Document Loaders"/><category term="PDF"/><category term="CSV"/><category term="Web Scraping"/></entry><entry><title>Intro to Vector Stores (Chroma, FAISS, Pinecone) with LangChain</title><link href="https://varunabishek.github.io/intro-to-vector-stores-langchain.html" rel="alternate"/><published>2026-08-19T00:00:00+05:30</published><updated>2026-08-19T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-08-19:/intro-to-vector-stores-langchain.html</id><summary type="html">&lt;p&gt;A vector store is a database that finds things by meaning, not exact match. You convert text into a numerical vector (an embedding), store it, and later search for semantically similar vectors. That's the retrieval half of RAG. Which store you pick depends on scale, whether you want persistence, and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A vector store is a database that finds things by meaning, not exact match. You convert text into a numerical vector (an embedding), store it, and later search for semantically similar vectors. That's the retrieval half of RAG. Which store you pick depends on scale, whether you want persistence, and whether you're staying local or going cloud.&lt;/p&gt;
&lt;h2&gt;How similarity search works&lt;/h2&gt;
&lt;p&gt;When you embed text, you map it into a high-dimensional space where similar meanings land close to each other. "How do I reset my password?" and "Forgot password steps" produce vectors that are geometrically near each other, even though they share no words.&lt;/p&gt;
&lt;p&gt;At query time:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Embed the user's question into the same space.&lt;/li&gt;
&lt;li&gt;Find the &lt;code&gt;k&lt;/code&gt; stored vectors closest to it (cosine similarity or Euclidean distance).&lt;/li&gt;
&lt;li&gt;Return the corresponding text chunks.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That's what every vector store does. The differences are in speed, persistence, and infrastructure.&lt;/p&gt;
&lt;h2&gt;Chroma — local, zero-config&lt;/h2&gt;
&lt;p&gt;Chroma runs in-process. No server, no Docker, just install and go. The right choice for development, prototypes, and small production apps where you control the machine.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;chromadb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbeddings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.vectorstores&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Chroma&lt;/span&gt;

&lt;span class="n"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbeddings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Create from documents&lt;/span&gt;
&lt;span class="n"&gt;vectorstore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Chroma&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;persist_directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./chroma_db&amp;quot;&lt;/span&gt;   &lt;span class="c1"&gt;# omit for in-memory only&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Load an existing store&lt;/span&gt;
&lt;span class="n"&gt;vectorstore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Chroma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;persist_directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./chroma_db&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;embedding_function&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Search&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vectorstore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;similarity_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;What is the refund policy?&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;persist_directory&lt;/code&gt; writes the index to disk. Without it, everything lives in memory and resets when the process ends. Chroma handles up to a few hundred thousand documents comfortably on a standard laptop.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Metadata filtering in Chroma:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vectorstore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;similarity_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;security protocols&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;source&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;handbook.pdf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Filter keys must match what you set in &lt;code&gt;.metadata&lt;/code&gt; at index time.&lt;/p&gt;
&lt;h2&gt;FAISS — fast, in-memory, CPU-native&lt;/h2&gt;
&lt;p&gt;FAISS (Facebook AI Similarity Search) is a C++ library optimized for large-scale nearest-neighbor search. It's faster than Chroma for big indexes and doesn't need a server, but it's in-memory — persistence requires manually serializing and deserializing the index.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;faiss-cpu&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# or faiss-gpu if you have CUDA&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.vectorstores&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FAISS&lt;/span&gt;

&lt;span class="c1"&gt;# Create&lt;/span&gt;
&lt;span class="n"&gt;vectorstore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FAISS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Save to disk&lt;/span&gt;
&lt;span class="n"&gt;vectorstore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save_local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;faiss_index&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Load from disk&lt;/span&gt;
&lt;span class="n"&gt;vectorstore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FAISS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;faiss_index&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;allow_dangerous_deserialization&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Search&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vectorstore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;similarity_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;explain the leave policy&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;FAISS doesn't have built-in metadata filtering the way Chroma does. You filter post-retrieval by inspecting &lt;code&gt;doc.metadata&lt;/code&gt; yourself. For production use cases needing filter logic, Chroma or Pinecone are easier.&lt;/p&gt;
&lt;p&gt;FAISS shines when you have millions of vectors and want fast retrieval on your own hardware — local LLM setups, air-gapped environments, research workloads.&lt;/p&gt;
&lt;h2&gt;Pinecone — managed, cloud-native&lt;/h2&gt;
&lt;p&gt;Pinecone is a hosted vector database. You push vectors to their servers; they handle indexing, scaling, and replication. No infra to manage, but it's a paid service and your data leaves your machine.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;pinecone-client
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_pinecone&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PineconeVectorStore&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pinecone&lt;/span&gt;

&lt;span class="n"&gt;pinecone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;your-api-key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-1-aws&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create index (one-time setup, not per run)&lt;/span&gt;
&lt;span class="n"&gt;pinecone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;my-index&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dimension&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1536&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;# OpenAI ada-002 output dimension&lt;/span&gt;
    &lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cosine&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Upsert documents&lt;/span&gt;
&lt;span class="n"&gt;vectorstore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PineconeVectorStore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;my-index&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Connect to existing index&lt;/span&gt;
&lt;span class="n"&gt;vectorstore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PineconeVectorStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;my-index&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Search with metadata filter&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vectorstore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;similarity_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;quarterly revenue targets&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;department&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;finance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pinecone's main advantage is that it scales horizontally without any work on your side. Billions of vectors, concurrent queries, namespace isolation — all handled. The tradeoff is cost and the fact that your data lives in their infrastructure.&lt;/p&gt;
&lt;h2&gt;Comparing the three&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Chroma&lt;/th&gt;
&lt;th&gt;FAISS&lt;/th&gt;
&lt;th&gt;Pinecone&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hosting&lt;/td&gt;
&lt;td&gt;Local&lt;/td&gt;
&lt;td&gt;Local&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistence&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;Manual (save/load)&lt;/td&gt;
&lt;td&gt;Always on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metadata filtering&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Post-retrieval only&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scale&lt;/td&gt;
&lt;td&gt;~100k docs&lt;/td&gt;
&lt;td&gt;Millions&lt;/td&gt;
&lt;td&gt;Billions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Paid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Dev / small prod&lt;/td&gt;
&lt;td&gt;Large local indexes&lt;/td&gt;
&lt;td&gt;Production at scale&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Which retriever mode to use&lt;/h2&gt;
&lt;p&gt;All three expose &lt;code&gt;.as_retriever()&lt;/code&gt;, which is what you pass into LangChain chains:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;retriever&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vectorstore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_retriever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;search_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mmr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;# or &amp;quot;similarity&amp;quot; (default)&lt;/span&gt;
    &lt;span class="n"&gt;search_kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;k&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;fetch_k&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;similarity&lt;/code&gt;&lt;/strong&gt; — Returns the top-k most similar chunks. Simple, fast, but can return redundant results if your docs have repeated content.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;mmr&lt;/code&gt; (Maximal Marginal Relevance)&lt;/strong&gt; — Balances similarity with diversity. Fetches a larger candidate set (&lt;code&gt;fetch_k&lt;/code&gt;), then picks &lt;code&gt;k&lt;/code&gt; results that are relevant but not too similar to each other. Better for documents with repeated sections or when you want varied context.&lt;/p&gt;
&lt;h2&gt;Swapping stores without rewriting your chain&lt;/h2&gt;
&lt;p&gt;LangChain's interfaces are consistent across stores. Switch from Chroma to Pinecone by changing one line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before&lt;/span&gt;
&lt;span class="n"&gt;retriever&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chroma_store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_retriever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search_kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;k&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# After&lt;/span&gt;
&lt;span class="n"&gt;retriever&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pinecone_store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_retriever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search_kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;k&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The chain, prompts, and LLM stay the same. This makes it easy to start local with Chroma and migrate to Pinecone when you need to scale.&lt;/p&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="LangChain"/><category term="Vector Store"/><category term="Chroma"/><category term="FAISS"/><category term="Pinecone"/><category term="Embeddings"/></entry><entry><title>LangChain vs. LlamaIndex vs. Raw API Calls — When to Use What</title><link href="https://varunabishek.github.io/langchain-vs-llamaindex-vs-raw-api.html" rel="alternate"/><published>2026-08-19T00:00:00+05:30</published><updated>2026-08-19T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-08-19:/langchain-vs-llamaindex-vs-raw-api.html</id><summary type="html">&lt;p&gt;Three ways to build with LLMs. Each makes different tradeoffs, and picking the wrong one early costs you time when the project grows. This isn't a ranking — it's a map of when each tool actually fits.&lt;/p&gt;
&lt;h2&gt;Raw API calls&lt;/h2&gt;
&lt;p&gt;Direct calls to OpenAI, Anthropic, Gemini, or any provider. No framework …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Three ways to build with LLMs. Each makes different tradeoffs, and picking the wrong one early costs you time when the project grows. This isn't a ranking — it's a map of when each tool actually fits.&lt;/p&gt;
&lt;h2&gt;Raw API calls&lt;/h2&gt;
&lt;p&gt;Direct calls to OpenAI, Anthropic, Gemini, or any provider. No framework, just HTTP.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;gpt-4o&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;role&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;system&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;You are a helpful assistant.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;role&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;user&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Explain transformers in one paragraph.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;When this fits:&lt;/strong&gt;
- You're doing one thing — a single prompt, a classification call, a rewrite task.
- You need full control over request structure and don't want a framework deciding how to format things.
- You're building a lightweight microservice where a framework would add weight for no reason.
- You're learning. Frameworks abstract the parts you should understand first.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Where it breaks down:&lt;/strong&gt;
- You start needing RAG. Now you're writing a vector store wrapper, a chunking function, a retrieval loop, and token management yourself.
- You want conversation memory. Now you're manually tracking message history per session.
- You want to swap providers. Now you're rewriting every call.&lt;/p&gt;
&lt;p&gt;Raw API calls don't scale to complex pipelines without you rebuilding the things frameworks already built.&lt;/p&gt;
&lt;h2&gt;LangChain&lt;/h2&gt;
&lt;p&gt;LangChain is a general-purpose framework for chaining LLM calls with tools, memory, retrievers, and data loaders. It covers almost every use case — sometimes too broadly.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.chains&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RetrievalQA&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.vectorstores&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Chroma&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbeddings&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;gpt-3.5-turbo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;vectorstore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Chroma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;persist_directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./db&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embedding_function&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OpenAIEmbeddings&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;retriever&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vectorstore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_retriever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search_kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;k&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RetrievalQA&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_chain_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;query&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;What does the contract say about late fees?&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;When this fits:&lt;/strong&gt;
- You're building a multi-step pipeline: load → split → embed → retrieve → generate.
- You need agents — LLMs that pick tools, take actions, and loop based on output.
- You want memory management handled for you (buffer, window, summary).
- You're connecting to external tools: web search, APIs, databases, code interpreters.
- You want to swap LLMs or vector stores without rewriting the whole pipeline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Where it breaks down:&lt;/strong&gt;
- Retrieval quality is your primary concern. LangChain supports many retrieval strategies, but tuning them requires working around the abstractions rather than with them.
- The abstraction layers make debugging harder. When something returns wrong results, the error is often buried three layers deep.
- LangChain has gone through significant API churn (v0.1 → LCEL → v0.3). Code from tutorials written six months ago may not run without changes.&lt;/p&gt;
&lt;p&gt;LangChain is best when you need breadth — many connected components — and are willing to trade some debuggability for speed of assembly.&lt;/p&gt;
&lt;h2&gt;LlamaIndex&lt;/h2&gt;
&lt;p&gt;LlamaIndex (formerly GPT Index) is purpose-built for indexing and retrieval. Its primitives — nodes, indexes, query engines, retrievers — are designed around document ingestion and high-quality search. LangChain can do RAG; LlamaIndex was built specifically for it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;llama_index.core&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;VectorStoreIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SimpleDirectoryReader&lt;/span&gt;

&lt;span class="c1"&gt;# Load and index&lt;/span&gt;
&lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SimpleDirectoryReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;docs/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VectorStoreIndex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Query&lt;/span&gt;
&lt;span class="n"&gt;query_engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_query_engine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;What are the payment terms?&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Four lines. No manual chunking, no retriever config, no chain setup.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When this fits:&lt;/strong&gt;
- Document Q&amp;amp;A is the core feature, not a component inside a larger pipeline.
- You need fine-grained retrieval: hierarchical indexes, sub-question decomposition, hybrid search, reranking.
- You want to index structured data (SQL, JSON, dataframes) alongside unstructured docs.
- You care about retrieval evaluation — LlamaIndex has built-in evals for this.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Where it breaks down:&lt;/strong&gt;
- You need agents with complex tool use. LlamaIndex has agents, but LangChain's are more mature and have more integrations.
- Your pipeline has many non-retrieval components (custom memory, complex branching logic, tool chains). LangChain's LCEL is better suited for composing those.
- You need a wider ecosystem of third-party integrations. LangChain has more.&lt;/p&gt;
&lt;p&gt;LlamaIndex is better when retrieval quality is what you're optimizing for and the rest of the pipeline is simple.&lt;/p&gt;
&lt;h2&gt;Side by side&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Raw API&lt;/th&gt;
&lt;th&gt;LangChain&lt;/th&gt;
&lt;th&gt;LlamaIndex&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Learning curve&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAG support&lt;/td&gt;
&lt;td&gt;DIY&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent support&lt;/td&gt;
&lt;td&gt;DIY&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory management&lt;/td&gt;
&lt;td&gt;DIY&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retrieval tuning&lt;/td&gt;
&lt;td&gt;DIY&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Deep&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debugging&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;Hard&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ecosystem / integrations&lt;/td&gt;
&lt;td&gt;Provider-specific&lt;/td&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;Growing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API stability&lt;/td&gt;
&lt;td&gt;Stable&lt;/td&gt;
&lt;td&gt;Churn&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;The real decision flow&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Start with raw API calls if:&lt;/strong&gt; You're learning, prototyping a single prompt interaction, or building something where a framework would be overhead with no benefit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reach for LangChain if:&lt;/strong&gt; Your pipeline connects multiple components — loaders, retrievers, tools, memory, agents — and you want the ecosystem to handle the wiring.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reach for LlamaIndex if:&lt;/strong&gt; Your core problem is document retrieval and Q&amp;amp;A, and you want the best retrieval quality with the least configuration overhead.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use both if:&lt;/strong&gt; It's more common than you'd think. LangChain for orchestration, LlamaIndex as the retrieval backend. They interoperate:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;llama_index.core&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;VectorStoreIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SimpleDirectoryReader&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.retrievers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LlamaIndexRetriever&lt;/span&gt;

&lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SimpleDirectoryReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;docs/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VectorStoreIndex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Use LlamaIndex&amp;#39;s query engine as a LangChain retriever&lt;/span&gt;
&lt;span class="n"&gt;retriever&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LlamaIndexRetriever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_query_engine&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The frameworks aren't mutually exclusive. Pick the best tool for each layer of your stack.&lt;/p&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="LLM"/><category term="LangChain"/><category term="LlamaIndex"/><category term="API"/><category term="Python"/><category term="Architecture"/></entry><entry><title>Managing Conversation History Without Blowing Your Token Budget</title><link href="https://varunabishek.github.io/managing-conversation-history-tokens.html" rel="alternate"/><published>2026-08-19T00:00:00+05:30</published><updated>2026-08-19T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-08-19:/managing-conversation-history-tokens.html</id><summary type="html">&lt;p&gt;LLMs have no memory between calls. Every time you hit the API, the model starts fresh. To build a chatbot that remembers what was said three messages ago, you have to manually pass that history back in on every request. The problem: each message adds tokens, and token budgets are …&lt;/p&gt;</summary><content type="html">&lt;p&gt;LLMs have no memory between calls. Every time you hit the API, the model starts fresh. To build a chatbot that remembers what was said three messages ago, you have to manually pass that history back in on every request. The problem: each message adds tokens, and token budgets are finite. A naive implementation that keeps the full conversation will eventually hit the context limit — or cost you more than it should.&lt;/p&gt;
&lt;h2&gt;The naive approach (and why it breaks)&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;role&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;user&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_message&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;gpt-3.5-turbo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
    &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;role&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;assistant&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This works until it doesn't. After enough turns, &lt;code&gt;history&lt;/code&gt; overflows the context window and the API throws an error. Even before that, you're paying for tokens from 40 messages ago that have nothing to do with the current question.&lt;/p&gt;
&lt;p&gt;LangChain's memory modules solve this by managing what gets kept.&lt;/p&gt;
&lt;h2&gt;ConversationBufferMemory — full history, simple&lt;/h2&gt;
&lt;p&gt;The most basic option: keep every message, no trimming.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ConversationBufferMemory&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.chains&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ConversationChain&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;gpt-3.5-turbo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConversationBufferMemory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConversationChain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;My name is Varun.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;What&amp;#39;s my name?&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# → &amp;quot;Your name is Varun.&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Fine for short conversations. Breaks on long ones for exactly the same reason the naive approach does.&lt;/p&gt;
&lt;h2&gt;ConversationBufferWindowMemory — sliding window&lt;/h2&gt;
&lt;p&gt;Keep only the last &lt;code&gt;k&lt;/code&gt; exchanges. Everything older gets dropped:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ConversationBufferWindowMemory&lt;/span&gt;

&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConversationBufferWindowMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# last 5 human+AI turns&lt;/span&gt;
&lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConversationChain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Simple and predictable. The tradeoff is that context beyond &lt;code&gt;k&lt;/code&gt; turns ago is gone entirely. A user who mentioned their project name 10 messages back and references it now will confuse the model.&lt;/p&gt;
&lt;p&gt;Good for: customer support bots, short-session apps, anywhere long-term context doesn't matter.&lt;/p&gt;
&lt;h2&gt;ConversationSummaryMemory — compress old turns&lt;/h2&gt;
&lt;p&gt;Summarize older messages instead of dropping them. The LLM itself writes the summary as history grows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ConversationSummaryMemory&lt;/span&gt;

&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConversationSummaryMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConversationChain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Recent turns are kept verbatim; older ones get collapsed into a running summary. The model always has the gist of what happened earlier, just not the exact words.&lt;/p&gt;
&lt;p&gt;The catch: summarization costs extra tokens and adds latency on each turn. For long conversations, this is usually worth it. For short ones, it's overhead you don't need.&lt;/p&gt;
&lt;h2&gt;ConversationSummaryBufferMemory — the practical middle ground&lt;/h2&gt;
&lt;p&gt;Keeps recent messages verbatim and summarizes anything older than &lt;code&gt;max_token_limit&lt;/code&gt; tokens:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ConversationSummaryBufferMemory&lt;/span&gt;

&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConversationSummaryBufferMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_token_limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;    &lt;span class="c1"&gt;# keep last ~1000 tokens verbatim; summarize the rest&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConversationChain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is the most practical default for production chatbots. Recent context stays sharp; older context survives in compressed form. The token budget stays bounded without losing the thread of a long session.&lt;/p&gt;
&lt;h2&gt;ConversationTokenBufferMemory — token-precise trimming&lt;/h2&gt;
&lt;p&gt;Trims by exact token count rather than by message count:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ConversationTokenBufferMemory&lt;/span&gt;

&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConversationTokenBufferMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_token_limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;   &lt;span class="c1"&gt;# never exceed 2000 tokens in memory&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;More precise than window-based trimming since different messages have very different lengths. A user who sends one-word replies needs a bigger window than one who writes paragraphs. Token-based trimming handles both correctly.&lt;/p&gt;
&lt;h2&gt;Persisting memory across sessions&lt;/h2&gt;
&lt;p&gt;All the memory classes above are in-memory. Close the process and the history is gone. For a real chatbot, you need to save and reload it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Manual serialization:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;

&lt;span class="c1"&gt;# Save&lt;/span&gt;
&lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_memory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;
&lt;span class="n"&gt;serialized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;role&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serialized&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;session_123.json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;w&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# Load&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.schema&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HumanMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AIMessage&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;session_123.json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;HumanMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;role&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;human&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;AIMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_memory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;With a database (Redis, MongoDB, Postgres):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;LangChain has &lt;code&gt;RedisChatMessageHistory&lt;/code&gt;, &lt;code&gt;MongoDBChatMessageHistory&lt;/code&gt;, and others that write directly to an external store. They slot into the same memory interface:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.chat_message_histories&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RedisChatMessageHistory&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ConversationBufferMemory&lt;/span&gt;

&lt;span class="n"&gt;message_history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RedisChatMessageHistory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;user_456&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;redis://localhost:6379&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConversationBufferMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;chat_memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;message_history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;return_messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Each session ID gets its own history. Users pick up where they left off, even after server restarts.&lt;/p&gt;
&lt;h2&gt;Token counting before you send&lt;/h2&gt;
&lt;p&gt;Sometimes you want to check token usage programmatically before committing to an API call:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;gpt-3.5-turbo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Count tokens in a message list&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.callbacks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_openai_callback&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;get_openai_callback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;How many tokens did that use?&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Total tokens: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_tokens&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Prompt tokens: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Completion tokens: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completion_tokens&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Cost: $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_cost&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.4f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;get_openai_callback&lt;/code&gt; wraps any LangChain call and reports usage. Useful for auditing costs in development and for setting hard limits in production.&lt;/p&gt;
&lt;h2&gt;The summary&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Memory type&lt;/th&gt;
&lt;th&gt;What it keeps&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BufferMemory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Everything&lt;/td&gt;
&lt;td&gt;Short conversations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BufferWindowMemory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Last k turns&lt;/td&gt;
&lt;td&gt;Short-session apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SummaryMemory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Running summary&lt;/td&gt;
&lt;td&gt;Long sessions, cost-sensitive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SummaryBufferMemory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Recent verbatim + summary&lt;/td&gt;
&lt;td&gt;Most production chatbots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TokenBufferMemory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bounded by token count&lt;/td&gt;
&lt;td&gt;Variable-length message flows&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Start with &lt;code&gt;ConversationSummaryBufferMemory&lt;/code&gt; at around 1000–1500 tokens and tune from there.&lt;/p&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="LLM"/><category term="LangChain"/><category term="Python"/><category term="Memory"/><category term="Token Management"/><category term="Chatbot"/></entry><entry><title>Web Scraping + Summarization Pipeline with LangChain</title><link href="https://varunabishek.github.io/web-scraping-summarization-langchain.html" rel="alternate"/><published>2026-08-19T00:00:00+05:30</published><updated>2026-08-19T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-08-19:/web-scraping-summarization-langchain.html</id><summary type="html">&lt;p&gt;You don't always need a fancy dataset. Sometimes the data you need is already live on the web — you just have to go get it and make sense of it. That's exactly what this pipeline does: scrape a URL, chunk the content, and summarize it using an LLM, all wired …&lt;/p&gt;</summary><content type="html">&lt;p&gt;You don't always need a fancy dataset. Sometimes the data you need is already live on the web — you just have to go get it and make sense of it. That's exactly what this pipeline does: scrape a URL, chunk the content, and summarize it using an LLM, all wired together with LangChain.&lt;/p&gt;
&lt;h2&gt;What you're building&lt;/h2&gt;
&lt;p&gt;The pipeline has three stages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fetch raw HTML from a URL&lt;/li&gt;
&lt;li&gt;Parse and clean it into readable text&lt;/li&gt;
&lt;li&gt;Pass that text through an LLM with a summarization prompt&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;LangChain handles the glue — loaders, text splitters, chains — so you spend less time on plumbing and more time on what actually matters.&lt;/p&gt;
&lt;h2&gt;Dependencies&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;langchain&lt;span class="w"&gt; &lt;/span&gt;langchain-community&lt;span class="w"&gt; &lt;/span&gt;langchain-openai&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;beautifulsoup4&lt;span class="w"&gt; &lt;/span&gt;requests&lt;span class="w"&gt; &lt;/span&gt;tiktoken
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You'll also need an OpenAI API key (or swap in any LLM provider LangChain supports).&lt;/p&gt;
&lt;h2&gt;Step 1 — Scrape the page&lt;/h2&gt;
&lt;p&gt;LangChain's &lt;code&gt;WebBaseLoader&lt;/code&gt; does this with one line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.document_loaders&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;WebBaseLoader&lt;/span&gt;

&lt;span class="n"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebBaseLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://example.com/article&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Under the hood it fetches the page with &lt;code&gt;requests&lt;/code&gt;, strips tags using BeautifulSoup, and returns a list of &lt;code&gt;Document&lt;/code&gt; objects. Each document has &lt;code&gt;.page_content&lt;/code&gt; (the text) and &lt;code&gt;.metadata&lt;/code&gt; (source URL, title, etc.).&lt;/p&gt;
&lt;p&gt;If you're scraping a JavaScript-heavy site, &lt;code&gt;WebBaseLoader&lt;/code&gt; won't work — it only processes static HTML. Use &lt;code&gt;SeleniumURLLoader&lt;/code&gt; or &lt;code&gt;PlaywrightURLLoader&lt;/code&gt; instead:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.document_loaders&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SeleniumURLLoader&lt;/span&gt;

&lt;span class="n"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SeleniumURLLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://js-heavy-site.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Step 2 — Split the text&lt;/h2&gt;
&lt;p&gt;LLMs have context limits. A long article will overflow the context window if you send it all at once. &lt;code&gt;RecursiveCharacterTextSplitter&lt;/code&gt; breaks the content into chunks that fit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.text_splitter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RecursiveCharacterTextSplitter&lt;/span&gt;

&lt;span class="n"&gt;splitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RecursiveCharacterTextSplitter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;chunk_overlap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;splitter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;chunk_overlap&lt;/code&gt; keeps a little context between adjacent chunks so the LLM doesn't lose thread between them. Tune &lt;code&gt;chunk_size&lt;/code&gt; based on your model's token limit — 2000 characters is a safe starting point for most GPT-3.5/4 setups.&lt;/p&gt;
&lt;h2&gt;Step 3 — Summarize with a chain&lt;/h2&gt;
&lt;p&gt;LangChain has built-in summarization chains. &lt;code&gt;load_summarize_chain&lt;/code&gt; wires together the LLM and a prompt strategy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.chains.summarize&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_summarize_chain&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;gpt-3.5-turbo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;load_summarize_chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chain_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;map_reduce&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;stuff&lt;/code&gt;&lt;/strong&gt; — Concatenates all chunks and sends them in one prompt. Works for short documents. Breaks on anything long.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;map_reduce&lt;/code&gt;&lt;/strong&gt; — Summarizes each chunk separately (map), then summarizes the summaries (reduce). Handles long content well, costs more tokens.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;refine&lt;/code&gt;&lt;/strong&gt; — Starts with the first chunk's summary, then iteratively refines it with each next chunk. Produces more coherent output than &lt;code&gt;map_reduce&lt;/code&gt; but is slower.&lt;/p&gt;
&lt;p&gt;For most web articles, &lt;code&gt;map_reduce&lt;/code&gt; is the right default.&lt;/p&gt;
&lt;h2&gt;Full pipeline&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.document_loaders&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;WebBaseLoader&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.text_splitter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RecursiveCharacterTextSplitter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.chains.summarize&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_summarize_chain&lt;/span&gt;

&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;OPENAI_API_KEY&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;your-key-here&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;summarize_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Load&lt;/span&gt;
    &lt;span class="n"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebBaseLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Split&lt;/span&gt;
    &lt;span class="n"&gt;splitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RecursiveCharacterTextSplitter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;chunk_overlap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;splitter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Summarize&lt;/span&gt;
    &lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;gpt-3.5-turbo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;load_summarize_chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chain_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;map_reduce&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://en.wikipedia.org/wiki/Large_language_model&amp;quot;&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summarize_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Custom prompt&lt;/h2&gt;
&lt;p&gt;The default prompt is generic. You can replace it with something that fits your use case:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.prompts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PromptTemplate&lt;/span&gt;

&lt;span class="n"&gt;map_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PromptTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;input_variables&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;Summarize the following content in 3-5 bullet points.&lt;/span&gt;
&lt;span class="s2"&gt;Focus on key facts, arguments, and conclusions.&lt;/span&gt;

&lt;span class="s2"&gt;Content:&lt;/span&gt;
&lt;span class="si"&gt;{text}&lt;/span&gt;

&lt;span class="s2"&gt;Summary:&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;combine_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PromptTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;input_variables&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;You have summaries from sections of an article.&lt;/span&gt;
&lt;span class="s2"&gt;Write a single, coherent paragraph summarizing all of them.&lt;/span&gt;

&lt;span class="s2"&gt;Summaries:&lt;/span&gt;
&lt;span class="si"&gt;{text}&lt;/span&gt;

&lt;span class="s2"&gt;Final Summary:&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;load_summarize_chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;chain_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;map_reduce&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;map_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;map_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;combine_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;combine_prompt&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This gives you control over tone, format, and what the model pays attention to.&lt;/p&gt;
&lt;h2&gt;Scaling it up&lt;/h2&gt;
&lt;p&gt;If you're summarizing many URLs, a few things to watch:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rate limits&lt;/strong&gt; — Add &lt;code&gt;time.sleep()&lt;/code&gt; between requests, or use async loaders (&lt;code&gt;AsyncHtmlLoader&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token costs&lt;/strong&gt; — &lt;code&gt;map_reduce&lt;/code&gt; on a 10,000-word article can rack up tokens fast. Cache results if you're hitting the same URLs repeatedly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Paywalled / bot-protected sites&lt;/strong&gt; — &lt;code&gt;WebBaseLoader&lt;/code&gt; will get blocked. You need session cookies, headers spoofing, or a scraping API like Browserless or ScrapingBee.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Structured extraction&lt;/strong&gt; — If you want specific fields (author, date, key claims) rather than a freeform summary, swap the summarization chain for an extraction chain or use &lt;code&gt;PydanticOutputParser&lt;/code&gt; to force structured JSON output.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Swapping the LLM&lt;/h2&gt;
&lt;p&gt;LangChain's abstraction makes it easy to drop in a different model. For a local setup:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_community.llms&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Ollama&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Ollama&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;llama3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The rest of the chain stays the same. This matters if you're building something where data can't leave your machine — which is exactly the kind of setup that makes sense for projects handling sensitive internal docs.&lt;/p&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="LLM"/><category term="LangChain"/><category term="Python"/><category term="Web Scraping"/><category term="NLP"/></entry><entry><title>EffortCommerce — How Kactii Turned Effort Into Currency</title><link href="https://varunabishek.github.io/effort-commerce.html" rel="alternate"/><published>2026-08-02T00:00:00+05:30</published><updated>2026-08-02T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-08-02:/effort-commerce.html</id><summary type="html">&lt;p&gt;My internship at Kactii doesn't just give me tasks to complete. It pays me for doing them — in credits.&lt;/p&gt;
&lt;h2&gt;What EffortCommerce means here&lt;/h2&gt;
&lt;p&gt;Every time I take a session, push code on a project, write something useful, or pitch an idea that holds up, I earn credits. They stack up …&lt;/p&gt;</summary><content type="html">&lt;p&gt;My internship at Kactii doesn't just give me tasks to complete. It pays me for doing them — in credits.&lt;/p&gt;
&lt;h2&gt;What EffortCommerce means here&lt;/h2&gt;
&lt;p&gt;Every time I take a session, push code on a project, write something useful, or pitch an idea that holds up, I earn credits. They stack up in my account. Then I can go to the Kactii Academy store and spend them on real products — JBL earphones, a power bank, books, mock interviews, ergonomic gear.&lt;/p&gt;
&lt;p&gt;That's the whole system. Work goes in, credits come out, things come home.&lt;/p&gt;
&lt;h2&gt;How it changes how I show up&lt;/h2&gt;
&lt;p&gt;The shift is subtle but real. When effort has a direct, tangible return, the motivation to do good work stops relying on abstract career benefits or end-of-internship certificates. There's something sitting in a store with a credit price on it, and the gap between me and that thing is a specific amount of work.&lt;/p&gt;
&lt;p&gt;Taking sessions earns credits. Writing working code earns credits. Giving an idea that actually makes it into a decision earns credits. So I'm not just logging hours — I'm accumulating something.&lt;/p&gt;
&lt;h2&gt;Why this works better than a stipend model&lt;/h2&gt;
&lt;p&gt;A fixed stipend pays the same whether you contribute a lot or a little. EffortCommerce doesn't. The store is open to everyone, but your buying power is determined entirely by what you've put in. That's a clean incentive structure.&lt;/p&gt;
&lt;p&gt;And because the credits come from specific actions — sessions, projects, code, ideas — there's no ambiguity about what counts. Show up and contribute, and the credits follow. Skip a week and your balance doesn't move.&lt;/p&gt;
&lt;h2&gt;What's actually in the store&lt;/h2&gt;
&lt;p&gt;The range is wider than I expected. There are everyday-useful items like a Zebronics power bank (17,400 credits) and an ergonomic mouse pad (4,200 credits). There are audio products from JBL and boAt. There's &lt;em&gt;The Psychology of Money&lt;/em&gt; for 6,700 credits. There's a Python mock interview by Kactii itself — which is an interesting one, because spending credits on interview prep means your internship effort is directly funding your job readiness.&lt;/p&gt;
&lt;p&gt;The credit prices vary enough that short-term and long-term goals both fit. You don't have to wait months to redeem something, but the bigger items give you something to work toward.&lt;/p&gt;
&lt;h2&gt;The part that sticks&lt;/h2&gt;
&lt;p&gt;Most internships end with a certificate and a LinkedIn recommendation. This one ends with whatever you earned. The effort-to-reward loop is short, specific, and honest. That's harder to build than it sounds.&lt;/p&gt;</content><category term="Internship"/><category term="Internship"/><category term="Kactii"/><category term="Productivity"/><category term="Learning"/></entry><entry><title>Chains 101: Understanding LLMChain and Sequential Chains</title><link href="https://varunabishek.github.io/chains-101-llmchain-and-sequential-chains.html" rel="alternate"/><published>2026-07-30T00:00:00+05:30</published><updated>2026-07-30T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-07-30:/chains-101-llmchain-and-sequential-chains.html</id><summary type="html">&lt;p&gt;If you've poked around LangChain for more than ten minutes, you've hit the word "chain." It's everywhere — and for good reason. Chains are how you turn a raw language model into something that actually does a job.&lt;/p&gt;
&lt;h2&gt;What a chain is&lt;/h2&gt;
&lt;p&gt;A chain is a unit of logic that takes …&lt;/p&gt;</summary><content type="html">&lt;p&gt;If you've poked around LangChain for more than ten minutes, you've hit the word "chain." It's everywhere — and for good reason. Chains are how you turn a raw language model into something that actually does a job.&lt;/p&gt;
&lt;h2&gt;What a chain is&lt;/h2&gt;
&lt;p&gt;A chain is a unit of logic that takes an input, does something with it, and returns an output. That "something" usually involves an LLM, but it can also include memory lookups, document retrievals, API calls, or other chains. At its core, a chain is just a pipeline with a clear contract: input in, output out.&lt;/p&gt;
&lt;p&gt;LangChain gives you a bunch of built-in chain types. Two of them are worth knowing cold before you touch anything else: &lt;code&gt;LLMChain&lt;/code&gt; and &lt;code&gt;SequentialChain&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;LLMChain&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;LLMChain&lt;/code&gt; is the baseline. It wires together three things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A prompt template&lt;/strong&gt; — defines the structure of what you send to the model&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An LLM&lt;/strong&gt; — the model that processes the prompt&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An output parser&lt;/strong&gt; (optional) — parses the raw text response into something usable&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.chains&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LLMChain&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.prompts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PromptTemplate&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PromptTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;input_variables&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;topic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Explain &lt;/span&gt;&lt;span class="si"&gt;{topic}&lt;/span&gt;&lt;span class="s2"&gt; in two sentences, like I&amp;#39;m 15.&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LLMChain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;gradient descent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When you call &lt;code&gt;chain.run(topic="gradient descent")&lt;/code&gt;, LangChain fills in the template, sends it to the LLM, and hands you back the response. That's the whole thing. Simple, but it sets the pattern for everything more complex.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;PromptTemplate&lt;/code&gt; is where most of the real work happens. You define &lt;code&gt;input_variables&lt;/code&gt; — the slots that get filled at runtime — and write the template string around them. The LLM never sees &lt;code&gt;{topic}&lt;/code&gt;, it sees the fully-rendered prompt.&lt;/p&gt;
&lt;h2&gt;Why PromptTemplate matters&lt;/h2&gt;
&lt;p&gt;You could skip the template and just format strings yourself. But &lt;code&gt;PromptTemplate&lt;/code&gt; enforces that the variables you claim exist actually get passed in. If you declare &lt;code&gt;input_variables=["topic"]&lt;/code&gt; and forget to pass &lt;code&gt;topic&lt;/code&gt; at runtime, you get an explicit error instead of a silent bad output. That matters more as chains get complicated.&lt;/p&gt;
&lt;h2&gt;SequentialChain&lt;/h2&gt;
&lt;p&gt;One LLM call is often enough. But a lot of tasks need multiple steps — and the output of step one feeds into step two.&lt;/p&gt;
&lt;p&gt;That's what &lt;code&gt;SequentialChain&lt;/code&gt; is for. It runs a list of chains in order and threads outputs from earlier steps into the inputs of later ones.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.chains&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LLMChain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SequentialChain&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.prompts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PromptTemplate&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Step 1: generate a product name&lt;/span&gt;
&lt;span class="n"&gt;name_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PromptTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;input_variables&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;product_description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Create a short, catchy product name for: &lt;/span&gt;&lt;span class="si"&gt;{product_description}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;name_chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LLMChain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;name_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;product_name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Step 2: write a tagline for that name&lt;/span&gt;
&lt;span class="n"&gt;tagline_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PromptTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;input_variables&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;product_name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Write a one-line marketing tagline for a product called &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{product_name}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;.&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tagline_chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LLMChain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tagline_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tagline&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Wire them together&lt;/span&gt;
&lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SequentialChain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;chains&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name_chain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tagline_chain&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;input_variables&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;product_description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;output_variables&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;product_name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;tagline&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;product_description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;a water bottle that tracks hydration via a smartphone app&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;product_name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tagline&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The key detail: &lt;code&gt;output_key&lt;/code&gt;. Every &lt;code&gt;LLMChain&lt;/code&gt; in a sequential pipeline needs one, so the next chain in line knows what variable name to pull from. &lt;code&gt;SequentialChain&lt;/code&gt; stitches them together automatically — &lt;code&gt;product_description&lt;/code&gt; goes into chain one, &lt;code&gt;product_name&lt;/code&gt; (the output) becomes the input to chain two.&lt;/p&gt;
&lt;h2&gt;SimpleSequentialChain vs SequentialChain&lt;/h2&gt;
&lt;p&gt;LangChain has a simpler version called &lt;code&gt;SimpleSequentialChain&lt;/code&gt;. It's fine when each step produces exactly one output and passes it to the next step. There's no naming involved — the output of step N is just the input to step N+1.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SequentialChain&lt;/code&gt; is what you want when steps produce multiple outputs, or when later steps need inputs from earlier steps that aren't just the immediate predecessor. The &lt;code&gt;output_key&lt;/code&gt; / &lt;code&gt;input_variables&lt;/code&gt; system gives you that flexibility.&lt;/p&gt;
&lt;h2&gt;A quick mental model&lt;/h2&gt;
&lt;p&gt;Think of it this way: &lt;code&gt;LLMChain&lt;/code&gt; is one function call. &lt;code&gt;SequentialChain&lt;/code&gt; is a composed pipeline of function calls where outputs flow through as named variables. Once you have that picture, the rest of LangChain's chain ecosystem — &lt;code&gt;RouterChain&lt;/code&gt;, &lt;code&gt;TransformChain&lt;/code&gt;, retrieval chains — is just variations on the same idea.&lt;/p&gt;
&lt;p&gt;Get comfortable with these two first. They cover a surprising number of real use cases on their own.&lt;/p&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="LLM"/><category term="LangChain"/><category term="Chains"/><category term="Python"/><category term="AI Engineering"/></entry><entry><title>Giving Your Chatbot Memory: ConversationBufferMemory vs. Other Memory Types</title><link href="https://varunabishek.github.io/chatbot-memory-conversationbuffermemory-vs-other-types.html" rel="alternate"/><published>2026-07-30T00:00:00+05:30</published><updated>2026-07-30T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-07-30:/chatbot-memory-conversationbuffermemory-vs-other-types.html</id><summary type="html">&lt;p&gt;By default, an LLM has no idea what you said two messages ago. Every call is stateless — the model processes whatever is in the current prompt and nothing else. If you want a chatbot that remembers the conversation, you have to build that yourself. LangChain's memory modules are how you …&lt;/p&gt;</summary><content type="html">&lt;p&gt;By default, an LLM has no idea what you said two messages ago. Every call is stateless — the model processes whatever is in the current prompt and nothing else. If you want a chatbot that remembers the conversation, you have to build that yourself. LangChain's memory modules are how you do it.&lt;/p&gt;
&lt;h2&gt;Why memory is a design decision&lt;/h2&gt;
&lt;p&gt;There's no single right way to handle memory. A short customer support chat needs something different from a long-running research assistant. The tradeoff is almost always the same: how much context do you keep vs. how much do you spend on tokens?&lt;/p&gt;
&lt;p&gt;LangChain gives you several memory types. Here's what each one does and when to reach for it.&lt;/p&gt;
&lt;h2&gt;ConversationBufferMemory&lt;/h2&gt;
&lt;p&gt;The simplest option. It stores every message in the conversation — human and AI — and injects the full history into each new prompt.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ConversationBufferMemory&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.chains&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ConversationChain&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConversationBufferMemory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;conversation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConversationChain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;conversation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hi, I&amp;#39;m Varun.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;conversation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;What&amp;#39;s my name?&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The second call works. The model knows your name because the first exchange is still sitting in the buffer, prepended to the new prompt.&lt;/p&gt;
&lt;p&gt;The catch: the buffer grows with every message. Long enough conversations will push you past the model's context window and start costing real money per call. Fine for short sessions, fragile for anything open-ended.&lt;/p&gt;
&lt;h2&gt;ConversationBufferWindowMemory&lt;/h2&gt;
&lt;p&gt;Same idea as the buffer, but with a sliding window. You set &lt;code&gt;k&lt;/code&gt; — the number of recent exchanges to keep — and anything older than that gets dropped.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ConversationBufferWindowMemory&lt;/span&gt;

&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConversationBufferWindowMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With &lt;code&gt;k=5&lt;/code&gt;, the model sees the last five human-AI pairs and nothing before that. It's predictable and token-efficient. The tradeoff is that older context disappears hard — the model won't remember what you said in turn one once you're past turn six.&lt;/p&gt;
&lt;p&gt;Good fit for chat interfaces where recent context is what matters.&lt;/p&gt;
&lt;h2&gt;ConversationSummaryMemory&lt;/h2&gt;
&lt;p&gt;Instead of storing raw messages, this one compresses older parts of the conversation into a running summary using an LLM call.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ConversationSummaryMemory&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConversationSummaryMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The summary gets prepended to the prompt in place of the full history. Older exchanges don't vanish — they get distilled. A 20-turn conversation might compress down to a paragraph.&lt;/p&gt;
&lt;p&gt;The cost: you're making extra LLM calls to generate summaries. For very short conversations, that's overhead with no benefit. But for long sessions where you genuinely need the model to know what happened early on, this is often the right choice.&lt;/p&gt;
&lt;h2&gt;ConversationSummaryBufferMemory&lt;/h2&gt;
&lt;p&gt;A hybrid. It keeps recent messages verbatim (like the buffer) and summarizes everything older than a token threshold.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ConversationSummaryBufferMemory&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConversationSummaryBufferMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_token_limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once the raw message history crosses &lt;code&gt;max_token_limit&lt;/code&gt; tokens, older messages get rolled into a summary. Recent messages stay as-is. You get precise recent context and a compressed view of history — without blowing up your token count.&lt;/p&gt;
&lt;p&gt;This is the one worth reaching for in production chatbots that need to handle both short and long conversations gracefully.&lt;/p&gt;
&lt;h2&gt;ConversationEntityMemory&lt;/h2&gt;
&lt;p&gt;A different approach entirely. Instead of storing a transcript or a summary, it extracts entities from the conversation — names, places, products, concepts — and tracks what the model knows about each one.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ConversationEntityMemory&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConversationEntityMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you mention "Priya is the team lead on the backend project," the memory stores that fact about Priya. Later, when you ask "what's Priya working on?", the model has the entity context to answer correctly — even if the original message was 50 turns ago.&lt;/p&gt;
&lt;p&gt;Useful when your chatbot needs to track facts about specific people or things across a long session, not just the conversational flow.&lt;/p&gt;
&lt;h2&gt;Picking one&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Memory type&lt;/th&gt;
&lt;th&gt;Keeps&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ConversationBufferMemory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full transcript&lt;/td&gt;
&lt;td&gt;Short sessions, prototypes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ConversationBufferWindowMemory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Last &lt;code&gt;k&lt;/code&gt; exchanges&lt;/td&gt;
&lt;td&gt;Chat UIs with short context needs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ConversationSummaryMemory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;LLM-generated summary&lt;/td&gt;
&lt;td&gt;Long sessions, token budget matters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ConversationSummaryBufferMemory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Recent messages + summary of older ones&lt;/td&gt;
&lt;td&gt;Production bots, variable session lengths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ConversationEntityMemory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Facts about named entities&lt;/td&gt;
&lt;td&gt;Assistants tracking people, places, things&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Start with &lt;code&gt;ConversationBufferMemory&lt;/code&gt; when you're prototyping — it's zero config and easy to debug. Move to &lt;code&gt;ConversationSummaryBufferMemory&lt;/code&gt; when you start hitting token limits or dealing with sessions that run long. Use &lt;code&gt;ConversationEntityMemory&lt;/code&gt; when your use case is genuinely about tracking facts across a conversation, not just maintaining thread.&lt;/p&gt;
&lt;p&gt;Every memory type plugs into &lt;code&gt;ConversationChain&lt;/code&gt; (or any chain that accepts a &lt;code&gt;memory&lt;/code&gt; argument) the same way. The interface doesn't change — just what gets stored and how.&lt;/p&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="LLM"/><category term="LangChain"/><category term="Memory"/><category term="Chatbot"/><category term="Python"/><category term="AI Engineering"/></entry><entry><title>Embeddings Explained for LangChain Beginners</title><link href="https://varunabishek.github.io/embeddings-explained-langchain-beginners.html" rel="alternate"/><published>2026-07-30T00:00:00+05:30</published><updated>2026-07-30T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-07-30:/embeddings-explained-langchain-beginners.html</id><summary type="html">&lt;p&gt;Before you can build a retrieval system, you need to understand what embeddings are and why they exist. Skip this and the rest of RAG won't make sense — you'll be configuring things without knowing what they do.&lt;/p&gt;
&lt;h2&gt;What an embedding is&lt;/h2&gt;
&lt;p&gt;An embedding is a list of numbers that represents …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Before you can build a retrieval system, you need to understand what embeddings are and why they exist. Skip this and the rest of RAG won't make sense — you'll be configuring things without knowing what they do.&lt;/p&gt;
&lt;h2&gt;What an embedding is&lt;/h2&gt;
&lt;p&gt;An embedding is a list of numbers that represents the meaning of a piece of text. A sentence, a paragraph, a document — each one gets converted into a fixed-length vector of floats, typically hundreds or thousands of dimensions long.&lt;/p&gt;
&lt;p&gt;The useful property: texts that mean similar things end up with vectors that are close together in that high-dimensional space. "The cat sat on the mat" and "A feline rested on the rug" will have similar embeddings. "Stock market futures" will be far away from both.&lt;/p&gt;
&lt;p&gt;This is what makes semantic search possible. You're not matching keywords — you're measuring meaning distance.&lt;/p&gt;
&lt;h2&gt;Why LangChain needs them&lt;/h2&gt;
&lt;p&gt;LangChain's retrieval pipeline works like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Split documents into chunks&lt;/li&gt;
&lt;li&gt;Embed each chunk into a vector&lt;/li&gt;
&lt;li&gt;Store those vectors in a vector database&lt;/li&gt;
&lt;li&gt;At query time, embed the user's question&lt;/li&gt;
&lt;li&gt;Find the chunks whose vectors are closest to the question vector&lt;/li&gt;
&lt;li&gt;Pass those chunks to the LLM as context&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Steps 2 and 4 are where embeddings come in. The same embedding model has to handle both — if you embed your documents with one model and your queries with another, the vectors live in different spaces and the similarity search breaks.&lt;/p&gt;
&lt;h2&gt;Using embeddings in LangChain&lt;/h2&gt;
&lt;p&gt;LangChain wraps embedding models behind a common interface. Here's the OpenAI embedder:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbeddings&lt;/span&gt;

&lt;span class="n"&gt;embedder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text-embedding-3-small&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Embed a single query&lt;/span&gt;
&lt;span class="n"&gt;query_vector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embedder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embed_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;What is gradient descent?&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Embed a list of documents&lt;/span&gt;
&lt;span class="n"&gt;doc_vectors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embedder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embed_documents&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;Gradient descent is an optimization algorithm.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;Neural networks learn by adjusting weights.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;Python is a general-purpose programming language.&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_vector&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;       &lt;span class="c1"&gt;# 1536 for text-embedding-3-small&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc_vectors&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;        &lt;span class="c1"&gt;# 3 vectors&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc_vectors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;     &lt;span class="c1"&gt;# 1536 floats per vector&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;embed_query&lt;/code&gt; is for single inputs at retrieval time. &lt;code&gt;embed_documents&lt;/code&gt; is for batching your corpus during indexing. The distinction is minor — some models apply different preprocessing to each — but it's the correct way to call the interface.&lt;/p&gt;
&lt;h2&gt;Using a local embedding model&lt;/h2&gt;
&lt;p&gt;You don't need an API for embeddings. HuggingFace models run locally and are free.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_huggingface&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HuggingFaceEmbeddings&lt;/span&gt;

&lt;span class="n"&gt;embedder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HuggingFaceEmbeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sentence-transformers/all-MiniLM-L6-v2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;vector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embedder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embed_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;How do transformers work?&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# 384&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt; is a solid starting point — small, fast, and good enough for most tasks. The vectors are 384-dimensional instead of 1536, which means smaller storage and faster search at the cost of some precision.&lt;/p&gt;
&lt;p&gt;For production use cases where accuracy matters more than latency or cost, &lt;code&gt;text-embedding-3-large&lt;/code&gt; from OpenAI or &lt;code&gt;bge-large-en-v1.5&lt;/code&gt; from BAAI are worth testing.&lt;/p&gt;
&lt;h2&gt;What similarity actually means&lt;/h2&gt;
&lt;p&gt;Once you have vectors, you need a way to measure how close two of them are. The two most common measures:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cosine similarity&lt;/strong&gt; — measures the angle between vectors. Two vectors pointing in the same direction score 1.0, regardless of their magnitude. This is the default for most embedding models because it handles texts of different lengths gracefully.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dot product&lt;/strong&gt; — measures both direction and magnitude. Faster to compute, but sensitive to vector scale. Some models (like OpenAI's) are trained to use dot product — check the model docs before assuming cosine is correct.&lt;/p&gt;
&lt;p&gt;In practice, LangChain and most vector databases handle this for you. But knowing which one your retriever uses matters when you're debugging why certain results rank unexpectedly.&lt;/p&gt;
&lt;h2&gt;Embeddings are frozen at index time&lt;/h2&gt;
&lt;p&gt;One thing that trips people up: the embedding model doesn't run at query time on your stored documents. You embed documents once, store the vectors, and those vectors stay fixed. The model only runs again when you embed a new query or add new documents.&lt;/p&gt;
&lt;p&gt;This means if you switch embedding models after indexing, you have to re-embed your entire document store. The old vectors and new query vectors are incompatible — they live in different spaces. Build your pipeline with a specific model chosen early, or you'll be re-indexing a lot.&lt;/p&gt;
&lt;h2&gt;A complete example&lt;/h2&gt;
&lt;p&gt;Here's the full flow from documents to query — embeddings included:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbeddings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.vectorstores&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FAISS&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.text_splitter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RecursiveCharacterTextSplitter&lt;/span&gt;

&lt;span class="c1"&gt;# Your raw text&lt;/span&gt;
&lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;LangChain is a framework for building LLM applications.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;FAISS is a library for efficient vector similarity search.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;Embeddings convert text into numerical vectors.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;RAG combines retrieval with language model generation.&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Split (skip for short texts, but good practice)&lt;/span&gt;
&lt;span class="n"&gt;splitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RecursiveCharacterTextSplitter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk_overlap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;splitter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Embed and store&lt;/span&gt;
&lt;span class="n"&gt;embedder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text-embedding-3-small&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;vectorstore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FAISS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embedder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Query&lt;/span&gt;
&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;What is RAG?&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vectorstore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;similarity_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page_content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;FAISS.from_documents&lt;/code&gt; calls &lt;code&gt;embed_documents&lt;/code&gt; under the hood and builds the index. &lt;code&gt;similarity_search&lt;/code&gt; calls &lt;code&gt;embed_query&lt;/code&gt; on your question, then finds the closest vectors. The LLM step would come after — you pass &lt;code&gt;results&lt;/code&gt; as context into a prompt.&lt;/p&gt;
&lt;h2&gt;Choosing an embedding model&lt;/h2&gt;
&lt;p&gt;A few things to weigh:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dimension size&lt;/strong&gt; — higher dimensions capture more nuance but cost more storage and compute. 384 is fine for prototypes; 1536 or 3072 for production.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Max input tokens&lt;/strong&gt; — most models cap at 512 or 8192 tokens per chunk. Chunks longer than the cap get silently truncated. Know your model's limit before indexing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Domain fit&lt;/strong&gt; — general models handle general text well. For legal, medical, or code-heavy corpora, look for domain-specific models or test a few before committing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost&lt;/strong&gt; — local models are free to run. API-based models charge per token. At scale, that adds up.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The embedding layer looks like configuration. It's actually one of the most load-bearing decisions in a RAG pipeline.&lt;/p&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="LLM"/><category term="LangChain"/><category term="Embeddings"/><category term="RAG"/><category term="Vector Search"/><category term="Python"/><category term="AI Engineering"/></entry><entry><title>LangSmith Basics: Debugging and Tracing Your Chains</title><link href="https://varunabishek.github.io/langsmith-basics-debugging-tracing-chains.html" rel="alternate"/><published>2026-07-30T00:00:00+05:30</published><updated>2026-07-30T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-07-30:/langsmith-basics-debugging-tracing-chains.html</id><summary type="html">&lt;p&gt;LLM applications fail in ways that are hard to see. A chain returns a bad answer and you don't know whether the prompt was wrong, the retriever missed, or the model just hallucinated. LangSmith is how you look inside.&lt;/p&gt;
&lt;h2&gt;What LangSmith is&lt;/h2&gt;
&lt;p&gt;LangSmith is Anthropic's observability platform for LangChain — it …&lt;/p&gt;</summary><content type="html">&lt;p&gt;LLM applications fail in ways that are hard to see. A chain returns a bad answer and you don't know whether the prompt was wrong, the retriever missed, or the model just hallucinated. LangSmith is how you look inside.&lt;/p&gt;
&lt;h2&gt;What LangSmith is&lt;/h2&gt;
&lt;p&gt;LangSmith is Anthropic's observability platform for LangChain — it records every step of your chain as a trace. Prompt inputs, LLM outputs, retrieval results, tool calls, token counts, latency — all of it, in one place, for every run.&lt;/p&gt;
&lt;p&gt;You don't change how your chain is written. You add a few environment variables and tracing happens automatically.&lt;/p&gt;
&lt;h2&gt;Setting it up&lt;/h2&gt;
&lt;p&gt;Create an account at &lt;a href="https://smith.langchain.com"&gt;smith.langchain.com&lt;/a&gt; and grab an API key from the settings page. Then set these before your chain runs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;LANGCHAIN_TRACING_V2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;true&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;LANGCHAIN_ENDPOINT&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://api.smith.langchain.com&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;LANGCHAIN_API_KEY&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;your-api-key-here&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;LANGCHAIN_PROJECT&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;my-first-project&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;LANGCHAIN_PROJECT&lt;/code&gt; is just a label — traces get grouped under it in the UI. Use something descriptive: the chain name, the feature, or the experiment you're running.&lt;/p&gt;
&lt;p&gt;After that, run any LangChain code. The traces show up in your project dashboard automatically.&lt;/p&gt;
&lt;h2&gt;What a trace looks like&lt;/h2&gt;
&lt;p&gt;Every chain run produces a tree of spans. The root span is the chain itself. Under it, you'll see child spans for each step — LLM calls, retrieval operations, tool invocations, memory reads.&lt;/p&gt;
&lt;p&gt;Each span shows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Input&lt;/strong&gt; — what was passed in at that step&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Output&lt;/strong&gt; — what came back&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Latency&lt;/strong&gt; — how long it took&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token usage&lt;/strong&gt; — prompt tokens, completion tokens, total&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Errors&lt;/strong&gt; — if the step threw, the full traceback&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a simple &lt;code&gt;ConversationChain&lt;/code&gt;, the trace is shallow — one LLM call with the formatted prompt and the raw response. For a multi-step agent, the tree can go several levels deep with tool calls branching off the main path.&lt;/p&gt;
&lt;h2&gt;Debugging a bad output&lt;/h2&gt;
&lt;p&gt;Say your RAG chain returns a wrong answer. Without tracing, you're guessing. With LangSmith, you open the trace and work backwards:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Look at the LLM span — what prompt did the model actually receive? Was the context there?&lt;/li&gt;
&lt;li&gt;Look at the retrieval span — what chunks came back? Were they relevant?&lt;/li&gt;
&lt;li&gt;Look at the query — did the question get reformulated somewhere upstream?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Most of the time, the problem is obvious once you see the actual prompt. A template bug, a missing variable, a retriever that returned garbage — these all show up immediately.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.chains&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RetrievalQA&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain_openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbeddings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.vectorstores&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FAISS&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;embedder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbeddings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;vectorstore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FAISS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_texts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;LangSmith traces every step of your LangChain runs.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;embedder&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;retriever&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vectorstore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_retriever&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RetrievalQA&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_chain_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# This run will appear in LangSmith with full retrieval + LLM trace&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;What does LangSmith do?&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Open the project in the UI after this runs. You'll see the retrieval span with the exact chunks that were fetched, then the LLM span with the fully-rendered prompt those chunks were injected into.&lt;/p&gt;
&lt;h2&gt;Adding metadata and tags&lt;/h2&gt;
&lt;p&gt;Traces are more useful when you can filter them. LangSmith lets you attach metadata and tags to runs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.callbacks.manager&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;collect_runs&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;collect_runs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;What does LangSmith do?&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;production&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;rag-v2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;user_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;u_123&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;session_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;s_456&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;run_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;traced_runs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Trace ID: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;run_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Tags and metadata let you slice your traces in the UI — filter by environment, by user, by chain version. When you're running experiments, tags are how you keep the results organized.&lt;/p&gt;
&lt;h2&gt;Comparing runs&lt;/h2&gt;
&lt;p&gt;LangSmith has a comparison view. Select two runs from the same project and it shows you inputs, outputs, and latencies side by side. This is useful when you change a prompt and want to know whether the new version actually improved things — not just on the one example you tested manually, but across a batch.&lt;/p&gt;
&lt;h2&gt;Datasets and evaluation&lt;/h2&gt;
&lt;p&gt;Once you have traces, you can promote individual runs to a dataset. Pick a run where the output was correct, save it as an example, and build up a test set over time. LangSmith can then run your chain against the dataset and score outputs automatically.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langsmith&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Create a dataset&lt;/span&gt;
&lt;span class="n"&gt;dataset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rag-eval-set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Add an example&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_example&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;query&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;What does LangSmith do?&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;answer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;LangSmith traces every step of LangChain runs.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;dataset_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;From here you can run evaluations programmatically or trigger them from the UI. It's a short path from "this answer looked right" to a repeatable regression test.&lt;/p&gt;
&lt;h2&gt;What to check first when things break&lt;/h2&gt;
&lt;p&gt;In rough order of how often each one is the actual problem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Formatted prompt&lt;/strong&gt; — look at the LLM input span. Is the prompt what you expected? Template bugs show up here.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retrieved chunks&lt;/strong&gt; — if the answer is in your documents but the model missed it, check what the retriever actually returned.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token counts&lt;/strong&gt; — if a run is slow or expensive, check whether a prompt is bloated. Memory accumulation and large context injections show up clearly here.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Latency by step&lt;/strong&gt; — if a chain is slow, the latency breakdown shows exactly which step is the bottleneck.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;LangSmith won't fix your chain for you, but it removes the guesswork. Most debugging sessions that used to take an hour of print statements take five minutes once you can see every step.&lt;/p&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="LLM"/><category term="LangChain"/><category term="LangSmith"/><category term="Debugging"/><category term="Tracing"/><category term="Observability"/><category term="Python"/><category term="AI Engineering"/></entry><entry><title>Text Splitters Explained: Why Chunking Strategy Matters</title><link href="https://varunabishek.github.io/text-splitters-chunking-strategy.html" rel="alternate"/><published>2026-07-30T00:00:00+05:30</published><updated>2026-07-30T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-07-30:/text-splitters-chunking-strategy.html</id><summary type="html">&lt;p&gt;When you build a retrieval-augmented system, most of the tuning work happens before the LLM ever sees a document. How you split text determines what gets retrieved — and bad splits mean bad answers, even with a great model.&lt;/p&gt;
&lt;h2&gt;The problem with long documents&lt;/h2&gt;
&lt;p&gt;LLMs have a context window. You can't …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When you build a retrieval-augmented system, most of the tuning work happens before the LLM ever sees a document. How you split text determines what gets retrieved — and bad splits mean bad answers, even with a great model.&lt;/p&gt;
&lt;h2&gt;The problem with long documents&lt;/h2&gt;
&lt;p&gt;LLMs have a context window. You can't dump a 50-page PDF into a prompt. So you split it into chunks, embed each chunk, store them in a vector database, and retrieve the most relevant ones at query time.&lt;/p&gt;
&lt;p&gt;The question is: how do you split? Cut in the wrong place and you get chunks that are missing context, start mid-sentence, or bury the answer across a boundary where no single retrieval will find it. The model can only work with what you hand it.&lt;/p&gt;
&lt;h2&gt;CharacterTextSplitter&lt;/h2&gt;
&lt;p&gt;The most basic option. It splits on a character — usually a newline or a space — and tries to keep chunks under a specified size.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.text_splitter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CharacterTextSplitter&lt;/span&gt;

&lt;span class="n"&gt;splitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CharacterTextSplitter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;separator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;chunk_overlap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;splitter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;your_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;chunk_size&lt;/code&gt; controls how big each chunk gets (in characters). &lt;code&gt;chunk_overlap&lt;/code&gt; is how much text repeats between adjacent chunks — this matters because answers sometimes straddle a boundary, and overlap gives the retriever a chance to catch them.&lt;/p&gt;
&lt;p&gt;It works. It's also blunt. A &lt;code&gt;\n&lt;/code&gt; split doesn't know whether it's cutting between paragraphs or in the middle of a table.&lt;/p&gt;
&lt;h2&gt;RecursiveCharacterTextSplitter&lt;/h2&gt;
&lt;p&gt;This is the one LangChain recommends by default — and for good reason. Instead of one separator, it tries a list of them in order: &lt;code&gt;["\n\n", "\n", " ", ""]&lt;/code&gt;. It starts with paragraph breaks, falls back to line breaks, then words, then characters.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.text_splitter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RecursiveCharacterTextSplitter&lt;/span&gt;

&lt;span class="n"&gt;splitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RecursiveCharacterTextSplitter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;chunk_overlap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;splitter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;your_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The result is chunks that respect natural text structure wherever possible. A paragraph that fits under the size limit stays together. Only when it's too big does the splitter go finer. That behavior makes retrieved chunks far more coherent than character-level splits.&lt;/p&gt;
&lt;p&gt;Use this as your starting point for most plain-text documents.&lt;/p&gt;
&lt;h2&gt;TokenTextSplitter&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;CharacterTextSplitter&lt;/code&gt; measures chunk size in characters. But LLMs charge for tokens, and token count doesn't map cleanly to character count — especially across languages, code, or punctuation-heavy text.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TokenTextSplitter&lt;/code&gt; measures in tokens instead.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.text_splitter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TokenTextSplitter&lt;/span&gt;

&lt;span class="n"&gt;splitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TokenTextSplitter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk_overlap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;splitter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;your_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When your context window budget is tight and you're working with text that has variable token density — technical docs, mixed-language content, code — this gives you more precise control.&lt;/p&gt;
&lt;h2&gt;MarkdownHeaderTextSplitter&lt;/h2&gt;
&lt;p&gt;Structure-aware splitting. For Markdown documents, this one splits on headers (&lt;code&gt;#&lt;/code&gt;, &lt;code&gt;##&lt;/code&gt;, &lt;code&gt;###&lt;/code&gt;) and carries the header metadata into each chunk.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.text_splitter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MarkdownHeaderTextSplitter&lt;/span&gt;

&lt;span class="n"&gt;headers_to_split_on&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Header 1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;##&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Header 2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;###&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Header 3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;splitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MarkdownHeaderTextSplitter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers_to_split_on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers_to_split_on&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;splitter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;your_markdown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Each chunk knows which section it came from. When you store these in a vector database, you can filter by section at retrieval time or include the header in the chunk text so the LLM has context about where the content lives.&lt;/p&gt;
&lt;p&gt;Good fit for technical documentation, wikis, or any structured Markdown corpus.&lt;/p&gt;
&lt;h2&gt;PythonCodeTextSplitter / Language splitters&lt;/h2&gt;
&lt;p&gt;Code has its own structure — functions, classes, blocks. Splitting it on character count will cut through function definitions and break the semantic unit the retriever should be working with.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;langchain.text_splitter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RecursiveCharacterTextSplitter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Language&lt;/span&gt;

&lt;span class="n"&gt;splitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RecursiveCharacterTextSplitter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_language&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Language&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PYTHON&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;chunk_overlap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;splitter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;your_python_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;LangChain supports multiple languages here — Python, JS, Markdown, HTML, and others. The splitter knows the syntax boundaries for each and tries to keep logical units intact.&lt;/p&gt;
&lt;h2&gt;Why chunk_overlap matters&lt;/h2&gt;
&lt;p&gt;Every splitter has a &lt;code&gt;chunk_overlap&lt;/code&gt; parameter and it's worth taking seriously. If your chunks are 500 characters with zero overlap, a sentence that starts at character 495 of one chunk and ends at character 10 of the next is effectively unfindable — no single retrieved chunk contains it whole.&lt;/p&gt;
&lt;p&gt;Overlap duplicates some text across adjacent chunks so that boundary content shows up fully in at least one of them. A 10–20% overlap is a reasonable default. Bigger overlaps help retrieval quality but increase your vector store size and embedding cost.&lt;/p&gt;
&lt;h2&gt;The actual impact on retrieval quality&lt;/h2&gt;
&lt;p&gt;Chunking strategy doesn't change what the LLM knows — it changes what the retriever can find. A model can only answer from what gets passed to it. If the relevant passage is split across two chunks and only one is retrieved, you get a partial answer or no answer. If chunks are too large, retrieved content is noisy and the signal gets buried.&lt;/p&gt;
&lt;p&gt;Most RAG failures that look like model failures are actually retrieval failures. And most retrieval failures trace back to chunking. Get this layer right before you start tuning embeddings or prompts.&lt;/p&gt;
&lt;p&gt;A reasonable starting point: &lt;code&gt;RecursiveCharacterTextSplitter&lt;/code&gt; with &lt;code&gt;chunk_size=500&lt;/code&gt;, &lt;code&gt;chunk_overlap=50&lt;/code&gt;, then look at what your actual retrieved chunks contain. If the model keeps missing things that are clearly in the document, the chunk boundary is usually why.&lt;/p&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="LLM"/><category term="LangChain"/><category term="RAG"/><category term="Text Splitters"/><category term="Chunking"/><category term="Python"/><category term="AI Engineering"/></entry><entry><title>Graph RAG: Retrieval Augmented Generation Meets Knowledge Graphs</title><link href="https://varunabishek.github.io/graph-rag.html" rel="alternate"/><published>2026-07-12T00:00:00+05:30</published><updated>2026-07-12T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-07-12:/graph-rag.html</id><summary type="html">&lt;p&gt;Standard RAG retrieves chunks of text and hands them to an LLM. Graph RAG retrieves a structured slice of a knowledge graph instead — entities, relationships, and the paths between them. That shift changes what kinds of questions the system can actually answer.&lt;/p&gt;
&lt;h2&gt;Why vector search runs out of road&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Chunk …&lt;/strong&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Standard RAG retrieves chunks of text and hands them to an LLM. Graph RAG retrieves a structured slice of a knowledge graph instead — entities, relationships, and the paths between them. That shift changes what kinds of questions the system can actually answer.&lt;/p&gt;
&lt;h2&gt;Why vector search runs out of road&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Chunk retrieval&lt;/strong&gt; — A vector database finds text chunks that are semantically similar to a query and stuffs them into the prompt. It works well for lookup questions: "What was the revenue in Q3?" It breaks down for questions that need connecting several facts scattered across different documents.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The multi-hop problem&lt;/strong&gt; — Ask "Which suppliers does our biggest customer's biggest competitor use?" and a vector search has no reliable way to chain that logic. Each chunk is retrieved independently based on similarity to the query, not based on the relationships between the entities involved.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lost global context&lt;/strong&gt; — A single chunk rarely captures how an entity relates to the rest of the document set. A knowledge graph does, because the relationships are stored explicitly rather than implied by proximity in text.&lt;/p&gt;
&lt;h2&gt;What Graph RAG actually does&lt;/h2&gt;
&lt;p&gt;Graph RAG builds a knowledge graph from source documents, then retrieves subgraphs relevant to a query instead of flat text chunks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Entity extraction&lt;/strong&gt; — An LLM (or a dedicated NER pipeline) reads the source documents and pulls out entities: people, organizations, products, events, dates.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Relationship extraction&lt;/strong&gt; — The same pass identifies relationships between entities — "acquired," "reports to," "supplies," "located in" — and stores them as edges connecting the entity nodes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Graph construction&lt;/strong&gt; — Entities and relationships get written into a graph database (Neo4j, TigerGraph, or a similar store). Each node can carry metadata and a link back to the source text it came from.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Community detection&lt;/strong&gt; — Many Graph RAG implementations, including Microsoft's original approach, cluster the graph into communities of closely related entities and generate a summary for each cluster. This gives the system a mid-level view between raw entities and the full graph.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Query-time retrieval&lt;/strong&gt; — At query time, the system identifies relevant entities in the question, walks the graph to pull connected nodes and relationships, and passes that structured context to the LLM alongside or instead of raw text chunks.&lt;/p&gt;
&lt;h2&gt;Local search vs global search&lt;/h2&gt;
&lt;p&gt;Microsoft's Graph RAG paper splits retrieval into two modes, and the distinction matters for how you design a system around it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Local search&lt;/strong&gt; — Starts from specific entities mentioned in the query and expands outward through their direct relationships. Good for questions about a particular entity: "What products does this company make?"&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Global search&lt;/strong&gt; — Uses the community summaries to answer questions about the dataset as a whole: "What are the main themes across all these documents?" Vector search can't do this at all, since no single chunk contains a dataset-wide view.&lt;/p&gt;
&lt;h2&gt;Where it helps and where it doesn't&lt;/h2&gt;
&lt;p&gt;Graph RAG isn't a universal upgrade over vector RAG. It's a different tool for a different shape of problem.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multi-hop reasoning across entities and relationships&lt;/li&gt;
&lt;li&gt;Questions that require a summary or theme across an entire corpus, not a single document&lt;/li&gt;
&lt;li&gt;Domains with a naturally graph-like structure: org charts, supply chains, citation networks, regulatory filings&lt;/li&gt;
&lt;li&gt;Answers where traceability to a specific relationship chain matters, since graph paths are auditable in a way that "top-k similar chunks" isn't&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It costs more to build and maintain. Extracting entities and relationships accurately requires either a strong LLM pass over every document or a hand-tuned extraction pipeline, and errors compound: a missed or mislabeled edge quietly breaks every query that depends on it. For a simple FAQ bot or single-document Q&amp;amp;A, plain vector RAG remains faster to build and cheaper to run.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Rule of thumb: if your questions are "find the fact," use vector RAG. If they're "connect the facts," look at Graph RAG.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;A few implementation notes&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Hybrid retrieval&lt;/strong&gt; — Most production systems don't pick one or the other. They combine vector search for initial candidate retrieval with graph traversal for relationship expansion, then merge both into the final context window.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Extraction quality is the bottleneck&lt;/strong&gt; — The graph is only as good as the entity and relationship extraction step. Garbage extraction produces a graph full of duplicate entities and missing edges, and no amount of clever retrieval logic fixes that downstream.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cost&lt;/strong&gt; — Building the graph is a one-time (or periodic) batch cost, separate from query-time cost. Budget for re-running extraction as source documents get added or updated, not just for the initial build.&lt;/p&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="LLM"/><category term="RAG"/><category term="Knowledge Graphs"/><category term="GraphRAG"/></entry><entry><title>MCP: A Common Protocol for Connecting LLMs to Tools</title><link href="https://varunabishek.github.io/model-context-protocol.html" rel="alternate"/><published>2026-07-12T00:00:00+05:30</published><updated>2026-07-12T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-07-12:/model-context-protocol.html</id><summary type="html">&lt;p&gt;Every LLM app that wants to read a file, query a database, or call an API needs some way to describe that capability to the model and route the model's requests back to the right system. Before MCP, every vendor built that wiring differently. MCP is Anthropic's attempt at a …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Every LLM app that wants to read a file, query a database, or call an API needs some way to describe that capability to the model and route the model's requests back to the right system. Before MCP, every vendor built that wiring differently. MCP is Anthropic's attempt at a shared standard for it.&lt;/p&gt;
&lt;h2&gt;The problem it solves&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;N×M integrations&lt;/strong&gt; — Without a standard, connecting M different LLM apps to N different tools means building M×N custom integrations. Each app writes its own connector for Slack, its own connector for GitHub, its own connector for a Postgres database.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No portability&lt;/strong&gt; — A tool integration built for one LLM app usually can't be reused in another. The tool description format, the auth handling, the request/response shape — all of it is bespoke.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Context fragmentation&lt;/strong&gt; — Tools built ad hoc for a single app tend to be shallow: enough to answer one demo query, not enough to be a real interface to the underlying system.&lt;/p&gt;
&lt;h2&gt;What MCP is&lt;/h2&gt;
&lt;p&gt;MCP defines a client-server protocol for exposing data and functionality to LLM applications, built on JSON-RPC.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MCP servers&lt;/strong&gt; — A server exposes one or more capabilities: tools (functions the model can call), resources (data the model can read, like files or database records), and prompts (reusable prompt templates). A server might wrap a single system — GitHub, Google Drive, a local filesystem — or several related ones.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MCP clients&lt;/strong&gt; — The LLM application (Claude Desktop, an IDE, a custom agent) runs an MCP client that connects to one or more servers, discovers what they expose, and routes model requests to the right server.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Transport&lt;/strong&gt; — Servers can run locally over stdio, which is common for filesystem or local-tool access, or remotely over HTTP with server-sent events, which is common for hosted services like a company's internal API.&lt;/p&gt;
&lt;h2&gt;The three primitives&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Tools&lt;/strong&gt; — Functions with a name, a description, and a JSON schema for inputs. The model decides when to call one based on the conversation; the client executes it and returns the result.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Resources&lt;/strong&gt; — Structured or unstructured data the client can fetch and inject into context — a file's contents, a database row, a page from a wiki. Unlike tools, resources are typically read, not invoked with arguments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prompts&lt;/strong&gt; — Predefined prompt templates a server can offer, often parameterized, so a client doesn't have to hardcode common workflows.&lt;/p&gt;
&lt;h2&gt;Why build a server instead of a custom integration&lt;/h2&gt;
&lt;p&gt;Building against MCP instead of a proprietary plugin API means the same server works across every MCP-compatible client without rewriting it. Anthropic ships MCP support in Claude Desktop and the API; other vendors have added client support since. A team maintaining a GitHub integration built as an MCP server gets Claude Desktop, IDE plugins, and custom agents as consumers, without three separate integration codebases.&lt;/p&gt;
&lt;p&gt;There's a real ecosystem argument here too: a growing catalog of community and vendor-built servers (Slack, Postgres, Google Drive, Sentry, and others) means many integrations don't need to be built at all.&lt;/p&gt;
&lt;h2&gt;Tradeoffs&lt;/h2&gt;
&lt;p&gt;MCP is young, and that shows up in a few places.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Server quality varies a lot — some are thin wrappers with poor error handling, others are well-built&lt;/li&gt;
&lt;li&gt;Security models differ by transport: a local stdio server runs with the permissions of whatever launched it, which is a different trust boundary than a hosted HTTP server behind auth&lt;/li&gt;
&lt;li&gt;Discovery and versioning across many servers is still mostly manual; there's no centralized registry with strong guarantees yet&lt;/li&gt;
&lt;li&gt;Not every LLM vendor supports it, so it's not yet a universal standard the way HTTP is&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of that erases the core value: MCP moves tool integration from "build it per app" to "build it once, connect it everywhere that speaks the protocol." Whether it becomes the actual industry standard depends on adoption outside Anthropic's own ecosystem, which is still playing out.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you're deciding whether to build a custom tool integration or an MCP server, the MCP server is almost always the better long-term bet — the marginal cost over a custom integration is small, and the reuse is large.&lt;/p&gt;
&lt;/blockquote&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="LLM"/><category term="MCP"/><category term="Tool Use"/><category term="Agents"/></entry><entry><title>Agents vs. Chains in LangChain: Who's Actually Driving?</title><link href="https://varunabishek.github.io/Whos-Actually-Driving.html" rel="alternate"/><published>2026-07-10T00:00:00+05:30</published><updated>2026-07-10T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-07-10:/Whos-Actually-Driving.html</id><summary type="html">&lt;p&gt;&lt;em&gt;A chain follows the map you drew. An agent draws its own.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;LangChain gives you two ways to wire an LLM into a workflow: chains and agents. Both connect prompts, tools, and models into something useful, but the control flow works in opposite directions.&lt;/p&gt;
&lt;h2&gt;Chains: fixed routes&lt;/h2&gt;
&lt;p&gt;A chain is …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;A chain follows the map you drew. An agent draws its own.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;LangChain gives you two ways to wire an LLM into a workflow: chains and agents. Both connect prompts, tools, and models into something useful, but the control flow works in opposite directions.&lt;/p&gt;
&lt;h2&gt;Chains: fixed routes&lt;/h2&gt;
&lt;p&gt;A chain is a sequence of steps you define up front. Step one calls the model, step two parses the output, step three feeds it into a database lookup, step four formats a response. The order is fixed at build time, and the LLM has no say in what happens next.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LLMChain&lt;/strong&gt; — The simplest building block: a prompt template plus a model call. Input goes in, text comes out.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SequentialChain&lt;/strong&gt; — Runs multiple chains back to back, passing outputs forward as inputs. Good for multi-stage pipelines like "summarize, then translate, then extract keywords."&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RouterChain&lt;/strong&gt; — Picks one of several chains to run based on the input, but the routing logic itself is deterministic code, not a model deciding mid-task.&lt;/p&gt;
&lt;p&gt;Chains are predictable. You can trace exactly what will happen before you run anything, and that makes them easy to test and debug.&lt;/p&gt;
&lt;h2&gt;Agents: the model picks the path&lt;/h2&gt;
&lt;p&gt;An agent flips the control. Instead of a hardcoded sequence, the LLM gets a set of tools and a goal, and it decides at each step what to do next: which tool to call, what input to give it, and when it's done.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tool&lt;/strong&gt; — A function the agent can call: a search API, a calculator, a SQL query runner, a custom Python function. The agent decides when and how to use it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AgentExecutor&lt;/strong&gt; — The runtime loop that lets the agent think, act, observe the result, and think again, until it reaches a final answer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ReAct pattern&lt;/strong&gt; — A common agent style where the model alternates between reasoning ("I need to look up the current stock price") and acting (calling the tool that does it).&lt;/p&gt;
&lt;p&gt;This loop keeps going until the agent decides it has enough information to answer, or it hits a step limit you set as a safeguard.&lt;/p&gt;
&lt;h2&gt;Where the two actually diverge&lt;/h2&gt;
&lt;p&gt;The core difference isn't the tools or the prompts. It's who decides the next step.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In a chain, you decide. The path is baked into your code.&lt;/li&gt;
&lt;li&gt;In an agent, the model decides. The path emerges while it runs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That single distinction cascades into everything else. Chains are deterministic and fast because there's no reasoning overhead between steps. Agents are flexible but slower and less predictable, since each decision costs a model call and a wrong turn can send the whole task off track.&lt;/p&gt;
&lt;h2&gt;When to use which&lt;/h2&gt;
&lt;p&gt;Reach for a chain when the task has a known shape: extract fields from a document, translate text, summarize an article. You already know the steps, so let code handle the sequencing and save the model calls for the parts that actually need judgment.&lt;/p&gt;
&lt;p&gt;Reach for an agent when the task's shape depends on the input: answering open-ended questions that might need a web search, a calculation, or a database query depending on what's asked. You can't write that branching logic by hand because you don't know in advance which branch applies.&lt;/p&gt;
&lt;p&gt;A lot of production systems end up doing both — a chain that occasionally calls out to an agent for the one step that needs open-ended tool use, then hands control back to the fixed sequence. Pick the smallest amount of agency the task actually requires. More autonomy means more places for things to go wrong.&lt;/p&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="LLM"/><category term="LangChain"/><category term="Agents"/></entry><entry><title>Stop Feeding Your LLM Guesses. Feed It Facts.</title><link href="https://varunabishek.github.io/Stop-Feeding-Your-LLM-Guesses.-Feed-It-Facts..html" rel="alternate"/><published>2026-07-04T00:00:00+05:30</published><updated>2026-07-04T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-07-04:/Stop-Feeding-Your-LLM-Guesses.-Feed-It-Facts..html</id><summary type="html">&lt;h1&gt;The trick that turns a language model into a research assistant.&lt;/h1&gt;
&lt;p&gt;Large language models are great at reasoning and writing, but they only know what they were trained on. Ask one about a document it's never seen, or something that happened after its training cutoff, and it will either say …&lt;/p&gt;</summary><content type="html">&lt;h1&gt;The trick that turns a language model into a research assistant.&lt;/h1&gt;
&lt;p&gt;Large language models are great at reasoning and writing, but they only know what they were trained on. Ask one about a document it's never seen, or something that happened after its training cutoff, and it will either say it doesn't know or, worse, make something up.&lt;/p&gt;
&lt;p&gt;Retrieval-Augmented Generation fixes this. Instead of relying only on the model's memory, you hand it relevant information at the moment you ask the question. The model reads that information and answers based on it. That's the whole idea. Everything else is implementation detail.&lt;/p&gt;
&lt;p&gt;This article walks through building a simple RAG app: one that answers questions about a PDF you give it.&lt;/p&gt;
&lt;h1&gt;What RAG actually does:&lt;/h1&gt;
&lt;p&gt;A RAG system has two jobs: find the right information, then generate an answer using it.&lt;/p&gt;
&lt;p&gt;Say you upload a 50-page company handbook and ask "how many vacation days do new hires get?" The system doesn't feed the entire handbook to the model — that would be slow and expensive, and most of it is irrelevant to the question. Instead, it searches the handbook for the few paragraphs most likely to contain the answer, and only sends those to the model along with your question.&lt;/p&gt;
&lt;p&gt;The model then reads that small chunk of text and answers, grounded in what it just read rather than guessing from memory.&lt;/p&gt;
&lt;h1&gt;The four steps:&lt;/h1&gt;
&lt;p&gt;Every RAG pipeline, no matter how complex it eventually gets, boils down to four steps:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Load the document&lt;/strong&gt;. Pull in your source material — a PDF, a website, a spreadsheet, whatever you're working with.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Split it into chunks.&lt;/strong&gt; Documents are usually too long to search effectively as one block. You break them into smaller pieces, often a few hundred words each, sometimes overlapping slightly so you don't cut a sentence in half.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Embed and store the chunks.&lt;/strong&gt; Each chunk gets converted into a vector — a list of numbers that captures its meaning. These vectors go into a vector database. This is what lets you search by meaning instead of exact keyword matches.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Retrieve and generate.&lt;/strong&gt; When a question comes in, you convert it into a vector too, find the stored chunks whose vectors are closest to it, and pass those chunks plus the question to the language model. The model generates an answer using that context.&lt;/p&gt;
&lt;h2&gt;Building it:&lt;/h2&gt;
&lt;p&gt;Here's what this looks like in code, using LangChain, OpenAI's embeddings, and a local vector store called Chroma.&lt;/p&gt;
&lt;h2&gt;Step 1: Install what you need&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;bashpip install langchain langchain-openai langchain-community chromadb pypdf&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Step 2: Load your document&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;pythonfrom langchain_community.document_loaders import PyPDFLoader&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;loader = PyPDFLoader("handbook.pdf")
documents = loader.load()&lt;/p&gt;
&lt;p&gt;documents is now a list of page objects, each holding the text from one page of the PDF.&lt;/p&gt;
&lt;h2&gt;Step 3: Split into chunks&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;pythonfrom langchain.text_splitter import RecursiveCharacterTextSplitter&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;splitter = RecursiveCharacterTextSplitter(&lt;/strong&gt;
    &lt;strong&gt;chunk_size=500,&lt;/strong&gt;
    &lt;strong&gt;chunk_overlap=50&lt;/strong&gt;
)
&lt;strong&gt;chunks = splitter.split_documents(documents)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The overlap matters more than it looks. Without it, a sentence that spans two chunks can get cut in a way that loses meaning in both pieces.&lt;/p&gt;
&lt;h2&gt;Step 4: Embed and store&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;pythonfrom langchain_openai import OpenAIEmbeddings&lt;/strong&gt;
&lt;strong&gt;from langchain_community.vectorstores import Chroma&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;embeddings = OpenAIEmbeddings()&lt;/strong&gt;
&lt;strong&gt;vectorstore = Chroma.from_documents(chunks, embeddings)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This one block does the heavy lifting: it turns every chunk into a vector and saves it so you can search it later.&lt;/p&gt;
&lt;h2&gt;Step 5: Set up retrieval and generation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;pythonfrom langchain_openai import ChatOpenAI&lt;/strong&gt;
&lt;strong&gt;from langchain.chains import RetrievalQA&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;qa_chain = RetrievalQA.from_chain_type(&lt;/strong&gt;
    &lt;strong&gt;llm=llm,&lt;/strong&gt;&lt;strong&gt;
    &lt;/strong&gt;retriever=vectorstore.as_retriever(),&lt;strong&gt;
    &lt;/strong&gt;return_source_documents=True&lt;strong&gt;
&lt;/strong&gt;)**&lt;/p&gt;
&lt;h2&gt;Step 6: Ask a question&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;pythonresult = qa_chain.invoke({"query": "How many vacation days do new hires get?"})&lt;/strong&gt;
&lt;strong&gt;print(result["result"])&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That's a working RAG app. Feed it a PDF, ask it questions, and it answers based on the actual document rather than a guess.&lt;/p&gt;
&lt;h2&gt;Why it works better than just asking the model directly:&lt;/h2&gt;
&lt;p&gt;Without retrieval, the model has no way to know what's in your specific PDF. It might recognize general patterns about vacation policies, but it can't tell you your company's actual number.&lt;/p&gt;
&lt;p&gt;With retrieval, the model sees the relevant paragraph before answering. It's the difference between asking someone to recall a policy from memory versus handing them the policy document and asking them to read the relevant page first.&lt;/p&gt;
&lt;p&gt;This also cuts down on hallucination. A model working from a document in front of it is far less likely to invent details than one working purely from its training data.&lt;/p&gt;
&lt;h2&gt;A few things to watch for:&lt;/h2&gt;
&lt;p&gt;Chunk size matters more than you'd think. Too small, and chunks lose context — a number without the sentence explaining what it refers to. Too large, and you dilute the search: the model retrieves a big chunk where only one sentence is actually relevant, and the rest is noise.&lt;/p&gt;
&lt;p&gt;Retrieval isn't perfect. If your question uses different words than the document, the vector search might miss the right chunk. Testing with a range of phrasings helps you catch this early.&lt;/p&gt;
&lt;p&gt;More chunks isn't always better. Retrieving the top 10 chunks instead of the top 3 sounds safer, but it can bury the model in irrelevant text and make the answer worse, not better.&lt;/p&gt;
&lt;h2&gt;Where to go from here:&lt;/h2&gt;
&lt;p&gt;Once this basic version works, a few natural upgrades:&lt;/p&gt;
&lt;p&gt;Swap in a hosted vector database like Pinecone or Weaviate if you need to scale past a local file.&lt;/p&gt;
&lt;p&gt;Add a step that shows which source chunks the answer came from, so users can verify it.&lt;/p&gt;
&lt;p&gt;Try different embedding models — some handle domain-specific language (legal, medical, technical) better than general-purpose ones.&lt;/p&gt;
&lt;p&gt;Experiment with re-ranking retrieved chunks before sending them to the model, which can noticeably improve answer quality on tricky questions.&lt;/p&gt;
&lt;p&gt;None of these are required to get started. The six steps above are enough to go from a static PDF to a system you can actually ask questions and get real answers from.&lt;/p&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="AI-agents"/><category term="LLM"/><category term="agentic-systems"/><category term="design-patterns"/><category term="reliability"/></entry><entry><title>The Five Building Blocks of LangChain</title><link href="https://varunabishek.github.io/The-Five-Building-Blocks-of-LangChain.html" rel="alternate"/><published>2026-07-04T00:00:00+05:30</published><updated>2026-07-04T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-07-04:/The-Five-Building-Blocks-of-LangChain.html</id><summary type="html">&lt;p&gt;Most people's first LangChain project looks the same. They copy a snippet, swap in their API key, run it, and get a working chatbot in about ten minutes. Then they try to add memory, or connect a second tool, or make the thing reason through a multi-step task, and the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Most people's first LangChain project looks the same. They copy a snippet, swap in their API key, run it, and get a working chatbot in about ten minutes. Then they try to add memory, or connect a second tool, or make the thing reason through a multi-step task, and the whole mental model falls apart. The tutorial made it look like magic. It wasn't magic. It was five components working together, and nobody explained what each one actually does.&lt;/p&gt;
&lt;p&gt;So let's fix that. Five pieces: Models, Prompts, Chains, Memory, Agents. Once you know what each one is for, LangChain stops feeling like a black box and starts feeling like a toolkit.&lt;/p&gt;
&lt;h2&gt;Models: the engine, not the car&lt;/h2&gt;
&lt;p&gt;A model in LangChain is just a wrapper around a language model API — OpenAI, Anthropic, Cohere, a local Llama instance, whatever you're using. LangChain doesn't make the model smarter. It gives you one consistent interface so you can swap GPT-4 for Claude for a local model without rewriting your application logic.&lt;/p&gt;
&lt;p&gt;That interface matters more than it sounds like it should. Say you build a product on OpenAI and six months later want to test whether Claude handles your use case better. Without an abstraction layer, that's a rewrite. With LangChain, it's often a one-line change.&lt;/p&gt;
&lt;p&gt;pythonfrom langchain_openai import ChatOpenAI&lt;/p&gt;
&lt;p&gt;model = ChatOpenAI(model="gpt-4", temperature=0.7)
response = model.invoke("Explain quantum computing in one sentence.")
print(response.content)&lt;/p&gt;
&lt;p&gt;Two things worth knowing early. First, temperature controls randomness — near 0 for consistent, deterministic output, higher for creative variation. Second, LangChain distinguishes between LLMs (plain text in, text out) and chat models (structured messages with roles like system, human, and AI). Almost everything you build today will use chat models, because that's what modern providers optimize for.&lt;/p&gt;
&lt;p&gt;The model is the engine. On its own, it's not a product. You need somewhere to point it.&lt;/p&gt;
&lt;h2&gt;Prompts: what you say determines what you get:&lt;/h2&gt;
&lt;p&gt;A prompt template is a reusable, parameterized instruction. Instead of hardcoding "Translate 'hello' to French," you write a template with a blank for the input, and reuse the same structure for any word or sentence.&lt;/p&gt;
&lt;p&gt;pythonfrom langchain.prompts import ChatPromptTemplate&lt;/p&gt;
&lt;p&gt;template = ChatPromptTemplate.from_template(
    "Translate the following text to {language}: {text}"
)&lt;/p&gt;
&lt;p&gt;prompt = template.format_messages(language="French", text="Good morning")&lt;/p&gt;
&lt;p&gt;This looks trivial, but it's where most of the actual engineering happens once you're past the demo stage. A prompt template can include few-shot examples, formatting rules, output constraints, persona instructions — anything that shapes how the model responds. Change the wording and the model's behavior changes with it, sometimes dramatically. Prompt engineering isn't a nice-to-have skill on top of LangChain. It's the interface layer between your intent and the model's output, and templates are what make that interface manageable instead of a pile of string concatenation.&lt;/p&gt;
&lt;h2&gt;Chains: connecting the pipes&lt;/h2&gt;
&lt;p&gt;A chain links components together so output flows automatically from one step to the next. The simplest chain takes a prompt template, feeds it to a model, and hands you the result — no manual formatting, no manual API call.&lt;/p&gt;
&lt;p&gt;pythonchain = template | model
result = chain.invoke({"language": "Spanish", "text": "See you tomorrow"})&lt;/p&gt;
&lt;p&gt;That pipe operator is LangChain Expression Language, or LCEL. It reads left to right: take the template, pipe it into the model. You can extend that pipeline as far as you need — parse the output, pass it into a second prompt, feed that into a different model, chain a retrieval step before any of it happens.&lt;/p&gt;
&lt;p&gt;Chains are where LangChain earns its name. A single API call to a model is rarely enough for a real application. You need to retrieve context, format it, generate a draft, check the draft against a rule, maybe regenerate. Each of those is a link. String enough of them together and you've built a pipeline that would otherwise be scattered across a dozen manual function calls — except now it's declarative, testable, and easy to swap pieces in and out of.&lt;/p&gt;
&lt;h2&gt;Memory: making the model remember it's mid-conversation:&lt;/h2&gt;
&lt;p&gt;Here's a fact that surprises a lot of beginners: language models are stateless. Every API call is a clean slate. The model has no idea what you said two messages ago unless you explicitly send it that history again.&lt;/p&gt;
&lt;p&gt;Memory in LangChain is the mechanism for doing that — storing past exchanges and re-injecting them into future prompts so the conversation feels continuous.&lt;/p&gt;
&lt;p&gt;pythonfrom langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain&lt;/p&gt;
&lt;p&gt;memory = ConversationBufferMemory()
conversation = ConversationChain(llm=model, memory=memory)&lt;/p&gt;
&lt;p&gt;conversation.invoke("My name is Alex.")
conversation.invoke("What's my name?")&lt;/p&gt;
&lt;p&gt;The second call works — the model answers "Alex" correctly — because memory quietly attached the first exchange to the second prompt. Without it, the model would have no way to know.&lt;/p&gt;
&lt;p&gt;The catch is that conversation history isn't free. Every stored message gets resent with every new call, and tokens cost money and hit context limits. That's why LangChain offers several memory strategies beyond the basic buffer: summarizing old exchanges instead of storing them verbatim, keeping only the last N messages, or storing information as structured facts rather than raw transcript. Picking the right memory strategy is a real design decision, not a default you set once and forget.&lt;/p&gt;
&lt;h2&gt;Agents: when the model decides what to do next&lt;/h2&gt;
&lt;p&gt;Chains follow a path you define in advance. Agents don't. An agent uses the language model to decide, at runtime, which action to take — which tool to call, in what order, based on what it learns from each step.&lt;/p&gt;
&lt;p&gt;Give an agent access to a calculator, a search API, and a database query tool, and ask it a multi-part question. It might search for a fact, calculate something with the result, then query the database to verify it — choosing that sequence itself rather than following a route you hardcoded.&lt;/p&gt;
&lt;p&gt;pythonfrom langchain.agents import create_react_agent, AgentExecutor
from langchain.tools import Tool&lt;/p&gt;
&lt;p&gt;tools = [
    Tool(name="Search", func=search_function, description="Search the web for current information"),
    Tool(name="Calculator", func=calculate, description="Perform mathematical calculations"),
]&lt;/p&gt;
&lt;p&gt;agent = create_react_agent(model, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools)&lt;/p&gt;
&lt;p&gt;executor.invoke({"input": "What's the population of Japan divided by the population of the UK?"})&lt;/p&gt;
&lt;p&gt;The agent recognizes it needs two numbers before it can do the division, searches for each one, then calculates the result — all without you writing that control flow by hand.&lt;/p&gt;
&lt;p&gt;This is also where things get harder to predict. Agents can loop, misuse a tool, or take a route you didn't anticipate, because the reasoning happens inside the model rather than in code you wrote. Debugging an agent means reading through its intermediate steps to see where its logic diverged from what you expected, which is a different skill than debugging a fixed chain.&lt;/p&gt;
&lt;h2&gt;Putting it together:&lt;/h2&gt;
&lt;p&gt;A useful way to see how these fit: models generate, prompts instruct, chains connect, memory persists, agents decide. A basic Q&amp;amp;A bot might need only a model and a prompt. A customer support assistant that remembers earlier tickets needs memory. A research assistant that has to search, calculate, and cross-reference on its own needs an agent.&lt;/p&gt;
&lt;p&gt;Most real applications end up using several of these together — a chain with memory attached, or an agent that calls a chain as one of its tools. Once you can name what each piece is doing, that combination stops looking like a black box and starts looking like a set of parts you're choosing.&lt;/p&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="AI-agents"/><category term="LLM"/><category term="agentic-systems"/><category term="design-patterns"/><category term="reliability"/></entry><entry><title>Time Is a Lie We All Agreed To</title><link href="https://varunabishek.github.io/Time-Is-a-Lie-We-All-Agreed-To.html" rel="alternate"/><published>2026-07-01T00:00:00+05:30</published><updated>2026-07-01T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-07-01:/Time-Is-a-Lie-We-All-Agreed-To.html</id><summary type="html">&lt;h1&gt;Why Does New York Have Two Different Time Zones? The Truth About EST, EDT, and Daylight Saving Time&lt;/h1&gt;
&lt;p&gt;Picture this: you schedule a video call with a colleague in New York for "3 PM Eastern." In March, that call lands at a different point in the day relative to the …&lt;/p&gt;</summary><content type="html">&lt;h1&gt;Why Does New York Have Two Different Time Zones? The Truth About EST, EDT, and Daylight Saving Time&lt;/h1&gt;
&lt;p&gt;Picture this: you schedule a video call with a colleague in New York for "3 PM Eastern." In March, that call lands at a different point in the day relative to the sun than it would in January — even though nobody moved, and the call is still "3 PM Eastern" both times. Confused? You're not alone. Millions of people use EST and EDT interchangeably, but they're not the same thing — and the gap between them is the reason you lose an hour of sleep every spring and gain it back every fall.&lt;/p&gt;
&lt;h2&gt;What Do EST and EDT Actually Mean?&lt;/h2&gt;
&lt;p&gt;Both belong to the same region — the Eastern Time Zone of North America, covering cities like New York, Boston, Atlanta, and Toronto — but they represent different offsets from Coordinated Universal Time (UTC).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EST — Eastern Standard Time&lt;/strong&gt; is the "default" time. It sits &lt;strong&gt;5 hours behind UTC (UTC−5)&lt;/strong&gt; and is used in late fall, winter, and early spring.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EDT — Eastern Daylight Time&lt;/strong&gt; is the "summer" version. It sits only &lt;strong&gt;4 hours behind UTC (UTC−4)&lt;/strong&gt; and is used in spring, summer, and early fall.&lt;/p&gt;
&lt;p&gt;The key detail: EDT is &lt;strong&gt;one hour ahead&lt;/strong&gt; of EST. They aren't two names for the same time — they're two different offsets used at different points in the year, depending on whether daylight saving time is active. Most people lump both under "Eastern Time" (ET), which just means whichever one currently applies — but for precise scheduling, knowing which one is in effect matters.&lt;/p&gt;
&lt;h2&gt;Why Does the Time Zone Change at All?&lt;/h2&gt;
&lt;p&gt;This is where Daylight Saving Time (DST) comes in — the engine behind the EST/EDT switch.&lt;/p&gt;
&lt;p&gt;Daylight Saving Time is the practice of moving clocks &lt;strong&gt;forward an hour in spring&lt;/strong&gt; and &lt;strong&gt;back an hour in fall&lt;/strong&gt;, so daylight hours line up better with when people are actually awake, shifting unused early-morning light into the evening instead.&lt;/p&gt;
&lt;p&gt;When DST starts, you "spring forward" and EST becomes EDT. When it ends, you "fall back" and EDT becomes EST — the "D" in EDT literally stands for &lt;strong&gt;Daylight&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Spring forward&lt;/strong&gt; → clocks move ahead 1 hour → EST → EDT&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fall back&lt;/strong&gt; → clocks move back 1 hour → EDT → EST&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the U.S. and most of Canada, the schedule has been fixed since 2007: DST begins the &lt;strong&gt;second Sunday in March&lt;/strong&gt; at 2:00 AM (clocks jump to 3:00 AM) and ends the &lt;strong&gt;first Sunday in November&lt;/strong&gt; at 2:00 AM (clocks fall back to 1:00 AM). For 2026, that means EDT starts March 8 and EST returns November 1. Notably, EDT is actually in effect for about eight months of the year — the longer stretch, not the exception.&lt;/p&gt;
&lt;h2&gt;Where Did This System Come From?&lt;/h2&gt;
&lt;p&gt;The U.S. adopted standardized time zones in 1918, amid real public resistance to the idea of "chasing daylight" by clock. Daylight saving itself took off as a wartime energy-conservation measure during World War I.&lt;/p&gt;
&lt;p&gt;For decades after World War II, states set their own daylight saving schedules with no coordination, creating chaos for interstate travel. The Uniform Time Act of 1966 finally standardized the practice nationally (though states could still opt out). In 1986, Congress extended the DST period to save energy, and the current schedule arrived with the Energy Policy Act of 2005, taking effect in 2007.&lt;/p&gt;
&lt;h2&gt;Not Everyone Plays Along&lt;/h2&gt;
&lt;p&gt;Daylight saving isn't universal even within the U.S. Arizona (apart from the Navajo Nation), Hawaii, Puerto Rico, and a few other territories stay on standard time all year — which is why their offset relative to neighboring states actually shifts depending on the season. Also worth knowing: the correct term is "Daylight Saving Time," not "Daylight Savings Time."&lt;/p&gt;
&lt;h2&gt;How to Keep It Straight&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Offset from UTC&lt;/th&gt;
&lt;th&gt;When it's used&lt;/th&gt;
&lt;th&gt;Mnemonic&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EST&lt;/strong&gt; (Standard)&lt;/td&gt;
&lt;td&gt;UTC−5&lt;/td&gt;
&lt;td&gt;Fall through winter&lt;/td&gt;
&lt;td&gt;"S" = sleepier, shorter days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EDT&lt;/strong&gt; (Daylight)&lt;/td&gt;
&lt;td&gt;UTC−4&lt;/td&gt;
&lt;td&gt;Spring through fall&lt;/td&gt;
&lt;td&gt;"D" = daylight, longer evenings&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Rule of thumb: between mid-March and early November, you're almost certainly in EDT. Outside that window, it's EST.&lt;/p&gt;
&lt;h2&gt;Why This Still Matters&lt;/h2&gt;
&lt;p&gt;Even with auto-updating phones, the EST/EDT switch still causes missed meetings and groggy mornings twice a year — and remains genuinely contested policy, with some pushing for permanent daylight saving time and others (including farmers' groups) preferring permanent standard time.&lt;/p&gt;
&lt;p&gt;So next time your phone quietly jumps an hour overnight, you'll know exactly why.&lt;/p&gt;</content><category term="GenAI"/><category term="#TimekeepingHistory #StandardTime #UniformTimeAct #TemporalPolicy #ClockChange #EasternTime #UTC #WartimeHistory"/></entry><entry><title>CORS: The Bouncer Your Browser Hired Without Telling You</title><link href="CORS-:-The-Bouncer-Your-Browser-Hired-Without-Telling-You.html" rel="alternate"/><published>2026-06-30T00:00:00+05:30</published><updated>2026-06-30T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:None,2026-06-30:-The-Bouncer-Your-Browser-Hired-Without-Telling-You.html</id><summary type="html">&lt;p&gt;What's really happening when your frontend can't talk to your backend&lt;/p&gt;
&lt;p&gt;You've built a beautiful web app. Your frontend hums along on localhost:3000, your API waits on localhost:5000, and you fire off your first request between them. Instead of data, you get a wall of red text: "blocked …&lt;/p&gt;</summary><content type="html">&lt;p&gt;What's really happening when your frontend can't talk to your backend&lt;/p&gt;
&lt;p&gt;You've built a beautiful web app. Your frontend hums along on localhost:3000, your API waits on localhost:5000, and you fire off your first request between them. Instead of data, you get a wall of red text: "blocked by CORS policy." No explanation that makes sense, just a browser flatly refusing to let two parts of your own project talk to each other.&lt;/p&gt;
&lt;p&gt;This single error has confused more developers and derailed more demos than almost any other concept in web development. The fix often feels unnecessary, because nothing about the request looks broken. The server is up, the URL is correct, the data is right there. So why won't the browser let it through?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The answer is CORS:&lt;/strong&gt; Cross-Origin Resource Sharing. Once you understand what it actually is, it stops looking like an obscure bug and starts looking like exactly what it is, a security guard doing its job a little too literally.&lt;/p&gt;
&lt;h2&gt;A Crime That Never Happened (But Almost Did)&lt;/h2&gt;
&lt;p&gt;To understand why CORS exists, picture the web before it did.&lt;/p&gt;
&lt;p&gt;You're logged into your bank's website in one tab. In another, you visit a sketchy site containing a hidden script. That script quietly fires a request to your bank's "transfer funds" endpoint, using your browser. Because your browser automatically attaches your bank's login cookie to requests sent to the bank's domain, the request looks legitimate to the server. It has no idea the request came from a malicious page instead of the bank's own interface. Money moves. You never clicked a thing.&lt;/p&gt;
&lt;p&gt;This is a real, historically common attack called Cross-Site Request Forgery, and the only reason it doesn't happen to you daily is that browsers stepped in to prevent it, using a system called the Same-Origin Policy, with CORS built on top to relax it safely when needed.&lt;/p&gt;
&lt;h2&gt;The Same-Origin Policy: The Rule Behind the Rule&lt;/h2&gt;
&lt;p&gt;Long before CORS, browsers adopted a blunt rule: a webpage from one origin should not read data from a different origin, full stop.&lt;/p&gt;
&lt;p&gt;An "origin" is defined by protocol, domain, and port together. If any one differs between two URLs, they're different origins, even if they look related. So https://myapp.com and http://myapp.com differ by protocol; https://myapp.com and https://api.myapp.com differ by domain; and localhost:3000 and localhost:5000 differ by port, which is exactly why your own frontend and backend trigger the same restriction a stranger's script would.&lt;/p&gt;
&lt;p&gt;This Same-Origin Policy says: by default, JavaScript on one origin cannot read responses from a different origin. The request might still go out, but the browser blocks the page's script from seeing what comes back.&lt;/p&gt;
&lt;p&gt;That's a great defense against the bank scenario. But the modern web depends on origins constantly talking to each other, frontends calling APIs, payment providers, maps, fonts, third-party logins. Taken literally, the Same-Origin Policy would forbid all of it. So the web needed an escape hatch: a way for a server to say, "I know this request is cross-origin, and that's fine, I trust it." That escape hatch is CORS.&lt;/p&gt;
&lt;h2&gt;CORS: Permission Slips for the Internet&lt;/h2&gt;
&lt;p&gt;CORS is a system of HTTP headers that lets a server explicitly tell browsers which other origins may access its resources. The Same-Origin Policy is the strict teacher who says no field trips. CORS is the signed note that says, "this specific kid can go on this specific trip."&lt;/p&gt;
&lt;p&gt;When your frontend sends a request to your API, the browser tags it with an Origin header. The server processes it and can include a response header called Access-Control-Allow-Origin. If that header lists the requesting origin (or a wildcard *), the browser hands the response to your JavaScript. If it's missing or lists something else, the browser discards the response and throws the familiar error.&lt;/p&gt;
&lt;p&gt;Notice the important detail: the server usually did receive and process the request, and the data was generated. The browser just refuses to let your script see it, because permission was never granted. This is why CORS errors feel disorienting, the failure isn't in the network; it's a rule enforced after the fact.&lt;/p&gt;
&lt;h2&gt;The Preflight Request: An Extra Knock Before Entering&lt;/h2&gt;
&lt;p&gt;Simple requests, like a basic GET with standard headers, follow the flow above directly. But complex requests, a POST carrying JSON, a request with custom headers like an authorization token, trigger an automatic preflight request first.&lt;/p&gt;
&lt;p&gt;A preflight uses the OPTIONS method, essentially asking the server: "before I send the real request, will you even allow this? What methods and headers are permitted?" The server answers with headers like Access-Control-Allow-Methods and Access-Control-Allow-Headers. Only if that preflight succeeds does the browser send the actual request, automatically and invisibly, unless you check the network tab.&lt;/p&gt;
&lt;h2&gt;Why This Lives in the Browser, Not the Server:&lt;/h2&gt;
&lt;p&gt;CORS is enforced by the browser, not the server or the network. This explains a lot. Tools like Postman or curl, or one backend calling another, are unaffected, since there's no browser deciding whether to hide a response from a script. "But it works in Postman!" is a common but misleading observation; Postman was never going to be blocked, so the test doesn't prove what people think it proves.&lt;/p&gt;
&lt;p&gt;This also clarifies what CORS actually protects. The request typically still reaches the server and gets processed; CORS isn't stopping that. What it protects is the user's browser, by preventing a malicious page from using an already-authenticated session to read sensitive responses it has no business seeing.&lt;/p&gt;
&lt;h2&gt;Fixing It: What the Server Actually Needs to Do&lt;/h2&gt;
&lt;p&gt;Because CORS is fundamentally about the server granting permission, the fix lives on the server side. A server can allow a specific origin with Access-Control-Allow-Origin: https://myapp.com, and most frameworks have CORS middleware that handles this in a few lines.&lt;/p&gt;
&lt;p&gt;A server can allow any origin with Access-Control-Allow-Origin: *, which suits fully public APIs with no sensitive data, but is risky for anything involving authentication, since it removes the protection CORS provides.&lt;/p&gt;
&lt;p&gt;For requests carrying cookies or credentials, the server must also set Access-Control-Allow-Credentials: true, and it cannot pair this with a wildcard origin; it must name the exact origin instead.&lt;/p&gt;
&lt;p&gt;A frontend developer can't truly fix CORS from the client side, since the browser is simply respecting the server's stated wishes. Browser extensions that "disable CORS" only affect your own local browser, useful for debugging, but no real fix for actual users on a deployed site.&lt;/p&gt;
&lt;h2&gt;The Takeaway:&lt;/h2&gt;
&lt;p&gt;CORS often gets treated as an annoying obstacle to silence rather than understand. But it's one of the quieter, more elegant pieces of web infrastructure, a system that lets the open, interconnected web coexist with the basic expectation that one site shouldn't be able to secretly rummage through what you're doing on another.&lt;/p&gt;
&lt;p&gt;The next time that red error shows up, it's not the browser being difficult for no reason. It's the bouncer at the door, checking the guest list, because somewhere, a server hasn't yet said "this one's with me."&lt;/p&gt;</content><category term="GenAI"/><category term="Web Development"/><category term="CORS"/><category term="Browser Security"/><category term="Reliability"/></entry><entry><title>Fine-Tuning Your AI: The Upgrade Everyone Wants and Almost No One Needs.</title><link href="Fine-Tuning-Your-AI-:-The-Upgrade-Everyone-Wants-and-Almost-No-One-Needs..html" rel="alternate"/><published>2026-06-21T00:00:00+05:30</published><updated>2026-06-21T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:None,2026-06-21:-The-Upgrade-Everyone-Wants-and-Almost-No-One-Needs..html</id><summary type="html">&lt;p&gt;It sounds like the obvious next step for any serious AI product. In practice, it's one of the most misunderstood — and most expensive — decisions in AI development.&lt;/p&gt;
&lt;p&gt;It sounds like the obvious next step for any serious AI product. In practice, it’s one of the most misunderstood — and most …&lt;/p&gt;</summary><content type="html">&lt;p&gt;It sounds like the obvious next step for any serious AI product. In practice, it's one of the most misunderstood — and most expensive — decisions in AI development.&lt;/p&gt;
&lt;p&gt;It sounds like the obvious next step for any serious AI product. In practice, it’s one of the most misunderstood — and most expensive — decisions in AI development.&lt;/p&gt;
&lt;p&gt;A founder once told me, “We need to fine-tune our model. That’s what the big companies do.”&lt;/p&gt;
&lt;p&gt;He wasn’t wrong that big companies fine-tune models. He was wrong about why, and about what it would cost him to do the same.&lt;/p&gt;
&lt;p&gt;Three months and a meaningful chunk of his budget later, his AI assistant was barely better than the version he started with — the one that took an afternoon to build instead of a quarter.&lt;/p&gt;
&lt;p&gt;This happens more often than you’d think. So let’s talk about what fine-tuning actually is, why it’s so tempting, and why it quietly costs far more than the price tag suggests.&lt;/p&gt;
&lt;h2&gt;What fine-tuning actually means:&lt;/h2&gt;
&lt;p&gt;Think of a general-purpose AI model like a brilliant new employee, fresh out of a top university. They know a little about everything — law, medicine, coding, history — but nothing about your business specifically.&lt;/p&gt;
&lt;p&gt;Fine-tuning is like sending that employee through months of specialized training, narrowly focused on your company’s exact way of doing things. Done right, you get someone deeply tuned to your specific needs.&lt;/p&gt;
&lt;p&gt;Sounds great. And sometimes, it is. But here’s what that training program actually costs you.&lt;/p&gt;
&lt;h2&gt;Cost #1: The data you don’t have&lt;/h2&gt;
&lt;p&gt;To fine-tune well, you need hundreds or thousands of high-quality examples of exactly the behavior you want. Most companies think they have this data. Few actually do in a clean, usable form.&lt;/p&gt;
&lt;p&gt;You usually end up paying people to create, clean, and label this data by hand — quietly one of the most expensive parts of the entire project, and the part least visible from the outside.&lt;/p&gt;
&lt;h2&gt;Cost #2: The model that forgets&lt;/h2&gt;
&lt;p&gt;Here’s the part that surprises people most: when you fine-tune a model on your data, it can get worse at things it used to do well. AI researchers call this “catastrophic forgetting” — the model overwrites old skills to make room for new ones, like a specialist who becomes so deep in their niche they lose their broader judgment.&lt;/p&gt;
&lt;p&gt;So you don’t just need to test the new skill. You need to re-test everything the model used to do, to make sure nothing quietly broke.&lt;/p&gt;
&lt;h2&gt;Cost #3: The moving target&lt;/h2&gt;
&lt;p&gt;The AI field moves fast. A model you spend three months fine-tuning might be outperformed by a brand-new general-purpose model before your project even ships — one you could have used for free, today, with no training at all.&lt;/p&gt;
&lt;p&gt;Fine-tuning locks you into a specific model version. Every time a better base model comes out, you face a choice: redo all that expensive work, or fall behind.&lt;/p&gt;
&lt;h2&gt;Cost #4: The maintenance nobody budgets for&lt;/h2&gt;
&lt;p&gt;A fine-tuned model isn’t a one-time purchase — it’s closer to a pet. It needs regular retraining as your business changes, ongoing monitoring to catch quiet drift in quality, and specialized people who know how to care for it. None of this shows up in the initial project plan. All of it shows up in the following year’s.&lt;/p&gt;
&lt;p&gt;So when is it actually worth it?&lt;/p&gt;
&lt;p&gt;Despite all this, fine-tuning isn’t a mistake — it’s a tool that’s right for a specific job. It tends to make sense when:&lt;/p&gt;
&lt;p&gt;You need a very particular tone, format, or style, applied consistently, thousands of times a day&lt;/p&gt;
&lt;p&gt;You have genuinely large amounts of clean, relevant data already sitting around&lt;/p&gt;
&lt;p&gt;You’ve already tried the cheaper options — and hit a real, measurable wall&lt;/p&gt;
&lt;p&gt;That last point matters most. Most teams hit no wall at all. They reach for fine-tuning before testing the much cheaper option: simply giving a general-purpose model clear instructions and relevant information at the moment it needs them — a technique that often gets 90% of the way to the dream outcome, for a fraction of the cost and none of the maintenance.&lt;/p&gt;
&lt;h2&gt;The real lesson&lt;/h2&gt;
&lt;p&gt;Fine-tuning isn’t a status symbol. It’s not the “advanced” version of using AI, and skipping it doesn’t mean you’re behind. It’s a specific tool for a specific, fairly narrow problem.&lt;/p&gt;
&lt;p&gt;The best AI teams don’t ask, “How do we fine-tune our model?” They ask, “What’s the cheapest, simplest way to solve this — and is fine-tuning genuinely the only thing left that can do it?”&lt;/p&gt;
&lt;p&gt;Nine times out of ten, it isn’t.&lt;/p&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="AI-agents"/><category term="LLM"/><category term="agentic-systems"/><category term="design-patterns"/><category term="reliability"/></entry><entry><title>The AI Menu Is Huge. Here's How to Order.</title><link href="https://varunabishek.github.io/LLM-SLM-reasoning-models-and-more-decoded-for-real-people..html" rel="alternate"/><published>2026-06-16T00:00:00+05:30</published><updated>2026-06-16T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-06-16:/LLM-SLM-reasoning-models-and-more-decoded-for-real-people..html</id><summary type="html">&lt;p&gt;If you’ve ever Googled “best AI model” and ended up more confused than when you started — you’re not alone.&lt;/p&gt;
&lt;p&gt;GPT this. Llama that. Claude, Mistral, Phi... it can feel like you’ve walked into a hardware store where every tool looks the same but nobody will tell you …&lt;/p&gt;</summary><content type="html">&lt;p&gt;If you’ve ever Googled “best AI model” and ended up more confused than when you started — you’re not alone.&lt;/p&gt;
&lt;p&gt;GPT this. Llama that. Claude, Mistral, Phi... it can feel like you’ve walked into a hardware store where every tool looks the same but nobody will tell you which one to grab.&lt;/p&gt;
&lt;p&gt;Here’s the good news: once you understand a few simple ideas, the whole thing clicks. Let’s break it down.&lt;/p&gt;
&lt;h1&gt;Think of It Like a Toolbox&lt;/h1&gt;
&lt;p&gt;AI models aren’t one-size-fits-all. Just like you wouldn’t use a hammer to cut wood, different models are built for different jobs.&lt;/p&gt;
&lt;p&gt;Microsoft’s AI Foundry catalog (a kind of “AI app store” for businesses) hosts over 1,900 models. Overwhelming? Sure. But they’re organized into helpful categories — so you don’t have to scroll through all 1,900.&lt;/p&gt;
&lt;h1&gt;Big Brain vs. Fast Brain: LLMs and SLMs&lt;/h1&gt;
&lt;p&gt;The first split you’ll encounter is between large and small language models.&lt;/p&gt;
&lt;p&gt;Large Language Models (LLMs) — think GPT-5 or Llama 3 70B — are the deep thinkers. They’re great at complex writing, reasoning through tricky problems, and understanding long, nuanced context. The trade-off? They’re slower and more expensive to run.&lt;/p&gt;
&lt;p&gt;Small Language Models (SLMs) — like Phi-4 or Llama 3 8B — are leaner and faster. They handle everyday tasks well and can even run on a laptop or a phone. If you need something quick and cost-effective, these are your go-to.&lt;/p&gt;
&lt;p&gt;Everyday analogy: LLMs are like calling a specialist doctor. SLMs are like a knowledgeable friend who can handle most questions on the spot.&lt;/p&gt;
&lt;p&gt;Some Models Have Superpowers
Beyond size, models are also specialized by what they’re good at:&lt;/p&gt;
&lt;p&gt;Reasoning models (like Claude Opus 4.6) are built to tackle hard problems — math, strategy, coding. They don’t just give you an answer; they show their work.&lt;/p&gt;
&lt;p&gt;Embedding models (like Ada or Cohere) don’t generate text at all. They turn words into numbers so a computer can understand meaning — useful for search engines and recommendation systems.&lt;/p&gt;
&lt;p&gt;Image generation models (like GPT-image-1) turn your words into pictures. Describe a sunset over Mumbai, and it draws it for you.&lt;/p&gt;
&lt;p&gt;Video, speech, and audio models go even further — generating video from text, reading text aloud, or transcribing spoken words into text.&lt;/p&gt;
&lt;h1&gt;Finding the Right One:&lt;/h1&gt;
&lt;p&gt;When browsing a catalog like Azure’s Foundry, you can filter models by:&lt;/p&gt;
&lt;p&gt;What it can do — reasoning, image analysis, translation, etc.&lt;/p&gt;
&lt;p&gt;Who made it — OpenAI, Anthropic, Meta, Mistral, and many others&lt;/p&gt;
&lt;p&gt;What industry it’s trained for — some models have been trained specifically on medical or legal data, which makes them sharper in those fields than a general-purpose model would be&lt;/p&gt;
&lt;h1&gt;The Takeaway:&lt;/h1&gt;
&lt;p&gt;AI models aren’t magic black boxes — they’re tools, and like any tool, the right one depends on your job.&lt;/p&gt;
&lt;p&gt;Need deep, complex reasoning? Go large. Need something fast and affordable? Go small. Need to search by meaning, generate images, or transcribe audio? There’s a specialized model for that too.&lt;/p&gt;
&lt;p&gt;The next time someone throws a model name at you, you’ll know exactly what questions to ask: How big is it? What’s it trained for? What task am I actually trying to do?&lt;/p&gt;
&lt;p&gt;That’s really all it takes to start navigating the world of AI with confidence.&lt;/p&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="AI-agents"/><category term="LLM"/><category term="agentic-systems"/><category term="design-patterns"/><category term="reliability"/></entry><entry><title>Agentic System Design Concepts - Patterns Every AI Engineer Should Know</title><link href="https://varunabishek.github.io/agentic-system-design-concepts-patterns-every-ai-engineer-should-know.html" rel="alternate"/><published>2026-04-11T00:00:00+05:30</published><updated>2026-04-11T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-04-11:/agentic-system-design-concepts-patterns-every-ai-engineer-should-know.html</id><summary type="html">&lt;p&gt;Building reliable AI agents isn't just about picking the right model — it's about the patterns you wire around it. Here's a concise reference of 15 agentic system design concepts worth knowing. Two lines each — just enough to understand what they do and why they matter.&lt;/p&gt;
&lt;h2&gt;Resilience &amp;amp; Failure Isolation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Agent Circuit …&lt;/strong&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Building reliable AI agents isn't just about picking the right model — it's about the patterns you wire around it. Here's a concise reference of 15 agentic system design concepts worth knowing. Two lines each — just enough to understand what they do and why they matter.&lt;/p&gt;
&lt;h2&gt;Resilience &amp;amp; Failure Isolation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Agent Circuit Breaker&lt;/strong&gt; — Prevents cascading failures by halting agent execution when downstream services or tools are repeatedly failing. Borrowed from distributed systems engineering, it stops a single broken tool from dragging the entire agent pipeline down.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Blast Radius Limiter&lt;/strong&gt; — Restricts the impact of an agent failure to a defined scope so it can't propagate across the system. Think of it as a blast door: when something goes wrong, the damage stays local.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dead Letter Queue for Agents&lt;/strong&gt; — A holding area where failed or unprocessable agent tasks are parked for later inspection instead of silently dropped. It gives you a recoverable audit trail when tasks fall through the cracks at runtime.&lt;/p&gt;
&lt;h2&gt;Control Flow &amp;amp; Decision Quality&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Orchestrator vs Choreography&lt;/strong&gt; — Defines whether agent interactions are centrally directed (orchestrator controls all moves) or emergent (agents react to events and coordinate peer-to-peer). The choice shapes coupling, debuggability, and how gracefully the system degrades.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Confidence Threshold Gate&lt;/strong&gt; — Ensures an agent only takes action when its internal confidence in a decision clears a defined threshold. A simple but powerful reliability lever: low-confidence branches pause for human review rather than guessing forward.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Replanning Loop&lt;/strong&gt; — Allows agents to re-evaluate their plan mid-execution when context changes or a step fails, rather than continuing blindly on a stale plan. Essential for long-horizon tasks where the environment isn't static.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Human Escalation Protocol&lt;/strong&gt; — Provides a structured mechanism for agents to hand off to a human when they're stuck, uncertain, or handling high-stakes decisions. It's not a failure mode — it's a designed off-ramp.&lt;/p&gt;
&lt;h2&gt;Tool Invocation Reliability&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Idempotent Tool Calls&lt;/strong&gt; — Ensures that a tool can be called multiple times with the same inputs without producing unintended side effects. Critical in agentic pipelines where retries happen frequently due to timeouts or partial failures.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tool Invocation Timeout&lt;/strong&gt; — Prevents agents from blocking indefinitely on a tool that is slow or unresponsive, forcing a graceful fallback or retry. Without this, a single flaky API can freeze an entire agent run.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Context Window Checkpointing&lt;/strong&gt; — Periodically saves the agent's progress so it can resume from a known-good state rather than restarting from scratch after a context overflow or crash. Especially important for long-running, multi-step tasks.&lt;/p&gt;
&lt;h2&gt;Infrastructure &amp;amp; Routing&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;LLM Gateway Pattern&lt;/strong&gt; — A single abstraction layer that manages all LLM API calls, handling routing, rate limiting, retries, and observability in one place. It decouples agent logic from model-specific SDKs, making provider swaps painless.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Semantic Caching&lt;/strong&gt; — Stores LLM responses keyed on semantic meaning rather than exact input strings, so similar queries hit the cache even when phrased differently. Reduces latency and cost without sacrificing answer quality.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Multi-Agent State Sync&lt;/strong&gt; — Maintains a consistent shared state across multiple agents working in parallel or in sequence. Without it, agents operating on stale or divergent state produce contradictory or redundant outputs.&lt;/p&gt;
&lt;h2&gt;Observability &amp;amp; Deployment&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Agentic Observability Tracing&lt;/strong&gt; — Tracks every decision, tool call, handoff, and LLM interaction across an agent run, producing a full execution trace for debugging and performance analysis. The difference between guessing why something failed and knowing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Canary Agent Deployment&lt;/strong&gt; — Rolls out a new agent version to a small slice of production traffic before full release, allowing you to compare behavior and catch regressions with limited blast radius. Applies standard software deployment discipline to the agent layer.&lt;/p&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="AI-agents"/><category term="LLM"/><category term="agentic-systems"/><category term="design-patterns"/><category term="reliability"/></entry><entry><title>Every Claude Code Concept You Need to Know</title><link href="https://varunabishek.github.io/every-claude-code-concept-you-need-to-know.html" rel="alternate"/><published>2026-04-11T00:00:00+05:30</published><updated>2026-04-11T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-04-11:/every-claude-code-concept-you-need-to-know.html</id><summary type="html">&lt;p&gt;Claude Code is not a chatbot. It lives in your terminal, reads your actual files, writes code, runs commands, and executes multi-step workflows — all with your permission. Here are 30 concepts you need to understand it properly. No fluff, no hand-holding.&lt;/p&gt;
&lt;h2&gt;The 30 Concepts&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. The Terminal&lt;/strong&gt; — Claude Code doesn't …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Claude Code is not a chatbot. It lives in your terminal, reads your actual files, writes code, runs commands, and executes multi-step workflows — all with your permission. Here are 30 concepts you need to understand it properly. No fluff, no hand-holding.&lt;/p&gt;
&lt;h2&gt;The 30 Concepts&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. The Terminal&lt;/strong&gt; — Claude Code doesn't run in a browser. It runs in the terminal, the same text-based interface developers use daily. If you've never opened a terminal before, that's your first homework assignment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Installation + Pricing&lt;/strong&gt; — Install with a single command via npm. Pricing is token-based through your Anthropic account. There's no flat monthly fee tied to a UI — you pay for what you use, which means costs scale with how hard you push it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. File Access&lt;/strong&gt; — Claude Code reads and edits files directly on your machine, with your permission. Not "paste your doc into a chat window." It opens the actual file, modifies it in-place, and saves it. This is the concept that makes it useful.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Image + PDF Reading&lt;/strong&gt; — Claude Code can ingest images and PDFs as inputs. Point it at a PDF proposal or a screenshot and it processes the content directly — no manual copy-paste required.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Tool Use&lt;/strong&gt; — Claude Code has built-in tools: file reading, file writing, shell execution, and more. These are the primitives it uses to act on your computer. You see each tool call as it happens in real time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;6. Prompting Techniques&lt;/strong&gt; — Vague prompts produce garbage results. "Help me with my marketing" is useless. "Write a 3-email welcome sequence for my dog walking business targeting first-time pet owners, 150 words each" is not. Specificity is the skill.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;7. CLAUDE.md&lt;/strong&gt; — A markdown file you create in your project directory that tells Claude Code the rules, context, and conventions for that project. Think of it as a standing system prompt that persists across sessions. Every serious Claude Code user has one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;8. Plan Mode&lt;/strong&gt; — Before Claude Code executes anything, you can ask it to plan first. It outputs what it intends to do, step by step, and waits for your approval. Run in plan mode for anything non-trivial. Review before you let it touch anything.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;9. Context Window&lt;/strong&gt; — The amount of text Claude can "hold in mind" at once during a session. Long conversations, large files, and extensive histories eat into it. When context fills up, older information gets dropped. This affects result quality.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;10. Tokens + Costs&lt;/strong&gt; — Everything processed by Claude Code — your prompts, the files it reads, its responses — is measured in tokens. Tokens drive cost. Reading a 50-page PDF burns tokens. Keep context lean and targeted to control spend.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;11. Model Selection&lt;/strong&gt; — You can choose which Claude model backs your session. Faster, cheaper models work for routine tasks. Heavier models are worth it for complex reasoning or production-grade code. Pick the right tool for the job.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;12. /compact&lt;/strong&gt; — A slash command that compresses your current conversation history into a shorter summary, freeing up context window space without wiping the session. Use it mid-task when context gets bloated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;13. /clear&lt;/strong&gt; — Wipes the entire conversation and starts fresh. Every new task should start with a clean context. Don't carry leftover noise from a previous task into the next one. Use this more than you think you need to.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;14. Session Management&lt;/strong&gt; — Claude Code has no persistent memory between sessions by default. Start each session with your CLAUDE.md re-read to restore project context. Design your workflow around this statelessness rather than fighting it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;15. Permission Modes&lt;/strong&gt; — By default, Claude Code asks for approval before running any shell command. This gets tedious fast. You can pre-approve safe, non-destructive commands (ls, cat, grep, mkdir, git status) in your settings.local.json. Destructive operations should always require explicit confirmation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;16. Effort Levels&lt;/strong&gt; — You can signal how much effort you want Claude to apply. Quick answers for exploration, thorough analysis for production decisions. Matching effort level to task type saves time and tokens.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;17. Interrupt + Redirect&lt;/strong&gt; — While Claude Code is running a task, you can interrupt it mid-execution and redirect it. If it starts going down the wrong path, stop it early. Don't let it burn tokens on a wrong approach when you can see it happening.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;18. Visual Studio Code&lt;/strong&gt; — Claude Code integrates directly with VS Code. You can run it inside the VS Code terminal and see file changes reflected in your editor in real time. If you're not a terminal-native developer, this is the recommended setup.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;19. Memory&lt;/strong&gt; — Claude Code supports memory files that persist across sessions. Unlike CLAUDE.md (project-specific), memory files can store user-level preferences and context. Useful for encoding your personal conventions once and never repeating them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;20. Project vs Global&lt;/strong&gt; — Configuration can be scoped at the project level (CLAUDE.md, settings.local.json) or at the global level (applies to all Claude Code sessions on your machine). Know which scope a setting lives in before you modify it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;21. Slash Commands&lt;/strong&gt; — Built-in commands prefixed with &lt;code&gt;/&lt;/code&gt; that control Claude Code's behavior: /clear, /compact, /help, and more. You can also define custom slash commands (skills) that map to your own workflows.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;22. Skills&lt;/strong&gt; — Custom slash commands you define once and reuse indefinitely. A skill is a markdown file that describes a reusable workflow. You build it once, invoke it with &lt;code&gt;/skill-name&lt;/code&gt;, and Claude follows the instructions every time. Hundreds of community-built skills already exist on GitHub in repos like &lt;code&gt;anthropics/skills&lt;/code&gt; and &lt;code&gt;hesreallyhim/awesome-claude-code&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;23. Hooks&lt;/strong&gt; — Scripts that run automatically before or after Claude Code actions. Quality gate hooks, for example, can intercept Claude's output before it's committed and check it against defined standards. Hooks are how you enforce consistency without relying on Claude to self-police.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;24. Web Browsing&lt;/strong&gt; — Claude Code can browse the web when given the appropriate tool access. It can fetch pages, read documentation, and pull in live information as part of a task — not just work from static local files.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;25. MCP Servers&lt;/strong&gt; — Model Context Protocol servers extend Claude Code's tool access to external services: Airtable, Google Drive, Slack, GitHub, and more. Tools handle what Claude does on your computer. MCP extends that to the internet and third-party APIs. This is the integration layer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;26. Perplexity MCP&lt;/strong&gt; — A specific MCP integration that gives Claude Code access to Perplexity's search capabilities. Useful when a task requires real-time research as part of a larger automated workflow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;27. Subagents&lt;/strong&gt; — Multiple Claude Code instances running simultaneously, each handling a distinct subtask. Instead of processing platforms one at a time, you spin up parallel agents and run them concurrently. Subagents are how you turn Claude Code from a sequential tool into a parallel workflow engine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;28. Remote Control&lt;/strong&gt; — Claude Code can be configured for remote access, meaning you can trigger and manage sessions from another machine or interface. Relevant for server automation and scheduled background tasks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;29. Scheduled Tasks&lt;/strong&gt; — Claude Code workflows can be scheduled to run automatically at defined intervals. Combine this with skills and hooks and you have a self-operating workflow system that runs without manual invocation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;30. Git Version Control&lt;/strong&gt; — Claude Code integrates with git. Every change it makes can be committed, branched, and rolled back through standard git workflows. This is your undo button. Always have Claude Code working inside a git-tracked project. Before: changes happen and you hope nothing breaks. After: every change is versioned, documented, and reversible.&lt;/p&gt;
&lt;h2&gt;The One Rule That Matters&lt;/h2&gt;
&lt;p&gt;Master five concepts before you touch the next five. The shiny object trap — jumping from MCP to subagents to hooks before understanding CLAUDE.md and context windows — is the single biggest waste of time. The gap between people getting real results and people falling behind is not talent. It is reps. Start with file access, prompting, CLAUDE.md, plan mode, and /clear. Everything else builds on those five.&lt;/p&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="Claude-Code"/><category term="LLM"/><category term="agents"/><category term="developer-tools"/><category term="local-AI"/></entry><entry><title>Missing ZIP Option in Windows Right-Click Menu — Here's How to Fix It</title><link href="https://varunabishek.github.io/missing-zip-option-windows-right-click-menu.html" rel="alternate"/><published>2026-04-11T00:00:00+05:30</published><updated>2026-04-11T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-04-11:/missing-zip-option-windows-right-click-menu.html</id><summary type="html">&lt;p&gt;The classic "Send to → Compressed (zipped) folder" option sometimes disappears from the Windows right-click context menu. Here's what causes it and how to get it back in under two minutes.&lt;/p&gt;
&lt;h2&gt;What Happened&lt;/h2&gt;
&lt;p&gt;Windows ships with a built-in ZIP shell extension handled by &lt;code&gt;zipfldr.dll&lt;/code&gt;. When third-party tools like Git, VLC …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The classic "Send to → Compressed (zipped) folder" option sometimes disappears from the Windows right-click context menu. Here's what causes it and how to get it back in under two minutes.&lt;/p&gt;
&lt;h2&gt;What Happened&lt;/h2&gt;
&lt;p&gt;Windows ships with a built-in ZIP shell extension handled by &lt;code&gt;zipfldr.dll&lt;/code&gt;. When third-party tools like Git, VLC, or OneDrive add their own context menu entries, they can displace or corrupt the ZIP handler registration — leaving you with a bloated menu but no ZIP option.&lt;/p&gt;
&lt;h2&gt;Fix 1 — Check the Send to Submenu&lt;/h2&gt;
&lt;p&gt;Before anything else, right-click your folder or file and hover over &lt;strong&gt;Send to →&lt;/strong&gt;. The "Compressed (zipped) folder" option is sometimes hiding in the submenu even when it's not visible at the top level.&lt;/p&gt;
&lt;h2&gt;Fix 2 — Re-register the ZIP Shell Extension&lt;/h2&gt;
&lt;p&gt;Open Command Prompt as Administrator and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;regsvr32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zipfldr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dll&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This re-registers the native ZIP handler with Windows Shell. Restart Explorer or reboot after running it.&lt;/p&gt;
&lt;h2&gt;Fix 3 — Restart Windows Explorer&lt;/h2&gt;
&lt;p&gt;Sometimes a stale shell session is all that's causing the issue. Run this in CMD:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;taskkill /f /im explorer.exe
start explorer.exe
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Fix 4 — Verify the Registry Key&lt;/h2&gt;
&lt;p&gt;Press &lt;code&gt;Win + R&lt;/code&gt;, type &lt;code&gt;regedit&lt;/code&gt;, and navigate to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;HKEY_CLASSES_ROOT\CompressedFolder
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If this key is missing or corrupted, the ZIP option will not appear anywhere in the context menu. You may need to restore it from another machine or via a &lt;code&gt;.reg&lt;/code&gt; export.&lt;/p&gt;
&lt;h2&gt;Root Cause&lt;/h2&gt;
&lt;p&gt;Heavy context menu contributors — Git Bash, Git GUI, VLC, SkyDrive Pro — are visible in the screenshot. Any one of them can push a bad shell extension that breaks ZIP registration as a side effect. Fix 2 resolves this in most cases.&lt;/p&gt;</content><category term="Windows"/><category term="Windows"/><category term="tips"/><category term="context-menu"/><category term="troubleshooting"/><category term="productivity"/></entry><entry><title>AI Agent Directory - Few Shots LLM Models</title><link href="https://varunabishek.github.io/ai-agent-directory-few-shots-llm-models.html" rel="alternate"/><published>2026-04-10T00:00:00+05:30</published><updated>2026-04-10T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-04-10:/ai-agent-directory-few-shots-llm-models.html</id><summary type="html">&lt;p&gt;The AI agent ecosystem is growing fast. Here's a quick directory of notable AI startups and a couple of few-shot LLM models worth knowing about. Two lines each — just enough to know what they do and why they matter.&lt;/p&gt;
&lt;h2&gt;AI Agent Directory&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Can of Soup&lt;/strong&gt; — An AI-powered app that lets …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The AI agent ecosystem is growing fast. Here's a quick directory of notable AI startups and a couple of few-shot LLM models worth knowing about. Two lines each — just enough to know what they do and why they matter.&lt;/p&gt;
&lt;h2&gt;AI Agent Directory&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Can of Soup&lt;/strong&gt; — An AI-powered app that lets you create fictional photos of you and your friends in imaginary scenarios. Built during Y Combinator, it uses generative AI to place people into any meme, outfit, or movie scene.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Deepgram&lt;/strong&gt; — A foundational voice AI platform offering speech-to-text, text-to-speech, and voice agent APIs. Their Nova models deliver high accuracy and low latency, supporting 30+ languages for real-time transcription.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Diffuse Bio&lt;/strong&gt; — Building generative AI for protein design, using diffusion models to engineer new proteins with control and accuracy. Their foundation model DSG-1 can generate 3D protein structures and design binders from user prompts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Draftaid&lt;/strong&gt; — An AI-powered CAD tool that converts 3D models into precise 2D manufacturing drawings automatically. It reduces manual drafting time by up to 90%, acting like a copilot for mechanical engineers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edgetrace&lt;/strong&gt; — A YC-backed AI video analytics platform that lets users search camera networks using natural language. Primarily used by law enforcement and transportation for real-time threat detection and suspect identification.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EzDubz&lt;/strong&gt; — A real-time AI dubbing tool that translates videos, livestreams, and phone calls while preserving the original speaker's voice. Their proprietary models clone voices on the fly and even replicate emotions across 20+ languages.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exa&lt;/strong&gt; — An AI-powered search engine and API built for developers and AI agents. Unlike traditional keyword search, Exa uses neural embeddings for semantic understanding, powering tools like Cursor and Lovable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Guide Labs&lt;/strong&gt; — Building interpretable AI foundation models that can explain their reasoning and are easy to audit. Their open-source Steerling-8B is an 8-billion-parameter LLM designed for transparency and debuggability.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Infinity AI&lt;/strong&gt; — Now known as Lemon Slice, they build a video foundation model for human motion and emotion. Their tech generates expressive, talking characters across styles from photorealistic to cartoon.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;K-Scale&lt;/strong&gt; — Building open-source humanoid robots for developers, with models starting at $999. Their integrated software, hardware, and ML stack lets developers focus on building applications for embodied AI.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sevn&lt;/strong&gt; — A generative design startup using AI to automate and optimize the creative design process. Users define parameters and constraints, and Sevn generates a range of design options to explore.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Linux Inc&lt;/strong&gt; — An AI startup focused on bringing intelligent tooling to the Linux ecosystem. They aim to simplify Linux administration and development workflows through AI-powered automation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Metalware&lt;/strong&gt; — A copilot for firmware engineers that automates low-level programming for embedded systems. Their binary analysis tool fuzzes ARM-based software to detect defects earlier in the development lifecycle.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Naiver AI&lt;/strong&gt; — Navier AI provides a web-based platform for running CFD (computational fluid dynamics) simulations at scale. Their AI agents handle geometry cleanup, meshing, solver configuration, and cloud resource management autonomously.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Osium AI&lt;/strong&gt; — An AI-powered platform that accelerates materials and chemicals R&amp;amp;D for industry leaders. Their software helps engineers design new materials faster, spanning alloys, polymers, textiles, and bio-based materials.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phind&lt;/strong&gt; — An AI search engine purpose-built for developers that generates direct, code-inclusive answers to technical questions. It combines real-time web search with specialized models trained on programming languages and frameworks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Piramidal&lt;/strong&gt; — Building a foundation model for the brain, trained on a massive corpus of EEG brainwave data. Their AI interprets neural signals for neurological diagnostics, already being deployed in ICU settings.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Playground&lt;/strong&gt; — A browser-based AI image generation and design platform used by over 9 million users. It combines text-to-image generation with a full graphic design suite for logos, social media posts, and more.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PlayHT&lt;/strong&gt; — An AI voice generation platform that offered ultra-realistic text-to-speech with 900+ voices in 142 languages. Known for voice cloning and custom voice creation through deep learning algorithms.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sonauto&lt;/strong&gt; — An AI music editor that turns prompts, lyrics, or melodies into full songs in any style. It supports thousands of styles with full-length songs up to 4.5 minutes, complete with vocals and instrumentation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tavus&lt;/strong&gt; — An AI video personalization platform that creates hyper-personalized videos at scale from a single recording. It uses deep learning for voice synthesis and face cloning to generate thousands of unique video variations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;YonduAI&lt;/strong&gt; — Building the robotic workforce of the future, starting with logistics automation in warehouses. They deploy humanoid robots with remote teleoperation that gradually transitions to full AI-driven automation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Yoneda Labs&lt;/strong&gt; — Building a foundation model for chemical reactions to help chemists optimize drug discovery. Their AI defines parameters like temperature, concentration, and catalyst to make synthesis faster and cheaper.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SyncLabs&lt;/strong&gt; — An AI lip-sync video generator that creates perfectly synchronized mouth movements from any audio track. Their zero-shot model handles any face in any video context without prior training on specific individuals.&lt;/p&gt;
&lt;h2&gt;Few-Shot LLM Models&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Llama 3.1&lt;/strong&gt; — Meta's open-source large language model available in 8B, 70B, and 405B parameter sizes. It supports 128K context length and multilingual capabilities, making it one of the most versatile open-weight models for fine-tuning and deployment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mixtral&lt;/strong&gt; — Mistral AI's open-source mixture-of-experts (MoE) model that activates only a subset of parameters per token for efficient inference. It delivers performance comparable to much larger dense models while being significantly faster and more cost-effective to run.&lt;/p&gt;</content><category term="GenAI"/><category term="GenAI"/><category term="AI-agents"/><category term="LLM"/><category term="startups"/><category term="directory"/></entry><entry><title>My GenAI Blogs</title><link href="https://varunabishek.github.io/my-genai-blogs.html" rel="alternate"/><published>2026-01-10T00:00:00+05:30</published><updated>2026-01-10T00:00:00+05:30</updated><author><name>Varun Abishek</name></author><id>tag:varunabishek.github.io,2026-01-10:/my-genai-blogs.html</id><summary type="html">&lt;h2&gt;Why GenAI?&lt;/h2&gt;
&lt;p&gt;Generative AI has completely changed how I think about software, creativity, and problem-solving. Over the past year, I've gone deep into the world of large language models, prompt engineering, retrieval-augmented generation, fine-tuning, and AI agents. The pace of change is incredible, and I wanted a place to document …&lt;/p&gt;</summary><content type="html">&lt;h2&gt;Why GenAI?&lt;/h2&gt;
&lt;p&gt;Generative AI has completely changed how I think about software, creativity, and problem-solving. Over the past year, I've gone deep into the world of large language models, prompt engineering, retrieval-augmented generation, fine-tuning, and AI agents. The pace of change is incredible, and I wanted a place to document what I'm learning as I go.&lt;/p&gt;
&lt;p&gt;This blog is that place. I'll be writing about my hands-on experiences with GenAI, the tools I'm experimenting with, things that worked, things that didn't, and the lessons I've picked up along the way.&lt;/p&gt;
&lt;h2&gt;What I've Been Exploring&lt;/h2&gt;
&lt;p&gt;My GenAI journey started with using ChatGPT and Claude for day-to-day coding tasks. That quickly evolved into deeper exploration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Prompt engineering&lt;/strong&gt; — learning how to get consistent, high-quality outputs from LLMs by structuring prompts effectively.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RAG (Retrieval-Augmented Generation)&lt;/strong&gt; — building pipelines that ground LLM responses in real data using vector databases and embeddings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fine-tuning&lt;/strong&gt; — adapting pre-trained models for specific tasks and domains.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI agents&lt;/strong&gt; — creating autonomous workflows where LLMs can use tools, reason through multi-step problems, and take actions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Local models&lt;/strong&gt; — running open-source models like LLaMA and Mistral locally to understand how they work under the hood.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'm not just reading about these topics. I'm building with them, breaking things, and learning from the results.&lt;/p&gt;
&lt;h2&gt;What to Expect&lt;/h2&gt;
&lt;p&gt;I plan to post at least one article a week covering topics like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Practical tutorials on building GenAI applications&lt;/li&gt;
&lt;li&gt;Comparisons of different models and frameworks&lt;/li&gt;
&lt;li&gt;Deep dives into concepts like embeddings, tokenization, and attention mechanisms&lt;/li&gt;
&lt;li&gt;Real-world use cases and project walkthroughs&lt;/li&gt;
&lt;li&gt;Opinions on where GenAI is heading and what matters for developers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some posts will be short and focused, others will be longer walkthroughs. The goal is to share useful, honest content from a developer's perspective.&lt;/p&gt;
&lt;h2&gt;Let's Go&lt;/h2&gt;
&lt;p&gt;I'm excited to start writing and sharing. GenAI is moving fast, and the best way to keep up is to build, experiment, and document. That's exactly what this blog is for.&lt;/p&gt;</content><category term="Announcement"/><category term="GenAI"/><category term="LLM"/><category term="machine-learning"/><category term="deep-learning"/><category term="announcement"/></entry></feed>