DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • The LLM Selection War Story: Part 4 - Your Production Failure Testing Suite
  • Intelligent Load Management for LLM Calls: From Static Rate Limits to Priority-Aware "Agent QoS"
  • Stop Writing Excel Specs: A Markdown-First Approach to Enterprise Java
  • Anthropic’s Model Context Protocol (MCP): A Developer’s Guide to Long-Context LLM Integration

Trending

  • Navigating the Complexities of AI-Driven Integration in Multi-Cloud Environments: A Veteran’s Insights
  • How to Build and Optimize AI Models for Real-World Applications
  • How AI Coding Assistants Are Changing Developer Flow
  • Stop Using the ATM-Didn’t-Kill-Jobs Story to Reassure Developers About AI
  1. DZone
  2. Data Engineering
  3. AI/ML
  4. LLM Agents and Getting Started with Them

LLM Agents and Getting Started with Them

As LLM agents take the center stage in 2026, let's discuss what are LLM agents and how to get started with creating one.

By 
Harshita Asnani user avatar
Harshita Asnani
·
Takshil Mehta user avatar
Takshil Mehta
·
May. 25, 26 · Tutorial
Likes (0)
Comment
Save
Tweet
Share
182 Views

Join the DZone community and get the full member experience.

Join For Free

LLM-powered agents are gaining popularity and 2026 is set to be the year of agents just like 2025. Generative AI applications have now moved from normal chatbot applications, search and retrieve systems to building more of autonomous agents that can break bigger tasks down in smaller sub-tasks, achieving a goal while also interacting with environment. Before diving deeper into LLM powered agents and tools to create one let's start by answering the most important question 

What is an Agent?

According to the gold standard definition that comes from Artificial Intelligence: A Modern Approach textbook by Stuart Russell and Peter Norvig "An agent is anything that can be viewed as perceiving its environment through sensors and acting upon that environment through actuators."

A vacuum cleaning robot is a good example of an agent. It uses sensors such as cameras, dirt detectors, bump sensors, and infrared sensors to gather information about its surroundings. To interact with and act upon its environment, it relies on actuators like wheels for movement, brushes for sweeping, and a suction motor for collecting dirt. This agent also performs a perception-action cycle to achieve its goals.

Plain Text
 
1. PERCEIVE  → Sensors detect dirty floor ahead
2. DECIDE    → Agent decides to move forward and clean
3. ACT       → Wheels move, brushes spin, motor sucks dirt
4. REPEAT    → Continue until floor is clean


The term percept refers to the input that the agent receives and perceives, percept sequence is a history of everything that the agent has received or perceived.

Broadly the agents can be divided into the following categories:

  • Simple Reflex Agents: These are the most simplest kind of agents, and can be considered as following a rule-based approach. If this then that kind of logical approach to problem solving. These agents take action only considering the current input or percept ignoring everything from the percept history
  • Model Based Reflex Agents: These agents are also reflex agents, however they take informed decisions by maintaining an internal state or storage that tracks the part of environment that it has visited, but cannot observe right now. If an environment changes then the agent behavior needs to be updated.
  • Goal Based Agents: These agents work backwards from a desired goal, take and plan actions in accordance with this goal. This is different from simple reflex based agents, as decision making considers what will happen if an action is performed. Hence, considering the impact of their current choice on future state.
  • Utility Based Agents: Utility based agents are also working backwards from a desired goal, but are also optimizing for a metric. The performance is tracked based on an utility function, the agent tries to achieve goals while maximizing the utility function. For example, an agent that is designed to find shortest path between two points, the goal is to find a path between the two points, while also considering that the length of path is shortest. 
  •  Learning Agents: The most advanced type of agent. This has three main elements: the learning element, the performance element and the problem generator. Imagine an agent with decomposes a tasks, critics the current set of actions and finally also suggest actions that will lead to new experience. Diagram 1 Image
    We will look at and construct simple examples of most of these agents.

While a vacuum uses physical sensors, an LLM agent 'perceives' through text inputs and API responses, and 'acts' by generating text or calling functions. Just as a vacuum can be a simple bump-and-turn robot or a sophisticated room-mapper, LLM agents vary in intelligence. We can categorize them into five levels of sophistication:, for a LLM-powered agent the "sensor" is the user input. These agents usually have four main components:

  1. The agent core or the agent brain
  2. The planning module
  3. Memory
  4. Tools 

Diagram 2 image

Theoretical definitions are fine, but how do we build them? We will use LangChain and LangGraph frameworks to build our first LLM powered agent. Both are open source frameworks that are used to build LLM powered applications. The choice depends on the type of agent we are building and the intended level of control we wish to have on the agentic architecture.

