arize-phoenix 11.23.1__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-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/METADATA +61 -36
- {arize_phoenix-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/RECORD +212 -162
- {arize_phoenix-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/WHEEL +1 -1
- {arize_phoenix-11.23.1.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 +2 -1
- phoenix/auth.py +27 -2
- phoenix/config.py +1594 -81
- phoenix/db/README.md +546 -28
- phoenix/db/bulk_inserter.py +119 -116
- phoenix/db/engines.py +140 -33
- phoenix/db/facilitator.py +22 -1
- phoenix/db/helpers.py +818 -65
- phoenix/db/iam_auth.py +64 -0
- phoenix/db/insertion/dataset.py +133 -1
- phoenix/db/insertion/document_annotation.py +9 -6
- phoenix/db/insertion/evaluation.py +2 -3
- phoenix/db/insertion/helpers.py +2 -2
- phoenix/db/insertion/session_annotation.py +176 -0
- phoenix/db/insertion/span_annotation.py +3 -4
- phoenix/db/insertion/trace_annotation.py +3 -4
- phoenix/db/insertion/types.py +41 -18
- 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/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 +364 -56
- phoenix/db/pg_config.py +10 -0
- phoenix/db/types/trace_retention.py +7 -6
- phoenix/experiments/functions.py +69 -19
- phoenix/inferences/inferences.py +1 -2
- phoenix/server/api/auth.py +9 -0
- phoenix/server/api/auth_messages.py +46 -0
- phoenix/server/api/context.py +60 -0
- phoenix/server/api/dataloaders/__init__.py +36 -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/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_summary_by_experiment_repeated_run_group.py +64 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_project.py +28 -14
- phoenix/server/api/dataloaders/span_costs.py +3 -9
- 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/exceptions.py +5 -1
- phoenix/server/api/helpers/playground_clients.py +263 -83
- phoenix/server/api/helpers/playground_spans.py +2 -1
- phoenix/server/api/helpers/playground_users.py +26 -0
- phoenix/server/api/helpers/prompts/conversions/google.py +103 -0
- phoenix/server/api/helpers/prompts/models.py +61 -19
- phoenix/server/api/input_types/{SpanAnnotationFilter.py → AnnotationFilter.py} +22 -14
- phoenix/server/api/input_types/ChatCompletionInput.py +3 -0
- phoenix/server/api/input_types/CreateProjectSessionAnnotationInput.py +37 -0
- phoenix/server/api/input_types/DatasetFilter.py +5 -2
- phoenix/server/api/input_types/ExperimentRunSort.py +237 -0
- phoenix/server/api/input_types/GenerativeModelInput.py +3 -0
- phoenix/server/api/input_types/ProjectSessionSort.py +158 -1
- phoenix/server/api/input_types/PromptVersionInput.py +47 -1
- phoenix/server/api/input_types/SpanSort.py +3 -2
- phoenix/server/api/input_types/UpdateAnnotationInput.py +34 -0
- phoenix/server/api/input_types/UserRoleInput.py +1 -0
- phoenix/server/api/mutations/__init__.py +8 -0
- phoenix/server/api/mutations/annotation_config_mutations.py +8 -8
- phoenix/server/api/mutations/api_key_mutations.py +15 -20
- phoenix/server/api/mutations/chat_mutations.py +106 -37
- 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 +11 -9
- phoenix/server/api/mutations/project_mutations.py +4 -4
- 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 +13 -8
- phoenix/server/api/mutations/trace_mutations.py +3 -3
- phoenix/server/api/mutations/user_mutations.py +55 -26
- phoenix/server/api/queries.py +501 -617
- phoenix/server/api/routers/__init__.py +2 -2
- phoenix/server/api/routers/auth.py +141 -87
- phoenix/server/api/routers/ldap.py +229 -0
- phoenix/server/api/routers/oauth2.py +349 -101
- phoenix/server/api/routers/v1/__init__.py +22 -4
- phoenix/server/api/routers/v1/annotation_configs.py +19 -30
- phoenix/server/api/routers/v1/annotations.py +455 -13
- phoenix/server/api/routers/v1/datasets.py +355 -68
- phoenix/server/api/routers/v1/documents.py +142 -0
- phoenix/server/api/routers/v1/evaluations.py +20 -28
- phoenix/server/api/routers/v1/experiment_evaluations.py +16 -6
- phoenix/server/api/routers/v1/experiment_runs.py +335 -59
- phoenix/server/api/routers/v1/experiments.py +475 -47
- phoenix/server/api/routers/v1/projects.py +16 -50
- phoenix/server/api/routers/v1/prompts.py +50 -39
- phoenix/server/api/routers/v1/sessions.py +108 -0
- phoenix/server/api/routers/v1/spans.py +156 -96
- phoenix/server/api/routers/v1/traces.py +51 -77
- phoenix/server/api/routers/v1/users.py +64 -24
- phoenix/server/api/routers/v1/utils.py +3 -7
- phoenix/server/api/subscriptions.py +257 -93
- 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/Dataset.py +199 -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 +215 -68
- phoenix/server/api/types/ExperimentComparison.py +3 -9
- phoenix/server/api/types/ExperimentRepeatedRunGroup.py +155 -0
- phoenix/server/api/types/ExperimentRepeatedRunGroupAnnotationSummary.py +9 -0
- phoenix/server/api/types/ExperimentRun.py +120 -70
- phoenix/server/api/types/ExperimentRunAnnotation.py +158 -39
- phoenix/server/api/types/GenerativeModel.py +95 -42
- phoenix/server/api/types/GenerativeProvider.py +1 -1
- phoenix/server/api/types/ModelInterface.py +7 -2
- phoenix/server/api/types/PlaygroundModel.py +12 -2
- phoenix/server/api/types/Project.py +218 -185
- phoenix/server/api/types/ProjectSession.py +146 -29
- 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/Span.py +130 -123
- phoenix/server/api/types/SpanAnnotation.py +189 -42
- phoenix/server/api/types/SystemApiKey.py +65 -1
- phoenix/server/api/types/Trace.py +184 -53
- phoenix/server/api/types/TraceAnnotation.py +149 -50
- phoenix/server/api/types/User.py +128 -33
- 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 +154 -36
- phoenix/server/authorization.py +5 -4
- phoenix/server/bearer_auth.py +13 -5
- phoenix/server/cost_tracking/cost_model_lookup.py +42 -14
- phoenix/server/cost_tracking/model_cost_manifest.json +1085 -194
- phoenix/server/daemons/generative_model_store.py +61 -9
- phoenix/server/daemons/span_cost_calculator.py +10 -8
- phoenix/server/dml_event.py +13 -0
- phoenix/server/email/sender.py +29 -2
- phoenix/server/grpc_server.py +9 -9
- phoenix/server/jwt_store.py +8 -6
- phoenix/server/ldap.py +1449 -0
- phoenix/server/main.py +9 -3
- phoenix/server/oauth2.py +330 -12
- phoenix/server/prometheus.py +43 -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 +51 -53
- phoenix/server/static/assets/components-BreFUQQa.js +6702 -0
- phoenix/server/static/assets/{index-BPCwGQr8.js → index-CTQoemZv.js} +42 -35
- 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-Bw30oz1A.js → vendor-recharts-V9cwpXsm.js} +7 -7
- phoenix/server/static/assets/{vendor-shiki-DZajAPeq.js → vendor-shiki-Do--csgv.js} +1 -1
- phoenix/server/static/assets/vendor-three-CmB8bl_y.js +3840 -0
- phoenix/server/templates/index.html +7 -1
- phoenix/server/thread_server.py +1 -2
- phoenix/server/utils.py +74 -0
- phoenix/session/client.py +55 -1
- phoenix/session/data_extractor.py +5 -0
- phoenix/session/evaluation.py +8 -4
- phoenix/session/session.py +44 -8
- phoenix/settings.py +2 -0
- phoenix/trace/attributes.py +80 -13
- phoenix/trace/dsl/query.py +2 -0
- phoenix/trace/projects.py +5 -0
- phoenix/utilities/template_formatters.py +1 -1
- phoenix/version.py +1 -1
- phoenix/server/api/types/Evaluation.py +0 -39
- phoenix/server/static/assets/components-D0DWAf0l.js +0 -5650
- phoenix/server/static/assets/pages-Creyamao.js +0 -8612
- phoenix/server/static/assets/vendor-CU36oj8y.js +0 -905
- phoenix/server/static/assets/vendor-CqDb5u4o.css +0 -1
- phoenix/server/static/assets/vendor-arizeai-Ctgw0e1G.js +0 -168
- phoenix/server/static/assets/vendor-codemirror-Cojjzqb9.js +0 -25
- phoenix/server/static/assets/vendor-three-BLWp5bic.js +0 -2998
- phoenix/utilities/deprecation.py +0 -31
- {arize_phoenix-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/entry_points.txt +0 -0
- {arize_phoenix-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -110,14 +110,20 @@
|
|
|
110
110
|
authenticationEnabled: Boolean("{{authentication_enabled}}" == "True"),
|
|
111
111
|
oAuth2Idps: {{ oauth2_idps | tojson }},
|
|
112
112
|
basicAuthDisabled: Boolean("{{basic_auth_disabled}}" == "True"),
|
|
113
|
+
ldapEnabled: Boolean("{{ldap_enabled}}" == "True"),
|
|
114
|
+
ldapManualUserCreationEnabled: Boolean("{{ldap_manual_user_creation_enabled}}" == "True"),
|
|
113
115
|
managementUrl: {{management_url | tojson}},
|
|
114
116
|
supportEmail: {{support_email | tojson}},
|
|
115
117
|
hasDbThreshold: Boolean("{{has_db_threshold}}" == "True"),
|
|
116
118
|
allowExternalResources: Boolean("{{allow_external_resources}}" == "True"),
|
|
119
|
+
authErrorMessages: {{auth_error_messages | tojson}},
|
|
117
120
|
}),
|
|
118
121
|
writable: false
|
|
119
122
|
});
|
|
120
123
|
})()</script>
|
|
124
|
+
{% if scarf_sh_pixel_id -%}
|
|
125
|
+
<img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid={{scarf_sh_pixel_id}}" />
|
|
126
|
+
{%- endif %}
|
|
121
127
|
{% if is_development -%}
|
|
122
128
|
<script type="module">
|
|
123
129
|
import RefreshRuntime from 'http://localhost:5173/@react-refresh'
|
|
@@ -131,5 +137,5 @@
|
|
|
131
137
|
{%- else -%}
|
|
132
138
|
{{- render_js() -}}
|
|
133
139
|
{%- endif -%}
|
|
134
|
-
|
|
140
|
+
</body>
|
|
135
141
|
</html>
|
phoenix/server/thread_server.py
CHANGED
|
@@ -6,7 +6,6 @@ from time import sleep, time
|
|
|
6
6
|
|
|
7
7
|
from fastapi import FastAPI
|
|
8
8
|
from uvicorn import Config, Server
|
|
9
|
-
from uvicorn.config import LoopSetupType
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
def _nest_asyncio_applied() -> bool:
|
|
@@ -31,7 +30,7 @@ class ThreadServer(Server):
|
|
|
31
30
|
) -> None:
|
|
32
31
|
# Must use asyncio loop if nest_asyncio is applied
|
|
33
32
|
# Otherwise the app crashes when the server is run in a thread
|
|
34
|
-
loop
|
|
33
|
+
loop = "asyncio" if _nest_asyncio_applied() else "auto"
|
|
35
34
|
config = Config(
|
|
36
35
|
app=app,
|
|
37
36
|
host=host,
|
phoenix/server/utils.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from typing import Any, Mapping
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def prepend_root_path(scope: Mapping[str, Any], path: str) -> str:
|
|
5
|
+
"""
|
|
6
|
+
Prepends the ASGI root path to the given path if one is configured.
|
|
7
|
+
|
|
8
|
+
Normalizes the input path by ensuring it has a leading slash and removing
|
|
9
|
+
trailing slashes. The root path is already normalized by get_root_path().
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
scope: The ASGI scope dictionary containing the root_path key
|
|
13
|
+
path: The path to prepend the root path to (e.g., "/login", "logout")
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
The normalized path with root path prepended if configured,
|
|
17
|
+
otherwise just the normalized path. Never has a trailing slash
|
|
18
|
+
except when the result is just "/".
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
With root_path="/apps/phoenix":
|
|
22
|
+
- prepend_root_path(request.scope, "/login") -> "/apps/phoenix/login"
|
|
23
|
+
- prepend_root_path(request.scope, "login") -> "/apps/phoenix/login"
|
|
24
|
+
- prepend_root_path(request.scope, "/") -> "/apps/phoenix"
|
|
25
|
+
- prepend_root_path(request.scope, "") -> "/apps/phoenix"
|
|
26
|
+
- prepend_root_path(request.scope, "login/") -> "/apps/phoenix/login"
|
|
27
|
+
- prepend_root_path(request.scope, "/login/") -> "/apps/phoenix/login"
|
|
28
|
+
- prepend_root_path(request.scope, "abc/def/") -> "/apps/phoenix/abc/def"
|
|
29
|
+
|
|
30
|
+
With no root_path:
|
|
31
|
+
- prepend_root_path(request.scope, "/login") -> "/login"
|
|
32
|
+
- prepend_root_path(request.scope, "login") -> "/login"
|
|
33
|
+
- prepend_root_path(request.scope, "/") -> "/"
|
|
34
|
+
- prepend_root_path(request.scope, "") -> "/"
|
|
35
|
+
- prepend_root_path(request.scope, "login/") -> "/login"
|
|
36
|
+
- prepend_root_path(request.scope, "/login/") -> "/login"
|
|
37
|
+
- prepend_root_path(request.scope, "abc/def/") -> "/abc/def"
|
|
38
|
+
"""
|
|
39
|
+
path = path if path.startswith("/") else f"/{path}"
|
|
40
|
+
path = path.rstrip("/") or "/"
|
|
41
|
+
root_path = get_root_path(scope)
|
|
42
|
+
if path == "/":
|
|
43
|
+
return root_path or "/"
|
|
44
|
+
return f"{root_path}{path}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_root_path(scope: Mapping[str, Any]) -> str:
|
|
48
|
+
"""
|
|
49
|
+
Extracts and normalizes the root path from the ASGI scope.
|
|
50
|
+
|
|
51
|
+
The root path is typically set by reverse proxies or when the application
|
|
52
|
+
is mounted at a sub-path (e.g., "/apps/phoenix" when behind a proxy).
|
|
53
|
+
If present, ensures the path has a leading slash and removes trailing slashes.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
scope: The ASGI scope dictionary containing the root_path key
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
The normalized root path as a string (with leading slash, no trailing slash)
|
|
60
|
+
if configured, otherwise empty string.
|
|
61
|
+
|
|
62
|
+
Examples:
|
|
63
|
+
- Behind proxy at "/apps/phoenix": returns "/apps/phoenix"
|
|
64
|
+
- Missing leading slash "apps/phoenix": returns "/apps/phoenix"
|
|
65
|
+
- With trailing slash "/apps/phoenix/": returns "/apps/phoenix"
|
|
66
|
+
- Direct deployment: returns ""
|
|
67
|
+
- None in scope: returns ""
|
|
68
|
+
"""
|
|
69
|
+
root_path = str(scope.get("root_path") or "")
|
|
70
|
+
if not root_path:
|
|
71
|
+
return ""
|
|
72
|
+
if not root_path.startswith("/"):
|
|
73
|
+
root_path = f"/{root_path}"
|
|
74
|
+
return root_path.rstrip("/")
|
phoenix/session/client.py
CHANGED
|
@@ -19,7 +19,7 @@ from opentelemetry.proto.common.v1.common_pb2 import AnyValue, KeyValue
|
|
|
19
19
|
from opentelemetry.proto.resource.v1.resource_pb2 import Resource
|
|
20
20
|
from opentelemetry.proto.trace.v1.trace_pb2 import ResourceSpans, ScopeSpans
|
|
21
21
|
from pyarrow import ArrowInvalid, Table
|
|
22
|
-
from typing_extensions import TypeAlias, assert_never
|
|
22
|
+
from typing_extensions import TypeAlias, assert_never, deprecated
|
|
23
23
|
|
|
24
24
|
from phoenix.config import (
|
|
25
25
|
get_env_collector_endpoint,
|
|
@@ -110,6 +110,7 @@ class Client(TraceDataExtractor):
|
|
|
110
110
|
return session.url
|
|
111
111
|
return str(self._client.base_url)
|
|
112
112
|
|
|
113
|
+
@deprecated("Migrate to using client.spans.get_spans_dataframe via arize-phoenix-client")
|
|
113
114
|
def query_spans(
|
|
114
115
|
self,
|
|
115
116
|
*queries: SpanQuery,
|
|
@@ -124,6 +125,11 @@ class Client(TraceDataExtractor):
|
|
|
124
125
|
orphan_span_as_root_span: bool = True,
|
|
125
126
|
) -> Optional[Union[pd.DataFrame, list[pd.DataFrame]]]:
|
|
126
127
|
"""
|
|
128
|
+
.. deprecated::
|
|
129
|
+
This method is deprecated. Use ``client.spans.get_spans_dataframe()`` via
|
|
130
|
+
arize-phoenix-client instead.
|
|
131
|
+
See https://arize-phoenix.readthedocs.io/projects/client/en/latest/
|
|
132
|
+
|
|
127
133
|
Queries spans from the Phoenix server or active session based on specified criteria.
|
|
128
134
|
|
|
129
135
|
Args:
|
|
@@ -222,6 +228,7 @@ class Client(TraceDataExtractor):
|
|
|
222
228
|
return None if df.shape == (0, 0) else df
|
|
223
229
|
return results
|
|
224
230
|
|
|
231
|
+
@deprecated("Migrate to using client.spans.get_span_annotations via arize-phoenix-client")
|
|
225
232
|
def get_evaluations(
|
|
226
233
|
self,
|
|
227
234
|
project_name: Optional[str] = None,
|
|
@@ -229,6 +236,11 @@ class Client(TraceDataExtractor):
|
|
|
229
236
|
timeout: Optional[int] = DEFAULT_TIMEOUT_IN_SECONDS,
|
|
230
237
|
) -> list[Evaluations]:
|
|
231
238
|
"""
|
|
239
|
+
.. deprecated::
|
|
240
|
+
This method is deprecated. Use ``client.spans.get_span_annotations()`` via
|
|
241
|
+
arize-phoenix-client instead.
|
|
242
|
+
See https://arize-phoenix.readthedocs.io/projects/client/en/latest/
|
|
243
|
+
|
|
232
244
|
Retrieves evaluations for a given project from the Phoenix server or active session.
|
|
233
245
|
|
|
234
246
|
Args:
|
|
@@ -276,6 +288,7 @@ class Client(TraceDataExtractor):
|
|
|
276
288
|
f"with `import phoenix as px; px.launch_app()`"
|
|
277
289
|
)
|
|
278
290
|
|
|
291
|
+
@deprecated("Migrate to using client.spans.log_span_annotations via arize-phoenix-client")
|
|
279
292
|
def log_evaluations(
|
|
280
293
|
self,
|
|
281
294
|
*evals: Evaluations,
|
|
@@ -283,6 +296,11 @@ class Client(TraceDataExtractor):
|
|
|
283
296
|
**kwargs: Any,
|
|
284
297
|
) -> None:
|
|
285
298
|
"""
|
|
299
|
+
.. deprecated::
|
|
300
|
+
This method is deprecated. Use ``client.spans.log_span_annotations()`` via
|
|
301
|
+
arize-phoenix-client instead.
|
|
302
|
+
See https://arize-phoenix.readthedocs.io/projects/client/en/latest/
|
|
303
|
+
|
|
286
304
|
Logs evaluation data to the Phoenix server.
|
|
287
305
|
|
|
288
306
|
Args:
|
|
@@ -309,8 +327,14 @@ class Client(TraceDataExtractor):
|
|
|
309
327
|
timeout=timeout,
|
|
310
328
|
).raise_for_status()
|
|
311
329
|
|
|
330
|
+
@deprecated("Migrate to using client.spans.log_spans via arize-phoenix-client")
|
|
312
331
|
def log_traces(self, trace_dataset: TraceDataset, project_name: Optional[str] = None) -> None:
|
|
313
332
|
"""
|
|
333
|
+
.. deprecated::
|
|
334
|
+
This method is deprecated. Use ``client.spans.log_spans()`` via
|
|
335
|
+
arize-phoenix-client instead.
|
|
336
|
+
See https://arize-phoenix.readthedocs.io/projects/client/en/latest/
|
|
337
|
+
|
|
314
338
|
Logs traces from a TraceDataset to the Phoenix server.
|
|
315
339
|
|
|
316
340
|
Args:
|
|
@@ -379,6 +403,7 @@ class Client(TraceDataExtractor):
|
|
|
379
403
|
dataset = records[0]
|
|
380
404
|
return str(dataset["id"])
|
|
381
405
|
|
|
406
|
+
@deprecated("Migrate to using client.datasets.get_dataset via arize-phoenix-client")
|
|
382
407
|
def get_dataset(
|
|
383
408
|
self,
|
|
384
409
|
*,
|
|
@@ -387,6 +412,11 @@ class Client(TraceDataExtractor):
|
|
|
387
412
|
version_id: Optional[str] = None,
|
|
388
413
|
) -> Dataset:
|
|
389
414
|
"""
|
|
415
|
+
.. deprecated::
|
|
416
|
+
This method is deprecated. Use ``client.datasets.get_dataset()`` via
|
|
417
|
+
arize-phoenix-client instead.
|
|
418
|
+
See https://arize-phoenix.readthedocs.io/projects/client/en/latest/
|
|
419
|
+
|
|
390
420
|
Gets the dataset for a specific version, or gets the latest version of
|
|
391
421
|
the dataset if no version is specified.
|
|
392
422
|
|
|
@@ -433,6 +463,7 @@ class Client(TraceDataExtractor):
|
|
|
433
463
|
examples=examples,
|
|
434
464
|
)
|
|
435
465
|
|
|
466
|
+
@deprecated("Migrate to using client.datasets.get_dataset_versions via arize-phoenix-client")
|
|
436
467
|
def get_dataset_versions(
|
|
437
468
|
self,
|
|
438
469
|
dataset_id: str,
|
|
@@ -440,6 +471,11 @@ class Client(TraceDataExtractor):
|
|
|
440
471
|
limit: Optional[int] = 100,
|
|
441
472
|
) -> pd.DataFrame:
|
|
442
473
|
"""
|
|
474
|
+
.. deprecated::
|
|
475
|
+
This method is deprecated. Use ``client.datasets.get_dataset_versions()`` via
|
|
476
|
+
arize-phoenix-client instead.
|
|
477
|
+
See https://arize-phoenix.readthedocs.io/projects/client/en/latest/
|
|
478
|
+
|
|
443
479
|
Get dataset versions as pandas DataFrame.
|
|
444
480
|
|
|
445
481
|
Args:
|
|
@@ -459,6 +495,7 @@ class Client(TraceDataExtractor):
|
|
|
459
495
|
df["created_at"] = df["created_at"].apply(datetime.fromisoformat)
|
|
460
496
|
return df
|
|
461
497
|
|
|
498
|
+
@deprecated("Migrate to using client.datasets.create_dataset via arize-phoenix-client")
|
|
462
499
|
def upload_dataset(
|
|
463
500
|
self,
|
|
464
501
|
*,
|
|
@@ -474,6 +511,11 @@ class Client(TraceDataExtractor):
|
|
|
474
511
|
dataset_description: Optional[str] = None,
|
|
475
512
|
) -> Dataset:
|
|
476
513
|
"""
|
|
514
|
+
.. deprecated::
|
|
515
|
+
This method is deprecated. Use ``client.datasets.create_dataset()`` via
|
|
516
|
+
arize-phoenix-client instead.
|
|
517
|
+
See https://arize-phoenix.readthedocs.io/projects/client/en/latest/
|
|
518
|
+
|
|
477
519
|
Upload examples as dataset to the Phoenix server. If `dataframe` or
|
|
478
520
|
`csv_file_path` are provided, must also provide `input_keys` (and
|
|
479
521
|
optionally with `output_keys` or `metadata_keys` or both), which is a
|
|
@@ -535,6 +577,7 @@ class Client(TraceDataExtractor):
|
|
|
535
577
|
dataset_description=dataset_description,
|
|
536
578
|
)
|
|
537
579
|
|
|
580
|
+
@deprecated("Migrate to using client.datasets.add_examples_to_dataset via arize-phoenix-client")
|
|
538
581
|
def append_to_dataset(
|
|
539
582
|
self,
|
|
540
583
|
*,
|
|
@@ -549,6 +592,11 @@ class Client(TraceDataExtractor):
|
|
|
549
592
|
metadata: Iterable[Mapping[str, Any]] = (),
|
|
550
593
|
) -> Dataset:
|
|
551
594
|
"""
|
|
595
|
+
.. deprecated::
|
|
596
|
+
This method is deprecated. Use ``client.datasets.add_examples_to_dataset()`` via
|
|
597
|
+
arize-phoenix-client instead.
|
|
598
|
+
See https://arize-phoenix.readthedocs.io/projects/client/en/latest/
|
|
599
|
+
|
|
552
600
|
Append examples to dataset on the Phoenix server. If `dataframe` or
|
|
553
601
|
`csv_file_path` are provided, must also provide `input_keys` (and
|
|
554
602
|
optionally with `output_keys` or `metadata_keys` or both), which is a
|
|
@@ -609,8 +657,14 @@ class Client(TraceDataExtractor):
|
|
|
609
657
|
action="append",
|
|
610
658
|
)
|
|
611
659
|
|
|
660
|
+
@deprecated("Migrate to using client.experiments.get_experiment via arize-phoenix-client")
|
|
612
661
|
def get_experiment(self, *, experiment_id: str) -> Experiment:
|
|
613
662
|
"""
|
|
663
|
+
.. deprecated::
|
|
664
|
+
This method is deprecated. Use ``client.experiments.get_experiment()`` via
|
|
665
|
+
arize-phoenix-client instead.
|
|
666
|
+
See https://arize-phoenix.readthedocs.io/projects/client/en/latest/
|
|
667
|
+
|
|
614
668
|
Get an experiment by ID.
|
|
615
669
|
|
|
616
670
|
Retrieve an Experiment object by ID, enables running `evaluate_experiment` after finishing
|
|
@@ -3,6 +3,7 @@ from datetime import datetime
|
|
|
3
3
|
from typing import Optional, Union, cast
|
|
4
4
|
|
|
5
5
|
import pandas as pd
|
|
6
|
+
from typing_extensions import deprecated
|
|
6
7
|
|
|
7
8
|
from phoenix.trace import Evaluations
|
|
8
9
|
from phoenix.trace.dsl import SpanQuery
|
|
@@ -18,6 +19,7 @@ class TraceDataExtractor(ABC):
|
|
|
18
19
|
`Session` so that they both implement the same methods.
|
|
19
20
|
"""
|
|
20
21
|
|
|
22
|
+
@deprecated("Migrate to client.spans.get_spans_dataframe() from arize-phoenix-client")
|
|
21
23
|
@abstractmethod
|
|
22
24
|
def query_spans(
|
|
23
25
|
self,
|
|
@@ -30,6 +32,7 @@ class TraceDataExtractor(ABC):
|
|
|
30
32
|
timeout: Optional[int] = DEFAULT_TIMEOUT_IN_SECONDS,
|
|
31
33
|
) -> Optional[Union[pd.DataFrame, list[pd.DataFrame]]]: ...
|
|
32
34
|
|
|
35
|
+
@deprecated("Migrate to client.spans.get_spans_dataframe() from arize-phoenix-client")
|
|
33
36
|
def get_spans_dataframe(
|
|
34
37
|
self,
|
|
35
38
|
filter_condition: Optional[str] = None,
|
|
@@ -55,11 +58,13 @@ class TraceDataExtractor(ABC):
|
|
|
55
58
|
)
|
|
56
59
|
|
|
57
60
|
@abstractmethod
|
|
61
|
+
@deprecated("Migrate to client.spans.get_span_annotations() from arize-phoenix-client")
|
|
58
62
|
def get_evaluations(
|
|
59
63
|
self,
|
|
60
64
|
project_name: Optional[str] = None,
|
|
61
65
|
) -> list[Evaluations]: ...
|
|
62
66
|
|
|
67
|
+
@deprecated("Migrate to client.spans.get_spans() from arize-phoenix-client")
|
|
63
68
|
def get_trace_dataset(
|
|
64
69
|
self,
|
|
65
70
|
project_name: Optional[str] = None,
|
phoenix/session/evaluation.py
CHANGED
|
@@ -19,6 +19,7 @@ from typing import (
|
|
|
19
19
|
|
|
20
20
|
import pandas as pd
|
|
21
21
|
from google.protobuf.wrappers_pb2 import DoubleValue, StringValue
|
|
22
|
+
from typing_extensions import deprecated
|
|
22
23
|
|
|
23
24
|
import phoenix.trace.v1 as pb
|
|
24
25
|
from phoenix.config import get_env_collector_endpoint, get_env_host, get_env_port
|
|
@@ -136,16 +137,19 @@ def _extract_result(row: "pd.Series[Any]") -> Optional[pb.Evaluation.Result]:
|
|
|
136
137
|
)
|
|
137
138
|
|
|
138
139
|
|
|
140
|
+
@deprecated("Migrate to using client.spans.log_span_annotations via arize-phoenix-client")
|
|
139
141
|
def log_evaluations(
|
|
140
142
|
*evals: Evaluations,
|
|
141
143
|
endpoint: Optional[str] = None,
|
|
142
144
|
host: Optional[str] = None,
|
|
143
145
|
port: Optional[int] = None,
|
|
144
146
|
) -> None:
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
"""
|
|
148
|
+
.. deprecated::
|
|
149
|
+
This function is deprecated. Use ``client.spans.log_span_annotations()`` via
|
|
150
|
+
arize-phoenix-client instead.
|
|
151
|
+
See https://arize-phoenix.readthedocs.io/projects/client/en/latest/
|
|
152
|
+
"""
|
|
149
153
|
host = host or get_env_host()
|
|
150
154
|
if host == "0.0.0.0":
|
|
151
155
|
host = "127.0.0.1"
|
phoenix/session/session.py
CHANGED
|
@@ -12,10 +12,11 @@ from importlib.util import find_spec
|
|
|
12
12
|
from itertools import chain
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
from tempfile import TemporaryDirectory
|
|
15
|
-
from typing import TYPE_CHECKING, Any, NamedTuple, Optional, Union
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Awaitable, Callable, NamedTuple, Optional, Union
|
|
16
16
|
from urllib.parse import urljoin
|
|
17
17
|
|
|
18
18
|
import pandas as pd
|
|
19
|
+
from typing_extensions import deprecated
|
|
19
20
|
|
|
20
21
|
from phoenix.config import (
|
|
21
22
|
ENV_NOTEBOOK_ENV,
|
|
@@ -25,6 +26,7 @@ from phoenix.config import (
|
|
|
25
26
|
ensure_working_dir_if_needed,
|
|
26
27
|
get_env_database_connection_str,
|
|
27
28
|
get_env_host,
|
|
29
|
+
get_env_host_root_path,
|
|
28
30
|
get_env_port,
|
|
29
31
|
get_exported_files,
|
|
30
32
|
get_working_dir,
|
|
@@ -117,6 +119,7 @@ class Session(TraceDataExtractor, ABC):
|
|
|
117
119
|
default_umap_parameters: Optional[Mapping[str, Any]] = None,
|
|
118
120
|
host: Optional[str] = None,
|
|
119
121
|
port: Optional[int] = None,
|
|
122
|
+
root_path: Optional[str] = None,
|
|
120
123
|
notebook_env: Optional[NotebookEnvironment] = None,
|
|
121
124
|
):
|
|
122
125
|
self._database_url = database_url
|
|
@@ -132,12 +135,21 @@ class Session(TraceDataExtractor, ABC):
|
|
|
132
135
|
self.export_path.mkdir(parents=True, exist_ok=True)
|
|
133
136
|
self.exported_data = ExportedData()
|
|
134
137
|
self.notebook_env = notebook_env or _get_notebook_environment()
|
|
135
|
-
self.root_path =
|
|
138
|
+
self.root_path = (
|
|
139
|
+
(get_env_host_root_path() or _get_root_path(self.notebook_env, self.port))
|
|
140
|
+
if root_path is None
|
|
141
|
+
else root_path
|
|
142
|
+
)
|
|
143
|
+
if self.root_path and self.root_path != "/":
|
|
144
|
+
if not self.root_path.startswith("/"):
|
|
145
|
+
self.root_path = f"/{self.root_path}"
|
|
146
|
+
self.root_path = self.root_path.rstrip("/")
|
|
136
147
|
host = "127.0.0.1" if self.host == "0.0.0.0" else self.host
|
|
137
148
|
self._client = Client(
|
|
138
149
|
endpoint=f"http://{host}:{self.port}", warn_if_server_not_running=False
|
|
139
150
|
)
|
|
140
151
|
|
|
152
|
+
@deprecated("Migrate to using client.spans.get_spans_dataframe via arize-phoenix-client")
|
|
141
153
|
def query_spans(
|
|
142
154
|
self,
|
|
143
155
|
*queries: SpanQuery,
|
|
@@ -151,6 +163,11 @@ class Session(TraceDataExtractor, ABC):
|
|
|
151
163
|
timeout: Optional[int] = DEFAULT_TIMEOUT_IN_SECONDS,
|
|
152
164
|
) -> Optional[Union[pd.DataFrame, list[pd.DataFrame]]]:
|
|
153
165
|
"""
|
|
166
|
+
.. deprecated::
|
|
167
|
+
This method is deprecated. Use ``client.spans.get_spans_dataframe()`` via
|
|
168
|
+
arize-phoenix-client instead.
|
|
169
|
+
See https://arize-phoenix.readthedocs.io/projects/client/en/latest/
|
|
170
|
+
|
|
154
171
|
Queries the spans in the project based on the provided parameters.
|
|
155
172
|
|
|
156
173
|
Parameters
|
|
@@ -192,11 +209,17 @@ class Session(TraceDataExtractor, ABC):
|
|
|
192
209
|
project_name=project_name,
|
|
193
210
|
)
|
|
194
211
|
|
|
212
|
+
@deprecated("Migrate to using client.spans.get_span_annotations via arize-phoenix-client")
|
|
195
213
|
def get_evaluations(
|
|
196
214
|
self,
|
|
197
215
|
project_name: Optional[str] = None,
|
|
198
216
|
) -> list[Evaluations]:
|
|
199
217
|
"""
|
|
218
|
+
.. deprecated::
|
|
219
|
+
This method is deprecated. Use ``client.spans.get_span_annotations()`` via
|
|
220
|
+
arize-phoenix-client instead.
|
|
221
|
+
See https://arize-phoenix.readthedocs.io/projects/client/en/latest/
|
|
222
|
+
|
|
200
223
|
Get the evaluations for a project.
|
|
201
224
|
|
|
202
225
|
Parameters
|
|
@@ -255,7 +278,7 @@ class Session(TraceDataExtractor, ABC):
|
|
|
255
278
|
@property
|
|
256
279
|
def url(self) -> str:
|
|
257
280
|
"""Returns the url for the phoenix app"""
|
|
258
|
-
return _get_url(self.host, self.port, self.notebook_env)
|
|
281
|
+
return _get_url(self.host, self.port, self.notebook_env, self.root_path)
|
|
259
282
|
|
|
260
283
|
@property
|
|
261
284
|
def database_url(self) -> str:
|
|
@@ -288,6 +311,7 @@ class ProcessSession(Session):
|
|
|
288
311
|
default_umap_parameters=default_umap_parameters,
|
|
289
312
|
host=host,
|
|
290
313
|
port=port,
|
|
314
|
+
root_path=root_path,
|
|
291
315
|
notebook_env=notebook_env,
|
|
292
316
|
)
|
|
293
317
|
primary_inferences.to_disc()
|
|
@@ -354,6 +378,7 @@ class ThreadSession(Session):
|
|
|
354
378
|
default_umap_parameters=default_umap_parameters,
|
|
355
379
|
host=host,
|
|
356
380
|
port=port,
|
|
381
|
+
root_path=root_path,
|
|
357
382
|
notebook_env=notebook_env,
|
|
358
383
|
)
|
|
359
384
|
self.model = create_model_from_inferences(
|
|
@@ -369,7 +394,10 @@ class ThreadSession(Session):
|
|
|
369
394
|
)
|
|
370
395
|
# Initialize an app service that keeps the server running
|
|
371
396
|
engine = create_engine_and_run_migrations(database_url)
|
|
372
|
-
|
|
397
|
+
shutdown_callbacks: list[Callable[[], None | Awaitable[None]]] = []
|
|
398
|
+
shutdown_callbacks.extend(instrument_engine_if_enabled(engine))
|
|
399
|
+
# Ensure engine is disposed on shutdown to properly close database connections
|
|
400
|
+
shutdown_callbacks.append(engine.dispose)
|
|
373
401
|
factory = DbSessionFactory(db=_db(engine), dialect=engine.dialect.name)
|
|
374
402
|
self.app = create_app(
|
|
375
403
|
db=factory,
|
|
@@ -384,7 +412,7 @@ class ThreadSession(Session):
|
|
|
384
412
|
if (trace_dataset and (initial_evaluations := trace_dataset.evaluations))
|
|
385
413
|
else None
|
|
386
414
|
),
|
|
387
|
-
shutdown_callbacks=
|
|
415
|
+
shutdown_callbacks=shutdown_callbacks,
|
|
388
416
|
)
|
|
389
417
|
self.server = ThreadServer(
|
|
390
418
|
app=self.app,
|
|
@@ -436,6 +464,7 @@ def launch_app(
|
|
|
436
464
|
default_umap_parameters: Optional[Mapping[str, Any]] = None,
|
|
437
465
|
host: Optional[str] = None,
|
|
438
466
|
port: Optional[int] = None,
|
|
467
|
+
root_path: Optional[str] = None,
|
|
439
468
|
run_in_thread: bool = True,
|
|
440
469
|
notebook_environment: Optional[Union[NotebookEnvironment, str]] = None,
|
|
441
470
|
use_temp_dir: bool = True,
|
|
@@ -461,6 +490,9 @@ def launch_app(
|
|
|
461
490
|
The port on which the server listens. When using traces this should not be
|
|
462
491
|
used and should instead set the environment variable `PHOENIX_PORT`.
|
|
463
492
|
Defaults to 6006.
|
|
493
|
+
root_path: str, optional
|
|
494
|
+
The root path to serve the application under (useful when behind a proxy).
|
|
495
|
+
Can also be set using environment variable `PHOENIX_HOST_ROOT_PATH`.
|
|
464
496
|
run_in_thread: bool, optional, default=True
|
|
465
497
|
Whether the server should run in a Thread or Process.
|
|
466
498
|
default_umap_parameters: dict[str, Union[int, float]], optional, default=None
|
|
@@ -573,6 +605,7 @@ def launch_app(
|
|
|
573
605
|
default_umap_parameters,
|
|
574
606
|
host=host,
|
|
575
607
|
port=port,
|
|
608
|
+
root_path=root_path,
|
|
576
609
|
notebook_env=nb_env,
|
|
577
610
|
)
|
|
578
611
|
# TODO: catch exceptions from thread
|
|
@@ -586,6 +619,7 @@ def launch_app(
|
|
|
586
619
|
default_umap_parameters,
|
|
587
620
|
host=host,
|
|
588
621
|
port=port,
|
|
622
|
+
root_path=root_path,
|
|
589
623
|
notebook_env=nb_env,
|
|
590
624
|
)
|
|
591
625
|
|
|
@@ -636,7 +670,7 @@ def close_app(delete_data: bool = False) -> None:
|
|
|
636
670
|
delete_all(prompt_before_delete=False)
|
|
637
671
|
|
|
638
672
|
|
|
639
|
-
def _get_url(host: str, port: int, notebook_env: NotebookEnvironment) -> str:
|
|
673
|
+
def _get_url(host: str, port: int, notebook_env: NotebookEnvironment, root_path: str) -> str:
|
|
640
674
|
"""Determines the IFrame URL based on whether this is in a Colab or in a local notebook"""
|
|
641
675
|
if notebook_env == NotebookEnvironment.COLAB:
|
|
642
676
|
from google.colab.output import eval_js
|
|
@@ -648,10 +682,12 @@ def _get_url(host: str, port: int, notebook_env: NotebookEnvironment) -> str:
|
|
|
648
682
|
if notebook_env == NotebookEnvironment.DATABRICKS:
|
|
649
683
|
context = _get_databricks_context()
|
|
650
684
|
return f"{_get_databricks_notebook_base_url(context)}/{port}/"
|
|
685
|
+
if not root_path.startswith("/"):
|
|
686
|
+
root_path = f"/{root_path}"
|
|
651
687
|
if host == "0.0.0.0" or host == "127.0.0.1":
|
|
652
688
|
# The app is running locally, so use localhost
|
|
653
|
-
return f"http://localhost:{port}
|
|
654
|
-
return f"http://{host}:{port}
|
|
689
|
+
return f"http://localhost:{port}{root_path}"
|
|
690
|
+
return f"http://{host}:{port}{root_path}"
|
|
655
691
|
|
|
656
692
|
|
|
657
693
|
def _is_colab() -> bool:
|
phoenix/settings.py
CHANGED
|
@@ -21,6 +21,8 @@ class _Settings:
|
|
|
21
21
|
disable_migrations: bool = field(default=False)
|
|
22
22
|
# FullStory organization ID for web analytics tracking
|
|
23
23
|
fullstory_org: Optional[str] = field(default=None)
|
|
24
|
+
# Scarf.sh pixel ID for open-source analytics and usage
|
|
25
|
+
scarf_sh_pixel_id: Optional[str] = field(default=None)
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
# Singleton instance of the settings
|