arize-phoenix 12.5.0__py3-none-any.whl → 12.6.1__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 (22) hide show
  1. {arize_phoenix-12.5.0.dist-info → arize_phoenix-12.6.1.dist-info}/METADATA +1 -1
  2. {arize_phoenix-12.5.0.dist-info → arize_phoenix-12.6.1.dist-info}/RECORD +19 -18
  3. phoenix/server/api/input_types/ExperimentRunSort.py +237 -0
  4. phoenix/server/api/types/Experiment.py +60 -25
  5. phoenix/server/static/.vite/manifest.json +44 -44
  6. phoenix/server/static/assets/{components-cwdYEs7B.js → components-XKc983B9.js} +353 -337
  7. phoenix/server/static/assets/{index-Dc0vD1Rn.js → index-DG8e74sg.js} +2 -2
  8. phoenix/server/static/assets/{pages-BDkB3a_a.js → pages-CSZW-lt0.js} +545 -577
  9. phoenix/server/static/assets/vendor-CQ4tN9P7.js +918 -0
  10. phoenix/server/static/assets/vendor-arizeai-Cb1ncvYH.js +106 -0
  11. phoenix/server/static/assets/{vendor-codemirror-Bv8J_7an.js → vendor-codemirror-CckmKopH.js} +3 -3
  12. phoenix/server/static/assets/{vendor-recharts-DcLgzI7g.js → vendor-recharts-BC1ysIKu.js} +1 -1
  13. phoenix/server/static/assets/{vendor-shiki-BF8rh_7m.js → vendor-shiki-B45T-YxN.js} +1 -1
  14. phoenix/server/static/assets/vendor-three-BtCyLs1w.js +3840 -0
  15. phoenix/version.py +1 -1
  16. phoenix/server/static/assets/vendor-Ce6GTAin.js +0 -914
  17. phoenix/server/static/assets/vendor-arizeai-CSF-1Kc5.js +0 -156
  18. phoenix/server/static/assets/vendor-three-BLWp5bic.js +0 -2998
  19. {arize_phoenix-12.5.0.dist-info → arize_phoenix-12.6.1.dist-info}/WHEEL +0 -0
  20. {arize_phoenix-12.5.0.dist-info → arize_phoenix-12.6.1.dist-info}/entry_points.txt +0 -0
  21. {arize_phoenix-12.5.0.dist-info → arize_phoenix-12.6.1.dist-info}/licenses/IP_NOTICE +0 -0
  22. {arize_phoenix-12.5.0.dist-info → arize_phoenix-12.6.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arize-phoenix
3
- Version: 12.5.0
3
+ Version: 12.6.1
4
4
  Summary: AI Observability and Evaluation
5
5
  Project-URL: Documentation, https://arize.com/docs/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=ngkyKGVatX3cO2WJdo2hKdaVKP-xJCMvqthvga6kJss,5196
8
8
  phoenix/settings.py,sha256=2kHfT3BNOVd4dAO1bq-syEQbHSG8oX2-7NhOwK2QREk,896
9
- phoenix/version.py,sha256=zMX_FM3cVT5krGMLJ7wIdD6aNccjxPXzb1MtTO9uB1g,23
9
+ phoenix/version.py,sha256=5g9mAbsc56Z-WYEJIjLkHlg26PEvy4WbqziZIvE-zXQ,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
@@ -234,6 +234,7 @@ phoenix/server/api/input_types/DeleteDatasetInput.py,sha256=p7xjCyWnVCIXHnezmDiW
234
234
  phoenix/server/api/input_types/DeleteExperimentsInput.py,sha256=4d9N0vSLYbuysAamGoPUP_m8vdVhwrZmXoi2vhy_HdI,141
235
235
  phoenix/server/api/input_types/DimensionFilter.py,sha256=eBYcn7ECSJQlEePvbStqkHBRicbIL4vEAzFJwX7bacQ,3137
236
236
  phoenix/server/api/input_types/DimensionInput.py,sha256=Vfx5FmiMKey4-EHDQsQRPzSAMRJMN5oVMLDUl4NKAa8,164
237
+ phoenix/server/api/input_types/ExperimentRunSort.py,sha256=3E7ucBdJCoC7u3tIKyqDxSorphUwPXDLPYFL_VJUvjs,8679
237
238
  phoenix/server/api/input_types/GenerativeCredentialInput.py,sha256=sEM9UtgDMMuhImLwGXctD8BWDs2V3hNQ1mosoklRZvc,219
238
239
  phoenix/server/api/input_types/GenerativeModelInput.py,sha256=M9R5LmLjyxOYPEIiann4ln23degOWJ2GTIk64bi73yI,841
239
240
  phoenix/server/api/input_types/Granularity.py,sha256=dbBlD_GsIBa8_xrx4JlLuR59bQ0NRB5H-cv1zvcb-cw,2299
@@ -337,7 +338,7 @@ phoenix/server/api/types/EvaluationSummary.py,sha256=vILYejnfPvMwWEXOwhQZsANvYe3
337
338
  phoenix/server/api/types/Event.py,sha256=iYt_Jx1Roioo0vZ0iPeJTHcTu6NSm4ilVMJ-IMUHAKk,3970
338
339
  phoenix/server/api/types/EventMetadata.py,sha256=-J0tYF9eZTHwCjwxQHY7Gckr2_MNW5OoWT1mydweZNM,635
339
340
  phoenix/server/api/types/ExampleRevisionInterface.py,sha256=gV3Gt9-3Oi5wjaVtepC6nOt3FzTzZFD1KebNnqiw56E,294
340
- phoenix/server/api/types/Experiment.py,sha256=hjmQH93_BQXfO98-bf_pJn3BUjEoog9gUCaC8uH4cvg,8105
341
+ phoenix/server/api/types/Experiment.py,sha256=ogE0Ks6MPIc5zeA77iyGNv5Y1pz_-NM1GU_de7-QSq8,9532
341
342
  phoenix/server/api/types/ExperimentAnnotationSummary.py,sha256=Uk3JtxIrsMoZT5tqc4nJdUOM3XegVzjUyoV3pkjNotE,256
342
343
  phoenix/server/api/types/ExperimentComparison.py,sha256=fj4KoAPFNJvfrHBMloGkHz_-7Lf403IMe0OwDDJyZWk,383
343
344
  phoenix/server/api/types/ExperimentRepeatedRunGroup.py,sha256=a2-C6ZWyFm-THvus6n4WDYGtmapm-ZWFaDahW7A2i28,5434
@@ -429,17 +430,17 @@ phoenix/server/static/apple-touch-icon-76x76.png,sha256=CT_xT12I0u2i0WU8JzBZBuOQ
429
430
  phoenix/server/static/apple-touch-icon.png,sha256=fOfpjqGpWYbJ0eAurKsyoZP1EAs6ZVooBJ_SGk2ZkDs,3801
430
431
  phoenix/server/static/favicon.ico,sha256=bY0vvCKRftemZfPShwZtE93DiiQdaYaozkPGwNFr6H8,34494
431
432
  phoenix/server/static/modernizr.js,sha256=mvK-XtkNqjOral-QvzoqsyOMECXIMu5BQwSVN_wcU9c,2564
432
- phoenix/server/static/.vite/manifest.json,sha256=silunyyOmVAGQps_jL9aTV8d03ZDtMiKo-42b52u49Q,2328
433
- phoenix/server/static/assets/components-cwdYEs7B.js,sha256=47qfw-bmftAyo9ANA81Ot24VTAU1kI35jML_g92NJt4,741688
434
- phoenix/server/static/assets/index-Dc0vD1Rn.js,sha256=zVCMmPbr4UA6BhhArmzav1NSOcjFv76EqqSbbUedLYs,63680
435
- phoenix/server/static/assets/pages-BDkB3a_a.js,sha256=CgzTVVYSyPnUWbQs_W0uf7fKDcYjklApAUDtiVyQ-zg,1352672
433
+ phoenix/server/static/.vite/manifest.json,sha256=xjC7Tc_4Oa2WqQ1jlEBc0llni_KaPR5Rj7nADQW7DIY,2328
434
+ phoenix/server/static/assets/components-XKc983B9.js,sha256=WyPG4CLRKAAV1VwdXyxAPbLf1824QZo4-mfCkuJI-vA,743011
435
+ phoenix/server/static/assets/index-DG8e74sg.js,sha256=ejjKc6edp7sIicanTRqOn44J7jE6zQ1D1vZ_2CCQsi8,63675
436
+ phoenix/server/static/assets/pages-CSZW-lt0.js,sha256=xE94-qOHiiGbsBYIbDdpESSsh8yxM5EoElbA5Z5z7-k,1352228
436
437
  phoenix/server/static/assets/vendor-BGzfc4EU.css,sha256=Nx5Lmx-bqYR7nsO_O4kEBcrJ8cwknWjZ6seHN3_s4UQ,3171
437
- phoenix/server/static/assets/vendor-Ce6GTAin.js,sha256=k2-KQcgnT72FQrld80rZ4i6xwZFLNOXYnzoxmafJ6dM,2709850
438
- phoenix/server/static/assets/vendor-arizeai-CSF-1Kc5.js,sha256=QzLkcp6Qyc8cBmTC-_HMBFskV7RH5C_gJL01tg5D398,107788
439
- phoenix/server/static/assets/vendor-codemirror-Bv8J_7an.js,sha256=OHKLBrrONfwV0plQcCulUS0pgeKYDaT_ZvYxxcwZYtE,413211
440
- phoenix/server/static/assets/vendor-recharts-DcLgzI7g.js,sha256=1ncLhLxQPfQC_7j90ms3HMxU_OLIWlna70T1kyoTDR8,231652
441
- phoenix/server/static/assets/vendor-shiki-BF8rh_7m.js,sha256=YcEirsQMcEwHSIUVn7A7Y65zo9Qs3wQxdY5J-hG18D8,305160
442
- phoenix/server/static/assets/vendor-three-BLWp5bic.js,sha256=vfSCVXS20jA0Ceo_O0mDxYBcROinWMdPE6RR4JXmtec,620972
438
+ phoenix/server/static/assets/vendor-CQ4tN9P7.js,sha256=T-jwFQyaPcs9NTsr1-LgZyTCOHuU79yNocsMVZpL9os,2647461
439
+ phoenix/server/static/assets/vendor-arizeai-Cb1ncvYH.js,sha256=SEQFDzn04e85NQ20oEHzQUtkhBghcT_ihBRr3PCX2_4,61555
440
+ phoenix/server/static/assets/vendor-codemirror-CckmKopH.js,sha256=MQyNw9XiM5yirfH35t-lnEh07dgg29PdGhy4iUiv9vA,413211
441
+ phoenix/server/static/assets/vendor-recharts-BC1ysIKu.js,sha256=D38l2FL3n4q-emg9NWzdIhye8tleaur0d9Qu-lMXgIE,231652
442
+ phoenix/server/static/assets/vendor-shiki-B45T-YxN.js,sha256=EaUAfs0m0Gy_vwpnvWRTZQpiSIHxcmaY6tk_ASmiPPQ,305160
443
+ phoenix/server/static/assets/vendor-three-BtCyLs1w.js,sha256=E6e1HbskKn61fWQPWmiZiPXXGHfZYn-30v0nofpDaqo,704132
443
444
  phoenix/server/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
444
445
  phoenix/server/templates/index.html,sha256=_iKIyXEDDr5cTTnrUCjCd617U6Alc1k-IXtdKSt8g14,7215
445
446
  phoenix/session/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -478,9 +479,9 @@ phoenix/utilities/project.py,sha256=auVpARXkDb-JgeX5f2aStyFIkeKvGwN9l7qrFeJMVxI,
478
479
  phoenix/utilities/re.py,sha256=6YyUWIkv0zc2SigsxfOWIHzdpjKA_TZo2iqKq7zJKvw,2081
479
480
  phoenix/utilities/span_store.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
480
481
  phoenix/utilities/template_formatters.py,sha256=gh9PJD6WEGw7TEYXfSst1UR4pWWwmjxMLrDVQ_CkpkQ,2779
481
- arize_phoenix-12.5.0.dist-info/METADATA,sha256=8noiF2OI1hEI45XtrnDzongEmwKPvXAfSFkeMpPXgmY,34069
482
- arize_phoenix-12.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
483
- arize_phoenix-12.5.0.dist-info/entry_points.txt,sha256=Pgpn8Upxx9P8z8joPXZWl2LlnAlGc3gcQoVchb06X1Q,94
484
- arize_phoenix-12.5.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
485
- arize_phoenix-12.5.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
486
- arize_phoenix-12.5.0.dist-info/RECORD,,
482
+ arize_phoenix-12.6.1.dist-info/METADATA,sha256=_YG43PHQEpq_VPoXQQLML-JbaCy6f3f_eM3PupHFE50,34069
483
+ arize_phoenix-12.6.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
484
+ arize_phoenix-12.6.1.dist-info/entry_points.txt,sha256=Pgpn8Upxx9P8z8joPXZWl2LlnAlGc3gcQoVchb06X1Q,94
485
+ arize_phoenix-12.6.1.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
486
+ arize_phoenix-12.6.1.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
487
+ arize_phoenix-12.6.1.dist-info/RECORD,,
@@ -0,0 +1,237 @@
1
+ import operator
2
+ from enum import Enum, auto
3
+ from typing import Any, Optional
4
+
5
+ import strawberry
6
+ from sqlalchemy import ColumnElement, Select, and_, func, literal, or_, select, tuple_
7
+ from sqlalchemy.sql.selectable import NamedFromClause
8
+ from strawberry import Maybe
9
+ from typing_extensions import assert_never
10
+
11
+ from phoenix.db import models
12
+ from phoenix.server.api.types.pagination import (
13
+ Cursor,
14
+ CursorSortColumn,
15
+ CursorSortColumnDataType,
16
+ CursorSortColumnValue,
17
+ )
18
+ from phoenix.server.api.types.SortDir import SortDir
19
+
20
+
21
+ @strawberry.enum
22
+ class ExperimentRunMetric(Enum):
23
+ latencyMs = auto()
24
+
25
+
26
+ @strawberry.input(one_of=True)
27
+ class ExperimentRunColumn:
28
+ metric: Maybe[ExperimentRunMetric]
29
+ annotation_name: Maybe[str]
30
+
31
+
32
+ @strawberry.input(description="The sort key and direction for experiment run connections")
33
+ class ExperimentRunSort:
34
+ col: ExperimentRunColumn
35
+ dir: SortDir
36
+
37
+
38
+ def get_experiment_run_cursor(
39
+ run: models.ExperimentRun, annotation_score: Optional[float], sort: Optional[ExperimentRunSort]
40
+ ) -> Cursor:
41
+ sort_column: Optional[CursorSortColumn] = None
42
+ if sort:
43
+ if sort.col.metric:
44
+ metric = sort.col.metric.value
45
+ assert metric is not None
46
+ if metric is ExperimentRunMetric.latencyMs:
47
+ sort_column = CursorSortColumn(
48
+ type=CursorSortColumnDataType.FLOAT,
49
+ value=run.latency_ms,
50
+ )
51
+ else:
52
+ assert_never(metric)
53
+ elif sort.col.annotation_name:
54
+ data_type = (
55
+ CursorSortColumnDataType.FLOAT
56
+ if annotation_score is not None
57
+ else CursorSortColumnDataType.NULL
58
+ )
59
+ sort_column = CursorSortColumn(
60
+ type=data_type,
61
+ value=annotation_score,
62
+ )
63
+ return Cursor(rowid=run.id, sort_column=sort_column)
64
+
65
+
66
+ def add_order_by_and_page_start_to_query(
67
+ query: Select[Any],
68
+ sort: Optional[ExperimentRunSort],
69
+ experiment_rowid: int,
70
+ after_experiment_run_rowid: Optional[int],
71
+ after_sort_column_value: Optional[CursorSortColumnValue] = None,
72
+ ) -> Select[Any]:
73
+ mean_annotation_scores: Optional[NamedFromClause] = None
74
+ if sort and sort.col.annotation_name:
75
+ annotation_name = sort.col.annotation_name.value
76
+ assert annotation_name is not None
77
+ mean_annotation_scores = _get_mean_annotation_scores_subquery(annotation_name)
78
+ order_by_columns = _get_order_by_columns(
79
+ sort=sort, experiment_rowid=experiment_rowid, mean_annotation_scores=mean_annotation_scores
80
+ )
81
+ query = query.order_by(*order_by_columns)
82
+ if after_experiment_run_rowid is not None:
83
+ query = _add_after_expression(
84
+ query=query,
85
+ sort=sort,
86
+ experiment_run_rowid=after_experiment_run_rowid,
87
+ after_sort_column_value=after_sort_column_value,
88
+ mean_annotation_scores=mean_annotation_scores,
89
+ )
90
+ query = _add_joins_and_selects_to_query(
91
+ query=query,
92
+ sort=sort,
93
+ mean_annotation_scores=mean_annotation_scores,
94
+ )
95
+ return query
96
+
97
+
98
+ def _get_order_by_columns(
99
+ sort: Optional[ExperimentRunSort],
100
+ experiment_rowid: int,
101
+ mean_annotation_scores: Optional[NamedFromClause],
102
+ ) -> tuple[ColumnElement[Any], ...]:
103
+ if not sort:
104
+ # Ideally, this would sort the runs by (example_id, repetition_number),
105
+ # but this would require making the cursor more complex or adding an additional query
106
+ # to handle the after cursor.
107
+ return (models.ExperimentRun.id.asc(),)
108
+ sort_direction = sort.dir
109
+ if sort.col.metric:
110
+ metric = sort.col.metric.value
111
+ assert metric is not None
112
+ if metric is ExperimentRunMetric.latencyMs:
113
+ if sort_direction is SortDir.asc:
114
+ return (models.ExperimentRun.latency_ms.asc(), models.ExperimentRun.id.asc())
115
+ else:
116
+ return (models.ExperimentRun.latency_ms.desc(), models.ExperimentRun.id.desc())
117
+ else:
118
+ assert_never(metric)
119
+ elif sort.col.annotation_name:
120
+ annotation_name = sort.col.annotation_name.value
121
+ assert annotation_name is not None
122
+ assert mean_annotation_scores is not None
123
+ if sort_direction is SortDir.asc:
124
+ return (
125
+ mean_annotation_scores.c.score.asc().nulls_last(),
126
+ models.ExperimentRun.id.asc(),
127
+ )
128
+ else:
129
+ return (
130
+ mean_annotation_scores.c.score.desc().nulls_last(),
131
+ models.ExperimentRun.id.desc(),
132
+ )
133
+ raise NotImplementedError
134
+
135
+
136
+ def _add_after_expression(
137
+ query: Select[Any],
138
+ sort: Optional[ExperimentRunSort],
139
+ experiment_run_rowid: int,
140
+ after_sort_column_value: Optional[CursorSortColumnValue],
141
+ mean_annotation_scores: Optional[NamedFromClause],
142
+ ) -> Select[Any]:
143
+ if not sort:
144
+ # Ideally, this would return the runs sorted by (example_id, repetition_number),
145
+ # but this would require making the cursor more complex or adding an additional query.
146
+ return query.where(models.ExperimentRun.id > literal(experiment_run_rowid))
147
+ sort_direction = sort.dir
148
+ compare_fn = operator.gt if sort_direction is SortDir.asc else operator.lt
149
+ if sort.col.metric:
150
+ metric = sort.col.metric.value
151
+ assert metric is not None
152
+ if metric is ExperimentRunMetric.latencyMs:
153
+ assert after_sort_column_value is not None
154
+ return query.where(
155
+ compare_fn(
156
+ tuple_(models.ExperimentRun.latency_ms, models.ExperimentRun.id),
157
+ tuple_(
158
+ literal(after_sort_column_value),
159
+ literal(experiment_run_rowid),
160
+ ),
161
+ )
162
+ )
163
+ else:
164
+ assert_never(metric)
165
+ elif sort.col.annotation_name:
166
+ annotation_name = sort.col.annotation_name.value
167
+ assert annotation_name is not None
168
+ assert mean_annotation_scores is not None
169
+ if after_sort_column_value is None:
170
+ return query.where(
171
+ and_(
172
+ compare_fn(models.ExperimentRun.id, literal(experiment_run_rowid)),
173
+ mean_annotation_scores.c.score.is_(None),
174
+ )
175
+ )
176
+ else:
177
+ return query.where(
178
+ or_(
179
+ compare_fn(
180
+ tuple_(mean_annotation_scores.c.score, models.ExperimentRun.id),
181
+ tuple_(
182
+ literal(after_sort_column_value),
183
+ literal(experiment_run_rowid),
184
+ ),
185
+ ),
186
+ mean_annotation_scores.c.score.is_(None),
187
+ )
188
+ )
189
+ raise NotImplementedError
190
+
191
+
192
+ def _get_mean_annotation_scores_subquery(annotation_name: str) -> NamedFromClause:
193
+ return (
194
+ select(
195
+ func.avg(models.ExperimentRunAnnotation.score).label("score"),
196
+ models.ExperimentRunAnnotation.experiment_run_id.label("experiment_run_id"),
197
+ )
198
+ .select_from(models.ExperimentRunAnnotation)
199
+ .join(
200
+ models.ExperimentRun,
201
+ models.ExperimentRunAnnotation.experiment_run_id == models.ExperimentRun.id,
202
+ )
203
+ .where(models.ExperimentRunAnnotation.name == annotation_name)
204
+ .group_by(models.ExperimentRunAnnotation.experiment_run_id)
205
+ .subquery()
206
+ .alias("mean_annotation_scores")
207
+ )
208
+
209
+
210
+ def _add_joins_and_selects_to_query(
211
+ query: Select[tuple[models.ExperimentRun]],
212
+ sort: Optional[ExperimentRunSort],
213
+ mean_annotation_scores: Optional[NamedFromClause],
214
+ ) -> Select[tuple[models.ExperimentRun]]:
215
+ if not sort:
216
+ return query
217
+ if sort.col.metric:
218
+ metric = sort.col.metric.value
219
+ assert metric is not None
220
+ if metric is ExperimentRunMetric.latencyMs:
221
+ return query
222
+ else:
223
+ assert_never(metric)
224
+ elif sort.col.annotation_name:
225
+ annotation_name = sort.col.annotation_name.value
226
+ assert annotation_name is not None
227
+ assert mean_annotation_scores is not None
228
+ query = query.join(
229
+ mean_annotation_scores,
230
+ mean_annotation_scores.c.experiment_run_id == models.ExperimentRun.id,
231
+ isouter=True,
232
+ )
233
+ query = query.add_columns(
234
+ mean_annotation_scores.c.score.label("score")
235
+ ) # the score must be in the select so that the value can be included in the cursor
236
+ return query
237
+ raise NotImplementedError
@@ -11,19 +11,27 @@ from strawberry.types import Info
11
11
 
12
12
  from phoenix.db import models
13
13
  from phoenix.server.api.context import Context
14
+ from phoenix.server.api.exceptions import BadRequest
15
+ from phoenix.server.api.input_types.ExperimentRunSort import (
16
+ ExperimentRunSort,
17
+ add_order_by_and_page_start_to_query,
18
+ get_experiment_run_cursor,
19
+ )
14
20
  from phoenix.server.api.types.CostBreakdown import CostBreakdown
15
21
  from phoenix.server.api.types.DatasetVersion import DatasetVersion
16
22
  from phoenix.server.api.types.ExperimentAnnotationSummary import ExperimentAnnotationSummary
17
23
  from phoenix.server.api.types.ExperimentRun import ExperimentRun, to_gql_experiment_run
18
24
  from phoenix.server.api.types.pagination import (
19
- ConnectionArgs,
25
+ Cursor,
20
26
  CursorString,
21
- connection_from_list,
27
+ connection_from_cursors_and_nodes,
22
28
  )
23
29
  from phoenix.server.api.types.Project import Project
24
30
  from phoenix.server.api.types.SpanCostDetailSummaryEntry import SpanCostDetailSummaryEntry
25
31
  from phoenix.server.api.types.SpanCostSummary import SpanCostSummary
26
32
 
33
+ _DEFAULT_EXPERIMENT_RUNS_PAGE_SIZE = 50
34
+
27
35
 
28
36
  @strawberry.type
29
37
  class Experiment(Node):
@@ -57,33 +65,60 @@ class Experiment(Node):
57
65
  async def runs(
58
66
  self,
59
67
  info: Info[Context, None],
60
- first: Optional[int] = 50,
61
- last: Optional[int] = UNSET,
68
+ first: Optional[int] = _DEFAULT_EXPERIMENT_RUNS_PAGE_SIZE,
62
69
  after: Optional[CursorString] = UNSET,
63
- before: Optional[CursorString] = UNSET,
70
+ sort: Optional[ExperimentRunSort] = UNSET,
64
71
  ) -> Connection[ExperimentRun]:
65
- args = ConnectionArgs(
66
- first=first,
67
- after=after if isinstance(after, CursorString) else None,
68
- last=last,
69
- before=before if isinstance(before, CursorString) else None,
72
+ if first is not None and first <= 0:
73
+ raise BadRequest("first must be a positive integer if set")
74
+ experiment_rowid = self.id_attr
75
+ page_size = first or _DEFAULT_EXPERIMENT_RUNS_PAGE_SIZE
76
+ experiment_runs_query = (
77
+ select(models.ExperimentRun)
78
+ .where(models.ExperimentRun.experiment_id == experiment_rowid)
79
+ .options(joinedload(models.ExperimentRun.trace).load_only(models.Trace.trace_id))
80
+ .limit(page_size + 1)
70
81
  )
71
- experiment_id = self.id_attr
82
+
83
+ after_experiment_run_rowid = None
84
+ after_sort_column_value = None
85
+ if after:
86
+ cursor = Cursor.from_string(after)
87
+ after_experiment_run_rowid = cursor.rowid
88
+ if cursor.sort_column is not None:
89
+ after_sort_column_value = cursor.sort_column.value
90
+
91
+ experiment_runs_query = add_order_by_and_page_start_to_query(
92
+ query=experiment_runs_query,
93
+ sort=sort,
94
+ experiment_rowid=experiment_rowid,
95
+ after_experiment_run_rowid=after_experiment_run_rowid,
96
+ after_sort_column_value=after_sort_column_value,
97
+ )
98
+
72
99
  async with info.context.db() as session:
73
- runs = (
74
- await session.scalars(
75
- select(models.ExperimentRun)
76
- .where(models.ExperimentRun.experiment_id == experiment_id)
77
- .order_by(
78
- models.ExperimentRun.dataset_example_id.asc(),
79
- models.ExperimentRun.repetition_number.asc(),
80
- )
81
- .options(
82
- joinedload(models.ExperimentRun.trace).load_only(models.Trace.trace_id)
83
- )
84
- )
85
- ).all()
86
- return connection_from_list([to_gql_experiment_run(run) for run in runs], args)
100
+ results = (await session.execute(experiment_runs_query)).all()
101
+
102
+ has_next_page = False
103
+ if len(results) > page_size:
104
+ results = results[:page_size]
105
+ has_next_page = True
106
+
107
+ cursors_and_nodes = []
108
+ for result in results:
109
+ run = result[0]
110
+ annotation_score = result[1] if len(result) > 1 else None
111
+ gql_run = to_gql_experiment_run(run)
112
+ cursor = get_experiment_run_cursor(
113
+ run=run, annotation_score=annotation_score, sort=sort
114
+ )
115
+ cursors_and_nodes.append((cursor, gql_run))
116
+
117
+ return connection_from_cursors_and_nodes(
118
+ cursors_and_nodes=cursors_and_nodes,
119
+ has_previous_page=False, # set to false since we are only doing forward pagination (https://relay.dev/graphql/connections.htm#sec-undefined.PageInfo.Fields) # noqa: E501
120
+ has_next_page=has_next_page,
121
+ )
87
122
 
88
123
  @strawberry.field
89
124
  async def run_count(self, info: Info[Context, None]) -> int:
@@ -1,93 +1,93 @@
1
1
  {
2
- "_components-cwdYEs7B.js": {
3
- "file": "assets/components-cwdYEs7B.js",
2
+ "_components-XKc983B9.js": {
3
+ "file": "assets/components-XKc983B9.js",
4
4
  "name": "components",
5
5
  "imports": [
6
- "_vendor-Ce6GTAin.js",
7
- "_pages-BDkB3a_a.js",
8
- "_vendor-arizeai-CSF-1Kc5.js",
9
- "_vendor-codemirror-Bv8J_7an.js",
10
- "_vendor-three-BLWp5bic.js"
6
+ "_vendor-CQ4tN9P7.js",
7
+ "_pages-CSZW-lt0.js",
8
+ "_vendor-arizeai-Cb1ncvYH.js",
9
+ "_vendor-codemirror-CckmKopH.js",
10
+ "_vendor-three-BtCyLs1w.js"
11
11
  ]
12
12
  },
13
- "_pages-BDkB3a_a.js": {
14
- "file": "assets/pages-BDkB3a_a.js",
13
+ "_pages-CSZW-lt0.js": {
14
+ "file": "assets/pages-CSZW-lt0.js",
15
15
  "name": "pages",
16
16
  "imports": [
17
- "_vendor-Ce6GTAin.js",
18
- "_components-cwdYEs7B.js",
19
- "_vendor-arizeai-CSF-1Kc5.js",
20
- "_vendor-codemirror-Bv8J_7an.js",
21
- "_vendor-recharts-DcLgzI7g.js"
17
+ "_vendor-CQ4tN9P7.js",
18
+ "_components-XKc983B9.js",
19
+ "_vendor-arizeai-Cb1ncvYH.js",
20
+ "_vendor-codemirror-CckmKopH.js",
21
+ "_vendor-recharts-BC1ysIKu.js"
22
22
  ]
23
23
  },
24
24
  "_vendor-BGzfc4EU.css": {
25
25
  "file": "assets/vendor-BGzfc4EU.css",
26
26
  "src": "_vendor-BGzfc4EU.css"
27
27
  },
28
- "_vendor-Ce6GTAin.js": {
29
- "file": "assets/vendor-Ce6GTAin.js",
28
+ "_vendor-CQ4tN9P7.js": {
29
+ "file": "assets/vendor-CQ4tN9P7.js",
30
30
  "name": "vendor",
31
31
  "imports": [
32
- "_vendor-three-BLWp5bic.js"
32
+ "_vendor-three-BtCyLs1w.js"
33
33
  ],
34
34
  "css": [
35
35
  "assets/vendor-BGzfc4EU.css"
36
36
  ]
37
37
  },
38
- "_vendor-arizeai-CSF-1Kc5.js": {
39
- "file": "assets/vendor-arizeai-CSF-1Kc5.js",
38
+ "_vendor-arizeai-Cb1ncvYH.js": {
39
+ "file": "assets/vendor-arizeai-Cb1ncvYH.js",
40
40
  "name": "vendor-arizeai",
41
41
  "imports": [
42
- "_vendor-Ce6GTAin.js"
42
+ "_vendor-CQ4tN9P7.js"
43
43
  ]
44
44
  },
45
- "_vendor-codemirror-Bv8J_7an.js": {
46
- "file": "assets/vendor-codemirror-Bv8J_7an.js",
45
+ "_vendor-codemirror-CckmKopH.js": {
46
+ "file": "assets/vendor-codemirror-CckmKopH.js",
47
47
  "name": "vendor-codemirror",
48
48
  "imports": [
49
- "_vendor-Ce6GTAin.js",
50
- "_vendor-shiki-BF8rh_7m.js"
49
+ "_vendor-CQ4tN9P7.js",
50
+ "_vendor-shiki-B45T-YxN.js"
51
51
  ],
52
52
  "dynamicImports": [
53
- "_vendor-shiki-BF8rh_7m.js",
54
- "_vendor-shiki-BF8rh_7m.js",
55
- "_vendor-shiki-BF8rh_7m.js"
53
+ "_vendor-shiki-B45T-YxN.js",
54
+ "_vendor-shiki-B45T-YxN.js",
55
+ "_vendor-shiki-B45T-YxN.js"
56
56
  ]
57
57
  },
58
- "_vendor-recharts-DcLgzI7g.js": {
59
- "file": "assets/vendor-recharts-DcLgzI7g.js",
58
+ "_vendor-recharts-BC1ysIKu.js": {
59
+ "file": "assets/vendor-recharts-BC1ysIKu.js",
60
60
  "name": "vendor-recharts",
61
61
  "imports": [
62
- "_vendor-Ce6GTAin.js"
62
+ "_vendor-CQ4tN9P7.js"
63
63
  ]
64
64
  },
65
- "_vendor-shiki-BF8rh_7m.js": {
66
- "file": "assets/vendor-shiki-BF8rh_7m.js",
65
+ "_vendor-shiki-B45T-YxN.js": {
66
+ "file": "assets/vendor-shiki-B45T-YxN.js",
67
67
  "name": "vendor-shiki",
68
68
  "isDynamicEntry": true,
69
69
  "imports": [
70
- "_vendor-Ce6GTAin.js"
70
+ "_vendor-CQ4tN9P7.js"
71
71
  ]
72
72
  },
73
- "_vendor-three-BLWp5bic.js": {
74
- "file": "assets/vendor-three-BLWp5bic.js",
73
+ "_vendor-three-BtCyLs1w.js": {
74
+ "file": "assets/vendor-three-BtCyLs1w.js",
75
75
  "name": "vendor-three"
76
76
  },
77
77
  "index.tsx": {
78
- "file": "assets/index-Dc0vD1Rn.js",
78
+ "file": "assets/index-DG8e74sg.js",
79
79
  "name": "index",
80
80
  "src": "index.tsx",
81
81
  "isEntry": true,
82
82
  "imports": [
83
- "_vendor-Ce6GTAin.js",
84
- "_vendor-arizeai-CSF-1Kc5.js",
85
- "_pages-BDkB3a_a.js",
86
- "_components-cwdYEs7B.js",
87
- "_vendor-three-BLWp5bic.js",
88
- "_vendor-codemirror-Bv8J_7an.js",
89
- "_vendor-shiki-BF8rh_7m.js",
90
- "_vendor-recharts-DcLgzI7g.js"
83
+ "_vendor-CQ4tN9P7.js",
84
+ "_vendor-arizeai-Cb1ncvYH.js",
85
+ "_pages-CSZW-lt0.js",
86
+ "_components-XKc983B9.js",
87
+ "_vendor-three-BtCyLs1w.js",
88
+ "_vendor-codemirror-CckmKopH.js",
89
+ "_vendor-shiki-B45T-YxN.js",
90
+ "_vendor-recharts-BC1ysIKu.js"
91
91
  ]
92
92
  }
93
93
  }