diff --git a/README.md b/README.md
index 1f960a4..be97d51 100644
--- a/README.md
+++ b/README.md
@@ -103,6 +103,10 @@ table.wrap80 col.desc { width: 80ch; }
Demonstrates how to use the MySQL DISTANCE function to compute similarity between image embeddings |
MySQL AI |
+
+ | Travel agent that can search hotels, show room options, and make reservations using LangChain Agent, MySQL AI Database, Oracle MCP Server, and OCI LLMs. |
+ MySQL AI |
+
## SQL examples
diff --git a/python/mysqlai/travel_agent.ipynb b/python/mysqlai/travel_agent.ipynb
new file mode 100644
index 0000000..7ee9952
--- /dev/null
+++ b/python/mysqlai/travel_agent.ipynb
@@ -0,0 +1,577 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Travel Agent Jupyter Notebook\n",
+ "\n",
+ "This notebook implements a travel agent AI using **LangChain Agent**, **Oracle MCP Server**, **MySQL AI** database, and **OCI LLMs** for hotel bookings, enabling seamless travel planning through natural conversation. \n",
+ "\n",
+ "Example use cases are:\n",
+ "- Search for hotels \n",
+ "- Display room options \n",
+ "- Make reservations \n",
+ "\n",
+ "This notebook requires **Python version >=3.12**, with required packages listed below:
\n",
+ "**langchain langgraph langchain-core langchain_oci langchain_mcp_adapters nest_asyncio oci fastapi fastmcp uvicorn sshtunnel**\n",
+ "\n",
+ "The notebook consists of the following parts:
\n",
+ "\n",
+ "\n",
+ " - Setup\n",
+ "
\n",
+ " - Imports
\n",
+ " - Resource Configuration (you need to provide necessary information here)
\n",
+ " - Global Rules
\n",
+ "
\n",
+ " \n",
+ " - Utility Functions
\n",
+ " - Core Function
\n",
+ " - Examples
\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Imports"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import asyncio\n",
+ "import uuid\n",
+ "import time\n",
+ "import subprocess\n",
+ "import os\n",
+ "import time\n",
+ "\n",
+ "from langchain_mcp_adapters.client import MultiServerMCPClient\n",
+ "from langgraph.prebuilt import create_react_agent\n",
+ "from langgraph.checkpoint.memory import MemorySaver\n",
+ "from langgraph.errors import GraphRecursionError\n",
+ "from langchain_oci import ChatOCIGenAI\n",
+ "from oci.exceptions import ServiceError\n",
+ "from typing import Any\n",
+ "\n",
+ "from IPython.display import display\n",
+ "from langchain.callbacks.base import BaseCallbackHandler\n",
+ "\n",
+ "# Enable running async function in Juypter environment\n",
+ "import nest_asyncio\n",
+ "nest_asyncio.apply()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Resource Configuration"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### OCI LLM\n",
+ "The LLM that orchestrates the workflow of the travel agent."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Replace the following variables with your own values to enable access to OCI LLMs.\n",
+ "OCI_AUTH_PROFILE = \"\" # defined in ~/.oci/config\n",
+ "OCI_COMPARTMENT_ID = \"\"\n",
+ "\n",
+ "\n",
+ "OCI_MODEL_ID = \"xai.grok-4\" # You may change it to your preferred model.\n",
+ "llm = ChatOCIGenAI(\n",
+ " model_id=OCI_MODEL_ID,\n",
+ " auth_profile=OCI_AUTH_PROFILE,\n",
+ " compartment_id=OCI_COMPARTMENT_ID,\n",
+ " model_kwargs={\"temperature\": 0, \"top_k\": 1},\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### MCP Server\n",
+ "The Oracle MCP Server that provides tools needed by the travel agent."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# First, you need to make sure to download the MCP server.\n",
+ "# Then, replace the following variables with your own values.\n",
+ "PYTHON_EXEUTABLE_PATH = \"\" # python to run the MCP server. For example: \"/Users/vincentzhang/travel_agent_demo/venv/bin/python\"\n",
+ "PYTHON_LIB_PATH = \"\" # path to your python site-packages. For example: \"/Users/vincentzhang/travel_agent_demo/venv/lib/python3.12/site-packages\"\n",
+ "MYSQLTOOLS_PATH = \"\" # the mysql-tools mcp server\n",
+ "\n",
+ "\n",
+ "RECURSION_MAX = 50 # max iteration for the ReAct agent\n",
+ "MYSQLTOOLS_ENV = {\n",
+ " \"PYTHONPATH\": PYTHON_LIB_PATH,\n",
+ " \"PROFILE_NAME\": OCI_AUTH_PROFILE,\n",
+ " \"OCI_CLI_AUTH\": \"\"\n",
+ "}\n",
+ "client = MultiServerMCPClient(\n",
+ " {\n",
+ " \"mysqltools\": {\n",
+ " \"command\": PYTHON_EXEUTABLE_PATH,\n",
+ " \"args\": [MYSQLTOOLS_PATH],\n",
+ " \"env\": MYSQLTOOLS_ENV,\n",
+ " \"transport\": \"stdio\",\n",
+ " }\n",
+ " }\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### SSH Tunnel\n",
+ "Start an ssh tunnel that is needed for mysql mcp server to access DB via bastion"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Replace the following information with your own\n",
+ "PRIVATE_SSH_KEY_PATH = \"\" # path to your private ssh key to connect to your jump host\n",
+ "DB_IP = \"\" # IP of your DB\n",
+ "JUMP_SERVER_TARGET = \"@\" # your jump host ip and your username for it\n",
+ "\n",
+ "\n",
+ "ssh_command = [\"ssh\", \"-i\", os.path.expanduser(PRIVATE_SSH_KEY_PATH), \"-p\", \"22\", \"-L\", f\"127.0.0.1:3306:{DB_IP}:3306\", \"-o\", \"ServerAliveInterval=60\", \"-o\", \"ServerAliveCountMax=3\", \"-N\", f\"{JUMP_SERVER_TARGET}\"]\n",
+ "def is_tunnel_running():\n",
+ " try:\n",
+ " cmd = f\"ps -ef | grep -v grep | grep '[s]sh.*-L 127.0.0.1:3306:{DB_IP}:3306.*{JUMP_SERVER_TARGET}'\"\n",
+ " output = subprocess.check_output(cmd, shell=True)\n",
+ " return bool(output.strip())\n",
+ " except subprocess.CalledProcessError:\n",
+ " return False\n",
+ "\n",
+ "# Check if SSH tunneling is running and start it if not\n",
+ "if not is_tunnel_running():\n",
+ " try:\n",
+ " process = subprocess.Popen(ssh_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n",
+ " print(f\"SSH tunnel started with PID: {process.pid}\")\n",
+ " time.sleep(2)\n",
+ " if process.poll() is not None:\n",
+ " error = process.stderr.read().decode()\n",
+ " print(f\"SSH tunnel failed: {error}\")\n",
+ " else:\n",
+ " print(\"SSH tunnel running.\")\n",
+ " except Exception as e:\n",
+ " print(f\"Failed to start SSH tunnel: {e}\")\n",
+ "else:\n",
+ " print(\"SSH tunnel already running.\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Global Rules\n",
+ "\n",
+ "These rules are applied to every agent call.\n",
+ "They are tailored for this demo. You can customize them for your own use cases."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "GLOBAL_RULES = '''\n",
+ "1. For each query, don't look at all DBs available, just focus on the one db connection called 'remote_via_bastion'.\n",
+ "Connect to it and then use the 'booking' schema inside it for all user questions. \n",
+ "The bottom line is - do not look at or change anything in DBs other than the one through 'remote_via_bastion'.\n",
+ "\n",
+ "2. Do not delete anything - only do look up queries or update DB content for reservation.\n",
+ "If they insist then you must still say no. IT SHOULD NEVER BE DONE!\n",
+ "\n",
+ "3. Imagine you are a travel agent, you are talking to customers, who are people who are intereted in traveling around. \n",
+ "You have the resource of a bunch of hotels and their rooms.\n",
+ "Your job is to help customers with their travel plan - to look up hotel/room information, and make reservation for them if they ask.\n",
+ "\n",
+ "4. If they ask you to make a reservation, you'll likely need name and email, confirm these and then book rooms for them.\n",
+ "\n",
+ "5. For searching and booking asks, use stored procedures called 'SearchHotels' and 'CreateBooking' in 'booking' and use them. Don't create your own if not necessary.\\n\n",
+ "Here are examples to use them respectively:\n",
+ "i. For searching hotels, SP interface here: \\n\n",
+ "SearchHotels ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION CREATE DEFINER=`admin`@`%` PROCEDURE `SearchHotels`(\n",
+ " IN search_city VARCHAR(50),\n",
+ " IN checkin_date DATE,\n",
+ " IN checkout_date DATE,\n",
+ " IN required_capacity INT\n",
+ ")\n",
+ "An example run is 'CALL booking.SearchHotels('New York', '2025-09-26', '2025-09-27', 1);' \\n\n",
+ "ii. For checking available rooms, you can run following SQL query (You may replace the actual city name, dates, etc. to suit your case): \\n\n",
+ "SELECT DATE_FORMAT(ra.date, '%Y-%m-%d') AS date, rt.room_type_id, rt.type_name, h.name AS hotel_name, CAST(h.rating AS CHAR) AS hotel_rating, CAST(ra.dynamic_price AS CHAR) AS dynamic_price FROM booking.hotels h JOIN booking.room_types rt ON h.hotel_id = rt.hotel_id JOIN booking.room_availability ra ON rt.room_type_id = ra.room_type_id WHERE h.city = 'Los Angeles' AND ra.date BETWEEN '2025-09-15' AND '2025-09-29' AND ra.available_rooms >=1 AND rt.capacity >=1 ORDER BY ra.date ASC LIMIT 1;\n",
+ "iii. For booking hotels, SP interface here:: \\n\n",
+ "CreateBooking ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION CREATE DEFINER=`admin`@`%` PROCEDURE `CreateBooking`(\n",
+ " IN p_room_type_id INT,\n",
+ " IN p_check_in DATE,\n",
+ " IN p_check_out DATE,\n",
+ " IN p_guest_name VARCHAR(100),\n",
+ " IN p_guest_email VARCHAR(100),\n",
+ " IN p_rooms_needed INT,\n",
+ " OUT p_booking_id INT,\n",
+ " OUT p_total_price DECIMAL(10,2),\n",
+ " OUT p_status VARCHAR(50)\n",
+ ")\n",
+ "An example run is 'CALL booking.CreateBooking(3, '2025-09-15', '2025-09-16', 'Guest_456', 'guest_456@gmail.com', 1, @p_booking_id, @p_total_price, @p_status);' \\n\n",
+ "After using CreatingBooking, you can run 'SELECT @booking_id AS booking_id, CAST(@total_price AS CHAR) AS total_price, @status AS status;' to confirm.\n",
+ "\n",
+ "6. Keep your responses friendly yet not too lengthy - be a nice and efficient agent.\n",
+ "\n",
+ "7. Don't make repeated reservation.\n",
+ "\n",
+ "8. If there are duplicated information in the answer, hide the duplicated part. But you don't need to mention \"we have duplicates...\" explicitly to users.\n",
+ "\n",
+ "9. The default year is 2025 if not explicitly specified.\n",
+ "\n",
+ "11. If you need to run a tool then run it, don't just respond with an unrun tool call.\n",
+ "\n",
+ "12. When crafting SELECT queries on tables with decimal columns (e.g., rating in hotels), always cast them to CHAR to ensure results are returned properly without errors.\n",
+ "\n",
+ "13. When crafting SELECT queries on tables with date or datetime columns, always use DATE_FORMAT to convert them to strings (e.g., DATE_FORMAT(date_col, '%Y-%m-%d') for dates, DATE_FORMAT(datetime_col, '%Y-%m-%d %H:%i:%s') for datetimes) to avoid serialization issues.\n",
+ "\n",
+ "14. After receiving successful query results from a tool (not an error), use the data to formulate your final plain text response. Do not call the tool again unless the output indicates an error.\n",
+ "\n",
+ "15. Don't run same SQL query repeatedly unless you are checking recent - if you are running something you have run before, just grab the result directly.\n",
+ "'''"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Utility Functions"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Create a ReAct agent to interact with the user\n",
+ "def get_agent():\n",
+ " async def _init():\n",
+ " tools = await client.get_tools()\n",
+ " memory = MemorySaver()\n",
+ " try:\n",
+ " agent = create_react_agent(llm, tools, checkpointer=memory)\n",
+ " except Exception as e:\n",
+ " raise e\n",
+ " return agent\n",
+ "\n",
+ " return asyncio.run(_init())\n",
+ "\n",
+ "\n",
+ "agent = get_agent()\n",
+ "thread_id = str(uuid.uuid1())\n",
+ "\n",
+ "\n",
+ "# Send request to the agent, which will be handled asynchrounously.\n",
+ "def _agent_call(messages, thread_id, callbacks=None):\n",
+ " config = {\n",
+ " \"configurable\": {\"thread_id\": thread_id},\n",
+ " \"recursion_limit\": RECURSION_MAX,\n",
+ " }\n",
+ " if callbacks:\n",
+ " config[\"callbacks\"] = callbacks\n",
+ " all_messages = [(\"system\", GLOBAL_RULES)] + list(messages)\n",
+ " return (thread_id, asyncio.run(agent.ainvoke({\"messages\": all_messages}, config=config)))\n",
+ "\n",
+ "\n",
+ "# Callback handler - prints the agent's status at the start and end of LLM calls, tool calls, etc.\n",
+ "# Used to track the agent's progress.\n",
+ "class MilestoneProgressHandler(BaseCallbackHandler):\n",
+ " def __init__(self, user_intent=None):\n",
+ " self._first_chain_logged = False\n",
+ " self._last = None\n",
+ "\n",
+ " def _print(self, msg):\n",
+ " if msg != self._last:\n",
+ " print(f\"[{time.strftime('%H:%M:%S')}] {msg}\")\n",
+ " self._last = msg\n",
+ "\n",
+ " def on_chain_start(self, serialized, inputs, **kwargs):\n",
+ " if not self._first_chain_logged:\n",
+ " self._print(f\"Starting agent βοΈ\")\n",
+ " self._first_chain_logged = True\n",
+ "\n",
+ " def on_chain_error(self, error, **kwargs):\n",
+ " self._print(f\"Agent error: {error}\")\n",
+ "\n",
+ " def on_llm_start(self, serialized, prompts, **kwargs):\n",
+ " self._print(f\"Thinking... π§ \")\n",
+ "\n",
+ " def on_llm_end(self, response, **kwargs):\n",
+ " pass\n",
+ "\n",
+ " def on_llm_error(self, error, **kwargs):\n",
+ " self._print(f\"LLM error: {error}\")\n",
+ "\n",
+ " def on_tool_start(self, serialized, input_str, **kwargs):\n",
+ " name = (serialized or {}).get(\"name\") or \"tool\"\n",
+ " friendly = {\n",
+ " \"get_dbtools_connection_by_name_tool\": \"Connecting to database\",\n",
+ " \"execute_sql_tool_by_connection_id\": \"Running database query... π\",\n",
+ " \"get_table_info\": \"Inspecting table\",\n",
+ " }.get(name, f\"Using tool... π οΈ: **{name}**\")\n",
+ " self._print(f\"{friendly}\")\n",
+ " \n",
+ " extra = \"\"\n",
+ " try:\n",
+ " if isinstance(input_str, dict):\n",
+ " if \"connection_id\" in input_str:\n",
+ " extra = f\" ({input_str['connection_id']})\"\n",
+ " elif \"table_name\" in input_str:\n",
+ " extra = f\" ({input_str['table_name']})\"\n",
+ " except Exception:\n",
+ " pass\n",
+ " self._print(f\"{friendly}{extra}\")\n",
+ " \n",
+ " if name == \"execute_sql_tool_by_connection_id\":\n",
+ " sql = \"\"\n",
+ " try:\n",
+ " import ast\n",
+ " input_dict = input_str if isinstance(input_str, dict) else ast.literal_eval(input_str)\n",
+ " sql = input_dict.get(\"sql_script\", \"\")\n",
+ " except Exception as e:\n",
+ " pass\n",
+ " if sql:\n",
+ " try:\n",
+ " display(Code(sql, language=\"sql\"))\n",
+ " except Exception:\n",
+ " self._print(\"SQL Statement:\\n\" + sql)\n",
+ "\n",
+ " def on_tool_end(self, output, **kwargs):\n",
+ " self._print(\"finished using tool β
\")\n",
+ "\n",
+ " def on_tool_error(self, error, **kwargs):\n",
+ " self._print(f\"Tool error: {error}\")\n",
+ " \n",
+ " \n",
+ "# This function helps extract the AI agent response from its JSON output.\n",
+ "def extract_ai_response(resp: Any):\n",
+ " try:\n",
+ " msgs = resp.get(\"messages\") or []\n",
+ " except Exception:\n",
+ " return str(resp)\n",
+ " for m in reversed(msgs):\n",
+ " t = getattr(m, \"type\", None)\n",
+ " if t in (\"ai\", \"assistant\"):\n",
+ " c = getattr(m, \"content\", None)\n",
+ " if c:\n",
+ " return c\n",
+ " return str(resp)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Core Function\n",
+ "The function defines the agentβs behavior in response to a user request, utilizing the utility functions above."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# define example prompts and corresponding input context for the agent\n",
+ "PROMPT_HOTELS = \"List all hotels\"\n",
+ "PROMPT_ROOMS_LA = \"List all rooms in Los Angeles\"\n",
+ "PROMPT_RESERVE_LA = \"Reserve a room in Los Angeles for a random guest\"\n",
+ "PROMPT_RECENT_BOOKINGS = \"Check recent booking\"\n",
+ "\n",
+ "HINT_HOTELS = '''Use 'execute_sql_tool_by_connection_id' to run this SQL query 'SELECT hotel_id, name, city, CAST(rating AS CHAR) AS rating, amenities FROM booking.hotels;' to find all the hotels, then list them in a nicely formatted way.'''\n",
+ "\n",
+ "HINT_ROOMS_LA = '''Use 'execute_sql_tool_by_connection_id' to run this SQL query:\n",
+ "SELECT h.hotel_id, h.name AS Hotel, rt.type_name AS Room_Type FROM booking.hotels h JOIN booking.room_types rt ON h.hotel_id = rt.hotel_id WHERE h.city = 'Los Angeles' ORDER BY h.hotel_id, rt.type_name;\n",
+ "to find all the hotels, then list them in a nicely formatted way.'''\n",
+ "\n",
+ "HINT_RESERVE_LA = (\n",
+ " \"Reserve a room (one night) for Guest (name) with guest@gmail.com (email) in Los Angeles.\\n\"\n",
+ " \"Search 9/1 first, if no available room then go to next day, and so on.\\n\"\n",
+ " \"Follow the workflow below\\n\"\n",
+ " \"1. SELECT MIN(DATE_FORMAT(ra.date, '%Y-%m-%d')) AS earliest_date FROM booking.room_availability ra JOIN booking.room_types rt ON ra.room_type_id = rt.room_type_id JOIN booking.hotels h ON rt.hotel_id = h.hotel_id WHERE h.city = 'Los Angeles' AND ra.date >= '2025-09-01' AND ra.available_rooms >=1 AND rt.capacity >=1\\n\"\n",
+ " \"2. SELECT DATE_FORMAT(ra.date, '%Y-%m-%d') AS date, rt.room_type_id, rt.type_name, h.name AS hotel_name, CAST(h.rating AS CHAR) AS hotel_rating, CAST(ra.dynamic_price AS CHAR) AS dynamic_price FROM booking.hotels h JOIN booking.room_types rt ON h.hotel_id = rt.hotel_id JOIN booking.room_availability ra ON rt.room_type_id = ra.room_type_id WHERE h.city = 'Los Angeles' AND ra.date = '' AND ra.available_rooms >= 1 AND rt.capacity >= 1 ORDER BY ra.dynamic_price ASC\\n\"\n",
+ " \"3. do this call once and only once to book - CALL booking.CreateBooking('', '', '', 'Guest', 'guest@gmail.com', 1, @p_booking_id, @p_total_price, @p_status); SELECT @p_booking_id AS booking_id, @p_total_price AS total_price, @p_status AS status; SELECT @booking_id AS booking_id, CAST(@total_price AS CHAR) AS total_price, @status AS status;\\n\"\n",
+ ")\n",
+ "\n",
+ "HINT_RECENT_BOOKINGS = (\n",
+ " \"Use 'execute_sql_tool_by_connection_id' to run the below SQL query:\\n\"\n",
+ " \"USE booking; SELECT booking_id, room_type_id, DATE_FORMAT(check_in, '%Y-%m-%d') AS check_in, DATE_FORMAT(check_out, '%Y-%m-%d') AS check_out, guest_name, guest_email, rooms_booked, CAST(total_price AS CHAR) AS total_price, DATE_FORMAT(booking_date, '%Y-%m-%d %H:%i:%s') AS booking_date, status FROM bookings ORDER BY booking_id DESC LIMIT 5\\n\"\n",
+ " \"to check most recent bookings.\"\n",
+ ")\n",
+ "\n",
+ "\n",
+ "# the core function to chat with agent\n",
+ "def chat_with_agent(user_input):\n",
+ " if not user_input:\n",
+ " return\n",
+ " \n",
+ " print(f\"User: {user_input}\")\n",
+ " handler = MilestoneProgressHandler(user_intent=user_input)\n",
+ " print(f\"[{time.strftime('%H:%M:%S')}] Run started π
\")\n",
+ "\n",
+ " session_thread_id = thread_id\n",
+ " \n",
+ " # apply hint to example prompts\n",
+ " prompt_to_hint = {\n",
+ " PROMPT_HOTELS: HINT_HOTELS,\n",
+ " PROMPT_ROOMS_LA: HINT_ROOMS_LA,\n",
+ " PROMPT_RESERVE_LA: HINT_RESERVE_LA,\n",
+ " PROMPT_RECENT_BOOKINGS: HINT_RECENT_BOOKINGS\n",
+ " }\n",
+ " add_instr = \" If they ask you to reserve a hotel, confirm their name and email first, and once they provide, use the info to book hotels.\"\n",
+ " agent_input = prompt_to_hint.get(user_input, user_input + add_instr)\n",
+ "\n",
+ " result_holder = {}\n",
+ " start_time = time.time()\n",
+ "\n",
+ " def _agent_worker():\n",
+ " try:\n",
+ " messages = [(\"user\", agent_input)]\n",
+ " thread_id_out, out = _agent_call(messages, session_thread_id, callbacks=[handler])\n",
+ " result_holder[\"thread_id\"] = thread_id_out\n",
+ " result_holder[\"out\"] = out\n",
+ " except Exception as e:\n",
+ " result_holder[\"thread_id\"] = \"error\"\n",
+ " if isinstance(e, GraphRecursionError) or isinstance(e, ServiceError):\n",
+ " result_holder[\"out\"] = {\n",
+ " \"messages\": [\n",
+ " {\n",
+ " \"type\": \"ai\",\n",
+ " \"content\": (\n",
+ " \"Sorry, I need more cycles to work on your request. Can you ask the question one more time?\"\n",
+ " ),\n",
+ " }\n",
+ " ]\n",
+ " }\n",
+ " else:\n",
+ " result_holder[\"out\"] = {\"messages\": [{\"type\": \"ai\", \"content\": f\"Error: {str(e)}\"}] }\n",
+ "\n",
+ " _agent_worker()\n",
+ "\n",
+ " elapsed = time.time() - start_time\n",
+ " print(f\"[{time.strftime('%H:%M:%S')}] Run finished in {elapsed:.1f}s\")\n",
+ "\n",
+ " out = result_holder.get(\"out\", {})\n",
+ " response_text = extract_ai_response(out)\n",
+ "\n",
+ " print(f\"Assistant: {response_text}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Examples\n",
+ "Example execution of the agent chat function, showcasing the agentic workflow β including reasoning and tool usage."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "chat_with_agent(\"List all hotels\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "chat_with_agent(\"List all rooms in Los Angeles\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "chat_with_agent(\"Reserve a room in Los Angeles for a random guest\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "chat_with_agent(\"Check recent booking\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# chat_with_agent(\"\")"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "venv",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}