Skip to content

[gen-ai] Add multimodal additions: document modality, byte_size, StrippedPart#3673

Closed
Mandark-droid wants to merge 5 commits into
open-telemetry:mainfrom
Mandark-droid:gen-ai/multimodal-content-parts
Closed

[gen-ai] Add multimodal additions: document modality, byte_size, StrippedPart#3673
Mandark-droid wants to merge 5 commits into
open-telemetry:mainfrom
Mandark-droid:gen-ai/multimodal-content-parts

Conversation

@Mandark-droid

Copy link
Copy Markdown

Summary

Narrow additions to gen-ai-input-messages.json and gen-ai-output-messages.json to better support multimodal LLM workloads:

  1. Add document to the Modality enum — needed for PDF / DOCX workloads (BFSI KYC extraction is a high-volume real example).
  2. Add optional byte_size field to BlobPart, FilePart, and UriPart — useful for cost-of-capture telemetry and storage planning. Optional, additive.
  3. Add new StrippedPart type for fail-closed observability. Records that the instrumentation detected a content part but intentionally did not capture its bytes (size cap exceeded, modality disallowed by configuration, redactor failed, store unavailable, no_store_configured). Without this, consumers cannot distinguish "no media in this turn" from "media was deliberately stripped" — which matters for compliance auditing and for diagnosing capture-pipeline regressions.

Why

Discussed in #3672. Original framing on that issue overstated the gap; on closer reading the existing schemas already define BlobPart, FilePart, UriPart and a Modality enum (image, video, audio). The substantive remaining gap is the three pieces above. The original issue body and follow-up comments now reflect this corrected scope.

Backwards compatibility

Purely additive — no removals, no changes to required fields on existing types. Consumers that don't recognise StrippedPart will fall through to the GenericPart matcher in parts.items.anyOf. Consumers that don't recognise document in the Modality enum can fall through to the string branch of the anyOf already permitted.

Reference implementation

genai-otel-instrument v1.1.1 emits the canonical JSON shape via gen_ai.input.messages / gen_ai.output.messages when OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai, including:

  • BlobPart for inline base64 bytes
  • UriPart with byte_size for offloaded objects (S3 / MinIO)
  • StrippedPart with stripped_reason for fail-closed cases

End-to-end validated through OTel collector → OpenSearch with bytes resolvable from MinIO via the uri field.

Files

  • docs/gen-ai/gen-ai-input-messages.jsonModality enum, three part types gain byte_size, new StrippedPart, ChatMessage.parts.items.anyOf accepts StrippedPart
  • docs/gen-ai/gen-ai-output-messages.json — same changes mirrored on OutputMessage.parts
  • .chloggen/gen-ai-multimodal-narrow.yaml — changelog entry

Open questions / scope notes

  • I have not signed the OTel CLA yet — will sign when the bot prompts on PR open.
  • Have not been able to run make check locally; happy to iterate on whatever the CI surfaces.
  • The data_url enum value for media_source discussed in the issue is not included here because the existing BlobPart (with inline base64 content) already covers that case — keeping the diff minimal.
  • Tool-call multimodal results (computer-use screenshots etc.) are deferred to a follow-up — would likely extend ToolCallResponsePart.

Resolves open-telemetry/semantic-conventions-genai#304 (reviewers: feel free to keep the issue open if you'd like it to remain the umbrella discussion).

@Mandark-droid
Mandark-droid requested review from a team as code owners April 28, 2026 11:07
@github-actions github-actions Bot added enhancement New feature or request area:gen-ai labels Apr 28, 2026
@lmolkova lmolkova moved this from Untriaged to Awaiting codeowners approval in Semantic Conventions Triage Apr 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please edit docs/gen-ai/non-normative/models.ipynb, these files should be generated from there, not manually edited.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Done in ae86b3f — moved the schema additions (document modality, byte_size, StrippedPart, StrippedReason) into docs/gen-ai/non-normative/models.ipynb and regenerated the JSON files from the notebook. Heads-up: regenerating with current pydantic surfaces a few emitter-style deltas vs. the previously-checked-in JSON (single-element oneOf$ref, additionalProperties: true on BlobPart, key ordering inside anyOf branches). These aren't introduced by this PR but become visible now that the notebook is the source of truth — happy to roll them back or pin a pydantic version if you'd prefer the diff stay tighter.

