arize-phoenix 3.16.0__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.0.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 -247
  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 +13 -107
  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.0.dist-info/METADATA +0 -495
  293. arize_phoenix-3.16.0.dist-info/RECORD +0 -178
  294. phoenix/core/project.py +0 -617
  295. phoenix/core/traces.py +0 -100
  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.0.dist-info → arize_phoenix-7.7.0.dist-info}/licenses/IP_NOTICE +0 -0
  335. {arize_phoenix-3.16.0.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
@@ -0,0 +1,68 @@
1
+ import logging
2
+ import os
3
+ from typing import TYPE_CHECKING
4
+
5
+ from phoenix.config import (
6
+ ENV_PHOENIX_SERVER_INSTRUMENTATION_OTLP_TRACE_COLLECTOR_GRPC_ENDPOINT,
7
+ ENV_PHOENIX_SERVER_INSTRUMENTATION_OTLP_TRACE_COLLECTOR_HTTP_ENDPOINT,
8
+ )
9
+
10
+ if TYPE_CHECKING:
11
+ from opentelemetry.trace import TracerProvider
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def normalize_http_collector_endpoint(endpoint: str) -> str:
17
+ normalized_endpoint = endpoint
18
+ if not normalized_endpoint.startswith("http://") and not normalized_endpoint.startswith(
19
+ "https://"
20
+ ):
21
+ logger.warning(
22
+ "HTTP collector endpoint should include the protocol (http:// or https://)."
23
+ "Assuming http."
24
+ )
25
+ # assume http if no protocol is provided
26
+ normalized_endpoint = f"http://{endpoint}"
27
+ if normalized_endpoint.endswith("/v1/traces"):
28
+ logger.warning(
29
+ "HTTP collector endpoint should not include the /v1/traces path. Removing it."
30
+ )
31
+ # remove the /v1/traces path
32
+ normalized_endpoint = normalized_endpoint[: -len("/v1/traces")]
33
+ # remove trailing slashes
34
+ normalized_endpoint = normalized_endpoint.rstrip("/")
35
+ return normalized_endpoint
36
+
37
+
38
+ def initialize_opentelemetry_tracer_provider() -> "TracerProvider":
39
+ logger.info("Initializing OpenTelemetry tracer provider")
40
+ from opentelemetry.sdk import trace as trace_sdk
41
+ from opentelemetry.sdk.resources import Resource
42
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
43
+ from opentelemetry.semconv.resource import ResourceAttributes
44
+
45
+ tracer_provider = trace_sdk.TracerProvider(
46
+ resource=Resource(attributes={ResourceAttributes.SERVICE_NAME: "arize-phoenix-server"})
47
+ )
48
+ if http_endpoint := os.getenv(
49
+ ENV_PHOENIX_SERVER_INSTRUMENTATION_OTLP_TRACE_COLLECTOR_HTTP_ENDPOINT
50
+ ):
51
+ logger.info(f"Using HTTP collector endpoint: {http_endpoint}")
52
+ http_endpoint = normalize_http_collector_endpoint(http_endpoint) + "/v1/traces"
53
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
54
+ OTLPSpanExporter as HttpExporter,
55
+ )
56
+
57
+ tracer_provider.add_span_processor(BatchSpanProcessor(HttpExporter(http_endpoint)))
58
+ if grpc_endpoint := os.getenv(
59
+ ENV_PHOENIX_SERVER_INSTRUMENTATION_OTLP_TRACE_COLLECTOR_GRPC_ENDPOINT
60
+ ):
61
+ logger.info(f"Using gRPC collector endpoint: {grpc_endpoint}")
62
+ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
63
+ OTLPSpanExporter as GrpcExporter,
64
+ )
65
+
66
+ tracer_provider.add_span_processor(BatchSpanProcessor(GrpcExporter(grpc_endpoint)))
67
+ logger.info("🔭 OpenTelemetry tracer provider initialized")
68
+ return tracer_provider
@@ -1,3 +1,47 @@
1
+ {%- set rendered_chunks = [] -%}
2
+ {%- set css_links = [] -%}
3
+ {%- set js_scripts = [] -%}
4
+ {%- macro collect_assets(chunk_name, level=0) -%}
5
+ {% if chunk_name not in rendered_chunks %}
6
+ {% set _ = rendered_chunks.append(chunk_name) %}
7
+ {% set chunk = manifest[chunk_name] %}
8
+ {% if chunk.css %}
9
+ {% for css_file in chunk.css %}
10
+ {% if css_file not in css_links %}
11
+ {% set _ = css_links.append((basename, css_file)) %}
12
+ {% endif %}
13
+ {% endfor %}
14
+ {% endif %}
15
+ {% if chunk.imports %}
16
+ {% for import in chunk.imports %}
17
+ {% set _ = collect_assets(import, level + 1) %}
18
+ {% endfor %}
19
+ {% endif %}
20
+ {% if chunk.file.endswith('.js') %}
21
+ {% if chunk.file not in js_scripts %}
22
+ {% set _ = js_scripts.append((basename, chunk.file, level == 0)) %}
23
+ {% endif %}
24
+ {% elif chunk.file.endswith('.css') %}
25
+ {% if chunk.file not in css_links %}
26
+ {% set _ = css_links.append((basename, chunk.file)) %}
27
+ {% endif %}
28
+ {% endif %}
29
+ {% endif %}
30
+ {%- endmacro -%}
31
+ {%- macro render_css() -%}
32
+ {%- for basename, css_file in css_links -%}
33
+ <link rel="stylesheet" href="{{ basename }}/{{ css_file }}">
34
+ {% endfor -%}
35
+ {%- endmacro -%}
36
+ {%- macro render_js() -%}
37
+ {%- for basename, js_file, is_entry in js_scripts -%}
38
+ {%- if is_entry -%}
39
+ <script type="module" src="{{ basename }}/{{ js_file }}"></script>
40
+ {% else -%}
41
+ <link rel="modulepreload" href="{{ basename }}/{{ js_file }}">
42
+ {% endif -%}
43
+ {%- endfor -%}
44
+ {%- endmacro -%}
1
45
  <!DOCTYPE html>
