arize-phoenix 4.26.0__py3-none-any.whl → 4.27.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.26.0
3
+ Version: 4.27.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
@@ -45,8 +45,7 @@ Requires-Dist: pydantic!=2.0.*,<3,>=1.0
45
45
  Requires-Dist: pyjwt
46
46
  Requires-Dist: python-multipart
47
47
  Requires-Dist: scikit-learn
48
- Requires-Dist: scipy!=1.14.1; platform_system == 'Darwin'
49
- Requires-Dist: scipy; platform_system != 'Darwin'
48
+ Requires-Dist: scipy
50
49
  Requires-Dist: sqlalchemy[asyncio]<3,>=2.0.4
51
50
  Requires-Dist: sqlean-py>=3.45.1
52
51
  Requires-Dist: starlette
@@ -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=4h0uTKk4f4HhC84z3Ggthi61qR_IkvFgq-cnwxm5tCU,23
9
+ phoenix/version.py,sha256=3tdrXCYXhzGl0HhTFxiRhMv5mTezDVgvqXVYnKeIJeo,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
@@ -51,7 +51,7 @@ phoenix/experiments/evaluators/llm_evaluators.py,sha256=zyGhxXBDNi1qoj_8I95PRSwj
51
51
  phoenix/experiments/evaluators/utils.py,sha256=XYqB0bOljyR0GewmR_mm9Ndl_q95EkjjDqfXd7YVqTk,9303
52
52
  phoenix/inferences/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
53
  phoenix/inferences/errors.py,sha256=cGp9vxnw4SewFoWBV3ZGMkhE0Kh73lPIv3Ppz_H_RoA,8261
54
- phoenix/inferences/fixtures.py,sha256=r5mN8I58rirO2YqHr_88G2aRC0ADYLSKozgxEG89MTM,20833
54
+ phoenix/inferences/fixtures.py,sha256=oTtfjkI9ULxQ9bKtt91QGSDd3eyJ6T1ZylGiJf1iueo,20892
55
55
  phoenix/inferences/inferences.py,sha256=r-ByeW_AU6cu199iJMn_Td3XywqtRfrLS7cDuHaayUA,31147
56
56
  phoenix/inferences/schema.py,sha256=UYej9IJ6pFeNW3fq721kJy16ONso_xVDm78Q68G4hl4,6643
57
57
  phoenix/inferences/validation.py,sha256=fdmbsjUBwtacRiVFdh9aem-QrgPfq_OlEmPdascWluc,8297
@@ -69,11 +69,11 @@ phoenix/pointcloud/pointcloud.py,sha256=4zAIkKs2xOUbchpj4XDAV-iPMXrfAJ15TG6rlIYG
69
69
  phoenix/pointcloud/projectors.py,sha256=zO_RrtDYSv2rqVOfIP2_9Cv11Dc8EmcZR94xhFcBYPU,1057
70
70
  phoenix/pointcloud/umap_parameters.py,sha256=3UQSjrysVOvq2V4KNpTMqNqNiK0BsTZnPBHWZ4fyJtQ,1708
71
71
  phoenix/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
- phoenix/server/app.py,sha256=KcxSXDR_awZlIcJg4YkUTZIXlje85_dpdMB2OM-M7SQ,22059
72
+ phoenix/server/app.py,sha256=rPzpaEpTSViIP-RLbHzYfxAf7zplKOEbFHxCb40YXFc,26954
73
73
  phoenix/server/dml_event.py,sha256=MpjCFqljxvgb9OB5Cez9vJesb3oHb3XxXictynBfcis,2851
74
74
  phoenix/server/dml_event_handler.py,sha256=6p-PucctivelVHfO-_9zNxWZYPr_eGjDF3bKjLtc5co,8251
75
75
  phoenix/server/grpc_server.py,sha256=jllxDNkpLQxDkvej4RhTokobowbvydF-SU8gSw1MTCc,3378
76
- phoenix/server/main.py,sha256=rcXBEcSpnwFHwTZcMIu3s8Y7ABCMEZYJ0xb8wKtBfL0,11731
76
+ phoenix/server/main.py,sha256=KcyiOtU7pJrWASTih4huF53WizXUdjCpWSqY6glk-mA,14037
77
77
  phoenix/server/prometheus.py,sha256=j9DHB2fERuq_ZKmwVaqR-9wx5WcPPuU1Cm5Bhg5241Y,2996
78
78
  phoenix/server/telemetry.py,sha256=T_2OKrxNViAeaANlNspEekg_Y5uZIFWvKAnpz8Aoqvk,2762
79
79
  phoenix/server/thread_server.py,sha256=RwXQGP_QhGD7le6WB7xEygEEuwBl5Ck_Zo8xGIYGi9M,2135
@@ -160,11 +160,11 @@ phoenix/server/api/routers/v1/datasets.py,sha256=l3Hlc9AVyvX5GdT9iOXBsV-i4c_vtnC
160
160
  phoenix/server/api/routers/v1/evaluations.py,sha256=FSfz9MTi8s65F07abDXlb9-y97fDZSYbqsCXpimwO7g,12628
161
161
  phoenix/server/api/routers/v1/experiment_evaluations.py,sha256=RTQnjupjmh07xowjq77ajbuAZhzIEfYxA4ZtECvGwOU,4844
162
162
  phoenix/server/api/routers/v1/experiment_runs.py,sha256=0G7GgGcZv9dzK47tsPp-p4k5O7W4F_aNRrsNuJN7mho,6393
163
- phoenix/server/api/routers/v1/experiments.py,sha256=GeT3Rya4bdaCr6sCf2Vx6fQ_gfMX5XyFHmODCSJiCfU,9951
163
+ phoenix/server/api/routers/v1/experiments.py,sha256=3u275sGuYSiMyzC_obbjK3mf6aYb7SkY2c_wOg3z4xg,11751
164
164
  phoenix/server/api/routers/v1/pydantic_compat.py,sha256=FeK8oe2brqu-djsoqRxiKL4tw5cHmi89OHVfCFxYsAo,2890
165
165
  phoenix/server/api/routers/v1/spans.py,sha256=MAkMLrONFtItQxkHJde_Wpvz0jsgydegxVZOkZkRUsU,8781
166
166
  phoenix/server/api/routers/v1/traces.py,sha256=HJDmYKMATL40dZEJro6uQ3imbCZBzk3nUun9d21jcDs,7799
167
- phoenix/server/api/routers/v1/utils.py,sha256=xvl2v-BKUkqmFVMmgmmWGFKuRBTrUdoiAeT3mCYEE68,3086
167
+ phoenix/server/api/routers/v1/utils.py,sha256=ph2tC3crWewKhzM2JnX-gAelEHfGLxZeFKXHVWrddmI,3086
168
168
  phoenix/server/api/types/Annotation.py,sha256=7Ym7iuVcbwHlw2yIRylz4nATAF_Cm-Z17qcjiooj1cc,751
169
169
  phoenix/server/api/types/AnnotationSummary.py,sha256=8B2LIROqcrPOi8hvYygsblKvSEBfSrysnKOV7F36hgA,1518
170
170
  phoenix/server/api/types/AnnotatorKind.py,sha256=rPgGdbN1Gvc109sGQ_ZH-gfJbp93V9wlarzTEJNtUwI,236
@@ -237,10 +237,10 @@ phoenix/server/static/apple-touch-icon-76x76.png,sha256=CT_xT12I0u2i0WU8JzBZBuOQ
237
237
  phoenix/server/static/apple-touch-icon.png,sha256=fOfpjqGpWYbJ0eAurKsyoZP1EAs6ZVooBJ_SGk2ZkDs,3801
238
238
  phoenix/server/static/favicon.ico,sha256=bY0vvCKRftemZfPShwZtE93DiiQdaYaozkPGwNFr6H8,34494
239
239
  phoenix/server/static/modernizr.js,sha256=mvK-XtkNqjOral-QvzoqsyOMECXIMu5BQwSVN_wcU9c,2564
240
- phoenix/server/static/.vite/manifest.json,sha256=5TDpyzP0p7LFbVpIP1n6hAq2Y9BIMRY07AZTk7p3_pA,1929
241
- phoenix/server/static/assets/components-1Ahruijo.js,sha256=wZdgukvLzeGCBdNCLuU-yOra8gsjFIuC9hSEwa7LuDw,187182
242
- phoenix/server/static/assets/index-BEE_RWJx.js,sha256=WrJXGQvMa2pSjnH_xjCwyRmDfm9MVD6FXq0YytraYEI,7461
243
- phoenix/server/static/assets/pages-CFS6mPnW.js,sha256=OLH9XUE7sS_MRSpD7x-CW4PJt981P-C2biAvQ0dO88g,466231
240
+ phoenix/server/static/.vite/manifest.json,sha256=NCacyzu0qbu92qY-iwPA7JHvzK56ZJnp_usNyMD3fZw,1929
241
+ phoenix/server/static/assets/components-1MfQimGx.js,sha256=NfJgri_ChJVeYEExpGOvfr5SLFuX_bZcvjfEVGH3HWI,187209
242
+ phoenix/server/static/assets/index-B263sE2x.js,sha256=PZG-hlU6oncPYh7r6tfdNm5pjuL94SFLHg4fCyr5Oe8,7515
243
+ phoenix/server/static/assets/pages-CqZDVx20.js,sha256=_Lor33vSFj6dZBGvOK4fPt1vXj9pndZFpyyi5BG_8AY,467596
244
244
  phoenix/server/static/assets/vendor-DxkFTwjz.css,sha256=nZrkr0u6NNElFGvpWHk9GTHeGoibCXCli1bE7mXZGZg,1816
245
245
  phoenix/server/static/assets/vendor-aSQri0vz.js,sha256=x_07SENutKMhtJ9HgFqkQHvwsDTfPkMmzQznY3HY7Zo,1359197
246
246
  phoenix/server/static/assets/vendor-arizeai-CsdcB1NH.js,sha256=VEn7hFJXcHV_DODmeDi9pEpF_D2NQ1bZYewbPe3BhIw,304008
@@ -250,7 +250,7 @@ phoenix/server/static/assets/vendor-three-DwGkEfCM.js,sha256=0D12ZgKzfKCTSdSTKJB
250
250
  phoenix/server/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
251
251
  phoenix/server/templates/index.html,sha256=dAm0IClgJUdT5AOmjZvtgMg8F_xGrRGv95SAkUyx_kg,4325
252
252
  phoenix/session/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
253
- phoenix/session/client.py,sha256=GzeSHbNQOh5dyzHV90t_cHJpn5VL0vkNmSnaMq-2ug4,32703
253
+ phoenix/session/client.py,sha256=SqnGTinAIiPGhAXFKu95MTiCHJKn4MMgfk2od2wW9s4,33291
254
254
  phoenix/session/data_extractor.py,sha256=gkEM3WWZAlWGMfRgQopAQlid4cSi6GNco-sdrGir0qc,2788
255
255
  phoenix/session/evaluation.py,sha256=3a33ilo-WU0F_Ze26lkCMMwni0GGceSXRRBLvP3CI1o,5352
256
256
  phoenix/session/session.py,sha256=3DSpXj_mlRAKnb9aNUkDjph19SLHx32IJSKYcR3r7cw,26994
@@ -259,7 +259,7 @@ phoenix/trace/attributes.py,sha256=B_OrzVaxZwFkrAFXZyicYoIti1UdUysURsvUS2GyW1U,1
259
259
  phoenix/trace/errors.py,sha256=wB1z8qdPckngdfU-TORToekvg3344oNFAA83_hC2yFY,180
260
260
  phoenix/trace/evaluation_conventions.py,sha256=t8jydM3U0-T5YpiQKRJ3tWdWGlHtzKyttYdw-ddvPOk,1048
261
261
  phoenix/trace/exporter.py,sha256=eAYemdvDCHMugDJiaR29BFFMTQBdf3oerdkz34Cl3hE,4736
262
- phoenix/trace/fixtures.py,sha256=PgawxpyxXz2596gJEj0yU27t-zcphz_ORGsxeAMvjKY,15345
262
+ phoenix/trace/fixtures.py,sha256=EHfqgvPoux6KkckX00WeG2Vhas8H5vqqFBMTztwgV-s,16857
263
263
  phoenix/trace/otel.py,sha256=WA720jvRadiZBAKjsYoPyXzypHwbyEK2OZRVUwtbjB8,9976
264
264
  phoenix/trace/projects.py,sha256=2BwlNjFE-uwpqYtCu5YyBiYZk9wRPpM13vh3-Cv7GkA,2157
265
265
  phoenix/trace/schemas.py,sha256=HpWSyzec0yDHEQXEDuwyLbhpvKrqkGps8BJqGiIFj8Y,5978
@@ -271,7 +271,7 @@ phoenix/trace/utils.py,sha256=1SEQr37cdHOM0P3BdL1dszArj3Zm-VJQyb1BcJs_qO8,1833
271
271
  phoenix/trace/dsl/README.md,sha256=ihmP9zGUC5V-TDbzKla76LuyDqPDQIBUH2BORwxNI68,2902
272
272
  phoenix/trace/dsl/__init__.py,sha256=WIQIjJg362XD3s50OsPJJ0xbDsGp41bSv7vDllLrPuA,144
273
273
  phoenix/trace/dsl/filter.py,sha256=9NwATCUOgJ4Pms8XsEcinROUuxZ9UW-ISV09o65Ms70,32600
274
- phoenix/trace/dsl/helpers.py,sha256=ULAhqWULPqYWCSNX7y50DVKIqfySx86nqb6hDvZPnVk,3896
274
+ phoenix/trace/dsl/helpers.py,sha256=STQtbmF3yI97GM4yH_V--mrGe1JqldUJJc5LO1o4NWo,3919
275
275
  phoenix/trace/dsl/query.py,sha256=W0t-tiXh2WIVb96lzFAGQOQ-U46uKux78d4KL3rW-PE,30316
276
276
  phoenix/trace/langchain/__init__.py,sha256=F37GfD1pd5Kuw7R7iRUM1zXXpO8xEcycNZh5dwqBXNk,109
277
277
  phoenix/trace/langchain/instrumentor.py,sha256=zdh9uZfG7HWna6Wug_agS7MxSbUlfV-nhf3jWFZm61U,1412
@@ -291,8 +291,8 @@ phoenix/utilities/logging.py,sha256=lDXd6EGaamBNcQxL4vP1au9-i_SXe0OraUDiJOcszSw,
291
291
  phoenix/utilities/project.py,sha256=8IJuMM4yUMoooPi37sictGj8Etu9rGmq6RFtc9848cQ,436
292
292
  phoenix/utilities/re.py,sha256=PDve_OLjRTM8yQQJHC8-n3HdIONi7aNils3ZKRZ5uBM,2045
293
293
  phoenix/utilities/span_store.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
294
- arize_phoenix-4.26.0.dist-info/METADATA,sha256=tf_mydvCrJ0GBxa58iEnat5FdCDItFKPN88hnZj7Dqs,12023
295
- arize_phoenix-4.26.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
296
- arize_phoenix-4.26.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
297
- arize_phoenix-4.26.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
298
- arize_phoenix-4.26.0.dist-info/RECORD,,
294
+ arize_phoenix-4.27.0.dist-info/METADATA,sha256=P6671H1bFkfBHk7FF-1pEwq0_9AoN8zR0RRwVJRqZ2w,11936
295
+ arize_phoenix-4.27.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
296
+ arize_phoenix-4.27.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
297
+ arize_phoenix-4.27.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
298
+ arize_phoenix-4.27.0.dist-info/RECORD,,
@@ -422,7 +422,7 @@ def get_inferences(
422
422
  Downloads primary and reference inferences for a fixture if they are not found
423
423
  locally.
424
424
  """
425
- fixture = _get_fixture_by_name(fixture_name=fixture_name)
425
+ fixture = get_fixture_by_name(fixture_name=fixture_name)
426
426
  if no_internet:
427
427
  paths = {role: INFERENCES_DIR / path for role, path in fixture.paths()}
428
428
  else:
@@ -436,9 +436,11 @@ def get_inferences(
436
436
  if fixture.reference_file_name is not None:
437
437
  reference_inferences = Inferences(
438
438
  read_parquet(paths[InferencesRole.REFERENCE]),
439
- fixture.reference_schema
440
- if fixture.reference_schema is not None
441
- else fixture.primary_schema,
439
+ (
440
+ fixture.reference_schema
441
+ if fixture.reference_schema is not None
442
+ else fixture.primary_schema
443
+ ),
442
444
  "training",
443
445
  )
444
446
  corpus_inferences = None
@@ -451,10 +453,14 @@ def get_inferences(
451
453
  return primary_inferences, reference_inferences, corpus_inferences
452
454
 
453
455
 
454
- def _get_fixture_by_name(fixture_name: str) -> Fixture:
456
+ def get_fixture_by_name(fixture_name: str) -> Fixture:
455
457
  """
456
- Returns the fixture whose name matches the input name. Raises a ValueError
457
- if the input fixture name does not match any known fixture names.
458
+ Returns the fixture whose name matches the input name.
459
+
460
+ Raises
461
+ ------
462
+ ValueError
463
+ if the input fixture name does not match any known fixture names.
458
464
  """
459
465
  if fixture_name not in NAME_TO_FIXTURE:
460
466
  valid_fixture_names = ", ".join(NAME_TO_FIXTURE.keys())
@@ -496,7 +502,7 @@ def load_example(use_case: str) -> ExampleInferences:
496
502
  reference).
497
503
 
498
504
  """
499
- fixture = _get_fixture_by_name(use_case)
505
+ fixture = get_fixture_by_name(use_case)
500
506
  primary_inferences, reference_inferences, corpus_inferences = get_inferences(use_case)
501
507
  print(f"📥 Loaded {use_case} example datasets.")
502
508
  print("ℹ️ About this use-case:")
@@ -1,6 +1,6 @@
1
1
  from datetime import datetime
2
2
  from random import getrandbits
3
- from typing import Any, Dict, Optional
3
+ from typing import Any, Dict, List, Optional
4
4
 
5
5
  from fastapi import APIRouter, HTTPException
6
6
  from pydantic import Field
@@ -252,3 +252,57 @@ async def get_experiment(request: Request, experiment_id: str) -> GetExperimentR
252
252
  updated_at=experiment.updated_at,
253
253
  )
