arize-phoenix 3.16.1__py3-none-any.whl → 7.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of arize-phoenix might be problematic. Click here for more details.

Files changed (338) hide show
  1. arize_phoenix-7.7.0.dist-info/METADATA +261 -0
  2. arize_phoenix-7.7.0.dist-info/RECORD +345 -0
  3. {arize_phoenix-3.16.1.dist-info → arize_phoenix-7.7.0.dist-info}/WHEEL +1 -1
  4. arize_phoenix-7.7.0.dist-info/entry_points.txt +3 -0
  5. phoenix/__init__.py +86 -14
  6. phoenix/auth.py +309 -0
  7. phoenix/config.py +675 -45
  8. phoenix/core/model.py +32 -30
  9. phoenix/core/model_schema.py +102 -109
  10. phoenix/core/model_schema_adapter.py +48 -45
  11. phoenix/datetime_utils.py +24 -3
  12. phoenix/db/README.md +54 -0
  13. phoenix/db/__init__.py +4 -0
  14. phoenix/db/alembic.ini +85 -0
  15. phoenix/db/bulk_inserter.py +294 -0
  16. phoenix/db/engines.py +208 -0
  17. phoenix/db/enums.py +20 -0
  18. phoenix/db/facilitator.py +113 -0
  19. phoenix/db/helpers.py +159 -0
  20. phoenix/db/insertion/constants.py +2 -0
  21. phoenix/db/insertion/dataset.py +227 -0
  22. phoenix/db/insertion/document_annotation.py +171 -0
  23. phoenix/db/insertion/evaluation.py +191 -0
  24. phoenix/db/insertion/helpers.py +98 -0
  25. phoenix/db/insertion/span.py +193 -0
  26. phoenix/db/insertion/span_annotation.py +158 -0
  27. phoenix/db/insertion/trace_annotation.py +158 -0
  28. phoenix/db/insertion/types.py +256 -0
  29. phoenix/db/migrate.py +86 -0
  30. phoenix/db/migrations/data_migration_scripts/populate_project_sessions.py +199 -0
  31. phoenix/db/migrations/env.py +114 -0
  32. phoenix/db/migrations/script.py.mako +26 -0
  33. phoenix/db/migrations/versions/10460e46d750_datasets.py +317 -0
  34. phoenix/db/migrations/versions/3be8647b87d8_add_token_columns_to_spans_table.py +126 -0
  35. phoenix/db/migrations/versions/4ded9e43755f_create_project_sessions_table.py +66 -0
  36. phoenix/db/migrations/versions/cd164e83824f_users_and_tokens.py +157 -0
  37. phoenix/db/migrations/versions/cf03bd6bae1d_init.py +280 -0
  38. phoenix/db/models.py +807 -0
  39. phoenix/exceptions.py +5 -1
  40. phoenix/experiments/__init__.py +6 -0
  41. phoenix/experiments/evaluators/__init__.py +29 -0
  42. phoenix/experiments/evaluators/base.py +158 -0
  43. phoenix/experiments/evaluators/code_evaluators.py +184 -0
  44. phoenix/experiments/evaluators/llm_evaluators.py +473 -0
  45. phoenix/experiments/evaluators/utils.py +236 -0
  46. phoenix/experiments/functions.py +772 -0
  47. phoenix/experiments/tracing.py +86 -0
  48. phoenix/experiments/types.py +726 -0
  49. phoenix/experiments/utils.py +25 -0
  50. phoenix/inferences/__init__.py +0 -0
  51. phoenix/{datasets → inferences}/errors.py +6 -5
  52. phoenix/{datasets → inferences}/fixtures.py +49 -42
  53. phoenix/{datasets/dataset.py → inferences/inferences.py} +121 -105
  54. phoenix/{datasets → inferences}/schema.py +11 -11
  55. phoenix/{datasets → inferences}/validation.py +13 -14
  56. phoenix/logging/__init__.py +3 -0
  57. phoenix/logging/_config.py +90 -0
  58. phoenix/logging/_filter.py +6 -0
  59. phoenix/logging/_formatter.py +69 -0
  60. phoenix/metrics/__init__.py +5 -4
  61. phoenix/metrics/binning.py +4 -3
  62. phoenix/metrics/metrics.py +2 -1
  63. phoenix/metrics/mixins.py +7 -6
  64. phoenix/metrics/retrieval_metrics.py +2 -1
  65. phoenix/metrics/timeseries.py +5 -4
  66. phoenix/metrics/wrappers.py +9 -3
  67. phoenix/pointcloud/clustering.py +5 -5
  68. phoenix/pointcloud/pointcloud.py +7 -5
  69. phoenix/pointcloud/projectors.py +5 -6
  70. phoenix/pointcloud/umap_parameters.py +53 -52
  71. phoenix/server/api/README.md +28 -0
  72. phoenix/server/api/auth.py +44 -0
  73. phoenix/server/api/context.py +152 -9
  74. phoenix/server/api/dataloaders/__init__.py +91 -0
  75. phoenix/server/api/dataloaders/annotation_summaries.py +139 -0
  76. phoenix/server/api/dataloaders/average_experiment_run_latency.py +54 -0
  77. phoenix/server/api/dataloaders/cache/__init__.py +3 -0
  78. phoenix/server/api/dataloaders/cache/two_tier_cache.py +68 -0
  79. phoenix/server/api/dataloaders/dataset_example_revisions.py +131 -0
  80. phoenix/server/api/dataloaders/dataset_example_spans.py +38 -0
  81. phoenix/server/api/dataloaders/document_evaluation_summaries.py +144 -0
  82. phoenix/server/api/dataloaders/document_evaluations.py +31 -0
  83. phoenix/server/api/dataloaders/document_retrieval_metrics.py +89 -0
  84. phoenix/server/api/dataloaders/experiment_annotation_summaries.py +79 -0
  85. phoenix/server/api/dataloaders/experiment_error_rates.py +58 -0
  86. phoenix/server/api/dataloaders/experiment_run_annotations.py +36 -0
  87. phoenix/server/api/dataloaders/experiment_run_counts.py +49 -0
  88. phoenix/server/api/dataloaders/experiment_sequence_number.py +44 -0
  89. phoenix/server/api/dataloaders/latency_ms_quantile.py +188 -0
  90. phoenix/server/api/dataloaders/min_start_or_max_end_times.py +85 -0
  91. phoenix/server/api/dataloaders/project_by_name.py +31 -0
  92. phoenix/server/api/dataloaders/record_counts.py +116 -0
  93. phoenix/server/api/dataloaders/session_io.py +79 -0
  94. phoenix/server/api/dataloaders/session_num_traces.py +30 -0
  95. phoenix/server/api/dataloaders/session_num_traces_with_error.py +32 -0
  96. phoenix/server/api/dataloaders/session_token_usages.py +41 -0
  97. phoenix/server/api/dataloaders/session_trace_latency_ms_quantile.py +55 -0
  98. phoenix/server/api/dataloaders/span_annotations.py +26 -0
  99. phoenix/server/api/dataloaders/span_dataset_examples.py +31 -0
  100. phoenix/server/api/dataloaders/span_descendants.py +57 -0
  101. phoenix/server/api/dataloaders/span_projects.py +33 -0
  102. phoenix/server/api/dataloaders/token_counts.py +124 -0
  103. phoenix/server/api/dataloaders/trace_by_trace_ids.py +25 -0
  104. phoenix/server/api/dataloaders/trace_root_spans.py +32 -0
  105. phoenix/server/api/dataloaders/user_roles.py +30 -0
  106. phoenix/server/api/dataloaders/users.py +33 -0
  107. phoenix/server/api/exceptions.py +48 -0
  108. phoenix/server/api/helpers/__init__.py +12 -0
  109. phoenix/server/api/helpers/dataset_helpers.py +217 -0
  110. phoenix/server/api/helpers/experiment_run_filters.py +763 -0
  111. phoenix/server/api/helpers/playground_clients.py +948 -0
  112. phoenix/server/api/helpers/playground_registry.py +70 -0
  113. phoenix/server/api/helpers/playground_spans.py +455 -0
  114. phoenix/server/api/input_types/AddExamplesToDatasetInput.py +16 -0
  115. phoenix/server/api/input_types/AddSpansToDatasetInput.py +14 -0
  116. phoenix/server/api/input_types/ChatCompletionInput.py +38 -0
  117. phoenix/server/api/input_types/ChatCompletionMessageInput.py +24 -0
  118. phoenix/server/api/input_types/ClearProjectInput.py +15 -0
  119. phoenix/server/api/input_types/ClusterInput.py +2 -2
  120. phoenix/server/api/input_types/CreateDatasetInput.py +12 -0
  121. phoenix/server/api/input_types/CreateSpanAnnotationInput.py +18 -0
  122. phoenix/server/api/input_types/CreateTraceAnnotationInput.py +18 -0
  123. phoenix/server/api/input_types/DataQualityMetricInput.py +5 -2
  124. phoenix/server/api/input_types/DatasetExampleInput.py +14 -0
  125. phoenix/server/api/input_types/DatasetSort.py +17 -0
  126. phoenix/server/api/input_types/DatasetVersionSort.py +16 -0
  127. phoenix/server/api/input_types/DeleteAnnotationsInput.py +7 -0
  128. phoenix/server/api/input_types/DeleteDatasetExamplesInput.py +13 -0
  129. phoenix/server/api/input_types/DeleteDatasetInput.py +7 -0
  130. phoenix/server/api/input_types/DeleteExperimentsInput.py +7 -0
  131. phoenix/server/api/input_types/DimensionFilter.py +4 -4
  132. phoenix/server/api/input_types/GenerativeModelInput.py +17 -0
  133. phoenix/server/api/input_types/Granularity.py +1 -1
  134. phoenix/server/api/input_types/InvocationParameters.py +162 -0
  135. phoenix/server/api/input_types/PatchAnnotationInput.py +19 -0
  136. phoenix/server/api/input_types/PatchDatasetExamplesInput.py +35 -0
  137. phoenix/server/api/input_types/PatchDatasetInput.py +14 -0
  138. phoenix/server/api/input_types/PerformanceMetricInput.py +5 -2
  139. phoenix/server/api/input_types/ProjectSessionSort.py +29 -0
  140. phoenix/server/api/input_types/SpanAnnotationSort.py +17 -0
  141. phoenix/server/api/input_types/SpanSort.py +134 -69
  142. phoenix/server/api/input_types/TemplateOptions.py +10 -0
  143. phoenix/server/api/input_types/TraceAnnotationSort.py +17 -0
  144. phoenix/server/api/input_types/UserRoleInput.py +9 -0
  145. phoenix/server/api/mutations/__init__.py +28 -0
  146. phoenix/server/api/mutations/api_key_mutations.py +167 -0
  147. phoenix/server/api/mutations/chat_mutations.py +593 -0
  148. phoenix/server/api/mutations/dataset_mutations.py +591 -0
  149. phoenix/server/api/mutations/experiment_mutations.py +75 -0
  150. phoenix/server/api/{types/ExportEventsMutation.py → mutations/export_events_mutations.py} +21 -18
  151. phoenix/server/api/mutations/project_mutations.py +57 -0
  152. phoenix/server/api/mutations/span_annotations_mutations.py +128 -0
  153. phoenix/server/api/mutations/trace_annotations_mutations.py +127 -0
  154. phoenix/server/api/mutations/user_mutations.py +329 -0
  155. phoenix/server/api/openapi/__init__.py +0 -0
  156. phoenix/server/api/openapi/main.py +17 -0
  157. phoenix/server/api/openapi/schema.py +16 -0
  158. phoenix/server/api/queries.py +738 -0
  159. phoenix/server/api/routers/__init__.py +11 -0
  160. phoenix/server/api/routers/auth.py +284 -0
  161. phoenix/server/api/routers/embeddings.py +26 -0
  162. phoenix/server/api/routers/oauth2.py +488 -0
  163. phoenix/server/api/routers/v1/__init__.py +64 -0
  164. phoenix/server/api/routers/v1/datasets.py +1017 -0
  165. phoenix/server/api/routers/v1/evaluations.py +362 -0
  166. phoenix/server/api/routers/v1/experiment_evaluations.py +115 -0
  167. phoenix/server/api/routers/v1/experiment_runs.py +167 -0
  168. phoenix/server/api/routers/v1/experiments.py +308 -0
  169. phoenix/server/api/routers/v1/pydantic_compat.py +78 -0
  170. phoenix/server/api/routers/v1/spans.py +267 -0
  171. phoenix/server/api/routers/v1/traces.py +208 -0
  172. phoenix/server/api/routers/v1/utils.py +95 -0
  173. phoenix/server/api/schema.py +44 -241
  174. phoenix/server/api/subscriptions.py +597 -0
  175. phoenix/server/api/types/Annotation.py +21 -0
  176. phoenix/server/api/types/AnnotationSummary.py +55 -0
  177. phoenix/server/api/types/AnnotatorKind.py +16 -0
  178. phoenix/server/api/types/ApiKey.py +27 -0
  179. phoenix/server/api/types/AuthMethod.py +9 -0
  180. phoenix/server/api/types/ChatCompletionMessageRole.py +11 -0
  181. phoenix/server/api/types/ChatCompletionSubscriptionPayload.py +46 -0
  182. phoenix/server/api/types/Cluster.py +25 -24
  183. phoenix/server/api/types/CreateDatasetPayload.py +8 -0
  184. phoenix/server/api/types/DataQualityMetric.py +31 -13
  185. phoenix/server/api/types/Dataset.py +288 -63
  186. phoenix/server/api/types/DatasetExample.py +85 -0
  187. phoenix/server/api/types/DatasetExampleRevision.py +34 -0
  188. phoenix/server/api/types/DatasetVersion.py +14 -0
  189. phoenix/server/api/types/Dimension.py +32 -31
  190. phoenix/server/api/types/DocumentEvaluationSummary.py +9 -8
  191. phoenix/server/api/types/EmbeddingDimension.py +56 -49
  192. phoenix/server/api/types/Evaluation.py +25 -31
  193. phoenix/server/api/types/EvaluationSummary.py +30 -50
  194. phoenix/server/api/types/Event.py +20 -20
  195. phoenix/server/api/types/ExampleRevisionInterface.py +14 -0
  196. phoenix/server/api/types/Experiment.py +152 -0
  197. phoenix/server/api/types/ExperimentAnnotationSummary.py +13 -0
  198. phoenix/server/api/types/ExperimentComparison.py +17 -0
  199. phoenix/server/api/types/ExperimentRun.py +119 -0
  200. phoenix/server/api/types/ExperimentRunAnnotation.py +56 -0
  201. phoenix/server/api/types/GenerativeModel.py +9 -0
  202. phoenix/server/api/types/GenerativeProvider.py +85 -0
  203. phoenix/server/api/types/Inferences.py +80 -0
  204. phoenix/server/api/types/InferencesRole.py +23 -0
  205. phoenix/server/api/types/LabelFraction.py +7 -0
  206. phoenix/server/api/types/MimeType.py +2 -2
  207. phoenix/server/api/types/Model.py +54 -54
  208. phoenix/server/api/types/PerformanceMetric.py +8 -5
  209. phoenix/server/api/types/Project.py +407 -142
  210. phoenix/server/api/types/ProjectSession.py +139 -0
  211. phoenix/server/api/types/Segments.py +4 -4
  212. phoenix/server/api/types/Span.py +221 -176
  213. phoenix/server/api/types/SpanAnnotation.py +43 -0
  214. phoenix/server/api/types/SpanIOValue.py +15 -0
  215. phoenix/server/api/types/SystemApiKey.py +9 -0
  216. phoenix/server/api/types/TemplateLanguage.py +10 -0
  217. phoenix/server/api/types/TimeSeries.py +19 -15
  218. phoenix/server/api/types/TokenUsage.py +11 -0
  219. phoenix/server/api/types/Trace.py +154 -0
  220. phoenix/server/api/types/TraceAnnotation.py +45 -0
  221. phoenix/server/api/types/UMAPPoints.py +7 -7
  222. phoenix/server/api/types/User.py +60 -0
  223. phoenix/server/api/types/UserApiKey.py +45 -0
  224. phoenix/server/api/types/UserRole.py +15 -0
  225. phoenix/server/api/types/node.py +4 -112
  226. phoenix/server/api/types/pagination.py +156 -57
  227. phoenix/server/api/utils.py +34 -0
  228. phoenix/server/app.py +864 -115
  229. phoenix/server/bearer_auth.py +163 -0
  230. phoenix/server/dml_event.py +136 -0
  231. phoenix/server/dml_event_handler.py +256 -0
  232. phoenix/server/email/__init__.py +0 -0
  233. phoenix/server/email/sender.py +97 -0
  234. phoenix/server/email/templates/__init__.py +0 -0
  235. phoenix/server/email/templates/password_reset.html +19 -0
  236. phoenix/server/email/types.py +11 -0
  237. phoenix/server/grpc_server.py +102 -0
  238. phoenix/server/jwt_store.py +505 -0
  239. phoenix/server/main.py +305 -116
  240. phoenix/server/oauth2.py +52 -0
  241. phoenix/server/openapi/__init__.py +0 -0
  242. phoenix/server/prometheus.py +111 -0
  243. phoenix/server/rate_limiters.py +188 -0
  244. phoenix/server/static/.vite/manifest.json +87 -0
  245. phoenix/server/static/assets/components-Cy9nwIvF.js +2125 -0
  246. phoenix/server/static/assets/index-BKvHIxkk.js +113 -0
  247. phoenix/server/static/assets/pages-CUi2xCVQ.js +4449 -0
  248. phoenix/server/static/assets/vendor-DvC8cT4X.js +894 -0
  249. phoenix/server/static/assets/vendor-DxkFTwjz.css +1 -0
  250. phoenix/server/static/assets/vendor-arizeai-Do1793cv.js +662 -0
  251. phoenix/server/static/assets/vendor-codemirror-BzwZPyJM.js +24 -0
  252. phoenix/server/static/assets/vendor-recharts-_Jb7JjhG.js +59 -0
  253. phoenix/server/static/assets/vendor-shiki-Cl9QBraO.js +5 -0
  254. phoenix/server/static/assets/vendor-three-DwGkEfCM.js +2998 -0
  255. phoenix/server/telemetry.py +68 -0
  256. phoenix/server/templates/index.html +82 -23
  257. phoenix/server/thread_server.py +3 -3
  258. phoenix/server/types.py +275 -0
  259. phoenix/services.py +27 -18
  260. phoenix/session/client.py +743 -68
  261. phoenix/session/data_extractor.py +31 -7
  262. phoenix/session/evaluation.py +3 -9
  263. phoenix/session/session.py +263 -219
  264. phoenix/settings.py +22 -0
  265. phoenix/trace/__init__.py +2 -22
  266. phoenix/trace/attributes.py +338 -0
  267. phoenix/trace/dsl/README.md +116 -0
  268. phoenix/trace/dsl/filter.py +663 -213
  269. phoenix/trace/dsl/helpers.py +73 -21
  270. phoenix/trace/dsl/query.py +574 -201
  271. phoenix/trace/exporter.py +24 -19
  272. phoenix/trace/fixtures.py +368 -32
  273. phoenix/trace/otel.py +71 -219
  274. phoenix/trace/projects.py +3 -2
  275. phoenix/trace/schemas.py +33 -11
  276. phoenix/trace/span_evaluations.py +21 -16
  277. phoenix/trace/span_json_decoder.py +6 -4
  278. phoenix/trace/span_json_encoder.py +2 -2
  279. phoenix/trace/trace_dataset.py +47 -32
  280. phoenix/trace/utils.py +21 -4
  281. phoenix/utilities/__init__.py +0 -26
  282. phoenix/utilities/client.py +132 -0
  283. phoenix/utilities/deprecation.py +31 -0
  284. phoenix/utilities/error_handling.py +3 -2
  285. phoenix/utilities/json.py +109 -0
  286. phoenix/utilities/logging.py +8 -0
  287. phoenix/utilities/project.py +2 -2
  288. phoenix/utilities/re.py +49 -0
  289. phoenix/utilities/span_store.py +0 -23
  290. phoenix/utilities/template_formatters.py +99 -0
  291. phoenix/version.py +1 -1
  292. arize_phoenix-3.16.1.dist-info/METADATA +0 -495
  293. arize_phoenix-3.16.1.dist-info/RECORD +0 -178
  294. phoenix/core/project.py +0 -619
  295. phoenix/core/traces.py +0 -96
  296. phoenix/experimental/evals/__init__.py +0 -73
  297. phoenix/experimental/evals/evaluators.py +0 -413
  298. phoenix/experimental/evals/functions/__init__.py +0 -4
  299. phoenix/experimental/evals/functions/classify.py +0 -453
  300. phoenix/experimental/evals/functions/executor.py +0 -353
  301. phoenix/experimental/evals/functions/generate.py +0 -138
  302. phoenix/experimental/evals/functions/processing.py +0 -76
  303. phoenix/experimental/evals/models/__init__.py +0 -14
  304. phoenix/experimental/evals/models/anthropic.py +0 -175
  305. phoenix/experimental/evals/models/base.py +0 -170
  306. phoenix/experimental/evals/models/bedrock.py +0 -221
  307. phoenix/experimental/evals/models/litellm.py +0 -134
  308. phoenix/experimental/evals/models/openai.py +0 -448
  309. phoenix/experimental/evals/models/rate_limiters.py +0 -246
  310. phoenix/experimental/evals/models/vertex.py +0 -173
  311. phoenix/experimental/evals/models/vertexai.py +0 -186
  312. phoenix/experimental/evals/retrievals.py +0 -96
  313. phoenix/experimental/evals/templates/__init__.py +0 -50
  314. phoenix/experimental/evals/templates/default_templates.py +0 -472
  315. phoenix/experimental/evals/templates/template.py +0 -195
  316. phoenix/experimental/evals/utils/__init__.py +0 -172
  317. phoenix/experimental/evals/utils/threads.py +0 -27
  318. phoenix/server/api/helpers.py +0 -11
  319. phoenix/server/api/routers/evaluation_handler.py +0 -109
  320. phoenix/server/api/routers/span_handler.py +0 -70
  321. phoenix/server/api/routers/trace_handler.py +0 -60
  322. phoenix/server/api/types/DatasetRole.py +0 -23
  323. phoenix/server/static/index.css +0 -6
  324. phoenix/server/static/index.js +0 -7447
  325. phoenix/storage/span_store/__init__.py +0 -23
  326. phoenix/storage/span_store/text_file.py +0 -85
  327. phoenix/trace/dsl/missing.py +0 -60
  328. phoenix/trace/langchain/__init__.py +0 -3
  329. phoenix/trace/langchain/instrumentor.py +0 -35
  330. phoenix/trace/llama_index/__init__.py +0 -3
  331. phoenix/trace/llama_index/callback.py +0 -102
  332. phoenix/trace/openai/__init__.py +0 -3
  333. phoenix/trace/openai/instrumentor.py +0 -30
  334. {arize_phoenix-3.16.1.dist-info → arize_phoenix-7.7.0.dist-info}/licenses/IP_NOTICE +0 -0
  335. {arize_phoenix-3.16.1.dist-info → arize_phoenix-7.7.0.dist-info}/licenses/LICENSE +0 -0
  336. /phoenix/{datasets → db/insertion}/__init__.py +0 -0
  337. /phoenix/{experimental → db/migrations}/__init__.py +0 -0
  338. /phoenix/{storage → db/migrations/data_migration_scripts}/__init__.py +0 -0
