arize-phoenix 5.5.2__py3-none-any.whl → 5.7.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 (186) hide show
  1. {arize_phoenix-5.5.2.dist-info → arize_phoenix-5.7.0.dist-info}/METADATA +4 -7
  2. arize_phoenix-5.7.0.dist-info/RECORD +330 -0
  3. phoenix/config.py +50 -8
  4. phoenix/core/model.py +3 -3
  5. phoenix/core/model_schema.py +41 -50
  6. phoenix/core/model_schema_adapter.py +17 -16
  7. phoenix/datetime_utils.py +2 -2
  8. phoenix/db/bulk_inserter.py +10 -20
  9. phoenix/db/engines.py +2 -1
  10. phoenix/db/enums.py +2 -2
  11. phoenix/db/helpers.py +8 -7
  12. phoenix/db/insertion/dataset.py +9 -19
  13. phoenix/db/insertion/document_annotation.py +14 -13
  14. phoenix/db/insertion/helpers.py +6 -16
  15. phoenix/db/insertion/span_annotation.py +14 -13
  16. phoenix/db/insertion/trace_annotation.py +14 -13
  17. phoenix/db/insertion/types.py +19 -30
  18. phoenix/db/migrations/versions/3be8647b87d8_add_token_columns_to_spans_table.py +8 -8
  19. phoenix/db/models.py +28 -28
  20. phoenix/experiments/evaluators/base.py +2 -1
  21. phoenix/experiments/evaluators/code_evaluators.py +4 -5
  22. phoenix/experiments/evaluators/llm_evaluators.py +157 -4
  23. phoenix/experiments/evaluators/utils.py +3 -2
  24. phoenix/experiments/functions.py +10 -21
  25. phoenix/experiments/tracing.py +2 -1
  26. phoenix/experiments/types.py +20 -29
  27. phoenix/experiments/utils.py +2 -1
  28. phoenix/inferences/errors.py +6 -5
  29. phoenix/inferences/fixtures.py +6 -5
  30. phoenix/inferences/inferences.py +37 -37
  31. phoenix/inferences/schema.py +11 -10
  32. phoenix/inferences/validation.py +13 -14
  33. phoenix/logging/_formatter.py +3 -3
  34. phoenix/metrics/__init__.py +5 -4
  35. phoenix/metrics/binning.py +2 -1
  36. phoenix/metrics/metrics.py +2 -1
  37. phoenix/metrics/mixins.py +7 -6
  38. phoenix/metrics/retrieval_metrics.py +2 -1
  39. phoenix/metrics/timeseries.py +5 -4
  40. phoenix/metrics/wrappers.py +2 -2
  41. phoenix/pointcloud/clustering.py +3 -4
  42. phoenix/pointcloud/pointcloud.py +7 -5
  43. phoenix/pointcloud/umap_parameters.py +2 -1
  44. phoenix/server/api/dataloaders/annotation_summaries.py +12 -19
  45. phoenix/server/api/dataloaders/average_experiment_run_latency.py +2 -2
  46. phoenix/server/api/dataloaders/cache/two_tier_cache.py +3 -2
  47. phoenix/server/api/dataloaders/dataset_example_revisions.py +3 -8
  48. phoenix/server/api/dataloaders/dataset_example_spans.py +2 -5
  49. phoenix/server/api/dataloaders/document_evaluation_summaries.py +12 -18
  50. phoenix/server/api/dataloaders/document_evaluations.py +3 -7
  51. phoenix/server/api/dataloaders/document_retrieval_metrics.py +6 -13
  52. phoenix/server/api/dataloaders/experiment_annotation_summaries.py +4 -8
  53. phoenix/server/api/dataloaders/experiment_error_rates.py +2 -5
  54. phoenix/server/api/dataloaders/experiment_run_annotations.py +3 -7
  55. phoenix/server/api/dataloaders/experiment_run_counts.py +1 -5
  56. phoenix/server/api/dataloaders/experiment_sequence_number.py +2 -5
  57. phoenix/server/api/dataloaders/latency_ms_quantile.py +21 -30
  58. phoenix/server/api/dataloaders/min_start_or_max_end_times.py +7 -13
  59. phoenix/server/api/dataloaders/project_by_name.py +3 -3
  60. phoenix/server/api/dataloaders/record_counts.py +11 -18
  61. phoenix/server/api/dataloaders/span_annotations.py +3 -7
  62. phoenix/server/api/dataloaders/span_dataset_examples.py +3 -8
  63. phoenix/server/api/dataloaders/span_descendants.py +3 -7
  64. phoenix/server/api/dataloaders/span_projects.py +2 -2
  65. phoenix/server/api/dataloaders/token_counts.py +12 -19
  66. phoenix/server/api/dataloaders/trace_row_ids.py +3 -7
  67. phoenix/server/api/dataloaders/user_roles.py +3 -3
  68. phoenix/server/api/dataloaders/users.py +3 -3
  69. phoenix/server/api/helpers/__init__.py +4 -3
  70. phoenix/server/api/helpers/dataset_helpers.py +10 -9
  71. phoenix/server/api/helpers/playground_clients.py +671 -0
  72. phoenix/server/api/helpers/playground_registry.py +70 -0
  73. phoenix/server/api/helpers/playground_spans.py +325 -0
  74. phoenix/server/api/input_types/AddExamplesToDatasetInput.py +2 -2
  75. phoenix/server/api/input_types/AddSpansToDatasetInput.py +2 -2
  76. phoenix/server/api/input_types/ChatCompletionInput.py +38 -0
  77. phoenix/server/api/input_types/ChatCompletionMessageInput.py +13 -1
  78. phoenix/server/api/input_types/ClusterInput.py +2 -2
  79. phoenix/server/api/input_types/DeleteAnnotationsInput.py +1 -3
  80. phoenix/server/api/input_types/DeleteDatasetExamplesInput.py +2 -2
  81. phoenix/server/api/input_types/DeleteExperimentsInput.py +1 -3
  82. phoenix/server/api/input_types/DimensionFilter.py +4 -4
  83. phoenix/server/api/input_types/GenerativeModelInput.py +17 -0
  84. phoenix/server/api/input_types/Granularity.py +1 -1
  85. phoenix/server/api/input_types/InvocationParameters.py +156 -13
  86. phoenix/server/api/input_types/PatchDatasetExamplesInput.py +2 -2
  87. phoenix/server/api/input_types/TemplateOptions.py +10 -0
  88. phoenix/server/api/mutations/__init__.py +4 -0
  89. phoenix/server/api/mutations/chat_mutations.py +374 -0
  90. phoenix/server/api/mutations/dataset_mutations.py +4 -4
  91. phoenix/server/api/mutations/experiment_mutations.py +1 -2
  92. phoenix/server/api/mutations/export_events_mutations.py +7 -7
  93. phoenix/server/api/mutations/span_annotations_mutations.py +4 -4
  94. phoenix/server/api/mutations/trace_annotations_mutations.py +4 -4
  95. phoenix/server/api/mutations/user_mutations.py +4 -4
  96. phoenix/server/api/openapi/schema.py +2 -2
  97. phoenix/server/api/queries.py +61 -72
  98. phoenix/server/api/routers/oauth2.py +4 -4
  99. phoenix/server/api/routers/v1/datasets.py +22 -36
  100. phoenix/server/api/routers/v1/evaluations.py +6 -5
  101. phoenix/server/api/routers/v1/experiment_evaluations.py +2 -2
  102. phoenix/server/api/routers/v1/experiment_runs.py +2 -2
  103. phoenix/server/api/routers/v1/experiments.py +4 -4
  104. phoenix/server/api/routers/v1/spans.py +13 -12
  105. phoenix/server/api/routers/v1/traces.py +5 -5
  106. phoenix/server/api/routers/v1/utils.py +5 -5
  107. phoenix/server/api/schema.py +42 -10
  108. phoenix/server/api/subscriptions.py +347 -494
  109. phoenix/server/api/types/AnnotationSummary.py +3 -3
  110. phoenix/server/api/types/ChatCompletionSubscriptionPayload.py +44 -0
  111. phoenix/server/api/types/Cluster.py +8 -7
  112. phoenix/server/api/types/Dataset.py +5 -4
  113. phoenix/server/api/types/Dimension.py +3 -3
  114. phoenix/server/api/types/DocumentEvaluationSummary.py +8 -7
  115. phoenix/server/api/types/EmbeddingDimension.py +6 -5
  116. phoenix/server/api/types/EvaluationSummary.py +3 -3
  117. phoenix/server/api/types/Event.py +7 -7
  118. phoenix/server/api/types/Experiment.py +3 -3
  119. phoenix/server/api/types/ExperimentComparison.py +2 -4
  120. phoenix/server/api/types/GenerativeProvider.py +27 -3
  121. phoenix/server/api/types/Inferences.py +9 -8
  122. phoenix/server/api/types/InferencesRole.py +2 -2
  123. phoenix/server/api/types/Model.py +2 -2
  124. phoenix/server/api/types/Project.py +11 -18
  125. phoenix/server/api/types/Segments.py +3 -3
  126. phoenix/server/api/types/Span.py +45 -7
  127. phoenix/server/api/types/TemplateLanguage.py +9 -0
  128. phoenix/server/api/types/TimeSeries.py +8 -7
  129. phoenix/server/api/types/Trace.py +2 -2
  130. phoenix/server/api/types/UMAPPoints.py +6 -6
  131. phoenix/server/api/types/User.py +3 -3
  132. phoenix/server/api/types/node.py +1 -3
  133. phoenix/server/api/types/pagination.py +4 -4
  134. phoenix/server/api/utils.py +2 -4
  135. phoenix/server/app.py +76 -37
  136. phoenix/server/bearer_auth.py +4 -10
  137. phoenix/server/dml_event.py +3 -3
  138. phoenix/server/dml_event_handler.py +10 -24
  139. phoenix/server/grpc_server.py +3 -2
  140. phoenix/server/jwt_store.py +22 -21
  141. phoenix/server/main.py +17 -4
  142. phoenix/server/oauth2.py +3 -2
  143. phoenix/server/rate_limiters.py +5 -8
  144. phoenix/server/static/.vite/manifest.json +31 -31
  145. phoenix/server/static/assets/components-Csu8UKOs.js +1612 -0
  146. phoenix/server/static/assets/{index-DCzakdJq.js → index-Bk5C9EA7.js} +2 -2
  147. phoenix/server/static/assets/{pages-CAL1FDMt.js → pages-UeWaKXNs.js} +337 -442
  148. phoenix/server/static/assets/{vendor-6IcPAw_j.js → vendor-CtqfhlbC.js} +6 -6
  149. phoenix/server/static/assets/{vendor-arizeai-DRZuoyuF.js → vendor-arizeai-C_3SBz56.js} +2 -2
  150. phoenix/server/static/assets/{vendor-codemirror-DVE2_WBr.js → vendor-codemirror-wfdk9cjp.js} +1 -1
  151. phoenix/server/static/assets/{vendor-recharts-DwrexFA4.js → vendor-recharts-BiVnSv90.js} +1 -1
  152. phoenix/server/templates/index.html +1 -0
  153. phoenix/server/thread_server.py +1 -1
  154. phoenix/server/types.py +17 -29
  155. phoenix/services.py +8 -3
  156. phoenix/session/client.py +12 -24
  157. phoenix/session/data_extractor.py +3 -3
  158. phoenix/session/evaluation.py +1 -2
  159. phoenix/session/session.py +26 -21
  160. phoenix/trace/attributes.py +16 -28
  161. phoenix/trace/dsl/filter.py +17 -21
  162. phoenix/trace/dsl/helpers.py +3 -3
  163. phoenix/trace/dsl/query.py +13 -22
  164. phoenix/trace/fixtures.py +11 -17
  165. phoenix/trace/otel.py +5 -15
  166. phoenix/trace/projects.py +3 -2
  167. phoenix/trace/schemas.py +2 -2
  168. phoenix/trace/span_evaluations.py +9 -8
  169. phoenix/trace/span_json_decoder.py +3 -3
  170. phoenix/trace/span_json_encoder.py +2 -2
  171. phoenix/trace/trace_dataset.py +6 -5
  172. phoenix/trace/utils.py +6 -6
  173. phoenix/utilities/deprecation.py +3 -2
  174. phoenix/utilities/error_handling.py +3 -2
  175. phoenix/utilities/json.py +2 -1
  176. phoenix/utilities/logging.py +2 -2
  177. phoenix/utilities/project.py +1 -1
  178. phoenix/utilities/re.py +3 -4
  179. phoenix/utilities/template_formatters.py +16 -5
  180. phoenix/version.py +1 -1
  181. arize_phoenix-5.5.2.dist-info/RECORD +0 -321
  182. phoenix/server/static/assets/components-hX0LgYz3.js +0 -1428
  183. {arize_phoenix-5.5.2.dist-info → arize_phoenix-5.7.0.dist-info}/WHEEL +0 -0
  184. {arize_phoenix-5.5.2.dist-info → arize_phoenix-5.7.0.dist-info}/entry_points.txt +0 -0
  185. {arize_phoenix-5.5.2.dist-info → arize_phoenix-5.7.0.dist-info}/licenses/IP_NOTICE +0 -0
  186. {arize_phoenix-5.5.2.dist-info → arize_phoenix-5.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -2,24 +2,10 @@ from __future__ import annotations
2
2
 
3
3
  from abc import ABC, abstractmethod
4
4
  from asyncio import gather
5
+ from collections.abc import Callable, Iterable, Iterator, Mapping
5
6
  from inspect import getmro
6
7
  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
- )
8
+ from typing import Any, Generic, Optional, TypedDict, TypeVar, Union, cast
23
9
 
