Multimodal content¶
Images, audio, video and documents in a request can be recorded on the span - by reference, or uploaded to a store with the span pointing at them. Off by default.
| Variable | Default | |
|---|---|---|
GENAI_OTEL_MEDIA_CAPTURE_MODE |
off |
reference_only: type, MIME type and size; nothing kept or uploaded. full: the bytes are redacted (if a redactor is registered), uploaded, and the span carries the URI |
GENAI_OTEL_MEDIA_STORE |
none |
filesystem, http, s3, minio |
GENAI_OTEL_MEDIA_STORE_ENDPOINT |
Directory (filesystem, default ./genai-otel-media), gateway URL (http), or S3 endpoint (s3 / minio; unset for AWS) |
|
GENAI_OTEL_MEDIA_STORE_BUCKET |
genai-otel-media |
Bucket, or the subdirectory of the filesystem store |
GENAI_OTEL_MEDIA_STORE_PREFIX |
traces/{date}/{trace_id}/ |
Key prefix |
GENAI_OTEL_MEDIA_STORE_ACCESS_KEY, ..._SECRET_KEY |
S3 credentials; otherwise the AWS SDK's own chain | |
GENAI_OTEL_MEDIA_MAX_BYTES |
10 MiB | Larger parts are not uploaded (stripped_reason = size_exceeded); inline media over it is not even decoded |
GENAI_OTEL_MEDIA_ALLOWED_MODALITIES |
image,audio,video,document |
Others are stripped (modality_not_allowed) |
GENAI_OTEL_MEDIA_REDACTOR |
See below |
Providers: OpenAI (and every client the OpenAI instrumentor traces), Anthropic, Google Gemini, Groq - as in the reference.
On the span¶
Per message n: gen_ai.prompt.{n}.role. Per part m: gen_ai.prompt.{n}.content.{m}.type,
then for media media_uri, media_mime_type, media_byte_size, media_source
(inline_offloaded, external_url, reference_only), and gen_ai.media.stripped_reason
when the bytes were dropped.
Text needs content capture as well. A text part's .text, and the canonical
gen_ai.input.messages (written under the GenAI semconv opt-in), carry what was said, so
they are written only with GENAI_ENABLE_CONTENT_CAPTURE on too - unlike the reference,
which writes them whenever media capture is on (SDK_ADDITIONS.md section 5). Media
capture is its own switch: turning it on is the decision to keep the media.
An upload can take a while; the span waits for it and still ends with the time the call
ended. flushTelemetry() waits for spans still uploading.
Redaction¶
The reference loads a redactor from a Python dotted path, with built-in ones that strip EXIF data, blur faces and scan PDFs. Here a redactor is a function the application registers:
import { setMediaRedactor } from '@traceverse/genai-otel-instrument-node';
setMediaRedactor(async (modality, mimeType, bytes) => stripMetadata(bytes));
It receives the bytes and returns what to upload. If it throws, the bytes are dropped
(redactor_error). If GENAI_OTEL_MEDIA_REDACTOR names a redactor and none is
registered - a Python dotted path, say - the bytes are dropped (redactor_not_allowed):
media is never uploaded unredacted when redaction was asked for. The reference's built-in
redactors need imaging libraries and are not ported.
Stores¶
| Store | Runtime | |
|---|---|---|
filesystem |
Node | Files are written 0600 under a 0700 directory; a key that would leave the store is refused. URI: file://... |
http |
all | PUT <endpoint>/<key>. Credentials in the endpoint URL are sent as Basic authorization and never appear in the URI on the span |
s3, minio |
Node | Needs @aws-sdk/client-s3 installed by the application. Path-style addressing; the bucket is created if missing. URI: <endpoint>/<bucket>/<key>, or s3://<bucket>/<key> for AWS |
Under a strict profile, or with external egress off, the network stores refuse a plain
http:// endpoint, as in the reference.
What was run¶
The detector, the offload rules and the canonical form are checked against vectors made by
running the reference's genai_otel.media (tests/fixtures/media/python-vectors.json).
The span attributes are tested through the real OpenAI and Anthropic clients against the
mock provider, with the filesystem store. The HTTP and S3 stores are tested against a mock
server. Not run against a real MinIO or S3.