2
46
  <html>
3
47
  <head>
@@ -11,37 +55,52 @@
11
55
  <meta property="og:description" content="AI Observability & Evaluation" />
12
56
  <meta property="og:image" content="https://raw.githubusercontent.com/Arize-ai/phoenix-assets/main/images/socal/social-preview-horizontal.jpg" />
13
57
  <meta name="theme-color" content="#ffffff" />
14
- <link rel="stylesheet" src="{{basename}}/index.css"></link>
15
58
  <link
16
59
  rel="stylesheet"
17
60
  href="https://fonts.googleapis.com/css2?family=Roboto:wght@100;300;400;500;700;900&display=swap"
18
61
  />
62
+ {% if not is_development -%}
63
+ {% set _ = collect_assets('index.tsx') -%}
64
+ {{- render_css() -}}
65
+ {%- endif -%}
19
66
  <script src="{{basename}}/modernizr.js"></script>
20
67
  </head>
21
68
  <body>
22
69
  <div id="root"></div>
23
70
  <script>(function() {
24
- // Request Info:
25
- // URL: {{request.url}}
26
- // Base URL: {{request.base_url}}
27
- // Root Path: {{root_path}}
28
- Object.defineProperty(window, "Config", {
29
- // Place any server-side injected config here
30
- // E.g. default UMAP parameters etc. that needs to be
31
- // injected into the client before React runs
32
- value: Object.freeze({
33
- basename: "{{basename}}",
34
- hasInferences: Boolean("{{has_inferences}}" == "True"),
35
- hasCorpus: Boolean("{{has_corpus}}" == "True"),
36
- UMAP: {
37
- minDist: parseFloat("{{min_dist}}"),
38
- nNeighbors: parseInt("{{n_neighbors}}"),
39
- nSamples: parseInt("{{n_samples}}"),
40
- }
41
- }),
42
- writable: false
43
- });
44
- })()</script>
45
- <script type="module" src="{{basename}}/index.js"></script>
71
+ Object.defineProperty(window, "Config", {
72
+ // Place any server-side injected config here
73
+ // E.g. default UMAP parameters etc. that needs to be
74
+ // injected into the client before React runs
75
+ value: Object.freeze({
76
+ basename: "{{basename}}",
77
+ platformVersion: "{{platform_version}}",
78
+ hasInferences: Boolean("{{has_inferences}}" == "True"),
79
+ hasCorpus: Boolean("{{has_corpus}}" == "True"),
80
+ UMAP: {
81
+ minDist: parseFloat("{{min_dist}}"),
82
+ nNeighbors: parseInt("{{n_neighbors}}"),
83
+ nSamples: parseInt("{{n_samples}}"),
84
+ },
85
+ authenticationEnabled: Boolean("{{authentication_enabled}}" == "True"),
86
+ oAuth2Idps: {{ oauth2_idps | tojson }},
87
+ websocketsEnabled: Boolean("{{websockets_enabled}}" == "True"),
88
+ }),
89
+ writable: false
90
+ });
91
+ })()</script>
92
+ {% if is_development -%}
93
+ <script type="module">
94
+ import RefreshRuntime from 'http://localhost:5173/@react-refresh'
95
+ RefreshRuntime.injectIntoGlobalHook(window)
96
+ window.$RefreshReg$ = () => {}
97
+ window.$RefreshSig$ = () => (type) => type
98
+ window.__vite_plugin_react_preamble_installed__ = true
99
+ </script>
100
+ <script type="module" src="http://localhost:5173/@vite/client"></script>
101
+ <script type="module" src="http://localhost:5173/index.tsx"></script>
102
+ {%- else -%}
103
+ {{- render_js() -}}
104
+ {%- endif -%}
46
105
  </body>
