mseep-agentops 0.4.18__py3-none-any.whl → 0.4.22__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentops/__init__.py +0 -0
- agentops/client/api/base.py +28 -30
- agentops/client/api/versions/v3.py +29 -25
- agentops/client/api/versions/v4.py +87 -46
- agentops/client/client.py +98 -29
- agentops/client/http/README.md +87 -0
- agentops/client/http/http_client.py +126 -172
- agentops/config.py +8 -2
- agentops/instrumentation/OpenTelemetry.md +133 -0
- agentops/instrumentation/README.md +167 -0
- agentops/instrumentation/__init__.py +13 -1
- agentops/instrumentation/agentic/ag2/__init__.py +18 -0
- agentops/instrumentation/agentic/ag2/instrumentor.py +922 -0
- agentops/instrumentation/agentic/agno/__init__.py +19 -0
- agentops/instrumentation/agentic/agno/attributes/__init__.py +20 -0
- agentops/instrumentation/agentic/agno/attributes/agent.py +250 -0
- agentops/instrumentation/agentic/agno/attributes/metrics.py +214 -0
- agentops/instrumentation/agentic/agno/attributes/storage.py +158 -0
- agentops/instrumentation/agentic/agno/attributes/team.py +195 -0
- agentops/instrumentation/agentic/agno/attributes/tool.py +210 -0
- agentops/instrumentation/agentic/agno/attributes/workflow.py +254 -0
- agentops/instrumentation/agentic/agno/instrumentor.py +1313 -0
- agentops/instrumentation/agentic/crewai/LICENSE +201 -0
- agentops/instrumentation/agentic/crewai/NOTICE.md +10 -0
- agentops/instrumentation/agentic/crewai/__init__.py +6 -0
- agentops/instrumentation/agentic/crewai/crewai_span_attributes.py +335 -0
- agentops/instrumentation/agentic/crewai/instrumentation.py +535 -0
- agentops/instrumentation/agentic/crewai/version.py +1 -0
- agentops/instrumentation/agentic/google_adk/__init__.py +19 -0
- agentops/instrumentation/agentic/google_adk/instrumentor.py +68 -0
- agentops/instrumentation/agentic/google_adk/patch.py +767 -0
- agentops/instrumentation/agentic/haystack/__init__.py +1 -0
- agentops/instrumentation/agentic/haystack/instrumentor.py +186 -0
- agentops/instrumentation/agentic/langgraph/__init__.py +3 -0
- agentops/instrumentation/agentic/langgraph/attributes.py +54 -0
- agentops/instrumentation/agentic/langgraph/instrumentation.py +598 -0
- agentops/instrumentation/agentic/langgraph/version.py +1 -0
- agentops/instrumentation/agentic/openai_agents/README.md +156 -0
- agentops/instrumentation/agentic/openai_agents/SPANS.md +145 -0
- agentops/instrumentation/agentic/openai_agents/TRACING_API.md +144 -0
- agentops/instrumentation/agentic/openai_agents/__init__.py +30 -0
- agentops/instrumentation/agentic/openai_agents/attributes/common.py +549 -0
- agentops/instrumentation/agentic/openai_agents/attributes/completion.py +172 -0
- agentops/instrumentation/agentic/openai_agents/attributes/model.py +58 -0
- agentops/instrumentation/agentic/openai_agents/attributes/tokens.py +275 -0
- agentops/instrumentation/agentic/openai_agents/exporter.py +469 -0
- agentops/instrumentation/agentic/openai_agents/instrumentor.py +107 -0
- agentops/instrumentation/agentic/openai_agents/processor.py +58 -0
- agentops/instrumentation/agentic/smolagents/README.md +88 -0
- agentops/instrumentation/agentic/smolagents/__init__.py +12 -0
- agentops/instrumentation/agentic/smolagents/attributes/agent.py +354 -0
- agentops/instrumentation/agentic/smolagents/attributes/model.py +205 -0
- agentops/instrumentation/agentic/smolagents/instrumentor.py +286 -0
- agentops/instrumentation/agentic/smolagents/stream_wrapper.py +258 -0
- agentops/instrumentation/agentic/xpander/__init__.py +15 -0
- agentops/instrumentation/agentic/xpander/context.py +112 -0
- agentops/instrumentation/agentic/xpander/instrumentor.py +877 -0
- agentops/instrumentation/agentic/xpander/trace_probe.py +86 -0
- agentops/instrumentation/agentic/xpander/version.py +3 -0
- agentops/instrumentation/common/README.md +65 -0
- agentops/instrumentation/common/attributes.py +1 -2
- agentops/instrumentation/providers/anthropic/__init__.py +24 -0
- agentops/instrumentation/providers/anthropic/attributes/__init__.py +23 -0
- agentops/instrumentation/providers/anthropic/attributes/common.py +64 -0
- agentops/instrumentation/providers/anthropic/attributes/message.py +541 -0
- agentops/instrumentation/providers/anthropic/attributes/tools.py +231 -0
- agentops/instrumentation/providers/anthropic/event_handler_wrapper.py +90 -0
- agentops/instrumentation/providers/anthropic/instrumentor.py +146 -0
- agentops/instrumentation/providers/anthropic/stream_wrapper.py +436 -0
- agentops/instrumentation/providers/google_genai/README.md +33 -0
- agentops/instrumentation/providers/google_genai/__init__.py +24 -0
- agentops/instrumentation/providers/google_genai/attributes/__init__.py +25 -0
- agentops/instrumentation/providers/google_genai/attributes/chat.py +125 -0
- agentops/instrumentation/providers/google_genai/attributes/common.py +88 -0
- agentops/instrumentation/providers/google_genai/attributes/model.py +284 -0
- agentops/instrumentation/providers/google_genai/instrumentor.py +170 -0
- agentops/instrumentation/providers/google_genai/stream_wrapper.py +238 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/__init__.py +28 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/attributes/__init__.py +27 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/attributes/attributes.py +277 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/attributes/common.py +104 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/instrumentor.py +162 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/stream_wrapper.py +302 -0
- agentops/instrumentation/providers/mem0/__init__.py +45 -0
- agentops/instrumentation/providers/mem0/common.py +377 -0
- agentops/instrumentation/providers/mem0/instrumentor.py +270 -0
- agentops/instrumentation/providers/mem0/memory.py +430 -0
- agentops/instrumentation/providers/openai/__init__.py +21 -0
- agentops/instrumentation/providers/openai/attributes/__init__.py +7 -0
- agentops/instrumentation/providers/openai/attributes/common.py +55 -0
- agentops/instrumentation/providers/openai/attributes/response.py +607 -0
- agentops/instrumentation/providers/openai/config.py +36 -0
- agentops/instrumentation/providers/openai/instrumentor.py +312 -0
- agentops/instrumentation/providers/openai/stream_wrapper.py +941 -0
- agentops/instrumentation/providers/openai/utils.py +44 -0
- agentops/instrumentation/providers/openai/v0.py +176 -0
- agentops/instrumentation/providers/openai/v0_wrappers.py +483 -0
- agentops/instrumentation/providers/openai/wrappers/__init__.py +30 -0
- agentops/instrumentation/providers/openai/wrappers/assistant.py +277 -0
- agentops/instrumentation/providers/openai/wrappers/chat.py +259 -0
- agentops/instrumentation/providers/openai/wrappers/completion.py +109 -0
- agentops/instrumentation/providers/openai/wrappers/embeddings.py +94 -0
- agentops/instrumentation/providers/openai/wrappers/image_gen.py +75 -0
- agentops/instrumentation/providers/openai/wrappers/responses.py +191 -0
- agentops/instrumentation/providers/openai/wrappers/shared.py +81 -0
- agentops/instrumentation/utilities/concurrent_futures/__init__.py +10 -0
- agentops/instrumentation/utilities/concurrent_futures/instrumentation.py +206 -0
- agentops/integration/callbacks/dspy/__init__.py +11 -0
- agentops/integration/callbacks/dspy/callback.py +471 -0
- agentops/integration/callbacks/langchain/README.md +59 -0
- agentops/integration/callbacks/langchain/__init__.py +15 -0
- agentops/integration/callbacks/langchain/callback.py +791 -0
- agentops/integration/callbacks/langchain/utils.py +54 -0
- agentops/legacy/crewai.md +121 -0
- agentops/logging/instrument_logging.py +4 -0
- agentops/sdk/README.md +220 -0
- agentops/sdk/core.py +75 -32
- agentops/sdk/descriptors/classproperty.py +28 -0
- agentops/sdk/exporters.py +152 -33
- agentops/semconv/README.md +125 -0
- agentops/semconv/span_kinds.py +0 -2
- agentops/validation.py +102 -63
- {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.22.dist-info}/METADATA +30 -40
- mseep_agentops-0.4.22.dist-info/RECORD +178 -0
- {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.22.dist-info}/WHEEL +1 -2
- mseep_agentops-0.4.18.dist-info/RECORD +0 -94
- mseep_agentops-0.4.18.dist-info/top_level.txt +0 -2
- tests/conftest.py +0 -10
- tests/unit/client/__init__.py +0 -1
- tests/unit/client/test_http_adapter.py +0 -221
- tests/unit/client/test_http_client.py +0 -206
- tests/unit/conftest.py +0 -54
- tests/unit/sdk/__init__.py +0 -1
- tests/unit/sdk/instrumentation_tester.py +0 -207
- tests/unit/sdk/test_attributes.py +0 -392
- tests/unit/sdk/test_concurrent_instrumentation.py +0 -468
- tests/unit/sdk/test_decorators.py +0 -763
- tests/unit/sdk/test_exporters.py +0 -241
- tests/unit/sdk/test_factory.py +0 -1188
- tests/unit/sdk/test_internal_span_processor.py +0 -397
- tests/unit/sdk/test_resource_attributes.py +0 -35
- tests/unit/test_config.py +0 -82
- tests/unit/test_context_manager.py +0 -777
- tests/unit/test_events.py +0 -27
- tests/unit/test_host_env.py +0 -54
- tests/unit/test_init_py.py +0 -501
- tests/unit/test_serialization.py +0 -433
- tests/unit/test_session.py +0 -676
- tests/unit/test_user_agent.py +0 -34
- tests/unit/test_validation.py +0 -405
- {tests → agentops/instrumentation/agentic/openai_agents/attributes}/__init__.py +0 -0
- /tests/unit/__init__.py → /agentops/instrumentation/providers/openai/attributes/tools.py +0 -0
- {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.22.dist-info}/licenses/LICENSE +0 -0
@@ -1,206 +0,0 @@
|
|
1
|
-
# """Tests for the HttpClient class."""
|
2
|
-
#
|
3
|
-
# import pytest
|
4
|
-
# import requests
|
5
|
-
# from unittest import mock
|
6
|
-
# from pytest_mock import MockerFixture
|
7
|
-
#
|
8
|
-
# from agentops.client.http.http_client import HttpClient
|
9
|
-
# from agentops.client.http.http_adapter import AuthenticatedHttpAdapter, BaseHTTPAdapter
|
10
|
-
# from agentops.client.auth_manager import AuthManager
|
11
|
-
#
|
12
|
-
#
|
13
|
-
# class TestHttpClient:
|
14
|
-
# """Tests for the HttpClient class."""
|
15
|
-
#
|
16
|
-
# def test_get_session_creates_new_session_if_none_exists(self):
|
17
|
-
# """Test that get_session creates a new session if none exists."""
|
18
|
-
# # Reset the session to ensure we're testing from a clean state
|
19
|
-
# HttpClient._session = None
|
20
|
-
#
|
21
|
-
# # Call get_session
|
22
|
-
# session = HttpClient.get_session()
|
23
|
-
#
|
24
|
-
# # Verify a session was created
|
25
|
-
# assert session is not None
|
26
|
-
# assert isinstance(session, requests.Session)
|
27
|
-
#
|
28
|
-
# # Verify the session has the expected adapters
|
29
|
-
# assert any(isinstance(adapter, BaseHTTPAdapter) for adapter in session.adapters.values())
|
30
|
-
#
|
31
|
-
# # Verify the session has the expected headers
|
32
|
-
# assert "Content-Type" in session.headers
|
33
|
-
# assert "Connection" in session.headers
|
34
|
-
# assert "Keep-Alive" in session.headers
|
35
|
-
#
|
36
|
-
# def test_get_session_returns_existing_session(self):
|
37
|
-
# """Test that get_session returns the existing session if one exists."""
|
38
|
-
# # Create a session
|
39
|
-
# HttpClient._session = None
|
40
|
-
# session1 = HttpClient.get_session()
|
41
|
-
#
|
42
|
-
# # Call get_session again
|
43
|
-
# session2 = HttpClient.get_session()
|
44
|
-
#
|
45
|
-
# # Verify the same session was returned
|
46
|
-
# assert session2 is session1
|
47
|
-
#
|
48
|
-
# def test_get_authenticated_session_creates_new_session(self):
|
49
|
-
# """Test that get_authenticated_session creates a new authenticated session."""
|
50
|
-
# # Call get_authenticated_session
|
51
|
-
# session = HttpClient.get_authenticated_session(
|
52
|
-
# endpoint="https://api.example.com",
|
53
|
-
# api_key="test-api-key"
|
54
|
-
# )
|
55
|
-
#
|
56
|
-
# # Verify a session was created
|
57
|
-
# assert session is not None
|
58
|
-
# assert isinstance(session, requests.Session)
|
59
|
-
#
|
60
|
-
# # Verify the session has the expected adapters
|
61
|
-
# assert any(isinstance(adapter, AuthenticatedHttpAdapter) for adapter in session.adapters.values())
|
62
|
-
#
|
63
|
-
# # Verify the session has the expected headers
|
64
|
-
# assert "Content-Type" in session.headers
|
65
|
-
# assert "Connection" in session.headers
|
66
|
-
# assert "Keep-Alive" in session.headers
|
67
|
-
#
|
68
|
-
# def test_get_authenticated_session_with_custom_token_fetcher(self, mocker: MockerFixture):
|
69
|
-
# """Test that get_authenticated_session accepts a custom token fetcher."""
|
70
|
-
# # Create a mock token fetcher
|
71
|
-
# mock_token_fetcher = mock.Mock(return_value="test-token")
|
72
|
-
#
|
73
|
-
# # Call get_authenticated_session with the custom token fetcher
|
74
|
-
# session = HttpClient.get_authenticated_session(
|
75
|
-
# endpoint="https://api.example.com",
|
76
|
-
# api_key="test-api-key",
|
77
|
-
# token_fetcher=mock_token_fetcher
|
78
|
-
# )
|
79
|
-
#
|
80
|
-
# # Verify a session was created
|
81
|
-
# assert session is not None
|
82
|
-
# assert isinstance(session, requests.Session)
|
83
|
-
#
|
84
|
-
# # Get the adapter
|
85
|
-
# adapter = next(adapter for adapter in session.adapters.values()
|
86
|
-
# if isinstance(adapter, AuthenticatedHttpAdapter))
|
87
|
-
#
|
88
|
-
# # Verify the adapter has the custom token fetcher
|
89
|
-
# assert adapter.token_fetcher is mock_token_fetcher
|
90
|
-
#
|
91
|
-
# def test_request_get(self, mocker: MockerFixture):
|
92
|
-
# """Test that request makes a GET request."""
|
93
|
-
# # Mock the session
|
94
|
-
# mock_session = mock.Mock()
|
95
|
-
# mock_get = mock.Mock()
|
96
|
-
# mock_session.get = mock_get
|
97
|
-
#
|
98
|
-
# # Mock get_session to return our mock session
|
99
|
-
# mocker.patch.object(HttpClient, "get_session", return_value=mock_session)
|
100
|
-
#
|
101
|
-
# # Call request
|
102
|
-
# HttpClient.request(
|
103
|
-
# method="get",
|
104
|
-
# url="https://api.example.com/test",
|
105
|
-
# headers={"X-Test": "test"},
|
106
|
-
# timeout=10
|
107
|
-
# )
|
108
|
-
#
|
109
|
-
# # Verify the session method was called with the expected arguments
|
110
|
-
# mock_get.assert_called_once_with(
|
111
|
-
# "https://api.example.com/test",
|
112
|
-
# headers={"X-Test": "test"},
|
113
|
-
# timeout=10,
|
114
|
-
# allow_redirects=False
|
115
|
-
# )
|
116
|
-
#
|
117
|
-
# def test_request_post(self, mocker: MockerFixture):
|
118
|
-
# """Test that request makes a POST request."""
|
119
|
-
# # Mock the session
|
120
|
-
# mock_session = mock.Mock()
|
121
|
-
# mock_post = mock.Mock()
|
122
|
-
# mock_session.post = mock_post
|
123
|
-
#
|
124
|
-
# # Mock get_session to return our mock session
|
125
|
-
# mocker.patch.object(HttpClient, "get_session", return_value=mock_session)
|
126
|
-
#
|
127
|
-
# # Call request
|
128
|
-
# HttpClient.request(
|
129
|
-
# method="post",
|
130
|
-
# url="https://api.example.com/test",
|
131
|
-
# data={"test": "data"},
|
132
|
-
# headers={"X-Test": "test"},
|
133
|
-
# timeout=10
|
134
|
-
# )
|
135
|
-
#
|
136
|
-
# # Verify the session method was called with the expected arguments
|
137
|
-
# mock_post.assert_called_once_with(
|
138
|
-
# "https://api.example.com/test",
|
139
|
-
# json={"test": "data"},
|
140
|
-
# headers={"X-Test": "test"},
|
141
|
-
# timeout=10,
|
142
|
-
# allow_redirects=False
|
143
|
-
# )
|
144
|
-
#
|
145
|
-
# def test_request_put(self, mocker: MockerFixture):
|
146
|
-
# """Test that request makes a PUT request."""
|
147
|
-
# # Mock the session
|
148
|
-
# mock_session = mock.Mock()
|
149
|
-
# mock_put = mock.Mock()
|
150
|
-
# mock_session.put = mock_put
|
151
|
-
#
|
152
|
-
# # Mock get_session to return our mock session
|
153
|
-
# mocker.patch.object(HttpClient, "get_session", return_value=mock_session)
|
154
|
-
#
|
155
|
-
# # Call request
|
156
|
-
# HttpClient.request(
|
157
|
-
# method="put",
|
158
|
-
# url="https://api.example.com/test",
|
159
|
-
# data={"test": "data"},
|
160
|
-
# headers={"X-Test": "test"},
|
161
|
-
# timeout=10
|
162
|
-
# )
|
163
|
-
#
|
164
|
-
# # Verify the session method was called with the expected arguments
|
165
|
-
# mock_put.assert_called_once_with(
|
166
|
-
# "https://api.example.com/test",
|
167
|
-
# json={"test": "data"},
|
168
|
-
# headers={"X-Test": "test"},
|
169
|
-
# timeout=10,
|
170
|
-
# allow_redirects=False
|
171
|
-
# )
|
172
|
-
#
|
173
|
-
# def test_request_delete(self, mocker: MockerFixture):
|
174
|
-
# """Test that request makes a DELETE request."""
|
175
|
-
# # Mock the session
|
176
|
-
# mock_session = mock.Mock()
|
177
|
-
# mock_delete = mock.Mock()
|
178
|
-
# mock_session.delete = mock_delete
|
179
|
-
#
|
180
|
-
# # Mock get_session to return our mock session
|
181
|
-
# mocker.patch.object(HttpClient, "get_session", return_value=mock_session)
|
182
|
-
#
|
183
|
-
# # Call request
|
184
|
-
# HttpClient.request(
|
185
|
-
# method="delete",
|
186
|
-
# url="https://api.example.com/test",
|
187
|
-
# headers={"X-Test": "test"},
|
188
|
-
# timeout=10
|
189
|
-
# )
|
190
|
-
#
|
191
|
-
# # Verify the session method was called with the expected arguments
|
192
|
-
# mock_delete.assert_called_once_with(
|
193
|
-
# "https://api.example.com/test",
|
194
|
-
# headers={"X-Test": "test"},
|
195
|
-
# timeout=10,
|
196
|
-
# allow_redirects=False
|
197
|
-
# )
|
198
|
-
#
|
199
|
-
# def test_request_unsupported_method(self):
|
200
|
-
# """Test that request raises an error for unsupported methods."""
|
201
|
-
# # Call request with an unsupported method
|
202
|
-
# with pytest.raises(ValueError, match="Unsupported HTTP method: patch"):
|
203
|
-
# HttpClient.request(
|
204
|
-
# method="patch",
|
205
|
-
# url="https://api.example.com/test"
|
206
|
-
# )
|
tests/unit/conftest.py
DELETED
@@ -1,54 +0,0 @@
|
|
1
|
-
import uuid
|
2
|
-
|
3
|
-
import pytest
|
4
|
-
import requests_mock
|
5
|
-
|
6
|
-
from agentops.config import Config
|
7
|
-
from tests.fixtures.client import * # noqa
|
8
|
-
from tests.unit.sdk.instrumentation_tester import InstrumentationTester
|
9
|
-
|
10
|
-
|
11
|
-
@pytest.fixture
|
12
|
-
def api_key() -> str:
|
13
|
-
"""Standard API key for testing"""
|
14
|
-
return "test-api-key"
|
15
|
-
|
16
|
-
|
17
|
-
@pytest.fixture
|
18
|
-
def endpoint() -> str:
|
19
|
-
"""Base API URL"""
|
20
|
-
return Config().endpoint
|
21
|
-
|
22
|
-
|
23
|
-
@pytest.fixture(autouse=True)
|
24
|
-
def mock_req(endpoint, api_key):
|
25
|
-
"""
|
26
|
-
Mocks AgentOps backend API requests.
|
27
|
-
"""
|
28
|
-
with requests_mock.Mocker(real_http=False) as m:
|
29
|
-
# Map session IDs to their JWTs
|
30
|
-
m.post(
|
31
|
-
endpoint + "/v3/auth/token",
|
32
|
-
json={"token": str(uuid.uuid4()), "project_id": "test-project-id", "api_key": api_key},
|
33
|
-
)
|
34
|
-
yield m
|
35
|
-
|
36
|
-
|
37
|
-
@pytest.fixture
|
38
|
-
def noinstrument():
|
39
|
-
# Tells the client to not instrument LLM calls
|
40
|
-
yield
|
41
|
-
|
42
|
-
|
43
|
-
@pytest.fixture
|
44
|
-
def mock_config(mocker):
|
45
|
-
"""Mock the Client.configure method"""
|
46
|
-
return mocker.patch("agentops.client.Client.configure")
|
47
|
-
|
48
|
-
|
49
|
-
@pytest.fixture
|
50
|
-
def instrumentation():
|
51
|
-
"""Fixture for the instrumentation tester."""
|
52
|
-
tester = InstrumentationTester()
|
53
|
-
yield tester
|
54
|
-
tester.reset()
|
tests/unit/sdk/__init__.py
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
# Test package for the SDK
|
@@ -1,207 +0,0 @@
|
|
1
|
-
from typing import Any, Dict, List, Protocol, Tuple, Union
|
2
|
-
import importlib
|
3
|
-
import unittest.mock as mock
|
4
|
-
|
5
|
-
from opentelemetry import trace as trace_api
|
6
|
-
from opentelemetry.sdk.trace import ReadableSpan, Span, TracerProvider
|
7
|
-
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
|
8
|
-
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
|
9
|
-
from opentelemetry.util.types import Attributes
|
10
|
-
|
11
|
-
from agentops.sdk.core import tracer
|
12
|
-
|
13
|
-
|
14
|
-
def create_tracer_provider(
|
15
|
-
**kwargs,
|
16
|
-
) -> Tuple[TracerProvider, InMemorySpanExporter, SimpleSpanProcessor]:
|
17
|
-
"""Helper to create a configured tracer provider.
|
18
|
-
|
19
|
-
Creates and configures a `TracerProvider` with a
|
20
|
-
`SimpleSpanProcessor` and a `InMemorySpanExporter`.
|
21
|
-
All the parameters passed are forwarded to the TracerProvider
|
22
|
-
constructor.
|
23
|
-
|
24
|
-
Returns:
|
25
|
-
A tuple with the tracer provider, memory exporter, and span processor.
|
26
|
-
"""
|
27
|
-
tracer_provider = TracerProvider(**kwargs)
|
28
|
-
memory_exporter = InMemorySpanExporter()
|
29
|
-
|
30
|
-
# Use SimpleSpanProcessor instead of both processors to avoid duplication
|
31
|
-
span_processor = SimpleSpanProcessor(memory_exporter)
|
32
|
-
tracer_provider.add_span_processor(span_processor)
|
33
|
-
|
34
|
-
return tracer_provider, memory_exporter, span_processor
|
35
|
-
|
36
|
-
|
37
|
-
def reset_trace_globals():
|
38
|
-
"""Reset the global trace state to avoid conflicts."""
|
39
|
-
# Reset tracer provider
|
40
|
-
trace_api._TRACER_PROVIDER = None
|
41
|
-
|
42
|
-
# Reload the trace module to clear warning state
|
43
|
-
importlib.reload(trace_api)
|
44
|
-
|
45
|
-
|
46
|
-
class HasAttributesViaProperty(Protocol):
|
47
|
-
@property
|
48
|
-
def attributes(self) -> Attributes:
|
49
|
-
...
|
50
|
-
|
51
|
-
|
52
|
-
class HasAttributesViaAttr(Protocol):
|
53
|
-
attributes: Attributes
|
54
|
-
|
55
|
-
|
56
|
-
HasAttributes = Union[HasAttributesViaProperty, HasAttributesViaAttr]
|
57
|
-
|
58
|
-
|
59
|
-
class InstrumentationTester:
|
60
|
-
"""
|
61
|
-
A utility class for testing instrumentation in the AgentOps SDK.
|
62
|
-
|
63
|
-
This class provides methods for setting up a test environment with
|
64
|
-
in-memory span exporters, and for asserting properties of spans.
|
65
|
-
"""
|
66
|
-
|
67
|
-
tracer_provider: TracerProvider
|
68
|
-
memory_exporter: InMemorySpanExporter
|
69
|
-
span_processor: SimpleSpanProcessor
|
70
|
-
|
71
|
-
def __init__(self):
|
72
|
-
"""Initialize the instrumentation tester."""
|
73
|
-
# Reset any global state first
|
74
|
-
reset_trace_globals()
|
75
|
-
|
76
|
-
# Shut down any existing tracing core
|
77
|
-
# self._shutdown_core()
|
78
|
-
|
79
|
-
# Create a new tracer provider and memory exporter
|
80
|
-
(
|
81
|
-
self.tracer_provider,
|
82
|
-
self.memory_exporter,
|
83
|
-
self.span_processor,
|
84
|
-
) = create_tracer_provider()
|
85
|
-
|
86
|
-
# Set the tracer provider
|
87
|
-
trace_api.set_tracer_provider(self.tracer_provider)
|
88
|
-
|
89
|
-
# Create a mock for the meter provider
|
90
|
-
self.mock_meter_provider = mock.MagicMock()
|
91
|
-
|
92
|
-
# Patch the setup_telemetry function to return our test providers
|
93
|
-
self.setup_telemetry_patcher = mock.patch(
|
94
|
-
"agentops.sdk.core.setup_telemetry", return_value=(self.tracer_provider, self.mock_meter_provider)
|
95
|
-
)
|
96
|
-
self.mock_setup_telemetry = self.setup_telemetry_patcher.start()
|
97
|
-
|
98
|
-
# Reset the tracing core to force reinitialization
|
99
|
-
core = tracer
|
100
|
-
core._initialized = False
|
101
|
-
core._provider = None
|
102
|
-
|
103
|
-
# Initialize the core, which will now use our mocked setup_telemetry
|
104
|
-
core.initialize()
|
105
|
-
|
106
|
-
self.clear_spans()
|
107
|
-
|
108
|
-
def _shutdown_core(self):
|
109
|
-
"""Safely shut down the tracing core."""
|
110
|
-
try:
|
111
|
-
tracer.shutdown()
|
112
|
-
except Exception as e:
|
113
|
-
print(f"Warning: Error shutting down tracing core: {e}")
|
114
|
-
|
115
|
-
def clear_spans(self):
|
116
|
-
"""Clear all spans from the memory exporter."""
|
117
|
-
# Force flush spans
|
118
|
-
self.span_processor.force_flush()
|
119
|
-
|
120
|
-
# Then clear the memory
|
121
|
-
self.memory_exporter.clear()
|
122
|
-
print("Cleared all spans from memory exporter")
|
123
|
-
|
124
|
-
def reset(self):
|
125
|
-
"""Reset the instrumentation tester."""
|
126
|
-
# Force flush any pending spans
|
127
|
-
self.span_processor.force_flush()
|
128
|
-
|
129
|
-
# Clear any existing spans
|
130
|
-
self.clear_spans()
|
131
|
-
|
132
|
-
# Reset global trace state
|
133
|
-
reset_trace_globals()
|
134
|
-
|
135
|
-
# Set our tracer provider again
|
136
|
-
trace_api.set_tracer_provider(self.tracer_provider)
|
137
|
-
|
138
|
-
# Shut down and re-initialize the tracing core
|
139
|
-
self._shutdown_core()
|
140
|
-
|
141
|
-
# Reset the mock setup_telemetry function
|
142
|
-
self.mock_setup_telemetry.reset_mock()
|
143
|
-
|
144
|
-
# Reset the tracing core to force reinitialization
|
145
|
-
core = tracer
|
146
|
-
core._initialized = False
|
147
|
-
core._provider = None
|
148
|
-
|
149
|
-
# Initialize the core, which will now use our mocked setup_telemetry
|
150
|
-
core.initialize()
|
151
|
-
|
152
|
-
def __del__(self):
|
153
|
-
"""Clean up resources when the tester is garbage collected."""
|
154
|
-
try:
|
155
|
-
# Stop the patcher when the tester is deleted
|
156
|
-
self.setup_telemetry_patcher.stop()
|
157
|
-
except Exception:
|
158
|
-
pass
|
159
|
-
|
160
|
-
def get_finished_spans(self) -> List[ReadableSpan]:
|
161
|
-
"""Get all finished spans."""
|
162
|
-
# Force flush any pending spans
|
163
|
-
self.span_processor.force_flush()
|
164
|
-
|
165
|
-
# Get the spans
|
166
|
-
spans = list(self.memory_exporter.get_finished_spans())
|
167
|
-
print(f"Instrumentation Tester: Found {len(spans)} finished spans")
|
168
|
-
for i, span in enumerate(spans):
|
169
|
-
print(f"Span {i}: name={span.name}, attributes={span.attributes}")
|
170
|
-
return spans
|
171
|
-
|
172
|
-
def get_spans_by_name(self, name: str) -> List[ReadableSpan]:
|
173
|
-
"""Get all spans with the given name."""
|
174
|
-
return [span for span in self.get_finished_spans() if span.name == name]
|
175
|
-
|
176
|
-
def get_spans_by_kind(self, kind: str) -> List[ReadableSpan]:
|
177
|
-
"""Get all spans with the given kind."""
|
178
|
-
return [
|
179
|
-
span for span in self.get_finished_spans() if span.attributes and span.attributes.get("span.kind") == kind
|
180
|
-
]
|
181
|
-
|
182
|
-
@staticmethod
|
183
|
-
def assert_has_attributes(obj: HasAttributes, attributes: Dict[str, Any]):
|
184
|
-
"""Assert that an object has the given attributes."""
|
185
|
-
import json
|
186
|
-
|
187
|
-
assert obj.attributes is not None
|
188
|
-
for key, val in attributes.items():
|
189
|
-
assert key in obj.attributes, f"Key {key!r} not found in attributes"
|
190
|
-
|
191
|
-
actual_val = obj.attributes[key]
|
192
|
-
|
193
|
-
# Try to handle JSON-serialized values
|
194
|
-
if isinstance(actual_val, str) and isinstance(val, (list, dict)):
|
195
|
-
try:
|
196
|
-
actual_val = json.loads(actual_val)
|
197
|
-
except json.JSONDecodeError:
|
198
|
-
pass
|
199
|
-
|
200
|
-
assert actual_val == val, f"Value for key {key!r} does not match: {actual_val} != {val}"
|
201
|
-
|
202
|
-
@staticmethod
|
203
|
-
def assert_span_instrumented_for(span: Union[Span, ReadableSpan], module):
|
204
|
-
"""Assert that a span is instrumented for the given module."""
|
205
|
-
assert span.instrumentation_scope is not None
|
206
|
-
assert span.instrumentation_scope.name == module.__name__
|
207
|
-
assert span.instrumentation_scope.version == module.__version__
|