Build a Tool Use-Based Agent Loop with Amazon Bedrock
Learn how to build a simple agentic loop using the Converse API’s function calling capabilities with large language models on Amazon Bedrock.
- The calling application passes (A) tool definitions and (B) a triggering message to the large language model.
- The model generates a tool use request, including the parameters to pass to the tool.
- The calling application extracts the parameters from the model’s tool use request and passes them to the corresponding local function to get some sort of result (that local function could then call an external service if necessary).
- The calling application passes the tool result back to the model to get a follow-on response.
- The model either returns a final response, or requests another tool (goto #3 above).
- If too many loops occur, then the process ends without resolution.
- Set up the Boto3 AWS SDK and Python: https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
- Select an AWS region that supports the Anthropic Claude 3 Sonnet model. I'm using us-west-2 (Oregon). You can check the documentation for model support by region.
- Configure Amazon Bedrock model access for your account and region. Example here: https://catalog.workshops.aws/building-with-amazon-bedrock/en-US/prerequisites/bedrock-setup
- If you don’t have your own local integrated development environment, you can try AWS Cloud9. Setup instructions here: https://catalog.workshops.aws/building-with-amazon-bedrock/en-US/prerequisites/cloud9-setup
- Large language models are non-deterministic. You should expect different results than those shown in this article.
- If you run this code from your own AWS account, you will be charged for the tokens consumed.
- I generally subscribe to a “Minimum Viable Prompt” philosophy. You may need to write more detailed prompts for your use case.
- Not every model supports all of the capabilities of the Converse API, so it’s important to review the supported model features in the official documentation.
1
2
3
4
import boto3, json, math
class ToolError(Exception):
pass
converse
method. We pass it a list of messages and a list of tools. We also set an output token limit and set the temperature to 0 to reduce the variability between calls (During development and testing, it can be preferable to set temperature
higher for more variability in responses).1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def call_bedrock(message_list, tool_list):
session = boto3.Session()
bedrock = session.client(service_name='bedrock-runtime')
response = bedrock.converse(
modelId="anthropic.claude-3-sonnet-20240229-v1:0",
messages=message_list,
inferenceConfig={
"maxTokens": 2000,
"temperature": 0
},
toolConfig={ "tools": tool_list }
)
return response
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def get_tool_result(tool_use_block):
tool_use_name = tool_use_block['name']
print(f"Using tool {tool_use_name}")
# Note: We're deliberately excluding tangent so something magical can happen
if tool_use_name == 'cosine':
return math.cos(tool_use_block['input']['x'])
elif tool_use_name == 'sine':
return math.sin(tool_use_block['input']['x'])
elif tool_use_name == 'divide_numbers':
return tool_use_block['input']['x'] / tool_use_block['input']['y']
else:
raise ToolError(f"Invalid function name: {tool_use_name}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def handle_response(response_message):
response_content_blocks = response_message['content']
follow_up_content_blocks = []
for content_block in response_content_blocks:
if 'toolUse' in content_block:
tool_use_block = content_block['toolUse']
try:
tool_result_value = get_tool_result(tool_use_block)
if tool_result_value is not None:
follow_up_content_blocks.append({
"toolResult": {
"toolUseId": tool_use_block['toolUseId'],
"content": [
{ "json": { "result": tool_result_value } }
]
}
})
except ToolError as e:
follow_up_content_blocks.append({
"toolResult": {
"toolUseId": tool_use_block['toolUseId'],
"content": [ { "text": repr(e) } ],
"status": "error"
}
})
if len(follow_up_content_blocks) > 0:
follow_up_message = {
"role": "user",
"content": follow_up_content_blocks,
}
return follow_up_message
else:
return None
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def run_loop(prompt, tool_list):
MAX_LOOPS = 6
loop_count = 0
continue_loop = True
message_list = [
{
"role": "user",
"content": [ { "text": prompt } ]
}
]
while continue_loop:
response = call_bedrock(message_list, tool_list)
response_message = response['output']['message']
message_list.append(response_message)
loop_count = loop_count + 1
if loop_count >= MAX_LOOPS:
print(f"Hit loop limit: {loop_count}")
break
follow_up_message = handle_response(response_message)
if follow_up_message is None:
# No remaining work to do, return final response to user
continue_loop = False
else:
message_list.append(follow_up_message)
return message_list
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
tools = [
{
"toolSpec": {
"name": "cosine",
"description": "Calculate the cosine of x.",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"x": {
"type": "number",
"description": "The number to pass to the function."
}
},
"required": ["x"]
}
}
}
},
{
"toolSpec": {
"name": "sine",
"description": "Calculate the sine of x.",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"x": {
"type": "number",
"description": "The number to pass to the function."
}
},
"required": ["x"]
}
}
}
},
{
"toolSpec": {
"name": "tangent",
"description": "Calculate the tangent of x.",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"x": {
"type": "number",
"description": "The number to pass to the function."
}
},
"required": ["x"]
}
}
}
},
{
"toolSpec": {
"name": "divide_numbers",
"description": "Divide x by y.",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"x": {
"type": "number",
"description": "The numerator."
},
"y": {
"type": "number",
"description": "The denominator."
}
},
"required": ["x", "y"]
}
}
}
}
]
1
2
3
4
messages = run_loop("What is the tangent of 7?", tools)
print("\nMESSAGES:\n")
print(json.dumps(messages, indent=4))
1
2
3
4
Using tool tangent
Using tool sine
Using tool cosine
Using tool divide_numbers
1
2
3
4
5
6
7
8
9
[
{
"role": "user",
"content": [
{
"text": "What is the tangent of 7?"
}
]
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"role": "assistant",
"content": [
{
"text": "To calculate the tangent of 7, we can use the \"tangent\" tool:"
},
{
"toolUse": {
"toolUseId": "tooluse_WnWkxaxYS7-_5BnHvxpLmg",
"name": "tangent",
"input": {
"x": 7
}
}
}
]
},
tangent
tool doesn't work properly! Whatever shall we do? Let's tell Claude the bad news:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"role": "user",
"content": [
{
"toolResult": {
"toolUseId": "tooluse_WnWkxaxYS7-_5BnHvxpLmg",
"content": [
{
"text": "ToolError('Invalid function name: tangent')"
}
],
"status": "error"
}
}
]
},
sine
tool:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"role": "assistant",
"content": [
{
"text": "Apologies, it seems the \"tangent\" tool is not available in this environment. Let me calculate the tangent of 7 manually:\n\nThe tangent is defined as tan(x) = sin(x) / cos(x)\n\nTo find sin(7) and cos(7), I can use the sine and cosine tools:"
},
{
"toolUse": {
"toolUseId": "tooluse_mXWdxEyNTxui14s9vktNNw",
"name": "sine",
"input": {
"x": 7
}
}
}
]
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"role": "user",
"content": [
{
"toolResult": {
"toolUseId": "tooluse_mXWdxEyNTxui14s9vktNNw",
"content": [
{
"json": {
"result": 0.6569865987187891
}
}
]
}
}
]
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"role": "assistant",
"content": [
{
"toolUse": {
"toolUseId": "tooluse_b0uIDNT6Tnq9ZeV9g4d-5g",
"name": "cosine",
"input": {
"x": 7
}
}
}
]
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"role": "user",
"content": [
{
"toolResult": {
"toolUseId": "tooluse_b0uIDNT6Tnq9ZeV9g4d-5g",
"content": [
{
"json": {
"result": 0.7539022543433046
}
}
]
}
}
]
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"role": "assistant",
"content": [
{
"text": "Great, now I have sin(7) = 0.6569865987187891 and cos(7) = 0.7539022543433046.\n\nTo get tan(7), I divide sin(7) by cos(7):"
},
{
"toolUse": {
"toolUseId": "tooluse_Ss9CW9ldQvmlGijl31biow",
"name": "divide_numbers",
"input": {
"x": 0.6569865987187891,
"y": 0.7539022543433046
}
}
}
]
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"role": "user",
"content": [
{
"toolResult": {
"toolUseId": "tooluse_Ss9CW9ldQvmlGijl31biow",
"content": [
{
"json": {
"result": 0.8714479827243188
}
}
]
}
}
]
},
1
2
3
4
5
6
7
8
9
{
"role": "assistant",
"content": [
{
"text": "The tangent of 7 is 0.8714479827243188."
}
]
}
]
- Not every model supports every capability of the Converse API, so it’s important to review the supported model features in the official documentation.
- You can find more generative AI hands-on activities at the Building with Amazon Bedrock workshop guide.
Any opinions in this post are those of the individual author and may not reflect the opinions of AWS.