arize-phoenix 4.30.1__py3-none-any.whl → 4.31.0__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: 4.30.1
3
+ Version: 4.31.0
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
@@ -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=OyML4t2XGnlqF0JXA9_uccL8HslTABxep9Ci7MViKEU,5216
8
8
  phoenix/settings.py,sha256=cO-qgis_S27nHirTobYI9hHPfZH18R--WMmxNdsVUwc,273
9
- phoenix/version.py,sha256=6kuemPn1sfBumZHqXy38x_dTEfi76ATKEh9dId-Cb78,23
9
+ phoenix/version.py,sha256=cwXnCkuybXvG8CJdFQgdInYSNEQ76nXEvTuAMbm7ENc,23
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
@@ -64,7 +64,7 @@ phoenix/metrics/retrieval_metrics.py,sha256=XFQPo66h16w7-1AJ92M1VL_BUIXIWxXHGKF_
64
64
  phoenix/metrics/timeseries.py,sha256=Cib3E0njJzi0vZpmyADvbakFQA98rIkfDaYAOmsmBz8,6277
65
65
  phoenix/metrics/wrappers.py,sha256=umZqa_5lf1wZSFe3FgzxF-qp1xbPdKD54W628GlGCUI,8392
66
66
  phoenix/otel/__init__.py,sha256=YvEiD-3aGZs9agwLNCXU34ofV3G-Q-dolfsiinOJuT0,407
67
- phoenix/otel/otel.py,sha256=pfFiqziSmTj8KepEZlVtjvSTd_SfNa6F8r9eqrOR6OQ,11829
67
+ phoenix/otel/otel.py,sha256=NMQ-5KtpfCVGVkRzcz7fBsjCFTTR7R7wHna-87gvxfQ,17065
68
68
  phoenix/otel/settings.py,sha256=Qr2-RkgLQRfLhJqtLpEkSpqns7qLjPoOvpEOTqeSohM,3026
69
69
  phoenix/pointcloud/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
70
70
  phoenix/pointcloud/clustering.py,sha256=IzcG67kJ2hPP7pcqVmKPSL_6gKRonKdOT3bCtbTOqnk,820
@@ -72,32 +72,33 @@ phoenix/pointcloud/pointcloud.py,sha256=4zAIkKs2xOUbchpj4XDAV-iPMXrfAJ15TG6rlIYG
72
72
  phoenix/pointcloud/projectors.py,sha256=zO_RrtDYSv2rqVOfIP2_9Cv11Dc8EmcZR94xhFcBYPU,1057
73
73
  phoenix/pointcloud/umap_parameters.py,sha256=3UQSjrysVOvq2V4KNpTMqNqNiK0BsTZnPBHWZ4fyJtQ,1708
74
74
  phoenix/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
- phoenix/server/app.py,sha256=7LiZiusw_yHZBVWMk8gH4tX8yFitTJ2jdyhMWVm2DdM,26817
75
+ phoenix/server/app.py,sha256=rnd4ps3JYRqD2DdE34TruMiismRTzFqgQrSupXLnVRU,28006
76
76
  phoenix/server/dml_event.py,sha256=MpjCFqljxvgb9OB5Cez9vJesb3oHb3XxXictynBfcis,2851
77
- phoenix/server/dml_event_handler.py,sha256=6p-PucctivelVHfO-_9zNxWZYPr_eGjDF3bKjLtc5co,8251
77
+ phoenix/server/dml_event_handler.py,sha256=yU23-DDwXcL35p5EPwFW0oZh6mxQxJrJAQPKcYZYJz4,8310
78
78
  phoenix/server/grpc_server.py,sha256=jllxDNkpLQxDkvej4RhTokobowbvydF-SU8gSw1MTCc,3378
79
- phoenix/server/main.py,sha256=KcyiOtU7pJrWASTih4huF53WizXUdjCpWSqY6glk-mA,14037
79
+ phoenix/server/main.py,sha256=OXqRIppf_AiFWBMMLIozvqRAMrJqwK7t5uFL6yQC2j8,14485
80
80
  phoenix/server/prometheus.py,sha256=j9DHB2fERuq_ZKmwVaqR-9wx5WcPPuU1Cm5Bhg5241Y,2996
81
81
  phoenix/server/telemetry.py,sha256=T_2OKrxNViAeaANlNspEekg_Y5uZIFWvKAnpz8Aoqvk,2762
82
82
  phoenix/server/thread_server.py,sha256=RwXQGP_QhGD7le6WB7xEygEEuwBl5Ck_Zo8xGIYGi9M,2135
83
83
  phoenix/server/types.py,sha256=S2dReLNboR2nzjRK5j3MUyUDqu6AQFD7KRwJkeKj1q4,3609
84
84
  phoenix/server/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
85
- phoenix/server/api/context.py,sha256=WuhGT2549C5Yc7pWj2S7NaPeT4a-N-_mmz-Vg5bUkI8,3637
85
+ phoenix/server/api/context.py,sha256=XLlRtIhBl3iEq7exrN_DaO0n-XyvuvdO5bLB4uDrQhI,3724
86
86
  phoenix/server/api/exceptions.py,sha256=KdAzgwNan-wQ7THDrSoeJU2k9zWQVcH6lRiB462VsRA,990
87
87
  phoenix/server/api/interceptor.py,sha256=ykDnoC_apUd-llVli3m1CW18kNSIgjz2qZ6m5JmPDu8,1294
88
88
  phoenix/server/api/queries.py,sha256=hUzeHOUWuBQ-kjXh13-d5LgJfkbB8XSpFaHJX4YXpC8,23875
