of highly effective AI fashions, corresponding to GPT-5 and Gemini 2.5 Professional, we additionally see a rise in agentic frameworks to make the most of these fashions. These frameworks make working with AI fashions easier by abstracting away quite a lot of challenges, corresponding to tool-calling, agentic state dealing with, and human-in-the-loop setups.
Thus, on this article, I’ll dive deeper into LangGraph, one of many out there agentic AI frameworks. I’ll put it to use to develop a easy agentic utility, with a number of steps highlighting the advantages of agentic AI packages. I’ll additionally cowl the professionals and cons of utilizing LangGraph and different related agentic frameworks.
I’m not sponsored in any method by LangGraph to create this text. I merely selected the framework because it is likely one of the most prevalent ones on the market. There are numerous different choices on the market, corresponding to:
- LangChain
- LlamaIndex
- CrewAI
Why do you want an agentic framework?
There are quite a few packages on the market which can be purported to make programming purposes simpler. In quite a lot of instances, these packages have the precise reverse impact, as a result of they obscure the code, don’t work properly in manufacturing, and typically make it tougher to debug.
Nonetheless, you’ll want to discover the packages that simplify your utility by abstracting away boilerplate code. This precept is usually highlighted within the startup world with a quote just like the one under:
Give attention to fixing the precise downside you’re attempting to resolve. All different (beforehand solved issues) ought to be outsourced to different purposes
An agentic framework is required as a result of it abstracts away quite a lot of issues you don’t want to take care of:
- Sustaining state. Not simply message historical past, however all different info you collect, for instance, when performing RAG
- Software utilization. You don’t wish to arrange your personal logic for executing instruments. Relatively, you need to merely outline them and let the agentic framework deal with how you can invoke the instruments. (That is particularly related for parallel and async instrument calling)
Thus, utilizing an agentic framework abstracts away quite a lot of issues, so you possibly can concentrate on the core a part of your product.
Fundamentals of LangGraph
To get began implementing LangGraph, I start by studying the docs, overlaying:
- Fundamental chatbot implementation
- Software utilization
- Sustaining and updating the state
LangGraph is, as its identify suggests, primarily based on constructing graphs and executing this graph per request. In a graph, you possibly can outline:
- The state (the present info saved in reminiscence)
- Nodes. Sometimes, an LLM or a instrument name, for instance, classifying consumer intent, or answering the consumer’s query
- Edges. Conditional logic determines which node to go to subsequent.
All of which stems from primary graph concept.
Implementing a workflow

