Skip to content

Commit c21ac15

Browse files
Merge pull request #12 from anthropics/messages
Messages
2 parents d5f8e78 + c141be7 commit c21ac15

11 files changed

+138
-48
lines changed

README.md

+20-19
Original file line numberDiff line numberDiff line change
@@ -80,25 +80,26 @@ time_tool_user = ToolUser([time_of_day_tool])
8080

8181
You can then make use of your ToolUser by calling its `use_tools()` method and passing in your desired prompt. Setting execution mode to "automatic" makes it execute the function; in the default "manual" mode it returns the function arguments back to the client to be executed there.
8282
```python
83-
messages = [{'role': 'human', 'content': 'What time is it in Los Angeles?'}]
83+
messages = [{'role': 'user', 'content': 'What time is it in Los Angeles?'}]
8484
time_tool_user.use_tools(messages, execution_mode='automatic')
8585
```
8686

8787
If you are accesing Claude through AWS Bedrock, set the parameter `first_party` to `False` (it is by default set to `True`):
8888
```python
8989
time_tool_user = ToolUser([time_of_day_tool], first_party=False)
9090
```
91+
NOTE: If using bedrock, this SDK only supports claude 2.1 (anthropic.claude-v2:1).
9192

9293
Notice that new `messages` format instead of passing in a simple prompt string? Never seen it before? Don't worry, we are about to walk through it.
9394

94-
### New Prompt Format
95-
Historically, interacting with Claude required that you directly pass it a string (including any required Human/Assistant formatting), and get back a string. This was already somewhat error prone from both an input and parsing standpoint and became more cumbersome to deal with in the context of tool use. The result is that anthropic-tools uses a new, *structured* prompt input and output format, coming as a list of messages. Let's take a quick tour of how to work with this list.
95+
### Prompt Format
96+
Anthropic-tools uses a *structured* prompt input and output format, coming as a list of messages, intending to mimic our Messages API format. Let's take a quick tour of how to work with this list.
9697

9798
`messages` is a python list of message dictionaries. A single message dictionary object can contain these fields but will never contain all of them (see the field comments below for more detail on what this means):
9899
```python
99100
{
100-
"role": str, # The role of the message. 'human' for a message from the human, 'assistant' for a message from the assistant, 'tool_inputs' for a request from the assistant to use tools, 'tool_outputs' for a response to a tool_inputs message containing the results of using the specified tools in the specified ways.
101-
"content": str, # The (non tool use) content of the message, which must be present for messages where role=(human, assistant, tool_inputs) and can not be present for messages where role=tool_outputs.
101+
"role": str, # The role of the message. 'user' for a message from the user, 'assistant' for a message from the assistant, 'tool_inputs' for a request from the assistant to use tools, 'tool_outputs' for a response to a tool_inputs message containing the results of using the specified tools in the specified ways.
102+
"content": str, # The (non tool use) content of the message, which must be present for messages where role=(user, assistant, tool_inputs) and can not be present for messages where role=tool_outputs.
102103
"tool_inputs": list[dict], # A list of dictionaries (see below). Must be specified in messages where role=tool_inputs.
103104
"tool_outputs": list[dict], # A list of tool_output dictionaries (see below). One of tool_outputs or tool_error must be specified in messages where role=tool_outputs, but the other must be specified as None.
104105
"tool_error": str # A tool error message corresponding to the first tool that errored to help Claude understand what it did wrong. One of tool_error or tool_outputs must be specified when role=tool_outputs, but the other must be specified as None.
@@ -164,21 +165,21 @@ Sometimes when Claude responds with a `tool_inputs` message it makes a mistake a
164165
```
165166

166167
So, what might `messages` look like in practice?
167-
Here is a human message:
168+
Here is a user message:
168169
```python
169-
human_message = {'role': 'human', 'content': 'Hi Claude, what US states start with C?'}
170-
messages = [human_message]
170+
user_message = {'role': 'user', 'content': 'Hi Claude, what US states start with C?'}
171+
messages = [user_message]
171172
```
172-
Here is a human message and an assistant response, with no tool use involved.
173+
Here is a user message and an assistant response, with no tool use involved.
173174
```python
174-
human_message = {'role': 'human', 'content': 'Hi Claude, what US states start with C?'}
175+
user_message = {'role': 'humuseran', 'content': 'Hi Claude, what US states start with C?'}
175176
assistant_message = {'role': 'assistant', 'content': 'California, Colorado, and Connecticut are the US states that start with the letter C.'}
176-
messages = [human_message, assistant_message]
177+
messages = [user_message, assistant_message]
177178
```
178179

