Skip to content

Add QwenPaw instrumentation - #311

Open
123liuziming wants to merge 14 commits into
open-telemetry:mainfrom
123liuziming:add-qwenpaw-instrumentation
Open

Add QwenPaw instrumentation#311
123liuziming wants to merge 14 commits into
open-telemetry:mainfrom
123liuziming:add-qwenpaw-instrumentation

Conversation

@123liuziming

Copy link
Copy Markdown
Contributor

Description

Adds opentelemetry-instrumentation-genai-qwenpaw, instrumentation for QwenPaw, a personal assistant application built on AgentScope. Each user turn handled by AgentRunner.query_handler is traced as one invoke_agent span (gen_ai.agent.id, gen_ai.agent.name, gen_ai.conversation.id, and opt-in input/output message content), plus the gen_ai.client.operation.duration metric, all through opentelemetry-util-genai.

QwenPaw delegates model (LLM) and tool execution to AgentScope, so this package intentionally emits no chat or execute_tool spans and the conformance suite covers none — those operations belong to the AgentScope instrumentation.

Supported range is qwenpaw >= 1.1.0, < 2.0.0 (2.0 removed AgentRunner.query_handler).

Type of change

  • New feature (non-breaking change which adds functionality)

How has this been tested?

  • tox -e py310-test-instrumentation-genai-qwenpaw-oldest (qwenpaw 1.1.0 via lowest-direct)
  • tox -e py312-test-instrumentation-genai-qwenpaw-latest (qwenpaw 1.1.12.post3)
  • tox -e py313-test-instrumentation-genai-qwenpaw-conformance (weaver live-check, 0 violations)
  • tox -e lint-instrumentation-genai-qwenpaw, precommit, typecheck, readme

Checklist

See CONTRIBUTING.md
for the style guide, changelog guidance, and more.

  • Followed the style guidelines of this project
  • Changelog updated if the change requires an entry
  • Unit tests added
  • Documentation updated

Made with Cursor

Traces each AgentRunner.query_handler turn as an invoke_agent span per the
GenAI semantic conventions, with unit and Weaver conformance tests. Model
and tool execution are delegated to AgentScope, so this package emits no
chat or execute_tool spans.

Change-Id: I11606f53aab9d7f90acd8c712a17337bbf3b67a4
Co-developed-by: Cursor <noreply@cursor.com>
Assisted-by: Claude Fable 5
@123liuziming
123liuziming requested a review from a team as a code owner July 26, 2026 14:23
Copilot AI review requested due to automatic review settings July 26, 2026 14:23
Change-Id: I387b13c54797040a9b37fdf4af1f39b5dd4a5391
Co-developed-by: Cursor <noreply@cursor.com>
@opentelemetry-pr-dashboard

opentelemetry-pr-dashboard Bot commented Jul 26, 2026

Copy link
Copy Markdown

Pull request dashboard status

Waiting on the author · refreshed 2026-08-21 01:40 UTC

Resolve merge conflicts.

Respond to 2 review items (e.g. link a commit, explain why not, ask a follow-up):

  • Inline threads: 1
  • Top-level threads: 2
Status above doesn't look right?
  • Just replied or pushed? Anything around or after the refresh time above may not be picked up yet — give it a few minutes.
  • Should this be with reviewers? Comment /dashboard route:reviewers to route it to them.
  • Anything wrong — including the routing? 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.

Pull request overview

Adds a new GenAI instrumentation package, opentelemetry-instrumentation-genai-qwenpaw, to trace QwenPaw AgentRunner.query_handler turns as invoke_agent operations using opentelemetry-util-genai, and wires the package into the monorepo’s test/release automation.

Changes:

  • Introduces the new QwenPaw instrumentor implementation (async-generator wrapping + message mapping helpers).
  • Adds unit tests and a conformance scenario for the single emitted semconv operation (invoke_agent), plus tox env wiring.
  • Registers the new package in workspace deps, release workflows, and per-package tooling configs.

Reviewed changes

Copilot reviewed 29 out of 32 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tox.ini Adds tox envs/deps/commands for qwenpaw oldest/latest/conformance + lint; adjusts typecheck install.
pyproject.toml Adds qwenpaw workspace dependency and pyright include/exclude entries.
instrumentation/README.md Lists the new qwenpaw instrumentation in the workspace table.
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/tests/test_query_handler.py End-to-end async tests for query_handler span/metric behavior and error cases.
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/tests/test_message_mapping.py Unit tests for call parsing + message mapping utilities.
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/tests/test_instrumentor.py Tests instrumentation dependency metadata and uninstrument behavior.
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/tests/test_conformance.py Runs the per-scenario conformance runner for qwenpaw.
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/tests/requirements.oldest.txt Defines oldest-factor test-only dependency pins (currently none).
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/tests/requirements.latest.txt Pins latest qwenpaw and installs editable util/instrumentation for tests.
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/tests/harness.py Test harness to drive query_handler without real model/tool execution.
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/tests/conftest.py Registers shared fixtures and defines package-local test fixtures.
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/tests/conformance/invoke_agent.py Conformance scenario for one invoke_agent turn.
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/tests/conformance/init.py Marks conformance package.
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/tests/init.py Marks tests package.
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/src/opentelemetry/instrumentation/genai/qwenpaw/version.py Adds package version module.
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/src/opentelemetry/instrumentation/genai/qwenpaw/utils.py Adds helpers for parsing/mapping input/output messages.
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/src/opentelemetry/instrumentation/genai/qwenpaw/patch.py Implements async stream wrapper + wrapt wrapper for query_handler.
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/src/opentelemetry/instrumentation/genai/qwenpaw/package.py Declares instruments metadata for the instrumentor.
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/src/opentelemetry/instrumentation/genai/qwenpaw/init.py Adds QwenPawInstrumentor implementation + package docstring.
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/README.rst Adds package README for PyPI long description.
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/pyproject.toml Adds package build metadata, deps, and instrumentor entry point.
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/LICENSE Adds Apache 2.0 license file for the new package.
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/.changelog/311.added Adds towncrier fragment for the new instrumentation.
instrumentation/opentelemetry-instrumentation-genai-qwenpaw/.changelog/.gitignore Ensures the changelog directory is retained in git.
eachdist.ini Adds qwenpaw to per-dist tooling configuration.
dev-requirements-conformance.txt Adds OTLP meta/proto-http pins needed by some conformance dependencies.
.github/workflows/release-package.yml Adds qwenpaw to the release workflow package choice list.
.github/workflows/prepare-release.yml Adds qwenpaw to the prepare-release workflow package choice list.
.github/workflows/prepare-backport-patch.yml Adds qwenpaw to the backport patch workflow package choice list.
.github/workflows/bump-package-minor.yml Adds qwenpaw to the bump-minor workflow package choice list.
.github/workflows/bump-package-major.yml Adds qwenpaw to the bump-major workflow package choice list.

Comment thread instrumentation/opentelemetry-instrumentation-genai-qwenpaw/pyproject.toml Outdated
Change-Id: I46107eb41fbacc8c5d6e846e8a9f6cd96dbb8cd9
Co-developed-by: Cursor <noreply@cursor.com>
@123liuziming 123liuziming changed the title Add QwenPaw instrumentation 【WIP】Add QwenPaw instrumentation Jul 26, 2026
- Wire the GenAI completion hook through instrument(), matching the other
  instrumentations, and document it.
- Fix the documented content-capture values: the util expects a
  ContentCapturingMode name, not "true".
- Close the wrapped async generator when the caller raises inside
  "async with", which previously leaked a suspended generator.
- Mark the instruments extra with the Python range qwenpaw supports so it
  stays installable on 3.14 tooling interpreters.
- Ignore "astroid" in codespell; it is a locked transitive dependency name.

Change-Id: I06dc0f24f110bf8a28a9a7693cfd8efc39a0888d
Co-developed-by: Cursor <noreply@cursor.com>
Assisted-by: Claude Opus 4.6
@opentelemetry-pr-dashboard

This comment has been minimized.

- Ship the standard examples/manual (with custom_hook.py) and
  examples/zero-code layouts and link them from the README.
- Drop the feature-specific validate() override from the conformance
  scenario; conformance validates the telemetry shape and the specific
  attribute values are already covered by unit tests.
- Start the new package at 1.0b0.dev like the other unreleased packages.

Change-Id: Ifc74679047aec04f06fe3dad8be85bece499c5cf
Co-developed-by: Qoder <noreply@qoder.com>
Assisted-by: Claude Opus 4.6
…tation

Change-Id: I867f7aa66287d1ebd69211f64309e68e5fb8c3b3
Co-developed-by: Qoder <noreply@qoder.com>
After merging main, util-genai records time_to_first_chunk for streamed
invocations; declare it in the scenario along with the known
gen_ai.provider.name gap (QwenPaw delegates model calls to AgentScope,
so no provider applies to invoke_agent).

Change-Id: I9a74da28a3cef297c62efec511515a00c6e4f3bc
Co-developed-by: Qoder <noreply@qoder.com>
Assisted-by: Claude Opus 4.6
@123liuziming 123liuziming changed the title 【WIP】Add QwenPaw instrumentation Add QwenPaw instrumentation Aug 4, 2026

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

LGTM!! Minor nit and can you please resolve the merge conflicts?

…tation

Change-Id: I8f6c901c598dba0b04a2b1fffbff621506edd751
Co-developed-by: Qoder <noreply@qoder.com>
Annotate the runner instance as object instead of Any: the runner is only
attribute-probed via getattr, and qwenpaw cannot be imported for typing on
Python >= 3.14. Also apply the workspace's updated ruff rules to the package.

Change-Id: Ibefdc830f39561963644504f97fb39e070a5d407
Co-developed-by: Qoder <noreply@qoder.com>
Assisted-by: Qoder
eternalcuriouslearner and others added 4 commits August 10, 2026 22:44
- Drop gen_ai.agent.id: qwenpaw's agent_id is a local config key, not a
  provider-assigned stable identifier, and semconv recommends against
  recording transient/in-process agent ids on this attribute.
- Narrow the remaining Any annotations on msgs/request/chunk to object.
- Re-verify the conformance suppression: the invoke_agent span no longer
  requires gen_ai.provider.name, but the time_to_first_chunk metric still
  inherits it as required from the registry's shared metric attribute
  group, so the suppression stays with a metric-scoped explanation.

Change-Id: I4fd76ada2efa1d1fcc775afb181a14b86988fade
Co-developed-by: Qoder <noreply@qoder.com>
Assisted-by: Qoder
…tation

Change-Id: Idfad0c13dab8432638302f9f98f82886f9e2713a
Co-developed-by: Qoder <noreply@qoder.com>

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.

Pull request overview

Copilot reviewed 39 out of 42 changed files in this pull request and generated no new comments.

Suppressed comments (6)

instrumentation/opentelemetry-instrumentation-genai-qwenpaw/src/opentelemetry/instrumentation/genai/qwenpaw/patch.py:115

  • Reading agent_name invokes QwenPaw's lazy property, which loads configuration and mutates its cache. This happens before the async generator starts and even on command paths that otherwise return before touching the property, so enabling instrumentation adds I/O and changes execution timing. Use only already-cached state (or omit the optional name) rather than evaluating this property, per the instrumentation transparency rule.
    agent_name = non_empty_str(getattr(instance, "agent_name", None))

instrumentation/opentelemetry-instrumentation-genai-qwenpaw/pyproject.toml:36

  • The supported QwenPaw releases pin AgentScope 1.0.18–1.0.20, whose runtime dependencies include opentelemetry-api, SDK, exporter, and semantic conventions, and which ships first-party setup_tracing, trace_reply, trace_llm, and tool tracing. Before adding a separate package, this needs an explicit assessment of why AgentScope's native telemetry cannot cover the turn and how duplicate/conflicting telemetry is avoided, as required by When to add an instrumentation here.
instruments = ["qwenpaw >= 1.1.0, < 2.0.0; python_version < '3.14'"]

instrumentation/opentelemetry-instrumentation-genai-qwenpaw/src/opentelemetry/instrumentation/genai/qwenpaw/patch.py:51

  • Assigning the invocation to the base class's reserved _self_invocation field opts this local INTERNAL invoke_agent span into gen_ai.client.operation.time_to_first_chunk/per-chunk metrics. Those metrics require a provider, which is why the conformance scenario has to suppress a gen_ai.provider.name violation. Keep the agent invocation in a separate field (as _AgentRunStreamWrapper does in the qwen-agent package) so the base sees no client-stream invocation, then remove the time-to-first-chunk expectation and violation.

This issue also appears on line 115 of the same file.

        self._self_invocation = invocation

