import os
# os.environ['ANTHROPIC_LOG'] = 'debug'
Tool loop
= models[-1] model
Anthropic provides an interesting example of using tools to mock up a hypothetical ordering system. We’re going to take it a step further, and show how we can dramatically simplify the process, whilst completing more complex tasks.
We’ll start by defining the same mock customer/order data as in Anthropic’s example, plus create a entity relationship between customers and orders:
def _get_orders_customers():
= {
orders "O1": dict(id="O1", product="Widget A", quantity=2, price=19.99, status="Shipped"),
"O2": dict(id="O2", product="Gadget B", quantity=1, price=49.99, status="Processing"),
"O3": dict(id="O3", product="Gadget B", quantity=2, price=49.99, status="Shipped")}
= {
customers "C1": dict(name="John Doe", email="[email protected]", phone="123-456-7890",
=[orders['O1'], orders['O2']]),
orders"C2": dict(name="Jane Smith", email="[email protected]", phone="987-654-3210",
=[orders['O3']])
orders
}return orders, customers
= _get_orders_customers() orders, customers
We can now define the same functions from the original example – but note that we don’t need to manually create the large JSON schema, since Claudette handles all that for us automatically from the functions directly. We’ll add some extra functionality to update order details when cancelling too.
def get_customer_info(
str # ID of the customer
customer_id:# Customer's name, email, phone number, and list of orders
): "Retrieves a customer's information and their orders based on the customer ID"
print(f'- Retrieving customer {customer_id}')
return customers.get(customer_id, "Customer not found")
def get_order_details(
str # ID of the order
order_id:# Order's ID, product name, quantity, price, and order status
): "Retrieves the details of a specific order based on the order ID"
print(f'- Retrieving order {order_id}')
return orders.get(order_id, "Order not found")
def cancel_order(
str # ID of the order to cancel
order_id:->bool: # True if the cancellation is successful
)"Cancels an order based on the provided order ID"
print(f'- Cancelling order {order_id}')
if order_id not in orders: return False
'status'] = 'Cancelled'
orders[order_id][return True
We’re now ready to start our chat.
= [get_customer_info, get_order_details, cancel_order]
tools = Chat(model, tools=tools) chat
We’ll start with the same request as Anthropic showed:
= chat('Can you tell me the email address for customer C1?')
r print(r.stop_reason)
r.content
- Retrieving customer C1
tool_use
[TextBlock(citations=None, text="I'll retrieve the customer information for customer ID C1.", type='text'),
ToolUseBlock(id='toolu_019SmCuZok1LjsZJMMu5aCyd', input={'customer_id': 'C1'}, name='get_customer_info', type='tool_use')]
Claude asks us to use a tool. Claudette handles that automatically by just calling it again:
= chat()
r contents(r)
'The email address for customer C1 (John Doe) is [email protected].\n\nIs there anything else I can help you with?'
Let’s consider a more complex case than in the original example – what happens if a customer wants to cancel all of their orders?
= Chat(model, tools=tools)
chat = chat('Please cancel all orders for customer C1 for me.')
r print(r.stop_reason)
r.content
- Retrieving customer C1
tool_use
[TextBlock(citations=None, text="I'll help you cancel all orders for customer C1. To do this, I'll first retrieve the customer's information to get their order details, and then cancel each order.", type='text'),
ToolUseBlock(id='toolu_011Xdbmm7FyVaZHgLLgKmiJN', input={'customer_id': 'C1'}, name='get_customer_info', type='tool_use')]
This is the start of a multi-stage tool use process. Doing it manually step by step is inconvenient, so let’s write a function to handle this for us:
Chat.toolloop
Chat.toolloop (pr, max_steps=10, trace_func:Optional[<built- infunctioncallable>]=None, cont_func:Optional[<built- infunctioncallable>]=<function noop>, final_prompt='You have no more tool uses. Please summarize your findings. If you did not complete your goal please tell the user what further work needs to be done so they can choose how best to proceed.', temp=None, maxtok=4096, maxthinktok=0, stream=False, prefill='', tool_choice:Optional[dict]=None)
Add prompt pr
to dialog and get a response from Claude, automatically following up with tool_use
messages
Type | Default | Details | |
---|---|---|---|
pr | Prompt to pass to Claude | ||
max_steps | int | 10 | Maximum number of tool requests to loop through |
trace_func | Optional | None | Function to trace tool use steps (e.g print ) |
cont_func | Optional | noop | Function that stops loop if returns False |
final_prompt | str | You have no more tool uses. Please summarize your findings. If you did not complete your goal please tell the user what further work needs to be done so they can choose how best to proceed. | Prompt to add if last message is a tool call |
temp | NoneType | None | Temperature |
maxtok | int | 4096 | Maximum tokens |
maxthinktok | int | 0 | Maximum thinking tokens |
stream | bool | False | Stream response? |
prefill | str | Optional prefill to pass to Claude as start of its response | |
tool_choice | Optional | None | Optionally force use of some tool |
Exported source
= "You have no more tool uses. Please summarize your findings. If you did not complete your goal please tell the user what further work needs to be done so they can choose how best to proceed."
_final_prompt
@patch
@delegates(Chat.__call__)
def toolloop(self:Chat,
# Prompt to pass to Claude
pr, =10, # Maximum number of tool requests to loop through
max_stepscallable]=None, # Function to trace tool use steps (e.g `print`)
trace_func:Optional[callable]=noop, # Function that stops loop if returns False
cont_func:Optional[=_final_prompt, # Prompt to add if last message is a tool call
final_prompt**kwargs):
"Add prompt `pr` to dialog and get a response from Claude, automatically following up with `tool_use` messages"
= n_msgs = len(self.h)
init_n = self(pr, **kwargs)
r for i in range(max_steps):
if r.stop_reason!='tool_use': break
if trace_func: trace_func(self.h[n_msgs:]); n_msgs = len(self.h)
= self(**kwargs)
r if not (cont_func or noop)(self.h[-2]): break
if r.stop_reason == 'tool_use':
if trace_func: trace_func(self.h[n_msgs:])
= self(final_prompt, **kwargs)
r
if trace_func: trace_func(self.h[n_msgs:])
= self.h[init_n+1:]
r.steps return r
We’ll start by re-running our previous request - we shouldn’t have to manually pass back the tool_use
message any more:
= Chat(model, tools=tools)
chat = chat.toolloop('Can you tell me the email address for customer C1?')
r r
- Retrieving customer C1
The email address for customer C1 (John Doe) is [email protected].
Is there anything else I can help you with?
- id:
msg_01VEyoiUNmvEbFDw5AFFaMVq
- content:
[{'citations': None, 'text': 'The email address for customer C1 (John Doe) is [email protected].\n\nIs there anything else I can help you with?', 'type': 'text'}]
- model:
claude-3-5-haiku-20241022
- role:
assistant
- stop_reason:
end_turn
- stop_sequence:
None
- type:
message
- usage:
{'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 741, 'output_tokens': 35}
- steps:
[{'role': 'user', 'content': 'Can you tell me the email address for customer C1?'}, {'role': 'assistant', 'content': [{'citations': None, 'text': "I'll retrieve the customer information for customer ID C1 using the get_customer_info function.", 'type': 'text'}, {'id': 'toolu_017Q8cVQmUVWv972c6itoc5A', 'input': {'customer_id': 'C1'}, 'name': 'get_customer_info', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_017Q8cVQmUVWv972c6itoc5A', 'content': "{'name': 'John Doe', 'email': '[email protected]', 'phone': '123-456-7890', 'orders': [{'id': 'O1', 'product': 'Widget A', 'quantity': 2, 'price': 19.99, 'status': 'Shipped'}, {'id': 'O2', 'product': 'Gadget B', 'quantity': 1, 'price': 49.99, 'status': 'Processing'}]}"}]}, {'role': 'assistant', 'content': [{'citations': None, 'text': 'The email address for customer C1 (John Doe) is [email protected].\n\nIs there anything else I can help you with?', 'type': 'text'}]}]
Let’s see if it can handle the multi-stage process now – we’ll add trace_func=print
to see each stage of the process:
= Chat(model, tools=tools)
chat = chat.toolloop('Please cancel all orders for customer C1 for me.', trace_func=print)
r r
- Retrieving customer C1
[{'role': 'user', 'content': 'Please cancel all orders for customer C1 for me.'}, {'role': 'assistant', 'content': [{'citations': None, 'text': "I'll help you cancel all orders for customer C1. To do this, I'll first retrieve the customer's information to get their order details, and then cancel each order.", 'type': 'text'}, {'id': 'toolu_017BmxsPHqFEN5UNwRbgWWdH', 'input': {'customer_id': 'C1'}, 'name': 'get_customer_info', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_017BmxsPHqFEN5UNwRbgWWdH', 'content': "{'name': 'John Doe', 'email': '[email protected]', 'phone': '123-456-7890', 'orders': [{'id': 'O1', 'product': 'Widget A', 'quantity': 2, 'price': 19.99, 'status': 'Shipped'}, {'id': 'O2', 'product': 'Gadget B', 'quantity': 1, 'price': 49.99, 'status': 'Processing'}]}"}]}]
- Cancelling order O1
[{'role': 'assistant', 'content': [{'citations': None, 'text': "I see that customer C1 (John Doe) has two orders: O1 and O2. I'll proceed to cancel both orders:", 'type': 'text'}, {'id': 'toolu_01RNu1hY8GejETs5uhFSWJe9', 'input': {'order_id': 'O1'}, 'name': 'cancel_order', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_01RNu1hY8GejETs5uhFSWJe9', 'content': 'True'}]}]
- Cancelling order O2
[{'role': 'assistant', 'content': [{'id': 'toolu_01QoNmCbTDxWaNRmKArVaV5B', 'input': {'order_id': 'O2'}, 'name': 'cancel_order', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_01QoNmCbTDxWaNRmKArVaV5B', 'content': 'True'}]}]
[{'role': 'assistant', 'content': [{'citations': None, 'text': "I've successfully cancelled both orders (O1 and O2) for customer C1. Both cancellation attempts returned 'True', which indicates they were processed successfully. Is there anything else I can help you with?", 'type': 'text'}]}]
I’ve successfully cancelled both orders (O1 and O2) for customer C1. Both cancellation attempts returned ‘True’, which indicates they were processed successfully. Is there anything else I can help you with?
- id:
msg_01R91TNeXguiwDFAfvYkky4x
- content:
[{'citations': None, 'text': "I've successfully cancelled both orders (O1 and O2) for customer C1. Both cancellation attempts returned 'True', which indicates they were processed successfully. Is there anything else I can help you with?", 'type': 'text'}]
- model:
claude-3-5-haiku-20241022
- role:
assistant
- stop_reason:
end_turn
- stop_sequence:
None
- type:
message
- usage:
{'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 926, 'output_tokens': 49}
- steps:
[{'role': 'user', 'content': 'Please cancel all orders for customer C1 for me.'}, {'role': 'assistant', 'content': [{'citations': None, 'text': "I'll help you cancel all orders for customer C1. To do this, I'll first retrieve the customer's information to get their order details, and then cancel each order.", 'type': 'text'}, {'id': 'toolu_017BmxsPHqFEN5UNwRbgWWdH', 'input': {'customer_id': 'C1'}, 'name': 'get_customer_info', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_017BmxsPHqFEN5UNwRbgWWdH', 'content': "{'name': 'John Doe', 'email': '[email protected]', 'phone': '123-456-7890', 'orders': [{'id': 'O1', 'product': 'Widget A', 'quantity': 2, 'price': 19.99, 'status': 'Shipped'}, {'id': 'O2', 'product': 'Gadget B', 'quantity': 1, 'price': 49.99, 'status': 'Processing'}]}"}]}, {'role': 'assistant', 'content': [{'citations': None, 'text': "I see that customer C1 (John Doe) has two orders: O1 and O2. I'll proceed to cancel both orders:", 'type': 'text'}, {'id': 'toolu_01RNu1hY8GejETs5uhFSWJe9', 'input': {'order_id': 'O1'}, 'name': 'cancel_order', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_01RNu1hY8GejETs5uhFSWJe9', 'content': 'True'}]}, {'role': 'assistant', 'content': [{'id': 'toolu_01QoNmCbTDxWaNRmKArVaV5B', 'input': {'order_id': 'O2'}, 'name': 'cancel_order', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_01QoNmCbTDxWaNRmKArVaV5B', 'content': 'True'}]}, {'role': 'assistant', 'content': [{'citations': None, 'text': "I've successfully cancelled both orders (O1 and O2) for customer C1. Both cancellation attempts returned 'True', which indicates they were processed successfully. Is there anything else I can help you with?", 'type': 'text'}]}]
OK Claude thinks the orders were cancelled – let’s check one:
'What is the status of order O2?') chat.toolloop(
- Retrieving order O2
The status of order O2 is “Cancelled”. This reflects the cancellation we just performed in the previous interaction. The order was for 1 Gadget B priced at $49.99, and it is now in a cancelled state.
- id:
msg_01MokUXuzewVUESD6d4r28jz
- content:
[{'citations': None, 'text': 'The status of order O2 is "Cancelled". This reflects the cancellation we just performed in the previous interaction. The order was for 1 Gadget B priced at $49.99, and it is now in a cancelled state.', 'type': 'text'}]
- model:
claude-3-5-haiku-20241022
- role:
assistant
- stop_reason:
end_turn
- stop_sequence:
None
- type:
message
- usage:
{'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 1117, 'output_tokens': 57}
- steps:
[{'role': 'user', 'content': 'What is the status of order O2?'}, {'role': 'assistant', 'content': [{'citations': None, 'text': "I'll retrieve the details of order O2 to check its current status:", 'type': 'text'}, {'id': 'toolu_015ezjWdD2HvebTL1rELC4RQ', 'input': {'order_id': 'O2'}, 'name': 'get_order_details', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_015ezjWdD2HvebTL1rELC4RQ', 'content': "{'id': 'O2', 'product': 'Gadget B', 'quantity': 1, 'price': 49.99, 'status': 'Cancelled'}"}]}, {'role': 'assistant', 'content': [{'citations': None, 'text': 'The status of order O2 is "Cancelled". This reflects the cancellation we just performed in the previous interaction. The order was for 1 Gadget B priced at $49.99, and it is now in a cancelled state.', 'type': 'text'}]}]
If we run out of tool loops lets see what happens:
= _get_orders_customers()
orders, customers = Chat(model, tools=tools)
chat = chat.toolloop('Please cancel all orders for customer C1 and C2 for me.', max_steps=1)
r r
- Retrieving customer C1
- Cancelling order O1
- Cancelling order O2
- Retrieving customer C2
Here’s a summary of my actions:
For Customer C1 (John Doe): - Found 2 orders (O1 and O2) - Successfully canceled both orders
For Customer C2 (Jane Smith): - Found 1 order (O3) - I was unable to complete the cancellation for this order due to running out of tool uses
To complete the full task of canceling all orders for both customers, I would need additional tool uses to cancel the order O3 for Customer C2.
Recommended next steps: 1. Confirm if you want me to proceed with canceling the remaining order O3 for Customer C2 in a subsequent interaction 2. If you have urgent needs, you may want to manually cancel the remaining order or seek assistance from customer service
Would you like me to help you complete the cancellation in the next interaction?
- id:
msg_019tnxx6uMv1qRS54WPD85ac
- content:
[{'citations': None, 'text': "Here's a summary of my actions:\n\nFor Customer C1 (John Doe):\n- Found 2 orders (O1 and O2)\n- Successfully canceled both orders\n\nFor Customer C2 (Jane Smith):\n- Found 1 order (O3)\n- I was unable to complete the cancellation for this order due to running out of tool uses\n\nTo complete the full task of canceling all orders for both customers, I would need additional tool uses to cancel the order O3 for Customer C2. \n\nRecommended next steps:\n1. Confirm if you want me to proceed with canceling the remaining order O3 for Customer C2 in a subsequent interaction\n2. If you have urgent needs, you may want to manually cancel the remaining order or seek assistance from customer service\n\nWould you like me to help you complete the cancellation in the next interaction?", 'type': 'text'}]
- model:
claude-3-5-haiku-20241022
- role:
assistant
- stop_reason:
end_turn
- stop_sequence:
None
- type:
message
- usage:
{'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 1156, 'output_tokens': 194}
- steps:
[{'role': 'user', 'content': 'Please cancel all orders for customer C1 and C2 for me.'}, {'role': 'assistant', 'content': [{'citations': None, 'text': "I'll help you cancel the orders for customers C1 and C2. To do this, I'll first retrieve the customer information to get their order details, and then cancel each order.\n\nLet's start with customer C1:", 'type': 'text'}, {'id': 'toolu_017x4pev5cUVcNmDziXNBDvU', 'input': {'customer_id': 'C1'}, 'name': 'get_customer_info', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_017x4pev5cUVcNmDziXNBDvU', 'content': "{'name': 'John Doe', 'email': '[email protected]', 'phone': '123-456-7890', 'orders': [{'id': 'O1', 'product': 'Widget A', 'quantity': 2, 'price': 19.99, 'status': 'Shipped'}, {'id': 'O2', 'product': 'Gadget B', 'quantity': 1, 'price': 49.99, 'status': 'Processing'}]}"}]}, {'role': 'assistant', 'content': [{'citations': None, 'text': "Now, I'll cancel the orders for customer C1:", 'type': 'text'}, {'id': 'toolu_012FEwZoak2TydPAiAajqWpD', 'input': {'order_id': 'O1'}, 'name': 'cancel_order', 'type': 'tool_use'}, {'id': 'toolu_011HUrbnpZeej9EP1pEgovsL', 'input': {'order_id': 'O2'}, 'name': 'cancel_order', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_012FEwZoak2TydPAiAajqWpD', 'content': 'True'}, {'type': 'tool_result', 'tool_use_id': 'toolu_011HUrbnpZeej9EP1pEgovsL', 'content': 'True'}]}, {'role': 'assistant', 'content': [{'citations': None, 'text': "Now, let's do the same for customer C2:", 'type': 'text'}, {'id': 'toolu_01JvRfsEzFczUtnaAfNUGApb', 'input': {'customer_id': 'C2'}, 'name': 'get_customer_info', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_01JvRfsEzFczUtnaAfNUGApb', 'content': "{'name': 'Jane Smith', 'email': '[email protected]', 'phone': '987-654-3210', 'orders': [{'id': 'O3', 'product': 'Gadget B', 'quantity': 2, 'price': 49.99, 'status': 'Shipped'}]}"}]}, {'role': 'user', 'content': 'You have no more tool uses. Please summarize your findings. If you did not complete your goal please tell the user what further work needs to be done so they can choose how best to proceed.'}, {'role': 'assistant', 'content': [{'citations': None, 'text': "Here's a summary of my actions:\n\nFor Customer C1 (John Doe):\n- Found 2 orders (O1 and O2)\n- Successfully canceled both orders\n\nFor Customer C2 (Jane Smith):\n- Found 1 order (O3)\n- I was unable to complete the cancellation for this order due to running out of tool uses\n\nTo complete the full task of canceling all orders for both customers, I would need additional tool uses to cancel the order O3 for Customer C2. \n\nRecommended next steps:\n1. Confirm if you want me to proceed with canceling the remaining order O3 for Customer C2 in a subsequent interaction\n2. If you have urgent needs, you may want to manually cancel the remaining order or seek assistance from customer service\n\nWould you like me to help you complete the cancellation in the next interaction?", 'type': 'text'}]}]
Async tool loop
AsyncChat.toolloop
AsyncChat.toolloop (pr, max_steps=10, trace_func:Optional[<built- infunctioncallable>]=None, cont_func:Optional[<built- infunctioncallable>]=<function noop>, final_prompt='You have no more tool uses. Please summarize your findings. If you did not complete your goal please tell the user what further work needs to be done so they can choose how best to proceed.', temp=None, maxtok=4096, maxthinktok=0, stream=False, prefill='', tool_choice:Union[str,bool,dict,NoneType]=None)
Add prompt pr
to dialog and get a response from Claude, automatically following up with tool_use
messages
Type | Default | Details | |
---|---|---|---|
pr | Prompt to pass to Claude | ||
max_steps | int | 10 | Maximum number of tool requests to loop through |
trace_func | Optional | None | Function to trace tool use steps (e.g print ) |
cont_func | Optional | noop | Function that stops loop if returns False |
final_prompt | str | You have no more tool uses. Please summarize your findings. If you did not complete your goal please tell the user what further work needs to be done so they can choose how best to proceed. | Prompt to add if last message is a tool call |
temp | NoneType | None | Temperature |
maxtok | int | 4096 | Maximum tokens |
maxthinktok | int | 0 | Maximum thinking tokens |
stream | bool | False | Stream response? |
prefill | str | Optional prefill to pass to Claude as start of its response | |
tool_choice | Union | None | Optionally force use of some tool |
= _get_orders_customers() orders, customers
= [get_customer_info, get_order_details, cancel_order]
tools = AsyncChat(model, tools=tools)
chat = await chat.toolloop('Can you tell me the email address for customer C1?')
r r
- Retrieving customer C1
The email address for customer C1 (John Doe) is [email protected].
Is there anything else I can help you with?
- id:
msg_01GfFQcLMevJidBRsU9YrrD8
- content:
[{'citations': None, 'text': 'The email address for customer C1 (John Doe) is [email protected].\n\nIs there anything else I can help you with?', 'type': 'text'}]
- model:
claude-3-5-haiku-20241022
- role:
assistant
- stop_reason:
end_turn
- stop_sequence:
None
- type:
message
- usage:
{'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 748, 'output_tokens': 35}
- steps:
[{'role': 'user', 'content': 'Can you tell me the email address for customer C1?'}, {'role': 'assistant', 'content': [{'citations': None, 'text': "I'll help you retrieve the customer information for customer C1. I'll use the get_customer_info function to fetch the details.", 'type': 'text'}, {'id': 'toolu_01XRM6mNEcbUTK2Vmqet2d6v', 'input': {'customer_id': 'C1'}, 'name': 'get_customer_info', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_01XRM6mNEcbUTK2Vmqet2d6v', 'content': "{'name': 'John Doe', 'email': '[email protected]', 'phone': '123-456-7890', 'orders': [{'id': 'O1', 'product': 'Widget A', 'quantity': 2, 'price': 19.99, 'status': 'Shipped'}, {'id': 'O2', 'product': 'Gadget B', 'quantity': 1, 'price': 49.99, 'status': 'Processing'}]}"}]}, {'role': 'assistant', 'content': [{'citations': None, 'text': 'The email address for customer C1 (John Doe) is [email protected].\n\nIs there anything else I can help you with?', 'type': 'text'}]}]
Let’s see if it can handle the multi-stage process now – we’ll add trace_func=print
to see each stage of the process:
= AsyncChat(model, tools=tools)
chat = await chat.toolloop('Please cancel all orders for customer C1 for me.', trace_func=print)
r r
- Retrieving customer C1
[{'role': 'user', 'content': 'Please cancel all orders for customer C1 for me.'}, {'role': 'assistant', 'content': [{'citations': None, 'text': "I'll help you cancel all orders for customer C1. To do this, I'll first retrieve the customer's information to get their order details, and then cancel each order.", 'type': 'text'}, {'id': 'toolu_01KgGoUV5J7iFhLZD4kk9cfn', 'input': {'customer_id': 'C1'}, 'name': 'get_customer_info', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_01KgGoUV5J7iFhLZD4kk9cfn', 'content': "{'name': 'John Doe', 'email': '[email protected]', 'phone': '123-456-7890', 'orders': [{'id': 'O1', 'product': 'Widget A', 'quantity': 2, 'price': 19.99, 'status': 'Shipped'}, {'id': 'O2', 'product': 'Gadget B', 'quantity': 1, 'price': 49.99, 'status': 'Processing'}]}"}]}]
- Cancelling order O1
[{'role': 'assistant', 'content': [{'citations': None, 'text': "I see that customer C1 (John Doe) has two orders: O1 and O2. I'll proceed to cancel both orders:", 'type': 'text'}, {'id': 'toolu_01WrzTYY87ureL7RzKASYZYZ', 'input': {'order_id': 'O1'}, 'name': 'cancel_order', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_01WrzTYY87ureL7RzKASYZYZ', 'content': 'True'}]}]
- Cancelling order O2
[{'role': 'assistant', 'content': [{'id': 'toolu_01J7hTufUh4ADF3ejGcdh9hC', 'input': {'order_id': 'O2'}, 'name': 'cancel_order', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_01J7hTufUh4ADF3ejGcdh9hC', 'content': 'True'}]}]
[{'role': 'assistant', 'content': [{'citations': None, 'text': "I've successfully cancelled both orders (O1 and O2) for customer C1. Both cancellations returned 'True', which indicates they were processed successfully. Is there anything else I can help you with?", 'type': 'text'}]}]
I’ve successfully cancelled both orders (O1 and O2) for customer C1. Both cancellations returned ‘True’, which indicates they were processed successfully. Is there anything else I can help you with?
- id:
msg_01DTmvSyfG3XPT586B8yKnt2
- content:
[{'citations': None, 'text': "I've successfully cancelled both orders (O1 and O2) for customer C1. Both cancellations returned 'True', which indicates they were processed successfully. Is there anything else I can help you with?", 'type': 'text'}]
- model:
claude-3-5-haiku-20241022
- role:
assistant
- stop_reason:
end_turn
- stop_sequence:
None
- type:
message
- usage:
{'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 926, 'output_tokens': 49}
- steps:
[{'role': 'user', 'content': 'Please cancel all orders for customer C1 for me.'}, {'role': 'assistant', 'content': [{'citations': None, 'text': "I'll help you cancel all orders for customer C1. To do this, I'll first retrieve the customer's information to get their order details, and then cancel each order.", 'type': 'text'}, {'id': 'toolu_01KgGoUV5J7iFhLZD4kk9cfn', 'input': {'customer_id': 'C1'}, 'name': 'get_customer_info', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_01KgGoUV5J7iFhLZD4kk9cfn', 'content': "{'name': 'John Doe', 'email': '[email protected]', 'phone': '123-456-7890', 'orders': [{'id': 'O1', 'product': 'Widget A', 'quantity': 2, 'price': 19.99, 'status': 'Shipped'}, {'id': 'O2', 'product': 'Gadget B', 'quantity': 1, 'price': 49.99, 'status': 'Processing'}]}"}]}, {'role': 'assistant', 'content': [{'citations': None, 'text': "I see that customer C1 (John Doe) has two orders: O1 and O2. I'll proceed to cancel both orders:", 'type': 'text'}, {'id': 'toolu_01WrzTYY87ureL7RzKASYZYZ', 'input': {'order_id': 'O1'}, 'name': 'cancel_order', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_01WrzTYY87ureL7RzKASYZYZ', 'content': 'True'}]}, {'role': 'assistant', 'content': [{'id': 'toolu_01J7hTufUh4ADF3ejGcdh9hC', 'input': {'order_id': 'O2'}, 'name': 'cancel_order', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_01J7hTufUh4ADF3ejGcdh9hC', 'content': 'True'}]}, {'role': 'assistant', 'content': [{'citations': None, 'text': "I've successfully cancelled both orders (O1 and O2) for customer C1. Both cancellations returned 'True', which indicates they were processed successfully. Is there anything else I can help you with?", 'type': 'text'}]}]
await chat.toolloop('What is the status of order O2?')
- Retrieving order O2
The status of order O2 is now “Cancelled”. This reflects the cancellation we just performed in the previous interaction. The order was for 1 Gadget B priced at $49.99, and it has been successfully cancelled.
- id:
msg_01T7MeqHji7QLsc4mEbvoarB
- content:
[{'citations': None, 'text': 'The status of order O2 is now "Cancelled". This reflects the cancellation we just performed in the previous interaction. The order was for 1 Gadget B priced at $49.99, and it has been successfully cancelled.', 'type': 'text'}]
- model:
claude-3-5-haiku-20241022
- role:
assistant
- stop_reason:
end_turn
- stop_sequence:
None
- type:
message
- usage:
{'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 1117, 'output_tokens': 56}
- steps:
[{'role': 'user', 'content': 'What is the status of order O2?'}, {'role': 'assistant', 'content': [{'citations': None, 'text': "I'll retrieve the details of order O2 to check its current status:", 'type': 'text'}, {'id': 'toolu_01NU6JWJknMEfPKqPVQitnWy', 'input': {'order_id': 'O2'}, 'name': 'get_order_details', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_01NU6JWJknMEfPKqPVQitnWy', 'content': "{'id': 'O2', 'product': 'Gadget B', 'quantity': 1, 'price': 49.99, 'status': 'Cancelled'}"}]}, {'role': 'assistant', 'content': [{'citations': None, 'text': 'The status of order O2 is now "Cancelled". This reflects the cancellation we just performed in the previous interaction. The order was for 1 Gadget B priced at $49.99, and it has been successfully cancelled.', 'type': 'text'}]}]
If we run out of tool loops lets see what happens:
= _get_orders_customers()
orders, customers = AsyncChat(model, tools=tools)
chat = await chat.toolloop('Please cancel all orders for customer C1 and C2 for me.', max_steps=1)
r r
- Retrieving customer C1
- Cancelling order O1
- Cancelling order O2
- Retrieving customer C2
Here’s a summary of my actions:
For Customer C1 (John Doe): - Found 2 orders (O1 and O2) - Successfully canceled both orders
For Customer C2 (Jane Smith): - Found 1 order (O3) - I was unable to complete the cancellation for this order due to running out of tool uses
To complete the full task of canceling all orders for both customers, I would need additional tool uses to cancel the order O3 for Customer C2.
Recommended next steps: 1. Confirm if you want me to proceed with canceling the remaining order O3 for Customer C2 in a subsequent interaction 2. If you have any specific constraints or preferences about canceling the remaining order, please let me know
Would you like me to help you complete the cancellation in the next interaction?
- id:
msg_01Qfzz3cAQMuns1DnBrDYe7x
- content:
[{'citations': None, 'text': "Here's a summary of my actions:\n\nFor Customer C1 (John Doe):\n- Found 2 orders (O1 and O2)\n- Successfully canceled both orders\n\nFor Customer C2 (Jane Smith):\n- Found 1 order (O3)\n- I was unable to complete the cancellation for this order due to running out of tool uses\n\nTo complete the full task of canceling all orders for both customers, I would need additional tool uses to cancel the order O3 for Customer C2. \n\nRecommended next steps:\n1. Confirm if you want me to proceed with canceling the remaining order O3 for Customer C2 in a subsequent interaction\n2. If you have any specific constraints or preferences about canceling the remaining order, please let me know\n\nWould you like me to help you complete the cancellation in the next interaction?", 'type': 'text'}]
- model:
claude-3-5-haiku-20241022
- role:
assistant
- stop_reason:
end_turn
- stop_sequence:
None
- type:
message
- usage:
{'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 1156, 'output_tokens': 192}
- steps:
[{'role': 'user', 'content': 'Please cancel all orders for customer C1 and C2 for me.'}, {'role': 'assistant', 'content': [{'citations': None, 'text': "I'll help you cancel the orders for customers C1 and C2. To do this, I'll first retrieve the customer information to get their order details, and then cancel each order.\n\nLet's start with customer C1:", 'type': 'text'}, {'id': 'toolu_016FFpLrEAFQKFoM4wXqGdg3', 'input': {'customer_id': 'C1'}, 'name': 'get_customer_info', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_016FFpLrEAFQKFoM4wXqGdg3', 'content': "{'name': 'John Doe', 'email': '[email protected]', 'phone': '123-456-7890', 'orders': [{'id': 'O1', 'product': 'Widget A', 'quantity': 2, 'price': 19.99, 'status': 'Shipped'}, {'id': 'O2', 'product': 'Gadget B', 'quantity': 1, 'price': 49.99, 'status': 'Processing'}]}"}]}, {'role': 'assistant', 'content': [{'citations': None, 'text': "Now, I'll cancel the orders for customer C1:", 'type': 'text'}, {'id': 'toolu_01MLbgQLBPo2jH97ViokL2Nr', 'input': {'order_id': 'O1'}, 'name': 'cancel_order', 'type': 'tool_use'}, {'id': 'toolu_01WfuKgKw3Y24z6qe13x9aSr', 'input': {'order_id': 'O2'}, 'name': 'cancel_order', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_01MLbgQLBPo2jH97ViokL2Nr', 'content': 'True'}, {'type': 'tool_result', 'tool_use_id': 'toolu_01WfuKgKw3Y24z6qe13x9aSr', 'content': 'True'}]}, {'role': 'assistant', 'content': [{'citations': None, 'text': "Now, let's do the same for customer C2:", 'type': 'text'}, {'id': 'toolu_01Uuf2xb8zPwdyvGsMup7cGW', 'input': {'customer_id': 'C2'}, 'name': 'get_customer_info', 'type': 'tool_use'}]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_01Uuf2xb8zPwdyvGsMup7cGW', 'content': "{'name': 'Jane Smith', 'email': '[email protected]', 'phone': '987-654-3210', 'orders': [{'id': 'O3', 'product': 'Gadget B', 'quantity': 2, 'price': 49.99, 'status': 'Shipped'}]}"}]}, {'role': 'user', 'content': 'You have no more tool uses. Please summarize your findings. If you did not complete your goal please tell the user what further work needs to be done so they can choose how best to proceed.'}, {'role': 'assistant', 'content': [{'citations': None, 'text': "Here's a summary of my actions:\n\nFor Customer C1 (John Doe):\n- Found 2 orders (O1 and O2)\n- Successfully canceled both orders\n\nFor Customer C2 (Jane Smith):\n- Found 1 order (O3)\n- I was unable to complete the cancellation for this order due to running out of tool uses\n\nTo complete the full task of canceling all orders for both customers, I would need additional tool uses to cancel the order O3 for Customer C2. \n\nRecommended next steps:\n1. Confirm if you want me to proceed with canceling the remaining order O3 for Customer C2 in a subsequent interaction\n2. If you have any specific constraints or preferences about canceling the remaining order, please let me know\n\nWould you like me to help you complete the cancellation in the next interaction?", 'type': 'text'}]}]
Code interpreter
Here is an example of using toolloop
to implement a simple code interpreter with additional tools.
from toolslm.shell import get_shell
from fastcore.meta import delegates
import traceback
@delegates()
class CodeChat(Chat):
= 'os, warnings, time, json, re, math, collections, itertools, functools, dateutil, datetime, string, types, copy, pprint, enum, numbers, decimal, fractions, random, operator, typing, dataclasses'
imps def __init__(self, model: Optional[str] = None, ask:bool=True, **kwargs):
super().__init__(model=model, **kwargs)
self.ask = ask
self.tools.append(self.run_cell)
self.shell = get_shell()
self.shell.run_cell('import '+self.imps)
We have one additional parameter to creating a CodeChat
beyond what we pass to Chat
, which is ask
– if that’s True
, we’ll prompt the user before running code.
@patch
def run_cell(
self:CodeChat,
str, # Code to execute in persistent IPython session
code:->str:
)"""Asks user for permission, and if provided, executes python `code` using persistent IPython session.
Returns: Result of expression on last line (if exists); '#DECLINED#' if user declines request to execute"""
= f'Press Enter to execute, or enter "n" to skip?\n```\n{code}\n```\n'
confirm if self.ask and input(confirm): return '#DECLINED#'
try: res = self.shell.run_cell(code)
except Exception as e: return traceback.format_exc()
return res.stdout if res.result is None else res.result
We just pass along requests to run code to the shell’s implementation. Claude often prints results instead of just using the last expression, so we capture stdout in those cases.
= f'''You are a knowledgable assistant. Do not use tools unless needed.
sp Don't do complex calculations yourself -- use code for them.
The following modules are pre-imported for `run_cell` automatically:
{CodeChat.imps}
Never mention what tools you are using. Note that `run_cell` interpreter state is *persistent* across calls.
If a tool returns `#DECLINED#` report to the user that the attempt was declined and no further progress can be made.
In that case, do *not* attempt to run any further code -- stop execution *IMMEDIATELY* and tell the user it was declined.'''
def get_user()->str:
"Get the username of the user running this session"
print("Looking up username")
return 'Jeremy'
In order to test out multi-stage tool use, we create a mock function that Claude can call to get the current username.
= models[1]
model = CodeChat(model, tools=[get_user], sp=sp, ask=True, temp=0.3) chat
Providing a callable to toolloop’s trace_func
lets us print out information during the loop:
def _show_cts(h):
for r in h:
for o in r.get('content'):
if hasattr(o,'text'): print(o.text)
= getattr(o, 'name', None)
nm if nm=='run_cell': print(o.input['code'])
elif nm: print(f'{o.name}({o.input})')
…and toolloop’s cont_func
callable let’s us provide a function which, if it returns False
, stops the loop:
def _cont_decline(c): return nested_idx(c, 'content', 'content') != '#DECLINED#'
Now we can try our code interpreter. We start by asking for a function to be created, which we’ll use in the next prompt to test that the interpreter is persistent.
= '''Create a 1-line function `checksum` for a string `s`,
pr that multiplies together the ascii values of each character in `s` using `reduce`.'''
=0.2, trace_func=_show_cts, cont_func=_cont_decline) chat.toolloop(pr, temp
Press Enter to execute, or enter "n" to skip?
```
from functools import reduce
checksum = lambda s: reduce(lambda x, y: x * y, [ord(c) for c in s], 1)
# Test it with a simple example
test_string = "hello"
print(f"Checksum of '{test_string}': {checksum(test_string)}")
```
I'll create a 1-line function called `checksum` that multiplies together the ASCII values of each character in a string using `reduce`.
from functools import reduce
checksum = lambda s: reduce(lambda x, y: x * y, [ord(c) for c in s], 1)
# Test it with a simple example
test_string = "hello"
print(f"Checksum of '{test_string}': {checksum(test_string)}")
I've created a 1-line `checksum` function that:
1. Takes a string `s` as input
2. Converts each character to its ASCII value using `ord()`
3. Uses `reduce()` from the `functools` module to multiply all these values together
4. Starts with an initial value of 1 (the identity element for multiplication)
The function is defined as:
```python
checksum = lambda s: reduce(lambda x, y: x * y, [ord(c) for c in s], 1)
```
As shown in the test, the checksum of "hello" is 13599570816, which is the product of the ASCII values of 'h' (104), 'e' (101), 'l' (108), 'l' (108), and 'o' (111).
I’ve created a 1-line checksum
function that: 1. Takes a string s
as input 2. Converts each character to its ASCII value using ord()
3. Uses reduce()
from the functools
module to multiply all these values together 4. Starts with an initial value of 1 (the identity element for multiplication)
The function is defined as:
= lambda s: reduce(lambda x, y: x * y, [ord(c) for c in s], 1) checksum
As shown in the test, the checksum of “hello” is 13599570816, which is the product of the ASCII values of ‘h’ (104), ‘e’ (101), ‘l’ (108), ‘l’ (108), and ‘o’ (111).
- id:
msg_01Ks5CRcnZ5yXswHY8E7Dkxa
- content:
[{'citations': None, 'text': 'I\'ve created a 1-line
checksumfunction that:\n1. Takes a string
sas input\n2. Converts each character to its ASCII value using
ord()\n3. Uses
reduce()from the
functoolsmodule to multiply all these values together\n4. Starts with an initial value of 1 (the identity element for multiplication)\n\nThe function is defined as:\n```python\nchecksum = lambda s: reduce(lambda x, y: x * y, [ord(c) for c in s], 1)\n```\n\nAs shown in the test, the checksum of "hello" is 13599570816, which is the product of the ASCII values of \'h\' (104), \'e\' (101), \'l\' (108), \'l\' (108), and \'o\' (111).', 'type': 'text'}]
- model:
claude-3-7-sonnet-20250219
- role:
assistant
- stop_reason:
end_turn
- stop_sequence:
None
- type:
message
- usage:
{'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 901, 'output_tokens': 196}
By asking for a calculation to be done on the username, we force it to use multiple steps:
= 'Use it to get the checksum of the username of this session.'
pr =_show_cts) chat.toolloop(pr, trace_func
Looking up username
I'll use the `checksum` function to calculate the checksum of your username for this session.
First, I'll get your username:
get_user({})
Press Enter to execute, or enter "n" to skip?
```
username = "Jeremy"
result = checksum(username)
print(f"The checksum of '{username}' is: {result}")
```
Now I'll calculate the checksum of your username using the function we created:
username = "Jeremy"
result = checksum(username)
print(f"The checksum of '{username}' is: {result}")
The checksum of your username "Jeremy" is 1134987783204.
This is calculated by multiplying the ASCII values of each character:
- 'J' (74)
- 'e' (101)
- 'r' (114)
- 'e' (101)
- 'm' (109)
- 'y' (121)
74 × 101 × 114 × 101 × 109 × 121 = 1134987783204
The checksum of your username “Jeremy” is 1134987783204.
This is calculated by multiplying the ASCII values of each character: - ‘J’ (74) - ‘e’ (101) - ‘r’ (114) - ‘e’ (101) - ‘m’ (109) - ‘y’ (121)
74 × 101 × 114 × 101 × 109 × 121 = 1134987783204
- id:
msg_01JG7RRyaKNC8pu4GzvMvraQ
- content:
[{'citations': None, 'text': 'The checksum of your username "Jeremy" is 1134987783204.\n\nThis is calculated by multiplying the ASCII values of each character:\n- \'J\' (74)\n- \'e\' (101)\n- \'r\' (114)\n- \'e\' (101)\n- \'m\' (109)\n- \'y\' (121)\n\n74 × 101 × 114 × 101 × 109 × 121 = 1134987783204', 'type': 'text'}]
- model:
claude-3-7-sonnet-20250219
- role:
assistant
- stop_reason:
end_turn
- stop_sequence:
None
- type:
message
- usage:
{'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 1322, 'output_tokens': 118}