Skip to content

Commit 7991a56

Browse files
committed
feat(tools): add safety OTel span attribute helper
1 parent e737426 commit 7991a56

2 files changed

Lines changed: 95 additions & 0 deletions

File tree

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# tRPC-Agent-Python is licensed under Apache-2.0.
6+
"""Tests for safety telemetry integration."""
7+
8+
from unittest.mock import MagicMock
9+
from unittest.mock import patch
10+
11+
from trpc_agent_sdk.tools.safety._telemetry import set_safety_span_attrs
12+
from trpc_agent_sdk.tools.safety._types import Decision
13+
from trpc_agent_sdk.tools.safety._types import RiskLevel
14+
from trpc_agent_sdk.tools.safety._types import RiskType
15+
from trpc_agent_sdk.tools.safety._types import RuleFinding
16+
from trpc_agent_sdk.tools.safety._types import ScanReport
17+
18+
19+
class TestSetSafetySpanAttrs:
20+
def test_span_attributes_set_on_active_span(self):
21+
mock_span = MagicMock()
22+
mock_span.is_recording.return_value = True
23+
24+
report = ScanReport(
25+
decision=Decision.DENY,
26+
risk_level=RiskLevel.CRITICAL,
27+
findings=[
28+
RuleFinding(
29+
rule_id="DANGEROUS_DELETE_001",
30+
risk_type=RiskType.DANGEROUS_FILE_OP,
31+
risk_level=RiskLevel.CRITICAL,
32+
evidence="rm -rf /",
33+
message="dangerous delete",
34+
recommendation="don't",
35+
),
36+
],
37+
scan_duration_ms=15.0,
38+
)
39+
40+
with patch("trpc_agent_sdk.tools.safety._telemetry.trace.get_current_span", return_value=mock_span):
41+
set_safety_span_attrs(report)
42+
43+
mock_span.set_attribute.assert_any_call("tool.safety.decision", "deny")
44+
mock_span.set_attribute.assert_any_call("tool.safety.risk_level", "critical")
45+
mock_span.set_attribute.assert_any_call("tool.safety.scan_duration_ms", 15.0)
46+
47+
def test_no_crash_when_no_span(self):
48+
mock_span = MagicMock()
49+
mock_span.is_recording.return_value = False
50+
51+
report = ScanReport(decision=Decision.ALLOW)
52+
53+
with patch("trpc_agent_sdk.tools.safety._telemetry.trace.get_current_span", return_value=mock_span):
54+
set_safety_span_attrs(report)
55+
56+
mock_span.set_attribute.assert_not_called()
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# tRPC-Agent-Python is licensed under Apache-2.0.
6+
"""OpenTelemetry integration for the Tool Script Safety Guard.
7+
8+
Decorates existing tool execution spans with safety metadata.
9+
"""
10+
11+
from __future__ import annotations
12+
13+
from opentelemetry import trace
14+
15+
from ._types import ScanReport
16+
17+
18+
def set_safety_span_attrs(report: ScanReport):
19+
"""Set safety-related span attributes on the current active span.
20+
21+
If no span is active or the span is not recording, this is a no-op.
22+
Intended to be called from ToolSafetyFilter._before() so the attributes
23+
appear on the tool execution span created by ToolsProcessor._execute_tool().
24+
25+
Attributes set:
26+
tool.safety.decision: allow | deny | needs_human_review
27+
tool.safety.risk_level: low | medium | high | critical | None
28+
tool.safety.rule_ids: JSON array of triggered rule IDs
29+
tool.safety.scan_duration_ms: float
30+
"""
31+
span = trace.get_current_span()
32+
if span is None or not span.is_recording():
33+
return
34+
35+
span.set_attribute("tool.safety.decision", report.decision.value)
36+
if report.risk_level:
37+
span.set_attribute("tool.safety.risk_level", report.risk_level.value)
38+
span.set_attribute("tool.safety.rule_ids", [f.rule_id for f in report.findings])
39+
span.set_attribute("tool.safety.scan_duration_ms", report.scan_duration_ms)

0 commit comments

Comments
 (0)