24
10
  from sqlalchemy import Select, select
25
11
  from typing_extensions import TypeAlias, Unpack
@@ -54,7 +40,7 @@ _DmlEventT = TypeVar("_DmlEventT", bound=DmlEvent)
54
40
  class _DmlEventQueue(Generic[_DmlEventT]):
55
41
  def __init__(self, **kwargs: Any) -> None:
56
42
  super().__init__(**kwargs)
57
- self._events: Set[_DmlEventT] = set()
43
+ self._events: set[_DmlEventT] = set()
58
44
 
59
45
  @property
60
46
  def empty(self) -> bool:
@@ -120,7 +106,7 @@ class _GenericDmlEventHandler(_DmlEventHandler[DmlEvent]):
120
106
  for id_ in e.ids:
121
107
  self._update(e.table, id_)
122
108
 
123
- def _update(self, table: Type[Base], id_: int) -> None:
109
+ def _update(self, table: type[Base], id_: int) -> None:
124
110
  self._last_updated_at.set(table, id_)
125
111
 
126
112
 
@@ -146,9 +132,9 @@ class _SpanDeleteEventHandler(_SpanDmlEventHandler):
146
132
 
147
133
 
148
134
  _AnnotationTable: TypeAlias = Union[
149
- Type[SpanAnnotation],
150
- Type[TraceAnnotation],
151
- Type[DocumentAnnotation],
135
+ type[SpanAnnotation],
136
+ type[TraceAnnotation],
137
+ type[DocumentAnnotation],
152
138
  ]
