{ "cells": [ { "cell_type": "markdown", "id": "e6da721a-f83d-4c14-ac97-517d3ac8ea6f", "metadata": {}, "source": [ "# Build a Customer Support Bot\n", "\n", "Customer support bots can free up teams' time by handling routine issues, but it can be hard to build a bot that reliably handles diverse tasks in a way that doesn't leave the user pulling their hair out.\n", "\n", "In this tutorial, you will build a customer support bot for an airline to help users research and make travel arrangements. You'll learn to use LangGraph's interrupts and checkpointers and more complex state to organize your assistant's tools and manage a user's flight bookings, hotel reservations, car rentals, and excursions. It assumes you are familiar with the concepts presented in the [LangGraph introductory tutorial](https://langchain-ai.github.io/langgraph/tutorials/introduction/).\n", "\n", "By the end, you'll have built a working bot and gained an understanding of LangGraph's key concepts and architectures. You'll be able to apply these design patterns to your other AI projects.\n", "\n", "Your final chat bot will look something like the following diagram:\n", "\n", "\n", "\n", "Let's start!\n", "\n", "## Prerequisites\n", "\n", "First, set up your environment. We'll install this tutorial's prerequisites, download the test DB, and define the tools we will reuse in each section.\n", "\n", "We'll be using Claude as our LLM and define a number of custom tools. While most of our tools will connect to a local sqlite database (and require no additional dependencies), we will also provide a general web search to the agent using Tavily." ] }, { "cell_type": "code", "execution_count": null, "id": "afc570bf-e129-415b-8f2d-8bbce08131ab", "metadata": {}, "outputs": [], "source": [ "%%capture --no-stderr\n", "% pip install -U langgraph langchain-community langchain-anthropic tavily-python pandas" ] }, { "cell_type": "code", "execution_count": 1, "id": "358e5666-b7c5-4e46-90a1-7ea273d86ee3", "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\")\n", "_set_env(\"TAVILY_API_KEY\")\n", "\n", "# Recommended\n", "_set_env(\"LANGCHAIN_API_KEY\")\n", "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", "os.environ[\"LANGCHAIN_PROJECT\"] = \"Customer Support Bot Tutorial\"" ] }, { "cell_type": "markdown", "id": "58121817-b31e-496d-9e46-2bec02c63300", "metadata": {}, "source": [ "#### Populate the database\n", "\n", "Run the next script to fetch a `sqlite` DB we've prepared for this tutorial and update it to look like it's current. The details are unimportant." ] }, { "cell_type": "code", "execution_count": 2, "id": "71638c2a-5038-439e-907a-de2bb548db34", "metadata": {}, "outputs": [], "source": [ "import os\n", "import shutil\n", "import sqlite3\n", "\n", "import pandas as pd\n", "import requests\n", "\n", "db_url = \"https://storage.googleapis.com/benchmarks-artifacts/travel-db/travel2.sqlite\"\n", "local_file = \"travel2.sqlite\"\n", "# The backup lets us restart for each tutorial section\n", "backup_file = \"travel2.backup.sqlite\"\n", "overwrite = False\n", "if overwrite or not os.path.exists(local_file):\n", " response = requests.get(db_url)\n", " response.raise_for_status() # Ensure the request was successful\n", " with open(local_file, \"wb\") as f:\n", " f.write(response.content)\n", " # Backup - we will use this to \"reset\" our DB in each section\n", " shutil.copy(local_file, backup_file)\n", "# Convert the flights to present time for our tutorial\n", "def update_dates(file):\n", " shutil.copy(backup_file, file)\n", " conn = sqlite3.connect(file)\n", " cursor = conn.cursor()\n", "\n", " tables = pd.read_sql(\n", " \"SELECT name FROM sqlite_master WHERE type='table';\", conn\n", " ).name.tolist()\n", " tdf = {}\n", " for t in tables:\n", " tdf[t] = pd.read_sql(f\"SELECT * from {t}\", conn)\n", "\n", " example_time = pd.to_datetime(\n", " tdf[\"flights\"][\"actual_departure\"].replace(\"\\\\N\", pd.NaT)\n", " ).max()\n", " current_time = pd.to_datetime(\"now\").tz_localize(example_time.tz)\n", " time_diff = current_time - example_time\n", "\n", " tdf[\"bookings\"][\"book_date\"] = (\n", " pd.to_datetime(tdf[\"bookings\"][\"book_date\"].replace(\"\\\\N\", pd.NaT), utc=True)\n", " + time_diff\n", " )\n", "\n", " datetime_columns = [\n", " \"scheduled_departure\",\n", " \"scheduled_arrival\",\n", " \"actual_departure\",\n", " \"actual_arrival\",\n", " ]\n", " for column in datetime_columns:\n", " tdf[\"flights\"][column] = (\n", " pd.to_datetime(tdf[\"flights\"][column].replace(\"\\\\N\", pd.NaT)) + time_diff\n", " )\n", "\n", " for table_name, df in tdf.items():\n", " df.to_sql(table_name, conn, if_exists=\"replace\", index=False)\n", " del df\n", " del tdf\n", " conn.commit()\n", " conn.close()\n", "\n", " return file\n", "\n", "db = update_dates(local_file)" ] }, { "cell_type": "markdown", "id": "ae3aa34e-923b-49a1-8f34-54a1b2a90825", "metadata": {}, "source": [ "## Tools\n", "\n", "Next, define our assistant's tools to search the airline's policy manual and search and manage reservations for flights, hotels, car rentals, and excursions. We will reuse these tools throughout the tutorial. The exact implementations\n", "aren't important, so feel free to run the code below and jump to [Part 1](#part-1-zero-shot).\n", "\n", "#### Lookup Company Policies\n", "\n", "The assistant retrieve policy information to answer user questions. Note that _enforcement_ of these policies still must be done within the tools/APIs themselves, since the LLM can always ignore this." ] }, { "cell_type": "code", "execution_count": 3, "id": "654e2f81", "metadata": {}, "outputs": [], "source": [ "import re\n", "\n", "import numpy as np\n", "import openai\n", "from langchain_core.tools import tool\n", "\n", "response = requests.get(\n", " \"https://storage.googleapis.com/benchmarks-artifacts/travel-db/swiss_faq.md\"\n", ")\n", "response.raise_for_status()\n", "faq_text = response.text\n", "\n", "docs = [{\"page_content\": txt} for txt in re.split(r\"(?=\\n##)\", faq_text)]\n", "\n", "\n", "class VectorStoreRetriever:\n", " def __init__(self, docs: list, vectors: list, oai_client):\n", " self._arr = np.array(vectors)\n", " self._docs = docs\n", " self._client = oai_client\n", "\n", " @classmethod\n", " def from_docs(cls, docs, oai_client):\n", " embeddings = oai_client.embeddings.create(\n", " model=\"text-embedding-3-small\", input=[doc[\"page_content\"] for doc in docs]\n", " )\n", " vectors = [emb.embedding for emb in embeddings.data]\n", " return cls(docs, vectors, oai_client)\n", "\n", " def query(self, query: str, k: int = 5) -> list[dict]:\n", " embed = self._client.embeddings.create(\n", " model=\"text-embedding-3-small\", input=[query]\n", " )\n", " # \"@\" is just a matrix multiplication in python\n", " scores = np.array(embed.data[0].embedding) @ self._arr.T\n", " top_k_idx = np.argpartition(scores, -k)[-k:]\n", " top_k_idx_sorted = top_k_idx[np.argsort(-scores[top_k_idx])]\n", " return [\n", " {**self._docs[idx], \"similarity\": scores[idx]} for idx in top_k_idx_sorted\n", " ]\n", "\n", "\n", "retriever = VectorStoreRetriever.from_docs(docs, openai.Client())\n", "\n", "\n", "@tool\n", "def lookup_policy(query: str) -> str:\n", " \"\"\"Consult the company policies to check whether certain options are permitted.\n", " Use this before making any flight changes performing other 'write' events.\"\"\"\n", " docs = retriever.query(query, k=2)\n", " return \"\\n\\n\".join([doc[\"page_content\"] for doc in docs])" ] }, { "cell_type": "markdown", "id": "f3556949", "metadata": {}, "source": [ "#### Flights\n", "\n", "Define the (`fetch_user_flight_information`) tool to let the agent see the current user's flight information. Then define tools to search for flights and manage the passenger's bookings stored in the SQL database.\n", "\n", "We the can [access the RunnableConfig](https://python.langchain.com/v0.2/docs/how_to/tool_configure/#inferring-by-parameter-type) for a given run to check the `passenger_id` of the user accessing this application. The LLM never has to provide these explicitly, they are provided for a given invocation of the graph so that each user cannot access other passengers' booking information.\n", "\n", "
Compatibility
\n", "\n", " This tutorial expects `langchain-core>=0.2.16` to use the injected RunnableConfig. Prior to that, you'd use `ensure_config` to collect the config from context.\n", "
\n", "