Skip to content

Commit 579f013

Browse files
feat(messages): add support for image inputs (anthropics#359)
1 parent 35b0347 commit 579f013

26 files changed

+756
-362
lines changed

README.md

+23-22
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ message = client.messages.create(
3535
messages=[
3636
{
3737
"role": "user",
38-
"content": "How does a court case get to the supreme court?",
38+
"content": "Hello, Claude",
3939
}
4040
],
41-
model="claude-2.1",
41+
model="claude-3-opus-20240229",
4242
)
4343
print(message.content)
4444
```
@@ -69,10 +69,10 @@ async def main() -> None:
6969
messages=[
7070
{
7171
"role": "user",
72-
"content": "How does a court case get to the supreme court?",
72+
"content": "Hello, Claude",
7373
}
7474
],
75-
model="claude-2.1",
75+
model="claude-3-opus-20240229",
7676
)
7777
print(message.content)
7878

@@ -96,10 +96,10 @@ stream = client.messages.create(
9696
messages=[
9797
{
9898
"role": "user",
99-
"content": "your prompt here",
99+
"content": "Hello, Claude",
100100
}
101101
],
102-
model="claude-2.1",
102+
model="claude-3-opus-20240229",
103103
stream=True,
104104
)
105105
for event in stream:
@@ -118,10 +118,10 @@ stream = await client.messages.create(
118118
messages=[
119119
{
120120
"role": "user",
121-
"content": "your prompt here",
121+
"content": "Hello, Claude",
122122
}
123123
],
124-
model="claude-2.1",
124+
model="claude-3-opus-20240229",
125125
stream=True,
126126
)
127127
async for event in stream:
@@ -147,7 +147,7 @@ async def main() -> None:
147147
"content": "Say hello there!",
148148
}
149149
],
150-
model="claude-2.1",
150+
model="claude-3-opus-20240229",
151151
) as stream:
152152
async for text in stream.text_stream:
153153
print(text, end="", flush=True)
@@ -190,11 +190,12 @@ For a more fully fledged example see [`examples/bedrock.py`](https://github.com/
190190

191191
## Token counting
192192

193-
You can estimate billing for a given request with the `client.count_tokens()` method, eg:
193+
You can see the exact usage for a given request through the `usage` response property, e.g.
194194

195195
```py
196-
client = Anthropic()
197-
client.count_tokens('Hello world!') # 3
196+
message = client.messages.create(...)
197+
message.usage
198+
# Usage(input_tokens=25, output_tokens=13)
198199
```
199200

200201
## Using types
@@ -227,10 +228,10 @@ try:
227228
messages=[
228229
{
229230
"role": "user",
230-
"content": "your prompt here",
231+
"content": "Hello, Claude",
231232
}
232233
],
233-
model="claude-2.1",
234+
model="claude-3-opus-20240229",
234235
)
235236
except anthropic.APIConnectionError as e:
236237
print("The server could not be reached")
@@ -279,10 +280,10 @@ client.with_options(max_retries=5).messages.create(
279280
messages=[
280281
{
281282
"role": "user",
282-
"content": "Can you help me effectively ask for a raise at work?",
283+
"content": "Hello, Claude",
283284
}
284285
],
285-
model="claude-2.1",
286+
model="claude-3-opus-20240229",
286287
)
287288
```
288289

@@ -311,10 +312,10 @@ client.with_options(timeout=5 * 1000).messages.create(
311312
messages=[
312313
{
313314
"role": "user",
314-
"content": "Where can I get a good coffee in my neighbourhood?",
315+
"content": "Hello, Claude",
315316
}
316317
],
317-
model="claude-2.1",
318+
model="claude-3-opus-20240229",
318319
)
319320
```
320321

@@ -374,9 +375,9 @@ response = client.messages.with_raw_response.create(
374375
max_tokens=1024,
375376
messages=[{
376377
"role": "user",
377-
"content": "Where can I get a good coffee in my neighbourhood?",
378+
"content": "Hello, Claude",
378379
}],
379-
model="claude-2.1",
380+
model="claude-3-opus-20240229",
380381
)
381382
print(response.headers.get('X-My-Header'))
382383

@@ -407,10 +408,10 @@ with client.messages.with_streaming_response.create(
407408
messages=[
408409
{
409410
"role": "user",
410-
"content": "Where can I get a good coffee in my neighbourhood?",
411+
"content": "Hello, Claude",
411412
}
412413
],
413-
model="claude-2.1",
414+
model="claude-3-opus-20240229",
414415
) as response:
415416
print(response.headers.get("X-My-Header"))
416417

api.md

+1-18
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,3 @@
1-
# Anthropic
2-
3-
Methods:
4-
5-
- <code>client.<a href="./src/anthropic/_client.py">count_tokens</a>(\*args) -> int</code>
6-
7-
# Completions
8-
9-
Types:
10-
11-
```python
12-
from anthropic.types import Completion
13-
```
14-
15-
Methods:
16-
17-
- <code title="post /v1/complete">client.completions.<a href="./src/anthropic/resources/completions.py">create</a>(\*\*<a href="src/anthropic/types/completion_create_params.py">params</a>) -> <a href="./src/anthropic/types/completion.py">Completion</a></code>
18-
191
# Messages
202

213
Types:
@@ -26,6 +8,7 @@ from anthropic.types import (
268
ContentBlockDeltaEvent,
279
ContentBlockStartEvent,
2810
ContentBlockStopEvent,
11+
ImageBlockParam,
2912
Message,
3013
MessageDeltaEvent,
3114
MessageDeltaUsage,

examples/images.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from pathlib import Path
2+
3+
from anthropic import Anthropic
4+
5+
client = Anthropic()
6+
7+
response = client.messages.create(
8+
max_tokens=1024,
9+
messages=[
10+
{
11+
"role": "user",
12+
"content": [
13+
{
14+
"type": "text",
15+
"text": "Hello!",
16+
},
17+
{
18+
"type": "image",
19+
"source": {
20+
"type": "base64",
21+
"media_type": "image/png",
22+
"data": Path(__file__).parent.joinpath("logo.png"),
23+
},
24+
},
25+
],
26+
},
27+
],
28+
model="claude-3-opus-20240229",
29+
)
30+
print(response.model_dump_json(indent=2))

examples/logo.png

6.25 KB
Loading

examples/messages.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"content": "Hello!",
1111
}
1212
],
13-
model="claude-2.1",
13+
model="claude-3-opus-20240229",
1414
)
1515
print(response)
1616

@@ -30,6 +30,6 @@
3030
"content": "How are you?",
3131
},
3232
],
33-
model="claude-2.1",
33+
model="claude-3-opus-20240229",
3434
)
3535
print(response2)

examples/messages_stream.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ async def main() -> None:
1414
"content": "Say hello there!",
1515
}
1616
],
17-
model="claude-2.1",
17+
model="claude-3-opus-20240229",
1818
) as stream:
1919
async for text in stream.text_stream:
2020
print(text, end="", flush=True)

examples/messages_stream_handler.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ async def main() -> None:
2222
"content": "Say hello there!",
2323
}
2424
],
25-
model="claude-2.1",
25+
model="claude-3-opus-20240229",
2626
event_handler=MyStream,
2727
) as stream:
2828
accumulated = await stream.get_final_message()
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/env -S poetry run python
2+
3+
import asyncio
4+
5+
import anthropic
6+
from anthropic import AsyncAnthropic
7+
8+
9+
async def main() -> None:
10+
client = AsyncAnthropic()
11+
12+
res = await client.completions.create(
13+
model="claude-2.1",
14+
prompt=f"{anthropic.HUMAN_PROMPT} how does a court case get to the Supreme Court? {anthropic.AI_PROMPT}",
15+
max_tokens_to_sample=1000,
16+
)
17+
print(res.completion)
18+
19+
20+
asyncio.run(main())
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env -S poetry run python
2+
3+
import anthropic
4+
from anthropic import Anthropic
5+
6+
7+
def main() -> None:
8+
client = Anthropic()
9+
10+
res = client.completions.create(
11+
model="claude-2.1",
12+
prompt=f"{anthropic.HUMAN_PROMPT} how does a court case get to the Supreme Court? {anthropic.AI_PROMPT}",
13+
max_tokens_to_sample=1000,
14+
)
15+
print(res.completion)
16+
17+
18+
main()
+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#!/usr/bin/env -S poetry run python
2+
3+
import asyncio
4+
5+
from anthropic import AI_PROMPT, HUMAN_PROMPT, Anthropic, APIStatusError, AsyncAnthropic
6+
7+
client = Anthropic()
8+
async_client = AsyncAnthropic()
9+
10+
question = """
11+
Hey Claude! How can I recursively list all files in a directory in Python?
12+
"""
13+
14+
15+
def sync_stream() -> None:
16+
stream = client.completions.create(
17+
prompt=f"{HUMAN_PROMPT} {question}{AI_PROMPT}",
18+
model="claude-2.1",
19+
stream=True,
20+
max_tokens_to_sample=300,
21+
)
22+
23+
for completion in stream:
24+
print(completion.completion, end="", flush=True)
25+
26+
print()
27+
28+
29+
async def async_stream() -> None:
30+
stream = await async_client.completions.create(
31+
prompt=f"{HUMAN_PROMPT} {question}{AI_PROMPT}",
32+
model="claude-2.1",
33+
stream=True,
34+
max_tokens_to_sample=300,
35+
)
36+
37+
async for completion in stream:
38+
print(completion.completion, end="", flush=True)
39+
40+
print()
41+
42+
43+
def stream_error() -> None:
44+
try:
45+
client.completions.create(
46+
prompt=f"{HUMAN_PROMPT} {question}{AI_PROMPT}",
47+
model="claude-unknown-model",
48+
stream=True,
49+
max_tokens_to_sample=300,
50+
)
51+
except APIStatusError as err:
52+
print(f"Caught API status error with response body: {err.response.text}")
53+
54+
55+
sync_stream()
56+
asyncio.run(async_stream())
57+
stream_error()

helpers.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ async with client.messages.stream(
1111
"content": "Say hello there!",
1212
}
1313
],
14-
model="claude-2.1",
14+
model="claude-3-opus-20240229",
1515
) as stream:
1616
async for text in stream.text_stream:
1717
print(text, end="", flush=True)
@@ -74,7 +74,7 @@ async def main() -> None:
7474
"content": "Say hello there!",
7575
}
7676
],
77-
model="claude-2.1",
77+
model="claude-3-opus-20240229",
7878
event_handler=MyStream,
7979
) as stream:
8080
message = await stream.get_final_message()

src/anthropic/_client.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,12 @@ def count_tokens(
267267
self,
268268
text: str,
269269
) -> int:
270-
"""Count the number of tokens in a given string"""
270+
"""Count the number of tokens in a given string.
271+
272+
Note that this is only accurate for older models, e.g. `claude-2.1`. For newer
273+
models this can only be used as a _very_ rough estimate, instead you should rely
274+
on the `usage` property in the response for exact counts.
275+
"""
271276
# Note: tokenizer is untyped
272277
tokenizer = self.get_tokenizer()
273278
encoded_text = tokenizer.encode(text) # type: ignore
@@ -522,7 +527,12 @@ async def count_tokens(
522527
self,
523528
text: str,
524529
) -> int:
525-
"""Count the number of tokens in a given string"""
530+
"""Count the number of tokens in a given string.
531+
532+
Note that this is only accurate for older models, e.g. `claude-2.1`. For newer
533+
models this can only be used as a _very_ rough estimate, instead you should rely
534+
on the `usage` property in the response for exact counts.
535+
"""
526536
# Note: tokenizer is untyped
527537
tokenizer = await self.get_tokenizer()
528538
encoded_text = tokenizer.encode(text) # type: ignore

src/anthropic/lib/streaming/_messages.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,8 @@ def accumulate_event(*, event: MessageStreamEvent, current_snapshot: Message | N
421421
)
422422
elif event.type == "content_block_delta":
423423
content = current_snapshot.content[event.index]
424-
content.text += event.delta.text
424+
if content.type == "text" and event.delta.type == "text_delta":
425+
content.text += event.delta.text
425426
elif event.type == "message_delta":
426427
current_snapshot.stop_reason = event.delta.stop_reason
427428
current_snapshot.stop_sequence = event.delta.stop_sequence

0 commit comments

Comments
 (0)