If you find the content useful and wish to support our platform’s development, you can contribute any amount toward our production costs. Scan the UPI QR code for payment within India. Or use the Ko-fi link to process a secure payment via PayPal.
If you find the content useful and wish to support our platform’s development, you can contribute any amount toward our production costs. Scan the UPI QR code for payment within India. Or use the Ko-fi link to process a secure payment via PayPal.
The evolution of Large Language Models has progressed rapidly from basic text generation to complex orchestration frameworks. However, while traditional orchestration workflows rely on static, developer-defined execution paths, enterprise requirements increasingly demand systems capable of dynamic problem-solving. Agentic AI represents a paradigm shift from deterministic code execution to goal-directed, autonomous reasoning. Rather than executing a predefined sequence of functions, an AI agent receives a goal, perceives its environment, reasons about necessary actions, calls external tools, evaluates intermediate outcomes, and adapts its path dynamically to achieve the target objective.
In enterprise environments, transition to agentic architectures is driven by the complexity of real-world workflows. Operations such as supply chain exception handling, automated incident response, financial fraud investigation, and cross-system data reconciliation cannot be easily reduced to fixed standard operating procedures. They require decision-making under uncertainty, multi-step planning, and real-time integration with legacy enterprise software.
This module covers the design, architecture, and implementation of production-grade agentic systems and multi-agent teams. You will examine core agent design patterns including ReAct (Reasoning + Acting), tool calling mechanisms, persistent state management, long-term memory architectures, dynamic planning, and human-in-the-loop governance. You will also transition from single-agent setups to multi-agent collaboration using LangGraph, learning how to design graph-based, stateful workflows that balance agent autonomy with rigorous enterprise control.
After completing this module, you should be able to:
Traditional enterprise software operates on explicit logic: given an input $X$, execute algorithm $Y$ to produce output $Z$. Even basic LLM applications often follow a fixed linear chain: prompt template $\rightarrow$ LLM call $\rightarrow$ parser output. An AI Agent, by contrast, introduces a continuous feedback loop driven by reasoning. The model acts as an autonomous reasoning engine that processes an input objective, evaluates current environmental state, formulates a hypothesis, selects an action, executes that action via an external tool, observes the environment's response, and determines its next step based on the observed result.
The foundational design pattern for agentic reasoning is ReAct (Reasoning + Acting). Proposed to overcome the limitations of isolated reasoning (Chain-of-Thought) and isolated acting (Action-only execution), ReAct interleaves natural language reasoning traces with explicit execution steps.
In enterprise deployments, pure unconstrained ReAct loops present significant risks. An unguided agent can easily enter infinite execution loops, execute redundant API calls, generate high latency, or trigger unauthorized mutations on production systems. Modern enterprise agents must therefore encapsulate the ReAct logic within deterministic state boundaries, enforced max-iteration thresholds, and strict token budgets.
max_iterations=N) on ReAct execution loops to prevent infinite loops and runaway API bills.For an agent to interact with enterprise infrastructure, it must move beyond raw text generation and invoke external code. Tool Calling (or Function Calling) is the standardized protocol that allows a language model to request the execution of predefined internal functions, database queries, or third-party web services.
Rather than having the LLM generate free-form text that must be parsed using brittle regular expressions, modern model providers natively support structured function schemas. The developer provides the model with a JSON Schema describing available functions, their required parameters, data types, and operational descriptions. When the model determines that a tool call is required, it emits a structured JSON object specifying the function name and target arguments.
In python-based enterprise frameworks like LangChain, Pydantic is used to enforce strict runtime type safety and schema validation for tools exposed to agents.
from typing import Type
from pydantic import BaseModel, Field
from langchain_core.tools import BaseTool
# 1. Define input argument schema using Pydantic
class InvoiceQueryInput(BaseModel):
vendor_id: str = Field(description="The unique enterprise vendor identifier, e.g., VEN-8841")
fiscal_year: int = Field(description="The 4-digit fiscal year to filter invoices")
limit: int = Field(default=5, description="Maximum number of invoice records to return")
# 2. Construct Custom Enterprise Tool
class EnterpriseInvoiceQueryTool(BaseTool):
name: str = "query_vendor_invoices"
description: str = (
"Retrieves historical invoice records for a given vendor and fiscal year. "
"Use this tool when you need to inspect financial obligations or billing details."
)
args_schema: Type[BaseModel] = InvoiceQueryInput
def _run(self, vendor_id: str, fiscal_year: int, limit: int = 5) -> str:
"""Simulated backend enterprise service lookup."""
mock_data = [
{"invoice_id": "INV-101", "amount": "$45,000", "status": "APPROVED"},
{"invoice_id": "INV-108", "amount": "$12,300", "status": "PENDING_APPROVAL"}
]
return f"Successfully queried vendor {vendor_id} for FY{fiscal_year}: {mock_data[:limit]}"
# 3. Instantiate tool for agent binding
invoice_tool = EnterpriseInvoiceQueryTool()
print(f"Generated Tool Schema: {invoice_tool.args}")
The quality of tool execution depends heavily on the description written inside the tool schema. LLMs use these docstrings and descriptions to decide when and how to call a function.
search_employee_directory over vague names like lookup_data.A foundational limitation of standard LLM interactions is statelessness. Every model call is an independent computation. To carry out complex, multi-step business procedures, agents require sophisticated Memory Architectures that store, index, retrieve, and modify context over time.
In enterprise agent engineering, memory is classified into three distinct tiers:
| Memory Tier | Scope | Storage & Implementation |
|---|---|---|
| Short-Term Memory | Active Execution Context (Thread History, Tool Calls) | In-memory buffers, sliding context windows, token-aware summarization |
| Long-Term Memory | Enterprise Knowledge & User Profiles (Episodic & Semantic) | Vector databases (Qdrant, Pinecone), Graph DBs (Neo4j) |
| Working Memory | Task State & Plan Tracking (Scratchpad State) | Structured Pydantic objects, relational stores |
Short-term memory manages the active conversation and reasoning history within a single execution session (or thread). It maintains the exact sequence of system messages, user inputs, assistant outputs, tool call arguments, and tool responses. Retaining this context relies on sliding window buffers or token-aware summarization processes.
Long-term memory allows agents to retain knowledge across multiple sessions, days, or months.
Working memory represents the agent’s explicit task tracking state. Rather than storing full conversational transcripts, working memory contains a structured, high-level summary of active goal progress, verified facts, extracted entities, and remaining steps.
from pydantic import BaseModel, Field
from typing import List, Dict, Any
class AgentWorkingMemory(BaseModel):
primary_goal: str
verified_facts: Dict[str, Any] = Field(default_factory=dict)
pending_subtasks: List[str] = Field(default_factory=list)
completed_subtasks: List[str] = Field(default_factory=list)
current_plan_step: int = 0
# Example Scratchpad Mutation
working_memory = AgentWorkingMemory(
primary_goal="Reconcile discrepancy in Q3 cloud expenditure",
verified_facts={"vendor": "AWS", "invoice_total": 125000.00},
pending_subtasks=["Fetch AWS Cost Explorer metrics", "Compare DB logs with invoice"],
completed_subtasks=["Retrieve PDF invoice"]
)
While simple agents can operate within basic linear loops, complex enterprise systems require cyclical, stateful, and conditionally branched execution graphs. Frameworks like LangGraph address the limitations of traditional DAG engines by modeling agent workflows as stateful graphs containing cycles.
In LangGraph, agent execution is expressed as a graph where:
The following example demonstrates how to build a production-grade ReAct agent loop using LangGraph, incorporating state persistence and conditional routing:
from typing import Annotated, TypedDict, Literal
from langchain_openai import ChatOpenAI
from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage
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
# 1. Define the Centralized Graph State
class AgentState(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
# 2. Define Mock Tools and Model Binding
@tool
def check_inventory_level(item_id: str) -> str:
"""Checks the current stock level for an inventory item."""
if item_id == "ITEM-100":
return "Stock Status: 450 units available at Central Warehouse."
return "Stock Status: Item identifier not found."
tools = [check_inventory_level]
tool_node = ToolNode(tools)
model = ChatOpenAI(model="gpt-4o-mini", temperature=0).bind_tools(tools)
# 3. Define Graph Nodes
def call_agent_node(state: AgentState) -> dict:
messages = state["messages"]
system_instruction = SystemMessage(
content="You are an enterprise logistics agent. Use tools to check inventory accurately."
)
response = model.invoke([system_instruction] + messages)
return {"messages": [response]}
# 4. Define Conditional Edge Logic
def should_continue(state: AgentState) -> Literal["tools", "__end__"]:
last_message = state["messages"][-1]
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
return "tools"
return END
# 5. Construct and Compile the StateGraph
workflow = StateGraph(AgentState)
workflow.add_node("agent", call_agent_node)
workflow.add_node("tools", tool_node)
workflow.add_edge(START, "agent")
workflow.add_conditional_edges("agent", should_continue, ["tools", END])
workflow.add_edge("tools", "agent")
app = workflow.compile()
# 6. Execute Graph Workflow
inputs = {"messages": [HumanMessage(content="What is the current stock level for ITEM-100?")]}
output = app.invoke(inputs)
for message in output["messages"]:
print(f"[{message.type.upper()}]: {message.content}")
When faced with complex operational requests, a standard single-step ReAct agent may fail due to error propagation or context distraction. High-performing enterprise agents rely on Dynamic Planning and Task Decomposition frameworks.
Planning architectures decouple the initial problem-solving phase from execution by introducing explicit planning, execution, reflection, and re-planning cycles.
As single-agent setups scale, monolithic system prompts become bloated, tool selection accuracy degrades, and reasoning performance drops. To maintain system reliability, enterprise architectures adopt Multi-Agent Systems (MAS), distributing tasks across specialized, narrow-scope agents.
from typing import Literal, TypedDict
from pydantic import BaseModel
from langgraph.graph import StateGraph, START, END
# Define overall system state
class MultiAgentState(TypedDict):
task: str
legal_analysis: str
financial_analysis: str
next_step: str
# 1. Supervisor Node
def supervisor_node(state: MultiAgentState) -> dict:
if not state.get("legal_analysis"):
return {"next_step": "legal_agent"}
elif not state.get("financial_analysis"):
return {"next_step": "finance_agent"}
else:
return {"next_step": "finalize"}
# 2. Specialized Worker Nodes
def legal_agent_node(state: MultiAgentState) -> dict:
analysis = f"Legal Audit Completed for task: '{state['task']}'. Compliance Status: PASS."
return {"legal_analysis": analysis}
def finance_agent_node(state: MultiAgentState) -> dict:
analysis = f"Financial Analysis Completed for task: '{state['task']}'. Budget Impact: +$12,000."
return {"financial_analysis": analysis}
# 3. Router Edge
def route_next(state: MultiAgentState) -> str:
return state["next_step"]
# 4. Construct Multi-Agent Graph
builder = StateGraph(MultiAgentState)
builder.add_node("supervisor", supervisor_node)
builder.add_node("legal_agent", legal_agent_node)
builder.add_node("finance_agent", finance_agent_node)
builder.add_edge(START, "supervisor")
builder.add_conditional_edges("supervisor", route_next, {
"legal_agent": "legal_agent",
"finance_agent": "finance_agent",
"finalize": END
})
builder.add_edge("legal_agent", "supervisor")
builder.add_edge("finance_agent", "supervisor")
multi_agent_app = builder.compile()
result = multi_agent_app.invoke({"task": "Evaluate Acquisition of Enterprise Corp"})
print(f"Legal Output: {result['legal_analysis']}")
print(f"Finance Output: {result['financial_analysis']}")
In enterprise deployments, high-stakes actions—such as executing financial transfers, deleting database records, modifying production code, or sending external emails—must be subject to Human-in-the-Loop (HITL) governance. HITL mechanisms interrupt agent execution prior to critical side-effect transitions, presenting current proposed actions to authorized human operators for approval, modification, or rejection.
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, START, END
from typing import TypedDict
class TransferState(TypedDict):
sender: str
recipient: str
amount: float
approved: bool
def plan_transfer(state: TransferState) -> dict:
print(f"[AGENT]: Formulating bank transfer plan: ${state['amount']} to {state['recipient']}")
return {}
def execute_transfer(state: TransferState) -> dict:
if state.get("approved"):
print(f"[ACTION EXECUTED]: Transferred ${state['amount']} to {state['recipient']}")
return {}
else:
print("[ACTION CANCELLED]: Human reviewer rejected the transfer.")
return {}
builder = StateGraph(TransferState)
builder.add_node("plan_transfer", plan_transfer)
builder.add_node("execute_transfer", execute_transfer)
builder.add_edge(START, "plan_transfer")
builder.add_edge("plan_transfer", "execute_transfer")
builder.add_edge("execute_transfer", END)
checkpointer = MemorySaver()
app = builder.compile(checkpointer=checkpointer, interrupt_before=["execute_transfer"])
config = {"configurable": {"thread_id": "session-tx-901"}}
# Step 1: Execute graph up to interrupt point
initial_input = {"sender": "ACC-01", "recipient": "ACC-99", "amount": 75000.00, "approved": False}
app.invoke(initial_input, config)
# Step 2: Human Operator Grants Approval
app.update_state(config, {"approved": True})
# Step 3: Resume Graph Execution
app.invoke(None, config)
Evaluating Agentic AI requires measuring multi-step operational execution over time. An agent can reach a correct final answer through a highly inefficient, costly, or dangerous sequence of tool calls. Enterprise evaluation analyzes both the Final Outcome and the Execution Trajectory.
| Trajectory Metrics | Outcome Metrics |
|---|---|
| Tool Selection Accuracy | Goal Success Rate (GSR) |
| Argument Schema Validity | Output Policy Compliance |
| Step Efficiency Ratio | Hallucination Rate |
| Redundant Loop Rate | Total Execution Latency & Cost |
Tool Selection Accuracy:
$$\text{Tool Selection Accuracy} = \frac{\text{Correct Tool Invocations}}{\text{Total Tool Call Attempts}}$$Step Efficiency Ratio:
$$\text{Efficiency Ratio} = \frac{\text{Optimal Steps Required}}{\text{Actual Agent Steps Taken}}$$Agentic AI marks a fundamental evolution from static language models to dynamic, stateful reasoning systems capable of executing multi-step business procedures. Throughout this module, you explored how the ReAct pattern combines natural language reasoning with practical tool usage, enabling AI models to interact dynamically with enterprise software.
Answer the following questions to review your understanding of this module's concepts. Click on each question below to reveal the answer and review your understanding of this module's concepts:
Chain-of-Thought prompting focuses purely on isolated internal reasoning steps without taking external actions. ReAct explicitly interleaves natural language reasoning traces ("Thought") with real-world tool execution ("Action") and environmental feedback ("Observation"), enabling interactive problem-solving.
Pydantic schemas enforce strict runtime type safety, parameter validation, and explicit documentation. This ensures parameter arguments generated by the LLM match expected data types before hitting backend production APIs.
Short-term memory tracks active execution logs within a single session thread. Working memory maintains an updated structural scratchpad of goal progress and facts. Long-term memory stores cross-session domain knowledge and episodic history using vector or graph databases.
Graph-based engines natively support cyclic execution loops, conditional branching, state persistence across nodes, and targeted Human-In-The-Loop interrupts, making them far better suited for complex dynamic workflows than linear DAG chains.
A Supervisor Agent acts as an orchestration router. It evaluates current workflow state, selects the appropriate specialized worker agent to execute the next task, manages state handoffs, and aggregates results once tasks are completed.
They pause execution before high-stakes nodes execute side effects, saving complete graph state to persistent storage. This enables human operators to review, approve, edit, or reject the proposed action before execution resumes.
Evaluating only the final answer masks underlying performance issues. Trajectory efficiency reveals whether an agent reached its answer through redundant tool calls, infinite loops, or unnecessarily expensive execution paths.
Planning should be decoupled for complex, highly contextual, multi-step requests. High-reasoning models build, critique, and adapt the macro plan, while faster, lower-cost models execute individual micro-tasks cleanly.
Tools should capture exceptions gracefully and return descriptive error messages as observations. This allows the agent's ReAct loop to analyze the failure context and attempt corrective action or alternative tool selection.
Summarizing past turns or applying sliding token windows prevents context window saturation, lowers API costs, and mitigates attention degradation caused by overly long prompts.
Take a short Quiz and find your score. You can always come back to this page and go through the content again!