Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions python/mirascope/llm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
AssistantContentChunk,
AssistantContentPart,
Audio,
Base64ImageSource,
Document,
Image,
Text,
Expand All @@ -43,6 +44,7 @@
ToolCallEndChunk,
ToolCallStartChunk,
ToolOutput,
URLImageSource,
UserContentPart,
)
from .context import Context
Expand Down Expand Up @@ -130,6 +132,7 @@
"Audio",
"AuthenticationError",
"BadRequestError",
"Base64ImageSource",
"ChunkIterator",
"ConnectionError",
"Context",
Expand Down Expand Up @@ -180,6 +183,7 @@
"ToolNotFoundError",
"ToolOutput",
"Toolkit",
"URLImageSource",
"UserContent",
"UserContentPart",
"UserMessage",
Expand Down
36 changes: 33 additions & 3 deletions python/mirascope/llm/clients/anthropic/_utils/encode.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
import json
from collections.abc import Sequence
from functools import lru_cache
from typing import TypedDict, cast
from typing import Literal, TypedDict, cast
from typing_extensions import Required

from anthropic import NotGiven, types as anthropic_types

from ....content import ContentPart
from ....exceptions import FormattingModeNotSupportedError
from ....content import ContentPart, ImageMimeType
from ....exceptions import FeatureNotSupportedError, FormattingModeNotSupportedError
from ....formatting import (
Format,
FormattableT,
Expand All @@ -23,6 +23,19 @@

DEFAULT_MAX_TOKENS = 16000

AnthropicImageMimeType = Literal["image/jpeg", "image/png", "image/gif", "image/webp"]


def encode_image_mime_type(
mime_type: ImageMimeType,
) -> AnthropicImageMimeType:
"""Convert an ImageMimeType into anthropic supported mime type"""
if mime_type in ("image/jpeg", "image/png", "image/gif", "image/webp"):
return mime_type
raise FeatureNotSupportedError(
feature=f"Image with mime_type: {mime_type}", provider="anthropic"
) # pragma: no cover


class MessageCreateKwargs(TypedDict, total=False):
"""Kwargs for Anthropic Message.create method."""
Expand Down Expand Up @@ -53,6 +66,23 @@ def _encode_content(
for part in content:
if part.type == "text":
blocks.append(anthropic_types.TextBlockParam(type="text", text=part.text))
elif part.type == "image":
source: (
anthropic_types.Base64ImageSourceParam
| anthropic_types.URLImageSourceParam
)
if part.source.type == "base64_image_source":
source = anthropic_types.Base64ImageSourceParam(
type="base64",
media_type=encode_image_mime_type(part.source.mime_type),
data=part.source.data,
)
else: # url_image_source
source = anthropic_types.URLImageSourceParam(
type="url",
url=part.source.url,
)
blocks.append(anthropic_types.ImageBlockParam(type="image", source=source))
elif part.type == "tool_output":
blocks.append(
anthropic_types.ToolResultBlockParam(
Expand Down
3 changes: 2 additions & 1 deletion python/mirascope/llm/content/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
TextDocumentSource,
URLDocumentSource,
)
from .image import Base64ImageSource, Image, URLImageSource
from .image import Base64ImageSource, Image, ImageMimeType, URLImageSource
from .text import Text, TextChunk, TextEndChunk, TextStartChunk
from .thought import Thought, ThoughtChunk, ThoughtEndChunk, ThoughtStartChunk
from .tool_call import ToolCall, ToolCallChunk, ToolCallEndChunk, ToolCallStartChunk
Expand Down Expand Up @@ -49,6 +49,7 @@
"ContentPart",
"Document",
"Image",
"ImageMimeType",
"Text",
"TextChunk",
"TextDocumentSource",
Expand Down
3 changes: 3 additions & 0 deletions python/tests/e2e/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,10 @@ def formatting_mode(
# python. (Ruff --fix will clean out unused symbols)
SNAPSHOT_IMPORT_SYMBOLS = [
"AssistantMessage",
"Base64ImageSource",
"FinishReason",
"Format",
"Image",
"SystemMessage",
"Text",
"TextChunk",
Expand All @@ -156,5 +158,6 @@ def formatting_mode(
"ToolCallEndChunk",
"ToolCallStartChunk",
"ToolOutput",
"URLImageSource",
"UserMessage",
]

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
interactions:
- request:
body: '{"max_tokens":16000,"messages":[{"role":"user","content":[{"type":"text","text":"Describe
the following image in one sentence"},{"type":"image","source":{"type":"url","url":"https://en.wikipedia.org/static/images/icons/wikipedia.png"}}]}],"model":"claude-sonnet-4-0"}'
headers:
accept:
- application/json
accept-encoding:
- gzip, deflate
anthropic-version:
- '2023-06-01'
connection:
- keep-alive
content-length:
- '267'
content-type:
- application/json
host:
- api.anthropic.com
user-agent:
- Anthropic/Python 0.64.0
x-stainless-arch:
- arm64
x-stainless-async:
- 'false'
x-stainless-lang:
- python
x-stainless-os:
- MacOS
x-stainless-package-version:
- 0.64.0
x-stainless-read-timeout:
- '600'
x-stainless-retry-count:
- '0'
x-stainless-runtime:
- CPython
x-stainless-runtime-version:
- 3.10.16
x-stainless-timeout:
- '600'
method: POST
uri: https://api.anthropic.com/v1/messages
response:
body:
string: !!binary |
H4sIAAAAAAAAA3SR34sTQQyA/5WQ5620Z/uyryIUFEERFUSWOJPuxpvNjJPMtV7p/y5bLOLJPQXy
fflBcsY5R07YY0jUIq8sq7Kvtqu79d1uvdtssUOJ2ONs47DefHj/6cfptG17svxl//rN21e2f+fY
of8qvFhsRiNjhzWnJUFmYk66OCGrszr2X8833/l0rV5Cjx8nMRADnxgkZJUAn+VeCkchSHnMHRyY
vFXREQisTFwlUILSHh8Tw5jyd4aZIkMrkA9wnMT5RotwYIOj+ATLQBCFB6qSm0EiHRuNbEAawUKV
4gaHmmegmpvG607HXFN8gZdvHZrnMlQmy4o9ssbBW1X8A4x/NtbA2GtLqcN2PUp/RtHSfPB8z2rY
v9x2GChMPITK5JJ1+FdY33hlis+xW+3Sn8vEM1dKw27+3/9LN9NTeukwN3+6nXF9kMCDC1fscflk
pBrxcvkNAAD//wMAZHCFKjoCAAA=
headers:
CF-RAY:
- 991d76f70ef049b2-SEA
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Tue, 21 Oct 2025 02:53:30 GMT
Server:
- cloudflare
Transfer-Encoding:
- chunked
X-Robots-Tag:
- none
anthropic-organization-id:
- 217a607f-ed5e-40af-8a7d-f83ed52d59d6
anthropic-ratelimit-input-tokens-limit:
- '2000000'
anthropic-ratelimit-input-tokens-remaining:
- '2000000'
anthropic-ratelimit-input-tokens-reset:
- '2025-10-21T02:53:29Z'
anthropic-ratelimit-output-tokens-limit:
- '400000'
anthropic-ratelimit-output-tokens-remaining:
- '400000'
anthropic-ratelimit-output-tokens-reset:
- '2025-10-21T02:53:30Z'
anthropic-ratelimit-requests-limit:
- '4000'
anthropic-ratelimit-requests-remaining:
- '3999'
anthropic-ratelimit-requests-reset:
- '2025-10-21T02:53:27Z'
anthropic-ratelimit-tokens-limit:
- '2400000'
anthropic-ratelimit-tokens-remaining:
- '2400000'
anthropic-ratelimit-tokens-reset:
- '2025-10-21T02:53:29Z'
cf-cache-status:
- DYNAMIC
request-id:
- req_011CUKb3QTvJnQoJcS9GAnS8
strict-transport-security:
- max-age=31536000; includeSubDomains; preload
via:
- 1.1 google
x-envoy-upstream-service-time:
- '2797'
status:
code: 200
message: OK
version: 1

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,10 +1,54 @@
from inline_snapshot import snapshot

from mirascope.llm import (
AssistantMessage,
Image,
Text,
URLImageSource,
UserMessage,
)

test_snapshot = snapshot(
{
"exception": {
"type": "NotImplementedError",
"args": "('Content type image not supported',)",
"response": {
"provider": "anthropic",
"model_id": "claude-sonnet-4-0",
"params": {},
"finish_reason": None,
"messages": [
UserMessage(
content=[
Text(text="Describe the following image in one sentence"),
Image(
source=URLImageSource(
type="url_image_source",
url="https://en.wikipedia.org/static/images/icons/wikipedia.png",
)
),
]
),
AssistantMessage(
content=[
Text(
text="This is the iconic Wikipedia logo, featuring a spherical puzzle globe made up of white puzzle pieces with text in various languages and scripts from around the world."
)
],
provider="anthropic",
model_id="claude-sonnet-4-0",
raw_message={
"role": "assistant",
"content": [
{
"citations": None,
"text": "This is the iconic Wikipedia logo, featuring a spherical puzzle globe made up of white puzzle pieces with text in various languages and scripts from around the world.",
"type": "text",
}
],
},
),
],
"format": None,
"tools": [],
}
}
)
4 changes: 2 additions & 2 deletions python/tests/e2e/input/test_call_with_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def test_call_with_image_content(
@llm.call(provider=provider, model_id=model_id)
def analyze_image(image_url: str) -> llm.UserContent:
return [
"Describe the following image",
"Describe the following image in one sentence",
llm.Image.download(image_url),
]

Expand All @@ -47,7 +47,7 @@ def test_call_with_image_url(
@llm.call(provider=provider, model_id=model_id)
def analyze_image(image_url: str) -> llm.UserContent:
return [
"Describe the following image",
"Describe the following image in one sentence",
llm.Image.from_url(image_url),
]

Expand Down