179-
Here is a human message, followed by a tool_inputs message, followed by a successful tool_outputs message:
180+
Here is a user message, followed by a tool_inputs message, followed by a successful tool_outputs message:
180181
```python
181-
human_message = {'role': 'human', 'content': 'If Maggie has 3 apples and eats 1, how many apples does maggie have left?'}
182+
user_message = {'role': 'user', 'content': 'If Maggie has 3 apples and eats 1, how many apples does maggie have left?'}
182183
tool_inputs_message = {
183184
'role': 'tool_inputs',
184185
'content': "Let's think this through. Maggie had 3 apples, she ate one so:",
@@ -189,12 +190,12 @@ tool_outputs_message = {
189190
'tool_outputs': [{"tool_name": 'perform_subtraction', 'tool_result': 2}],
190191
'tool_error': None
191192
}
192-
messages = [human_message, tool_inputs_message, tool_outputs_message]
193+
messages = [user_message, tool_inputs_message, tool_outputs_message]
193194
```
194195

195196
And here is what it would look like instead if Claude made a mistake and `perform_subtraction` failed.
196197
```python
197-
human_message = {'role': 'human', 'content': 'If Maggie has 3 apples and eats 1, how many apples does maggie have left?'}
198+
user_message = {'role': 'user', 'content': 'If Maggie has 3 apples and eats 1, how many apples does maggie have left?'}
198199
tool_inputs_message = {
199200
'role': 'tool_inputs',
200201
'content': "Let's think this through. Maggie had 3 apples, she ate one so:",
@@ -205,7 +206,7 @@ tool_outputs_message = {
205206
'tool_outputs': None,
206207
'tool_error': 'Missing required parameter "b" in tool perform_subtraction.'
207208
}
208-
messages = [human_message, tool_inputs_message, tool_outputs_message]
209+
messages = [user_message, tool_inputs_message, tool_outputs_message]
209210
```
210211

