{ "cells": [ { "cell_type": "markdown", "id": "4a1aae78-88a6-4133-b905-7e46c8e3772f", "metadata": {}, "source": [ "# Quick Start\n", "\n", "In this comprehensive quick start, we will build a support chatbot in LangGraph that can:\n", "\n", "- Answer common questions by searching the web\n", "- Maintain conversation state across calls\n", "- Route complex queries to a human for review\n", "- Use custom state to control its behavior\n", "- Rewind and explore alternative conversation paths\n", "\n", "We'll start with a basic chatbot and progressively add more sophisticated capabilities, introducing key LangGraph concepts along the way.\n", "\n", "## Setup\n", "\n", "First, install the required packages:" ] }, { "cell_type": "code", "execution_count": null, "id": "6f11d631-8679-4f28-822f-cdf1f2ddc21c", "metadata": {}, "outputs": [], "source": [ "%%capture --no-stderr\n", "%pip install -U langgraph langsmith\n", "\n", "# Used for this tutorial; not a requirement for LangGraph\n", "%pip install -U langchain_anthropic" ] }, { "cell_type": "markdown", "id": "a6d1e870-1bc0-4d44-86c0-96681ccf6113", "metadata": {}, "source": [ "Next, set your API keys:" ] }, { "cell_type": "code", "execution_count": 1, "id": "705d4020-6ee8-44cc-b1a5-8c34e7172fc7", "metadata": {}, "outputs": [], "source": [ "import getpass\n", "import os\n", "\n", "\n", "def _set_env(var: str):\n", " if not os.environ.get(var):\n", " os.environ[var] = getpass.getpass(f\"{var}: \")\n", "\n", "\n", "_set_env(\"ANTHROPIC_API_KEY\")" ] }, { "cell_type": "markdown", "id": "a98c72cf-33f9-4a37-9634-6c93a7c28815", "metadata": {}, "source": [ "(Encouraged) [LangSmith](https://smith.langchain.com/) makes it a lot easier to see what's going on \"under the hood.\"" ] }, { "cell_type": "code", "execution_count": 2, "id": "13cba9af-0572-41df-92f8-d6f56d5b5322", "metadata": {}, "outputs": [], "source": [ "_set_env(\"LANGSMITH_API_KEY\")\n", "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", "os.environ[\"LANGCHAIN_PROJECT\"] = \"LangGraph Tutorial\"" ] }, { "attachments": {}, "cell_type": "markdown", "id": "9c374e41-f9b7-439e-a520-6d8c853c5220", "metadata": {}, "source": [ "## Part 1: Build a Basic Chatbot\n", "\n", "We'll first create a simple chatbot using LangGraph. This chatbot will respond directly to user messages. Though simple, it will illustrate the core concepts of building with LangGraph. By the end of this section, you will have a built rudimentary chatbot.\n", "\n", "Start by creating a `StateGraph`. A `StateGraph` object defines the structure of our chatbot as a \"state machine\". We'll add `nodes` to represent the llm and functions our chatbot can call and `edges` to specify how the bot should transition between these functions." ] }, { "cell_type": "code", "execution_count": 3, "id": "e58df974-7579-4f25-9d91-66389b94eba2", "metadata": {}, "outputs": [], "source": [ "from typing import Annotated\n", "\n", "from typing_extensions import TypedDict\n", "\n", "from langgraph.graph import StateGraph, START, END\n", "from langgraph.graph.message import add_messages\n", "\n", "\n", "class State(TypedDict):\n", " # Messages have the type \"list\". The `add_messages` function\n", " # in the annotation defines how this state key should be updated\n", " # (in this case, it appends messages to the list, rather than overwriting them)\n", " messages: Annotated[list, add_messages]\n", "\n", "\n", "graph_builder = StateGraph(State)" ] }, { "cell_type": "markdown", "id": "31c755cd-8994-4867-bdff-96a55d7beae7", "metadata": {}, "source": [ "
Note
\n", "\n",
" The first thing you do when you define a graph is define the State of the graph. The State consists of the schema of the graph as well as reducer functions which specify how to apply updates to the state. In our example State is a TypedDict with a single key: messages. The messages key is annotated with the add_messages reducer function, which tells LangGraph to append new messages to the existing list, rather than overwriting it. State keys without an annotation will be overwritten by each update, storing the most recent value. Check out this conceptual guide to learn more about state, reducers and other low-level concepts.\n",
"
\n",
" \n",
"```python\n",
"from typing import Annotated\n",
"\n",
"from langchain_anthropic import ChatAnthropic\n",
"from typing_extensions import TypedDict\n",
"\n",
"from langgraph.graph import StateGraph\n",
"from langgraph.graph.message import add_messages\n",
"\n",
"\n",
"class State(TypedDict):\n",
" messages: Annotated[list, add_messages]\n",
"\n",
"\n",
"graph_builder = StateGraph(State)\n",
"\n",
"\n",
"llm = ChatAnthropic(model=\"claude-3-haiku-20240307\")\n",
"\n",
"\n",
"def chatbot(state: State):\n",
" return {\"messages\": [llm.invoke(state[\"messages\"])]}\n",
"\n",
"\n",
"# The first argument is the unique node name\n",
"# The second argument is the function or object that will be called whenever\n",
"# the node is used.\n",
"graph_builder.add_node(\"chatbot\", chatbot)\n",
"graph_builder.set_entry_point(\"chatbot\")\n",
"graph_builder.set_finish_point(\"chatbot\")\n",
"graph = graph_builder.compile()\n",
"```\n",
"\n",
"\n",
"\n",
"\n",
"```python\n",
"from typing import Annotated\n",
"\n",
"from langchain_anthropic import ChatAnthropic\n",
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
"from langchain_core.messages import BaseMessage\n",
"from typing_extensions import TypedDict\n",
"\n",
"from langgraph.graph import StateGraph\n",
"from langgraph.graph.message import add_messages\n",
"from langgraph.prebuilt import ToolNode, tools_condition\n",
"\n",
"\n",
"class State(TypedDict):\n",
" messages: Annotated[list, add_messages]\n",
"\n",
"\n",
"graph_builder = StateGraph(State)\n",
"\n",
"\n",
"tool = TavilySearchResults(max_results=2)\n",
"tools = [tool]\n",
"llm = ChatAnthropic(model=\"claude-3-haiku-20240307\")\n",
"llm_with_tools = llm.bind_tools(tools)\n",
"\n",
"\n",
"def chatbot(state: State):\n",
" return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
"\n",
"\n",
"graph_builder.add_node(\"chatbot\", chatbot)\n",
"\n",
"tool_node = ToolNode(tools=[tool])\n",
"graph_builder.add_node(\"tools\", tool_node)\n",
"\n",
"graph_builder.add_conditional_edges(\n",
" \"chatbot\",\n",
" tools_condition,\n",
")\n",
"# Any time a tool is called, we return to the chatbot to decide the next step\n",
"graph_builder.add_edge(\"tools\", \"chatbot\")\n",
"graph_builder.set_entry_point(\"chatbot\")\n",
"graph = graph_builder.compile()\n",
"```\n",
"\n",
"\n",
"\n",
"\n",
"```python\n",
"from typing import Annotated\n",
"\n",
"from langchain_anthropic import ChatAnthropic\n",
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
"from langchain_core.messages import BaseMessage\n",
"from typing_extensions import TypedDict\n",
"\n",
"from langgraph.checkpoint.memory import MemorySaver\n",
"from langgraph.graph import StateGraph\n",
"from langgraph.graph.message import add_messages\n",
"from langgraph.prebuilt import ToolNode\n",
"\n",
"\n",
"class State(TypedDict):\n",
" messages: Annotated[list, add_messages]\n",
"\n",
"\n",
"graph_builder = StateGraph(State)\n",
"\n",
"\n",
"tool = TavilySearchResults(max_results=2)\n",
"tools = [tool]\n",
"llm = ChatAnthropic(model=\"claude-3-haiku-20240307\")\n",
"llm_with_tools = llm.bind_tools(tools)\n",
"\n",
"\n",
"def chatbot(state: State):\n",
" return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
"\n",
"\n",
"graph_builder.add_node(\"chatbot\", chatbot)\n",
"\n",
"tool_node = ToolNode(tools=[tool])\n",
"graph_builder.add_node(\"tools\", tool_node)\n",
"\n",
"graph_builder.add_conditional_edges(\n",
" \"chatbot\",\n",
" tools_condition,\n",
")\n",
"graph_builder.add_edge(\"tools\", \"chatbot\")\n",
"graph_builder.set_entry_point(\"chatbot\")\n",
"graph = graph_builder.compile(checkpointer=memory)\n",
"```\n",
"\n",
"\n",
"\n",
"\n",
"```python\n",
"from typing import Annotated\n",
"\n",
"from langchain_anthropic import ChatAnthropic\n",
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
"from langchain_core.messages import BaseMessage\n",
"from typing_extensions import TypedDict\n",
"\n",
"from langgraph.checkpoint.memory import MemorySaver\n",
"from langgraph.graph import StateGraph\n",
"from langgraph.graph.message import add_messages\n",
"from langgraph.prebuilt import ToolNode\n",
"\n",
"\n",
"class State(TypedDict):\n",
" messages: Annotated[list, add_messages]\n",
"\n",
"\n",
"graph_builder = StateGraph(State)\n",
"\n",
"\n",
"tool = TavilySearchResults(max_results=2)\n",
"tools = [tool]\n",
"llm = ChatAnthropic(model=\"claude-3-haiku-20240307\")\n",
"llm_with_tools = llm.bind_tools(tools)\n",
"\n",
"\n",
"def chatbot(state: State):\n",
" return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
"\n",
"\n",
"graph_builder.add_node(\"chatbot\", chatbot)\n",
"\n",
"tool_node = ToolNode(tools=[tool])\n",
"graph_builder.add_node(\"tools\", tool_node)\n",
"\n",
"graph_builder.add_conditional_edges(\n",
" \"chatbot\",\n",
" tools_condition,\n",
")\n",
"graph_builder.add_edge(\"tools\", \"chatbot\")\n",
"graph_builder.set_entry_point(\"chatbot\")\n",
"\n",
"memory = MemorySaver()\n",
"graph = graph_builder.compile(\n",
" checkpointer=memory,\n",
" # This is new!\n",
" interrupt_before=[\"tools\"],\n",
" # Note: can also interrupt __after__ actions, if desired.\n",
" # interrupt_after=[\"tools\"]\n",
")\n",
"```\n",
"\n",
"\n",
"\n",
"```python\n",
"from typing import Annotated\n",
"\n",
"from langchain_anthropic import ChatAnthropic\n",
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
"from langchain_core.messages import BaseMessage\n",
"from langchain_core.pydantic_v1 import BaseModel\n",
"from typing_extensions import TypedDict\n",
"\n",
"from langgraph.checkpoint.memory import MemorySaver\n",
"from langgraph.graph import StateGraph\n",
"from langgraph.graph.message import add_messages\n",
"from langgraph.prebuilt import ToolNode, tools_condition\n",
"\n",
"\n",
"class State(TypedDict):\n",
" messages: Annotated[list, add_messages]\n",
" # This flag is new\n",
" ask_human: bool\n",
"\n",
"\n",
"class RequestAssistance(BaseModel):\n",
" \"\"\"Escalate the conversation to an expert. Use this if you are unable to assist directly or if the user requires support beyond your permissions.\n",
"\n",
" To use this function, relay the user's 'request' so the expert can provide the right guidance.\n",
" \"\"\"\n",
"\n",
" request: str\n",
"\n",
"\n",
"tool = TavilySearchResults(max_results=2)\n",
"tools = [tool]\n",
"llm = ChatAnthropic(model=\"claude-3-haiku-20240307\")\n",
"# We can bind the llm to a tool definition, a pydantic model, or a json schema\n",
"llm_with_tools = llm.bind_tools(tools + [RequestAssistance])\n",
"\n",
"\n",
"def chatbot(state: State):\n",
" response = llm_with_tools.invoke(state[\"messages\"])\n",
" ask_human = False\n",
" if (\n",
" response.tool_calls\n",
" and response.tool_calls[0][\"name\"] == RequestAssistance.__name__\n",
" ):\n",
" ask_human = True\n",
" return {\"messages\": [response], \"ask_human\": ask_human}\n",
"\n",
"\n",
"graph_builder = StateGraph(State)\n",
"\n",
"graph_builder.add_node(\"chatbot\", chatbot)\n",
"graph_builder.add_node(\"tools\", ToolNode(tools=[tool]))\n",
"\n",
"\n",
"def create_response(response: str, ai_message: AIMessage):\n",
" return ToolMessage(\n",
" content=response,\n",
" tool_call_id=ai_message.tool_calls[0][\"id\"],\n",
" )\n",
"\n",
"\n",
"def human_node(state: State):\n",
" new_messages = []\n",
" if not isinstance(state[\"messages\"][-1], ToolMessage):\n",
" # Typically, the user will have updated the state during the interrupt.\n",
" # If they choose not to, we will include a placeholder ToolMessage to\n",
" # let the LLM continue.\n",
" new_messages.append(\n",
" create_response(\"No response from human.\", state[\"messages\"][-1])\n",
" )\n",
" return {\n",
" # Append the new messages\n",
" \"messages\": new_messages,\n",
" # Unset the flag\n",
" \"ask_human\": False,\n",
" }\n",
"\n",
"\n",
"graph_builder.add_node(\"human\", human_node)\n",
"\n",
"\n",
"def select_next_node(state: State):\n",
" if state[\"ask_human\"]:\n",
" return \"human\"\n",
" # Otherwise, we can route as before\n",
" return tools_condition(state)\n",
"\n",
"\n",
"graph_builder.add_conditional_edges(\n",
" \"chatbot\",\n",
" select_next_node,\n",
" {\"human\": \"human\", \"tools\": \"tools\", \"__end__\": \"__end__\"},\n",
")\n",
"graph_builder.add_edge(\"tools\", \"chatbot\")\n",
"graph_builder.add_edge(\"human\", \"chatbot\")\n",
"graph_builder.set_entry_point(\"chatbot\")\n",
"memory = MemorySaver()\n",
"graph = graph_builder.compile(\n",
" checkpointer=memory,\n",
" interrupt_before=[\"human\"],\n",
")\n",
"```\n",
"\n",
"