arize-phoenix 3.16.1__py3-none-any.whl → 7.7.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.

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.1.dist-info/METADATA +261 -0
  2. arize_phoenix-7.7.1.dist-info/RECORD +345 -0
  3. {arize_phoenix-3.16.1.dist-info → arize_phoenix-7.7.1.dist-info}/WHEEL +1 -1
  4. arize_phoenix-7.7.1.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.1.dist-info}/licenses/IP_NOTICE +0 -0
  335. {arize_phoenix-3.16.1.dist-info → arize_phoenix-7.7.1.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
@@ -1,52 +1,53 @@
1
- from dataclasses import dataclass
2
- from typing import Any, Mapping, Optional
3
-
4
- DEFAULT_MIN_DIST = 0.0
5
- DEFAULT_N_NEIGHBORS = 30
6
- DEFAULT_N_SAMPLES = 500
7
-
8
- MIN_NEIGHBORS = 5
9
- MAX_NEIGHBORS = 100
10
- MIN_SAMPLES = 1
11
- MAX_SAMPLES = 1000
12
- MIN_MIN_DIST = 0.0
13
- MAX_MIN_DIST = 0.99
14
-
15
-
16
- @dataclass
17
- class UMAPParameters:
18
- min_dist: float = DEFAULT_MIN_DIST
19
- n_neighbors: int = DEFAULT_N_NEIGHBORS
20
- n_samples: int = DEFAULT_N_SAMPLES
21
-
22
- def __post_init__(self) -> None:
23
- if not isinstance(self.min_dist, float) or not (
24
- MIN_MIN_DIST <= self.min_dist <= MAX_MIN_DIST
25
- ):
26
- raise ValueError(
27
- f"minDist must be float type, and between {MIN_MIN_DIST} and {MAX_MIN_DIST}"
28
- )
29
-
30
- if not isinstance(self.n_neighbors, int) or not (
31
- MIN_NEIGHBORS <= self.n_neighbors <= MAX_NEIGHBORS
32
- ):
33
- raise ValueError(
34
- f"nNeighbors must be int type, and between {MIN_NEIGHBORS} and {MAX_NEIGHBORS}"
35
- )
36
-
37
- if not isinstance(self.n_samples, int) or not (
38
- MIN_SAMPLES <= self.n_samples <= MAX_SAMPLES
39
- ):
40
- raise ValueError(
41
- f"nSamples must be int type, and between {MIN_SAMPLES} and {MAX_SAMPLES}"
42
- )
43
-
44
-
45
- def get_umap_parameters(default_umap_parameters: Optional[Mapping[str, Any]]) -> UMAPParameters:
46
- if not default_umap_parameters:
47
- return UMAPParameters()
48
- return UMAPParameters(
49
- min_dist=float(default_umap_parameters.get("min_dist", DEFAULT_MIN_DIST)),
50
- n_neighbors=int(default_umap_parameters.get("n_neighbors", DEFAULT_N_NEIGHBORS)),
51
- n_samples=int(default_umap_parameters.get("n_samples", DEFAULT_N_SAMPLES)),
52
- )
1
+ from collections.abc import Mapping
2
+ from dataclasses import dataclass
3
+ from typing import Any, Optional
4
+
5
+ DEFAULT_MIN_DIST = 0.0
6
+ DEFAULT_N_NEIGHBORS = 30
7
+ DEFAULT_N_SAMPLES = 500
8
+
9
+ MIN_NEIGHBORS = 5
10
+ MAX_NEIGHBORS = 100
11
+ MIN_SAMPLES = 1
12
+ MAX_SAMPLES = 1000
13
+ MIN_MIN_DIST = 0.0
14
+ MAX_MIN_DIST = 0.99
15
+
16
+
17
+ @dataclass
18
+ class UMAPParameters:
19
+ min_dist: float = DEFAULT_MIN_DIST
20
+ n_neighbors: int = DEFAULT_N_NEIGHBORS
21
+ n_samples: int = DEFAULT_N_SAMPLES
22
+
23
+ def __post_init__(self) -> None:
24
+ if not isinstance(self.min_dist, float) or not (
25
+ MIN_MIN_DIST <= self.min_dist <= MAX_MIN_DIST
26
+ ):
27
+ raise ValueError(
28
+ f"minDist must be float type, and between {MIN_MIN_DIST} and {MAX_MIN_DIST}"
29
+ )
30
+
31
+ if not isinstance(self.n_neighbors, int) or not (
32
+ MIN_NEIGHBORS <= self.n_neighbors <= MAX_NEIGHBORS
33
+ ):
34
+ raise ValueError(
35
+ f"nNeighbors must be int type, and between {MIN_NEIGHBORS} and {MAX_NEIGHBORS}"
36
+ )
37
+
38
+ if not isinstance(self.n_samples, int) or not (
39
+ MIN_SAMPLES <= self.n_samples <= MAX_SAMPLES
40
+ ):
41
+ raise ValueError(
42
+ f"nSamples must be int type, and between {MIN_SAMPLES} and {MAX_SAMPLES}"
43
+ )
44
+
45
+
46
+ def get_umap_parameters(default_umap_parameters: Optional[Mapping[str, Any]]) -> UMAPParameters:
47
+ if not default_umap_parameters:
48
+ return UMAPParameters()
49
+ return UMAPParameters(
50
+ min_dist=float(default_umap_parameters.get("min_dist", DEFAULT_MIN_DIST)),
51
+ n_neighbors=int(default_umap_parameters.get("n_neighbors", DEFAULT_N_NEIGHBORS)),
52
+ n_samples=int(default_umap_parameters.get("n_samples", DEFAULT_N_SAMPLES)),
53
+ )
@@ -0,0 +1,28 @@
1
+ # Permission Matrix for GraphQL API
2
+
3
+ ## Mutations
4
+
5
+ | Action | Admin | Member |
6
+ |:-----------------------------|:-----:|:------:|
7
+ | Create User | Yes | No |
8
+ | Delete User | Yes | No |
9
+ | Change Own Password | Yes | Yes |
10
+ | Change Other's Password | Yes | No |
11
+ | Change Own Username | Yes | Yes |
12
+ | Change Other's Username | Yes | No |
13
+ | Change Own Email | No | No |
14
+ | Change Other's Email | No | No |
15
+ | Create System API Keys | Yes | No |
16
+ | Delete System API Keys | Yes | No |
17
+ | Create Own User API Keys | Yes | Yes |
18
+ | Delete Own User API Keys | Yes | Yes |
19
+ | Delete Other's User API Keys | Yes | No |
20
+
21
+ ## Queries
22
+
23
+ | Action | Admin | Member |
24
+ |:-------------------------------------|:-----:|:------:|
25
+ | List All System API Keys | Yes | No |
26
+ | List All User API Keys | Yes | No |
27
+ | List All Users | Yes | No |
28
+ | Fetch Other User's Info, e.g. emails | Yes | No |
@@ -0,0 +1,44 @@
1
+ from abc import ABC
2
+ from typing import Any
3
+
4
+ from strawberry import Info
5
+ from strawberry.permission import BasePermission
6
+
7
+ from phoenix.server.api.exceptions import Unauthorized
8
+ from phoenix.server.bearer_auth import PhoenixUser
9
+
10
+
11
+ class Authorization(BasePermission, ABC):
12
+ def on_unauthorized(self) -> None:
13
+ raise Unauthorized(self.message)
14
+
15
+
16
+ class IsNotReadOnly(Authorization):
17
+ message = "Application is read-only"
18
+
19
+ def has_permission(self, source: Any, info: Info, **kwargs: Any) -> bool:
20
+ return not info.context.read_only
21
+
22
+
23
+ class IsLocked(Authorization):
24
+ """
25
+ Disables mutations and subscriptions that create or update data but allows
26
+ queries and delete mutations.
27
+ """
28
+
29
+ message = "Operations that write or modify data are locked"
30
+
31
+ def has_permission(self, source: Any, info: Info, **kwargs: Any) -> bool:
32
+ return not info.context.locked
33
+
34
+
35
+ MSG_ADMIN_ONLY = "Only admin can perform this action"
36
+
37
+
38
+ class IsAdmin(Authorization):
39
+ message = MSG_ADMIN_ONLY
40
+
41
+ def has_permission(self, source: Any, info: Info, **kwargs: Any) -> bool:
42
+ if not info.context.auth_enabled:
43
+ return False
44
+ return isinstance((user := info.context.user), PhoenixUser) and user.is_admin
@@ -1,20 +1,163 @@
1
+ from asyncio import get_running_loop
1
2
  from dataclasses import dataclass
3
+ from functools import cached_property, partial
2
4
  from pathlib import Path
3
- from typing import Optional, Union
5
+ from typing import Any, Optional, cast
4
6
 
5
- from starlette.requests import Request
6
- from starlette.responses import Response
7
- from starlette.websockets import WebSocket
7
+ from starlette.requests import Request as StarletteRequest
8
+ from starlette.responses import Response as StarletteResponse
9
+ from strawberry.fastapi import BaseContext
8
10
 
11
+ from phoenix.auth import (
12
+ compute_password_hash,
13
+ )
9
14
  from phoenix.core.model_schema import Model
10
- from phoenix.core.traces import Traces
15
+ from phoenix.db import models
16
+ from phoenix.server.api.dataloaders import (
17
+ AnnotationSummaryDataLoader,
18
+ AverageExperimentRunLatencyDataLoader,
19
+ CacheForDataLoaders,
20
+ DatasetExampleRevisionsDataLoader,
21
+ DatasetExampleSpansDataLoader,
22
+ DocumentEvaluationsDataLoader,
23
+ DocumentEvaluationSummaryDataLoader,
24
+ DocumentRetrievalMetricsDataLoader,
25
+ ExperimentAnnotationSummaryDataLoader,
26
+ ExperimentErrorRatesDataLoader,
27
+ ExperimentRunAnnotations,
28
+ ExperimentRunCountsDataLoader,
29
+ ExperimentSequenceNumberDataLoader,
30
+ LatencyMsQuantileDataLoader,
31
+ MinStartOrMaxEndTimeDataLoader,
32
+ ProjectByNameDataLoader,
33
+ RecordCountDataLoader,
34
+ SessionIODataLoader,
35
+ SessionNumTracesDataLoader,
36
+ SessionNumTracesWithErrorDataLoader,
37
+ SessionTokenUsagesDataLoader,
38
+ SessionTraceLatencyMsQuantileDataLoader,
39
+ SpanAnnotationsDataLoader,
40
+ SpanDatasetExamplesDataLoader,
41
+ SpanDescendantsDataLoader,
42
+ SpanProjectsDataLoader,
43
+ TokenCountDataLoader,
44
+ TraceByTraceIdsDataLoader,
45
+ TraceRootSpansDataLoader,
46
+ UserRolesDataLoader,
47
+ UsersDataLoader,
48
+ )
49
+ from phoenix.server.bearer_auth import PhoenixUser
50
+ from phoenix.server.dml_event import DmlEvent
51
+ from phoenix.server.types import (
52
+ CanGetLastUpdatedAt,
53
+ CanPutItem,
54
+ DbSessionFactory,
55
+ TokenStore,
56
+ UserId,
57
+ )
11
58
 
12
59
 
13
60
  @dataclass
14
- class Context:
15
- request: Union[Request, WebSocket]
16
- response: Optional[Response]
61
+ class DataLoaders:
62
+ average_experiment_run_latency: AverageExperimentRunLatencyDataLoader
63
+ dataset_example_revisions: DatasetExampleRevisionsDataLoader
64
+ dataset_example_spans: DatasetExampleSpansDataLoader
65
+ document_evaluation_summaries: DocumentEvaluationSummaryDataLoader
66
+ document_evaluations: DocumentEvaluationsDataLoader
67
+ document_retrieval_metrics: DocumentRetrievalMetricsDataLoader
68
+ annotation_summaries: AnnotationSummaryDataLoader
69
+ experiment_annotation_summaries: ExperimentAnnotationSummaryDataLoader
70
+ experiment_error_rates: ExperimentErrorRatesDataLoader
71
+ experiment_run_annotations: ExperimentRunAnnotations
72
+ experiment_run_counts: ExperimentRunCountsDataLoader
73
+ experiment_sequence_number: ExperimentSequenceNumberDataLoader
74
+ latency_ms_quantile: LatencyMsQuantileDataLoader
75
+ min_start_or_max_end_times: MinStartOrMaxEndTimeDataLoader
76
+ record_counts: RecordCountDataLoader
77
+ session_first_inputs: SessionIODataLoader
78
+ session_last_outputs: SessionIODataLoader
79
+ session_num_traces: SessionNumTracesDataLoader
80
+ session_num_traces_with_error: SessionNumTracesWithErrorDataLoader
81
+ session_token_usages: SessionTokenUsagesDataLoader
82
+ session_trace_latency_ms_quantile: SessionTraceLatencyMsQuantileDataLoader
83
+ span_annotations: SpanAnnotationsDataLoader
84
+ span_dataset_examples: SpanDatasetExamplesDataLoader
85
+ span_descendants: SpanDescendantsDataLoader
86
+ span_projects: SpanProjectsDataLoader
87
+ token_counts: TokenCountDataLoader
88
+ trace_by_trace_ids: TraceByTraceIdsDataLoader
89
+ trace_root_spans: TraceRootSpansDataLoader
90
+ project_by_name: ProjectByNameDataLoader
91
+ users: UsersDataLoader
92
+ user_roles: UserRolesDataLoader
93
+
94
+
95
+ class _NoOp:
96
+ def get(self, *args: Any, **kwargs: Any) -> Any: ...
97
+ def put(self, *args: Any, **kwargs: Any) -> Any: ...
98
+
99
+
100
+ @dataclass
101
+ class Context(BaseContext):
102
+ db: DbSessionFactory
103
+ data_loaders: DataLoaders
104
+ cache_for_dataloaders: Optional[CacheForDataLoaders]
17
105
  model: Model
18
106
  export_path: Path
107
+ last_updated_at: CanGetLastUpdatedAt = _NoOp()
108
+ event_queue: CanPutItem[DmlEvent] = _NoOp()
19
109
  corpus: Optional[Model] = None
20
- traces: Optional[Traces] = None
110
+ read_only: bool = False
111
+ locked: bool = False
112
+ auth_enabled: bool = False
113
+ secret: Optional[str] = None
114
+ token_store: Optional[TokenStore] = None
115
+
116
+ def get_secret(self) -> str:
117
+ """A type-safe way to get the application secret. Throws an error if the secret is not set.
118
+
119
+ Returns:
120
+ str: the phoenix secret
121
+ """
122
+ if self.secret is None:
123
+ raise ValueError(
124
+ "Application secret not set."
125
+ " Please set the PHOENIX_SECRET environment variable and re-deploy the application."
126
+ )
127
+ return self.secret
128
+
129
+ def get_request(self) -> StarletteRequest:
130
+ """
131
+ A type-safe way to get the request object. Throws an error if the request is not set.
132
+ """
133
+ if not isinstance(request := self.request, StarletteRequest):
134
+ raise ValueError("no request is set")
135
+ return request
136
+
137
+ def get_response(self) -> StarletteResponse:
138
+ """
139
+ A type-safe way to get the response object. Throws an error if the response is not set.
140
+ """
141
+ if (response := self.response) is None:
142
+ raise ValueError("no response is set")
143
+ return response
144
+
145
+ async def is_valid_password(self, password: str, user: models.User) -> bool:
146
+ return (
147
+ (hash_ := user.password_hash) is not None
148
+ and (salt := user.password_salt) is not None
149
+ and hash_ == await self.hash_password(password, salt)
150
+ )
151
+
152
+ @staticmethod
153
+ async def hash_password(password: str, salt: bytes) -> bytes:
154
+ compute = partial(compute_password_hash, password=password, salt=salt)
155
+ return await get_running_loop().run_in_executor(None, compute)
156
+
157
+ async def log_out(self, user_id: int) -> None:
158
+ assert self.token_store is not None
159
+ await self.token_store.log_out(UserId(user_id))
160
+
161
+ @cached_property
162
+ def user(self) -> PhoenixUser:
163
+ return cast(PhoenixUser, self.get_request().user)
@@ -0,0 +1,91 @@
1
+ from dataclasses import dataclass, field
2
+
3
+ from .annotation_summaries import AnnotationSummaryCache, AnnotationSummaryDataLoader
4
+ from .average_experiment_run_latency import AverageExperimentRunLatencyDataLoader
5
+ from .dataset_example_revisions import DatasetExampleRevisionsDataLoader
6
+ from .dataset_example_spans import DatasetExampleSpansDataLoader
7
+ from .document_evaluation_summaries import (
8
+ DocumentEvaluationSummaryCache,
9
+ DocumentEvaluationSummaryDataLoader,
10
+ )
11
+ from .document_evaluations import DocumentEvaluationsDataLoader
12
+ from .document_retrieval_metrics import DocumentRetrievalMetricsDataLoader
13
+ from .experiment_annotation_summaries import ExperimentAnnotationSummaryDataLoader
14
+ from .experiment_error_rates import ExperimentErrorRatesDataLoader
15
+ from .experiment_run_annotations import ExperimentRunAnnotations
16
+ from .experiment_run_counts import ExperimentRunCountsDataLoader
17
+ from .experiment_sequence_number import ExperimentSequenceNumberDataLoader
18
+ from .latency_ms_quantile import LatencyMsQuantileCache, LatencyMsQuantileDataLoader
19
+ from .min_start_or_max_end_times import MinStartOrMaxEndTimeCache, MinStartOrMaxEndTimeDataLoader
20
+ from .project_by_name import ProjectByNameDataLoader
21
+ from .record_counts import RecordCountCache, RecordCountDataLoader
22
+ from .session_io import SessionIODataLoader
23
+ from .session_num_traces import SessionNumTracesDataLoader
24
+ from .session_num_traces_with_error import SessionNumTracesWithErrorDataLoader
25
+ from .session_token_usages import SessionTokenUsagesDataLoader
26
+ from .session_trace_latency_ms_quantile import SessionTraceLatencyMsQuantileDataLoader
27
+ from .span_annotations import SpanAnnotationsDataLoader
28
+ from .span_dataset_examples import SpanDatasetExamplesDataLoader
29
+ from .span_descendants import SpanDescendantsDataLoader
30
+ from .span_projects import SpanProjectsDataLoader
31
+ from .token_counts import TokenCountCache, TokenCountDataLoader
32
+ from .trace_by_trace_ids import TraceByTraceIdsDataLoader
33
+ from .trace_root_spans import TraceRootSpansDataLoader
34
+ from .user_roles import UserRolesDataLoader
35
+ from .users import UsersDataLoader
36
+
37
+ __all__ = [
38
+ "CacheForDataLoaders",
39
+ "AverageExperimentRunLatencyDataLoader",
40
+ "DatasetExampleRevisionsDataLoader",
41
+ "DatasetExampleSpansDataLoader",
42
+ "DocumentEvaluationSummaryDataLoader",
43
+ "DocumentEvaluationsDataLoader",
44
+ "DocumentRetrievalMetricsDataLoader",
45
+ "AnnotationSummaryDataLoader",
46
+ "ExperimentAnnotationSummaryDataLoader",
47
+ "ExperimentErrorRatesDataLoader",
48
+ "ExperimentRunAnnotations",
49
+ "ExperimentRunCountsDataLoader",
50
+ "ExperimentSequenceNumberDataLoader",
51
+ "LatencyMsQuantileDataLoader",
52
+ "MinStartOrMaxEndTimeDataLoader",
53
+ "RecordCountDataLoader",
54
+ "SessionIODataLoader",
55
+ "SessionNumTracesDataLoader",
56
+ "SessionNumTracesWithErrorDataLoader",
57
+ "SessionTokenUsagesDataLoader",
58
+ "SessionTraceLatencyMsQuantileDataLoader",
59
+ "SpanDatasetExamplesDataLoader",
60
+ "SpanDescendantsDataLoader",
61
+ "SpanProjectsDataLoader",
62
+ "TokenCountDataLoader",
63
+ "TraceByTraceIdsDataLoader",
64
+ "TraceRootSpansDataLoader",
65
+ "ProjectByNameDataLoader",
66
+ "SpanAnnotationsDataLoader",
67
+ "UsersDataLoader",
68
+ "UserRolesDataLoader",
69
+ ]
70
+
71
+
72
+ @dataclass(frozen=True)
73
+ class CacheForDataLoaders:
74
+ document_evaluation_summary: DocumentEvaluationSummaryCache = field(
75
+ default_factory=DocumentEvaluationSummaryCache,
76
+ )
77
+ annotation_summary: AnnotationSummaryCache = field(
78
+ default_factory=AnnotationSummaryCache,
79
+ )
80
+ latency_ms_quantile: LatencyMsQuantileCache = field(
81
+ default_factory=LatencyMsQuantileCache,
82
+ )
83
+ min_start_or_max_end_time: MinStartOrMaxEndTimeCache = field(
84
+ default_factory=MinStartOrMaxEndTimeCache,
85
+ )
86
+ record_count: RecordCountCache = field(
87
+ default_factory=RecordCountCache,
88
+ )
89
+ token_count: TokenCountCache = field(
90
+ default_factory=TokenCountCache,
91
+ )
@@ -0,0 +1,139 @@
1
+ from collections import defaultdict
2
+ from datetime import datetime
3
+ from typing import Any, Literal, Optional
4
+
5
+ import pandas as pd
6
+ from aioitertools.itertools import groupby
7
+ from cachetools import LFUCache, TTLCache
8
+ from sqlalchemy import Select, func, or_, select
9
+ from strawberry.dataloader import AbstractCache, DataLoader
10
+ from typing_extensions import TypeAlias, assert_never
11
+
12
+ from phoenix.db import models
13
+ from phoenix.server.api.dataloaders.cache import TwoTierCache
14
+ from phoenix.server.api.input_types.TimeRange import TimeRange
15
+ from phoenix.server.api.types.AnnotationSummary import AnnotationSummary
16
+ from phoenix.server.types import DbSessionFactory
17
+ from phoenix.trace.dsl import SpanFilter
18
+
19
+ Kind: TypeAlias = Literal["span", "trace"]
20
+ ProjectRowId: TypeAlias = int
21
+ TimeInterval: TypeAlias = tuple[Optional[datetime], Optional[datetime]]
22
+ FilterCondition: TypeAlias = Optional[str]
23
+ AnnotationName: TypeAlias = str
24
+
25
+ Segment: TypeAlias = tuple[Kind, ProjectRowId, TimeInterval, FilterCondition]
26
+ Param: TypeAlias = AnnotationName
27
+
28
+ Key: TypeAlias = tuple[Kind, ProjectRowId, Optional[TimeRange], FilterCondition, AnnotationName]
29
+ Result: TypeAlias = Optional[AnnotationSummary]
30
+ ResultPosition: TypeAlias = int
31
+ DEFAULT_VALUE: Result = None
32
+
33
+
34
+ def _cache_key_fn(key: Key) -> tuple[Segment, Param]:
35
+ kind, project_rowid, time_range, filter_condition, eval_name = key
36
+ interval = (
37
+ (time_range.start, time_range.end) if isinstance(time_range, TimeRange) else (None, None)
38
+ )
39
+ return (kind, project_rowid, interval, filter_condition), eval_name
40
+
41
+
42
+ _Section: TypeAlias = tuple[ProjectRowId, AnnotationName, Kind]
43
+ _SubKey: TypeAlias = tuple[TimeInterval, FilterCondition]
44
+
45
+
46
+ class AnnotationSummaryCache(
47
+ TwoTierCache[Key, Result, _Section, _SubKey],
48
+ ):
49
+ def __init__(self) -> None:
50
+ super().__init__(
51
+ # TTL=3600 (1-hour) because time intervals are always moving forward, but
52
+ # interval endpoints are rounded down to the hour by the UI, so anything
53
+ # older than an hour most likely won't be a cache-hit anyway.
54
+ main_cache=TTLCache(maxsize=64 * 32 * 2, ttl=3600),
55
+ sub_cache_factory=lambda: LFUCache(maxsize=2 * 2),
56
+ )
57
+
58
+ def invalidate_project(self, project_rowid: ProjectRowId) -> None:
59
+ for section in self._cache.keys():
60
+ if section[0] == project_rowid:
61
+ del self._cache[section]
62
+
63
+ def _cache_key(self, key: Key) -> tuple[_Section, _SubKey]:
64
+ (kind, project_rowid, interval, filter_condition), annotation_name = _cache_key_fn(key)
65
+ return (project_rowid, annotation_name, kind), (interval, filter_condition)
66
+
67
+
68
+ class AnnotationSummaryDataLoader(DataLoader[Key, Result]):
69
+ def __init__(
70
+ self,
71
+ db: DbSessionFactory,
72
+ cache_map: Optional[AbstractCache[Key, Result]] = None,
73
+ ) -> None:
74
+ super().__init__(
75
+ load_fn=self._load_fn,
76
+ cache_key_fn=_cache_key_fn,
77
+ cache_map=cache_map,
78
+ )
79
+ self._db = db
80
+
81
+ async def _load_fn(self, keys: list[Key]) -> list[Result]:
82
+ results: list[Result] = [DEFAULT_VALUE] * len(keys)
83
+ arguments: defaultdict[
84
+ Segment,
85
+ defaultdict[Param, list[ResultPosition]],
86
+ ] = defaultdict(lambda: defaultdict(list))
87
+ for position, key in enumerate(keys):
88
+ segment, param = _cache_key_fn(key)
89
+ arguments[segment][param].append(position)
90
+ for segment, params in arguments.items():
91
+ stmt = _get_stmt(segment, *params.keys())
92
+ async with self._db() as session:
93
+ data = await session.stream(stmt)
94
+ async for annotation_name, group in groupby(data, lambda row: row.name):
95
+ summary = AnnotationSummary(pd.DataFrame(group))
96
+ for position in params[annotation_name]:
97
+ results[position] = summary
98
+ return results
99
+
100
+
101
+ def _get_stmt(
102
+ segment: Segment,
103
+ *annotation_names: Param,
104
+ ) -> Select[Any]:
105
+ kind, project_rowid, (start_time, end_time), filter_condition = segment
106
+ stmt = select()
107
+ if kind == "span":
108
+ msa = models.SpanAnnotation
109
+ name_column, label_column, score_column = msa.name, msa.label, msa.score
110
+ time_column = models.Span.start_time
111
+ stmt = stmt.join(models.Span).join_from(models.Span, models.Trace)
112
+ if filter_condition:
113
+ sf = SpanFilter(filter_condition)
114
+ stmt = sf(stmt)
115
+ elif kind == "trace":
116
+ mta = models.TraceAnnotation
117
+ name_column, label_column, score_column = mta.name, mta.label, mta.score
118
+ time_column = models.Trace.start_time
119
+ stmt = stmt.join(models.Trace)
120
+ else:
121
+ assert_never(kind)
122
+ stmt = stmt.add_columns(
123
+ name_column,
124
+ label_column,
125
+ func.count().label("record_count"),
126
+ func.count(label_column).label("label_count"),
127
+ func.count(score_column).label("score_count"),
128
+ func.sum(score_column).label("score_sum"),
129
+ )
130
+ stmt = stmt.group_by(name_column, label_column)
131
+ stmt = stmt.order_by(name_column, label_column)
132
+ stmt = stmt.where(models.Trace.project_rowid == project_rowid)
133
+ stmt = stmt.where(or_(score_column.is_not(None), label_column.is_not(None)))
134
+ stmt = stmt.where(name_column.in_(annotation_names))
135
+ if start_time:
136
+ stmt = stmt.where(start_time <= time_column)
137
+ if end_time:
138
+ stmt = stmt.where(time_column < end_time)
139
+ return stmt
@@ -0,0 +1,54 @@
1
+ from typing import Optional
2
+
3
+ from sqlalchemy import func, select
4
+ from strawberry.dataloader import DataLoader
5
+ from typing_extensions import TypeAlias
6
+
7
+ from phoenix.db import models
8
+ from phoenix.server.types import DbSessionFactory
9
+
10
+ ExperimentID: TypeAlias = int
11
+ RunLatency: TypeAlias = Optional[float]
12
+ Key: TypeAlias = ExperimentID
13
+ Result: TypeAlias = RunLatency
14
+
15
+
16
+ class AverageExperimentRunLatencyDataLoader(DataLoader[Key, Result]):
17
+ def __init__(
18
+ self,
19
+ db: DbSessionFactory,
20
+ ) -> None:
21
+ super().__init__(load_fn=self._load_fn)
22
+ self._db = db
23
+
24
+ async def _load_fn(self, keys: list[Key]) -> list[Result]:
25
+ experiment_ids = keys
26
+ resolved_experiment_ids = (
27
+ select(models.Experiment.id)
28
+ .where(models.Experiment.id.in_(set(experiment_ids)))
29
+ .subquery()
30
+ )
31
+ query = (
32
+ select(
33
+ resolved_experiment_ids.c.id,
34
+ func.avg(
35
+ func.extract("epoch", models.ExperimentRun.end_time)
36
+ - func.extract("epoch", models.ExperimentRun.start_time)
37
+ ),
38
+ )
39
+ .outerjoin_from(
40
+ from_=resolved_experiment_ids,
41
+ target=models.ExperimentRun,
42
+ onclause=resolved_experiment_ids.c.id == models.ExperimentRun.experiment_id,
43
+ )
44
+ .group_by(resolved_experiment_ids.c.id)
45
+ )
46
+ async with self._db() as session:
47
+ avg_latencies = {
48
+ experiment_id: avg_latency
49
+ async for experiment_id, avg_latency in await session.stream(query)
50
+ }
51
+ return [
52
+ avg_latencies.get(experiment_id, ValueError(f"Unknown experiment: {experiment_id}"))
53
+ for experiment_id in keys
54
+ ]
@@ -0,0 +1,3 @@
1
+ from phoenix.server.api.dataloaders.cache.two_tier_cache import TwoTierCache
2
+
3
+ __all__ = ("TwoTierCache",)