153
139
 
154
140
  _AnnotationDmlEventT = TypeVar(
@@ -165,7 +151,7 @@ class _AnnotationDmlEventHandler(
165
151
  ABC,
166
152
  ):
167
153
  _table: _AnnotationTable
168
- _base_stmt: Union[Select[Tuple[int, str]], Select[Tuple[int]]] = (
154
+ _base_stmt: Union[Select[tuple[int, str]], Select[tuple[int]]] = (
169
155
  select(Project.id).join_from(Project, Trace).distinct()
170
156
  )
171
157
 
@@ -175,7 +161,7 @@ class _AnnotationDmlEventHandler(
175
161
  if self._cache_for_dataloaders:
176
162
  self._stmt = self._stmt.add_columns(self._table.name)
177
163
 
178
- def _get_stmt(self) -> Union[Select[Tuple[int, str]], Select[Tuple[int]]]:
164
+ def _get_stmt(self) -> Union[Select[tuple[int, str]], Select[tuple[int]]]:
179
165
  ids = set(chain.from_iterable(e.ids for e in self._batch))
180
166
  return self._stmt.where(self._table.id.in_(ids))
181
167
 
@@ -242,7 +228,7 @@ class DmlEventHandler:
242
228
  cache_for_dataloaders=cache_for_dataloaders,
243
229
  sleep_seconds=sleep_seconds,
244
230
  )
245
- self._handlers: Mapping[Type[DmlEvent], Iterable[_DmlEventHandler[Any]]] = {
231
+ self._handlers: Mapping[type[DmlEvent], Iterable[_DmlEventHandler[Any]]] = {
246
232
  DmlEvent: [_GenericDmlEventHandler(**kwargs)],
247
233
  SpanDmlEvent: [_SpanDmlEventHandler(**kwargs)],
248
234
  SpanDeleteEvent: [_SpanDeleteEventHandler(**kwargs)],
@@ -1,4 +1,5 @@
1
- from typing import TYPE_CHECKING, Any, Awaitable, Callable, List, Optional
1
+ from collections.abc import Awaitable, Callable
2
+ from typing import TYPE_CHECKING, Any, Optional
2
3
 
3
4
  import grpc
4
5
  from grpc.aio import RpcContext, Server, ServerInterceptor
@@ -66,7 +67,7 @@ class GrpcServer:
66
67
  async def __aenter__(self) -> None:
67
68
  if self._disabled:
68
69
  return
69
- interceptors: List[ServerInterceptor] = []
70
+ interceptors: list[ServerInterceptor] = []
70
71
  if self._token_store:
71
72
  interceptors.append(ApiKeyInterceptor(self._token_store))
72
73
  if self._enable_prometheus:
@@ -1,11 +1,12 @@
1
1
  import logging
2
2
  from abc import ABC, abstractmethod
3
3
  from asyncio import create_task, gather, sleep
4
+ from collections.abc import Callable, Coroutine
4
5
  from copy import deepcopy
5
6
  from dataclasses import replace
6
7
  from datetime import datetime, timezone
7
8
  from functools import cached_property, singledispatchmethod
8
- from typing import Any, Callable, Coroutine, Dict, Generic, List, Optional, Tuple, Type, TypeVar
9
+ from typing import Any, Generic, Optional, TypeVar
9
10
 
10
11
  from authlib.jose import jwt
11
12
  from authlib.jose.errors import JoseError
@@ -65,7 +66,7 @@ class JwtStore:
65
66
  self._api_key_store = _ApiKeyStore(*args, **kwargs)
66
67
 
67
68
  @cached_property
68
- def _stores(self) -> Tuple[DaemonTask, ...]:
69
+ def _stores(self) -> tuple[DaemonTask, ...]:
69
70
  return tuple(dt for dt in self.__dict__.values() if isinstance(dt, _Store))
70
71
 
71
72
  async def __aenter__(self) -> None:
@@ -131,34 +132,34 @@ class JwtStore:
131
132
  async def create_password_reset_token(
132
133
  self,
133
134
  claim: PasswordResetTokenClaims,
134
- ) -> Tuple[PasswordResetToken, PasswordResetTokenId]:
135
+ ) -> tuple[PasswordResetToken, PasswordResetTokenId]:
135
136
  return await self._password_reset_token_store.create(claim)
136
137
 
137
138
  async def create_access_token(
138
139
  self,
139
140
  claim: AccessTokenClaims,
140
- ) -> Tuple[AccessToken, AccessTokenId]:
141
+ ) -> tuple[AccessToken, AccessTokenId]:
141
142
  return await self._access_token_store.create(claim)
142
143
 
143
144
  async def create_refresh_token(
144
145
  self,
145
146
  claim: RefreshTokenClaims,
146
- ) -> Tuple[RefreshToken, RefreshTokenId]:
147
+ ) -> tuple[RefreshToken, RefreshTokenId]:
147
148
  return await self._refresh_token_store.create(claim)
148
149
 
149
150
  async def create_api_key(
150
151
  self,
151
152
  claim: ApiKeyClaims,
152
- ) -> Tuple[ApiKey, ApiKeyId]:
153
+ ) -> tuple[ApiKey, ApiKeyId]:
153
154
  return await self._api_key_store.create(claim)
154
155
 
155
156
  async def revoke(self, *token_ids: TokenId) -> None:
156
157
  if not token_ids:
157
158
  return
158
- password_reset_token_ids: List[PasswordResetTokenId] = []
159
- access_token_ids: List[AccessTokenId] = []
160
- refresh_token_ids: List[RefreshTokenId] = []
161
- api_key_ids: List[ApiKeyId] = []
159
+ password_reset_token_ids: list[PasswordResetTokenId] = []
160
+ access_token_ids: list[AccessTokenId] = []
161
+ refresh_token_ids: list[RefreshTokenId] = []
162
+ api_key_ids: list[ApiKeyId] = []
162
163
  for token_id in token_ids:
163
164
  if isinstance(token_id, PasswordResetTokenId):
164
165
  password_reset_token_ids.append(token_id)
@@ -168,7 +169,7 @@ class JwtStore:
168
169
  refresh_token_ids.append(token_id)
169
170
  elif isinstance(token_id, ApiKeyId):
170
171
  api_key_ids.append(token_id)
171
- coroutines: List[Coroutine[None, None, None]] = []
172
+ coroutines: list[Coroutine[None, None, None]] = []
172
173
  if password_reset_token_ids:
173
174
  coroutines.append(self._password_reset_token_store.revoke(*password_reset_token_ids))
