arize-phoenix 3.16.1__py3-none-any.whl → 7.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of arize-phoenix might be problematic. Click here for more details.
- arize_phoenix-7.7.0.dist-info/METADATA +261 -0
- arize_phoenix-7.7.0.dist-info/RECORD +345 -0
- {arize_phoenix-3.16.1.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 -241
- 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 +4 -112
- 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.1.dist-info/METADATA +0 -495
- arize_phoenix-3.16.1.dist-info/RECORD +0 -178
- phoenix/core/project.py +0 -619
- phoenix/core/traces.py +0 -96
- 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.1.dist-info → arize_phoenix-7.7.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-3.16.1.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
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from threading import Thread
|
|
3
|
+
|
|
4
|
+
import psutil
|
|
5
|
+
from prometheus_client import (
|
|
6
|
+
Counter,
|
|
7
|
+
Gauge,
|
|
8
|
+
Summary,
|
|
9
|
+
start_http_server,
|
|
10
|
+
)
|
|
11
|
+
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
|
|
12
|
+
from starlette.requests import Request
|
|
13
|
+
from starlette.responses import Response
|
|
14
|
+
from starlette.routing import Match
|
|
15
|
+
|
|
16
|
+
REQUESTS_PROCESSING_TIME = Summary(
|
|
17
|
+
name="starlette_requests_processing_time_seconds_summary",
|
|
18
|
+
documentation="Summary of requests processing time by method and path (in seconds)",
|
|
19
|
+
labelnames=["method", "path"],
|
|
20
|
+
)
|
|
21
|
+
EXCEPTIONS = Counter(
|
|
22
|
+
name="starlette_exceptions_total",
|
|
23
|
+
documentation="Total count of exceptions raised by method, path and exception type",
|
|
24
|
+
labelnames=["method", "path", "exception_type"],
|
|
25
|
+
)
|
|
26
|
+
RAM_METRIC = Gauge(
|
|
27
|
+
name="memory_usage_bytes",
|
|
28
|
+
documentation="Memory usage in bytes",
|
|
29
|
+
labelnames=["type"],
|
|
30
|
+
)
|
|
31
|
+
CPU_METRIC = Gauge(
|
|
32
|
+
name="cpu_usage_percent",
|
|
33
|
+
documentation="CPU usage percent",
|
|
34
|
+
labelnames=["core"],
|
|
35
|
+
)
|
|
36
|
+
BULK_LOADER_INSERTION_TIME = Summary(
|
|
37
|
+
name="bulk_loader_insertion_time_seconds_summary",
|
|
38
|
+
documentation="Summary of database insertion time (seconds)",
|
|
39
|
+
)
|
|
40
|
+
BULK_LOADER_SPAN_INSERTIONS = Counter(
|
|
41
|
+
name="bulk_loader_span_insertions_total",
|
|
42
|
+
documentation="Total count of bulk loader span insertions",
|
|
43
|
+
)
|
|
44
|
+
BULK_LOADER_EVALUATION_INSERTIONS = Counter(
|
|
45
|
+
name="bulk_loader_evaluation_insertions_total",
|
|
46
|
+
documentation="Total count of bulk loader evaluation insertions",
|
|
47
|
+
)
|
|
48
|
+
BULK_LOADER_EXCEPTIONS = Counter(
|
|
49
|
+
name="bulk_loader_exceptions_total",
|
|
50
|
+
documentation="Total count of bulk loader exceptions",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
RATE_LIMITER_CACHE_SIZE = Gauge(
|
|
54
|
+
name="rate_limiter_cache_size",
|
|
55
|
+
documentation="Current size of the rate limiter cache",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
RATE_LIMITER_THROTTLES = Counter(
|
|
59
|
+
name="rate_limiter_throttles_total",
|
|
60
|
+
documentation="Total count of rate limiter throttles",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
JWT_STORE_TOKENS_ACTIVE = Gauge(
|
|
64
|
+
name="jwt_store_tokens_active",
|
|
65
|
+
documentation="Current number of refresh tokens in the JWT store",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
JWT_STORE_API_KEYS_ACTIVE = Gauge(
|
|
69
|
+
name="jwt_store_api_keys_active",
|
|
70
|
+
documentation="Current number of API keys in the JWT store",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class PrometheusMiddleware(BaseHTTPMiddleware):
|
|
75
|
+
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
|
|
76
|
+
for route in request.app.routes:
|
|
77
|
+
match, _ = route.matches(request.scope)
|
|
78
|
+
if match is Match.FULL:
|
|
79
|
+
path = route.path
|
|
80
|
+
break
|
|
81
|
+
else:
|
|
82
|
+
return await call_next(request)
|
|
83
|
+
method = request.method
|
|
84
|
+
start_time = time.perf_counter()
|
|
85
|
+
try:
|
|
86
|
+
response = await call_next(request)
|
|
87
|
+
except BaseException as e:
|
|
88
|
+
EXCEPTIONS.labels(method=method, path=path, exception_type=type(e).__name__).inc()
|
|
89
|
+
raise
|
|
90
|
+
end_time = time.perf_counter()
|
|
91
|
+
REQUESTS_PROCESSING_TIME.labels(method=method, path=path).observe(end_time - start_time)
|
|
92
|
+
return response
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def start_prometheus() -> None:
|
|
96
|
+
Thread(target=gather_system_data, daemon=True).start()
|
|
97
|
+
start_http_server(9090, addr="::")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def gather_system_data() -> None:
|
|
101
|
+
while True:
|
|
102
|
+
time.sleep(1)
|
|
103
|
+
|
|
104
|
+
ram = psutil.virtual_memory()
|
|
105
|
+
swap = psutil.swap_memory()
|
|
106
|
+
|
|
107
|
+
RAM_METRIC.labels(type="virtual").set(ram.used)
|
|
108
|
+
RAM_METRIC.labels(type="swap").set(swap.used)
|
|
109
|
+
|
|
110
|
+
for core, percent in enumerate(psutil.cpu_percent(interval=1, percpu=True)):
|
|
111
|
+
CPU_METRIC.labels(core=core).set(percent)
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import time
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from collections.abc import Callable, Coroutine
|
|
5
|
+
from functools import partial
|
|
6
|
+
from typing import (
|
|
7
|
+
Any,
|
|
8
|
+
Optional,
|
|
9
|
+
Pattern, # import from re module when we drop support for 3.8
|
|
10
|
+
Union,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from fastapi import HTTPException, Request
|
|
14
|
+
|
|
15
|
+
from phoenix.config import get_env_enable_prometheus
|
|
16
|
+
from phoenix.exceptions import PhoenixException
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class UnavailableTokensError(PhoenixException):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TokenBucket:
|
|
24
|
+
"""
|
|
25
|
+
An implementation of the token-bucket algorithm for use as a rate limiter.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
per_second_request_rate (float): The allowed request rate.
|
|
29
|
+
enforcement_window_minutes (float): The time window over which the rate limit is enforced.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
per_second_request_rate: float,
|
|
35
|
+
enforcement_window_seconds: float = 1,
|
|
36
|
+
):
|
|
37
|
+
self.enforcement_window = enforcement_window_seconds
|
|
38
|
+
self.rate = per_second_request_rate
|
|
39
|
+
|
|
40
|
+
now = time.time()
|
|
41
|
+
self.last_checked = now
|
|
42
|
+
self.tokens = self.max_tokens()
|
|
43
|
+
|
|
44
|
+
def max_tokens(self) -> float:
|
|
45
|
+
return self.rate * self.enforcement_window
|
|
46
|
+
|
|
47
|
+
def available_tokens(self) -> float:
|
|
48
|
+
now = time.time()
|
|
49
|
+
time_since_last_checked = now - self.last_checked
|
|
50
|
+
self.tokens = min(self.max_tokens(), self.rate * time_since_last_checked + self.tokens)
|
|
51
|
+
self.last_checked = now
|
|
52
|
+
return self.tokens
|
|
53
|
+
|
|
54
|
+
def make_request_if_ready(self) -> None:
|
|
55
|
+
if self.available_tokens() < 1:
|
|
56
|
+
if get_env_enable_prometheus():
|
|
57
|
+
from phoenix.server.prometheus import RATE_LIMITER_THROTTLES
|
|
58
|
+
|
|
59
|
+
RATE_LIMITER_THROTTLES.inc()
|
|
60
|
+
raise UnavailableTokensError
|
|
61
|
+
self.tokens -= 1
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ServerRateLimiter:
|
|
65
|
+
"""
|
|
66
|
+
This rate limiter holds a cache of token buckets that enforce rate limits.
|
|
67
|
+
|
|
68
|
+
The cache is kept in partitions that rotate every `partition_seconds`. Each user's rate limiter
|
|
69
|
+
can be accessed from all active partitions, the number of active partitions is set with
|
|
70
|
+
`active_partitions`. This guarantees that a user's rate limiter will sit in the cache for at
|
|
71
|
+
least:
|
|
72
|
+
|
|
73
|
+
minimum_cache_lifetime = (active_partitions - 1) * partition_seconds
|
|
74
|
+
|
|
75
|
+
Every time the cache is accessed, inactive partitions are purged. If enough time has passed,
|
|
76
|
+
the entire cache is purged.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
per_second_rate_limit: float = 0.5,
|
|
82
|
+
enforcement_window_seconds: float = 5,
|
|
83
|
+
partition_seconds: float = 60,
|
|
84
|
+
active_partitions: int = 2,
|
|
85
|
+
):
|
|
86
|
+
self.bucket_factory = partial(
|
|
87
|
+
TokenBucket,
|
|
88
|
+
per_second_request_rate=per_second_rate_limit,
|
|
89
|
+
enforcement_window_seconds=enforcement_window_seconds,
|
|
90
|
+
)
|
|
91
|
+
self.partition_seconds = partition_seconds
|
|
92
|
+
self.active_partitions = active_partitions
|
|
93
|
+
self.num_partitions = active_partitions + 2 # two overflow partitions to avoid edge cases
|
|
94
|
+
self._reset_rate_limiters()
|
|
95
|
+
self._last_cleanup_time = time.time()
|
|
96
|
+
|
|
97
|
+
def _reset_rate_limiters(self) -> None:
|
|
98
|
+
self.cache_partitions: list[defaultdict[Any, TokenBucket]] = [
|
|
99
|
+
defaultdict(self.bucket_factory) for _ in range(self.num_partitions)
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
def _current_partition_index(self, timestamp: float) -> int:
|
|
103
|
+
return (
|
|
104
|
+
int(timestamp // self.partition_seconds) % self.num_partitions
|
|
105
|
+
) # a cyclic bucket index
|
|
106
|
+
|
|
107
|
+
def _active_partition_indices(self, current_index: int) -> list[int]:
|
|
108
|
+
return [(current_index - ii) % self.num_partitions for ii in range(self.active_partitions)]
|
|
109
|
+
|
|
110
|
+
def _inactive_partition_indices(self, current_index: int) -> list[int]:
|
|
111
|
+
active_indices = set(self._active_partition_indices(current_index))
|
|
112
|
+
all_indices = set(range(self.num_partitions))
|
|
113
|
+
return list(all_indices - active_indices)
|
|
114
|
+
|
|
115
|
+
def _cleanup_expired_limiters(self, request_time: float) -> None:
|
|
116
|
+
time_since_last_cleanup = request_time - self._last_cleanup_time
|
|
117
|
+
if time_since_last_cleanup >= ((self.num_partitions - 1) * self.partition_seconds):
|
|
118
|
+
# Reset the cache to avoid "looping" back to the same partitions
|
|
119
|
+
self._reset_rate_limiters()
|
|
120
|
+
self._last_cleanup_time = request_time
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
current_partition_index = self._current_partition_index(request_time)
|
|
124
|
+
inactive_indices = self._inactive_partition_indices(current_partition_index)
|
|
125
|
+
for ii in inactive_indices:
|
|
126
|
+
self.cache_partitions[ii] = defaultdict(self.bucket_factory)
|
|
127
|
+
self._last_cleanup_time = request_time
|
|
128
|
+
|
|
129
|
+
def _fetch_token_bucket(self, key: str, request_time: float) -> TokenBucket:
|
|
130
|
+
current_partition_index = self._current_partition_index(request_time)
|
|
131
|
+
active_indices = self._active_partition_indices(current_partition_index)
|
|
132
|
+
bucket: Optional[TokenBucket] = None
|
|
133
|
+
for ii in active_indices:
|
|
134
|
+
partition = self.cache_partitions[ii]
|
|
135
|
+
if key in partition:
|
|
136
|
+
bucket = partition.pop(key)
|
|
137
|
+
break
|
|
138
|
+
|
|
139
|
+
current_partition = self.cache_partitions[current_partition_index]
|
|
140
|
+
if key not in current_partition and bucket is not None:
|
|
141
|
+
current_partition[key] = bucket
|
|
142
|
+
return current_partition[key]
|
|
143
|
+
|
|
144
|
+
def make_request(self, key: str) -> None:
|
|
145
|
+
request_time = time.time()
|
|
146
|
+
self._cleanup_expired_limiters(request_time)
|
|
147
|
+
rate_limiter = self._fetch_token_bucket(key, request_time)
|
|
148
|
+
rate_limiter.make_request_if_ready()
|
|
149
|
+
if get_env_enable_prometheus():
|
|
150
|
+
from phoenix.server.prometheus import RATE_LIMITER_CACHE_SIZE
|
|
151
|
+
|
|
152
|
+
RATE_LIMITER_CACHE_SIZE.set(sum(len(partition) for partition in self.cache_partitions))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def fastapi_ip_rate_limiter(
|
|
156
|
+
rate_limiter: ServerRateLimiter, paths: Optional[list[Union[str, Pattern[str]]]] = None
|
|
157
|
+
) -> Callable[[Request], Coroutine[Any, Any, Request]]:
|
|
158
|
+
async def dependency(request: Request) -> Request:
|
|
159
|
+
if paths is None or any(path_match(request.url.path, path) for path in paths):
|
|
160
|
+
client = request.client
|
|
161
|
+
if client: # bypasses rate limiter if no client
|
|
162
|
+
client_ip = client.host
|
|
163
|
+
try:
|
|
164
|
+
rate_limiter.make_request(client_ip)
|
|
165
|
+
except UnavailableTokensError:
|
|
166
|
+
raise HTTPException(status_code=429, detail="Too Many Requests")
|
|
167
|
+
return request
|
|
168
|
+
|
|
169
|
+
return dependency
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def fastapi_route_rate_limiter(
|
|
173
|
+
rate_limiter: ServerRateLimiter,
|
|
174
|
+
) -> Callable[[Request], Coroutine[Any, Any, Request]]:
|
|
175
|
+
async def dependency(request: Request) -> Request:
|
|
176
|
+
try:
|
|
177
|
+
rate_limiter.make_request(request.url.path)
|
|
178
|
+
except UnavailableTokensError:
|
|
179
|
+
raise HTTPException(status_code=429, detail="Too Many Requests")
|
|
180
|
+
return request
|
|
181
|
+
|
|
182
|
+
return dependency
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def path_match(path: str, match_pattern: Union[str, Pattern[str]]) -> bool:
|
|
186
|
+
if isinstance(match_pattern, re.Pattern):
|
|
187
|
+
return bool(match_pattern.match(path))
|
|
188
|
+
return path == match_pattern
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_components-Cy9nwIvF.js": {
|
|
3
|
+
"file": "assets/components-Cy9nwIvF.js",
|
|
4
|
+
"name": "components",
|
|
5
|
+
"imports": [
|
|
6
|
+
"_vendor-DvC8cT4X.js",
|
|
7
|
+
"_pages-CUi2xCVQ.js",
|
|
8
|
+
"_vendor-arizeai-Do1793cv.js",
|
|
9
|
+
"_vendor-codemirror-BzwZPyJM.js",
|
|
10
|
+
"_vendor-three-DwGkEfCM.js"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"_pages-CUi2xCVQ.js": {
|
|
14
|
+
"file": "assets/pages-CUi2xCVQ.js",
|
|
15
|
+
"name": "pages",
|
|
16
|
+
"imports": [
|
|
17
|
+
"_vendor-DvC8cT4X.js",
|
|
18
|
+
"_vendor-arizeai-Do1793cv.js",
|
|
19
|
+
"_components-Cy9nwIvF.js",
|
|
20
|
+
"_vendor-codemirror-BzwZPyJM.js",
|
|
21
|
+
"_vendor-recharts-_Jb7JjhG.js"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"_vendor-!~{003}~.js": {
|
|
25
|
+
"file": "assets/vendor-DxkFTwjz.css",
|
|
26
|
+
"src": "_vendor-!~{003}~.js"
|
|
27
|
+
},
|
|
28
|
+
"_vendor-DvC8cT4X.js": {
|
|
29
|
+
"file": "assets/vendor-DvC8cT4X.js",
|
|
30
|
+
"name": "vendor",
|
|
31
|
+
"imports": [
|
|
32
|
+
"_vendor-three-DwGkEfCM.js"
|
|
33
|
+
],
|
|
34
|
+
"css": [
|
|
35
|
+
"assets/vendor-DxkFTwjz.css"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"_vendor-arizeai-Do1793cv.js": {
|
|
39
|
+
"file": "assets/vendor-arizeai-Do1793cv.js",
|
|
40
|
+
"name": "vendor-arizeai",
|
|
41
|
+
"imports": [
|
|
42
|
+
"_vendor-DvC8cT4X.js"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"_vendor-codemirror-BzwZPyJM.js": {
|
|
46
|
+
"file": "assets/vendor-codemirror-BzwZPyJM.js",
|
|
47
|
+
"name": "vendor-codemirror",
|
|
48
|
+
"imports": [
|
|
49
|
+
"_vendor-DvC8cT4X.js",
|
|
50
|
+
"_vendor-shiki-Cl9QBraO.js"
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
"_vendor-recharts-_Jb7JjhG.js": {
|
|
54
|
+
"file": "assets/vendor-recharts-_Jb7JjhG.js",
|
|
55
|
+
"name": "vendor-recharts",
|
|
56
|
+
"imports": [
|
|
57
|
+
"_vendor-DvC8cT4X.js"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"_vendor-shiki-Cl9QBraO.js": {
|
|
61
|
+
"file": "assets/vendor-shiki-Cl9QBraO.js",
|
|
62
|
+
"name": "vendor-shiki",
|
|
63
|
+
"imports": [
|
|
64
|
+
"_vendor-DvC8cT4X.js"
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
"_vendor-three-DwGkEfCM.js": {
|
|
68
|
+
"file": "assets/vendor-three-DwGkEfCM.js",
|
|
69
|
+
"name": "vendor-three"
|
|
70
|
+
},
|
|
71
|
+
"index.tsx": {
|
|
72
|
+
"file": "assets/index-BKvHIxkk.js",
|
|
73
|
+
"name": "index",
|
|
74
|
+
"src": "index.tsx",
|
|
75
|
+
"isEntry": true,
|
|
76
|
+
"imports": [
|
|
77
|
+
"_vendor-DvC8cT4X.js",
|
|
78
|
+
"_vendor-arizeai-Do1793cv.js",
|
|
79
|
+
"_pages-CUi2xCVQ.js",
|
|
80
|
+
"_components-Cy9nwIvF.js",
|
|
81
|
+
"_vendor-three-DwGkEfCM.js",
|
|
82
|
+
"_vendor-codemirror-BzwZPyJM.js",
|
|
83
|
+
"_vendor-shiki-Cl9QBraO.js",
|
|
84
|
+
"_vendor-recharts-_Jb7JjhG.js"
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
}
|