Comment thread docs/gen-ai/gen-ai-input-messages.json Outdated
Mandark-droid pushed a commit to Mandark-droid/semantic-conventions that referenced this pull request Apr 28, 2026
Address review feedback on open-telemetry#3673:

- Move the multimodal schema additions (document modality, byte_size,
  StrippedPart, StrippedReason) into docs/gen-ai/non-normative/models.ipynb
  and regenerate the JSON schemas from the notebook so they are no longer
  hand-edited (per @alexmojaki).
- Add a worked multimodal example to docs/gen-ai/non-normative/examples-llm-calls.md
  demonstrating a document URI part with byte_size and a stripped video part
  with stripped_reason=size_exceeded (per @alexmojaki).

Note: regenerating from the notebook with current pydantic surfaces some
emitter-style differences vs. the previous hand-edited JSON (single-element
$ref vs oneOf, additionalProperties:true on BlobPart, key ordering inside
anyOf branches). These are not introduced by this change set but become
visible once the notebook becomes the source of truth.
@linux-foundation-easycla

linux-foundation-easycla Bot commented Apr 28, 2026

Copy link
Copy Markdown

CLA Signed

The committers listed above are authorized under a signed CLA.

Address review feedback on open-telemetry#3673:

- Move the multimodal schema additions (document modality, byte_size,
  StrippedPart, StrippedReason) into docs/gen-ai/non-normative/models.ipynb
  and regenerate the JSON schemas from the notebook so they are no longer
  hand-edited (per @alexmojaki).
- Add a worked multimodal example to docs/gen-ai/non-normative/examples-llm-calls.md
  demonstrating a document URI part with byte_size and a stripped video part
  with stripped_reason=size_exceeded (per @alexmojaki).

Note: regenerating from the notebook with current pydantic surfaces some
emitter-style differences vs. the previous hand-edited JSON (single-element
$ref vs oneOf, additionalProperties:true on BlobPart, key ordering inside
anyOf branches). These are not introduced by this change set but become
visible once the notebook becomes the source of truth.
@Mandark-droid
Mandark-droid force-pushed the gen-ai/multimodal-content-parts branch from ae86b3f to 5bdf6e6 Compare April 28, 2026 14:26
Comment thread docs/gen-ai/non-normative/models.ipynb Outdated
Comment thread docs/gen-ai/non-normative/models.ipynb Outdated
Comment thread docs/gen-ai/non-normative/models.ipynb Outdated
Address review feedback on open-telemetry#3673:

- Drop the new `StrippedPart` type and the `StrippedReason` enum. Instead,
  make `content` / `file_id` / `uri` optional on BlobPart / FilePart / UriPart
  and add an optional free-form `stripped_reason` field on each. This keeps
  the original part type (and therefore "what kind of part was sent to the
  model") intact while still allowing fail-closed observability when the
  instrumentation intentionally does not capture bytes / id / URI
  (per @alexmojaki).
- Make `stripped_reason` a free-form short string instead of an enum, so
  instrumentations can describe their own capture-skipping conditions
  ("size_exceeded", "modality_not_allowed", etc.) without needing a spec
  change. Drops the previously-introduced enum values that referenced
  implementation-specific concepts ("redactor", "store") (per @alexmojaki).
- Harmonize the `byte_size` description across BlobPart / FilePart / UriPart
  to a single shared phrasing (per @alexmojaki).
- Update examples-llm-calls.md to demonstrate a `document` URI part with
  `byte_size` and a `blob` part stripped via `stripped_reason: size_exceeded`.
- Update chloggen entry to reflect the narrower scope.
Comment thread docs/gen-ai/non-normative/models.ipynb Outdated
Comment on lines +182 to +183
" byte_size: Optional[int] = Field(default=None, ge=0, description=_BYTE_SIZE_DESC)\n",
" stripped_reason: Optional[str] = Field(default=None, description=_STRIPPED_REASON_DESC)\n",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it feels like we've reached the point where we need a common base class for the 3 media parts

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in f627524d — extracted MediaPart as a common base for BlobPart/FilePart/UriPart holding mime_type, modality, byte_size, and stripped_reason. Each part now contributes only its type literal and its own content-bearing field (content / file_id / uri).

Regen findings, flagging proactively: pydantic flattens MediaPart cleanly — no MediaPart entry appears in $defs, and no allOf/$ref indirection is introduced in the three child schemas. The only wire-format diff is property ordering within each child: inherited fields (mime_type, modality, byte_size, stripped_reason) now appear before the subclass-specific fields (type, content/file_id/uri), per pydantic's MRO. Happy to override field ordering if you'd rather keep type first in the JSON.

Address review feedback to introduce a common base class for the three
media-attachment parts. MediaPart holds the fields shared across BlobPart,
FilePart, and UriPart (mime_type, modality, byte_size, stripped_reason).
Each subclass now contributes only its 'type' literal and its own
content-bearing field (content / file_id / uri).

Notebook is shorter and the family relationship between the three parts
is explicit. Regenerated JSON schemas are substantively identical to the
previous version: no allOf/$ref indirection is introduced (pydantic
flattens MediaPart, no MediaPart entry appears in $defs), and the only
diff is property ordering within each subclass (inherited fields appear
before subclass-specific fields, per pydantic's MRO).
@wolfgangcodes

Copy link
Copy Markdown

Hello @Mandark-droid!

With the merging of Move GenAI semantic conventions to its own dedicated repository, the area:gen-ai semantic conventions have a new home. We've made this split in an to be better able to support the rapid pace of change that we are seeing with Gen AI related semantic conventions.

Our ask is that you move this PR to the new repo here: https://github.com/open-telemetry/semantic-conventions-genai

The contribution guide for the new repo can be found here: https://github.com/open-telemetry/semantic-conventions-genai/blob/main/CONTRIBUTING.md. A major difference is making sure there's a representative scenario and reference outlined in:

4. Update reference scenarioss
Changes under model/ or docs/ typically require updating the reference scenarios under reference/ to demonstrate that the proposed updates are capturable.

You may also find that you need to additional updates to you proposal as the new repo use the V2 Schema.

If you would like to discuss or have questions you can reply here, post in slack here, or add an agenda item for the Gen AI SIG.

Thank you so much!

@lmolkova lmolkova moved this from Awaiting codeowners approval to Blocked in Semantic Conventions Triage May 10, 2026
@trask

trask commented May 11, 2026

Copy link
Copy Markdown
Member

Closing here, but looking forward to seeing this in the new repo!

@Mandark-droid

Copy link
Copy Markdown
Author

Thanks @wolfgangcodes for the redirect and @trask for closing — refiled in the new repo. Per the new CONTRIBUTING.md's "keep PRs small" guidance and the explicit splitting suggestion, I broke the three original additions into independent PRs against semantic-conventions-genai:

All three carry forward the design pivots from the review on this PR (notebook is source of truth, harmonized byte_size description, no StrippedPart type — extend existing parts instead, no closed StrippedReason enum — free-form short string). make check-policies and make generate-all run clean against each branch.

One open question I've flagged on each new PR: there are currently no multimodal scenarios under reference/scenarios/ for any library, and CONTRIBUTING.md step 4 says docs/ changes "typically" need a reference update. Happy to add a multimodal reference scenario as a follow-up — flagging upfront to defer to reviewer preference on shape (extend an existing <library> scenario vs. a dedicated <library>-multimodal one).

github-merge-queue Bot pushed a commit to open-telemetry/semantic-conventions-genai that referenced this pull request May 16, 2026
* gen-ai: add document modality to multimodal content parts

Adds the 'document' value to the Modality enum in the gen-ai input,
output, and system-instructions message JSON schemas. Today PDF/DOCX
and similar parts have to fall through to the free-form string branch
of the modality anyOf; this change gives them a first-class enum value
matching the existing image/video/audio entries.

Pure addition: no removals, no changes to required fields. The notebook
source under docs/gen-ai/non-normative/models.ipynb has been updated to
keep the regen-from-notebook flow consistent, and a worked example was
added to examples-llm-calls.md.

Follow-up from open-telemetry/semantic-conventions#3673 (closed and
refiled here per the new repo home for gen-ai semantic conventions).

* gen-ai: add document-modality reference scenario to openai

Per @trask's direction on PR #143, the multimodal scenario lives in
#142 so it can carry the document modality first, then be extended
in #143 (byte_size) and #144 (stripped_reason) once this lands.

Adds run_chat_with_document_input_reference(client) to the openai
scenario, exercising the new 'document' enum value on a UriPart in
gen_ai.input.messages. The OpenAI SDK call uses the documented file
content block; the canonical OTel attribute carries a TextPart plus a
document-modality UriPart, mirroring how a KYC document-extraction
workload surfaces in the wire shape.

* gen-ai: apply ruff format to multimodal scenario block

* gen-ai: address scenario-evaluation feedback on PR #142

@trask's reference-scenario SKILL evaluation flagged that the previous
openai multimodal block fabricated a URI and mime_type that aren't on
the chat.completions.create boundary -- file_id is opaque, neither
yields a public URI or mime type without a separate Files-API roundtrip.

Two fixes per the evaluation:

1. Rewrite the openai scenario to use the inline file_data shape
   ({'type': 'file', 'file': {'file_data': 'data:application/pdf;base64,...',
   'filename': '...'}}) and emit a BlobPart instead of a UriPart. Every
   emitted BlobPart field now derives directly from the SDK arg:
     - mime_type from the data-URI prefix
     - content from the base64 portion
     - modality 'document' from the classification of application/pdf
   No fabricated URIs.

2. Add run_chat_with_document_input() to the anthropic scenario.
   Anthropic's Messages API has a first-class document content block
   ({'type': 'document', 'source': {'type': 'base64',
   'media_type': '...', 'data': '...'}}), so the BlobPart emission is
   directly observable from the SDK call boundary with no derivation
   gymnastics.

Both scenarios run cleanly against the local mock server; weaver
live-check reports no after_resolution policy violations on either.
The bedrock-converse demonstration trask also suggested is deferred to
a follow-up (DocumentBlock has the same shape; happy to add if needed).

Addresses #142 (comment)...

* gen-ai: add Bedrock Converse DocumentBlock reference + ruff import-sort fix

Completes @trask's three-scenario request for PR #142:

- openai: inline file_data + BlobPart (previous commit, derives mime/content/modality from SDK arg)
- anthropic: native document content block + BlobPart (previous commit)
- aws-bedrock: native Converse DocumentBlock + BlobPart (this commit) -- the
  DocumentBlock 'format' / 'source.bytes' fields map 1:1 to the BlobPart
  mime_type/content with zero out-of-band derivation. Most direct of the three.

Also applies the ruff isort fix from this run (I001 in the anthropic scenario
file: import base64 should sort after import anthropic in the new function
-- ruff check --fix moved it).

All three scenarios validated locally against the mock LLM server; weaver
live-check reports no after_resolution policy violation on any of them.
ruff format + ruff check both clean.

* gen-ai: drop incorrect filename claim from anthropic doc-input docstring

Round-3 Copilot review on PR #142: the docstring claimed Anthropic's
document content block exposes 'mime type, source bytes, and filename'
on the SDK boundary, but the scenario does not include a filename
field in its payload. Removing the filename claim keeps the docstring
accurate to the scenario; if/when a filename field is added to the
schema in a follow-up (per issue #50), the docstring can be updated
alongside.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:gen-ai enhancement New feature or request

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

[gen-ai] Standardize multimodal content-part attributes (image / audio / video / document)

5 participants