Table of Contents

    LangChain and LangGraph Orchestration

    Generative AI and Large Language Models

    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.

    Core principle: Use deterministic application logic wherever behavior must be predictable. Use model-driven decisions only where language understanding or flexible reasoning provides measurable value.

    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
    Provider requirement: Install only the provider integration required by your approved model endpoint. Keep API keys and credentials outside source code and notebooks.

    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.

    Orchestrated Application
    Input → Validation → Routing → Retrieval or Tools → Model → Validation → Output
    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
    Relationship: LangChain components can be used inside LangGraph nodes. LangGraph can also be used without requiring every LangChain abstraction.

    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
    Recommended Approach Start with the simplest deterministic workflow that satisfies the task. Add agentic decision-making only where fixed logic is insufficient.

    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
    )
    Configuration requirement: Use an approved provider integration and model identifier. Keep API keys in a secret manager or protected environment variable.

    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
        )
    )
    Validation principle: Structured output improves integration but does not establish that the values are factually or operationally correct. Apply business validation after schema validation.

    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()
    )
    Runnable Composition
    Input → Prompt Template → Structured Model → Validated Object

    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
    )
    Deterministic boundary: Input length, required fields, authorization, and schema rules should be enforced by application code rather than delegated to the language 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
            )
        }
    Tool design: Prefer small, task-specific tools over unrestricted interfaces that expose complete databases, shells, file systems, or external services.

    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."
                    )
                }
            ]
        }
    )
    Agent limitation: Tool availability does not guarantee correct selection, arguments, authorization, sequencing, or result interpretation. Evaluate every tool behavior required by the application.

    Retrieval-Augmented Chain

    RAG Orchestration
    Question → Authorization → Retriever → Context Formatter → Model → Grounded Answer
    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()
    )
    Authorization requirement: Configure retrieval filters using trusted server-side identity and policy information before documents are returned to the chain.

    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
        }
    Node principle: Keep nodes focused and testable. A node should have a clear input contract, output contract, timeout, side-effect policy, and error behavior.

    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.

    Graph Flow
    START → Normalize → Classify → Conditional Route → END

    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"
        }
    )
    Routing principle: Use deterministic routing when explicit rules can make the decision. If model-based routing is required, constrain its labels and validate the selected route.

    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.

    Validation Loop
    Generate → Validate → Pass → END
    Validate → Fail → Revise → Validate
    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"
    Loop-safety rule: Bound retries, tool calls, iterations, elapsed time, token use, and cost. Define fallback behavior when the loop cannot resolve the problem.

    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
    Reducer concept: A reducer defines how a field's existing value and a node's update are combined. Without an appropriate reducer, a new update may replace the previous value.

    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
    )
    Production persistence: In-memory persistence is suitable for learning and local testing. Production systems require an approved durable store, access control, backup, retention, recovery, and schema-migration strategy.

    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
    Memory principle: Do not store information merely because it may be useful later. Long-term storage requires an approved purpose, ownership, access control, correction, expiration, and deletion process.

    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
    Approval principle: Human review must be meaningful. Do not present an unclear approval request that hides the action, target, evidence, or expected effect.

    Resume an Interrupted Graph

    from langgraph.types import Command
    
    
    resumed_result = (
        persistent_workflow.invoke(
            Command(
                resume="approve"
            ),
            config=workflow_configuration
        )
    )
    Consistency requirement: Resume the exact persisted thread using the approved state and verified identity. Do not recreate a consequential action from untrusted client-side data.

    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
    Streaming-security rule: Stream only approved user-facing information. Internal prompts, credentials, sensitive tool arguments, private state, and hidden reasoning must not be exposed.

    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
    Retry caution: Do not retry non-idempotent actions automatically unless the operation uses an approved idempotency mechanism.

    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
    Implementation caution: Cancellation behavior depends on the called operation and runtime. Configure request-level timeouts in model, database, HTTP, and tool clients as well as workflow-level limits.

    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
    Unnecessary Complexity Create multiple autonomous agents for a task that one deterministic pipeline can complete reliably.
    Controlled Specialization Introduce additional agents only when separation of responsibilities provides measured quality, security, maintainability, or operational value.

    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
    )
    Limit selection: The example limit only illustrates configuration. Select graph, tool, token, time, and cost limits from actual workflow requirements and tested failure behavior.

    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()
            )
    Testing principle: Test deterministic nodes without calling a language model. Mock or replace external services during unit testing.

    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
    Privacy requirement: Tracing must not expose credentials, complete confidential prompts, unnecessary retrieved content, private graph state, or sensitive tool arguments.

    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
    Unsafe Design Give an agent unrestricted access to databases, file systems, email, external APIs, and administrative operations.
    Controlled Design Expose narrowly scoped tools with explicit schemas, policy checks, bounded permissions, validation, audit logging, and human approval where required.

    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
    Rollback caution: Restoring graph code without compatible tools, prompts, state, persistence, and policies is not a complete rollback.

    Common Orchestration Mistakes

    Mistake Use an agent when a deterministic chain is sufficient.
    Correction Use fixed, testable execution paths for predictable tasks and add model-driven routing only where required.
    Mistake Allow an unbounded agent loop.
    Correction Define maximum steps, retries, tool calls, duration, tokens, and cost.
    Mistake Store credentials in graph state.
    Correction Resolve credentials through approved runtime identity or secret management.
    Mistake Trust a model-generated tool argument because it matches the schema.
    Correction Apply authorization, semantic validation, business rules, and operation-specific policy checks.
    Mistake Treat retrieved documents as trusted instructions.
    Correction Treat retrieved content as evidence and prevent it from modifying system policy or tool permissions.
    Mistake Give every node access to the complete state.
    Correction Minimize state and component access according to responsibility.
    Mistake Add human approval without showing the exact proposed action.
    Correction Present the target, arguments, evidence, effect, risks, and approval choices clearly.
    Mistake Evaluate only the final answer.
    Correction Evaluate routes, retrieval, tool calls, state transitions, iterations, approvals, latency, cost, and final output.

    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

    1

    Define the Application Objective

    Identify users, tasks, data, tools, quality requirements, prohibited actions, latency, cost, and risk.

    2

    Start with Deterministic Control

    Define fixed validation, routing, retrieval, tool, and output rules wherever possible.

    3

    Select LangChain Components

    Configure approved models, prompts, structured output, retrievers, tools, middleware, and parsers.

    4

    Design Graph State

    Define minimal, typed, versioned state fields and reducer behavior.

    5

    Create Nodes and Routes

    Implement focused nodes with explicit transitions, validation, limits, and error behavior.

    6

    Add Persistence and Approval

    Configure durable state and meaningful human review where the workflow requires interruption or consequential action.

    7

    Apply Security Controls

    Enforce authentication, authorization, least-privilege tools, retrieval filters, secret protection, and prompt-injection defenses.

    8

    Test Every Layer

    Test nodes, routes, state, tools, loops, checkpoints, interrupts, failures, model behavior, and end-to-end outcomes.

    9

    Deploy Through Controlled Environments

    Use staging, canary, version-specific monitoring, fallback, and rollback.

    10

    Observe and Improve

    Track quality, routes, tools, failures, approvals, latency, tokens, cost, security, complaints, and business outcomes.

    Hands-On Practice

    1

    Build a Structured Chain

    Create a prompt, model, Pydantic schema, and runnable pipeline for a classification task.

    2

    Create a Safe Tool

    Define one narrowly scoped tool with typed arguments, deterministic validation, and a structured result.

    3

    Build a RAG Chain

    Retrieve authorized evidence, preserve source IDs, and generate an answer using only the retrieved context.

    4

    Create a LangGraph State

    Define typed fields for input, route, context, output, errors, retries, and final response.

    5

    Add Conditional Routing

    Route knowledge, calculation, and general requests to separate nodes.

    6

    Add a Validation Loop

    Validate output, retry a bounded number of times, and use fallback after the limit is reached.

    7

    Add Persistence and Approval

    Save state using a thread identifier, pause before a controlled action, and resume after an approved decision.

    8

    Run an Evaluation Suite

    Evaluate routes, tools, retrieval, final answers, failure handling, latency, token use, cost, and approval behavior.

    Knowledge Check

    1

    What is LLM orchestration?

    Explain how it coordinates models, prompts, data, tools, validation, state, and human decisions.

    2

    What is LangChain?

    Explain its role in composing model, prompt, retrieval, tool, structured-output, and agent components.

    3

    What is LangGraph?

    Explain how it orchestrates stateful workflows through nodes, edges, routes, loops, and persistence.

    4

    How does a chain differ from an agent?

    Explain fixed application-defined sequencing compared with model-driven action selection.

    5

    What is graph state?

    Explain the shared typed data snapshot read and updated by graph nodes.

    6

    What is a conditional edge?

    Explain how routing logic selects the next graph node.

    7

    Why must loops be bounded?

    Explain the risks of unlimited model calls, tool actions, latency, token use, and cost.

    8

    What does a checkpointer do?

    Explain how it preserves graph state associated with a workflow thread.

    9

    What is a graph interrupt?

    Explain how execution pauses for external data, review, or approval.

    10

    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.