Chapter 02
02 messages format
Working with messages
Lesson goals
- Understand the messages API format
- Work with and understand model response objects
- Build a simple multi-turn chatbot
Basic setup
We'll start by importing the packages we need and initializing a client object. See the previous tutorial for details on how to get an API key and properly store it.
from dotenv import load_dotenv
from anthropic import Anthropic
#load environment variable
load_dotenv()
#automatically looks for an "ANTHROPIC_API_KEY" environment variable
client = Anthropic()Messages format
As we saw in the previous lesson, we can use client.messages.create() to send a message to Claude and get a response:
response = client.messages.create(
model="claude-3-haiku-20240307",
max_tokens=1000,
messages=[
{"role": "user", "content": "What flavors are used in Dr. Pepper?"}
]
)
print(response)Output
Message(id='msg_013wVsHLHRjuDM2WgvVJ8RNm', content=[ContentBlock(text='The exact flavor formula for Dr Pepper is a closely guarded trade secret, but here are some of the main flavors that are believed to be used:\n\n- Cherry - This is one of the most prominent flavors in Dr Pepper. The cherry flavor comes from the use of a type of cherry extract.\n\n- Prune - Dr Pepper contains a prune-like flavor which contributes to its unique profile.\n\n- Vanilla - Vanilla is another key component that helps round out the flavor.\n\n- Spices - Various spices like cinnamon, prune, and other aromatics are believed to be part of the blend.\n\n- Citrus - Flavors like orange, lemon, and prune add some citrus notes.\n\nThe exact combination of these and other secret ingredients is what gives Dr Pepper its signature taste that differentiates it from other cola or soda flavors. The complex blend of sweet, spicy, and tart notes is part of what makes Dr Pepper a unique and iconic soft drink flavor.', type='text')], model='claude-3-haiku-20240307', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(input_tokens=18, output_tokens=225))
Let's take a closer look at this bit:
messages=[
{"role": "user", "content": "What flavors are used in Dr. Pepper?"}
]The messages parameter is a crucial part of interacting with the Claude API. It allows you to provide the conversation history and context for Claude to generate a relevant response.
The messages parameter expects a list of message dictionaries, where each dictionary represents a single message in the conversation. Each message dictionary should have the following keys:
role: A string indicating the role of the message sender. It can be either "user" (for messages sent by the user) or "assistant" (for messages sent by Claude).content: A string or list of content dictionaries representing the actual content of the message. If a string is provided, it will be treated as a single text content block. If a list of content dictionaries is provided, each dictionary should have a "type" (e.g., "text" or "image") and the corresponding content. For now, we'll leavecontentas a single string.
Here's an example of a messages list with a single user message:
messages = [
{"role": "user", "content": "Hello Claude! How are you today?"}
]And here's an example with multiple messages representing a conversation:
messages = [
{"role": "user", "content": "Hello Claude! How are you today?"},
{"role": "assistant", "content": "Hello! I'm doing well, thank you. How can I assist you today?"},
{"role": "user", "content": "Can you tell me a fun fact about ferrets?"},
{"role": "assistant", "content": "Sure! Did you know that excited ferrets make a clucking vocalization known as 'dooking'?"},
]Remember that messages always alternate between user and assistant messages.
The messages format allows us to structure our API calls to Claude in the form of a conversation, allowing for context preservation: The messages format allows for maintaining an entire conversation history, including both user and assistant messages. This ensures that Claude has access to the full context of the conversation when generating responses, leading to more coherent and relevant outputs.
Note: many use-cases don't require a conversation history, and there's nothing wrong with providing a list of messages that only contains a single message!
Quiz
What are the two required keys in each message?
- a) "sender" and "text"
- b) "role" and "content"
- c) "user" and "assistant"
- d) "input" and "output"
The correct answer is b. Every message should have a "role" and "content"
Inspecting the message response
Next, let's take a look at the shape of the response we get back from Claude.
Let's ask Claude to do something simple:
response = client.messages.create(
model="claude-3-haiku-20240307",
max_tokens=1000,
messages=[
{"role": "user", "content": "Translate hello to French. Respond with a single word"}
]
)Now let's inspect the contents of the response that we get back:
responseOutput
Message(id='msg_01SuDqJSTJaRpkDmHGrbfxCt', content=[ContentBlock(text='Bonjour.', type='text')], model='claude-3-haiku-20240307', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(input_tokens=19, output_tokens=8))
We get back a Message object that contains a handful of properties. Here's an example:
Message(id='msg_01Mq5gDnUmDESukTgwPV8xtG', content=[TextBlock(text='Bonjour.', type='text')], model='claude-3-haiku-20240307', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(input_tokens=19, output_tokens=8))The most important piece of information is the content property: this contains the actual content the model generated for us. This is a list of content blocks, each of which has a type that determines its shape.
In order to access the actual text content of the model's response, we need to do the following:
print(response.content[0].text)Output
Bonjour.
In addition to content, the Message object contains some other pieces of information:
id- a unique object identifiertype- The object type, which will always be "message"role- The conversational role of the generated message. This will always be "assistant".model- The model that handled the request and generated the responsestop_reason- The reason the model stopped generating. We'll learn more about this later.stop_sequence- We'll learn more about this shortly.usage- information on billing and rate-limit usage. Contains information on:input_tokens- The number of input tokens that were used.output_tokens- The number of output tokens that were used.
It's important to know that we have access to these pieces of information, but if you only remember one thing, make it this: content contains the actual model-generated content
Exercise
Write a function called translate that expects two arguments:
- A word
- A language
When you call the translate function, it should return the result of asking Claude to translate word into language. For example:
translate("hello", "Spanish")
# 'The word "hello" translated into Spanish is: Hola'
translate("chicken", "Italian")
# 'The Italian word for "chicken" is: pollo'Bonus points if you can write a prompt so that Claude only responds with the translated word and no preamble, like this:
translate("chicken", "Italian")
# 'pollo'Here's one possible solution:
def translate(word, language):
response = client.messages.create(
model="claude-3-opus-20240229",
max_tokens=1000,
messages=[
{"role": "user", "content": f"Translate the word {word} into {language}. Only respond with the translated word, nothing else"}
]
)
return response.content[0].text Message list mistakes
Mistake #1: starting with an assistant message
When you're starting out, it's easy to make mistakes when working with the messages list. The list of messages must start with a user message. The following code generates an error because the messages list starts with an assistant message:
response = client.messages.create(
model="claude-3-haiku-20240307",
max_tokens=1000,
messages=[
{"role": "assistant", "content": "Hello there!"}
]
)
print(response.content[0].text)Output
[0;31m---------------------------------------------------------------------------[0m
[0;31mBadRequestError[0m Traceback (most recent call last)
Cell [0;32mIn[10], line 1[0m
[0;32m----> 1[0m response [38;5;241m=[39m [43mclient[49m[38;5;241;43m.[39;49m[43mmessages[49m[38;5;241;43m.[39;49m[43mcreate[49m[43m([49m
[1;32m 2[0m [43m [49m[43mmodel[49m[38;5;241;43m=[39;49m[38;5;124;43m"[39;49m[38;5;124;43mclaude-3-haiku-20240307[39;49m[38;5;124;43m"[39;49m[43m,[49m
[1;32m 3[0m [43m [49m[43mmax_tokens[49m[38;5;241;43m=[39;49m[38;5;241;43m1000[39;49m[43m,[49m
[1;32m 4[0m [43m [49m[43mmessages[49m[38;5;241;43m=[39;49m[43m[[49m
[1;32m 5[0m [43m [49m[43m{[49m[38;5;124;43m"[39;49m[38;5;124;43mrole[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[38;5;124;43m"[39;49m[38;5;124;43massistant[39;49m[38;5;124;43m"[39;49m[43m,[49m[43m [49m[38;5;124;43m"[39;49m[38;5;124;43mcontent[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[38;5;124;43m"[39;49m[38;5;124;43mTranslate hello to French. Respond with a single word[39;49m[38;5;124;43m"[39;49m[43m}[49m
[1;32m 6[0m [43m [49m[43m][49m
[1;32m 7[0m [43m)[49m
[1;32m 8[0m [38;5;28mprint[39m(response[38;5;241m.[39mcontent[[38;5;241m0[39m][38;5;241m.[39mtext)
File [0;32m/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/anthropic/_utils/_utils.py:277[0m, in [0;36mrequired_args.<locals>.inner.<locals>.wrapper[0;34m(*args, **kwargs)[0m
[1;32m 275[0m msg [38;5;241m=[39m [38;5;124mf[39m[38;5;124m"[39m[38;5;124mMissing required argument: [39m[38;5;132;01m{[39;00mquote(missing[[38;5;241m0[39m])[38;5;132;01m}[39;00m[38;5;124m"[39m
[1;32m 276[0m [38;5;28;01mraise[39;00m [38;5;167;01mTypeError[39;00m(msg)
[0;32m--> 277[0m [38;5;28;01mreturn[39;00m [43mfunc[49m[43m([49m[38;5;241;43m*[39;49m[43margs[49m[43m,[49m[43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m)[49m
File [0;32m/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/anthropic/resources/messages.py:681[0m, in [0;36mMessages.create[0;34m(self, max_tokens, messages, model, metadata, stop_sequences, stream, system, temperature, top_k, top_p, extra_headers, extra_query, extra_body, timeout)[0m
[1;32m 650[0m [38;5;129m@required_args[39m([[38;5;124m"[39m[38;5;124mmax_tokens[39m[38;5;124m"[39m, [38;5;124m"[39m[38;5;124mmessages[39m[38;5;124m"[39m, [38;5;124m"[39m[38;5;124mmodel[39m[38;5;124m"[39m], [[38;5;124m"[39m[38;5;124mmax_tokens[39m[38;5;124m"[39m, [38;5;124m"[39m[38;5;124mmessages[39m[38;5;124m"[39m, [38;5;124m"[39m[38;5;124mmodel[39m[38;5;124m"[39m, [38;5;124m"[39m[38;5;124mstream[39m[38;5;124m"[39m])
[1;32m 651[0m [38;5;28;01mdef[39;00m [38;5;21mcreate[39m(
[1;32m 652[0m [38;5;28mself[39m,
[0;32m (...)[0m
[1;32m 679[0m timeout: [38;5;28mfloat[39m [38;5;241m|[39m httpx[38;5;241m.[39mTimeout [38;5;241m|[39m [38;5;28;01mNone[39;00m [38;5;241m|[39m NotGiven [38;5;241m=[39m [38;5;241m600[39m,
[1;32m 680[0m ) [38;5;241m-[39m[38;5;241m>[39m Message [38;5;241m|[39m Stream[MessageStreamEvent]:
[0;32m--> 681[0m [38;5;28;01mreturn[39;00m [38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_post[49m[43m([49m
[1;32m 682[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43m/v1/messages[39;49m[38;5;124;43m"[39;49m[43m,[49m
[1;32m 683[0m [43m [49m[43mbody[49m[38;5;241;43m=[39;49m[43mmaybe_transform[49m[43m([49m
[1;32m 684[0m [43m [49m[43m{[49m
[1;32m 685[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43mmax_tokens[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43mmax_tokens[49m[43m,[49m
[1;32m 686[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43mmessages[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43mmessages[49m[43m,[49m
[1;32m 687[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43mmodel[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43mmodel[49m[43m,[49m
[1;32m 688[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43mmetadata[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43mmetadata[49m[43m,[49m
[1;32m 689[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43mstop_sequences[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43mstop_sequences[49m[43m,[49m
[1;32m 690[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43mstream[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43mstream[49m[43m,[49m
[1;32m 691[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43msystem[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43msystem[49m[43m,[49m
[1;32m 692[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43mtemperature[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43mtemperature[49m[43m,[49m
[1;32m 693[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43mtop_k[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43mtop_k[49m[43m,[49m
[1;32m 694[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43mtop_p[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43mtop_p[49m[43m,[49m
[1;32m 695[0m [43m [49m[43m}[49m[43m,[49m
[1;32m 696[0m [43m [49m[43mmessage_create_params[49m[38;5;241;43m.[39;49m[43mMessageCreateParams[49m[43m,[49m
[1;32m 697[0m [43m [49m[43m)[49m[43m,[49m
[1;32m 698[0m [43m [49m[43moptions[49m[38;5;241;43m=[39;49m[43mmake_request_options[49m[43m([49m
[1;32m 699[0m [43m [49m[43mextra_headers[49m[38;5;241;43m=[39;49m[43mextra_headers[49m[43m,[49m[43m [49m[43mextra_query[49m[38;5;241;43m=[39;49m[43mextra_query[49m[43m,[49m[43m [49m[43mextra_body[49m[38;5;241;43m=[39;49m[43mextra_body[49m[43m,[49m[43m [49m[43mtimeout[49m[38;5;241;43m=[39;49m[43mtimeout[49m
[1;32m 700[0m [43m [49m[43m)[49m[43m,[49m
[1;32m 701[0m [43m [49m[43mcast_to[49m[38;5;241;43m=[39;49m[43mMessage[49m[43m,[49m
[1;32m 702[0m [43m [49m[43mstream[49m[38;5;241;43m=[39;49m[43mstream[49m[43m [49m[38;5;129;43;01mor[39;49;00m[43m [49m[38;5;28;43;01mFalse[39;49;00m[43m,[49m
[1;32m 703[0m [43m [49m[43mstream_cls[49m[38;5;241;43m=[39;49m[43mStream[49m[43m[[49m[43mMessageStreamEvent[49m[43m][49m[43m,[49m
[1;32m 704[0m [43m [49m[43m)[49m
File [0;32m/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/anthropic/_base_client.py:1232[0m, in [0;36mSyncAPIClient.post[0;34m(self, path, cast_to, body, options, files, stream, stream_cls)[0m
[1;32m 1218[0m [38;5;28;01mdef[39;00m [38;5;21mpost[39m(
[1;32m 1219[0m [38;5;28mself[39m,
[1;32m 1220[0m path: [38;5;28mstr[39m,
[0;32m (...)[0m
[1;32m 1227[0m stream_cls: [38;5;28mtype[39m[_StreamT] [38;5;241m|[39m [38;5;28;01mNone[39;00m [38;5;241m=[39m [38;5;28;01mNone[39;00m,
[1;32m 1228[0m ) [38;5;241m-[39m[38;5;241m>[39m ResponseT [38;5;241m|[39m _StreamT:
[1;32m 1229[0m opts [38;5;241m=[39m FinalRequestOptions[38;5;241m.[39mconstruct(
[1;32m 1230[0m method[38;5;241m=[39m[38;5;124m"[39m[38;5;124mpost[39m[38;5;124m"[39m, url[38;5;241m=[39mpath, json_data[38;5;241m=[39mbody, files[38;5;241m=[39mto_httpx_files(files), [38;5;241m*[39m[38;5;241m*[39moptions
[1;32m 1231[0m )
[0;32m-> 1232[0m [38;5;28;01mreturn[39;00m cast(ResponseT, [38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mrequest[49m[43m([49m[43mcast_to[49m[43m,[49m[43m [49m[43mopts[49m[43m,[49m[43m [49m[43mstream[49m[38;5;241;43m=[39;49m[43mstream[49m[43m,[49m[43m [49m[43mstream_cls[49m[38;5;241;43m=[39;49m[43mstream_cls[49m[43m)[49m)
File [0;32m/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/anthropic/_base_client.py:921[0m, in [0;36mSyncAPIClient.request[0;34m(self, cast_to, options, remaining_retries, stream, stream_cls)[0m
[1;32m 912[0m [38;5;28;01mdef[39;00m [38;5;21mrequest[39m(
[1;32m 913[0m [38;5;28mself[39m,
[1;32m 914[0m cast_to: Type[ResponseT],
[0;32m (...)[0m
[1;32m 919[0m stream_cls: [38;5;28mtype[39m[_StreamT] [38;5;241m|[39m [38;5;28;01mNone[39;00m [38;5;241m=[39m [38;5;28;01mNone[39;00m,
[1;32m 920[0m ) [38;5;241m-[39m[38;5;241m>[39m ResponseT [38;5;241m|[39m _StreamT:
[0;32m--> 921[0m [38;5;28;01mreturn[39;00m [38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_request[49m[43m([49m
[1;32m 922[0m [43m [49m[43mcast_to[49m[38;5;241;43m=[39;49m[43mcast_to[49m[43m,[49m
[1;32m 923[0m [43m [49m[43moptions[49m[38;5;241;43m=[39;49m[43moptions[49m[43m,[49m
[1;32m 924[0m [43m [49m[43mstream[49m[38;5;241;43m=[39;49m[43mstream[49m[43m,[49m
[1;32m 925[0m [43m [49m[43mstream_cls[49m[38;5;241;43m=[39;49m[43mstream_cls[49m[43m,[49m
[1;32m 926[0m [43m [49m[43mremaining_retries[49m[38;5;241;43m=[39;49m[43mremaining_retries[49m[43m,[49m
[1;32m 927[0m [43m [49m[43m)[49m
File [0;32m/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/anthropic/_base_client.py:1012[0m, in [0;36mSyncAPIClient._request[0;34m(self, cast_to, options, remaining_retries, stream, stream_cls)[0m
[1;32m 1009[0m err[38;5;241m.[39mresponse[38;5;241m.[39mread()
[1;32m 1011[0m log[38;5;241m.[39mdebug([38;5;124m"[39m[38;5;124mRe-raising status error[39m[38;5;124m"[39m)
[0;32m-> 1012[0m [38;5;28;01mraise[39;00m [38;5;28mself[39m[38;5;241m.[39m_make_status_error_from_response(err[38;5;241m.[39mresponse) [38;5;28;01mfrom[39;00m [38;5;28;01mNone[39;00m
[1;32m 1014[0m [38;5;28;01mreturn[39;00m [38;5;28mself[39m[38;5;241m.[39m_process_response(
[1;32m 1015[0m cast_to[38;5;241m=[39mcast_to,
[1;32m 1016[0m options[38;5;241m=[39moptions,
[0;32m (...)[0m
[1;32m 1019[0m stream_cls[38;5;241m=[39mstream_cls,
[1;32m 1020[0m )
[0;31mBadRequestError[0m: Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': 'messages: first message must use the "user" role'}}Mistake #2: improperly alternating messages
Messages must alternate between user and assistant, and we'll get an error if we don't follow this rule:
response = client.messages.create(
model="claude-3-haiku-20240307",
max_tokens=1000,
messages=[
{"role": "user", "content": "Hey there!"},
{"role": "assistant", "content": "Hi there!"},
{"role": "assistant", "content": "How can I help you??"}
]
)
print(response.content[0].text)Output
[0;31m---------------------------------------------------------------------------[0m
[0;31mBadRequestError[0m Traceback (most recent call last)
Cell [0;32mIn[12], line 1[0m
[0;32m----> 1[0m response [38;5;241m=[39m [43mclient[49m[38;5;241;43m.[39;49m[43mmessages[49m[38;5;241;43m.[39;49m[43mcreate[49m[43m([49m
[1;32m 2[0m [43m [49m[43mmodel[49m[38;5;241;43m=[39;49m[38;5;124;43m"[39;49m[38;5;124;43mclaude-3-haiku-20240307[39;49m[38;5;124;43m"[39;49m[43m,[49m
[1;32m 3[0m [43m [49m[43mmax_tokens[49m[38;5;241;43m=[39;49m[38;5;241;43m1000[39;49m[43m,[49m
[1;32m 4[0m [43m [49m[43mmessages[49m[38;5;241;43m=[39;49m[43m[[49m
[1;32m 5[0m [43m [49m[43m{[49m[38;5;124;43m"[39;49m[38;5;124;43mrole[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[38;5;124;43m"[39;49m[38;5;124;43muser[39;49m[38;5;124;43m"[39;49m[43m,[49m[43m [49m[38;5;124;43m"[39;49m[38;5;124;43mcontent[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[38;5;124;43m"[39;49m[38;5;124;43mHey there![39;49m[38;5;124;43m"[39;49m[43m}[49m[43m,[49m
[1;32m 6[0m [43m [49m[43m{[49m[38;5;124;43m"[39;49m[38;5;124;43mrole[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[38;5;124;43m"[39;49m[38;5;124;43massistant[39;49m[38;5;124;43m"[39;49m[43m,[49m[43m [49m[38;5;124;43m"[39;49m[38;5;124;43mcontent[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[38;5;124;43m"[39;49m[38;5;124;43mHi there![39;49m[38;5;124;43m"[39;49m[43m}[49m[43m,[49m
[1;32m 7[0m [43m [49m[43m{[49m[38;5;124;43m"[39;49m[38;5;124;43mrole[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[38;5;124;43m"[39;49m[38;5;124;43massistant[39;49m[38;5;124;43m"[39;49m[43m,[49m[43m [49m[38;5;124;43m"[39;49m[38;5;124;43mcontent[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[38;5;124;43m"[39;49m[38;5;124;43mHow can I help you??[39;49m[38;5;124;43m"[39;49m[43m}[49m
[1;32m 8[0m [43m [49m[43m][49m
[1;32m 9[0m [43m)[49m
[1;32m 10[0m [38;5;28mprint[39m(response[38;5;241m.[39mcontent[[38;5;241m0[39m][38;5;241m.[39mtext)
File [0;32m/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/anthropic/_utils/_utils.py:277[0m, in [0;36mrequired_args.<locals>.inner.<locals>.wrapper[0;34m(*args, **kwargs)[0m
[1;32m 275[0m msg [38;5;241m=[39m [38;5;124mf[39m[38;5;124m"[39m[38;5;124mMissing required argument: [39m[38;5;132;01m{[39;00mquote(missing[[38;5;241m0[39m])[38;5;132;01m}[39;00m[38;5;124m"[39m
[1;32m 276[0m [38;5;28;01mraise[39;00m [38;5;167;01mTypeError[39;00m(msg)
[0;32m--> 277[0m [38;5;28;01mreturn[39;00m [43mfunc[49m[43m([49m[38;5;241;43m*[39;49m[43margs[49m[43m,[49m[43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m)[49m
File [0;32m/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/anthropic/resources/messages.py:681[0m, in [0;36mMessages.create[0;34m(self, max_tokens, messages, model, metadata, stop_sequences, stream, system, temperature, top_k, top_p, extra_headers, extra_query, extra_body, timeout)[0m
[1;32m 650[0m [38;5;129m@required_args[39m([[38;5;124m"[39m[38;5;124mmax_tokens[39m[38;5;124m"[39m, [38;5;124m"[39m[38;5;124mmessages[39m[38;5;124m"[39m, [38;5;124m"[39m[38;5;124mmodel[39m[38;5;124m"[39m], [[38;5;124m"[39m[38;5;124mmax_tokens[39m[38;5;124m"[39m, [38;5;124m"[39m[38;5;124mmessages[39m[38;5;124m"[39m, [38;5;124m"[39m[38;5;124mmodel[39m[38;5;124m"[39m, [38;5;124m"[39m[38;5;124mstream[39m[38;5;124m"[39m])
[1;32m 651[0m [38;5;28;01mdef[39;00m [38;5;21mcreate[39m(
[1;32m 652[0m [38;5;28mself[39m,
[0;32m (...)[0m
[1;32m 679[0m timeout: [38;5;28mfloat[39m [38;5;241m|[39m httpx[38;5;241m.[39mTimeout [38;5;241m|[39m [38;5;28;01mNone[39;00m [38;5;241m|[39m NotGiven [38;5;241m=[39m [38;5;241m600[39m,
[1;32m 680[0m ) [38;5;241m-[39m[38;5;241m>[39m Message [38;5;241m|[39m Stream[MessageStreamEvent]:
[0;32m--> 681[0m [38;5;28;01mreturn[39;00m [38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_post[49m[43m([49m
[1;32m 682[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43m/v1/messages[39;49m[38;5;124;43m"[39;49m[43m,[49m
[1;32m 683[0m [43m [49m[43mbody[49m[38;5;241;43m=[39;49m[43mmaybe_transform[49m[43m([49m
[1;32m 684[0m [43m [49m[43m{[49m
[1;32m 685[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43mmax_tokens[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43mmax_tokens[49m[43m,[49m
[1;32m 686[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43mmessages[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43mmessages[49m[43m,[49m
[1;32m 687[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43mmodel[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43mmodel[49m[43m,[49m
[1;32m 688[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43mmetadata[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43mmetadata[49m[43m,[49m
[1;32m 689[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43mstop_sequences[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43mstop_sequences[49m[43m,[49m
[1;32m 690[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43mstream[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43mstream[49m[43m,[49m
[1;32m 691[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43msystem[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43msystem[49m[43m,[49m
[1;32m 692[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43mtemperature[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43mtemperature[49m[43m,[49m
[1;32m 693[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43mtop_k[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43mtop_k[49m[43m,[49m
[1;32m 694[0m [43m [49m[38;5;124;43m"[39;49m[38;5;124;43mtop_p[39;49m[38;5;124;43m"[39;49m[43m:[49m[43m [49m[43mtop_p[49m[43m,[49m
[1;32m 695[0m [43m [49m[43m}[49m[43m,[49m
[1;32m 696[0m [43m [49m[43mmessage_create_params[49m[38;5;241;43m.[39;49m[43mMessageCreateParams[49m[43m,[49m
[1;32m 697[0m [43m [49m[43m)[49m[43m,[49m
[1;32m 698[0m [43m [49m[43moptions[49m[38;5;241;43m=[39;49m[43mmake_request_options[49m[43m([49m
[1;32m 699[0m [43m [49m[43mextra_headers[49m[38;5;241;43m=[39;49m[43mextra_headers[49m[43m,[49m[43m [49m[43mextra_query[49m[38;5;241;43m=[39;49m[43mextra_query[49m[43m,[49m[43m [49m[43mextra_body[49m[38;5;241;43m=[39;49m[43mextra_body[49m[43m,[49m[43m [49m[43mtimeout[49m[38;5;241;43m=[39;49m[43mtimeout[49m
[1;32m 700[0m [43m [49m[43m)[49m[43m,[49m
[1;32m 701[0m [43m [49m[43mcast_to[49m[38;5;241;43m=[39;49m[43mMessage[49m[43m,[49m
[1;32m 702[0m [43m [49m[43mstream[49m[38;5;241;43m=[39;49m[43mstream[49m[43m [49m[38;5;129;43;01mor[39;49;00m[43m [49m[38;5;28;43;01mFalse[39;49;00m[43m,[49m
[1;32m 703[0m [43m [49m[43mstream_cls[49m[38;5;241;43m=[39;49m[43mStream[49m[43m[[49m[43mMessageStreamEvent[49m[43m][49m[43m,[49m
[1;32m 704[0m [43m [49m[43m)[49m
File [0;32m/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/anthropic/_base_client.py:1232[0m, in [0;36mSyncAPIClient.post[0;34m(self, path, cast_to, body, options, files, stream, stream_cls)[0m
[1;32m 1218[0m [38;5;28;01mdef[39;00m [38;5;21mpost[39m(
[1;32m 1219[0m [38;5;28mself[39m,
[1;32m 1220[0m path: [38;5;28mstr[39m,
[0;32m (...)[0m
[1;32m 1227[0m stream_cls: [38;5;28mtype[39m[_StreamT] [38;5;241m|[39m [38;5;28;01mNone[39;00m [38;5;241m=[39m [38;5;28;01mNone[39;00m,
[1;32m 1228[0m ) [38;5;241m-[39m[38;5;241m>[39m ResponseT [38;5;241m|[39m _StreamT:
[1;32m 1229[0m opts [38;5;241m=[39m FinalRequestOptions[38;5;241m.[39mconstruct(
[1;32m 1230[0m method[38;5;241m=[39m[38;5;124m"[39m[38;5;124mpost[39m[38;5;124m"[39m, url[38;5;241m=[39mpath, json_data[38;5;241m=[39mbody, files[38;5;241m=[39mto_httpx_files(files), [38;5;241m*[39m[38;5;241m*[39moptions
[1;32m 1231[0m )
[0;32m-> 1232[0m [38;5;28;01mreturn[39;00m cast(ResponseT, [38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mrequest[49m[43m([49m[43mcast_to[49m[43m,[49m[43m [49m[43mopts[49m[43m,[49m[43m [49m[43mstream[49m[38;5;241;43m=[39;49m[43mstream[49m[43m,[49m[43m [49m[43mstream_cls[49m[38;5;241;43m=[39;49m[43mstream_cls[49m[43m)[49m)
File [0;32m/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/anthropic/_base_client.py:921[0m, in [0;36mSyncAPIClient.request[0;34m(self, cast_to, options, remaining_retries, stream, stream_cls)[0m
[1;32m 912[0m [38;5;28;01mdef[39;00m [38;5;21mrequest[39m(
[1;32m 913[0m [38;5;28mself[39m,
[1;32m 914[0m cast_to: Type[ResponseT],
[0;32m (...)[0m
[1;32m 919[0m stream_cls: [38;5;28mtype[39m[_StreamT] [38;5;241m|[39m [38;5;28;01mNone[39;00m [38;5;241m=[39m [38;5;28;01mNone[39;00m,
[1;32m 920[0m ) [38;5;241m-[39m[38;5;241m>[39m ResponseT [38;5;241m|[39m _StreamT:
[0;32m--> 921[0m [38;5;28;01mreturn[39;00m [38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_request[49m[43m([49m
[1;32m 922[0m [43m [49m[43mcast_to[49m[38;5;241;43m=[39;49m[43mcast_to[49m[43m,[49m
[1;32m 923[0m [43m [49m[43moptions[49m[38;5;241;43m=[39;49m[43moptions[49m[43m,[49m
[1;32m 924[0m [43m [49m[43mstream[49m[38;5;241;43m=[39;49m[43mstream[49m[43m,[49m
[1;32m 925[0m [43m [49m[43mstream_cls[49m[38;5;241;43m=[39;49m[43mstream_cls[49m[43m,[49m
[1;32m 926[0m [43m [49m[43mremaining_retries[49m[38;5;241;43m=[39;49m[43mremaining_retries[49m[43m,[49m
[1;32m 927[0m [43m [49m[43m)[49m
File [0;32m/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/anthropic/_base_client.py:1012[0m, in [0;36mSyncAPIClient._request[0;34m(self, cast_to, options, remaining_retries, stream, stream_cls)[0m
[1;32m 1009[0m err[38;5;241m.[39mresponse[38;5;241m.[39mread()
[1;32m 1011[0m log[38;5;241m.[39mdebug([38;5;124m"[39m[38;5;124mRe-raising status error[39m[38;5;124m"[39m)
[0;32m-> 1012[0m [38;5;28;01mraise[39;00m [38;5;28mself[39m[38;5;241m.[39m_make_status_error_from_response(err[38;5;241m.[39mresponse) [38;5;28;01mfrom[39;00m [38;5;28;01mNone[39;00m
[1;32m 1014[0m [38;5;28;01mreturn[39;00m [38;5;28mself[39m[38;5;241m.[39m_process_response(
[1;32m 1015[0m cast_to[38;5;241m=[39mcast_to,
[1;32m 1016[0m options[38;5;241m=[39moptions,
[0;32m (...)[0m
[1;32m 1019[0m stream_cls[38;5;241m=[39mstream_cls,
[1;32m 1020[0m )
[0;31mBadRequestError[0m: Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': 'messages: roles must alternate between "user" and "assistant", but found multiple "assistant" roles in a row'}}Messages list use cases
Putting words in Claude's mouth
Another common strategy for getting very specific outputs is to "put words in Claude's mouth". Instead of only providing user messages to Claude, we can also supply an assistant message that Claude will use when generating output.
When using Anthropic’s API, you are not limited to just the user message. If you supply an assistant message, Claude will continue the conversation from the last assistant token. Just remember that we must start with a user message.
Suppose I want Claude to write me a haiku that starts with the first line, "calming mountain air". I can provide the following conversation history:
messages=[
{"role": "user", "content": f"Generate a beautiful haiku"},
{"role": "assistant", "content": "calming mountain air"}
]We tell Claude that we want it to generate a Haiku AND we put the first line of the Haiku in Claude's mouth
response = client.messages.create(
model="claude-3-haiku-20240307",
max_tokens=500,
messages=[
{"role": "user", "content": f"Generate a beautiful haiku"},
{"role": "assistant", "content": "calming mountain air"}
]
)
print(response.content[0].text)Output
, dancing sunlight on still waters, nature's gentle grace.
To get the entire haiku, starting with the line we provided:
print("calming mountain air" + response.content[0].text)Output
calming mountain air, dancing sunlight on still waters, nature's gentle grace.
Few-shot prompting
One of the most useful prompting strategies is called "few-shot prompting" which involves providing a model with a small number of examples. These examples help guide Claude's generated output. The messages conversation history is an easy way to provide examples to Claude.
For example, suppose we want to use Claude to analyze the sentiment in tweets. We could start by simply asking Claude to "please analyze the sentiment in this tweet: " and see what sort of output we get:
response = client.messages.create(
model="claude-3-haiku-20240307",
max_tokens=500,
messages=[
{"role": "user", "content": f"Analyze the sentiment in this tweet: Just tried the new spicy pickles from @PickleCo, and my taste buds are doing a happy dance! 🌶️🥒 #pickleslove #spicyfood"},
]
)
print(response.content[0].text)Output
The sentiment in this tweet is overwhelmingly positive. The user expresses their enjoyment of the new spicy pickles from @PickleCo, using enthusiastic language and emojis to convey their delight. Positive indicators: 1. "My taste buds are doing a happy dance!" - This phrase indicates that the user is extremely pleased with the taste of the pickles, to the point of eliciting a joyful physical response. 2. Emojis - The use of the hot pepper 🌶️ and cucumber 🥒 emojis further emphasizes the user's excitement about the spicy pickles. 3. Hashtags - The inclusion of #pickleslove and #spicyfood hashtags suggests that the user has a strong affinity for pickles and spicy food, and the new product aligns perfectly with their preferences. 4. Exclamation mark - The exclamation mark at the end of the first sentence adds emphasis to the user's positive experience. Overall, the tweet conveys a strong sense of satisfaction, excitement, and enjoyment related to trying the new spicy pickles from @PickleCo.
The first time I ran the above code, Claude generated this long response:
The sentiment in this tweet is overwhelmingly positive. The user expresses their enjoyment of the new spicy pickles from @PickleCo, using enthusiastic language and emojis to convey their delight.
Positive indicators:
1. "My taste buds are doing a happy dance!" - This phrase indicates that the user is extremely pleased with the taste of the pickles, to the point of eliciting a joyful physical response.
2. Emojis - The use of the hot pepper 🌶️ and cucumber 🥒 emojis further emphasizes the user's excitement about the spicy pickles.
3. Hashtags - The inclusion of #pickleslove and #spicyfood hashtags suggests that the user has a strong affinity for pickles and spicy food, and the new product aligns perfectly with their preferences.
4. Exclamation mark - The exclamation mark at the end of the first sentence adds emphasis to the user's positive experience.
Overall, the tweet conveys a strong sense of satisfaction, excitement, and enjoyment related to trying the new spicy pickles from @PickleCo.This is a great response, but it's probably way more information than we need from Claude, especially if we're trying to automate the sentiment analysis of a large number of tweets.
We might prefer that Claude respond with a standardized output format like a single word (POSITIVE, NEUTRAL, NEGATIVE) or a numeric value (1, 0, -1). For readability and simplicity, let's get Claude to respond with either "POSITIVE" or "NEGATIVE". One way of doing this is through few-shot prompting. We can provide Claude with a conversation history that shows exactly how we want it to respond:
messages=[
{"role": "user", "content": "Unpopular opinion: Pickles are disgusting. Don't @ me"},
{"role": "assistant", "content": "NEGATIVE"},
{"role": "user", "content": "I think my love for pickles might be getting out of hand. I just bought a pickle-shaped pool float"},
{"role": "assistant", "content": "POSITIVE"},
{"role": "user", "content": "Seriously why would anyone ever eat a pickle? Those things are nasty!"},
{"role": "assistant", "content": "NEGATIVE"},
{"role": "user", "content": "Just tried the new spicy pickles from @PickleCo, and my taste buds are doing a happy dance! 🌶️🥒 #pickleslove #spicyfood"},
]response = client.messages.create(
model="claude-3-haiku-20240307",
max_tokens=500,
messages=[
{"role": "user", "content": "Unpopular opinion: Pickles are disgusting. Don't @ me"},
{"role": "assistant", "content": "NEGATIVE"},
{"role": "user", "content": "I think my love for pickles might be getting out of hand. I just bought a pickle-shaped pool float"},
{"role": "assistant", "content": "POSITIVE"},
{"role": "user", "content": "Seriously why would anyone ever eat a pickle? Those things are nasty!"},
{"role": "assistant", "content": "NEGATIVE"},
{"role": "user", "content": "Just tried the new spicy pickles from @PickleCo, and my taste buds are doing a happy dance! 🌶️🥒 #pickleslove #spicyfood"},
]
)
print(response.content[0].text)Output
POSITIVE
Exercise
Your task: build a chatbot
Build a simple multi-turn command-line chatbot script. The messages format lends itself to building chat-based applications. To build a chat-bot with Claude, it's as simple as:
- Keep a list to store the conversation history
- Ask a user for a message using
input()and add the user input to the messages list - Send the message history to Claude
- Print out Claude's response to the user
- Add Claude's assistant response to the history
- Go back to step 2 and repeat! (use a loop and provide a way for users to quit!)
```py
conversation_history = []
while True:
user_input = input("User: ")
if user_input.lower() == "quit":
print("Conversation ended.")
break
conversation_history.append({"role": "user", "content": user_input})
response = client.messages.create(
model="claude-3-haiku-20240307",
messages=conversation_history,
max_tokens=500
)
assistant_response = response.content[0].text
print(f"Assistant: {assistant_response}")
conversation_history.append({"role": "assistant", "content": assistant_response})
```