47
106
  </html>
@@ -1,10 +1,10 @@
1
1
  import asyncio
2
2
  import logging
3
+ from collections.abc import Generator
3
4
  from threading import Thread
4
5
  from time import sleep, time
5
- from typing import Generator
6
6
 
7
- from starlette.applications import Starlette
7
+ from fastapi import FastAPI
8
8
  from uvicorn import Config, Server
9
9
  from uvicorn.config import LoopSetupType
10
10
 
@@ -24,7 +24,7 @@ class ThreadServer(Server):
24
24
 
25
25
  def __init__(
26
26
  self,
27
- app: Starlette,
27
+ app: FastAPI,
28
28
  host: str,
29
29
  port: int,
30
30
  root_path: str,
@@ -0,0 +1,275 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from asyncio import Task, create_task, sleep
5
+ from collections import defaultdict
6
+ from collections.abc import Callable, Iterator
7
+ from contextlib import AbstractAsyncContextManager
8
+ from dataclasses import dataclass
9
+ from datetime import datetime, timezone
10
+ from typing import Any, Generic, Optional, Protocol, TypeVar, final
11
+
12
+ from cachetools import LRUCache
13
+ from sqlalchemy.ext.asyncio import AsyncSession
14
+
15
+ from phoenix.auth import CanReadToken, ClaimSet, Token, TokenAttributes
16
+ from phoenix.db import enums, models
17
+ from phoenix.db.helpers import SupportedSQLDialect
18
+
19
+
20
+ class CanSetLastUpdatedAt(Protocol):
21
+ def set(self, table: type[models.Base], id_: int) -> None: ...
22
+
23
+
24
+ class CanGetLastUpdatedAt(Protocol):
25
+ def get(self, table: type[models.Base], id_: Optional[int] = None) -> Optional[datetime]: ...
26
+
27
+
28
+ class DbSessionFactory:
29
+ def __init__(
30
+ self,
31
+ db: Callable[[], AbstractAsyncContextManager[AsyncSession]],
32
+ dialect: str,
33
+ ):
34
+ self._db = db
35
+ self.dialect = SupportedSQLDialect(dialect)
36
+
37
+ def __call__(self) -> AbstractAsyncContextManager[AsyncSession]:
38
+ return self._db()
39
+
40
+
41
+ _AnyT = TypeVar("_AnyT")
42
+ _ItemT_contra = TypeVar("_ItemT_contra", contravariant=True)
43
+
44
+
45
+ class CanPutItem(Protocol[_ItemT_contra]):
46
+ def put(self, item: _ItemT_contra) -> None: ...
47
+
48
+
49
+ class _Batch(CanPutItem[_AnyT], Protocol[_AnyT]):
50
+ @property
51
+ def empty(self) -> bool: ...
52
+ def clear(self) -> None: ...
53
+ def __iter__(self) -> Iterator[_AnyT]: ...
54
+
55
+
56
+ class _HasBatch(Generic[_ItemT_contra], ABC):
57
+ _batch_factory: Callable[[], _Batch[_ItemT_contra]]
58
+
59
+ def __init__(self) -> None:
60
+ self._batch = self._batch_factory()
61
+
62
+ def put(self, item: _ItemT_contra) -> None:
63
+ self._batch.put(item)
64
+
65
+
66
+ class DaemonTask(ABC):
67
+ def __init__(self, **kwargs: Any) -> None:
68
+ super().__init__(**kwargs)
69
+ self._running = False
70
+ self._tasks: list[Task[None]] = []
71
+
72
+ async def start(self) -> None:
73
+ self._running = True
74
+ if not self._tasks:
75
+ self._tasks.append(create_task(self._run()))
76
+
77
+ async def stop(self) -> None:
78
+ self._running = False
79
+ for task in reversed(self._tasks):
80
+ if not task.done():
81
+ task.cancel()
82
+ self._tasks.clear()
83
+
84
+ async def __aenter__(self) -> None:
85
+ await self.start()
86
+
87
+ async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
88
+ await self.stop()
89
+
90
+ @abstractmethod
91
+ async def _run(self) -> None: ...
92
+
93
+
94
+ class BatchedCaller(DaemonTask, _HasBatch[_AnyT], Generic[_AnyT], ABC):
95
+ def __init__(self, *, sleep_seconds: float = 0.1, **kwargs: Any) -> None:
96
+ assert sleep_seconds > 0
97
+ super().__init__(**kwargs)
98
+ self._seconds = sleep_seconds
99
+
100
+ @abstractmethod
101
+ async def __call__(self) -> None: ...
102
+
103
+ async def _run(self) -> None:
104
+ while self._running:
105
+ self._tasks.append(create_task(sleep(self._seconds)))
106
+ await self._tasks[-1]
107
+ self._tasks.pop()
108
+ if self._batch.empty:
109
+ continue
110
+ self._tasks.append(create_task(self()))
111
+ await self._tasks[-1]
112
+ self._tasks.pop()
113
+ self._batch.clear()
114
+
115
+
116
+ class LastUpdatedAt:
117
+ def __init__(self) -> None:
118
+ self._cache: defaultdict[
119
+ type[models.Base],
120
+ LRUCache[int, datetime],
121
+ ] = defaultdict(lambda: LRUCache(maxsize=100))
122
+
123
+ def get(self, table: type[models.Base], id_: Optional[int] = None) -> Optional[datetime]:
124
+ if not (cache := self._cache.get(table)):
125
+ return None
126
+ if id_ is None:
127
+ return max(filter(bool, cache.values()), default=None)
128
+ return cache.get(id_)
129
+
130
+ def set(self, table: type[models.Base], id_: int) -> None:
131
+ self._cache[table][id_] = datetime.now(timezone.utc)
132
+
133
+
134
+ class PasswordResetToken(Token): ...
135
+
136
+
137
+ class AccessToken(Token): ...
138
+
139
+
140
+ class RefreshToken(Token): ...
141
+
142
+
143
+ class ApiKey(Token): ...
144
+
145
+
146
+ @dataclass(frozen=True)
147
+ class UserTokenAttributes(TokenAttributes):
148
+ user_role: enums.UserRole
149
+
150
+
151
+ @dataclass(frozen=True)
152
+ class RefreshTokenAttributes(UserTokenAttributes): ...
153
+
154
+
155
+ @dataclass(frozen=True)
156
+ class PasswordResetTokenAttributes(UserTokenAttributes): ...
157
+
158
+
159
+ @dataclass(frozen=True)
160
+ class AccessTokenAttributes(UserTokenAttributes):
161
+ refresh_token_id: RefreshTokenId
162
+
163
+
164
+ @dataclass(frozen=True)
165
+ class ApiKeyAttributes(UserTokenAttributes):
166
+ name: str
167
+ description: Optional[str] = None
168
+
169
+
170
+ class _DbId(str, ABC):
171
+ table: type[models.Base]
172
+
173
+ def __new__(cls, id_: int) -> _DbId:
174
+ assert isinstance(id_, int)
175
+ return super().__new__(cls, f"{cls.table.__name__}:{id_}")
176
+
177
+ def __int__(self) -> int:
178
+ return int(self.split(":")[1])
179
+
180
+ def __deepcopy__(self, memo: Any) -> _DbId:
181
+ return self
182
+
183
+
184
+ class TokenId(_DbId, ABC):
185
+ @classmethod
186
+ def parse(cls, value: str) -> Optional[TokenId]:
187
+ table_name, _, id_ = value.partition(":")
188
+ if not id_.isnumeric():
189
+ return None
190
+ for sub in cls.__subclasses__():
191
+ if sub.table.__name__ == table_name:
192
+ return sub(int(id_))
193
+ return None
194
+
195
+
196
+ @final
197
+ class PasswordResetTokenId(TokenId):
198
+ table = models.PasswordResetToken
199
+
200
+
201
+ @final
202
+ class AccessTokenId(TokenId):
203
+ table = models.AccessToken
204
+
205
+
206
+ @final
207
+ class RefreshTokenId(TokenId):
208
+ table = models.RefreshToken
209
+
210
+
211
+ @final
212
+ class ApiKeyId(TokenId):
213
+ table = models.ApiKey
214
+
215
+
216
+ @final
217
+ class UserId(_DbId):
218
+ table = models.User
219
+
220
+
221
+ @dataclass(frozen=True)
222
+ class UserClaimSet(ClaimSet): # type: ignore[override,unused-ignore]
223
+ subject: Optional[UserId] = None
224
+ attributes: Optional[UserTokenAttributes] = None
225
+
226
+
227
+ @dataclass(frozen=True)
228
+ class PasswordResetTokenClaims(UserClaimSet): # type: ignore[override,unused-ignore]
229
+ token_id: Optional[PasswordResetTokenId] = None
230
+ attributes: Optional[PasswordResetTokenAttributes] = None
231
+
232
+
233
+ @dataclass(frozen=True)
234
+ class AccessTokenClaims(UserClaimSet): # type: ignore[override,unused-ignore]
235
+ token_id: Optional[AccessTokenId] = None
236
+ attributes: Optional[AccessTokenAttributes] = None
237
+
238
+
239
+ @dataclass(frozen=True)
240
+ class RefreshTokenClaims(UserClaimSet): # type: ignore[override,unused-ignore]
241
+ token_id: Optional[RefreshTokenId] = None
242
+ attributes: Optional[RefreshTokenAttributes] = None
243
+
244
+
245
+ @dataclass(frozen=True)
246
+ class ApiKeyClaims(UserClaimSet): # type: ignore[override,unused-ignore]
247
+ token_id: Optional[ApiKeyId] = None
248
+ attributes: Optional[ApiKeyAttributes] = None
249
+
250
+
251
+ class CanRevokeTokens(Protocol):
252
+ async def revoke(self, *token_ids: TokenId) -> None: ...
253
+
254
+
255
+ class CanLogOutUser(Protocol):
256
+ async def log_out(self, user_id: UserId) -> None: ...
257
+
258
+
259
+ class TokenStore(CanReadToken, CanRevokeTokens, CanLogOutUser, Protocol):
260
+ async def create_password_reset_token(
261
+ self,
262
+ claims: PasswordResetTokenClaims,
263
+ ) -> tuple[PasswordResetToken, PasswordResetTokenId]: ...
264
+ async def create_access_token(
265
+ self,
266
+ claims: AccessTokenClaims,
267
+ ) -> tuple[AccessToken, AccessTokenId]: ...
268
+ async def create_refresh_token(
269
+ self,
270
+ claims: RefreshTokenClaims,
271
+ ) -> tuple[RefreshToken, RefreshTokenId]: ...
272
+ async def create_api_key(
273
+ self,
274
+ claims: ApiKeyClaims,
275
+ ) -> tuple[ApiKey, ApiKeyId]: ...
phoenix/services.py CHANGED
@@ -3,9 +3,10 @@ import os
3
3
  import signal
4
4
  import subprocess
5
5
  import sys
6
+ from collections.abc import Callable
6
7
  from pathlib import Path
7
8
  from time import sleep, time
8
- from typing import Callable, List, Optional
9
+ from typing import Optional
9
10
 
10
11
  import psutil
11
12
 
@@ -31,7 +32,7 @@ class Service:
31
32
  )