instrumentation/opentelemetry-instrumentation-genai-qwenpaw/src/opentelemetry/instrumentation/genai/qwenpaw/utils.py:82

  • This records finish_reason="stop" for every assistant chunk, although QwenPaw's tuple explicitly carries a last flag and early-close tests consume a chunk where last=False. Early closure therefore exports a partial response as if the agent completed normally. Only use stop when the tuple's last value is true; partial output needs a representation without a terminal finish reason (extend the util type if necessary).
    return OutputMessage(
        role="assistant",
        parts=[Text(content=text)],
        finish_reason="stop",

instrumentation/opentelemetry-instrumentation-genai-qwenpaw/examples/manual/README.rst:11

  • The implementation and tests deliberately omit gen_ai.agent.id because QwenPaw's agent_id is only a local config key, so this example's claim that each span carries the agent ID is incorrect. Remove “agent id” and describe only the emitted agent name and session/conversation ID.
turn handled by the agent runner produces an ``invoke_agent`` span carrying
the agent id, agent name, and session id. Pair it with the AgentScope
instrumentation to also capture the model and tool calls QwenPaw delegates.

pyproject.toml:48

  • These are global uv overrides, so every workspace sync now ignores CrewAI's declared chromadb~=1.1.0 range and the QwenPaw/oss2 jmespath<1.0 range. That makes unrelated packages install and type-check against versions their maintainers explicitly exclude, masking real dependency incompatibilities and potentially breaking the existing CrewAI/QwenPaw environments. Keep incompatible instrument extras out of the same aggregate environment or scope overrides to the one test/typecheck environment after compatibility is established, rather than weakening both constraints globally.

_logger = logging.getLogger(__name__)


class QueryHandlerStreamWrapper(AsyncStreamWrapper[object]):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Telemetry is finalized only through the proxy's own methods, but the real callers do not always call them. agents/acp/server.py:572 breaks out of the async for when the client cancels, and the runner treats asyncio.CancelledError as a normal path (runner.py:863), so a half consumed turn is expected traffic. When the caller drops the proxy, GC hands the raw async generator to the event loop's finalizer, which calls aclose() on the underlying generator directly and never reaches QueryHandlerStreamWrapper.aclose(). The span is never ended and never exported.

This test fails today with zero spans collected:

@pytest.mark.asyncio
async def test_break_mid_stream_finalizes_span(
    instrument_no_content, runner_module, span_exporter
):
    async def multi_chunk(request, msgs, runner):
        for i in range(3):
            yield assistant_reply(f"chunk-{i}"), i == 2

    runner = runner_module.AgentRunner(agent_id="entry-agent")
    with patched_command_path(runner_module, multi_chunk):
        async for _ in runner.query_handler(user_command_msgs(), make_request()):
            break
    gc.collect()

    (span,) = span_exporter.get_finished_spans()
    assert span.name.startswith("invoke_agent")

Finalization has to hang off the generator's own teardown, so that GeneratorExit reaches a finally, rather than off proxy methods the caller never calls. No proxy-based wrapper can close this hole, since GC finalizes the underlying generator without going through it. Filed as #386 against util-genai, since qwen-agent has the same shape and the fix belongs there.

Fine to land as is for now: it only triggers when a caller stops consuming early, and the leaked context dies with the request task. Worth knowing that it is not purely a lost span though, as later spans in that task nest under the span that never ended.

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.

Thanks for the thorough analysis — agreed. Since GC finalizes the underlying generator directly, no proxy-based wrapper can intercept that path, so the fix belongs in util-genai's stream wrapper as you filed in #386 (qwen-agent shares the same shape). I'll adopt the util-genai fix here (and add your break-mid-stream test) once it lands; leaving this thread open until then.

…ial output on error, document 1.1.x-only support

Change-Id: Ia4cd5e072da43678c0f01e913837d7fdadfccd33
Co-developed-by: Qoder <noreply@qoder.com>
@opentelemetry-pr-dashboard

opentelemetry-pr-dashboard Bot commented Aug 19, 2026

Copy link
Copy Markdown

Hi @123liuziming — just a friendly reminder that this pull request is waiting on you. The dashboard status comment has the open items and is kept current.

  • Replying is enough to hand it off — answer, explain why no change is needed, or ask a follow-up. The dashboard routes it onward once nothing on the list is waiting on you.
  • To hand it back for any other reason, including the dashboard getting this wrong, comment /dashboard route:reviewers.

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.

4 participants