Skip to content

fix(openai): recognize Responses API input_text/input_image/input_file content parts#1902

Open
Mai0313 wants to merge 1 commit intopydantic:mainfrom
Mai0313:fix/responses-api-content-types
Open

fix(openai): recognize Responses API input_text/input_image/input_file content parts#1902
Mai0313 wants to merge 1 commit intopydantic:mainfrom
Mai0313:fix/responses-api-content-types

Conversation

@Mai0313
Copy link
Copy Markdown

@Mai0313 Mai0313 commented May 4, 2026

Summary

Fixes #1901.

logfire.instrument_openai() currently produces gen_ai.unknown events for the standard Responses API content types — input_text, input_image, input_file — because _convert_content_part (latest semconv path) and input_to_events (legacy semconv path) only know about Chat Completions types (text, output_text, image_url, input_audio). Any non-trivial Responses API call (multi-part user message, image input, file input) ends up with most of its content as gen_ai.unknown, even though the payload is exactly what the OpenAI docs prescribe.

This PR teaches both code paths the three missing types.

Mapping

Responses API part semconv mapping Notes
input_text TextPart (latest) / gen_ai.{role}.message (legacy) Same shape as output_text, just on the input side.
input_image UriPart(modality='image') image_url here is a flat string (URL or data URI), distinct from Chat Completions where it's a nested {url: ...} dict.
input_file UriPart(modality='document') when file_url or file_data is present file_id-only inputs fall through to a generic dict, since there's no URI to point at and no obvious semconv mapping for an opaque ID.

Test plan

  • Added 4 unit tests covering the three new types in _convert_content_part_or_parts and the input_text branch in input_to_events.
  • Full tests/otel_integrations/test_openai.py passes (65 tests).
  • tests/otel_integrations/test_openai_agents.py and tests/otel_integrations/test_litellm.py (which share input_to_events) pass.
  • ruff check + ruff format --check clean.

I deliberately kept the legacy input_to_events change to just input_text (mirroring how output_text is the only recognized non-string content there) — adding image/file support to the legacy semconv path would require designing a new event shape for media, and that path is on its way out per #1586. Happy to extend if you'd prefer.

Related: #1476, #1586, #1769.

…e content parts

Previously `_convert_content_part` (latest semconv) only handled the Chat
Completions content types (`text`, `output_text`, `image_url`, `input_audio`)
and `input_to_events` (legacy semconv) only handled `output_text`. As a
result, valid Responses API content parts — `input_text`, `input_image`,
`input_file` per the official OpenAI docs — fell through to `gen_ai.unknown`
events / a generic dict, making traces for any non-trivial Responses API
call (image input, file input, multi-part user messages) very noisy.

Add the three missing types:

* `input_text` → TextPart (latest) / `gen_ai.{role}.message` (legacy)
* `input_image` → UriPart with modality=image. Note that `image_url` here
  is a flat string (URL or data URI), not the nested `{url: ...}` dict
  used by Chat Completions.
* `input_file` → UriPart with modality=document when `file_url` or
  `file_data` is present; falls through to a generic dict for the
  `file_id`-only case (no URI to point at).

Closes pydantic#1901
Copilot AI review requested due to automatic review settings May 4, 2026 12:55
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes OpenAI Responses API instrumentation so standard input content parts are normalized correctly instead of being recorded as unknown events. It fits into the ongoing work to align the OpenAI integration with newer semantic-convention-based message handling while preserving the legacy event path where still needed.

Changes:

  • Added input_text, input_image, and input_file handling in _convert_content_part for the latest semconv path.
  • Added legacy input_to_events support for input_text so user text inputs are no longer emitted as gen_ai.unknown.
  • Added focused unit tests covering Responses input text, image, file, and legacy input_text event conversion.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
logfire/_internal/integrations/llm_providers/openai.py Extends Responses API content-part parsing for latest semconv output and legacy event generation.
tests/otel_integrations/test_openai.py Adds regression tests for the newly recognized Responses API input part types.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 2 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 4, 2026

Codecov Report

❌ Patch coverage is 80.00000% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ire/_internal/integrations/llm_providers/openai.py 80.00% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

url = part.get('image_url', {}).get('url', '')
return UriPart(type='uri', uri=url, modality='image')
elif part_type == 'input_image':
# Responses API: image_url is a flat string (URL or data URI),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-input-messages.json specifies that blob parts with the actual bytes should be used instead of data URIs

uri = part.get('file_url') or part.get('file_data')
if uri:
return UriPart(type='uri', uri=uri, modality='document')
return {**part, 'type': part_type}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

there's a FilePart in https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-input-messages.json, it should be added and used here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenAI Responses API content types input_text/input_image/input_file not recognized by instrument_openai

3 participants