254
254
  )
255
+
256
+
257
+ class ListExperimentsResponseBody(ResponseBody[List[Experiment]]):
258
+ pass
259
+
260
+
261
+ @router.get(
262
+ "/datasets/{dataset_id}/experiments",
263
+ operation_id="listExperiments",
264
+ summary="List experiments by dataset",
265
+ response_description="Experiments retrieved successfully",
266
+ )
267
+ async def list_experiments(
268
+ request: Request,
269
+ dataset_id: str,
270
+ ) -> ListExperimentsResponseBody:
271
+ dataset_gid = GlobalID.from_id(dataset_id)
272
+ try:
273
+ dataset_rowid = from_global_id_with_expected_type(dataset_gid, "Dataset")
274
+ except ValueError:
275
+ raise HTTPException(
276
+ detail=f"Dataset with ID {dataset_gid} does not exist",
277
+ status_code=HTTP_404_NOT_FOUND,
278
+ )
279
+ async with request.app.state.db() as session:
280
+ query = (
281
+ select(models.Experiment)
282
+ .where(models.Experiment.dataset_id == dataset_rowid)
283
+ .order_by(models.Experiment.id.desc())
284
+ )
285
+
286
+ result = await session.execute(query)
287
+ experiments = result.scalars().all()
288
+
289
+ if not experiments:
290
+ return ListExperimentsResponseBody(data=[])
291
+
292
+ data = [
293
+ Experiment(
294
+ id=str(GlobalID("Experiment", str(experiment.id))),
295
+ dataset_id=str(GlobalID("Dataset", str(experiment.dataset_id))),
296
+ dataset_version_id=str(
297
+ GlobalID("DatasetVersion", str(experiment.dataset_version_id))
298
+ ),
299
+ repetitions=experiment.repetitions,
300
+ metadata=experiment.metadata_,
301
+ project_name=None,
302
+ created_at=experiment.created_at,
303
+ updated_at=experiment.updated_at,
304
+ )
305
+ for experiment in experiments
306
+ ]
307
+
308
+ return ListExperimentsResponseBody(data=data)
@@ -1,6 +1,6 @@
1
- from typing import Any, Dict, Generic, List, Optional, TypedDict, Union
1
+ from typing import Any, Dict, Generic, List, Optional, TypedDict, TypeVar, Union
2
2
 
3
- from typing_extensions import TypeAlias, TypeVar, assert_never
3
+ from typing_extensions import TypeAlias, assert_never
4
4
 
5
5
  from .pydantic_compat import V1RoutesBaseModel
6
6
 
phoenix/server/app.py CHANGED
@@ -2,6 +2,7 @@ import asyncio
2
2
  import contextlib
3
3
  import json
4
4
  import logging
5
+ from datetime import datetime, timedelta, timezone
5
6
  from functools import cached_property
6
7
  from pathlib import Path
7
8
  from types import MethodType
@@ -10,12 +11,14 @@ from typing import (
10
11
  Any,
11
12
  AsyncContextManager,
12
13
  AsyncIterator,
14
+ Awaitable,
13
15
  Callable,
14
16
  Dict,
15
17
  Iterable,
16
18
  List,
17
19
  NamedTuple,
18
20
  Optional,
21
+ Set,
19
22
  Tuple,
20
23
  Union,
21
24
  cast,
@@ -26,11 +29,8 @@ from fastapi import APIRouter, FastAPI
26
29
  from fastapi.middleware.gzip import GZipMiddleware
27
30
  from fastapi.responses import FileResponse
28
31
  from fastapi.utils import is_body_allowed_for_status_code
29
- from sqlalchemy.ext.asyncio import (
30
- AsyncEngine,
31
- AsyncSession,
32
- async_sessionmaker,
33
- )
32
+ from sqlalchemy import select
33
+ from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker
34
34
  from starlette.datastructures import State as StarletteState
35
35
  from starlette.exceptions import HTTPException
36
36
  from starlette.middleware import Middleware
@@ -46,12 +46,9 @@ from typing_extensions import TypeAlias
46
46
 
47
47
  import phoenix
48
48
  import phoenix.trace.v1 as pb
49
- from phoenix.config import (
50
- DEFAULT_PROJECT_NAME,
51
- SERVER_DIR,
52
- server_instrumentation_is_enabled,
53
- )
49
+ from phoenix.config import DEFAULT_PROJECT_NAME, SERVER_DIR, server_instrumentation_is_enabled
54
50
  from phoenix.core.model_schema import Model
51
+ from phoenix.db import models
55
52
  from phoenix.db.bulk_inserter import BulkInserter
56
53
  from phoenix.db.engines import create_engine
57
54
  from phoenix.db.helpers import SupportedSQLDialect
@@ -93,9 +90,17 @@ from phoenix.server.telemetry import initialize_opentelemetry_tracer_provider
93
90
  from phoenix.server.types import (
94
91
  CanGetLastUpdatedAt,
95
92
  CanPutItem,
93
+ DaemonTask,
96
94
  DbSessionFactory,
97
95
  LastUpdatedAt,
98
96
  )
97
+ from phoenix.trace.fixtures import (
98
+ get_evals_from_fixture,
99
+ get_trace_fixture_by_name,
100
+ load_example_traces,
101
+ reset_fixture_span_ids_and_timestamps,
102
+ )
103
+ from phoenix.trace.otel import decode_otlp_span, encode_span_to_otlp
99
104
  from phoenix.trace.schemas import Span
100
105
  from phoenix.utilities.client import PHOENIX_SERVER_VERSION_HEADER
101
106
 
@@ -103,12 +108,23 @@ if TYPE_CHECKING:
103
108
  from opentelemetry.trace import TracerProvider
104
109
 
105
110
  logger = logging.getLogger(__name__)
111
+ logger.setLevel(logging.INFO)
106
112
  logger.addHandler(logging.NullHandler())
107
113
 
108
114
  router = APIRouter(include_in_schema=False)
109
115
 
110
116
  templates = Jinja2Templates(directory=SERVER_DIR / "templates")
111
117
 
118
+ """
119
+ Threshold (in minutes) to determine if database is booted up for the first time.
120
+
121
+ Used to assess whether the `default` project was created recently.
122
+ If so, demo data is automatically ingested upon initial boot up to populate the database.
123
+ """
124
+ NEW_DB_AGE_THRESHOLD_MINUTES = 2
125
+
126
+ ProjectName: TypeAlias = str
127
+
112
128
 
113
129
  class AppConfig(NamedTuple):
114
130
  has_inferences: bool
@@ -226,9 +242,99 @@ def _db(engine: AsyncEngine) -> Callable[[], AsyncContextManager[AsyncSession]]:
226
242
  return factory
227
243
 
228
244
 
245
+ class Scaffolder(DaemonTask):
246
+ def __init__(
247
+ self,
248
+ db: DbSessionFactory,
249
+ queue_span: Callable[[Span, ProjectName], Awaitable[None]],
250
+ queue_evaluation: Callable[[pb.Evaluation], Awaitable[None]],
251
+ tracing_fixture_names: Set[str] = set(),
252
+ force_fixture_ingestion: bool = False,
253
+ ) -> None:
254
+ super().__init__()
255
+ self._db = db
256
+ self._queue_span = queue_span
257
+ self._queue_evaluation = queue_evaluation
258
+ self._tracing_fixtures = set(
259
+ get_trace_fixture_by_name(name) for name in tracing_fixture_names
260
+ )
261
+ self._force_fixture_ingestion = force_fixture_ingestion
262
+
263
+ async def __aenter__(self) -> None:
264
+ await self.start()
265
+
266
+ async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
267
+ await self.stop()
268
+
269
+ async def _run(self) -> None:
270
+ """
271
+ Main entry point for Scaffolder.
272
+ Determines whether to load fixtures and handles them.
273
+ """
274
+ if await self._should_load_fixtures():
275
+ logger.info("Loading trace fixtures.")
276
+ await self._handle_tracing_fixtures()
277
+ logger.info("Finished loading fixtures.")
278
+ else:
279
+ logger.info("DB is not new, avoid loading demo fixtures.")
280
+
281
+ async def _should_load_fixtures(self) -> bool:
282
+ if self._force_fixture_ingestion:
283
+ return True
284
+
285
+ async with self._db() as session:
286
+ created_at = await session.scalar(
287
+ select(models.Project.created_at).where(models.Project.name == "default")
288
+ )
289
+ if created_at is None:
290
+ return False
291
+
292
+ is_new_db = datetime.now(timezone.utc) - created_at < timedelta(
293
+ minutes=NEW_DB_AGE_THRESHOLD_MINUTES
294
+ )
295
+ return is_new_db
296
+
297
+ async def _handle_tracing_fixtures(self) -> None:
298
+ """
299
+ Main handler for processing trace fixtures. Process each fixture by
300
+ loading its trace dataframe, gettting and processings its
301
+ spans and evals, and queuing.
302
+ """
303
+ loop = asyncio.get_running_loop()
304
+ for fixture in self._tracing_fixtures:
305
+ try:
306
+ trace_ds = await loop.run_in_executor(None, load_example_traces, fixture.name)
307
+
308
+ fixture_spans, fixture_evals = await loop.run_in_executor(
309
+ None,
310
+ reset_fixture_span_ids_and_timestamps,
311
+ (
312
+ # Apply `encode` here because legacy jsonl files contains UUIDs as strings.
313
+ # `encode` removes the hyphens in the UUIDs.
314
+ decode_otlp_span(encode_span_to_otlp(span))
315
+ for span in trace_ds.to_spans()
316
+ ),
317
+ get_evals_from_fixture(fixture.name),
318
+ )
319
+
320
+ project_name = fixture.project_name or fixture.name
321
+ logger.info(f"Loading '{project_name}' fixtures...")
322
+ for span in fixture_spans:
323
+ await self._queue_span(span, project_name)
324
+ for evaluation in fixture_evals:
325
+ await self._queue_evaluation(evaluation)
326
+
327
+ except FileNotFoundError:
328
+ logger.warning(f"Fixture file not found for '{fixture.name}'")
329
+ except ValueError as e:
330
+ logger.error(f"Error processing fixture '{fixture.name}': {e}")
331
+ except Exception as e:
332
+ logger.error(f"Unexpected error processing fixture '{fixture.name}': {e}")
333
+
334
+
229
335
  def _lifespan(
230
336
  *,
231
- dialect: SupportedSQLDialect,
337
+ db: DbSessionFactory,
232
338
  bulk_inserter: BulkInserter,
233
339
  dml_event_handler: DmlEventHandler,
234
340
  tracer_provider: Optional["TracerProvider"] = None,
@@ -236,11 +342,13 @@ def _lifespan(
236
342
  startup_callbacks: Iterable[Callable[[], None]] = (),
237
343
  shutdown_callbacks: Iterable[Callable[[], None]] = (),
238
344
  read_only: bool = False,
345
+ tracing_fixture_names: Set[str] = set(),
346
+ force_fixture_ingestion: bool = False,
239
347
  ) -> StatefulLifespan[FastAPI]:
240
348
  @contextlib.asynccontextmanager
241
349
  async def lifespan(_: FastAPI) -> AsyncIterator[Dict[str, Any]]:
242
350
  global DB_MUTEX
243
- DB_MUTEX = asyncio.Lock() if dialect is SupportedSQLDialect.SQLITE else None
351
+ DB_MUTEX = asyncio.Lock() if db.dialect is SupportedSQLDialect.SQLITE else None
244
352
  async with bulk_inserter as (
245
353
  enqueue,
246
354
  queue_span,
@@ -251,7 +359,13 @@ def _lifespan(
251
359
  disabled=read_only,
252
360
  tracer_provider=tracer_provider,
253
361
  enable_prometheus=enable_prometheus,
254
- ), dml_event_handler:
362
+ ), dml_event_handler, Scaffolder(
363
+ db=db,
364
+ queue_span=queue_span,
365
+ queue_evaluation=queue_evaluation,
366
+ tracing_fixture_names=tracing_fixture_names,
367
+ force_fixture_ingestion=force_fixture_ingestion,
368
+ ):
255
369
  for callback in startup_callbacks:
256
370
  callback()
257
371
  yield {
@@ -317,17 +431,19 @@ def create_graphql_router(
317
431
  dataset_example_spans=DatasetExampleSpansDataLoader(db),
318
432
  document_evaluation_summaries=DocumentEvaluationSummaryDataLoader(
319
433
  db,
320
- cache_map=cache_for_dataloaders.document_evaluation_summary
321
- if cache_for_dataloaders
322
- else None,
434
+ cache_map=(
435
+ cache_for_dataloaders.document_evaluation_summary
436
+ if cache_for_dataloaders
437
+ else None
438
+ ),
323
439
  ),
324
440
  document_evaluations=DocumentEvaluationsDataLoader(db),
325
441
  document_retrieval_metrics=DocumentRetrievalMetricsDataLoader(db),
326
442
  annotation_summaries=AnnotationSummaryDataLoader(
327
443
  db,
328
- cache_map=cache_for_dataloaders.annotation_summary
329
- if cache_for_dataloaders
330
- else None,
444
+ cache_map=(
445
+ cache_for_dataloaders.annotation_summary if cache_for_dataloaders else None
446
+ ),
331
447
  ),
332
448
  experiment_annotation_summaries=ExperimentAnnotationSummaryDataLoader(db),
333
449
  experiment_error_rates=ExperimentErrorRatesDataLoader(db),
@@ -335,15 +451,17 @@ def create_graphql_router(
335
451
  experiment_sequence_number=ExperimentSequenceNumberDataLoader(db),
336
452
  latency_ms_quantile=LatencyMsQuantileDataLoader(
337
453
  db,
338
- cache_map=cache_for_dataloaders.latency_ms_quantile
339
- if cache_for_dataloaders
340
- else None,
454
+ cache_map=(
455
+ cache_for_dataloaders.latency_ms_quantile if cache_for_dataloaders else None
456
+ ),
341
457
  ),
342
458
  min_start_or_max_end_times=MinStartOrMaxEndTimeDataLoader(
343
459
  db,
344
- cache_map=cache_for_dataloaders.min_start_or_max_end_time
345
- if cache_for_dataloaders
346
- else None,
460
+ cache_map=(
461
+ cache_for_dataloaders.min_start_or_max_end_time
462
+ if cache_for_dataloaders
463
+ else None
464
+ ),
347
465
  ),
348
466
  record_counts=RecordCountDataLoader(
349
467
  db,
@@ -438,6 +556,8 @@ def create_app(
438
556
  startup_callbacks: Iterable[Callable[[], None]] = (),
439
557
  shutdown_callbacks: Iterable[Callable[[], None]] = (),
440
558
  secret: Optional[str] = None,
559
+ tracing_fixture_names: Set[str] = set(),
560
+ force_fixture_ingestion: bool = False,
441
561
  ) -> FastAPI:
442
562
  startup_callbacks_list: List[Callable[[], None]] = list(startup_callbacks)
443
563
  shutdown_callbacks_list: List[Callable[[], None]] = list(shutdown_callbacks)
@@ -510,11 +630,12 @@ def create_app(
510
630
  prometheus_middlewares = [Middleware(PrometheusMiddleware)]
511
631
  else:
512
632
  prometheus_middlewares = []
633
+
513
634
  app = FastAPI(
514
635
  title="Arize-Phoenix REST API",
515
636
  version=REST_API_VERSION,
516
637
  lifespan=_lifespan(
517
- dialect=db.dialect,
638
+ db=db,
518
639
  read_only=read_only,
519
640
  bulk_inserter=bulk_inserter,
520
641
  dml_event_handler=dml_event_handler,
@@ -522,6 +643,8 @@ def create_app(
522
643
  enable_prometheus=enable_prometheus,
523
644
  shutdown_callbacks=shutdown_callbacks_list,
524
645
  startup_callbacks=startup_callbacks_list,
646
+ tracing_fixture_names=tracing_fixture_names,
647
+ force_fixture_ingestion=force_fixture_ingestion,
525
648
  ),
526
649
  middleware=[
527
650
  Middleware(HeadersMiddleware),