phoenix/config.py CHANGED
@@ -1,13 +1,33 @@
1
+ import logging
1
2
  import os
3
+ import re
2
4
  import tempfile
5
+ from dataclasses import dataclass
6
+ from datetime import timedelta
3
7
  from enum import Enum
8
+ from importlib.metadata import version
4
9
  from pathlib import Path
5
- from typing import List, Optional
10
+ from typing import Optional, cast, overload
11
+ from urllib.parse import urlparse
12
+
13
+ from phoenix.utilities.logging import log_a_list
14
+
15
+ from .utilities.re import parse_env_headers
16
+
17
+ logger = logging.getLogger(__name__)
6
18
 
7
19
  # Phoenix environment variables
8
20
  ENV_PHOENIX_PORT = "PHOENIX_PORT"
21
+ ENV_PHOENIX_GRPC_PORT = "PHOENIX_GRPC_PORT"
9
22
  ENV_PHOENIX_HOST = "PHOENIX_HOST"
23
+ ENV_PHOENIX_HOST_ROOT_PATH = "PHOENIX_HOST_ROOT_PATH"
10
24
  ENV_NOTEBOOK_ENV = "PHOENIX_NOTEBOOK_ENV"
25
+ ENV_PHOENIX_CLIENT_HEADERS = "PHOENIX_CLIENT_HEADERS"
26
+ """
27
+ The headers to include in Phoenix client requests.
28
+ Note: This overrides OTEL_EXPORTER_OTLP_HEADERS in the case where
29
+ phoenix.trace instrumentors are used.
30
+ """
11
31
  ENV_PHOENIX_COLLECTOR_ENDPOINT = "PHOENIX_COLLECTOR_ENDPOINT"
12
32
  """
13
33
  The endpoint traces and evals are sent to. This must be set if the Phoenix
@@ -22,12 +42,129 @@ ENV_PHOENIX_PROJECT_NAME = "PHOENIX_PROJECT_NAME"
22
42
  """
23
43
  The project name to use when logging traces and evals. defaults to 'default'.
24
44
  """
25
- ENV_SPAN_STORAGE_TYPE = "__DANGEROUS__PHOENIX_SPAN_STORAGE_TYPE"
45
+ ENV_PHOENIX_SQL_DATABASE_URL = "PHOENIX_SQL_DATABASE_URL"
46
+ """
47
+ The SQL database URL to use when logging traces and evals.
48
+ By default, Phoenix uses an SQLite database and stores it in the working directory.
49
+
50
+ Phoenix supports two types of database URLs:
51
+ - SQLite: 'sqlite:///path/to/database.db'
52
+ - PostgreSQL: 'postgresql://@host/dbname?user=user&password=password' or 'postgresql://user:password@host/dbname'
53
+
54
+ Note that if you plan on using SQLite, it's advised to to use a persistent volume
55
+ and simply point the PHOENIX_WORKING_DIR to that volume.
56
+ """
57
+ ENV_PHOENIX_SQL_DATABASE_SCHEMA = "PHOENIX_SQL_DATABASE_SCHEMA"
58
+ """
59
+ The schema to use for the PostgresSQL database. (This is ignored for SQLite.)
60
+ See e.g. https://www.postgresql.org/docs/current/ddl-schemas.html
61
+ """
62
+ ENV_PHOENIX_ENABLE_PROMETHEUS = "PHOENIX_ENABLE_PROMETHEUS"
63
+ """
64
+ Whether to enable Prometheus. Defaults to false.
65
+ """
66
+ ENV_LOGGING_MODE = "PHOENIX_LOGGING_MODE"
67
+ """
68
+ The logging mode (either 'default' or 'structured').
69
+ """
70
+ ENV_LOGGING_LEVEL = "PHOENIX_LOGGING_LEVEL"
71
+ """
72
+ The logging level ('debug', 'info', 'warning', 'error', 'critical') for the Phoenix backend. For
73
+ database logging see ENV_DB_LOGGING_LEVEL. Defaults to info.
74
+ """
75
+ ENV_DB_LOGGING_LEVEL = "PHOENIX_DB_LOGGING_LEVEL"
76
+ """
77
+ The logging level ('debug', 'info', 'warning', 'error', 'critical') for the Phoenix ORM.
78
+ Defaults to warning.
79
+ """
80
+ ENV_LOG_MIGRATIONS = "PHOENIX_LOG_MIGRATIONS"
81
+ """
82
+ Whether or not to log migrations. Defaults to true.
83
+ """
84
+ ENV_PHOENIX_ENABLE_WEBSOCKETS = "PHOENIX_ENABLE_WEBSOCKETS"
85
+ """
86
+ Whether or not to enable websockets. Defaults to None.
26
87
  """
27
- **EXPERIMENTAL**
28
- The type of span storage to use.
88
+
89
+ # Phoenix server OpenTelemetry instrumentation environment variables
90
+ ENV_PHOENIX_SERVER_INSTRUMENTATION_OTLP_TRACE_COLLECTOR_HTTP_ENDPOINT = (
91
+ "PHOENIX_SERVER_INSTRUMENTATION_OTLP_TRACE_COLLECTOR_HTTP_ENDPOINT"
92
+ )
93
+ ENV_PHOENIX_SERVER_INSTRUMENTATION_OTLP_TRACE_COLLECTOR_GRPC_ENDPOINT = (
94
+ "PHOENIX_SERVER_INSTRUMENTATION_OTLP_TRACE_COLLECTOR_GRPC_ENDPOINT"
95
+ )
96
+
97
+ # Authentication settings
98
+ ENV_PHOENIX_ENABLE_AUTH = "PHOENIX_ENABLE_AUTH"
99
+ ENV_PHOENIX_DISABLE_RATE_LIMIT = "PHOENIX_DISABLE_RATE_LIMIT"
100
+ ENV_PHOENIX_SECRET = "PHOENIX_SECRET"
101
+ ENV_PHOENIX_DEFAULT_ADMIN_INITIAL_PASSWORD = "PHOENIX_DEFAULT_ADMIN_INITIAL_PASSWORD"
102
+ """
103
+ The initial password for the default admin account, which defaults to ‘admin’ if not
104
+ explicitly set. Note that changing this value will have no effect if the default admin
105
+ record already exists in the database. In such cases, the default admin password must
106
+ be updated manually in the application.
107
+ """
108
+ ENV_PHOENIX_API_KEY = "PHOENIX_API_KEY"
109
+ ENV_PHOENIX_USE_SECURE_COOKIES = "PHOENIX_USE_SECURE_COOKIES"
110
+ ENV_PHOENIX_ACCESS_TOKEN_EXPIRY_MINUTES = "PHOENIX_ACCESS_TOKEN_EXPIRY_MINUTES"
111
+ """
112
+ The duration, in minutes, before access tokens expire.
113
+ """
114
+ ENV_PHOENIX_REFRESH_TOKEN_EXPIRY_MINUTES = "PHOENIX_REFRESH_TOKEN_EXPIRY_MINUTES"
115
+ """
116
+ The duration, in minutes, before refresh tokens expire.
117
+ """
118
+ ENV_PHOENIX_PASSWORD_RESET_TOKEN_EXPIRY_MINUTES = "PHOENIX_PASSWORD_RESET_TOKEN_EXPIRY_MINUTES"
119
+ """
120
+ The duration, in minutes, before password reset tokens expire.
121
+ """
122
+ ENV_PHOENIX_CSRF_TRUSTED_ORIGINS = "PHOENIX_CSRF_TRUSTED_ORIGINS"
123
+ """
124
+ A comma-separated list of origins allowed to bypass Cross-Site Request Forgery (CSRF)
125
+ protection. This setting is recommended when configuring OAuth2 clients or sending
126
+ password reset emails. If this variable is left unspecified or contains no origins, CSRF
127
+ protection will not be enabled. In such cases, when a request includes `origin` or `referer`
128
+ headers, those values will not be validated.
29
129
  """
30
130
 
131
+ # SMTP settings
132
+ ENV_PHOENIX_SMTP_HOSTNAME = "PHOENIX_SMTP_HOSTNAME"
133
+ """
134
+ The SMTP server hostname to use for sending emails. SMTP is disabled if this is not set.
135
+ """
136
+ ENV_PHOENIX_SMTP_PORT = "PHOENIX_SMTP_PORT"
137
+ """
138
+ The SMTP server port to use for sending emails. Defaults to 587.
139
+ """
140
+ ENV_PHOENIX_SMTP_USERNAME = "PHOENIX_SMTP_USERNAME"
141
+ """
142
+ The SMTP server username to use for sending emails. Should be set if SMTP is enabled.
143
+ """
144
+ ENV_PHOENIX_SMTP_PASSWORD = "PHOENIX_SMTP_PASSWORD"
145
+ """
146
+ The SMTP server password to use for sending emails. Should be set if SMTP is enabled.
147
+ """
148
+ ENV_PHOENIX_SMTP_MAIL_FROM = "PHOENIX_SMTP_MAIL_FROM"
149
+ """
150
+ The email address to use as the sender when sending emails. Should be set if SMTP is enabled.
151
+ """
152
+ ENV_PHOENIX_SMTP_VALIDATE_CERTS = "PHOENIX_SMTP_VALIDATE_CERTS"
153
+ """
154
+ Whether to validate SMTP server certificates. Defaults to true.
155
+ """
156
+
157
+ # API extension settings
158
+ ENV_PHOENIX_FASTAPI_MIDDLEWARE_PATHS = "PHOENIX_FASTAPI_MIDDLEWARE_PATHS"
159
+ ENV_PHOENIX_GQL_EXTENSION_PATHS = "PHOENIX_GQL_EXTENSION_PATHS"
160
+ ENV_PHOENIX_GRPC_INTERCEPTOR_PATHS = "PHOENIX_GRPC_INTERCEPTOR_PATHS"
161
+
162
+
163
+ def server_instrumentation_is_enabled() -> bool:
164
+ return bool(
165
+ os.getenv(ENV_PHOENIX_SERVER_INSTRUMENTATION_OTLP_TRACE_COLLECTOR_HTTP_ENDPOINT)
166
+ ) or bool(os.getenv(ENV_PHOENIX_SERVER_INSTRUMENTATION_OTLP_TRACE_COLLECTOR_GRPC_ENDPOINT))
167
+
31
168
 
32
169
  def _get_temp_path() -> Path:
33
170
  """Get path to directory in which to store temp phoenix server files."""
@@ -62,44 +199,331 @@ def get_working_dir() -> Path:
62
199
  return Path.home().resolve() / ".phoenix"
63
200
 
64
201
 
65
- def get_storage_dir() -> Path:
202
+ @overload
203
+ def _bool_val(env_var: str) -> Optional[bool]: ...
204
+ @overload
205
+ def _bool_val(env_var: str, default: bool) -> bool: ...
206
+ def _bool_val(env_var: str, default: Optional[bool] = None) -> Optional[bool]:
207
+ """
208
+ Parses a boolean environment variable, returning `default` if the variable is not set.
209
+ """
210
+ if (value := os.environ.get(env_var)) is None:
211
+ return default
212
+ assert (lower := value.lower()) in (
213
+ "true",
214
+ "false",
215
+ ), f"{env_var} must be set to TRUE or FALSE (case-insensitive)"
216
+ return lower == "true"
217
+
218
+
219
+ @overload
220
+ def _float_val(env_var: str) -> Optional[float]: ...
221
+ @overload
222
+ def _float_val(env_var: str, default: float) -> float: ...
223
+ def _float_val(env_var: str, default: Optional[float] = None) -> Optional[float]:
224
+ """
225
+ Parses a numeric environment variable, returning `default` if the variable is not set.
226
+ """
227
+ if (value := os.environ.get(env_var)) is None:
228
+ return default
229
+ try:
230
+ return float(value)
231
+ except ValueError:
232
+ raise ValueError(
233
+ f"Invalid value for environment variable {env_var}: {value}. "
234
+ f"Value must be a number."
235
+ )
236
+
237
+
238
+ @overload
239
+ def _int_val(env_var: str) -> Optional[int]: ...
240
+ @overload
241
+ def _int_val(env_var: str, default: int) -> int: ...
242
+ def _int_val(env_var: str, default: Optional[int] = None) -> Optional[int]:
243
+ """
244
+ Parses a numeric environment variable, returning `default` if the variable is not set.
245
+ """
246
+ if (value := os.environ.get(env_var)) is None:
247
+ return default
248
+ try:
249
+ return int(value)
250
+ except ValueError:
251
+ raise ValueError(
252
+ f"Invalid value for environment variable {env_var}: {value}. "
253
+ f"Value must be an integer."
254
+ )
255
+
256
+
257
+ def get_env_enable_auth() -> bool:
258
+ """
259
+ Gets the value of the PHOENIX_ENABLE_AUTH environment variable.
260
+ """
261
+ return _bool_val(ENV_PHOENIX_ENABLE_AUTH, False)
262
+
263
+
264
+ def get_env_disable_rate_limit() -> bool:
265
+ """
266
+ Gets the value of the PHOENIX_DISABLE_RATE_LIMIT environment variable.
267
+ """
268
+ return _bool_val(ENV_PHOENIX_DISABLE_RATE_LIMIT, False)
269
+
270
+
271
+ def get_env_phoenix_secret() -> Optional[str]:
272
+ """
273
+ Gets the value of the PHOENIX_SECRET environment variable
274
+ and performs validation.
275
+ """
276
+ phoenix_secret = os.environ.get(ENV_PHOENIX_SECRET)
277
+ if phoenix_secret is None:
278
+ return None
279
+ from phoenix.auth import REQUIREMENTS_FOR_PHOENIX_SECRET
280
+
281
+ REQUIREMENTS_FOR_PHOENIX_SECRET.validate(phoenix_secret, "Phoenix secret")
282
+ return phoenix_secret
283
+
284
+
285
+ def get_env_default_admin_initial_password() -> str:
286
+ from phoenix.auth import DEFAULT_ADMIN_PASSWORD
287
+
288
+ return os.environ.get(ENV_PHOENIX_DEFAULT_ADMIN_INITIAL_PASSWORD) or DEFAULT_ADMIN_PASSWORD
289
+
290
+
291
+ def get_env_phoenix_use_secure_cookies() -> bool:
292
+ return _bool_val(ENV_PHOENIX_USE_SECURE_COOKIES, False)
293
+
294
+
295
+ def get_env_phoenix_api_key() -> Optional[str]:
296
+ return os.environ.get(ENV_PHOENIX_API_KEY)
297
+
298
+
299
+ def get_env_auth_settings() -> tuple[bool, Optional[str]]:
66
300
  """
67
- Get the directory for storing traces.
301
+ Gets auth settings and performs validation.
68
302
  """
69
- return get_working_dir() / "storage"
303
+ enable_auth = get_env_enable_auth()
304
+ phoenix_secret = get_env_phoenix_secret()
305
+ if enable_auth and not phoenix_secret:
306
+ raise ValueError(
307
+ f"`{ENV_PHOENIX_SECRET}` must be set when "
308
+ f"auth is enabled with `{ENV_PHOENIX_ENABLE_AUTH}`"
309
+ )
310
+ return enable_auth, phoenix_secret
311
+
312
+
313
+ def get_env_password_reset_token_expiry() -> timedelta:
314
+ """
315
+ Gets the password reset token expiry.
316
+ """
317
+ from phoenix.auth import DEFAULT_PASSWORD_RESET_TOKEN_EXPIRY_MINUTES
318
+
319
+ minutes = _float_val(
320
+ ENV_PHOENIX_PASSWORD_RESET_TOKEN_EXPIRY_MINUTES,
321
+ DEFAULT_PASSWORD_RESET_TOKEN_EXPIRY_MINUTES,
322
+ )
323
+ assert minutes > 0
324
+ return timedelta(minutes=minutes)
325
+
326
+
327
+ def get_env_access_token_expiry() -> timedelta:
328
+ """
329
+ Gets the access token expiry.
330
+ """
331
+ from phoenix.auth import DEFAULT_ACCESS_TOKEN_EXPIRY_MINUTES
332
+
333
+ minutes = _float_val(
334
+ ENV_PHOENIX_ACCESS_TOKEN_EXPIRY_MINUTES,
335
+ DEFAULT_ACCESS_TOKEN_EXPIRY_MINUTES,
336
+ )
337
+ assert minutes > 0
338
+ return timedelta(minutes=minutes)
339
+
340
+
341
+ def get_env_refresh_token_expiry() -> timedelta:
342
+ """
343
+ Gets the refresh token expiry.
344
+ """
345
+ from phoenix.auth import DEFAULT_REFRESH_TOKEN_EXPIRY_MINUTES
346
+
347
+ minutes = _float_val(
348
+ ENV_PHOENIX_REFRESH_TOKEN_EXPIRY_MINUTES,
349
+ DEFAULT_REFRESH_TOKEN_EXPIRY_MINUTES,
350
+ )
351
+ assert minutes > 0
352
+ return timedelta(minutes=minutes)
353
+
354
+
355
+ def get_env_csrf_trusted_origins() -> list[str]:
356
+ origins: list[str] = []
357
+ if not (csrf_trusted_origins := os.getenv(ENV_PHOENIX_CSRF_TRUSTED_ORIGINS)):
358
+ return origins
359
+ for origin in csrf_trusted_origins.split(","):
360
+ if not origin:
361
+ continue
362
+ if not urlparse(origin).hostname:
363
+ raise ValueError(
364
+ f"The environment variable `{ENV_PHOENIX_CSRF_TRUSTED_ORIGINS}` contains a url "
365
+ f"with missing hostname. Please ensure that each url has a valid hostname."
366
+ )
367
+ origins.append(origin)
368
+ return sorted(set(origins))
369
+
370
+
371
+ def get_env_smtp_username() -> str:
372
+ return os.getenv(ENV_PHOENIX_SMTP_USERNAME) or ""
373
+
374
+
375
+ def get_env_smtp_password() -> str:
376
+ return os.getenv(ENV_PHOENIX_SMTP_PASSWORD) or ""
377
+
378
+
379
+ def get_env_smtp_mail_from() -> str:
380
+ return os.getenv(ENV_PHOENIX_SMTP_MAIL_FROM) or "noreply@arize.com"
381
+
382
+
383
+ def get_env_smtp_hostname() -> str:
384
+ return os.getenv(ENV_PHOENIX_SMTP_HOSTNAME) or ""
385
+
386
+
387
+ def get_env_smtp_port() -> int:
388
+ port = _int_val(ENV_PHOENIX_SMTP_PORT, 587)
389
+ assert 0 < port <= 65_535
390
+ return port
391
+
392
+
393
+ def get_env_smtp_validate_certs() -> bool:
394
+ return _bool_val(ENV_PHOENIX_SMTP_VALIDATE_CERTS, True)
395
+
396
+
397
+ def get_env_enable_websockets() -> Optional[bool]:
398
+ return _bool_val(ENV_PHOENIX_ENABLE_WEBSOCKETS)
399
+
400
+
401
+ @dataclass(frozen=True)
402
+ class OAuth2ClientConfig:
403
+ idp_name: str
404
+ idp_display_name: str
405
+ client_id: str
406
+ client_secret: str
407
+ oidc_config_url: str
408
+
409
+ @classmethod
410
+ def from_env(cls, idp_name: str) -> "OAuth2ClientConfig":
411
+ idp_name_upper = idp_name.upper()
412
+ if not (
413
+ client_id := os.getenv(
414
+ client_id_env_var := f"PHOENIX_OAUTH2_{idp_name_upper}_CLIENT_ID"
415
+ )
416
+ ):
417
+ raise ValueError(
418
+ f"A client id must be set for the {idp_name} OAuth2 IDP "
419
+ f"via the {client_id_env_var} environment variable"
420
+ )
421
+ if not (
422
+ client_secret := os.getenv(
423
+ client_secret_env_var := f"PHOENIX_OAUTH2_{idp_name_upper}_CLIENT_SECRET"
424
+ )
425
+ ):
426
+ raise ValueError(
427
+ f"A client secret must be set for the {idp_name} OAuth2 IDP "
428
+ f"via the {client_secret_env_var} environment variable"
429
+ )
430
+ if not (
431
+ oidc_config_url := (
432
+ os.getenv(
433
+ oidc_config_url_env_var := f"PHOENIX_OAUTH2_{idp_name_upper}_OIDC_CONFIG_URL",
434
+ )
435
+ )
436
+ ):
437
+ raise ValueError(
438
+ f"An OpenID Connect configuration URL must be set for the {idp_name} OAuth2 IDP "
439
+ f"via the {oidc_config_url_env_var} environment variable"
440
+ )
441
+ parsed_oidc_config_url = urlparse(oidc_config_url)
442
+ is_local_oidc_config_url = parsed_oidc_config_url.hostname in ("localhost", "127.0.0.1")
443
+ if parsed_oidc_config_url.scheme != "https" and not is_local_oidc_config_url:
444
+ raise ValueError(
445
+ f"Server metadata URL for {idp_name} OAuth2 IDP "
446
+ "must be a valid URL using the https protocol"
447
+ )
448
+ return cls(
449
+ idp_name=idp_name,
450
+ idp_display_name=os.getenv(
451
+ f"PHOENIX_OAUTH2_{idp_name_upper}_DISPLAY_NAME",
452
+ _get_default_idp_display_name(idp_name),
453
+ ),
454
+ client_id=client_id,
455
+ client_secret=client_secret,
456
+ oidc_config_url=oidc_config_url,
457
+ )
458
+
459
+
460
+ def get_env_oauth2_settings() -> list[OAuth2ClientConfig]:
461
+ """
462
+ Get OAuth2 settings from environment variables.
463
+ """
464
+
465
+ idp_names = set()
466
+ pattern = re.compile(
467
+ r"^PHOENIX_OAUTH2_(\w+)_(DISPLAY_NAME|CLIENT_ID|CLIENT_SECRET|OIDC_CONFIG_URL)$"
468
+ )
469
+ for env_var in os.environ:
470
+ if (match := pattern.match(env_var)) is not None and (idp_name := match.group(1).lower()):
471
+ idp_names.add(idp_name)
472
+ return [OAuth2ClientConfig.from_env(idp_name) for idp_name in sorted(idp_names)]
70
473
 
71
474
 
72
475
  PHOENIX_DIR = Path(__file__).resolve().parent
73
476
  # Server config
74
477
  SERVER_DIR = PHOENIX_DIR / "server"
75
- # The host the server will run on after launch_app is called
76
478
  HOST = "0.0.0.0"
77
- # The port the server will run on after launch_app is called
479
+ """The host the server will run on after launch_app is called."""
78
480
  PORT = 6006
79
- # The prefix of datasets that are auto-assigned a name
80
- GENERATED_DATASET_NAME_PREFIX = "phoenix_dataset_"
81
- # The work directory for saving, loading, and exporting datasets
481
+ """The port the server will run on after launch_app is called."""
482
+ HOST_ROOT_PATH = ""
483
+ """The ASGI root path of the server, i.e. the root path where the web application is mounted"""
484
+ GRPC_PORT = 4317
485
+ """The port the gRPC server will run on after launch_app is called.
486
+ The default network port for OTLP/gRPC is 4317.
487
+ See https://opentelemetry.io/docs/specs/otlp/#otlpgrpc-default-port"""
488
+ GENERATED_INFERENCES_NAME_PREFIX = "phoenix_inferences_"
489
+ """The prefix of datasets that are auto-assigned a name."""
82
490
  WORKING_DIR = get_working_dir()
491
+ """The work directory for saving, loading, and exporting data."""
492
+
493
+ ROOT_DIR = WORKING_DIR
494
+ EXPORT_DIR = ROOT_DIR / "exports"
495
+ INFERENCES_DIR = ROOT_DIR / "inferences"
496
+ TRACE_DATASETS_DIR = ROOT_DIR / "trace_datasets"
497
+
498
+
499
+ def ensure_working_dir() -> None:
500
+ """
501
+ Ensure the working directory exists. This is needed because the working directory
502
+ must exist before certain operations can be performed.
503
+ """
504
+ logger.info(f"📋 Ensuring phoenix working directory: {WORKING_DIR}")
505
+ try:
506
+ for path in (
507
+ ROOT_DIR,
508
+ EXPORT_DIR,
509
+ INFERENCES_DIR,
510
+ TRACE_DATASETS_DIR,
511
+ ):
512
+ path.mkdir(parents=True, exist_ok=True)
513
+ except Exception as e:
514
+ print(
515
+ "💥 Failed to initialize the working directory at "
516
+ + f"{WORKING_DIR} due to an error: {str(e)}."
517
+ + "Phoenix requires a working directory to persist data"
518
+ )
519
+ raise
83
520
 
84
- try:
85
- for path in (
86
- ROOT_DIR := WORKING_DIR,
87
- EXPORT_DIR := ROOT_DIR / "exports",
88
- DATASET_DIR := ROOT_DIR / "datasets",
89
- TRACE_DATASET_DIR := ROOT_DIR / "trace_datasets",
90
- ):
91
- path.mkdir(parents=True, exist_ok=True)
92
- except Exception as e:
93
- print(
94
- f"⚠️ Failed to initialize the working directory at {WORKING_DIR} due to an error: {str(e)}"
95
- )
96
- print("⚠️ While phoenix will still run, you will not be able to save, load, or export data")
97
- print(
98
- f"ℹ️ To change, set the `{ENV_PHOENIX_WORKING_DIR}` environment variable before importing phoenix." # noqa: E501
99
- )
100
521
 
