arize-phoenix 4.20.0__py3-none-any.whl → 4.20.2__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 (46) hide show
  1. {arize_phoenix-4.20.0.dist-info → arize_phoenix-4.20.2.dist-info}/METADATA +2 -1
  2. {arize_phoenix-4.20.0.dist-info → arize_phoenix-4.20.2.dist-info}/RECORD +45 -43
  3. phoenix/db/bulk_inserter.py +24 -98
  4. phoenix/db/insertion/document_annotation.py +13 -0
  5. phoenix/db/insertion/span.py +9 -0
  6. phoenix/db/insertion/span_annotation.py +13 -0
  7. phoenix/db/insertion/trace_annotation.py +13 -0
  8. phoenix/db/insertion/types.py +34 -28
  9. phoenix/db/migrations/versions/10460e46d750_datasets.py +28 -2
  10. phoenix/db/migrations/versions/3be8647b87d8_add_token_columns_to_spans_table.py +134 -0
  11. phoenix/db/migrations/versions/cf03bd6bae1d_init.py +28 -2
  12. phoenix/db/models.py +9 -1
  13. phoenix/server/api/context.py +8 -6
  14. phoenix/server/api/dataloaders/__init__.py +0 -47
  15. phoenix/server/api/dataloaders/token_counts.py +2 -7
  16. phoenix/server/api/input_types/SpanSort.py +3 -8
  17. phoenix/server/api/mutations/dataset_mutations.py +9 -3
  18. phoenix/server/api/mutations/experiment_mutations.py +2 -0
  19. phoenix/server/api/mutations/project_mutations.py +5 -5
  20. phoenix/server/api/mutations/span_annotations_mutations.py +10 -2
  21. phoenix/server/api/mutations/trace_annotations_mutations.py +10 -2
  22. phoenix/server/api/queries.py +9 -0
  23. phoenix/server/api/routers/v1/datasets.py +2 -0
  24. phoenix/server/api/routers/v1/experiment_evaluations.py +2 -0
  25. phoenix/server/api/routers/v1/experiment_runs.py +2 -0
  26. phoenix/server/api/routers/v1/experiments.py +2 -0
  27. phoenix/server/api/routers/v1/spans.py +12 -8
  28. phoenix/server/api/routers/v1/traces.py +12 -10
  29. phoenix/server/api/types/Dataset.py +6 -1
  30. phoenix/server/api/types/Experiment.py +6 -1
  31. phoenix/server/api/types/Project.py +4 -1
  32. phoenix/server/api/types/Span.py +5 -17
  33. phoenix/server/app.py +25 -8
  34. phoenix/server/dml_event.py +136 -0
  35. phoenix/server/dml_event_handler.py +272 -0
  36. phoenix/server/static/.vite/manifest.json +14 -14
  37. phoenix/server/static/assets/{components-CAummAJx.js → components-BSw2e1Zr.js} +108 -100
  38. phoenix/server/static/assets/{index-Cg5hdf3g.js → index-BYUFcdtx.js} +1 -1
  39. phoenix/server/static/assets/{pages-BU__X1UX.js → pages-p_fuED5k.js} +251 -237
  40. phoenix/server/static/assets/{vendor-arizeai-CkyzG9Wl.js → vendor-arizeai-CIETbKDq.js} +28 -28
  41. phoenix/server/types.py +106 -1
  42. phoenix/version.py +1 -1
  43. phoenix/db/migrations/types.py +0 -29
  44. {arize_phoenix-4.20.0.dist-info → arize_phoenix-4.20.2.dist-info}/WHEEL +0 -0
  45. {arize_phoenix-4.20.0.dist-info → arize_phoenix-4.20.2.dist-info}/licenses/IP_NOTICE +0 -0
  46. {arize_phoenix-4.20.0.dist-info → arize_phoenix-4.20.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,272 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from asyncio import gather
5
+ from inspect import getmro
6
+ from itertools import chain
7
+ from typing import (
8
+ Any,
9
+ Callable,
10
+ Generic,
11
+ Iterable,
12
+ Iterator,
13
+ Mapping,
14
+ Optional,
15
+ Set,
16
+ Tuple,
17
+ Type,
18
+ TypedDict,
19
+ TypeVar,
20
+ Union,
21
+ cast,
22
+ )
23
+
24
+ from sqlalchemy import Select, select
25
+ from typing_extensions import TypeAlias, Unpack
26
+
27
+ from phoenix.db.models import (
28
+ Base,
29
+ DocumentAnnotation,
30
+ Project,
31
+ Span,
32
+ SpanAnnotation,
33
+ Trace,
34
+ TraceAnnotation,
35
+ )
36
+ from phoenix.server.api.dataloaders import CacheForDataLoaders
37
+ from phoenix.server.dml_event import (
38
+ DmlEvent,
39
+ DocumentAnnotationDmlEvent,
40
+ SpanAnnotationDmlEvent,
41
+ SpanDeleteEvent,
42
+ SpanDmlEvent,
43
+ TraceAnnotationDmlEvent,
44
+ )
45
+ from phoenix.server.types import (
46
+ BatchedCaller,
47
+ CanSetLastUpdatedAt,
48
+ DbSessionFactory,
49
+ )
50
+
51
+ _DmlEventT = TypeVar("_DmlEventT", bound=DmlEvent)
52
+
53
+
54
+ class _DmlEventQueue(Generic[_DmlEventT]):
55
+ def __init__(self, **kwargs: Any) -> None:
56
+ super().__init__(**kwargs)
57
+ self._events: Set[_DmlEventT] = set()
58
+
59
+ @property
60
+ def empty(self) -> bool:
61
+ return not self._events
62
+
63
+ def put(self, event: _DmlEventT) -> None:
64
+ self._events.add(event)
65
+
66
+ def clear(self) -> None:
67
+ self._events.clear()
68
+
69
+ def __iter__(self) -> Iterator[_DmlEventT]:
70
+ yield from self._events
71
+
72
+
73
+ class _HandlerParams(TypedDict):
74
+ db: DbSessionFactory
75
+ last_updated_at: CanSetLastUpdatedAt
76
+ cache_for_dataloaders: Optional[CacheForDataLoaders]
77
+ sleep_seconds: float
78
+
79
+
80
+ class _HasLastUpdatedAt(ABC):
81
+ def __init__(
82
+ self,
83
+ last_updated_at: CanSetLastUpdatedAt,
84
+ **kwargs: Any,
85
+ ) -> None:
86
+ super().__init__(**kwargs)
87
+ self._last_updated_at = last_updated_at
88
+
89
+
90
+ class _HasCacheForDataLoaders(ABC):
91
+ def __init__(
92
+ self,
93
+ cache_for_dataloaders: Optional[CacheForDataLoaders] = None,
94
+ **kwargs: Any,
95
+ ) -> None:
96
+ super().__init__(**kwargs)
97
+ self._cache_for_dataloaders = cache_for_dataloaders
98
+
99
+
100
+ class _DmlEventHandler(
101
+ _HasLastUpdatedAt,
102
+ _HasCacheForDataLoaders,
103
+ BatchedCaller[_DmlEventT],
104
+ Generic[_DmlEventT],
105
+ ABC,
106
+ ):
107
+ _batch_factory = cast(Callable[[], _DmlEventQueue[_DmlEventT]], _DmlEventQueue)
108
+
109
+ def __init__(self, *, db: DbSessionFactory, **kwargs: Any) -> None:
110
+ super().__init__(**kwargs)
111
+ self._db = db
112
+
113
+ def __hash__(self) -> int:
114
+ return id(self)
115
+
116
+
117
+ class _GenericDmlEventHandler(_DmlEventHandler[DmlEvent]):
118
+ async def __call__(self) -> None:
119
+ for e in self._batch:
120
+ for id_ in e.ids:
121
+ self._update(e.table, id_)
122
+
123
+ def _update(self, table: Type[Base], id_: int) -> None:
124
+ self._last_updated_at.set(table, id_)
125
+
126
+
127
+ class _SpanDmlEventHandler(_DmlEventHandler[SpanDmlEvent]):
128
+ async def __call__(self) -> None:
129
+ if cache := self._cache_for_dataloaders:
130
+ for id_ in set(chain.from_iterable(e.ids for e in self._batch)):
131
+ self._clear(cache, id_)
132
+
133
+ @staticmethod
134
+ def _clear(cache: CacheForDataLoaders, project_id: int) -> None:
135
+ cache.latency_ms_quantile.invalidate(project_id)
136
+ cache.token_count.invalidate(project_id)
137
+ cache.record_count.invalidate(project_id)
138
+ cache.min_start_or_max_end_time.invalidate(project_id)
139
+
140
+
141
+ class _SpanDeleteEventHandler(_SpanDmlEventHandler):
142
+ @staticmethod
143
+ def _clear(cache: CacheForDataLoaders, project_id: int) -> None:
144
+ cache.annotation_summary.invalidate_project(project_id)
145
+ cache.evaluation_summary.invalidate_project(project_id)
146
+ cache.document_evaluation_summary.invalidate_project(project_id)
147
+
148
+
149
+ _AnnotationTable: TypeAlias = Union[
150
+ Type[SpanAnnotation],
151
+ Type[TraceAnnotation],
152
+ Type[DocumentAnnotation],
153
+ ]
154
+
155
+ _AnnotationDmlEventT = TypeVar(
156
+ "_AnnotationDmlEventT",
157
+ SpanAnnotationDmlEvent,
158
+ TraceAnnotationDmlEvent,
159
+ DocumentAnnotationDmlEvent,
160
+ )
161
+
162
+
163
+ class _AnnotationDmlEventHandler(
164
+ _DmlEventHandler[_AnnotationDmlEventT],
165
+ Generic[_AnnotationDmlEventT],
166
+ ABC,
167
+ ):
168
+ _table: _AnnotationTable
169
+ _base_stmt: Union[Select[Tuple[int, str]], Select[Tuple[int]]] = (
170
+ select(Project.id).join_from(Project, Trace).distinct()
171
+ )
172
+
173
+ def __init__(self, **kwargs: Unpack[_HandlerParams]) -> None:
174
+ super().__init__(**kwargs)
175
+ self._stmt = self._base_stmt
176
+ if self._cache_for_dataloaders:
177
+ self._stmt = self._stmt.add_columns(self._table.name)
178
+
179
+ def _get_stmt(self) -> Union[Select[Tuple[int, str]], Select[Tuple[int]]]:
180
+ ids = set(chain.from_iterable(e.ids for e in self._batch))
181
+ return self._stmt.where(self._table.id.in_(ids))
182
+
183
+ @staticmethod
184
+ @abstractmethod
185
+ def _clear(cache: CacheForDataLoaders, project_id: int, name: str) -> None: ...
186
+
187
+ async def __call__(self) -> None:
188
+ async with self._db() as session:
189
+ async for row in await session.stream(self._get_stmt()):
190
+ if cache := self._cache_for_dataloaders:
191
+ self._clear(cache, row.id, row.name)
192
+
193
+
194
+ class _SpanAnnotationDmlEventHandler(_AnnotationDmlEventHandler[SpanAnnotationDmlEvent]):
195
+ _table = SpanAnnotation
196
+
197
+ def __init__(self, **kwargs: Unpack[_HandlerParams]) -> None:
198
+ super().__init__(**kwargs)
199
+ self._stmt = self._stmt.join_from(Trace, Span).join_from(Span, self._table)
200
+
201
+ @staticmethod
202
+ def _clear(cache: CacheForDataLoaders, project_id: int, name: str) -> None:
203
+ cache.annotation_summary.invalidate((project_id, name, "span"))
204
+ cache.evaluation_summary.invalidate((project_id, name, "span"))
205
+
206
+
207
+ class _TraceAnnotationDmlEventHandler(_AnnotationDmlEventHandler[TraceAnnotationDmlEvent]):
208
+ _table = TraceAnnotation
209
+
210
+ def __init__(self, **kwargs: Unpack[_HandlerParams]) -> None:
211
+ super().__init__(**kwargs)
212
+ self._stmt = self._stmt.join_from(Trace, self._table)
213
+
214
+ @staticmethod
215
+ def _clear(cache: CacheForDataLoaders, project_id: int, name: str) -> None:
216
+ cache.annotation_summary.invalidate((project_id, name, "trace"))
217
+ cache.evaluation_summary.invalidate((project_id, name, "trace"))
218
+
219
+
220
+ class _DocumentAnnotationDmlEventHandler(_AnnotationDmlEventHandler[DocumentAnnotationDmlEvent]):
221
+ _table = DocumentAnnotation
222
+
223
+ def __init__(self, **kwargs: Unpack[_HandlerParams]) -> None:
224
+ super().__init__(**kwargs)
225
+ self._stmt = self._stmt.join_from(Trace, Span).join_from(Span, self._table)
226
+
227
+ @staticmethod
228
+ def _clear(cache: CacheForDataLoaders, project_id: int, name: str) -> None:
229
+ cache.document_evaluation_summary.invalidate((project_id, name))
230
+
231
+
232
+ class DmlEventHandler:
233
+ def __init__(
234
+ self,
235
+ *,
236
+ db: DbSessionFactory,
237
+ last_updated_at: CanSetLastUpdatedAt,
238
+ cache_for_dataloaders: Optional[CacheForDataLoaders] = None,
239
+ sleep_seconds: float = 0.1,
240
+ ) -> None:
241
+ kwargs = _HandlerParams(
242
+ db=db,
243
+ last_updated_at=last_updated_at,
244
+ cache_for_dataloaders=cache_for_dataloaders,
245
+ sleep_seconds=sleep_seconds,
246
+ )
247
+ self._handlers: Mapping[Type[DmlEvent], Iterable[_DmlEventHandler[Any]]] = {
248
+ DmlEvent: [_GenericDmlEventHandler(**kwargs)],
249
+ SpanDmlEvent: [_SpanDmlEventHandler(**kwargs)],
250
+ SpanDeleteEvent: [_SpanDeleteEventHandler(**kwargs)],
251
+ SpanAnnotationDmlEvent: [_SpanAnnotationDmlEventHandler(**kwargs)],
252
+ TraceAnnotationDmlEvent: [_TraceAnnotationDmlEventHandler(**kwargs)],
253
+ DocumentAnnotationDmlEvent: [_DocumentAnnotationDmlEventHandler(**kwargs)],
254
+ }
255
+ self._all_handlers = frozenset(chain.from_iterable(self._handlers.values()))
256
+
257
+ async def __aenter__(self) -> None:
258
+ await gather(*(h.start() for h in self._all_handlers))
259
+
260
+ async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
261
+ await gather(*(h.stop() for h in self._all_handlers))
262
+
263
+ def put(self, event: DmlEvent) -> None:
264
+ if not (isinstance(event, DmlEvent) and event):
265
+ return
266
+ for cls in getmro(type(event)):
267
+ if not (issubclass(cls, DmlEvent) and (handlers := self._handlers.get(cls))):
268
+ continue
269
+ for h in handlers:
270
+ h.put(event)
271
+ if cls is DmlEvent:
272
+ break
@@ -1,22 +1,22 @@
1
1
  {
2
- "_components-CAummAJx.js": {
3
- "file": "assets/components-CAummAJx.js",
2
+ "_components-BSw2e1Zr.js": {
3
+ "file": "assets/components-BSw2e1Zr.js",
4
4
  "name": "components",
5
5
  "imports": [
6
6
  "_vendor-BMWfu6zp.js",
7
- "_vendor-arizeai-CkyzG9Wl.js",
8
- "_pages-BU__X1UX.js",
7
+ "_vendor-arizeai-CIETbKDq.js",
8
+ "_pages-p_fuED5k.js",
9
9
  "_vendor-three-DwGkEfCM.js",
10
10
  "_vendor-codemirror-DO3VqEcD.js"
11
11
  ]
12
12
  },
13
- "_pages-BU__X1UX.js": {
14
- "file": "assets/pages-BU__X1UX.js",
13
+ "_pages-p_fuED5k.js": {
14
+ "file": "assets/pages-p_fuED5k.js",
15
15
  "name": "pages",
16
16
  "imports": [
17
17
  "_vendor-BMWfu6zp.js",
18
- "_components-CAummAJx.js",
19
- "_vendor-arizeai-CkyzG9Wl.js",
18
+ "_components-BSw2e1Zr.js",
19
+ "_vendor-arizeai-CIETbKDq.js",
20
20
  "_vendor-recharts-BGN0SxgJ.js",
21
21
  "_vendor-codemirror-DO3VqEcD.js"
22
22
  ]
@@ -35,8 +35,8 @@
35
35
  "assets/vendor-DxkFTwjz.css"
36
36
  ]
37
37
  },
38
- "_vendor-arizeai-CkyzG9Wl.js": {
39
- "file": "assets/vendor-arizeai-CkyzG9Wl.js",
38
+ "_vendor-arizeai-CIETbKDq.js": {
39
+ "file": "assets/vendor-arizeai-CIETbKDq.js",
40
40
  "name": "vendor-arizeai",
41
41
  "imports": [
42
42
  "_vendor-BMWfu6zp.js"
@@ -61,15 +61,15 @@
61
61
  "name": "vendor-three"
62
62
  },
63
63
  "index.tsx": {
64
- "file": "assets/index-Cg5hdf3g.js",
64
+ "file": "assets/index-BYUFcdtx.js",
65
65
  "name": "index",
66
66
  "src": "index.tsx",
67
67
  "isEntry": true,
68
68
  "imports": [
69
69
  "_vendor-BMWfu6zp.js",
70
- "_vendor-arizeai-CkyzG9Wl.js",
71
- "_components-CAummAJx.js",
72
- "_pages-BU__X1UX.js",
70
+ "_vendor-arizeai-CIETbKDq.js",
71
+ "_components-BSw2e1Zr.js",
72
+ "_pages-p_fuED5k.js",
73
73
  "_vendor-three-DwGkEfCM.js",
74
74
  "_vendor-codemirror-DO3VqEcD.js",
75
75
  "_vendor-recharts-BGN0SxgJ.js"