Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement customer service agent recipe #1

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
393 changes: 393 additions & 0 deletions tool_use/customer_service_agent.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,393 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "9e977e89-045f-474f-948f-84e5db2460eb",
"metadata": {},
"source": [
"# Creating a Customer Service Agent with Client-Side Tools\n",
"In this recipe, we'll demonstrate how to create a customer service chatbot using Claude 3 plus client-side tools. The chatbot will be able to look up customer information, retrieve order details, and cancel orders on behalf of the customer. We'll define the necessary tools and simulate synthetic responses to showcase the chatbot's capabilities."
]
},
{
"cell_type": "markdown",
"id": "f9f0ef18-e7fa-4cdc-aa99-5ca8e84d3d1f",
"metadata": {},
"source": [
"## Step 1: Set up the environment\n",
"First, let's install the anthropic-rb gem."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5851a1d6-b938-4d79-9673-d1ddca200a06",
"metadata": {},
"outputs": [],
"source": [
"require \"bundler/inline\"\n",
"\n",
"gemfile do\n",
" source \"https://rubygems.org\"\n",
"\n",
" gem \"anthropic-rb\"\n",
"end"
]
},
{
"cell_type": "markdown",
"id": "5f1be6a5-3ce4-4ba9-99b1-963ee686c81d",
"metadata": {},
"source": [
"Then, we'll initialize the library with our API key."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "6798777b-9bf3-4f28-84fa-3311e4d9687f",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{:completions=>[{\"version\"=>\"2023-06-01\", \"endpoint\"=>\"/v1/complete\", \"schema\"=>{\"type\"=>\"object\", \"required\"=>[\"model\", \"prompt\", \"max_tokens_to_sample\"], \"properties\"=>{\"model\"=>{\"type\"=>\"string\"}, \"prompt\"=>{\"type\"=>\"string\"}, \"max_tokens_to_sample\"=>{\"type\"=>\"integer\"}, \"stop_sequences\"=>{\"type\"=>\"array\", \"items\"=>{\"type\"=>\"string\"}}, \"temperature\"=>{\"type\"=>\"number\"}, \"top_k\"=>{\"type\"=>\"integer\"}, \"top_p\"=>{\"type\"=>\"number\"}, \"metadata\"=>{\"type\"=>\"object\"}, \"stream\"=>{\"type\"=>\"boolean\"}}, \"additionalProperties\"=>false}}], :messages=>[{\"version\"=>\"2023-06-01\", \"endpoint\"=>\"/v1/messages\", \"schema\"=>{\"type\"=>\"object\", \"required\"=>[\"model\", \"messages\", \"max_tokens\"], \"properties\"=>{\"model\"=>{\"type\"=>\"string\"}, \"messages\"=>{\"type\"=>\"array\"}, \"max_tokens\"=>{\"type\"=>\"integer\"}, \"system\"=>{\"type\"=>\"string\"}, \"stop_sequences\"=>{\"type\"=>\"array\", \"items\"=>{\"type\"=>\"string\"}}, \"temperature\"=>{\"type\"=>\"number\"}, \"top_k\"=>{\"type\"=>\"integer\"}, \"top_p\"=>{\"type\"=>\"number\"}, \"metadata\"=>{\"type\"=>\"object\"}, \"stream\"=>{\"type\"=>\"boolean\"}}, \"additionalProperties\"=>false}}]}"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"require 'anthropic'\n",
"\n",
"Anthropic.setup do |config|\n",
" config.api_key = ENV.fetch('ANTHROPIC_API_KEY')\n",
"end"
]
},
{
"cell_type": "markdown",
"id": "9a386c4f-5b21-44c6-8725-f9dc4bb56922",
"metadata": {},
"source": [
"## Step 2: Define the client-side tools\n",
"Next, we'll define the client-side tools that our chatbot will use to assist customers. We'll create three tools: get_customer_info, get_order_details, and cancel_order."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "3a508503-a45e-40af-80b5-289ac7c91b8e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{:name=>\"get_customer_info\", :description=>\"Retrieves customer information based on their customer ID. Returns the customer's name, email, and phone number.\", :input_schema=>{:type=>\"object\", :properties=>{:customer_id=>{:type=>\"string\", :description=>\"The unique identifier for the customer.\"}}, :required=>[\"customer_id\"]}}, {:name=>\"get_order_details\", :description=>\"Retrieves the details of a specific order based on the order ID. Returns the order ID, product name, quantity, price, and order status.\", :input_schema=>{:type=>\"object\", :properties=>{:order_id=>{:type=>\"string\", :description=>\"The unique identifier for the order.\"}}, :required=>[\"order_id\"]}}, {:name=>\"cancel_order\", :description=>\"Cancels an order based on the provided order ID. Returns a confirmation message if the cancellation is successful.\", :input_schema=>{:type=>\"object\", :properties=>{:order_id=>{:type=>\"string\", :description=>\"The unique identifier for the order to be cancelled.\"}}, :required=>[\"order_id\"]}}]"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"@tools = [\n",
" {\n",
" name: \"get_customer_info\",\n",
" description: \"Retrieves customer information based on their customer ID. Returns the customer's name, email, and phone number.\",\n",
" input_schema: {\n",
" type: \"object\",\n",
" properties: {\n",
" customer_id: {\n",
" type: \"string\",\n",
" description: \"The unique identifier for the customer.\"\n",
" }\n",
" },\n",
" required: [\"customer_id\"]\n",
" }\n",
" },\n",
" {\n",
" name: \"get_order_details\",\n",
" description: \"Retrieves the details of a specific order based on the order ID. Returns the order ID, product name, quantity, price, and order status.\",\n",
" input_schema: {\n",
" type: \"object\",\n",
" properties: {\n",
" order_id: {\n",
" type: \"string\",\n",
" description: \"The unique identifier for the order.\"\n",
" }\n",
" },\n",
" required: [\"order_id\"]\n",
" }\n",
" },\n",
" {\n",
" name: \"cancel_order\",\n",
" description: \"Cancels an order based on the provided order ID. Returns a confirmation message if the cancellation is successful.\",\n",
" input_schema: {\n",
" type: \"object\",\n",
" properties: {\n",
" order_id: {\n",
" type: \"string\",\n",
" description: \"The unique identifier for the order to be cancelled.\"\n",
" }\n",
" },\n",
" required: [\"order_id\"]\n",
" }\n",
" }\n",
"]"
]
},
{
"cell_type": "markdown",
"id": "e39b82d0-c0f4-4732-98d1-1b07b5958ffd",
"metadata": {},
"source": [
"## Step 3: Simulate synthetic tool responses\n",
"Since we don't have real customer data or order information, we'll simulate synthetic responses for our tools. In a real-world scenario, these functions would interact with your actual customer database and order management system."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "319fa7ad-bbe5-41c5-ad60-b04d929ee9b0",
"metadata": {},
"outputs": [],
"source": [
"def get_customer_info(customer_id)\n",
" # Simulated customer data\n",
" customers = {\n",
" \"C1\" => { name: \"John Doe\", email: \"[email protected]\", phone: \"123-456-7890\" },\n",
" \"C2\" => { name: \"Jane Smith\", email: \"[email protected]\", phone: \"987-654-3210\" }\n",
" }\n",
" customers.fetch(customer_id, \"Customer not found\")\n",
"end\n",
"\n",
"def get_order_details(order_id)\n",
" # Simulated order data\n",
" orders = {\n",
" \"O1\" => { id: \"O1\", product: \"Widget A\", quantity: 2, price: 19.99, status: \"Shipped\" },\n",
" \"O2\" => { id: \"O2\", product: \"Gadget B\", quantity: 1, price: 49.99, status: \"Processing\" }\n",
" }\n",
" orders.fetch(order_id, \"Order not found\")\n",
"end\n",
"\n",
"def cancel_order(order_id)\n",
" # Simulated order cancellation\n",
" [\"O1\", \"O2\"].include?(order_id)\n",
"end"
]
},
{
"cell_type": "markdown",
"id": "737484a2-8ead-4ed6-b38d-b3d5a812a168",
"metadata": {},
"source": [
"## Step 4: Process tool calls and return results\n",
"We'll create a function to process the tool calls made by Claude and return the appropriate results."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "8a36ff4a-3e85-429a-b9bf-bd466dc818f4",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
":process_tool_call"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def process_tool_call(tool_name, tool_input)\n",
" case tool_name\n",
" when \"get_customer_info\"\n",
" get_customer_info(tool_input[\"customer_id\"])\n",
" when \"get_order_details\"\n",
" get_order_details(tool_input[\"order_id\"])\n",
" when \"cancel_order\"\n",
" cancel_order(tool_input[\"order_id\"])\n",
" end\n",
"end"
]
},
{
"cell_type": "markdown",
"id": "7164f7af-3afa-4c89-a0fd-eee957f09434",
"metadata": {},
"source": [
"## Step 5: Interact with the chatbot\n",
"Now, let's create a function to interact with the chatbot. We'll send a user message, process any tool calls made by Claude, and return the final response to the user."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "8056e84d-8762-417b-a6b2-dc902f0d207d",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
":chatbot_interaction"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def chatbot_interaction(user_message)\n",
" puts(\"#{'=' * 50}\\nUser Message: #{user_message}\\n#{'=' * 50}\")\n",
" message = Anthropic.messages(beta: 'tools-2024-04-04').create(\n",
" model: 'claude-3-opus-20240229',\n",
" max_tokens: 200,\n",
" tools: @tools,\n",
" messages: [{ role: 'user', content: user_message }]\n",
" )\n",
" puts(\"\\nInitial Response:\")\n",
" puts(\"Stop Reason: #{message[:stop_reason]}\")\n",
" puts(\"Content: #{message[:content]}\")\n",
"\n",
" if message[:stop_reason] == 'tool_use'\n",
" tool_use_block = message[:content].find { |block| block[:type] == 'tool_use' }\n",
" puts(\"\\nTool Used: #{tool_use_block[:name]}\")\n",
" puts(\"Tool Input: #{tool_use_block[:input]}\")\n",
" tool_result = process_tool_call(tool_use_block[:name], tool_use_block[:input]) \n",
" puts(\"Tool Result: #{tool_result}\")\n",
" response = Anthropic.messages(beta: 'tools-2024-04-04').create(\n",
" model: 'claude-3-opus-20240229',\n",
" max_tokens: 200,\n",
" tools: @tools,\n",
" messages: [\n",
" { role: 'user', content: user_message },\n",
" { role: 'assistant', content: message[:content] },\n",
" {\n",
" role: 'user',\n",
" content: [\n",
" {\n",
" type: 'tool_result',\n",
" tool_use_id: tool_use_block[:id],\n",
" content: tool_result\n",
" }\n",
" ]\n",
" }\n",
" ]\n",
" )\n",
" puts(\"\\nFinal Response: #{response[:content]}\\n\")\n",
" end\n",
"end"
]
},
{
"cell_type": "markdown",
"id": "389802a4-2c28-45bd-83fd-421284da0a51",
"metadata": {},
"source": [
"## Step 6: Test the chatbot\n",
"Let's test our customer service chatbot with a few sample queries."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "585511b1-0f45-4167-afb5-0e2028dd99b9",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"==================================================\n",
"User Message: Can you tell me the email address for customer C1?\n",
"==================================================\n",
"\n",
"Initial Response:\n",
"Stop Reason: tool_use\n",
"Content: [{:type=>\"text\", :text=>\"<thinking>\\nThe user is asking for the email address of a specific customer with the ID \\\"C1\\\". To retrieve this information, the get_customer_info function would be the most relevant tool, as it returns the customer's name, email, and phone number given a customer ID.\\n\\nThe get_customer_info function requires a single parameter:\\n- customer_id (string): The customer ID is directly provided by the user as \\\"C1\\\".\\n\\nSince the required customer_id parameter is present in the user's request, we can proceed with calling the get_customer_info function.\\n</thinking>\"}, {:type=>\"tool_use\", :id=>\"toolu_013hGH56hiMPa4CWMeicrNKg\", :name=>\"get_customer_info\", :input=>{:customer_id=>\"C1\"}}]\n",
"\n",
"Tool Used: get_customer_info\n",
"Tool Input: {:customer_id=>\"C1\"}\n",
"Tool Result: Customer not found\n",
"\n",
"Final Response: [{:type=>\"text\", :text=>\"Based on the function result, there is no customer with the ID \\\"C1\\\" in the system. I apologize, but I was unable to retrieve the email address you requested because no customer exists with the ID \\\"C1\\\". Please double check the customer ID and let me know if you have any other questions!\"}]\n",
"==================================================\n",
"User Message: What is the status of order O2?\n",
"==================================================\n",
"\n",
"Initial Response:\n",
"Stop Reason: tool_use\n",
"Content: [{:type=>\"text\", :text=>\"<thinking>\\nThe relevant function to answer this request is get_order_details, which retrieves the details of a specific order based on the order ID. \\n\\nThe get_order_details function requires one parameter:\\n- order_id (string): The unique identifier for the order. \\n\\nThe user has directly provided the value for this parameter in their request - they specified order ID \\\"O2\\\".\\n\\nSince the required order_id parameter has been provided, we can proceed with calling the get_order_details function.\\n</thinking>\"}, {:type=>\"tool_use\", :id=>\"toolu_01NJxnAVHv8Xv3t67o3rqnXX\", :name=>\"get_order_details\", :input=>{:order_id=>\"O2\"}}]\n",
"\n",
"Tool Used: get_order_details\n",
"Tool Input: {:order_id=>\"O2\"}\n",
"Tool Result: Order not found\n",
"\n",
"Final Response: [{:type=>\"text\", :text=>\"Based on the result from calling the get_order_details function, the system was unable to find an order with the ID \\\"O2\\\". So unfortunately I don't have any information about the status of that order. It's possible the order ID provided was incorrect. Please double check the order ID and provide the correct one if you'd like me to look up the order status again.\"}]\n",
"==================================================\n",
"User Message: Please cancel order O1 for me.\n",
"==================================================\n",
"\n",
"Initial Response:\n",
"Stop Reason: tool_use\n",
"Content: [{:type=>\"text\", :text=>\"<thinking>\\nThe relevant tool to cancel an order is the cancel_order function.\\n\\nThe cancel_order function requires one parameter:\\norder_id - This is provided by the user, which is \\\"O1\\\"\\n\\nSince the required parameter is provided, we can proceed with calling the cancel_order function.\\n</thinking>\"}, {:type=>\"tool_use\", :id=>\"toolu_01BdadjUireisfJxxWtyui9u\", :name=>\"cancel_order\", :input=>{:order_id=>\"O1\"}}]\n",
"\n",
"Tool Used: cancel_order\n",
"Tool Input: {:order_id=>\"O1\"}\n",
"Tool Result: false\n"
]
},
{
"ename": "Anthropic::Errors::BadRequestError",
"evalue": "{:type=>\"error\", :error=>{:type=>\"invalid_request_error\", :message=>\"messages.2.content.0.tool_result.content: Found a boolean, but `tool_result` content must either be a string or a list of content blocks.\"}}",
"output_type": "error",
"traceback": [
"\u001b[31mAnthropic::Errors::BadRequestError\u001b[0m: {:type=>\"error\", :error=>{:type=>\"invalid_request_error\", :message=>\"messages.2.content.0.tool_result.content: Found a boolean, but `tool_result` content must either be a string or a list of content blocks.\"}}",
"/Users/dick/.asdf/installs/ruby/3.2.2/lib/ruby/gems/3.2.0/gems/anthropic-rb-0.5.0/lib/anthropic/client.rb:25:in `post'",
"/Users/dick/.asdf/installs/ruby/3.2.2/lib/ruby/gems/3.2.0/gems/anthropic-rb-0.5.0/lib/anthropic/api/concerns/requestable.rb:10:in `post'",
"/Users/dick/.asdf/installs/ruby/3.2.2/lib/ruby/gems/3.2.0/gems/anthropic-rb-0.5.0/lib/anthropic/api/messages.rb:15:in `create'",
"(irb):18:in `chatbot_interaction'",
"(irb):2:in `<top (required)>'"
]
}
],
"source": [
"chatbot_interaction(\"Can you tell me the email address for customer C1?\")\n",
"chatbot_interaction(\"What is the status of order O2?\")\n",
"chatbot_interaction(\"Please cancel order O1 for me.\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "804ce260-fec0-4929-bd21-bac4ac03223f",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Ruby 3.2.2",
"language": "ruby",
"name": "ruby"
},
"language_info": {
"file_extension": ".rb",
"mimetype": "application/x-ruby",
"name": "ruby",
"version": "3.2.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}