diff --git a/images/claudette-title.png b/images/claudette-title.png new file mode 100644 index 0000000..9e77742 Binary files /dev/null and b/images/claudette-title.png differ diff --git a/images/claudette.mp4 b/images/claudette.mp4 new file mode 100644 index 0000000..103b7a2 Binary files /dev/null and b/images/claudette.mp4 differ diff --git a/images/support-bot.png b/images/support-bot.png new file mode 100644 index 0000000..040a8ec Binary files /dev/null and b/images/support-bot.png differ diff --git a/images/thread-response.png b/images/thread-response.png new file mode 100644 index 0000000..b1649b2 Binary files /dev/null and b/images/thread-response.png differ diff --git a/images/token-scopes.png b/images/token-scopes.png new file mode 100644 index 0000000..c50bb08 Binary files /dev/null and b/images/token-scopes.png differ diff --git a/images/welcome-message.png b/images/welcome-message.png new file mode 100644 index 0000000..51b6d9d Binary files /dev/null and b/images/welcome-message.png differ diff --git a/posts/2024-06-22 Support bot.ipynb b/posts/2024-06-22 Support bot.ipynb new file mode 100644 index 0000000..397df1a --- /dev/null +++ b/posts/2024-06-22 Support bot.ipynb @@ -0,0 +1,983 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "ea5f38ca", + "metadata": {}, + "source": [ + "---\n", + "title: Support bot with Claude 3.5 Sonnet using Claudette and Slack-SDK\n", + "subtitle: Creating a support bot that support API calls using Claudette\n", + "description: | \n", + " As part of this blog post we will build a support bot that can answer support queries from a slack channel using Claudette (a thin python wrapper on top of Anthropic CLI)\n", + "categories:\n", + " - LLM\n", + "author: Aman Arora\n", + "date: \"06/22/2024\"\n", + "toc: true\n", + "number-sections: true\n", + "title-block-banner: true\n", + "bibliography: ../references.bib\n", + "reference-location: margin\n", + "citation-location: margin\n", + "code-fold: false\n", + "image: ../images/claudette-title.png\n", + "---" + ] + }, + { + "cell_type": "raw", + "id": "5e160301", + "metadata": {}, + "source": [ + "{{< video ../images/claudette.mp4 \n", + " title=\"Claudette in Slack\"\n", + ">}}" + ] + }, + { + "cell_type": "markdown", + "id": "2e961c21", + "metadata": {}, + "source": [ + "**Problem Statement:** You are the owner of a graphical and tech company called *\"DRAMA77IC\"* that creates dramatic and graphical visualisations for games for users all over the world. You have an API that contains information about various games such as genre, date of release, description and so on. You want to create a support channel, so your users can directly ask questions about your offerings through this Slack channel." + ] + }, + { + "cell_type": "markdown", + "id": "12389fc3", + "metadata": {}, + "source": [ + "Now that we have a well defined problem statement, let's go about creating a solution using `Claudette`!" + ] + }, + { + "cell_type": "markdown", + "id": "7051a5db", + "metadata": {}, + "source": [ + "Recently Answer.AI team released [Claudette](https://www.answer.ai/posts/2024-06-21-claudette.html). It is built on top of Claude 3.5 Sonnet - the most powerful language model at the time of writing this blog post. \n", + "\n", + "As part of this blog post, I will show you how to use Claudette to create a support bot built on top of Slack. You should be able to easily integrate the steps shown below to respond to user queries by calling any function. \n", + "\n", + "There are two parts to this blog post. \n", + "\n", + "1. Create a slack app - this is pure setup to create a slack application so that we can respond to user messages automatically.\n", + "2. Integrate this slack application with Claude using Claudette.\n", + "\n", + "Finally, we might to test this out and showcase a demo. " + ] + }, + { + "cell_type": "markdown", + "id": "7c1b4669", + "metadata": {}, + "source": [ + "## Creating a slack APP\n", + "\n", + "On Slack, I created a new workspace for this blog post. \n", + "\n", + "Next, you want to go to https://api.slack.com/apps and create a new app." + ] + }, + { + "cell_type": "markdown", + "id": "9f4dd6a8", + "metadata": {}, + "source": [ + "\"alt" + ] + }, + { + "cell_type": "markdown", + "id": "85e1c51d-20b9-4ea8-be04-70172175489e", + "metadata": {}, + "source": [ + "Let's say **DRAMA77IC** is a tech company that works on games specifically creating dramatic and graphical games. This company has an API that can give out information about the various games that this company has built. \n", + "The users of this company interact with the support team using a Slack channel (not discord, because we want to showcase slack-sdk as part of this blog post)." + ] + }, + { + "cell_type": "markdown", + "id": "cb0b17fe", + "metadata": {}, + "source": [ + "Next, create a new slack app - \"support-bot\". " + ] + }, + { + "cell_type": "markdown", + "id": "d19fcb90", + "metadata": {}, + "source": [ + "Here's what you want to do:\n", + "\n", + "1. Enable socket mode, when you do this, a new APP level token will also be created with scope `\n", + "connections:write`.\n", + "2. Go over to **OAuth & Permissions** and add the following scopes: \n", + " 1. *app_mentions:read*\n", + " 2. *channels:history*\n", + " 3. *channels:read*\n", + " 4. *chat:write*\n", + " 5. *im:history*\n", + " 6. *im:read*\n", + " 7. *im:write*\n", + " 8. *reactions:write*\n", + "3. Enable event subscriptions.\n", + "4. Install your bot to workspace and add it to your support channel. " + ] + }, + { + "cell_type": "markdown", + "id": "cde44392", + "metadata": {}, + "source": [ + "\"alt" + ] + }, + { + "cell_type": "markdown", + "id": "837f8941", + "metadata": {}, + "source": [ + "We are now ready to start sending messages to public slack channels using our bot. Copy over your `SLACK_APP_TOKEN` and `SLACK_BOT_TOKEN` to a `.env` file and let's use `dotenv` to load them." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "2f0f765c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import dotenv\n", + "\n", + "dotenv.load_dotenv()" + ] + }, + { + "cell_type": "markdown", + "id": "ec4d5d96", + "metadata": {}, + "source": [ + "Now let's make some imports and get our `BOT_USER_ID`. Each user in Slack has a `user_id`. To read more about the slack client, refer [here](https://slack.dev/python-slack-sdk/web/index.html)." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "1138b0c0", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from slack_sdk import WebClient\n", + "from slack_sdk.errors import SlackApiError" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "329f4766", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'U07932L0L5U'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "client = WebClient(token=os.environ[\"SLACK_BOT_TOKEN\"])\n", + "BOT_USER_ID = client.auth_test()[\"user_id\"]\n", + "BOT_USER_ID" + ] + }, + { + "cell_type": "markdown", + "id": "f2d64c24", + "metadata": {}, + "source": [ + "You can also add the channel ID to your dotenv, and we can load it like below:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "2ae8ef04", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'C078V28044F'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "channel_id = os.environ['LISTEN_CHANNEL_ID']\n", + "channel_id" + ] + }, + { + "cell_type": "markdown", + "id": "22926cf3", + "metadata": {}, + "source": [ + "To post the message to your channel, simply use `client.chat_postMessage`. But, before you do that, make sure to add the support-bot to your channel.\n", + "\n", + "I created a new channel called #blog and added support-bot to it." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "892e99a4", + "metadata": {}, + "outputs": [], + "source": [ + "response = client.chat_postMessage(\n", + " channel=channel_id, \n", + " text=\"Bonjour! My name is Claudia, I am your support-bot for DRAMA77IC, a made-up company name for blogging purposes.\", \n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "ba2d2edf", + "metadata": {}, + "source": [ + "\"alt\n" + ] + }, + { + "cell_type": "markdown", + "id": "5615cbc1", + "metadata": {}, + "source": [ + "A little bit more about slack - each message in Slack a timestamp represented by `ts`. " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "6f6e6995", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'user': 'U07932L0L5U',\n", + " 'type': 'message',\n", + " 'ts': '1719101633.566529',\n", + " 'bot_id': 'B079C5SV99A',\n", + " 'app_id': 'A079C53DT9A',\n", + " 'text': 'Bonjour! My name is Claudia, I am your support-bot for DRAMA77IC, a made-up company name for blogging purposes.',\n", + " 'team': 'T079C2R49GC',\n", + " 'bot_profile': {'id': 'B079C5SV99A',\n", + " 'deleted': False,\n", + " 'name': 'support-bot',\n", + " 'updated': 1719017903,\n", + " 'app_id': 'A079C53DT9A',\n", + " 'icons': {'image_36': 'https://a.slack-edge.com/80588/img/plugins/app/bot_36.png',\n", + " 'image_48': 'https://a.slack-edge.com/80588/img/plugins/app/bot_48.png',\n", + " 'image_72': 'https://a.slack-edge.com/80588/img/plugins/app/service_72.png'},\n", + " 'team_id': 'T079C2R49GC'},\n", + " 'blocks': [{'type': 'rich_text',\n", + " 'block_id': 'Xwfz',\n", + " 'elements': [{'type': 'rich_text_section',\n", + " 'elements': [{'type': 'text',\n", + " 'text': 'Bonjour! My name is Claudia, I am your support-bot for DRAMA77IC, a made-up company name for blogging purposes.'}]}]}]}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "message = client.conversations_history(channel=channel_id)['messages'][0]\n", + "message" + ] + }, + { + "cell_type": "markdown", + "id": "009f8184", + "metadata": {}, + "source": [ + "To respond to this very message, we can pass in the timestamp as a `thread_ts` parameter." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "37d4db3b", + "metadata": {}, + "outputs": [], + "source": [ + "response = client.chat_postMessage(\n", + " channel=channel_id, \n", + " text=\"I was just told to respond to my own message. So I am doing that.\", \n", + " thread_ts=message['ts']\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "052e71b1", + "metadata": {}, + "source": [ + "\"alt\n" + ] + }, + { + "cell_type": "markdown", + "id": "df1db1b1", + "metadata": {}, + "source": [ + "Now we have the basics in place to start working on our support-bot using Claudette. " + ] + }, + { + "cell_type": "markdown", + "id": "ae3c6e2a", + "metadata": {}, + "source": [ + "## Support Bot using Claudette" + ] + }, + { + "cell_type": "markdown", + "id": "ff8f3c1b", + "metadata": {}, + "source": [ + "Next up, let's get Claudette to respond to one of the user messages automatically. First things first, let's install the library.\n", + "\n", + "```\n", + "pip install claudette\n", + "```\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "b1eefa8d", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from slack_sdk.web import WebClient\n", + "from slack_sdk.socket_mode import SocketModeClient\n", + "from slack_sdk.socket_mode.response import SocketModeResponse\n", + "from slack_sdk.socket_mode.request import SocketModeRequest\n", + "import dotenv\n", + "import logging\n", + "from datetime import datetime, timedelta\n", + "import time\n", + "from claudette import *" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "ad6f5032", + "metadata": {}, + "outputs": [], + "source": [ + "# claude's latest and most powerful version\n", + "model='claude-3-5-sonnet-20240620'" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "ce05f2b1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "Hello Alice! It's nice to meet you. I'm Claudia. How are you doing today? Is there anything in particular you'd like to chat about?\n", + "\n", + "
\n", + "\n", + "- id: msg_01HzdXGnHQFFtHAGMZfqU8Fj\n", + "- content: [{'text': \"Hello Alice! It's nice to meet you. I'm Claudia. How are you doing today? Is there anything in particular you'd like to chat about?\", 'type': 'text'}]\n", + "- model: claude-3-5-sonnet-20240620\n", + "- role: assistant\n", + "- stop_reason: end_turn\n", + "- stop_sequence: None\n", + "- type: message\n", + "- usage: {'input_tokens': 31, 'output_tokens': 36}\n", + "\n", + "
" + ], + "text/plain": [ + "Message(id='msg_01HzdXGnHQFFtHAGMZfqU8Fj', content=[TextBlock(text=\"Hello Alice! It's nice to meet you. I'm Claudia. How are you doing today? Is there anything in particular you'd like to chat about?\", type='text')], model='claude-3-5-sonnet-20240620', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=In: 31; Out: 36; Total: 67)" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chat = Chat(model=model, \n", + " sp=\"You are Claudia. Do not share what tools you use to respond to user requests.\")\n", + "chat(\"Hi, I'm Alice.\")" + ] + }, + { + "cell_type": "markdown", + "id": "c423f197", + "metadata": {}, + "source": [ + "Now, the best part about `claudette` is that it allows function calling and it has been made really simple. Let's take the example of the following games. Let's say the company has the following five games - G1 to G5 and two customers C1 & C2." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "889ea158", + "metadata": {}, + "outputs": [], + "source": [ + "customers = {\n", + " \"C1\": dict(name=\"Alice Johnson\", email=\"alice@example.com\", phone=\"123-456-7890\",\n", + " games=[\"G1\", \"G2\", \"G3\"]),\n", + " \"C2\": dict(name=\"Bob Smith\", email=\"bob@example.com\", phone=\"987-654-3210\",\n", + " games=[\"G4\", \"G5\"])\n", + "}\n", + "\n", + "games = {\n", + " \"G1\": dict(id=\"G1\", name=\"Shadow Realms\", release_date=\"2023-03-15\", description=\"Navigate enchanted forests and haunted castles.\", status=\"Shipped\"),\n", + " \"G2\": dict(id=\"G2\", name=\"Solar Winds\", release_date=\"2023-07-22\", description=\"Explore space with stunning visuals and alien planets.\", status=\"Shipped\"),\n", + " \"G3\": dict(id=\"G3\", name=\"Mystic Legends\", release_date=\"2023-11-10\", description=\"Epic fantasy RPG with beautiful landscapes.\", status=\"Shipped\"),\n", + " \"G4\": dict(id=\"G4\", name=\"Cyber Revolution\", release_date=\"2024-02-28\", description=\"Dystopian future with advanced technology and cyber warfare.\", status=\"Shipped\"),\n", + " \"G5\": dict(id=\"G5\", name=\"Desert Storm\", release_date=\"2024-05-05\", description=\"Tactical shooter in a war-torn desert.\", status=\"Processing\")\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "19c3546c", + "metadata": {}, + "source": [ + "Let's say we have a database of games and customers. Let's now define some functions." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "6f7718f5", + "metadata": {}, + "outputs": [], + "source": [ + "def get_customer_info(\n", + " customer_id: str # ID of the customer\n", + "): # Customer's name, email, phone number, and list of games\n", + " \"Retrieves a customer's information and their orders based on the customer ID\"\n", + " print(f'- Retrieving customer {customer_id}')\n", + " return customers.get(customer_id, \"Customer not found\")\n", + "\n", + "def get_game_details(\n", + " game_id: str # ID of the game\n", + "): # Game's ID, name, release date, description & status\n", + " \"Retrieves the details of a specific game based on the game ID\"\n", + " print(f'- Retrieving game {game_id}')\n", + " return games.get(game_id, \"Game not found\")\n", + "\n", + "def return_game(\n", + " game_id:str # ID of the order to cancel\n", + ")->bool: # True if the return is successful\n", + " \"Returns a game to the cmpany based on game ID.\"\n", + " print(f'- Returning game {game_id}')\n", + " if game_id not in games: return False\n", + " games[game_id]['status'] = 'Returned'\n", + " return True" + ] + }, + { + "cell_type": "markdown", + "id": "49be577c", + "metadata": {}, + "source": [ + "Now we can simply define these tools with claudette. Note, we no longer need to provide the chunky json version, `claudette` automatically handles that for us using docments." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "877e7014", + "metadata": {}, + "outputs": [], + "source": [ + "tools = [get_customer_info, get_game_details, return_game]\n", + "chat = Chat(model, tools=tools)" + ] + }, + { + "cell_type": "markdown", + "id": "e68b6cbe", + "metadata": {}, + "source": [ + "Let's now do a function call as customer C1 and return one of the games." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "016f7640", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "end_turn\n" + ] + }, + { + "data": { + "text/plain": [ + "[TextBlock(text=\"Hello Alice Johnson! It's great to hear from you. I'm doing well, thank you for asking. I hope you're doing well too. \\n\\nI see that you've provided your Customer ID. That's very helpful! Would you like me to retrieve your customer information and order details? I can do that for you using the Customer ID you've provided. This will allow me to assist you better with any questions or concerns you might have. \\n\\nShall I go ahead and fetch your customer information?\", type='text')]" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "r = chat('Hi! How are you? This is Alice Johnson. (Customer ID: \"C1\")')\n", + "print(r.stop_reason)\n", + "r.content" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "d9aed58d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "- Retrieving customer C1\n", + "tool_use\n" + ] + }, + { + "data": { + "text/plain": [ + "[TextBlock(text=\"Certainly, Alice! I'd be happy to help you with that. To get the information about the games you currently have, I'll need to retrieve your customer information first. I'll use the Customer ID you provided to do this.\", type='text'),\n", + " ToolUseBlock(id='toolu_01TrdXW3C3VfJpcLi9UMeyYp', input={'customer_id': 'C1'}, name='get_customer_info', type='tool_use')]" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "r = chat('Can you tell me more about the games I currently have? Just give me a list of games I own.')\n", + "print(r.stop_reason)\n", + "r.content" + ] + }, + { + "cell_type": "markdown", + "id": "33d34354", + "metadata": {}, + "source": [ + "Claude recognises that we are doing a function call to retrieve information about C1. Claudette let's you call the function automatically by simply calling it again." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "aec52b9b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Thank you for your patience, Alice. I've retrieved your customer information, including the list of games you currently own. Here's a list of the games associated with your account:\n", + "\n", + "1. Game ID: G1\n", + "2. Game ID: G2\n", + "3. Game ID: G3\n", + "\n", + "These are the games you currently have in your possession. Would you like more detailed information about any of these games? I can provide you with specific details for each game if you're interested. Just let me know which game(s) you'd like to know more about, and I'll be happy to fetch that information for you.\n" + ] + } + ], + "source": [ + "r = chat()\n", + "print(contents(r))" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "26a96484", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "- Returning game G2\n", + "tool_use\n" + ] + }, + { + "data": { + "text/plain": [ + "[TextBlock(text=\"Certainly, Alice. I understand that you want to return the game with ID G2. I'd be happy to help you process that return. I'll use the return_game function to do this for you right away.\", type='text'),\n", + " ToolUseBlock(id='toolu_018X45fRcYQ69TL4MUZS5rXg', input={'game_id': 'G2'}, name='return_game', type='tool_use')]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "r = chat(\"No, that's fine. Can you just return my game G2? I don't want it anymore.\")\n", + "print(r.stop_reason)\n", + "r.content" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "a67c01c0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Great news, Alice! The return for game G2 has been successfully processed. The system confirms that the return was completed successfully.\n", + "\n", + "To summarize:\n", + "- You've returned the game with ID G2.\n", + "- The return has been recorded in our system.\n", + "- You should no longer have this game in your possession.\n", + "\n", + "Is there anything else you'd like me to help you with regarding your games or account? Perhaps you'd like to know more about the remaining games you have, or if you have any other questions, I'm here to assist.\n" + ] + } + ], + "source": [ + "r = chat()\n", + "print(contents(r))" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "1b9ba8f9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "end_turn\n" + ] + }, + { + "data": { + "text/plain": [ + "[TextBlock(text=\"You're welcome, Alice! I'm glad I could help you with returning game G2. \\n\\nJust a small correction: my name isn't Claudia. I'm an AI assistant without a specific name. But I'm always here to help you with any questions or concerns you might have about your games or account.\\n\\nIs there anything else you need assistance with today? If not, I hope you have a wonderful day!\", type='text')]" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "r = chat(\"That's it. Thank you Claudia.\")\n", + "print(r.stop_reason)\n", + "r.content" + ] + }, + { + "cell_type": "markdown", + "id": "e41fae6d", + "metadata": {}, + "source": [ + "Now, that's a good looking customer support conversation but we want to have this in Slack instead. For that case, we will write a small Python script that constantly monitors the channel and looks for new messages. Claudette only responds if the bot has been mentioned with \"@support-bot\". Let's go ahead and write that script now and show it in action. " + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "73345696", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "In: 5609; Out: 658; Total: 6267" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# we can also check the total tokens used in our conversation\n", + "chat.use" + ] + }, + { + "cell_type": "markdown", + "id": "ad6475c3", + "metadata": {}, + "source": [ + "This is not very convenient, is it? To have to run `chat()` again every time when Claude wants to do a function call. In this case, we have another method called `toolloop()`. This continues to call functions until we are finished. Let's take the example of having to delete all orders." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "ca69fc77", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "- Returning game G1\n", + "- Returning game G3\n" + ] + }, + { + "data": { + "text/markdown": [ + "Great news! I've successfully processed the returns for both of your remaining games. Here's a summary:\n", + "\n", + "1. Game G1: Successfully returned\n", + "2. Game G3: Successfully returned\n", + "\n", + "All of your games have now been returned to the company. Your account should no longer have any active game rentals.\n", + "\n", + "Is there anything else you would like me to help you with regarding your account or our services?\n", + "\n", + "
\n", + "\n", + "- id: msg_01SKrBoyXMbFNUF2uWGFErZK\n", + "- content: [{'text': \"Great news! I've successfully processed the returns for both of your remaining games. Here's a summary:\\n\\n1. Game G1: Successfully returned\\n2. Game G3: Successfully returned\\n\\nAll of your games have now been returned to the company. Your account should no longer have any active game rentals.\\n\\nIs there anything else you would like me to help you with regarding your account or our services?\", 'type': 'text'}]\n", + "- model: claude-3-5-sonnet-20240620\n", + "- role: assistant\n", + "- stop_reason: end_turn\n", + "- stop_sequence: None\n", + "- type: message\n", + "- usage: {'input_tokens': 1633, 'output_tokens': 88}\n", + "\n", + "
" + ], + "text/plain": [ + "Message(id='msg_01SKrBoyXMbFNUF2uWGFErZK', content=[TextBlock(text=\"Great news! I've successfully processed the returns for both of your remaining games. Here's a summary:\\n\\n1. Game G1: Successfully returned\\n2. Game G3: Successfully returned\\n\\nAll of your games have now been returned to the company. Your account should no longer have any active game rentals.\\n\\nIs there anything else you would like me to help you with regarding your account or our services?\", type='text')], model='claude-3-5-sonnet-20240620', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=In: 1633; Out: 88; Total: 1721)" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chat.toolloop(\"Hey Claudia. Can you return all the games for me?\")" + ] + }, + { + "cell_type": "markdown", + "id": "0233e689", + "metadata": {}, + "source": [ + "There you go! Now, we were able to call multiple functions in a loop. Which is great. To confirm let's check the `games` dict and confirm that the order status has changed." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "b89ce560", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'G1': {'id': 'G1',\n", + " 'name': 'Shadow Realms',\n", + " 'release_date': '2023-03-15',\n", + " 'description': 'Navigate enchanted forests and haunted castles.',\n", + " 'status': 'Returned'},\n", + " 'G2': {'id': 'G2',\n", + " 'name': 'Solar Winds',\n", + " 'release_date': '2023-07-22',\n", + " 'description': 'Explore space with stunning visuals and alien planets.',\n", + " 'status': 'Returned'},\n", + " 'G3': {'id': 'G3',\n", + " 'name': 'Mystic Legends',\n", + " 'release_date': '2023-11-10',\n", + " 'description': 'Epic fantasy RPG with beautiful landscapes.',\n", + " 'status': 'Returned'},\n", + " 'G4': {'id': 'G4',\n", + " 'name': 'Cyber Revolution',\n", + " 'release_date': '2024-02-28',\n", + " 'description': 'Dystopian future with advanced technology and cyber warfare.',\n", + " 'status': 'Shipped'},\n", + " 'G5': {'id': 'G5',\n", + " 'name': 'Desert Storm',\n", + " 'release_date': '2024-05-05',\n", + " 'description': 'Tactical shooter in a war-torn desert.',\n", + " 'status': 'Processing'}}" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "games" + ] + }, + { + "cell_type": "markdown", + "id": "9f9d9465", + "metadata": {}, + "source": [ + "As can be seen from the dictionary above, we can see that games `G1`, `G2` & `G3` have been returned. " + ] + }, + { + "cell_type": "markdown", + "id": "90f52ea5", + "metadata": {}, + "source": [ + "## Claudette in Slack" + ] + }, + { + "cell_type": "markdown", + "id": "ec67a376", + "metadata": {}, + "source": [ + "Now that we have a good idea on how to use claudette for function calling, let's integrate it with Slack so that we can allow our support-bot to respond to user queries in a thread. " + ] + }, + { + "cell_type": "markdown", + "id": "3d30f97e", + "metadata": {}, + "source": [ + "Mostly all, we need is a process function like below:\n", + "\n", + "```python\n", + "def process(client: SocketModeClient, req: SocketModeRequest):\n", + " print(req.payload)\n", + " if req.type == \"events_api\" or req.type == \"event_callback\":\n", + " response = SocketModeResponse(envelope_id=req.envelope_id)\n", + " client.send_socket_mode_response(response)\n", + " if (\n", + " req.payload[\"event\"][\"type\"] == \"message\"\n", + " and req.payload[\"event\"].get(\"subtype\") is None\n", + " and \"bot_profile\" not in req.payload[\"event\"].keys()\n", + " ):\n", + " thread_ts = req.payload[\"event\"][\"ts\"]\n", + " if \"thread_ts\" in req.payload[\"event\"].keys():\n", + " thread_ts = req.payload[\"event\"][\"thread_ts\"]\n", + " text = req.payload[\"event\"][\"text\"]\n", + " r = chat.toolloop(text, maxtok=200)\n", + " response = _client.chat_postMessage(\n", + " channel=CHANNEL_ID, text=contents(r), thread_ts=thread_ts\n", + " )\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "987cddba", + "metadata": {}, + "source": [ + "Using claudette has made this function really easy. Because claudette already takes care of state, and past messages, that is something we don't have to worry about and can simply delegate to Claudette to take care of it all. \n", + "\n", + "Once we get a request, we can get a timestamp, and if the user responds in a thread itself, then we get the timestamp from the thread. Next, we extract the message as a string and pass it over to claudette.\n", + "\n", + "Using `toolloop` allows claudette to make function calls directly to Claude and return the answer. A sample conversation on Slack using this setup looks something like below." + ] + }, + { + "cell_type": "raw", + "id": "2bbaea01", + "metadata": {}, + "source": [ + "{{< video ../images/claudette.mp4 \n", + " title=\"Claudette in Slack\"\n", + ">}}" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.11.9" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": {}, + "toc_section_display": true, + "toc_window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}