89
89
  phoenix/server/api/schema.py,sha256=4L2m6QXhaV13YPTZCEZ3hqCPQFHZOy3QnJVLRYQFzpg,548
90
90
  phoenix/server/api/utils.py,sha256=Kl47G-1A7QKTDrc75BU2QK6HupsG6MWuXxy351FOfKQ,858
91
- phoenix/server/api/dataloaders/__init__.py,sha256=TrOGnU_SD_vEIxOE_dm8HrD5C2ScLFQ4xQ7f8r-E76s,3064
91
+ phoenix/server/api/dataloaders/__init__.py,sha256=D1xEKunv8ZUBEPk2eStX-JhoPR3_NaQcNOp47H-oChM,3161
92
92
  phoenix/server/api/dataloaders/annotation_summaries.py,sha256=Wv8AORZoGd5TJ4Y-em8iqJu87AMpZP7lWOTr-SML-x8,5560
93
93
  phoenix/server/api/dataloaders/average_experiment_run_latency.py,sha256=q091UmkXx37OBKh7L-GJ5LXHyRXfX2w4XTk1NMHtPpw,1827
94
- phoenix/server/api/dataloaders/dataset_example_revisions.py,sha256=rZhJoIYUGgYhXwVBtq5u0bqtHmIQ2Sh6HNnJsSGIXis,3767
94
+ phoenix/server/api/dataloaders/dataset_example_revisions.py,sha256=_X2CwCc6lAkD4VuwAiyo4DfSVS8uuzub05sKyKiEXm8,5149
95
95
  phoenix/server/api/dataloaders/dataset_example_spans.py,sha256=-TjdyyJv2c2JiN1OXu6MMmQ-BEKlHXucEDcuObeRVsU,1416
96
96
  phoenix/server/api/dataloaders/document_evaluation_summaries.py,sha256=5XOom2KRAmCwPmtlraiZOSl3vhfaW-eiiYkmetAEalw,5616
97
97
  phoenix/server/api/dataloaders/document_evaluations.py,sha256=V6sE34jON_qFxt7eArJbktykAsty-gnBZHlEkORcj0E,1296
98
98
  phoenix/server/api/dataloaders/document_retrieval_metrics.py,sha256=JqDqkUuoeG6WfcmWSrmQptfF6IPX8XgzYzyECXTAjgg,4202
99
99
  phoenix/server/api/dataloaders/experiment_annotation_summaries.py,sha256=qDEBRo0XKJMYBYlXBgJ-69jwPK-0r4pi9ZhbjC_vx6M,2813
100
100
  phoenix/server/api/dataloaders/experiment_error_rates.py,sha256=wWTFOO2UxGIYUu53nnzzLk04-mJxw-BQgJELA9gT5bY,1949
101
+ phoenix/server/api/dataloaders/experiment_run_annotations.py,sha256=Ey42FYy6WIASWlO-KKwHewfvH_RBL0Ci3W3xrv4oyaE,1361
101
102
  phoenix/server/api/dataloaders/experiment_run_counts.py,sha256=8VlKRaXwD56-7q_uonRr7L6QlEkyEQubkFKmKx6vuz0,1661
102
103
  phoenix/server/api/dataloaders/experiment_sequence_number.py,sha256=hSpKyOz3ES4UTfOzOfHeQ1ZXat-bzcdKybVSbYmhrBw,1563
103
104
  phoenix/server/api/dataloaders/latency_ms_quantile.py,sha256=5Y2OQ_GeH1My2573eOm7zPbqpwDZ_WeMLoZMq3KDoNQ,7403
@@ -198,7 +199,7 @@ phoenix/server/api/types/ExampleRevisionInterface.py,sha256=gV3Gt9-3Oi5wjaVtepC6
198
199
  phoenix/server/api/types/Experiment.py,sha256=YNs8SzOMSYUKiAMANuWSv8NxSNFzSIFOBOWb_yztE0s,5482
199
200
  phoenix/server/api/types/ExperimentAnnotationSummary.py,sha256=Uk3JtxIrsMoZT5tqc4nJdUOM3XegVzjUyoV3pkjNotE,256
200
201
  phoenix/server/api/types/ExperimentComparison.py,sha256=0sFz6MoBDw39dds0qVyaqhVs9qqO5rkG1FMSjmfBeCc,441
201
- phoenix/server/api/types/ExperimentRun.py,sha256=122_SID7SLKPUq2dJ2Y4BBw40DNUtcxo6QCZuO8UbBs,2997
202
+ phoenix/server/api/types/ExperimentRun.py,sha256=LkR7fBRZ3nH57ZoHJwePTGtJYcTMM-EH5r1TVPiMyxg,2687
202
203
  phoenix/server/api/types/ExperimentRunAnnotation.py,sha256=iBxDaD9DgiF-Qymp5QyxWfJRGYXM1_CeWA_qzsZBqkI,1812
203
204
  phoenix/server/api/types/ExportedFile.py,sha256=e3GTn7B5LgsTbqiwjhMCQH7VsiqXitrBO4aCMS1lHsg,163
204
205
  phoenix/server/api/types/Functionality.py,sha256=tzV9xdhB8zqfsjWxP66NDC7EZsplYkYO7jRbLWJIeeg,382
@@ -241,10 +242,10 @@ phoenix/server/static/apple-touch-icon-76x76.png,sha256=CT_xT12I0u2i0WU8JzBZBuOQ
241
242
  phoenix/server/static/apple-touch-icon.png,sha256=fOfpjqGpWYbJ0eAurKsyoZP1EAs6ZVooBJ_SGk2ZkDs,3801
242
243
  phoenix/server/static/favicon.ico,sha256=bY0vvCKRftemZfPShwZtE93DiiQdaYaozkPGwNFr6H8,34494
243
244
  phoenix/server/static/modernizr.js,sha256=mvK-XtkNqjOral-QvzoqsyOMECXIMu5BQwSVN_wcU9c,2564
244
- phoenix/server/static/.vite/manifest.json,sha256=rkEVApCRSYofJpf7L3LFtAc9R6Yv7rijaaUCY2_AnXU,1929
245
- phoenix/server/static/assets/components-CkSg5zK4.js,sha256=kMP22Fi58XDiVcsTUSkisv-SYM1E4y1R8Wp06r_pJNo,244317
246
- phoenix/server/static/assets/index-DTecsU5w.js,sha256=qQbWg4v_Z385UAuIH30xlqhF3dFNhngnauRf3sOpj4o,7515
247
- phoenix/server/static/assets/pages-C6emDFIO.js,sha256=5tmkYOe8IYM-CbQym_pAnkBv4SHEa1B1nNIqOqQ5Yzg,502735
245
+ phoenix/server/static/.vite/manifest.json,sha256=l5PYEBo0V10WvPZcDPW9uwNedEA5nmLoOrC_-ZMrU-k,1929
246
+ phoenix/server/static/assets/components-CEsu6itL.js,sha256=s16CoqiDIY0P1koerMJLXKRGckrry2j7TdFmT_tusLk,244317
247
+ phoenix/server/static/assets/index-Cuxn1Qdi.js,sha256=1j6OndkjUTZy8MlmlLOeOdoYadDgn60nBYhEN3lh-20,7515
248
+ phoenix/server/static/assets/pages-eeRVG3GZ.js,sha256=uyG6j1wduaoEtcO4HwdPrtQCQAoECeC0TiA6edyT6bk,504931
248
249
  phoenix/server/static/assets/vendor-DsnEJuEV.js,sha256=YyePucCaVO5Wke5vLm6NF6V3_rAomN7TqcBzv8QY6q0,1435199
249
250
  phoenix/server/static/assets/vendor-DxkFTwjz.css,sha256=nZrkr0u6NNElFGvpWHk9GTHeGoibCXCli1bE7mXZGZg,1816
250
251
  phoenix/server/static/assets/vendor-arizeai-DtynTLNi.js,sha256=7D2zoxiUlFzqSpK-BDO4HlhbruUMmw3_CCDYQq6Mw-4,304008
@@ -263,7 +264,7 @@ phoenix/trace/attributes.py,sha256=B_OrzVaxZwFkrAFXZyicYoIti1UdUysURsvUS2GyW1U,1
263
264
  phoenix/trace/errors.py,sha256=wB1z8qdPckngdfU-TORToekvg3344oNFAA83_hC2yFY,180
264
265
  phoenix/trace/evaluation_conventions.py,sha256=t8jydM3U0-T5YpiQKRJ3tWdWGlHtzKyttYdw-ddvPOk,1048
265
266
  phoenix/trace/exporter.py,sha256=eAYemdvDCHMugDJiaR29BFFMTQBdf3oerdkz34Cl3hE,4736
266
- phoenix/trace/fixtures.py,sha256=EHfqgvPoux6KkckX00WeG2Vhas8H5vqqFBMTztwgV-s,16857
267
+ phoenix/trace/fixtures.py,sha256=YsKJJEnqo1f_yJzVrfQnnrmS0LlQl7k37lbLY2pZ7ds,17856
267
268
  phoenix/trace/otel.py,sha256=WA720jvRadiZBAKjsYoPyXzypHwbyEK2OZRVUwtbjB8,9976
268
269
  phoenix/trace/projects.py,sha256=2BwlNjFE-uwpqYtCu5YyBiYZk9wRPpM13vh3-Cv7GkA,2157
269
270
  phoenix/trace/schemas.py,sha256=HpWSyzec0yDHEQXEDuwyLbhpvKrqkGps8BJqGiIFj8Y,5978
@@ -271,7 +272,7 @@ phoenix/trace/span_evaluations.py,sha256=GaADtJLi2njra4aYaie0BIwkSgdxPB_SNseglI4
271
272
  phoenix/trace/span_json_decoder.py,sha256=jkpYSmOUsSptvahOvetrPjVkPIuI4h6b87eizL8wHp8,3230
272
273
  phoenix/trace/span_json_encoder.py,sha256=tzSCIQJbeFBm33K68G8A5M12n_86tCDyuU0WAobxEz4,2010
273
274
  phoenix/trace/trace_dataset.py,sha256=Wq89jJ4hYQ1Qt-Uj11ZNzKQYQeKmGY6NqWStQiiTlMw,14351
274
- phoenix/trace/utils.py,sha256=1SEQr37cdHOM0P3BdL1dszArj3Zm-VJQyb1BcJs_qO8,1833
275
+ phoenix/trace/utils.py,sha256=lWinMM7Tsq_DCsBD5Ck97lxouBHrY4muGNsh8JD3qg4,1694
275
276
  phoenix/trace/dsl/README.md,sha256=ihmP9zGUC5V-TDbzKla76LuyDqPDQIBUH2BORwxNI68,2902
276
277
  phoenix/trace/dsl/__init__.py,sha256=WIQIjJg362XD3s50OsPJJ0xbDsGp41bSv7vDllLrPuA,144
277
278
  phoenix/trace/dsl/filter.py,sha256=9NwATCUOgJ4Pms8XsEcinROUuxZ9UW-ISV09o65Ms70,32600
@@ -295,8 +296,8 @@ phoenix/utilities/logging.py,sha256=lDXd6EGaamBNcQxL4vP1au9-i_SXe0OraUDiJOcszSw,
295
296
  phoenix/utilities/project.py,sha256=8IJuMM4yUMoooPi37sictGj8Etu9rGmq6RFtc9848cQ,436
