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.
- progress/observability/__init__.py +22 -0
- progress/observability/constants.py +31 -0
- progress/observability/decorators.py +288 -0
- progress/observability/enums.py +51 -0
- progress/observability/exceptions.py +61 -0
- progress/observability/helpers.py +143 -0
- progress/observability/model_fix_processor.py +804 -0
- progress/observability/sdk.py +296 -0
- progress_observability-1.1.4.dist-info/METADATA +68 -0
- progress_observability-1.1.4.dist-info/RECORD +13 -0
- progress_observability-1.1.4.dist-info/WHEEL +4 -0
- progress_observability-1.1.4.dist-info/licenses/LICENSE +7 -0
- progress_observability-1.1.4.dist-info/licenses/notices.txt +1424 -0
|
@@ -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,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.
|