arize-phoenix 5.1.0__py3-none-any.whl → 5.1.2__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.
Potentially problematic release.
This version of arize-phoenix might be problematic. Click here for more details.
- {arize_phoenix-5.1.0.dist-info → arize_phoenix-5.1.2.dist-info}/METADATA +4 -3
- {arize_phoenix-5.1.0.dist-info → arize_phoenix-5.1.2.dist-info}/RECORD +10 -13
- phoenix/pointcloud/clustering.py +2 -1
- phoenix/pointcloud/projectors.py +5 -6
- phoenix/server/api/types/EmbeddingDimension.py +10 -10
- phoenix/trace/fixtures.py +4 -4
- phoenix/version.py +1 -1
- phoenix/otel/__init__.py +0 -22
- phoenix/otel/otel.py +0 -451
- phoenix/otel/settings.py +0 -91
- {arize_phoenix-5.1.0.dist-info → arize_phoenix-5.1.2.dist-info}/WHEEL +0 -0
- {arize_phoenix-5.1.0.dist-info → arize_phoenix-5.1.2.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-5.1.0.dist-info → arize_phoenix-5.1.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: arize-phoenix
|
|
3
|
-
Version: 5.1.
|
|
3
|
+
Version: 5.1.2
|
|
4
4
|
Summary: AI Observability and Evaluation
|
|
5
5
|
Project-URL: Documentation, https://docs.arize.com/phoenix/
|
|
6
6
|
Project-URL: Issues, https://github.com/Arize-ai/phoenix/issues
|
|
@@ -27,10 +27,11 @@ Requires-Dist: fastapi
|
|
|
27
27
|
Requires-Dist: fastapi-mail
|
|
28
28
|
Requires-Dist: grpc-interceptor
|
|
29
29
|
Requires-Dist: grpcio
|
|
30
|
-
Requires-Dist: hdbscan>=0.8.
|
|
30
|
+
Requires-Dist: hdbscan>=0.8.38
|
|
31
31
|
Requires-Dist: httpx
|
|
32
32
|
Requires-Dist: jinja2
|
|
33
|
-
Requires-Dist:
|
|
33
|
+
Requires-Dist: numba>=0.60.0
|
|
34
|
+
Requires-Dist: numpy!=2.0.0
|
|
34
35
|
Requires-Dist: openinference-instrumentation>=0.1.12
|
|
35
36
|
Requires-Dist: openinference-semantic-conventions>=0.1.9
|
|
36
37
|
Requires-Dist: opentelemetry-exporter-otlp
|
|
@@ -6,7 +6,7 @@ phoenix/exceptions.py,sha256=n2L2KKuecrdflB9MsCdAYCiSEvGJptIsfRkXMoJle7A,169
|
|
|
6
6
|
phoenix/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
7
7
|
phoenix/services.py,sha256=aTxhcOA1pZHB6U-B3TEcp6fqDF5oT0xCUvEUNMZVTUQ,5175
|
|
8
8
|
phoenix/settings.py,sha256=ht-0oN-sMV6SPXrk7Tu1EZlngpAYkGNLYPhO8DyrdQI,661
|
|
9
|
-
phoenix/version.py,sha256=
|
|
9
|
+
phoenix/version.py,sha256=UBCMYF3p9TNaBbKwlj4g040bBVzeptAOIiaN6Za44mY,22
|
|
10
10
|
phoenix/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
phoenix/core/embedding_dimension.py,sha256=zKGbcvwOXgLf-yrJBpQyKtd-LEOPRKHnUToyAU8Owis,87
|
|
12
12
|
phoenix/core/model.py,sha256=km_a--PBHOuA337ClRw9xqhOHhrUT6Rl9pz_zV0JYkQ,4843
|
|
@@ -67,13 +67,10 @@ phoenix/metrics/mixins.py,sha256=moZ5hENIKzUQt2IRhWOd5EFXnoqQkVrpqEqMH7KQzyA,744
|
|
|
67
67
|
phoenix/metrics/retrieval_metrics.py,sha256=XFQPo66h16w7-1AJ92M1VL_BUIXIWxXHGKF_QVOABZI,4384
|
|
68
68
|
phoenix/metrics/timeseries.py,sha256=Cib3E0njJzi0vZpmyADvbakFQA98rIkfDaYAOmsmBz8,6277
|
|
69
69
|
phoenix/metrics/wrappers.py,sha256=umZqa_5lf1wZSFe3FgzxF-qp1xbPdKD54W628GlGCUI,8392
|
|
70
|
-
phoenix/otel/__init__.py,sha256=YvEiD-3aGZs9agwLNCXU34ofV3G-Q-dolfsiinOJuT0,407
|
|
71
|
-
phoenix/otel/otel.py,sha256=Pe_M5eIKN3CQT6JS9FE6CnmWeiP1kG7zxkxzUgVJ_rE,19248
|
|
72
|
-
phoenix/otel/settings.py,sha256=JYGUeV87NFnhJbp34AjoNiSejn3QYMSdPIx4NpA97tM,3272
|
|
73
70
|
phoenix/pointcloud/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
74
|
-
phoenix/pointcloud/clustering.py,sha256=
|
|
71
|
+
phoenix/pointcloud/clustering.py,sha256=YDX3Cc57yAAvixeMuV0fgoIfgbkwgEVkzYMLOdN8Tko,829
|
|
75
72
|
phoenix/pointcloud/pointcloud.py,sha256=4zAIkKs2xOUbchpj4XDAV-iPMXrfAJ15TG6rlIYGrao,2145
|
|
76
|
-
phoenix/pointcloud/projectors.py,sha256=
|
|
73
|
+
phoenix/pointcloud/projectors.py,sha256=TQgwc9cJDjJkin1WZyZzgl3HsYrLLiyWD7Czy4jNW3U,1088
|
|
77
74
|
phoenix/pointcloud/umap_parameters.py,sha256=3UQSjrysVOvq2V4KNpTMqNqNiK0BsTZnPBHWZ4fyJtQ,1708
|
|
78
75
|
phoenix/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
79
76
|
phoenix/server/app.py,sha256=V3ltox8-ptAQPpE68YmF39WlfYbaquMmdJ333M_uIiw,31539
|
|
@@ -203,7 +200,7 @@ phoenix/server/api/types/DimensionType.py,sha256=JLikZUBVqbHlUWcYYd8d60gB1_hmAcu
|
|
|
203
200
|
phoenix/server/api/types/DimensionWithValue.py,sha256=4_koirnDrZBBdFkIWka2f-OeqZ6IzQXwqz3ccjdgHrc,484
|
|
204
201
|
phoenix/server/api/types/DocumentEvaluationSummary.py,sha256=EpgKn0uiH2Vbpyltc1MeoWOZjzbRuHkPCZ2Zkoqfni8,3480
|
|
205
202
|
phoenix/server/api/types/DocumentRetrievalMetrics.py,sha256=amkpC3H5IU5-9GvO0telpbq00m6lIcv_2v446OpwFwc,1822
|
|
206
|
-
phoenix/server/api/types/EmbeddingDimension.py,sha256=
|
|
203
|
+
phoenix/server/api/types/EmbeddingDimension.py,sha256=faF4kM6v8gp6cpSYzGWJcinBrIkWhocbCVtIZauUvlM,19011
|
|
207
204
|
phoenix/server/api/types/EmbeddingMetadata.py,sha256=fJvNNYCbkf3SJalArLy9rcBq9Uj1SNac60zjqe1PFnM,461
|
|
208
205
|
phoenix/server/api/types/Evaluation.py,sha256=rLJptUSRUPPx9sdUyc4UPWxzkr9gcKtkpAS5Zi_EyFQ,2406
|
|
209
206
|
phoenix/server/api/types/EvaluationSummary.py,sha256=N1DhPXGhBbOOQakF43OGuZ5fl4vye7Uc-HnW5M262F8,1518
|
|
@@ -283,7 +280,7 @@ phoenix/trace/attributes.py,sha256=B_OrzVaxZwFkrAFXZyicYoIti1UdUysURsvUS2GyW1U,1
|
|
|
283
280
|
phoenix/trace/errors.py,sha256=wB1z8qdPckngdfU-TORToekvg3344oNFAA83_hC2yFY,180
|
|
284
281
|
phoenix/trace/evaluation_conventions.py,sha256=t8jydM3U0-T5YpiQKRJ3tWdWGlHtzKyttYdw-ddvPOk,1048
|
|
285
282
|
phoenix/trace/exporter.py,sha256=bUXh8fjJIbHurrnt4bAm-cCWqUN5FqNsIc8DZzzklkQ,4695
|
|
286
|
-
phoenix/trace/fixtures.py,sha256=
|
|
283
|
+
phoenix/trace/fixtures.py,sha256=nfVbYYCNNO6HX5SRZHPxcy6tZZkC8xc3FbvvMLfsDhA,19005
|
|
287
284
|
phoenix/trace/otel.py,sha256=WA720jvRadiZBAKjsYoPyXzypHwbyEK2OZRVUwtbjB8,9976
|
|
288
285
|
phoenix/trace/projects.py,sha256=2BwlNjFE-uwpqYtCu5YyBiYZk9wRPpM13vh3-Cv7GkA,2157
|
|
289
286
|
phoenix/trace/schemas.py,sha256=HpWSyzec0yDHEQXEDuwyLbhpvKrqkGps8BJqGiIFj8Y,5978
|
|
@@ -309,8 +306,8 @@ phoenix/utilities/logging.py,sha256=B8t2WPULOwVyuGLRLbwKsw5N41N26vtgF-lCAYgWTEk,
|
|
|
309
306
|
phoenix/utilities/project.py,sha256=8IJuMM4yUMoooPi37sictGj8Etu9rGmq6RFtc9848cQ,436
|
|
310
307
|
phoenix/utilities/re.py,sha256=nr_B0txj_7CXc45953X6vr2KCRSWMuaXJSEkL8s8Sjc,2036
|
|
311
308
|
phoenix/utilities/span_store.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
312
|
-
arize_phoenix-5.1.
|
|
313
|
-
arize_phoenix-5.1.
|
|
314
|
-
arize_phoenix-5.1.
|
|
315
|
-
arize_phoenix-5.1.
|
|
316
|
-
arize_phoenix-5.1.
|
|
309
|
+
arize_phoenix-5.1.2.dist-info/METADATA,sha256=Krw91Q3bZMcvgMCAWwIPbpVlpQO-bOi4jts1aOXAeuI,11861
|
|
310
|
+
arize_phoenix-5.1.2.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
311
|
+
arize_phoenix-5.1.2.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
|
|
312
|
+
arize_phoenix-5.1.2.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
|
|
313
|
+
arize_phoenix-5.1.2.dist-info/RECORD,,
|
phoenix/pointcloud/clustering.py
CHANGED
|
@@ -3,7 +3,6 @@ from typing import List, Set
|
|
|
3
3
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
import numpy.typing as npt
|
|
6
|
-
from hdbscan import HDBSCAN
|
|
7
6
|
from typing_extensions import TypeAlias
|
|
8
7
|
|
|
9
8
|
RowIndex: TypeAlias = int
|
|
@@ -18,6 +17,8 @@ class Hdbscan:
|
|
|
18
17
|
cluster_selection_epsilon: float = 0.0
|
|
19
18
|
|
|
20
19
|
def find_clusters(self, mat: Matrix) -> List[RawCluster]:
|
|
20
|
+
from hdbscan import HDBSCAN
|
|
21
|
+
|
|
21
22
|
cluster_ids: npt.NDArray[np.int_] = HDBSCAN(**asdict(self)).fit_predict(mat)
|
|
22
23
|
ans: List[RawCluster] = [set() for _ in range(np.max(cluster_ids) + 1)]
|
|
23
24
|
for row_idx, cluster_id in enumerate(cluster_ids):
|
phoenix/pointcloud/projectors.py
CHANGED
|
@@ -6,12 +6,6 @@ import numpy as np
|
|
|
6
6
|
import numpy.typing as npt
|
|
7
7
|
from typing_extensions import TypeAlias
|
|
8
8
|
|
|
9
|
-
with warnings.catch_warnings():
|
|
10
|
-
from numba.core.errors import NumbaWarning
|
|
11
|
-
|
|
12
|
-
warnings.simplefilter("ignore", category=NumbaWarning)
|
|
13
|
-
from umap import UMAP
|
|
14
|
-
|
|
15
9
|
Matrix: TypeAlias = npt.NDArray[np.float64]
|
|
16
10
|
|
|
17
11
|
|
|
@@ -25,6 +19,11 @@ class Umap:
|
|
|
25
19
|
min_dist: float = 0.1
|
|
26
20
|
|
|
27
21
|
def project(self, mat: Matrix, n_components: int) -> Matrix:
|
|
22
|
+
with warnings.catch_warnings():
|
|
23
|
+
from numba.core.errors import NumbaWarning
|
|
24
|
+
|
|
25
|
+
warnings.simplefilter("ignore", category=NumbaWarning)
|
|
26
|
+
from umap import UMAP
|
|
28
27
|
config = asdict(self)
|
|
29
28
|
config["n_components"] = n_components
|
|
30
29
|
if len(mat) <= n_components:
|
|
@@ -422,17 +422,17 @@ class EmbeddingDimension(Node):
|
|
|
422
422
|
if isinstance(inferences_role, InferencesRole):
|
|
423
423
|
dataset = model[inferences_role.value]
|
|
424
424
|
embedding_metadata = EmbeddingMetadata(
|
|
425
|
-
prediction_id=dataset[PREDICTION_ID][row_id],
|
|
426
|
-
link_to_data=dataset[self.dimension.link_to_data][row_id],
|
|
427
|
-
raw_data=dataset[self.dimension.raw_data][row_id],
|
|
425
|
+
prediction_id=dataset[PREDICTION_ID].iloc[row_id],
|
|
426
|
+
link_to_data=dataset[self.dimension.link_to_data].iloc[row_id],
|
|
427
|
+
raw_data=dataset[self.dimension.raw_data].iloc[row_id],
|
|
428
428
|
)
|
|
429
429
|
elif (corpus := info.context.corpus) is not None:
|
|
430
430
|
dataset = corpus[PRIMARY]
|
|
431
431
|
dimension = cast(ms.EmbeddingDimension, corpus[PROMPT])
|
|
432
432
|
embedding_metadata = EmbeddingMetadata(
|
|
433
|
-
prediction_id=dataset[PREDICTION_ID][row_id],
|
|
434
|
-
link_to_data=dataset[dimension.link_to_data][row_id],
|
|
435
|
-
raw_data=dataset[dimension.raw_data][row_id],
|
|
433
|
+
prediction_id=dataset[PREDICTION_ID].iloc[row_id],
|
|
434
|
+
link_to_data=dataset[dimension.link_to_data].iloc[row_id],
|
|
435
|
+
raw_data=dataset[dimension.raw_data].iloc[row_id],
|
|
436
436
|
)
|
|
437
437
|
else:
|
|
438
438
|
continue
|
|
@@ -445,10 +445,10 @@ class EmbeddingDimension(Node):
|
|
|
445
445
|
event_id=event_id,
|
|
446
446
|
coordinates=to_gql_coordinates(vector),
|
|
447
447
|
event_metadata=EventMetadata(
|
|
448
|
-
prediction_label=dataset[PREDICTION_LABEL][row_id],
|
|
449
|
-
prediction_score=dataset[PREDICTION_SCORE][row_id],
|
|
450
|
-
actual_label=dataset[ACTUAL_LABEL][row_id],
|
|
451
|
-
actual_score=dataset[ACTUAL_SCORE][row_id],
|
|
448
|
+
prediction_label=dataset[PREDICTION_LABEL].iloc[row_id],
|
|
449
|
+
prediction_score=dataset[PREDICTION_SCORE].iloc[row_id],
|
|
450
|
+
actual_label=dataset[ACTUAL_LABEL].iloc[row_id],
|
|
451
|
+
actual_score=dataset[ACTUAL_SCORE].iloc[row_id],
|
|
452
452
|
),
|
|
453
453
|
embedding_metadata=embedding_metadata,
|
|
454
454
|
)
|
phoenix/trace/fixtures.py
CHANGED
|
@@ -147,21 +147,21 @@ demo_llama_index_rag_fixture = TracesFixture(
|
|
|
147
147
|
|
|
148
148
|
demo_code_based_agent_fixture = TracesFixture(
|
|
149
149
|
name="demo_code_based_agent",
|
|
150
|
-
project_name="
|
|
150
|
+
project_name="demo_code_based_agent",
|
|
151
151
|
description="Shows traces captured from a code-based agent.",
|
|
152
152
|
file_name="agent-demo-traces.parquet",
|
|
153
153
|
)
|
|
154
154
|
|
|
155
155
|
demo_langgraph_agent_fixture = TracesFixture(
|
|
156
156
|
name="demo_langgraph_agent",
|
|
157
|
-
project_name="
|
|
157
|
+
project_name="demo_langgraph_agent",
|
|
158
158
|
description="Shows traces captured from a Langgraph agent.",
|
|
159
|
-
file_name="langgraph-demo-traces.parquet",
|
|
159
|
+
file_name="langgraph-demo-traces-format-updated.parquet",
|
|
160
160
|
)
|
|
161
161
|
|
|
162
162
|
demo_llamaindex_workflows_agent_fixture = TracesFixture(
|
|
163
163
|
name="demo_llamaindex_workflows_agent",
|
|
164
|
-
project_name="
|
|
164
|
+
project_name="demo_llamaindex_workflows_agent",
|
|
165
165
|
description="Shows traces captured from a LlamaIndex Workflows agent.",
|
|
166
166
|
file_name="llamaindex-workflow-demo-traces.parquet",
|
|
167
167
|
)
|
phoenix/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "5.1.
|
|
1
|
+
__version__ = "5.1.2"
|
phoenix/otel/__init__.py
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
from opentelemetry.sdk.resources import Resource
|
|
2
|
-
|
|
3
|
-
from .otel import (
|
|
4
|
-
PROJECT_NAME,
|
|
5
|
-
BatchSpanProcessor,
|
|
6
|
-
GRPCSpanExporter,
|
|
7
|
-
HTTPSpanExporter,
|
|
8
|
-
SimpleSpanProcessor,
|
|
9
|
-
TracerProvider,
|
|
10
|
-
register,
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
__all__ = [
|
|
14
|
-
"TracerProvider",
|
|
15
|
-
"SimpleSpanProcessor",
|
|
16
|
-
"BatchSpanProcessor",
|
|
17
|
-
"HTTPSpanExporter",
|
|
18
|
-
"GRPCSpanExporter",
|
|
19
|
-
"Resource",
|
|
20
|
-
"PROJECT_NAME",
|
|
21
|
-
"register",
|
|
22
|
-
]
|
phoenix/otel/otel.py
DELETED
|
@@ -1,451 +0,0 @@
|
|
|
1
|
-
import inspect
|
|
2
|
-
import os
|
|
3
|
-
import sys
|
|
4
|
-
import warnings
|
|
5
|
-
from typing import Any, Dict, List, Optional, Tuple, Type, Union, cast
|
|
6
|
-
from urllib.parse import ParseResult, urlparse
|
|
7
|
-
|
|
8
|
-
from openinference.semconv.resource import ResourceAttributes as _ResourceAttributes
|
|
9
|
-
from opentelemetry import trace as trace_api
|
|
10
|
-
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
|
|
11
|
-
OTLPSpanExporter as _GRPCSpanExporter,
|
|
12
|
-
)
|
|
13
|
-
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
|
|
14
|
-
OTLPSpanExporter as _HTTPSpanExporter,
|
|
15
|
-
)
|
|
16
|
-
from opentelemetry.sdk.resources import Resource
|
|
17
|
-
from opentelemetry.sdk.trace import SpanProcessor
|
|
18
|
-
from opentelemetry.sdk.trace import TracerProvider as _TracerProvider
|
|
19
|
-
from opentelemetry.sdk.trace.export import BatchSpanProcessor as _BatchSpanProcessor
|
|
20
|
-
from opentelemetry.sdk.trace.export import SimpleSpanProcessor as _SimpleSpanProcessor
|
|
21
|
-
from opentelemetry.sdk.trace.export import SpanExporter
|
|
22
|
-
|
|
23
|
-
from .settings import (
|
|
24
|
-
get_env_client_headers,
|
|
25
|
-
get_env_collector_endpoint,
|
|
26
|
-
get_env_phoenix_auth_header,
|
|
27
|
-
get_env_project_name,
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
PROJECT_NAME = _ResourceAttributes.PROJECT_NAME
|
|
31
|
-
|
|
32
|
-
_DEFAULT_GRPC_PORT = 4317
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def register(
|
|
36
|
-
*,
|
|
37
|
-
endpoint: Optional[str] = None,
|
|
38
|
-
project_name: Optional[str] = None,
|
|
39
|
-
batch: bool = False,
|
|
40
|
-
set_global_tracer_provider: bool = True,
|
|
41
|
-
headers: Optional[Dict[str, str]] = None,
|
|
42
|
-
verbose: bool = True,
|
|
43
|
-
) -> _TracerProvider:
|
|
44
|
-
"""
|
|
45
|
-
Creates an OpenTelemetry TracerProvider for enabling OpenInference tracing.
|
|
46
|
-
|
|
47
|
-
For futher configuration, the `phoenix.otel` module provides drop-in replacements for
|
|
48
|
-
OpenTelemetry TracerProvider, SimpleSpanProcessor, BatchSpanProcessor, HTTPSpanExporter, and
|
|
49
|
-
GRPCSpanExporter objects with Phoenix-aware defaults. Documentation on how to configure tracing
|
|
50
|
-
can be found at https://opentelemetry.io/docs/specs/otel/trace/sdk/.
|
|
51
|
-
|
|
52
|
-
Args:
|
|
53
|
-
endpoint (str, optional): The collector endpoint to which spans will be exported. If not
|
|
54
|
-
provided, the `PHOENIX_OTEL_COLLECTOR_ENDPOINT` environment variable will be used. The
|
|
55
|
-
export protocol will be inferred from the endpoint.
|
|
56
|
-
project_name (str, optional): The name of the project to which spans will be associated. If
|
|
57
|
-
not provided, the `PHOENIX_PROJECT_NAME` environment variable will be used.
|
|
58
|
-
batch (bool): If True, spans will be processed using a BatchSpanprocessor. If False, spans
|
|
59
|
-
will be processed one at a time using a SimpleSpanProcessor.
|
|
60
|
-
set_global_tracer_provider (bool): If False, the TracerProvider will not be set as the
|
|
61
|
-
global tracer provider. Defaults to True.
|
|
62
|
-
headers (dict, optional): Optional headers to include in the request to the collector.
|
|
63
|
-
If not provided, the `PHOENIX_CLIENT_HEADERS` environment variable will be used.
|
|
64
|
-
verbose (bool): If True, configuration details will be printed to stdout.
|
|
65
|
-
"""
|
|
66
|
-
|
|
67
|
-
project_name = project_name or get_env_project_name()
|
|
68
|
-
resource = Resource.create({PROJECT_NAME: project_name})
|
|
69
|
-
tracer_provider = TracerProvider(resource=resource, verbose=False)
|
|
70
|
-
span_processor: SpanProcessor
|
|
71
|
-
if batch:
|
|
72
|
-
span_processor = BatchSpanProcessor(endpoint=endpoint, headers=headers)
|
|
73
|
-
else:
|
|
74
|
-
span_processor = SimpleSpanProcessor(endpoint=endpoint, headers=headers)
|
|
75
|
-
tracer_provider.add_span_processor(span_processor)
|
|
76
|
-
tracer_provider._default_processor = True
|
|
77
|
-
|
|
78
|
-
if set_global_tracer_provider:
|
|
79
|
-
trace_api.set_tracer_provider(tracer_provider)
|
|
80
|
-
global_provider_msg = (
|
|
81
|
-
"| \n"
|
|
82
|
-
"| `register` has set this TracerProvider as the global OpenTelemetry default.\n"
|
|
83
|
-
"| To disable this behavior, call `register` with "
|
|
84
|
-
"`set_global_tracer_provider=False`.\n"
|
|
85
|
-
)
|
|
86
|
-
else:
|
|
87
|
-
global_provider_msg = ""
|
|
88
|
-
|
|
89
|
-
details = tracer_provider._tracing_details()
|
|
90
|
-
if verbose:
|
|
91
|
-
print(f"{details}" f"{global_provider_msg}")
|
|
92
|
-
return tracer_provider
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
class TracerProvider(_TracerProvider):
|
|
96
|
-
"""
|
|
97
|
-
An extension of `opentelemetry.sdk.trace.TracerProvider` with Phoenix-aware defaults.
|
|
98
|
-
|
|
99
|
-
Extended keyword arguments are documented in the `Args` section. For further documentation, see
|
|
100
|
-
the OpenTelemetry documentation at https://opentelemetry.io/docs/specs/otel/trace/sdk/.
|
|
101
|
-
|
|
102
|
-
Args:
|
|
103
|
-
endpoint (str, optional): The collector endpoint to which spans will be exported. If
|
|
104
|
-
specified, a default SpanProcessor will be created and added to this TracerProvider.
|
|
105
|
-
If not provided, the `PHOENIX_OTEL_COLLECTOR_ENDPOINT` environment variable will be
|
|
106
|
-
used to infer which collector endpoint to use, defaults to the gRPC endpoint. When
|
|
107
|
-
specifying the endpoint, the transport method (HTTP or gRPC) will be inferred from the
|
|
108
|
-
URL.
|
|
109
|
-
verbose (bool): If True, configuration details will be printed to stdout.
|
|
110
|
-
"""
|
|
111
|
-
|
|
112
|
-
def __init__(
|
|
113
|
-
self, *args: Any, endpoint: Optional[str] = None, verbose: bool = True, **kwargs: Any
|
|
114
|
-
):
|
|
115
|
-
sig = _get_class_signature(_TracerProvider)
|
|
116
|
-
bound_args = sig.bind_partial(*args, **kwargs)
|
|
117
|
-
bound_args.apply_defaults()
|
|
118
|
-
if bound_args.arguments.get("resource") is None:
|
|
119
|
-
bound_args.arguments["resource"] = Resource.create(
|
|
120
|
-
{PROJECT_NAME: get_env_project_name()}
|
|
121
|
-
)
|
|
122
|
-
super().__init__(*bound_args.args, **bound_args.kwargs)
|
|
123
|
-
|
|
124
|
-
parsed_url, endpoint = _normalized_endpoint(endpoint)
|
|
125
|
-
self._default_processor = False
|
|
126
|
-
|
|
127
|
-
if _maybe_http_endpoint(parsed_url):
|
|
128
|
-
http_exporter: SpanExporter = HTTPSpanExporter(endpoint=endpoint)
|
|
129
|
-
self.add_span_processor(SimpleSpanProcessor(span_exporter=http_exporter))
|
|
130
|
-
self._default_processor = True
|
|
131
|
-
elif _maybe_grpc_endpoint(parsed_url):
|
|
132
|
-
grpc_exporter: SpanExporter = GRPCSpanExporter(endpoint=endpoint)
|
|
133
|
-
self.add_span_processor(SimpleSpanProcessor(span_exporter=grpc_exporter))
|
|
134
|
-
self._default_processor = True
|
|
135
|
-
if verbose:
|
|
136
|
-
print(self._tracing_details())
|
|
137
|
-
|
|
138
|
-
def add_span_processor(self, *args: Any, **kwargs: Any) -> None:
|
|
139
|
-
"""
|
|
140
|
-
Registers a new `SpanProcessor` for this `TracerProvider`.
|
|
141
|
-
|
|
142
|
-
If this `TracerProvider` has a default processor, it will be removed.
|
|
143
|
-
"""
|
|
144
|
-
|
|
145
|
-
if self._default_processor:
|
|
146
|
-
self._active_span_processor.shutdown()
|
|
147
|
-
self._active_span_processor._span_processors = tuple() # remove default processors
|
|
148
|
-
self._default_processor = False
|
|
149
|
-
return super().add_span_processor(*args, **kwargs)
|
|
150
|
-
|
|
151
|
-
def _tracing_details(self) -> str:
|
|
152
|
-
project = self.resource.attributes.get(PROJECT_NAME)
|
|
153
|
-
processor_name: Optional[str] = None
|
|
154
|
-
endpoint: Optional[str] = None
|
|
155
|
-
transport: Optional[str] = None
|
|
156
|
-
headers: Optional[Union[Dict[str, str], str]] = None
|
|
157
|
-
|
|
158
|
-
if self._active_span_processor:
|
|
159
|
-
if processors := self._active_span_processor._span_processors:
|
|
160
|
-
if len(processors) == 1:
|
|
161
|
-
span_processor = self._active_span_processor._span_processors[0]
|
|
162
|
-
if exporter := getattr(span_processor, "span_exporter"):
|
|
163
|
-
processor_name = span_processor.__class__.__name__
|
|
164
|
-
endpoint = exporter._endpoint
|
|
165
|
-
transport = _exporter_transport(exporter)
|
|
166
|
-
headers = _printable_headers(exporter._headers)
|
|
167
|
-
else:
|
|
168
|
-
processor_name = "Multiple Span Processors"
|
|
169
|
-
endpoint = "Multiple Span Exporters"
|
|
170
|
-
transport = "Multiple Span Exporters"
|
|
171
|
-
headers = "Multiple Span Exporters"
|
|
172
|
-
|
|
173
|
-
if os.name == "nt":
|
|
174
|
-
details_header = "OpenTelemetry Tracing Details"
|
|
175
|
-
else:
|
|
176
|
-
details_header = "🔭 OpenTelemetry Tracing Details 🔭"
|
|
177
|
-
|
|
178
|
-
configuration_msg = (
|
|
179
|
-
"| Using a default SpanProcessor. `add_span_processor` will overwrite this default.\n"
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
details_msg = (
|
|
183
|
-
f"{details_header}\n"
|
|
184
|
-
f"| Phoenix Project: {project}\n"
|
|
185
|
-
f"| Span Processor: {processor_name}\n"
|
|
186
|
-
f"| Collector Endpoint: {endpoint}\n"
|
|
187
|
-
f"| Transport: {transport}\n"
|
|
188
|
-
f"| Transport Headers: {headers}\n"
|
|
189
|
-
"| \n"
|
|
190
|
-
f"{configuration_msg if self._default_processor else ''}"
|
|
191
|
-
)
|
|
192
|
-
return details_msg
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
class SimpleSpanProcessor(_SimpleSpanProcessor):
|
|
196
|
-
"""
|
|
197
|
-
Simple SpanProcessor implementation.
|
|
198
|
-
|
|
199
|
-
SimpleSpanProcessor is an implementation of `SpanProcessor` that passes ended spans directly to
|
|
200
|
-
the configured `SpanExporter`.
|
|
201
|
-
|
|
202
|
-
Args:
|
|
203
|
-
span_exporter (SpanExporter, optional): The `SpanExporter` to which ended spans will be
|
|
204
|
-
passed.
|
|
205
|
-
endpoint (str, optional): The collector endpoint to which spans will be exported. If not
|
|
206
|
-
provided, the `PHOENIX_OTEL_COLLECTOR_ENDPOINT` environment variable will be used to
|
|
207
|
-
infer which collector endpoint to use, defaults to the gRPC endpoint. When specifying
|
|
208
|
-
the endpoint, the transport method (HTTP or gRPC) will be inferred from the URL.
|
|
209
|
-
headers (dict, optional): Optional headers to include in the request to the collector.
|
|
210
|
-
If not provided, the `PHOENIX_CLIENT_HEADERS` or `OTEL_EXPORTER_OTLP_HEADERS`
|
|
211
|
-
environment variable will be used.
|
|
212
|
-
"""
|
|
213
|
-
|
|
214
|
-
def __init__(
|
|
215
|
-
self,
|
|
216
|
-
span_exporter: Optional[SpanExporter] = None,
|
|
217
|
-
endpoint: Optional[str] = None,
|
|
218
|
-
headers: Optional[Dict[str, str]] = None,
|
|
219
|
-
):
|
|
220
|
-
if span_exporter is None:
|
|
221
|
-
parsed_url, endpoint = _normalized_endpoint(endpoint)
|
|
222
|
-
if _maybe_http_endpoint(parsed_url):
|
|
223
|
-
span_exporter = HTTPSpanExporter(endpoint=endpoint, headers=headers)
|
|
224
|
-
elif _maybe_grpc_endpoint(parsed_url):
|
|
225
|
-
span_exporter = GRPCSpanExporter(endpoint=endpoint, headers=headers)
|
|
226
|
-
else:
|
|
227
|
-
warnings.warn("Could not infer collector endpoint protocol, defaulting to HTTP.")
|
|
228
|
-
span_exporter = HTTPSpanExporter(endpoint=endpoint, headers=headers)
|
|
229
|
-
super().__init__(span_exporter)
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
class BatchSpanProcessor(_BatchSpanProcessor):
|
|
233
|
-
"""
|
|
234
|
-
Batch SpanProcessor implementation.
|
|
235
|
-
|
|
236
|
-
`BatchSpanProcessor` is an implementation of `SpanProcessor` that batches ended spans and
|
|
237
|
-
pushes them to the configured `SpanExporter`.
|
|
238
|
-
|
|
239
|
-
`BatchSpanProcessor` is configurable with the following environment variables which correspond
|
|
240
|
-
to constructor parameters:
|
|
241
|
-
|
|
242
|
-
- :envvar:`OTEL_BSP_SCHEDULE_DELAY`
|
|
243
|
-
- :envvar:`OTEL_BSP_MAX_QUEUE_SIZE`
|
|
244
|
-
- :envvar:`OTEL_BSP_MAX_EXPORT_BATCH_SIZE`
|
|
245
|
-
- :envvar:`OTEL_BSP_EXPORT_TIMEOUT`
|
|
246
|
-
|
|
247
|
-
Args:
|
|
248
|
-
span_exporter (SpanExporter, optional): The `SpanExporter` to which ended spans will be
|
|
249
|
-
passed.
|
|
250
|
-
endpoint (str, optional): The collector endpoint to which spans will be exported. If not
|
|
251
|
-
provided, the `PHOENIX_OTEL_COLLECTOR_ENDPOINT` environment variable will be used to
|
|
252
|
-
infer which collector endpoint to use, defaults to the gRPC endpoint. When specifying
|
|
253
|
-
the endpoint, the transport method (HTTP or gRPC) will be inferred from the URL.
|
|
254
|
-
headers (dict, optional): Optional headers to include in the request to the collector.
|
|
255
|
-
If not provided, the `PHOENIX_CLIENT_HEADERS` or `OTEL_EXPORTER_OTLP_HEADERS`
|
|
256
|
-
environment variable will be used.
|
|
257
|
-
max_queue_size (int, optional): The maximum queue size.
|
|
258
|
-
schedule_delay_millis (float, optional): The delay between two consecutive exports in
|
|
259
|
-
milliseconds.
|
|
260
|
-
max_export_batch_size (int, optional): The maximum batch size.
|
|
261
|
-
export_timeout_millis (float, optional): The batch timeout in milliseconds.
|
|
262
|
-
"""
|
|
263
|
-
|
|
264
|
-
def __init__(
|
|
265
|
-
self,
|
|
266
|
-
span_exporter: Optional[SpanExporter] = None,
|
|
267
|
-
endpoint: Optional[str] = None,
|
|
268
|
-
headers: Optional[Dict[str, str]] = None,
|
|
269
|
-
):
|
|
270
|
-
if span_exporter is None:
|
|
271
|
-
parsed_url, endpoint = _normalized_endpoint(endpoint)
|
|
272
|
-
if _maybe_http_endpoint(parsed_url):
|
|
273
|
-
span_exporter = HTTPSpanExporter(endpoint=endpoint, headers=headers)
|
|
274
|
-
elif _maybe_grpc_endpoint(parsed_url):
|
|
275
|
-
span_exporter = GRPCSpanExporter(endpoint=endpoint, headers=headers)
|
|
276
|
-
else:
|
|
277
|
-
warnings.warn("Could not infer collector endpoint protocol, defaulting to HTTP.")
|
|
278
|
-
span_exporter = HTTPSpanExporter(endpoint=endpoint, headers=headers)
|
|
279
|
-
super().__init__(span_exporter)
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
class HTTPSpanExporter(_HTTPSpanExporter):
|
|
283
|
-
"""
|
|
284
|
-
OTLP span exporter using HTTP.
|
|
285
|
-
|
|
286
|
-
For more information, see:
|
|
287
|
-
- `opentelemetry.exporter.otlp.proto.http.trace_exporter.OTLPSpanExporter`
|
|
288
|
-
|
|
289
|
-
Args:
|
|
290
|
-
endpoint (str, optional): OpenTelemetry Collector receiver endpoint. If not provided, the
|
|
291
|
-
`PHOENIX_OTEL_COLLECTOR_ENDPOINT` environment variable will be used to infer which
|
|
292
|
-
collector endpoint to use, defaults to the HTTP endpoint.
|
|
293
|
-
headers: Headers to send when exporting. If not provided, the `PHOENIX_CLIENT_HEADERS`
|
|
294
|
-
or `OTEL_EXPORTER_OTLP_HEADERS` environment variables will be used.
|
|
295
|
-
"""
|
|
296
|
-
|
|
297
|
-
def __init__(self, *args: Any, **kwargs: Any):
|
|
298
|
-
sig = _get_class_signature(_HTTPSpanExporter)
|
|
299
|
-
bound_args = sig.bind_partial(*args, **kwargs)
|
|
300
|
-
bound_args.apply_defaults()
|
|
301
|
-
|
|
302
|
-
if not bound_args.arguments.get("headers"):
|
|
303
|
-
env_headers = get_env_client_headers()
|
|
304
|
-
auth_header = get_env_phoenix_auth_header()
|
|
305
|
-
headers = {
|
|
306
|
-
**(env_headers or dict()),
|
|
307
|
-
**(auth_header or dict()),
|
|
308
|
-
}
|
|
309
|
-
bound_args.arguments["headers"] = headers if headers else None
|
|
310
|
-
else:
|
|
311
|
-
headers = dict()
|
|
312
|
-
for header_field, value in bound_args.arguments["headers"].items():
|
|
313
|
-
headers[header_field.lower()] = value
|
|
314
|
-
|
|
315
|
-
# If the auth header is not in the headers, add it
|
|
316
|
-
if "authorization" not in headers:
|
|
317
|
-
auth_header = get_env_phoenix_auth_header()
|
|
318
|
-
bound_args.arguments["headers"] = {
|
|
319
|
-
**headers,
|
|
320
|
-
**(auth_header or dict()),
|
|
321
|
-
}
|
|
322
|
-
else:
|
|
323
|
-
bound_args.arguments["headers"] = headers
|
|
324
|
-
|
|
325
|
-
if bound_args.arguments.get("endpoint") is None:
|
|
326
|
-
_, endpoint = _normalized_endpoint(None, use_http=True)
|
|
327
|
-
bound_args.arguments["endpoint"] = endpoint
|
|
328
|
-
super().__init__(*bound_args.args, **bound_args.kwargs)
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
class GRPCSpanExporter(_GRPCSpanExporter):
|
|
332
|
-
"""
|
|
333
|
-
OTLP span exporter using gRPC.
|
|
334
|
-
|
|
335
|
-
For more information, see:
|
|
336
|
-
- `opentelemetry.exporter.otlp.proto.grpc.trace_exporter.OTLPSpanExporter`
|
|
337
|
-
|
|
338
|
-
Args:
|
|
339
|
-
endpoint (str, optional): OpenTelemetry Collector receiver endpoint. If not provided, the
|
|
340
|
-
`PHOENIX_OTEL_COLLECTOR_ENDPOINT` environment variable will be used to infer which
|
|
341
|
-
collector endpoint to use, defaults to the gRPC endpoint.
|
|
342
|
-
insecure: Connection type
|
|
343
|
-
credentials: Credentials object for server authentication
|
|
344
|
-
headers: Headers to send when exporting. If not provided, the `PHOENIX_CLIENT_HEADERS`
|
|
345
|
-
or `OTEL_EXPORTER_OTLP_HEADERS` environment variables will be used.
|
|
346
|
-
timeout: Backend request timeout in seconds
|
|
347
|
-
compression: gRPC compression method to use
|
|
348
|
-
"""
|
|
349
|
-
|
|
350
|
-
def __init__(self, *args: Any, **kwargs: Any):
|
|
351
|
-
sig = _get_class_signature(_GRPCSpanExporter)
|
|
352
|
-
bound_args = sig.bind_partial(*args, **kwargs)
|
|
353
|
-
bound_args.apply_defaults()
|
|
354
|
-
|
|
355
|
-
if not bound_args.arguments.get("headers"):
|
|
356
|
-
env_headers = get_env_client_headers()
|
|
357
|
-
auth_header = get_env_phoenix_auth_header()
|
|
358
|
-
headers = {
|
|
359
|
-
**(env_headers or dict()),
|
|
360
|
-
**(auth_header or dict()),
|
|
361
|
-
}
|
|
362
|
-
bound_args.arguments["headers"] = headers if headers else None
|
|
363
|
-
else:
|
|
364
|
-
headers = dict()
|
|
365
|
-
for header_field, value in bound_args.arguments["headers"].items():
|
|
366
|
-
headers[header_field.lower()] = value
|
|
367
|
-
|
|
368
|
-
# If the auth header is not in the headers, add it
|
|
369
|
-
if "authorization" not in headers:
|
|
370
|
-
auth_header = get_env_phoenix_auth_header()
|
|
371
|
-
bound_args.arguments["headers"] = {
|
|
372
|
-
**headers,
|
|
373
|
-
**(auth_header or dict()),
|
|
374
|
-
}
|
|
375
|
-
else:
|
|
376
|
-
bound_args.arguments["headers"] = headers
|
|
377
|
-
|
|
378
|
-
if bound_args.arguments.get("endpoint") is None:
|
|
379
|
-
_, endpoint = _normalized_endpoint(None)
|
|
380
|
-
bound_args.arguments["endpoint"] = endpoint
|
|
381
|
-
super().__init__(*bound_args.args, **bound_args.kwargs)
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
def _maybe_http_endpoint(parsed_endpoint: ParseResult) -> bool:
|
|
385
|
-
if parsed_endpoint.path == "/v1/traces":
|
|
386
|
-
return True
|
|
387
|
-
return False
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
def _maybe_grpc_endpoint(parsed_endpoint: ParseResult) -> bool:
|
|
391
|
-
if not parsed_endpoint.path and parsed_endpoint.port == 4317:
|
|
392
|
-
return True
|
|
393
|
-
return False
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
def _exporter_transport(exporter: SpanExporter) -> str:
|
|
397
|
-
if isinstance(exporter, _HTTPSpanExporter):
|
|
398
|
-
return "HTTP"
|
|
399
|
-
if isinstance(exporter, _GRPCSpanExporter):
|
|
400
|
-
return "gRPC"
|
|
401
|
-
else:
|
|
402
|
-
return exporter.__class__.__name__
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
def _printable_headers(headers: Union[List[Tuple[str, str]], Dict[str, str]]) -> Dict[str, str]:
|
|
406
|
-
if isinstance(headers, dict):
|
|
407
|
-
return {key: "****" for key, _ in headers.items()}
|
|
408
|
-
return {key: "****" for key, _ in headers}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
def _construct_http_endpoint(parsed_endpoint: ParseResult) -> ParseResult:
|
|
412
|
-
return parsed_endpoint._replace(path="/v1/traces")
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
def _construct_grpc_endpoint(parsed_endpoint: ParseResult) -> ParseResult:
|
|
416
|
-
return parsed_endpoint._replace(netloc=f"{parsed_endpoint.hostname}:{_DEFAULT_GRPC_PORT}")
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
_KNOWN_PROVIDERS = {
|
|
420
|
-
"app.phoenix.arize.com": _construct_http_endpoint,
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
def _normalized_endpoint(
|
|
425
|
-
endpoint: Optional[str], use_http: bool = False
|
|
426
|
-
) -> Tuple[ParseResult, str]:
|
|
427
|
-
if endpoint is None:
|
|
428
|
-
base_endpoint = get_env_collector_endpoint() or "http://localhost:6006"
|
|
429
|
-
parsed = urlparse(base_endpoint)
|
|
430
|
-
if parsed.hostname in _KNOWN_PROVIDERS:
|
|
431
|
-
parsed = _KNOWN_PROVIDERS[parsed.hostname](parsed)
|
|
432
|
-
elif use_http:
|
|
433
|
-
parsed = _construct_http_endpoint(parsed)
|
|
434
|
-
else:
|
|
435
|
-
parsed = _construct_grpc_endpoint(parsed)
|
|
436
|
-
else:
|
|
437
|
-
parsed = urlparse(endpoint)
|
|
438
|
-
parsed = cast(ParseResult, parsed)
|
|
439
|
-
return parsed, parsed.geturl()
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
def _get_class_signature(fn: Type[Any]) -> inspect.Signature:
|
|
443
|
-
if sys.version_info >= (3, 9):
|
|
444
|
-
return inspect.signature(fn)
|
|
445
|
-
elif sys.version_info >= (3, 8):
|
|
446
|
-
init_signature = inspect.signature(fn.__init__)
|
|
447
|
-
new_params = list(init_signature.parameters.values())[1:] # Skip 'self'
|
|
448
|
-
new_sig = init_signature.replace(parameters=new_params)
|
|
449
|
-
return new_sig
|
|
450
|
-
else:
|
|
451
|
-
raise RuntimeError("Unsupported Python version")
|
phoenix/otel/settings.py
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import os
|
|
3
|
-
import urllib
|
|
4
|
-
from re import compile
|
|
5
|
-
from typing import Dict, List, Optional
|
|
6
|
-
|
|
7
|
-
logger = logging.getLogger(__name__)
|
|
8
|
-
|
|
9
|
-
# Environment variables specific to the subpackage
|
|
10
|
-
ENV_PHOENIX_COLLECTOR_ENDPOINT = "PHOENIX_COLLECTOR_ENDPOINT"
|
|
11
|
-
ENV_PHOENIX_PROJECT_NAME = "PHOENIX_PROJECT_NAME"
|
|
12
|
-
ENV_PHOENIX_CLIENT_HEADERS = "PHOENIX_CLIENT_HEADERS"
|
|
13
|
-
ENV_PHOENIX_API_KEY = "PHOENIX_API_KEY"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def get_env_collector_endpoint() -> Optional[str]:
|
|
17
|
-
return os.getenv(ENV_PHOENIX_COLLECTOR_ENDPOINT)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def get_env_project_name() -> str:
|
|
21
|
-
return os.getenv(ENV_PHOENIX_PROJECT_NAME, "default")
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def get_env_client_headers() -> Optional[Dict[str, str]]:
|
|
25
|
-
if headers_str := os.getenv(ENV_PHOENIX_CLIENT_HEADERS):
|
|
26
|
-
return parse_env_headers(headers_str)
|
|
27
|
-
return None
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def get_env_phoenix_auth_header() -> Optional[Dict[str, str]]:
|
|
31
|
-
api_key = os.environ.get(ENV_PHOENIX_API_KEY)
|
|
32
|
-
if api_key:
|
|
33
|
-
return dict(authorization=f"Bearer {api_key}")
|
|
34
|
-
else:
|
|
35
|
-
return None
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
# Optional whitespace
|
|
39
|
-
_OWS = r"[ \t]*"
|
|
40
|
-
# A key contains printable US-ASCII characters except: SP and "(),/:;<=>?@[\]{}
|
|
41
|
-
_KEY_FORMAT = r"[\x21\x23-\x27\x2a\x2b\x2d\x2e\x30-\x39\x41-\x5a\x5e-\x7a\x7c\x7e]+"
|
|
42
|
-
# A value contains a URL-encoded UTF-8 string. The encoded form can contain any
|
|
43
|
-
# printable US-ASCII characters (0x20-0x7f) other than SP, DEL, and ",;/
|
|
44
|
-
_VALUE_FORMAT = r"[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*"
|
|
45
|
-
# A key-value is key=value, with optional whitespace surrounding key and value
|
|
46
|
-
_KEY_VALUE_FORMAT = rf"{_OWS}{_KEY_FORMAT}{_OWS}={_OWS}{_VALUE_FORMAT}{_OWS}"
|
|
47
|
-
|
|
48
|
-
_HEADER_PATTERN = compile(_KEY_VALUE_FORMAT)
|
|
49
|
-
_DELIMITER_PATTERN = compile(r"[ \t]*,[ \t]*")
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def parse_env_headers(s: str) -> Dict[str, str]:
|
|
53
|
-
"""
|
|
54
|
-
Parse ``s``, which is a ``str`` instance containing HTTP headers encoded
|
|
55
|
-
for use in ENV variables per the W3C Baggage HTTP header format at
|
|
56
|
-
https://www.w3.org/TR/baggage/#baggage-http-header-format, except that
|
|
57
|
-
additional semi-colon delimited metadata is not supported.
|
|
58
|
-
|
|
59
|
-
If the headers are not urlencoded, we will log a warning and attempt to urldecode them.
|
|
60
|
-
"""
|
|
61
|
-
headers: Dict[str, str] = {}
|
|
62
|
-
headers_list: List[str] = _DELIMITER_PATTERN.split(s)
|
|
63
|
-
|
|
64
|
-
for header in headers_list:
|
|
65
|
-
if not header: # empty string
|
|
66
|
-
continue
|
|
67
|
-
|
|
68
|
-
match = _HEADER_PATTERN.fullmatch(header.strip())
|
|
69
|
-
if not match:
|
|
70
|
-
parts = header.split("=", 1)
|
|
71
|
-
name, value = parts
|
|
72
|
-
encoded_header = f"{urllib.parse.quote(name)}={urllib.parse.quote(value)}"
|
|
73
|
-
match = _HEADER_PATTERN.fullmatch(encoded_header.strip())
|
|
74
|
-
if not match:
|
|
75
|
-
logger.warning(
|
|
76
|
-
"Header format invalid! Header values in environment variables must be "
|
|
77
|
-
"URL encoded: %s",
|
|
78
|
-
f"{name}: ****",
|
|
79
|
-
)
|
|
80
|
-
continue
|
|
81
|
-
logger.warning(
|
|
82
|
-
"Header values in environment variables should be URL encoded, attempting to "
|
|
83
|
-
"URL encode header: {name}: ****"
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
name, value = header.split("=", 1)
|
|
87
|
-
name = urllib.parse.unquote(name).strip().lower()
|
|
88
|
-
value = urllib.parse.unquote(value).strip()
|
|
89
|
-
headers[name] = value
|
|
90
|
-
|
|
91
|
-
return headers
|
|
File without changes
|
|
File without changes
|
|
File without changes
|