296
297
  phoenix/utilities/re.py,sha256=PDve_OLjRTM8yQQJHC8-n3HdIONi7aNils3ZKRZ5uBM,2045
297
298
  phoenix/utilities/span_store.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
298
- arize_phoenix-4.30.1.dist-info/METADATA,sha256=dygvZQ4Y3cjF7a80jliAm0DoupHUB_N3STUVDRmL-D4,11977
299
- arize_phoenix-4.30.1.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
300
- arize_phoenix-4.30.1.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
301
- arize_phoenix-4.30.1.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
302
- arize_phoenix-4.30.1.dist-info/RECORD,,
299
+ arize_phoenix-4.31.0.dist-info/METADATA,sha256=yKHi2ksTlU_0xsldV4ozw7XATWGBRaS8w18wnaLElJE,11977
300
+ arize_phoenix-4.31.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
301
+ arize_phoenix-4.31.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
302
+ arize_phoenix-4.31.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
303
+ arize_phoenix-4.31.0.dist-info/RECORD,,
phoenix/otel/otel.py CHANGED
@@ -51,9 +51,10 @@ def register(
51
51
  not provided, the `PHOENIX_PROJECT_NAME` environment variable will be used.
52
52
  batch (bool): If True, spans will be processed using a BatchSpanprocessor. If False, spans
53
53
  will be processed one at a time using a SimpleSpanProcessor.
54
- set_global_tracer_provider (bool): If False, the TracerProvider will not be set as the global
55
- tracer provider. Defaults to True.
56
- headers (dict, optional): Optional headers to include in the HTTP request to the collector.
54
+ set_global_tracer_provider (bool): If False, the TracerProvider will not be set as the
55
+ global tracer provider. Defaults to True.
56
+ headers (dict, optional): Optional headers to include in the request to the collector.
57
+ If not provided, the `PHOENIX_CLIENT_HEADERS` environment variable will be used.
57
58
  verbose (bool): If True, configuration details will be printed to stdout.
58
59
  """
59
60
 
@@ -73,7 +74,8 @@ def register(
73
74
  global_provider_msg = (
74
75
  "| \n"
75
76
  "| `register` has set this TracerProvider as the global OpenTelemetry default.\n"
76
- "| To disable this behavior, call `register` with `set_global_tracer_provider=False`.\n"
77
+ "| To disable this behavior, call `register` with "
78
+ "`set_global_tracer_provider=False`.\n"
77
79
  )
78
80
  else:
79
81
  global_provider_msg = ""
@@ -85,6 +87,22 @@ def register(
85
87
 
86
88
 
87
89
  class TracerProvider(_TracerProvider):
90
+ """
91
+ An extension of `opentelemetry.sdk.trace.TracerProvider` with Phoenix-aware defaults.
92
+
93
+ Extended keyword arguments are documented in the `Args` section. For further documentation, see
94
+ the OpenTelemetry documentation at https://opentelemetry.io/docs/specs/otel/trace/sdk/.
95
+
96
+ Args:
97
+ endpoint (str, optional): The collector endpoint to which spans will be exported. If
98
+ specified, a default SpanProcessor will be created and added to this TracerProvider.
99
+ If not provided, the `PHOENIX_OTEL_COLLECTOR_ENDPOINT` environment variable will be
100
+ used to infer which collector endpoint to use, defaults to the gRPC endpoint. When
101
+ specifying the endpoint, the transport method (HTTP or gRPC) will be inferred from the
102
+ URL.
103
+ verbose (bool): If True, configuration details will be printed to stdout.
104
+ """
105
+
88
106
  def __init__(
89
107
  self, *args: Any, endpoint: Optional[str] = None, verbose: bool = True, **kwargs: Any
90
108
  ):
@@ -112,6 +130,12 @@ class TracerProvider(_TracerProvider):
112
130
  print(self._tracing_details())
113
131
 
114
132
  def add_span_processor(self, *args: Any, **kwargs: Any) -> None:
133
+ """
134
+ Registers a new `SpanProcessor` for this `TracerProvider`.
135
+
136
+ If this `TracerProvider` has a default processor, it will be removed.
137
+ """
138
+
115
139
  if self._default_processor:
116
140
  self._active_span_processor.shutdown()
117
141
  self._active_span_processor._span_processors = tuple() # remove default processors
@@ -163,6 +187,24 @@ class TracerProvider(_TracerProvider):
163
187
 
164
188
 
165
189
  class SimpleSpanProcessor(_SimpleSpanProcessor):
190
+ """
191
+ Simple SpanProcessor implementation.
192
+
193
+ SimpleSpanProcessor is an implementation of `SpanProcessor` that passes ended spans directly to
194
+ the configured `SpanExporter`.
195
+
196
+ Args:
197
+ span_exporter (SpanExporter, optional): The `SpanExporter` to which ended spans will be
198
+ passed.
199
+ endpoint (str, optional): The collector endpoint to which spans will be exported. If not
200
+ provided, the `PHOENIX_OTEL_COLLECTOR_ENDPOINT` environment variable will be used to
201
+ infer which collector endpoint to use, defaults to the gRPC endpoint. When specifying
202
+ the endpoint, the transport method (HTTP or gRPC) will be inferred from the URL.
203
+ headers (dict, optional): Optional headers to include in the request to the collector.
204
+ If not provided, the `PHOENIX_CLIENT_HEADERS` or `OTEL_EXPORTER_OTLP_HEADERS`
205
+ environment variable will be used.
206
+ """
207
+
166
208
  def __init__(
167
209
  self,
168
210
  span_exporter: Optional[SpanExporter] = None,
@@ -182,6 +224,37 @@ class SimpleSpanProcessor(_SimpleSpanProcessor):
182
224
 
183
225
 
184
226
  class BatchSpanProcessor(_BatchSpanProcessor):
227
+ """
228
+ Batch SpanProcessor implementation.
229
+
230
+ `BatchSpanProcessor` is an implementation of `SpanProcessor` that batches ended spans and
231
+ pushes them to the configured `SpanExporter`.
232
+
233
+ `BatchSpanProcessor` is configurable with the following environment variables which correspond
234
+ to constructor parameters:
235
+
236
+ - :envvar:`OTEL_BSP_SCHEDULE_DELAY`
237
+ - :envvar:`OTEL_BSP_MAX_QUEUE_SIZE`
238
+ - :envvar:`OTEL_BSP_MAX_EXPORT_BATCH_SIZE`
239
+ - :envvar:`OTEL_BSP_EXPORT_TIMEOUT`
240
+
241
+ Args:
242
+ span_exporter (SpanExporter, optional): The `SpanExporter` to which ended spans will be
243
+ passed.
244
+ endpoint (str, optional): The collector endpoint to which spans will be exported. If not
245
+ provided, the `PHOENIX_OTEL_COLLECTOR_ENDPOINT` environment variable will be used to
246
+ infer which collector endpoint to use, defaults to the gRPC endpoint. When specifying
247
+ the endpoint, the transport method (HTTP or gRPC) will be inferred from the URL.
248
+ headers (dict, optional): Optional headers to include in the request to the collector.
249
+ If not provided, the `PHOENIX_CLIENT_HEADERS` or `OTEL_EXPORTER_OTLP_HEADERS`
250
+ environment variable will be used.
251
+ max_queue_size (int, optional): The maximum queue size.
252
+ schedule_delay_millis (float, optional): The delay between two consecutive exports in
253
+ milliseconds.
254
+ max_export_batch_size (int, optional): The maximum batch size.
255
+ export_timeout_millis (float, optional): The batch timeout in milliseconds.
256
+ """
257
+
185
258
  def __init__(
186
259
  self,
187
260
  span_exporter: Optional[SpanExporter] = None,
@@ -201,6 +274,20 @@ class BatchSpanProcessor(_BatchSpanProcessor):
201
274
 
202
275
 
203
276
  class HTTPSpanExporter(_HTTPSpanExporter):
277
+ """
278
+ OTLP span exporter using HTTP.
279
+
280
+ For more information, see:
281
+ - `opentelemetry.exporter.otlp.proto.http.trace_exporter.OTLPSpanExporter`
282
+
283
+ Args:
284
+ endpoint (str, optional): OpenTelemetry Collector receiver endpoint. If not provided, the
285
+ `PHOENIX_OTEL_COLLECTOR_ENDPOINT` environment variable will be used to infer which
286
+ collector endpoint to use, defaults to the HTTP endpoint.
287
+ headers: Headers to send when exporting. If not provided, the `PHOENIX_CLIENT_HEADERS`
288
+ or `OTEL_EXPORTER_OTLP_HEADERS` environment variables will be used.
289
+ """
290
+
204
291
  def __init__(self, *args: Any, **kwargs: Any):
205
292
  sig = inspect.signature(_HTTPSpanExporter)
206
293
  bound_args = sig.bind_partial(*args, **kwargs)
@@ -210,12 +297,30 @@ class HTTPSpanExporter(_HTTPSpanExporter):
210
297
  bound_args.arguments["headers"] = get_env_client_headers()
211
298
 
212
299
  if bound_args.arguments.get("endpoint") is None:
213
- _, endpoint = _normalized_endpoint(None)
300
+ _, endpoint = _normalized_endpoint(None, use_http=True)
214
301
  bound_args.arguments["endpoint"] = endpoint
215
302
  super().__init__(**bound_args.arguments)
216
303
 
217
304
 
218
305
  class GRPCSpanExporter(_GRPCSpanExporter):
306
+ """
307
+ OTLP span exporter using gRPC.
308
+
309
+ For more information, see:
310
+ - `opentelemetry.exporter.otlp.proto.grpc.trace_exporter.OTLPSpanExporter`
311
+
312
+ Args:
313
+ endpoint (str, optional): OpenTelemetry Collector receiver endpoint. If not provided, the
314
+ `PHOENIX_OTEL_COLLECTOR_ENDPOINT` environment variable will be used to infer which
315
+ collector endpoint to use, defaults to the gRPC endpoint.
316
+ insecure: Connection type
317
+ credentials: Credentials object for server authentication
318
+ headers: Headers to send when exporting. If not provided, the `PHOENIX_CLIENT_HEADERS`
319
+ or `OTEL_EXPORTER_OTLP_HEADERS` environment variables will be used.
320
+ timeout: Backend request timeout in seconds
321
+ compression: gRPC compression method to use
322
+ """
323
+
219
324
  def __init__(self, *args: Any, **kwargs: Any):
220
325
  sig = inspect.signature(_GRPCSpanExporter)
221
326
  bound_args = sig.bind_partial(*args, **kwargs)
@@ -270,12 +375,16 @@ _KNOWN_PROVIDERS = {
270
375
  }
271
376
 
272
377
 
273
- def _normalized_endpoint(endpoint: Optional[str]) -> Tuple[ParseResult, str]:
378
+ def _normalized_endpoint(
379
+ endpoint: Optional[str], use_http: bool = False
380
+ ) -> Tuple[ParseResult, str]:
274
381
  if endpoint is None:
275
382
  base_endpoint = get_env_collector_endpoint() or "http://localhost:6006"
276
383
  parsed = urlparse(base_endpoint)
277
384
  if parsed.hostname in _KNOWN_PROVIDERS:
278
385
  parsed = _KNOWN_PROVIDERS[parsed.hostname](parsed)
386
+ elif use_http:
387
+ parsed = _construct_http_endpoint(parsed)
279
388
  else:
280
389
  parsed = _construct_grpc_endpoint(parsed)
281
390
  else:
@@ -17,6 +17,7 @@ from phoenix.server.api.dataloaders import (
17
17
  DocumentRetrievalMetricsDataLoader,
18
18
  ExperimentAnnotationSummaryDataLoader,
19
19
  ExperimentErrorRatesDataLoader,
20
+ ExperimentRunAnnotations,
20
21
  ExperimentRunCountsDataLoader,
21
22
  ExperimentSequenceNumberDataLoader,
22
23
  LatencyMsQuantileDataLoader,
@@ -45,6 +46,7 @@ class DataLoaders:
45
46
  annotation_summaries: AnnotationSummaryDataLoader
46
47
  experiment_annotation_summaries: ExperimentAnnotationSummaryDataLoader
47
48
  experiment_error_rates: ExperimentErrorRatesDataLoader
49
+ experiment_run_annotations: ExperimentRunAnnotations
48
50
  experiment_run_counts: ExperimentRunCountsDataLoader
49
51
  experiment_sequence_number: ExperimentSequenceNumberDataLoader
50
52
  latency_ms_quantile: LatencyMsQuantileDataLoader
@@ -12,6 +12,7 @@ from .document_evaluations import DocumentEvaluationsDataLoader
12
12
  from .document_retrieval_metrics import DocumentRetrievalMetricsDataLoader
13
13
  from .experiment_annotation_summaries import ExperimentAnnotationSummaryDataLoader
14
14
  from .experiment_error_rates import ExperimentErrorRatesDataLoader
15
+ from .experiment_run_annotations import ExperimentRunAnnotations
15
16
  from .experiment_run_counts import ExperimentRunCountsDataLoader
16
17
  from .experiment_sequence_number import ExperimentSequenceNumberDataLoader
17
18
  from .latency_ms_quantile import LatencyMsQuantileCache, LatencyMsQuantileDataLoader
@@ -36,6 +37,7 @@ __all__ = [
36
37
  "AnnotationSummaryDataLoader",
37
38
  "ExperimentAnnotationSummaryDataLoader",
38
39
  "ExperimentErrorRatesDataLoader",
40
+ "ExperimentRunAnnotations",
39
41
  "ExperimentRunCountsDataLoader",
40
42
  "ExperimentSequenceNumberDataLoader",
41
43
  "LatencyMsQuantileDataLoader",
@@ -5,7 +5,8 @@ from typing import (
5
5
  Union,
6
6
  )
7
7
 
8
- from sqlalchemy import Integer, case, func, literal, or_, select, union
8
+ from sqlalchemy import and_, case, func, null, or_, select
9
+ from sqlalchemy.sql.expression import literal
9
10
  from strawberry.dataloader import DataLoader
10
11
  from typing_extensions import TypeAlias
11
12
 
@@ -22,40 +23,76 @@ Result: TypeAlias = DatasetExampleRevision
22
23
 
23
24
  class DatasetExampleRevisionsDataLoader(DataLoader[Key, Result]):
24
25
  def __init__(self, db: DbSessionFactory) -> None:
25
- super().__init__(load_fn=self._load_fn)
26
+ super().__init__(
27
+ load_fn=self._load_fn,
28
+ max_batch_size=200, # needed to prevent the size of the query from getting too large
29
+ )
26
30
  self._db = db
27
31
 
28
32
  async def _load_fn(self, keys: List[Key]) -> List[Union[Result, NotFound]]:
29
- # sqlalchemy has limited SQLite support for VALUES, so use UNION ALL instead.
30
- # For details, see https://github.com/sqlalchemy/sqlalchemy/issues/7228
31
- keys_subquery = union(
32
- *(
33
+ example_and_version_ids = tuple(
34
+ set(
35
+ (example_id, version_id)
36
+ for example_id, version_id in keys
37
+ if version_id is not None
38
+ )
39
+ )
40
+ versionless_example_ids = tuple(
41
+ set(example_id for example_id, version_id in keys if version_id is None)
42
+ )
43
+ resolved_example_and_version_ids = (
44
+ (
33
45
  select(
34
- literal(example_id, Integer).label("example_id"),
35
- literal(version_id, Integer).label("version_id"),
46
+ models.DatasetExample.id.label("example_id"),
47
+ models.DatasetVersion.id.label("version_id"),
48
+ )
49
+ .select_from(models.DatasetExample)
50
+ .join(
51
+ models.DatasetVersion,
52
+ onclause=literal(True), # cross join
53
+ )
54
+ .where(
55
+ or_(
56
+ *(
57
+ and_(
58
+ models.DatasetExample.id == example_id,
59
+ models.DatasetVersion.id == version_id,
60
+ )
61
+ for example_id, version_id in example_and_version_ids
62
+ )
63
+ )
36
64
  )
37
- for example_id, version_id in keys
38
65
  )
39
- ).subquery()
66
+ .union(
67
+ select(
68
+ models.DatasetExample.id.label("example_id"), null().label("version_id")
69
+ ).where(models.DatasetExample.id.in_(versionless_example_ids))
70
+ )
71
+ .subquery()
72
+ )
40
73
  revision_ids = (
41
74
  select(
42
- keys_subquery.c.example_id,
43
- keys_subquery.c.version_id,
75
+ resolved_example_and_version_ids.c.example_id,
76
+ resolved_example_and_version_ids.c.version_id,
44
77
  func.max(models.DatasetExampleRevision.id).label("revision_id"),
45
78
  )
46
- .select_from(keys_subquery)
79
+ .select_from(resolved_example_and_version_ids)
47
80
  .join(
48
81
  models.DatasetExampleRevision,
49
- onclause=keys_subquery.c.example_id
82
+ onclause=resolved_example_and_version_ids.c.example_id
50
83
  == models.DatasetExampleRevision.dataset_example_id,
51
84
  )
52
85
  .where(
53
86
  or_(
54
- keys_subquery.c.version_id.is_(None),
55
- models.DatasetExampleRevision.dataset_version_id <= keys_subquery.c.version_id,
87
+ resolved_example_and_version_ids.c.version_id.is_(None),
88
+ models.DatasetExampleRevision.dataset_version_id
89
+ <= resolved_example_and_version_ids.c.version_id,
56
90
  )
57
91
  )
58
- .group_by(keys_subquery.c.example_id, keys_subquery.c.version_id)
92
+ .group_by(
93
+ resolved_example_and_version_ids.c.example_id,
94
+ resolved_example_and_version_ids.c.version_id,
95
+ )
59
96
  ).subquery()
60
97
  query = (
61
98
  select(
@@ -0,0 +1,40 @@
1
+ from collections import defaultdict
2
+ from typing import (
3
+ DefaultDict,
4
+ List,
5
+ )
6
+
7
+ from sqlalchemy import select
8
+ from strawberry.dataloader import DataLoader
9
+ from typing_extensions import TypeAlias
10
+
11
+ from phoenix.db.models import ExperimentRunAnnotation as OrmExperimentRunAnnotation
12
+ from phoenix.server.types import DbSessionFactory
13
+
14
+ ExperimentRunID: TypeAlias = int
15
+ Key: TypeAlias = ExperimentRunID
16
+ Result: TypeAlias = List[OrmExperimentRunAnnotation]
17
+
18
+
19
+ class ExperimentRunAnnotations(DataLoader[Key, Result]):
20
+ def __init__(
21
+ self,
22
+ db: DbSessionFactory,
23
+ ) -> None:
24
+ super().__init__(load_fn=self._load_fn)
25
+ self._db = db
26
+
27
+ async def _load_fn(self, keys: List[Key]) -> List[Result]:
28
+ run_ids = tuple(set(keys))
29
+ annotations: DefaultDict[Key, Result] = defaultdict(list)
30
+ async with self._db() as session:
31
+ async for run_id, annotation in await session.stream(
32
+ select(
33
+ OrmExperimentRunAnnotation.experiment_run_id, OrmExperimentRunAnnotation
34
+ ).where(OrmExperimentRunAnnotation.experiment_run_id.in_(run_ids))
35
+ ):
36
+ annotations[run_id].append(annotation)
37
+ return [
38
+ sorted(annotations[run_id], key=lambda annotation: annotation.name, reverse=True)
39
+ for run_id in run_ids
40
+ ]
@@ -2,7 +2,6 @@ from datetime import datetime
2
2
  from typing import Optional
3
3
 
4
4
  import strawberry
5
- from sqlalchemy import select
6
5
  from strawberry import UNSET
7
6
  from strawberry.relay import Connection, GlobalID, Node, NodeID
8
7
  from strawberry.scalars import JSON
@@ -48,14 +47,7 @@ class ExperimentRun(Node):
48
47
  before=before if isinstance(before, CursorString) else None,
49
48
  )
50
49
  run_id = self.id_attr
51
- async with info.context.db() as session:
52
- annotations = (
53
- await session.scalars(
54
- select(models.ExperimentRunAnnotation)
55
- .where(models.ExperimentRunAnnotation.experiment_run_id == run_id)
56
- .order_by(models.ExperimentRunAnnotation.name.desc())
57
- )
58
- ).all()
50
+ annotations = await info.context.data_loaders.experiment_run_annotations.load(run_id)
59
51
  return connection_from_list(
60
52
  [to_gql_experiment_run_annotation(annotation) for annotation in annotations], args
61
53
  )
phoenix/server/app.py CHANGED
@@ -46,7 +46,13 @@ from typing_extensions import TypeAlias
46
46
 
47
47
  import phoenix
48
48
  import phoenix.trace.v1 as pb
49
- from phoenix.config import DEFAULT_PROJECT_NAME, SERVER_DIR, server_instrumentation_is_enabled
49
+ from phoenix.config import (
50
+ DEFAULT_PROJECT_NAME,
51
+ SERVER_DIR,
52
+ get_env_host,
53
+ get_env_port,
54
+ server_instrumentation_is_enabled,
55
+ )
50
56
  from phoenix.core.model_schema import Model
51
57
  from phoenix.db import models
52
58
  from phoenix.db.bulk_inserter import BulkInserter
@@ -66,6 +72,7 @@ from phoenix.server.api.dataloaders import (
66
72
  DocumentRetrievalMetricsDataLoader,
67
73
  ExperimentAnnotationSummaryDataLoader,
68
74
  ExperimentErrorRatesDataLoader,
75
+ ExperimentRunAnnotations,
69
76
  ExperimentRunCountsDataLoader,
70
77
  ExperimentSequenceNumberDataLoader,
71
78
  LatencyMsQuantileDataLoader,
@@ -94,10 +101,13 @@ from phoenix.server.types import (
94
101
  LastUpdatedAt,
95
102
  )
96
103
  from phoenix.trace.fixtures import (
104
+ TracesFixture,
105
+ get_dataset_fixtures,
97
106
  get_evals_from_fixture,
98
107
  get_trace_fixture_by_name,
99
108
  load_example_traces,
100
109
  reset_fixture_span_ids_and_timestamps,
110
+ send_dataset_fixtures,
101
111
  )
102
112
  from phoenix.trace.otel import decode_otlp_span, encode_span_to_otlp
103
113
  from phoenix.trace.schemas import Span
@@ -249,6 +259,7 @@ class Scaffolder(DaemonTask):
249
259
  queue_evaluation: Callable[[pb.Evaluation], Awaitable[None]],
250
260
  tracing_fixture_names: Set[str] = set(),
251
261
  force_fixture_ingestion: bool = False,
262
+ scaffold_datasets: bool = False,
252
263
  ) -> None:
253
264
  super().__init__()
254
265
  self._db = db
@@ -258,6 +269,7 @@ class Scaffolder(DaemonTask):
258
269
  get_trace_fixture_by_name(name) for name in tracing_fixture_names
259
270
  )
260
271
  self._force_fixture_ingestion = force_fixture_ingestion
272
+ self._scaffold_datasets = scaffold_datasets
261
273
 
262
274
  async def __aenter__(self) -> None:
263
275
  await self.start()
@@ -271,7 +283,7 @@ class Scaffolder(DaemonTask):
271
283
  Determines whether to load fixtures and handles them.
272
284
  """
273
285
  if await self._should_load_fixtures():
274
- logger.info("Loading trace fixtures.")
286
+ logger.info("Loading trace fixtures...")
275
287
  await self._handle_tracing_fixtures()
276
288
  logger.info("Finished loading fixtures.")
277
289
  else:
@@ -316,6 +328,10 @@ class Scaffolder(DaemonTask):
316
328
  get_evals_from_fixture(fixture.name),
317
329
  )
