donb-hf commited on
Commit
cca16c9
β€’
1 Parent(s): 6de6c8c

update tool handler

Browse files
.gitignore CHANGED
@@ -1,2 +1,4 @@
1
  .venv/
2
- .env
 
 
 
1
  .venv/
2
+ .env
3
+ __pycache__/config.cpython-310.pyc
4
+ __pycache__/tool_handler.cpython-310.pyc
app.py CHANGED
@@ -1,10 +1,9 @@
1
  import gradio as gr
2
  import anthropic
3
  import json
4
- import requests
5
- import warnings
6
  import logging
7
- import os
 
8
  from datasets import load_dataset
9
  import pandas as pd
10
  from dotenv import load_dotenv
@@ -17,154 +16,7 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(
17
  logger = logging.getLogger(__name__)
18
 
19
  # Initialize Anthropoc client with API key
20
- client = anthropic.Client(api_key=os.getenv('ANTHROPIC_API_KEY'))
21
- MODEL_NAME = "claude-3-5-sonnet-20240620"
22
-
23
- # Define the base URL for the FastAPI service
24
- BASE_URL = "https://dwb2023-blackbird-svc.hf.space"
25
-
26
- # Define tools
27
- tools = [
28
- {
29
- "name": "get_user",
30
- "description": "Looks up a user by email, phone, or username.",
31
- "input_schema": {
32
- "type": "object",
33
- "properties": {
34
- "key": {
35
- "type": "string",
36
- "enum": ["email", "phone", "username"],
37
- "description": "The attribute to search for a user by (email, phone, or username)."
38
- },
39
- "value": {
40
- "type": "string",
41
- "description": "The value to match for the specified attribute."
42
- }
43
- },
44
- "required": ["key", "value"]
45
- }
46
- },
47
- {
48
- "name": "get_order_by_id",
49
- "description": "Retrieves the details of a specific order based on the order ID.",
50
- "input_schema": {
51
- "type": "object",
52
- "properties": {
53
- "order_id": {
54
- "type": "string",
55
- "description": "The unique identifier for the order."
56
- }
57
- },
58
- "required": ["order_id"]
59
- }
60
- },
61
- {
62
- "name": "get_customer_orders",
63
- "description": "Retrieves the list of orders belonging to a user based on a user's customer id.",
64
- "input_schema": {
65
- "type": "object",
66
- "properties": {
67
- "customer_id": {
68
- "type": "string",
69
- "description": "The customer_id belonging to the user"
70
- }
71
- },
72
- "required": ["customer_id"]
73
- }
74
- },
75
- {
76
- "name": "cancel_order",
77
- "description": "Cancels an order based on a provided order_id. Only orders that are 'processing' can be cancelled.",
78
- "input_schema": {
79
- "type": "object",
80
- "properties": {
81
- "order_id": {
82
- "type": "string",
83
- "description": "The order_id pertaining to a particular order"
84
- }
85
- },
86
- "required": ["order_id"]
87
- }
88
- },
89
- {
90
- "name": "update_user_contact",
91
- "description": "Updates a user's email and/or phone number.",
92
- "input_schema": {
93
- "type": "object",
94
- "properties": {
95
- "user_id": {
96
- "type": "string",
97
- "description": "The ID of the user"
98
- },
99
- "email": {
100
- "type": "string",
101
- "description": "The new email address of the user"
102
- },
103
- "phone": {
104
- "type": "string",
105
- "description": "The new phone number of the user"
106
- }
107
- },
108
- "required": ["user_id"]
109
- }
110
- },
111
- {
112
- "name": "get_user_info",
113
- "description": "Retrieves a user's information along with their order history based on email, phone, or username.",
114
- "input_schema": {
115
- "type": "object",
116
- "properties": {
117
- "key": {
118
- "type": "string",
119
- "enum": ["email", "phone", "username"],
120
- "description": "The attribute to search for a user by (email, phone, or username)."
121
- },
122
- "value": {
123
- "type": "string",
124
- "description": "The value to match for the specified attribute."
125
- }
126
- },
127
- "required": ["key", "value"]
128
- }
129
- }
130
- ]
131
-
132
- # Suppress the InsecureRequestWarning
133
- warnings.filterwarnings("ignore", category=requests.urllib3.exceptions.InsecureRequestWarning)
134
-
135
- def process_tool_call(tool_name, tool_input):
136
- tool_endpoints = {
137
- "get_user": "get_user",
138
- "get_order_by_id": "get_order_by_id",
139
- "get_customer_orders": "get_customer_orders",
140
- "cancel_order": "cancel_order",
141
- "update_user_contact": "update_user",
142
- "get_user_info": "get_user_info"
143
- }
144
-
145
- if tool_name in tool_endpoints:
146
- response = requests.post(f"{BASE_URL}/{tool_endpoints[tool_name]}", json=tool_input, verify=False)
147
- else:
148
- logger.error(f"Invalid tool name: {tool_name}")
149
- return {"error": "Invalid tool name"}
150
-
151
- if response.status_code == 200:
152
- return response.json()
153
- else:
154
- logger.error(f"Tool call failed: {response.text}")
155
- return {"error": response.text}
156
-
157
- system_prompt = """
158
- You are a customer support chat bot for an online retailer called BlackBird.
159
- Your job is to help users look up their account, orders, and cancel orders.
160
- Be helpful and brief in your responses.
161
- You have access to a set of tools, but only use them when needed.
162
- If you do not have enough information to use a tool correctly, ask a user follow up questions to get the required inputs.
163
- Do not call any of the tools unless you have the required data from a user.
164
-
165
- In each conversational turn, you will begin by thinking about your response.
166
- Once you're done, you will write a user-facing response.
167
- """
168
 
169
  def simple_chat(user_message, history):
170
  # Reconstruct the message history
@@ -180,15 +32,16 @@ def simple_chat(user_message, history):
180
 
181
  while iteration_count < MAX_ITERATIONS:
182
  try:
183
- logger.info(f"Sending messages to API: {json.dumps(messages, indent=2)}")
184
  response = client.messages.create(
185
  model=MODEL_NAME,
186
- system=system_prompt,
187
  max_tokens=4096,
188
  tools=tools,
189
  messages=messages,
190
  )
191
-
 
192
  assistant_message = response.content[0].text if isinstance(response.content, list) else response.content
193
 
194
  if response.stop_reason == "tool_use":
@@ -273,10 +126,12 @@ def load_orders_dataset():
273
  return df
274
 
275
  example_inputs = [
276
- "What's the status of my orders? My Customer id is 2837622",
277
- "Can you confirm my customer info and order status? My email is new_email@example.com",
278
- "I'd like to cancel an order",
279
- "Can you update my email address to newemail@example.com?",
 
 
280
  ]
281
 
282
  # Create Gradio App
@@ -285,15 +140,20 @@ app = gr.Blocks(theme="sudeepshouche/minimalist")
285
  with app:
286
  with gr.Tab("Chatbot"):
287
  gr.Markdown("# BlackBird Customer Support Chat")
 
 
 
 
288
  with gr.Row():
289
  with gr.Column():
290
  msg = gr.Textbox(label="Your message")
291
- submit = gr.Button("Submit", variant="primary")
292
- clear = gr.Button("Clear", variant="secondary")
293
  examples = gr.Examples(
294
  examples=example_inputs,
295
  inputs=msg
296
  )
 
 
297
  with gr.Column():
298
  chatbot = gr.Chatbot()
299
 
@@ -318,6 +178,5 @@ with app:
318
  with gr.Tab("Orders"):
319
  orders_df = gr.Dataframe(load_orders_dataset(), label="Orders Data")
320
 
321
-
322
  if __name__ == "__main__":
323
  app.launch()
 
1
  import gradio as gr
2
  import anthropic
3
  import json
 
 
4
  import logging
5
+ from tool_handler import process_tool_call, tools
6
+ from config import SYSTEM_PROMPT, API_KEY, MODEL_NAME
7
  from datasets import load_dataset
8
  import pandas as pd
9
  from dotenv import load_dotenv
 
16
  logger = logging.getLogger(__name__)
17
 
18
  # Initialize Anthropoc client with API key
19
+ client = anthropic.Client(api_key=API_KEY)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  def simple_chat(user_message, history):
22
  # Reconstruct the message history
 
32
 
33
  while iteration_count < MAX_ITERATIONS:
34
  try:
35
+ logger.info(f"Sending messages to LLM API: {json.dumps(messages, indent=2)}")
36
  response = client.messages.create(
37
  model=MODEL_NAME,
38
+ system=SYSTEM_PROMPT,
39
  max_tokens=4096,
40
  tools=tools,
41
  messages=messages,
42
  )
43
+ logger.info(f"LLM API response: {json.dumps(response.to_dict(), indent=2)}")
44
+
45
  assistant_message = response.content[0].text if isinstance(response.content, list) else response.content
46
 
47
  if response.stop_reason == "tool_use":
 
126
  return df
127
 
128
  example_inputs = [
129
+ "Can you lookup my user id? My email is...",
130
+ "I'm checking on the status of an order, the order id is...",
131
+ "Can you send me a list of my recent orders? My customer id is...",
132
+ "I need to cancel Order ID...",
133
+ "I lost my phone and need to update my contact information. My user id is...",
134
+ "I need to confirm my current user info and order status. My email is...",
135
  ]
136
 
137
  # Create Gradio App
 
140
  with app:
141
  with gr.Tab("Chatbot"):
142
  gr.Markdown("# BlackBird Customer Support Chat")
143
+ gr.Markdown("## leveraging **Claude Sonnet 3.5** for microservice-based function calling")
144
+ gr.Markdown("FastAPI Backend - runing on Docker: [blackbird-svc](https://huggingface.co/spaces/dwb2023/blackbird-svc)")
145
+ gr.Markdown("Data Sources - HF Datasets: [blackbird-customers](https://huggingface.co/datasets/dwb2023/blackbird-customers) [blackbird-orders](https://huggingface.co/datasets/dwb2023/blackbird-orders)")
146
+
147
  with gr.Row():
148
  with gr.Column():
149
  msg = gr.Textbox(label="Your message")
150
+ gr.Markdown("⬆️ checkout the *Customers* and *Orders* tabs above πŸ‘† for sample email addresses, order ids, etc.*")
 
151
  examples = gr.Examples(
152
  examples=example_inputs,
153
  inputs=msg
154
  )
155
+ submit = gr.Button("Submit", variant="primary")
156
+ clear = gr.Button("Clear", variant="secondary")
157
  with gr.Column():
158
  chatbot = gr.Chatbot()
159
 
 
178
  with gr.Tab("Orders"):
179
  orders_df = gr.Dataframe(load_orders_dataset(), label="Orders Data")
180
 
 
181
  if __name__ == "__main__":
182
  app.launch()
config.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # config.py
2
+ import os
3
+ from dotenv import load_dotenv
4
+
5
+ load_dotenv()
6
+
7
+ # Define the base URL for the FastAPI service
8
+ BASE_URL = "https://dwb2023-blackbird-svc.hf.space"
9
+
10
+ # LLM Config
11
+ API_KEY = os.getenv('ANTHROPIC_API_KEY')
12
+ MODEL_NAME = "claude-3-5-sonnet-20240620"
13
+
14
+ SYSTEM_PROMPT = """
15
+ You are a customer support chat bot for an online retailer called Blackbird.
16
+ Your job is to help users look up their account, orders, and cancel orders.
17
+ Be helpful and brief in your responses.
18
+ You have access to a set of tools, but only use them when needed.
19
+ If you do not have enough information to use a tool correctly, ask a user follow up questions to get the required inputs.
20
+ Do not call any of the tools unless you have the required data from a user.
21
+
22
+ In each conversational turn, you will begin by thinking about your response.
23
+ Once you're done, you will write a user-facing response.
24
+ It's important to place all user-facing conversational responses in <reply></reply> XML tags to make them easy to parse.
25
+ """
response_formatter.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # response_formatter.py
2
+ # may only be needed when using Claude Opus
3
+ # we are using Claude Sonnet 3.5
4
+ def extract_reply(reply):
5
+ start_tag = "<reply>"
6
+ end_tag = "</reply>"
7
+ start_index = reply.find(start_tag) + len(start_tag)
8
+ end_index = reply.find(end_tag)
9
+ if start_index != -1 and end_index != -1:
10
+ return reply[start_index:end_index].strip()
11
+ return reply
function_orchestrator.py β†’ tool_handler.py RENAMED
@@ -1,18 +1,18 @@
1
- import anthropic
2
- import json
3
- import re
4
- import requests
5
 
6
- client = anthropic.Client()
7
- MODEL_NAME = "claude-3-sonnet-20240229"
 
8
 
9
- # Define the base URL for the FastAPI service
10
- BASE_URL = "https://huggingface.co/spaces/dwb2023/blackbird-svc:7860"
11
 
12
  # Define tools
13
  tools = [
14
  {
15
- "name": "get_user"
16
  "description": "Looks up a user by email, phone, or username.",
17
  "input_schema": {
18
  "type": "object",
@@ -115,87 +115,26 @@ tools = [
115
  }
116
  ]
117
 
118
- # Function to process tool calls
119
  def process_tool_call(tool_name, tool_input):
120
- if tool_name == "get_user":
121
- response = requests.post(f"{BASE_URL}/get_user", json=tool_input)
122
- elif tool_name == "get_order_by_id":
123
- response = requests.post(f"{BASE_URL}/get_order_by_id", json=tool_input)
124
- elif tool_name == "get_customer_orders":
125
- response = requests.post(f"{BASE_URL}/get_customer_orders", json=tool_input)
126
- elif tool_name == "cancel_order":
127
- response = requests.post(f"{BASE_URL}/cancel_order", json=tool_input)
128
- elif tool_name == "update_user_contact":
129
- response = requests.post(f"{BASE_URL}/update_user", json=tool_input)
130
- elif tool_name == "get_user_info":
131
- response = requests.post(f"{BASE_URL}/get_user_info", json=tool_input)
132
  else:
 
133
  return {"error": "Invalid tool name"}
134
 
135
  if response.status_code == 200:
 
136
  return response.json()
137
  else:
 
138
  return {"error": response.text}
139
-
140
- # Function to handle interactive chat session
141
- def simple_chat():
142
- system_prompt = """
143
- You are a customer support chat bot for an online retailer called TechNova.
144
- Your job is to help users look up their account, orders, and cancel orders.
145
- Be helpful and brief in your responses.
146
- You have access to a set of tools, but only use them when needed.
147
- If you do not have enough information to use a tool correctly, ask a user follow up questions to get the required inputs.
148
- Do not call any of the tools unless you have the required data from a user.
149
-
150
- In each conversational turn, you will begin by thinking about your response.
151
- Once you're done, you will write a user-facing response.
152
- """
153
- user_message = input("\nUser: ")
154
- messages = [{"role": "user", "content": user_message}]
155
- while True:
156
- #If the last message is from the assistant, get another input from the user
157
- if messages[-1].get("role") == "assistant":
158
- user_message = input("\nUser: ")
159
- messages.append({"role": "user", "content": user_message})
160
-
161
- #Send a request to Claude
162
- response = client.messages.create(
163
- model=MODEL_NAME,
164
- max_tokens=4096,
165
- tools=tools,
166
- messages=messages
167
- )
168
- # Update messages to include Claude's response
169
- messages.append(
170
- {"role": "assistant", "content": response.content}
171
- )
172
-
173
- #If Claude stops because it wants to use a tool:
174
- if response.stop_reason == "tool_use":
175
- tool_use = response.content[-1] #Naive approach assumes only 1 tool is called at a time
176
- tool_name = tool_use.name
177
- tool_input = tool_use.input
178
- print(f"======Claude wants to use the {tool_name} tool======")
179
-
180
- #Actually run the underlying tool functionality on our db
181
- tool_result = process_tool_call(tool_name, tool_input)
182
-
183
- #Add our tool_result message:
184
- messages.append(
185
- {
186
- "role": "user",
187
- "content": [
188
- {
189
- "type": "tool_result",
190
- "tool_use_id": tool_use.id,
191
- "content": str(tool_result),
192
- }
193
- ],
194
- },
195
- )
196
- else:
197
- #If Claude does NOT want to use a tool, just print out the text reponse
198
- print("\nTechNova Support: " + f"{response.content[0].text}" )
199
-
200
- # Start the chat!!
201
- simple_chat()
 
1
+ # tool_handler.py
2
+ import warnings, logging, requests
3
+ from config import BASE_URL
 
4
 
5
+ # Configure logging
6
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
7
+ logger = logging.getLogger(__name__)
8
 
9
+ # Suppress the InsecureRequestWarning
10
+ warnings.filterwarnings("ignore", category=requests.urllib3.exceptions.InsecureRequestWarning)
11
 
12
  # Define tools
13
  tools = [
14
  {
15
+ "name": "get_user",
16
  "description": "Looks up a user by email, phone, or username.",
17
  "input_schema": {
18
  "type": "object",
 
115
  }
116
  ]
117
 
 
118
  def process_tool_call(tool_name, tool_input):
119
+ tool_endpoints = {
120
+ "get_user": "get_user",
121
+ "get_order_by_id": "get_order_by_id",
122
+ "get_customer_orders": "get_customer_orders",
123
+ "cancel_order": "cancel_order",
124
+ "update_user_contact": "update_user",
125
+ "get_user_info": "get_user_info"
126
+ }
127
+
128
+ if tool_name in tool_endpoints:
129
+ logger.info(f"tool_handler Calling tool: {tool_name}")
130
+ response = requests.post(f"{BASE_URL}/{tool_endpoints[tool_name]}", json=tool_input, verify=False)
131
  else:
132
+ logger.error(f"tool_handle Invalid tool name: {tool_name}")
133
  return {"error": "Invalid tool name"}
134
 
135
  if response.status_code == 200:
136
+ logger.info(f"tool_handler Tool call successful: {response.json()}")
137
  return response.json()
138
  else:
139
+ logger.error(f"tool_handler Tool call failed: {response.text}")
140
  return {"error": response.text}