Repository navigation
fix(qwen-agent): accept tuple inputs for messages and content - #913
Conversation
Pull request dashboard statusMerged · refreshed 2026-10-22 03:17 UTC Status above doesn't look right?
|
There was a problem hiding this comment.
🟡 Changes recommended
General sequences and dictionary-based tuple content still produce missing or incorrect telemetry.
2 open issues
What changed in this PR
Updates qwen-agent telemetry conversion to support tuple-based messages and content for #904.
Changes:
- Adds tuple handling and widens annotations to
Sequence. - Adds helper and agent-run regression tests.
- Records the fix in the package changelog.
| File | Description |
|---|---|
| instrumentation/opentelemetry-instrumentation-genai-qwen-agent/tests/test_agent.py | Adds tuple-input regression tests. |
| instrumentation/opentelemetry-instrumentation-genai-qwen-agent/src/opentelemetry/instrumentation/genai/qwen_agent/utils.py | Extends message, content, and tool-history handling. |
| instrumentation/opentelemetry-instrumentation-genai-qwen-agent/.changelog/904.fixed | Documents tuple capture support. |
Review effort: Balanced
| if isinstance(content, (list, tuple)): | ||
| texts: list[str] = [] | ||
| for item in content: | ||
| text = getattr(item, "text", None) |
There was a problem hiding this comment.
Resolved in commit 0efdb34: updated _extract_content_text to read text with _field_value(item, "text") and convert to str, ensuring raw dictionary content items like ({"text": "Hello"},) are extracted properly. Added regression test test_convert_to_input_messages_handles_raw_dict_tuple_content.
| return [] | ||
|
|
||
| if not isinstance(messages, list): | ||
| if not isinstance(messages, (list, tuple)): |
There was a problem hiding this comment.
Resolved in commit 0efdb34: defined _is_sequence helper recognizing general Sequence types while excluding scalar strings, bytes, and mappings. Applied _is_sequence across _extract_content_text, find_tool_call_id, convert_to_input_messages, convert_to_output_messages, and convert_to_final_output_messages. Added regression tests test_conversion_helpers_handle_user_list_sequences and test_agent_run_with_user_list_messages using collections.UserList.
|
|
||
| def convert_to_output_messages( | ||
| messages: QwenMessage | list[QwenMessage] | None, | ||
| messages: QwenMessage | Sequence[QwenMessage] | None, |
There was a problem hiding this comment.
question(blocking): Could we scope this to tuple inputs? Agent.run() rebuilds each output as a list, so is tuple support needed in the output converters? Similarly, since it normalizes inputs into a list before calling _run(), is there a path where find_tool_call_id() receives a tuple history?
There was a problem hiding this comment.
Updated in commit acfe76f: scoped tuple inputs strictly to convert_to_input_messages and message content extraction, while keeping convert_to_output_messages, convert_to_final_output_messages, and find_tool_call_id scoped to standard lists as outputs and histories are normalized by Agent.run().
|
|
||
| def find_tool_call_id( | ||
| messages: list[QwenMessage] | None, tool_name: str | ||
| messages: Sequence[QwenMessage] | None, tool_name: str |
There was a problem hiding this comment.
question(blocking): Could we scope this to tuple inputs? Agent.run() rebuilds each output as a list, so is tuple support needed in the output converters? Similarly, since it normalizes inputs into a list before calling _run(), is there a path where find_tool_call_id() receives a tuple history?
|
|
||
|
|
||
| def _is_sequence(val: object) -> bool: | ||
| """Check if a value is a non-string, non-mapping sequence.""" |
There was a problem hiding this comment.
question(blocking): Could we fix the type narrowing? _is_sequence() -> bool doesn’t narrow the callers’ union types, causing pyright errors. Use TypeGuard[Sequence[Any]] for positive checks, and normalize converter inputs separately because TypeGuard doesn’t narrow false branches:
def _as_message_sequence(
messages: QwenMessage | Sequence[QwenMessage] | None,
) -> Sequence[QwenMessage]:
if messages is None:
return ()
if isinstance(messages, Sequence):
return messages
return (messages,)Then iterate over _as_message_sequence(messages) instead of reassigning messages. Could you verify with uv run tox -e typecheck?
There was a problem hiding this comment.
Resolved in commit acfe76f: annotated _is_sequence with TypeGuard[Sequence[Any]] and introduced _as_message_sequence to normalize QwenMessage | Sequence[QwenMessage] | None inputs without false-branch narrowing issues. Verified with pyright across the package with zero errors.
| text = _field_value(item, "text") | ||
| if text is not None: | ||
| texts.append(text) | ||
| texts.append(str(text)) |
There was a problem hiding this comment.
question(non-blocking): Is this needed?
There was a problem hiding this comment.
Kept str(text) as defensive conversion in commit acfe76f so non-string text values from raw dictionary items are handled consistently without raising in str.join, verified with pyright with zero errors.
| @@ -0,0 +1 @@ | |||
| Capture qwen-agent messages, content parts, and tool calls provided as tuples. | |||
There was a problem hiding this comment.
can you please rename the file to 913 instead of 904?
There was a problem hiding this comment.
Renamed to instrumentation/opentelemetry-instrumentation-genai-qwen-agent/.changelog/913.fixed in commit acfe76f.
| assert child_spans[0].parent.span_id == parent_spans[0].context.span_id | ||
|
|
||
|
|
||
| def test_convert_to_input_messages_handles_tuples(): |
There was a problem hiding this comment.
question(blocking): Could we make the integration tests pass tuple inputs to Agent.run(), have _run() yield normal lists, and assert both captured input and output? For tuple/UserList content coverage, could we use raw dictionary messages so the content reaches instrumentation unchanged, since Message(...) converts those containers to lists during validation?
There was a problem hiding this comment.
Updated in commit acfe76f: integration tests now pass tuple and UserList inputs with raw dictionary messages to Agent.run(), ensuring container shapes reach instrumentation intact without being converted by Message(...) validation. _run() yields standard lists, and assertions verify both captured input messages and final output messages.
|
All review feedback from @eternalcuriouslearner has been addressed in commit acfe76f:
|
| """Normalize input messages into a sequence without copying.""" | ||
| if messages is None: | ||
| return () | ||
| if isinstance(messages, Sequence) and not isinstance( |
There was a problem hiding this comment.
Should we use if _is_sequence(messages): directly here?
There was a problem hiding this comment.
Good catch, updated to use _is_sequence(messages) directly in commit 766a21e.
Update qwen-agent conversion helpers to accept tuples for messages and content parts. Fixes open-telemetry#904 Signed-off-by: Soumyajit Ghosh <jobsoumyajit6124@gmail.com>
Address review feedback: - Use _is_sequence across conversion helpers to support general Sequence types like UserList. - Use _field_value in _extract_content_text to read text from dictionary content items. - Add regression tests covering dict content items and UserList sequences. Signed-off-by: Soumyajit Ghosh <jobsoumyajit6124@gmail.com>
…name changelog to 913 Address review feedback from @eternalcuriouslearner: - Implement _as_message_sequence helper and TypeGuard[Sequence[Any]] on _is_sequence to resolve pyright union narrowing errors cleanly. - Scope tuple inputs to input messages and content, keeping output converters and tool call lookup scoped to standard lists. - Update integration and unit tests to pass tuple/UserList inputs with raw dictionary messages so content reaches instrumentation unchanged, with _run() yielding normal lists. - Rename changelog entry from 904.fixed to 913.fixed. Signed-off-by: Soumyajit Ghosh <jobsoumyajit6124@gmail.com>
Signed-off-by: Soumyajit Ghosh <jobsoumyajit6124@gmail.com>
|
/dashboard route:reviewers |
|
@somuai, your reviewer-routing request was recorded; this pull request has the approvals it needs and is now waiting on maintainers. The handoff remains active across pushes until newer actionable human feedback arrives. Top-level feedback through this request will not return; unresolved review threads remain open. |
27fa2d3 to
07a6e45
Compare

Description
This pull request resolves #904 by updating the qwen-agent instrumentation conversion helpers to recognize and handle messages, content items, and tool call histories provided as tuples or general Sequences.
Previously,
convert_to_input_messages,convert_to_output_messages, andconvert_to_final_output_messagescheckedif not isinstance(messages, list): messages = [messages]. When messages were provided as a tuple, the entire tuple was wrapped as a single element ([(m1, m2)]), causing property extraction to fail and dropping or corrupting telemetry. Similarly,_extract_content_textonly inspectedisinstance(content, list), andfind_tool_call_idonly checkedisinstance(messages, list).Changes
utils.py:from collections.abc import Sequence._extract_content_textto checkisinstance(content, (list, tuple))and annotated withSequence[QwenContentItem].find_tool_call_idto checkisinstance(messages, (list, tuple))and annotated withSequence[QwenMessage].convert_to_input_messages,convert_to_output_messages, andconvert_to_final_output_messagesto checknot isinstance(messages, (list, tuple))before wrapping, and updated type signatures toSequence[QwenMessage].create_agent_invocationmessages type signature toSequence[QwenMessage].test_agent.py):test_convert_to_input_messages_handles_tuplesverifying tuple messages and tuple content items.test_convert_to_output_messages_handles_tuplesverifying tuple output messages and tuple content items.test_find_tool_call_id_handles_tuplesverifying function id resolution over tuple message histories.test_agent_run_with_tuple_messagesverifying end-to-endAgent.run()span generation when messages and yielded responses are provided as tuples.instrumentation/opentelemetry-instrumentation-genai-qwen-agent/.changelog/904.fixed.Testing
instrumentation/opentelemetry-instrumentation-genai-qwen-agent/tests/passed cleanly (27 passed, 1 skipped conformance).uv run --with "ruff==0.16.1" ruff checkanduv run --with "ruff==0.16.1" ruff format --check.Fixes #904