arize-phoenix 3.16.0__py3-none-any.whl → 7.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of arize-phoenix might be problematic. Click here for more details.
- arize_phoenix-7.7.0.dist-info/METADATA +261 -0
- arize_phoenix-7.7.0.dist-info/RECORD +345 -0
- {arize_phoenix-3.16.0.dist-info → arize_phoenix-7.7.0.dist-info}/WHEEL +1 -1
- arize_phoenix-7.7.0.dist-info/entry_points.txt +3 -0
- phoenix/__init__.py +86 -14
- phoenix/auth.py +309 -0
- phoenix/config.py +675 -45
- phoenix/core/model.py +32 -30
- phoenix/core/model_schema.py +102 -109
- phoenix/core/model_schema_adapter.py +48 -45
- phoenix/datetime_utils.py +24 -3
- phoenix/db/README.md +54 -0
- phoenix/db/__init__.py +4 -0
- phoenix/db/alembic.ini +85 -0
- phoenix/db/bulk_inserter.py +294 -0
- phoenix/db/engines.py +208 -0
- phoenix/db/enums.py +20 -0
- phoenix/db/facilitator.py +113 -0
- phoenix/db/helpers.py +159 -0
- phoenix/db/insertion/constants.py +2 -0
- phoenix/db/insertion/dataset.py +227 -0
- phoenix/db/insertion/document_annotation.py +171 -0
- phoenix/db/insertion/evaluation.py +191 -0
- phoenix/db/insertion/helpers.py +98 -0
- phoenix/db/insertion/span.py +193 -0
- phoenix/db/insertion/span_annotation.py +158 -0
- phoenix/db/insertion/trace_annotation.py +158 -0
- phoenix/db/insertion/types.py +256 -0
- phoenix/db/migrate.py +86 -0
- phoenix/db/migrations/data_migration_scripts/populate_project_sessions.py +199 -0
- phoenix/db/migrations/env.py +114 -0
- phoenix/db/migrations/script.py.mako +26 -0
- phoenix/db/migrations/versions/10460e46d750_datasets.py +317 -0
- phoenix/db/migrations/versions/3be8647b87d8_add_token_columns_to_spans_table.py +126 -0
- phoenix/db/migrations/versions/4ded9e43755f_create_project_sessions_table.py +66 -0
- phoenix/db/migrations/versions/cd164e83824f_users_and_tokens.py +157 -0
- phoenix/db/migrations/versions/cf03bd6bae1d_init.py +280 -0
- phoenix/db/models.py +807 -0
- phoenix/exceptions.py +5 -1
- phoenix/experiments/__init__.py +6 -0
- phoenix/experiments/evaluators/__init__.py +29 -0
- phoenix/experiments/evaluators/base.py +158 -0
- phoenix/experiments/evaluators/code_evaluators.py +184 -0
- phoenix/experiments/evaluators/llm_evaluators.py +473 -0
- phoenix/experiments/evaluators/utils.py +236 -0
- phoenix/experiments/functions.py +772 -0
- phoenix/experiments/tracing.py +86 -0
- phoenix/experiments/types.py +726 -0
- phoenix/experiments/utils.py +25 -0
- phoenix/inferences/__init__.py +0 -0
- phoenix/{datasets → inferences}/errors.py +6 -5
- phoenix/{datasets → inferences}/fixtures.py +49 -42
- phoenix/{datasets/dataset.py → inferences/inferences.py} +121 -105
- phoenix/{datasets → inferences}/schema.py +11 -11
- phoenix/{datasets → inferences}/validation.py +13 -14
- phoenix/logging/__init__.py +3 -0
- phoenix/logging/_config.py +90 -0
- phoenix/logging/_filter.py +6 -0
- phoenix/logging/_formatter.py +69 -0
- phoenix/metrics/__init__.py +5 -4
- phoenix/metrics/binning.py +4 -3
- phoenix/metrics/metrics.py +2 -1
- phoenix/metrics/mixins.py +7 -6
- phoenix/metrics/retrieval_metrics.py +2 -1
- phoenix/metrics/timeseries.py +5 -4
- phoenix/metrics/wrappers.py +9 -3
- phoenix/pointcloud/clustering.py +5 -5
- phoenix/pointcloud/pointcloud.py +7 -5
- phoenix/pointcloud/projectors.py +5 -6
- phoenix/pointcloud/umap_parameters.py +53 -52
- phoenix/server/api/README.md +28 -0
- phoenix/server/api/auth.py +44 -0
- phoenix/server/api/context.py +152 -9
- phoenix/server/api/dataloaders/__init__.py +91 -0
- phoenix/server/api/dataloaders/annotation_summaries.py +139 -0
- phoenix/server/api/dataloaders/average_experiment_run_latency.py +54 -0
- phoenix/server/api/dataloaders/cache/__init__.py +3 -0
- phoenix/server/api/dataloaders/cache/two_tier_cache.py +68 -0
- phoenix/server/api/dataloaders/dataset_example_revisions.py +131 -0
- phoenix/server/api/dataloaders/dataset_example_spans.py +38 -0
- phoenix/server/api/dataloaders/document_evaluation_summaries.py +144 -0
- phoenix/server/api/dataloaders/document_evaluations.py +31 -0
- phoenix/server/api/dataloaders/document_retrieval_metrics.py +89 -0
- phoenix/server/api/dataloaders/experiment_annotation_summaries.py +79 -0
- phoenix/server/api/dataloaders/experiment_error_rates.py +58 -0
- phoenix/server/api/dataloaders/experiment_run_annotations.py +36 -0
- phoenix/server/api/dataloaders/experiment_run_counts.py +49 -0
- phoenix/server/api/dataloaders/experiment_sequence_number.py +44 -0
- phoenix/server/api/dataloaders/latency_ms_quantile.py +188 -0
- phoenix/server/api/dataloaders/min_start_or_max_end_times.py +85 -0
- phoenix/server/api/dataloaders/project_by_name.py +31 -0
- phoenix/server/api/dataloaders/record_counts.py +116 -0
- phoenix/server/api/dataloaders/session_io.py +79 -0
- phoenix/server/api/dataloaders/session_num_traces.py +30 -0
- phoenix/server/api/dataloaders/session_num_traces_with_error.py +32 -0
- phoenix/server/api/dataloaders/session_token_usages.py +41 -0
- phoenix/server/api/dataloaders/session_trace_latency_ms_quantile.py +55 -0
- phoenix/server/api/dataloaders/span_annotations.py +26 -0
- phoenix/server/api/dataloaders/span_dataset_examples.py +31 -0
- phoenix/server/api/dataloaders/span_descendants.py +57 -0
- phoenix/server/api/dataloaders/span_projects.py +33 -0
- phoenix/server/api/dataloaders/token_counts.py +124 -0
- phoenix/server/api/dataloaders/trace_by_trace_ids.py +25 -0
- phoenix/server/api/dataloaders/trace_root_spans.py +32 -0
- phoenix/server/api/dataloaders/user_roles.py +30 -0
- phoenix/server/api/dataloaders/users.py +33 -0
- phoenix/server/api/exceptions.py +48 -0
- phoenix/server/api/helpers/__init__.py +12 -0
- phoenix/server/api/helpers/dataset_helpers.py +217 -0
- phoenix/server/api/helpers/experiment_run_filters.py +763 -0
- phoenix/server/api/helpers/playground_clients.py +948 -0
- phoenix/server/api/helpers/playground_registry.py +70 -0
- phoenix/server/api/helpers/playground_spans.py +455 -0
- phoenix/server/api/input_types/AddExamplesToDatasetInput.py +16 -0
- phoenix/server/api/input_types/AddSpansToDatasetInput.py +14 -0
- phoenix/server/api/input_types/ChatCompletionInput.py +38 -0
- phoenix/server/api/input_types/ChatCompletionMessageInput.py +24 -0
- phoenix/server/api/input_types/ClearProjectInput.py +15 -0
- phoenix/server/api/input_types/ClusterInput.py +2 -2
- phoenix/server/api/input_types/CreateDatasetInput.py +12 -0
- phoenix/server/api/input_types/CreateSpanAnnotationInput.py +18 -0
- phoenix/server/api/input_types/CreateTraceAnnotationInput.py +18 -0
- phoenix/server/api/input_types/DataQualityMetricInput.py +5 -2
- phoenix/server/api/input_types/DatasetExampleInput.py +14 -0
- phoenix/server/api/input_types/DatasetSort.py +17 -0
- phoenix/server/api/input_types/DatasetVersionSort.py +16 -0
- phoenix/server/api/input_types/DeleteAnnotationsInput.py +7 -0
- phoenix/server/api/input_types/DeleteDatasetExamplesInput.py +13 -0
- phoenix/server/api/input_types/DeleteDatasetInput.py +7 -0
- phoenix/server/api/input_types/DeleteExperimentsInput.py +7 -0
- phoenix/server/api/input_types/DimensionFilter.py +4 -4
- phoenix/server/api/input_types/GenerativeModelInput.py +17 -0
- phoenix/server/api/input_types/Granularity.py +1 -1
- phoenix/server/api/input_types/InvocationParameters.py +162 -0
- phoenix/server/api/input_types/PatchAnnotationInput.py +19 -0
- phoenix/server/api/input_types/PatchDatasetExamplesInput.py +35 -0
- phoenix/server/api/input_types/PatchDatasetInput.py +14 -0
- phoenix/server/api/input_types/PerformanceMetricInput.py +5 -2
- phoenix/server/api/input_types/ProjectSessionSort.py +29 -0
- phoenix/server/api/input_types/SpanAnnotationSort.py +17 -0
- phoenix/server/api/input_types/SpanSort.py +134 -69
- phoenix/server/api/input_types/TemplateOptions.py +10 -0
- phoenix/server/api/input_types/TraceAnnotationSort.py +17 -0
- phoenix/server/api/input_types/UserRoleInput.py +9 -0
- phoenix/server/api/mutations/__init__.py +28 -0
- phoenix/server/api/mutations/api_key_mutations.py +167 -0
- phoenix/server/api/mutations/chat_mutations.py +593 -0
- phoenix/server/api/mutations/dataset_mutations.py +591 -0
- phoenix/server/api/mutations/experiment_mutations.py +75 -0
- phoenix/server/api/{types/ExportEventsMutation.py → mutations/export_events_mutations.py} +21 -18
- phoenix/server/api/mutations/project_mutations.py +57 -0
- phoenix/server/api/mutations/span_annotations_mutations.py +128 -0
- phoenix/server/api/mutations/trace_annotations_mutations.py +127 -0
- phoenix/server/api/mutations/user_mutations.py +329 -0
- phoenix/server/api/openapi/__init__.py +0 -0
- phoenix/server/api/openapi/main.py +17 -0
- phoenix/server/api/openapi/schema.py +16 -0
- phoenix/server/api/queries.py +738 -0
- phoenix/server/api/routers/__init__.py +11 -0
- phoenix/server/api/routers/auth.py +284 -0
- phoenix/server/api/routers/embeddings.py +26 -0
- phoenix/server/api/routers/oauth2.py +488 -0
- phoenix/server/api/routers/v1/__init__.py +64 -0
- phoenix/server/api/routers/v1/datasets.py +1017 -0
- phoenix/server/api/routers/v1/evaluations.py +362 -0
- phoenix/server/api/routers/v1/experiment_evaluations.py +115 -0
- phoenix/server/api/routers/v1/experiment_runs.py +167 -0
- phoenix/server/api/routers/v1/experiments.py +308 -0
- phoenix/server/api/routers/v1/pydantic_compat.py +78 -0
- phoenix/server/api/routers/v1/spans.py +267 -0
- phoenix/server/api/routers/v1/traces.py +208 -0
- phoenix/server/api/routers/v1/utils.py +95 -0
- phoenix/server/api/schema.py +44 -247
- phoenix/server/api/subscriptions.py +597 -0
- phoenix/server/api/types/Annotation.py +21 -0
- phoenix/server/api/types/AnnotationSummary.py +55 -0
- phoenix/server/api/types/AnnotatorKind.py +16 -0
- phoenix/server/api/types/ApiKey.py +27 -0
- phoenix/server/api/types/AuthMethod.py +9 -0
- phoenix/server/api/types/ChatCompletionMessageRole.py +11 -0
- phoenix/server/api/types/ChatCompletionSubscriptionPayload.py +46 -0
- phoenix/server/api/types/Cluster.py +25 -24
- phoenix/server/api/types/CreateDatasetPayload.py +8 -0
- phoenix/server/api/types/DataQualityMetric.py +31 -13
- phoenix/server/api/types/Dataset.py +288 -63
- phoenix/server/api/types/DatasetExample.py +85 -0
- phoenix/server/api/types/DatasetExampleRevision.py +34 -0
- phoenix/server/api/types/DatasetVersion.py +14 -0
- phoenix/server/api/types/Dimension.py +32 -31
- phoenix/server/api/types/DocumentEvaluationSummary.py +9 -8
- phoenix/server/api/types/EmbeddingDimension.py +56 -49
- phoenix/server/api/types/Evaluation.py +25 -31
- phoenix/server/api/types/EvaluationSummary.py +30 -50
- phoenix/server/api/types/Event.py +20 -20
- phoenix/server/api/types/ExampleRevisionInterface.py +14 -0
- phoenix/server/api/types/Experiment.py +152 -0
- phoenix/server/api/types/ExperimentAnnotationSummary.py +13 -0
- phoenix/server/api/types/ExperimentComparison.py +17 -0
- phoenix/server/api/types/ExperimentRun.py +119 -0
- phoenix/server/api/types/ExperimentRunAnnotation.py +56 -0
- phoenix/server/api/types/GenerativeModel.py +9 -0
- phoenix/server/api/types/GenerativeProvider.py +85 -0
- phoenix/server/api/types/Inferences.py +80 -0
- phoenix/server/api/types/InferencesRole.py +23 -0
- phoenix/server/api/types/LabelFraction.py +7 -0
- phoenix/server/api/types/MimeType.py +2 -2
- phoenix/server/api/types/Model.py +54 -54
- phoenix/server/api/types/PerformanceMetric.py +8 -5
- phoenix/server/api/types/Project.py +407 -142
- phoenix/server/api/types/ProjectSession.py +139 -0
- phoenix/server/api/types/Segments.py +4 -4
- phoenix/server/api/types/Span.py +221 -176
- phoenix/server/api/types/SpanAnnotation.py +43 -0
- phoenix/server/api/types/SpanIOValue.py +15 -0
- phoenix/server/api/types/SystemApiKey.py +9 -0
- phoenix/server/api/types/TemplateLanguage.py +10 -0
- phoenix/server/api/types/TimeSeries.py +19 -15
- phoenix/server/api/types/TokenUsage.py +11 -0
- phoenix/server/api/types/Trace.py +154 -0
- phoenix/server/api/types/TraceAnnotation.py +45 -0
- phoenix/server/api/types/UMAPPoints.py +7 -7
- phoenix/server/api/types/User.py +60 -0
- phoenix/server/api/types/UserApiKey.py +45 -0
- phoenix/server/api/types/UserRole.py +15 -0
- phoenix/server/api/types/node.py +13 -107
- phoenix/server/api/types/pagination.py +156 -57
- phoenix/server/api/utils.py +34 -0
- phoenix/server/app.py +864 -115
- phoenix/server/bearer_auth.py +163 -0
- phoenix/server/dml_event.py +136 -0
- phoenix/server/dml_event_handler.py +256 -0
- phoenix/server/email/__init__.py +0 -0
- phoenix/server/email/sender.py +97 -0
- phoenix/server/email/templates/__init__.py +0 -0
- phoenix/server/email/templates/password_reset.html +19 -0
- phoenix/server/email/types.py +11 -0
- phoenix/server/grpc_server.py +102 -0
- phoenix/server/jwt_store.py +505 -0
- phoenix/server/main.py +305 -116
- phoenix/server/oauth2.py +52 -0
- phoenix/server/openapi/__init__.py +0 -0
- phoenix/server/prometheus.py +111 -0
- phoenix/server/rate_limiters.py +188 -0
- phoenix/server/static/.vite/manifest.json +87 -0
- phoenix/server/static/assets/components-Cy9nwIvF.js +2125 -0
- phoenix/server/static/assets/index-BKvHIxkk.js +113 -0
- phoenix/server/static/assets/pages-CUi2xCVQ.js +4449 -0
- phoenix/server/static/assets/vendor-DvC8cT4X.js +894 -0
- phoenix/server/static/assets/vendor-DxkFTwjz.css +1 -0
- phoenix/server/static/assets/vendor-arizeai-Do1793cv.js +662 -0
- phoenix/server/static/assets/vendor-codemirror-BzwZPyJM.js +24 -0
- phoenix/server/static/assets/vendor-recharts-_Jb7JjhG.js +59 -0
- phoenix/server/static/assets/vendor-shiki-Cl9QBraO.js +5 -0
- phoenix/server/static/assets/vendor-three-DwGkEfCM.js +2998 -0
- phoenix/server/telemetry.py +68 -0
- phoenix/server/templates/index.html +82 -23
- phoenix/server/thread_server.py +3 -3
- phoenix/server/types.py +275 -0
- phoenix/services.py +27 -18
- phoenix/session/client.py +743 -68
- phoenix/session/data_extractor.py +31 -7
- phoenix/session/evaluation.py +3 -9
- phoenix/session/session.py +263 -219
- phoenix/settings.py +22 -0
- phoenix/trace/__init__.py +2 -22
- phoenix/trace/attributes.py +338 -0
- phoenix/trace/dsl/README.md +116 -0
- phoenix/trace/dsl/filter.py +663 -213
- phoenix/trace/dsl/helpers.py +73 -21
- phoenix/trace/dsl/query.py +574 -201
- phoenix/trace/exporter.py +24 -19
- phoenix/trace/fixtures.py +368 -32
- phoenix/trace/otel.py +71 -219
- phoenix/trace/projects.py +3 -2
- phoenix/trace/schemas.py +33 -11
- phoenix/trace/span_evaluations.py +21 -16
- phoenix/trace/span_json_decoder.py +6 -4
- phoenix/trace/span_json_encoder.py +2 -2
- phoenix/trace/trace_dataset.py +47 -32
- phoenix/trace/utils.py +21 -4
- phoenix/utilities/__init__.py +0 -26
- phoenix/utilities/client.py +132 -0
- phoenix/utilities/deprecation.py +31 -0
- phoenix/utilities/error_handling.py +3 -2
- phoenix/utilities/json.py +109 -0
- phoenix/utilities/logging.py +8 -0
- phoenix/utilities/project.py +2 -2
- phoenix/utilities/re.py +49 -0
- phoenix/utilities/span_store.py +0 -23
- phoenix/utilities/template_formatters.py +99 -0
- phoenix/version.py +1 -1
- arize_phoenix-3.16.0.dist-info/METADATA +0 -495
- arize_phoenix-3.16.0.dist-info/RECORD +0 -178
- phoenix/core/project.py +0 -617
- phoenix/core/traces.py +0 -100
- phoenix/experimental/evals/__init__.py +0 -73
- phoenix/experimental/evals/evaluators.py +0 -413
- phoenix/experimental/evals/functions/__init__.py +0 -4
- phoenix/experimental/evals/functions/classify.py +0 -453
- phoenix/experimental/evals/functions/executor.py +0 -353
- phoenix/experimental/evals/functions/generate.py +0 -138
- phoenix/experimental/evals/functions/processing.py +0 -76
- phoenix/experimental/evals/models/__init__.py +0 -14
- phoenix/experimental/evals/models/anthropic.py +0 -175
- phoenix/experimental/evals/models/base.py +0 -170
- phoenix/experimental/evals/models/bedrock.py +0 -221
- phoenix/experimental/evals/models/litellm.py +0 -134
- phoenix/experimental/evals/models/openai.py +0 -448
- phoenix/experimental/evals/models/rate_limiters.py +0 -246
- phoenix/experimental/evals/models/vertex.py +0 -173
- phoenix/experimental/evals/models/vertexai.py +0 -186
- phoenix/experimental/evals/retrievals.py +0 -96
- phoenix/experimental/evals/templates/__init__.py +0 -50
- phoenix/experimental/evals/templates/default_templates.py +0 -472
- phoenix/experimental/evals/templates/template.py +0 -195
- phoenix/experimental/evals/utils/__init__.py +0 -172
- phoenix/experimental/evals/utils/threads.py +0 -27
- phoenix/server/api/helpers.py +0 -11
- phoenix/server/api/routers/evaluation_handler.py +0 -109
- phoenix/server/api/routers/span_handler.py +0 -70
- phoenix/server/api/routers/trace_handler.py +0 -60
- phoenix/server/api/types/DatasetRole.py +0 -23
- phoenix/server/static/index.css +0 -6
- phoenix/server/static/index.js +0 -7447
- phoenix/storage/span_store/__init__.py +0 -23
- phoenix/storage/span_store/text_file.py +0 -85
- phoenix/trace/dsl/missing.py +0 -60
- phoenix/trace/langchain/__init__.py +0 -3
- phoenix/trace/langchain/instrumentor.py +0 -35
- phoenix/trace/llama_index/__init__.py +0 -3
- phoenix/trace/llama_index/callback.py +0 -102
- phoenix/trace/openai/__init__.py +0 -3
- phoenix/trace/openai/instrumentor.py +0 -30
- {arize_phoenix-3.16.0.dist-info → arize_phoenix-7.7.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-3.16.0.dist-info → arize_phoenix-7.7.0.dist-info}/licenses/LICENSE +0 -0
- /phoenix/{datasets → db/insertion}/__init__.py +0 -0
- /phoenix/{experimental → db/migrations}/__init__.py +0 -0
- /phoenix/{storage → db/migrations/data_migration_scripts}/__init__.py +0 -0
|
@@ -2,7 +2,7 @@ import json
|
|
|
2
2
|
from dataclasses import asdict
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from enum import Enum
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any
|
|
6
6
|
from uuid import UUID
|
|
7
7
|
|
|
8
8
|
import numpy as np
|
|
@@ -60,5 +60,5 @@ def span_to_json(span: Span) -> str:
|
|
|
60
60
|
return json.dumps(span, cls=SpanJSONEncoder)
|
|
61
61
|
|
|
62
62
|
|
|
63
|
-
def spans_to_jsonl(spans:
|
|
63
|
+
def spans_to_jsonl(spans: list[Span]) -> str:
|
|
64
64
|
return "\n".join(span_to_json(span) for span in spans)
|
phoenix/trace/trace_dataset.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import json
|
|
2
|
+
from collections.abc import Iterable, Iterator
|
|
2
3
|
from datetime import datetime
|
|
3
4
|
from pathlib import Path
|
|
4
|
-
from typing import Any,
|
|
5
|
+
from typing import Any, Optional, Union, cast
|
|
5
6
|
from uuid import UUID, uuid4
|
|
6
7
|
from warnings import warn
|
|
7
8
|
|
|
9
|
+
import numpy as np
|
|
8
10
|
import pandas as pd
|
|
9
11
|
from openinference.semconv.trace import (
|
|
10
12
|
DocumentAttributes,
|
|
@@ -14,8 +16,9 @@ from openinference.semconv.trace import (
|
|
|
14
16
|
from pandas import DataFrame, read_parquet
|
|
15
17
|
from pyarrow import Schema, Table, parquet
|
|
16
18
|
|
|
17
|
-
from phoenix.config import
|
|
19
|
+
from phoenix.config import GENERATED_INFERENCES_NAME_PREFIX, INFERENCES_DIR, TRACE_DATASETS_DIR
|
|
18
20
|
from phoenix.datetime_utils import normalize_timestamps
|
|
21
|
+
from phoenix.trace.attributes import flatten, unflatten
|
|
19
22
|
from phoenix.trace.errors import InvalidParquetMetadataError
|
|
20
23
|
from phoenix.trace.schemas import ATTRIBUTE_PREFIX, CONTEXT_PREFIX, Span
|
|
21
24
|
from phoenix.trace.span_evaluations import Evaluations, SpanEvaluations
|
|
@@ -59,6 +62,7 @@ def normalize_dataframe(dataframe: DataFrame) -> "DataFrame":
|
|
|
59
62
|
# Convert the start and end times to datetime
|
|
60
63
|
dataframe["start_time"] = normalize_timestamps(dataframe["start_time"])
|
|
61
64
|
dataframe["end_time"] = normalize_timestamps(dataframe["end_time"])
|
|
65
|
+
dataframe = dataframe.replace({np.nan: None})
|
|
62
66
|
return dataframe
|
|
63
67
|
|
|
64
68
|
|
|
@@ -98,6 +102,14 @@ class TraceDataset:
|
|
|
98
102
|
"""
|
|
99
103
|
A TraceDataset is a wrapper around a dataframe which is a flattened representation
|
|
100
104
|
of Spans. The collection of spans trace the LLM application's execution.
|
|
105
|
+
|
|
106
|
+
Typical usage example::
|
|
107
|
+
|
|
108
|
+
from phoenix.trace.utils import json_lines_to_df
|
|
109
|
+
|
|
110
|
+
with open("trace.jsonl", "r") as f:
|
|
111
|
+
trace_ds = TraceDataset(json_lines_to_df(f.readlines()))
|
|
112
|
+
px.launch_app(trace=trace_ds)
|
|
101
113
|
"""
|
|
102
114
|
|
|
103
115
|
name: str
|
|
@@ -105,7 +117,7 @@ class TraceDataset:
|
|
|
105
117
|
A human readable name for the dataset.
|
|
106
118
|
"""
|
|
107
119
|
dataframe: pd.DataFrame
|
|
108
|
-
evaluations:
|
|
120
|
+
evaluations: list[Evaluations] = []
|
|
109
121
|
_id: UUID
|
|
110
122
|
_data_file_name: str = "data.parquet"
|
|
111
123
|
|
|
@@ -119,15 +131,15 @@ class TraceDataset:
|
|
|
119
131
|
Constructs a TraceDataset from a dataframe of spans. Optionally takes in
|
|
120
132
|
evaluations for the spans in the dataset.
|
|
121
133
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
134
|
+
Args:
|
|
135
|
+
dataframe (pandas.DataFrame): The pandas dataframe containing the
|
|
136
|
+
tracing data. Each row of which is a flattened representation
|
|
137
|
+
of a span.
|
|
138
|
+
name (str): The name used to identify the dataset in the application.
|
|
139
|
+
If not provided, a random name will be generated.
|
|
140
|
+
evaluations (Optional[Iterable[SpanEvaluations]]): An optional list of
|
|
141
|
+
evaluations for the spans in the dataset. If provided, the evaluations
|
|
142
|
+
can be materialized into a unified dataframe as annotations.
|
|
131
143
|
"""
|
|
132
144
|
# Validate the the dataframe has required fields
|
|
133
145
|
if missing_columns := set(REQUIRED_COLUMNS) - set(dataframe.columns):
|
|
@@ -137,15 +149,15 @@ class TraceDataset:
|
|
|
137
149
|
self._id = uuid4()
|
|
138
150
|
self.dataframe = normalize_dataframe(dataframe)
|
|
139
151
|
# TODO: This is not used in any meaningful way. Should remove
|
|
140
|
-
self.name = name or f"{
|
|
152
|
+
self.name = name or f"{GENERATED_INFERENCES_NAME_PREFIX}{str(self._id)}"
|
|
141
153
|
self.evaluations = list(evaluations)
|
|
142
154
|
|
|
143
155
|
@classmethod
|
|
144
|
-
def from_spans(cls, spans:
|
|
156
|
+
def from_spans(cls, spans: list[Span]) -> "TraceDataset":
|
|
145
157
|
"""Creates a TraceDataset from a list of spans.
|
|
146
158
|
|
|
147
159
|
Args:
|
|
148
|
-
spans (
|
|
160
|
+
spans (list[Span]): A list of spans.
|
|
149
161
|
|
|
150
162
|
Returns:
|
|
151
163
|
TraceDataset: A TraceDataset containing the spans.
|
|
@@ -161,13 +173,16 @@ class TraceDataset:
|
|
|
161
173
|
for _, row in self.dataframe.iterrows():
|
|
162
174
|
is_attribute = row.index.str.startswith(ATTRIBUTE_PREFIX)
|
|
163
175
|
attribute_keys = row.index[is_attribute]
|
|
164
|
-
attributes = (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
176
|
+
attributes = unflatten(
|
|
177
|
+
flatten(
|
|
178
|
+
row.loc[is_attribute]
|
|
179
|
+
.rename(
|
|
180
|
+
{key: key[len(ATTRIBUTE_PREFIX) :] for key in attribute_keys},
|
|
181
|
+
)
|
|
182
|
+
.dropna()
|
|
183
|
+
.to_dict(),
|
|
184
|
+
recurse_on_sequence=True,
|
|
168
185
|
)
|
|
169
|
-
.dropna()
|
|
170
|
-
.to_dict()
|
|
171
186
|
)
|
|
172
187
|
is_context = row.index.str.startswith(CONTEXT_PREFIX)
|
|
173
188
|
context_keys = row.index[is_context]
|
|
@@ -200,13 +215,13 @@ class TraceDataset:
|
|
|
200
215
|
@classmethod
|
|
201
216
|
def from_name(cls, name: str) -> "TraceDataset":
|
|
202
217
|
"""Retrieves a dataset by name from the file system"""
|
|
203
|
-
directory =
|
|
218
|
+
directory = INFERENCES_DIR / name
|
|
204
219
|
df = read_parquet(directory / cls._data_file_name)
|
|
205
220
|
return cls(df, name)
|
|
206
221
|
|
|
207
222
|
def to_disc(self) -> None:
|
|
208
223
|
"""writes the data to disc"""
|
|
209
|
-
directory =
|
|
224
|
+
directory = INFERENCES_DIR / self.name
|
|
210
225
|
directory.mkdir(parents=True, exist_ok=True)
|
|
211
226
|
get_serializable_spans_dataframe(self.dataframe).to_parquet(
|
|
212
227
|
directory / self._data_file_name,
|
|
@@ -222,14 +237,14 @@ class TraceDataset:
|
|
|
222
237
|
|
|
223
238
|
Args:
|
|
224
239
|
directory (Optional[Union[str, Path]], optional): An optional path
|
|
225
|
-
|
|
226
|
-
|
|
240
|
+
to a directory where the data will be written. If not provided, the
|
|
241
|
+
data will be written to a default location.
|
|
227
242
|
|
|
228
243
|
Returns:
|
|
229
244
|
UUID: The id of the trace dataset, which can be used as key to load
|
|
230
|
-
|
|
245
|
+
the dataset from disk using `load`.
|
|
231
246
|
"""
|
|
232
|
-
directory = Path(directory or
|
|
247
|
+
directory = Path(directory or TRACE_DATASETS_DIR)
|
|
233
248
|
for evals in self.evaluations:
|
|
234
249
|
evals.save(directory)
|
|
235
250
|
path = directory / TRACE_DATASET_PARQUET_FILE_NAME.format(id=self._id)
|
|
@@ -270,16 +285,16 @@ class TraceDataset:
|
|
|
270
285
|
id (Union[str, UUID]): The ID of the trace dataset to be loaded.
|
|
271
286
|
|
|
272
287
|
directory (Optional[Union[str, Path]], optional): The path to the
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
288
|
+
directory containing the persisted trace dataset parquet file. If
|
|
289
|
+
not provided, the parquet file will be loaded from the same default
|
|
290
|
+
location used by `save`.
|
|
276
291
|
|
|
277
292
|
Returns:
|
|
278
293
|
TraceDataset: The loaded trace dataset.
|
|
279
294
|
"""
|
|
280
295
|
if not isinstance(id, UUID):
|
|
281
296
|
id = UUID(id)
|
|
282
|
-
path = Path(directory or
|
|
297
|
+
path = Path(directory or TRACE_DATASETS_DIR) / TRACE_DATASET_PARQUET_FILE_NAME.format(id=id)
|
|
283
298
|
schema = parquet.read_schema(path)
|
|
284
299
|
dataset_id, dataset_name, eval_ids = _parse_schema_metadata(schema)
|
|
285
300
|
if id != dataset_id:
|
|
@@ -335,7 +350,7 @@ class TraceDataset:
|
|
|
335
350
|
return pd.concat([df, evals_df], axis=1)
|
|
336
351
|
|
|
337
352
|
|
|
338
|
-
def _parse_schema_metadata(schema: Schema) ->
|
|
353
|
+
def _parse_schema_metadata(schema: Schema) -> tuple[UUID, str, list[UUID]]:
|
|
339
354
|
"""
|
|
340
355
|
Returns parsed metadata from a parquet schema or raises an exception if the
|
|
341
356
|
metadata is invalid.
|
phoenix/trace/utils.py
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import os
|
|
2
3
|
import re
|
|
3
4
|
from traceback import format_exception
|
|
4
|
-
from typing import
|
|
5
|
+
from typing import Optional, cast
|
|
6
|
+
from urllib import request
|
|
5
7
|
|
|
6
8
|
import pandas as pd
|
|
7
9
|
|
|
8
10
|
|
|
9
|
-
def
|
|
11
|
+
def parse_file_extension(file_path: str) -> str:
|
|
12
|
+
return os.path.splitext(file_path)[-1]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def download_json_traces_fixture(
|
|
16
|
+
url: str,
|
|
17
|
+
) -> list[str]:
|
|
18
|
+
"""
|
|
19
|
+
Stores the traces fixture as list of jsons from the jsonl files in the phoenix bucket.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
with request.urlopen(url) as f:
|
|
23
|
+
return cast(list[str], f.readlines())
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def json_lines_to_df(lines: list[str]) -> pd.DataFrame:
|
|
10
27
|
"""
|
|
11
28
|
Convert a list of JSON line strings to a Pandas DataFrame.
|
|
12
29
|
"""
|
|
@@ -39,9 +56,9 @@ def get_stacktrace(exception: BaseException) -> str:
|
|
|
39
56
|
_VERSION_TRIPLET_REGEX = re.compile(r"(\d+)\.(\d+)\.(\d+)")
|
|
40
57
|
|
|
41
58
|
|
|
42
|
-
def extract_version_triplet(version: str) -> Optional[
|
|
59
|
+
def extract_version_triplet(version: str) -> Optional[tuple[int, int, int]]:
|
|
43
60
|
return (
|
|
44
|
-
cast(
|
|
61
|
+
cast(tuple[int, int, int], tuple(map(int, match.groups())))
|
|
45
62
|
if (match := _VERSION_TRIPLET_REGEX.search(version))
|
|
46
63
|
else None
|
|
47
64
|
)
|
phoenix/utilities/__init__.py
CHANGED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
|
-
from typing import List, Optional
|
|
3
|
-
|
|
4
|
-
import pandas as pd
|
|
5
|
-
|
|
6
|
-
from phoenix.core.project import Project
|
|
7
|
-
from phoenix.trace.dsl import SpanQuery
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def query_spans(
|
|
11
|
-
project: Optional[Project],
|
|
12
|
-
*queries: SpanQuery,
|
|
13
|
-
start_time: Optional[datetime] = None,
|
|
14
|
-
stop_time: Optional[datetime] = None,
|
|
15
|
-
root_spans_only: Optional[bool] = None,
|
|
16
|
-
) -> List[pd.DataFrame]:
|
|
17
|
-
if not queries or not project:
|
|
18
|
-
return []
|
|
19
|
-
spans = tuple(
|
|
20
|
-
project.get_spans(
|
|
21
|
-
start_time=start_time,
|
|
22
|
-
stop_time=stop_time,
|
|
23
|
-
root_spans_only=root_spans_only,
|
|
24
|
-
)
|
|
25
|
-
)
|
|
26
|
-
return [query(spans) for query in queries]
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
|
|
6
|
+
from phoenix.config import get_env_client_headers, get_env_phoenix_api_key
|
|
7
|
+
|
|
8
|
+
PHOENIX_SERVER_VERSION_HEADER = "x-phoenix-server-version"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class VersionedClient(httpx.Client):
|
|
12
|
+
"""
|
|
13
|
+
A httpx.Client wrapper that warns if there is a server/client version mismatch.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, *args: Any, **kwargs: Any):
|
|
17
|
+
from phoenix.version import __version__ as phoenix_version
|
|
18
|
+
|
|
19
|
+
super().__init__(*args, **kwargs)
|
|
20
|
+
|
|
21
|
+
if env_headers := get_env_client_headers():
|
|
22
|
+
self.headers.update(env_headers)
|
|
23
|
+
if "authorization" not in [k.lower() for k in self.headers]:
|
|
24
|
+
if api_key := get_env_phoenix_api_key():
|
|
25
|
+
self.headers["Authorization"] = f"Bearer {api_key}"
|
|
26
|
+
|
|
27
|
+
self._client_phoenix_version = phoenix_version
|
|
28
|
+
self._warned_on_minor_version_mismatch = False
|
|
29
|
+
|
|
30
|
+
def _check_version(self, response: httpx.Response) -> None:
|
|
31
|
+
server_version = response.headers.get(PHOENIX_SERVER_VERSION_HEADER)
|
|
32
|
+
|
|
33
|
+
if server_version is None:
|
|
34
|
+
warnings.warn(
|
|
35
|
+
"The Phoenix server has an unknown version and may have compatibility issues."
|
|
36
|
+
)
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
client_major, client_minor, client_patch = map(
|
|
41
|
+
int, self._client_phoenix_version.split(".")
|
|
42
|
+
)
|
|
43
|
+
server_major, server_minor, server_patch = map(int, server_version.split("."))
|
|
44
|
+
if abs(server_major - client_major) >= 1:
|
|
45
|
+
warnings.warn(
|
|
46
|
+
f"⚠️⚠️ The Phoenix server ({server_version}) and client "
|
|
47
|
+
f"({self._client_phoenix_version}) versions are severely mismatched. Upgrade "
|
|
48
|
+
" either the client or server to ensure API compatibility ⚠️⚠️"
|
|
49
|
+
)
|
|
50
|
+
elif (
|
|
51
|
+
abs(server_minor - client_minor) >= 1 and not self._warned_on_minor_version_mismatch
|
|
52
|
+
):
|
|
53
|
+
self._warned_on_minor_version_mismatch = True
|
|
54
|
+
warnings.warn(
|
|
55
|
+
f"The Phoenix server ({server_version}) and client "
|
|
56
|
+
f"({self._client_phoenix_version}) versions are mismatched and may have "
|
|
57
|
+
"compatibility issues."
|
|
58
|
+
)
|
|
59
|
+
except ValueError:
|
|
60
|
+
# if either the client or server version includes a suffix e.g. "rc1", check for an
|
|
61
|
+
# exact version match of the version string
|
|
62
|
+
if self._client_phoenix_version != server_version:
|
|
63
|
+
warnings.warn(
|
|
64
|
+
f"The Phoenix server ({server_version}) and client "
|
|
65
|
+
f"({self._client_phoenix_version}) versions are mismatched and may have "
|
|
66
|
+
"compatibility issues."
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def request(self, *args: Any, **kwargs: Any) -> httpx.Response:
|
|
70
|
+
response = super().request(*args, **kwargs)
|
|
71
|
+
self._check_version(response)
|
|
72
|
+
return response
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class VersionedAsyncClient(httpx.AsyncClient):
|
|
76
|
+
"""
|
|
77
|
+
A httpx.Client wrapper that warns if there is a server/client version mismatch.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __init__(self, *args: Any, **kwargs: Any):
|
|
81
|
+
from phoenix.version import __version__ as phoenix_version
|
|
82
|
+
|
|
83
|
+
super().__init__(*args, **kwargs)
|
|
84
|
+
|
|
85
|
+
if env_headers := get_env_client_headers():
|
|
86
|
+
self.headers.update(env_headers)
|
|
87
|
+
if "authorization" not in [k.lower() for k in self.headers]:
|
|
88
|
+
if api_key := get_env_phoenix_api_key():
|
|
89
|
+
self.headers["Authorization"] = f"Bearer {api_key}"
|
|
90
|
+
|
|
91
|
+
self._client_phoenix_version = phoenix_version
|
|
92
|
+
self._warned_on_minor_version_mismatch = False
|
|
93
|
+
|
|
94
|
+
def _check_version(self, response: httpx.Response) -> None:
|
|
95
|
+
server_version = response.headers.get(PHOENIX_SERVER_VERSION_HEADER)
|
|
96
|
+
|
|
97
|
+
if server_version is None:
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
client_major, client_minor, client_patch = map(
|
|
102
|
+
int, self._client_phoenix_version.split(".")
|
|
103
|
+
)
|
|
104
|
+
server_major, server_minor, server_patch = map(int, server_version.split("."))
|
|
105
|
+
if abs(server_major - client_major) >= 1:
|
|
106
|
+
warnings.warn(
|
|
107
|
+
f"⚠️⚠️ The Phoenix server ({server_version}) and client "
|
|
108
|
+
f"({self._client_phoenix_version}) versions are severely mismatched. Upgrade "
|
|
109
|
+
" either the client or server to ensure API compatibility ⚠️⚠️"
|
|
110
|
+
)
|
|
111
|
+
elif (
|
|
112
|
+
abs(server_minor - client_minor) >= 1 and not self._warned_on_minor_version_mismatch
|
|
113
|
+
):
|
|
114
|
+
self._warned_on_minor_version_mismatch = True
|
|
115
|
+
warnings.warn(
|
|
116
|
+
f"The Phoenix server ({server_version}) and client "
|
|
117
|
+
f"({self._client_phoenix_version}) versions are mismatched and may have "
|
|
118
|
+
"compatibility issues."
|
|
119
|
+
)
|
|
120
|
+
except ValueError:
|
|
121
|
+
# if the version includes a suffix e.g. "rc1", check for an exact version match
|
|
122
|
+
if self._client_phoenix_version != server_version:
|
|
123
|
+
warnings.warn(
|
|
124
|
+
f"The Phoenix server ({server_version}) and client "
|
|
125
|
+
f"({self._client_phoenix_version}) versions are mismatched and may have "
|
|
126
|
+
"compatibility issues."
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
async def request(self, *args: Any, **kwargs: Any) -> httpx.Response:
|
|
130
|
+
response = await super().request(*args, **kwargs)
|
|
131
|
+
self._check_version(response)
|
|
132
|
+
return response
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import warnings
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import Any, TypeVar
|
|
5
|
+
|
|
6
|
+
GenericClass = TypeVar("GenericClass", bound=type[Any])
|
|
7
|
+
CallableType = TypeVar("CallableType", bound=Callable[..., Any])
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def deprecated_class(message: str) -> Callable[[GenericClass], GenericClass]:
|
|
11
|
+
def decorator(original_class: GenericClass) -> GenericClass:
|
|
12
|
+
@functools.wraps(original_class)
|
|
13
|
+
def new_class(*args: Any, **kwargs: Any) -> Any:
|
|
14
|
+
warnings.warn(message, DeprecationWarning, stacklevel=2)
|
|
15
|
+
return original_class(*args, **kwargs)
|
|
16
|
+
|
|
17
|
+
return new_class # type: ignore
|
|
18
|
+
|
|
19
|
+
return decorator
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def deprecated(message: str) -> Callable[[CallableType], CallableType]:
|
|
23
|
+
def decorator(original_func: CallableType) -> CallableType:
|
|
24
|
+
@functools.wraps(original_func)
|
|
25
|
+
def new_func(*args: Any, **kwargs: Any) -> Any:
|
|
26
|
+
warnings.warn(message, DeprecationWarning, stacklevel=2)
|
|
27
|
+
return original_func(*args, **kwargs)
|
|
28
|
+
|
|
29
|
+
return new_func # type: ignore
|
|
30
|
+
|
|
31
|
+
return decorator
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import traceback
|
|
3
|
-
from
|
|
3
|
+
from collections.abc import Callable, Iterable
|
|
4
|
+
from typing import Any, Optional, TypeVar, cast
|
|
4
5
|
|
|
5
6
|
F = TypeVar("F", bound=Callable[..., Any])
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
def graceful_fallback(
|
|
9
|
-
fallback_method: Callable[..., Any], exceptions: Optional[Iterable[
|
|
10
|
+
fallback_method: Callable[..., Any], exceptions: Optional[Iterable[type[BaseException]]] = None
|
|
10
11
|
) -> Callable[[F], F]:
|
|
11
12
|
"""
|
|
12
13
|
Decorator that reroutes failing functions to a specified fallback method.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import datetime
|
|
3
|
+
from collections.abc import Mapping, Sequence
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from io import StringIO
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Union, cast, get_args, get_origin
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pandas as pd
|
|
11
|
+
from pandas.io.json import build_table_schema
|
|
12
|
+
from pandas.io.json._table_schema import parse_table_schema # type: ignore
|
|
13
|
+
from strawberry import UNSET
|
|
14
|
+
from strawberry.types.base import StrawberryObjectDefinition
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from pandas.io.json import ujson_dumps # type: ignore
|
|
18
|
+
except ImportError:
|
|
19
|
+
# https://github.com/pandas-dev/pandas/pull/54581
|
|
20
|
+
from pandas.io.json import dumps as ujson_dumps # type: ignore
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def jsonify(obj: Any) -> Any:
|
|
24
|
+
"""
|
|
25
|
+
Coerce object to be json serializable.
|
|
26
|
+
"""
|
|
27
|
+
if isinstance(obj, Enum):
|
|
28
|
+
return jsonify(obj.value)
|
|
29
|
+
if isinstance(obj, (str, int, float, bool)) or obj is None:
|
|
30
|
+
return obj
|
|
31
|
+
if isinstance(obj, (list, set, frozenset, Sequence)):
|
|
32
|
+
return [jsonify(v) for v in obj]
|
|
33
|
+
if isinstance(obj, (dict, Mapping)):
|
|
34
|
+
return {jsonify(k): jsonify(v) for k, v in obj.items()}
|
|
35
|
+
is_strawberry_type = isinstance(
|
|
36
|
+
getattr(obj, "__strawberry_definition__", None), StrawberryObjectDefinition
|
|
37
|
+
)
|
|
38
|
+
if is_strawberry_type:
|
|
39
|
+
return {
|
|
40
|
+
k: jsonify(v)
|
|
41
|
+
for field in dataclasses.fields(obj)
|
|
42
|
+
if (v := getattr(obj, (k := field.name))) is not UNSET
|
|
43
|
+
}
|
|
44
|
+
if dataclasses.is_dataclass(obj):
|
|
45
|
+
return {
|
|
46
|
+
k: jsonify(v)
|
|
47
|
+
for field in dataclasses.fields(obj)
|
|
48
|
+
if not (
|
|
49
|
+
(v := getattr(obj, (k := field.name))) is None
|
|
50
|
+
and get_origin(field) is Union
|
|
51
|
+
and type(None) in get_args(field)
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
if isinstance(obj, (datetime.date, datetime.datetime, datetime.time)):
|
|
55
|
+
return obj.isoformat()
|
|
56
|
+
if isinstance(obj, datetime.timedelta):
|
|
57
|
+
return obj.total_seconds()
|
|
58
|
+
if isinstance(obj, Path):
|
|
59
|
+
return str(obj)
|
|
60
|
+
if isinstance(obj, BaseException):
|
|
61
|
+
return str(obj)
|
|
62
|
+
if isinstance(obj, np.ndarray):
|
|
63
|
+
return [jsonify(v) for v in obj]
|
|
64
|
+
if hasattr(obj, "__float__"):
|
|
65
|
+
return float(obj)
|
|
66
|
+
if hasattr(obj, "model_dump") and callable(obj.model_dump):
|
|
67
|
+
# pydantic v2
|
|
68
|
+
try:
|
|
69
|
+
assert isinstance(d := obj.model_dump(), dict)
|
|
70
|
+
except BaseException:
|
|
71
|
+
pass
|
|
72
|
+
else:
|
|
73
|
+
return jsonify(d)
|
|
74
|
+
if hasattr(obj, "dict") and callable(obj.dict):
|
|
75
|
+
# pydantic v1
|
|
76
|
+
try:
|
|
77
|
+
assert isinstance(d := obj.dict(), dict)
|
|
78
|
+
except BaseException:
|
|
79
|
+
pass
|
|
80
|
+
else:
|
|
81
|
+
return jsonify(d)
|
|
82
|
+
cls = obj.__class__
|
|
83
|
+
return f"<{cls.__module__}.{cls.__name__} object>"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def encode_df_as_json_string(df: pd.DataFrame) -> str:
|
|
87
|
+
index_names = df.index.names
|
|
88
|
+
n = len(index_names)
|
|
89
|
+
primary_key = [f"{i}_{(x or '')}" for i, x in enumerate(index_names)]
|
|
90
|
+
df = df.set_axis([f"{i}_{x}" for i, x in enumerate(df.columns, n)], axis=1)
|
|
91
|
+
df = df.reset_index(names=primary_key)
|
|
92
|
+
schema = build_table_schema(df, False, primary_key) # type: ignore
|
|
93
|
+
data = df.to_dict("records")
|
|
94
|
+
return cast(
|
|
95
|
+
str,
|
|
96
|
+
ujson_dumps(
|
|
97
|
+
{"schema": schema, "data": data},
|
|
98
|
+
date_unit="ns",
|
|
99
|
+
iso_dates=True,
|
|
100
|
+
ensure_ascii=False,
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def decode_df_from_json_string(obj: str) -> pd.DataFrame:
|
|
106
|
+
# Note: read_json converts an all null column to NaN
|
|
107
|
+
df = cast(pd.DataFrame, parse_table_schema(StringIO(obj).read(), False))
|
|
108
|
+
df.index.names = [x.split("_", 1)[1] or None for x in df.index.names] # type: ignore
|
|
109
|
+
return df.set_axis([x.split("_", 1)[1] for x in df.columns], axis=1)
|
phoenix/utilities/logging.py
CHANGED
|
@@ -8,3 +8,11 @@ from tqdm.auto import tqdm
|
|
|
8
8
|
def printif(condition: bool, *args: Any, **kwargs: Any) -> None:
|
|
9
9
|
if condition:
|
|
10
10
|
tqdm.write(*args, **kwargs)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def log_a_list(list_of_str: list[str], join_word: str) -> str:
|
|
14
|
+
if list_of_str is None or len(list_of_str) == 0:
|
|
15
|
+
return ""
|
|
16
|
+
if len(list_of_str) == 1:
|
|
17
|
+
return list_of_str[0]
|
|
18
|
+
return f"{', '.join(map(str, list_of_str[:-1]))} {join_word} {list_of_str[-1]}"
|
phoenix/utilities/project.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import Iterable
|
|
2
2
|
|
|
3
3
|
from openinference.semconv.resource import ResourceAttributes
|
|
4
4
|
from opentelemetry.proto.common.v1.common_pb2 import KeyValue
|
|
@@ -9,5 +9,5 @@ from phoenix.config import DEFAULT_PROJECT_NAME
|
|
|
9
9
|
def get_project_name(attributes: Iterable[KeyValue]) -> str:
|
|
10
10
|
for kv in attributes:
|
|
11
11
|
if kv.key == ResourceAttributes.PROJECT_NAME and (v := kv.value.string_value):
|
|
12
|
-
return v
|
|
12
|
+
return str(v)
|
|
13
13
|
return DEFAULT_PROJECT_NAME
|
phoenix/utilities/re.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from re import compile, split
|
|
3
|
+
from urllib.parse import unquote
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
# Optional whitespace
|
|
8
|
+
_OWS = r"[ \t]*"
|
|
9
|
+
# A key contains printable US-ASCII characters except: SP and "(),/:;<=>?@[\]{}
|
|
10
|
+
_KEY_FORMAT = r"[\x21\x23-\x27\x2a\x2b\x2d\x2e\x30-\x39\x41-\x5a\x5e-\x7a\x7c\x7e]+"
|
|
11
|
+
# A value contains a URL-encoded UTF-8 string. The encoded form can contain any
|
|
12
|
+
# printable US-ASCII characters (0x20-0x7f) other than SP, DEL, and ",;/
|
|
13
|
+
_VALUE_FORMAT = r"[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*"
|
|
14
|
+
# A key-value is key=value, with optional whitespace surrounding key and value
|
|
15
|
+
_KEY_VALUE_FORMAT = rf"{_OWS}{_KEY_FORMAT}{_OWS}={_OWS}{_VALUE_FORMAT}{_OWS}"
|
|
16
|
+
|
|
17
|
+
_HEADER_PATTERN = compile(_KEY_VALUE_FORMAT)
|
|
18
|
+
_DELIMITER_PATTERN = compile(r"[ \t]*,[ \t]*")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def parse_env_headers(s: str) -> dict[str, str]:
|
|
22
|
+
"""
|
|
23
|
+
Parse ``s``, which is a ``str`` instance containing HTTP headers encoded
|
|
24
|
+
for use in ENV variables per the W3C Baggage HTTP header format at
|
|
25
|
+
https://www.w3.org/TR/baggage/#baggage-http-header-format, except that
|
|
26
|
+
additional semi-colon delimited metadata is not supported.
|
|
27
|
+
|
|
28
|
+
src: https://github.com/open-telemetry/opentelemetry-python/blob/2d5cd58f33bd8a16f45f30be620a96699bc14297/opentelemetry-api/src/opentelemetry/util/re.py#L52
|
|
29
|
+
"""
|
|
30
|
+
headers: dict[str, str] = {}
|
|
31
|
+
headers_list: list[str] = split(_DELIMITER_PATTERN, s)
|
|
32
|
+
for header in headers_list:
|
|
33
|
+
if not header: # empty string
|
|
34
|
+
continue
|
|
35
|
+
match = _HEADER_PATTERN.fullmatch(header.strip())
|
|
36
|
+
if not match:
|
|
37
|
+
logger.warning(
|
|
38
|
+
"Header format invalid! Header values in environment variables must be "
|
|
39
|
+
"URL encoded: %s",
|
|
40
|
+
header,
|
|
41
|
+
)
|
|
42
|
+
continue
|
|
43
|
+
# value may contain any number of `=`
|
|
44
|
+
name, value = match.string.split("=", 1)
|
|
45
|
+
name = unquote(name).strip().lower()
|
|
46
|
+
value = unquote(value).strip()
|
|
47
|
+
headers[name] = value
|
|
48
|
+
|
|
49
|
+
return headers
|
phoenix/utilities/span_store.py
CHANGED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
|
-
from phoenix.config import get_env_span_storage_type, get_storage_dir
|
|
4
|
-
from phoenix.core.traces import Traces
|
|
5
|
-
from phoenix.storage.span_store import SPAN_STORE_FACTORIES, SpanStore
|
|
6
|
-
from phoenix.trace.otel import decode
|
|
7
|
-
from phoenix.utilities.project import get_project_name
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def get_span_store() -> Optional[SpanStore]:
|
|
11
|
-
if span_store_type := get_env_span_storage_type():
|
|
12
|
-
span_store_factory = SPAN_STORE_FACTORIES[span_store_type]
|
|
13
|
-
return span_store_factory(get_storage_dir())
|
|
14
|
-
return None
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def load_traces_data_from_store(traces: Traces, span_store: SpanStore) -> None:
|
|
18
|
-
for traces_data in span_store.load():
|
|
19
|
-
for resource_spans in traces_data.resource_spans:
|
|
20
|
-
project_name = get_project_name(resource_spans.resource.attributes)
|
|
21
|
-
for scope_span in resource_spans.scope_spans:
|
|
22
|
-
for span in scope_span.spans:
|
|
23
|
-
traces.put(decode(span), project_name=project_name)
|