522
+ # Invoke ensure_working_dir() to ensure the working directory exists
523
+ ensure_working_dir()
101
524
 
102
- def get_exported_files(directory: Path) -> List[Path]:
525
+
526
+ def get_exported_files(directory: Path) -> list[Path]:
103
527
  """
104
528
  Yields the list of paths of exported files.
105
529
 
@@ -110,17 +534,39 @@ def get_exported_files(directory: Path) -> List[Path]:
110
534
 
111
535
  Returns
112
536
  -------
113
- list: List[Path]
537
+ list: list[Path]
114
538
  List of paths of the exported files.
115
539
  """
116
540
  return list(directory.glob("*.parquet"))
117
541
 
118
542
 
119
543
  def get_env_port() -> int:
120
- return (
121
- int(port)
122
- if isinstance(port := os.getenv(ENV_PHOENIX_PORT), str) and port.isnumeric()
123
- else PORT
544
+ if not (port := os.getenv(ENV_PHOENIX_PORT)):
545
+ return PORT
546
+ if port.isnumeric():
547
+ return int(port)
548
+ if _KUBERNETES_PHOENIX_PORT_PATTERN.match(port) is not None:
549
+ raise ValueError(
550
+ 'If you are deploying Phoenix with Kubernetes using a service named "phoenix", '
551
+ "Kubernetes will automatically generate an environment variable `PHOENIX_PORT` "
552
+ 'of the form "tcp://<IP>:<PORT>" that is not the integer format Phoenix expects. '
553
+ "To resolve this issue, explicitly set the `PHOENIX_PORT` environment variable to "
554
+ "an integer value in your Kubernetes deployment configuration."
555
+ )
556
+ raise ValueError(
557
+ f"Invalid value for environment variable {ENV_PHOENIX_PORT}: "
558
+ f"{port}. Value must be an integer."
559
+ )
560
+
561
+
562
+ def get_env_grpc_port() -> int:
563
+ if not (port := os.getenv(ENV_PHOENIX_GRPC_PORT)):
564
+ return GRPC_PORT
565
+ if port.isnumeric():
566
+ return int(port)
567
+ raise ValueError(
568
+ f"Invalid value for environment variable {ENV_PHOENIX_GRPC_PORT}: "
569
+ f"{port}. Value must be an integer."
124
570
  )
125
571
 
126
572
 
@@ -128,6 +574,22 @@ def get_env_host() -> str:
128
574
  return os.getenv(ENV_PHOENIX_HOST) or HOST
129
575
 
130
576
 
577
+ def get_env_host_root_path() -> str:
578
+ if (host_root_path := os.getenv(ENV_PHOENIX_HOST_ROOT_PATH)) is None:
579
+ return HOST_ROOT_PATH
580
+ if not host_root_path.startswith("/"):
581
+ raise ValueError(
582
+ f"Invalid value for environment variable {ENV_PHOENIX_HOST_ROOT_PATH}: "
583
+ f"{host_root_path}. Value must start with '/'"
584
+ )
585
+ if host_root_path.endswith("/"):
586
+ raise ValueError(
587
+ f"Invalid value for environment variable {ENV_PHOENIX_HOST_ROOT_PATH}: "
588
+ f"{host_root_path}. Value cannot end with '/'"
589
+ )
590
+ return host_root_path
591
+
592
+
131
593
  def get_env_collector_endpoint() -> Optional[str]:
132
594
  return os.getenv(ENV_PHOENIX_COLLECTOR_ENDPOINT)
133
595
 
@@ -136,24 +598,192 @@ def get_env_project_name() -> str:
136
598
  return os.getenv(ENV_PHOENIX_PROJECT_NAME) or DEFAULT_PROJECT_NAME
137
599
 
138
600
 
139
- def get_env_span_storage_type() -> Optional["SpanStorageType"]:
140
- """
141
- Get the type of span storage to use.
142
- """
143
- if not (env_type_str := os.getenv(ENV_SPAN_STORAGE_TYPE)):
601
+ def get_env_database_connection_str() -> str:
602
+ env_url = os.getenv(ENV_PHOENIX_SQL_DATABASE_URL)
603
+ if env_url is None:
604
+ working_dir = get_working_dir()
605
+ return f"sqlite:///{working_dir}/phoenix.db"
606
+ return env_url
607
+
608
+
609
+ def get_env_database_schema() -> Optional[str]:
610
+ if get_env_database_connection_str().startswith("sqlite"):
144
611
  return None
612
+ return os.getenv(ENV_PHOENIX_SQL_DATABASE_SCHEMA)
613
+
614
+
615
+ def get_env_enable_prometheus() -> bool:
616
+ if (enable_promotheus := os.getenv(ENV_PHOENIX_ENABLE_PROMETHEUS)) is None or (
617
+ enable_promotheus_lower := enable_promotheus.lower()
618
+ ) == "false":
619
+ return False
620
+ if enable_promotheus_lower == "true":
621
+ return True
622
+ raise ValueError(
623
+ f"Invalid value for environment variable {ENV_PHOENIX_ENABLE_PROMETHEUS}: "
624
+ f"{enable_promotheus}. Value values are 'TRUE' and 'FALSE' (case-insensitive)."
625
+ )
626
+
627
+
628
+ def get_env_client_headers() -> Optional[dict[str, str]]:
629
+ if headers_str := os.getenv(ENV_PHOENIX_CLIENT_HEADERS):
630
+ return parse_env_headers(headers_str)
631
+ return None
632
+
633
+
634
+ def get_base_url() -> str:
635
+ host = get_env_host()
636
+ if host == "0.0.0.0":
637
+ host = "127.0.0.1"
638
+ base_url = get_env_collector_endpoint() or f"http://{host}:{get_env_port()}"
639
+ return base_url if base_url.endswith("/") else base_url + "/"
640
+
641
+
642
+ def get_web_base_url() -> str:
643
+ """Return the web UI base URL.
644
+
645
+ Returns:
646
+ str: the web UI base URL
647
+ """
648
+ from phoenix.session.session import active_session
649
+
650
+ if session := active_session():
651
+ return session.url
652
+ return get_base_url()
653
+
654
+
655
+ class LoggingMode(Enum):
656
+ DEFAULT = "default"
657
+ STRUCTURED = "structured"
658
+
659
+
660
+ def get_env_logging_mode() -> LoggingMode:
661
+ if (logging_mode := os.getenv(ENV_LOGGING_MODE)) is None:
662
+ return LoggingMode.DEFAULT
145
663
  try:
146
- return SpanStorageType(env_type_str.lower())
664
+ return LoggingMode(logging_mode.lower().strip())
147
665
  except ValueError:
148
666
  raise ValueError(
149
- f"⚠️ Invalid span storage type value `{env_type_str}` defined by the "
150
- f"environment variable `{ENV_SPAN_STORAGE_TYPE}`. Valid values are: "
151
- f"{', '.join(t.value for t in SpanStorageType)}."
667
+ f"Invalid value `{logging_mode}` for env var `{ENV_LOGGING_MODE}`. "
668
+ f"Valid values are: {log_a_list([mode.value for mode in LoggingMode],'and')} "
669
+ "(case-insensitive)."
152
670
  )
153
671
 
154
672
 
155
- class SpanStorageType(Enum):
156
- TEXT_FILES = "text-files"
673
+ def get_env_logging_level() -> int:
674
+ return _get_logging_level(
675
+ env_var=ENV_LOGGING_LEVEL,
676
+ default_level=logging.INFO,
677
+ )
678
+
679
+
680
+ def get_env_db_logging_level() -> int:
681
+ return _get_logging_level(
682
+ env_var=ENV_DB_LOGGING_LEVEL,
683
+ default_level=logging.WARNING,
684
+ )
685
+
686
+
687
+ def get_env_fastapi_middleware_paths() -> list[tuple[str, str]]:
688
+ env_value = os.getenv(ENV_PHOENIX_FASTAPI_MIDDLEWARE_PATHS, "")
689
+ paths = []
690
+ for entry in env_value.split(","):
691
+ entry = entry.strip()
692
+ if entry:
693
+ if ":" not in entry:
694
+ raise ValueError(
695
+ f"Invalid middleware entry '{entry}'. Expected format 'file_path:ClassName'."
696
+ )
697
+ file_path, object_name = entry.split(":", 1)
698
+ paths.append((file_path.strip(), object_name.strip()))
699
+ return paths
700
+
701
+
702
+ def get_env_gql_extension_paths() -> list[tuple[str, str]]:
703
+ env_value = os.getenv(ENV_PHOENIX_GQL_EXTENSION_PATHS, "")
704
+ paths = []
705
+ for entry in env_value.split(","):
706
+ entry = entry.strip()
707
+ if entry:
708
+ if ":" not in entry:
709
+ raise ValueError(
710
+ f"Invalid extension entry '{entry}'. Expected format 'file_path:ClassName'."
711
+ )
712
+ file_path, object_name = entry.split(":", 1)
713
+ paths.append((file_path.strip(), object_name.strip()))
714
+ return paths
715
+
716
+
717
+ def get_env_grpc_interceptor_paths() -> list[tuple[str, str]]:
718
+ env_value = os.getenv(ENV_PHOENIX_GRPC_INTERCEPTOR_PATHS, "")
719
+ paths = []
720
+ for entry in env_value.split(","):
721
+ entry = entry.strip()
722
+ if entry:
723
+ if ":" not in entry:
724
+ raise ValueError(
725
+ f"Invalid interceptor entry '{entry}'. Expected format 'file_path:ClassName'."
726
+ )
727
+ file_path, object_name = entry.split(":", 1)
728
+ paths.append((file_path.strip(), object_name.strip()))
729
+ return paths
730
+
731
+
732
+ def _get_logging_level(env_var: str, default_level: int) -> int:
733
+ logging_level = os.getenv(env_var)
734
+ if not logging_level:
735
+ return default_level
736
+
737
+ # levelNamesMapping = logging.getLevelNamesMapping() is not supported in python 3.8
738
+ # but is supported in 3.12. Hence, we define the mapping ourselves and will remove
739
+ # this once we drop support for older python versions
740
+ levelNamesMapping = logging._nameToLevel.copy()
741
+
742
+ valid_values = [level for level in levelNamesMapping if level != "NOTSET"]
743
+
744
+ if logging_level.upper() not in valid_values:
745
+ raise ValueError(
746
+ f"Invalid value `{logging_level}` for env var `{env_var}`. "
747
+ f"Valid values are: {log_a_list(valid_values,'and')} (case-insensitive)."
748
+ )
749
+ return levelNamesMapping[logging_level.upper()]
750
+
751
+
752
+ def get_env_log_migrations() -> bool:
753
+ log_migrations = os.getenv(ENV_LOG_MIGRATIONS)
754
+ # Default to True
755
+ if log_migrations is None:
756
+ return True
757
+
758
+ if log_migrations.lower() == "true":
759
+ return True
760
+ elif log_migrations.lower() == "false":
761
+ return False
762
+ else:
763
+ raise ValueError(
764
+ f"Invalid value for environment variable {ENV_LOG_MIGRATIONS}: "
765
+ f"{log_migrations}. Value values are 'TRUE' and 'FALSE' (case-insensitive)."
766
+ )
767
+
768
+
769
+ class OAuth2Idp(Enum):
770
+ AWS_COGNITO = "aws_cognito"
771
+ GOOGLE = "google"
772
+ MICROSOFT_ENTRA_ID = "microsoft_entra_id"
773
+
774
+
775
+ def _get_default_idp_display_name(idp_name: str) -> str:
776
+ """
777
+ Get the default display name for an OAuth2 IDP.
778
+ """
779
+ if idp_name == OAuth2Idp.AWS_COGNITO.value:
780
+ return "AWS Cognito"
781
+ if idp_name == OAuth2Idp.MICROSOFT_ENTRA_ID.value:
782
+ return "Microsoft Entra ID"
783
+ return idp_name.replace("_", " ").title()
157
784
 
158
785
 
159
786
  DEFAULT_PROJECT_NAME = "default"
787
+ _KUBERNETES_PHOENIX_PORT_PATTERN = re.compile(r"^tcp://\d{1,3}[.]\d{1,3}[.]\d{1,3}[.]\d{1,3}:\d+$")
788
+
789
+ SKLEARN_VERSION = cast(tuple[int, int], tuple(map(int, version("scikit-learn").split(".", 2)[:2])))