32
33
 
33
34
  @property
34
- def command(self) -> List[str]:
35
+ def command(self) -> list[str]:
35
36
  raise NotImplementedError(f"{type(self)} must define `command`")
36
37
 
37
38
  def start(self) -> psutil.Popen:
@@ -99,40 +100,46 @@ class AppService(Service):
99
100
 
100
101
  working_dir = SERVER_DIR
101
102
 
102
- # Internal references to the name / directory of the dataset(s)
103
- __primary_dataset_name: str
104
- __reference_dataset_name: Optional[str]
105
- __corpus_dataset_name: Optional[str]
103
+ # Internal references to the name / directory of the inferences(s)
104
+ __primary_inferences_name: str
105
+ __reference_inferences_name: Optional[str]
106
+ __corpus_inferences_name: Optional[str]
106
107
  __trace_dataset_name: Optional[str]
107
108
 
108
109
  def __init__(
109
110
  self,
111
+ database_url: str,
110
112
  export_path: Path,
111
113
  host: str,
112
114
  port: int,
113
115
  root_path: str,
114
- primary_dataset_name: str,
116
+ primary_inferences_name: str,
115
117
  umap_params: str,
116
- reference_dataset_name: Optional[str],
117
- corpus_dataset_name: Optional[str],
118
+ reference_inferences_name: Optional[str],
119
+ corpus_inferences_name: Optional[str],
118
120
  trace_dataset_name: Optional[str],
121
+ enable_websockets: bool,
119
122
  ):
