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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: arize-phoenix
3
- Version: 5.1.0
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.33
30
+ Requires-Dist: hdbscan>=0.8.38
31
31
  Requires-Dist: httpx
32
32
  Requires-Dist: jinja2
33
- Requires-Dist: numpy<2
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=lJXb1iNZTQ4hv-awaWFttBVNuyeuI-R9bpTLzszc-Ps,22
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=IzcG67kJ2hPP7pcqVmKPSL_6gKRonKdOT3bCtbTOqnk,820
71
+ phoenix/pointcloud/clustering.py,sha256=YDX3Cc57yAAvixeMuV0fgoIfgbkwgEVkzYMLOdN8Tko,829
75
72
  phoenix/pointcloud/pointcloud.py,sha256=4zAIkKs2xOUbchpj4XDAV-iPMXrfAJ15TG6rlIYGrao,2145
76
- phoenix/pointcloud/projectors.py,sha256=zO_RrtDYSv2rqVOfIP2_9Cv11Dc8EmcZR94xhFcBYPU,1057
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=2OTVzAcfY-2xnJLl7122EAaqJyp0sob1v9-ryaNWknw,18961
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=Lx-O467dGf91hPosZagYOTXcPHA1b-VsN3GYECdpSmo,18990
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.0.dist-info/METADATA,sha256=2flUBvxpbZoW42x8R2_TT3TP78wOgzz_T-hmRAPDgrc,11827
313
- arize_phoenix-5.1.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
314
- arize_phoenix-5.1.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
315
- arize_phoenix-5.1.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
316
- arize_phoenix-5.1.0.dist-info/RECORD,,
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,,
@@ -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):
@@ -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="code_based_agent_demo",
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="langgraph_agent_demo",
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="llamaindex_workflows_agent_demo",
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.0"
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