
Enable attachment uploads
url_upload_enabled so
Openlayer fetches it and keeps its own copy:
Attachments require
openlayer>=0.17.0, and url_upload_enabled requires
openlayer>=0.17.9.Attach a file to a step
Calllog_attachment() inside any traced function to attach media to the step currently
being recorded:
The multimodal helpers live in
openlayer.lib.tracing, not in
openlayer.lib.log_attachment() accepts a file path, raw bytes, a file-like object, or an Attachment
you built yourself:
metadata dict. Use it for whatever you need to filter or
debug on later: the upload channel, a page count, an audio duration, a document revision.
Build attachments explicitly
For more control, construct anAttachment and pass it to log_attachment():
Identical files are uploaded once per trace — attachments are deduplicated by MD5
checksum, so attaching the same image to three steps costs one upload.
Multimodal inputs and outputs
The attachments above hang off a step as supporting artifacts. To model a message that is itself part text and part media — the shape a vision or audio model actually receives — use content items instead:TextContent, ImageContent, AudioContent, and
FileContent — and a single message can mix as many as you need. Openlayer renders the
message in order, so the text and the media it refers to stay together.
Automatic capture from OpenAI
If you send multimodal messages through a traced OpenAI client, you do not need to write any of the above. Openlayer reads the content array and converts it to attachments for you, on both the Chat Completions and Responses APIs:image_url, input_image), audio (input_audio), and files (file) are all
recognized, whether they arrive as a URL, a base64 data URL, or an uploaded file ID.
Generated images in Responses API output are captured the same way.
How attachments appear in Openlayer
Uploaded attachments are rendered wherever the trace is shown — in the row, in the row detail view, and on the individual step:
Every attachment can be downloaded, whatever its type. Downloads keep the attachment’s
name and extension as part of the filename.
The attachment format
Attachments are plain JSON inside your trace, so any client that can publish a row can publish an attachment. This is what the SDK writes:storageUri is what makes an attachment displayable — it is the reference to the copy in
your workspace storage. To obtain one for media you upload yourself, request a presigned
URL, upload the bytes to it, and keep the storageUri that comes back:
url to upload to, any form fields that upload requires, and
the storageUri to record. You can then put the attachment in a column when you
stream the row:
Complete example
An expense claim that arrives as a photo, a voice note, and a policy document — attached to the trace, then read by a vision model:Troubleshooting
My attachment does not appear in the platform
My attachment does not appear in the platform
Check
attachment_upload_enabled=True first — it is off by default, and without it
nothing is uploaded. If the attachment came from Attachment.from_url(), you also
need url_upload_enabled=True, otherwise Openlayer never fetches a copy it can
display.An attachment silently disappeared from the trace
An attachment silently disappeared from the trace
Attachments with no data and no reference are dropped rather than published — most
often because a file path does not exist. The SDK logs a warning when this happens, so
enable logging while you debug:
Will attachment uploads slow down my application?
Will attachment uploads slow down my application?
No. Uploads happen when the trace completes, on a background thread, so your request
is not held up. A failed upload is logged and the trace is still published — you get
the trace without the media rather than an exception.
Looking to add non-media context to your traces instead? See Add metadata to
traces.