174
175
  if access_token_ids:
@@ -202,7 +203,7 @@ _RecordT = TypeVar(
202
203
 
203
204
  class _Claims(Generic[_TokenIdT, _ClaimSetT]):
204
205
  def __init__(self) -> None:
205
- self._cache: Dict[_TokenIdT, _ClaimSetT] = {}
206
+ self._cache: dict[_TokenIdT, _ClaimSetT] = {}
206
207
 
207
208
  def __getitem__(self, token_id: _TokenIdT) -> Optional[_ClaimSetT]:
208
209
  claim = self._cache.get(token_id)
@@ -223,7 +224,7 @@ class _Claims(Generic[_TokenIdT, _ClaimSetT]):
223
224
 
224
225
 
225
226
  class _Store(DaemonTask, Generic[_ClaimSetT, _TokenT, _TokenIdT, _RecordT], ABC):
226
- _table: Type[_RecordT]
227
+ _table: type[_RecordT]
227
228
  _token_id: Callable[[int], _TokenIdT]
228
229
  _token: Callable[[str], _TokenT]
229
230
 
@@ -244,7 +245,7 @@ class _Store(DaemonTask, Generic[_ClaimSetT, _TokenT, _TokenIdT, _RecordT], ABC)
244
245
  self._algorithm = algorithm
245
246
 
246
247
  def _encode(self, claim: ClaimSet) -> str:
247
- payload: Dict[str, Any] = dict(jti=claim.token_id)
248
+ payload: dict[str, Any] = dict(jti=claim.token_id)
248
249
  header = {"alg": self._algorithm}
249
250
  jwt_bytes: bytes = jwt.encode(header=header, payload=payload, key=self._secret)
250
251
  return jwt_bytes.decode()
@@ -275,12 +276,12 @@ class _Store(DaemonTask, Generic[_ClaimSetT, _TokenT, _TokenIdT, _RecordT], ABC)
275
276
  await session.execute(stmt)
276
277
 
277
278
  @abstractmethod
278
- def _from_db(self, record: _RecordT, role: UserRole) -> Tuple[_TokenIdT, _ClaimSetT]: ...
279
+ def _from_db(self, record: _RecordT, role: UserRole) -> tuple[_TokenIdT, _ClaimSetT]: ...
279
280
 
280
281
  @abstractmethod
281
282
  def _to_db(self, claims: _ClaimSetT) -> _RecordT: ...
282
283
 
283
- async def create(self, claim: _ClaimSetT) -> Tuple[_TokenT, _TokenIdT]:
284
+ async def create(self, claim: _ClaimSetT) -> tuple[_TokenT, _TokenIdT]:
284
285
  record = self._to_db(claim)
285
286
  async with self._db() as session:
286
287
  session.add(record)
@@ -303,7 +304,7 @@ class _Store(DaemonTask, Generic[_ClaimSetT, _TokenT, _TokenIdT, _RecordT], ABC)
303
304
  self._claims = claims
304
305
 
305
306
  @cached_property
306
- def _update_stmt(self) -> Select[Tuple[_RecordT, str]]:
307
+ def _update_stmt(self) -> Select[tuple[_RecordT, str]]:
307
308
  return (
308
309
  select(self._table, models.UserRole.name)
309
310
  .join_from(self._table, models.User)
@@ -340,7 +341,7 @@ class _PasswordResetTokenStore(
340
341
  self,
341
342
  record: models.PasswordResetToken,
342
343
  user_role: UserRole,
343
- ) -> Tuple[PasswordResetTokenId, PasswordResetTokenClaims]:
344
+ ) -> tuple[PasswordResetTokenId, PasswordResetTokenClaims]:
344
345
  token_id = PasswordResetTokenId(record.id)
345
346
  return token_id, PasswordResetTokenClaims(
346
347
  token_id=token_id,
@@ -379,7 +380,7 @@ class _AccessTokenStore(
379
380
  self,
380
381
  record: models.AccessToken,
381
382
  user_role: UserRole,
382
- ) -> Tuple[AccessTokenId, AccessTokenClaims]:
383
+ ) -> tuple[AccessTokenId, AccessTokenClaims]:
383
384
  token_id = AccessTokenId(record.id)
384
385
  refresh_token_id = RefreshTokenId(record.refresh_token_id)
385
386
  return token_id, AccessTokenClaims(
@@ -423,7 +424,7 @@ class _RefreshTokenStore(
423
424
  self,
424
425
  record: models.RefreshToken,
425
426
  user_role: UserRole,
426
- ) -> Tuple[RefreshTokenId, RefreshTokenClaims]:
427
+ ) -> tuple[RefreshTokenId, RefreshTokenClaims]:
427
428
  token_id = RefreshTokenId(record.id)
428
429
  return token_id, RefreshTokenClaims(
429
430
  token_id=token_id,
@@ -469,7 +470,7 @@ class _ApiKeyStore(
469
470
  self,
470
471
  record: models.ApiKey,
471
472
  user_role: UserRole,
472
- ) -> Tuple[ApiKeyId, ApiKeyClaims]:
473
+ ) -> tuple[ApiKeyId, ApiKeyClaims]:
473
474
  token_id = ApiKeyId(record.id)
474
475
  return token_id, ApiKeyClaims(
475
476
  token_id=token_id,
phoenix/server/main.py CHANGED
@@ -6,7 +6,7 @@ from argparse import SUPPRESS, ArgumentParser
6
6
  from pathlib import Path
7
7
  from threading import Thread
8
8
  from time import sleep, time
9
- from typing import List, Optional
9
+ from typing import Optional
10
10
  from urllib.parse import urljoin
11
11
 
12
12
  from jinja2 import BaseLoader, Environment
@@ -21,6 +21,7 @@ from phoenix.config import (
21
21
  get_env_database_schema,
22
22
  get_env_db_logging_level,
23
23
  get_env_enable_prometheus,
24
+ get_env_enable_websockets,
24
25
  get_env_grpc_port,
25
26
  get_env_host,
26
27
  get_env_host_root_path,
@@ -95,6 +96,7 @@ _WELCOME_MESSAGE = Environment(loader=BaseLoader()).from_string("""
95
96
  | 🚀 Phoenix Server 🚀
96
97
  | Phoenix UI: {{ ui_path }}
97
98
  | Authentication: {{ auth_enabled }}
99
+ | Websockets: {{ websockets_enabled }}
98
100
  | Log traces:
99
101
  | - gRPC: {{ grpc_path }}
100
102
  | - HTTP: {{ http_path }}
@@ -162,7 +164,7 @@ def main() -> None:
162
164
  parser.add_argument("--debug", action="store_true", help=SUPPRESS)
163
165
  parser.add_argument("--dev", action="store_true", help=SUPPRESS)
164
166
  parser.add_argument("--no-ui", action="store_true", help=SUPPRESS)
165
-
167
+ parser.add_argument("--enable-websockets", type=str, help=SUPPRESS)
166
168
  subparsers = parser.add_subparsers(dest="command", required=True, help=SUPPRESS)
167
169
 
168
170
  serve_parser = subparsers.add_parser("serve")
@@ -312,8 +314,8 @@ def main() -> None:
312
314
 
313
315
  authentication_enabled, secret = get_env_auth_settings()
314
316
 
315
- fixture_spans: List[Span] = []
316
- fixture_evals: List[pb.Evaluation] = []
317
+ fixture_spans: list[Span] = []
318
+ fixture_evals: list[pb.Evaluation] = []
317
319
  if trace_dataset_name is not None:
318
320
  fixture_spans, fixture_evals = reset_fixture_span_ids_and_timestamps(
319
321
  (
@@ -348,6 +350,14 @@ def main() -> None:
348
350
  corpus_model = (
349
351
  None if corpus_inferences is None else create_model_from_inferences(corpus_inferences)
350
352
  )
353
+
354
+ # Get enable_websockets from environment variable or command line argument
355
+ enable_websockets = get_env_enable_websockets()
356
+ if args.enable_websockets is not None:
357
+ enable_websockets = args.enable_websockets.lower() == "true"
358
+ if enable_websockets is None:
359
+ enable_websockets = True
360
+
351
361
  # Print information about the server
352
362
  root_path = urljoin(f"http://{host}:{port}", host_root_path)
353
363
  msg = _WELCOME_MESSAGE.render(
@@ -358,6 +368,7 @@ def main() -> None:
358
368
  storage=get_printable_db_url(db_connection_str),
359
369
  schema=get_env_database_schema(),
360
370
  auth_enabled=authentication_enabled,
371
+ websockets_enabled=enable_websockets,
361
372
  )
362
373
  if sys.platform.startswith("win"):
363
374
  msg = codecs.encode(msg, "ascii", errors="ignore").decode("ascii").strip()
@@ -382,10 +393,12 @@ def main() -> None:
382
393
  connection_method="STARTTLS",
383
394
  validate_certs=get_env_smtp_validate_certs(),
384
395
  )
396
+
385
397
  app = create_app(
386
398
  db=factory,
387
399
  export_path=export_path,
388
400
  model=model,
401
+ enable_websockets=enable_websockets,
389
402
  authentication_enabled=authentication_enabled,
390
403
  umap_params=umap_params,
391
404
  corpus=corpus_model,
phoenix/server/oauth2.py CHANGED
@@ -1,4 +1,5 @@
1
- from typing import Any, Dict, Iterable
1
+ from collections.abc import Iterable
2
+ from typing import Any
2
3
 
3
4
  from authlib.integrations.base_client import BaseApp
4
5
  from authlib.integrations.base_client.async_app import AsyncOAuth2Mixin
@@ -24,7 +25,7 @@ class OAuth2Client(AsyncOAuth2Mixin, AsyncOpenIDMixin, BaseApp): # type:ignore[
24
25
 
25
26
  class OAuth2Clients:
26
27
  def __init__(self) -> None:
27
- self._clients: Dict[str, OAuth2Client] = {}
28
+ self._clients: dict[str, OAuth2Client] = {}
28
29
 
29
30
  def add_client(self, config: OAuth2ClientConfig) -> None:
30
31
  if (idp_name := config.idp_name) in self._clients:
@@ -1,13 +1,10 @@
1
1
  import re
2
2
  import time
3
3
  from collections import defaultdict
4
+ from collections.abc import Callable, Coroutine
4
5
  from functools import partial
5
6
  from typing import (
6
7
  Any,
7
- Callable,
8
- Coroutine,
9
- DefaultDict,
10
- List,
11
8
  Optional,
12
9
  Pattern, # import from re module when we drop support for 3.8
13
10
  Union,
@@ -98,7 +95,7 @@ class ServerRateLimiter:
98
95
  self._last_cleanup_time = time.time()
99
96
 
100
97
  def _reset_rate_limiters(self) -> None:
101
- self.cache_partitions: List[DefaultDict[Any, TokenBucket]] = [
98
+ self.cache_partitions: list[defaultdict[Any, TokenBucket]] = [
102
99
  defaultdict(self.bucket_factory) for _ in range(self.num_partitions)
103
100
  ]
104
101
 
@@ -107,10 +104,10 @@ class ServerRateLimiter:
107
104
  int(timestamp // self.partition_seconds) % self.num_partitions
108
105
  ) # a cyclic bucket index
109
106
 
110
- def _active_partition_indices(self, current_index: int) -> List[int]:
107
+ def _active_partition_indices(self, current_index: int) -> list[int]:
111
108
  return [(current_index - ii) % self.num_partitions for ii in range(self.active_partitions)]
112
109
 
113
- def _inactive_partition_indices(self, current_index: int) -> List[int]:
110
+ def _inactive_partition_indices(self, current_index: int) -> list[int]:
114
111
  active_indices = set(self._active_partition_indices(current_index))
115
112
  all_indices = set(range(self.num_partitions))
116
113
  return list(all_indices - active_indices)
@@ -156,7 +153,7 @@ class ServerRateLimiter:
156
153
 
157
154
 
158
155
  def fastapi_ip_rate_limiter(
159
- rate_limiter: ServerRateLimiter, paths: Optional[List[Union[str, Pattern[str]]]] = None
156
+ rate_limiter: ServerRateLimiter, paths: Optional[list[Union[str, Pattern[str]]]] = None
160
157
  ) -> Callable[[Request], Coroutine[Any, Any, Request]]:
161
158
  async def dependency(request: Request) -> Request:
162
159
  if paths is None or any(path_match(request.url.path, path) for path in paths):
@@ -1,32 +1,32 @@
1
1
  {
2
- "_components-hX0LgYz3.js": {
3
- "file": "assets/components-hX0LgYz3.js",
2
+ "_components-Csu8UKOs.js": {
3
+ "file": "assets/components-Csu8UKOs.js",
4
4
  "name": "components",
5
5
  "imports": [
6
- "_vendor-6IcPAw_j.js",
7
- "_vendor-arizeai-DRZuoyuF.js",
8
- "_pages-CAL1FDMt.js",
6
+ "_vendor-CtqfhlbC.js",
7
+ "_vendor-arizeai-C_3SBz56.js",
8
+ "_pages-UeWaKXNs.js",
9
9
  "_vendor-three-DwGkEfCM.js",
10
- "_vendor-codemirror-DVE2_WBr.js"
10
+ "_vendor-codemirror-wfdk9cjp.js"
11
11
  ]
12
12
  },
13
- "_pages-CAL1FDMt.js": {
14
- "file": "assets/pages-CAL1FDMt.js",
13
+ "_pages-UeWaKXNs.js": {
14
+ "file": "assets/pages-UeWaKXNs.js",
15
15
  "name": "pages",
16
16
  "imports": [
17
- "_vendor-6IcPAw_j.js",
18
- "_vendor-arizeai-DRZuoyuF.js",
19
- "_components-hX0LgYz3.js",
20
- "_vendor-recharts-DwrexFA4.js",
21
- "_vendor-codemirror-DVE2_WBr.js"
17
+ "_vendor-CtqfhlbC.js",
18
+ "_vendor-arizeai-C_3SBz56.js",
19
+ "_components-Csu8UKOs.js",
20
+ "_vendor-recharts-BiVnSv90.js",
21
+ "_vendor-codemirror-wfdk9cjp.js"
22
22
  ]
23
23
  },
24
24
  "_vendor-!~{003}~.js": {
25
25
  "file": "assets/vendor-DxkFTwjz.css",
26
26
  "src": "_vendor-!~{003}~.js"
27
27
  },
28
- "_vendor-6IcPAw_j.js": {
29
- "file": "assets/vendor-6IcPAw_j.js",
28
+ "_vendor-CtqfhlbC.js": {
29
+ "file": "assets/vendor-CtqfhlbC.js",
30
30
  "name": "vendor",
31
31
  "imports": [
32
32
  "_vendor-three-DwGkEfCM.js"
@@ -35,25 +35,25 @@
35
35
  "assets/vendor-DxkFTwjz.css"
36
36
  ]
37
37
  },
38
- "_vendor-arizeai-DRZuoyuF.js": {
39
- "file": "assets/vendor-arizeai-DRZuoyuF.js",
38
+ "_vendor-arizeai-C_3SBz56.js": {
39
+ "file": "assets/vendor-arizeai-C_3SBz56.js",
40
40
  "name": "vendor-arizeai",
41
41
  "imports": [
42
- "_vendor-6IcPAw_j.js"
42
+ "_vendor-CtqfhlbC.js"
43
43
  ]
44
44
  },
45
- "_vendor-codemirror-DVE2_WBr.js": {
46
- "file": "assets/vendor-codemirror-DVE2_WBr.js",
45
+ "_vendor-codemirror-wfdk9cjp.js": {
46
+ "file": "assets/vendor-codemirror-wfdk9cjp.js",
47
47
  "name": "vendor-codemirror",
48
48
  "imports": [
49
- "_vendor-6IcPAw_j.js"
49
+ "_vendor-CtqfhlbC.js"
50
50
  ]
51
51
  },
52
- "_vendor-recharts-DwrexFA4.js": {
53
- "file": "assets/vendor-recharts-DwrexFA4.js",
52
+ "_vendor-recharts-BiVnSv90.js": {
53
+ "file": "assets/vendor-recharts-BiVnSv90.js",
54
54
  "name": "vendor-recharts",
55
55
  "imports": [
56
- "_vendor-6IcPAw_j.js"
56
+ "_vendor-CtqfhlbC.js"
57
57
  ]
58
58
  },
59
59
  "_vendor-three-DwGkEfCM.js": {
@@ -61,18 +61,18 @@
61
61
  "name": "vendor-three"
62
62
  },
63
63
  "index.tsx": {
64
- "file": "assets/index-DCzakdJq.js",
64
+ "file": "assets/index-Bk5C9EA7.js",
65
65
  "name": "index",
66
66
  "src": "index.tsx",
67
67
  "isEntry": true,
68
68
  "imports": [
69
- "_vendor-6IcPAw_j.js",
70
- "_vendor-arizeai-DRZuoyuF.js",
71
- "_pages-CAL1FDMt.js",
72
- "_components-hX0LgYz3.js",
69
+ "_vendor-CtqfhlbC.js",
70
+ "_vendor-arizeai-C_3SBz56.js",
71
+ "_pages-UeWaKXNs.js",
72
+ "_components-Csu8UKOs.js",
73
73
  "_vendor-three-DwGkEfCM.js",
74
- "_vendor-recharts-DwrexFA4.js",
75
- "_vendor-codemirror-DVE2_WBr.js"
74
+ "_vendor-recharts-BiVnSv90.js",
75
+ "_vendor-codemirror-wfdk9cjp.js"
76
76
  ]
77
77
  }
78
78
  }