LangChain is an open source framework with a pre-built agent architecture and integrations for any model or tool — so you can build agents that adapt as fast as the ecosystem evolve.

LangChain is used to build on ideas of chain or a pipeline, a sequence of steps executed in a linear order. Every LangChain workflow is treated as a DAG (Direct Acyclic Graph) where tasks flow in one direction without any cycles or loops. LangGraph is great at handling complex workflows, loops, decision branches in workflows, complex decision trees etc, it also provides great flexibility and control into each component of agent setup.

In this article we will create an agent using both LangChain and LangGraph to understand the pros and cons and usage of both these frameworks. For Simple Reflex Agents, that follow a straight line (Input -> Output), LangChain is our go-to framework. However, as we move toward Model based, Goal and Utility Agents that require loops, self-correction, and state management, we need the flexibility of LangGraph.

Let’s see this evolution in action by building a 'Chef Agent' that grows smarter with every iteration.We will see how we can go from building a simple reflex agent for this task to a utility based agent, based on the complexities we add for this agent. Let's set up our environment for this task. We will be using Groq to invoke gpt-oss api. You will need to get your API access key from here, we will be using open-source models for this exercise. 

We will start by installing the required libraries

Python
 
pip install langchain langchain-groq langgraph langchain-core pydantic


Next we will set up few variables that we will use across all our agents, these include the API key, model name. 

Python
 
#config
import os
from dotenv import load_dotenv

load_dotenv()

# Set your API key
GROQ_API_KEY="YOUR_API_KEY"
os.environ["GROQ_API_KEY"] = GROQ_API_KEY
model = 'openai/gpt-oss-safeguard-20b'


Now let's start with building a simple reflex agent. A simple reflex agent is a very simple agent, that in this case if the user asks for a recipe suggests the recipe. It works with if else logic blocks. We use langchain to create this agent, the agent suggests whatever recipe the user requests. 

Python
 
######### llm brain #############

import os
from langchain_groq import ChatGroq

# Setup Groq LLM
llm = ChatGroq(
    temperature=0,
    model_name=model,
    api_key=os.environ.get("GROQ_API_KEY")
)

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

# 1. Perception -> Action (Direct Chain)
reflex_prompt = ChatPromptTemplate.from_template(
    "You are a chef. Given a request: {input}, provide a single recipe immediately."
)

reflex_agent = reflex_prompt | llm | StrOutputParser()

#invoke the brain

print(reflex_agent.invoke({"input": "I want a spicy pasta."}))


Imagine you start using this agent to get a recipe for your dinner tonight, however you lack the ingredients that are needed to prepare the food. Such a bummer! Wouldn't it be nice to have an agent that has knowledge of the ingredients in your pantry or your dietary preferences before suggesting a recipe?

Our Reflex Agent is fast, but it’s 'forgetful.' It suggests a spicy pasta even if you have no pasta in your pantry or a gluten allergy. To make it a Model-Based Reflex Agent, we must give it a way to track the 'internal state' of its world specifically, your pantry inventory and dietary needs.For this, we move to LangGraph, which allows the agent to maintain a persistent memory (State) and use tools to 'sense' its environment

Python
 
from langchain.tools import tool
from langchain.chat_models import init_chat_model
import operator
from langgraph.prebuilt import ToolNode, InjectedState
import operator
from typing import Annotated, List, Literal

llm = ChatGroq(
    temperature=0,
    model_name=model,
    api_key=os.environ.get("GROQ_API_KEY")
)



@tool
def get_inventory(state:Annotated[dict, InjectedState]):
  "Get cuurent user inventory"
  return state["inventory"]

@tool
def get_dietary_prefs(state:Annotated[dict, InjectedState]):
  "Get user dietary preferences"
  return state["dietary_prefs"]

@tool
def get_history(state:Annotated[dict, InjectedState]):
  "Get user history"
  return state["history"]


# Augment the LLM with tools
tools = [get_inventory, get_dietary_prefs, get_history]
tools_by_name = {tool.name: tool for tool in tools}
model_with_tools = llm.bind_tools(tools)

# Step 2: Define state

from langchain.messages import AnyMessage
from typing_extensions import TypedDict
import operator

class RecipeState(TypedDict):
    inventory: List[str]      # What's in the fridge
    dietary_prefs: List[str]  # e.g., "Vegetarian"
    suggestion: str           # The output
    messages: Annotated[List[AnyMessage], operator.add]


