arize-phoenix 12.4.0__py3-none-any.whl → 12.6.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 (65) hide show
  1. {arize_phoenix-12.4.0.dist-info → arize_phoenix-12.6.0.dist-info}/METADATA +1 -1
  2. {arize_phoenix-12.4.0.dist-info → arize_phoenix-12.6.0.dist-info}/RECORD +62 -60
  3. phoenix/auth.py +8 -2
  4. phoenix/db/models.py +3 -3
  5. phoenix/server/api/auth.py +9 -0
  6. phoenix/server/api/context.py +2 -0
  7. phoenix/server/api/dataloaders/__init__.py +2 -0
  8. phoenix/server/api/dataloaders/dataset_dataset_splits.py +52 -0
  9. phoenix/server/api/input_types/ExperimentRunSort.py +237 -0
  10. phoenix/server/api/input_types/ProjectSessionSort.py +158 -1
  11. phoenix/server/api/input_types/SpanSort.py +2 -1
  12. phoenix/server/api/input_types/UserRoleInput.py +1 -0
  13. phoenix/server/api/mutations/annotation_config_mutations.py +6 -6
  14. phoenix/server/api/mutations/api_key_mutations.py +13 -5
  15. phoenix/server/api/mutations/chat_mutations.py +3 -3
  16. phoenix/server/api/mutations/dataset_label_mutations.py +6 -6
  17. phoenix/server/api/mutations/dataset_mutations.py +8 -8
  18. phoenix/server/api/mutations/dataset_split_mutations.py +7 -7
  19. phoenix/server/api/mutations/experiment_mutations.py +2 -2
  20. phoenix/server/api/mutations/export_events_mutations.py +3 -3
  21. phoenix/server/api/mutations/model_mutations.py +4 -4
  22. phoenix/server/api/mutations/project_mutations.py +4 -4
  23. phoenix/server/api/mutations/project_session_annotations_mutations.py +4 -4
  24. phoenix/server/api/mutations/project_trace_retention_policy_mutations.py +8 -4
  25. phoenix/server/api/mutations/prompt_label_mutations.py +7 -7
  26. phoenix/server/api/mutations/prompt_mutations.py +7 -7
  27. phoenix/server/api/mutations/prompt_version_tag_mutations.py +3 -3
  28. phoenix/server/api/mutations/span_annotations_mutations.py +5 -5
  29. phoenix/server/api/mutations/trace_annotations_mutations.py +4 -4
  30. phoenix/server/api/mutations/trace_mutations.py +3 -3
  31. phoenix/server/api/mutations/user_mutations.py +8 -5
  32. phoenix/server/api/routers/auth.py +2 -2
  33. phoenix/server/api/routers/v1/__init__.py +16 -1
  34. phoenix/server/api/routers/v1/annotation_configs.py +7 -1
  35. phoenix/server/api/routers/v1/datasets.py +48 -8
  36. phoenix/server/api/routers/v1/experiment_runs.py +7 -1
  37. phoenix/server/api/routers/v1/experiments.py +41 -5
  38. phoenix/server/api/routers/v1/projects.py +3 -31
  39. phoenix/server/api/routers/v1/users.py +0 -7
  40. phoenix/server/api/subscriptions.py +3 -3
  41. phoenix/server/api/types/Dataset.py +95 -6
  42. phoenix/server/api/types/Experiment.py +60 -25
  43. phoenix/server/api/types/Project.py +24 -68
  44. phoenix/server/app.py +2 -0
  45. phoenix/server/authorization.py +3 -1
  46. phoenix/server/bearer_auth.py +9 -0
  47. phoenix/server/jwt_store.py +8 -6
  48. phoenix/server/static/.vite/manifest.json +44 -44
  49. phoenix/server/static/assets/{components-BvsExS75.js → components-CboqzKQ9.js} +520 -397
  50. phoenix/server/static/assets/{index-iq8WDxat.js → index-CYYGI5-x.js} +2 -2
  51. phoenix/server/static/assets/{pages-Ckg4SLQ9.js → pages-DdlUeKi2.js} +616 -604
  52. phoenix/server/static/assets/vendor-CQ4tN9P7.js +918 -0
  53. phoenix/server/static/assets/vendor-arizeai-Cb1ncvYH.js +106 -0
  54. phoenix/server/static/assets/{vendor-codemirror-1bq_t1Ec.js → vendor-codemirror-CckmKopH.js} +3 -3
  55. phoenix/server/static/assets/{vendor-recharts-DQ4xfrf4.js → vendor-recharts-BC1ysIKu.js} +1 -1
  56. phoenix/server/static/assets/{vendor-shiki-GGmcIQxA.js → vendor-shiki-B45T-YxN.js} +1 -1
  57. phoenix/server/static/assets/vendor-three-BtCyLs1w.js +3840 -0
  58. phoenix/version.py +1 -1
  59. phoenix/server/static/assets/vendor-D2eEI-6h.js +0 -914
  60. phoenix/server/static/assets/vendor-arizeai-kfOei7nf.js +0 -156
  61. phoenix/server/static/assets/vendor-three-BLWp5bic.js +0 -2998
  62. {arize_phoenix-12.4.0.dist-info → arize_phoenix-12.6.0.dist-info}/WHEEL +0 -0
  63. {arize_phoenix-12.4.0.dist-info → arize_phoenix-12.6.0.dist-info}/entry_points.txt +0 -0
  64. {arize_phoenix-12.4.0.dist-info → arize_phoenix-12.6.0.dist-info}/licenses/IP_NOTICE +0 -0
  65. {arize_phoenix-12.4.0.dist-info → arize_phoenix-12.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -7,7 +7,6 @@ from aioitertools.itertools import groupby, islice
7
7
  from openinference.semconv.trace import SpanAttributes
8
8
  from sqlalchemy import and_, case, desc, distinct, exists, func, or_, select
9
9
  from sqlalchemy.dialects import postgresql, sqlite
10
- from sqlalchemy.sql.elements import ColumnElement
11
10
  from sqlalchemy.sql.expression import tuple_
12
11
  from sqlalchemy.sql.functions import percentile_cont
13
12
  from strawberry import ID, UNSET, Private, lazy
@@ -21,8 +20,8 @@ from phoenix.db.helpers import SupportedSQLDialect, date_trunc
21
20
  from phoenix.server.api.context import Context
22
21
  from phoenix.server.api.exceptions import BadRequest
23
22
  from phoenix.server.api.input_types.ProjectSessionSort import (
24
- ProjectSessionColumn,
25
23
  ProjectSessionSort,
24
+ ProjectSessionSortConfig,
26
25
  )
27
26
  from phoenix.server.api.input_types.SpanSort import SpanColumn, SpanSort, SpanSortConfig
28
27
  from phoenix.server.api.input_types.TimeBinConfig import TimeBinConfig, TimeBinScale
@@ -459,74 +458,31 @@ class Project(Node):
459
458
  end_time=time_range.end if time_range else None,
460
459
  )
461
460
  stmt = stmt.where(table.id.in_(filtered_session_rowids))
461
+ sort_config: Optional[ProjectSessionSortConfig] = None
462
+ cursor_rowid_column: Any = table.id
462
463
  if sort:
463
- key: ColumnElement[Any]
464
- if sort.col is ProjectSessionColumn.startTime:
465
- key = table.start_time.label("key")
466
- elif sort.col is ProjectSessionColumn.endTime:
467
- key = table.end_time.label("key")
468
- elif (
469
- sort.col is ProjectSessionColumn.tokenCountTotal
470
- or sort.col is ProjectSessionColumn.numTraces
471
- ):
472
- if sort.col is ProjectSessionColumn.tokenCountTotal:
473
- sort_subq = (
474
- select(
475
- models.Trace.project_session_rowid.label("id"),
476
- func.sum(models.Span.cumulative_llm_token_count_total).label("key"),
477
- )
478
- .join_from(models.Trace, models.Span)
479
- .where(models.Span.parent_id.is_(None))
480
- .group_by(models.Trace.project_session_rowid)
481
- ).subquery()
482
- elif sort.col is ProjectSessionColumn.numTraces:
483
- sort_subq = (
484
- select(
485
- models.Trace.project_session_rowid.label("id"),
486
- func.count(models.Trace.id).label("key"),
487
- ).group_by(models.Trace.project_session_rowid)
488
- ).subquery()
464
+ sort_config = sort.update_orm_expr(stmt)
465
+ stmt = sort_config.stmt
466
+ if sort_config.dir is SortDir.desc:
467
+ cursor_rowid_column = desc(cursor_rowid_column)
468
+ if after:
469
+ cursor = Cursor.from_string(after)
470
+ if sort_config and cursor.sort_column:
471
+ sort_column = cursor.sort_column
472
+ compare = operator.lt if sort_config.dir is SortDir.desc else operator.gt
473
+ if sort_column.type is CursorSortColumnDataType.NULL:
474
+ stmt = stmt.where(sort_config.orm_expression.is_(None))
475
+ stmt = stmt.where(compare(table.id, cursor.rowid))
489
476
  else:
490
- assert_never(sort.col)
491
- key = sort_subq.c.key
492
- stmt = stmt.join(sort_subq, table.id == sort_subq.c.id)
493
- elif sort.col is ProjectSessionColumn.costTotal:
494
- sort_subq = (
495
- select(
496
- models.Trace.project_session_rowid.label("id"),
497
- func.sum(models.SpanCost.total_cost).label("key"),
498
- )
499
- .join_from(
500
- models.Trace,
501
- models.SpanCost,
502
- models.Trace.id == models.SpanCost.trace_rowid,
477
+ stmt = stmt.where(
478
+ compare(
479
+ tuple_(sort_config.orm_expression, table.id),
480
+ (sort_column.value, cursor.rowid),
481
+ )
503
482
  )
504
- .group_by(models.Trace.project_session_rowid)
505
- ).subquery()
506
- key = sort_subq.c.key
507
- stmt = stmt.join(sort_subq, table.id == sort_subq.c.id)
508
- else:
509
- assert_never(sort.col)
510
- stmt = stmt.add_columns(key)
511
- if sort.dir is SortDir.asc:
512
- stmt = stmt.order_by(key.asc(), table.id.asc())
513
483
  else:
514
- stmt = stmt.order_by(key.desc(), table.id.desc())
515
- if after:
516
- cursor = Cursor.from_string(after)
517
- assert cursor.sort_column is not None
518
- compare = operator.lt if sort.dir is SortDir.desc else operator.gt
519
- stmt = stmt.where(
520
- compare(
521
- tuple_(key, table.id),
522
- (cursor.sort_column.value, cursor.rowid),
523
- )
524
- )
525
- else:
526
- stmt = stmt.order_by(table.id.desc())
527
- if after:
528
- cursor = Cursor.from_string(after)
529
484
  stmt = stmt.where(table.id < cursor.rowid)
485
+ stmt = stmt.order_by(cursor_rowid_column)
530
486
  if first:
531
487
  stmt = stmt.limit(
532
488
  first + 1 # over-fetch by one to determine whether there's a next page
@@ -537,10 +493,10 @@ class Project(Node):
537
493
  async for record in islice(records, first):
538
494
  project_session = record[0]
539
495
  cursor = Cursor(rowid=project_session.id)
540
- if sort:
496
+ if sort_config:
541
497
  assert len(record) > 1
542
498
  cursor.sort_column = CursorSortColumn(
543
- type=sort.col.data_type,
499
+ type=sort_config.column_data_type,
544
500
  value=record[1],
545
501
  )
546
502
  cursors_and_nodes.append((cursor, to_gql_project_session(project_session)))
@@ -724,7 +680,7 @@ class Project(Node):
724
680
  stmt = span_filter(select(models.Span))
725
681
  dialect = info.context.db.dialect
726
682
  if dialect is SupportedSQLDialect.POSTGRESQL:
727
- str(stmt.compile(dialect=sqlite.dialect())) # type: ignore[no-untyped-call]
683
+ str(stmt.compile(dialect=sqlite.dialect()))
728
684
  elif dialect is SupportedSQLDialect.SQLITE:
729
685
  str(stmt.compile(dialect=postgresql.dialect())) # type: ignore[no-untyped-call]
730
686
  else:
phoenix/server/app.py CHANGED
@@ -88,6 +88,7 @@ from phoenix.server.api.dataloaders import (
88
88
  AverageExperimentRepeatedRunGroupLatencyDataLoader,
89
89
  AverageExperimentRunLatencyDataLoader,
90
90
  CacheForDataLoaders,
91
+ DatasetDatasetSplitsDataLoader,
91
92
  DatasetExampleRevisionsDataLoader,
92
93
  DatasetExamplesAndVersionsByExperimentRunDataLoader,
93
94
  DatasetExampleSpansDataLoader,
@@ -709,6 +710,7 @@ def create_graphql_router(
709
710
  db
710
711
  ),
711
712
  average_experiment_run_latency=AverageExperimentRunLatencyDataLoader(db),
713
+ dataset_dataset_splits=DatasetDatasetSplitsDataLoader(db),
712
714
  dataset_example_revisions=DatasetExampleRevisionsDataLoader(db),
713
715
  dataset_example_spans=DatasetExampleSpansDataLoader(db),
714
716
  dataset_examples_and_versions_by_experiment_run=DatasetExamplesAndVersionsByExperimentRunDataLoader(
@@ -42,8 +42,10 @@ def require_admin(request: Request) -> None:
42
42
  Behavior:
43
43
  - Allows access if the authenticated user is an admin or a system user.
44
44
  - Raises HTTP 403 Forbidden if the user is not authorized.
45
- - Expects authentication to be enabled and request.user to be set by the authentication.
45
+ - Allows access if authentication is not enabled.
46
46
  """
47
+ if not request.app.state.authentication_enabled:
48
+ return
47
49
  user = getattr(request, "user", None)
48
50
  # System users have all privileges
49
51
  if not (isinstance(user, PhoenixUser) and user.is_admin):
@@ -75,11 +75,18 @@ class PhoenixUser(BaseUser):
75
75
  self._is_admin = (
76
76
  claims.status is ClaimSetStatus.VALID and claims.attributes.user_role == "ADMIN"
77
77
  )
78
+ self._is_viewer = (
79
+ claims.status is ClaimSetStatus.VALID and claims.attributes.user_role == "VIEWER"
80
+ )
78
81
 
79
82
  @cached_property
80
83
  def is_admin(self) -> bool:
81
84
  return self._is_admin
82
85
 
86
+ @cached_property
87
+ def is_viewer(self) -> bool:
88
+ return self._is_viewer
89
+
83
90
  @cached_property
84
91
  def identity(self) -> UserId:
85
92
  return self._user_id
@@ -92,6 +99,8 @@ class PhoenixUser(BaseUser):
92
99
  class PhoenixSystemUser(PhoenixUser):
93
100
  def __init__(self, user_id: UserId) -> None:
94
101
  self._user_id = user_id
102
+ self._is_admin = True # System users have admin privileges
103
+ self._is_viewer = False # System users are not viewers
95
104
 
96
105
  @property
97
106
  def is_admin(self) -> bool:
@@ -164,7 +164,7 @@ class JwtStore:
164
164
  for token_id in token_ids:
165
165
  if isinstance(token_id, PasswordResetTokenId):
166
166
  password_reset_token_ids.append(token_id)
167
- if isinstance(token_id, AccessTokenId):
167
+ elif isinstance(token_id, AccessTokenId):
168
168
  access_token_ids.append(token_id)
169
169
  elif isinstance(token_id, RefreshTokenId):
170
170
  refresh_token_ids.append(token_id)
@@ -182,10 +182,10 @@ class JwtStore:
182
182
  await gather(*coroutines)
183
183
 
184
184
  async def log_out(self, user_id: UserId) -> None:
185
- for cls in (AccessTokenId, RefreshTokenId):
186
- table = cls.table
187
- stmt = delete(table).where(table.user_id == int(user_id)).returning(table.id)
188
- async with self._db() as session:
185
+ async with self._db() as session:
186
+ for cls in (AccessTokenId, RefreshTokenId):
187
+ table = cls.table
188
+ stmt = delete(table).where(table.user_id == int(user_id)).returning(table.id)
189
189
  async for id_ in await session.stream_scalars(stmt):
190
190
  await self._evict(cls(id_))
191
191
 
@@ -314,7 +314,9 @@ class _Store(DaemonTask, Generic[_ClaimSetT, _TokenT, _TokenIdT, _RecordT], ABC)
314
314
 
315
315
  async def _delete_expired_tokens(self, session: Any) -> None:
316
316
  now = datetime.now(timezone.utc)
317
- await session.execute(delete(self._table).where(self._table.expires_at < now))
317
+ # Per JWT RFC 7519 Section 4.1.4, tokens expire "on or after" the expiration time.
318
+ # Use <= to include tokens expiring at exactly this moment.
319
+ await session.execute(delete(self._table).where(self._table.expires_at <= now))
318
320
 
319
321
  async def _run(self) -> None:
320
322
  while self._running:
@@ -1,93 +1,93 @@
1
1
  {
2
- "_components-BvsExS75.js": {
3
- "file": "assets/components-BvsExS75.js",
2
+ "_components-CboqzKQ9.js": {
3
+ "file": "assets/components-CboqzKQ9.js",
4
4
  "name": "components",
5
5
  "imports": [
6
- "_vendor-D2eEI-6h.js",
7
- "_pages-Ckg4SLQ9.js",
8
- "_vendor-arizeai-kfOei7nf.js",
9
- "_vendor-codemirror-1bq_t1Ec.js",
10
- "_vendor-three-BLWp5bic.js"
6
+ "_vendor-CQ4tN9P7.js",
7
+ "_pages-DdlUeKi2.js",
8
+ "_vendor-arizeai-Cb1ncvYH.js",
9
+ "_vendor-codemirror-CckmKopH.js",
10
+ "_vendor-three-BtCyLs1w.js"
11
11
  ]
12
12
  },
13
- "_pages-Ckg4SLQ9.js": {
14
- "file": "assets/pages-Ckg4SLQ9.js",
13
+ "_pages-DdlUeKi2.js": {
14
+ "file": "assets/pages-DdlUeKi2.js",
15
15
  "name": "pages",
16
16
  "imports": [
17
- "_vendor-D2eEI-6h.js",
18
- "_components-BvsExS75.js",
19
- "_vendor-arizeai-kfOei7nf.js",
20
- "_vendor-codemirror-1bq_t1Ec.js",
21
- "_vendor-recharts-DQ4xfrf4.js"
17
+ "_vendor-CQ4tN9P7.js",
18
+ "_components-CboqzKQ9.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-D2eEI-6h.js": {
29
- "file": "assets/vendor-D2eEI-6h.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-kfOei7nf.js": {
39
- "file": "assets/vendor-arizeai-kfOei7nf.js",
38
+ "_vendor-arizeai-Cb1ncvYH.js": {
39
+ "file": "assets/vendor-arizeai-Cb1ncvYH.js",
40
40
  "name": "vendor-arizeai",
41
41
  "imports": [
42
- "_vendor-D2eEI-6h.js"
42
+ "_vendor-CQ4tN9P7.js"
43
43
  ]
44
44
  },
45
- "_vendor-codemirror-1bq_t1Ec.js": {
46
- "file": "assets/vendor-codemirror-1bq_t1Ec.js",
45
+ "_vendor-codemirror-CckmKopH.js": {
46
+ "file": "assets/vendor-codemirror-CckmKopH.js",
47
47
  "name": "vendor-codemirror",
48
48
  "imports": [
49
- "_vendor-D2eEI-6h.js",
50
- "_vendor-shiki-GGmcIQxA.js"
49
+ "_vendor-CQ4tN9P7.js",
50
+ "_vendor-shiki-B45T-YxN.js"
51
51
  ],
52
52
  "dynamicImports": [
53
- "_vendor-shiki-GGmcIQxA.js",
54
- "_vendor-shiki-GGmcIQxA.js",
55
- "_vendor-shiki-GGmcIQxA.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-DQ4xfrf4.js": {
59
- "file": "assets/vendor-recharts-DQ4xfrf4.js",
58
+ "_vendor-recharts-BC1ysIKu.js": {
59
+ "file": "assets/vendor-recharts-BC1ysIKu.js",
60
60
  "name": "vendor-recharts",
61
61
  "imports": [
62
- "_vendor-D2eEI-6h.js"
62
+ "_vendor-CQ4tN9P7.js"
63
63
  ]
64
64
  },
65
- "_vendor-shiki-GGmcIQxA.js": {
66
- "file": "assets/vendor-shiki-GGmcIQxA.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-D2eEI-6h.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-iq8WDxat.js",
78
+ "file": "assets/index-CYYGI5-x.js",
79
79
  "name": "index",
80
80
  "src": "index.tsx",
81
81
  "isEntry": true,
82
82
  "imports": [
83
- "_vendor-D2eEI-6h.js",
84
- "_vendor-arizeai-kfOei7nf.js",
85
- "_pages-Ckg4SLQ9.js",
86
- "_components-BvsExS75.js",
87
- "_vendor-three-BLWp5bic.js",
88
- "_vendor-codemirror-1bq_t1Ec.js",
89
- "_vendor-shiki-GGmcIQxA.js",
90
- "_vendor-recharts-DQ4xfrf4.js"
83
+ "_vendor-CQ4tN9P7.js",
84
+ "_vendor-arizeai-Cb1ncvYH.js",
85
+ "_pages-DdlUeKi2.js",
86
+ "_components-CboqzKQ9.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
  }