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.
- arize_phoenix-7.7.0.dist-info/METADATA +261 -0
- arize_phoenix-7.7.0.dist-info/RECORD +345 -0
- {arize_phoenix-3.16.0.dist-info → arize_phoenix-7.7.0.dist-info}/WHEEL +1 -1
- arize_phoenix-7.7.0.dist-info/entry_points.txt +3 -0
- phoenix/__init__.py +86 -14
- phoenix/auth.py +309 -0
- phoenix/config.py +675 -45
- phoenix/core/model.py +32 -30
- phoenix/core/model_schema.py +102 -109
- phoenix/core/model_schema_adapter.py +48 -45
- phoenix/datetime_utils.py +24 -3
- phoenix/db/README.md +54 -0
- phoenix/db/__init__.py +4 -0
- phoenix/db/alembic.ini +85 -0
- phoenix/db/bulk_inserter.py +294 -0
- phoenix/db/engines.py +208 -0
- phoenix/db/enums.py +20 -0
- phoenix/db/facilitator.py +113 -0
- phoenix/db/helpers.py +159 -0
- phoenix/db/insertion/constants.py +2 -0
- phoenix/db/insertion/dataset.py +227 -0
- phoenix/db/insertion/document_annotation.py +171 -0
- phoenix/db/insertion/evaluation.py +191 -0
- phoenix/db/insertion/helpers.py +98 -0
- phoenix/db/insertion/span.py +193 -0
- phoenix/db/insertion/span_annotation.py +158 -0
- phoenix/db/insertion/trace_annotation.py +158 -0
- phoenix/db/insertion/types.py +256 -0
- phoenix/db/migrate.py +86 -0
- phoenix/db/migrations/data_migration_scripts/populate_project_sessions.py +199 -0
- phoenix/db/migrations/env.py +114 -0
- phoenix/db/migrations/script.py.mako +26 -0
- phoenix/db/migrations/versions/10460e46d750_datasets.py +317 -0
- phoenix/db/migrations/versions/3be8647b87d8_add_token_columns_to_spans_table.py +126 -0
- phoenix/db/migrations/versions/4ded9e43755f_create_project_sessions_table.py +66 -0
- phoenix/db/migrations/versions/cd164e83824f_users_and_tokens.py +157 -0
- phoenix/db/migrations/versions/cf03bd6bae1d_init.py +280 -0
- phoenix/db/models.py +807 -0
- phoenix/exceptions.py +5 -1
- phoenix/experiments/__init__.py +6 -0
- phoenix/experiments/evaluators/__init__.py +29 -0
- phoenix/experiments/evaluators/base.py +158 -0
- phoenix/experiments/evaluators/code_evaluators.py +184 -0
- phoenix/experiments/evaluators/llm_evaluators.py +473 -0
- phoenix/experiments/evaluators/utils.py +236 -0
- phoenix/experiments/functions.py +772 -0
- phoenix/experiments/tracing.py +86 -0
- phoenix/experiments/types.py +726 -0
- phoenix/experiments/utils.py +25 -0
- phoenix/inferences/__init__.py +0 -0
- phoenix/{datasets → inferences}/errors.py +6 -5
- phoenix/{datasets → inferences}/fixtures.py +49 -42
- phoenix/{datasets/dataset.py → inferences/inferences.py} +121 -105
- phoenix/{datasets → inferences}/schema.py +11 -11
- phoenix/{datasets → inferences}/validation.py +13 -14
- phoenix/logging/__init__.py +3 -0
- phoenix/logging/_config.py +90 -0
- phoenix/logging/_filter.py +6 -0
- phoenix/logging/_formatter.py +69 -0
- phoenix/metrics/__init__.py +5 -4
- phoenix/metrics/binning.py +4 -3
- phoenix/metrics/metrics.py +2 -1
- phoenix/metrics/mixins.py +7 -6
- phoenix/metrics/retrieval_metrics.py +2 -1
- phoenix/metrics/timeseries.py +5 -4
- phoenix/metrics/wrappers.py +9 -3
- phoenix/pointcloud/clustering.py +5 -5
- phoenix/pointcloud/pointcloud.py +7 -5
- phoenix/pointcloud/projectors.py +5 -6
- phoenix/pointcloud/umap_parameters.py +53 -52
- phoenix/server/api/README.md +28 -0
- phoenix/server/api/auth.py +44 -0
- phoenix/server/api/context.py +152 -9
- phoenix/server/api/dataloaders/__init__.py +91 -0
- phoenix/server/api/dataloaders/annotation_summaries.py +139 -0
- phoenix/server/api/dataloaders/average_experiment_run_latency.py +54 -0
- phoenix/server/api/dataloaders/cache/__init__.py +3 -0
- phoenix/server/api/dataloaders/cache/two_tier_cache.py +68 -0
- phoenix/server/api/dataloaders/dataset_example_revisions.py +131 -0
- phoenix/server/api/dataloaders/dataset_example_spans.py +38 -0
- phoenix/server/api/dataloaders/document_evaluation_summaries.py +144 -0
- phoenix/server/api/dataloaders/document_evaluations.py +31 -0
- phoenix/server/api/dataloaders/document_retrieval_metrics.py +89 -0
- phoenix/server/api/dataloaders/experiment_annotation_summaries.py +79 -0
- phoenix/server/api/dataloaders/experiment_error_rates.py +58 -0
- phoenix/server/api/dataloaders/experiment_run_annotations.py +36 -0
- phoenix/server/api/dataloaders/experiment_run_counts.py +49 -0
- phoenix/server/api/dataloaders/experiment_sequence_number.py +44 -0
- phoenix/server/api/dataloaders/latency_ms_quantile.py +188 -0
- phoenix/server/api/dataloaders/min_start_or_max_end_times.py +85 -0
- phoenix/server/api/dataloaders/project_by_name.py +31 -0
- phoenix/server/api/dataloaders/record_counts.py +116 -0
- phoenix/server/api/dataloaders/session_io.py +79 -0
- phoenix/server/api/dataloaders/session_num_traces.py +30 -0
- phoenix/server/api/dataloaders/session_num_traces_with_error.py +32 -0
- phoenix/server/api/dataloaders/session_token_usages.py +41 -0
- phoenix/server/api/dataloaders/session_trace_latency_ms_quantile.py +55 -0
- phoenix/server/api/dataloaders/span_annotations.py +26 -0
- phoenix/server/api/dataloaders/span_dataset_examples.py +31 -0
- phoenix/server/api/dataloaders/span_descendants.py +57 -0
- phoenix/server/api/dataloaders/span_projects.py +33 -0
- phoenix/server/api/dataloaders/token_counts.py +124 -0
- phoenix/server/api/dataloaders/trace_by_trace_ids.py +25 -0
- phoenix/server/api/dataloaders/trace_root_spans.py +32 -0
- phoenix/server/api/dataloaders/user_roles.py +30 -0
- phoenix/server/api/dataloaders/users.py +33 -0
- phoenix/server/api/exceptions.py +48 -0
- phoenix/server/api/helpers/__init__.py +12 -0
- phoenix/server/api/helpers/dataset_helpers.py +217 -0
- phoenix/server/api/helpers/experiment_run_filters.py +763 -0
- phoenix/server/api/helpers/playground_clients.py +948 -0
- phoenix/server/api/helpers/playground_registry.py +70 -0
- phoenix/server/api/helpers/playground_spans.py +455 -0
- phoenix/server/api/input_types/AddExamplesToDatasetInput.py +16 -0
- phoenix/server/api/input_types/AddSpansToDatasetInput.py +14 -0
- phoenix/server/api/input_types/ChatCompletionInput.py +38 -0
- phoenix/server/api/input_types/ChatCompletionMessageInput.py +24 -0
- phoenix/server/api/input_types/ClearProjectInput.py +15 -0
- phoenix/server/api/input_types/ClusterInput.py +2 -2
- phoenix/server/api/input_types/CreateDatasetInput.py +12 -0
- phoenix/server/api/input_types/CreateSpanAnnotationInput.py +18 -0
- phoenix/server/api/input_types/CreateTraceAnnotationInput.py +18 -0
- phoenix/server/api/input_types/DataQualityMetricInput.py +5 -2
- phoenix/server/api/input_types/DatasetExampleInput.py +14 -0
- phoenix/server/api/input_types/DatasetSort.py +17 -0
- phoenix/server/api/input_types/DatasetVersionSort.py +16 -0
- phoenix/server/api/input_types/DeleteAnnotationsInput.py +7 -0
- phoenix/server/api/input_types/DeleteDatasetExamplesInput.py +13 -0
- phoenix/server/api/input_types/DeleteDatasetInput.py +7 -0
- phoenix/server/api/input_types/DeleteExperimentsInput.py +7 -0
- phoenix/server/api/input_types/DimensionFilter.py +4 -4
- phoenix/server/api/input_types/GenerativeModelInput.py +17 -0
- phoenix/server/api/input_types/Granularity.py +1 -1
- phoenix/server/api/input_types/InvocationParameters.py +162 -0
- phoenix/server/api/input_types/PatchAnnotationInput.py +19 -0
- phoenix/server/api/input_types/PatchDatasetExamplesInput.py +35 -0
- phoenix/server/api/input_types/PatchDatasetInput.py +14 -0
- phoenix/server/api/input_types/PerformanceMetricInput.py +5 -2
- phoenix/server/api/input_types/ProjectSessionSort.py +29 -0
- phoenix/server/api/input_types/SpanAnnotationSort.py +17 -0
- phoenix/server/api/input_types/SpanSort.py +134 -69
- phoenix/server/api/input_types/TemplateOptions.py +10 -0
- phoenix/server/api/input_types/TraceAnnotationSort.py +17 -0
- phoenix/server/api/input_types/UserRoleInput.py +9 -0
- phoenix/server/api/mutations/__init__.py +28 -0
- phoenix/server/api/mutations/api_key_mutations.py +167 -0
- phoenix/server/api/mutations/chat_mutations.py +593 -0
- phoenix/server/api/mutations/dataset_mutations.py +591 -0
- phoenix/server/api/mutations/experiment_mutations.py +75 -0
- phoenix/server/api/{types/ExportEventsMutation.py → mutations/export_events_mutations.py} +21 -18
- phoenix/server/api/mutations/project_mutations.py +57 -0
- phoenix/server/api/mutations/span_annotations_mutations.py +128 -0
- phoenix/server/api/mutations/trace_annotations_mutations.py +127 -0
- phoenix/server/api/mutations/user_mutations.py +329 -0
- phoenix/server/api/openapi/__init__.py +0 -0
- phoenix/server/api/openapi/main.py +17 -0
- phoenix/server/api/openapi/schema.py +16 -0
- phoenix/server/api/queries.py +738 -0
- phoenix/server/api/routers/__init__.py +11 -0
- phoenix/server/api/routers/auth.py +284 -0
- phoenix/server/api/routers/embeddings.py +26 -0
- phoenix/server/api/routers/oauth2.py +488 -0
- phoenix/server/api/routers/v1/__init__.py +64 -0
- phoenix/server/api/routers/v1/datasets.py +1017 -0
- phoenix/server/api/routers/v1/evaluations.py +362 -0
- phoenix/server/api/routers/v1/experiment_evaluations.py +115 -0
- phoenix/server/api/routers/v1/experiment_runs.py +167 -0
- phoenix/server/api/routers/v1/experiments.py +308 -0
- phoenix/server/api/routers/v1/pydantic_compat.py +78 -0
- phoenix/server/api/routers/v1/spans.py +267 -0
- phoenix/server/api/routers/v1/traces.py +208 -0
- phoenix/server/api/routers/v1/utils.py +95 -0
- phoenix/server/api/schema.py +44 -247
- phoenix/server/api/subscriptions.py +597 -0
- phoenix/server/api/types/Annotation.py +21 -0
- phoenix/server/api/types/AnnotationSummary.py +55 -0
- phoenix/server/api/types/AnnotatorKind.py +16 -0
- phoenix/server/api/types/ApiKey.py +27 -0
- phoenix/server/api/types/AuthMethod.py +9 -0
- phoenix/server/api/types/ChatCompletionMessageRole.py +11 -0
- phoenix/server/api/types/ChatCompletionSubscriptionPayload.py +46 -0
- phoenix/server/api/types/Cluster.py +25 -24
- phoenix/server/api/types/CreateDatasetPayload.py +8 -0
- phoenix/server/api/types/DataQualityMetric.py +31 -13
- phoenix/server/api/types/Dataset.py +288 -63
- phoenix/server/api/types/DatasetExample.py +85 -0
- phoenix/server/api/types/DatasetExampleRevision.py +34 -0
- phoenix/server/api/types/DatasetVersion.py +14 -0
- phoenix/server/api/types/Dimension.py +32 -31
- phoenix/server/api/types/DocumentEvaluationSummary.py +9 -8
- phoenix/server/api/types/EmbeddingDimension.py +56 -49
- phoenix/server/api/types/Evaluation.py +25 -31
- phoenix/server/api/types/EvaluationSummary.py +30 -50
- phoenix/server/api/types/Event.py +20 -20
- phoenix/server/api/types/ExampleRevisionInterface.py +14 -0
- phoenix/server/api/types/Experiment.py +152 -0
- phoenix/server/api/types/ExperimentAnnotationSummary.py +13 -0
- phoenix/server/api/types/ExperimentComparison.py +17 -0
- phoenix/server/api/types/ExperimentRun.py +119 -0
- phoenix/server/api/types/ExperimentRunAnnotation.py +56 -0
- phoenix/server/api/types/GenerativeModel.py +9 -0
- phoenix/server/api/types/GenerativeProvider.py +85 -0
- phoenix/server/api/types/Inferences.py +80 -0
- phoenix/server/api/types/InferencesRole.py +23 -0
- phoenix/server/api/types/LabelFraction.py +7 -0
- phoenix/server/api/types/MimeType.py +2 -2
- phoenix/server/api/types/Model.py +54 -54
- phoenix/server/api/types/PerformanceMetric.py +8 -5
- phoenix/server/api/types/Project.py +407 -142
- phoenix/server/api/types/ProjectSession.py +139 -0
- phoenix/server/api/types/Segments.py +4 -4
- phoenix/server/api/types/Span.py +221 -176
- phoenix/server/api/types/SpanAnnotation.py +43 -0
- phoenix/server/api/types/SpanIOValue.py +15 -0
- phoenix/server/api/types/SystemApiKey.py +9 -0
- phoenix/server/api/types/TemplateLanguage.py +10 -0
- phoenix/server/api/types/TimeSeries.py +19 -15
- phoenix/server/api/types/TokenUsage.py +11 -0
- phoenix/server/api/types/Trace.py +154 -0
- phoenix/server/api/types/TraceAnnotation.py +45 -0
- phoenix/server/api/types/UMAPPoints.py +7 -7
- phoenix/server/api/types/User.py +60 -0
- phoenix/server/api/types/UserApiKey.py +45 -0
- phoenix/server/api/types/UserRole.py +15 -0
- phoenix/server/api/types/node.py +13 -107
- phoenix/server/api/types/pagination.py +156 -57
- phoenix/server/api/utils.py +34 -0
- phoenix/server/app.py +864 -115
- phoenix/server/bearer_auth.py +163 -0
- phoenix/server/dml_event.py +136 -0
- phoenix/server/dml_event_handler.py +256 -0
- phoenix/server/email/__init__.py +0 -0
- phoenix/server/email/sender.py +97 -0
- phoenix/server/email/templates/__init__.py +0 -0
- phoenix/server/email/templates/password_reset.html +19 -0
- phoenix/server/email/types.py +11 -0
- phoenix/server/grpc_server.py +102 -0
- phoenix/server/jwt_store.py +505 -0
- phoenix/server/main.py +305 -116
- phoenix/server/oauth2.py +52 -0
- phoenix/server/openapi/__init__.py +0 -0
- phoenix/server/prometheus.py +111 -0
- phoenix/server/rate_limiters.py +188 -0
- phoenix/server/static/.vite/manifest.json +87 -0
- phoenix/server/static/assets/components-Cy9nwIvF.js +2125 -0
- phoenix/server/static/assets/index-BKvHIxkk.js +113 -0
- phoenix/server/static/assets/pages-CUi2xCVQ.js +4449 -0
- phoenix/server/static/assets/vendor-DvC8cT4X.js +894 -0
- phoenix/server/static/assets/vendor-DxkFTwjz.css +1 -0
- phoenix/server/static/assets/vendor-arizeai-Do1793cv.js +662 -0
- phoenix/server/static/assets/vendor-codemirror-BzwZPyJM.js +24 -0
- phoenix/server/static/assets/vendor-recharts-_Jb7JjhG.js +59 -0
- phoenix/server/static/assets/vendor-shiki-Cl9QBraO.js +5 -0
- phoenix/server/static/assets/vendor-three-DwGkEfCM.js +2998 -0
- phoenix/server/telemetry.py +68 -0
- phoenix/server/templates/index.html +82 -23
- phoenix/server/thread_server.py +3 -3
- phoenix/server/types.py +275 -0
- phoenix/services.py +27 -18
- phoenix/session/client.py +743 -68
- phoenix/session/data_extractor.py +31 -7
- phoenix/session/evaluation.py +3 -9
- phoenix/session/session.py +263 -219
- phoenix/settings.py +22 -0
- phoenix/trace/__init__.py +2 -22
- phoenix/trace/attributes.py +338 -0
- phoenix/trace/dsl/README.md +116 -0
- phoenix/trace/dsl/filter.py +663 -213
- phoenix/trace/dsl/helpers.py +73 -21
- phoenix/trace/dsl/query.py +574 -201
- phoenix/trace/exporter.py +24 -19
- phoenix/trace/fixtures.py +368 -32
- phoenix/trace/otel.py +71 -219
- phoenix/trace/projects.py +3 -2
- phoenix/trace/schemas.py +33 -11
- phoenix/trace/span_evaluations.py +21 -16
- phoenix/trace/span_json_decoder.py +6 -4
- phoenix/trace/span_json_encoder.py +2 -2
- phoenix/trace/trace_dataset.py +47 -32
- phoenix/trace/utils.py +21 -4
- phoenix/utilities/__init__.py +0 -26
- phoenix/utilities/client.py +132 -0
- phoenix/utilities/deprecation.py +31 -0
- phoenix/utilities/error_handling.py +3 -2
- phoenix/utilities/json.py +109 -0
- phoenix/utilities/logging.py +8 -0
- phoenix/utilities/project.py +2 -2
- phoenix/utilities/re.py +49 -0
- phoenix/utilities/span_store.py +0 -23
- phoenix/utilities/template_formatters.py +99 -0
- phoenix/version.py +1 -1
- arize_phoenix-3.16.0.dist-info/METADATA +0 -495
- arize_phoenix-3.16.0.dist-info/RECORD +0 -178
- phoenix/core/project.py +0 -617
- phoenix/core/traces.py +0 -100
- phoenix/experimental/evals/__init__.py +0 -73
- phoenix/experimental/evals/evaluators.py +0 -413
- phoenix/experimental/evals/functions/__init__.py +0 -4
- phoenix/experimental/evals/functions/classify.py +0 -453
- phoenix/experimental/evals/functions/executor.py +0 -353
- phoenix/experimental/evals/functions/generate.py +0 -138
- phoenix/experimental/evals/functions/processing.py +0 -76
- phoenix/experimental/evals/models/__init__.py +0 -14
- phoenix/experimental/evals/models/anthropic.py +0 -175
- phoenix/experimental/evals/models/base.py +0 -170
- phoenix/experimental/evals/models/bedrock.py +0 -221
- phoenix/experimental/evals/models/litellm.py +0 -134
- phoenix/experimental/evals/models/openai.py +0 -448
- phoenix/experimental/evals/models/rate_limiters.py +0 -246
- phoenix/experimental/evals/models/vertex.py +0 -173
- phoenix/experimental/evals/models/vertexai.py +0 -186
- phoenix/experimental/evals/retrievals.py +0 -96
- phoenix/experimental/evals/templates/__init__.py +0 -50
- phoenix/experimental/evals/templates/default_templates.py +0 -472
- phoenix/experimental/evals/templates/template.py +0 -195
- phoenix/experimental/evals/utils/__init__.py +0 -172
- phoenix/experimental/evals/utils/threads.py +0 -27
- phoenix/server/api/helpers.py +0 -11
- phoenix/server/api/routers/evaluation_handler.py +0 -109
- phoenix/server/api/routers/span_handler.py +0 -70
- phoenix/server/api/routers/trace_handler.py +0 -60
- phoenix/server/api/types/DatasetRole.py +0 -23
- phoenix/server/static/index.css +0 -6
- phoenix/server/static/index.js +0 -7447
- phoenix/storage/span_store/__init__.py +0 -23
- phoenix/storage/span_store/text_file.py +0 -85
- phoenix/trace/dsl/missing.py +0 -60
- phoenix/trace/langchain/__init__.py +0 -3
- phoenix/trace/langchain/instrumentor.py +0 -35
- phoenix/trace/llama_index/__init__.py +0 -3
- phoenix/trace/llama_index/callback.py +0 -102
- phoenix/trace/openai/__init__.py +0 -3
- phoenix/trace/openai/instrumentor.py +0 -30
- {arize_phoenix-3.16.0.dist-info → arize_phoenix-7.7.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-3.16.0.dist-info → arize_phoenix-7.7.0.dist-info}/licenses/LICENSE +0 -0
- /phoenix/{datasets → db/insertion}/__init__.py +0 -0
- /phoenix/{experimental → db/migrations}/__init__.py +0 -0
- /phoenix/{storage → db/migrations/data_migration_scripts}/__init__.py +0 -0
|
@@ -1,52 +1,53 @@
|
|
|
1
|
-
from
|
|
2
|
-
from
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
phoenix/server/api/context.py
CHANGED
|
@@ -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,
|
|
5
|
+
from typing import Any, Optional, cast
|
|
4
6
|
|
|
5
|
-
from starlette.requests import Request
|
|
6
|
-
from starlette.responses import Response
|
|
7
|
-
from
|
|
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.
|
|
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
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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
|
+
]
|