# Step 3: Define model node
from langchain.messages import SystemMessage


def llm_call(state: dict):
    """LLM decides whether to call a tool or not"""

    # Combine system message with user messages
    messages_to_send = [
        SystemMessage(
            content="""You are a helpful chef agent. When user asks for a recipe, look at user's current inventory, dietary prefrences to suggest the recipe.
            You can use the available tools whenever needed."""
        )
    ] + state["messages"]

    response = model_with_tools.invoke(messages_to_send)

    return {
        "messages": [response],
        "inventory": state.get("inventory", []),
        "dietary_prefs": state.get("dietary_prefs", [])
    }


# Step 4: Define tool node

from langchain.messages import ToolMessage

tool_node = ToolNode(tools)

# Step 5: Define logic to determine whether to end

from langgraph.graph import StateGraph, START, END


# Conditional edge function to route to the tool node or end based upon whether the LLM made a tool call
def should_continue(state: RecipeState) -> Literal["tool_node", END]:
    """Decide if we should continue the loop or stop based upon whether the LLM made a tool call"""

    messages = state["messages"]
    last_message = messages[-1]

    # If the LLM makes a tool call, then perform an action
    if last_message.tool_calls:
        return "tool_node"

    # Otherwise, we stop (reply to the user)
    return END

# Step 6: Build agent

# Build workflow
agent_builder = StateGraph(RecipeState)

# Add nodes
agent_builder.add_node("llm_call", llm_call)
agent_builder.add_node("tool_node", tool_node)

# Add edges to connect nodes
agent_builder.add_edge(START, "llm_call")
agent_builder.add_conditional_edges(
    "llm_call",
    should_continue,
    ["tool_node", END]
)
agent_builder.add_edge("tool_node", "llm_call")

# Compile the agent
agent = agent_builder.compile()


from IPython.display import Image, display
# Show the agent
display(Image(agent.get_graph(xray=True).draw_mermaid_png()))

# Invoke
from langchain.messages import HumanMessage
messages = [HumanMessage(content="Suggest a recipe for pasta")]
current_inventory = ["pasta", "water", "tomatoes", "salt","parmesan"]
current_dietary_prefs = ["vegetarian"]
messages = agent.invoke({"inventory":current_inventory,
                         "dietary_prefs":current_dietary_prefs,
                         "messages": messages})
for m in messages["messages"]:
    m.pretty_print()


This agent considers not just the current user request, but also takes into account the user environment, in this case the pantry and suggest recipes based on the items actually available in the pantry.

Having memory is a start, but a truly intelligent chef doesn't just look at what's in the fridge, it works toward a specific outcome. This brings us to Goal and Utility-Based Agents.
In this next evolution, the agent doesn't just suggest any recipe; it must satisfy a 'Goal': suggesting a meal that fits within a specific calorie budget. This requires a Planning/Verification loop where the agent critiques its own suggestion before presenting it to you.

Python
 
# Step 1: Define tools and model

from langchain.tools import tool
from langchain.chat_models import init_chat_model
import operator
from langgraph.prebuilt import ToolNode, InjectedState

llm = ChatGroq(
    temperature=0,
    model_name=model,
    api_key=os.environ.get("GROQ_API_KEY")
)



@tool
def get_inventory(state:Annotated[dict, InjectedState]):
  "Get cuurent user inventory"
  return state["inventory"]

@tool
def get_dietary_prefs(state:Annotated[dict, InjectedState]):
  "Ger user dietary prefrences"
  return state["dietary_prefs"]

@tool
def get_remaining_calories_range(state:Annotated[dict, InjectedState]):
  "Get range of remaining calories"
  return (state["total_calories"]- state["consumed_calories"]+state["error_margin_calories"],
          state["total_calories"]- state["consumed_calories"]-state["error_margin_calories"])


# Augment the LLM with tools
tools = [get_inventory, get_dietary_prefs, get_history]
tools_by_name = {tool.name: tool for tool in tools}
model_with_tools = llm.bind_tools(tools)

# Step 2: Define state

from langchain.messages import AnyMessage,HumanMessage, SystemMessage
from typing_extensions import TypedDict, Annotated
import operator
from typing import List

class RecipeState(TypedDict):
    inventory: List[str]      # What's in the fridge
    dietary_prefs: List[str]  # e.g., "Vegetarian"
    suggestion: str           # The output
    total_calories: int       # The total number of calories to consume
    consumed_calories: int     # The number of calories already_consumed
    error_margin_calories: int  # The number of calories that can be added or deleted from total calories
    num_tries: int            # The number of tries the agent has made
    messages: Annotated[List[AnyMessage], operator.add]


