Skip to content

[opentelemetry-instrumentation-genai-anthropic] Capture Anthropic image and document inputs as GenAI BlobPart, UriPart, FilePart, and GenericPart message parts - #589

Open
rads-1996 wants to merge 12 commits into
open-telemetry:mainfrom
rads-1996:anthropic-parity-for-multimodal
Open

[opentelemetry-instrumentation-genai-anthropic] Capture Anthropic image and document inputs as GenAI BlobPart, UriPart, FilePart, and GenericPart message parts#589
rads-1996 wants to merge 12 commits into
open-telemetry:mainfrom
rads-1996:anthropic-parity-for-multimodal

Conversation

@rads-1996

@rads-1996 rads-1996 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Description

Ensures multimodal feature parity with - #296

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How has this been tested?

Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. List any relevant details for your test
configuration.

  • uv run tox -e py312-test-instrumentation-genai-anthropic -- -q
  • uv run tox -e py312-test-instrumentation-genai-anthropic-conformance -- -q
  • uv run --python 3.12 tox -e lint-instrumentation-genai-anthropic

Checklist

See CONTRIBUTING.md
for the style guide, changelog guidance, and more.

  • Followed the style guidelines of this project
  • Changelog updated if the change requires an entry
  • Unit tests added
  • Documentation updated

@opentelemetry-pr-dashboard

opentelemetry-pr-dashboard Bot commented Sep 3, 2026

Copy link
Copy Markdown

Pull request dashboard status

Waiting on the author · refreshed 2026-09-11 22:33 UTC

Respond to 5 review items (e.g. link a commit, explain why not, ask a follow-up):

  • Inline threads: 1, 2, 3, 4, 5
Status above doesn't look right?
  • Just replied or pushed? Anything around or after the refresh time above may not be picked up yet — give it a few minutes.
  • Should this be with reviewers? Comment /dashboard route:reviewers to route it to them.
  • Anything wrong — including the routing? Report it with what you expected; it helps us improve the dashboard.

@rads-1996
rads-1996 force-pushed the anthropic-parity-for-multimodal branch 7 times, most recently from 1d081e3 to 2346929 Compare September 9, 2026 14:23
@rads-1996 rads-1996 changed the title Capture Anthropic image and document inputs as GenAI BlobPart, UriPart, FilePart, and GenericPart message parts [opentelemetry-instrumentation-anthropic] - Capture Anthropic image and document inputs as GenAI BlobPart, UriPart, FilePart, and GenericPart message parts Sep 9, 2026
@rads-1996 rads-1996 changed the title [opentelemetry-instrumentation-anthropic] - Capture Anthropic image and document inputs as GenAI BlobPart, UriPart, FilePart, and GenericPart message parts [opentelemetry-instrumentation-anthropic] Capture Anthropic image and document inputs as GenAI BlobPart, UriPart, FilePart, and GenericPart message parts Sep 9, 2026
@rads-1996
rads-1996 force-pushed the anthropic-parity-for-multimodal branch from 2346929 to ad6177c Compare September 9, 2026 21:18
@rads-1996
rads-1996 marked this pull request as ready for review September 9, 2026 21:25
Copilot AI lite review requested due to automatic review settings September 9, 2026 21:25
@rads-1996
rads-1996 requested a review from a team as a code owner September 9, 2026 21:25

Copilot AI left a comment

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.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Pull request overview

Adds multimodal (image + document) input extraction for the Anthropic instrumentation so spans represent inputs as GenAI BlobPart, UriPart, FilePart, and GenericPart.

Changes:

  • Extend Anthropic content conversion to support image/document sources (base64, url, file, nested content).
  • Add unit tests verifying extractor behavior and span-captured multimodal parts (sync/async/streaming) plus a new conformance scenario.
  • Add changelog entry and a new VCR cassette for the conformance scenario.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
instrumentation/opentelemetry-instrumentation-genai-anthropic/src/opentelemetry/instrumentation/genai/anthropic/utils.py Implements multimodal source parsing into GenAI message parts.
instrumentation/opentelemetry-instrumentation-genai-anthropic/tests/test_messages_extractors.py Adds unit coverage for image/document source extraction and ordering.
instrumentation/opentelemetry-instrumentation-genai-anthropic/tests/test_sync_messages.py Adds sync span-capture tests for multimodal inputs and generator-preservation.
instrumentation/opentelemetry-instrumentation-genai-anthropic/tests/test_async_messages.py Adds async span-capture tests for multimodal inputs (create + streaming).
instrumentation/opentelemetry-instrumentation-genai-anthropic/tests/test_conformance.py Registers the new multimodal conformance scenario.
instrumentation/opentelemetry-instrumentation-genai-anthropic/tests/conformance/multimodal.py New conformance scenario asserting multimodal parts are emitted correctly.
instrumentation/opentelemetry-instrumentation-genai-anthropic/tests/cassettes/test_chat_anthropic_multimodal_image_llm_call.yaml Adds VCR cassette for conformance replay.
instrumentation/opentelemetry-instrumentation-genai-anthropic/.changelog/589.added Documents the new multimodal capture capability.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@rads-1996
rads-1996 marked this pull request as draft September 9, 2026 21:46
@rads-1996
rads-1996 force-pushed the anthropic-parity-for-multimodal branch 4 times, most recently from b0e67ad to 5ba834a Compare September 10, 2026 17:56
@rads-1996
rads-1996 requested a lite review from Copilot September 10, 2026 19:32

Copilot AI left a comment

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.

🟡 Changes recommended

The multimodal streaming tests reference non-matching existing cassettes and the new document URL/text MIME type handling currently hard-codes values that can produce incorrect telemetry.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (2)

instrumentation/opentelemetry-instrumentation-genai-anthropic/src/opentelemetry/instrumentation/genai/anthropic/utils.py:160

  • For document URL sources, the code hard-codes mime_type="application/pdf" even when the request doesn't provide a media type. This can record an incorrect MIME type for non-PDF documents; prefer using a provided "media_type" when present, otherwise leave mime_type as None.
            return [
                UriPart(
                    mime_type="application/pdf",
                    modality="document",
                    uri=url,

instrumentation/opentelemetry-instrumentation-genai-anthropic/src/opentelemetry/instrumentation/genai/anthropic/utils.py:173

  • For document text sources, the code hard-codes mime_type="text/plain" instead of using the request's "media_type" field. This can produce incorrect telemetry when a different media type is supplied.
                BlobPart(
                    mime_type="text/plain",
                    modality="document",
                    content=data.encode(),
                )
            ]
  • Files reviewed: 10/10 changed files
  • Comments generated: 5
  • Review effort level: Lite

Copilot AI left a comment

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.

🟡 Changes recommended

The new document-content conversion can drop generator-backed nested document inputs entirely (losing evidence that a document was provided), and the PR description’s listed test commands don’t match the Anthropic changes.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

instrumentation/opentelemetry-instrumentation-genai-anthropic/.changelog/589.added:1

  • The PR description's "How has this been tested?" section lists only LangChain tox envs, but this change is in the Anthropic instrumentation package (new Anthropic unit + conformance coverage and cassettes). Please update the PR description to reflect the actual Anthropic test commands that were run so reviewers can reproduce accurately.
Capture Anthropic image and document inputs as GenAI ``BlobPart``, ``UriPart``, ``FilePart``, and ``GenericPart`` message parts
  • Files reviewed: 12/12 changed files
  • Comments generated: 1
  • Review effort level: Lite

@rads-1996
rads-1996 force-pushed the anthropic-parity-for-multimodal branch from d3f947b to 777373f Compare September 10, 2026 22:41
…Part`, `FilePart`, and `GenericPart` message parts
@rads-1996
rads-1996 force-pushed the anthropic-parity-for-multimodal branch from 08a265f to d156507 Compare September 11, 2026 15:14
@rads-1996
rads-1996 marked this pull request as ready for review September 11, 2026 15:24
Comment on lines +108 to +109
if isinstance(data, PathLike) or callable(getattr(data, "read", None)):
return GenericPart(type=modality)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
if isinstance(data, PathLike) or callable(getattr(data, "read", None)):
return GenericPart(type=modality)
if isinstance(data, PathLike) or callable(getattr(data, "read", None)):
return GenericPart(type="blob")

In GenAI semconv message parts, type is "blob" (with modality indicating "image" or "document"). "image" and "document" are not valid part types.

Comment on lines +160 to +166
return [
UriPart(
mime_type="application/pdf",
modality="document",
uri=url,
)
]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
return [
UriPart(
mime_type="application/pdf",
modality="document",
uri=url,
)
]
media_type = source_dict.get("media_type")
return [
UriPart(
mime_type=media_type if isinstance(media_type, str) else None,
modality="document",
uri=url,
)
]

Anthropic document URL sources can specify a media_type. Hardcoding "application/pdf" overrides the actual media type when non-PDF documents (or unknown types) are supplied.

Comment on lines +195 to +213
def _convert_document_block(block: Mapping[str, Any]) -> MessagePart | None:
parts = _extract_document_source(block.get("source"))
metadata = {
key: block[key]
for key in ("title", "context", "citations")
if block.get(key) is not None
}
source = block.get("source")
source_mapping = (
cast(Mapping[str, object], source)
if isinstance(source, Mapping)
else None
)
is_nested = (
source_mapping is not None and source_mapping.get("type") == "content"
)
if metadata or (is_nested and parts):
return GenericPart(type="document")
return parts[0] if parts else None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When document metadata like title, context, or citations is present, returning GenericPart(type="document") discards the extracted BlobPart or UriPart (and "document" is not a valid semconv part type). We should preserve the extracted content part regardless of metadata.

Also, if source.type == "content" has multiple nested parts (e.g. text + image), returning a single MessagePart forces dropping them. If convert_content_to_parts extends a list of parts per block, we can preserve all nested parts without collapsing them into GenericPart:

# Test showing the issue:
content = [{
    "type": "document",
    "title": "Report",
    "source": {"type": "base64", "media_type": "application/pdf", "data": "QUJD"},
}]
# Expecting BlobPart(mime_type="application/pdf", modality="document", content=b"ABC")
# Currently returns GenericPart(type="document")

return parsed


def _multimodal_input_message():

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: _multimodal_input_message and _assert_multimodal_input are duplicated identically in tests/test_async_messages.py. Consider moving them to a shared helper or conftest to keep them in sync.

@@ -0,0 +1 @@
Capture Anthropic image and document inputs as GenAI ``BlobPart``, ``UriPart``, ``FilePart``, and ``GenericPart`` message parts

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The PR description mentions Fixes #348 and lists LangChain tox environments. Issue #348 was for OpenAI parity (already resolved in #540). Could you update the PR description to decouple from #348 and list the Anthropic test commands?

@rads-1996 rads-1996 changed the title [opentelemetry-instrumentation-anthropic] Capture Anthropic image and document inputs as GenAI BlobPart, UriPart, FilePart, and GenericPart message parts [opentelemetry-instrumentation-genai-anthropic] Capture Anthropic image and document inputs as GenAI BlobPart, UriPart, FilePart, and GenericPart message parts Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants