progress-observability 1.1.4__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.
@@ -0,0 +1,296 @@
1
+ """
2
+ Progress Observability - Zero-intrusion AI agent telemetry
3
+
4
+ Provides granular control over AI agent tracing with zero code changes required
5
+ to existing agent implementations. Simply add one line at the beginning of your
6
+ agent code to enable comprehensive telemetry.
7
+
8
+ Quick Start:
9
+
10
+ from progress.observability.instrumentation import Observability
11
+
12
+ # Basic initialization
13
+ Observability.instrument(
14
+ app_name="my-agent-app",
15
+ endpoint='https://collector.observability.progress.com:443',
16
+ api_key="<YOUR_API_KEY>",
17
+ )
18
+
19
+
20
+ """
21
+
22
+ import sys
23
+ from contextlib import redirect_stdout, redirect_stderr
24
+ from io import StringIO
25
+ from typing import Any, Dict, Optional, Set
26
+ from urllib.parse import urlparse
27
+
28
+ from .enums import ObservabilityInstruments
29
+ from .helpers import init_environment, setup_api_key_headers, patch_traceloop_modules, clear_sdk_env_vars
30
+ from .model_fix_processor import ModelFixProcessor
31
+ from .exceptions import (
32
+ EndpointValidationError,
33
+ InvalidPortError,
34
+ MissingHostError,
35
+ MissingPortError,
36
+ NonNumericPortError,
37
+ UnsupportedSchemeError,
38
+ InvalidHostError,
39
+ )
40
+
41
+ patch_traceloop_modules()
42
+ clear_sdk_env_vars()
43
+
44
+ from traceloop.sdk import Traceloop
45
+
46
+
47
+ class Observability:
48
+ """
49
+ Progress Observability - Zero-intrusion AI agent telemetry
50
+
51
+ Provides granular control over AI agent tracing with zero code changes required
52
+ to existing agent implementations. Simply add one line at the beginning of your
53
+ agent code to enable comprehensive telemetry.
54
+ """
55
+
56
+ _initialized = False
57
+ _model_fix_processor = None # Store for debugging access
58
+ SDK = Traceloop
59
+
60
+ @staticmethod
61
+ def _validate_endpoint(endpoint: Optional[str]) -> None:
62
+ """
63
+ Validate the endpoint URL format and components.
64
+
65
+ Args:
66
+ endpoint: The endpoint URL to validate
67
+
68
+ Raises:
69
+ EndpointValidationError: If the endpoint is invalid
70
+ """
71
+ if endpoint is None or endpoint == "":
72
+ # Empty string and None are allowed (will use defaults)
73
+ return
74
+
75
+ try:
76
+ parsed = urlparse(endpoint)
77
+ except Exception as e:
78
+ raise EndpointValidationError(f"Failed to parse endpoint '{endpoint}': {e}")
79
+
80
+ # Check scheme
81
+ if parsed.scheme not in ("http", "https"):
82
+ raise UnsupportedSchemeError(parsed.scheme or "missing")
83
+
84
+ # Check host
85
+ if not parsed.hostname:
86
+ raise MissingHostError(endpoint)
87
+
88
+ # Check for spaces in hostname
89
+ if " " in parsed.hostname:
90
+ raise InvalidHostError(parsed.hostname)
91
+
92
+ # Check port
93
+ try:
94
+ port = parsed.port
95
+ except ValueError as ve:
96
+ # Handle port out of range or non-numeric port
97
+ msg = str(ve)
98
+ if "out of range" in msg:
99
+ # Extract port from netloc
100
+ port_part = parsed.netloc.split(":")[-1]
101
+ raise InvalidPortError(port_part)
102
+ else:
103
+ port_part = parsed.netloc.split(":")[-1]
104
+ raise NonNumericPortError(port_part)
105
+
106
+ if port is None:
107
+ # Check if there's a colon without a port number
108
+ if ":" in parsed.netloc and not parsed.netloc.endswith(":"):
109
+ port_part = parsed.netloc.split(":")[-1]
110
+ if port_part and not port_part.isdigit():
111
+ raise NonNumericPortError(port_part)
112
+ elif parsed.netloc.endswith(":"):
113
+ raise MissingPortError(endpoint)
114
+ else:
115
+ if port < 1 or port > 65535:
116
+ raise InvalidPortError(str(port))
117
+
118
+ @staticmethod
119
+ def _build_init_kwargs(
120
+ *,
121
+ app_name: Optional[str],
122
+ api_key: Optional[str],
123
+ endpoint: Optional[str],
124
+ instruments: Optional[Set[ObservabilityInstruments]],
125
+ block_instruments: Optional[Set[ObservabilityInstruments]],
126
+ disable_batch: bool,
127
+ resource_attributes: Optional[Dict[str, Any]],
128
+ extra_kwargs: Dict[str, Any],
129
+ ) -> Dict[str, Any]:
130
+ """Build keyword arguments for the underlying SDK.init call.
131
+
132
+ Converts Observability enums to Traceloop values, applies batching and
133
+ resource attributes, configures API key headers when provided, and
134
+ merges any additional kwargs.
135
+
136
+ Args:
137
+ app_name: Application name for telemetry identification
138
+ api_key: Optional API key for authentication
139
+ endpoint: Optional collector endpoint URL
140
+ instruments: Optional set of instruments to enable
141
+ block_instruments: Optional set of instruments to block
142
+ disable_batch: Whether to disable batching (default True)
143
+ resource_attributes: Optional additional resource attributes
144
+ extra_kwargs: Additional kwargs from caller (highest precedence)
145
+
146
+ Returns:
147
+ Dictionary of keyword arguments ready for SDK.init()
148
+ """
149
+
150
+ # Convert ObservabilityInstruments to Traceloop instruments
151
+ traceloop_instruments = (
152
+ {instrument.value for instrument in instruments}
153
+ if instruments
154
+ else None
155
+ )
156
+ traceloop_block_instruments = (
157
+ {instrument.value for instrument in block_instruments}
158
+ if block_instruments
159
+ else None
160
+ )
161
+
162
+ init_kwargs: Dict[str, Any] = {
163
+ "app_name": app_name,
164
+ "disable_batch": disable_batch,
165
+ "telemetry_enabled": False
166
+ }
167
+
168
+ if api_key:
169
+ init_kwargs["api_key"] = api_key
170
+ if endpoint:
171
+ init_kwargs["api_endpoint"] = endpoint
172
+
173
+ if traceloop_instruments:
174
+ init_kwargs["instruments"] = traceloop_instruments
175
+ if traceloop_block_instruments:
176
+ init_kwargs["block_instruments"] = traceloop_block_instruments
177
+
178
+ if api_key:
179
+ # configure headers for exporters if api_key is provided
180
+ setup_api_key_headers(api_key, endpoint, init_kwargs, extra_kwargs)
181
+
182
+ if resource_attributes:
183
+ init_kwargs["resource_attributes"] = dict(resource_attributes)
184
+
185
+ # Merge additional kwargs last so callers can override defaults
186
+ init_kwargs.update(extra_kwargs)
187
+ return init_kwargs
188
+
189
+ @classmethod
190
+ def instrument(
191
+ cls,
192
+ app_name: str = sys.argv[0],
193
+ endpoint: Optional[str] = None,
194
+ api_key: Optional[str] = None,
195
+ instruments: Optional[Set[ObservabilityInstruments]] = None,
196
+ block_instruments: Optional[Set[ObservabilityInstruments]] = None,
197
+ disable_batch: bool = True,
198
+ trace_content: Optional[bool] = None,
199
+ resource_attributes: Optional[Dict[str, Any]] = None,
200
+ debug: bool = False,
201
+ **kwargs: Any,
202
+ ) -> None:
203
+ """
204
+ Initialize Observability with granular control over what gets traced.
205
+
206
+ Args:
207
+ app_name: Application name for telemetry identification
208
+ endpoint: Collector endpoint URL
209
+ api_key: Collector API key for authentication
210
+ instruments: Set of ObservabilityInstruments to enable for tracing
211
+ block_instruments: Set of ObservabilityInstruments to exclude from tracing
212
+ disable_batch: Send traces immediately vs batching
213
+ trace_content: Whether to log prompts/completions (default True, can also use OBSERVABILITY_TRACE_CONTENT env var)
214
+ resource_attributes: Additional resource attributes for traces
215
+ debug: Enable verbose debugging output (can also use OBSERVABILITY_DEBUG env var)
216
+ **kwargs: Additional parameters passed to underlying SDK
217
+ """
218
+
219
+ # Always allow re-initialization to support different instrument configurations
220
+ # The underlying traceloop SDK will handle duplicate initialization safely
221
+
222
+ # Validate endpoint before processing
223
+ cls._validate_endpoint(endpoint)
224
+
225
+ app_name, endpoint, api_key = init_environment(app_name, endpoint, api_key, trace_content)
226
+
227
+ # Create the model fix processor with debug mode
228
+ model_fix_processor = ModelFixProcessor(debug=debug)
229
+ cls._model_fix_processor = model_fix_processor
230
+
231
+ # Just pass the method directly - you can set breakpoints inside on_end()
232
+ if 'span_postprocess_callback' not in kwargs:
233
+ kwargs['span_postprocess_callback'] = model_fix_processor.on_end
234
+
235
+ init_kwargs = cls._build_init_kwargs(
236
+ app_name=app_name,
237
+ api_key=api_key,
238
+ endpoint=endpoint,
239
+ instruments=instruments,
240
+ block_instruments=block_instruments,
241
+ disable_batch=disable_batch,
242
+ resource_attributes=resource_attributes,
243
+ extra_kwargs=kwargs,
244
+ )
245
+
246
+ # Suppress SDK init output to keep user console clean
247
+ with redirect_stdout(StringIO()), redirect_stderr(StringIO()):
248
+ cls.SDK.init(**init_kwargs)
249
+
250
+ cls._initialized = True
251
+
252
+ @classmethod
253
+ def shutdown(cls, timeout_millis: int = 30000) -> bool:
254
+ """
255
+ Shutdown Progress Observability instrumentation and clean up resources.
256
+
257
+ This method performs a shutdown of the tracing infrastructure:
258
+ - Shuts down the TracerProvider and all associated span processors
259
+ - Ensures all pending spans are exported before shutdown
260
+ - Prevents further telemetry collection
261
+ - Resets initialization state
262
+
263
+ Args:
264
+ timeout_millis: Maximum time to wait for shutdown completion in milliseconds.
265
+ Defaults to 30000ms (30 seconds) as per OpenTelemetry spec.
266
+
267
+ Returns:
268
+ bool: True if shutdown completed successfully within timeout,
269
+ False if shutdown failed or timed out.
270
+
271
+ Note:
272
+ This method should be called only once per Observability instance.
273
+ After shutdown, subsequent calls to instrument() will reinitialize
274
+ the instrumentation.
275
+ """
276
+ if not cls._initialized:
277
+ return True
278
+
279
+ try:
280
+ from opentelemetry import trace
281
+ from opentelemetry.sdk.trace import TracerProvider
282
+
283
+ tracer_provider = trace.get_tracer_provider()
284
+
285
+ if isinstance(tracer_provider, TracerProvider):
286
+ tracer_provider.shutdown()
287
+ cls._initialized = False
288
+ return True
289
+ else:
290
+ cls._initialized = False
291
+ return True
292
+ except Exception as e:
293
+ print(f"Error during Observability shutdown: {e}", file=sys.stderr)
294
+ cls._initialized = False
295
+ return False
296
+
@@ -0,0 +1,68 @@
1
+ Metadata-Version: 2.4
2
+ Name: progress-observability
3
+ Version: 1.1.4
4
+ Summary: Progress Observability instrumentation for Python agents
5
+ Project-URL: Homepage, https://github.com/telerik/agentclarity
6
+ Project-URL: Repository, https://github.com/telerik/agentclarity
7
+ Project-URL: Documentation, https://docs.telerik.com/agentclarity
8
+ Project-URL: Bug Reports, https://github.com/telerik/agentclarity/issues
9
+ License: MIT
10
+ License-File: LICENSE
11
+ License-File: notices.txt
12
+ Requires-Python: <4.0.0,>=3.10.0
13
+ Requires-Dist: traceloop-sdk==0.49.5
14
+ Provides-Extra: dev
15
+ Requires-Dist: autopep8>=2.3.1; extra == 'dev'
16
+ Requires-Dist: flake8>=7.1.0; extra == 'dev'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # Progress Observability Instrumentation (Python)
20
+
21
+ Zero-intrusion telemetry for AI agents and LLM apps. Built on Traceloop SDK and OpenTelemetry with a simple one-line init and optional decorators.
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ pip install progress-observability
27
+ ```
28
+
29
+ Or from wheel file:
30
+
31
+ ```bash
32
+ pip install progress_observability-x.y.z-py3-none-any.whl
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ```python
38
+ from progress.observability import Observability, ObservabilityInstruments
39
+
40
+ # Initialize once at process start
41
+ Observability.instrument(
42
+ app_name="my-app",
43
+ api_key="<your-api-key>",
44
+ # endpoint="https://collector.observability.progress.com:443" # Optional: has default
45
+ )
46
+ ```
47
+
48
+ ## Configuration
49
+
50
+ Environment overrides (optional):
51
+
52
+ - `OBSERVABILITY_APP_NAME`
53
+ - `OBSERVABILITY_ENDPOINT`
54
+ - `OBSERVABILITY_API_KEY`
55
+
56
+ Auth headers are added automatically for HTTP(S) endpoints when api_key is provided.
57
+
58
+ ## Package Structure
59
+
60
+ ```text
61
+ src/progress/observability/
62
+ ├── __init__.py # Package entry point
63
+ ├── sdk.py # Main Observability SDK
64
+ ├── decorators.py # @task, @workflow, @agent, @tool decorators
65
+ ├── constants.py # Environment variables and constants
66
+ ├── enums.py # ObservabilityInstruments enum
67
+ └── helpers.py # Helper functions
68
+ ```
@@ -0,0 +1,13 @@
1
+ progress/observability/__init__.py,sha256=bAO7njeq-xtQmt9RhLGUqSYvP7E3b7dLADodyhJIKEI,548
2
+ progress/observability/constants.py,sha256=TBndnOlR7BPMRdKLowVUozncXvyJ08zUZjhoD4N_l88,825
3
+ progress/observability/decorators.py,sha256=ImshbMQ1R9sjITTw2JvkE9MFWA-EmaJFImegUQUhmVA,8422
4
+ progress/observability/enums.py,sha256=0rFPZp2hU0HMQrEF_6jMtpAH1YYdT-8pQ_igozCJvx0,1563
5
+ progress/observability/exceptions.py,sha256=97GkeIhC1U2yE43QgRD5pSY3sGNCZvTCYQNTJNJjKeE,2358
6
+ progress/observability/helpers.py,sha256=Iskmj5xxBsnyAP9jDkq3n5AjL1-zQrRb40cNQx0lqFE,5275
7
+ progress/observability/model_fix_processor.py,sha256=xbvxlnCk5P-6L1uObafFMU5J57-9FSvspKaOLzJSAL8,38248
8
+ progress/observability/sdk.py,sha256=zf71hUUkDJOUrafSuXZ_aKNmTmLEyZ2H9KZYMXKdkVI,10876
9
+ progress_observability-1.1.4.dist-info/METADATA,sha256=INFTAWBHWQ6vBBMZNM4jf0FCG_p54y3KcVC0owfmpL8,1990
10
+ progress_observability-1.1.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
11
+ progress_observability-1.1.4.dist-info/licenses/LICENSE,sha256=z0eiep_n9zMQKnqS8v3WlGZnYdMAMjV0PHSEwluAM9o,1081
12
+ progress_observability-1.1.4.dist-info/licenses/notices.txt,sha256=YAMFoLYOZuPhLkcgl4E_o42Iwy3wpm8d15jcZbGuCuE,72182
13
+ progress_observability-1.1.4.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2025 Progress Software Corporation
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.