I consider probably the greatest methods of studying is to easily strive issues out for your self. Thus, I’ll implement a easy workflow in LangGraph. You possibly can study constructing these workflows within the workflow docs, which relies on Anthropic’s Constructing efficient brokers weblog (certainly one of my favourite weblog posts about brokers, which I’ve lined in a number of of my earlier articles. I extremely suggest studying it.
I’ll make a easy workflow to outline an utility the place a consumer can:
- Create paperwork with textual content
- Delete paperwork
- Search in paperwork
To do that, I’ll create the next workflow:
- Detect consumer intent. Do they wish to create a doc, delete a doc, or search in a doc?
- Given the end result of step 1, I’ll have totally different flows to deal with every of them.
You can additionally do that by merely defining all of the instruments and giving the agent entry to create/delete/search a doc. Nonetheless, if you wish to do extra actions relying on intent, doing an intent classification routing step first is the way in which to go.
Loading imports and LLM
First, I’ll load the required imports and the LLM I’m utilizing. I’ll be utilizing AWS Bedrock, although you should use different suppliers, as you possibly can see from step 3 in this tutorial.
"""
Make a doc handler workflow the place a consumer can
create a brand new doc to the database (at present only a dictionary)
delete a doc from the database
ask a query a few doc
"""
from typing_extensions import TypedDict, Literal
from langgraph.checkpoint.reminiscence import InMemorySaver
from langgraph.graph import StateGraph, START, END
from langgraph.sorts import Command, interrupt
from langchain_aws import ChatBedrockConverse
from langchain_core.messages import HumanMessage, SystemMessage
from pydantic import BaseModel, Discipline
from IPython.show import show, Picture
from dotenv import load_dotenv
import os
load_dotenv()
aws_access_key_id = os.getenv("AWS_ACCESS_KEY_ID") or ""
aws_secret_access_key = os.getenv("AWS_SECRET_ACCESS_KEY") or ""
os.environ["AWS_ACCESS_KEY_ID"] = aws_access_key_id
os.environ["AWS_SECRET_ACCESS_KEY"] = aws_secret_access_key
llm = ChatBedrockConverse(
model_id="us.anthropic.claude-3-5-haiku-20241022-v1:0", # that is the mannequin id (added us. earlier than id in platform)
region_name="us-east-1",
aws_access_key_id=aws_access_key_id,
aws_secret_access_key=aws_secret_access_key,
)
document_database: dict[str, str] = {} # a dictionary with key: filename, worth: textual content in doc
I additionally outlined the database as a dictionary of information. In manufacturing, you’ll naturally use a correct database; nevertheless, I simplify it for this tutorial
Defining the graph
Subsequent, it’s time to outline the graph. I first create the Router object, which can classify the consumer’s immediate into certainly one of three intents:
- add_document
- delete_document
- ask_document
# Outline state
class State(TypedDict):
enter: str
determination: str | None
output: str | None
# Schema for structured output to make use of as routing logic
class Route(BaseModel):
step: Literal["add_document", "delete_document", "ask_document"] = Discipline(
description="The subsequent step within the routing course of"
)
# Increase the LLM with schema for structured output
router = llm.with_structured_output(Route)
def llm_call_router(state: State):
"""Route the consumer enter to the suitable node"""
# Run the augmented LLM with structured output to function routing logic
determination = router.invoke(
[
SystemMessage(
content="""Route the user input to one of the following 3 intents:
- 'add_document'
- 'delete_document'
- 'ask_document'
You only need to return the intent, not any other text.
"""
),
HumanMessage(content=state["input"]),
]
)
return {"determination": determination.step}
# Conditional edge perform to path to the suitable node
def route_decision(state: State):
# Return the node identify you wish to go to subsequent
if state["decision"] == "add_document":
return "add_document_to_database_tool"
elif state["decision"] == "delete_document":
return "delete_document_from_database_tool"
elif state["decision"] == "ask_document":
return "ask_document_tool"
I outline the state the place we retailer the consumer enter, the router’s determination (one of many three intents), after which guarantee structured output from the LLM. The structured output ensures the mannequin responds with one of many three intents.
Persevering with, I’ll outline the instruments we’re utilizing on this article, one for every of the intents.
# Nodes
def add_document_to_database_tool(state: State):
"""Add a doc to the database. Given consumer question, extract the filename and content material for the doc. If not supplied, is not going to add the doc to the database."""
user_query = state["input"]
# extract filename and content material from consumer question
filename_prompt = f"Given the next consumer question, extract the filename for the doc: {user_query}. Solely return the filename, not some other textual content."
output = llm.invoke(filename_prompt)
filename = output.content material
content_prompt = f"Given the next consumer question, extract the content material for the doc: {user_query}. Solely return the content material, not some other textual content."
output = llm.invoke(content_prompt)
content material = output.content material
# add doc to database
document_database[filename] = content material
return {"output": f"Doc {filename} added to database"}
def delete_document_from_database_tool(state: State):
"""Delete a doc from the database. Given consumer question, extract the filename of the doc to delete. If not supplied, is not going to delete the doc from the database."""
user_query = state["input"]
# extract filename from consumer question
filename_prompt = f"Given the next consumer question, extract the filename of the doc to delete: {user_query}. Solely return the filename, not some other textual content."
output = llm.invoke(filename_prompt)
filename = output.content material
# delete doc from database if it exsits, if not retunr data about failure
if filename not in document_database:
return {"output": f"Doc {filename} not present in database"}
document_database.pop(filename)
return {"output": f"Doc {filename} deleted from database"}
def ask_document_tool(state: State):
"""Ask a query a few doc. Given consumer question, extract the filename and query for the doc. If not supplied, is not going to ask the query concerning the doc."""
user_query = state["input"]
# extract filename and query from consumer question
filename_prompt = f"Given the next consumer question, extract the filename of the doc to ask a query about: {user_query}. Solely return the filename, not some other textual content."
output = llm.invoke(filename_prompt)
filename = output.content material
question_prompt = f"Given the next consumer question, extract the query to ask concerning the doc: {user_query}. Solely return the query, not some other textual content."
output = llm.invoke(question_prompt)
query = output.content material
# ask query about doc
if filename not in document_database:
return {"output": f"Doc {filename} not present in database"}
consequence = llm.invoke(f"Doc: {document_database[filename]}nnQuestion: {query}")
return {"output": f"Doc question consequence: {consequence.content material}"}
And eventually, we construct the graph with nodes and edges:
# Construct workflow
router_builder = StateGraph(State)
# Add nodes
router_builder.add_node("add_document_to_database_tool", add_document_to_database_tool)
router_builder.add_node("delete_document_from_database_tool", delete_document_from_database_tool)
router_builder.add_node("ask_document_tool", ask_document_tool)
router_builder.add_node("llm_call_router", llm_call_router)
# Add edges to attach nodes
router_builder.add_edge(START, "llm_call_router")
router_builder.add_conditional_edges(
"llm_call_router",
route_decision,
{ # Title returned by route_decision : Title of subsequent node to go to
"add_document_to_database_tool": "add_document_to_database_tool",
"delete_document_from_database_tool": "delete_document_from_database_tool",
"ask_document_tool": "ask_document_tool",
},
)
router_builder.add_edge("add_document_to_database_tool", END)
router_builder.add_edge("delete_document_from_database_tool", END)
router_builder.add_edge("ask_document_tool", END)
# Compile workflow
reminiscence = InMemorySaver()
router_workflow = router_builder.compile(checkpointer=reminiscence)
config = {"configurable": {"thread_id": "1"}}
# Present the workflow
show(Picture(router_workflow.get_graph().draw_mermaid_png()))
The final show perform ought to present the graph as you see under:

Now you possibly can check out the workflow by asking a query per intent.
Add a doc:
user_input = "Add the doc 'check.txt' with content material 'It is a check doc' to the database"
state = router_workflow.invoke({"enter": user_input}, config)
print(state["output"]
# -> Doc check.txt added to database
Search a doc:
user_input = "Give me a abstract of the doc 'check.txt'"
state = router_workflow.invoke({"enter": user_input}, config)
print(state["output"])
# -> A short, generic check doc with a easy descriptive sentence.
Delete a doc:
user_input = "Delete the doc 'check.txt' from the database"
state = router_workflow.invoke({"enter": user_input}, config)
print(state["output"])
# -> Doc check.txt deleted from database
Nice! You possibly can see the workflow is working with the totally different routing choices. Be at liberty so as to add extra intents or extra nodes per intent to create a extra advanced workflow.
Stronger agentic use instances
The distinction between agentic workflows and absolutely agentic purposes is usually complicated. Nonetheless, to separate the 2 phrases, I’ll use the quote under from Anthropic’s Building effective agents:
Workflows are programs the place LLMs and instruments are orchestrated by predefined code paths. Brokers, alternatively, are programs the place LLMs dynamically direct their very own processes and gear utilization, sustaining management over how they accomplish duties.
Most challenges you remedy with LLMs will use the workflow sample, as a result of most issues (from my expertise) are pre-defined, and may have a pre-determined set of guardrails to comply with. Within the instance above, when including/deleting/looking out paperwork, you need to completely arrange a pre-determined workflow by defining the intent classifier and what to do given every intent.
Nonetheless, typically, you additionally need extra autonomous agentic use instances. Think about, for instance, Cursor, the place they need a coding agent that may search by your code, verify the most recent documentation on-line, and modify your code. In these cases, it’s troublesome to create pre-determined workflows as a result of there are such a lot of totally different eventualities that may happen.
If you wish to create extra autonomous agentic programs, you possibly can learn extra about that here.
LangGraph execs and cons
Execs
My three most important positives about LangGraph are:
- Straightforward to arrange
- Open-source
- Simplifies your code
It was easy to arrange LangGraph and rapidly get it working. Particularly when following their documentation, or feeding their documentation to Cursor and prompting it to implement particular workflows.
Moreover, the code for LangGraph is open-source, which means you possibly can preserve operating the code, it doesn’t matter what occurs to the corporate behind it or adjustments they resolve to make. I believe that is essential if you wish to deploy it to manufacturing. Lastly, LangGraph additionally simplifies quite a lot of the code and abstracts away quite a lot of logic you’ll’ve needed to write in Python your self.
Cons
Nonetheless, there are additionally some downsides to LangGraph that I’ve seen throughout implementation.
- Nonetheless a shocking quantity of boilerplate code
- You’ll encounter LangGraph-specific errors
When implementing my very own customized workflow, I felt I nonetheless had so as to add quite a lot of boilerplate code. Although the quantity of code was undoubtedly lower than if I’d applied every little thing from scratch, I discovered myself shocked by the quantity of code I had so as to add to create a comparatively easy workflow. Nonetheless, I believe a part of that is that LangGraph makes an attempt to place itself as a lower-code instrument than, for instance, quite a lot of performance you discover in LangChain (which I believe is nice as a result of LangChain, in my view, abstracts away an excessive amount of, making it tougher to debug your code).
Moreover, as with many externally put in packages, you’ll encounter LangGraph-specific points when implementing the bundle. For instance, after I needed to preview the graph of the workflow I created, I acquired a difficulty regarding the draw_mermaid_png perform. Encountering such errors is inevitable when utilizing exterior packages, and it’ll at all times be a trade-off between the useful code abstractions a bundle offers you, versus the totally different sorts of bugs chances are you’ll face utilizing such packages.
Abstract
All in all, I discover LangGraph a useful bundle when coping with agentic programs. Establishing my desired workflow by first doing intent classification and continuing with totally different flows relying on intent was comparatively easy. Moreover, I believe LangGraph discovered a superb center floor between not abstracting away all logic (obscuring the code, making it tougher to debug) and really abstracting away challenges I don’t wish to take care of when creating my agentic system. There are each positives and negatives to implementing such agentic frameworks, and I believe one of the simplest ways to make this trade-off is by implementing easy workflows your self.
👉 Discover me on socials:
🧑💻 Get in touch
✍️ Medium
If you wish to be taught extra about agentic workflows, you possibly can learn my article on Building Effective AI Agents To Process Millions of Documents. You too can be taught extra about LLMs in my article on LLM Validation.