# Step 3: Define Planner Node
def planner_node(state: RecipeState):
    """Suggests a recipe and estimates calories."""
    messages_to_send = [
        SystemMessage(content=(
            "You are a chef. Suggest a recipe based on inventory and dietary prefs. "
            "IMPORTANT: You MUST provide an estimated calorie count for the meal."
            "You can use the available tools whenever needed."
        ))
    ] + state["messages"]

    response = model_with_tools.invoke(messages_to_send)
    return {"messages": [response],
            "num_tries":state.get("num_tries",0)}

def verify_search_node(state: RecipeState):
    """Checks if the suggested meal fits the calorie constraints."""
    last_message = state["messages"][-1].content

    # Simple logic: Ask LLM to extract or verify,
    # or use regex/logic if you want to be strict.
    prompt = f"""
    The user's goal is {state['total_calories']} calories (margin: +/- {state['error_margin_calories']}).
    Current consumed: {state['consumed_calories']}.
    The chef suggested: {last_message}

    Does this recipe fit the remaining calorie budget?
    If yes, reply 'VALID'. If no, explain why.
    You can use the avilable tools whenever needed.
    """

    verification_response = llm.invoke([HumanMessage(content=prompt)])

    # We can add this to messages to keep track of the critique
    return {"messages": [verification_response]}

  
def should_verify(state: RecipeState) -> Literal["verify_node", "tool_node", END]:
    last_message = state["messages"][-1]

    if last_message.tool_calls:
        return "tool_node"

    # If no tool calls, it means the LLM has made a suggestion. Now verify it.
    return "verify_node"

def is_it_valid(state: RecipeState) -> Literal["planner_node", END]:
    last_message = state["messages"][-1].content
    if "VALID" in last_message.upper():
        return END
    # If not valid, loop back to the planner to try again
    return "planner_node"
def num_tries_exceeded(state: RecipeState) -> Literal["planner_node",END]:
    if state["num_tries"] > 5:
        return END
    return "planner_node"


# Build workflow
agent_builder = StateGraph(RecipeState)

agent_builder.add_node("planner_node", planner_node)
agent_builder.add_node("tool_node", tool_node)
agent_builder.add_node("verify_node", verify_search_node)

agent_builder.add_edge(START, "planner_node")

# Route from Planner
agent_builder.add_conditional_edges(
    "planner_node",
    should_verify,
    {"tool_node": "tool_node",
     "verify_node": "verify_node",
     END: END}
)

# Route from Tool back to Planner
agent_builder.add_edge("tool_node", "planner_node")

# Route from Verifier back to Planner OR End
agent_builder.add_conditional_edges(
    "verify_node",
    is_it_valid,
    {"planner_node":
     "planner_node",
     END: END}
)

agent = agent_builder.compile()

from IPython.display import Image, display
# Show the agent
display(Image(agent.get_graph(xray=True).draw_mermaid_png()))

# Invoke
from langchain.messages import HumanMessage
messages = [HumanMessage(content="Suggest a recipe for pasta")]
current_inventory = ["pasta", "water", "tomatoes", "salt","parmesan"]
current_dietary_prefs = ["vegetarian"]
messages = agent.invoke({"inventory":current_inventory,
                         "dietary_prefs":current_dietary_prefs,
                         "total_calories": 1000,
                         "consumed_calories": 800,
                         "error_margin_calories": 100,
                         "messages": messages})
for m in messages["messages"]:
    m.pretty_print()


We have seen how an agent evolves from a simple 'If-Then' reflex into a sophisticated system capable of maintaining state and verifying its own goals. By moving from LangChain’s linear chains to LangGraph’s cyclic workflows, we’ve bridged the gap between basic automation and autonomous reasoning. However, the true power of agents lies in their ability to optimize for complex preferences and learn from their mistakes. Because Utility-Based Agents and Learning Agents involve more intricate scoring functions and feedback loops, we will dedicate our next entire article to mastering those advanced architectures.

Data Types large language model Agent-based model

Published at DZone with permission of Harshita Asnani. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • The LLM Selection War Story: Part 4 - Your Production Failure Testing Suite
  • Intelligent Load Management for LLM Calls: From Static Rate Limits to Priority-Aware "Agent QoS"
  • Stop Writing Excel Specs: A Markdown-First Approach to Enterprise Java
  • Anthropic’s Model Context Protocol (MCP): A Developer’s Guide to Long-Context LLM Integration

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook