LangChain and LangGraph Orchestration
LangChain and LangGraph Orchestration
Learn how to orchestrate reliable Large Language Model applications using LangChain components and LangGraph stateful workflows, including prompts, models, structured output, tools, retrieval, agents, state, nodes, edges, routing, loops, persistence, human approval, streaming, testing, observability, security, and production controls.
Introduction
A basic Large Language Model application may send one prompt to one model and return the generated response. Production applications are usually more complex. They may validate input, retrieve authorized knowledge, call tools, generate structured output, request human approval, retry failed operations, preserve state, and record complete execution evidence.
Orchestration coordinates these components and controls the order in which they run. It determines what data each component receives, how intermediate results are stored, when the workflow branches or loops, and how failures are handled.
LangChain provides reusable abstractions for prompts, models, structured outputs, retrievers, tools, middleware, and agents. LangGraph provides lower-level orchestration for stateful workflows represented as graphs.
Learning Objectives
By the End of This Lesson
- Define LLM orchestration
- Explain the roles of LangChain and LangGraph
- Distinguish chains, agents, and graph workflows
- Create reusable prompt templates
- Build runnable LangChain pipelines
- Generate and validate structured output
- Define tools with explicit schemas
- Build a retrieval-augmented chain
- Understand agent tool-calling loops
- Define LangGraph state
- Create nodes and edges
- Implement conditional routing
- Create controlled graph loops
- Persist short-term workflow state
- Understand long-term memory boundaries
- Add human-in-the-loop approval
- Stream workflow updates
- Handle failures and retries safely
- Test and observe orchestrated applications
- Create an orchestration-readiness checklist
Prerequisites
Recommended Knowledge
- Python programming
- Functions, dictionaries, and type annotations
- Large Language Model fundamentals
- Prompt engineering
- Structured output and JSON
- Retrieval-Augmented Generation
- Embeddings and vector databases
- API and tool-calling fundamentals
- State machines and directed graphs
- Exception handling
- LLM evaluation and observability
Example Packages
- LangChain
- LangGraph
- A compatible model-provider integration
- Pydantic
- python-dotenv for local development
python -m pip install \
langchain \
langgraph \
langchain-openai \
pydantic \
python-dotenv
What Is LLM Orchestration?
LLM orchestration is the coordination of prompts, models, data, tools, validation, state, policies, and human decisions within one controlled application workflow.
| Orchestration Responsibility | Example |
|---|---|
| Sequence | Retrieve evidence before generating an answer |
| Routing | Send policy questions to an approved retriever |
| State | Preserve messages and intermediate results |
| Tool Use | Call an authorized system through a validated interface |
| Validation | Reject malformed model output |
| Approval | Pause before a consequential action |
| Recovery | Retry or use fallback after a temporary failure |
| Observability | Trace nodes, tool calls, latency, and outcomes |
What Is LangChain?
LangChain provides composable interfaces for connecting models with prompts, structured output, retrievers, tools, middleware, and agent workflows.
| LangChain Component | Purpose |
|---|---|
| Chat Model | Provides a common interface to a compatible language model |
| Prompt Template | Creates consistent model instructions |
| Output Parser | Converts model output into a required representation |
| Structured Output | Requests output matching a defined schema |
| Retriever | Returns relevant authorized content |
| Tool | Exposes a controlled operation to a model or workflow |
| Agent | Selects actions dynamically according to model decisions |
| Runnable | Provides a composable execution interface |
| Middleware | Adds cross-cutting controls around model or agent execution |
What Is LangGraph?
LangGraph represents an application as a graph consisting of state, nodes, and edges. It is useful when a workflow needs explicit branching, loops, persistence, interrupts, streaming, or human-in-the-loop behavior.
| LangGraph Concept | Meaning |
|---|---|
| State | The shared data snapshot used by graph nodes |
| Node | A function or operation that reads and updates state |
| Edge | A transition from one node to another |
| Conditional Edge | A transition selected using routing logic |
| START | The graph entry point |
| END | The graph completion point |
| Reducer | Defines how node updates are merged into state |
| Checkpointer | Persists graph state for a workflow thread |
| Interrupt | Pauses execution for external input or approval |
LangChain vs LangGraph
| Area | LangChain | LangGraph |
|---|---|---|
| Primary Focus | Composable model, prompt, tool, retrieval, and agent components | Explicit orchestration of stateful workflows |
| Control Flow | Runnable pipelines and higher-level agent patterns | Nodes, edges, branches, and loops |
| State | Passed through runnable or agent interfaces | Defined as a graph-wide state schema |
| Persistence | Depends on the selected application design | Supports graph checkpointing patterns |
| Human Approval | Can be implemented within application logic | Can be represented through graph interrupts |
| Best Fit | Model composition and common agent applications | Custom, stateful, long-running, or branching workflows |
Chain vs Agent vs Graph
| Pattern | Control Decision | Suitable Use |
|---|---|---|
| Chain | Application defines a mostly fixed sequence | Predictable prompt, model, retrieval, and parsing pipelines |
| Agent | Model dynamically selects tools or next actions | Open-ended tasks requiring controlled tool choice |
| Graph | Application defines explicit nodes, routing, state, and loops | Complex, stateful, auditable, and resumable workflows |
Configure a Chat Model
import os
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model=(
os.environ[
"APPROVED_CHAT_MODEL"
]
),
api_key=(
os.environ[
"APPROVED_MODEL_API_KEY"
]
),
temperature=0
)
Create a Prompt Template
from langchain_core.prompts import ChatPromptTemplate
classification_prompt = (
ChatPromptTemplate.from_messages(
[
(
"system",
"Classify the supplied support request. "
"Use only the approved categories: "
"Bug, Billing, Access, How-To, or Other."
),
(
"human",
"{support_request}"
)
]
)
)
Prompt Template Should Define
- The model's task
- Allowed evidence
- Required output
- Prohibited behavior
- Uncertainty and abstention behavior
- Tool-use boundaries
- Human-approval requirements
- Template version
Request Structured Output
from typing import Literal
from pydantic import BaseModel
from pydantic import Field
class SupportClassification(
BaseModel
):
category: Literal[
"Bug",
"Billing",
"Access",
"How-To",
"Other"
]
confidence: float = Field(
ge=0.0,
le=1.0
)
reason: str = Field(
min_length=1
)
structured_model = (
model.with_structured_output(
SupportClassification
)
)
Compose a LangChain Runnable
classification_chain = (
classification_prompt
|
structured_model
)
classification_result = (
classification_chain.invoke(
{
"support_request": (
"The application closes "
"after I upload a document."
)
}
)
)
print(
classification_result.model_dump()
)
Add Deterministic Preprocessing
from langchain_core.runnables import RunnableLambda
def validate_support_request(
input_data
):
support_request = str(
input_data.get(
"support_request",
""
)
).strip()
if not support_request:
raise ValueError(
"A support request is required."
)
if len(
support_request
) > 4000:
raise ValueError(
"The support request exceeds "
"the approved length."
)
return {
"support_request": support_request
}
validated_classification_chain = (
RunnableLambda(
validate_support_request
)
|
classification_prompt
|
structured_model
)
What Is a Tool?
A tool is a controlled function or operation that an application or model can request. Every tool should have a clear purpose, typed inputs, bounded permissions, validated outputs, and defined failure behavior.
| Tool Property | Requirement |
|---|---|
| Name | Clearly identifies the operation |
| Description | Explains when the tool should and should not be used |
| Input Schema | Defines required arguments and data types |
| Authorization | Restricts access according to the caller and action |
| Output Contract | Defines the returned structure |
| Timeout | Limits the duration of the operation |
| Idempotency | Controls the effect of repeated calls |
| Audit Evidence | Records the request, decision, result, and responsible identity |
Define a LangChain Tool
from langchain_core.tools import tool
@tool
def calculate_order_total(
item_prices: list[
float
]
) -> dict:
"""Calculate the sum of non-negative item prices."""
if not item_prices:
raise ValueError(
"At least one item price is required."
)
if any(
item_price < 0
for item_price in item_prices
):
raise ValueError(
"Item prices cannot be negative."
)
total = sum(
item_prices
)
return {
"item_count": len(
item_prices
),
"total": round(
total,
2
)
}
Create a Tool-Calling Agent
from langchain.agents import create_agent
agent = create_agent(
model=model,
tools=[
calculate_order_total
],
system_prompt=(
"You are a calculation assistant. "
"Use the provided tool for order totals. "
"Do not estimate totals manually."
)
)
agent_result = agent.invoke(
{
"messages": [
{
"role": "user",
"content": (
"Calculate the total for "
"12.50, 7.25, and 3.00."
)
}
]
}
)
Retrieval-Augmented Chain
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda
from langchain_core.runnables import RunnablePassthrough
def format_documents(
documents
):
formatted_sections = []
for document in documents:
source_id = document.metadata.get(
"source_id",
"unknown-source"
)
formatted_sections.append(
f"Source: {source_id}\n"
f"{document.page_content}"
)
return "\n\n".join(
formatted_sections
)
rag_prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"Answer using only the supplied context. "
"If the context is insufficient, state that "
"the answer cannot be established."
),
(
"human",
"Question:\n{question}\n\n"
"Context:\n{context}"
)
]
)
rag_chain = (
{
"question": RunnablePassthrough(),
"context": (
retriever
|
RunnableLambda(
format_documents
)
)
}
|
rag_prompt
|
model
|
StrOutputParser()
)
Treat Retrieved Content as Untrusted
Retrieval Security Controls
- Apply authorization before retrieval
- Treat retrieved text as data, not system instructions
- Preserve source identifiers
- Validate document versions and effective dates
- Limit context size
- Detect suspicious instruction-like content where required
- Do not grant tools based on retrieved instructions
- Require approval for consequential actions
- Log selected source metadata safely
- Test indirect prompt-injection cases
LangGraph State
State is the shared data structure passed through the graph. Each node reads required state fields and returns updates.
from typing import TypedDict
class WorkflowState(
TypedDict,
total=False
):
user_request: str
request_category: str
retrieved_context: list[
str
]
draft_response: str
validation_errors: list[
str
]
retry_count: int
final_response: str
State Design Principles
- Store only information required by the workflow
- Use explicit field types
- Separate user input from trusted system metadata
- Do not store credentials in graph state
- Control sensitive-data retention
- Document which node owns each update
- Version the state schema
- Validate checkpoint compatibility
LangGraph Nodes
A node is a Python callable representing one workflow operation. It receives the current state and returns a state update.
def normalize_request(
state: WorkflowState
) -> dict:
user_request = str(
state.get(
"user_request",
""
)
).strip()
if not user_request:
raise ValueError(
"A user request is required."
)
return {
"user_request": user_request,
"retry_count": 0,
"validation_errors": []
}
def classify_request(
state: WorkflowState
) -> dict:
request_text = state[
"user_request"
].lower()
if "policy" in request_text:
category = "knowledge"
elif "calculate" in request_text:
category = "calculation"
else:
category = "general"
return {
"request_category": category
}
Edges and Control Flow
Edges connect nodes. A normal edge always follows one path. A conditional edge selects the next node according to routing logic.
Build a Basic LangGraph
from langgraph.graph import END
from langgraph.graph import START
from langgraph.graph import StateGraph
workflow_builder = StateGraph(
WorkflowState
)
workflow_builder.add_node(
"normalize_request",
normalize_request
)
workflow_builder.add_node(
"classify_request",
classify_request
)
workflow_builder.add_edge(
START,
"normalize_request"
)
workflow_builder.add_edge(
"normalize_request",
"classify_request"
)
workflow_builder.add_edge(
"classify_request",
END
)
workflow = workflow_builder.compile()
workflow_result = workflow.invoke(
{
"user_request": (
"Explain the approved policy."
)
}
)
print(
workflow_result
)
Conditional Routing
def route_request(
state: WorkflowState
) -> str:
category = state[
"request_category"
]
allowed_routes = {
"knowledge",
"calculation",
"general"
}
if category not in allowed_routes:
raise ValueError(
"Unsupported request category."
)
return category
def retrieve_knowledge(
state: WorkflowState
) -> dict:
return {
"retrieved_context": [
"Replace with authorized "
"retrieval results."
]
}
def perform_calculation(
state: WorkflowState
) -> dict:
return {
"draft_response": (
"Replace with a validated "
"calculation result."
)
}
def answer_general_request(
state: WorkflowState
) -> dict:
return {
"draft_response": (
"Replace with the configured "
"general-response chain."
)
}
workflow_builder.add_node(
"retrieve_knowledge",
retrieve_knowledge
)
workflow_builder.add_node(
"perform_calculation",
perform_calculation
)
workflow_builder.add_node(
"answer_general_request",
answer_general_request
)
workflow_builder.add_conditional_edges(
"classify_request",
route_request,
{
"knowledge": "retrieve_knowledge",
"calculation": "perform_calculation",
"general": "answer_general_request"
}
)
Controlled Loops
Graphs can loop when an output requires correction, another tool call, additional evidence, or human input. Every loop must have a clear termination condition.
MAXIMUM_RETRIES = 2
def validate_response(
state: WorkflowState
) -> dict:
draft_response = str(
state.get(
"draft_response",
""
)
).strip()
validation_errors = []
if not draft_response:
validation_errors.append(
"Response is empty."
)
if len(
draft_response
) > 5000:
validation_errors.append(
"Response exceeds the approved length."
)
return {
"validation_errors": (
validation_errors
)
}
def route_after_validation(
state: WorkflowState
) -> str:
validation_errors = state.get(
"validation_errors",
[]
)
retry_count = state.get(
"retry_count",
0
)
if not validation_errors:
return "complete"
if retry_count >= MAXIMUM_RETRIES:
return "fallback"
return "revise"
Message State and Reducers
Conversational workflows commonly need to accumulate messages rather than replace them after every node.
from langgraph.graph import MessagesState
class AgentState(
MessagesState
):
approved: bool
action_summary: str
Short-Term Persistence
A checkpointer can preserve graph state for a workflow thread. A thread identifier connects repeated invocations to the correct persisted state.
from langgraph.checkpoint.memory import InMemorySaver
checkpointer = InMemorySaver()
persistent_workflow = (
workflow_builder.compile(
checkpointer=checkpointer
)
)
workflow_configuration = {
"configurable": {
"thread_id": (
"generated-workflow-thread-id"
)
}
}
result = persistent_workflow.invoke(
{
"user_request": (
"Explain the approved process."
)
},
config=workflow_configuration
)
Short-Term State vs Long-Term Memory
| Area | Short-Term State | Long-Term Memory |
|---|---|---|
| Scope | One thread, task, or conversation | Information reused across threads or sessions |
| Examples | Messages, current plan, tool result, retry count | Approved preferences, reusable facts, or application records |
| Lifetime | Usually bounded by the workflow lifecycle | Persisted according to an explicit retention policy |
| Main Risk | Oversized context or cross-step inconsistency | Privacy, incorrect memories, stale data, and cross-user leakage |
Human-in-the-Loop Approval
A graph can pause before a consequential action and request an external approval decision.
from langgraph.types import interrupt
def request_action_approval(
state: WorkflowState
) -> dict:
approval_decision = interrupt(
{
"action": (
"proposed-consequential-action"
),
"summary": state.get(
"draft_response",
""
),
"required_decision": [
"approve",
"reject"
]
}
)
if approval_decision not in {
"approve",
"reject"
}:
raise ValueError(
"Unsupported approval decision."
)
return {
"final_response": (
"Action approved."
if approval_decision == "approve"
else "Action rejected."
)
}
Approval Screen Should Show
- The proposed action
- The target system or record
- The arguments and expected effect
- The evidence used
- The requesting identity
- The model and workflow versions
- Warnings and policy checks
- Approve, reject, or edit options
Resume an Interrupted Graph
from langgraph.types import Command
resumed_result = (
persistent_workflow.invoke(
Command(
resume="approve"
),
config=workflow_configuration
)
)
Streaming Graph Updates
for workflow_update in (
persistent_workflow.stream(
{
"user_request": (
"Explain the approved process."
)
},
config=workflow_configuration,
stream_mode="updates"
)
):
print(
workflow_update
)
Streaming Can Expose
- Generated text tokens
- Node updates
- Tool-call status
- Retrieval status
- Approval requests
- Progress events
- Completion or failure status
Retry and Fallback
| Failure | Possible Response |
|---|---|
| Temporary Model Failure | Apply a bounded retry with backoff where safe |
| Invalid Structured Output | Retry with constrained correction or return controlled failure |
| Retriever Unavailable | Use approved fallback retrieval or state that evidence is unavailable |
| Tool Timeout | Stop, retry only when safe, or request human review |
| Authorization Failure | Reject immediately without model override |
| Maximum Iterations Reached | Stop the loop and return fallback or escalation |
| Persistence Failure | Prevent unsafe continuation and preserve incident evidence |
Timeout Wrapper
from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import TimeoutError
def run_with_timeout(
function,
timeout_seconds,
*args,
**kwargs
):
if timeout_seconds <= 0:
raise ValueError(
"timeout_seconds must be positive."
)
with ThreadPoolExecutor(
max_workers=1
) as executor:
future = executor.submit(
function,
*args,
**kwargs
)
try:
return future.result(
timeout=timeout_seconds
)
except TimeoutError as error:
future.cancel()
raise TimeoutError(
"Operation exceeded the "
"approved timeout."
) from error
Multi-Agent Orchestration
Multi-agent systems divide work among specialized agent roles. They can be useful when roles require distinct tools, context, policies, or evaluation criteria.
| Pattern | General Structure |
|---|---|
| Supervisor | A supervisor routes tasks to specialized workers |
| Pipeline | Agents process work in a predefined sequence |
| Network | Agents can transfer control among permitted peers |
| Hierarchical | Supervisors coordinate lower-level groups |
| Reviewer Pattern | One agent creates an output and another evaluates it |
Recursion and Iteration Limits
graph_configuration = {
"configurable": {
"thread_id": (
"generated-thread-id"
)
},
"recursion_limit": 20
}
result = workflow.invoke(
{
"user_request": (
"Process the supplied request."
)
},
config=graph_configuration
)
Test Nodes Independently
def test_normalize_request():
result = normalize_request(
{
"user_request": (
" Explain the policy. "
)
}
)
assert result[
"user_request"
] == "Explain the policy."
assert result[
"retry_count"
] == 0
def test_normalize_request_rejects_empty_input():
try:
normalize_request(
{
"user_request": " "
}
)
assert False
except ValueError as error:
assert (
"required"
in str(
error
).lower()
)
Test Routing
def test_knowledge_route():
route = route_request(
{
"request_category": (
"knowledge"
)
}
)
assert route == "knowledge"
def test_unsupported_route():
try:
route_request(
{
"request_category": (
"unknown"
)
}
)
assert False
except ValueError:
assert True
End-to-End Graph Testing
| Test Type | Purpose |
|---|---|
| Node Unit Test | Validates one deterministic operation |
| Routing Test | Validates branch selection |
| State Test | Validates state updates and reducers |
| Tool Test | Validates schemas, permissions, and side effects |
| Checkpoint Test | Validates persistence and resume behavior |
| Interrupt Test | Validates pause, approval, rejection, and resume |
| Loop Test | Validates termination and maximum iterations |
| Regression Evaluation | Validates model-driven behavior on a versioned dataset |
| Failure Injection | Validates model, retrieval, tool, and persistence failures |
Observability
Trace for Every Workflow
- Workflow and thread identifiers
- Application and graph versions
- Node start, completion, and failure
- Selected routes
- Model and prompt versions
- Retriever and source versions
- Tool names and validated argument metadata
- Interrupt and approval events
- Retry and fallback decisions
- Node and end-to-end latency
- Token and cost measurements
- Final outcome and evaluation results
Orchestration Metrics
| Metric | Purpose |
|---|---|
| Workflow Success Rate | Measures successful end-to-end execution |
| Node Failure Rate | Identifies unreliable workflow components |
| Route Distribution | Tracks branch-selection behavior |
| Tool-Call Error Rate | Tracks invalid or failed tool calls |
| Average Iterations | Tracks loop and agent efficiency |
| Maximum-Limit Rate | Tracks runs terminated by safety limits |
| Interrupt Rate | Tracks workflows requiring external input |
| Approval and Rejection Rate | Tracks human decisions |
| Fallback Rate | Tracks use of alternate behavior |
| End-to-End Latency | Measures complete workflow duration |
| Cost per Completed Task | Measures operational efficiency |
Security Controls
Required Controls
- Authenticate every workflow caller
- Authorize every tool and data source independently
- Keep credentials outside prompts and graph state
- Use least-privilege tool interfaces
- Validate tool arguments server-side
- Apply retrieval authorization before returning content
- Treat tool and retrieval outputs as untrusted data
- Limit iterations, tokens, time, and cost
- Require approval for consequential actions
- Protect checkpoints and memory stores
- Audit graph changes and workflow executions
- Use secure transport and approved endpoints
Prompt Injection Defense
Orchestration Defenses
- Separate instructions from untrusted content
- Do not let retrieved text modify tool permissions
- Do not expose hidden prompts or credentials
- Validate routes and tool calls outside the model
- Restrict network and file-system access
- Require confirmation before consequential actions
- Limit the data returned by each tool
- Use allowlists for permitted operations
- Test direct and indirect injection attacks
- Stop safely when authorization or evidence is insufficient
Cost and Performance
| Cost Driver | Possible Control |
|---|---|
| Repeated Model Calls | Use deterministic preprocessing and bounded loops |
| Large Context | Retrieve, filter, deduplicate, and compress context |
| Unnecessary Agents | Use fixed chains for predictable workflows |
| Excessive Tool Calls | Validate need and cap call count |
| Large State | Store references instead of duplicating content |
| Long Checkpoint Retention | Apply lifecycle and deletion policies |
| Repeated Retrieval | Cache only authorized and version-compatible results |
| Expensive Judge Calls | Use deterministic tests and representative sampling first |
Version the Complete Workflow
| Asset | Versioning Requirement |
|---|---|
| Graph Definition | Nodes, edges, conditional routes, and loop rules |
| State Schema | Fields, types, reducers, and migration rules |
| Models | Provider, model, and immutable revision |
| Prompts | System, task, routing, and correction templates |
| Tools | Names, descriptions, schemas, permissions, and implementations |
| Retrievers | Corpus, embedding, index, filters, and top-k policy |
| Middleware | Authentication, authorization, validation, and limits |
| Evaluation | Dataset, rubrics, metrics, and release thresholds |
| Persistence | Checkpoint schema, store, retention, and recovery |
| Deployment | Application, container, configuration, and policy versions |
Rollback
Rollback Must Restore
- Graph definition
- State schema compatibility
- Model and prompt versions
- Tool schemas and implementations
- Retriever and corpus versions
- Authorization policies
- Iteration and cost limits
- Persistence and checkpoint compatibility
- Evaluation and monitoring configuration
- Fallback and human-approval behavior
Common Orchestration Mistakes
Troubleshooting
| Symptom | Investigation |
|---|---|
| Incorrect Route | Classification labels, router prompt, validation, and route mapping |
| Graph Does Not Terminate | Conditional edges, retry count, recursion limit, and stop conditions |
| Messages Disappear | State schema, reducer configuration, and node updates |
| Tool Is Not Called | Tool description, schema, prompt, provider support, and agent configuration |
| Wrong Tool Arguments | Schema ambiguity, prompt context, model capability, and validation |
| Thread State Is Missing | Checkpointer, thread identifier, persistence configuration, and retention |
| Resume Fails | Checkpoint availability, state-schema version, interrupt, and configuration |
| Streaming Leaks Internal Data | Stream mode, event filtering, state content, and response serialization |
| High Cost | Loop count, context size, model selection, repeated retrieval, and tool usage |
| Good Demo but Poor Production Behavior | Evaluation coverage, failures, concurrency, authorization, persistence, and real input distribution |
Recommended Orchestration Workflow
Define the Application Objective
Identify users, tasks, data, tools, quality requirements, prohibited actions, latency, cost, and risk.
Start with Deterministic Control
Define fixed validation, routing, retrieval, tool, and output rules wherever possible.
Select LangChain Components
Configure approved models, prompts, structured output, retrievers, tools, middleware, and parsers.
Design Graph State
Define minimal, typed, versioned state fields and reducer behavior.
Create Nodes and Routes
Implement focused nodes with explicit transitions, validation, limits, and error behavior.
Add Persistence and Approval
Configure durable state and meaningful human review where the workflow requires interruption or consequential action.
Apply Security Controls
Enforce authentication, authorization, least-privilege tools, retrieval filters, secret protection, and prompt-injection defenses.
Test Every Layer
Test nodes, routes, state, tools, loops, checkpoints, interrupts, failures, model behavior, and end-to-end outcomes.
Deploy Through Controlled Environments
Use staging, canary, version-specific monitoring, fallback, and rollback.
Observe and Improve
Track quality, routes, tools, failures, approvals, latency, tokens, cost, security, complaints, and business outcomes.
Hands-On Practice
Build a Structured Chain
Create a prompt, model, Pydantic schema, and runnable pipeline for a classification task.
Create a Safe Tool
Define one narrowly scoped tool with typed arguments, deterministic validation, and a structured result.
Build a RAG Chain
Retrieve authorized evidence, preserve source IDs, and generate an answer using only the retrieved context.
Create a LangGraph State
Define typed fields for input, route, context, output, errors, retries, and final response.
Add Conditional Routing
Route knowledge, calculation, and general requests to separate nodes.
Add a Validation Loop
Validate output, retry a bounded number of times, and use fallback after the limit is reached.
Add Persistence and Approval
Save state using a thread identifier, pause before a controlled action, and resume after an approved decision.
Run an Evaluation Suite
Evaluate routes, tools, retrieval, final answers, failure handling, latency, token use, cost, and approval behavior.
Knowledge Check
What is LLM orchestration?
Explain how it coordinates models, prompts, data, tools, validation, state, and human decisions.
What is LangChain?
Explain its role in composing model, prompt, retrieval, tool, structured-output, and agent components.
What is LangGraph?
Explain how it orchestrates stateful workflows through nodes, edges, routes, loops, and persistence.
How does a chain differ from an agent?
Explain fixed application-defined sequencing compared with model-driven action selection.
What is graph state?
Explain the shared typed data snapshot read and updated by graph nodes.
What is a conditional edge?
Explain how routing logic selects the next graph node.
Why must loops be bounded?
Explain the risks of unlimited model calls, tool actions, latency, token use, and cost.
What does a checkpointer do?
Explain how it preserves graph state associated with a workflow thread.
What is a graph interrupt?
Explain how execution pauses for external data, review, or approval.
Why must tools use least privilege?
Explain how narrowly scoped permissions reduce the impact of incorrect, manipulated, or unauthorized agent actions.
Orchestration Readiness Checklist
Confirm Before Production Use
- The application objective is documented
- Intended and prohibited actions are documented
- Deterministic logic is used wherever practical
- Model and immutable revision are recorded
- Prompt templates are versioned
- Structured outputs are schema validated
- Business validation follows schema validation
- Tool descriptions are explicit
- Tool input and output schemas are versioned
- Tool permissions follow least privilege
- Tool arguments are validated server-side
- Non-idempotent actions are protected
- Retrieval authorization occurs before content is returned
- Retrieved content is treated as untrusted data
- The graph state schema is typed and versioned
- Sensitive state fields are minimized
- Reducers are tested
- Every node has a clear contract
- Every conditional route is validated
- Loops have explicit termination conditions
- Iteration, token, time, tool, and cost limits are defined
- Fallback behavior is defined
- Checkpoint persistence is approved
- Thread identities are protected
- Checkpoint retention is documented
- State-schema migration is tested
- Human approval is required where appropriate
- Approval screens show exact proposed actions
- Resume behavior is tested
- Streaming output is filtered
- Prompt-injection scenarios are tested
- Unit and routing tests pass
- Tool and retrieval tests pass
- Failure-injection tests pass
- End-to-end evaluation passes
- Workflow traces are available
- Sensitive telemetry is protected
- Latency, token use, and cost are measured
- Canary, fallback, and rollback are tested
- The complete workflow release is auditable
Lesson Summary
| Concept | Main Outcome |
|---|---|
| Orchestration | Coordinates models, data, tools, state, and policies |
| LangChain | Provides composable model-application components |
| LangGraph | Provides explicit stateful graph orchestration |
| Runnable | Provides a composable execution interface |
| Structured Output | Converts model responses into a typed contract |
| Tool | Exposes a controlled external operation |
| Agent | Selects actions dynamically within defined limits |
| State | Preserves the current workflow data snapshot |
| Node | Performs one focused stateful operation |
| Conditional Edge | Routes execution according to validated logic |
| Reducer | Controls how state updates are combined |
| Checkpointer | Persists workflow state by thread |
| Interrupt | Pauses execution for external input or approval |
| Streaming | Exposes controlled progress and output updates |
| Observability | Provides evidence about routes, nodes, tools, and outcomes |
Key Takeaway
LangChain provides reusable components for prompts, models, structured outputs, retrieval, tools, middleware, and agents. LangGraph provides explicit orchestration for stateful workflows through state schemas, nodes, edges, conditional routing, loops, checkpointing, interrupts, and streaming. A reliable application should not make every decision agentic. Deterministic code should enforce input validation, authorization, tool permissions, routes, limits, schemas, and consequential-action policies whenever possible. Model-driven behavior should remain bounded, observable, evaluated, and recoverable. Production orchestration also requires typed and versioned state, least-privilege tools, trusted retrieval filters, prompt-injection defenses, meaningful human approval, bounded retries, durable persistence, complete tracing, cost controls, fallback, and rollback. The goal is not to create the most complex agent. The goal is to build the simplest workflow that completes the intended task reliably, securely, and responsibly.
Get Machine Learning updates by email
Short, practical lessons and course updates from Rumman Ansari. No spam.