123
+ self.database_url = database_url
120
124
  self.export_path = export_path
121
125
  self.host = host
122
126
  self.port = port
123
127
  self.root_path = root_path # TODO(mikeldking): Add support for root_path
124
- self.__primary_dataset_name = primary_dataset_name
128
+ self.__primary_inferences_name = primary_inferences_name
125
129
  self.__umap_params = umap_params
126
- self.__reference_dataset_name = reference_dataset_name
127
- self.__corpus_dataset_name = corpus_dataset_name
130
+ self.__reference_inferences_name = reference_inferences_name
131
+ self.__corpus_inferences_name = corpus_inferences_name
128
132
  self.__trace_dataset_name = trace_dataset_name
133
+ self.enable_websockets = enable_websockets
129
134
  super().__init__()
130
135
 
131
136
  @property
132
- def command(self) -> List[str]:
137
+ def command(self) -> list[str]:
133
138
  command = [
134
139
  sys.executable,
135
140
  "main.py",
141
+ "--database-url",
142
+ self.database_url,
136
143
  "--export_path",
137
144
  str(self.export_path),
138
145
  "--host",
@@ -143,13 +150,15 @@ class AppService(Service):
143
150
  self.__umap_params,
144
151
  "datasets",
145
152
  "--primary",
146
- str(self.__primary_dataset_name),
153
+ str(self.__primary_inferences_name),
147
154
  ]
148
- if self.__reference_dataset_name is not None:
149
- command.extend(["--reference", str(self.__reference_dataset_name)])
150
- if self.__corpus_dataset_name is not None:
151
- command.extend(["--corpus", str(self.__corpus_dataset_name)])
155
+ if self.__reference_inferences_name is not None:
156
+ command.extend(["--reference", str(self.__reference_inferences_name)])
157
+ if self.__corpus_inferences_name is not None:
158
+ command.extend(["--corpus", str(self.__corpus_inferences_name)])
152
159
  if self.__trace_dataset_name is not None:
153
160
  command.extend(["--trace", str(self.__trace_dataset_name)])
161
+ if self.enable_websockets:
162
+ command.append("--enable-websockets")
154
163
  logger.info(f"command: {' '.join(command)}")
155
164
  return command