Capture OpenAI multimodal image content (image_url and Responses API input_image) as BlobPart, UriPart, or FilePart message parts - #540
Conversation
2ec1848 to
568ca8b
Compare
Pull request dashboard statusWaiting on the author · refreshed 2026-09-04 22:20 UTC Resolve merge conflicts. Respond to 3 review items (e.g. link a commit, explain why not, ask a follow-up): Status above doesn't look right?
|
5698089 to
8564fb8
Compare
There was a problem hiding this comment.
🟡 Changes recommended
_content_to_parts() currently drops image_url values when they are objects with a .url attribute (only dict-mappings are handled), which can silently miss image parts for SDK-model style inputs.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds multimodal (image) input capture to the OpenAI GenAI instrumentation by converting OpenAI image_url (Chat Completions) and input_image (Responses API) content blocks into structured MessagePart instances (BlobPart, UriPart, FilePart) so they can be emitted via the existing GenAI message content model.
Changes:
- Introduces
_content_to_parts()to parse mixed text/image content blocks intoMessagePartlists and wires it into input-message extraction paths. - Expands unit and conformance coverage for multimodal image inputs, including new conformance scenarios and VCR cassettes.
- Adds a towncrier changelog fragment documenting the new capability.
File summaries
| File | Description |
|---|---|
| instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/utils.py | Adds _content_to_parts() and uses it when preparing input messages to capture text + image parts. |
| instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/response_extractors.py | Reuses _content_to_parts() for Responses API input-message extraction (supports images + file IDs). |
| instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_utils.py | Adds focused unit tests for _content_to_parts() across text, URL images, and data-URL images. |
| instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_response_extractors.py | Updates/extends extraction tests to assert UriPart/BlobPart/FilePart results for multimodal inputs. |
| instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_conformance.py | Registers new multimodal scenarios in the conformance suite. |
| instrumentation/opentelemetry-instrumentation-genai-openai/tests/conformance/multimodal.py | Adds new conformance scenarios validating that an image blob part appears on input messages. |
| instrumentation/opentelemetry-instrumentation-genai-openai/tests/cassettes/chat_completions_multimodal_conformance.yaml | VCR cassette for Chat Completions multimodal conformance scenario. |
| instrumentation/opentelemetry-instrumentation-genai-openai/tests/cassettes/responses_multimodal_conformance.yaml | VCR cassette for Responses API multimodal conformance scenario. |
| instrumentation/opentelemetry-instrumentation-genai-openai/.changelog/540.added | Changelog fragment for the new multimodal capture behavior. |
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
lmolkova
left a comment
There was a problem hiding this comment.
Would this work for streaming? Do we have any test coverage for streaming / async multi-modal content?
| @@ -205,8 +252,7 @@ def _prepare_input_messages(messages) -> list[InputMessage]: | |||
| tool_calls = get_property_value(message, "tool_calls") | |||
| if tool_calls: | |||
| chat_message.parts += extract_tool_calls_new(tool_calls) | |||
There was a problem hiding this comment.
In response_extractors.py, messages with empty parts are dropped (if parts: messages.append(...)). Should _prepare_input_messages also avoid appending messages when parts is empty?
There was a problem hiding this comment.
Yes, that makes sense. I will add changes for it.
…input_image) as BlobPart, UriPart, or FilePart message parts
e9af0ca to
35db213
Compare
Yes, this would work for streaming. I had missed adding the test coverage for it earlier. Added it now. Thanks for pointing it out. |
lmolkova
left a comment
There was a problem hiding this comment.
Thanks for the update! It seems there is a regression my AI bot found, can you please check ?
|
|
||
| part_type = get_property_value(item, "type") | ||
| text = get_property_value(item, "text") | ||
| if part_type in ("text", "input_text") or ( |
There was a problem hiding this comment.
Regression: the type allowlist drops Responses API output_text parts, so a prior assistant turn fed back into input disappears from gen_ai.input.messages. The old code took any part with a text field.
Fails on this branch, passes on main:
def test_extract_input_messages_keeps_assistant_output_text(loaded_module):
messages = loaded_module.get_input_messages(
[
{"role": "user", "content": [{"type": "input_text", "text": "Hi"}]},
{
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "Hello!",
"annotations": [],
}
],
},
]
)
assert [(msg.role, msg.parts) for msg in messages] == [
("user", [TextPart(content="Hi")]),
("assistant", [TextPart(content="Hello!")]),
]ResponsesConversationScenario uses previous_response_id, so nothing else covers multi-turn Responses input.
| ) | ||
|
|
||
|
|
||
| def test_chat_completion_streaming_captures_multimodal_input( |
There was a problem hiding this comment.
Sync non-streaming multimodal is uncovered - async has test_async_chat_completion_captures_multimodal_input, sync only gets the streaming variant.
Description
Fixes # (#348)
Type of change
Please delete options that are not relevant.
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.
Checklist
See CONTRIBUTING.md
for the style guide, changelog guidance, and more.