arize-phoenix 11.23.1__py3-none-any.whl → 12.28.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. {arize_phoenix-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/METADATA +61 -36
  2. {arize_phoenix-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/RECORD +212 -162
  3. {arize_phoenix-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/WHEEL +1 -1
  4. {arize_phoenix-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/licenses/IP_NOTICE +1 -1
  5. phoenix/__generated__/__init__.py +0 -0
  6. phoenix/__generated__/classification_evaluator_configs/__init__.py +20 -0
  7. phoenix/__generated__/classification_evaluator_configs/_document_relevance_classification_evaluator_config.py +17 -0
  8. phoenix/__generated__/classification_evaluator_configs/_hallucination_classification_evaluator_config.py +17 -0
  9. phoenix/__generated__/classification_evaluator_configs/_models.py +18 -0
  10. phoenix/__generated__/classification_evaluator_configs/_tool_selection_classification_evaluator_config.py +17 -0
  11. phoenix/__init__.py +2 -1
  12. phoenix/auth.py +27 -2
  13. phoenix/config.py +1594 -81
  14. phoenix/db/README.md +546 -28
  15. phoenix/db/bulk_inserter.py +119 -116
  16. phoenix/db/engines.py +140 -33
  17. phoenix/db/facilitator.py +22 -1
  18. phoenix/db/helpers.py +818 -65
  19. phoenix/db/iam_auth.py +64 -0
  20. phoenix/db/insertion/dataset.py +133 -1
  21. phoenix/db/insertion/document_annotation.py +9 -6
  22. phoenix/db/insertion/evaluation.py +2 -3
  23. phoenix/db/insertion/helpers.py +2 -2
  24. phoenix/db/insertion/session_annotation.py +176 -0
  25. phoenix/db/insertion/span_annotation.py +3 -4
  26. phoenix/db/insertion/trace_annotation.py +3 -4
  27. phoenix/db/insertion/types.py +41 -18
  28. phoenix/db/migrations/versions/01a8342c9cdf_add_user_id_on_datasets.py +40 -0
  29. phoenix/db/migrations/versions/0df286449799_add_session_annotations_table.py +105 -0
  30. phoenix/db/migrations/versions/272b66ff50f8_drop_single_indices.py +119 -0
  31. phoenix/db/migrations/versions/58228d933c91_dataset_labels.py +67 -0
  32. phoenix/db/migrations/versions/699f655af132_experiment_tags.py +57 -0
  33. phoenix/db/migrations/versions/735d3d93c33e_add_composite_indices.py +41 -0
  34. phoenix/db/migrations/versions/ab513d89518b_add_user_id_on_dataset_versions.py +40 -0
  35. phoenix/db/migrations/versions/d0690a79ea51_users_on_experiments.py +40 -0
  36. phoenix/db/migrations/versions/deb2c81c0bb2_dataset_splits.py +139 -0
  37. phoenix/db/migrations/versions/e76cbd66ffc3_add_experiments_dataset_examples.py +87 -0
  38. phoenix/db/models.py +364 -56
  39. phoenix/db/pg_config.py +10 -0
  40. phoenix/db/types/trace_retention.py +7 -6
  41. phoenix/experiments/functions.py +69 -19
  42. phoenix/inferences/inferences.py +1 -2
  43. phoenix/server/api/auth.py +9 -0
  44. phoenix/server/api/auth_messages.py +46 -0
  45. phoenix/server/api/context.py +60 -0
  46. phoenix/server/api/dataloaders/__init__.py +36 -0
  47. phoenix/server/api/dataloaders/annotation_summaries.py +60 -8
  48. phoenix/server/api/dataloaders/average_experiment_repeated_run_group_latency.py +50 -0
  49. phoenix/server/api/dataloaders/average_experiment_run_latency.py +17 -24
  50. phoenix/server/api/dataloaders/cache/two_tier_cache.py +1 -2
  51. phoenix/server/api/dataloaders/dataset_dataset_splits.py +52 -0
  52. phoenix/server/api/dataloaders/dataset_example_revisions.py +0 -1
  53. phoenix/server/api/dataloaders/dataset_example_splits.py +40 -0
  54. phoenix/server/api/dataloaders/dataset_examples_and_versions_by_experiment_run.py +47 -0
  55. phoenix/server/api/dataloaders/dataset_labels.py +36 -0
  56. phoenix/server/api/dataloaders/document_evaluation_summaries.py +2 -2
  57. phoenix/server/api/dataloaders/document_evaluations.py +6 -9
  58. phoenix/server/api/dataloaders/experiment_annotation_summaries.py +88 -34
  59. phoenix/server/api/dataloaders/experiment_dataset_splits.py +43 -0
  60. phoenix/server/api/dataloaders/experiment_error_rates.py +21 -28
  61. phoenix/server/api/dataloaders/experiment_repeated_run_group_annotation_summaries.py +77 -0
  62. phoenix/server/api/dataloaders/experiment_repeated_run_groups.py +57 -0
  63. phoenix/server/api/dataloaders/experiment_runs_by_experiment_and_example.py +44 -0
  64. phoenix/server/api/dataloaders/latency_ms_quantile.py +40 -8
  65. phoenix/server/api/dataloaders/record_counts.py +37 -10
  66. phoenix/server/api/dataloaders/session_annotations_by_session.py +29 -0
  67. phoenix/server/api/dataloaders/span_cost_summary_by_experiment_repeated_run_group.py +64 -0
  68. phoenix/server/api/dataloaders/span_cost_summary_by_project.py +28 -14
  69. phoenix/server/api/dataloaders/span_costs.py +3 -9
  70. phoenix/server/api/dataloaders/table_fields.py +2 -2
  71. phoenix/server/api/dataloaders/token_prices_by_model.py +30 -0
  72. phoenix/server/api/dataloaders/trace_annotations_by_trace.py +27 -0
  73. phoenix/server/api/exceptions.py +5 -1
  74. phoenix/server/api/helpers/playground_clients.py +263 -83
  75. phoenix/server/api/helpers/playground_spans.py +2 -1
  76. phoenix/server/api/helpers/playground_users.py +26 -0
  77. phoenix/server/api/helpers/prompts/conversions/google.py +103 -0
  78. phoenix/server/api/helpers/prompts/models.py +61 -19
  79. phoenix/server/api/input_types/{SpanAnnotationFilter.py → AnnotationFilter.py} +22 -14
  80. phoenix/server/api/input_types/ChatCompletionInput.py +3 -0
  81. phoenix/server/api/input_types/CreateProjectSessionAnnotationInput.py +37 -0
  82. phoenix/server/api/input_types/DatasetFilter.py +5 -2
  83. phoenix/server/api/input_types/ExperimentRunSort.py +237 -0
  84. phoenix/server/api/input_types/GenerativeModelInput.py +3 -0
  85. phoenix/server/api/input_types/ProjectSessionSort.py +158 -1
  86. phoenix/server/api/input_types/PromptVersionInput.py +47 -1
  87. phoenix/server/api/input_types/SpanSort.py +3 -2
  88. phoenix/server/api/input_types/UpdateAnnotationInput.py +34 -0
  89. phoenix/server/api/input_types/UserRoleInput.py +1 -0
  90. phoenix/server/api/mutations/__init__.py +8 -0
  91. phoenix/server/api/mutations/annotation_config_mutations.py +8 -8
  92. phoenix/server/api/mutations/api_key_mutations.py +15 -20
  93. phoenix/server/api/mutations/chat_mutations.py +106 -37
  94. phoenix/server/api/mutations/dataset_label_mutations.py +243 -0
  95. phoenix/server/api/mutations/dataset_mutations.py +21 -16
  96. phoenix/server/api/mutations/dataset_split_mutations.py +351 -0
  97. phoenix/server/api/mutations/experiment_mutations.py +2 -2
  98. phoenix/server/api/mutations/export_events_mutations.py +3 -3
  99. phoenix/server/api/mutations/model_mutations.py +11 -9
  100. phoenix/server/api/mutations/project_mutations.py +4 -4
  101. phoenix/server/api/mutations/project_session_annotations_mutations.py +158 -0
  102. phoenix/server/api/mutations/project_trace_retention_policy_mutations.py +8 -4
  103. phoenix/server/api/mutations/prompt_label_mutations.py +74 -65
  104. phoenix/server/api/mutations/prompt_mutations.py +65 -129
  105. phoenix/server/api/mutations/prompt_version_tag_mutations.py +11 -8
  106. phoenix/server/api/mutations/span_annotations_mutations.py +15 -10
  107. phoenix/server/api/mutations/trace_annotations_mutations.py +13 -8
  108. phoenix/server/api/mutations/trace_mutations.py +3 -3
  109. phoenix/server/api/mutations/user_mutations.py +55 -26
  110. phoenix/server/api/queries.py +501 -617
  111. phoenix/server/api/routers/__init__.py +2 -2
  112. phoenix/server/api/routers/auth.py +141 -87
  113. phoenix/server/api/routers/ldap.py +229 -0
  114. phoenix/server/api/routers/oauth2.py +349 -101
  115. phoenix/server/api/routers/v1/__init__.py +22 -4
  116. phoenix/server/api/routers/v1/annotation_configs.py +19 -30
  117. phoenix/server/api/routers/v1/annotations.py +455 -13
  118. phoenix/server/api/routers/v1/datasets.py +355 -68
  119. phoenix/server/api/routers/v1/documents.py +142 -0
  120. phoenix/server/api/routers/v1/evaluations.py +20 -28
  121. phoenix/server/api/routers/v1/experiment_evaluations.py +16 -6
  122. phoenix/server/api/routers/v1/experiment_runs.py +335 -59
  123. phoenix/server/api/routers/v1/experiments.py +475 -47
  124. phoenix/server/api/routers/v1/projects.py +16 -50
  125. phoenix/server/api/routers/v1/prompts.py +50 -39
  126. phoenix/server/api/routers/v1/sessions.py +108 -0
  127. phoenix/server/api/routers/v1/spans.py +156 -96
  128. phoenix/server/api/routers/v1/traces.py +51 -77
  129. phoenix/server/api/routers/v1/users.py +64 -24
  130. phoenix/server/api/routers/v1/utils.py +3 -7
  131. phoenix/server/api/subscriptions.py +257 -93
  132. phoenix/server/api/types/Annotation.py +90 -23
  133. phoenix/server/api/types/ApiKey.py +13 -17
  134. phoenix/server/api/types/AuthMethod.py +1 -0
  135. phoenix/server/api/types/ChatCompletionSubscriptionPayload.py +1 -0
  136. phoenix/server/api/types/Dataset.py +199 -72
  137. phoenix/server/api/types/DatasetExample.py +88 -18
  138. phoenix/server/api/types/DatasetExperimentAnnotationSummary.py +10 -0
  139. phoenix/server/api/types/DatasetLabel.py +57 -0
  140. phoenix/server/api/types/DatasetSplit.py +98 -0
  141. phoenix/server/api/types/DatasetVersion.py +49 -4
  142. phoenix/server/api/types/DocumentAnnotation.py +212 -0
  143. phoenix/server/api/types/Experiment.py +215 -68
  144. phoenix/server/api/types/ExperimentComparison.py +3 -9
  145. phoenix/server/api/types/ExperimentRepeatedRunGroup.py +155 -0
  146. phoenix/server/api/types/ExperimentRepeatedRunGroupAnnotationSummary.py +9 -0
  147. phoenix/server/api/types/ExperimentRun.py +120 -70
  148. phoenix/server/api/types/ExperimentRunAnnotation.py +158 -39
  149. phoenix/server/api/types/GenerativeModel.py +95 -42
  150. phoenix/server/api/types/GenerativeProvider.py +1 -1
  151. phoenix/server/api/types/ModelInterface.py +7 -2
  152. phoenix/server/api/types/PlaygroundModel.py +12 -2
  153. phoenix/server/api/types/Project.py +218 -185
  154. phoenix/server/api/types/ProjectSession.py +146 -29
  155. phoenix/server/api/types/ProjectSessionAnnotation.py +187 -0
  156. phoenix/server/api/types/ProjectTraceRetentionPolicy.py +1 -1
  157. phoenix/server/api/types/Prompt.py +119 -39
  158. phoenix/server/api/types/PromptLabel.py +42 -25
  159. phoenix/server/api/types/PromptVersion.py +11 -8
  160. phoenix/server/api/types/PromptVersionTag.py +65 -25
  161. phoenix/server/api/types/Span.py +130 -123
  162. phoenix/server/api/types/SpanAnnotation.py +189 -42
  163. phoenix/server/api/types/SystemApiKey.py +65 -1
  164. phoenix/server/api/types/Trace.py +184 -53
  165. phoenix/server/api/types/TraceAnnotation.py +149 -50
  166. phoenix/server/api/types/User.py +128 -33
  167. phoenix/server/api/types/UserApiKey.py +73 -26
  168. phoenix/server/api/types/node.py +10 -0
  169. phoenix/server/api/types/pagination.py +11 -2
  170. phoenix/server/app.py +154 -36
  171. phoenix/server/authorization.py +5 -4
  172. phoenix/server/bearer_auth.py +13 -5
  173. phoenix/server/cost_tracking/cost_model_lookup.py +42 -14
  174. phoenix/server/cost_tracking/model_cost_manifest.json +1085 -194
  175. phoenix/server/daemons/generative_model_store.py +61 -9
  176. phoenix/server/daemons/span_cost_calculator.py +10 -8
  177. phoenix/server/dml_event.py +13 -0
  178. phoenix/server/email/sender.py +29 -2
  179. phoenix/server/grpc_server.py +9 -9
  180. phoenix/server/jwt_store.py +8 -6
  181. phoenix/server/ldap.py +1449 -0
  182. phoenix/server/main.py +9 -3
  183. phoenix/server/oauth2.py +330 -12
  184. phoenix/server/prometheus.py +43 -6
  185. phoenix/server/rate_limiters.py +4 -9
  186. phoenix/server/retention.py +33 -20
  187. phoenix/server/session_filters.py +49 -0
  188. phoenix/server/static/.vite/manifest.json +51 -53
  189. phoenix/server/static/assets/components-BreFUQQa.js +6702 -0
  190. phoenix/server/static/assets/{index-BPCwGQr8.js → index-CTQoemZv.js} +42 -35
  191. phoenix/server/static/assets/pages-DBE5iYM3.js +9524 -0
  192. phoenix/server/static/assets/vendor-BGzfc4EU.css +1 -0
  193. phoenix/server/static/assets/vendor-DCE4v-Ot.js +920 -0
  194. phoenix/server/static/assets/vendor-codemirror-D5f205eT.js +25 -0
  195. phoenix/server/static/assets/{vendor-recharts-Bw30oz1A.js → vendor-recharts-V9cwpXsm.js} +7 -7
  196. phoenix/server/static/assets/{vendor-shiki-DZajAPeq.js → vendor-shiki-Do--csgv.js} +1 -1
  197. phoenix/server/static/assets/vendor-three-CmB8bl_y.js +3840 -0
  198. phoenix/server/templates/index.html +7 -1
  199. phoenix/server/thread_server.py +1 -2
  200. phoenix/server/utils.py +74 -0
  201. phoenix/session/client.py +55 -1
  202. phoenix/session/data_extractor.py +5 -0
  203. phoenix/session/evaluation.py +8 -4
  204. phoenix/session/session.py +44 -8
  205. phoenix/settings.py +2 -0
  206. phoenix/trace/attributes.py +80 -13
  207. phoenix/trace/dsl/query.py +2 -0
  208. phoenix/trace/projects.py +5 -0
  209. phoenix/utilities/template_formatters.py +1 -1
  210. phoenix/version.py +1 -1
  211. phoenix/server/api/types/Evaluation.py +0 -39
  212. phoenix/server/static/assets/components-D0DWAf0l.js +0 -5650
  213. phoenix/server/static/assets/pages-Creyamao.js +0 -8612
  214. phoenix/server/static/assets/vendor-CU36oj8y.js +0 -905
  215. phoenix/server/static/assets/vendor-CqDb5u4o.css +0 -1
  216. phoenix/server/static/assets/vendor-arizeai-Ctgw0e1G.js +0 -168
  217. phoenix/server/static/assets/vendor-codemirror-Cojjzqb9.js +0 -25
  218. phoenix/server/static/assets/vendor-three-BLWp5bic.js +0 -2998
  219. phoenix/utilities/deprecation.py +0 -31
  220. {arize_phoenix-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/entry_points.txt +0 -0
  221. {arize_phoenix-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/licenses/LICENSE +0 -0
phoenix/db/models.py CHANGED
@@ -20,6 +20,7 @@ from sqlalchemy import (
20
20
  Integer,
21
21
  MetaData,
22
22
  Null,
23
+ PrimaryKeyConstraint,
23
24
  String,
24
25
  TypeDecorator,
25
26
  UniqueConstraint,
@@ -43,6 +44,7 @@ from sqlalchemy.orm import (
43
44
  )
44
45
  from sqlalchemy.sql import Values, column, compiler, expression, literal, roles, union_all
45
46
  from sqlalchemy.sql.compiler import SQLCompiler
47
+ from sqlalchemy.sql.elements import Case
46
48
  from sqlalchemy.sql.functions import coalesce
47
49
  from typing_extensions import TypeAlias
48
50
 
@@ -154,7 +156,7 @@ def render_values_w_union(
154
156
  return compiler.process(subquery, from_linter=from_linter, **kw)
155
157
 
156
158
 
157
- UserRoleName: TypeAlias = Literal["SYSTEM", "ADMIN", "MEMBER"]
159
+ UserRoleName: TypeAlias = Literal["SYSTEM", "ADMIN", "MEMBER", "VIEWER"]
158
160
  AuthMethod: TypeAlias = Literal["LOCAL", "OAUTH2"]
159
161
 
160
162
 
@@ -188,7 +190,7 @@ class JsonDict(TypeDecorator[dict[str, Any]]):
188
190
  impl = JSON_
189
191
 
190
192
  def process_bind_param(self, value: Optional[dict[str, Any]], _: Dialect) -> dict[str, Any]:
191
- return orjson.loads(orjson.dumps(value)) if isinstance(value, dict) else {}
193
+ return value if isinstance(value, dict) else {}
192
194
 
193
195
  def process_result_value(self, value: Optional[Any], _: Dialect) -> Optional[dict[str, Any]]:
194
196
  return orjson.loads(orjson.dumps(value)) if isinstance(value, dict) and value else value
@@ -200,7 +202,7 @@ class JsonList(TypeDecorator[list[Any]]):
200
202
  impl = JSON_
201
203
 
202
204
  def process_bind_param(self, value: Optional[list[Any]], _: Dialect) -> list[Any]:
203
- return orjson.loads(orjson.dumps(value)) if isinstance(value, list) else []
205
+ return value if isinstance(value, list) else []
204
206
 
205
207
  def process_result_value(self, value: Optional[Any], _: Dialect) -> Optional[list[Any]]:
206
208
  return orjson.loads(orjson.dumps(value)) if isinstance(value, list) and value else value
@@ -442,12 +444,32 @@ class _RegexStr(TypeDecorator[re.Pattern[str]]):
442
444
  return re.compile(value)
443
445
 
444
446
 
447
+ _HEX_COLOR_PATTERN = re.compile(r"^#([0-9a-f]{6})$")
448
+
449
+
450
+ class _HexColor(TypeDecorator[str]):
451
+ # See https://docs.sqlalchemy.org/en/20/core/custom_types.html
452
+ cache_ok = True
453
+ impl = String
454
+
455
+ def process_bind_param(self, value: Optional[str], _: Dialect) -> Optional[str]:
456
+ if value is None:
457
+ return None
458
+ if not _HEX_COLOR_PATTERN.match(value):
459
+ raise ValueError(f"Expected a hex color, got {value}")
460
+ return value
461
+
462
+ def process_result_value(self, value: Optional[str], _: Dialect) -> Optional[str]:
463
+ if value is None:
464
+ return None
465
+ return value
466
+
467
+
445
468
  class ExperimentRunOutput(TypedDict, total=False):
446
469
  task_output: Any
447
470
 
448
471
 
449
472
  class Base(DeclarativeBase):
450
- id: Mapped[int] = mapped_column(primary_key=True)
451
473
  # Enforce best practices for naming constraints
452
474
  # https://alembic.sqlalchemy.org/en/latest/naming.html#integration-of-naming-conventions-into-operations-autogenerate
453
475
  metadata = MetaData(
@@ -467,7 +489,12 @@ class Base(DeclarativeBase):
467
489
  }
468
490
 
469
491
 
470
- class ProjectTraceRetentionPolicy(Base):
492
+ class HasId(Base):
493
+ __abstract__ = True
494
+ id: Mapped[int] = mapped_column(primary_key=True)
495
+
496
+
497
+ class ProjectTraceRetentionPolicy(HasId):
471
498
  __tablename__ = "project_trace_retention_policies"
472
499
  name: Mapped[str] = mapped_column(String, nullable=False)
473
500
  cron_expression: Mapped[TraceRetentionCronExpression] = mapped_column(
@@ -479,7 +506,7 @@ class ProjectTraceRetentionPolicy(Base):
479
506
  )
480
507
 
481
508
 
482
- class Project(Base):
509
+ class Project(HasId):
483
510
  __tablename__ = "projects"
484
511
  name: Mapped[str]
485
512
  description: Mapped[Optional[str]]
@@ -519,7 +546,7 @@ class Project(Base):
519
546
  )
520
547
 
521
548
 
522
- class ProjectSession(Base):
549
+ class ProjectSession(HasId):
523
550
  __tablename__ = "project_sessions"
524
551
  session_id: Mapped[str] = mapped_column(String, nullable=False, unique=True)
525
552
  project_id: Mapped[int] = mapped_column(
@@ -536,7 +563,7 @@ class ProjectSession(Base):
536
563
  )
537
564
 
538
565
 
539
- class Trace(Base):
566
+ class Trace(HasId):
540
567
  __tablename__ = "traces"
541
568
  project_rowid: Mapped[int] = mapped_column(
542
569
  ForeignKey("projects.id", ondelete="CASCADE"),
@@ -591,13 +618,13 @@ class Trace(Base):
591
618
  )
592
619
 
593
620
 
594
- class Span(Base):
621
+ class Span(HasId):
595
622
  __tablename__ = "spans"
596
623
  trace_rowid: Mapped[int] = mapped_column(
597
624
  ForeignKey("traces.id", ondelete="CASCADE"),
598
625
  index=True,
599
626
  )
600
- span_id: Mapped[str] = mapped_column(index=True)
627
+ span_id: Mapped[str]
601
628
  parent_id: Mapped[Optional[str]] = mapped_column(index=True)
602
629
  name: Mapped[str]
603
630
  span_kind: Mapped[str]
@@ -803,14 +830,27 @@ class NumDocuments(expression.FunctionElement[int]):
803
830
  @compiles(NumDocuments)
804
831
  def _(element: Any, compiler: SQLCompiler, **kw: Any) -> Any:
805
832
  # See https://docs.sqlalchemy.org/en/20/core/compiler.html
806
- array_length = (
807
- func.json_array_length if isinstance(compiler, SQLiteCompiler) else func.jsonb_array_length
808
- )
809
833
  attributes, span_kind = list(element.clauses)
810
834
  retrieval_docs = attributes[RETRIEVAL_DOCUMENTS]
811
- num_retrieval_docs = coalesce(array_length(retrieval_docs), 0)
835
+ num_retrieval_docs: Case[Any] | coalesce[Any]
812
836
  reranker_docs = attributes[RERANKER_OUTPUT_DOCUMENTS]
813
- num_reranker_docs = coalesce(array_length(reranker_docs), 0)
837
+ num_reranker_docs: Case[Any] | coalesce[Any]
838
+ if isinstance(compiler, SQLiteCompiler):
839
+ # SQLite's json_array_length returns 0 for non-array values
840
+ num_retrieval_docs = coalesce(func.json_array_length(retrieval_docs), 0)
841
+ num_reranker_docs = coalesce(func.json_array_length(reranker_docs), 0)
842
+ else:
843
+ # PostgreSQL's jsonb_array_length throws "cannot get array length of a scalar"
844
+ # for non-array values, so check the type first
845
+ num_retrieval_docs = sql.case(
846
+ (func.jsonb_typeof(retrieval_docs) == "array", func.jsonb_array_length(retrieval_docs)),
847
+ else_=0,
848
+ )
849
+ num_reranker_docs = sql.case(
850
+ (func.jsonb_typeof(reranker_docs) == "array", func.jsonb_array_length(reranker_docs)),
851
+ else_=0,
852
+ )
853
+
814
854
  return compiler.process(
815
855
  sql.case(
816
856
  (func.upper(span_kind) == "RERANKER", num_reranker_docs),
@@ -894,15 +934,15 @@ async def init_models(engine: AsyncEngine) -> None:
894
934
  )
895
935
 
896
936
 
897
- class SpanAnnotation(Base):
937
+ class SpanAnnotation(HasId):
898
938
  __tablename__ = "span_annotations"
899
939
  span_rowid: Mapped[int] = mapped_column(
900
940
  ForeignKey("spans.id", ondelete="CASCADE"),
901
941
  index=True,
902
942
  )
903
943
  name: Mapped[str]
904
- label: Mapped[Optional[str]] = mapped_column(String, index=True)
905
- score: Mapped[Optional[float]] = mapped_column(Float, index=True)
944
+ label: Mapped[Optional[str]]
945
+ score: Mapped[Optional[float]]
906
946
  explanation: Mapped[Optional[str]]
907
947
  metadata_: Mapped[dict[str, Any]] = mapped_column("metadata")
908
948
  annotator_kind: Mapped[Literal["LLM", "CODE", "HUMAN"]] = mapped_column(
@@ -934,15 +974,15 @@ class SpanAnnotation(Base):
934
974
  )
935
975
 
936
976
 
937
- class TraceAnnotation(Base):
977
+ class TraceAnnotation(HasId):
938
978
  __tablename__ = "trace_annotations"
939
979
  trace_rowid: Mapped[int] = mapped_column(
940
980
  ForeignKey("traces.id", ondelete="CASCADE"),
941
981
  index=True,
942
982
  )
943
983
  name: Mapped[str]
944
- label: Mapped[Optional[str]] = mapped_column(String, index=True)
945
- score: Mapped[Optional[float]] = mapped_column(Float, index=True)
984
+ label: Mapped[Optional[str]]
985
+ score: Mapped[Optional[float]]
946
986
  explanation: Mapped[Optional[str]]
947
987
  metadata_: Mapped[dict[str, Any]] = mapped_column("metadata")
948
988
  annotator_kind: Mapped[Literal["LLM", "CODE", "HUMAN"]] = mapped_column(
@@ -971,7 +1011,7 @@ class TraceAnnotation(Base):
971
1011
  )
972
1012
 
973
1013
 
974
- class DocumentAnnotation(Base):
1014
+ class DocumentAnnotation(HasId):
975
1015
  __tablename__ = "document_annotations"
976
1016
  span_rowid: Mapped[int] = mapped_column(
977
1017
  ForeignKey("spans.id", ondelete="CASCADE"),
@@ -979,8 +1019,8 @@ class DocumentAnnotation(Base):
979
1019
  )
980
1020
  document_position: Mapped[int]
981
1021
  name: Mapped[str]
982
- label: Mapped[Optional[str]] = mapped_column(String, index=True)
983
- score: Mapped[Optional[float]] = mapped_column(Float, index=True)
1022
+ label: Mapped[Optional[str]]
1023
+ score: Mapped[Optional[float]]
984
1024
  explanation: Mapped[Optional[str]]
985
1025
  metadata_: Mapped[dict[str, Any]] = mapped_column("metadata")
986
1026
  annotator_kind: Mapped[Literal["LLM", "CODE", "HUMAN"]] = mapped_column(
@@ -1012,7 +1052,44 @@ class DocumentAnnotation(Base):
1012
1052
  )
1013
1053
 
1014
1054
 
1015
- class Dataset(Base):
1055
+ class ProjectSessionAnnotation(HasId):
1056
+ __tablename__ = "project_session_annotations"
1057
+ project_session_id: Mapped[int] = mapped_column(
1058
+ ForeignKey("project_sessions.id", ondelete="CASCADE"),
1059
+ index=True,
1060
+ )
1061
+ name: Mapped[str]
1062
+ label: Mapped[Optional[str]]
1063
+ score: Mapped[Optional[float]]
1064
+ explanation: Mapped[Optional[str]]
1065
+ metadata_: Mapped[dict[str, Any]] = mapped_column("metadata")
1066
+ annotator_kind: Mapped[Literal["LLM", "CODE", "HUMAN"]] = mapped_column(
1067
+ CheckConstraint("annotator_kind IN ('LLM', 'CODE', 'HUMAN')", name="valid_annotator_kind"),
1068
+ )
1069
+ created_at: Mapped[datetime] = mapped_column(UtcTimeStamp, server_default=func.now())
1070
+ updated_at: Mapped[datetime] = mapped_column(
1071
+ UtcTimeStamp, server_default=func.now(), onupdate=func.now()
1072
+ )
1073
+ identifier: Mapped[str] = mapped_column(
1074
+ String,
1075
+ server_default="",
1076
+ nullable=False,
1077
+ )
1078
+ source: Mapped[Literal["API", "APP"]] = mapped_column(
1079
+ CheckConstraint("source IN ('API', 'APP')", name="valid_source"),
1080
+ )
1081
+ user_id: Mapped[Optional[int]] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"))
1082
+
1083
+ __table_args__ = (
1084
+ UniqueConstraint(
1085
+ "name",
1086
+ "project_session_id",
1087
+ "identifier",
1088
+ ),
1089
+ )
1090
+
1091
+
1092
+ class Dataset(HasId):
1016
1093
  __tablename__ = "datasets"
1017
1094
  name: Mapped[str] = mapped_column(unique=True)
1018
1095
  description: Mapped[Optional[str]]
@@ -1021,6 +1098,14 @@ class Dataset(Base):
1021
1098
  updated_at: Mapped[datetime] = mapped_column(
1022
1099
  UtcTimeStamp, server_default=func.now(), onupdate=func.now()
1023
1100
  )
1101
+ user_id: Mapped[Optional[int]] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"))
1102
+ user: Mapped[Optional["User"]] = relationship("User")
1103
+ experiment_tags: Mapped[list["ExperimentTag"]] = relationship(
1104
+ "ExperimentTag", back_populates="dataset"
1105
+ )
1106
+ datasets_dataset_labels: Mapped[list["DatasetsDatasetLabel"]] = relationship(
1107
+ "DatasetsDatasetLabel", back_populates="dataset"
1108
+ )
1024
1109
 
1025
1110
  @hybrid_property
1026
1111
  def example_count(self) -> Optional[int]:
@@ -1071,7 +1156,45 @@ class Dataset(Base):
1071
1156
  )
1072
1157
 
1073
1158
 
1074
- class DatasetVersion(Base):
1159
+ class DatasetLabel(HasId):
1160
+ __tablename__ = "dataset_labels"
1161
+ name: Mapped[str] = mapped_column(unique=True)
1162
+ description: Mapped[Optional[str]]
1163
+ color: Mapped[str] = mapped_column(_HexColor, nullable=False)
1164
+ datasets_dataset_labels: Mapped[list["DatasetsDatasetLabel"]] = relationship(
1165
+ "DatasetsDatasetLabel", back_populates="dataset_label"
1166
+ )
1167
+ user_id: Mapped[Optional[int]] = mapped_column(
1168
+ ForeignKey("users.id", ondelete="SET NULL"),
1169
+ nullable=True,
1170
+ )
1171
+ user: Mapped[Optional["User"]] = relationship("User")
1172
+
1173
+
1174
+ class DatasetsDatasetLabel(Base):
1175
+ __tablename__ = "datasets_dataset_labels"
1176
+ dataset_id: Mapped[int] = mapped_column(
1177
+ ForeignKey("datasets.id", ondelete="CASCADE"),
1178
+ )
1179
+ dataset_label_id: Mapped[int] = mapped_column(
1180
+ ForeignKey("dataset_labels.id", ondelete="CASCADE"),
1181
+ # index on the second element of the composite primary key
1182
+ index=True,
1183
+ )
1184
+ dataset: Mapped["Dataset"] = relationship("Dataset", back_populates="datasets_dataset_labels")
1185
+ dataset_label: Mapped["DatasetLabel"] = relationship(
1186
+ "DatasetLabel", back_populates="datasets_dataset_labels"
1187
+ )
1188
+
1189
+ __table_args__ = (
1190
+ PrimaryKeyConstraint(
1191
+ "dataset_id",
1192
+ "dataset_label_id",
1193
+ ),
1194
+ )
1195
+
1196
+
1197
+ class DatasetVersion(HasId):
1075
1198
  __tablename__ = "dataset_versions"
1076
1199
  dataset_id: Mapped[int] = mapped_column(
1077
1200
  ForeignKey("datasets.id", ondelete="CASCADE"),
@@ -1080,9 +1203,11 @@ class DatasetVersion(Base):
1080
1203
  description: Mapped[Optional[str]]
1081
1204
  metadata_: Mapped[dict[str, Any]] = mapped_column("metadata")
1082
1205
  created_at: Mapped[datetime] = mapped_column(UtcTimeStamp, server_default=func.now())
1206
+ user_id: Mapped[Optional[int]] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"))
1207
+ user: Mapped[Optional["User"]] = relationship("User")
1083
1208
 
1084
1209
 
1085
- class DatasetExample(Base):
1210
+ class DatasetExample(HasId):
1086
1211
  __tablename__ = "dataset_examples"
1087
1212
  dataset_id: Mapped[int] = mapped_column(
1088
1213
  ForeignKey("datasets.id", ondelete="CASCADE"),
@@ -1096,13 +1221,20 @@ class DatasetExample(Base):
1096
1221
  created_at: Mapped[datetime] = mapped_column(UtcTimeStamp, server_default=func.now())
1097
1222
 
1098
1223
  span: Mapped[Optional[Span]] = relationship(back_populates="dataset_examples")
1224
+ dataset_splits_dataset_examples: Mapped[list["DatasetSplitDatasetExample"]] = relationship(
1225
+ "DatasetSplitDatasetExample",
1226
+ back_populates="dataset_example",
1227
+ )
1228
+ experiment_dataset_examples: Mapped[list["ExperimentDatasetExample"]] = relationship(
1229
+ "ExperimentDatasetExample",
1230
+ back_populates="dataset_example",
1231
+ )
1099
1232
 
1100
1233
 
1101
- class DatasetExampleRevision(Base):
1234
+ class DatasetExampleRevision(HasId):
1102
1235
  __tablename__ = "dataset_example_revisions"
1103
1236
  dataset_example_id: Mapped[int] = mapped_column(
1104
1237
  ForeignKey("dataset_examples.id", ondelete="CASCADE"),
1105
- index=True,
1106
1238
  )
1107
1239
  dataset_version_id: Mapped[int] = mapped_column(
1108
1240
  ForeignKey("dataset_versions.id", ondelete="CASCADE"),
@@ -1118,6 +1250,11 @@ class DatasetExampleRevision(Base):
1118
1250
  )
1119
1251
  created_at: Mapped[datetime] = mapped_column(UtcTimeStamp, server_default=func.now())
1120
1252
 
1253
+ experiment_dataset_examples: Mapped[list["ExperimentDatasetExample"]] = relationship(
1254
+ "ExperimentDatasetExample",
1255
+ back_populates="dataset_example_revision",
1256
+ )
1257
+
1121
1258
  __table_args__ = (
1122
1259
  UniqueConstraint(
1123
1260
  "dataset_example_id",
@@ -1126,7 +1263,56 @@ class DatasetExampleRevision(Base):
1126
1263
  )
1127
1264
 
1128
1265
 
1129
- class Experiment(Base):
1266
+ class DatasetSplit(HasId):
1267
+ __tablename__ = "dataset_splits"
1268
+
1269
+ user_id: Mapped[Optional[int]] = mapped_column(
1270
+ ForeignKey("users.id", ondelete="SET NULL"),
1271
+ nullable=True,
1272
+ index=True,
1273
+ )
1274
+ name: Mapped[str] = mapped_column(String, nullable=False, unique=True)
1275
+ description: Mapped[Optional[str]]
1276
+ color: Mapped[str] = mapped_column(String, nullable=False)
1277
+ metadata_: Mapped[dict[str, Any]] = mapped_column("metadata")
1278
+ created_at: Mapped[datetime] = mapped_column(UtcTimeStamp, server_default=func.now())
1279
+ updated_at: Mapped[datetime] = mapped_column(
1280
+ UtcTimeStamp, server_default=func.now(), onupdate=func.now()
1281
+ )
1282
+ dataset_splits_dataset_examples: Mapped[list["DatasetSplitDatasetExample"]] = relationship(
1283
+ "DatasetSplitDatasetExample",
1284
+ back_populates="dataset_split",
1285
+ )
1286
+ experiment_dataset_splits: Mapped[list["ExperimentDatasetSplit"]] = relationship(
1287
+ "ExperimentDatasetSplit",
1288
+ back_populates="dataset_split",
1289
+ )
1290
+
1291
+
1292
+ class DatasetSplitDatasetExample(Base):
1293
+ __tablename__ = "dataset_splits_dataset_examples"
1294
+ dataset_split_id: Mapped[int] = mapped_column(
1295
+ ForeignKey("dataset_splits.id", ondelete="CASCADE"),
1296
+ )
1297
+ dataset_example_id: Mapped[int] = mapped_column(
1298
+ ForeignKey("dataset_examples.id", ondelete="CASCADE"),
1299
+ index=True,
1300
+ )
1301
+ dataset_split: Mapped["DatasetSplit"] = relationship(
1302
+ "DatasetSplit", back_populates="dataset_splits_dataset_examples"
1303
+ )
1304
+ dataset_example: Mapped["DatasetExample"] = relationship(
1305
+ "DatasetExample", back_populates="dataset_splits_dataset_examples"
1306
+ )
1307
+ __table_args__ = (
1308
+ PrimaryKeyConstraint(
1309
+ "dataset_split_id",
1310
+ "dataset_example_id",
1311
+ ),
1312
+ )
1313
+
1314
+
1315
+ class Experiment(HasId):
1130
1316
  __tablename__ = "experiments"
1131
1317
  dataset_id: Mapped[int] = mapped_column(
1132
1318
  ForeignKey("datasets.id", ondelete="CASCADE"),
@@ -1141,18 +1327,83 @@ class Experiment(Base):
1141
1327
  repetitions: Mapped[int]
1142
1328
  metadata_: Mapped[dict[str, Any]] = mapped_column("metadata")
1143
1329
  project_name: Mapped[Optional[str]] = mapped_column(index=True)
1330
+ user_id: Mapped[Optional[int]] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"))
1144
1331
  created_at: Mapped[datetime] = mapped_column(UtcTimeStamp, server_default=func.now())
1145
1332
  updated_at: Mapped[datetime] = mapped_column(
1146
1333
  UtcTimeStamp, server_default=func.now(), onupdate=func.now()
1147
1334
  )
1335
+ user: Mapped[Optional["User"]] = relationship("User")
1336
+ experiment_dataset_splits: Mapped[list["ExperimentDatasetSplit"]] = relationship(
1337
+ "ExperimentDatasetSplit",
1338
+ back_populates="experiment",
1339
+ )
1340
+ experiment_dataset_examples: Mapped[list["ExperimentDatasetExample"]] = relationship(
1341
+ "ExperimentDatasetExample",
1342
+ back_populates="experiment",
1343
+ )
1344
+ experiment_tags: Mapped[list["ExperimentTag"]] = relationship(
1345
+ "ExperimentTag", back_populates="experiment"
1346
+ )
1148
1347
 
1149
1348
 
1150
- class ExperimentRun(Base):
1151
- __tablename__ = "experiment_runs"
1349
+ class ExperimentDatasetSplit(Base):
1350
+ __tablename__ = "experiments_dataset_splits"
1351
+ experiment_id: Mapped[int] = mapped_column(
1352
+ ForeignKey("experiments.id", ondelete="CASCADE"),
1353
+ )
1354
+ dataset_split_id: Mapped[int] = mapped_column(
1355
+ ForeignKey("dataset_splits.id", ondelete="CASCADE"),
1356
+ index=True,
1357
+ )
1358
+ experiment: Mapped["Experiment"] = relationship(
1359
+ "Experiment", back_populates="experiment_dataset_splits"
1360
+ )
1361
+ dataset_split: Mapped["DatasetSplit"] = relationship(
1362
+ "DatasetSplit", back_populates="experiment_dataset_splits"
1363
+ )
1364
+ __table_args__ = (
1365
+ PrimaryKeyConstraint(
1366
+ "experiment_id",
1367
+ "dataset_split_id",
1368
+ ),
1369
+ )
1370
+
1371
+
1372
+ class ExperimentDatasetExample(Base):
1373
+ __tablename__ = "experiments_dataset_examples"
1152
1374
  experiment_id: Mapped[int] = mapped_column(
1153
1375
  ForeignKey("experiments.id", ondelete="CASCADE"),
1376
+ )
1377
+ dataset_example_id: Mapped[int] = mapped_column(
1378
+ ForeignKey("dataset_examples.id", ondelete="CASCADE"),
1154
1379
  index=True,
1155
1380
  )
1381
+ dataset_example_revision_id: Mapped[int] = mapped_column(
1382
+ ForeignKey("dataset_example_revisions.id", ondelete="CASCADE"),
1383
+ index=True,
1384
+ )
1385
+ experiment: Mapped["Experiment"] = relationship(
1386
+ "Experiment", back_populates="experiment_dataset_examples"
1387
+ )
1388
+ dataset_example: Mapped["DatasetExample"] = relationship(
1389
+ "DatasetExample", back_populates="experiment_dataset_examples"
1390
+ )
1391
+ dataset_example_revision: Mapped["DatasetExampleRevision"] = relationship(
1392
+ "DatasetExampleRevision", back_populates="experiment_dataset_examples"
1393
+ )
1394
+ __table_args__ = (
1395
+ PrimaryKeyConstraint(
1396
+ "experiment_id",
1397
+ "dataset_example_id",
1398
+ ),
1399
+ )
1400
+
1401
+
1402
+ class ExperimentRun(HasId):
1403
+ __tablename__ = "experiment_runs"
1404
+ experiment_id: Mapped[int] = mapped_column(
1405
+ ForeignKey("experiments.id", ondelete="CASCADE"),
1406
+ )
1156
1407
  dataset_example_id: Mapped[int] = mapped_column(
1157
1408
  ForeignKey("dataset_examples.id", ondelete="CASCADE"),
1158
1409
  index=True,
@@ -1192,11 +1443,10 @@ class ExperimentRun(Base):
1192
1443
  )
1193
1444
 
1194
1445
 
1195
- class ExperimentRunAnnotation(Base):
1446
+ class ExperimentRunAnnotation(HasId):
1196
1447
  __tablename__ = "experiment_run_annotations"
1197
1448
  experiment_run_id: Mapped[int] = mapped_column(
1198
1449
  ForeignKey("experiment_runs.id", ondelete="CASCADE"),
1199
- index=True,
1200
1450
  )
1201
1451
  name: Mapped[str]
1202
1452
  annotator_kind: Mapped[str] = mapped_column(
@@ -1223,13 +1473,36 @@ class ExperimentRunAnnotation(Base):
1223
1473
  )
1224
1474
 
1225
1475
 
1226
- class UserRole(Base):
1476
+ class ExperimentTag(HasId):
1477
+ __tablename__ = "experiment_tags"
1478
+ experiment_id: Mapped[int] = mapped_column(
1479
+ ForeignKey("experiments.id", ondelete="CASCADE"),
1480
+ index=True,
1481
+ )
1482
+ dataset_id: Mapped[int] = mapped_column(
1483
+ ForeignKey("datasets.id", ondelete="CASCADE"),
1484
+ )
1485
+ user_id: Mapped[Optional[int]] = mapped_column(
1486
+ ForeignKey("users.id", ondelete="SET NULL"),
1487
+ index=True,
1488
+ nullable=True,
1489
+ )
1490
+ name: Mapped[str]
1491
+ description: Mapped[Optional[str]]
1492
+ experiment: Mapped["Experiment"] = relationship("Experiment", back_populates="experiment_tags")
1493
+ dataset: Mapped["Dataset"] = relationship("Dataset", back_populates="experiment_tags")
1494
+ user: Mapped[Optional["User"]] = relationship("User")
1495
+
1496
+ __table_args__ = (UniqueConstraint("dataset_id", "name"),)
1497
+
1498
+
1499
+ class UserRole(HasId):
1227
1500
  __tablename__ = "user_roles"
1228
1501
  name: Mapped[UserRoleName] = mapped_column(unique=True, index=True)
1229
1502
  users: Mapped[list["User"]] = relationship("User", back_populates="role")
1230
1503
 
1231
1504
 
1232
- class User(Base):
1505
+ class User(HasId):
1233
1506
  __tablename__ = "users"
1234
1507
  user_role_id: Mapped[int] = mapped_column(
1235
1508
  ForeignKey("user_roles.id", ondelete="CASCADE"),
@@ -1339,7 +1612,41 @@ class OAuth2User(User):
1339
1612
  )
1340
1613
 
1341
1614
 
1342
- class PasswordResetToken(Base):
1615
+ def LDAPUser(
1616
+ *,
1617
+ email: str,
1618
+ username: str,
1619
+ unique_id: str | None = None,
1620
+ user_role_id: int | None = None,
1621
+ ) -> OAuth2User:
1622
+ """Factory function to create an LDAP user stored as OAuth2User.
1623
+
1624
+ This is a zero-migration approach: LDAP users are stored in the existing
1625
+ OAuth2User table with a special Unicode marker in oauth2_client_id to
1626
+ distinguish them from actual OAuth2 users. This avoids schema changes
1627
+ while allowing LDAP authentication to coexist with OAuth2.
1628
+
1629
+ Args:
1630
+ email: User's email address
1631
+ username: User's display name
1632
+ unique_id: User's LDAP unique ID (stored in oauth2_user_id)
1633
+ user_role_id: Phoenix role ID (ADMIN, MEMBER, VIEWER)
1634
+
1635
+ Returns:
1636
+ OAuth2User instance configured as an LDAP user
1637
+ """
1638
+ from phoenix.server.ldap import LDAP_CLIENT_ID_MARKER
1639
+
1640
+ return OAuth2User(
1641
+ email=email,
1642
+ username=username,
1643
+ oauth2_client_id=LDAP_CLIENT_ID_MARKER,
1644
+ oauth2_user_id=unique_id,
1645
+ user_role_id=user_role_id,
1646
+ )
1647
+
1648
+
1649
+ class PasswordResetToken(HasId):
1343
1650
  __tablename__ = "password_reset_tokens"
1344
1651
  user_id: Mapped[int] = mapped_column(
1345
1652
  ForeignKey("users.id", ondelete="CASCADE"),
@@ -1348,11 +1655,11 @@ class PasswordResetToken(Base):
1348
1655
  )
1349
1656
  user: Mapped["User"] = relationship("User", back_populates="password_reset_token")
1350
1657
  created_at: Mapped[datetime] = mapped_column(UtcTimeStamp, server_default=func.now())
1351
- expires_at: Mapped[Optional[datetime]] = mapped_column(UtcTimeStamp, nullable=False, index=True)
1658
+ expires_at: Mapped[datetime] = mapped_column(UtcTimeStamp, nullable=False, index=True)
1352
1659
  __table_args__ = (dict(sqlite_autoincrement=True),)
1353
1660
 
1354
1661
 
1355
- class RefreshToken(Base):
1662
+ class RefreshToken(HasId):
1356
1663
  __tablename__ = "refresh_tokens"
1357
1664
  user_id: Mapped[int] = mapped_column(
1358
1665
  ForeignKey("users.id", ondelete="CASCADE"),
@@ -1360,11 +1667,11 @@ class RefreshToken(Base):
1360
1667
  )
1361
1668
  user: Mapped["User"] = relationship("User", back_populates="refresh_tokens")
1362
1669
  created_at: Mapped[datetime] = mapped_column(UtcTimeStamp, server_default=func.now())
1363
- expires_at: Mapped[Optional[datetime]] = mapped_column(UtcTimeStamp, nullable=False, index=True)
1670
+ expires_at: Mapped[datetime] = mapped_column(UtcTimeStamp, nullable=False, index=True)
1364
1671
  __table_args__ = (dict(sqlite_autoincrement=True),)
1365
1672
 
1366
1673
 
1367
- class AccessToken(Base):
1674
+ class AccessToken(HasId):
1368
1675
  __tablename__ = "access_tokens"
1369
1676
  user_id: Mapped[int] = mapped_column(
1370
1677
  ForeignKey("users.id", ondelete="CASCADE"),
@@ -1372,7 +1679,7 @@ class AccessToken(Base):
1372
1679
  )
1373
1680
  user: Mapped["User"] = relationship("User", back_populates="access_tokens")
1374
1681
  created_at: Mapped[datetime] = mapped_column(UtcTimeStamp, server_default=func.now())
1375
- expires_at: Mapped[Optional[datetime]] = mapped_column(UtcTimeStamp, nullable=False, index=True)
1682
+ expires_at: Mapped[datetime] = mapped_column(UtcTimeStamp, nullable=False, index=True)
1376
1683
  refresh_token_id: Mapped[int] = mapped_column(
1377
1684
  ForeignKey("refresh_tokens.id", ondelete="CASCADE"),
1378
1685
  index=True,
@@ -1381,7 +1688,7 @@ class AccessToken(Base):
1381
1688
  __table_args__ = (dict(sqlite_autoincrement=True),)
1382
1689
 
1383
1690
 
1384
- class ApiKey(Base):
1691
+ class ApiKey(HasId):
1385
1692
  __tablename__ = "api_keys"
1386
1693
  user_id: Mapped[int] = mapped_column(
1387
1694
  ForeignKey("users.id", ondelete="CASCADE"),
@@ -1398,7 +1705,7 @@ class ApiKey(Base):
1398
1705
  CostType: TypeAlias = Literal["DEFAULT", "OVERRIDE"]
1399
1706
 
1400
1707
 
1401
- class GenerativeModel(Base):
1708
+ class GenerativeModel(HasId):
1402
1709
  __tablename__ = "generative_models"
1403
1710
  name: Mapped[str] = mapped_column(String, nullable=False)
1404
1711
  provider: Mapped[str]
@@ -1447,7 +1754,7 @@ class GenerativeModel(Base):
1447
1754
  )
1448
1755
 
1449
1756
 
1450
- class TokenPrice(Base):
1757
+ class TokenPrice(HasId):
1451
1758
  __tablename__ = "token_prices"
1452
1759
  model_id: Mapped[int] = mapped_column(
1453
1760
  ForeignKey("generative_models.id", ondelete="CASCADE"),
@@ -1473,7 +1780,7 @@ class TokenPrice(Base):
1473
1780
  )
1474
1781
 
1475
1782
 
1476
- class PromptLabel(Base):
1783
+ class PromptLabel(HasId):
1477
1784
  __tablename__ = "prompt_labels"
1478
1785
  name: Mapped[str] = mapped_column(String, unique=True, index=True, nullable=False)
1479
1786
  description: Mapped[Optional[str]]
@@ -1487,7 +1794,7 @@ class PromptLabel(Base):
1487
1794
  )
1488
1795
 
1489
1796
 
1490
- class Prompt(Base):
1797
+ class Prompt(HasId):
1491
1798
  __tablename__ = "prompts"
1492
1799
  source_prompt_id: Mapped[Optional[int]] = mapped_column(
1493
1800
  ForeignKey("prompts.id", ondelete="SET NULL"),
@@ -1524,7 +1831,7 @@ class Prompt(Base):
1524
1831
  )
1525
1832
 
1526
1833
 
1527
- class PromptPromptLabel(Base):
1834
+ class PromptPromptLabel(HasId):
1528
1835
  __tablename__ = "prompts_prompt_labels"
1529
1836
  prompt_label_id: Mapped[int] = mapped_column(
1530
1837
  ForeignKey("prompt_labels.id", ondelete="CASCADE"),
@@ -1545,7 +1852,7 @@ class PromptPromptLabel(Base):
1545
1852
  __table_args__ = (UniqueConstraint("prompt_label_id", "prompt_id"),)
1546
1853
 
1547
1854
 
1548
- class PromptVersion(Base):
1855
+ class PromptVersion(HasId):
1549
1856
  __tablename__ = "prompt_versions"
1550
1857
 
1551
1858
  prompt_id: Mapped[int] = mapped_column(
@@ -1594,7 +1901,7 @@ class PromptVersion(Base):
1594
1901
  )
1595
1902
 
1596
1903
 
1597
- class PromptVersionTag(Base):
1904
+ class PromptVersionTag(HasId):
1598
1905
  __tablename__ = "prompt_version_tags"
1599
1906
 
1600
1907
  name: Mapped[Identifier] = mapped_column(_Identifier, nullable=False)
@@ -1623,13 +1930,13 @@ class PromptVersionTag(Base):
1623
1930
  __table_args__ = (UniqueConstraint("name", "prompt_id"),)
1624
1931
 
1625
1932
 
1626
- class AnnotationConfig(Base):
1933
+ class AnnotationConfig(HasId):
1627
1934
  __tablename__ = "annotation_configs"
1628
1935
  name: Mapped[str] = mapped_column(String, nullable=False, unique=True)
1629
1936
  config: Mapped[AnnotationConfigType] = mapped_column(_AnnotationConfig, nullable=False)
1630
1937
 
1631
1938
 
1632
- class ProjectAnnotationConfig(Base):
1939
+ class ProjectAnnotationConfig(HasId):
1633
1940
  __tablename__ = "project_annotation_configs"
1634
1941
  project_id: Mapped[int] = mapped_column(
1635
1942
  ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True
@@ -1641,16 +1948,18 @@ class ProjectAnnotationConfig(Base):
1641
1948
  __table_args__ = (UniqueConstraint("project_id", "annotation_config_id"),)
1642
1949
 
1643
1950
 
1644
- class SpanCost(Base):
1951
+ class SpanCost(HasId):
1645
1952
  __tablename__ = "span_costs"
1646
1953
 
1647
1954
  span_rowid: Mapped[int] = mapped_column(
1648
1955
  ForeignKey("spans.id", ondelete="CASCADE"),
1649
1956
  nullable=False,
1957
+ index=True,
1650
1958
  )
1651
1959
  trace_rowid: Mapped[int] = mapped_column(
1652
1960
  ForeignKey("traces.id", ondelete="CASCADE"),
1653
1961
  nullable=False,
1962
+ index=True,
1654
1963
  )
1655
1964
  span_start_time: Mapped[datetime] = mapped_column(
1656
1965
  UtcTimeStamp,
@@ -1753,14 +2062,13 @@ class SpanCost(Base):
1753
2062
  self.total_tokens = (self.total_tokens or 0) + tokens
1754
2063
 
1755
2064
 
1756
- class SpanCostDetail(Base):
2065
+ class SpanCostDetail(HasId):
1757
2066
  __tablename__ = "span_cost_details"
1758
2067
  span_cost_id: Mapped[int] = mapped_column(
1759
2068
  ForeignKey("span_costs.id", ondelete="CASCADE"),
1760
2069
  nullable=False,
1761
- index=True,
1762
2070
  )
1763
- token_type: Mapped[str]
2071
+ token_type: Mapped[str] = mapped_column(index=True)
1764
2072
  is_prompt: Mapped[bool]
1765
2073
 
1766
2074
  cost: Mapped[Optional[float]]