arize-phoenix 4.4.4rc5__py3-none-any.whl → 4.5.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 (118) hide show
  1. {arize_phoenix-4.4.4rc5.dist-info → arize_phoenix-4.5.0.dist-info}/METADATA +5 -5
  2. {arize_phoenix-4.4.4rc5.dist-info → arize_phoenix-4.5.0.dist-info}/RECORD +56 -117
  3. {arize_phoenix-4.4.4rc5.dist-info → arize_phoenix-4.5.0.dist-info}/WHEEL +1 -1
  4. phoenix/__init__.py +27 -0
  5. phoenix/config.py +7 -21
  6. phoenix/core/model.py +25 -25
  7. phoenix/core/model_schema.py +62 -64
  8. phoenix/core/model_schema_adapter.py +25 -27
  9. phoenix/db/bulk_inserter.py +14 -54
  10. phoenix/db/insertion/evaluation.py +6 -6
  11. phoenix/db/insertion/helpers.py +2 -13
  12. phoenix/db/migrations/versions/cf03bd6bae1d_init.py +28 -2
  13. phoenix/db/models.py +4 -236
  14. phoenix/inferences/fixtures.py +23 -23
  15. phoenix/inferences/inferences.py +7 -7
  16. phoenix/inferences/validation.py +1 -1
  17. phoenix/server/api/context.py +0 -18
  18. phoenix/server/api/dataloaders/__init__.py +0 -18
  19. phoenix/server/api/dataloaders/span_descendants.py +3 -2
  20. phoenix/server/api/routers/v1/__init__.py +2 -77
  21. phoenix/server/api/routers/v1/evaluations.py +2 -4
  22. phoenix/server/api/routers/v1/spans.py +1 -3
  23. phoenix/server/api/routers/v1/traces.py +4 -1
  24. phoenix/server/api/schema.py +303 -2
  25. phoenix/server/api/types/Cluster.py +19 -19
  26. phoenix/server/api/types/Dataset.py +63 -282
  27. phoenix/server/api/types/DatasetRole.py +23 -0
  28. phoenix/server/api/types/Dimension.py +29 -30
  29. phoenix/server/api/types/EmbeddingDimension.py +34 -40
  30. phoenix/server/api/types/Event.py +16 -16
  31. phoenix/server/api/{mutations/export_events_mutations.py → types/ExportEventsMutation.py} +14 -17
  32. phoenix/server/api/types/Model.py +42 -43
  33. phoenix/server/api/types/Project.py +12 -26
  34. phoenix/server/api/types/Span.py +2 -79
  35. phoenix/server/api/types/TimeSeries.py +6 -6
  36. phoenix/server/api/types/Trace.py +4 -15
  37. phoenix/server/api/types/UMAPPoints.py +1 -1
  38. phoenix/server/api/types/node.py +111 -5
  39. phoenix/server/api/types/pagination.py +52 -10
  40. phoenix/server/app.py +49 -101
  41. phoenix/server/main.py +27 -49
  42. phoenix/server/openapi/docs.py +0 -3
  43. phoenix/server/static/index.js +2595 -3523
  44. phoenix/server/templates/index.html +0 -1
  45. phoenix/services.py +15 -15
  46. phoenix/session/client.py +21 -438
  47. phoenix/session/session.py +37 -47
  48. phoenix/trace/exporter.py +9 -14
  49. phoenix/trace/fixtures.py +7 -133
  50. phoenix/trace/schemas.py +2 -1
  51. phoenix/trace/span_evaluations.py +3 -3
  52. phoenix/trace/trace_dataset.py +6 -6
  53. phoenix/version.py +1 -1
  54. phoenix/datasets/__init__.py +0 -0
  55. phoenix/datasets/evaluators/__init__.py +0 -18
  56. phoenix/datasets/evaluators/code_evaluators.py +0 -99
  57. phoenix/datasets/evaluators/llm_evaluators.py +0 -244
  58. phoenix/datasets/evaluators/utils.py +0 -292
  59. phoenix/datasets/experiments.py +0 -550
  60. phoenix/datasets/tracing.py +0 -85
  61. phoenix/datasets/types.py +0 -178
  62. phoenix/db/insertion/dataset.py +0 -237
  63. phoenix/db/migrations/types.py +0 -29
  64. phoenix/db/migrations/versions/10460e46d750_datasets.py +0 -291
  65. phoenix/server/api/dataloaders/dataset_example_revisions.py +0 -100
  66. phoenix/server/api/dataloaders/dataset_example_spans.py +0 -43
  67. phoenix/server/api/dataloaders/experiment_annotation_summaries.py +0 -85
  68. phoenix/server/api/dataloaders/experiment_error_rates.py +0 -43
  69. phoenix/server/api/dataloaders/experiment_run_counts.py +0 -42
  70. phoenix/server/api/dataloaders/experiment_sequence_number.py +0 -49
  71. phoenix/server/api/dataloaders/project_by_name.py +0 -31
  72. phoenix/server/api/dataloaders/span_projects.py +0 -33
  73. phoenix/server/api/dataloaders/trace_row_ids.py +0 -39
  74. phoenix/server/api/helpers/dataset_helpers.py +0 -179
  75. phoenix/server/api/input_types/AddExamplesToDatasetInput.py +0 -16
  76. phoenix/server/api/input_types/AddSpansToDatasetInput.py +0 -14
  77. phoenix/server/api/input_types/ClearProjectInput.py +0 -15
  78. phoenix/server/api/input_types/CreateDatasetInput.py +0 -12
  79. phoenix/server/api/input_types/DatasetExampleInput.py +0 -14
  80. phoenix/server/api/input_types/DatasetSort.py +0 -17
  81. phoenix/server/api/input_types/DatasetVersionSort.py +0 -16
  82. phoenix/server/api/input_types/DeleteDatasetExamplesInput.py +0 -13
  83. phoenix/server/api/input_types/DeleteDatasetInput.py +0 -7
  84. phoenix/server/api/input_types/DeleteExperimentsInput.py +0 -9
  85. phoenix/server/api/input_types/PatchDatasetExamplesInput.py +0 -35
  86. phoenix/server/api/input_types/PatchDatasetInput.py +0 -14
  87. phoenix/server/api/mutations/__init__.py +0 -13
  88. phoenix/server/api/mutations/auth.py +0 -11
  89. phoenix/server/api/mutations/dataset_mutations.py +0 -520
  90. phoenix/server/api/mutations/experiment_mutations.py +0 -65
  91. phoenix/server/api/mutations/project_mutations.py +0 -47
  92. phoenix/server/api/openapi/__init__.py +0 -0
  93. phoenix/server/api/openapi/main.py +0 -6
  94. phoenix/server/api/openapi/schema.py +0 -16
  95. phoenix/server/api/queries.py +0 -503
  96. phoenix/server/api/routers/v1/dataset_examples.py +0 -178
  97. phoenix/server/api/routers/v1/datasets.py +0 -965
  98. phoenix/server/api/routers/v1/experiment_evaluations.py +0 -66
  99. phoenix/server/api/routers/v1/experiment_runs.py +0 -108
  100. phoenix/server/api/routers/v1/experiments.py +0 -174
  101. phoenix/server/api/types/AnnotatorKind.py +0 -10
  102. phoenix/server/api/types/CreateDatasetPayload.py +0 -8
  103. phoenix/server/api/types/DatasetExample.py +0 -85
  104. phoenix/server/api/types/DatasetExampleRevision.py +0 -34
  105. phoenix/server/api/types/DatasetVersion.py +0 -14
  106. phoenix/server/api/types/ExampleRevisionInterface.py +0 -14
  107. phoenix/server/api/types/Experiment.py +0 -140
  108. phoenix/server/api/types/ExperimentAnnotationSummary.py +0 -13
  109. phoenix/server/api/types/ExperimentComparison.py +0 -19
  110. phoenix/server/api/types/ExperimentRun.py +0 -91
  111. phoenix/server/api/types/ExperimentRunAnnotation.py +0 -57
  112. phoenix/server/api/types/Inferences.py +0 -80
  113. phoenix/server/api/types/InferencesRole.py +0 -23
  114. phoenix/utilities/json.py +0 -61
  115. phoenix/utilities/re.py +0 -50
  116. {arize_phoenix-4.4.4rc5.dist-info → arize_phoenix-4.5.0.dist-info}/licenses/IP_NOTICE +0 -0
  117. {arize_phoenix-4.4.4rc5.dist-info → arize_phoenix-4.5.0.dist-info}/licenses/LICENSE +0 -0
  118. /phoenix/server/api/{helpers/__init__.py → helpers.py} +0 -0
@@ -48,7 +48,7 @@ from pandas.core.dtypes.common import (
48
48
  from typing_extensions import TypeAlias, TypeGuard
49
49
  from wrapt import ObjectProxy
50
50
 
51
- from phoenix.config import GENERATED_INFERENCES_NAME_PREFIX
51
+ from phoenix.config import GENERATED_DATASET_NAME_PREFIX
52
52
  from phoenix.datetime_utils import floor_to_minute
53
53
 
54
54
 
@@ -185,7 +185,7 @@ class RetrievalEmbedding(Embedding):
185
185
  yield value
186
186
 
187
187
 
188
- class InferencesRole(Enum):
188
+ class DatasetRole(Enum):
189
189
  """A dataframe's role in a Model: primary or reference (as
190
190
  baseline for drift).
191
191
  """
@@ -194,8 +194,8 @@ class InferencesRole(Enum):
194
194
  REFERENCE = auto()
195
195
 
196
196
 
197
- PRIMARY = InferencesRole.PRIMARY
198
- REFERENCE = InferencesRole.REFERENCE
197
+ PRIMARY = DatasetRole.PRIMARY
198
+ REFERENCE = DatasetRole.REFERENCE
199
199
 
200
200
 
201
201
  @dataclass(frozen=True, repr=False, eq=False)
@@ -381,7 +381,7 @@ class Dimension(Column, ABC):
381
381
  # But we really want the role to be specified for a Dimension.
382
382
  raise ValueError("role must be assigned")
383
383
 
384
- def __getitem__(self, df_role: InferencesRole) -> "pd.Series[Any]":
384
+ def __getitem__(self, df_role: DatasetRole) -> "pd.Series[Any]":
385
385
  if self._model is None:
386
386
  return pd.Series(dtype=object)
387
387
  model = cast(Model, self._model)
@@ -416,7 +416,7 @@ class ScalarDimension(Dimension):
416
416
  if self._model is None or self.data_type is CONTINUOUS:
417
417
  return ()
418
418
  model = cast(Model, self._model)
419
- return model.dimension_categories_from_all_inferences(self.name)
419
+ return model.dimension_categories_from_all_datasets(self.name)
420
420
 
421
421
 
422
422
  @dataclass(frozen=True)
@@ -582,7 +582,7 @@ class EventId(NamedTuple):
582
582
  """Identifies an event."""
583
583
 
584
584
  row_id: int = 0
585
- inferences_id: InferencesRole = PRIMARY
585
+ dataset_id: DatasetRole = PRIMARY
586
586
 
587
587
  def __str__(self) -> str:
588
588
  return ":".join(map(str, self))
@@ -625,7 +625,7 @@ class Events(ModelData):
625
625
  self,
626
626
  df: pd.DataFrame,
627
627
  /,
628
- role: InferencesRole,
628
+ role: DatasetRole,
629
629
  **kwargs: Any,
630
630
  ) -> None:
631
631
  super().__init__(df, **kwargs)
@@ -676,7 +676,7 @@ class Events(ModelData):
676
676
  return super().__getitem__(key)
677
677
 
678
678
 
679
- class Inferences(Events):
679
+ class Dataset(Events):
680
680
  """pd.DataFrame wrapped with extra functions and metadata."""
681
681
 
682
682
  def __init__(
@@ -701,13 +701,13 @@ class Inferences(Events):
701
701
  friendly. Falls back to the role of the dataset if no name is provided.
702
702
  """
703
703
  ds_name = self._self_name
704
- if ds_name.startswith(GENERATED_INFERENCES_NAME_PREFIX):
704
+ if ds_name.startswith(GENERATED_DATASET_NAME_PREFIX):
705
705
  # The generated names are UUIDs so use the role as the name
706
- return "primary" if self.role is InferencesRole.PRIMARY else "reference"
706
+ return "primary" if self.role is DatasetRole.PRIMARY else "reference"
707
707
  return ds_name
708
708
 
709
709
  @property
710
- def role(self) -> InferencesRole:
710
+ def role(self) -> DatasetRole:
711
711
  return self._self_role
712
712
 
713
713
  @property
@@ -746,14 +746,14 @@ class Model:
746
746
  a column of NaNs.
747
747
  """
748
748
 
749
- _inference_sets: Dict[InferencesRole, Inferences]
749
+ _datasets: Dict[DatasetRole, Dataset]
750
750
  _dimensions: Dict[Name, Dimension]
751
751
  _dim_names_by_role: Dict[DimensionRole, List[Name]]
752
- _original_columns_by_role: Dict[InferencesRole, "pd.Index[Any]"]
752
+ _original_columns_by_role: Dict[DatasetRole, "pd.Index[Any]"]
753
753
  _default_timestamps_factory: _ConstantValueSeriesFactory
754
754
  _nan_series_factory: _ConstantValueSeriesFactory
755
- _dimension_categories_from_all_inferences: _Cache[Name, Tuple[str, ...]]
756
- _dimension_min_max_from_all_inferences: _Cache[Name, Tuple[float, float]]
755
+ _dimension_categories_from_all_datasets: _Cache[Name, Tuple[str, ...]]
756
+ _dimension_min_max_from_all_datasets: _Cache[Name, Tuple[float, float]]
757
757
 
758
758
  def __init__(
759
759
  self,
@@ -769,12 +769,12 @@ class Model:
769
769
  # memoization
770
770
  object.__setattr__(
771
771
  self,
772
- "_dimension_categories_from_all_inferences",
772
+ "_dimension_categories_from_all_datasets",
773
773
  _Cache[Name, "pd.Series[Any]"](),
774
774
  )
775
775
  object.__setattr__(
776
776
  self,
777
- "_dimension_min_max_from_all_inferences",
777
+ "_dimension_min_max_from_all_datasets",
778
778
  _Cache[Name, Tuple[float, float]](),
779
779
  )
780
780
 
@@ -785,21 +785,21 @@ class Model:
785
785
  str_col_dfs = _coerce_str_column_names(dfs)
786
786
  padded_dfs = _add_padding(str_col_dfs, pd.DataFrame)
787
787
  padded_df_names = _add_padding(df_names, _rand_str)
788
- inference_sets = starmap(
789
- self._new_inferences,
790
- zip(padded_dfs, padded_df_names, InferencesRole),
788
+ datasets = starmap(
789
+ self._new_dataset,
790
+ zip(padded_dfs, padded_df_names, DatasetRole),
791
791
  )
792
- # Store inferences by role.
792
+ # Store datasets by role.
793
793
  object.__setattr__(
794
794
  self,
795
- "_inference_sets",
796
- {inferences.role: inferences for inferences in inference_sets},
795
+ "_datasets",
796
+ {dataset.role: dataset for dataset in datasets},
797
797
  )
798
798
  # Preserve originals, useful for exporting.
799
799
  object.__setattr__(
800
800
  self,
801
801
  "_original_columns_by_role",
802
- {role: inferences.columns for role, inferences in self._inference_sets.items()},
802
+ {role: dataset.columns for role, dataset in self._datasets.items()},
803
803
  )
804
804
 
805
805
  object.__setattr__(
@@ -828,7 +828,7 @@ class Model:
828
828
  (name, self._new_dimension(name, role=FEATURE))
829
829
  for name in _get_omitted_column_names(
830
830
  self._dimensions.values(),
831
- self._inference_sets.values(),
831
+ self._datasets.values(),
832
832
  )
833
833
  )
834
834
 
@@ -849,7 +849,7 @@ class Model:
849
849
  data_type=(
850
850
  _guess_data_type(
851
851
  dataset.loc[:, dim.name]
852
- for dataset in self._inference_sets.values()
852
+ for dataset in self._datasets.values()
853
853
  if dim.name in dataset.columns
854
854
  )
855
855
  ),
@@ -859,9 +859,9 @@ class Model:
859
859
  # Add TIMESTAMP if missing.
860
860
  # If needed, normalize the timestamps values.
861
861
  # If needed, sort the dataframes by time.
862
- for inferences_role, dataset in list(self._inference_sets.items()):
862
+ for dataset_role, dataset in list(self._datasets.items()):
863
863
  df = dataset.__wrapped__
864
- df_original_columns = self._original_columns_by_role[inferences_role]
864
+ df_original_columns = self._original_columns_by_role[dataset_role]
865
865
 
866
866
  # PREDICTION_ID
867
867
  dim_pred_id = self._dimensions.get(
@@ -897,20 +897,20 @@ class Model:
897
897
  df = df.set_index(dim_time.name, drop=False)
898
898
 
899
899
  # Update dataset since its dataframe may have changed.
900
- self._inference_sets[inferences_role] = self._new_inferences(
901
- df, name=dataset.name, role=inferences_role
900
+ self._datasets[dataset_role] = self._new_dataset(
901
+ df, name=dataset.name, role=dataset_role
902
902
  )
903
903
 
904
904
  @cached_property
905
905
  def is_empty(self) -> bool:
906
906
  """Returns True if the model has no data."""
907
- return not any(map(len, self._inference_sets.values()))
907
+ return not any(map(len, self._datasets.values()))
908
908
 
909
909
  def export_rows_as_parquet_file(
910
910
  self,
911
- row_numbers: Mapping[InferencesRole, Iterable[int]],
911
+ row_numbers: Mapping[DatasetRole, Iterable[int]],
912
912
  parquet_file: BinaryIO,
913
- cluster_ids: Optional[Mapping[InferencesRole, Mapping[int, str]]] = None,
913
+ cluster_ids: Optional[Mapping[DatasetRole, Mapping[int, str]]] = None,
914
914
  ) -> None:
915
915
  """
916
916
  Given row numbers, exports dataframe subset into parquet file.
@@ -921,31 +921,29 @@ class Model:
921
921
 
922
922
  Parameters
923
923
  ----------
924
- row_numbers: Mapping[InferencesRole, Iterable[int]]
924
+ row_numbers: Mapping[DatasetRole, Iterable[int]]
925
925
  mapping of dataset role to list of row numbers
926
926
  parquet_file: file handle
927
927
  output parquet file handle
928
- cluster_ids: Optional[Mapping[InferencesRole, Mapping[int, str]]]
929
- mapping of inferences role to mapping of row number to cluster id.
928
+ cluster_ids: Optional[Mapping[DatasetRole, Mapping[int, str]]]
929
+ mapping of dataset role to mapping of row number to cluster id.
930
930
  If cluster_ids is non-empty, a new column is inserted to the
931
931
  dataframe containing the cluster IDs of each row in the exported
932
932
  data. The name of the added column name is `__phoenix_cluster_id__`.
933
933
  """
934
934
  export_dataframes = [pd.DataFrame()]
935
- model_has_multiple_inference_sets = (
936
- sum(not df.empty for df in self._inference_sets.values()) > 1
937
- )
938
- for inferences_role, numbers in row_numbers.items():
939
- df = self._inference_sets[inferences_role]
935
+ model_has_multiple_datasets = sum(not df.empty for df in self._datasets.values()) > 1
936
+ for dataset_role, numbers in row_numbers.items():
937
+ df = self._datasets[dataset_role]
940
938
  columns = [
941
939
  df.columns.get_loc(column_name)
942
- for column_name in self._original_columns_by_role[inferences_role]
940
+ for column_name in self._original_columns_by_role[dataset_role]
943
941
  ]
944
942
  rows = pd.Series(sorted(set(numbers)))
945
943
  filtered_df = df.iloc[rows, columns].reset_index(drop=True)
946
- if model_has_multiple_inference_sets:
944
+ if model_has_multiple_datasets:
947
945
  filtered_df["__phoenix_dataset_name__"] = df.display_name
948
- if cluster_ids and (ids := cluster_ids.get(inferences_role)):
946
+ if cluster_ids and (ids := cluster_ids.get(dataset_role)):
949
947
  filtered_df["__phoenix_cluster_id__"] = rows.apply(ids.get)
950
948
  export_dataframes.append(filtered_df)
951
949
  pd.concat(export_dataframes).to_parquet(
@@ -979,24 +977,24 @@ class Model:
979
977
  if not dim.is_dummy and isinstance(dim, EmbeddingDimension)
980
978
  )
981
979
 
982
- def dimension_categories_from_all_inferences(
980
+ def dimension_categories_from_all_datasets(
983
981
  self,
984
982
  dimension_name: Name,
985
983
  ) -> Tuple[str, ...]:
986
984
  dim = self[dimension_name]
987
985
  if dim.data_type is CONTINUOUS:
988
986
  return cast(Tuple[str, ...], ())
989
- with self._dimension_categories_from_all_inferences() as cache:
987
+ with self._dimension_categories_from_all_datasets() as cache:
990
988
  try:
991
989
  return cache[dimension_name]
992
990
  except KeyError:
993
991
  pass
994
992
  categories_by_dataset = (
995
- pd.Series(dim[role].unique()).dropna().astype(str) for role in InferencesRole
993
+ pd.Series(dim[role].unique()).dropna().astype(str) for role in DatasetRole
996
994
  )
997
995
  all_values_combined = chain.from_iterable(categories_by_dataset)
998
996
  ans = tuple(np.sort(pd.Series(all_values_combined).unique()))
999
- with self._dimension_categories_from_all_inferences() as cache:
997
+ with self._dimension_categories_from_all_datasets() as cache:
1000
998
  cache[dimension_name] = ans
1001
999
  return ans
1002
1000
 
@@ -1007,24 +1005,24 @@ class Model:
1007
1005
  dim = self[dimension_name]
1008
1006
  if dim.data_type is not CONTINUOUS:
1009
1007
  return (np.nan, np.nan)
1010
- with self._dimension_min_max_from_all_inferences() as cache:
1008
+ with self._dimension_min_max_from_all_datasets() as cache:
1011
1009
  try:
1012
1010
  return cache[dimension_name]
1013
1011
  except KeyError:
1014
1012
  pass
1015
- min_max_by_df = (_agg_min_max(dim[df_role]) for df_role in InferencesRole)
1013
+ min_max_by_df = (_agg_min_max(dim[df_role]) for df_role in DatasetRole)
1016
1014
  all_values_combined = chain.from_iterable(min_max_by_df)
1017
1015
  min_max = _agg_min_max(pd.Series(all_values_combined))
1018
1016
  ans = (min_max.min(), min_max.max())
1019
- with self._dimension_min_max_from_all_inferences() as cache:
1017
+ with self._dimension_min_max_from_all_datasets() as cache:
1020
1018
  cache[dimension_name] = ans
1021
1019
  return ans
1022
1020
 
1023
1021
  @overload
1024
- def __getitem__(self, key: Type[Inferences]) -> Iterator[Inferences]: ...
1022
+ def __getitem__(self, key: Type[Dataset]) -> Iterator[Dataset]: ...
1025
1023
 
1026
1024
  @overload
1027
- def __getitem__(self, key: InferencesRole) -> Inferences: ...
1025
+ def __getitem__(self, key: DatasetRole) -> Dataset: ...
1028
1026
 
1029
1027
  @overload
1030
1028
  def __getitem__(self, key: ColumnKey) -> Dimension: ...
@@ -1051,10 +1049,10 @@ class Model:
1051
1049
  ) -> Iterator[Dimension]: ...
1052
1050
 
1053
1051
  def __getitem__(self, key: Any) -> Any:
1054
- if key is Inferences:
1055
- return self._inference_sets.values()
1056
- if isinstance(key, InferencesRole):
1057
- return self._inference_sets[key]
1052
+ if key is Dataset:
1053
+ return self._datasets.values()
1054
+ if isinstance(key, DatasetRole):
1055
+ return self._datasets[key]
1058
1056
  if _is_column_key(key):
1059
1057
  return self._get_dim(key)
1060
1058
  if _is_multi_dimension_key(key):
@@ -1154,17 +1152,17 @@ class Model:
1154
1152
  )
1155
1153
  raise ValueError(f"invalid argument: {repr(obj)}")
1156
1154
 
1157
- def _new_inferences(
1155
+ def _new_dataset(
1158
1156
  self,
1159
1157
  df: pd.DataFrame,
1160
1158
  /,
1161
1159
  name: str,
1162
- role: InferencesRole,
1163
- ) -> Inferences:
1164
- """Creates a new Inferences, setting the model weak reference to the
1160
+ role: DatasetRole,
1161
+ ) -> Dataset:
1162
+ """Creates a new Dataset, setting the model weak reference to the
1165
1163
  `self` Model instance.
1166
1164
  """
1167
- return Inferences(df, name=name, role=role, _model=proxy(self))
1165
+ return Dataset(df, name=name, role=role, _model=proxy(self))
1168
1166
 
1169
1167
 
1170
1168
  @dataclass(frozen=True)
@@ -1346,7 +1344,7 @@ def _series_uuid(length: int) -> "pd.Series[str]":
1346
1344
 
1347
1345
 
1348
1346
  def _raise_if_too_many_dataframes(given: int) -> None:
1349
- limit = len(InferencesRole)
1347
+ limit = len(DatasetRole)
1350
1348
  if not 0 < given <= limit:
1351
1349
  raise ValueError(f"expected between 1 to {limit} dataframes, but {given} were given")
1352
1350
 
@@ -10,21 +10,21 @@ from phoenix import EmbeddingColumnNames, Inferences
10
10
  from phoenix.core.model import _get_embedding_dimensions
11
11
  from phoenix.core.model_schema import Embedding, Model, RetrievalEmbedding, Schema
12
12
  from phoenix.inferences.schema import RetrievalEmbeddingColumnNames
13
- from phoenix.inferences.schema import Schema as InferencesSchema
13
+ from phoenix.inferences.schema import Schema as DatasetSchema
14
14
 
15
- InferencesName: TypeAlias = str
15
+ DatasetName: TypeAlias = str
16
16
  ColumnName: TypeAlias = str
17
17
  DisplayName: TypeAlias = str
18
18
 
19
19
 
20
- def create_model_from_inferences(*inference_sets: Optional[Inferences]) -> Model:
20
+ def create_model_from_datasets(*datasets: Optional[Inferences]) -> Model:
21
21
  # TODO: move this validation into model_schema.Model.
22
- if len(inference_sets) > 1 and inference_sets[0] is not None:
22
+ if len(datasets) > 1 and datasets[0] is not None:
23
23
  # Check that for each embedding dimension all vectors
24
- # have the same length between inferences.
25
- _ = _get_embedding_dimensions(inference_sets[0], inference_sets[1])
24
+ # have the same length between datasets.
25
+ _ = _get_embedding_dimensions(datasets[0], datasets[1])
26
26
 
27
- named_dataframes: List[Tuple[InferencesName, pd.DataFrame]] = []
27
+ named_dataframes: List[Tuple[DatasetName, pd.DataFrame]] = []
28
28
  prediction_ids: List[ColumnName] = []
29
29
  timestamps: List[ColumnName] = []
30
30
  prediction_labels: List[ColumnName] = []
@@ -37,35 +37,33 @@ def create_model_from_inferences(*inference_sets: Optional[Inferences]) -> Model
37
37
  prompts: List[EmbeddingColumnNames] = []
38
38
  responses: List[Union[str, EmbeddingColumnNames]] = []
39
39
 
40
- for inferences in filter(_is_inferences, inference_sets):
41
- df = inferences.dataframe
40
+ for dataset in filter(_is_dataset, datasets):
41
+ df = dataset.dataframe
42
42
  # Coerce string column names at run time.
43
43
  df = df.set_axis(
44
44
  map(str, df.columns),
45
45
  axis=1,
46
46
  )
47
- named_dataframes.append((inferences.name, df))
48
- inferences_schema = (
49
- inferences.schema if inferences.schema is not None else InferencesSchema()
50
- )
47
+ named_dataframes.append((dataset.name, df))
48
+ dataset_schema = dataset.schema if dataset.schema is not None else DatasetSchema()
51
49
  for display_name, embedding in (
52
- inferences_schema.embedding_feature_column_names or {}
50
+ dataset_schema.embedding_feature_column_names or {}
53
51
  ).items():
54
52
  if display_name not in embeddings:
55
53
  embeddings[display_name] = embedding
56
- if inferences_schema.prompt_column_names is not None:
57
- prompts.append(inferences_schema.prompt_column_names)
58
- if inferences_schema.response_column_names is not None:
59
- responses.append(inferences_schema.response_column_names)
54
+ if dataset_schema.prompt_column_names is not None:
55
+ prompts.append(dataset_schema.prompt_column_names)
56
+ if dataset_schema.response_column_names is not None:
57
+ responses.append(dataset_schema.response_column_names)
60
58
  for source, sink in (
61
- ([inferences_schema.prediction_id_column_name], prediction_ids),
62
- ([inferences_schema.timestamp_column_name], timestamps),
63
- ([inferences_schema.prediction_label_column_name], prediction_labels),
64
- ([inferences_schema.prediction_score_column_name], prediction_scores),
65
- ([inferences_schema.actual_label_column_name], actual_labels),
66
- ([inferences_schema.actual_score_column_name], actual_scores),
67
- (inferences_schema.feature_column_names or (), features),
68
- (inferences_schema.tag_column_names or (), tags),
59
+ ([dataset_schema.prediction_id_column_name], prediction_ids),
60
+ ([dataset_schema.timestamp_column_name], timestamps),
61
+ ([dataset_schema.prediction_label_column_name], prediction_labels),
62
+ ([dataset_schema.prediction_score_column_name], prediction_scores),
63
+ ([dataset_schema.actual_label_column_name], actual_labels),
64
+ ([dataset_schema.actual_score_column_name], actual_scores),
65
+ (dataset_schema.feature_column_names or (), features),
66
+ (dataset_schema.tag_column_names or (), tags),
69
67
  ):
70
68
  # Coerce None to "" to simplify type checks.
71
69
  sink.extend(map(lambda s: "" if s is None else str(s), source))
@@ -134,7 +132,7 @@ def create_model_from_inferences(*inference_sets: Optional[Inferences]) -> Model
134
132
  )
135
133
 
136
134
 
137
- def _is_inferences(obj: Optional[Inferences]) -> TypeGuard[Inferences]:
135
+ def _is_dataset(obj: Optional[Inferences]) -> TypeGuard[Inferences]:
138
136
  return type(obj) is Inferences
139
137
 
140
138
 
@@ -1,6 +1,5 @@
1
1
  import asyncio
2
2
  import logging
3
- from asyncio import Queue
4
3
  from dataclasses import dataclass, field
5
4
  from datetime import datetime, timezone
6
5
  from itertools import islice
@@ -15,7 +14,6 @@ from typing import (
15
14
  Optional,
16
15
  Set,
17
16
  Tuple,
18
- cast,
19
17
  )
20
18
 
21
19
  from cachetools import LRUCache
@@ -24,11 +22,10 @@ from typing_extensions import TypeAlias
24
22
 
25
23
  import phoenix.trace.v1 as pb
26
24
  from phoenix.db.insertion.evaluation import (
27
- EvaluationInsertionEvent,
25
+ EvaluationInsertionResult,
28
26
  InsertEvaluationError,
29
27
  insert_evaluation,
30
28
  )
31
- from phoenix.db.insertion.helpers import DataManipulation, DataManipulationEvent
32
29
  from phoenix.db.insertion.span import SpanInsertionEvent, insert_span
33
30
  from phoenix.server.api.dataloaders import CacheForDataLoaders
34
31
  from phoenix.trace.schemas import Span
@@ -49,29 +46,23 @@ class BulkInserter:
49
46
  db: Callable[[], AsyncContextManager[AsyncSession]],
50
47
  *,
51
48
  cache_for_dataloaders: Optional[CacheForDataLoaders] = None,
52
- initial_batch_of_operations: Iterable[DataManipulation] = (),
53
49
  initial_batch_of_spans: Optional[Iterable[Tuple[Span, str]]] = None,
54
50
  initial_batch_of_evaluations: Optional[Iterable[pb.Evaluation]] = None,
55
51
  sleep: float = 0.1,
56
- max_ops_per_transaction: int = 1000,
57
- max_queue_size: int = 1000,
52
+ max_num_per_transaction: int = 1000,
58
53
  enable_prometheus: bool = False,
59
54
  ) -> None:
60
55
  """
61
56
  :param db: A function to initiate a new database session.
62
57
  :param initial_batch_of_spans: Initial batch of spans to insert.
63
58
  :param sleep: The time to sleep between bulk insertions
64
- :param max_ops_per_transaction: The maximum number of operations to dequeue from
65
- the operations queue for each transaction.
66
- :param max_queue_size: The maximum length of the operations queue.
67
- :param enable_prometheus: Whether Prometheus is enabled.
59
+ :param max_num_per_transaction: The maximum number of items to insert in a single
60
+ transaction. Multiple transactions will be used if there are more items in the batch.
68
61
  """
69
62
  self._db = db
70
63
  self._running = False
71
64
  self._sleep = sleep
72
- self._max_ops_per_transaction = max_ops_per_transaction
73
- self._operations: Optional[Queue[DataManipulation]] = None
74
- self._max_queue_size = max_queue_size
65
+ self._max_num_per_transaction = max_num_per_transaction
75
66
  self._spans: List[Tuple[Span, str]] = (
76
67
  [] if initial_batch_of_spans is None else list(initial_batch_of_spans)
77
68
  )
@@ -90,58 +81,27 @@ class BulkInserter:
90
81
 
91
82
  async def __aenter__(
92
83
  self,
93
- ) -> Tuple[
94
- Callable[[Span, str], Awaitable[None]],
95
- Callable[[pb.Evaluation], Awaitable[None]],
96
- Callable[[DataManipulation], None],
97
- ]:
84
+ ) -> Tuple[Callable[[Span, str], Awaitable[None]], Callable[[pb.Evaluation], Awaitable[None]]]:
98
85
  self._running = True
99
- self._operations = Queue(maxsize=self._max_queue_size)
100
86
  self._task = asyncio.create_task(self._bulk_insert())
101
- return (
102
- self._queue_span,
103
- self._queue_evaluation,
104
- self._enqueue_operation,
105
- )
87
+ return self._queue_span, self._queue_evaluation
106
88
 
107
89
  async def __aexit__(self, *args: Any) -> None:
108
- self._operations = None
109
90
  self._running = False
110
91
 
111
- def _enqueue_operation(self, operation: DataManipulation) -> None:
112
- cast("Queue[DataManipulation]", self._operations).put_nowait(operation)
113
-
114
92
  async def _queue_span(self, span: Span, project_name: str) -> None:
115
93
  self._spans.append((span, project_name))
116
94
 
117
95
  async def _queue_evaluation(self, evaluation: pb.Evaluation) -> None:
118
96
  self._evaluations.append(evaluation)
119
97
 
120
- async def _process_events(self, events: Iterable[Optional[DataManipulationEvent]]) -> None: ...
121
-
122
98
  async def _bulk_insert(self) -> None:
123
- assert isinstance(self._operations, Queue)
124
99
  spans_buffer, evaluations_buffer = None, None
125
100
  # start first insert immediately if the inserter has not run recently
126
- while self._running or not self._operations.empty() or self._spans or self._evaluations:
127
- if self._operations.empty() and not (self._spans or self._evaluations):
101
+ while self._spans or self._evaluations or self._running:
102
+ if not (self._spans or self._evaluations):
128
103
  await asyncio.sleep(self._sleep)
129
104
  continue
130
- ops_remaining, events = self._max_ops_per_transaction, []
131
- async with self._db() as session:
132
- while ops_remaining and not self._operations.empty():
133
- ops_remaining -= 1
134
- op = await self._operations.get()
135
- try:
136
- async with session.begin_nested():
137
- events.append(await op(session))
138
- except Exception as e:
139
- if self._enable_prometheus:
140
- from phoenix.server.prometheus import BULK_LOADER_EXCEPTIONS
141
-
142
- BULK_LOADER_EXCEPTIONS.inc()
143
- logger.exception(str(e))
144
- await self._process_events(events)
145
105
  # It's important to grab the buffers at the same time so there's
146
106
  # no race condition, since an eval insertion will fail if the span
147
107
  # it references doesn't exist. Grabbing the eval buffer later may
@@ -170,11 +130,11 @@ class BulkInserter:
170
130
 
171
131
  async def _insert_spans(self, spans: List[Tuple[Span, str]]) -> TransactionResult:
172
132
  transaction_result = TransactionResult()
173
- for i in range(0, len(spans), self._max_ops_per_transaction):
133
+ for i in range(0, len(spans), self._max_num_per_transaction):
174
134
  try:
175
135
  start = perf_counter()
176
136
  async with self._db() as session:
177
- for span, project_name in islice(spans, i, i + self._max_ops_per_transaction):
137
+ for span, project_name in islice(spans, i, i + self._max_num_per_transaction):
178
138
  if self._enable_prometheus:
179
139
  from phoenix.server.prometheus import BULK_LOADER_SPAN_INSERTIONS
180
140
 
@@ -209,16 +169,16 @@ class BulkInserter:
209
169
 
210
170
  async def _insert_evaluations(self, evaluations: List[pb.Evaluation]) -> TransactionResult:
211
171
  transaction_result = TransactionResult()
212
- for i in range(0, len(evaluations), self._max_ops_per_transaction):
172
+ for i in range(0, len(evaluations), self._max_num_per_transaction):
213
173
  try:
214
174
  start = perf_counter()
215
175
  async with self._db() as session:
216
- for evaluation in islice(evaluations, i, i + self._max_ops_per_transaction):
176
+ for evaluation in islice(evaluations, i, i + self._max_num_per_transaction):
217
177
  if self._enable_prometheus:
218
178
  from phoenix.server.prometheus import BULK_LOADER_EVALUATION_INSERTIONS
219
179
 
220
180
  BULK_LOADER_EVALUATION_INSERTIONS.inc()
221
- result: Optional[EvaluationInsertionEvent] = None
181
+ result: Optional[EvaluationInsertionResult] = None
222
182
  try:
223
183
  async with session.begin_nested():
224
184
  result = await insert_evaluation(session, evaluation)
@@ -15,24 +15,24 @@ class InsertEvaluationError(PhoenixException):
15
15
  pass
16
16
 
17
17
 
18
- class EvaluationInsertionEvent(NamedTuple):
18
+ class EvaluationInsertionResult(NamedTuple):
19
19
  project_rowid: int
20
20
  evaluation_name: str
21
21
 
22
22
 
23
- class SpanEvaluationInsertionEvent(EvaluationInsertionEvent): ...
23
+ class SpanEvaluationInsertionEvent(EvaluationInsertionResult): ...
24
24
 
25
25
 
26
- class TraceEvaluationInsertionEvent(EvaluationInsertionEvent): ...
26
+ class TraceEvaluationInsertionEvent(EvaluationInsertionResult): ...
27
27
 
28
28
 
29
- class DocumentEvaluationInsertionEvent(EvaluationInsertionEvent): ...
29
+ class DocumentEvaluationInsertionEvent(EvaluationInsertionResult): ...
30
30
 
31
31
 
32
32
  async def insert_evaluation(
33
33
  session: AsyncSession,
34
34
  evaluation: pb.Evaluation,
35
- ) -> Optional[EvaluationInsertionEvent]:
35
+ ) -> Optional[EvaluationInsertionResult]:
36
36
  evaluation_name = evaluation.name
37
37
  result = evaluation.result
38
38
  label = result.label.value if result.HasField("label") else None
@@ -160,7 +160,7 @@ async def _insert_document_evaluation(
160
160
  label: Optional[str],
161
161
  score: Optional[float],
162
162
  explanation: Optional[str],
163
- ) -> EvaluationInsertionEvent:
163
+ ) -> EvaluationInsertionResult:
164
164
  dialect = SupportedSQLDialect(session.bind.dialect.name)
165
165
  stmt = (
166
166
  select(
@@ -1,25 +1,14 @@
1
- from abc import ABC
2
1
  from enum import Enum, auto
3
- from typing import Any, Awaitable, Callable, Mapping, Optional, Sequence
2
+ from typing import Any, Mapping, Optional, Sequence
4
3
 
5
4
  from sqlalchemy import Insert, insert
6
5
  from sqlalchemy.dialects.postgresql import insert as insert_postgresql
7
6
  from sqlalchemy.dialects.sqlite import insert as insert_sqlite
8
- from sqlalchemy.ext.asyncio import AsyncSession
9
- from typing_extensions import TypeAlias, assert_never
7
+ from typing_extensions import assert_never
10
8
 
11
9
  from phoenix.db.helpers import SupportedSQLDialect
12
10
 
13
11
 
14
- class DataManipulationEvent(ABC):
15
- """
16
- Execution of DML (Data Manipulation Language) statements.
17
- """
18
-
19
-
20
- DataManipulation: TypeAlias = Callable[[AsyncSession], Awaitable[Optional[DataManipulationEvent]]]
21
-
22
-
23
12
  class OnConflict(Enum):
24
13
  DO_NOTHING = auto()
25
14
  DO_UPDATE = auto()