Skip to content

AnswerDotAI/claudette

Repository files navigation

claudette

NB: If you are reading this in GitHub’s readme, we recommend you instead read the much more nicely formatted documentation format of this tutorial.

Claudette is a wrapper for Anthropic’s Python SDK.

The SDK works well, but it is quite low level – it leaves the developer to do a lot of stuff manually. That’s a lot of extra work and boilerplate! Claudette automates pretty much everything that can be automated, whilst providing full control. Amongst the features provided:

  • A Chat class that creates stateful dialogs
  • Support for prefill, which tells Claude what to use as the first few words of its response
  • Convenient image support
  • Simple and convenient support for Claude’s new Tool Use API.

You’ll need to set the ANTHROPIC_API_KEY environment variable to the key provided to you by Anthropic in order to use this library.

Note that this library is the first ever “literate nbdev” project. That means that the actual source code for the library is a rendered Jupyter Notebook which includes callout notes and tips, HTML tables and images, detailed explanations, and teaches how and why the code is written the way it is. Even if you’ve never used the Anthropic Python SDK or Claude API before, you should be able to read the source code. Click Claudette’s Source to read it, or clone the git repo and execute the notebook yourself to see every step of the creation process in action. The tutorial below includes links to API details which will take you to relevant parts of the source. The reason this project is a new kind of literal program is because we take seriously Knuth’s call to action, that we have a “moral commitment” to never write an “illiterate program” – and so we have a commitment to making literate programming and easy and pleasant experience. (For more on this, see this talk from Hamel Husain.)

Let us change our traditional attitude to the construction of programs: Instead of imagining that our main task is to instruct a computer what to do, let us concentrate rather on explaining to human beings what we want a computer to do.” Donald E. Knuth, Literate Programming (1984)

Install

pip install claudette

Getting started

Anthropic’s Python SDK will automatically be installed with Claudette, if you don’t already have it.

import os
# os.environ['ANTHROPIC_LOG'] = 'debug'

To print every HTTP request and response in full, uncomment the above line.

from claudette import *

Claudette only exports the symbols that are needed to use the library, so you can use import * to import them. Alternatively, just use:

import claudette

…and then add the prefix claudette. to any usages of the module.

Claudette provides models, which is a list of models currently available from the SDK.

models
('claude-3-opus-20240229',
 'claude-3-sonnet-20240229',
 'claude-3-haiku-20240307')

For these examples, we’ll use Haiku, since it’s fast and cheap (and surprisingly good!)

model = models[-1]

Chat

The main interface to Claudia is the Chat class, which provides a stateful interface to Claude:

chat = Chat(model, sp="""You are a helpful and concise assistant.""")
chat("I'm Jeremy")

It’s nice to meet you, Jeremy! I’m an AI assistant created by Anthropic. I’m here to help with any questions or tasks you may have. Please let me know if there’s anything I can assist you with.

  • id: msg_01EjhYadGnCCLvkexGeCCgvA
  • content: [{‘text’: “It’s nice to meet you, Jeremy! I’m an AI assistant created by Anthropic. I’m here to help with any questions or tasks you may have. Please let me know if there’s anything I can assist you with.”, ‘type’: ‘text’}]
  • model: claude-3-haiku-20240307
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {‘input_tokens’: 19, ‘output_tokens’: 51}
r = chat("What's my name?")
r

Your name is Jeremy.

  • id: msg_01AWejHwja71nsarE5DKCTgo
  • content: [{‘text’: ‘Your name is Jeremy.’, ‘type’: ‘text’}]
  • model: claude-3-haiku-20240307
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {‘input_tokens’: 78, ‘output_tokens’: 8}

As you see above, displaying the results of a call in a notebook shows just the message contents, with the other details hidden behind a collapsible section. Alternatively you can print the details:

print(r)
ToolsBetaMessage(id='msg_01AWejHwja71nsarE5DKCTgo', content=[TextBlock(text='Your name is Jeremy.', type='text')], model='claude-3-haiku-20240307', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=In: 78; Out: 8; Total: 86)

Claude supports adding an extra assistant message at the end, which contains the prefill – i.e. the text we want Claude to assume the response starts with. Let’s try it out:

chat("Concisely, what is the meaning of life?",
     prefill='According to Douglas Adams,')

