arize-phoenix 8.12.0__py3-none-any.whl → 8.13.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.

Files changed (24) hide show
  1. {arize_phoenix-8.12.0.dist-info → arize_phoenix-8.13.0.dist-info}/METADATA +1 -1
  2. {arize_phoenix-8.12.0.dist-info → arize_phoenix-8.13.0.dist-info}/RECORD +22 -22
  3. phoenix/db/migrations/versions/10460e46d750_datasets.py +1 -1
  4. phoenix/db/migrations/versions/3be8647b87d8_add_token_columns_to_spans_table.py +1 -1
  5. phoenix/db/migrations/versions/bc8fea3c2bc8_add_prompt_tables.py +1 -1
  6. phoenix/db/migrations/versions/cf03bd6bae1d_init.py +1 -1
  7. phoenix/db/models.py +1 -1
  8. phoenix/server/api/routers/v1/experiments.py +181 -98
  9. phoenix/server/static/.vite/manifest.json +40 -40
  10. phoenix/server/static/assets/{components-CbkVj1OQ.js → components-Igx2_EFY.js} +347 -215
  11. phoenix/server/static/assets/{index-BNK6eNx3.js → index-C2-HcjQo.js} +9 -7
  12. phoenix/server/static/assets/{pages-CyqzCvBY.js → pages-x8AxTGd1.js} +362 -349
  13. phoenix/server/static/assets/vendor-CwOvppSU.js +902 -0
  14. phoenix/server/static/assets/{vendor-arizeai-0BoxbU9V.js → vendor-arizeai-bw-hz0cw.js} +27 -35
  15. phoenix/server/static/assets/vendor-codemirror-Du5cjwoZ.js +24 -0
  16. phoenix/server/static/assets/{vendor-recharts-BLborQJX.js → vendor-recharts-BMT-13Pz.js} +1 -1
  17. phoenix/server/static/assets/{vendor-shiki-DpPpUgzv.js → vendor-shiki-CevgDqNV.js} +1 -1
  18. phoenix/version.py +1 -1
  19. phoenix/server/static/assets/vendor-Byx9ZW8r.js +0 -894
  20. phoenix/server/static/assets/vendor-codemirror-BX78TDyo.js +0 -24
  21. {arize_phoenix-8.12.0.dist-info → arize_phoenix-8.13.0.dist-info}/WHEEL +0 -0
  22. {arize_phoenix-8.12.0.dist-info → arize_phoenix-8.13.0.dist-info}/entry_points.txt +0 -0
  23. {arize_phoenix-8.12.0.dist-info → arize_phoenix-8.13.0.dist-info}/licenses/IP_NOTICE +0 -0
  24. {arize_phoenix-8.12.0.dist-info → arize_phoenix-8.13.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arize-phoenix
3
- Version: 8.12.0
3
+ Version: 8.13.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=kpW1WL0kiB8XJsO6XycvZVJ-lBkNoenhQ7atCvBoSe8,5365
8
8
  phoenix/settings.py,sha256=ht-0oN-sMV6SPXrk7Tu1EZlngpAYkGNLYPhO8DyrdQI,661
9
- phoenix/version.py,sha256=hURJbBJpRlARLVE-_bmA1Vwr56PpPmoJTwIlaM1pNE4,23
9
+ phoenix/version.py,sha256=sk_qeEM7orIiG3sa7MgoA8R7f-PiyLmkT3IJccgKp_Q,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=qBFraOtmwCCnWJltKNP18DDG0mULXigytlFsa6YOz6k,4837
@@ -21,7 +21,7 @@ phoenix/db/enums.py,sha256=tt7iovXLhVTLZ3_LbHNGgcI44SnNjXfkKtLAZG57T54,428
21
21
  phoenix/db/facilitator.py,sha256=sAYqzBXYSVBKPTQVYrd7ZmtqMAr1zP9dVJwjfNGW7hc,4207
22
22
  phoenix/db/helpers.py,sha256=daKbpY2QhTPo9a_T1xNHKI4WzWHkMmmrGIws7Hw-RZ4,4884
23
23
  phoenix/db/migrate.py,sha256=oUrXH8yEbcpL4eh09aSCuUiSrhFli0eT5D_j4ZmYChY,2797
24
- phoenix/db/models.py,sha256=kTXUlNzWyMxSNZxbTnnfdAfJcDLj8j6Nny2Q6DbqfSQ,45723
24
+ phoenix/db/models.py,sha256=TbHgtT7WWdkQK-OtLsUqp3MwP23HGV1IaSAWTqCf5ac,45707
25
25
  phoenix/db/insertion/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  phoenix/db/insertion/constants.py,sha256=8wifm7X-1XvroZ__R2Gc96NsgLhTDn0zXl4lehlXtcA,70
27
27
  phoenix/db/insertion/dataset.py,sha256=I9OC1ouVx7m6BH_c8hvcxW1dWGRAtpvXee29yBTuFkg,7136
@@ -37,12 +37,12 @@ phoenix/db/migrations/env.py,sha256=tFO3ceuCx9Es5_2w_BXclaQMmNQKNX21b1UEV5mYdeo,
37
37
  phoenix/db/migrations/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635
38
38
  phoenix/db/migrations/data_migration_scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
39
  phoenix/db/migrations/data_migration_scripts/populate_project_sessions.py,sha256=NJPoEX2-OhYxQXl0jRcX5eoq1azgLi8RLArZZw2pTY4,6046
40
- phoenix/db/migrations/versions/10460e46d750_datasets.py,sha256=3wftHgat1sEeKBl0EcQ24r_oOK_T-Tcmd2Loo73RVCs,8679
41
- phoenix/db/migrations/versions/3be8647b87d8_add_token_columns_to_spans_table.py,sha256=GxuKD501WSx083uIv21GhJS0l4Xk3exovfycaGAxY30,3638
40
+ phoenix/db/migrations/versions/10460e46d750_datasets.py,sha256=E2xC5NCmY6bEvJGf1SbxblvmERHdxeEq13mbWTz3clQ,8663
41
+ phoenix/db/migrations/versions/3be8647b87d8_add_token_columns_to_spans_table.py,sha256=RxE_1vrpSAy2GKwHvrU3cOLA_bxcRRDibAj87nJEDKE,3622
42
42
  phoenix/db/migrations/versions/4ded9e43755f_create_project_sessions_table.py,sha256=HDP3X5yxm5PwxMkODd-wrSwspQpFotTEz40jXRgq320,1787
43
- phoenix/db/migrations/versions/bc8fea3c2bc8_add_prompt_tables.py,sha256=BKkhvBjw4FjdVcxDM8z23_PRIp7eheoS3Bj_F_u761M,5543
43
+ phoenix/db/migrations/versions/bc8fea3c2bc8_add_prompt_tables.py,sha256=rq-bwg0grRVvbKvwE2ZBCFG_nQRmYNCd2PJeFd_5SDk,5527
44
44
  phoenix/db/migrations/versions/cd164e83824f_users_and_tokens.py,sha256=fkpmh5PgMZJiZpvLbZIaqlI2cucVpVbbNYpQ-Tznil8,5180
45
- phoenix/db/migrations/versions/cf03bd6bae1d_init.py,sha256=lorqWUcujC0fsYIOriGZlZw2LKjS3nF34KHxi1fp_Z8,8646
45
+ phoenix/db/migrations/versions/cf03bd6bae1d_init.py,sha256=ZNQzTUyb3p9Bkq7rjd5MRxGQV8W08zpY8E3GlzSJ2cM,8630
46
46
  phoenix/db/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
47
  phoenix/db/types/identifier.py,sha256=TSm3o4CDnd7vNyQj3c8CKtrx04ZNaDFDbYLFHTi0_7I,181
48
48
  phoenix/db/types/model_provider.py,sha256=96UMeqiy5X9PmYMOWA6dZAmI_BSV3yVxt9HEVYGe5Ns,157
@@ -215,7 +215,7 @@ phoenix/server/api/routers/v1/datasets.py,sha256=gHlF4x0EmWiJ-8vwJygoh0bO3gvDBmi
215
215
  phoenix/server/api/routers/v1/evaluations.py,sha256=RpOkTylp5Da6BvPZGuN8ksnxz_BVXRIwyOvwX9Iko8U,12647
216
216
  phoenix/server/api/routers/v1/experiment_evaluations.py,sha256=vx4CKlE84sAL1vtPiM_XWnbfrATQujOSzzduJDYgcyM,4829
217
217
  phoenix/server/api/routers/v1/experiment_runs.py,sha256=bInuasRv7ogiYf8fq-LwpJ5tptmMQsBNDlJAqwdymko,6378
218
- phoenix/server/api/routers/v1/experiments.py,sha256=UZiEf7OnxL3juO119sbE0gV9ScK5XKTfgjl4rj72ESY,16909
218
+ phoenix/server/api/routers/v1/experiments.py,sha256=V9_sxqLTE1MKGFu9H3FEdGKr70lYMbGZx813MGaavfQ,20430
219
219
  phoenix/server/api/routers/v1/models.py,sha256=r0nM2kFJ3mxDqgc5vFr1cjNuyOPs3RIKE_DS2VMdF48,1749
220
220
  phoenix/server/api/routers/v1/prompts.py,sha256=ytK8HnOZNxUMDtC7XAFxzaTSM9DMMua13vWsqqd4PAw,14986
221
221
  phoenix/server/api/routers/v1/spans.py,sha256=uoU_bwIgz86fuvPjP5sX8goDyuCcnsTig-x3f17p60U,9625
@@ -314,16 +314,16 @@ phoenix/server/static/apple-touch-icon-76x76.png,sha256=CT_xT12I0u2i0WU8JzBZBuOQ
314
314
  phoenix/server/static/apple-touch-icon.png,sha256=fOfpjqGpWYbJ0eAurKsyoZP1EAs6ZVooBJ_SGk2ZkDs,3801
315
315
  phoenix/server/static/favicon.ico,sha256=bY0vvCKRftemZfPShwZtE93DiiQdaYaozkPGwNFr6H8,34494
316
316
  phoenix/server/static/modernizr.js,sha256=mvK-XtkNqjOral-QvzoqsyOMECXIMu5BQwSVN_wcU9c,2564
317
- phoenix/server/static/.vite/manifest.json,sha256=Bf5J32vA6mK0TQjtx_F6dK7GeibGq2gQTQDhNSfEJHE,2165
318
- phoenix/server/static/assets/components-CbkVj1OQ.js,sha256=gW4kLgfLvzuO0KPA-gbisHNqImkZtXEhFg5q2njd-Ek,430635
319
- phoenix/server/static/assets/index-BNK6eNx3.js,sha256=hWGabY3aNGqt12pOp_v-JEswgRpLB5O2QmDsFXOeKGE,59739
320
- phoenix/server/static/assets/pages-CyqzCvBY.js,sha256=3ZVi4bXfGDDEj10g3ax0Q4MS7KmgopWL8d5epxZ1FWM,841781
321
- phoenix/server/static/assets/vendor-Byx9ZW8r.js,sha256=l6S82ZzhNfzfjoYYM9ZrwUCc0TBKvJdWZVF0oBj8wkA,2281565
317
+ phoenix/server/static/.vite/manifest.json,sha256=NxC9EGNVFUMSpwNaz0pPAK9OI-qPyQGEeBhOAhshDR4,2165
318
+ phoenix/server/static/assets/components-Igx2_EFY.js,sha256=HsxZBGhU8TVlZ1QHaHOSH9PPruR3nQ5ntqOV2Uk_2Nw,435889
319
+ phoenix/server/static/assets/index-C2-HcjQo.js,sha256=sm6mrMg0h8691PwWD69R3j3Hq0hSNcxok8D5ivmisKc,59755
320
+ phoenix/server/static/assets/pages-x8AxTGd1.js,sha256=QlAXYLLXdKmP5uhV9Ty7iRoogfZkL3Ga-dSWnlxz9VE,846285
322
321
  phoenix/server/static/assets/vendor-Cg6lcjUC.css,sha256=nZrkr0u6NNElFGvpWHk9GTHeGoibCXCli1bE7mXZGZg,1816
323
- phoenix/server/static/assets/vendor-arizeai-0BoxbU9V.js,sha256=innWhffEldVIqKcGGH7XAMlE7vvJTGbPzrZxNYvu9fU,200901
324
- phoenix/server/static/assets/vendor-codemirror-BX78TDyo.js,sha256=Isk5hEoLzUYzDxDPQgGjRzTZOsE91GuJSzVA_zhMpU4,390090
325
- phoenix/server/static/assets/vendor-recharts-BLborQJX.js,sha256=-s5VSfS1H08wvgAcjvGyoQQz3EG3WQcKOO2X3TwFYPM,282095
326
- phoenix/server/static/assets/vendor-shiki-DpPpUgzv.js,sha256=_KvBjUsXiXQ-Piup8bgG17MfigFVwN6FPcE2CWxen00,8980312
322
+ phoenix/server/static/assets/vendor-CwOvppSU.js,sha256=zorIsWgg0PkDjHxKCG-oYmme0wYQaIg_7qA54v4HOYM,2421407
323
+ phoenix/server/static/assets/vendor-arizeai-bw-hz0cw.js,sha256=2HKtN3-Kx959BVQ4cv_wH9hGNZBHvRB7zET_fAWNMx0,194761
324
+ phoenix/server/static/assets/vendor-codemirror-Du5cjwoZ.js,sha256=C2Ak-WHgWXh51I6sW4xjpkG5O1UfH4OsNizSpdyQRx8,400557
325
+ phoenix/server/static/assets/vendor-recharts-BMT-13Pz.js,sha256=s3xYgt-cqmBSDYeI1gW6pK-q4T1Ze8hnlLR9i2TFRC8,282095
326
+ phoenix/server/static/assets/vendor-shiki-CevgDqNV.js,sha256=Sv_qlOPUDy9iCCaw7olaBHyspOSkczZh6J6I_vfsB2s,8980312
327
327
  phoenix/server/static/assets/vendor-three-C-AGeJYv.js,sha256=c9nLPH5YDRFCzTNuwWO8_5KqcnunPo53mQsb7y9JFW8,620972
328
328
  phoenix/server/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
329
329
  phoenix/server/templates/index.html,sha256=e8_jdi7Eo19SK7DI_gglkTW094D17E0VAegoMmmmvIc,4330
@@ -364,9 +364,9 @@ phoenix/utilities/project.py,sha256=auVpARXkDb-JgeX5f2aStyFIkeKvGwN9l7qrFeJMVxI,
364
364
  phoenix/utilities/re.py,sha256=6YyUWIkv0zc2SigsxfOWIHzdpjKA_TZo2iqKq7zJKvw,2081
365
365
  phoenix/utilities/span_store.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
366
366
  phoenix/utilities/template_formatters.py,sha256=gh9PJD6WEGw7TEYXfSst1UR4pWWwmjxMLrDVQ_CkpkQ,2779
367
- arize_phoenix-8.12.0.dist-info/METADATA,sha256=qM9RkF7DlQKdlLKsF0efcPwOHrhebyRuSLDmqcWBhCI,23739
368
- arize_phoenix-8.12.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
369
- arize_phoenix-8.12.0.dist-info/entry_points.txt,sha256=Pgpn8Upxx9P8z8joPXZWl2LlnAlGc3gcQoVchb06X1Q,94
370
- arize_phoenix-8.12.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
371
- arize_phoenix-8.12.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
372
- arize_phoenix-8.12.0.dist-info/RECORD,,
367
+ arize_phoenix-8.13.0.dist-info/METADATA,sha256=xXuBNvw2h5cwUZ1a9ILQuo_Xm5hxWJXXOis2iwwg6SU,23739
368
+ arize_phoenix-8.13.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
369
+ arize_phoenix-8.13.0.dist-info/entry_points.txt,sha256=Pgpn8Upxx9P8z8joPXZWl2LlnAlGc3gcQoVchb06X1Q,94
370
+ arize_phoenix-8.13.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
371
+ arize_phoenix-8.13.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
372
+ arize_phoenix-8.13.0.dist-info/RECORD,,
@@ -29,7 +29,7 @@ def _(*args: Any, **kwargs: Any) -> str:
29
29
  JSON_ = (
30
30
  JSON()
31
31
  .with_variant(
32
- postgresql.JSONB(), # type: ignore
32
+ postgresql.JSONB(),
33
33
  "postgresql",
34
34
  )
35
35
  .with_variant(
@@ -41,7 +41,7 @@ def _(*args: Any, **kwargs: Any) -> str:
41
41
  JSON_ = (
42
42
  JSON()
43
43
  .with_variant(
44
- postgresql.JSONB(), # type: ignore
44
+ postgresql.JSONB(),
45
45
  "postgresql",
46
46
  )
47
47
  .with_variant(
@@ -29,7 +29,7 @@ def _(*args: Any, **kwargs: Any) -> str:
29
29
  JSON_ = (
30
30
  JSON()
31
31
  .with_variant(
32
- postgresql.JSONB(), # type: ignore
32
+ postgresql.JSONB(),
33
33
  "postgresql",
34
34
  )
35
35
  .with_variant(
@@ -29,7 +29,7 @@ def _(*args: Any, **kwargs: Any) -> str:
29
29
  JSON_ = (
30
30
  JSON()
31
31
  .with_variant(
32
- postgresql.JSONB(), # type: ignore
32
+ postgresql.JSONB(),
33
33
  "postgresql",
34
34
  )
35
35
  .with_variant(
phoenix/db/models.py CHANGED
@@ -159,7 +159,7 @@ def _(*args: Any, **kwargs: Any) -> str:
159
159
  JSON_ = (
160
160
  JSON()
161
161
  .with_variant(
162
- postgresql.JSONB(), # type: ignore
162
+ postgresql.JSONB(),
163
163
  "postgresql",
164
164
  )
165
165
  .with_variant(
@@ -3,13 +3,15 @@ from datetime import datetime
3
3
  from random import getrandbits
4
4
  from typing import Any, Optional
5
5
 
6
+ import pandas as pd
6
7
  from fastapi import APIRouter, HTTPException, Path, Response
7
8
  from pydantic import Field
8
9
  from sqlalchemy import and_, func, select
10
+ from sqlalchemy.ext.asyncio import AsyncSession
9
11
  from sqlalchemy.orm import joinedload
10
12
  from starlette.requests import Request
11
13
  from starlette.responses import PlainTextResponse
12
- from starlette.status import HTTP_404_NOT_FOUND
14
+ from starlette.status import HTTP_200_OK, HTTP_404_NOT_FOUND, HTTP_422_UNPROCESSABLE_ENTITY
13
15
  from strawberry.relay import GlobalID
14
16
 
15
17
  from phoenix.db import models
@@ -19,7 +21,7 @@ from phoenix.server.api.types.node import from_global_id_with_expected_type
19
21
  from phoenix.server.dml_event import ExperimentInsertEvent
20
22
 
21
23
  from .models import V1RoutesBaseModel
22
- from .utils import ResponseBody, add_errors_to_responses
24
+ from .utils import ResponseBody, add_errors_to_responses, add_text_csv_content_to_responses
23
25
 
24
26
  router = APIRouter(tags=["experiments"], include_in_schema=True)
25
27
 
@@ -311,6 +313,66 @@ async def list_experiments(
311
313
  return ListExperimentsResponseBody(data=data)
312
314
 
313
315
 
316
+ async def _get_experiment_runs_and_revisions(
317
+ session: AsyncSession, experiment_rowid: int
318
+ ) -> tuple[models.Experiment, tuple[models.ExperimentRun], tuple[models.DatasetExampleRevision]]:
319
+ experiment = await session.get(models.Experiment, experiment_rowid)
320
+ if not experiment:
321
+ raise HTTPException(detail="Experiment not found", status_code=HTTP_404_NOT_FOUND)
322
+ revision_ids = (
323
+ select(func.max(models.DatasetExampleRevision.id))
324
+ .join(
325
+ models.DatasetExample,
326
+ models.DatasetExample.id == models.DatasetExampleRevision.dataset_example_id,
327
+ )
328
+ .where(
329
+ and_(
330
+ models.DatasetExampleRevision.dataset_version_id <= experiment.dataset_version_id,
331
+ models.DatasetExample.dataset_id == experiment.dataset_id,
332
+ )
333
+ )
334
+ .group_by(models.DatasetExampleRevision.dataset_example_id)
335
+ .scalar_subquery()
336
+ )
337
+ runs_and_revisions = (
338
+ (
339
+ await session.execute(
340
+ select(models.ExperimentRun, models.DatasetExampleRevision)
341
+ .join(
342
+ models.DatasetExample,
343
+ models.DatasetExample.id == models.ExperimentRun.dataset_example_id,
344
+ )
345
+ .join(
346
+ models.DatasetExampleRevision,
347
+ and_(
348
+ models.DatasetExample.id
349
+ == models.DatasetExampleRevision.dataset_example_id,
350
+ models.DatasetExampleRevision.id.in_(revision_ids),
351
+ models.DatasetExampleRevision.revision_kind != "DELETE",
352
+ ),
353
+ )
354
+ .options(
355
+ joinedload(models.ExperimentRun.annotations),
356
+ )
357
+ .where(models.ExperimentRun.experiment_id == experiment_rowid)
358
+ .order_by(
359
+ models.ExperimentRun.dataset_example_id,
360
+ models.ExperimentRun.repetition_number,
361
+ )
362
+ )
363
+ )
364
+ .unique()
365
+ .all()
366
+ )
367
+ if not runs_and_revisions:
368
+ raise HTTPException(
369
+ detail="Experiment has no runs",
370
+ status_code=HTTP_404_NOT_FOUND,
371
+ )
372
+ runs, revisions = zip(*runs_and_revisions)
373
+ return experiment, runs, revisions
374
+
375
+
314
376
  @router.get(
315
377
  "/experiments/{experiment_id}/json",
316
378
  operation_id="getExperimentJSON",
@@ -324,112 +386,133 @@ async def list_experiments(
324
386
  )
325
387
  async def get_experiment_json(
326
388
  request: Request,
327
- response: Response,
328
389
  experiment_id: str = Path(..., title="Experiment ID"),
329
- ) -> str:
390
+ ) -> Response:
330
391
  experiment_globalid = GlobalID.from_id(experiment_id)
331
392
  try:
332
393
  experiment_rowid = from_global_id_with_expected_type(experiment_globalid, "Experiment")
333
394
  except ValueError:
334
395
  raise HTTPException(
335
- detail=f"Experiment with ID {experiment_globalid} does not exist",
336
- status_code=HTTP_404_NOT_FOUND,
396
+ detail=f"Invalid experiment ID: {experiment_globalid}",
397
+ status_code=HTTP_422_UNPROCESSABLE_ENTITY,
337
398
  )
338
399
 
339
400
  async with request.app.state.db() as session:
340
- experiment = await session.get(models.Experiment, experiment_rowid)
341
- if not experiment:
342
- raise HTTPException(
343
- detail=f"Experiment with ID {experiment_globalid} does not exist",
344
- status_code=HTTP_404_NOT_FOUND,
345
- )
346
- revision_ids = (
347
- select(func.max(models.DatasetExampleRevision.id))
348
- .join(
349
- models.DatasetExample,
350
- models.DatasetExample.id == models.DatasetExampleRevision.dataset_example_id,
351
- )
352
- .where(
353
- and_(
354
- models.DatasetExampleRevision.dataset_version_id
355
- <= experiment.dataset_version_id,
356
- models.DatasetExample.dataset_id == experiment.dataset_id,
357
- )
358
- )
359
- .group_by(models.DatasetExampleRevision.dataset_example_id)
360
- .scalar_subquery()
401
+ experiment, runs, revisions = await _get_experiment_runs_and_revisions(
402
+ session, experiment_rowid
361
403
  )
362
- runs_and_revisions = (
363
- (
364
- await session.execute(
365
- select(models.ExperimentRun, models.DatasetExampleRevision)
366
- .join(
367
- models.DatasetExample,
368
- models.DatasetExample.id == models.ExperimentRun.dataset_example_id,
369
- )
370
- .join(
371
- models.DatasetExampleRevision,
372
- and_(
373
- models.DatasetExample.id
374
- == models.DatasetExampleRevision.dataset_example_id,
375
- models.DatasetExampleRevision.id.in_(revision_ids),
376
- models.DatasetExampleRevision.revision_kind != "DELETE",
377
- ),
378
- )
379
- .options(
380
- joinedload(models.ExperimentRun.annotations),
381
- )
382
- .where(models.ExperimentRun.experiment_id == experiment_rowid)
383
- .order_by(
384
- models.ExperimentRun.dataset_example_id,
385
- models.ExperimentRun.repetition_number,
386
- )
404
+ records = []
405
+ for run, revision in zip(runs, revisions):
406
+ annotations = []
407
+ for annotation in run.annotations:
408
+ annotations.append(
409
+ {
410
+ "name": annotation.name,
411
+ "annotator_kind": annotation.annotator_kind,
412
+ "label": annotation.label,
413
+ "score": annotation.score,
414
+ "explanation": annotation.explanation,
415
+ "trace_id": annotation.trace_id,
416
+ "error": annotation.error,
417
+ "metadata": annotation.metadata_,
418
+ "start_time": annotation.start_time.isoformat(),
419
+ "end_time": annotation.end_time.isoformat(),
420
+ }
387
421
  )
388
- )
389
- .unique()
390
- .all()
422
+ record = {
423
+ "example_id": str(
424
+ GlobalID(models.DatasetExample.__name__, str(run.dataset_example_id))
425
+ ),
426
+ "repetition_number": run.repetition_number,
427
+ "input": revision.input,
428
+ "reference_output": revision.output,
429
+ "output": run.output["task_output"],
430
+ "error": run.error,
431
+ "latency_ms": run.latency_ms,
432
+ "start_time": run.start_time.isoformat(),
433
+ "end_time": run.end_time.isoformat(),
434
+ "trace_id": run.trace_id,
435
+ "prompt_token_count": run.prompt_token_count,
436
+ "completion_token_count": run.completion_token_count,
437
+ "annotations": annotations,
438
+ }
439
+ records.append(record)
440
+
441
+ return Response(
442
+ content=json.dumps(records, ensure_ascii=False, indent=2),
443
+ headers={"content-disposition": f'attachment; filename="{experiment.name}.json"'},
444
+ media_type="application/json",
391
445
  )
392
- if not runs_and_revisions:
393
- raise HTTPException(
394
- detail=f"Experiment with ID {experiment_globalid} has no runs",
395
- status_code=HTTP_404_NOT_FOUND,
396
- )
397
- records = []
398
- for run, revision in runs_and_revisions:
399
- annotations = []
400
- for annotation in run.annotations:
401
- annotations.append(
402
- {
403
- "name": annotation.name,
404
- "annotator_kind": annotation.annotator_kind,
405
- "label": annotation.label,
406
- "score": annotation.score,
407
- "explanation": annotation.explanation,
408
- "trace_id": annotation.trace_id,
409
- "error": annotation.error,
410
- "metadata": annotation.metadata_,
411
- "start_time": annotation.start_time.isoformat(),
412
- "end_time": annotation.end_time.isoformat(),
413
- }
446
+
447
+
448
+ @router.get(
449
+ "/experiments/{experiment_id}/csv",
450
+ operation_id="getExperimentCSV",
451
+ summary="Download experiment runs as a CSV file",
452
+ responses={**add_text_csv_content_to_responses(HTTP_200_OK)},
453
+ )
454
+ async def get_experiment_csv(
455
+ request: Request,
456
+ experiment_id: str = Path(..., title="Experiment ID"),
457
+ ) -> Response:
458
+ experiment_globalid = GlobalID.from_id(experiment_id)
459
+ try:
460
+ experiment_rowid = from_global_id_with_expected_type(experiment_globalid, "Experiment")
461
+ except ValueError:
462
+ raise HTTPException(
463
+ detail=f"Invalid experiment ID: {experiment_globalid}",
464
+ status_code=HTTP_422_UNPROCESSABLE_ENTITY,
465
+ )
466
+
467
+ async with request.app.state.db() as session:
468
+ experiment, runs, revisions = await _get_experiment_runs_and_revisions(
469
+ session, experiment_rowid
470
+ )
471
+ records = []
472
+ for run, revision in zip(runs, revisions):
473
+ serialized_run_output = (
474
+ json.dumps(run.output["task_output"])
475
+ if isinstance(run.output["task_output"], dict)
476
+ else run.output["task_output"]
414
477
  )
415
- record = {
416
- "example_id": str(
417
- GlobalID(models.DatasetExample.__name__, str(run.dataset_example_id))
418
- ),
419
- "repetition_number": run.repetition_number,
420
- "input": revision.input,
421
- "reference_output": revision.output,
422
- "output": run.output["task_output"],
423
- "error": run.error,
424
- "latency_ms": run.latency_ms,
425
- "start_time": run.start_time.isoformat(),
426
- "end_time": run.end_time.isoformat(),
427
- "trace_id": run.trace_id,
428
- "prompt_token_count": run.prompt_token_count,
429
- "completion_token_count": run.completion_token_count,
430
- "annotations": annotations,
431
- }
432
- records.append(record)
433
-
434
- response.headers["content-disposition"] = f'attachment; filename="{experiment.name}.json"'
435
- return json.dumps(records, ensure_ascii=False, indent=2)
478
+ record = {
479
+ "example_id": str(GlobalID("DatasetExample", str(run.dataset_example_id))),
480
+ "repetition_number": run.repetition_number,
481
+ "input": json.dumps(revision.input),
482
+ "reference_output": json.dumps(revision.output),
483
+ "output": serialized_run_output,
484
+ "error": run.error,
485
+ "latency_ms": run.latency_ms,
486
+ "start_time": run.start_time.isoformat(),
487
+ "end_time": run.end_time.isoformat(),
488
+ "trace_id": run.trace_id,
489
+ "prompt_token_count": run.prompt_token_count,
490
+ "completion_token_count": run.completion_token_count,
491
+ }
492
+ for annotation in run.annotations:
493
+ prefix = f"annotation_{annotation.name}"
494
+ record.update(
495
+ {
496
+ f"{prefix}_label": annotation.label,
497
+ f"{prefix}_score": annotation.score,
498
+ f"{prefix}_explanation": annotation.explanation,
499
+ f"{prefix}_metadata": json.dumps(annotation.metadata_),
500
+ f"{prefix}_annotator_kind": annotation.annotator_kind,
501
+ f"{prefix}_trace_id": annotation.trace_id,
502
+ f"{prefix}_error": annotation.error,
503
+ f"{prefix}_start_time": annotation.start_time.isoformat(),
504
+ f"{prefix}_end_time": annotation.end_time.isoformat(),
505
+ }
506
+ )
507
+ records.append(record)
508
+
509
+ df = pd.DataFrame.from_records(records)
510
+ csv_content = df.to_csv(index=False).encode()
511
+
512
+ return Response(
513
+ content=csv_content,
514
+ headers={
515
+ "content-disposition": f'attachment; filename="{experiment.name}.csv"',
516
+ "content-type": "text/csv",
517
+ },
518
+ )
@@ -1,28 +1,32 @@
1
1
  {
2
- "_components-CbkVj1OQ.js": {
3
- "file": "assets/components-CbkVj1OQ.js",
2
+ "_components-Igx2_EFY.js": {
3
+ "file": "assets/components-Igx2_EFY.js",
4
4
  "name": "components",
5
5
  "imports": [
6
- "_vendor-Byx9ZW8r.js",
7
- "_pages-CyqzCvBY.js",
8
- "_vendor-arizeai-0BoxbU9V.js",
9
- "_vendor-codemirror-BX78TDyo.js",
6
+ "_vendor-CwOvppSU.js",
7
+ "_pages-x8AxTGd1.js",
8
+ "_vendor-arizeai-bw-hz0cw.js",
9
+ "_vendor-codemirror-Du5cjwoZ.js",
10
10
  "_vendor-three-C-AGeJYv.js"
11
11
  ]
12
12
  },
13
- "_pages-CyqzCvBY.js": {
14
- "file": "assets/pages-CyqzCvBY.js",
13
+ "_pages-x8AxTGd1.js": {
14
+ "file": "assets/pages-x8AxTGd1.js",
15
15
  "name": "pages",
16
16
  "imports": [
17
- "_vendor-Byx9ZW8r.js",
18
- "_vendor-arizeai-0BoxbU9V.js",
19
- "_components-CbkVj1OQ.js",
20
- "_vendor-codemirror-BX78TDyo.js",
21
- "_vendor-recharts-BLborQJX.js"
17
+ "_vendor-CwOvppSU.js",
18
+ "_vendor-arizeai-bw-hz0cw.js",
19
+ "_components-Igx2_EFY.js",
20
+ "_vendor-codemirror-Du5cjwoZ.js",
21
+ "_vendor-recharts-BMT-13Pz.js"
22
22
  ]
23
23
  },
24
- "_vendor-Byx9ZW8r.js": {
25
- "file": "assets/vendor-Byx9ZW8r.js",
24
+ "_vendor-Cg6lcjUC.css": {
25
+ "file": "assets/vendor-Cg6lcjUC.css",
26
+ "src": "_vendor-Cg6lcjUC.css"
27
+ },
28
+ "_vendor-CwOvppSU.js": {
29
+ "file": "assets/vendor-CwOvppSU.js",
26
30
  "name": "vendor",
27
31
  "imports": [
28
32
  "_vendor-three-C-AGeJYv.js"
@@ -31,37 +35,33 @@
31
35
  "assets/vendor-Cg6lcjUC.css"
32
36
  ]
33
37
  },
34
- "_vendor-Cg6lcjUC.css": {
35
- "file": "assets/vendor-Cg6lcjUC.css",
36
- "src": "_vendor-Cg6lcjUC.css"
37
- },
38
- "_vendor-arizeai-0BoxbU9V.js": {
39
- "file": "assets/vendor-arizeai-0BoxbU9V.js",
38
+ "_vendor-arizeai-bw-hz0cw.js": {
39
+ "file": "assets/vendor-arizeai-bw-hz0cw.js",
40
40
  "name": "vendor-arizeai",
41
41
  "imports": [
42
- "_vendor-Byx9ZW8r.js"
42
+ "_vendor-CwOvppSU.js"
43
43
  ]
44
44
  },
45
- "_vendor-codemirror-BX78TDyo.js": {
46
- "file": "assets/vendor-codemirror-BX78TDyo.js",
45
+ "_vendor-codemirror-Du5cjwoZ.js": {
46
+ "file": "assets/vendor-codemirror-Du5cjwoZ.js",
47
47
  "name": "vendor-codemirror",
48
48
  "imports": [
49
- "_vendor-Byx9ZW8r.js",
50
- "_vendor-shiki-DpPpUgzv.js"
49
+ "_vendor-CwOvppSU.js",
50
+ "_vendor-shiki-CevgDqNV.js"
51
51
  ]
52
52
  },
53
- "_vendor-recharts-BLborQJX.js": {
54
- "file": "assets/vendor-recharts-BLborQJX.js",
53
+ "_vendor-recharts-BMT-13Pz.js": {
54
+ "file": "assets/vendor-recharts-BMT-13Pz.js",
55
55
  "name": "vendor-recharts",
56
56
  "imports": [
57
- "_vendor-Byx9ZW8r.js"
57
+ "_vendor-CwOvppSU.js"
58
58
  ]
59
59
  },
60
- "_vendor-shiki-DpPpUgzv.js": {
61
- "file": "assets/vendor-shiki-DpPpUgzv.js",
60
+ "_vendor-shiki-CevgDqNV.js": {
61
+ "file": "assets/vendor-shiki-CevgDqNV.js",
62
62
  "name": "vendor-shiki",
63
63
  "imports": [
64
- "_vendor-Byx9ZW8r.js"
64
+ "_vendor-CwOvppSU.js"
65
65
  ]
66
66
  },
67
67
  "_vendor-three-C-AGeJYv.js": {
@@ -69,19 +69,19 @@
69
69
  "name": "vendor-three"
70
70
  },
71
71
  "index.tsx": {
72
- "file": "assets/index-BNK6eNx3.js",
72
+ "file": "assets/index-C2-HcjQo.js",
73
73
  "name": "index",
74
74
  "src": "index.tsx",
75
75
  "isEntry": true,
76
76
  "imports": [
77
- "_vendor-Byx9ZW8r.js",
78
- "_vendor-arizeai-0BoxbU9V.js",
79
- "_pages-CyqzCvBY.js",
80
- "_components-CbkVj1OQ.js",
77
+ "_vendor-CwOvppSU.js",
78
+ "_vendor-arizeai-bw-hz0cw.js",
79
+ "_pages-x8AxTGd1.js",
80
+ "_components-Igx2_EFY.js",
81
81
  "_vendor-three-C-AGeJYv.js",
82
- "_vendor-codemirror-BX78TDyo.js",
83
- "_vendor-shiki-DpPpUgzv.js",
84
- "_vendor-recharts-BLborQJX.js"
82
+ "_vendor-codemirror-Du5cjwoZ.js",
83
+ "_vendor-shiki-CevgDqNV.js",
84
+ "_vendor-recharts-BMT-13Pz.js"
85
85
  ]
86
86
  }
87
87
  }