Trace Images & Media Files
Capture the images and files your LLM application sends to models in your traces
Overview
When your AI app sends images or documents to a model, confident-trace records them on the LLM span alongside the rest of the messages. You can see exactly what the model was shown when you open the trace on Confident AI, instead of a placeholder where the image used to be.
There's nothing extra to set up. init() already instruments supported integrations, and each one captures the media parts of the messages it records.
How It Works
Each media part in a message is recorded in one of three ways, depending on how you passed it to the model and what type of file it is:
| You send the model | Recorded on the span as |
|---|---|
| An image or PDF as inline data (base64) | The file itself, viewable in the trace |
| A URL, or a cloud storage location (S3, GCS) | A reference to that location, the file is never downloaded |
| Inline audio, video, or any other file type | The media type only, with the content marked as omitted |
| A provider file ID (an uploaded file) | The media type only, with the content marked as omitted |
Images (any image/* type) and PDFs are the only inline types whose content is kept. A URL reference is kept for any type, so an audio file you pass to the model by URL still shows up as a link to that audio file.
Supported Integrations
The following integrations capture images and PDFs from the messages they record:
| Integration | Python | TypeScript |
|---|---|---|
| OpenAI | Yes | Yes |
| Anthropic | Yes | Yes |
| Google GenAI | Yes | Yes |
| AWS Bedrock | Yes | — |
| LiteLLM | Yes | — |
| OpenRouter | Yes | Yes |
| Portkey | Yes | Yes |
| LangChain and LangGraph | Yes | Yes |
| Vercel AI SDK | — | Yes |
| Mastra | — | Yes |
Frameworks that don't record model calls themselves, such as LlamaIndex, CrewAI, Agno, and smolagents, capture media whenever the provider they call is one of the integrations above.
Capture Images & Files
Send images and files to your model the way your provider SDK expects them. As long as init() has run, the media parts are captured with the rest of the messages:
import base64
from pathlib import Path
from confident_trace import init, shutdown
from openai import OpenAI
init()
client = OpenAI()
image = base64.b64encode(Path("chart.png").read_bytes()).decode()
document = base64.b64encode(Path("report.pdf").read_bytes()).decode()
try:
response = client.chat.completions.create(
model="gpt-4.1",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "Does the chart match the report?"},
{
"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{image}"},
},
{
"type": "file",
"file": {
"filename": "report.pdf",
"file_data": f"data:application/pdf;base64,{document}",
},
},
],
}],
)
print(response.choices[0].message.content)
finally:
shutdown()import { readFileSync } from "node:fs";
import OpenAI from "openai";
import { init } from "confident-trace";
const runtime = init();
const client = new OpenAI();
const image = readFileSync("chart.png").toString("base64");
const document = readFileSync("report.pdf").toString("base64");
try {
const response = await client.chat.completions.create({
model: "gpt-4.1",
messages: [{
role: "user",
content: [
{ type: "text", text: "Does the chart match the report?" },
{ type: "image_url", image_url: { url: `data:image/png;base64,${image}` } },
{
type: "file",
file: {
filename: "report.pdf",
file_data: `data:application/pdf;base64,${document}`,
},
},
],
}],
});
console.log(response.choices[0]?.message.content);
} finally {
await runtime.shutdown();
}Remember to launch your entry point with the Node preload so the OpenAI call is instrumented.
The same applies to every supported integration: an Anthropic image or document block, a Gemini inline file, or a LangChain image or file content block is captured the same way. If you pass the image as a URL instead of inline data, the span records the URL as a reference.
Media Size Limits
To keep exports bounded, confident-trace limits how much inline media each span carries. References (URLs and storage locations) are never counted toward these limits.
| Limit | Python init() | TypeScript init() | Default | What it does |
|---|---|---|---|---|
| Per media item | max_media_bytes | maxMediaBytes | 5 MiB | The largest single image or PDF that is kept, measured in decoded bytes |
| Per span | — | maxMediaTotalBytes | 16 MiB | The total inline media kept across all of a span's input and output messages |
A media item over either limit is omitted, never truncated. It stays in the message as a media part with its type and an omission marker, so the span still shows that the model was sent a file. Within a span, media items are kept in the order they appear until the per-span budget runs out.
from confident_trace import init
init(max_media_bytes=10 * 1024 * 1024)import { init } from "confident-trace";
const runtime = init({
maxMediaBytes: 10 * 1024 * 1024,
maxMediaTotalBytes: 32 * 1024 * 1024,
});Disable Media Capture
To keep text content but stop exporting inline media, set max_media_bytes / maxMediaBytes to 0. Every inline image and PDF is then recorded with its type and an omission marker, while URL references are still recorded.
from confident_trace import init
init(max_media_bytes=0)import { init } from "confident-trace";
const runtime = init({ maxMediaBytes: 0 });Next Steps
With media captured on your traces, group them into conversations or control what content leaves your app.
Thread Traces
Group traces into threads to track multi-turn conversations and evaluate entire workflows.
Mask Sensitive Trace Data
Redact sensitive data and set content limits before traces are sent to Confident AI.
Last updated on