According to Douglas Adams, “The answer to the ultimate question of life, the universe, and everything is 42.”

  • id: msg_01HgD1ymaAzDxmTwbjjJq3rn
  • content: [{‘text’: ‘According to Douglas Adams, “The answer to the ultimate question of life, the universe, and everything is 42.”’, ‘type’: ‘text’}]
  • model: claude-3-haiku-20240307
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {‘input_tokens’: 106, ‘output_tokens’: 23}

You can add stream=True to stream the results as soon as they arrive (although you will only see the gradual generation if you execute the notebook yourself, of course!)

for o in chat("Concisely, what book was that in?", prefill='It was in', stream=True):
    print(o, end='')
It was in The Hitchhiker's Guide to the Galaxy.

Tool use

Tool use lets Claude use external tools.

We use docments to make defining Python functions as ergonomic as possible. Each parameter (and the return value) should have a type, and a docments comment with the description of what it is. As an example we’ll write a simple function that adds numbers together, and will tell us when it’s being called:

def sums(
    a:int,  # First thing to sum
    b:int=1 # Second thing to sum
) -> int: # The sum of the inputs
    "Adds a + b."
    print(f"Finding the sum of {a} and {b}")
    return a + b

Sometimes Claude will say something like “according to the sums tool the answer is” – generally we’d rather it just tells the user the answer, so we can use a system prompt to help with this:

sp = "Never mention what tools you use."

We’ll get Claude to add up some long numbers:

a,b = 604542,6458932
pr = f"What is {a}+{b}?"
pr
'What is 604542+6458932?'

To use tools, pass a list of them to Chat:

chat = Chat(model, sp=sp, tools=[sums])

Now when we call that with our prompt, Claude doesn’t return the answer, but instead returns a tool_use message, which means we have to call the named tool with the provided parameters:

r = chat(pr)
r
Finding the sum of 604542 and 6458932

ToolUseBlock(id=‘toolu_01FsSYBgRzcSWBzUfXPHVAEj’, input={‘a’: 604542, ‘b’: 6458932}, name=‘sums’, type=‘tool_use’)

  • id: msg_014wPisMcJqAYPWpVX4ZGbaH
  • content: [{‘id’: ‘toolu_01FsSYBgRzcSWBzUfXPHVAEj’, ‘input’: {‘a’: 604542, ‘b’: 6458932}, ‘name’: ‘sums’, ‘type’: ‘tool_use’}]
  • model: claude-3-haiku-20240307
  • role: assistant
  • stop_reason: tool_use
  • stop_sequence: None
  • type: message
  • usage: {‘input_tokens’: 398, ‘output_tokens’: 72}

Claudette handles all that for us – we just call it again, and it all happens automatically:

chat()

The sum of 604,542 and 6,458,932 is 7,063,474.

  • id: msg_01H4g1VTiFHTHV9EL35Rb9HE
  • content: [{‘text’: ‘The sum of 604,542 and 6,458,932 is 7,063,474.’, ‘type’: ‘text’}]
  • model: claude-3-haiku-20240307
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {‘input_tokens’: 485, ‘output_tokens’: 28}

You can see how many tokens have been used at any time by checking the use property. Note that (as of May 2024) tool use in Claude uses a lot of tokens, since it automatically adds a large system prompt.

chat.use
In: 883; Out: 100; Total: 983

We can do everything needed to use tools in a single step, by using Chat.toolloop. This can even call multiple tools as needed solve a problem. For example, let’s define a tool to handle multiplication:

def mults(
    a:int,  # First thing to multiply
    b:int=1 # Second thing to multiply
) -> int: # The product of the inputs
    "Multiplies a * b."
    print(f"Finding the product of {a} and {b}")
    return a * b

Now with a single call we can calculate (a+b)*2 – by passing show_trace we can see each response from Claude in the process:

chat = Chat(model, sp=sp, tools=[sums,mults])
pr = f'Calculate ({a}+{b})*2'
pr
'Calculate (604542+6458932)*2'
chat.toolloop(pr, trace_func=print)
Finding the sum of 604542 and 6458932
ToolsBetaMessage(id='msg_01SXeCYNCQ6Vb1VYobVLbWgU', content=[TextBlock(text='Here is the calculation:', type='text'), ToolUseBlock(id='toolu_0169pKwJyjHPG5VCSPYkvvMo', input={'a': 604542, 'b': 6458932}, name='sums', type='tool_use')], model='claude-3-haiku-20240307', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=In: 508; Out: 78; Total: 586)
Finding the product of 7063474 and 2
ToolsBetaMessage(id='msg_01Lrv6bgkWAvtLFKZoU6CTgh', content=[ToolUseBlock(id='toolu_016io96DFsXyVrLDSzHQ87RB', input={'a': 7063474, 'b': 2}, name='mults', type='tool_use')], model='claude-3-haiku-20240307', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=In: 600; Out: 72; Total: 672)
ToolsBetaMessage(id='msg_01LBaXtqa9erqyCq7QZshxS3', content=[TextBlock(text='Therefore, the result of (604542 + 6458932) * 2 is 14126948.', type='text')], model='claude-3-haiku-20240307', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=In: 686; Out: 29; Total: 715)

Therefore, the result of (604542 + 6458932) * 2 is 14126948.

  • id: msg_01LBaXtqa9erqyCq7QZshxS3
  • content: [{‘text’: ‘Therefore, the result of (604542 + 6458932) * 2 is 14126948.’, ‘type’: ‘text’}]
  • model: claude-3-haiku-20240307
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {‘input_tokens’: 686, ‘output_tokens’: 29}

Images

Claude can handle image data as well. As everyone knows, when testing image APIs you have to use a cute puppy.

fn = Path('samples/puppy.jpg')
display.Image(filename=fn, width=200)

We create a Chat object as before:

chat = Chat(model)

Claudia expects images as a list of bytes, so we read in the file:

img = fn.read_bytes()

Prompts to Claudia can be lists, containing text, images, or both, eg:

chat([img, "In brief, what color flowers are in this image?"])

The image contains purple or lavender-colored flowers, which appear to be daisies or a similar type of flower.

  • id: msg_01X5J1GB26h8QLsFmyZM1buo
  • content: [{‘text’: ‘The image contains purple or lavender-colored flowers, which appear to be daisies or a similar type of flower.’, ‘type’: ‘text’}]
  • model: claude-3-haiku-20240307
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {‘input_tokens’: 110, ‘output_tokens’: 28}

The image is included as input tokens.

chat.use
In: 110; Out: 28; Total: 138

Alternatively, Claudette supports creating a multi-stage chat with separate image and text prompts. For instance, you can pass just the image as the initial prompt (in which case Claude will make some general comments about what it sees), and then follow up with questions in additional prompts:

chat = Chat(model)
chat(img)

The image shows a cute puppy lying in the grass. The puppy appears to be a Cavalier King Charles Spaniel, with a fluffy brown and white coat. The puppy is looking directly at the camera with a friendly, curious expression. In the background, there are some purple flowers, adding a nice natural setting to the scene. The image captures the adorable and playful nature of this young pup.

  • id: msg_011LA5EKWh6rV8R3STcD5em8
  • content: [{‘text’: ‘The image shows a cute puppy lying in the grass. The puppy appears to be a Cavalier King Charles Spaniel, with a fluffy brown and white coat. The puppy is looking directly at the camera with a friendly, curious expression. In the background, there are some purple flowers, adding a nice natural setting to the scene. The image captures the adorable and playful nature of this young pup.’, ‘type’: ‘text’}]
  • model: claude-3-haiku-20240307
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {‘input_tokens’: 98, ‘output_tokens’: 91}
chat('What direction is the puppy facing?')

The puppy in the image is facing towards the camera, looking directly at the viewer.

  • id: msg_01ELRVyupU4WfN9Ctbq6WabB
  • content: [{‘text’: ‘The puppy in the image is facing towards the camera, looking directly at the viewer.’, ‘type’: ‘text’}]
  • model: claude-3-haiku-20240307
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {‘input_tokens’: 200, ‘output_tokens’: 21}
chat('What color is it?')

The puppy in the image has a brown and white coat color. It appears to be a Cavalier King Charles Spaniel breed, with the characteristic long, silky fur in those colors.

  • id: msg_01L2Zre86FUWrnLAN8jk8ogF
  • content: [{‘text’: ‘The puppy in the image has a brown and white coat color. It appears to be a Cavalier King Charles Spaniel breed, with the characteristic long, silky fur in those colors.’, ‘type’: ‘text’}]
  • model: claude-3-haiku-20240307
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {‘input_tokens’: 229, ‘output_tokens’: 44}

Note that the image is passed in again for every input in the dialog, so that number of input tokens increases quickly with this kind of chat.

chat.use
In: 527; Out: 156; Total: 683