Hirenix kaise padhata hai
Ek chapter. 90 minute.
Interview ke liye taiyaar.
Har concept ek real-world problem se — jaisa production code mein aata hai, waisa. Ratna nahi padta, samajh aa jaata hai. Har question ka model answer diya hai: interviewer ko exactly kya bolna hai, aur kyun. Phir usi chapter ka AI mock interview.
- 📖Concept, 5 min meinJargon nahi — seedhi baat
- 🛠️Real-world problemJaisa production code mein aata hai
- 💬Model answerInterview mein kya bolna hai
- 🧠FlashcardsRevision 10 min mein
- 🤖AI mock interviewFollow-up bhi poochta hai
- 📊Weak topicsKahan phans rahe ho, pata chale

Farq content ka nahi, filter ka hai — sirf wahi jo production mein actually use hota hai aur interview mein actually poocha jaata hai. Kitaabi topics jo industry mein kahin nahi chalte, wo yahan nahi milenge.
What you’ll learn
- ●LangChain kya hai?
- ●LangChain ke saath RAG
- @tool se toolsFree account
- LangGraph basics: StateGraphFree account
- LangGraph agentsFree account
- Conditional routingFree account
- Human in the loopFree account
- Multi-agent systemsFree account
- LangSmith: tracing aur evalFree account
- RecapFree account
- ●Project: AI Research Agent
- Project: Support GraphFree account
- Project: Multi-Agent WriterFree account
LangChain kya hai?
Ek car wash socho: teri dusty car ek end se andar jaati hai, aur ek series of connected stations — soap, scrub, rinse, dry — har station agli station ko automatically car handover kar deta hai. Tu khud car ko station se station tak nahi le jaata; stations physically ek pipe se connected hain, ek hi line me. Dirty andar, clean bahar, ek hi pass me.
LangChain me, us piped line ko chain kehte hain, aur pipe khud ek real operator hai: |. Prompt template soap station hai — ye tere raw variables leke unhe exact message me shape karta hai jo model expect karta hai. Chat model scrub station hai — ye actual thinking/generation ka kaam karta hai. Output parser dry station hai — ye model ke reply se wrapper hata ke tujhe plain text (ya structured data) de deta hai.
Ch5 me ye loop tu khud raw SDK se bana chuka hai: khud string format kiya, khud model call kiya, khud response se .content nikala. LangChain ka contribution hai us pattern ko naam dena — LCEL (LangChain Expression Language) — aur use ek composable object bana dena: chain = prompt | model | parser. Ek baar bana lo, aur .invoke() (ek input), .stream() (token-by-token), aur .batch() (multiple inputs ek saath) free me milte hain — koi extra code nahi.
Purane LangChain tutorials (0.x) chains ek class se banate the — LLMChain — aur chain.run(...) se chalate the. Dono current 1.x line me DELETE ho chuke hain — LLMChain import karte hi ModuleNotFoundError aata hai, program shuru hone se pehle hi. Agar koi tutorial LLMChain ya .run() dikhata hai, toh wo dead API sikha raha hai; | pipe hi ek chain-building pattern hai jo abhi bhi chalta hai.
🌍 Real-world example: ek support-ticket triage app raw customer message leta hai, prompt template use "classify this ticket" instruction me shape karta hai, model ek category return karta hai, aur parser model ke reply ko sirf category string tak strip kar deta hai jo tera database column expect karta hai — teen stations, ek pipe.
💡 Runnable = koi bhi cheez jiske paas
.invoke(),.stream(), aur.batch()hai — ye ek interface har LangChain building block (prompts, models, parsers, chains) share karta hai.
💡 LCEL (LangChain Expression Language) =
|syntax jo Runnables ko pipe karke ek bada Runnable banata hai, jiseRunnableSequencekehte hain.
💡 Output parser = ek Runnable jo model ke raw reply ko us format me reshape karta hai jo tera code actually chahta hai (string, JSON, etc.).
Standard definition (interview me bolo): LangChain is a framework for composing LLM calls, built around the Runnable interface (.invoke()/.stream()/.batch()); LCEL uses the | operator to pipe Runnables — such as prompt | model | parser — into a single chain, replacing the deleted 0.x LLMChain/.run() pattern.
import os
os.environ.setdefault("OPENAI_API_KEY", "sk-...") # normally already set, like in Ch5
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_messages([
("system", "You are a {role}."),
("human", "{question}")
])
model = ChatOpenAI(model="gpt-4o-mini") # model IDs change — check the provider's current list
chain = prompt | model | StrOutputParser()
print(type(chain).__name__)
print(hasattr(chain, "invoke"), hasattr(chain, "stream"), hasattr(chain, "batch"))
try:
from langchain.chains import LLMChain
except ModuleNotFoundError as e:
print("LLMChain is gone:", e)
# result = chain.invoke({"role": "Python tutor", "question": "What are decorators?"})
# print(result) # illustrative only -- needs a real API keyLangChain ke saath RAG
Factory assembly line soch. Ek box station 1 pe aata hai, ek part add hota hai, station 2 pe move karta hai, ek aur part add hota hai, station 3 pe move karta hai, aur end me finished nikalta hai. Koi box ko haath se station-to-station carry nahi karta — ek conveyor belt unko connect karti hai. Ch3/Ch4 me tune RAG haath se banaya tha: doc khud chunk kiya, khud embed kiya, khud cosine similarity nikali, phir khud prompt string bana ke model call kiya. LangChain ka kaam wahi conveyor belt banna hai — same stations, ek operator se wired.
In LangChain, wo conveyor belt hai LCEL (LangChain Expression Language): | Runnables ko connect karta hai (kuch bhi jispe .invoke()/.stream()/.batch() ho). Ek RAG chain hoti hai retriever | prompt | model | parser — ya jab retrieved docs aur raw question dono ek saath prompt me jaane chahiye, ek dict shape: {"context": retriever | format_docs, "question": RunnablePassthrough()}. Ye dict SAME input pe dono branches ko parallel me chalata hai aur unke outputs ko ek object me merge karta hai jo prompt template fill karta hai. RunnablePassthrough() ka matlab hai "input jaisa hai waisa hi le lo" — yahan, raw question ko koi transformation nahi chahiye, sirf context branch ko chahiye (retrieve karo, phir retrieved docs ko ek string me flatten karo).
Underlying RAG idea kuch nahi badalta: chunks me split karo, har chunk embed karo, vectors store karo, question ke liye closest wale retrieve karo, prompt me daalo, model se poocho. LangChain retrieval ko smart nahi banata na LLM call cheap karta hai — chain ka har node jo embedding model ya chat model call karta hai wo still ek billed API call hai, Ch3-Ch5 jaisi hi economics. Ye jo deta hai wo hai composition: vector store swap karo, streaming add karo, retries add karo, bina plumbing haath se dobara likhe.
🌍 Real-world example: Ek company policy chatbot.
retriever.invoke("How many sick leaves?")HR policy doc se top-k matching chunks koDocumentobjects ke roop me return karta hai.format_docsunke.page_contentko ek context string me jod deta hai. Chain{context}aur{question}prompt template me bharti hai, model ko bhejti hai, aurStrOutputParser()reply koAIMessageobject se plain text tak unwrap kar deta hai — tohrag_chain.invoke("...")ek string return karta hai, koi object nahi jise dig karna pade.
💡 Runnable = kuch bhi jispe
.invoke()/.stream()/.batch()ho — ek retriever, ek prompt template, ek chat model, aur ek parser sab Runnables hain, ISI WAJAH SE inko|se pipe kar sakte ho.
💡 retriever = ek vector store jo Runnable ki tarah wrap kiya gaya hai (
vectorstore.as_retriever(search_kwargs={"k": N})); ise call karne pe top-k sabse similar chunksDocumentobjects ke roop me milte hain (Ch3 ki cosine-similarity search, tere liye ready-made).
💡 RunnablePassthrough() = ek Runnable jo apna input bina badle wapas kar deta hai — tab use hota hai jab parallel step ke ek branch ko koi transformation nahi chahiye.
💡 StrOutputParser() = model ke
AIMessageresponse ko plain string tak unwrap karta hai, taaki chain ka final.invoke()result seedha print ya UI ko bheja jaa sake.
Ek honest gotcha: langchain_community ka Chroma import (jo zyaadatar tutorials abhi bhi dikhate hain) chalta toh hai par DeprecationWarning deta hai — wo package sunset ho raha hai. Ek dedicated langchain-chroma package, ya chhote/demo data ke liye langchain_core ka InMemoryVectorStore, current tareeka hai.
Standard definition (interview me bolo): A LangChain RAG chain wires a text splitter, an embedding model, a vector store retriever, a prompt template, a chat model, and an output parser into one LCEL pipeline ({"context": retriever | format_docs, "question": RunnablePassthrough()} | prompt | model | parser) so the whole retrieve-then-generate flow runs as a single composable, streamable Runnable.
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
# Step 1: split the source document into chunks (Ch3/Ch4 chunking, LangChain's splitter class)
text = """LangChain is a framework for building LLM applications.
It provides abstractions for chains, prompts, and retrievers.
RAG stands for Retrieval Augmented Generation.
It combines a retriever with a language model to answer questions using external documents.
The LangChain Expression Language (LCEL) lets you compose Runnables with the pipe operator.
This makes chains easy to read, stream, and debug."""
splitter = RecursiveCharacterTextSplitter(chunk_size=120, chunk_overlap=20)
chunks = splitter.split_text(text)
for i, c in enumerate(chunks):
print(f"Chunk {i+1} ({len(c)} chars): {c!r}")
# Step 2: embed the chunks and store them (needs a real API key to actually run)
vectorstore = InMemoryVectorStore.from_texts(chunks, embedding=OpenAIEmbeddings())
retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
# Step 3: the LCEL RAG chain
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
prompt = ChatPromptTemplate.from_template(
"Answer based ONLY on the following context:\n\n{context}\n\nQuestion: {question}"
)
model = ChatOpenAI(model="gpt-4o-mini", temperature=0) # model IDs change — check the provider's current list
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
print(type(rag_chain))
answer = rag_chain.invoke("What does LCEL let you do?")
print(answer)Project: AI Research Agent
Kya bana rahe hain: ek AI Research Agent — ek LangGraph graph jo ek question leta hai, reason → act → observe loop chalata hai ek real web-search tool aur ek tool-calling LLM ke saath, aur ek synthesised answer return karta hai. Ye Ch5 ke haath se likhe agent loop aur Ch6 ke poore LangGraph mental model ko ek runnable project mein fuse karta hai: ek StateGraph jismein agent node (model, decide karta hai kya karna hai), tools node (ek real ToolNode, jo model ne maanga wo execute karta hai), ek conditional edge (aur search chahiye ya ho gaya?), aur ek recursion_limit taaki ye kabhi hamesha ke liye spin na kare.
Step 0 — Mental model (pehle padho)
User question
│
▼
[agent] ──model se poochta hai: "search karna hai ya ab answer de du?"──┐
│ │
tool_calls hai? tool_calls nahi hai
│ │
▼ ▼
[tools] ──web_search real mein chalata hai, result append karta hai──▶ wapas [agent] [END] — final answer
Ye EXACTLY Ch5 ka while loop hai (model call karo → tool calls check karo → execute karo → result wapas bhejo → repeat), bas ek explicit, inspectable graph ke roop mein draw kiya gaya hai, Python control flow ke andar chhupa hone ki jagah. Upar ka har arrow ek real LangGraph edge hai jo neeche likhoge.
💡 Reason → Act → Observe = model REASON karta hai ki use kya chahiye (ek LLM call), ACT karta hai ek tool call maang ke (khud tool run nahi karta), aur OBSERVE karta hai tool ka real result phir se reason karne se pehle.
Step 1 — Agent ko ek real tool do
from langchain_core.tools import tool
@tool
def web_search(query: str) -> str:
"""Search the web for current information on a topic and return short snippets."""
# Real build mein ye body ek live search API call se replace hoti hai.
# Is project ke liye ise stub rakha hai taaki WIRING zero API keys se chale.
return f"[search stub] top result for '{query}'"
Ho ye raha hai: @tool function ka naam, docstring aur type hints inspect karta hai aur unhe ek schema mein badalta hai jo model padh sake — web_search.name, .description, .args. Ye bilkul wahi rule hai langchain-tools se: model sirf ye SCHEMA dekhta hai, function ki body kabhi nahi. Chaahe body ek real HTTP call kare search API ko, ya ek stub string return kare, model ka kaam (decide karna KAB call karna hai) nahi badalta — isiliye tum poore graph ki wiring zero API keys se verify kar sakte ho, aur sirf is ek function ki body baad mein real search call se swap karni hoti hai.
🌍 Real-world example:
C:/lcvpeweb_search.argsprint karne se{'query': {'title': 'Query', 'type': 'string'}}milta hai — wahi dict model ko dikhaya jaane wala schema HAI. Kuch chhupa nahi, kuch magic nahi.
Step 2 — Ek tool-calling model banao
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4.1-mini") # model IDs change — check the provider's current list
model_with_tools = llm.bind_tools([web_search])
Ho ye raha hai: bind_tools([...]) API call nahi karta — ye ek naya Runnable return karta hai jo apni har request ke saath tool schemas ATTACH karega. ChatOpenAI(...) ko construct hone ke liye ek API-key VALUE present chahiye (real key, ya ek placeholder string / env var), kyunki ye internally ek OpenAI client object banata hai — par construct hote waqt koi network call nahi hoti aur key validate bhi nahi hoti. Sirf model ko actually invoke karne (Step 8) ke liye ek genuinely working key chahiye. Ye line C:/lcv pe ek placeholder key aur zero network activity ke saath cleanly construct hoti hai, verified.
Step 3 — State define karo
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph.message import add_messages
class ResearchState(TypedDict):
messages: Annotated[list, add_messages]
Ho ye raha hai: ek field, messages, add_messages reducer ke saath tagged. Neeche har node ek PARTIAL update return karta hai — bas naye message(s) — aur LangGraph unhe list mein REPLACE karne ki jagah APPEND karta hai. Ye tag skip karo to har tool result us question ko mita dega jisne run start kiya tha.
Step 4 — agent node (reason)
def agent(state: ResearchState):
response = model_with_tools.invoke(state["messages"])
return {"messages": [response]}
Ho ye raha hai: POORA reasoning step ek line hai — model_with_tools.invoke(...) poori message history bhejta hai aur ek AIMessage wapas leta hai. Us message mein ya to .tool_calls hoga (model search karna chahta hai) ya nahi hoga (model answer dene ke liye ready hai). Node khud koi decision nahi leta — bas model run karta hai aur uska message return karta hai; DECIDE karna Step 5 ke router mein hota hai. Ye bilkul Ch5 ka rule hai: model sirf tool call karne ki request RETURN karta hai, khud tool kabhi run nahi karta.
Step 5 — Router + real ToolNode (act)
from typing import Literal
from langgraph.prebuilt import ToolNode
from langgraph.graph import END
def should_continue(state: ResearchState) -> Literal["tools", "__end__"]:
last = state["messages"][-1]
if last.tool_calls:
return "tools"
return END
tool_node = ToolNode([web_search])
Ho ye raha hai: should_continue ROUTER hai — last message inspect karta hai aur ek plain string return karta hai, agle node ki key. ToolNode wo piece hai jo model ke maange tool calls ko actually EXECUTE karta hai, real Python function run karta hai, aur har result ko ek ToolMessage mein wrap karke wapas messages mein add karta hai. Ye "act" aur "observe" beats hain: ToolNode act karta hai (function real mein run karta hai), aur uska ToolMessage output wo hai jo agent node apne agle turn mein observe karta hai.
Step 6 — Graph wire karo
from langgraph.graph import StateGraph, START
graph = StateGraph(ResearchState)
graph.add_node("agent", agent)
graph.add_node("tools", tool_node)
graph.add_edge(START, "agent")
graph.add_conditional_edges("agent", should_continue, {"tools": "tools", END: END})
graph.add_edge("tools", "agent")
research_agent = graph.compile()
Ho ye raha hai: START → agent hamesha pehle chalta hai. agent se, add_conditional_edges should_continue ke return value pe branch karta hai: "tools" tools node pe route karta hai, END graph ko rok deta hai. tools → agent loop close karta hai — har tool result seedha agent ko wapas jaata hai agle reasoning pass ke liye. .compile() is wiring ko ek Runnable mein badalta hai .invoke() ke saath. C:/lcv pe verified: ye exact graph ek placeholder API-key value aur zero network calls ke saath compile hota hai — ChatOpenAI ko client object banane ke liye key VALUE present chahiye, par na construction na .compile() kabhi network touch karte; sirf Step 8 ka .invoke() karta.
Step 7 — Wiring ko bina API key ke prove karo
Ek real model pe trust karne se pehle, graph ki STRUCTURE ek placeholder agent node se prove karo jo haath se wahi message shapes return karta hai jo ek real tool-calling model deta:
from langchain_core.messages import AIMessage, HumanMessage
_turn = {"n": 0}
def stub_agent(state: ResearchState):
_turn["n"] += 1
if _turn["n"] == 1:
return {"messages": [AIMessage(content="", tool_calls=[
{"name": "web_search", "args": {"query": "LangGraph recursion_limit"}, "id": "call_1"}
])]}
return {"messages": [AIMessage(content="Final synthesised answer using the real tool result.")]}
stub_graph = StateGraph(ResearchState)
stub_graph.add_node("agent", stub_agent)
stub_graph.add_node("tools", tool_node)
stub_graph.add_edge(START, "agent")
stub_graph.add_conditional_edges("agent", should_continue, {"tools": "tools", END: END})
stub_graph.add_edge("tools", "agent")
stub_app = stub_graph.compile()
result = stub_app.invoke(
{"messages": [HumanMessage(content="How does LangGraph stop an agent from looping forever?")]},
{"recursion_limit": 10}
)
for m in result["messages"]:
print(type(m).__name__, "|", getattr(m, "content", m)[:60])
Ye C:/lcv pe ACTUALLY chala — zero API keys — aur bilkul ye state transition diya, message by message:
[0] HumanMessage: 'How does LangGraph stop an agent from looping forever?'
[1] AIMessage: '' tool_calls=[{'name': 'web_search', 'args': {'query': 'LangGraph recursion_limit'}, ...}]
[2] ToolMessage: "[search stub] top result for 'LangGraph recursion_limit'"
[3] AIMessage: 'Final synthesised answer using the real tool result.'
Beat by beat trace karo: [0] question state mein enter ho raha hai. [1] agent ka PEHLA reasoning pass hai — usne decide kiya ki search chahiye, to should_continue ne "tools" pe route kiya. [2] REAL ToolNode hai jo actually web_search(query="LangGraph recursion_limit") call kar raha hai aur uska genuine return value ek ToolMessage ke roop mein append kar raha hai — is part ne real tool function use kiya, stub nahi. [3] agent ka DOOSRA reasoning pass hai: is baar returned AIMessage mein tool_calls nahi hai, to should_continue ne END return kiya aur graph ruk gaya. Chaar messages aaye, chaaron bache — add_messages ne har ek ko append kiya, kabhi overwrite nahi kiya.
Step 8 — Real, model-backed run (illustrative)
stub_agent ko wapas Step 4 ke real agent node se swap karo (model_with_tools.invoke(...)) aur tumhare paas research_agent hai Step 6 se. Ek real OPENAI_API_KEY ke saath, ise call karna SHAPE mein Step 7 ke proven trace jaisa hi dikhega — bas ab [1] ka tool call aur [3] ka final answer genuinely model se generate hote hain, haath se likhe nahi:
result = research_agent.invoke(
{"messages": [("user", "What is LangGraph's recursion_limit and why does it matter?")]},
{"recursion_limit": 10}
)
print(result["messages"][-1].content)
⚠️ Illustrative — ise author karne ke liye koi live key use nahi hui. Message ki SEQUENCE (
Human → AI-with-tool-call → Tool → AI-final) real verified hai, kyunki Step 7 ne exact wahi routing exact wahi graph ke saath chalayi. Sirf model ke tool-call arguments aur final answer ki WORDING neeche illustrative hai, jo realweb_searchtool se jo poocha aur mila hota uske consistent rakhi gayi hai:
AI (illustrative): The recursion_limit config value caps how many super-steps a
LangGraph run can take. Once an agent loop exceeds it, LangGraph raises a
GraphRecursionError instead of looping forever, which matters because an agent
that never routes to END would otherwise call the model — and the search tool —
indefinitely, burning tokens and API quota on every extra pass.
Step 9 — Loop ko real mein bound karo
try:
research_agent.invoke({"messages": [("user", "loop forever")]}, {"recursion_limit": 6})
except Exception as e:
print(type(e).__name__, str(e)[:80])
Ye C:/lcv pe test kiya gaya, ek graph ke saath jo kabhi END pe route nahi karta (ek router jo hamesha "tools" return karne ke liye hard-coded hai), aur isne ek real GraphRecursionError: Recursion limit of 6 reached without hitting a stop condition. diya — koi hang nahi, koi silent failure nahi. Isiliye recursion_limit optional polish nahi hai: kisi bhi agent graph mein router bug ho (ya genuinely confused model jo tool request karta rahe), ye loudly bound hota hai, aur tumhara API bill chupke se infinite loop mein nahi badhta.
✅ Jo tumne abhi banaya — aur resume framing
Tumne ek graph banaya jo: @tool se ek real tool define karta hai, ise ek tool-calling model se bind karta hai, conversation state ko sahi add_messages reducer se track karta hai, ek real conditional edge se route karta hai, ek real ToolNode se tool calls execute karta hai, reason→act→observe loop chalata hai jab tak model done na ho, aur recursion_limit se bound hai taaki kabhi run away na kare. Ye ek legitimate LangGraph agent hai, toy nahi — SAME shape (agent node + tools node + conditional edge + loop-back edge) hai jo create_react_agent(model, tools) (note: LangGraph 1.0 se deprecated, langchain.agents.create_agent iska successor hai, par ye naam abhi bhi chalta hai aur zyadatar tutorials isi ka use karte hain) tumhe prebuilt deta hai; ab tumhe exactly pata hai ye under the hood kya karta hai kyunki tumne ise haath se wire kiya.
Resume/interview ke liye: "Built an AI research agent in LangGraph — a StateGraph implementing a reason-act-observe loop with a real tool-calling model, a ToolNode executing web search, conditional routing between reasoning and tool execution, and a bounded recursion_limit to prevent runaway agent loops." Ye sentence dikhata hai ki tum agent architecture samajhte ho, sirf ek API call kiya nahi.
Ise extend karne ke liye bonus features (project claim karne ke liye teenon banana zaroori NAHI hai):
- Multiple tools —
web_searchke saath ekcalculatortool add karo;ToolNodealready handle karta hai model ek turn mein kai tool calls maange, koi wiring change nahi chahiye. - Human-in-the-loop —
toolsnode se pehleinterrupt()daalo taaki koi insaan search approve kare chalne se pehle,MemorySavercheckpointer aur ekthread_idke saath compile kiya hua (dekhohuman-in-the-loop). - Streaming —
research_agentpe.invoke()ki jagah.stream()use karo taaki har node ka output ho hote hi print ho, poore run khatam hone ka wait kiye bina.
Standard definition (interview me bolo): an AI research agent is a LangGraph StateGraph that implements the reason-act-observe agent loop as an explicit graph — a tool-binding model node that reasons and requests tool calls, a ToolNode that acts by executing them for real, a conditional edge that routes between reasoning and acting until the model signals it is done, and a recursion_limit that bounds the loop against runaway execution.
from typing import Annotated, Literal
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
@tool
def web_search(query: str) -> str:
"""Search the web for current information on a topic and return short snippets."""
# A real build swaps this body for a live search API call.
return f"[search stub] top result for '{query}'"
llm = ChatOpenAI(model="gpt-4.1-mini") # model IDs change — check the provider's current list
model_with_tools = llm.bind_tools([web_search])
class ResearchState(TypedDict):
messages: Annotated[list, add_messages] # reducer: APPENDS, never overwrites
def agent(state: ResearchState):
response = model_with_tools.invoke(state["messages"])
return {"messages": [response]}
def should_continue(state: ResearchState) -> Literal["tools", "__end__"]:
last = state["messages"][-1]
if last.tool_calls:
return "tools"
return END
tool_node = ToolNode([web_search])
graph = StateGraph(ResearchState)
graph.add_node("agent", agent)
graph.add_node("tools", tool_node)
graph.add_edge(START, "agent")
graph.add_conditional_edges("agent", should_continue, {"tools": "tools", END: END})
graph.add_edge("tools", "agent")
research_agent = graph.compile()
result = research_agent.invoke(
{"messages": [("user", "What is LangGraph's recursion_limit and why does it matter?")]},
{"recursion_limit": 10}
)
for m in result["messages"]:
print(type(m).__name__, "|", getattr(m, "content", m))LangChain aur LangGraphinterview questions & answers
10 sample questions below — 145+ in the full bank inside.
Human-in-the-loop flow me, kya LLM khud decide karta hai pause karke wait karna, ya ye graph ka kaam hai?
Graph ka kaam hai. LLM sirf decide karta hai kaunsa tool call karna hai aur kaunse arguments ke sath — usme execution pause karne ya human ka wait karne ki koi ability nahi hoti. Pausing isliye hota hai kyunki graph ki routing logic aur node ke andar `interrupt()` call explicitly run ko rok deta hai risky tool actually execute hone se pehle; LLM ko iska pata bhi nahi chalta.
In simple terms: LLM ek intern jaisa hai jo purchase request form fill karta hai — kya kharidna hai aur kitna, ye decide karta hai — lekin office ka approval process (intern nahi) hi hai jo form ko hold karta hai jab tak manager sign off na kare. Example: `llm_with_tools.invoke(...)` ek message return karta hai 'call delete_file("x.txt")'; graph ka `check_approval` router aur `interrupt()`, LLM nahi, isko turant chalne se rokta hai.
LangChain kya hai, ek line me?
LangChain ek framework hai jo LLM calls ko pipelines me compose karta hai — prompts, models, output parsers, tools aur retrievers sab standard building blocks hain jo tum connect karte ho instead of plumbing khud likhne ke. Ye Ch5 wale raw SDK call ke upar baithta hai, uski jagah nahi leta.
In simple terms: Isko Express.js jaisa socho Node ke liye — Node already HTTP requests handle karta hai, lekin Express routing aur middleware ready-made pieces deta hai instead of raw sockets khud parse karne ke. Example: manually prompt string banane, API call karne, aur reply parse karne ki jagah, LangChain me `prompt | model | parser` likh ke teeno steps ek reusable unit ban jate hain.
Kuch realistic examples do jaha agent me human-in-the-loop add karoge.
Koi bhi action jo paisa kharch karta hai, undo karna mushkil hai, ya system ke bahar kisi ko affect karta hai: user ki taraf se email ya message bhejna, payment ya order karna, file/database row delete ya overwrite karna, ya kuch publicly post karna. Read-only ya easily reversible actions — search, lookup, bina send kiye text draft karna — usually approval nahi chahiye.
In simple terms: Rule of thumb ye hai: kya tum ek naye employee ko day one pe bina puche ye karne doge? Agar nahi, to gate karo. Example: agent calendar check kare availability ke liye — approval nahi chahiye; wahi agent actually client ko calendar invite bhej de — approval chahiye.
`chain = prompt | model | parser` likhne pe `|` operator kya karta hai?
Ye teeno Runnables ko ek `RunnableSequence` me compose kar deta hai — `prompt` ka output `model` ka input ban jata hai, aur `model` ka output `parser` ka input ban jata hai. `chain.invoke({...})` call karne pe teeno steps order me chalte hain aur final parsed result milta hai.
In simple terms: Ye ek assembly line jaisa hai — raw material ek end se andar jata hai, har station apna kaam karta hai, finished product doosre end se bahar aata hai. Example: `chain.invoke({"question": "What is Python?"})` pehle prompt template fill karta hai, phir formatted messages model ko bhejta hai, phir model ke reply ko plain string me parse karta hai.
LangGraph graph me conditional routing kya hota hai?
Conditional routing wo tareeka hai jisse graph branch karta hai: node hamesha same next node pe jaane ke bajaye, ek router function runtime pe current State check karta hai aur decide karta hai ki agla kaunsa node jaana hai. Ye wahi mechanism hai jo ek seedhi chain ko if/elif/else ya decision tree jaisa banata hai.
In simple terms: Ye road ka ek fork hai jisme signpost pointing karne se pehle current situation padhta hai — fixed one-way street nahi. Jaise: `classify` node ke `state["category"]` ko "billing", "technical", ya "general" set karne ke baad, router us field ko padhta hai aur graph ko matching handler node pe bhej deta hai.
Agent me human-in-the-loop (HITL) kya hota hai, aur isko kyu add karte hain?
Human-in-the-loop ka matlab hai agent graph kisi risky action se pehle ruk jata hai — jaise email bhejna, file delete karna, ya payment karna — aur human ke approve/reject/edit karne ka wait karta hai tab continue hota hai. Isko isliye add karte hain kyunki LLM agent galat args ya galat intent se bhi tool call kar sakta hai, aur kuch actions itne costly ya irreversible hote hain ki unhe bina supervision chodna risky hai.
In simple terms: Ek junior employee ko socho jo client ko email draft kar sakta hai lekin send karne se pehle manager ko dikhana zaroori hai — drafting autonomous hai, send nahi. Example: agent decide karta hai `delete_file("report.pdf")` call karne ka; turant execute karne ke bajaye, graph ruk jata hai aur puchta hai 'report.pdf delete karu — confirm?' tab jaake tool actually chalta hai.
Kya conditional edge do se zyada nodes me branch kar sakti hai?
Haan — `path_map` me kitni bhi key-to-node-name mappings ho sakti hain, isliye ek hi router execution ko utni hi branches me bhej sakta hai jitne tumhare categories hain. Ek router jo query ko "billing", "technical", ya "general" classify karke teen alag handler nodes pe route karta hai wo normal, verified three-way branch hai, koi special case nahi.
In simple terms: Ye coin flip nahi hai (sirf 2 outcomes) — ye mail sorter jaisa hai jo address padh ke letter ko kai bins me se ek me daal deta hai. Concretely verify kiya: ek `classify` node ke baad `add_conditional_edges("classify", route_question, {"billing": "billing", "technical": "technical", "general": "general"})` ne refund wale message ko sahi se `billing` bheja, bug-report wale ko `technical`, aur hours-of-operation wale ko `general`.
`add_conditional_edges` ka signature kya hai, aur har argument kya karta hai?
`graph.add_conditional_edges(source, router_fn, path_map)` — `source` wo node hai jiske baad ye branching hoti hai, `router_fn` ek function hai jo current State leta hai aur agle node ka naam batane wala value return karta hai, aur `path_map` ek optional dict hai jo un return values ko real node names se map karta hai. `langgraph` 1.x pe verify kiya: `path_map` omit ho sakta hai, tab `router_fn` ka return value seedha destination node ka naam ban jaata hai.
In simple terms: `source` batata hai 'kaunse stop ke baad decision hota hai', `router_fn` batata hai 'call kaun leta hai', aur `path_map` translation table hai agar router ka answer already exact node name na ho. Jaise: `graph.add_conditional_edges("classify", route_question, {"billing": "billing", "technical": "technical", "general": "general"})` — yahan map identity map hai, isliye ye omit bhi ho sakta tha.
'Classify then route' pattern me, classify node kya karta hai aur router kya karta hai?
Classify node ek regular node hai — ye usually model (ya plain logic) call karke latest message check karta hai aur apna decision State me likhta hai, jaise `{"category": "billing"}`. Router, jo `add_conditional_edges` se wire hota hai, fir bas us already-computed field ko State se wapas padh leta hai aur return karta hai, khud koi classification kaam nahi karta.
In simple terms: Classify wo banda hai jo intake form bharta hai; router wo receptionist hai jo us form ko padh ke sahi room bataata hai. Concretely verify kiya: `classify(state)` ne refund message ke liye `{"category": "billing"}` return kiya, aur `route_question(state)` (router) ne bas `state["category"]` return kiya, jisse execution `billing` node pe gaya.
`ChatPromptTemplate.from_messages([...])` f-string se kya extra deta hai?
Ye ek reusable, typed prompt object banata hai jisme role-tagged messages (system/human/etc.) ke across named `{variable}` placeholders hote hain, aur `.invoke({...})` unhe fill kar ke ek proper message list return karta hai — sirf string nahi. Ye message list hi LCEL model me pipe karta hai, aur raw f-string ke unlike `|` ke saath cleanly compose hoti hai.
In simple terms: F-string ek baar likha hua chit-note hai jo tum har baar dobara likhte ho; `ChatPromptTemplate` ek printed form hai jisme labelled blanks hain jo reuse ho sakta hai aur kisi ko bhi de sakte ho. Example: `ChatPromptTemplate.from_messages([("system", "You are a {role}."), ("human", "{question}")])` phir `.invoke({"role": "tutor", "question": "hi"})` system + human message pair return karta hai jo model ke liye ready hai.
135+ more LangChain aur LangGraph questions inside
Create a free account to read the full question bank, learn every topic, and practise with an AI mock interview.
Unlock all questions — freeReady to practise LangChain aur LangGraph?
Unlock every topic free, then face an AI interviewer that asks follow-ups and grades your answers.