318
330
 
331
+ # Ingest dataset fixtures
332
+ if self._scaffold_datasets:
333
+ await self._handle_dataset_fixtures(fixture)
334
+
319
335
  project_name = fixture.project_name or fixture.name
320
336
  logger.info(f"Loading '{project_name}' fixtures...")
321
337
  for span in fixture_spans:
@@ -330,6 +346,19 @@ class Scaffolder(DaemonTask):
330
346
  except Exception as e:
331
347
  logger.error(f"Unexpected error processing fixture '{fixture.name}': {e}")
332
348
 
349
+ async def _handle_dataset_fixtures(self, fixture: TracesFixture) -> None:
350
+ loop = asyncio.get_running_loop()
351
+ try:
352
+ dataset_fixtures = await loop.run_in_executor(None, get_dataset_fixtures, fixture.name)
353
+ await loop.run_in_executor(
354
+ None,
355
+ send_dataset_fixtures,
356
+ f"http://{get_env_host()}:{get_env_port()}",
357
+ dataset_fixtures,
358
+ )
359
+ except Exception as e:
360
+ logger.error(f"Error processing dataset fixture: {e}")
361
+
333
362
 
334
363
  def _lifespan(
335
364
  *,
@@ -343,6 +372,7 @@ def _lifespan(
343
372
  read_only: bool = False,
344
373
  tracing_fixture_names: Set[str] = set(),
345
374
  force_fixture_ingestion: bool = False,
375
+ scaffold_datasets: bool = False,
346
376
  ) -> StatefulLifespan[FastAPI]:
347
377
  @contextlib.asynccontextmanager
348
378
  async def lifespan(_: FastAPI) -> AsyncIterator[Dict[str, Any]]:
@@ -364,6 +394,7 @@ def _lifespan(
364
394
  queue_evaluation=queue_evaluation,
365
395
  tracing_fixture_names=tracing_fixture_names,
366
396
  force_fixture_ingestion=force_fixture_ingestion,
397
+ scaffold_datasets=scaffold_datasets,
367
398
  ):
368
399
  for callback in startup_callbacks:
369
400
  callback()
@@ -446,6 +477,7 @@ def create_graphql_router(
446
477
  ),
447
478
  experiment_annotation_summaries=ExperimentAnnotationSummaryDataLoader(db),
448
479
  experiment_error_rates=ExperimentErrorRatesDataLoader(db),
480
+ experiment_run_annotations=ExperimentRunAnnotations(db),
449
481
  experiment_run_counts=ExperimentRunCountsDataLoader(db),
450
482
  experiment_sequence_number=ExperimentSequenceNumberDataLoader(db),
451
483
  latency_ms_quantile=LatencyMsQuantileDataLoader(
@@ -557,6 +589,7 @@ def create_app(
557
589
  secret: Optional[str] = None,
558
590
  tracing_fixture_names: Set[str] = set(),
559
591
  force_fixture_ingestion: bool = False,
592
+ scaffold_datasets: bool = False,
560
593
  ) -> FastAPI:
561
594
  startup_callbacks_list: List[Callable[[], None]] = list(startup_callbacks)
562
595
  shutdown_callbacks_list: List[Callable[[], None]] = list(shutdown_callbacks)
@@ -644,6 +677,7 @@ def create_app(
644
677
  startup_callbacks=startup_callbacks_list,
645
678
  tracing_fixture_names=tracing_fixture_names,
646
679
  force_fixture_ingestion=force_fixture_ingestion,
680
+ scaffold_datasets=scaffold_datasets,
647
681
  ),
648
682
  middleware=[
649
683
  Middleware(HeadersMiddleware),
@@ -186,6 +186,7 @@ class _AnnotationDmlEventHandler(
186
186
  async def __call__(self) -> None:
187
187
  async with self._db() as session:
188
188
  async for row in await session.stream(self._get_stmt()):
189
+ self._last_updated_at.set(Project, row.id)
189
190
  if cache := self._cache_for_dataloaders:
190
191
  self._clear(cache, row.id, row.name)
191
192