Skip to content

Commit 15d92a7

Browse files
Yigtwxxlmolkova
andauthored
opentelemetry-instrumentation-genai-qwen-agent: support strict typing (open-telemetry#366)
* chore(qwen-agent): enable strict pyright type checking Add the package to the pyright include list and exclude its tests and examples, following the progressive rollout the config already describes. Strict mode reported eight errors in utils.py, all from isinstance() narrowing producing dict[Unknown, Unknown] / list[Unknown]. Narrow those with cast(dict[str, Any], ...) and cast(list[Any], ...), matching the existing idiom in the agno package. No behavior change. Assisted-by: Claude Opus 5 * Add changelog fragment for strict typing support * chore(qwen-agent): type message elements instead of casting to Any The strict-mode narrowing was done with cast(list[Any], ...), which satisfies pyright but throws away the element type. qwen-agent's own Message and ContentItem describe those elements, so annotate the signatures with them (under TYPE_CHECKING, since qwen-agent is an optional dependency) and let pyright narrow the isinstance checks on its own. All five list casts are gone as a result. --------- Co-authored-by: Liudmila Molkova <neskazu@gmail.com>
1 parent 2abfeac commit 15d92a7

3 files changed

Lines changed: 31 additions & 9 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Support strict typing: the package is now covered by the repository pyright configuration.

‎instrumentation/opentelemetry-instrumentation-genai-qwen-agent/src/opentelemetry/instrumentation/genai/qwen_agent/utils.py‎

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import json
1010
import logging
11-
from typing import Any
11+
from typing import TYPE_CHECKING, Any, cast
1212

1313
from opentelemetry.util.genai.handler import TelemetryHandler
1414
from opentelemetry.util.genai.invocation import AgentInvocation
@@ -21,6 +21,15 @@
2121
ToolCallResponse,
2222
)
2323

24+
if TYPE_CHECKING:
25+
from qwen_agent.llm.schema import ContentItem, Message
26+
27+
# qwen-agent accepts a ``Message`` or a plain dict wherever a message or a
28+
# content item is expected and normalizes them internally, so both shapes
29+
# reach the instrumented methods.
30+
QwenMessage = Message | dict[str, Any]
31+
QwenContentItem = ContentItem | dict[str, Any]
32+
2433
_logger = logging.getLogger(__name__)
2534

2635

@@ -31,8 +40,9 @@ def _field_value(value: Any, *names: str) -> Any:
3140

3241
for name in names:
3342
if isinstance(value, dict):
34-
if name in value:
35-
return value[name]
43+
mapping = cast(dict[str, Any], value)
44+
if name in mapping:
45+
return mapping[name]
3646
continue
3747

3848
attr_value = getattr(value, name, None)
@@ -51,7 +61,7 @@ def _field_value(value: Any, *names: str) -> Any:
5161
return None
5262

5363

54-
def _extract_content_text(content: Any) -> str:
64+
def _extract_content_text(content: str | list[QwenContentItem] | None) -> str:
5565
"""Extract text from a qwen-agent Message content field.
5666
5767
Content can be a plain string or a list of ContentItem.
@@ -68,7 +78,9 @@ def _extract_content_text(content: Any) -> str:
6878
return str(content) if content else ""
6979

7080

71-
def find_tool_call_id(messages: Any, tool_name: str) -> str | None:
81+
def find_tool_call_id(
82+
messages: list[QwenMessage] | None, tool_name: str
83+
) -> str | None:
7284
"""Best-effort lookup of the current tool call's function id.
7385
7486
``FnCallAgent`` passes the message history to ``_call_tool``; the call
@@ -127,7 +139,9 @@ def _tool_call_response_id(msg: Any) -> str:
127139
return tool_call_id
128140

129141

130-
def convert_to_input_messages(messages: Any) -> list[InputMessage]:
142+
def convert_to_input_messages(
143+
messages: QwenMessage | list[QwenMessage] | None,
144+
) -> list[InputMessage]:
131145
"""Convert a qwen-agent Message list to GenAI InputMessage objects."""
132146
if not messages:
133147
return []
@@ -170,7 +184,9 @@ def convert_to_input_messages(messages: Any) -> list[InputMessage]:
170184
return input_messages
171185

172186

173-
def convert_to_output_messages(messages: Any) -> list[OutputMessage]:
187+
def convert_to_output_messages(
188+
messages: QwenMessage | list[QwenMessage] | None,
189+
) -> list[OutputMessage]:
174190
"""Convert qwen-agent response messages to GenAI OutputMessage objects."""
175191
if not messages:
176192
return []
@@ -213,7 +229,9 @@ def convert_to_output_messages(messages: Any) -> list[OutputMessage]:
213229
return output_messages
214230

215231

216-
def convert_to_final_output_messages(messages: Any) -> list[OutputMessage]:
232+
def convert_to_final_output_messages(
233+
messages: QwenMessage | list[QwenMessage] | None,
234+
) -> list[OutputMessage]:
217235
"""Convert only the final qwen-agent answer to OutputMessage objects.
218236
219237
An agent run yields the full response history including intermediate
@@ -249,7 +267,7 @@ def convert_to_final_output_messages(messages: Any) -> list[OutputMessage]:
249267
def create_agent_invocation(
250268
handler: TelemetryHandler,
251269
agent_instance: Any,
252-
messages: Any,
270+
messages: QwenMessage | list[QwenMessage] | None,
253271
) -> AgentInvocation:
254272
"""Create and start an AgentInvocation for Agent.run()."""
255273
llm_instance = getattr(agent_instance, "llm", None)

‎pyproject.toml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ include = [
121121
"instrumentation/opentelemetry-instrumentation-genai-langchain",
122122
"instrumentation/opentelemetry-instrumentation-genai-llama-index",
123123
"instrumentation/opentelemetry-instrumentation-genai-openai-agents",
124+
"instrumentation/opentelemetry-instrumentation-genai-qwen-agent",
124125
"instrumentation/opentelemetry-instrumentation-genai-weaviate-client",
125126
"util/opentelemetry-util-genai",
126127
]
@@ -143,6 +144,8 @@ exclude = [
143144
"instrumentation/opentelemetry-instrumentation-genai-llama-index/examples/**/*.py",
144145
"instrumentation/opentelemetry-instrumentation-genai-openai-agents/tests/**/*.py",
145146
"instrumentation/opentelemetry-instrumentation-genai-openai-agents/examples/**",
147+
"instrumentation/opentelemetry-instrumentation-genai-qwen-agent/tests/**/*.py",
148+
"instrumentation/opentelemetry-instrumentation-genai-qwen-agent/examples/**/*.py",
146149
"instrumentation/opentelemetry-instrumentation-genai-weaviate-client/tests/**/*.py",
147150
"instrumentation/opentelemetry-instrumentation-genai-weaviate-client/examples/**/*.py",
148151
"util/opentelemetry-util-genai/tests/**/*.py",

0 commit comments

Comments
 (0)