arize-phoenix 10.0.4__py3-none-any.whl → 12.28.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.
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/METADATA +124 -72
- arize_phoenix-12.28.1.dist-info/RECORD +499 -0
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/WHEEL +1 -1
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/licenses/IP_NOTICE +1 -1
- phoenix/__generated__/__init__.py +0 -0
- phoenix/__generated__/classification_evaluator_configs/__init__.py +20 -0
- phoenix/__generated__/classification_evaluator_configs/_document_relevance_classification_evaluator_config.py +17 -0
- phoenix/__generated__/classification_evaluator_configs/_hallucination_classification_evaluator_config.py +17 -0
- phoenix/__generated__/classification_evaluator_configs/_models.py +18 -0
- phoenix/__generated__/classification_evaluator_configs/_tool_selection_classification_evaluator_config.py +17 -0
- phoenix/__init__.py +5 -4
- phoenix/auth.py +39 -2
- phoenix/config.py +1763 -91
- phoenix/datetime_utils.py +120 -2
- phoenix/db/README.md +595 -25
- phoenix/db/bulk_inserter.py +145 -103
- phoenix/db/engines.py +140 -33
- phoenix/db/enums.py +3 -12
- phoenix/db/facilitator.py +302 -35
- phoenix/db/helpers.py +1000 -65
- phoenix/db/iam_auth.py +64 -0
- phoenix/db/insertion/dataset.py +135 -2
- phoenix/db/insertion/document_annotation.py +9 -6
- phoenix/db/insertion/evaluation.py +2 -3
- phoenix/db/insertion/helpers.py +17 -2
- phoenix/db/insertion/session_annotation.py +176 -0
- phoenix/db/insertion/span.py +15 -11
- phoenix/db/insertion/span_annotation.py +3 -4
- phoenix/db/insertion/trace_annotation.py +3 -4
- phoenix/db/insertion/types.py +50 -20
- phoenix/db/migrations/versions/01a8342c9cdf_add_user_id_on_datasets.py +40 -0
- phoenix/db/migrations/versions/0df286449799_add_session_annotations_table.py +105 -0
- phoenix/db/migrations/versions/272b66ff50f8_drop_single_indices.py +119 -0
- phoenix/db/migrations/versions/58228d933c91_dataset_labels.py +67 -0
- phoenix/db/migrations/versions/699f655af132_experiment_tags.py +57 -0
- phoenix/db/migrations/versions/735d3d93c33e_add_composite_indices.py +41 -0
- phoenix/db/migrations/versions/a20694b15f82_cost.py +196 -0
- phoenix/db/migrations/versions/ab513d89518b_add_user_id_on_dataset_versions.py +40 -0
- phoenix/db/migrations/versions/d0690a79ea51_users_on_experiments.py +40 -0
- phoenix/db/migrations/versions/deb2c81c0bb2_dataset_splits.py +139 -0
- phoenix/db/migrations/versions/e76cbd66ffc3_add_experiments_dataset_examples.py +87 -0
- phoenix/db/models.py +669 -56
- phoenix/db/pg_config.py +10 -0
- phoenix/db/types/model_provider.py +4 -0
- phoenix/db/types/token_price_customization.py +29 -0
- phoenix/db/types/trace_retention.py +23 -15
- phoenix/experiments/evaluators/utils.py +3 -3
- phoenix/experiments/functions.py +160 -52
- phoenix/experiments/tracing.py +2 -2
- phoenix/experiments/types.py +1 -1
- phoenix/inferences/inferences.py +1 -2
- phoenix/server/api/auth.py +38 -7
- phoenix/server/api/auth_messages.py +46 -0
- phoenix/server/api/context.py +100 -4
- phoenix/server/api/dataloaders/__init__.py +79 -5
- phoenix/server/api/dataloaders/annotation_configs_by_project.py +31 -0
- phoenix/server/api/dataloaders/annotation_summaries.py +60 -8
- phoenix/server/api/dataloaders/average_experiment_repeated_run_group_latency.py +50 -0
- phoenix/server/api/dataloaders/average_experiment_run_latency.py +17 -24
- phoenix/server/api/dataloaders/cache/two_tier_cache.py +1 -2
- phoenix/server/api/dataloaders/dataset_dataset_splits.py +52 -0
- phoenix/server/api/dataloaders/dataset_example_revisions.py +0 -1
- phoenix/server/api/dataloaders/dataset_example_splits.py +40 -0
- phoenix/server/api/dataloaders/dataset_examples_and_versions_by_experiment_run.py +47 -0
- phoenix/server/api/dataloaders/dataset_labels.py +36 -0
- phoenix/server/api/dataloaders/document_evaluation_summaries.py +2 -2
- phoenix/server/api/dataloaders/document_evaluations.py +6 -9
- phoenix/server/api/dataloaders/experiment_annotation_summaries.py +88 -34
- phoenix/server/api/dataloaders/experiment_dataset_splits.py +43 -0
- phoenix/server/api/dataloaders/experiment_error_rates.py +21 -28
- phoenix/server/api/dataloaders/experiment_repeated_run_group_annotation_summaries.py +77 -0
- phoenix/server/api/dataloaders/experiment_repeated_run_groups.py +57 -0
- phoenix/server/api/dataloaders/experiment_runs_by_experiment_and_example.py +44 -0
- phoenix/server/api/dataloaders/last_used_times_by_generative_model_id.py +35 -0
- phoenix/server/api/dataloaders/latency_ms_quantile.py +40 -8
- phoenix/server/api/dataloaders/record_counts.py +37 -10
- phoenix/server/api/dataloaders/session_annotations_by_session.py +29 -0
- phoenix/server/api/dataloaders/span_cost_by_span.py +24 -0
- phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_generative_model.py +56 -0
- phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_project_session.py +57 -0
- phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_span.py +43 -0
- phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_trace.py +56 -0
- phoenix/server/api/dataloaders/span_cost_details_by_span_cost.py +27 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_experiment.py +57 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_experiment_repeated_run_group.py +64 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_experiment_run.py +58 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_generative_model.py +55 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_project.py +152 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_project_session.py +56 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_trace.py +55 -0
- phoenix/server/api/dataloaders/span_costs.py +29 -0
- phoenix/server/api/dataloaders/table_fields.py +2 -2
- phoenix/server/api/dataloaders/token_prices_by_model.py +30 -0
- phoenix/server/api/dataloaders/trace_annotations_by_trace.py +27 -0
- phoenix/server/api/dataloaders/types.py +29 -0
- phoenix/server/api/exceptions.py +11 -1
- phoenix/server/api/helpers/dataset_helpers.py +5 -1
- phoenix/server/api/helpers/playground_clients.py +1243 -292
- phoenix/server/api/helpers/playground_registry.py +2 -2
- phoenix/server/api/helpers/playground_spans.py +8 -4
- phoenix/server/api/helpers/playground_users.py +26 -0
- phoenix/server/api/helpers/prompts/conversions/aws.py +83 -0
- phoenix/server/api/helpers/prompts/conversions/google.py +103 -0
- phoenix/server/api/helpers/prompts/models.py +205 -22
- phoenix/server/api/input_types/{SpanAnnotationFilter.py → AnnotationFilter.py} +22 -14
- phoenix/server/api/input_types/ChatCompletionInput.py +6 -2
- phoenix/server/api/input_types/CreateProjectInput.py +27 -0
- phoenix/server/api/input_types/CreateProjectSessionAnnotationInput.py +37 -0
- phoenix/server/api/input_types/DatasetFilter.py +17 -0
- phoenix/server/api/input_types/ExperimentRunSort.py +237 -0
- phoenix/server/api/input_types/GenerativeCredentialInput.py +9 -0
- phoenix/server/api/input_types/GenerativeModelInput.py +5 -0
- phoenix/server/api/input_types/ProjectSessionSort.py +161 -1
- phoenix/server/api/input_types/PromptFilter.py +14 -0
- phoenix/server/api/input_types/PromptVersionInput.py +52 -1
- phoenix/server/api/input_types/SpanSort.py +44 -7
- phoenix/server/api/input_types/TimeBinConfig.py +23 -0
- phoenix/server/api/input_types/UpdateAnnotationInput.py +34 -0
- phoenix/server/api/input_types/UserRoleInput.py +1 -0
- phoenix/server/api/mutations/__init__.py +10 -0
- phoenix/server/api/mutations/annotation_config_mutations.py +8 -8
- phoenix/server/api/mutations/api_key_mutations.py +19 -23
- phoenix/server/api/mutations/chat_mutations.py +154 -47
- phoenix/server/api/mutations/dataset_label_mutations.py +243 -0
- phoenix/server/api/mutations/dataset_mutations.py +21 -16
- phoenix/server/api/mutations/dataset_split_mutations.py +351 -0
- phoenix/server/api/mutations/experiment_mutations.py +2 -2
- phoenix/server/api/mutations/export_events_mutations.py +3 -3
- phoenix/server/api/mutations/model_mutations.py +210 -0
- phoenix/server/api/mutations/project_mutations.py +49 -10
- phoenix/server/api/mutations/project_session_annotations_mutations.py +158 -0
- phoenix/server/api/mutations/project_trace_retention_policy_mutations.py +8 -4
- phoenix/server/api/mutations/prompt_label_mutations.py +74 -65
- phoenix/server/api/mutations/prompt_mutations.py +65 -129
- phoenix/server/api/mutations/prompt_version_tag_mutations.py +11 -8
- phoenix/server/api/mutations/span_annotations_mutations.py +15 -10
- phoenix/server/api/mutations/trace_annotations_mutations.py +14 -10
- phoenix/server/api/mutations/trace_mutations.py +47 -3
- phoenix/server/api/mutations/user_mutations.py +66 -41
- phoenix/server/api/queries.py +768 -293
- phoenix/server/api/routers/__init__.py +2 -2
- phoenix/server/api/routers/auth.py +154 -88
- phoenix/server/api/routers/ldap.py +229 -0
- phoenix/server/api/routers/oauth2.py +369 -106
- phoenix/server/api/routers/v1/__init__.py +24 -4
- phoenix/server/api/routers/v1/annotation_configs.py +23 -31
- phoenix/server/api/routers/v1/annotations.py +481 -17
- phoenix/server/api/routers/v1/datasets.py +395 -81
- phoenix/server/api/routers/v1/documents.py +142 -0
- phoenix/server/api/routers/v1/evaluations.py +24 -31
- phoenix/server/api/routers/v1/experiment_evaluations.py +19 -8
- phoenix/server/api/routers/v1/experiment_runs.py +337 -59
- phoenix/server/api/routers/v1/experiments.py +479 -48
- phoenix/server/api/routers/v1/models.py +7 -0
- phoenix/server/api/routers/v1/projects.py +18 -49
- phoenix/server/api/routers/v1/prompts.py +54 -40
- phoenix/server/api/routers/v1/sessions.py +108 -0
- phoenix/server/api/routers/v1/spans.py +1091 -81
- phoenix/server/api/routers/v1/traces.py +132 -78
- phoenix/server/api/routers/v1/users.py +389 -0
- phoenix/server/api/routers/v1/utils.py +3 -7
- phoenix/server/api/subscriptions.py +305 -88
- phoenix/server/api/types/Annotation.py +90 -23
- phoenix/server/api/types/ApiKey.py +13 -17
- phoenix/server/api/types/AuthMethod.py +1 -0
- phoenix/server/api/types/ChatCompletionSubscriptionPayload.py +1 -0
- phoenix/server/api/types/CostBreakdown.py +12 -0
- phoenix/server/api/types/Dataset.py +226 -72
- phoenix/server/api/types/DatasetExample.py +88 -18
- phoenix/server/api/types/DatasetExperimentAnnotationSummary.py +10 -0
- phoenix/server/api/types/DatasetLabel.py +57 -0
- phoenix/server/api/types/DatasetSplit.py +98 -0
- phoenix/server/api/types/DatasetVersion.py +49 -4
- phoenix/server/api/types/DocumentAnnotation.py +212 -0
- phoenix/server/api/types/Experiment.py +264 -59
- phoenix/server/api/types/ExperimentComparison.py +5 -10
- phoenix/server/api/types/ExperimentRepeatedRunGroup.py +155 -0
- phoenix/server/api/types/ExperimentRepeatedRunGroupAnnotationSummary.py +9 -0
- phoenix/server/api/types/ExperimentRun.py +169 -65
- phoenix/server/api/types/ExperimentRunAnnotation.py +158 -39
- phoenix/server/api/types/GenerativeModel.py +245 -3
- phoenix/server/api/types/GenerativeProvider.py +70 -11
- phoenix/server/api/types/{Model.py → InferenceModel.py} +1 -1
- phoenix/server/api/types/ModelInterface.py +16 -0
- phoenix/server/api/types/PlaygroundModel.py +20 -0
- phoenix/server/api/types/Project.py +1278 -216
- phoenix/server/api/types/ProjectSession.py +188 -28
- phoenix/server/api/types/ProjectSessionAnnotation.py +187 -0
- phoenix/server/api/types/ProjectTraceRetentionPolicy.py +1 -1
- phoenix/server/api/types/Prompt.py +119 -39
- phoenix/server/api/types/PromptLabel.py +42 -25
- phoenix/server/api/types/PromptVersion.py +11 -8
- phoenix/server/api/types/PromptVersionTag.py +65 -25
- phoenix/server/api/types/ServerStatus.py +6 -0
- phoenix/server/api/types/Span.py +167 -123
- phoenix/server/api/types/SpanAnnotation.py +189 -42
- phoenix/server/api/types/SpanCostDetailSummaryEntry.py +10 -0
- phoenix/server/api/types/SpanCostSummary.py +10 -0
- phoenix/server/api/types/SystemApiKey.py +65 -1
- phoenix/server/api/types/TokenPrice.py +16 -0
- phoenix/server/api/types/TokenUsage.py +3 -3
- phoenix/server/api/types/Trace.py +223 -51
- phoenix/server/api/types/TraceAnnotation.py +149 -50
- phoenix/server/api/types/User.py +137 -32
- phoenix/server/api/types/UserApiKey.py +73 -26
- phoenix/server/api/types/node.py +10 -0
- phoenix/server/api/types/pagination.py +11 -2
- phoenix/server/app.py +290 -45
- phoenix/server/authorization.py +38 -3
- phoenix/server/bearer_auth.py +34 -24
- phoenix/server/cost_tracking/cost_details_calculator.py +196 -0
- phoenix/server/cost_tracking/cost_model_lookup.py +179 -0
- phoenix/server/cost_tracking/helpers.py +68 -0
- phoenix/server/cost_tracking/model_cost_manifest.json +3657 -830
- phoenix/server/cost_tracking/regex_specificity.py +397 -0
- phoenix/server/cost_tracking/token_cost_calculator.py +57 -0
- phoenix/server/daemons/__init__.py +0 -0
- phoenix/server/daemons/db_disk_usage_monitor.py +214 -0
- phoenix/server/daemons/generative_model_store.py +103 -0
- phoenix/server/daemons/span_cost_calculator.py +99 -0
- phoenix/server/dml_event.py +17 -0
- phoenix/server/dml_event_handler.py +5 -0
- phoenix/server/email/sender.py +56 -3
- phoenix/server/email/templates/db_disk_usage_notification.html +19 -0
- phoenix/server/email/types.py +11 -0
- phoenix/server/experiments/__init__.py +0 -0
- phoenix/server/experiments/utils.py +14 -0
- phoenix/server/grpc_server.py +11 -11
- phoenix/server/jwt_store.py +17 -15
- phoenix/server/ldap.py +1449 -0
- phoenix/server/main.py +26 -10
- phoenix/server/oauth2.py +330 -12
- phoenix/server/prometheus.py +66 -6
- phoenix/server/rate_limiters.py +4 -9
- phoenix/server/retention.py +33 -20
- phoenix/server/session_filters.py +49 -0
- phoenix/server/static/.vite/manifest.json +55 -51
- phoenix/server/static/assets/components-BreFUQQa.js +6702 -0
- phoenix/server/static/assets/{index-E0M82BdE.js → index-CTQoemZv.js} +140 -56
- phoenix/server/static/assets/pages-DBE5iYM3.js +9524 -0
- phoenix/server/static/assets/vendor-BGzfc4EU.css +1 -0
- phoenix/server/static/assets/vendor-DCE4v-Ot.js +920 -0
- phoenix/server/static/assets/vendor-codemirror-D5f205eT.js +25 -0
- phoenix/server/static/assets/vendor-recharts-V9cwpXsm.js +37 -0
- phoenix/server/static/assets/vendor-shiki-Do--csgv.js +5 -0
- phoenix/server/static/assets/vendor-three-CmB8bl_y.js +3840 -0
- phoenix/server/templates/index.html +40 -6
- phoenix/server/thread_server.py +1 -2
- phoenix/server/types.py +14 -4
- phoenix/server/utils.py +74 -0
- phoenix/session/client.py +56 -3
- phoenix/session/data_extractor.py +5 -0
- phoenix/session/evaluation.py +14 -5
- phoenix/session/session.py +45 -9
- phoenix/settings.py +5 -0
- phoenix/trace/attributes.py +80 -13
- phoenix/trace/dsl/helpers.py +90 -1
- phoenix/trace/dsl/query.py +8 -6
- phoenix/trace/projects.py +5 -0
- phoenix/utilities/template_formatters.py +1 -1
- phoenix/version.py +1 -1
- arize_phoenix-10.0.4.dist-info/RECORD +0 -405
- phoenix/server/api/types/Evaluation.py +0 -39
- phoenix/server/cost_tracking/cost_lookup.py +0 -255
- phoenix/server/static/assets/components-DULKeDfL.js +0 -4365
- phoenix/server/static/assets/pages-Cl0A-0U2.js +0 -7430
- phoenix/server/static/assets/vendor-WIZid84E.css +0 -1
- phoenix/server/static/assets/vendor-arizeai-Dy-0mSNw.js +0 -649
- phoenix/server/static/assets/vendor-codemirror-DBtifKNr.js +0 -33
- phoenix/server/static/assets/vendor-oB4u9zuV.js +0 -905
- phoenix/server/static/assets/vendor-recharts-D-T4KPz2.js +0 -59
- phoenix/server/static/assets/vendor-shiki-BMn4O_9F.js +0 -5
- phoenix/server/static/assets/vendor-three-C5WAXd5r.js +0 -2998
- phoenix/utilities/deprecation.py +0 -31
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/entry_points.txt +0 -0
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/licenses/LICENSE +0 -0
phoenix/server/app.py
CHANGED
|
@@ -4,7 +4,6 @@ import importlib
|
|
|
4
4
|
import json
|
|
5
5
|
import logging
|
|
6
6
|
import os
|
|
7
|
-
from collections.abc import AsyncIterator, Awaitable, Callable, Iterable, Sequence
|
|
8
7
|
from contextlib import AbstractAsyncContextManager, AsyncExitStack
|
|
9
8
|
from dataclasses import dataclass, field
|
|
10
9
|
from datetime import datetime, timedelta, timezone
|
|
@@ -14,19 +13,27 @@ from types import MethodType
|
|
|
14
13
|
from typing import (
|
|
15
14
|
TYPE_CHECKING,
|
|
16
15
|
Any,
|
|
16
|
+
AsyncIterator,
|
|
17
|
+
Awaitable,
|
|
18
|
+
Callable,
|
|
19
|
+
Iterable,
|
|
17
20
|
NamedTuple,
|
|
18
21
|
Optional,
|
|
22
|
+
Protocol,
|
|
23
|
+
Sequence,
|
|
19
24
|
TypedDict,
|
|
20
25
|
Union,
|
|
21
26
|
cast,
|
|
22
27
|
)
|
|
23
28
|
from urllib.parse import urlparse
|
|
24
29
|
|
|
30
|
+
import grpc
|
|
25
31
|
import strawberry
|
|
26
32
|
from fastapi import APIRouter, Depends, FastAPI
|
|
27
33
|
from fastapi.middleware.cors import CORSMiddleware
|
|
28
34
|
from fastapi.utils import is_body_allowed_for_status_code
|
|
29
35
|
from grpc.aio import ServerInterceptor
|
|
36
|
+
from grpc_interceptor import AsyncServerInterceptor
|
|
30
37
|
from sqlalchemy import select
|
|
31
38
|
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker
|
|
32
39
|
from starlette.datastructures import URL, Secret
|
|
@@ -38,13 +45,12 @@ from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoin
|
|
|
38
45
|
from starlette.requests import Request
|
|
39
46
|
from starlette.responses import JSONResponse, PlainTextResponse, RedirectResponse, Response
|
|
40
47
|
from starlette.staticfiles import StaticFiles
|
|
41
|
-
from starlette.status import HTTP_401_UNAUTHORIZED
|
|
42
48
|
from starlette.templating import Jinja2Templates
|
|
43
49
|
from starlette.types import Scope, StatefulLifespan
|
|
44
50
|
from strawberry.extensions import SchemaExtension
|
|
45
51
|
from strawberry.fastapi import GraphQLRouter
|
|
46
52
|
from strawberry.subscriptions import GRAPHQL_TRANSPORT_WS_PROTOCOL
|
|
47
|
-
from typing_extensions import TypeAlias
|
|
53
|
+
from typing_extensions import TypeAlias, override
|
|
48
54
|
|
|
49
55
|
import phoenix.trace.v1 as pb
|
|
50
56
|
from phoenix.config import (
|
|
@@ -52,13 +58,17 @@ from phoenix.config import (
|
|
|
52
58
|
ENV_PHOENIX_CSRF_TRUSTED_ORIGINS,
|
|
53
59
|
SERVER_DIR,
|
|
54
60
|
OAuth2ClientConfig,
|
|
61
|
+
get_env_allow_external_resources,
|
|
55
62
|
get_env_csrf_trusted_origins,
|
|
63
|
+
get_env_database_allocated_storage_capacity_gibibytes,
|
|
64
|
+
get_env_database_usage_insertion_blocking_threshold_percentage,
|
|
56
65
|
get_env_fastapi_middleware_paths,
|
|
57
66
|
get_env_gql_extension_paths,
|
|
58
67
|
get_env_grpc_interceptor_paths,
|
|
59
68
|
get_env_host,
|
|
60
|
-
|
|
69
|
+
get_env_max_spans_queue_size,
|
|
61
70
|
get_env_port,
|
|
71
|
+
get_env_support_email,
|
|
62
72
|
server_instrumentation_is_enabled,
|
|
63
73
|
verify_server_environment_variables,
|
|
64
74
|
)
|
|
@@ -70,21 +80,32 @@ from phoenix.db.facilitator import Facilitator
|
|
|
70
80
|
from phoenix.db.helpers import SupportedSQLDialect
|
|
71
81
|
from phoenix.exceptions import PhoenixMigrationError
|
|
72
82
|
from phoenix.pointcloud.umap_parameters import UMAPParameters
|
|
83
|
+
from phoenix.server.api.auth_messages import AUTH_ERROR_MESSAGES, AuthErrorCode
|
|
73
84
|
from phoenix.server.api.context import Context, DataLoaders
|
|
74
85
|
from phoenix.server.api.dataloaders import (
|
|
86
|
+
AnnotationConfigsByProjectDataLoader,
|
|
75
87
|
AnnotationSummaryDataLoader,
|
|
88
|
+
AverageExperimentRepeatedRunGroupLatencyDataLoader,
|
|
76
89
|
AverageExperimentRunLatencyDataLoader,
|
|
77
90
|
CacheForDataLoaders,
|
|
91
|
+
DatasetDatasetSplitsDataLoader,
|
|
78
92
|
DatasetExampleRevisionsDataLoader,
|
|
93
|
+
DatasetExamplesAndVersionsByExperimentRunDataLoader,
|
|
79
94
|
DatasetExampleSpansDataLoader,
|
|
95
|
+
DatasetExampleSplitsDataLoader,
|
|
80
96
|
DocumentEvaluationsDataLoader,
|
|
81
97
|
DocumentEvaluationSummaryDataLoader,
|
|
82
98
|
DocumentRetrievalMetricsDataLoader,
|
|
83
99
|
ExperimentAnnotationSummaryDataLoader,
|
|
100
|
+
ExperimentDatasetSplitsDataLoader,
|
|
84
101
|
ExperimentErrorRatesDataLoader,
|
|
102
|
+
ExperimentRepeatedRunGroupAnnotationSummariesDataLoader,
|
|
103
|
+
ExperimentRepeatedRunGroupsDataLoader,
|
|
85
104
|
ExperimentRunAnnotations,
|
|
86
105
|
ExperimentRunCountsDataLoader,
|
|
106
|
+
ExperimentRunsByExperimentAndExampleDataLoader,
|
|
87
107
|
ExperimentSequenceNumberDataLoader,
|
|
108
|
+
LastUsedTimesByGenerativeModelIdDataLoader,
|
|
88
109
|
LatencyMsQuantileDataLoader,
|
|
89
110
|
MinStartOrMaxEndTimeDataLoader,
|
|
90
111
|
NumChildSpansDataLoader,
|
|
@@ -93,6 +114,7 @@ from phoenix.server.api.dataloaders import (
|
|
|
93
114
|
ProjectIdsByTraceRetentionPolicyIdDataLoader,
|
|
94
115
|
PromptVersionSequenceNumberDataLoader,
|
|
95
116
|
RecordCountDataLoader,
|
|
117
|
+
SessionAnnotationsBySessionDataLoader,
|
|
96
118
|
SessionIODataLoader,
|
|
97
119
|
SessionNumTracesDataLoader,
|
|
98
120
|
SessionNumTracesWithErrorDataLoader,
|
|
@@ -100,19 +122,35 @@ from phoenix.server.api.dataloaders import (
|
|
|
100
122
|
SessionTraceLatencyMsQuantileDataLoader,
|
|
101
123
|
SpanAnnotationsDataLoader,
|
|
102
124
|
SpanByIdDataLoader,
|
|
125
|
+
SpanCostBySpanDataLoader,
|
|
126
|
+
SpanCostDetailsBySpanCostDataLoader,
|
|
127
|
+
SpanCostDetailSummaryEntriesByGenerativeModelDataLoader,
|
|
128
|
+
SpanCostDetailSummaryEntriesByProjectSessionDataLoader,
|
|
129
|
+
SpanCostDetailSummaryEntriesBySpanDataLoader,
|
|
130
|
+
SpanCostDetailSummaryEntriesByTraceDataLoader,
|
|
131
|
+
SpanCostSummaryByExperimentDataLoader,
|
|
132
|
+
SpanCostSummaryByExperimentRepeatedRunGroupDataLoader,
|
|
133
|
+
SpanCostSummaryByExperimentRunDataLoader,
|
|
134
|
+
SpanCostSummaryByGenerativeModelDataLoader,
|
|
135
|
+
SpanCostSummaryByProjectDataLoader,
|
|
136
|
+
SpanCostSummaryByProjectSessionDataLoader,
|
|
137
|
+
SpanCostSummaryByTraceDataLoader,
|
|
103
138
|
SpanDatasetExamplesDataLoader,
|
|
104
139
|
SpanDescendantsDataLoader,
|
|
105
140
|
SpanProjectsDataLoader,
|
|
106
141
|
TableFieldsDataLoader,
|
|
107
142
|
TokenCountDataLoader,
|
|
143
|
+
TokenPricesByModelDataLoader,
|
|
144
|
+
TraceAnnotationsByTraceDataLoader,
|
|
108
145
|
TraceByTraceIdsDataLoader,
|
|
109
146
|
TraceRetentionPolicyIdByProjectIdDataLoader,
|
|
110
147
|
TraceRootSpansDataLoader,
|
|
111
148
|
UserRolesDataLoader,
|
|
112
149
|
UsersDataLoader,
|
|
113
150
|
)
|
|
151
|
+
from phoenix.server.api.dataloaders.dataset_labels import DatasetLabelsDataLoader
|
|
114
152
|
from phoenix.server.api.routers import (
|
|
115
|
-
|
|
153
|
+
create_auth_router,
|
|
116
154
|
create_embeddings_router,
|
|
117
155
|
create_v1_router,
|
|
118
156
|
oauth2_router,
|
|
@@ -120,6 +158,9 @@ from phoenix.server.api.routers import (
|
|
|
120
158
|
from phoenix.server.api.routers.v1 import REST_API_VERSION
|
|
121
159
|
from phoenix.server.api.schema import build_graphql_schema
|
|
122
160
|
from phoenix.server.bearer_auth import BearerTokenAuthBackend, is_authenticated
|
|
161
|
+
from phoenix.server.daemons.db_disk_usage_monitor import DbDiskUsageMonitor
|
|
162
|
+
from phoenix.server.daemons.generative_model_store import GenerativeModelStore
|
|
163
|
+
from phoenix.server.daemons.span_cost_calculator import SpanCostCalculator
|
|
123
164
|
from phoenix.server.dml_event import DmlEvent
|
|
124
165
|
from phoenix.server.dml_event_handler import DmlEventHandler
|
|
125
166
|
from phoenix.server.email.types import EmailSender
|
|
@@ -127,6 +168,7 @@ from phoenix.server.grpc_server import GrpcServer
|
|
|
127
168
|
from phoenix.server.jwt_store import JwtStore
|
|
128
169
|
from phoenix.server.middleware.gzip import GZipMiddleware
|
|
129
170
|
from phoenix.server.oauth2 import OAuth2Clients
|
|
171
|
+
from phoenix.server.prometheus import SPAN_QUEUE_REJECTIONS
|
|
130
172
|
from phoenix.server.retention import TraceDataSweeper
|
|
131
173
|
from phoenix.server.telemetry import initialize_opentelemetry_tracer_provider
|
|
132
174
|
from phoenix.server.types import (
|
|
@@ -137,6 +179,7 @@ from phoenix.server.types import (
|
|
|
137
179
|
LastUpdatedAt,
|
|
138
180
|
TokenStore,
|
|
139
181
|
)
|
|
182
|
+
from phoenix.server.utils import get_root_path, prepend_root_path
|
|
140
183
|
from phoenix.settings import Settings
|
|
141
184
|
from phoenix.trace.fixtures import (
|
|
142
185
|
TracesFixture,
|
|
@@ -155,6 +198,8 @@ from phoenix.version import __version__ as phoenix_version
|
|
|
155
198
|
if TYPE_CHECKING:
|
|
156
199
|
from opentelemetry.trace import TracerProvider
|
|
157
200
|
|
|
201
|
+
from phoenix.config import LDAPConfig
|
|
202
|
+
|
|
158
203
|
logger = logging.getLogger(__name__)
|
|
159
204
|
|
|
160
205
|
router = APIRouter(include_in_schema=False)
|
|
@@ -211,9 +256,27 @@ class AppConfig(NamedTuple):
|
|
|
211
256
|
web_manifest_path: Path
|
|
212
257
|
authentication_enabled: bool
|
|
213
258
|
""" Whether authentication is enabled """
|
|
259
|
+
auth_error_messages: dict[AuthErrorCode, str]
|
|
260
|
+
""" Mapping of auth error codes to user-friendly messages """
|
|
214
261
|
oauth2_idps: Sequence[OAuth2Idp]
|
|
215
262
|
basic_auth_disabled: bool = False
|
|
263
|
+
ldap_enabled: bool = False
|
|
264
|
+
""" Whether LDAP authentication is configured """
|
|
265
|
+
ldap_manual_user_creation_enabled: bool = False
|
|
266
|
+
""" Whether manual LDAP user creation is allowed (False when LDAP disabled or no email attr) """
|
|
216
267
|
auto_login_idp_name: Optional[str] = None
|
|
268
|
+
fullstory_org: Optional[str] = None
|
|
269
|
+
""" FullStory organization ID for web analytics tracking """
|
|
270
|
+
scarf_sh_pixel_id: Optional[str] = None
|
|
271
|
+
""" Scarf.sh pixel ID for open-source analytics and usage """
|
|
272
|
+
management_url: Optional[str] = None
|
|
273
|
+
""" URL for a phoenix management interface, only visible to management users """
|
|
274
|
+
support_email: Optional[str] = None
|
|
275
|
+
""" Support email address for user assistance """
|
|
276
|
+
has_db_threshold: bool = False
|
|
277
|
+
""" Whether the database has a threshold for usage """
|
|
278
|
+
allow_external_resources: bool = True
|
|
279
|
+
""" Whether to allow external resources like Google Fonts in the web interface """
|
|
217
280
|
|
|
218
281
|
|
|
219
282
|
class Static(StaticFiles):
|
|
@@ -235,9 +298,6 @@ class Static(StaticFiles):
|
|
|
235
298
|
return {}
|
|
236
299
|
raise e
|
|
237
300
|
|
|
238
|
-
def _sanitize_basename(self, basename: str) -> str:
|
|
239
|
-
return basename[:-1] if basename.endswith("/") else basename
|
|
240
|
-
|
|
241
301
|
async def get_response(self, path: str, scope: Scope) -> Response:
|
|
242
302
|
# Redirect to the oauth2 login page if basic auth is disabled and auto_login is enabled
|
|
243
303
|
# TODO: this needs to be refactored to be cleaner
|
|
@@ -246,14 +306,10 @@ class Static(StaticFiles):
|
|
|
246
306
|
and self._app_config.basic_auth_disabled
|
|
247
307
|
and self._app_config.auto_login_idp_name
|
|
248
308
|
):
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
str(
|
|
252
|
-
Path(get_env_host_root_path())
|
|
253
|
-
/ f"oauth2/{self._app_config.auto_login_idp_name}/login"
|
|
254
|
-
)
|
|
309
|
+
redirect_path = prepend_root_path(
|
|
310
|
+
scope, f"oauth2/{self._app_config.auto_login_idp_name}/login"
|
|
255
311
|
)
|
|
256
|
-
url =
|
|
312
|
+
url = URL(redirect_path).include_query_params(**Request(scope).query_params)
|
|
257
313
|
return RedirectResponse(url=url)
|
|
258
314
|
try:
|
|
259
315
|
response = await super().get_response(path, scope)
|
|
@@ -270,7 +326,7 @@ class Static(StaticFiles):
|
|
|
270
326
|
"min_dist": self._app_config.min_dist,
|
|
271
327
|
"n_neighbors": self._app_config.n_neighbors,
|
|
272
328
|
"n_samples": self._app_config.n_samples,
|
|
273
|
-
"basename":
|
|
329
|
+
"basename": get_root_path(scope),
|
|
274
330
|
"platform_version": phoenix_version,
|
|
275
331
|
"request": request,
|
|
276
332
|
"is_development": self._app_config.is_development,
|
|
@@ -278,7 +334,16 @@ class Static(StaticFiles):
|
|
|
278
334
|
"authentication_enabled": self._app_config.authentication_enabled,
|
|
279
335
|
"oauth2_idps": self._app_config.oauth2_idps,
|
|
280
336
|
"basic_auth_disabled": self._app_config.basic_auth_disabled,
|
|
337
|
+
"ldap_enabled": self._app_config.ldap_enabled,
|
|
338
|
+
"ldap_manual_user_creation_enabled": self._app_config.ldap_manual_user_creation_enabled, # noqa: E501
|
|
281
339
|
"auto_login_idp_name": self._app_config.auto_login_idp_name,
|
|
340
|
+
"fullstory_org": self._app_config.fullstory_org,
|
|
341
|
+
"scarf_sh_pixel_id": self._app_config.scarf_sh_pixel_id,
|
|
342
|
+
"management_url": self._app_config.management_url,
|
|
343
|
+
"support_email": self._app_config.support_email,
|
|
344
|
+
"has_db_threshold": self._app_config.has_db_threshold,
|
|
345
|
+
"allow_external_resources": self._app_config.allow_external_resources,
|
|
346
|
+
"auth_error_messages": self._app_config.auth_error_messages,
|
|
282
347
|
},
|
|
283
348
|
)
|
|
284
349
|
except Exception as e:
|
|
@@ -301,7 +366,7 @@ class RequestOriginHostnameValidator(BaseHTTPMiddleware):
|
|
|
301
366
|
if not (url := headers.get(key)):
|
|
302
367
|
continue
|
|
303
368
|
if urlparse(url).hostname not in self._trusted_hostnames:
|
|
304
|
-
return Response(f"untrusted {key}", status_code=
|
|
369
|
+
return Response(f"untrusted {key}", status_code=401)
|
|
305
370
|
return await call_next(request)
|
|
306
371
|
|
|
307
372
|
|
|
@@ -360,19 +425,16 @@ async def version() -> PlainTextResponse:
|
|
|
360
425
|
return PlainTextResponse(f"{phoenix_version}")
|
|
361
426
|
|
|
362
427
|
|
|
363
|
-
DB_MUTEX: Optional[asyncio.Lock] = None
|
|
364
|
-
|
|
365
|
-
|
|
366
428
|
def _db(
|
|
367
|
-
engine: AsyncEngine,
|
|
368
|
-
) -> Callable[[], AbstractAsyncContextManager[AsyncSession]]:
|
|
429
|
+
engine: AsyncEngine,
|
|
430
|
+
) -> Callable[[Optional[asyncio.Lock]], AbstractAsyncContextManager[AsyncSession]]:
|
|
369
431
|
Session = async_sessionmaker(engine, expire_on_commit=False)
|
|
370
432
|
|
|
371
433
|
@contextlib.asynccontextmanager
|
|
372
|
-
async def factory() -> AsyncIterator[AsyncSession]:
|
|
434
|
+
async def factory(lock: Optional[asyncio.Lock] = None) -> AsyncIterator[AsyncSession]:
|
|
373
435
|
async with contextlib.AsyncExitStack() as stack:
|
|
374
|
-
if
|
|
375
|
-
await stack.enter_async_context(
|
|
436
|
+
if lock:
|
|
437
|
+
await stack.enter_async_context(lock)
|
|
376
438
|
yield await stack.enter_async_context(Session.begin())
|
|
377
439
|
|
|
378
440
|
return factory
|
|
@@ -391,13 +453,13 @@ class Scaffolder(DaemonTask):
|
|
|
391
453
|
def __init__(
|
|
392
454
|
self,
|
|
393
455
|
config: ScaffolderConfig,
|
|
394
|
-
|
|
395
|
-
|
|
456
|
+
enqueue_span: Callable[[Span, ProjectName], Awaitable[None]],
|
|
457
|
+
enqueue_evaluation: Callable[[pb.Evaluation], Awaitable[None]],
|
|
396
458
|
) -> None:
|
|
397
459
|
super().__init__()
|
|
398
460
|
self._db = config.db
|
|
399
|
-
self.
|
|
400
|
-
self.
|
|
461
|
+
self._enqueue_span = enqueue_span
|
|
462
|
+
self._enqueue_evaluation = enqueue_evaluation
|
|
401
463
|
self._tracing_fixtures = [
|
|
402
464
|
get_trace_fixture_by_name(name) for name in set(config.tracing_fixture_names)
|
|
403
465
|
]
|
|
@@ -468,9 +530,9 @@ class Scaffolder(DaemonTask):
|
|
|
468
530
|
project_name = fixture.project_name or fixture.name
|
|
469
531
|
logger.info(f"Loading '{project_name}' fixtures...")
|
|
470
532
|
for span in fixture_spans:
|
|
471
|
-
await self.
|
|
533
|
+
await self._enqueue_span(span, project_name)
|
|
472
534
|
for evaluation in fixture_evals:
|
|
473
|
-
await self.
|
|
535
|
+
await self._enqueue_evaluation(evaluation)
|
|
474
536
|
|
|
475
537
|
except FileNotFoundError:
|
|
476
538
|
logger.warning(f"Fixture file not found for '{fixture.name}'")
|
|
@@ -493,12 +555,41 @@ class Scaffolder(DaemonTask):
|
|
|
493
555
|
logger.error(f"Error processing dataset fixture: {e}")
|
|
494
556
|
|
|
495
557
|
|
|
558
|
+
class _CapacityIndicator(Protocol):
|
|
559
|
+
@property
|
|
560
|
+
def is_full(self) -> bool: ...
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
class CapacityInterceptor(AsyncServerInterceptor):
|
|
564
|
+
def __init__(self, indicator: _CapacityIndicator):
|
|
565
|
+
self._indicator = indicator
|
|
566
|
+
|
|
567
|
+
@override
|
|
568
|
+
async def intercept(
|
|
569
|
+
self,
|
|
570
|
+
method: Callable[[Any, grpc.aio.ServicerContext], Awaitable[Any]],
|
|
571
|
+
request_or_iterator: Any,
|
|
572
|
+
context: grpc.aio.ServicerContext,
|
|
573
|
+
method_name: str,
|
|
574
|
+
) -> Any:
|
|
575
|
+
if self._indicator.is_full:
|
|
576
|
+
SPAN_QUEUE_REJECTIONS.inc()
|
|
577
|
+
context.set_code(grpc.StatusCode.RESOURCE_EXHAUSTED)
|
|
578
|
+
context.set_details("Server is at capacity and cannot process more requests")
|
|
579
|
+
return
|
|
580
|
+
|
|
581
|
+
return await method(request_or_iterator, context)
|
|
582
|
+
|
|
583
|
+
|
|
496
584
|
def _lifespan(
|
|
497
585
|
*,
|
|
498
586
|
db: DbSessionFactory,
|
|
499
587
|
bulk_inserter: BulkInserter,
|
|
500
588
|
dml_event_handler: DmlEventHandler,
|
|
501
589
|
trace_data_sweeper: Optional[TraceDataSweeper],
|
|
590
|
+
span_cost_calculator: SpanCostCalculator,
|
|
591
|
+
generative_model_store: GenerativeModelStore,
|
|
592
|
+
db_disk_usage_monitor: DbDiskUsageMonitor,
|
|
502
593
|
token_store: Optional[TokenStore] = None,
|
|
503
594
|
tracer_provider: Optional["TracerProvider"] = None,
|
|
504
595
|
enable_prometheus: bool = False,
|
|
@@ -506,47 +597,55 @@ def _lifespan(
|
|
|
506
597
|
shutdown_callbacks: Iterable[_Callback] = (),
|
|
507
598
|
read_only: bool = False,
|
|
508
599
|
scaffolder_config: Optional[ScaffolderConfig] = None,
|
|
600
|
+
grpc_interceptors: Iterable[AsyncServerInterceptor] = (),
|
|
509
601
|
) -> StatefulLifespan[FastAPI]:
|
|
510
602
|
@contextlib.asynccontextmanager
|
|
511
603
|
async def lifespan(_: FastAPI) -> AsyncIterator[dict[str, Any]]:
|
|
512
604
|
for callback in startup_callbacks:
|
|
513
605
|
if isinstance((res := callback()), Awaitable):
|
|
514
606
|
await res
|
|
515
|
-
|
|
516
|
-
DB_MUTEX = asyncio.Lock() if db.dialect is SupportedSQLDialect.SQLITE else None
|
|
607
|
+
db.lock = asyncio.Lock() if db.dialect is SupportedSQLDialect.SQLITE else None
|
|
517
608
|
async with AsyncExitStack() as stack:
|
|
518
609
|
(
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
610
|
+
enqueue_annotations,
|
|
611
|
+
enqueue_span,
|
|
612
|
+
enqueue_evaluation,
|
|
522
613
|
enqueue_operation,
|
|
523
614
|
) = await stack.enter_async_context(bulk_inserter)
|
|
615
|
+
interceptors = [
|
|
616
|
+
CapacityInterceptor(bulk_inserter),
|
|
617
|
+
*user_grpc_interceptors(),
|
|
618
|
+
*grpc_interceptors,
|
|
619
|
+
]
|
|
524
620
|
grpc_server = GrpcServer(
|
|
525
|
-
|
|
621
|
+
enqueue_span,
|
|
526
622
|
disabled=read_only,
|
|
527
623
|
tracer_provider=tracer_provider,
|
|
528
624
|
enable_prometheus=enable_prometheus,
|
|
529
625
|
token_store=token_store,
|
|
530
|
-
interceptors=
|
|
626
|
+
interceptors=interceptors,
|
|
531
627
|
)
|
|
532
628
|
await stack.enter_async_context(grpc_server)
|
|
533
629
|
await stack.enter_async_context(dml_event_handler)
|
|
534
630
|
if trace_data_sweeper:
|
|
535
631
|
await stack.enter_async_context(trace_data_sweeper)
|
|
632
|
+
await stack.enter_async_context(span_cost_calculator)
|
|
633
|
+
await stack.enter_async_context(generative_model_store)
|
|
634
|
+
await stack.enter_async_context(db_disk_usage_monitor)
|
|
536
635
|
if scaffolder_config:
|
|
537
636
|
scaffolder = Scaffolder(
|
|
538
637
|
config=scaffolder_config,
|
|
539
|
-
|
|
540
|
-
|
|
638
|
+
enqueue_span=enqueue_span,
|
|
639
|
+
enqueue_evaluation=enqueue_evaluation,
|
|
541
640
|
)
|
|
542
641
|
await stack.enter_async_context(scaffolder)
|
|
543
642
|
if isinstance(token_store, AbstractAsyncContextManager):
|
|
544
643
|
await stack.enter_async_context(token_store)
|
|
545
644
|
yield {
|
|
546
645
|
"event_queue": dml_event_handler,
|
|
547
|
-
"
|
|
548
|
-
"
|
|
549
|
-
"
|
|
646
|
+
"enqueue_annotations": enqueue_annotations,
|
|
647
|
+
"enqueue_span": enqueue_span,
|
|
648
|
+
"enqueue_evaluation": enqueue_evaluation,
|
|
550
649
|
"enqueue_operation": enqueue_operation,
|
|
551
650
|
}
|
|
552
651
|
for callback in shutdown_callbacks:
|
|
@@ -580,6 +679,7 @@ def create_graphql_router(
|
|
|
580
679
|
export_path: Path,
|
|
581
680
|
last_updated_at: CanGetLastUpdatedAt,
|
|
582
681
|
authentication_enabled: bool,
|
|
682
|
+
span_cost_calculator: SpanCostCalculator,
|
|
583
683
|
corpus: Optional[Model] = None,
|
|
584
684
|
cache_for_dataloaders: Optional[CacheForDataLoaders] = None,
|
|
585
685
|
event_queue: CanPutItem[DmlEvent],
|
|
@@ -597,6 +697,7 @@ def create_graphql_router(
|
|
|
597
697
|
export_path (Path): the file path to export data to for download (legacy)
|
|
598
698
|
last_updated_at (CanGetLastUpdatedAt): How to get the last updated timestamp for updates.
|
|
599
699
|
authentication_enabled (bool): Whether authentication is enabled.
|
|
700
|
+
span_cost_calculator (SpanCostCalculator): The span cost calculator for calculating costs.
|
|
600
701
|
event_queue (CanPutItem[DmlEvent]): The event queue for DML events.
|
|
601
702
|
corpus (Optional[Model], optional): the corpus for UMAP projection. Defaults to None.
|
|
602
703
|
cache_for_dataloaders (Optional[CacheForDataLoaders], optional): GraphQL data loaders.
|
|
@@ -618,9 +719,24 @@ def create_graphql_router(
|
|
|
618
719
|
last_updated_at=last_updated_at,
|
|
619
720
|
event_queue=event_queue,
|
|
620
721
|
data_loaders=DataLoaders(
|
|
722
|
+
annotation_configs_by_project=AnnotationConfigsByProjectDataLoader(db),
|
|
723
|
+
average_experiment_repeated_run_group_latency=AverageExperimentRepeatedRunGroupLatencyDataLoader(
|
|
724
|
+
db
|
|
725
|
+
),
|
|
621
726
|
average_experiment_run_latency=AverageExperimentRunLatencyDataLoader(db),
|
|
727
|
+
dataset_dataset_splits=DatasetDatasetSplitsDataLoader(db),
|
|
728
|
+
dataset_example_fields=TableFieldsDataLoader(db, models.DatasetExample),
|
|
622
729
|
dataset_example_revisions=DatasetExampleRevisionsDataLoader(db),
|
|
623
730
|
dataset_example_spans=DatasetExampleSpansDataLoader(db),
|
|
731
|
+
dataset_examples_and_versions_by_experiment_run=DatasetExamplesAndVersionsByExperimentRunDataLoader(
|
|
732
|
+
db
|
|
733
|
+
),
|
|
734
|
+
dataset_example_splits=DatasetExampleSplitsDataLoader(db),
|
|
735
|
+
dataset_fields=TableFieldsDataLoader(db, models.Dataset),
|
|
736
|
+
dataset_split_fields=TableFieldsDataLoader(db, models.DatasetSplit),
|
|
737
|
+
dataset_version_fields=TableFieldsDataLoader(db, models.DatasetVersion),
|
|
738
|
+
dataset_labels=DatasetLabelsDataLoader(db),
|
|
739
|
+
dataset_label_fields=TableFieldsDataLoader(db, models.DatasetLabel),
|
|
624
740
|
document_evaluation_summaries=DocumentEvaluationSummaryDataLoader(
|
|
625
741
|
db,
|
|
626
742
|
cache_map=(
|
|
@@ -629,6 +745,7 @@ def create_graphql_router(
|
|
|
629
745
|
else None
|
|
630
746
|
),
|
|
631
747
|
),
|
|
748
|
+
document_annotation_fields=TableFieldsDataLoader(db, models.DocumentAnnotation),
|
|
632
749
|
document_evaluations=DocumentEvaluationsDataLoader(db),
|
|
633
750
|
document_retrieval_metrics=DocumentRetrievalMetricsDataLoader(db),
|
|
634
751
|
annotation_summaries=AnnotationSummaryDataLoader(
|
|
@@ -638,10 +755,27 @@ def create_graphql_router(
|
|
|
638
755
|
),
|
|
639
756
|
),
|
|
640
757
|
experiment_annotation_summaries=ExperimentAnnotationSummaryDataLoader(db),
|
|
758
|
+
experiment_dataset_splits=ExperimentDatasetSplitsDataLoader(db),
|
|
641
759
|
experiment_error_rates=ExperimentErrorRatesDataLoader(db),
|
|
760
|
+
experiment_fields=TableFieldsDataLoader(db, models.Experiment),
|
|
761
|
+
experiment_repeated_run_group_annotation_summaries=ExperimentRepeatedRunGroupAnnotationSummariesDataLoader(
|
|
762
|
+
db
|
|
763
|
+
),
|
|
764
|
+
experiment_repeated_run_groups=ExperimentRepeatedRunGroupsDataLoader(db),
|
|
765
|
+
experiment_run_annotation_fields=TableFieldsDataLoader(
|
|
766
|
+
db, models.ExperimentRunAnnotation
|
|
767
|
+
),
|
|
642
768
|
experiment_run_annotations=ExperimentRunAnnotations(db),
|
|
643
769
|
experiment_run_counts=ExperimentRunCountsDataLoader(db),
|
|
770
|
+
experiment_run_fields=TableFieldsDataLoader(db, models.ExperimentRun),
|
|
771
|
+
experiment_runs_by_experiment_and_example=ExperimentRunsByExperimentAndExampleDataLoader(
|
|
772
|
+
db
|
|
773
|
+
),
|
|
644
774
|
experiment_sequence_number=ExperimentSequenceNumberDataLoader(db),
|
|
775
|
+
generative_model_fields=TableFieldsDataLoader(db, models.GenerativeModel),
|
|
776
|
+
last_used_times_by_generative_model_id=LastUsedTimesByGenerativeModelIdDataLoader(
|
|
777
|
+
db
|
|
778
|
+
),
|
|
645
779
|
latency_ms_quantile=LatencyMsQuantileDataLoader(
|
|
646
780
|
db,
|
|
647
781
|
cache_map=(
|
|
@@ -662,20 +796,59 @@ def create_graphql_router(
|
|
|
662
796
|
projects_by_trace_retention_policy_id=ProjectIdsByTraceRetentionPolicyIdDataLoader(
|
|
663
797
|
db
|
|
664
798
|
),
|
|
799
|
+
prompt_fields=TableFieldsDataLoader(db, models.Prompt),
|
|
800
|
+
prompt_label_fields=TableFieldsDataLoader(db, models.PromptLabel),
|
|
665
801
|
prompt_version_sequence_number=PromptVersionSequenceNumberDataLoader(db),
|
|
802
|
+
prompt_version_tag_fields=TableFieldsDataLoader(db, models.PromptVersionTag),
|
|
803
|
+
project_session_annotation_fields=TableFieldsDataLoader(
|
|
804
|
+
db, models.ProjectSessionAnnotation
|
|
805
|
+
),
|
|
806
|
+
project_session_fields=TableFieldsDataLoader(db, models.ProjectSession),
|
|
666
807
|
record_counts=RecordCountDataLoader(
|
|
667
808
|
db,
|
|
668
809
|
cache_map=cache_for_dataloaders.record_count if cache_for_dataloaders else None,
|
|
669
810
|
),
|
|
811
|
+
session_annotations_by_session=SessionAnnotationsBySessionDataLoader(db),
|
|
670
812
|
session_first_inputs=SessionIODataLoader(db, "first_input"),
|
|
671
813
|
session_last_outputs=SessionIODataLoader(db, "last_output"),
|
|
672
814
|
session_num_traces=SessionNumTracesDataLoader(db),
|
|
673
815
|
session_num_traces_with_error=SessionNumTracesWithErrorDataLoader(db),
|
|
674
816
|
session_token_usages=SessionTokenUsagesDataLoader(db),
|
|
675
817
|
session_trace_latency_ms_quantile=SessionTraceLatencyMsQuantileDataLoader(db),
|
|
818
|
+
span_annotation_fields=TableFieldsDataLoader(db, models.SpanAnnotation),
|
|
676
819
|
span_annotations=SpanAnnotationsDataLoader(db),
|
|
677
820
|
span_fields=TableFieldsDataLoader(db, models.Span),
|
|
678
821
|
span_by_id=SpanByIdDataLoader(db),
|
|
822
|
+
span_cost_by_span=SpanCostBySpanDataLoader(db),
|
|
823
|
+
span_cost_detail_summary_entries_by_generative_model=SpanCostDetailSummaryEntriesByGenerativeModelDataLoader(
|
|
824
|
+
db
|
|
825
|
+
),
|
|
826
|
+
span_cost_detail_summary_entries_by_project_session=SpanCostDetailSummaryEntriesByProjectSessionDataLoader(
|
|
827
|
+
db
|
|
828
|
+
),
|
|
829
|
+
span_cost_detail_summary_entries_by_span=SpanCostDetailSummaryEntriesBySpanDataLoader(
|
|
830
|
+
db
|
|
831
|
+
),
|
|
832
|
+
span_cost_detail_summary_entries_by_trace=SpanCostDetailSummaryEntriesByTraceDataLoader(
|
|
833
|
+
db
|
|
834
|
+
),
|
|
835
|
+
span_cost_details_by_span_cost=SpanCostDetailsBySpanCostDataLoader(db),
|
|
836
|
+
span_cost_detail_fields=TableFieldsDataLoader(db, models.SpanCostDetail),
|
|
837
|
+
span_cost_fields=TableFieldsDataLoader(db, models.SpanCost),
|
|
838
|
+
span_cost_summary_by_experiment=SpanCostSummaryByExperimentDataLoader(db),
|
|
839
|
+
span_cost_summary_by_experiment_repeated_run_group=SpanCostSummaryByExperimentRepeatedRunGroupDataLoader(
|
|
840
|
+
db
|
|
841
|
+
),
|
|
842
|
+
span_cost_summary_by_experiment_run=SpanCostSummaryByExperimentRunDataLoader(db),
|
|
843
|
+
span_cost_summary_by_generative_model=SpanCostSummaryByGenerativeModelDataLoader(
|
|
844
|
+
db
|
|
845
|
+
),
|
|
846
|
+
span_cost_summary_by_project=SpanCostSummaryByProjectDataLoader(
|
|
847
|
+
db,
|
|
848
|
+
cache_map=cache_for_dataloaders.token_cost if cache_for_dataloaders else None,
|
|
849
|
+
),
|
|
850
|
+
span_cost_summary_by_project_session=SpanCostSummaryByProjectSessionDataLoader(db),
|
|
851
|
+
span_cost_summary_by_trace=SpanCostSummaryByTraceDataLoader(db),
|
|
679
852
|
span_dataset_examples=SpanDatasetExamplesDataLoader(db),
|
|
680
853
|
span_descendants=SpanDescendantsDataLoader(db),
|
|
681
854
|
span_projects=SpanProjectsDataLoader(db),
|
|
@@ -683,6 +856,9 @@ def create_graphql_router(
|
|
|
683
856
|
db,
|
|
684
857
|
cache_map=cache_for_dataloaders.token_count if cache_for_dataloaders else None,
|
|
685
858
|
),
|
|
859
|
+
token_prices_by_model=TokenPricesByModelDataLoader(db),
|
|
860
|
+
trace_annotation_fields=TableFieldsDataLoader(db, models.TraceAnnotation),
|
|
861
|
+
trace_annotations_by_trace=TraceAnnotationsByTraceDataLoader(db),
|
|
686
862
|
trace_by_trace_ids=TraceByTraceIdsDataLoader(db),
|
|
687
863
|
trace_fields=TableFieldsDataLoader(db, models.Trace),
|
|
688
864
|
trace_retention_policy_id_by_project_id=TraceRetentionPolicyIdByProjectIdDataLoader(
|
|
@@ -694,6 +870,8 @@ def create_graphql_router(
|
|
|
694
870
|
trace_root_spans=TraceRootSpansDataLoader(db),
|
|
695
871
|
project_by_name=ProjectByNameDataLoader(db),
|
|
696
872
|
users=UsersDataLoader(db),
|
|
873
|
+
user_api_key_fields=TableFieldsDataLoader(db, models.ApiKey),
|
|
874
|
+
user_fields=TableFieldsDataLoader(db, models.User),
|
|
697
875
|
user_roles=UserRolesDataLoader(db),
|
|
698
876
|
),
|
|
699
877
|
cache_for_dataloaders=cache_for_dataloaders,
|
|
@@ -702,6 +880,7 @@ def create_graphql_router(
|
|
|
702
880
|
secret=secret,
|
|
703
881
|
token_store=token_store,
|
|
704
882
|
email_sender=email_sender,
|
|
883
|
+
span_cost_calculator=span_cost_calculator,
|
|
705
884
|
)
|
|
706
885
|
|
|
707
886
|
return GraphQLRouter(
|
|
@@ -766,6 +945,37 @@ async def plain_text_http_exception_handler(request: Request, exc: HTTPException
|
|
|
766
945
|
return PlainTextResponse(str(exc.detail), status_code=exc.status_code, headers=headers)
|
|
767
946
|
|
|
768
947
|
|
|
948
|
+
class _HasDbStatus(Protocol):
|
|
949
|
+
@property
|
|
950
|
+
def should_not_insert_or_update(self) -> bool: ...
|
|
951
|
+
|
|
952
|
+
|
|
953
|
+
class DbDiskUsageInterceptor(AsyncServerInterceptor):
|
|
954
|
+
def __init__(self, db: _HasDbStatus) -> None:
|
|
955
|
+
self._db = db
|
|
956
|
+
|
|
957
|
+
@override
|
|
958
|
+
async def intercept(
|
|
959
|
+
self,
|
|
960
|
+
method: Callable[[Any, grpc.aio.ServicerContext], Awaitable[Any]],
|
|
961
|
+
request_or_iterator: Any,
|
|
962
|
+
context: grpc.aio.ServicerContext,
|
|
963
|
+
method_name: str,
|
|
964
|
+
) -> Any:
|
|
965
|
+
if (
|
|
966
|
+
method_name.endswith("trace.v1.TraceService/Export")
|
|
967
|
+
and self._db.should_not_insert_or_update
|
|
968
|
+
):
|
|
969
|
+
details = (
|
|
970
|
+
"Database operations are disabled due to insufficient storage. "
|
|
971
|
+
"Please delete old data or increase storage."
|
|
972
|
+
)
|
|
973
|
+
if support_email := get_env_support_email():
|
|
974
|
+
details += f" Need help? Contact us at {support_email}"
|
|
975
|
+
await context.abort(grpc.StatusCode.RESOURCE_EXHAUSTED, details)
|
|
976
|
+
return await method(request_or_iterator, context)
|
|
977
|
+
|
|
978
|
+
|
|
769
979
|
def create_app(
|
|
770
980
|
db: DbSessionFactory,
|
|
771
981
|
export_path: Path,
|
|
@@ -789,9 +999,11 @@ def create_app(
|
|
|
789
999
|
scaffolder_config: Optional[ScaffolderConfig] = None,
|
|
790
1000
|
email_sender: Optional[EmailSender] = None,
|
|
791
1001
|
oauth2_client_configs: Optional[list[OAuth2ClientConfig]] = None,
|
|
1002
|
+
ldap_config: Optional["LDAPConfig"] = None,
|
|
792
1003
|
basic_auth_disabled: bool = False,
|
|
793
1004
|
bulk_inserter_factory: Optional[Callable[..., BulkInserter]] = None,
|
|
794
1005
|
allowed_origins: Optional[list[str]] = None,
|
|
1006
|
+
management_url: Optional[str] = None,
|
|
795
1007
|
) -> FastAPI:
|
|
796
1008
|
verify_server_environment_variables()
|
|
797
1009
|
if model.embedding_dimensions:
|
|
@@ -857,12 +1069,15 @@ def create_app(
|
|
|
857
1069
|
db=db,
|
|
858
1070
|
dml_event_handler=dml_event_handler,
|
|
859
1071
|
)
|
|
1072
|
+
generative_model_store = GenerativeModelStore(db)
|
|
1073
|
+
span_cost_calculator = SpanCostCalculator(db, generative_model_store)
|
|
860
1074
|
bulk_inserter = bulk_inserter_factory(
|
|
861
1075
|
db,
|
|
862
|
-
|
|
1076
|
+
span_cost_calculator=span_cost_calculator,
|
|
863
1077
|
event_queue=dml_event_handler,
|
|
864
1078
|
initial_batch_of_spans=initial_batch_of_spans,
|
|
865
1079
|
initial_batch_of_evaluations=initial_batch_of_evaluations,
|
|
1080
|
+
max_spans_queue_size=get_env_max_spans_queue_size(),
|
|
866
1081
|
)
|
|
867
1082
|
tracer_provider = None
|
|
868
1083
|
graphql_schema_extensions: list[Union[type[SchemaExtension], SchemaExtension]] = []
|
|
@@ -901,11 +1116,14 @@ def create_app(
|
|
|
901
1116
|
secret=secret,
|
|
902
1117
|
token_store=token_store,
|
|
903
1118
|
email_sender=email_sender,
|
|
1119
|
+
span_cost_calculator=span_cost_calculator,
|
|
904
1120
|
)
|
|
905
1121
|
if enable_prometheus:
|
|
906
1122
|
from phoenix.server.prometheus import PrometheusMiddleware
|
|
907
1123
|
|
|
908
1124
|
middlewares.append(Middleware(PrometheusMiddleware))
|
|
1125
|
+
grpc_interceptors: list[AsyncServerInterceptor] = []
|
|
1126
|
+
grpc_interceptors.append(DbDiskUsageInterceptor(db))
|
|
909
1127
|
app = FastAPI(
|
|
910
1128
|
title="Arize-Phoenix REST API",
|
|
911
1129
|
version=REST_API_VERSION,
|
|
@@ -915,6 +1133,10 @@ def create_app(
|
|
|
915
1133
|
bulk_inserter=bulk_inserter,
|
|
916
1134
|
dml_event_handler=dml_event_handler,
|
|
917
1135
|
trace_data_sweeper=trace_data_sweeper,
|
|
1136
|
+
span_cost_calculator=span_cost_calculator,
|
|
1137
|
+
generative_model_store=generative_model_store,
|
|
1138
|
+
db_disk_usage_monitor=DbDiskUsageMonitor(db, email_sender),
|
|
1139
|
+
grpc_interceptors=grpc_interceptors,
|
|
918
1140
|
token_store=token_store,
|
|
919
1141
|
tracer_provider=tracer_provider,
|
|
920
1142
|
enable_prometheus=enable_prometheus,
|
|
@@ -936,7 +1158,8 @@ def create_app(
|
|
|
936
1158
|
app.include_router(router)
|
|
937
1159
|
app.include_router(graphql_router)
|
|
938
1160
|
if authentication_enabled:
|
|
939
|
-
|
|
1161
|
+
# Only register LDAP endpoint if LDAP is configured
|
|
1162
|
+
app.include_router(create_auth_router(ldap_enabled=ldap_config is not None))
|
|
940
1163
|
app.include_router(oauth2_router)
|
|
941
1164
|
app.add_middleware(GZipMiddleware)
|
|
942
1165
|
web_manifest_path = SERVER_DIR / "static" / ".vite" / "manifest.json"
|
|
@@ -963,7 +1186,22 @@ def create_app(
|
|
|
963
1186
|
web_manifest_path=web_manifest_path,
|
|
964
1187
|
oauth2_idps=oauth2_idps,
|
|
965
1188
|
basic_auth_disabled=basic_auth_disabled,
|
|
1189
|
+
ldap_enabled=ldap_config is not None,
|
|
1190
|
+
# Disable manual user creation when LDAP disabled or no email attr
|
|
1191
|
+
ldap_manual_user_creation_enabled=(
|
|
1192
|
+
ldap_config.attr_email is not None if ldap_config else False
|
|
1193
|
+
),
|
|
966
1194
|
auto_login_idp_name=auto_login_idp_name,
|
|
1195
|
+
fullstory_org=Settings.fullstory_org,
|
|
1196
|
+
scarf_sh_pixel_id=Settings.scarf_sh_pixel_id,
|
|
1197
|
+
management_url=management_url,
|
|
1198
|
+
support_email=get_env_support_email(),
|
|
1199
|
+
has_db_threshold=bool(
|
|
1200
|
+
get_env_database_allocated_storage_capacity_gibibytes()
|
|
1201
|
+
and get_env_database_usage_insertion_blocking_threshold_percentage()
|
|
1202
|
+
),
|
|
1203
|
+
allow_external_resources=get_env_allow_external_resources(),
|
|
1204
|
+
auth_error_messages=dict(AUTH_ERROR_MESSAGES) if authentication_enabled else {},
|
|
967
1205
|
),
|
|
968
1206
|
),
|
|
969
1207
|
name="static",
|
|
@@ -975,8 +1213,15 @@ def create_app(
|
|
|
975
1213
|
app.state.access_token_expiry = access_token_expiry
|
|
976
1214
|
app.state.refresh_token_expiry = refresh_token_expiry
|
|
977
1215
|
app.state.oauth2_clients = OAuth2Clients.from_configs(oauth2_client_configs or [])
|
|
1216
|
+
# Cache LDAPAuthenticator to avoid re-parsing TLS config on every login
|
|
1217
|
+
if ldap_config:
|
|
1218
|
+
from phoenix.server.ldap import LDAPAuthenticator
|
|
1219
|
+
|
|
1220
|
+
app.state.ldap_authenticator = LDAPAuthenticator(ldap_config)
|
|
978
1221
|
app.state.db = db
|
|
979
1222
|
app.state.email_sender = email_sender
|
|
1223
|
+
app.state.span_cost_calculator = span_cost_calculator
|
|
1224
|
+
app.state.span_queue_is_full = lambda: bulk_inserter.is_full
|
|
980
1225
|
app = _add_get_secret_method(app=app, secret=secret)
|
|
981
1226
|
app = _add_get_token_store_method(app=app, token_store=token_store)
|
|
982
1227
|
if tracer_provider:
|