opentelemetry-util-genai: Align message-part class names with semconv (*Part suffix) (Util changes) - #450
Conversation
b38650a to
c6c78f1
Compare
opentelemetry-genai-util: Align message-part class names with semconv (*Part suffix) (Util changes)opentelemetry-util-genai: Align message-part class names with semconv (*Part suffix) (Util changes)
Pull request dashboard statusWaiting on the author · refreshed 2026-08-22 16:36 UTC Respond to 4 review items (e.g. link a commit, explain why not, ask a follow-up): Status above doesn't look right?
|
There was a problem hiding this comment.
Pull request overview
Updates opentelemetry-util-genai message-part model naming to align with the GenAI semconv non-normative Python models (using *Part suffixes), while keeping backwards-compatible access to the pre-*Part names.
Changes:
- Renamed message-part dataclasses in
types.pyto*Partnames and updated theMessagePartunion accordingly. - Updated util tests and upload-related logic to use the new
*Partnames; added tests asserting the legacy names still resolve. - Updated util README wording and added Towncrier fragments describing the change + deprecations.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| util/opentelemetry-util-genai/src/opentelemetry/util/genai/types.py | Renames message-part classes to *Part, updates MessagePart, and adds legacy-name aliases. |
| util/opentelemetry-util-genai/src/opentelemetry/util/genai/_upload/completion_hook.py | Updates hashability check to use TextPart. |
| util/opentelemetry-util-genai/src/opentelemetry/util/genai/_tool_invocation.py | Updates docstring to refer to ToolCallRequestPart for message parts. |
| util/opentelemetry-util-genai/tests/test_utils.py | Switches helpers to TextPart and adds tests for legacy alias behavior. |
| util/opentelemetry-util-genai/tests/test_toolcall.py | Updates tests to *Part tool-call types and adds alias-compat tests. |
| util/opentelemetry-util-genai/tests/test_upload.py | Updates upload hook tests to use TextPart and *Part tool-call message parts. |
| util/opentelemetry-util-genai/tests/test_workflow_invocation.py | Updates workflow invocation tests to use TextPart. |
| util/opentelemetry-util-genai/tests/test_handler_workflow.py | Updates handler workflow tests to use TextPart. |
| util/opentelemetry-util-genai/tests/test_handler_fetch_response.py | Updates handler fetch-response test data to use TextPart. |
| util/opentelemetry-util-genai/tests/test_handler_completion_hook.py | Updates completion-hook tests to use TextPart. |
| util/opentelemetry-util-genai/tests/test_handler_agent.py | Updates agent handler tests to use TextPart. |
| util/opentelemetry-util-genai/README.rst | Updates README terminology to reference *Part message types. |
| util/opentelemetry-util-genai/.changelog/450.changed | Towncrier fragment for the naming alignment. |
| util/opentelemetry-util-genai/.changelog/450.deprecated | Towncrier fragment documenting the deprecation of legacy names. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| @dataclass() | ||
| class Uri(UriPart): | ||
| """Represents an external referenced file sent to the model by URI | ||
|
|
||
| .. deprecated:: 1.2b0 | ||
| Use ``UriPart`` instead. | ||
| """ | ||
|
|
There was a problem hiding this comment.
All these deprecated notices and extra classes looks like a lot of tech debt to take on for an experimental package.
Is there a strong reason to align with the non-normative conventions? We could even update the non-normative conventions to match the names in this util. WDYT?
There was a problem hiding this comment.
@aabmass The initial discussion for this change started in another PR -
Following Liudmila's remark, I went and checked that for some data classes we were following the non-normative semantic conventions and for some we weren't.
Even if we move with the suggestion of modifying the semantic conventions, we will still have to provide backward compatibility for the classes which already included the *Part suffix such as GenericPart, CompactionPart. Unless we don't mind some classes having the *Part suffix and others not.
There was a problem hiding this comment.
We could even update the non-normative conventions to match the names in this util. WDYT?
- The JSON schemas in semconv are normative
- I think
BlobandUrlare not great names - they are too broad and confusing. Having Part suffix make them readable. - We have an inconsistent mix of parts, some of them have Part suffix and others don't
Having few deprecated things that will be removed in around the util stabilization time does not look like a huge debt to me.
6e0b3e6 to
32f99cf
Compare
| @dataclass() | ||
| class ToolCallResponse: | ||
| class ToolCallRequest(ToolCallRequestPart): | ||
| """Represents a tool call requested by the model (message part only). | ||
|
|
||
| .. deprecated:: 1.2b0 | ||
| Use ``ToolCallRequestPart`` instead. | ||
| """ |
There was a problem hiding this comment.
Subclassing isn't a drop-in rename - a dataclass subclass is not equal to its parent, and isinstance doesn't hold in the parent -> child direction. Against this branch:
Text(content="a") == TextPart(content="a") # False
isinstance(TextPart(content="a"), Text) # False
isinstance(ToolCallRequestPart(...), ToolCallRequest) # FalseSo a CompletionHook written against the old names silently stops matching once instrumentations emit *Part:
for p in message.parts:
if isinstance(p, Text): # False for TextPart -> content is no longer redacted
p.content = "[redacted]"In this repo, langchain test_callback_handler.py (isinstance(part, ToolCallRequest)) and smolagents test_models.py (isinstance(parts[1], Blob)) break as soon as the instrumentation PR lands.
A plain alias keeps identity, equality and isinstance intact. Sphinx autodoc picks up the string literal after a module-level assignment as that attribute's docstring, so the deprecation note still renders:
| @dataclass() | |
| class ToolCallResponse: | |
| class ToolCallRequest(ToolCallRequestPart): | |
| """Represents a tool call requested by the model (message part only). | |
| .. deprecated:: 1.2b0 | |
| Use ``ToolCallRequestPart`` instead. | |
| """ | |
| ToolCallRequest = ToolCallRequestPart | |
| """.. deprecated:: 1.2b0 | |
| Alias of :class:`ToolCallRequestPart`, kept for backwards compatibility. | |
| """ |
Same for the other eight deprecated names (ToolCallResponse, ServerToolCall, ServerToolCallResponse, Text, Reasoning, Blob, File, Uri).
| def test_names_subclass_part_classes(self): | ||
| self.assertTrue(issubclass(Text, TextPart)) | ||
| self.assertTrue(issubclass(Reasoning, ReasoningPart)) | ||
| self.assertTrue(issubclass(Blob, BlobPart)) | ||
| self.assertTrue(issubclass(File, FilePart)) | ||
| self.assertTrue(issubclass(Uri, UriPart)) | ||
|
|
There was a problem hiding this comment.
issubclass passes for both the subclass and the alias shape, so it can't pin the behavior that matters. Assert identity:
| def test_names_subclass_part_classes(self): | |
| self.assertTrue(issubclass(Text, TextPart)) | |
| self.assertTrue(issubclass(Reasoning, ReasoningPart)) | |
| self.assertTrue(issubclass(Blob, BlobPart)) | |
| self.assertTrue(issubclass(File, FilePart)) | |
| self.assertTrue(issubclass(Uri, UriPart)) | |
| def test_deprecated_names_are_aliases(self): | |
| self.assertIs(Text, TextPart) | |
| self.assertIs(Reasoning, ReasoningPart) | |
| self.assertIs(Blob, BlobPart) | |
| self.assertIs(File, FilePart) | |
| self.assertIs(Uri, UriPart) | |
| def test_parts_compare_equal_across_names(self): | |
| self.assertEqual(Text(content="hello"), TextPart(content="hello")) | |
| self.assertIsInstance(TextPart(content="hello"), Text) |
| def test_deprecated_tool_call_names_subclass_part_classes(): | ||
| """The pre-*Part tool call names subclass their replacements.""" | ||
| assert issubclass(ToolCallRequest, ToolCallRequestPart) | ||
| assert issubclass(ToolCallResponse, ToolCallResponsePart) | ||
| assert issubclass(ServerToolCall, ServerToolCallPart) | ||
| assert issubclass(ServerToolCallResponse, ServerToolCallResponsePart) |
There was a problem hiding this comment.
| def test_deprecated_tool_call_names_subclass_part_classes(): | |
| """The pre-*Part tool call names subclass their replacements.""" | |
| assert issubclass(ToolCallRequest, ToolCallRequestPart) | |
| assert issubclass(ToolCallResponse, ToolCallResponsePart) | |
| assert issubclass(ServerToolCall, ServerToolCallPart) | |
| assert issubclass(ServerToolCallResponse, ServerToolCallResponsePart) | |
| def test_deprecated_tool_call_names_are_aliases(): | |
| """The pre-*Part tool call names are aliases of their replacements.""" | |
| assert ToolCallRequest is ToolCallRequestPart | |
| assert ToolCallResponse is ToolCallResponsePart | |
| assert ServerToolCall is ServerToolCallPart | |
| assert ServerToolCallResponse is ServerToolCallResponsePart |
Description
Complies with the semantic conventions for non-normative models. (util changes)
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.