diff --git a/pyproject.toml b/pyproject.toml index 82d3c108..2e750880 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "sap-cloud-sdk" -version = "0.53.0" +version = "0.53.1" description = "SAP Cloud SDK for Python" readme = "README.md" license = "Apache-2.0" diff --git a/src/sap_cloud_sdk/core/telemetry/__init__.py b/src/sap_cloud_sdk/core/telemetry/__init__.py index b1d616e4..ad8a88e6 100644 --- a/src/sap_cloud_sdk/core/telemetry/__init__.py +++ b/src/sap_cloud_sdk/core/telemetry/__init__.py @@ -43,6 +43,7 @@ ATTR_SUMMARY_TOOL_CALL_COUNT, ATTR_SUMMARY_HOOK_CALL_COUNT, ATTR_SUMMARY_HAS_INSTRUCTION, + ATTR_SUMMARY_JOULE_STUDIO_GSID, resolve_source_info, build_extension_span_attributes, reset_tool_call_metrics, @@ -96,6 +97,7 @@ "ATTR_SUMMARY_TOOL_CALL_COUNT", "ATTR_SUMMARY_HOOK_CALL_COUNT", "ATTR_SUMMARY_HAS_INSTRUCTION", + "ATTR_SUMMARY_JOULE_STUDIO_GSID", "resolve_source_info", "build_extension_span_attributes", "reset_tool_call_metrics", diff --git a/src/sap_cloud_sdk/core/telemetry/extensions.py b/src/sap_cloud_sdk/core/telemetry/extensions.py index e07737d4..6c77f159 100644 --- a/src/sap_cloud_sdk/core/telemetry/extensions.py +++ b/src/sap_cloud_sdk/core/telemetry/extensions.py @@ -217,6 +217,7 @@ def get_extension_context() -> dict[str, Any] | None: ATTR_SUMMARY_TOOL_CALL_COUNT = "sap.extension.summary.toolCallCount" ATTR_SUMMARY_HOOK_CALL_COUNT = "sap.extension.summary.hookCallCount" ATTR_SUMMARY_HAS_INSTRUCTION = "sap.extension.summary.hasInstruction" +ATTR_SUMMARY_JOULE_STUDIO_GSID = "sap.extension.joule_studio_gsid" # --------------------------------------------------------------------------- # Private state @@ -612,6 +613,7 @@ def emit_extensions_summary_span( hook_call_count: int, has_instruction: bool, total_duration_ms: float, + joule_studio_gsid: str = "", ) -> None: """Emit a sibling summary span with aggregate extension metrics. @@ -633,6 +635,8 @@ def emit_extensions_summary_span( into the system prompt. total_duration_ms: Wall-clock sum (milliseconds) of all extension operations. + joule_studio_gsid: Global solution ID of Joule Studio (empty string + if not available). """ total = tool_call_count + hook_call_count + (1 if has_instruction else 0) attrs = { @@ -642,6 +646,8 @@ def emit_extensions_summary_span( ATTR_SUMMARY_HOOK_CALL_COUNT: hook_call_count, ATTR_SUMMARY_HAS_INSTRUCTION: has_instruction, } + if joule_studio_gsid: + attrs[ATTR_SUMMARY_JOULE_STUDIO_GSID] = joule_studio_gsid span = _tracer.start_span("agent_extensions_summary", attributes=attrs) span.end() diff --git a/src/sap_cloud_sdk/extensibility/_models.py b/src/sap_cloud_sdk/extensibility/_models.py index b3c73a48..771aa1c4 100644 --- a/src/sap_cloud_sdk/extensibility/_models.py +++ b/src/sap_cloud_sdk/extensibility/_models.py @@ -641,6 +641,9 @@ class ExtensionCapabilityImplementation: hooks: List of hooks attached for this extension capability. source: Per-tool and per-hook attribution mapping. ``None`` when the backend does not provide source information. + joule_studio_gsid: Global solution ID of Joule Studio. Set when a + single Joule Studio extension contributes to this capability; + empty string otherwise. """ capability_id: str @@ -649,6 +652,7 @@ class ExtensionCapabilityImplementation: instruction: Optional[str] = None hooks: List[Hook] = field(default_factory=list) source: Optional[ExtensionSourceMapping] = None + joule_studio_gsid: str = "" @classmethod def from_dict(cls, obj: Dict[str, Any]) -> ExtensionCapabilityImplementation: diff --git a/src/sap_cloud_sdk/extensibility/_ums_transport.py b/src/sap_cloud_sdk/extensibility/_ums_transport.py index 92ebf9c6..1db87be3 100644 --- a/src/sap_cloud_sdk/extensibility/_ums_transport.py +++ b/src/sap_cloud_sdk/extensibility/_ums_transport.py @@ -404,6 +404,15 @@ def _transform_ums_response( instruction = "\n\n".join(instructions) if instructions else None + joule_studio_gsid = next( + ( + node.get("jouleStudioGsid") or "" + for node in nodes + if node.get("jouleStudioGsid") + ), + "", + ) + return ExtensionCapabilityImplementation( capability_id=capability_id, extension_names=extension_names, @@ -411,6 +420,7 @@ def _transform_ums_response( instruction=instruction, hooks=hooks, source=source, + joule_studio_gsid=joule_studio_gsid, ) diff --git a/tests/core/unit/telemetry/test_extensions.py b/tests/core/unit/telemetry/test_extensions.py index 90607c53..f32fcfea 100644 --- a/tests/core/unit/telemetry/test_extensions.py +++ b/tests/core/unit/telemetry/test_extensions.py @@ -25,6 +25,7 @@ ATTR_SUMMARY_TOOL_CALL_COUNT, ATTR_SUMMARY_HOOK_CALL_COUNT, ATTR_SUMMARY_HAS_INSTRUCTION, + ATTR_SUMMARY_JOULE_STUDIO_GSID, resolve_source_info, build_extension_span_attributes, reset_tool_call_metrics, @@ -1074,6 +1075,37 @@ def test_no_instruction_count(self): attrs = mock_tracer.start_span.call_args[1]["attributes"] assert attrs[ATTR_SUMMARY_TOTAL_OPERATION_COUNT] == 1 # no +1 + def test_joule_studio_gsid_included_when_provided(self): + with patch("sap_cloud_sdk.core.telemetry.extensions._tracer") as mock_tracer: + mock_span = MagicMock() + mock_tracer.start_span.return_value = mock_span + + emit_extensions_summary_span( + tool_call_count=1, + hook_call_count=0, + has_instruction=False, + total_duration_ms=100.0, + joule_studio_gsid="019ffc41-01a5-7b73-bda2-cb33b2eae292", + ) + + attrs = mock_tracer.start_span.call_args[1]["attributes"] + assert attrs[ATTR_SUMMARY_JOULE_STUDIO_GSID] == "019ffc41-01a5-7b73-bda2-cb33b2eae292" + + def test_joule_studio_gsid_omitted_when_empty(self): + with patch("sap_cloud_sdk.core.telemetry.extensions._tracer") as mock_tracer: + mock_span = MagicMock() + mock_tracer.start_span.return_value = mock_span + + emit_extensions_summary_span( + tool_call_count=1, + hook_call_count=0, + has_instruction=False, + total_duration_ms=100.0, + ) + + attrs = mock_tracer.start_span.call_args[1]["attributes"] + assert ATTR_SUMMARY_JOULE_STUDIO_GSID not in attrs + # --------------------------------------------------------------------------- # ExtensionContextLogFilter diff --git a/uv.lock b/uv.lock index 2e7c15f3..96a0de6e 100644 --- a/uv.lock +++ b/uv.lock @@ -4286,7 +4286,7 @@ wheels = [ [[package]] name = "sap-cloud-sdk" -version = "0.53.0" +version = "0.53.1" source = { editable = "." } dependencies = [ { name = "cryptography" },