211212
That's it for the new messages format. To help wrap your head around this concept, at the end of the "Putting it Together" section below, we will build a python function to handle these sorts of requests.
@@ -249,12 +250,12 @@ subtraction_tool = SubtractionTool(subtraction_tool_name, subtraction_tool_descr
249250
math_tool_user = ToolUser([addition_tool, subtraction_tool])
250251

251252
# Build messages
252-
human_message = {
253-
"role": "human",
253+
user_message = {
254+
"role": "user",
254255
"content": "Sally has 17 apples. She gives 9 to Jim. Later that day, Peter gives 6 Bananas to Sally. How many pieces of fruit does Sally have at the end of the day?"
255256
}
256257

257-
messages = [human_message]
258+
messages = [user_message]
258259

259260
# Use Claude With the Provided Tools
260261
math_tool_user.use_tools(messages, execution_mode='automatic')

requirements.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
aiohttp==3.8.6
22
aiosignal==1.3.1
33
annotated-types==0.6.0
4-
anthropic==0.4.1
5-
anthropic-bedrock==0.4.0
4+
anthropic==0.16.0
5+
anthropic-bedrock==0.8.0
66
anyio==3.7.1
77
async-timeout==4.0.3
88
attrs==23.1.0

tool_use_package/EXAMPLES.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ We then call tool_user.use_tools() with our query to let claude answer our quest
5656
tool_user = ToolUser([weather_tool])
5757

5858
# Call the tool_user with a prompt to get a version of Claude that can use your tools!
59-
messages = [{"role": "human", "content": "I live in San Francisco, what shold I wear today?"}]
59+
messages = [{"role": "user", "content": "I live in San Francisco, what shold I wear today?"}]
6060
print(tool_user.use_tools(messages, execution_mode='automatic'))
6161
```
6262
You may also notice that we set `execution_mode='automatic'`, recall that this means Claude will have its tool usage requests automatically executed and fed back in until it decides it has done enough to answer your query, at which point it will respond to you with that answer. If you set `execution_mode='manual'`, Claude will stop after its first request to use a tool/tools and you will be returned the requested tool(s) to use and the arguments to use them with.
@@ -141,7 +141,7 @@ Finally, we pass `sql_tool` to `ToolUser` and run our query!
141141
```python
142142
tool_user = ToolUser([sql_tool])
143143

144-
messages = [{"role": "human", "content": "Who is our oldest employee?"}]
144+
messages = [{"role": "user", "content": "Who is our oldest employee?"}]
145145
print(tool_user.use_tools(messages, single_function_call=False))
146146
```
147147
When you are done you can either manually delete the test.db file or run `os.remove('test.db')` to get rid of the temporary database we created.
@@ -233,7 +233,7 @@ Finally, we pass our `amazon_search_tool` to `ToolUser`, define our message, and
233233
```python
234234
tool_user = ToolUser([amazon_search_tool])
235235

236-
messages = [{"role":"human", "content":"I want to get my daughter more interested in science. What kind of gifts should I get her?"}]
236+
messages = [{"role":"user", "content":"I want to get my daughter more interested in science. What kind of gifts should I get her?"}]
237237

238238
print(tool_user.use_tools(messages, execution_mode="automatic"))
239239
```
@@ -340,7 +340,7 @@ Finally, we pass our `amazon_search_tool` to `ToolUser`, define our message, and
340340
```python
341341
tool_user = ToolUser([amazon_search_tool])
342342

343-
messages = [{"role":"human", "content":"I want to get my daughter more interested in science. What kind of gifts should I get her?"}]
343+
messages = [{"role":"user", "content":"I want to get my daughter more interested in science. What kind of gifts should I get her?"}]
344344

345345
print(tool_user.use_tools(messages, execution_mode="automatic"))
346346
```

tool_use_package/calculator_example.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,5 @@ def use_tool(self, a, b):
4646

4747
# Call the tool_user with a prompt to get a version of Claude that can use your tools!
4848
if __name__ == '__main__':
49-
messages = [{"role":"human", "content":"John has three apples. Maggie has nine apples. Tim has 4 bananas. If John gives Maggie 1 apple and Tim gives John 2 bananas, how much fruit does each person have?"}]
49+
messages = [{"role":"user", "content":"John has three apples. Maggie has nine apples. Tim has 4 bananas. If John gives Maggie 1 apple and Tim gives John 2 bananas, how much fruit does each person have?"}]
5050
print(tool_user.use_tools(messages, execution_mode="automatic"))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
def convert_completion_to_messages(completion):
2+
result = {"messages": []}
3+
4+
parts = completion.split('\n\nHuman:')
5+
if len(parts) < 2:
6+
raise ValueError("No human message found in the completion")
7+
8+
# Assign the first part as the system message
9+
result["system"] = parts[0].strip()
10+
11+
# Process the remaining parts
12+
for i in range(1, len(parts)):
13+
content_parts = parts[i].split('\n\nAssistant:', 1)
14+
15+
result["messages"].append({"role": "user", "content": content_parts[0].strip()})
16+
17+
if len(content_parts) == 2:
18+
result["messages"].append({"role": "assistant", "content": content_parts[1].strip()})
19+
elif len(content_parts) > 2:
20+
# If there are more than two parts, it means there are consecutive assistant messages
21+
raise ValueError("Consecutive assistant messages found")
22+
elif i < len(parts) - 1:
23+
# If there is no assistant message and it's not the last part, raise an error for consecutive human messages
24+
raise ValueError("Consecutive human messages found")
25+
26+
return result
27+
28+
class MiniCompletion:
29+
def __init__(self, stop_reason, stop, completion):
30+
self.stop_reason = stop_reason
31+
self.stop = stop
32+
self.completion = completion
33+
34+
35+
def convert_messages_completion_object_to_completions_completion_object(message):
36+
if message.stop_reason == 'end_turn':
37+
stop_reason = 'stop_sequence'
38+
elif message.stop_reason == 'stop_sequence':
39+
stop_reason = 'stop_sequence'
40+
else:
41+
stop_reason = message.stop_reason
42+
43+
if message.stop_sequence is None:
44+
stop_sequence = '\n\nHuman:'
45+
else:
46+
stop_sequence = message.stop_sequence
47+
48+
if message.content:
49+
if message.content[0].text:
50+
content = message.content[0].text
51+
else:
52+
content=''
53+
54+
return MiniCompletion(
55+
stop_reason=stop_reason,
56+
stop=stop_sequence,
57+
completion=content
58+
)

tool_use_package/prompt_constructors.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def construct_tool_use_system_prompt(tools):
2525
return tool_use_system_prompt
2626

2727
def construct_use_tools_prompt(prompt, tools, last_message_role):
28-
if last_message_role == 'human':
28+
if last_message_role == 'user':
2929
constructed_prompt = (
3030
f"{construct_tool_use_system_prompt(tools)}"
3131
f"{prompt}"
@@ -108,27 +108,27 @@ def construct_prompt_from_messages(messages):
108108

109109
constructed_prompt = ""
110110
for i, message in enumerate(messages):
111-
if message['role'] == 'human':
112-
if (i > 0 and messages[i-1]['role'] != 'human') or i == 0:
111+
if message['role'] == 'user':
112+
if (i > 0 and messages[i-1]['role'] != 'user') or i == 0:
113113
constructed_prompt = f"{constructed_prompt}\n\nHuman: {message['content']}"
114114
else:
115115
constructed_prompt = f"{constructed_prompt}\n\n{message['content']}"
116116
if message['role'] == 'assistant':
117-
if (i > 0 and messages[i-1]['role'] == 'human') or i == 0:
117+
if (i > 0 and messages[i-1]['role'] == 'user') or i == 0:
118118
constructed_prompt = f"{constructed_prompt}\n\nAssistant: {message['content']}"
119119
else:
120120
constructed_prompt = f"{constructed_prompt}\n\n{message['content']}"
121121
if message['role'] == 'tool_inputs':
122122
appendage = construct_tool_inputs_message(message['content'], message['tool_inputs'])
123-
if (i > 0 and messages[i-1]['role'] == 'human') or i == 0:
123+
if (i > 0 and messages[i-1]['role'] == 'user') or i == 0:
124124
constructed_prompt = f"{constructed_prompt}\n\nAssistant:{appendage}"
125125
elif message['content'] == "":
126126
constructed_prompt = f"{constructed_prompt}{appendage}"
127127
else:
128128
constructed_prompt = f"{constructed_prompt}\n\n{appendage}"
129129
if message['role'] == 'tool_outputs':
130130
appendage = construct_tool_outputs_message(message['tool_outputs'], message['tool_error'])
131-
if (i > 0 and messages[i-1]['role'] == 'human') or i == 0:
131+
if (i > 0 and messages[i-1]['role'] == 'user') or i == 0:
132132
constructed_prompt = f"{constructed_prompt}\n\nAssistant:{appendage}"
133133
else:
134134
constructed_prompt = f"{constructed_prompt}{appendage}"
@@ -141,19 +141,19 @@ def validate_messages(messages):
141141
if len(messages) < 1:
142142
raise ValueError("Messages must be a list of length > 0.")
143143

144-
valid_roles = ['human', 'assistant', 'tool_inputs', 'tool_outputs']
144+
valid_roles = ['user', 'assistant', 'tool_inputs', 'tool_outputs']
145145
for message in messages:
146146
if not isinstance(message, dict):
147147
raise ValueError("All messages in messages list should be dictionaries.")
148148
if 'role' not in message:
149149
raise ValueError("All messages must have a 'role' key.")
150150
if message['role'] not in valid_roles:
151151
raise ValueError(f"{message['role']} is not a valid role. Valid roles are {valid_roles}")
152-
if message['role'] == 'human' or message['role'] == 'assistant':
152+
if message['role'] == 'user' or message['role'] == 'assistant':
153153
if 'content' not in message:
154-
raise ValueError("All messages with human or assistant roles must have a 'content' key.")
154+
raise ValueError("All messages with user or assistant roles must have a 'content' key.")
155155
if not isinstance(message['content'], str):
156-
raise ValueError("For messages with role='human' or role='assistant', content must be a string.")
156+
raise ValueError("For messages with role='user' or role='assistant', content must be a string.")
157157
if message['role'] == 'tool_inputs':
158158
if 'tool_inputs' not in message:
159159
raise ValueError("All messages with tool_inputs roles must have a 'tool_inputs' key.")

tool_use_package/tests/test_prompt_constructors.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
class TestPromptConstructors(unittest.TestCase):
2323
def test_construct_use_tools_prompt(self):
2424
tools = [weather_tool]
25-
self.assertEqual(construct_use_tools_prompt("\n\nHuman: I live in San Francisco, what shold I wear today?", tools, 'human'), test_use_tools_prompt)
25+
self.assertEqual(construct_use_tools_prompt("\n\nHuman: I live in San Francisco, what shold I wear today?", tools, 'user'), test_use_tools_prompt)
2626

2727
def test_construct_successful_function_run_injection_prompt(self):
2828
self.assertEqual(construct_successful_function_run_injection_prompt([{"tool_name": "perform_addition", "tool_result": 8}]), test_successful_function_run_injection_prompt)

0 commit comments

Comments
 (0)