Skip to content

fix(qwen-agent): accept tuple inputs for messages and content - #913

Merged
lmolkova merged 4 commits into
open-telemetry:mainfrom
somuai:fix/qwen-agent-tuple-inputs
Oct 22, 2026
Merged

lmolkova merged 4 commits into
open-telemetry:mainfrom
somuai:fix/qwen-agent-tuple-inputs

Conversation

@somuai

@somuai somuai commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

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, and convert_to_final_output_messages checked if 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_text only inspected isinstance(content, list), and find_tool_call_id only checked isinstance(messages, list).

Changes

  • utils.py:
    • Added from collections.abc import Sequence.
    • Updated _extract_content_text to check isinstance(content, (list, tuple)) and annotated with Sequence[QwenContentItem].
    • Updated find_tool_call_id to check isinstance(messages, (list, tuple)) and annotated with Sequence[QwenMessage].
    • Updated convert_to_input_messages, convert_to_output_messages, and convert_to_final_output_messages to check not isinstance(messages, (list, tuple)) before wrapping, and updated type signatures to Sequence[QwenMessage].
    • Updated create_agent_invocation messages type signature to Sequence[QwenMessage].
  • Tests (test_agent.py):
    • Added test_convert_to_input_messages_handles_tuples verifying tuple messages and tuple content items.
    • Added test_convert_to_output_messages_handles_tuples verifying tuple output messages and tuple content items.
    • Added test_find_tool_call_id_handles_tuples verifying function id resolution over tuple message histories.
    • Added test_agent_run_with_tuple_messages verifying end-to-end Agent.run() span generation when messages and yielded responses are provided as tuples.
  • Changelog: Added instrumentation/opentelemetry-instrumentation-genai-qwen-agent/.changelog/904.fixed.

Testing

  • Unit tests: All 28 tests in instrumentation/opentelemetry-instrumentation-genai-qwen-agent/tests/ passed cleanly (27 passed, 1 skipped conformance).
  • Lint and formatting: 100% clean under uv run --with "ruff==0.16.1" ruff check and uv run --with "ruff==0.16.1" ruff format --check.

Fixes #904

@somuai
somuai requested a review from a team as a code owner October 17, 2026 20:57
Copilot AI balanced review requested due to automatic review settings October 17, 2026 20:57
@opentelemetry-pr-dashboard

opentelemetry-pr-dashboard Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Pull request dashboard status

Merged · refreshed 2026-10-22 03:17 UTC

Status above doesn't look right?
  • Anything look wrong? Report it with what you expected; it helps us improve the dashboard.

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

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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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)):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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,

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.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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

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.

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."""

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.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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))

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.

question(non-blocking): Is this needed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

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.

can you please rename the file to 913 instead of 904?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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():

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.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

@somuai

somuai commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

All review feedback from @eternalcuriouslearner has been addressed in commit acfe76f:

  • Scoped tuple inputs to input messages and content extraction, while keeping output converters and tool call lookup scoped to standard lists.
  • Implemented _as_message_sequence and annotated _is_sequence with TypeGuard[Sequence[Any]], achieving zero typecheck errors with pyright.
  • Renamed changelog entry to .changelog/913.fixed.
  • Updated integration tests to pass tuple and UserList inputs with raw dictionary messages so content containers reach instrumentation intact, with _run() yielding normal lists.
  • All 28 unit and integration tests, ruff check, and pyright pass cleanly.

"""Normalize input messages into a sequence without copying."""
if messages is None:
return ()
if isinstance(messages, Sequence) and not isinstance(

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.

Should we use if _is_sequence(messages): directly here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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>
@somuai

somuai commented Oct 21, 2026

Copy link
Copy Markdown
Contributor Author

/dashboard route:reviewers

@opentelemetry-pr-dashboard

Copy link
Copy Markdown

@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.

@somuai
somuai force-pushed the fix/qwen-agent-tuple-inputs branch from 27fa2d3 to 07a6e45 Compare October 21, 2026 14:02
@lmolkova
lmolkova enabled auto-merge October 22, 2026 01:36
@lmolkova
lmolkova added this pull request to the merge queue Oct 22, 2026
Merged via the queue into open-telemetry:main with commit 9b8af3a Oct 22, 2026
82 checks passed
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.

fix(qwen-agent): accept tuple inputs for messages and content

5 participants