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", + "
  1. Setup\n", + " \n", + "
  2. \n", + "
  3. Utility Functions
  4. \n", + "
  5. Core Function
  6. \n", + "
  7. Examples
  8. \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 +}