progress-observability 1.5.1__tar.gz → 1.5.3__tar.gz
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-1.5.1 → progress_observability-1.5.3}/PKG-INFO +6 -3
- {progress_observability-1.5.1 → progress_observability-1.5.3}/README.md +3 -1
- {progress_observability-1.5.1 → progress_observability-1.5.3}/pyproject.toml +3 -2
- {progress_observability-1.5.1 → progress_observability-1.5.3}/src/progress/observability/genkit_span_processor.py +25 -0
- {progress_observability-1.5.1 → progress_observability-1.5.3}/src/progress/observability/sdk.py +125 -8
- {progress_observability-1.5.1 → progress_observability-1.5.3}/uv.lock +4 -2
- {progress_observability-1.5.1 → progress_observability-1.5.3}/.gitignore +0 -0
- {progress_observability-1.5.1 → progress_observability-1.5.3}/.python-version +0 -0
- {progress_observability-1.5.1 → progress_observability-1.5.3}/LICENSE +0 -0
- {progress_observability-1.5.1 → progress_observability-1.5.3}/notices.txt +0 -0
- {progress_observability-1.5.1 → progress_observability-1.5.3}/src/progress/observability/__init__.py +0 -0
- {progress_observability-1.5.1 → progress_observability-1.5.3}/src/progress/observability/constants.py +0 -0
- {progress_observability-1.5.1 → progress_observability-1.5.3}/src/progress/observability/decorators.py +0 -0
- {progress_observability-1.5.1 → progress_observability-1.5.3}/src/progress/observability/enums.py +0 -0
- {progress_observability-1.5.1 → progress_observability-1.5.3}/src/progress/observability/exceptions.py +0 -0
- {progress_observability-1.5.1 → progress_observability-1.5.3}/src/progress/observability/genai_message_processor.py +0 -0
- {progress_observability-1.5.1 → progress_observability-1.5.3}/src/progress/observability/helpers.py +0 -0
- {progress_observability-1.5.1 → progress_observability-1.5.3}/src/progress/observability/model_fix_processor.py +0 -0
- {progress_observability-1.5.1 → progress_observability-1.5.3}/src/progress/observability/tags.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: progress-observability
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.3
|
|
4
4
|
Summary: Progress Observability instrumentation for Python agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/telerik/agentclarity
|
|
6
6
|
Project-URL: Repository, https://github.com/telerik/agentclarity
|
|
@@ -10,7 +10,8 @@ License: MIT
|
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
License-File: notices.txt
|
|
12
12
|
Requires-Python: <4.0.0,>=3.10.0
|
|
13
|
-
Requires-Dist: httpx
|
|
13
|
+
Requires-Dist: httpx==0.28.1
|
|
14
|
+
Requires-Dist: requests==2.34.2
|
|
14
15
|
Requires-Dist: traceloop-sdk==0.62.3
|
|
15
16
|
Description-Content-Type: text/markdown
|
|
16
17
|
|
|
@@ -50,7 +51,7 @@ Common options (for `Observability.instrument`):
|
|
|
50
51
|
- `app_name` - Application name for telemetry
|
|
51
52
|
- `endpoint` - OpenTelemetry collector endpoint
|
|
52
53
|
- `api_key` - Authentication key
|
|
53
|
-
- `debug` -
|
|
54
|
+
- `debug` - Show sanitized OTLP trace-delivery failures on standard error (default: `False`)
|
|
54
55
|
- `instruments` / `block_instruments` - Control what gets traced via `ObservabilityInstruments`
|
|
55
56
|
- `trace_content` - when set to `False` will stop sending LLM prompt/responses as part of the telemetry data
|
|
56
57
|
- `additional_tags` - custom tags (see below)
|
|
@@ -61,8 +62,10 @@ Environment overrides (optional):
|
|
|
61
62
|
- `OBSERVABILITY_ENDPOINT` sets a custom endpoint for collecting data (e.g. "https://collector.observability.progress.com:443")
|
|
62
63
|
- `OBSERVABILITY_API_KEY` sets the authentication key to use when sending data
|
|
63
64
|
- `OBSERVABILITY_TRACE_CONTENT` when set to `false` will stop sending LLM prompt/responses as part of the telemetry data
|
|
65
|
+
- `OBSERVABILITY_DEBUG` enables sanitized trace-delivery errors when set to `1`, `true`, or `yes`
|
|
64
66
|
|
|
65
67
|
Auth headers are added automatically for HTTP(S) endpoints when api_key is provided.
|
|
68
|
+
Invalid endpoints and missing or empty API keys raise during initialization. Delivery failures do not terminate the application and remain silent unless debug logging is enabled. Debug messages never include API keys, authorization headers, or telemetry payloads.
|
|
66
69
|
|
|
67
70
|
## Custom Tags
|
|
68
71
|
|
|
@@ -34,7 +34,7 @@ Common options (for `Observability.instrument`):
|
|
|
34
34
|
- `app_name` - Application name for telemetry
|
|
35
35
|
- `endpoint` - OpenTelemetry collector endpoint
|
|
36
36
|
- `api_key` - Authentication key
|
|
37
|
-
- `debug` -
|
|
37
|
+
- `debug` - Show sanitized OTLP trace-delivery failures on standard error (default: `False`)
|
|
38
38
|
- `instruments` / `block_instruments` - Control what gets traced via `ObservabilityInstruments`
|
|
39
39
|
- `trace_content` - when set to `False` will stop sending LLM prompt/responses as part of the telemetry data
|
|
40
40
|
- `additional_tags` - custom tags (see below)
|
|
@@ -45,8 +45,10 @@ Environment overrides (optional):
|
|
|
45
45
|
- `OBSERVABILITY_ENDPOINT` sets a custom endpoint for collecting data (e.g. "https://collector.observability.progress.com:443")
|
|
46
46
|
- `OBSERVABILITY_API_KEY` sets the authentication key to use when sending data
|
|
47
47
|
- `OBSERVABILITY_TRACE_CONTENT` when set to `false` will stop sending LLM prompt/responses as part of the telemetry data
|
|
48
|
+
- `OBSERVABILITY_DEBUG` enables sanitized trace-delivery errors when set to `1`, `true`, or `yes`
|
|
48
49
|
|
|
49
50
|
Auth headers are added automatically for HTTP(S) endpoints when api_key is provided.
|
|
51
|
+
Invalid endpoints and missing or empty API keys raise during initialization. Delivery failures do not terminate the application and remain silent unless debug logging is enabled. Debug messages never include API keys, authorization headers, or telemetry payloads.
|
|
50
52
|
|
|
51
53
|
## Custom Tags
|
|
52
54
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "progress-observability"
|
|
7
|
-
version = "1.5.
|
|
7
|
+
version = "1.5.3"
|
|
8
8
|
description = "Progress Observability instrumentation for Python agents"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -12,7 +12,8 @@ license-files = ["LICENSE", "notices.txt"]
|
|
|
12
12
|
requires-python = ">=3.10.0,<4.0.0"
|
|
13
13
|
dependencies = [
|
|
14
14
|
"traceloop-sdk==0.62.3",
|
|
15
|
-
"httpx
|
|
15
|
+
"httpx==0.28.1",
|
|
16
|
+
"requests==2.34.2",
|
|
16
17
|
]
|
|
17
18
|
[project.urls]
|
|
18
19
|
Homepage = "https://github.com/telerik/agentclarity"
|
|
@@ -122,6 +122,31 @@ class GenkitSpanProcessor:
|
|
|
122
122
|
attributes.setdefault("gen_ai.tool.call.result", tool_output)
|
|
123
123
|
return
|
|
124
124
|
if subtype != "model":
|
|
125
|
+
# The root span of ai.generate()/ai.generate_stream() calls made outside of an
|
|
126
|
+
# explicit flow isn't tagged with a subtype (it's registered as a "util"
|
|
127
|
+
# action), so it falls through every branch above and is left without a span
|
|
128
|
+
# kind or provider. Since genkit:input carries the requested model name, derive
|
|
129
|
+
# both from it the same way the model span does. Genkit's built-in generate
|
|
130
|
+
# action always reports subtype "util" (or omits it for nested turns); guard
|
|
131
|
+
# on that so a user-defined action merely named "generate" with a different
|
|
132
|
+
# subtype (e.g. "prompt") isn't misclassified.
|
|
133
|
+
if attributes.get("genkit:name") == "generate" and subtype in (None, "util"):
|
|
134
|
+
attributes.setdefault("traceloop.span.kind", "agent")
|
|
135
|
+
input_data = _parse_json(attributes.get("genkit:input"))
|
|
136
|
+
model_name = input_data.get("model") if input_data else None
|
|
137
|
+
if isinstance(model_name, str) and model_name:
|
|
138
|
+
separator = model_name.find("/")
|
|
139
|
+
if separator >= 0:
|
|
140
|
+
plugin = model_name[:separator].lower()
|
|
141
|
+
model = model_name[separator + 1 :]
|
|
142
|
+
if "gen_ai.provider.name" not in attributes and plugin:
|
|
143
|
+
attributes["gen_ai.provider.name"] = _PROVIDERS.get(
|
|
144
|
+
plugin, plugin[:1].upper() + plugin[1:]
|
|
145
|
+
)
|
|
146
|
+
else:
|
|
147
|
+
model = model_name
|
|
148
|
+
attributes.setdefault("gen_ai.request.model", model)
|
|
149
|
+
attributes.setdefault("gen_ai.response.model", model)
|
|
125
150
|
return
|
|
126
151
|
|
|
127
152
|
name = attributes.get("genkit:name")
|
{progress_observability-1.5.1 → progress_observability-1.5.3}/src/progress/observability/sdk.py
RENAMED
|
@@ -19,6 +19,7 @@ Quick Start:
|
|
|
19
19
|
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
+
import re
|
|
22
23
|
import sys
|
|
23
24
|
import os
|
|
24
25
|
from contextlib import redirect_stdout, redirect_stderr
|
|
@@ -63,6 +64,88 @@ class Observability:
|
|
|
63
64
|
_tags_processor = None # TagsSpanProcessor instance for tag propagation
|
|
64
65
|
SDK = Traceloop
|
|
65
66
|
|
|
67
|
+
_EXPORTER_LOGGER_NAME = "opentelemetry.exporter.otlp.proto.http.trace_exporter"
|
|
68
|
+
_EXPORTER_HANDLER_MARKER = "_progress_observability_handler"
|
|
69
|
+
_EXPORTER_FILTER_MARKER = "_progress_observability_filter"
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def _sanitize_exporter_message(message: str) -> str:
|
|
73
|
+
"""Map exporter failures to useful messages without exposing request data."""
|
|
74
|
+
normalized = message.lower()
|
|
75
|
+
|
|
76
|
+
status_match = re.search(r"\b(401|403)\b", normalized)
|
|
77
|
+
if status_match:
|
|
78
|
+
status_messages = {
|
|
79
|
+
"401": "HTTP 401 Unauthorized.",
|
|
80
|
+
"403": "HTTP 403 Forbidden.",
|
|
81
|
+
}
|
|
82
|
+
return status_messages[status_match.group(1)]
|
|
83
|
+
if "timed out" in normalized or "timeout" in normalized:
|
|
84
|
+
return "request timed out."
|
|
85
|
+
if any(
|
|
86
|
+
term in normalized for term in ("name resolution", "resolve host", "dns")
|
|
87
|
+
):
|
|
88
|
+
return "collector host could not be resolved."
|
|
89
|
+
if any(
|
|
90
|
+
term in normalized
|
|
91
|
+
for term in (
|
|
92
|
+
"connection refused",
|
|
93
|
+
"unreachable",
|
|
94
|
+
"failed to establish a new connection",
|
|
95
|
+
)
|
|
96
|
+
):
|
|
97
|
+
return "collector is unreachable."
|
|
98
|
+
return "failed to export traces."
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def _configure_exporter_logging(cls, debug_enabled: bool) -> None:
|
|
102
|
+
exporter_logger = logging.getLogger(cls._EXPORTER_LOGGER_NAME)
|
|
103
|
+
|
|
104
|
+
for handler in list(exporter_logger.handlers):
|
|
105
|
+
if getattr(handler, cls._EXPORTER_HANDLER_MARKER, False):
|
|
106
|
+
exporter_logger.removeHandler(handler)
|
|
107
|
+
handler.close()
|
|
108
|
+
for logger_filter in list(exporter_logger.filters):
|
|
109
|
+
if getattr(logger_filter, cls._EXPORTER_FILTER_MARKER, False):
|
|
110
|
+
exporter_logger.removeFilter(logger_filter)
|
|
111
|
+
|
|
112
|
+
exporter_logger.propagate = False
|
|
113
|
+
exporter_logger.disabled = not debug_enabled
|
|
114
|
+
|
|
115
|
+
if not debug_enabled:
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
class SanitizingFilter(logging.Filter):
|
|
119
|
+
_progress_observability_filter = True
|
|
120
|
+
|
|
121
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
122
|
+
record.msg = cls._sanitize_exporter_message(record.getMessage())
|
|
123
|
+
record.args = ()
|
|
124
|
+
record.exc_info = None
|
|
125
|
+
record.exc_text = None
|
|
126
|
+
record.stack_info = None
|
|
127
|
+
return True
|
|
128
|
+
|
|
129
|
+
exporter_logger.setLevel(logging.ERROR)
|
|
130
|
+
handler = logging.StreamHandler(sys.stderr)
|
|
131
|
+
handler.setLevel(logging.ERROR)
|
|
132
|
+
handler.setFormatter(
|
|
133
|
+
logging.Formatter(
|
|
134
|
+
"[Observability] Failed to export traces: %(message)s"
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
setattr(handler, cls._EXPORTER_HANDLER_MARKER, True)
|
|
138
|
+
exporter_logger.addFilter(SanitizingFilter())
|
|
139
|
+
exporter_logger.addHandler(handler)
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def _report_configuration_failure(debug_enabled: bool, message: str) -> None:
|
|
143
|
+
if debug_enabled:
|
|
144
|
+
print(
|
|
145
|
+
f"[Observability] Configuration error: {message}",
|
|
146
|
+
file=sys.stderr,
|
|
147
|
+
)
|
|
148
|
+
|
|
66
149
|
@staticmethod
|
|
67
150
|
def _validate_endpoint(endpoint: Optional[str]) -> str:
|
|
68
151
|
"""
|
|
@@ -131,7 +214,7 @@ class Observability:
|
|
|
131
214
|
return endpoint
|
|
132
215
|
|
|
133
216
|
@staticmethod
|
|
134
|
-
def _validate_api_key(api_key: Optional[str]) ->
|
|
217
|
+
def _validate_api_key(api_key: Optional[str]) -> str:
|
|
135
218
|
"""Validate the API key is provided via parameter or environment variable.
|
|
136
219
|
Args:
|
|
137
220
|
api_key: The API key parameter value to validate
|
|
@@ -258,17 +341,49 @@ class Observability:
|
|
|
258
341
|
# Always allow re-initialization to support different instrument configurations
|
|
259
342
|
# The underlying traceloop SDK will handle duplicate initialization safely
|
|
260
343
|
|
|
261
|
-
# Validate and resolve endpoint and API key before processing
|
|
262
|
-
endpoint = cls._validate_endpoint(endpoint)
|
|
263
|
-
api_key = cls._validate_api_key(api_key)
|
|
264
|
-
|
|
265
|
-
app_name = init_environment(app_name, trace_content)
|
|
266
|
-
|
|
267
344
|
# Ensure debug logs are visible when explicitly requested.
|
|
268
345
|
# Honor both the debug parameter and the OBSERVABILITY_DEBUG env var.
|
|
269
346
|
# Only raise the progress.observability logger - not the root logger,
|
|
270
347
|
# which would make every third-party library verbose.
|
|
271
|
-
debug_enabled = debug or os.getenv("OBSERVABILITY_DEBUG", "").lower() in (
|
|
348
|
+
debug_enabled = debug or os.getenv("OBSERVABILITY_DEBUG", "").lower() in (
|
|
349
|
+
"1",
|
|
350
|
+
"true",
|
|
351
|
+
"yes",
|
|
352
|
+
)
|
|
353
|
+
cls._configure_exporter_logging(debug_enabled)
|
|
354
|
+
|
|
355
|
+
endpoint_error: Optional[Exception] = None
|
|
356
|
+
api_key_error: Optional[Exception] = None
|
|
357
|
+
|
|
358
|
+
try:
|
|
359
|
+
endpoint = cls._validate_endpoint(endpoint)
|
|
360
|
+
except EndpointValidationError as exc:
|
|
361
|
+
endpoint_error = exc
|
|
362
|
+
cls._report_configuration_failure(
|
|
363
|
+
debug_enabled,
|
|
364
|
+
"endpoint is invalid. Use a valid http/https URL with host and port.",
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
try:
|
|
368
|
+
api_key = cls._validate_api_key(api_key)
|
|
369
|
+
except (TypeError, ValueError) as exc:
|
|
370
|
+
api_key_error = exc
|
|
371
|
+
cls._report_configuration_failure(
|
|
372
|
+
debug_enabled,
|
|
373
|
+
"API key is missing or empty.",
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
if endpoint_error and api_key_error:
|
|
377
|
+
raise ValueError(
|
|
378
|
+
"Invalid configuration: endpoint is invalid and API key is missing or empty."
|
|
379
|
+
) from api_key_error
|
|
380
|
+
if endpoint_error:
|
|
381
|
+
raise endpoint_error
|
|
382
|
+
if api_key_error:
|
|
383
|
+
raise api_key_error
|
|
384
|
+
|
|
385
|
+
app_name = init_environment(app_name, trace_content)
|
|
386
|
+
|
|
272
387
|
if debug_enabled:
|
|
273
388
|
pkg_logger = logging.getLogger("progress.observability")
|
|
274
389
|
pkg_logger.setLevel(logging.DEBUG)
|
|
@@ -380,6 +495,8 @@ class Observability:
|
|
|
380
495
|
After shutdown, subsequent calls to instrument() will reinitialize
|
|
381
496
|
the instrumentation.
|
|
382
497
|
"""
|
|
498
|
+
cls._configure_exporter_logging(False)
|
|
499
|
+
|
|
383
500
|
if not cls._initialized:
|
|
384
501
|
return True
|
|
385
502
|
|
|
@@ -1544,16 +1544,18 @@ wheels = [
|
|
|
1544
1544
|
|
|
1545
1545
|
[[package]]
|
|
1546
1546
|
name = "progress-observability"
|
|
1547
|
-
version = "1.5.
|
|
1547
|
+
version = "1.5.3"
|
|
1548
1548
|
source = { editable = "." }
|
|
1549
1549
|
dependencies = [
|
|
1550
1550
|
{ name = "httpx" },
|
|
1551
|
+
{ name = "requests" },
|
|
1551
1552
|
{ name = "traceloop-sdk" },
|
|
1552
1553
|
]
|
|
1553
1554
|
|
|
1554
1555
|
[package.metadata]
|
|
1555
1556
|
requires-dist = [
|
|
1556
|
-
{ name = "httpx", specifier = "
|
|
1557
|
+
{ name = "httpx", specifier = "==0.28.1" },
|
|
1558
|
+
{ name = "requests", specifier = "==2.34.2" },
|
|
1557
1559
|
{ name = "traceloop-sdk", specifier = "==0.62.3" },
|
|
1558
1560
|
]
|
|
1559
1561
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{progress_observability-1.5.1 → progress_observability-1.5.3}/src/progress/observability/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{progress_observability-1.5.1 → progress_observability-1.5.3}/src/progress/observability/enums.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{progress_observability-1.5.1 → progress_observability-1.5.3}/src/progress/observability/helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
{progress_observability-1.5.1 → progress_observability-1.5.3}/src/progress/observability/tags.py
RENAMED
|
File without changes
|