From 271cd352f9568c6ddd6d16f10cd9dec2e6d7b8f0 Mon Sep 17 00:00:00 2001 From: "yulan.zxd" Date: Sun, 25 Oct 2026 18:48:35 +0800 Subject: [PATCH 1/4] fix(observability): bind tool arguments with builtin dict typing.Dict cannot be instantiated; the TypeError was swallowed by the broad except in _bind_tool_arguments, so every @observe_tool span recorded {"_error": "could_not_bind_arguments"} instead of the real arguments. --- .../reinforcement/component/observability/genai/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dashscope/finetune/reinforcement/component/observability/genai/tools.py b/dashscope/finetune/reinforcement/component/observability/genai/tools.py index c0d48b85..bdf20b9a 100644 --- a/dashscope/finetune/reinforcement/component/observability/genai/tools.py +++ b/dashscope/finetune/reinforcement/component/observability/genai/tools.py @@ -535,7 +535,7 @@ def _bind_tool_arguments( bound = sig.bind_partial(*args, **kwargs) bound.apply_defaults() param_names = list(sig.parameters.keys()) - raw = Dict(bound.arguments) + raw = dict(bound.arguments) if param_names and param_names[0] in ("self", "cls"): raw = {k: v for k, v in raw.items() if k not in ("self", "cls")} return _json_serializable(raw) From 1e57d45ea78d3bcf187102276bbc81da933d6673 Mon Sep 17 00:00:00 2001 From: "yulan.zxd" Date: Sun, 25 Oct 2026 18:48:36 +0800 Subject: [PATCH 2/4] fix(observability): emit reasoning parts in LLM output messages The OpenAI and DashScope output-message converters ignored reasoning_content, so chat spans never echoed model thinking although the schema (Reasoning part) supports it. Export Reasoning from the _core facade and insert the part first. --- .../component/observability/genai/_core.py | 3 ++- .../component/observability/genai/messages.py | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/dashscope/finetune/reinforcement/component/observability/genai/_core.py b/dashscope/finetune/reinforcement/component/observability/genai/_core.py index 3c0fe794..fd26703e 100644 --- a/dashscope/finetune/reinforcement/component/observability/genai/_core.py +++ b/dashscope/finetune/reinforcement/component/observability/genai/_core.py @@ -55,6 +55,7 @@ from opentelemetry.util.genai.types import ( InputMessage, OutputMessage, + Reasoning, Text, ToolCall, ) @@ -64,7 +65,7 @@ get_extended_telemetry_handler = None # type: ignore[assignment] ExecuteToolInvocation = None # type: ignore[assignment] # type: ignore[assignment] - InputMessage = OutputMessage = Text = ToolCall = None + InputMessage = OutputMessage = Reasoning = Text = ToolCall = None GENAI_AVAILABLE = False _logger = logging.getLogger(__name__) diff --git a/dashscope/finetune/reinforcement/component/observability/genai/messages.py b/dashscope/finetune/reinforcement/component/observability/genai/messages.py index 44abe0f5..ab09826a 100644 --- a/dashscope/finetune/reinforcement/component/observability/genai/messages.py +++ b/dashscope/finetune/reinforcement/component/observability/genai/messages.py @@ -454,6 +454,23 @@ def openai_chat_messages_to_input_messages( return out +def _reasoning_part(msg: Any) -> Any: + """Return a ``Reasoning`` part when the message carries thinking content. + + ``None`` when the GenAI util is unavailable or the field is absent, so + callers keep their existing part ordering logic. + """ + if _core.Reasoning is None or msg is None: + return None + if isinstance(msg, Dict): + reasoning = msg.get("reasoning_content") + else: + reasoning = getattr(msg, "reasoning_content", None) + if not isinstance(reasoning, str) or not reasoning: + return None + return _core.Reasoning(content=reasoning) + + def openai_completion_to_output_messages( # pylint: disable=too-many-branches completion: Any, ) -> List[Any]: @@ -513,6 +530,9 @@ def openai_completion_to_output_messages( # pylint: disable=too-many-branches tool_calls = getattr(msg, "tool_calls", None) if tool_calls: parts.extend(tool_calls_to_parts(tool_calls)) + reasoning_part = _reasoning_part(msg) + if reasoning_part is not None: + parts.insert(0, reasoning_part) if not parts: parts.append(_core.Text(content="")) result.append( @@ -561,6 +581,9 @@ def dashscope_response_to_output_messages(response: Any) -> List[Any]: ) if tool_calls: parts.extend(tool_calls_to_parts(tool_calls)) + reasoning_part = _reasoning_part(msg) + if reasoning_part is not None: + parts.insert(0, reasoning_part) if not parts: parts.append(_core.Text(content="")) result.append( From 48021eb6cf63604004413def52e185f1abc73698 Mon Sep 17 00:00:00 2001 From: "yulan.zxd" Date: Mon, 26 Oct 2026 19:31:12 +0800 Subject: [PATCH 3/4] style(observability): mark Reasoning facade re-export for pylint W0611 unused-import: Reasoning is re-exported for the converters in messages.py, not referenced inside _core. --- .../reinforcement/component/observability/genai/_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dashscope/finetune/reinforcement/component/observability/genai/_core.py b/dashscope/finetune/reinforcement/component/observability/genai/_core.py index fd26703e..a8538945 100644 --- a/dashscope/finetune/reinforcement/component/observability/genai/_core.py +++ b/dashscope/finetune/reinforcement/component/observability/genai/_core.py @@ -52,7 +52,7 @@ get_extended_telemetry_handler, ) from opentelemetry.util.genai.extended_types import ExecuteToolInvocation - from opentelemetry.util.genai.types import ( + from opentelemetry.util.genai.types import ( # pylint: disable=unused-import # noqa: E501 # facade re-export InputMessage, OutputMessage, Reasoning, From be55a1b181e9e3c531bb74000260186575504ca2 Mon Sep 17 00:00:00 2001 From: "yulan.zxd" Date: Mon, 26 Oct 2026 19:40:11 +0800 Subject: [PATCH 4/4] fix(observability): keep tracing enabled on genai util without Reasoning Import Reasoning in a nested try so environments with loongsuite-util-genai < 0.4.0 degrade to missing reasoning parts instead of GENAI_AVAILABLE=False. --- .../component/observability/genai/_core.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/dashscope/finetune/reinforcement/component/observability/genai/_core.py b/dashscope/finetune/reinforcement/component/observability/genai/_core.py index a8538945..08091c6c 100644 --- a/dashscope/finetune/reinforcement/component/observability/genai/_core.py +++ b/dashscope/finetune/reinforcement/component/observability/genai/_core.py @@ -52,14 +52,20 @@ get_extended_telemetry_handler, ) from opentelemetry.util.genai.extended_types import ExecuteToolInvocation - from opentelemetry.util.genai.types import ( # pylint: disable=unused-import # noqa: E501 # facade re-export + from opentelemetry.util.genai.types import ( InputMessage, OutputMessage, - Reasoning, Text, ToolCall, ) + try: # Reasoning part exists in loongsuite-util-genai >= 0.4.0. + from opentelemetry.util.genai import types as _genai_types + + Reasoning = getattr(_genai_types, "Reasoning", None) + except ImportError: # pragma: no cover - older genai util + Reasoning = None # type: ignore[assignment,misc] + GENAI_AVAILABLE = True except ImportError: # pragma: no cover get_extended_telemetry_handler = None # type: ignore[assignment]