arize-phoenix 10.0.4__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 (276) hide show
  1. {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/METADATA +124 -72
  2. arize_phoenix-12.28.1.dist-info/RECORD +499 -0
  3. {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/WHEEL +1 -1
  4. {arize_phoenix-10.0.4.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 +5 -4
  12. phoenix/auth.py +39 -2
  13. phoenix/config.py +1763 -91
  14. phoenix/datetime_utils.py +120 -2
  15. phoenix/db/README.md +595 -25
  16. phoenix/db/bulk_inserter.py +145 -103
  17. phoenix/db/engines.py +140 -33
  18. phoenix/db/enums.py +3 -12
  19. phoenix/db/facilitator.py +302 -35
  20. phoenix/db/helpers.py +1000 -65
  21. phoenix/db/iam_auth.py +64 -0
  22. phoenix/db/insertion/dataset.py +135 -2
  23. phoenix/db/insertion/document_annotation.py +9 -6
  24. phoenix/db/insertion/evaluation.py +2 -3
  25. phoenix/db/insertion/helpers.py +17 -2
  26. phoenix/db/insertion/session_annotation.py +176 -0
  27. phoenix/db/insertion/span.py +15 -11
  28. phoenix/db/insertion/span_annotation.py +3 -4
  29. phoenix/db/insertion/trace_annotation.py +3 -4
  30. phoenix/db/insertion/types.py +50 -20
  31. phoenix/db/migrations/versions/01a8342c9cdf_add_user_id_on_datasets.py +40 -0
  32. phoenix/db/migrations/versions/0df286449799_add_session_annotations_table.py +105 -0
  33. phoenix/db/migrations/versions/272b66ff50f8_drop_single_indices.py +119 -0
  34. phoenix/db/migrations/versions/58228d933c91_dataset_labels.py +67 -0
  35. phoenix/db/migrations/versions/699f655af132_experiment_tags.py +57 -0
  36. phoenix/db/migrations/versions/735d3d93c33e_add_composite_indices.py +41 -0
  37. phoenix/db/migrations/versions/a20694b15f82_cost.py +196 -0
  38. phoenix/db/migrations/versions/ab513d89518b_add_user_id_on_dataset_versions.py +40 -0
  39. phoenix/db/migrations/versions/d0690a79ea51_users_on_experiments.py +40 -0
  40. phoenix/db/migrations/versions/deb2c81c0bb2_dataset_splits.py +139 -0
  41. phoenix/db/migrations/versions/e76cbd66ffc3_add_experiments_dataset_examples.py +87 -0
  42. phoenix/db/models.py +669 -56
  43. phoenix/db/pg_config.py +10 -0
  44. phoenix/db/types/model_provider.py +4 -0
  45. phoenix/db/types/token_price_customization.py +29 -0
  46. phoenix/db/types/trace_retention.py +23 -15
  47. phoenix/experiments/evaluators/utils.py +3 -3
  48. phoenix/experiments/functions.py +160 -52
  49. phoenix/experiments/tracing.py +2 -2
  50. phoenix/experiments/types.py +1 -1
  51. phoenix/inferences/inferences.py +1 -2
  52. phoenix/server/api/auth.py +38 -7
  53. phoenix/server/api/auth_messages.py +46 -0
  54. phoenix/server/api/context.py +100 -4
  55. phoenix/server/api/dataloaders/__init__.py +79 -5
  56. phoenix/server/api/dataloaders/annotation_configs_by_project.py +31 -0
  57. phoenix/server/api/dataloaders/annotation_summaries.py +60 -8
  58. phoenix/server/api/dataloaders/average_experiment_repeated_run_group_latency.py +50 -0
  59. phoenix/server/api/dataloaders/average_experiment_run_latency.py +17 -24
  60. phoenix/server/api/dataloaders/cache/two_tier_cache.py +1 -2
  61. phoenix/server/api/dataloaders/dataset_dataset_splits.py +52 -0
  62. phoenix/server/api/dataloaders/dataset_example_revisions.py +0 -1
  63. phoenix/server/api/dataloaders/dataset_example_splits.py +40 -0
  64. phoenix/server/api/dataloaders/dataset_examples_and_versions_by_experiment_run.py +47 -0
  65. phoenix/server/api/dataloaders/dataset_labels.py +36 -0
  66. phoenix/server/api/dataloaders/document_evaluation_summaries.py +2 -2
  67. phoenix/server/api/dataloaders/document_evaluations.py +6 -9
  68. phoenix/server/api/dataloaders/experiment_annotation_summaries.py +88 -34
  69. phoenix/server/api/dataloaders/experiment_dataset_splits.py +43 -0
  70. phoenix/server/api/dataloaders/experiment_error_rates.py +21 -28
  71. phoenix/server/api/dataloaders/experiment_repeated_run_group_annotation_summaries.py +77 -0
  72. phoenix/server/api/dataloaders/experiment_repeated_run_groups.py +57 -0
  73. phoenix/server/api/dataloaders/experiment_runs_by_experiment_and_example.py +44 -0
  74. phoenix/server/api/dataloaders/last_used_times_by_generative_model_id.py +35 -0
  75. phoenix/server/api/dataloaders/latency_ms_quantile.py +40 -8
  76. phoenix/server/api/dataloaders/record_counts.py +37 -10
  77. phoenix/server/api/dataloaders/session_annotations_by_session.py +29 -0
  78. phoenix/server/api/dataloaders/span_cost_by_span.py +24 -0
  79. phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_generative_model.py +56 -0
  80. phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_project_session.py +57 -0
  81. phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_span.py +43 -0
  82. phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_trace.py +56 -0
  83. phoenix/server/api/dataloaders/span_cost_details_by_span_cost.py +27 -0
  84. phoenix/server/api/dataloaders/span_cost_summary_by_experiment.py +57 -0
  85. phoenix/server/api/dataloaders/span_cost_summary_by_experiment_repeated_run_group.py +64 -0
  86. phoenix/server/api/dataloaders/span_cost_summary_by_experiment_run.py +58 -0
  87. phoenix/server/api/dataloaders/span_cost_summary_by_generative_model.py +55 -0
  88. phoenix/server/api/dataloaders/span_cost_summary_by_project.py +152 -0
  89. phoenix/server/api/dataloaders/span_cost_summary_by_project_session.py +56 -0
  90. phoenix/server/api/dataloaders/span_cost_summary_by_trace.py +55 -0
  91. phoenix/server/api/dataloaders/span_costs.py +29 -0
  92. phoenix/server/api/dataloaders/table_fields.py +2 -2
  93. phoenix/server/api/dataloaders/token_prices_by_model.py +30 -0
  94. phoenix/server/api/dataloaders/trace_annotations_by_trace.py +27 -0
  95. phoenix/server/api/dataloaders/types.py +29 -0
  96. phoenix/server/api/exceptions.py +11 -1
  97. phoenix/server/api/helpers/dataset_helpers.py +5 -1
  98. phoenix/server/api/helpers/playground_clients.py +1243 -292
  99. phoenix/server/api/helpers/playground_registry.py +2 -2
  100. phoenix/server/api/helpers/playground_spans.py +8 -4
  101. phoenix/server/api/helpers/playground_users.py +26 -0
  102. phoenix/server/api/helpers/prompts/conversions/aws.py +83 -0
  103. phoenix/server/api/helpers/prompts/conversions/google.py +103 -0
  104. phoenix/server/api/helpers/prompts/models.py +205 -22
  105. phoenix/server/api/input_types/{SpanAnnotationFilter.py → AnnotationFilter.py} +22 -14
  106. phoenix/server/api/input_types/ChatCompletionInput.py +6 -2
  107. phoenix/server/api/input_types/CreateProjectInput.py +27 -0
  108. phoenix/server/api/input_types/CreateProjectSessionAnnotationInput.py +37 -0
  109. phoenix/server/api/input_types/DatasetFilter.py +17 -0
  110. phoenix/server/api/input_types/ExperimentRunSort.py +237 -0
  111. phoenix/server/api/input_types/GenerativeCredentialInput.py +9 -0
  112. phoenix/server/api/input_types/GenerativeModelInput.py +5 -0
  113. phoenix/server/api/input_types/ProjectSessionSort.py +161 -1
  114. phoenix/server/api/input_types/PromptFilter.py +14 -0
  115. phoenix/server/api/input_types/PromptVersionInput.py +52 -1
  116. phoenix/server/api/input_types/SpanSort.py +44 -7
  117. phoenix/server/api/input_types/TimeBinConfig.py +23 -0
  118. phoenix/server/api/input_types/UpdateAnnotationInput.py +34 -0
  119. phoenix/server/api/input_types/UserRoleInput.py +1 -0
  120. phoenix/server/api/mutations/__init__.py +10 -0
  121. phoenix/server/api/mutations/annotation_config_mutations.py +8 -8
  122. phoenix/server/api/mutations/api_key_mutations.py +19 -23
  123. phoenix/server/api/mutations/chat_mutations.py +154 -47
  124. phoenix/server/api/mutations/dataset_label_mutations.py +243 -0
  125. phoenix/server/api/mutations/dataset_mutations.py +21 -16
  126. phoenix/server/api/mutations/dataset_split_mutations.py +351 -0
  127. phoenix/server/api/mutations/experiment_mutations.py +2 -2
  128. phoenix/server/api/mutations/export_events_mutations.py +3 -3
  129. phoenix/server/api/mutations/model_mutations.py +210 -0
  130. phoenix/server/api/mutations/project_mutations.py +49 -10
  131. phoenix/server/api/mutations/project_session_annotations_mutations.py +158 -0
  132. phoenix/server/api/mutations/project_trace_retention_policy_mutations.py +8 -4
  133. phoenix/server/api/mutations/prompt_label_mutations.py +74 -65
  134. phoenix/server/api/mutations/prompt_mutations.py +65 -129
  135. phoenix/server/api/mutations/prompt_version_tag_mutations.py +11 -8
  136. phoenix/server/api/mutations/span_annotations_mutations.py +15 -10
  137. phoenix/server/api/mutations/trace_annotations_mutations.py +14 -10
  138. phoenix/server/api/mutations/trace_mutations.py +47 -3
  139. phoenix/server/api/mutations/user_mutations.py +66 -41
  140. phoenix/server/api/queries.py +768 -293
  141. phoenix/server/api/routers/__init__.py +2 -2
  142. phoenix/server/api/routers/auth.py +154 -88
  143. phoenix/server/api/routers/ldap.py +229 -0
  144. phoenix/server/api/routers/oauth2.py +369 -106
  145. phoenix/server/api/routers/v1/__init__.py +24 -4
  146. phoenix/server/api/routers/v1/annotation_configs.py +23 -31
  147. phoenix/server/api/routers/v1/annotations.py +481 -17
  148. phoenix/server/api/routers/v1/datasets.py +395 -81
  149. phoenix/server/api/routers/v1/documents.py +142 -0
  150. phoenix/server/api/routers/v1/evaluations.py +24 -31
  151. phoenix/server/api/routers/v1/experiment_evaluations.py +19 -8
  152. phoenix/server/api/routers/v1/experiment_runs.py +337 -59
  153. phoenix/server/api/routers/v1/experiments.py +479 -48
  154. phoenix/server/api/routers/v1/models.py +7 -0
  155. phoenix/server/api/routers/v1/projects.py +18 -49
  156. phoenix/server/api/routers/v1/prompts.py +54 -40
  157. phoenix/server/api/routers/v1/sessions.py +108 -0
  158. phoenix/server/api/routers/v1/spans.py +1091 -81
  159. phoenix/server/api/routers/v1/traces.py +132 -78
  160. phoenix/server/api/routers/v1/users.py +389 -0
  161. phoenix/server/api/routers/v1/utils.py +3 -7
  162. phoenix/server/api/subscriptions.py +305 -88
  163. phoenix/server/api/types/Annotation.py +90 -23
  164. phoenix/server/api/types/ApiKey.py +13 -17
  165. phoenix/server/api/types/AuthMethod.py +1 -0
  166. phoenix/server/api/types/ChatCompletionSubscriptionPayload.py +1 -0
  167. phoenix/server/api/types/CostBreakdown.py +12 -0
  168. phoenix/server/api/types/Dataset.py +226 -72
  169. phoenix/server/api/types/DatasetExample.py +88 -18
  170. phoenix/server/api/types/DatasetExperimentAnnotationSummary.py +10 -0
  171. phoenix/server/api/types/DatasetLabel.py +57 -0
  172. phoenix/server/api/types/DatasetSplit.py +98 -0
  173. phoenix/server/api/types/DatasetVersion.py +49 -4
  174. phoenix/server/api/types/DocumentAnnotation.py +212 -0
  175. phoenix/server/api/types/Experiment.py +264 -59
  176. phoenix/server/api/types/ExperimentComparison.py +5 -10
  177. phoenix/server/api/types/ExperimentRepeatedRunGroup.py +155 -0
  178. phoenix/server/api/types/ExperimentRepeatedRunGroupAnnotationSummary.py +9 -0
  179. phoenix/server/api/types/ExperimentRun.py +169 -65
  180. phoenix/server/api/types/ExperimentRunAnnotation.py +158 -39
  181. phoenix/server/api/types/GenerativeModel.py +245 -3
  182. phoenix/server/api/types/GenerativeProvider.py +70 -11
  183. phoenix/server/api/types/{Model.py → InferenceModel.py} +1 -1
  184. phoenix/server/api/types/ModelInterface.py +16 -0
  185. phoenix/server/api/types/PlaygroundModel.py +20 -0
  186. phoenix/server/api/types/Project.py +1278 -216
  187. phoenix/server/api/types/ProjectSession.py +188 -28
  188. phoenix/server/api/types/ProjectSessionAnnotation.py +187 -0
  189. phoenix/server/api/types/ProjectTraceRetentionPolicy.py +1 -1
  190. phoenix/server/api/types/Prompt.py +119 -39
  191. phoenix/server/api/types/PromptLabel.py +42 -25
  192. phoenix/server/api/types/PromptVersion.py +11 -8
  193. phoenix/server/api/types/PromptVersionTag.py +65 -25
  194. phoenix/server/api/types/ServerStatus.py +6 -0
  195. phoenix/server/api/types/Span.py +167 -123
  196. phoenix/server/api/types/SpanAnnotation.py +189 -42
  197. phoenix/server/api/types/SpanCostDetailSummaryEntry.py +10 -0
  198. phoenix/server/api/types/SpanCostSummary.py +10 -0
  199. phoenix/server/api/types/SystemApiKey.py +65 -1
  200. phoenix/server/api/types/TokenPrice.py +16 -0
  201. phoenix/server/api/types/TokenUsage.py +3 -3
  202. phoenix/server/api/types/Trace.py +223 -51
  203. phoenix/server/api/types/TraceAnnotation.py +149 -50
  204. phoenix/server/api/types/User.py +137 -32
  205. phoenix/server/api/types/UserApiKey.py +73 -26
  206. phoenix/server/api/types/node.py +10 -0
  207. phoenix/server/api/types/pagination.py +11 -2
  208. phoenix/server/app.py +290 -45
  209. phoenix/server/authorization.py +38 -3
  210. phoenix/server/bearer_auth.py +34 -24
  211. phoenix/server/cost_tracking/cost_details_calculator.py +196 -0
  212. phoenix/server/cost_tracking/cost_model_lookup.py +179 -0
  213. phoenix/server/cost_tracking/helpers.py +68 -0
  214. phoenix/server/cost_tracking/model_cost_manifest.json +3657 -830
  215. phoenix/server/cost_tracking/regex_specificity.py +397 -0
  216. phoenix/server/cost_tracking/token_cost_calculator.py +57 -0
  217. phoenix/server/daemons/__init__.py +0 -0
  218. phoenix/server/daemons/db_disk_usage_monitor.py +214 -0
  219. phoenix/server/daemons/generative_model_store.py +103 -0
  220. phoenix/server/daemons/span_cost_calculator.py +99 -0
  221. phoenix/server/dml_event.py +17 -0
  222. phoenix/server/dml_event_handler.py +5 -0
  223. phoenix/server/email/sender.py +56 -3
  224. phoenix/server/email/templates/db_disk_usage_notification.html +19 -0
  225. phoenix/server/email/types.py +11 -0
  226. phoenix/server/experiments/__init__.py +0 -0
  227. phoenix/server/experiments/utils.py +14 -0
  228. phoenix/server/grpc_server.py +11 -11
  229. phoenix/server/jwt_store.py +17 -15
  230. phoenix/server/ldap.py +1449 -0
  231. phoenix/server/main.py +26 -10
  232. phoenix/server/oauth2.py +330 -12
  233. phoenix/server/prometheus.py +66 -6
  234. phoenix/server/rate_limiters.py +4 -9
  235. phoenix/server/retention.py +33 -20
  236. phoenix/server/session_filters.py +49 -0
  237. phoenix/server/static/.vite/manifest.json +55 -51
  238. phoenix/server/static/assets/components-BreFUQQa.js +6702 -0
  239. phoenix/server/static/assets/{index-E0M82BdE.js → index-CTQoemZv.js} +140 -56
  240. phoenix/server/static/assets/pages-DBE5iYM3.js +9524 -0
  241. phoenix/server/static/assets/vendor-BGzfc4EU.css +1 -0
  242. phoenix/server/static/assets/vendor-DCE4v-Ot.js +920 -0
  243. phoenix/server/static/assets/vendor-codemirror-D5f205eT.js +25 -0
  244. phoenix/server/static/assets/vendor-recharts-V9cwpXsm.js +37 -0
  245. phoenix/server/static/assets/vendor-shiki-Do--csgv.js +5 -0
  246. phoenix/server/static/assets/vendor-three-CmB8bl_y.js +3840 -0
  247. phoenix/server/templates/index.html +40 -6
  248. phoenix/server/thread_server.py +1 -2
  249. phoenix/server/types.py +14 -4
  250. phoenix/server/utils.py +74 -0
  251. phoenix/session/client.py +56 -3
  252. phoenix/session/data_extractor.py +5 -0
  253. phoenix/session/evaluation.py +14 -5
  254. phoenix/session/session.py +45 -9
  255. phoenix/settings.py +5 -0
  256. phoenix/trace/attributes.py +80 -13
  257. phoenix/trace/dsl/helpers.py +90 -1
  258. phoenix/trace/dsl/query.py +8 -6
  259. phoenix/trace/projects.py +5 -0
  260. phoenix/utilities/template_formatters.py +1 -1
  261. phoenix/version.py +1 -1
  262. arize_phoenix-10.0.4.dist-info/RECORD +0 -405
  263. phoenix/server/api/types/Evaluation.py +0 -39
  264. phoenix/server/cost_tracking/cost_lookup.py +0 -255
  265. phoenix/server/static/assets/components-DULKeDfL.js +0 -4365
  266. phoenix/server/static/assets/pages-Cl0A-0U2.js +0 -7430
  267. phoenix/server/static/assets/vendor-WIZid84E.css +0 -1
  268. phoenix/server/static/assets/vendor-arizeai-Dy-0mSNw.js +0 -649
  269. phoenix/server/static/assets/vendor-codemirror-DBtifKNr.js +0 -33
  270. phoenix/server/static/assets/vendor-oB4u9zuV.js +0 -905
  271. phoenix/server/static/assets/vendor-recharts-D-T4KPz2.js +0 -59
  272. phoenix/server/static/assets/vendor-shiki-BMn4O_9F.js +0 -5
  273. phoenix/server/static/assets/vendor-three-C5WAXd5r.js +0 -2998
  274. phoenix/utilities/deprecation.py +0 -31
  275. {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/entry_points.txt +0 -0
  276. {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/licenses/LICENSE +0 -0
phoenix/server/app.py CHANGED
@@ -4,7 +4,6 @@ import importlib
4
4
  import json
5
5
  import logging
6
6
  import os
7
- from collections.abc import AsyncIterator, Awaitable, Callable, Iterable, Sequence
8
7
  from contextlib import AbstractAsyncContextManager, AsyncExitStack
9
8
  from dataclasses import dataclass, field
10
9
  from datetime import datetime, timedelta, timezone
@@ -14,19 +13,27 @@ from types import MethodType
14
13
  from typing import (
15
14
  TYPE_CHECKING,
16
15
  Any,
16
+ AsyncIterator,
17
+ Awaitable,
18
+ Callable,
19
+ Iterable,
17
20
  NamedTuple,
18
21
  Optional,
22
+ Protocol,
23
+ Sequence,
19
24
  TypedDict,
20
25
  Union,
21
26
  cast,
22
27
  )
23
28
  from urllib.parse import urlparse
24
29
 
30
+ import grpc
25
31
  import strawberry
26
32
  from fastapi import APIRouter, Depends, FastAPI
27
33
  from fastapi.middleware.cors import CORSMiddleware
28
34
  from fastapi.utils import is_body_allowed_for_status_code
29
35
  from grpc.aio import ServerInterceptor
36
+ from grpc_interceptor import AsyncServerInterceptor
30
37
  from sqlalchemy import select
31
38
  from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker
32
39
  from starlette.datastructures import URL, Secret
@@ -38,13 +45,12 @@ from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoin
38
45
  from starlette.requests import Request
39
46
  from starlette.responses import JSONResponse, PlainTextResponse, RedirectResponse, Response
40
47
  from starlette.staticfiles import StaticFiles
41
- from starlette.status import HTTP_401_UNAUTHORIZED
42
48
  from starlette.templating import Jinja2Templates
43
49
  from starlette.types import Scope, StatefulLifespan
44
50
  from strawberry.extensions import SchemaExtension
45
51
  from strawberry.fastapi import GraphQLRouter
46
52
  from strawberry.subscriptions import GRAPHQL_TRANSPORT_WS_PROTOCOL
47
- from typing_extensions import TypeAlias
53
+ from typing_extensions import TypeAlias, override
48
54
 
49
55
  import phoenix.trace.v1 as pb
50
56
  from phoenix.config import (
@@ -52,13 +58,17 @@ from phoenix.config import (
52
58
  ENV_PHOENIX_CSRF_TRUSTED_ORIGINS,
53
59
  SERVER_DIR,
54
60
  OAuth2ClientConfig,
61
+ get_env_allow_external_resources,
55
62
  get_env_csrf_trusted_origins,
63
+ get_env_database_allocated_storage_capacity_gibibytes,
64
+ get_env_database_usage_insertion_blocking_threshold_percentage,
56
65
  get_env_fastapi_middleware_paths,
57
66
  get_env_gql_extension_paths,
58
67
  get_env_grpc_interceptor_paths,
59
68
  get_env_host,
60
- get_env_host_root_path,
69
+ get_env_max_spans_queue_size,
61
70
  get_env_port,
71
+ get_env_support_email,
62
72
  server_instrumentation_is_enabled,
63
73
  verify_server_environment_variables,
64
74
  )
@@ -70,21 +80,32 @@ from phoenix.db.facilitator import Facilitator
70
80
  from phoenix.db.helpers import SupportedSQLDialect
71
81
  from phoenix.exceptions import PhoenixMigrationError
72
82
  from phoenix.pointcloud.umap_parameters import UMAPParameters
83
+ from phoenix.server.api.auth_messages import AUTH_ERROR_MESSAGES, AuthErrorCode
73
84
  from phoenix.server.api.context import Context, DataLoaders
74
85
  from phoenix.server.api.dataloaders import (
86
+ AnnotationConfigsByProjectDataLoader,
75
87
  AnnotationSummaryDataLoader,
88
+ AverageExperimentRepeatedRunGroupLatencyDataLoader,
76
89
  AverageExperimentRunLatencyDataLoader,
77
90
  CacheForDataLoaders,
91
+ DatasetDatasetSplitsDataLoader,
78
92
  DatasetExampleRevisionsDataLoader,
93
+ DatasetExamplesAndVersionsByExperimentRunDataLoader,
79
94
  DatasetExampleSpansDataLoader,
95
+ DatasetExampleSplitsDataLoader,
80
96
  DocumentEvaluationsDataLoader,
81
97
  DocumentEvaluationSummaryDataLoader,
82
98
  DocumentRetrievalMetricsDataLoader,
83
99
  ExperimentAnnotationSummaryDataLoader,
100
+ ExperimentDatasetSplitsDataLoader,
84
101
  ExperimentErrorRatesDataLoader,
102
+ ExperimentRepeatedRunGroupAnnotationSummariesDataLoader,
103
+ ExperimentRepeatedRunGroupsDataLoader,
85
104
  ExperimentRunAnnotations,
86
105
  ExperimentRunCountsDataLoader,
106
+ ExperimentRunsByExperimentAndExampleDataLoader,
87
107
  ExperimentSequenceNumberDataLoader,
108
+ LastUsedTimesByGenerativeModelIdDataLoader,
88
109
  LatencyMsQuantileDataLoader,
89
110
  MinStartOrMaxEndTimeDataLoader,
90
111
  NumChildSpansDataLoader,
@@ -93,6 +114,7 @@ from phoenix.server.api.dataloaders import (
93
114
  ProjectIdsByTraceRetentionPolicyIdDataLoader,
94
115
  PromptVersionSequenceNumberDataLoader,
95
116
  RecordCountDataLoader,
117
+ SessionAnnotationsBySessionDataLoader,
96
118
  SessionIODataLoader,
97
119
  SessionNumTracesDataLoader,
98
120
  SessionNumTracesWithErrorDataLoader,
@@ -100,19 +122,35 @@ from phoenix.server.api.dataloaders import (
100
122
  SessionTraceLatencyMsQuantileDataLoader,
101
123
  SpanAnnotationsDataLoader,
102
124
  SpanByIdDataLoader,
125
+ SpanCostBySpanDataLoader,
126
+ SpanCostDetailsBySpanCostDataLoader,
127
+ SpanCostDetailSummaryEntriesByGenerativeModelDataLoader,
128
+ SpanCostDetailSummaryEntriesByProjectSessionDataLoader,
129
+ SpanCostDetailSummaryEntriesBySpanDataLoader,
130
+ SpanCostDetailSummaryEntriesByTraceDataLoader,
131
+ SpanCostSummaryByExperimentDataLoader,
132
+ SpanCostSummaryByExperimentRepeatedRunGroupDataLoader,
133
+ SpanCostSummaryByExperimentRunDataLoader,
134
+ SpanCostSummaryByGenerativeModelDataLoader,
135
+ SpanCostSummaryByProjectDataLoader,
136
+ SpanCostSummaryByProjectSessionDataLoader,
137
+ SpanCostSummaryByTraceDataLoader,
103
138
  SpanDatasetExamplesDataLoader,
104
139
  SpanDescendantsDataLoader,
105
140
  SpanProjectsDataLoader,
106
141
  TableFieldsDataLoader,
107
142
  TokenCountDataLoader,
143
+ TokenPricesByModelDataLoader,
144
+ TraceAnnotationsByTraceDataLoader,
108
145
  TraceByTraceIdsDataLoader,
109
146
  TraceRetentionPolicyIdByProjectIdDataLoader,
110
147
  TraceRootSpansDataLoader,
111
148
  UserRolesDataLoader,
112
149
  UsersDataLoader,
113
150
  )
151
+ from phoenix.server.api.dataloaders.dataset_labels import DatasetLabelsDataLoader
114
152
  from phoenix.server.api.routers import (
115
- auth_router,
153
+ create_auth_router,
116
154
  create_embeddings_router,
117
155
  create_v1_router,
118
156
  oauth2_router,
@@ -120,6 +158,9 @@ from phoenix.server.api.routers import (
120
158
  from phoenix.server.api.routers.v1 import REST_API_VERSION
121
159
  from phoenix.server.api.schema import build_graphql_schema
122
160
  from phoenix.server.bearer_auth import BearerTokenAuthBackend, is_authenticated
161
+ from phoenix.server.daemons.db_disk_usage_monitor import DbDiskUsageMonitor
162
+ from phoenix.server.daemons.generative_model_store import GenerativeModelStore
163
+ from phoenix.server.daemons.span_cost_calculator import SpanCostCalculator
123
164
  from phoenix.server.dml_event import DmlEvent
124
165
  from phoenix.server.dml_event_handler import DmlEventHandler
125
166
  from phoenix.server.email.types import EmailSender
@@ -127,6 +168,7 @@ from phoenix.server.grpc_server import GrpcServer
127
168
  from phoenix.server.jwt_store import JwtStore
128
169
  from phoenix.server.middleware.gzip import GZipMiddleware
129
170
  from phoenix.server.oauth2 import OAuth2Clients
171
+ from phoenix.server.prometheus import SPAN_QUEUE_REJECTIONS
130
172
  from phoenix.server.retention import TraceDataSweeper
131
173
  from phoenix.server.telemetry import initialize_opentelemetry_tracer_provider
132
174
  from phoenix.server.types import (
@@ -137,6 +179,7 @@ from phoenix.server.types import (
137
179
  LastUpdatedAt,
138
180
  TokenStore,
139
181
  )
182
+ from phoenix.server.utils import get_root_path, prepend_root_path
140
183
  from phoenix.settings import Settings
141
184
  from phoenix.trace.fixtures import (
142
185
  TracesFixture,
@@ -155,6 +198,8 @@ from phoenix.version import __version__ as phoenix_version
155
198
  if TYPE_CHECKING:
156
199
  from opentelemetry.trace import TracerProvider
157
200
 
201
+ from phoenix.config import LDAPConfig
202
+
158
203
  logger = logging.getLogger(__name__)
159
204
 
160
205
  router = APIRouter(include_in_schema=False)
@@ -211,9 +256,27 @@ class AppConfig(NamedTuple):
211
256
  web_manifest_path: Path
212
257
  authentication_enabled: bool
213
258
  """ Whether authentication is enabled """
259
+ auth_error_messages: dict[AuthErrorCode, str]
260
+ """ Mapping of auth error codes to user-friendly messages """
214
261
  oauth2_idps: Sequence[OAuth2Idp]
215
262
  basic_auth_disabled: bool = False
263
+ ldap_enabled: bool = False
264
+ """ Whether LDAP authentication is configured """
265
+ ldap_manual_user_creation_enabled: bool = False
266
+ """ Whether manual LDAP user creation is allowed (False when LDAP disabled or no email attr) """
216
267
  auto_login_idp_name: Optional[str] = None
268
+ fullstory_org: Optional[str] = None
269
+ """ FullStory organization ID for web analytics tracking """
270
+ scarf_sh_pixel_id: Optional[str] = None
271
+ """ Scarf.sh pixel ID for open-source analytics and usage """
272
+ management_url: Optional[str] = None
273
+ """ URL for a phoenix management interface, only visible to management users """
274
+ support_email: Optional[str] = None
275
+ """ Support email address for user assistance """
276
+ has_db_threshold: bool = False
277
+ """ Whether the database has a threshold for usage """
278
+ allow_external_resources: bool = True
279
+ """ Whether to allow external resources like Google Fonts in the web interface """
217
280
 
218
281
 
219
282
  class Static(StaticFiles):
@@ -235,9 +298,6 @@ class Static(StaticFiles):
235
298
  return {}
236
299
  raise e
237
300
 
238
- def _sanitize_basename(self, basename: str) -> str:
239
- return basename[:-1] if basename.endswith("/") else basename
240
-
241
301
  async def get_response(self, path: str, scope: Scope) -> Response:
242
302
  # Redirect to the oauth2 login page if basic auth is disabled and auto_login is enabled
243
303
  # TODO: this needs to be refactored to be cleaner
@@ -246,14 +306,10 @@ class Static(StaticFiles):
246
306
  and self._app_config.basic_auth_disabled
247
307
  and self._app_config.auto_login_idp_name
248
308
  ):
249
- request = Request(scope)
250
- url = URL(
251
- str(
252
- Path(get_env_host_root_path())
253
- / f"oauth2/{self._app_config.auto_login_idp_name}/login"
254
- )
309
+ redirect_path = prepend_root_path(
310
+ scope, f"oauth2/{self._app_config.auto_login_idp_name}/login"
255
311
  )
256
- url = url.include_query_params(**request.query_params)
312
+ url = URL(redirect_path).include_query_params(**Request(scope).query_params)
257
313
  return RedirectResponse(url=url)
258
314
  try:
259
315
  response = await super().get_response(path, scope)
@@ -270,7 +326,7 @@ class Static(StaticFiles):
270
326
  "min_dist": self._app_config.min_dist,
271
327
  "n_neighbors": self._app_config.n_neighbors,
272
328
  "n_samples": self._app_config.n_samples,
273
- "basename": self._sanitize_basename(request.scope.get("root_path", "")),
329
+ "basename": get_root_path(scope),
274
330
  "platform_version": phoenix_version,
275
331
  "request": request,
276
332
  "is_development": self._app_config.is_development,
@@ -278,7 +334,16 @@ class Static(StaticFiles):
278
334
  "authentication_enabled": self._app_config.authentication_enabled,
279
335
  "oauth2_idps": self._app_config.oauth2_idps,
280
336
  "basic_auth_disabled": self._app_config.basic_auth_disabled,
337
+ "ldap_enabled": self._app_config.ldap_enabled,
338
+ "ldap_manual_user_creation_enabled": self._app_config.ldap_manual_user_creation_enabled, # noqa: E501
281
339
  "auto_login_idp_name": self._app_config.auto_login_idp_name,
340
+ "fullstory_org": self._app_config.fullstory_org,
341
+ "scarf_sh_pixel_id": self._app_config.scarf_sh_pixel_id,
342
+ "management_url": self._app_config.management_url,
343
+ "support_email": self._app_config.support_email,
344
+ "has_db_threshold": self._app_config.has_db_threshold,
345
+ "allow_external_resources": self._app_config.allow_external_resources,
346
+ "auth_error_messages": self._app_config.auth_error_messages,
282
347
  },
283
348
  )
284
349
  except Exception as e:
@@ -301,7 +366,7 @@ class RequestOriginHostnameValidator(BaseHTTPMiddleware):
301
366
  if not (url := headers.get(key)):
302
367
  continue
303
368
  if urlparse(url).hostname not in self._trusted_hostnames:
304
- return Response(f"untrusted {key}", status_code=HTTP_401_UNAUTHORIZED)
369
+ return Response(f"untrusted {key}", status_code=401)
305
370
  return await call_next(request)
306
371
 
307
372
 
@@ -360,19 +425,16 @@ async def version() -> PlainTextResponse:
360
425
  return PlainTextResponse(f"{phoenix_version}")
361
426
 
362
427
 
363
- DB_MUTEX: Optional[asyncio.Lock] = None
364
-
365
-
366
428
  def _db(
367
- engine: AsyncEngine, bypass_lock: bool = False
368
- ) -> Callable[[], AbstractAsyncContextManager[AsyncSession]]:
429
+ engine: AsyncEngine,
430
+ ) -> Callable[[Optional[asyncio.Lock]], AbstractAsyncContextManager[AsyncSession]]:
369
431
  Session = async_sessionmaker(engine, expire_on_commit=False)
370
432
 
371
433
  @contextlib.asynccontextmanager
372
- async def factory() -> AsyncIterator[AsyncSession]:
434
+ async def factory(lock: Optional[asyncio.Lock] = None) -> AsyncIterator[AsyncSession]:
373
435
  async with contextlib.AsyncExitStack() as stack:
374
- if not bypass_lock and DB_MUTEX:
375
- await stack.enter_async_context(DB_MUTEX)
436
+ if lock:
437
+ await stack.enter_async_context(lock)
376
438
  yield await stack.enter_async_context(Session.begin())
377
439
 
378
440
  return factory
@@ -391,13 +453,13 @@ class Scaffolder(DaemonTask):
391
453
  def __init__(
392
454
  self,
393
455
  config: ScaffolderConfig,
394
- queue_span: Callable[[Span, ProjectName], Awaitable[None]],
395
- queue_evaluation: Callable[[pb.Evaluation], Awaitable[None]],
456
+ enqueue_span: Callable[[Span, ProjectName], Awaitable[None]],
457
+ enqueue_evaluation: Callable[[pb.Evaluation], Awaitable[None]],
396
458
  ) -> None:
397
459
  super().__init__()
398
460
  self._db = config.db
399
- self._queue_span = queue_span
400
- self._queue_evaluation = queue_evaluation
461
+ self._enqueue_span = enqueue_span
462
+ self._enqueue_evaluation = enqueue_evaluation
401
463
  self._tracing_fixtures = [
402
464
  get_trace_fixture_by_name(name) for name in set(config.tracing_fixture_names)
403
465
  ]
@@ -468,9 +530,9 @@ class Scaffolder(DaemonTask):
468
530
  project_name = fixture.project_name or fixture.name
469
531
  logger.info(f"Loading '{project_name}' fixtures...")
470
532
  for span in fixture_spans:
471
- await self._queue_span(span, project_name)
533
+ await self._enqueue_span(span, project_name)
472
534
  for evaluation in fixture_evals:
473
- await self._queue_evaluation(evaluation)
535
+ await self._enqueue_evaluation(evaluation)
474
536
 
475
537
  except FileNotFoundError:
476
538
  logger.warning(f"Fixture file not found for '{fixture.name}'")
@@ -493,12 +555,41 @@ class Scaffolder(DaemonTask):
493
555
  logger.error(f"Error processing dataset fixture: {e}")
494
556
 
495
557
 
558
+ class _CapacityIndicator(Protocol):
559
+ @property
560
+ def is_full(self) -> bool: ...
561
+
562
+
563
+ class CapacityInterceptor(AsyncServerInterceptor):
564
+ def __init__(self, indicator: _CapacityIndicator):
565
+ self._indicator = indicator
566
+
567
+ @override
568
+ async def intercept(
569
+ self,
570
+ method: Callable[[Any, grpc.aio.ServicerContext], Awaitable[Any]],
571
+ request_or_iterator: Any,
572
+ context: grpc.aio.ServicerContext,
573
+ method_name: str,
574
+ ) -> Any:
575
+ if self._indicator.is_full:
576
+ SPAN_QUEUE_REJECTIONS.inc()
577
+ context.set_code(grpc.StatusCode.RESOURCE_EXHAUSTED)
578
+ context.set_details("Server is at capacity and cannot process more requests")
579
+ return
580
+
581
+ return await method(request_or_iterator, context)
582
+
583
+
496
584
  def _lifespan(
497
585
  *,
498
586
  db: DbSessionFactory,
499
587
  bulk_inserter: BulkInserter,
500
588
  dml_event_handler: DmlEventHandler,
501
589
  trace_data_sweeper: Optional[TraceDataSweeper],
590
+ span_cost_calculator: SpanCostCalculator,
591
+ generative_model_store: GenerativeModelStore,
592
+ db_disk_usage_monitor: DbDiskUsageMonitor,
502
593
  token_store: Optional[TokenStore] = None,
503
594
  tracer_provider: Optional["TracerProvider"] = None,
504
595
  enable_prometheus: bool = False,
@@ -506,47 +597,55 @@ def _lifespan(
506
597
  shutdown_callbacks: Iterable[_Callback] = (),
507
598
  read_only: bool = False,
508
599
  scaffolder_config: Optional[ScaffolderConfig] = None,
600
+ grpc_interceptors: Iterable[AsyncServerInterceptor] = (),
509
601
  ) -> StatefulLifespan[FastAPI]:
510
602
  @contextlib.asynccontextmanager
511
603
  async def lifespan(_: FastAPI) -> AsyncIterator[dict[str, Any]]:
512
604
  for callback in startup_callbacks:
513
605
  if isinstance((res := callback()), Awaitable):
514
606
  await res
515
- global DB_MUTEX
516
- DB_MUTEX = asyncio.Lock() if db.dialect is SupportedSQLDialect.SQLITE else None
607
+ db.lock = asyncio.Lock() if db.dialect is SupportedSQLDialect.SQLITE else None
517
608
  async with AsyncExitStack() as stack:
518
609
  (
519
- enqueue,
520
- queue_span,
521
- queue_evaluation,
610
+ enqueue_annotations,
611
+ enqueue_span,
612
+ enqueue_evaluation,
522
613
  enqueue_operation,
523
614
  ) = await stack.enter_async_context(bulk_inserter)
615
+ interceptors = [
616
+ CapacityInterceptor(bulk_inserter),
617
+ *user_grpc_interceptors(),
618
+ *grpc_interceptors,
619
+ ]
524
620
  grpc_server = GrpcServer(
525
- queue_span,
621
+ enqueue_span,
526
622
  disabled=read_only,
527
623
  tracer_provider=tracer_provider,
528
624
  enable_prometheus=enable_prometheus,
529
625
  token_store=token_store,
530
- interceptors=user_grpc_interceptors(),
626
+ interceptors=interceptors,
531
627
  )
532
628
  await stack.enter_async_context(grpc_server)
533
629
  await stack.enter_async_context(dml_event_handler)
534
630
  if trace_data_sweeper:
535
631
  await stack.enter_async_context(trace_data_sweeper)
632
+ await stack.enter_async_context(span_cost_calculator)
633
+ await stack.enter_async_context(generative_model_store)
634
+ await stack.enter_async_context(db_disk_usage_monitor)
536
635
  if scaffolder_config:
537
636
  scaffolder = Scaffolder(
538
637
  config=scaffolder_config,
539
- queue_span=queue_span,
540
- queue_evaluation=queue_evaluation,
638
+ enqueue_span=enqueue_span,
639
+ enqueue_evaluation=enqueue_evaluation,
541
640
  )
542
641
  await stack.enter_async_context(scaffolder)
543
642
  if isinstance(token_store, AbstractAsyncContextManager):
544
643
  await stack.enter_async_context(token_store)
545
644
  yield {
546
645
  "event_queue": dml_event_handler,
547
- "enqueue": enqueue,
548
- "queue_span_for_bulk_insert": queue_span,
549
- "queue_evaluation_for_bulk_insert": queue_evaluation,
646
+ "enqueue_annotations": enqueue_annotations,
647
+ "enqueue_span": enqueue_span,
648
+ "enqueue_evaluation": enqueue_evaluation,
550
649
  "enqueue_operation": enqueue_operation,
551
650
  }
552
651
  for callback in shutdown_callbacks:
@@ -580,6 +679,7 @@ def create_graphql_router(
580
679
  export_path: Path,
581
680
  last_updated_at: CanGetLastUpdatedAt,
582
681
  authentication_enabled: bool,
682
+ span_cost_calculator: SpanCostCalculator,
583
683
  corpus: Optional[Model] = None,
584
684
  cache_for_dataloaders: Optional[CacheForDataLoaders] = None,
585
685
  event_queue: CanPutItem[DmlEvent],
@@ -597,6 +697,7 @@ def create_graphql_router(
597
697
  export_path (Path): the file path to export data to for download (legacy)
598
698
  last_updated_at (CanGetLastUpdatedAt): How to get the last updated timestamp for updates.
599
699
  authentication_enabled (bool): Whether authentication is enabled.
700
+ span_cost_calculator (SpanCostCalculator): The span cost calculator for calculating costs.
600
701
  event_queue (CanPutItem[DmlEvent]): The event queue for DML events.
601
702
  corpus (Optional[Model], optional): the corpus for UMAP projection. Defaults to None.
602
703
  cache_for_dataloaders (Optional[CacheForDataLoaders], optional): GraphQL data loaders.
@@ -618,9 +719,24 @@ def create_graphql_router(
618
719
  last_updated_at=last_updated_at,
619
720
  event_queue=event_queue,
620
721
  data_loaders=DataLoaders(
722
+ annotation_configs_by_project=AnnotationConfigsByProjectDataLoader(db),
723
+ average_experiment_repeated_run_group_latency=AverageExperimentRepeatedRunGroupLatencyDataLoader(
724
+ db
725
+ ),
621
726
  average_experiment_run_latency=AverageExperimentRunLatencyDataLoader(db),
727
+ dataset_dataset_splits=DatasetDatasetSplitsDataLoader(db),
728
+ dataset_example_fields=TableFieldsDataLoader(db, models.DatasetExample),
622
729
  dataset_example_revisions=DatasetExampleRevisionsDataLoader(db),
623
730
  dataset_example_spans=DatasetExampleSpansDataLoader(db),
731
+ dataset_examples_and_versions_by_experiment_run=DatasetExamplesAndVersionsByExperimentRunDataLoader(
732
+ db
733
+ ),
734
+ dataset_example_splits=DatasetExampleSplitsDataLoader(db),
735
+ dataset_fields=TableFieldsDataLoader(db, models.Dataset),
736
+ dataset_split_fields=TableFieldsDataLoader(db, models.DatasetSplit),
737
+ dataset_version_fields=TableFieldsDataLoader(db, models.DatasetVersion),
738
+ dataset_labels=DatasetLabelsDataLoader(db),
739
+ dataset_label_fields=TableFieldsDataLoader(db, models.DatasetLabel),
624
740
  document_evaluation_summaries=DocumentEvaluationSummaryDataLoader(
625
741
  db,
626
742
  cache_map=(
@@ -629,6 +745,7 @@ def create_graphql_router(
629
745
  else None
630
746
  ),
631
747
  ),
748
+ document_annotation_fields=TableFieldsDataLoader(db, models.DocumentAnnotation),
632
749
  document_evaluations=DocumentEvaluationsDataLoader(db),
633
750
  document_retrieval_metrics=DocumentRetrievalMetricsDataLoader(db),
634
751
  annotation_summaries=AnnotationSummaryDataLoader(
@@ -638,10 +755,27 @@ def create_graphql_router(
638
755
  ),
639
756
  ),
640
757
  experiment_annotation_summaries=ExperimentAnnotationSummaryDataLoader(db),
758
+ experiment_dataset_splits=ExperimentDatasetSplitsDataLoader(db),
641
759
  experiment_error_rates=ExperimentErrorRatesDataLoader(db),
760
+ experiment_fields=TableFieldsDataLoader(db, models.Experiment),
761
+ experiment_repeated_run_group_annotation_summaries=ExperimentRepeatedRunGroupAnnotationSummariesDataLoader(
762
+ db
763
+ ),
764
+ experiment_repeated_run_groups=ExperimentRepeatedRunGroupsDataLoader(db),
765
+ experiment_run_annotation_fields=TableFieldsDataLoader(
766
+ db, models.ExperimentRunAnnotation
767
+ ),
642
768
  experiment_run_annotations=ExperimentRunAnnotations(db),
643
769
  experiment_run_counts=ExperimentRunCountsDataLoader(db),
770
+ experiment_run_fields=TableFieldsDataLoader(db, models.ExperimentRun),
771
+ experiment_runs_by_experiment_and_example=ExperimentRunsByExperimentAndExampleDataLoader(
772
+ db
773
+ ),
644
774
  experiment_sequence_number=ExperimentSequenceNumberDataLoader(db),
775
+ generative_model_fields=TableFieldsDataLoader(db, models.GenerativeModel),
776
+ last_used_times_by_generative_model_id=LastUsedTimesByGenerativeModelIdDataLoader(
777
+ db
778
+ ),
645
779
  latency_ms_quantile=LatencyMsQuantileDataLoader(
646
780
  db,
647
781
  cache_map=(
@@ -662,20 +796,59 @@ def create_graphql_router(
662
796
  projects_by_trace_retention_policy_id=ProjectIdsByTraceRetentionPolicyIdDataLoader(
663
797
  db
664
798
  ),
799
+ prompt_fields=TableFieldsDataLoader(db, models.Prompt),
800
+ prompt_label_fields=TableFieldsDataLoader(db, models.PromptLabel),
665
801
  prompt_version_sequence_number=PromptVersionSequenceNumberDataLoader(db),
802
+ prompt_version_tag_fields=TableFieldsDataLoader(db, models.PromptVersionTag),
803
+ project_session_annotation_fields=TableFieldsDataLoader(
804
+ db, models.ProjectSessionAnnotation
805
+ ),
806
+ project_session_fields=TableFieldsDataLoader(db, models.ProjectSession),
666
807
  record_counts=RecordCountDataLoader(
667
808
  db,
668
809
  cache_map=cache_for_dataloaders.record_count if cache_for_dataloaders else None,
669
810
  ),
811
+ session_annotations_by_session=SessionAnnotationsBySessionDataLoader(db),
670
812
  session_first_inputs=SessionIODataLoader(db, "first_input"),
671
813
  session_last_outputs=SessionIODataLoader(db, "last_output"),
672
814
  session_num_traces=SessionNumTracesDataLoader(db),
673
815
  session_num_traces_with_error=SessionNumTracesWithErrorDataLoader(db),
674
816
  session_token_usages=SessionTokenUsagesDataLoader(db),
675
817
  session_trace_latency_ms_quantile=SessionTraceLatencyMsQuantileDataLoader(db),
818
+ span_annotation_fields=TableFieldsDataLoader(db, models.SpanAnnotation),
676
819
  span_annotations=SpanAnnotationsDataLoader(db),
677
820
  span_fields=TableFieldsDataLoader(db, models.Span),
678
821
  span_by_id=SpanByIdDataLoader(db),
822
+ span_cost_by_span=SpanCostBySpanDataLoader(db),
823
+ span_cost_detail_summary_entries_by_generative_model=SpanCostDetailSummaryEntriesByGenerativeModelDataLoader(
824
+ db
825
+ ),
826
+ span_cost_detail_summary_entries_by_project_session=SpanCostDetailSummaryEntriesByProjectSessionDataLoader(
827
+ db
828
+ ),
829
+ span_cost_detail_summary_entries_by_span=SpanCostDetailSummaryEntriesBySpanDataLoader(
830
+ db
831
+ ),
832
+ span_cost_detail_summary_entries_by_trace=SpanCostDetailSummaryEntriesByTraceDataLoader(
833
+ db
834
+ ),
835
+ span_cost_details_by_span_cost=SpanCostDetailsBySpanCostDataLoader(db),
836
+ span_cost_detail_fields=TableFieldsDataLoader(db, models.SpanCostDetail),
837
+ span_cost_fields=TableFieldsDataLoader(db, models.SpanCost),
838
+ span_cost_summary_by_experiment=SpanCostSummaryByExperimentDataLoader(db),
839
+ span_cost_summary_by_experiment_repeated_run_group=SpanCostSummaryByExperimentRepeatedRunGroupDataLoader(
840
+ db
841
+ ),
842
+ span_cost_summary_by_experiment_run=SpanCostSummaryByExperimentRunDataLoader(db),
843
+ span_cost_summary_by_generative_model=SpanCostSummaryByGenerativeModelDataLoader(
844
+ db
845
+ ),
846
+ span_cost_summary_by_project=SpanCostSummaryByProjectDataLoader(
847
+ db,
848
+ cache_map=cache_for_dataloaders.token_cost if cache_for_dataloaders else None,
849
+ ),
850
+ span_cost_summary_by_project_session=SpanCostSummaryByProjectSessionDataLoader(db),
851
+ span_cost_summary_by_trace=SpanCostSummaryByTraceDataLoader(db),
679
852
  span_dataset_examples=SpanDatasetExamplesDataLoader(db),
680
853
  span_descendants=SpanDescendantsDataLoader(db),
681
854
  span_projects=SpanProjectsDataLoader(db),
@@ -683,6 +856,9 @@ def create_graphql_router(
683
856
  db,
684
857
  cache_map=cache_for_dataloaders.token_count if cache_for_dataloaders else None,
685
858
  ),
859
+ token_prices_by_model=TokenPricesByModelDataLoader(db),
860
+ trace_annotation_fields=TableFieldsDataLoader(db, models.TraceAnnotation),
861
+ trace_annotations_by_trace=TraceAnnotationsByTraceDataLoader(db),
686
862
  trace_by_trace_ids=TraceByTraceIdsDataLoader(db),
687
863
  trace_fields=TableFieldsDataLoader(db, models.Trace),
688
864
  trace_retention_policy_id_by_project_id=TraceRetentionPolicyIdByProjectIdDataLoader(
@@ -694,6 +870,8 @@ def create_graphql_router(
694
870
  trace_root_spans=TraceRootSpansDataLoader(db),
695
871
  project_by_name=ProjectByNameDataLoader(db),
696
872
  users=UsersDataLoader(db),
873
+ user_api_key_fields=TableFieldsDataLoader(db, models.ApiKey),
874
+ user_fields=TableFieldsDataLoader(db, models.User),
697
875
  user_roles=UserRolesDataLoader(db),
698
876
  ),
699
877
  cache_for_dataloaders=cache_for_dataloaders,
@@ -702,6 +880,7 @@ def create_graphql_router(
702
880
  secret=secret,
703
881
  token_store=token_store,
704
882
  email_sender=email_sender,
883
+ span_cost_calculator=span_cost_calculator,
705
884
  )
706
885
 
707
886
  return GraphQLRouter(
@@ -766,6 +945,37 @@ async def plain_text_http_exception_handler(request: Request, exc: HTTPException
766
945
  return PlainTextResponse(str(exc.detail), status_code=exc.status_code, headers=headers)
767
946
 
768
947
 
948
+ class _HasDbStatus(Protocol):
949
+ @property
950
+ def should_not_insert_or_update(self) -> bool: ...
951
+
952
+
953
+ class DbDiskUsageInterceptor(AsyncServerInterceptor):
954
+ def __init__(self, db: _HasDbStatus) -> None:
955
+ self._db = db
956
+
957
+ @override
958
+ async def intercept(
959
+ self,
960
+ method: Callable[[Any, grpc.aio.ServicerContext], Awaitable[Any]],
961
+ request_or_iterator: Any,
962
+ context: grpc.aio.ServicerContext,
963
+ method_name: str,
964
+ ) -> Any:
965
+ if (
966
+ method_name.endswith("trace.v1.TraceService/Export")
967
+ and self._db.should_not_insert_or_update
968
+ ):
969
+ details = (
970
+ "Database operations are disabled due to insufficient storage. "
971
+ "Please delete old data or increase storage."
972
+ )
973
+ if support_email := get_env_support_email():
974
+ details += f" Need help? Contact us at {support_email}"
975
+ await context.abort(grpc.StatusCode.RESOURCE_EXHAUSTED, details)
976
+ return await method(request_or_iterator, context)
977
+
978
+
769
979
  def create_app(
770
980
  db: DbSessionFactory,
771
981
  export_path: Path,
@@ -789,9 +999,11 @@ def create_app(
789
999
  scaffolder_config: Optional[ScaffolderConfig] = None,
790
1000
  email_sender: Optional[EmailSender] = None,
791
1001
  oauth2_client_configs: Optional[list[OAuth2ClientConfig]] = None,
1002
+ ldap_config: Optional["LDAPConfig"] = None,
792
1003
  basic_auth_disabled: bool = False,
793
1004
  bulk_inserter_factory: Optional[Callable[..., BulkInserter]] = None,
794
1005
  allowed_origins: Optional[list[str]] = None,
1006
+ management_url: Optional[str] = None,
795
1007
  ) -> FastAPI:
796
1008
  verify_server_environment_variables()
797
1009
  if model.embedding_dimensions:
@@ -857,12 +1069,15 @@ def create_app(
857
1069
  db=db,
858
1070
  dml_event_handler=dml_event_handler,
859
1071
  )
1072
+ generative_model_store = GenerativeModelStore(db)
1073
+ span_cost_calculator = SpanCostCalculator(db, generative_model_store)
860
1074
  bulk_inserter = bulk_inserter_factory(
861
1075
  db,
862
- enable_prometheus=enable_prometheus,
1076
+ span_cost_calculator=span_cost_calculator,
863
1077
  event_queue=dml_event_handler,
864
1078
  initial_batch_of_spans=initial_batch_of_spans,
865
1079
  initial_batch_of_evaluations=initial_batch_of_evaluations,
1080
+ max_spans_queue_size=get_env_max_spans_queue_size(),
866
1081
  )
867
1082
  tracer_provider = None
868
1083
  graphql_schema_extensions: list[Union[type[SchemaExtension], SchemaExtension]] = []
@@ -901,11 +1116,14 @@ def create_app(
901
1116
  secret=secret,
902
1117
  token_store=token_store,
903
1118
  email_sender=email_sender,
1119
+ span_cost_calculator=span_cost_calculator,
904
1120
  )
905
1121
  if enable_prometheus:
906
1122
  from phoenix.server.prometheus import PrometheusMiddleware
907
1123
 
908
1124
  middlewares.append(Middleware(PrometheusMiddleware))
1125
+ grpc_interceptors: list[AsyncServerInterceptor] = []
1126
+ grpc_interceptors.append(DbDiskUsageInterceptor(db))
909
1127
  app = FastAPI(
910
1128
  title="Arize-Phoenix REST API",
911
1129
  version=REST_API_VERSION,
@@ -915,6 +1133,10 @@ def create_app(
915
1133
  bulk_inserter=bulk_inserter,
916
1134
  dml_event_handler=dml_event_handler,
917
1135
  trace_data_sweeper=trace_data_sweeper,
1136
+ span_cost_calculator=span_cost_calculator,
1137
+ generative_model_store=generative_model_store,
1138
+ db_disk_usage_monitor=DbDiskUsageMonitor(db, email_sender),
1139
+ grpc_interceptors=grpc_interceptors,
918
1140
  token_store=token_store,
919
1141
  tracer_provider=tracer_provider,
920
1142
  enable_prometheus=enable_prometheus,
@@ -936,7 +1158,8 @@ def create_app(
936
1158
  app.include_router(router)
937
1159
  app.include_router(graphql_router)
938
1160
  if authentication_enabled:
939
- app.include_router(auth_router)
1161
+ # Only register LDAP endpoint if LDAP is configured
1162
+ app.include_router(create_auth_router(ldap_enabled=ldap_config is not None))
940
1163
  app.include_router(oauth2_router)
941
1164
  app.add_middleware(GZipMiddleware)
942
1165
  web_manifest_path = SERVER_DIR / "static" / ".vite" / "manifest.json"
@@ -963,7 +1186,22 @@ def create_app(
963
1186
  web_manifest_path=web_manifest_path,
964
1187
  oauth2_idps=oauth2_idps,
965
1188
  basic_auth_disabled=basic_auth_disabled,
1189
+ ldap_enabled=ldap_config is not None,
1190
+ # Disable manual user creation when LDAP disabled or no email attr
1191
+ ldap_manual_user_creation_enabled=(
1192
+ ldap_config.attr_email is not None if ldap_config else False
1193
+ ),
966
1194
  auto_login_idp_name=auto_login_idp_name,
1195
+ fullstory_org=Settings.fullstory_org,
1196
+ scarf_sh_pixel_id=Settings.scarf_sh_pixel_id,
1197
+ management_url=management_url,
1198
+ support_email=get_env_support_email(),
1199
+ has_db_threshold=bool(
1200
+ get_env_database_allocated_storage_capacity_gibibytes()
1201
+ and get_env_database_usage_insertion_blocking_threshold_percentage()
1202
+ ),
1203
+ allow_external_resources=get_env_allow_external_resources(),
1204
+ auth_error_messages=dict(AUTH_ERROR_MESSAGES) if authentication_enabled else {},
967
1205
  ),
968
1206
  ),
969
1207
  name="static",
@@ -975,8 +1213,15 @@ def create_app(
975
1213
  app.state.access_token_expiry = access_token_expiry
976
1214
  app.state.refresh_token_expiry = refresh_token_expiry
977
1215
  app.state.oauth2_clients = OAuth2Clients.from_configs(oauth2_client_configs or [])
1216
+ # Cache LDAPAuthenticator to avoid re-parsing TLS config on every login
1217
+ if ldap_config:
1218
+ from phoenix.server.ldap import LDAPAuthenticator
1219
+
1220
+ app.state.ldap_authenticator = LDAPAuthenticator(ldap_config)
978
1221
  app.state.db = db
979
1222
  app.state.email_sender = email_sender
1223
+ app.state.span_cost_calculator = span_cost_calculator
1224
+ app.state.span_queue_is_full = lambda: bulk_inserter.is_full
980
1225
  app = _add_get_secret_method(app=app, secret=secret)
981
1226
  app = _add_get_token_store_method(app=app, token_store=token_store)
982
1227
  if tracer_provider: