How to create an AI agent with LangChain and LangGraph

Build a tool-using AI agent using LangChain and LangGraph in Python and TypeScript

AI agents are programs that use a large language model (LLM) to decide which actions to take, invoke tools, and iterate until a task is complete. LangChain provides the tool-calling primitives and model integrations, while LangGraph gives you a graph-based framework for orchestrating multi-step agent loops with state management.

This guide walks through creating a simple agent that can search the web and do math, first in Python and then in TypeScript.

What is an Agent?

At its core, an agent is a loop:

  1. The LLM receives a prompt (including conversation history and available tools).
  2. It decides whether to respond directly or call a tool.
  3. If it calls a tool, the tool executes and the result is fed back to the LLM.
  4. This repeats until the LLM produces a final answer.

LangGraph models this as a state graph where each node is a step (call the model, execute a tool) and edges control the flow.

Python Example

Install dependencies

pip install langchain langgraph langchain-openai

Define tools

We’ll create two simple tools: one for adding numbers and one that simulates a web search.

from langchain_core.tools import tool


@tool
def add_numbers(a: float, b: float) -> float:
    """Add two numbers together."""
    return a + b


@tool
def search_web(query: str) -> str:
    """Search the web for information. Returns a summary of top results."""
    # In production, integrate a real search API (e.g., Tavily, SerpAPI)
    return f"Search results for '{query}': Example result content..."

Build the agent graph

from typing import Annotated
from typing_extensions import TypedDict

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition


# 1. Define the state
class State(TypedDict):
    messages: Annotated[list, add_messages]


# 2. Set up the model and bind tools
tools = [add_numbers, search_web]

model = ChatOpenAI(model="gpt-4o", temperature=0)
model_with_tools = model.bind_tools(tools)

SYSTEM_PROMPT = SystemMessage(
    content=(
        "You are a helpful assistant. "
        "Use the tools available to you when you need to look something up "
        "or perform calculations. Always cite your sources."
    )
)


# 3. Define the model-calling node
def call_model(state: State) -> dict:
    messages = [SYSTEM_PROMPT] + state["messages"]
    response = model_with_tools.invoke(messages)
    return {"messages": [response]}


# 4. Build the graph
graph = StateGraph(State)

graph.add_node("agent", call_model)
graph.add_node("tools", ToolNode(tools))

graph.add_edge(START, "agent")
graph.add_conditional_edges("agent", tools_condition, {"tools": "tools", END: END})
graph.add_edge("tools", "agent")

app = graph.compile()

Run the agent

result = app.invoke({
    "messages": [HumanMessage(content="What is 2,345 + 6,789? Also search for the population of Tokyo.")]
})

for msg in result["messages"]:
    print(f"{msg.__class__.__name__}: {msg.content}")

Expected output

HumanMessage: What is 2,345 + 6,789? Also search for the population of Tokyo.
AIMessage:  (tool calls: add_numbers, search_web)
ToolMessage: 9134
ToolMessage: Search results for 'population of Tokyo': ...
AIMessage: The sum of 2,345 and 6,789 is **9,134**. According to my search, the population of Tokyo is approximately 13.96 million.

TypeScript Example

Install dependencies

npm install @langchain/core @langchain/openai @langchain/langgraph

Define tools

import { tool } from "@langchain/core/tools";
import { z } from "zod";

const addNumbers = tool(
  async ({ a, b }: { a: number; b: number }): Promise<number> => {
    return a + b;
  },
  {
    name: "add_numbers",
    description: "Add two numbers together.",
    schema: z.object({
      a: z.number().describe("First number"),
      b: z.number().describe("Second number"),
    }),
  }
);

const searchWeb = tool(
  async ({ query }: { query: string }): Promise<string> => {
    // In production, integrate a real search API
    return `Search results for '${query}': Example result content...`;
  },
  {
    name: "search_web",
    description: "Search the web for information. Returns a summary of top results.",
    schema: z.object({
      query: z.string().describe("The search query"),
    }),
  }
);

const tools = [addNumbers, searchWeb];

Build the agent graph

import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
import {
  StateGraph,
  START,
  END,
  Annotation,
  messagesStateReducer,
} from "@langchain/langgraph";
import { ToolNode } from "@langchain/langgraph/prebuilt";

// 1. Define the state
const State = Annotation.Root({
  messages: Annotation({
    reducer: messagesStateReducer,
  }),
});

// 2. Set up the model and bind tools
const model = new ChatOpenAI({ model: "gpt-4o", temperature: 0 });
const modelWithTools = model.bindTools(tools);

const SYSTEM_PROMPT = new SystemMessage(
  "You are a helpful assistant. " +
    "Use the tools available to you when you need to look something up " +
    "or perform calculations. Always cite your sources."
);

// 3. Define the model-calling node
async function callModel(state: typeof State.State) {
  const messages = [SYSTEM_PROMPT, ...state.messages];
  const response = await modelWithTools.invoke(messages);
  return { messages: [response] };
}

// 4. Build the graph
const toolNode = new ToolNode(tools);

const graph = new StateGraph(State)
  .addNode("agent", callModel)
  .addNode("tools", toolNode)
  .addEdge(START, "agent")
  .addConditionalEdges("agent", async (state) => {
    const lastMessage = state.messages[state.messages.length - 1];
    if (
      "tool_calls" in lastMessage &&
      Array.isArray(lastMessage.tool_calls) &&
      lastMessage.tool_calls.length > 0
    ) {
      return "tools";
    }
    return END;
  })
  .addEdge("tools", "agent");

const app = graph.compile();

Run the agent

const result = await app.invoke({
  messages: [
    new HumanMessage("What is 2,345 + 6,789? Also search for the population of Tokyo."),
  ],
});

for (const msg of result.messages) {
  console.log(`${msg._getType()}: ${msg.content}`);
}

Key Concepts

Concept Description
State A typed dictionary that flows through the graph. Typically holds the message list.
Nodes Functions that read and update state. call_model invokes the LLM; ToolNode executes tool calls.
Conditional edges Routes based on the LLM’s output. If the model calls a tool, we route to the tool node; otherwise, we finish.
Tool binding model.bind_tools(tools) tells the LLM which tools are available and their schemas.
tools_condition A built-in conditional that checks if the last AIMessage contains tool calls.

Tips

Further Reading