arize-phoenix 3.16.1__py3-none-any.whl → 7.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of arize-phoenix might be problematic. Click here for more details.
- arize_phoenix-7.7.0.dist-info/METADATA +261 -0
- arize_phoenix-7.7.0.dist-info/RECORD +345 -0
- {arize_phoenix-3.16.1.dist-info → arize_phoenix-7.7.0.dist-info}/WHEEL +1 -1
- arize_phoenix-7.7.0.dist-info/entry_points.txt +3 -0
- phoenix/__init__.py +86 -14
- phoenix/auth.py +309 -0
- phoenix/config.py +675 -45
- phoenix/core/model.py +32 -30
- phoenix/core/model_schema.py +102 -109
- phoenix/core/model_schema_adapter.py +48 -45
- phoenix/datetime_utils.py +24 -3
- phoenix/db/README.md +54 -0
- phoenix/db/__init__.py +4 -0
- phoenix/db/alembic.ini +85 -0
- phoenix/db/bulk_inserter.py +294 -0
- phoenix/db/engines.py +208 -0
- phoenix/db/enums.py +20 -0
- phoenix/db/facilitator.py +113 -0
- phoenix/db/helpers.py +159 -0
- phoenix/db/insertion/constants.py +2 -0
- phoenix/db/insertion/dataset.py +227 -0
- phoenix/db/insertion/document_annotation.py +171 -0
- phoenix/db/insertion/evaluation.py +191 -0
- phoenix/db/insertion/helpers.py +98 -0
- phoenix/db/insertion/span.py +193 -0
- phoenix/db/insertion/span_annotation.py +158 -0
- phoenix/db/insertion/trace_annotation.py +158 -0
- phoenix/db/insertion/types.py +256 -0
- phoenix/db/migrate.py +86 -0
- phoenix/db/migrations/data_migration_scripts/populate_project_sessions.py +199 -0
- phoenix/db/migrations/env.py +114 -0
- phoenix/db/migrations/script.py.mako +26 -0
- phoenix/db/migrations/versions/10460e46d750_datasets.py +317 -0
- phoenix/db/migrations/versions/3be8647b87d8_add_token_columns_to_spans_table.py +126 -0
- phoenix/db/migrations/versions/4ded9e43755f_create_project_sessions_table.py +66 -0
- phoenix/db/migrations/versions/cd164e83824f_users_and_tokens.py +157 -0
- phoenix/db/migrations/versions/cf03bd6bae1d_init.py +280 -0
- phoenix/db/models.py +807 -0
- phoenix/exceptions.py +5 -1
- phoenix/experiments/__init__.py +6 -0
- phoenix/experiments/evaluators/__init__.py +29 -0
- phoenix/experiments/evaluators/base.py +158 -0
- phoenix/experiments/evaluators/code_evaluators.py +184 -0
- phoenix/experiments/evaluators/llm_evaluators.py +473 -0
- phoenix/experiments/evaluators/utils.py +236 -0
- phoenix/experiments/functions.py +772 -0
- phoenix/experiments/tracing.py +86 -0
- phoenix/experiments/types.py +726 -0
- phoenix/experiments/utils.py +25 -0
- phoenix/inferences/__init__.py +0 -0
- phoenix/{datasets → inferences}/errors.py +6 -5
- phoenix/{datasets → inferences}/fixtures.py +49 -42
- phoenix/{datasets/dataset.py → inferences/inferences.py} +121 -105
- phoenix/{datasets → inferences}/schema.py +11 -11
- phoenix/{datasets → inferences}/validation.py +13 -14
- phoenix/logging/__init__.py +3 -0
- phoenix/logging/_config.py +90 -0
- phoenix/logging/_filter.py +6 -0
- phoenix/logging/_formatter.py +69 -0
- phoenix/metrics/__init__.py +5 -4
- phoenix/metrics/binning.py +4 -3
- phoenix/metrics/metrics.py +2 -1
- phoenix/metrics/mixins.py +7 -6
- phoenix/metrics/retrieval_metrics.py +2 -1
- phoenix/metrics/timeseries.py +5 -4
- phoenix/metrics/wrappers.py +9 -3
- phoenix/pointcloud/clustering.py +5 -5
- phoenix/pointcloud/pointcloud.py +7 -5
- phoenix/pointcloud/projectors.py +5 -6
- phoenix/pointcloud/umap_parameters.py +53 -52
- phoenix/server/api/README.md +28 -0
- phoenix/server/api/auth.py +44 -0
- phoenix/server/api/context.py +152 -9
- phoenix/server/api/dataloaders/__init__.py +91 -0
- phoenix/server/api/dataloaders/annotation_summaries.py +139 -0
- phoenix/server/api/dataloaders/average_experiment_run_latency.py +54 -0
- phoenix/server/api/dataloaders/cache/__init__.py +3 -0
- phoenix/server/api/dataloaders/cache/two_tier_cache.py +68 -0
- phoenix/server/api/dataloaders/dataset_example_revisions.py +131 -0
- phoenix/server/api/dataloaders/dataset_example_spans.py +38 -0
- phoenix/server/api/dataloaders/document_evaluation_summaries.py +144 -0
- phoenix/server/api/dataloaders/document_evaluations.py +31 -0
- phoenix/server/api/dataloaders/document_retrieval_metrics.py +89 -0
- phoenix/server/api/dataloaders/experiment_annotation_summaries.py +79 -0
- phoenix/server/api/dataloaders/experiment_error_rates.py +58 -0
- phoenix/server/api/dataloaders/experiment_run_annotations.py +36 -0
- phoenix/server/api/dataloaders/experiment_run_counts.py +49 -0
- phoenix/server/api/dataloaders/experiment_sequence_number.py +44 -0
- phoenix/server/api/dataloaders/latency_ms_quantile.py +188 -0
- phoenix/server/api/dataloaders/min_start_or_max_end_times.py +85 -0
- phoenix/server/api/dataloaders/project_by_name.py +31 -0
- phoenix/server/api/dataloaders/record_counts.py +116 -0
- phoenix/server/api/dataloaders/session_io.py +79 -0
- phoenix/server/api/dataloaders/session_num_traces.py +30 -0
- phoenix/server/api/dataloaders/session_num_traces_with_error.py +32 -0
- phoenix/server/api/dataloaders/session_token_usages.py +41 -0
- phoenix/server/api/dataloaders/session_trace_latency_ms_quantile.py +55 -0
- phoenix/server/api/dataloaders/span_annotations.py +26 -0
- phoenix/server/api/dataloaders/span_dataset_examples.py +31 -0
- phoenix/server/api/dataloaders/span_descendants.py +57 -0
- phoenix/server/api/dataloaders/span_projects.py +33 -0
- phoenix/server/api/dataloaders/token_counts.py +124 -0
- phoenix/server/api/dataloaders/trace_by_trace_ids.py +25 -0
- phoenix/server/api/dataloaders/trace_root_spans.py +32 -0
- phoenix/server/api/dataloaders/user_roles.py +30 -0
- phoenix/server/api/dataloaders/users.py +33 -0
- phoenix/server/api/exceptions.py +48 -0
- phoenix/server/api/helpers/__init__.py +12 -0
- phoenix/server/api/helpers/dataset_helpers.py +217 -0
- phoenix/server/api/helpers/experiment_run_filters.py +763 -0
- phoenix/server/api/helpers/playground_clients.py +948 -0
- phoenix/server/api/helpers/playground_registry.py +70 -0
- phoenix/server/api/helpers/playground_spans.py +455 -0
- phoenix/server/api/input_types/AddExamplesToDatasetInput.py +16 -0
- phoenix/server/api/input_types/AddSpansToDatasetInput.py +14 -0
- phoenix/server/api/input_types/ChatCompletionInput.py +38 -0
- phoenix/server/api/input_types/ChatCompletionMessageInput.py +24 -0
- phoenix/server/api/input_types/ClearProjectInput.py +15 -0
- phoenix/server/api/input_types/ClusterInput.py +2 -2
- phoenix/server/api/input_types/CreateDatasetInput.py +12 -0
- phoenix/server/api/input_types/CreateSpanAnnotationInput.py +18 -0
- phoenix/server/api/input_types/CreateTraceAnnotationInput.py +18 -0
- phoenix/server/api/input_types/DataQualityMetricInput.py +5 -2
- phoenix/server/api/input_types/DatasetExampleInput.py +14 -0
- phoenix/server/api/input_types/DatasetSort.py +17 -0
- phoenix/server/api/input_types/DatasetVersionSort.py +16 -0
- phoenix/server/api/input_types/DeleteAnnotationsInput.py +7 -0
- phoenix/server/api/input_types/DeleteDatasetExamplesInput.py +13 -0
- phoenix/server/api/input_types/DeleteDatasetInput.py +7 -0
- phoenix/server/api/input_types/DeleteExperimentsInput.py +7 -0
- phoenix/server/api/input_types/DimensionFilter.py +4 -4
- phoenix/server/api/input_types/GenerativeModelInput.py +17 -0
- phoenix/server/api/input_types/Granularity.py +1 -1
- phoenix/server/api/input_types/InvocationParameters.py +162 -0
- phoenix/server/api/input_types/PatchAnnotationInput.py +19 -0
- phoenix/server/api/input_types/PatchDatasetExamplesInput.py +35 -0
- phoenix/server/api/input_types/PatchDatasetInput.py +14 -0
- phoenix/server/api/input_types/PerformanceMetricInput.py +5 -2
- phoenix/server/api/input_types/ProjectSessionSort.py +29 -0
- phoenix/server/api/input_types/SpanAnnotationSort.py +17 -0
- phoenix/server/api/input_types/SpanSort.py +134 -69
- phoenix/server/api/input_types/TemplateOptions.py +10 -0
- phoenix/server/api/input_types/TraceAnnotationSort.py +17 -0
- phoenix/server/api/input_types/UserRoleInput.py +9 -0
- phoenix/server/api/mutations/__init__.py +28 -0
- phoenix/server/api/mutations/api_key_mutations.py +167 -0
- phoenix/server/api/mutations/chat_mutations.py +593 -0
- phoenix/server/api/mutations/dataset_mutations.py +591 -0
- phoenix/server/api/mutations/experiment_mutations.py +75 -0
- phoenix/server/api/{types/ExportEventsMutation.py → mutations/export_events_mutations.py} +21 -18
- phoenix/server/api/mutations/project_mutations.py +57 -0
- phoenix/server/api/mutations/span_annotations_mutations.py +128 -0
- phoenix/server/api/mutations/trace_annotations_mutations.py +127 -0
- phoenix/server/api/mutations/user_mutations.py +329 -0
- phoenix/server/api/openapi/__init__.py +0 -0
- phoenix/server/api/openapi/main.py +17 -0
- phoenix/server/api/openapi/schema.py +16 -0
- phoenix/server/api/queries.py +738 -0
- phoenix/server/api/routers/__init__.py +11 -0
- phoenix/server/api/routers/auth.py +284 -0
- phoenix/server/api/routers/embeddings.py +26 -0
- phoenix/server/api/routers/oauth2.py +488 -0
- phoenix/server/api/routers/v1/__init__.py +64 -0
- phoenix/server/api/routers/v1/datasets.py +1017 -0
- phoenix/server/api/routers/v1/evaluations.py +362 -0
- phoenix/server/api/routers/v1/experiment_evaluations.py +115 -0
- phoenix/server/api/routers/v1/experiment_runs.py +167 -0
- phoenix/server/api/routers/v1/experiments.py +308 -0
- phoenix/server/api/routers/v1/pydantic_compat.py +78 -0
- phoenix/server/api/routers/v1/spans.py +267 -0
- phoenix/server/api/routers/v1/traces.py +208 -0
- phoenix/server/api/routers/v1/utils.py +95 -0
- phoenix/server/api/schema.py +44 -241
- phoenix/server/api/subscriptions.py +597 -0
- phoenix/server/api/types/Annotation.py +21 -0
- phoenix/server/api/types/AnnotationSummary.py +55 -0
- phoenix/server/api/types/AnnotatorKind.py +16 -0
- phoenix/server/api/types/ApiKey.py +27 -0
- phoenix/server/api/types/AuthMethod.py +9 -0
- phoenix/server/api/types/ChatCompletionMessageRole.py +11 -0
- phoenix/server/api/types/ChatCompletionSubscriptionPayload.py +46 -0
- phoenix/server/api/types/Cluster.py +25 -24
- phoenix/server/api/types/CreateDatasetPayload.py +8 -0
- phoenix/server/api/types/DataQualityMetric.py +31 -13
- phoenix/server/api/types/Dataset.py +288 -63
- phoenix/server/api/types/DatasetExample.py +85 -0
- phoenix/server/api/types/DatasetExampleRevision.py +34 -0
- phoenix/server/api/types/DatasetVersion.py +14 -0
- phoenix/server/api/types/Dimension.py +32 -31
- phoenix/server/api/types/DocumentEvaluationSummary.py +9 -8
- phoenix/server/api/types/EmbeddingDimension.py +56 -49
- phoenix/server/api/types/Evaluation.py +25 -31
- phoenix/server/api/types/EvaluationSummary.py +30 -50
- phoenix/server/api/types/Event.py +20 -20
- phoenix/server/api/types/ExampleRevisionInterface.py +14 -0
- phoenix/server/api/types/Experiment.py +152 -0
- phoenix/server/api/types/ExperimentAnnotationSummary.py +13 -0
- phoenix/server/api/types/ExperimentComparison.py +17 -0
- phoenix/server/api/types/ExperimentRun.py +119 -0
- phoenix/server/api/types/ExperimentRunAnnotation.py +56 -0
- phoenix/server/api/types/GenerativeModel.py +9 -0
- phoenix/server/api/types/GenerativeProvider.py +85 -0
- phoenix/server/api/types/Inferences.py +80 -0
- phoenix/server/api/types/InferencesRole.py +23 -0
- phoenix/server/api/types/LabelFraction.py +7 -0
- phoenix/server/api/types/MimeType.py +2 -2
- phoenix/server/api/types/Model.py +54 -54
- phoenix/server/api/types/PerformanceMetric.py +8 -5
- phoenix/server/api/types/Project.py +407 -142
- phoenix/server/api/types/ProjectSession.py +139 -0
- phoenix/server/api/types/Segments.py +4 -4
- phoenix/server/api/types/Span.py +221 -176
- phoenix/server/api/types/SpanAnnotation.py +43 -0
- phoenix/server/api/types/SpanIOValue.py +15 -0
- phoenix/server/api/types/SystemApiKey.py +9 -0
- phoenix/server/api/types/TemplateLanguage.py +10 -0
- phoenix/server/api/types/TimeSeries.py +19 -15
- phoenix/server/api/types/TokenUsage.py +11 -0
- phoenix/server/api/types/Trace.py +154 -0
- phoenix/server/api/types/TraceAnnotation.py +45 -0
- phoenix/server/api/types/UMAPPoints.py +7 -7
- phoenix/server/api/types/User.py +60 -0
- phoenix/server/api/types/UserApiKey.py +45 -0
- phoenix/server/api/types/UserRole.py +15 -0
- phoenix/server/api/types/node.py +4 -112
- phoenix/server/api/types/pagination.py +156 -57
- phoenix/server/api/utils.py +34 -0
- phoenix/server/app.py +864 -115
- phoenix/server/bearer_auth.py +163 -0
- phoenix/server/dml_event.py +136 -0
- phoenix/server/dml_event_handler.py +256 -0
- phoenix/server/email/__init__.py +0 -0
- phoenix/server/email/sender.py +97 -0
- phoenix/server/email/templates/__init__.py +0 -0
- phoenix/server/email/templates/password_reset.html +19 -0
- phoenix/server/email/types.py +11 -0
- phoenix/server/grpc_server.py +102 -0
- phoenix/server/jwt_store.py +505 -0
- phoenix/server/main.py +305 -116
- phoenix/server/oauth2.py +52 -0
- phoenix/server/openapi/__init__.py +0 -0
- phoenix/server/prometheus.py +111 -0
- phoenix/server/rate_limiters.py +188 -0
- phoenix/server/static/.vite/manifest.json +87 -0
- phoenix/server/static/assets/components-Cy9nwIvF.js +2125 -0
- phoenix/server/static/assets/index-BKvHIxkk.js +113 -0
- phoenix/server/static/assets/pages-CUi2xCVQ.js +4449 -0
- phoenix/server/static/assets/vendor-DvC8cT4X.js +894 -0
- phoenix/server/static/assets/vendor-DxkFTwjz.css +1 -0
- phoenix/server/static/assets/vendor-arizeai-Do1793cv.js +662 -0
- phoenix/server/static/assets/vendor-codemirror-BzwZPyJM.js +24 -0
- phoenix/server/static/assets/vendor-recharts-_Jb7JjhG.js +59 -0
- phoenix/server/static/assets/vendor-shiki-Cl9QBraO.js +5 -0
- phoenix/server/static/assets/vendor-three-DwGkEfCM.js +2998 -0
- phoenix/server/telemetry.py +68 -0
- phoenix/server/templates/index.html +82 -23
- phoenix/server/thread_server.py +3 -3
- phoenix/server/types.py +275 -0
- phoenix/services.py +27 -18
- phoenix/session/client.py +743 -68
- phoenix/session/data_extractor.py +31 -7
- phoenix/session/evaluation.py +3 -9
- phoenix/session/session.py +263 -219
- phoenix/settings.py +22 -0
- phoenix/trace/__init__.py +2 -22
- phoenix/trace/attributes.py +338 -0
- phoenix/trace/dsl/README.md +116 -0
- phoenix/trace/dsl/filter.py +663 -213
- phoenix/trace/dsl/helpers.py +73 -21
- phoenix/trace/dsl/query.py +574 -201
- phoenix/trace/exporter.py +24 -19
- phoenix/trace/fixtures.py +368 -32
- phoenix/trace/otel.py +71 -219
- phoenix/trace/projects.py +3 -2
- phoenix/trace/schemas.py +33 -11
- phoenix/trace/span_evaluations.py +21 -16
- phoenix/trace/span_json_decoder.py +6 -4
- phoenix/trace/span_json_encoder.py +2 -2
- phoenix/trace/trace_dataset.py +47 -32
- phoenix/trace/utils.py +21 -4
- phoenix/utilities/__init__.py +0 -26
- phoenix/utilities/client.py +132 -0
- phoenix/utilities/deprecation.py +31 -0
- phoenix/utilities/error_handling.py +3 -2
- phoenix/utilities/json.py +109 -0
- phoenix/utilities/logging.py +8 -0
- phoenix/utilities/project.py +2 -2
- phoenix/utilities/re.py +49 -0
- phoenix/utilities/span_store.py +0 -23
- phoenix/utilities/template_formatters.py +99 -0
- phoenix/version.py +1 -1
- arize_phoenix-3.16.1.dist-info/METADATA +0 -495
- arize_phoenix-3.16.1.dist-info/RECORD +0 -178
- phoenix/core/project.py +0 -619
- phoenix/core/traces.py +0 -96
- phoenix/experimental/evals/__init__.py +0 -73
- phoenix/experimental/evals/evaluators.py +0 -413
- phoenix/experimental/evals/functions/__init__.py +0 -4
- phoenix/experimental/evals/functions/classify.py +0 -453
- phoenix/experimental/evals/functions/executor.py +0 -353
- phoenix/experimental/evals/functions/generate.py +0 -138
- phoenix/experimental/evals/functions/processing.py +0 -76
- phoenix/experimental/evals/models/__init__.py +0 -14
- phoenix/experimental/evals/models/anthropic.py +0 -175
- phoenix/experimental/evals/models/base.py +0 -170
- phoenix/experimental/evals/models/bedrock.py +0 -221
- phoenix/experimental/evals/models/litellm.py +0 -134
- phoenix/experimental/evals/models/openai.py +0 -448
- phoenix/experimental/evals/models/rate_limiters.py +0 -246
- phoenix/experimental/evals/models/vertex.py +0 -173
- phoenix/experimental/evals/models/vertexai.py +0 -186
- phoenix/experimental/evals/retrievals.py +0 -96
- phoenix/experimental/evals/templates/__init__.py +0 -50
- phoenix/experimental/evals/templates/default_templates.py +0 -472
- phoenix/experimental/evals/templates/template.py +0 -195
- phoenix/experimental/evals/utils/__init__.py +0 -172
- phoenix/experimental/evals/utils/threads.py +0 -27
- phoenix/server/api/helpers.py +0 -11
- phoenix/server/api/routers/evaluation_handler.py +0 -109
- phoenix/server/api/routers/span_handler.py +0 -70
- phoenix/server/api/routers/trace_handler.py +0 -60
- phoenix/server/api/types/DatasetRole.py +0 -23
- phoenix/server/static/index.css +0 -6
- phoenix/server/static/index.js +0 -7447
- phoenix/storage/span_store/__init__.py +0 -23
- phoenix/storage/span_store/text_file.py +0 -85
- phoenix/trace/dsl/missing.py +0 -60
- phoenix/trace/langchain/__init__.py +0 -3
- phoenix/trace/langchain/instrumentor.py +0 -35
- phoenix/trace/llama_index/__init__.py +0 -3
- phoenix/trace/llama_index/callback.py +0 -102
- phoenix/trace/openai/__init__.py +0 -3
- phoenix/trace/openai/instrumentor.py +0 -30
- {arize_phoenix-3.16.1.dist-info → arize_phoenix-7.7.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-3.16.1.dist-info → arize_phoenix-7.7.0.dist-info}/licenses/LICENSE +0 -0
- /phoenix/{datasets → db/insertion}/__init__.py +0 -0
- /phoenix/{experimental → db/migrations}/__init__.py +0 -0
- /phoenix/{storage → db/migrations/data_migration_scripts}/__init__.py +0 -0
phoenix/trace/otel.py
CHANGED
|
@@ -1,39 +1,34 @@
|
|
|
1
|
-
import inspect
|
|
2
1
|
import json
|
|
3
2
|
from binascii import hexlify, unhexlify
|
|
3
|
+
from collections.abc import Iterable, Iterator, Mapping, Sequence
|
|
4
4
|
from datetime import datetime, timezone
|
|
5
5
|
from types import MappingProxyType
|
|
6
|
-
from typing import
|
|
7
|
-
Any,
|
|
8
|
-
DefaultDict,
|
|
9
|
-
Dict,
|
|
10
|
-
Iterable,
|
|
11
|
-
Iterator,
|
|
12
|
-
List,
|
|
13
|
-
Mapping,
|
|
14
|
-
Optional,
|
|
15
|
-
Sequence,
|
|
16
|
-
Set,
|
|
17
|
-
SupportsFloat,
|
|
18
|
-
Tuple,
|
|
19
|
-
Union,
|
|
20
|
-
cast,
|
|
21
|
-
)
|
|
6
|
+
from typing import Any, Optional, SupportsFloat, cast
|
|
22
7
|
|
|
23
8
|
import numpy as np
|
|
24
9
|
import opentelemetry.proto.trace.v1.trace_pb2 as otlp
|
|
25
|
-
from openinference.semconv import
|
|
26
|
-
|
|
10
|
+
from openinference.semconv.trace import (
|
|
11
|
+
DocumentAttributes,
|
|
12
|
+
OpenInferenceMimeTypeValues,
|
|
13
|
+
SpanAttributes,
|
|
14
|
+
)
|
|
27
15
|
from opentelemetry.proto.common.v1.common_pb2 import AnyValue, ArrayValue, KeyValue
|
|
28
16
|
from opentelemetry.util.types import Attributes, AttributeValue
|
|
29
17
|
from typing_extensions import TypeAlias, assert_never
|
|
30
18
|
|
|
19
|
+
from phoenix.trace.attributes import (
|
|
20
|
+
JSON_STRING_ATTRIBUTES,
|
|
21
|
+
flatten,
|
|
22
|
+
get_attribute_value,
|
|
23
|
+
has_mapping,
|
|
24
|
+
load_json_strings,
|
|
25
|
+
unflatten,
|
|
26
|
+
)
|
|
31
27
|
from phoenix.trace.schemas import (
|
|
32
28
|
EXCEPTION_ESCAPED,
|
|
33
29
|
EXCEPTION_MESSAGE,
|
|
34
30
|
EXCEPTION_STACKTRACE,
|
|
35
31
|
EXCEPTION_TYPE,
|
|
36
|
-
MimeType,
|
|
37
32
|
Span,
|
|
38
33
|
SpanContext,
|
|
39
34
|
SpanEvent,
|
|
@@ -43,6 +38,7 @@ from phoenix.trace.schemas import (
|
|
|
43
38
|
SpanStatusCode,
|
|
44
39
|
TraceID,
|
|
45
40
|
)
|
|
41
|
+
from phoenix.utilities.json import jsonify
|
|
46
42
|
|
|
47
43
|
DOCUMENT_METADATA = DocumentAttributes.DOCUMENT_METADATA
|
|
48
44
|
INPUT_MIME_TYPE = SpanAttributes.INPUT_MIME_TYPE
|
|
@@ -53,28 +49,45 @@ OUTPUT_MIME_TYPE = SpanAttributes.OUTPUT_MIME_TYPE
|
|
|
53
49
|
OUTPUT_VALUE = SpanAttributes.OUTPUT_VALUE
|
|
54
50
|
TOOL_PARAMETERS = SpanAttributes.TOOL_PARAMETERS
|
|
55
51
|
LLM_PROMPT_TEMPLATE_VARIABLES = SpanAttributes.LLM_PROMPT_TEMPLATE_VARIABLES
|
|
52
|
+
LLM_TOKEN_COUNT_PROMPT = SpanAttributes.LLM_TOKEN_COUNT_PROMPT
|
|
53
|
+
LLM_TOKEN_COUNT_COMPLETION = SpanAttributes.LLM_TOKEN_COUNT_COMPLETION
|
|
54
|
+
LLM_TOKEN_COUNT_TOTAL = SpanAttributes.LLM_TOKEN_COUNT_TOTAL
|
|
56
55
|
|
|
57
56
|
|
|
58
|
-
def
|
|
57
|
+
def coerce_otlp_span_attributes(
|
|
58
|
+
decoded_attributes: Iterable[tuple[str, Any]],
|
|
59
|
+
) -> Iterator[tuple[str, Any]]:
|
|
60
|
+
for key, value in decoded_attributes:
|
|
61
|
+
if key in (LLM_TOKEN_COUNT_PROMPT, LLM_TOKEN_COUNT_COMPLETION, LLM_TOKEN_COUNT_TOTAL):
|
|
62
|
+
try:
|
|
63
|
+
value = int(value)
|
|
64
|
+
except BaseException:
|
|
65
|
+
pass
|
|
66
|
+
yield key, value
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def decode_otlp_span(otlp_span: otlp.Span) -> Span:
|
|
59
70
|
trace_id = cast(TraceID, _decode_identifier(otlp_span.trace_id))
|
|
60
71
|
span_id = cast(SpanID, _decode_identifier(otlp_span.span_id))
|
|
61
72
|
parent_id = _decode_identifier(otlp_span.parent_span_id)
|
|
62
73
|
|
|
63
74
|
start_time = _decode_unix_nano(otlp_span.start_time_unix_nano)
|
|
64
|
-
end_time = (
|
|
65
|
-
_decode_unix_nano(otlp_span.end_time_unix_nano) if otlp_span.end_time_unix_nano else None
|
|
66
|
-
)
|
|
75
|
+
end_time = _decode_unix_nano(otlp_span.end_time_unix_nano)
|
|
67
76
|
|
|
68
|
-
attributes =
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if mime_type in attributes:
|
|
73
|
-
attributes[mime_type] = MimeType(attributes[mime_type])
|
|
77
|
+
attributes = unflatten(
|
|
78
|
+
load_json_strings(coerce_otlp_span_attributes(_decode_key_values(otlp_span.attributes)))
|
|
79
|
+
)
|
|
80
|
+
span_kind = SpanKind(get_attribute_value(attributes, OPENINFERENCE_SPAN_KIND))
|
|
74
81
|
|
|
75
82
|
status_code, status_message = _decode_status(otlp_span.status)
|
|
76
83
|
events = [_decode_event(event) for event in otlp_span.events]
|
|
77
84
|
|
|
85
|
+
if (input_value := get_attribute_value(attributes, INPUT_VALUE)) and not isinstance(
|
|
86
|
+
input_value, str
|
|
87
|
+
):
|
|
88
|
+
attributes["input"]["value"] = json.dumps(input_value)
|
|
89
|
+
attributes["input"]["mime_type"] = OpenInferenceMimeTypeValues.JSON.value
|
|
90
|
+
|
|
78
91
|
return Span(
|
|
79
92
|
name=otlp_span.name,
|
|
80
93
|
context=SpanContext(
|
|
@@ -127,7 +140,7 @@ def _decode_unix_nano(time_unix_nano: int) -> datetime:
|
|
|
127
140
|
|
|
128
141
|
def _decode_key_values(
|
|
129
142
|
key_values: Iterable[KeyValue],
|
|
130
|
-
) -> Iterator[
|
|
143
|
+
) -> Iterator[tuple[str, Any]]:
|
|
131
144
|
return ((kv.key, _decode_value(kv.value)) for kv in key_values)
|
|
132
145
|
|
|
133
146
|
|
|
@@ -152,28 +165,6 @@ def _decode_value(any_value: AnyValue) -> Any:
|
|
|
152
165
|
assert_never(which)
|
|
153
166
|
|
|
154
167
|
|
|
155
|
-
_JSON_STRING_ATTRIBUTES = (
|
|
156
|
-
DOCUMENT_METADATA,
|
|
157
|
-
LLM_PROMPT_TEMPLATE_VARIABLES,
|
|
158
|
-
METADATA,
|
|
159
|
-
TOOL_PARAMETERS,
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
def _load_json_strings(key_values: Iterable[Tuple[str, Any]]) -> Iterator[Tuple[str, Any]]:
|
|
164
|
-
for key, value in key_values:
|
|
165
|
-
if key.endswith(_JSON_STRING_ATTRIBUTES):
|
|
166
|
-
try:
|
|
167
|
-
dict_value = json.loads(value)
|
|
168
|
-
except Exception:
|
|
169
|
-
yield key, value
|
|
170
|
-
else:
|
|
171
|
-
if dict_value:
|
|
172
|
-
yield key, dict_value
|
|
173
|
-
else:
|
|
174
|
-
yield key, value
|
|
175
|
-
|
|
176
|
-
|
|
177
168
|
StatusMessage: TypeAlias = str
|
|
178
169
|
|
|
179
170
|
_STATUS_DECODING = MappingProxyType(
|
|
@@ -185,129 +176,15 @@ _STATUS_DECODING = MappingProxyType(
|
|
|
185
176
|
)
|
|
186
177
|
|
|
187
178
|
|
|
188
|
-
def _decode_status(otlp_status: otlp.Status) ->
|
|
179
|
+
def _decode_status(otlp_status: otlp.Status) -> tuple[SpanStatusCode, StatusMessage]:
|
|
189
180
|
status_code = _STATUS_DECODING.get(otlp_status.code, SpanStatusCode.UNSET)
|
|
190
181
|
return status_code, otlp_status.message
|
|
191
182
|
|
|
192
183
|
|
|
193
|
-
_SEMANTIC_CONVENTIONS: List[str] = sorted(
|
|
194
|
-
(
|
|
195
|
-
getattr(klass, attr)
|
|
196
|
-
for name in dir(trace)
|
|
197
|
-
if name.endswith("Attributes") and inspect.isclass(klass := getattr(trace, name))
|
|
198
|
-
for attr in dir(klass)
|
|
199
|
-
if attr.isupper()
|
|
200
|
-
),
|
|
201
|
-
reverse=True,
|
|
202
|
-
) # sorted so the longer strings go first
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
def _semantic_convention_prefix_partition(key: str, separator: str = ".") -> Tuple[str, str, str]:
|
|
206
|
-
"""Return the longest prefix of `key` that is a semantic convention, and the remaining suffix
|
|
207
|
-
separated by `.`. For example, if `key` is "retrieval.documents.2.document.score", return
|
|
208
|
-
("retrieval.documents", ".", "2.document.score"). The return signature is based on Python's
|
|
209
|
-
`.partition` method for strings.
|
|
210
|
-
"""
|
|
211
|
-
for prefix in _SEMANTIC_CONVENTIONS:
|
|
212
|
-
if key == prefix:
|
|
213
|
-
return key, "", ""
|
|
214
|
-
if key.startswith(prefix) and key[len(prefix) :].startswith(separator):
|
|
215
|
-
return prefix, separator, key[len(prefix) + len(separator) :]
|
|
216
|
-
return "", "", ""
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
class _Trie(DefaultDict[Union[str, int], "_Trie"]):
|
|
220
|
-
"""Prefix Tree with special handling for indices (i.e. all-digit keys)."""
|
|
221
|
-
|
|
222
|
-
def __init__(self) -> None:
|
|
223
|
-
super().__init__(_Trie)
|
|
224
|
-
self.value: Any = None
|
|
225
|
-
self.indices: Set[int] = set()
|
|
226
|
-
self.branches: Set[Union[str, int]] = set()
|
|
227
|
-
|
|
228
|
-
def set_value(self, value: Any) -> None:
|
|
229
|
-
self.value = value
|
|
230
|
-
# value and indices must not coexist
|
|
231
|
-
self.branches.update(self.indices)
|
|
232
|
-
self.indices.clear()
|
|
233
|
-
|
|
234
|
-
def add_index(self, index: int) -> "_Trie":
|
|
235
|
-
if self.value is not None:
|
|
236
|
-
self.branches.add(index)
|
|
237
|
-
elif index not in self.branches:
|
|
238
|
-
self.indices.add(index)
|
|
239
|
-
return self[index]
|
|
240
|
-
|
|
241
|
-
def add_branch(self, branch: Union[str, int]) -> "_Trie":
|
|
242
|
-
if branch in self.indices:
|
|
243
|
-
self.indices.discard(cast(int, branch))
|
|
244
|
-
self.branches.add(branch)
|
|
245
|
-
return self[branch]
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
# FIXME: Ideally we should not need something so complicated as a Trie, but it's useful here
|
|
249
|
-
# for backward compatibility reasons regarding some deeply nested objects such as TOOL_PARAMETERS.
|
|
250
|
-
# In the future, we should `json_dumps` them and not let things get too deeply nested.
|
|
251
|
-
def _build_trie(
|
|
252
|
-
key_value_pairs: Iterable[Tuple[str, Any]],
|
|
253
|
-
separator: str = ".",
|
|
254
|
-
) -> _Trie:
|
|
255
|
-
"""Build a Trie (a.k.a. prefix tree) from `key_value_pairs`, by partitioning the keys by
|
|
256
|
-
separator. Each partition is a branch in the Trie. Special handling is done for partitions
|
|
257
|
-
that are all digits, e.g. "0", "12", etc., which are converted to integers and collected
|
|
258
|
-
as indices.
|
|
259
|
-
"""
|
|
260
|
-
trie = _Trie()
|
|
261
|
-
for key, value in key_value_pairs:
|
|
262
|
-
if value is None:
|
|
263
|
-
continue
|
|
264
|
-
t = trie
|
|
265
|
-
while True:
|
|
266
|
-
prefix, _, suffix = _semantic_convention_prefix_partition(key, separator)
|
|
267
|
-
if prefix:
|
|
268
|
-
t = t.add_branch(prefix)
|
|
269
|
-
else:
|
|
270
|
-
prefix, _, suffix = key.partition(separator)
|
|
271
|
-
if prefix.isdigit():
|
|
272
|
-
index = int(prefix)
|
|
273
|
-
t = t.add_index(index) if suffix else t.add_branch(index)
|
|
274
|
-
else:
|
|
275
|
-
t = t.add_branch(prefix)
|
|
276
|
-
if not suffix:
|
|
277
|
-
break
|
|
278
|
-
key = suffix
|
|
279
|
-
t.set_value(value)
|
|
280
|
-
return trie
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
def _walk(trie: _Trie, prefix: str = "") -> Iterator[Tuple[str, Any]]:
|
|
284
|
-
if trie.value is not None:
|
|
285
|
-
yield prefix, trie.value
|
|
286
|
-
elif prefix and trie.indices:
|
|
287
|
-
yield prefix, [dict(_walk(trie[index])) for index in sorted(trie.indices)]
|
|
288
|
-
elif trie.indices:
|
|
289
|
-
for index in trie.indices:
|
|
290
|
-
yield from _walk(trie[index], prefix=f"{index}")
|
|
291
|
-
elif prefix:
|
|
292
|
-
yield prefix, dict(_walk(trie))
|
|
293
|
-
return
|
|
294
|
-
for branch in trie.branches:
|
|
295
|
-
new_prefix = f"{prefix}.{branch}" if prefix else f"{branch}"
|
|
296
|
-
yield from _walk(trie[branch], new_prefix)
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
def _unflatten(
|
|
300
|
-
key_value_pairs: Iterable[Tuple[str, Any]],
|
|
301
|
-
separator: str = ".",
|
|
302
|
-
) -> Iterator[Tuple[str, Any]]:
|
|
303
|
-
trie = _build_trie(key_value_pairs, separator)
|
|
304
|
-
yield from _walk(trie)
|
|
305
|
-
|
|
306
|
-
|
|
307
184
|
_BILLION = 1_000_000_000 # for converting seconds to nanoseconds
|
|
308
185
|
|
|
309
186
|
|
|
310
|
-
def
|
|
187
|
+
def encode_span_to_otlp(span: Span) -> otlp.Span:
|
|
311
188
|
trace_id: bytes = _encode_identifier(span.context.trace_id)
|
|
312
189
|
span_id: bytes = _encode_identifier(span.context.span_id)
|
|
313
190
|
parent_span_id: bytes = _encode_identifier(span.parent_id)
|
|
@@ -316,11 +193,7 @@ def encode(span: Span) -> otlp.Span:
|
|
|
316
193
|
start_time_unix_nano: int = int(span.start_time.timestamp() * _BILLION)
|
|
317
194
|
end_time_unix_nano: int = int(span.end_time.timestamp() * _BILLION) if span.end_time else 0
|
|
318
195
|
|
|
319
|
-
attributes:
|
|
320
|
-
|
|
321
|
-
for mime_type in (INPUT_MIME_TYPE, OUTPUT_MIME_TYPE):
|
|
322
|
-
if mime_type in attributes:
|
|
323
|
-
attributes[mime_type] = attributes[mime_type].value
|
|
196
|
+
attributes: dict[str, Any] = dict(span.attributes)
|
|
324
197
|
|
|
325
198
|
for key, value in span.attributes.items():
|
|
326
199
|
if value is None:
|
|
@@ -328,19 +201,34 @@ def encode(span: Span) -> otlp.Span:
|
|
|
328
201
|
attributes.pop(key, None)
|
|
329
202
|
elif isinstance(value, Mapping):
|
|
330
203
|
attributes.pop(key, None)
|
|
331
|
-
if key.endswith(
|
|
332
|
-
attributes[key] = json.dumps(value)
|
|
204
|
+
if key.endswith(JSON_STRING_ATTRIBUTES):
|
|
205
|
+
attributes[key] = json.dumps(jsonify(value))
|
|
333
206
|
else:
|
|
334
|
-
attributes.update(
|
|
207
|
+
attributes.update(
|
|
208
|
+
flatten(
|
|
209
|
+
value,
|
|
210
|
+
prefix=key,
|
|
211
|
+
recurse_on_sequence=True,
|
|
212
|
+
json_string_attributes=JSON_STRING_ATTRIBUTES,
|
|
213
|
+
)
|
|
214
|
+
)
|
|
335
215
|
elif (
|
|
336
216
|
not isinstance(value, str)
|
|
337
217
|
and (isinstance(value, Sequence) or isinstance(value, np.ndarray))
|
|
338
|
-
and
|
|
218
|
+
and has_mapping(value)
|
|
339
219
|
):
|
|
340
220
|
attributes.pop(key, None)
|
|
341
|
-
attributes.update(
|
|
342
|
-
|
|
343
|
-
|
|
221
|
+
attributes.update(
|
|
222
|
+
flatten(
|
|
223
|
+
value,
|
|
224
|
+
prefix=key,
|
|
225
|
+
recurse_on_sequence=True,
|
|
226
|
+
json_string_attributes=JSON_STRING_ATTRIBUTES,
|
|
227
|
+
)
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
if OPENINFERENCE_SPAN_KIND not in attributes:
|
|
231
|
+
attributes[OPENINFERENCE_SPAN_KIND] = span.span_kind.value
|
|
344
232
|
|
|
345
233
|
status = _encode_status(span.status_code, span.status_message)
|
|
346
234
|
events = map(_encode_event, span.events)
|
|
@@ -381,42 +269,6 @@ def _encode_identifier(identifier: Optional[str]) -> bytes:
|
|
|
381
269
|
return unhexlify(identifier)
|
|
382
270
|
|
|
383
271
|
|
|
384
|
-
def _has_mapping(sequence: Sequence[Any]) -> bool:
|
|
385
|
-
for item in sequence:
|
|
386
|
-
if isinstance(item, Mapping):
|
|
387
|
-
return True
|
|
388
|
-
return False
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
def _flatten_mapping(
|
|
392
|
-
mapping: Mapping[str, Any],
|
|
393
|
-
prefix: str,
|
|
394
|
-
) -> Iterator[Tuple[str, Any]]:
|
|
395
|
-
for key, value in mapping.items():
|
|
396
|
-
prefixed_key = f"{prefix}.{key}"
|
|
397
|
-
if isinstance(value, Mapping):
|
|
398
|
-
if key.endswith(_JSON_STRING_ATTRIBUTES):
|
|
399
|
-
yield prefixed_key, json.dumps(value)
|
|
400
|
-
else:
|
|
401
|
-
yield from _flatten_mapping(value, prefixed_key)
|
|
402
|
-
elif isinstance(value, Sequence):
|
|
403
|
-
yield from _flatten_sequence(value, prefixed_key)
|
|
404
|
-
elif value is not None:
|
|
405
|
-
yield prefixed_key, value
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
def _flatten_sequence(
|
|
409
|
-
sequence: Sequence[Any],
|
|
410
|
-
prefix: str,
|
|
411
|
-
) -> Iterator[Tuple[str, Any]]:
|
|
412
|
-
if isinstance(sequence, str) or not _has_mapping(sequence):
|
|
413
|
-
yield prefix, sequence
|
|
414
|
-
for idx, obj in enumerate(sequence):
|
|
415
|
-
if not isinstance(obj, Mapping):
|
|
416
|
-
continue
|
|
417
|
-
yield from _flatten_mapping(obj, f"{prefix}.{idx}")
|
|
418
|
-
|
|
419
|
-
|
|
420
272
|
def _encode_event(event: SpanEvent) -> otlp.Span.Event:
|
|
421
273
|
return otlp.Span.Event(
|
|
422
274
|
name=event.name,
|
|
@@ -451,6 +303,6 @@ def _encode_value(value: AttributeValue) -> AnyValue:
|
|
|
451
303
|
|
|
452
304
|
|
|
453
305
|
__all__ = [
|
|
454
|
-
"
|
|
455
|
-
"
|
|
306
|
+
"encode_span_to_otlp",
|
|
307
|
+
"decode_otlp_span",
|
|
456
308
|
]
|
phoenix/trace/projects.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import types
|
|
2
|
-
from
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from typing import Any, Optional
|
|
3
4
|
|
|
4
5
|
from openinference.semconv.resource import ResourceAttributes
|
|
5
6
|
from opentelemetry.sdk import trace
|
|
@@ -58,7 +59,7 @@ class using_project:
|
|
|
58
59
|
|
|
59
60
|
def __exit__(
|
|
60
61
|
self,
|
|
61
|
-
exc_type: Optional[
|
|
62
|
+
exc_type: Optional[type[BaseException]],
|
|
62
63
|
exc_value: Optional[BaseException],
|
|
63
64
|
traceback: Optional[types.TracebackType],
|
|
64
65
|
) -> None:
|
phoenix/trace/schemas.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
from enum import Enum
|
|
4
|
-
from typing import Any,
|
|
4
|
+
from typing import Any, Mapping, NamedTuple, Optional
|
|
5
5
|
from uuid import UUID
|
|
6
6
|
|
|
7
7
|
EXCEPTION_TYPE = "exception.type"
|
|
@@ -29,8 +29,6 @@ class SpanKind(Enum):
|
|
|
29
29
|
"""
|
|
30
30
|
SpanKind is loosely inspired by OpenTelemetry's SpanKind
|
|
31
31
|
It captures the type of work that a Span encapsulates.
|
|
32
|
-
|
|
33
|
-
NB: this is actively under construction
|
|
34
32
|
"""
|
|
35
33
|
|
|
36
34
|
TOOL = "TOOL"
|
|
@@ -40,6 +38,8 @@ class SpanKind(Enum):
|
|
|
40
38
|
EMBEDDING = "EMBEDDING"
|
|
41
39
|
AGENT = "AGENT"
|
|
42
40
|
RERANKER = "RERANKER"
|
|
41
|
+
EVALUATOR = "EVALUATOR"
|
|
42
|
+
GUARDRAIL = "GUARDRAIL"
|
|
43
43
|
UNKNOWN = "UNKNOWN"
|
|
44
44
|
|
|
45
45
|
def __str__(self) -> str:
|
|
@@ -47,16 +47,14 @@ class SpanKind(Enum):
|
|
|
47
47
|
|
|
48
48
|
@classmethod
|
|
49
49
|
def _missing_(cls, v: Any) -> Optional["SpanKind"]:
|
|
50
|
-
if v and isinstance(v, str) and not v.isupper():
|
|
50
|
+
if v and isinstance(v, str) and v.isascii() and not v.isupper():
|
|
51
51
|
return cls(v.upper())
|
|
52
|
-
return
|
|
52
|
+
return cls.UNKNOWN
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
TraceID = str
|
|
56
56
|
SpanID = str
|
|
57
|
-
|
|
58
|
-
AttributeValue = Union[AttributePrimitiveValue, List[AttributePrimitiveValue]]
|
|
59
|
-
SpanAttributes = Dict[str, AttributeValue]
|
|
57
|
+
SpanAttributes = Mapping[str, Any]
|
|
60
58
|
|
|
61
59
|
|
|
62
60
|
@dataclass(frozen=True)
|
|
@@ -73,7 +71,7 @@ class SpanConversationAttributes:
|
|
|
73
71
|
|
|
74
72
|
|
|
75
73
|
@dataclass(frozen=True)
|
|
76
|
-
class SpanEvent
|
|
74
|
+
class SpanEvent:
|
|
77
75
|
"""
|
|
78
76
|
A Span Event can be thought of as a structured log message (or annotation)
|
|
79
77
|
on a Span, typically used to denote a meaningful, singular point in time
|
|
@@ -142,7 +140,7 @@ class Span:
|
|
|
142
140
|
"If the parent_id is None, this is the root span"
|
|
143
141
|
parent_id: Optional[SpanID]
|
|
144
142
|
start_time: datetime
|
|
145
|
-
end_time:
|
|
143
|
+
end_time: datetime
|
|
146
144
|
status_code: SpanStatusCode
|
|
147
145
|
status_message: str
|
|
148
146
|
"""
|
|
@@ -171,7 +169,7 @@ class Span:
|
|
|
171
169
|
OpenTelemetry Inspiration:
|
|
172
170
|
https://opentelemetry.io/docs/concepts/signals/traces/#span-events
|
|
173
171
|
"""
|
|
174
|
-
events:
|
|
172
|
+
events: list[SpanEvent]
|
|
175
173
|
|
|
176
174
|
"""
|
|
177
175
|
An extension of the OpenTelemetry Span interface to include the
|
|
@@ -189,6 +187,22 @@ class MimeType(Enum):
|
|
|
189
187
|
return None if v else cls.TEXT
|
|
190
188
|
|
|
191
189
|
|
|
190
|
+
@dataclass(frozen=True)
|
|
191
|
+
class SpanIOValue:
|
|
192
|
+
value: str
|
|
193
|
+
mime_type: MimeType = MimeType.TEXT
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@dataclass(frozen=True)
|
|
197
|
+
class TokenUsage:
|
|
198
|
+
prompt: int = 0
|
|
199
|
+
completion: int = 0
|
|
200
|
+
|
|
201
|
+
def __post_init__(self) -> None:
|
|
202
|
+
assert self.prompt >= 0, "prompt must be non-negative"
|
|
203
|
+
assert self.completion >= 0, "completion must be non-negative"
|
|
204
|
+
|
|
205
|
+
|
|
192
206
|
ATTRIBUTE_PREFIX = "attributes."
|
|
193
207
|
CONTEXT_PREFIX = "context."
|
|
194
208
|
COMPUTED_PREFIX = "__computed__."
|
|
@@ -202,3 +216,11 @@ class ComputedAttributes(Enum):
|
|
|
202
216
|
CUMULATIVE_LLM_TOKEN_COUNT_COMPLETION = "cumulative_token_count.completion"
|
|
203
217
|
ERROR_COUNT = "error_count"
|
|
204
218
|
CUMULATIVE_ERROR_COUNT = "cumulative_error_count"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class ComputedValues(NamedTuple):
|
|
222
|
+
latency_ms: float
|
|
223
|
+
cumulative_error_count: int
|
|
224
|
+
cumulative_llm_token_count_prompt: int
|
|
225
|
+
cumulative_llm_token_count_completion: int
|
|
226
|
+
cumulative_llm_token_count_total: int
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from abc import ABC
|
|
3
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
3
4
|
from dataclasses import dataclass, field
|
|
4
5
|
from itertools import product
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from types import MappingProxyType
|
|
7
|
-
from typing import Any,
|
|
8
|
+
from typing import Any, Optional, Union
|
|
8
9
|
from uuid import UUID, uuid4
|
|
9
10
|
|
|
10
11
|
import pandas as pd
|
|
11
12
|
from pandas.api.types import is_integer_dtype, is_numeric_dtype, is_string_dtype
|
|
12
13
|
from pyarrow import RecordBatchStreamReader, Schema, Table, parquet
|
|
13
14
|
|
|
14
|
-
from phoenix.config import
|
|
15
|
+
from phoenix.config import TRACE_DATASETS_DIR
|
|
16
|
+
from phoenix.exceptions import PhoenixEvaluationNameIsMissing
|
|
15
17
|
from phoenix.trace.errors import InvalidParquetMetadataError
|
|
16
18
|
|
|
17
19
|
EVAL_NAME_COLUMN_PREFIX = "eval."
|
|
@@ -19,11 +21,11 @@ EVAL_PARQUET_FILE_NAME = "evaluations-{id}.parquet"
|
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
class NeedsNamedIndex(ABC):
|
|
22
|
-
index_names: Mapping[
|
|
23
|
-
all_valid_index_name_sorted_combos:
|
|
24
|
+
index_names: Mapping[tuple[str, ...], Callable[[Any], bool]]
|
|
25
|
+
all_valid_index_name_sorted_combos: set[tuple[str, ...]]
|
|
24
26
|
|
|
25
27
|
@classmethod
|
|
26
|
-
def preferred_names(cls) ->
|
|
28
|
+
def preferred_names(cls) -> list[str]:
|
|
27
29
|
return [choices[0] for choices in cls.index_names.keys()]
|
|
28
30
|
|
|
29
31
|
@classmethod
|
|
@@ -42,7 +44,7 @@ class NeedsNamedIndex(ABC):
|
|
|
42
44
|
)
|
|
43
45
|
|
|
44
46
|
@classmethod
|
|
45
|
-
def find_valid_index_names(cls, dtypes: "pd.Series[Any]") -> Optional[
|
|
47
|
+
def find_valid_index_names(cls, dtypes: "pd.Series[Any]") -> Optional[list[str]]:
|
|
46
48
|
valid_names = []
|
|
47
49
|
for names, check_type in cls.index_names.items():
|
|
48
50
|
for name in names:
|
|
@@ -64,14 +66,15 @@ class NeedsResultColumns(ABC):
|
|
|
64
66
|
)
|
|
65
67
|
|
|
66
68
|
@classmethod
|
|
67
|
-
def is_valid_result_columns(cls,
|
|
69
|
+
def is_valid_result_columns(cls, df: pd.DataFrame) -> bool:
|
|
70
|
+
dtypes = df.dtypes
|
|
68
71
|
names = cls.result_column_names.keys()
|
|
69
72
|
intersection = dtypes.index.intersection(names) # type: ignore
|
|
70
73
|
if not len(intersection):
|
|
71
74
|
return False
|
|
72
75
|
for name in intersection:
|
|
73
76
|
check_type = cls.result_column_names[name]
|
|
74
|
-
if not check_type(dtypes[name]):
|
|
77
|
+
if not check_type(dtypes[name]) and not df.loc[:, name].isna().all():
|
|
75
78
|
return False
|
|
76
79
|
return True
|
|
77
80
|
|
|
@@ -91,7 +94,7 @@ class Evaluations(NeedsNamedIndex, NeedsResultColumns, ABC):
|
|
|
91
94
|
f"dataframe=<rows: {len(self.dataframe)!r}>)"
|
|
92
95
|
)
|
|
93
96
|
|
|
94
|
-
def __dir__(self) ->
|
|
97
|
+
def __dir__(self) -> list[str]:
|
|
95
98
|
return ["get_dataframe"]
|
|
96
99
|
|
|
97
100
|
def get_dataframe(self, prefix_columns_with_name: bool = True) -> pd.DataFrame:
|
|
@@ -136,7 +139,7 @@ class Evaluations(NeedsNamedIndex, NeedsResultColumns, ABC):
|
|
|
136
139
|
)
|
|
137
140
|
|
|
138
141
|
# Validate that the dataframe contains result columns of appropriate types.
|
|
139
|
-
if not self.is_valid_result_columns(dataframe
|
|
142
|
+
if not self.is_valid_result_columns(dataframe):
|
|
140
143
|
raise ValueError(
|
|
141
144
|
f"The dataframe must contain one of these columns with appropriate "
|
|
142
145
|
f"value types: {self.result_column_names.keys()} "
|
|
@@ -152,7 +155,7 @@ class Evaluations(NeedsNamedIndex, NeedsResultColumns, ABC):
|
|
|
152
155
|
|
|
153
156
|
def __init_subclass__(
|
|
154
157
|
cls,
|
|
155
|
-
index_names: Mapping[
|
|
158
|
+
index_names: Mapping[tuple[str, ...], Callable[[Any], bool]],
|
|
156
159
|
**kwargs: Any,
|
|
157
160
|
) -> None:
|
|
158
161
|
super().__init_subclass__(**kwargs)
|
|
@@ -200,7 +203,7 @@ class Evaluations(NeedsNamedIndex, NeedsResultColumns, ABC):
|
|
|
200
203
|
UUID: The ID of the evaluations, which can be used as a key to load
|
|
201
204
|
the evaluations from disk using `load`.
|
|
202
205
|
"""
|
|
203
|
-
directory = Path(directory) if directory else
|
|
206
|
+
directory = Path(directory) if directory else TRACE_DATASETS_DIR
|
|
204
207
|
path = directory / EVAL_PARQUET_FILE_NAME.format(id=self.id)
|
|
205
208
|
table = self.to_pyarrow_table()
|
|
206
209
|
parquet.write_table(table, path)
|
|
@@ -228,7 +231,7 @@ class Evaluations(NeedsNamedIndex, NeedsResultColumns, ABC):
|
|
|
228
231
|
"""
|
|
229
232
|
if not isinstance(id, UUID):
|
|
230
233
|
id = UUID(id)
|
|
231
|
-
path = Path(directory or
|
|
234
|
+
path = Path(directory or TRACE_DATASETS_DIR) / EVAL_PARQUET_FILE_NAME.format(id=id)
|
|
232
235
|
schema = parquet.read_schema(path)
|
|
233
236
|
eval_id, eval_name, evaluations_cls = _parse_schema_metadata(schema)
|
|
234
237
|
if id != eval_id:
|
|
@@ -326,7 +329,7 @@ class TraceEvaluations(
|
|
|
326
329
|
): ...
|
|
327
330
|
|
|
328
331
|
|
|
329
|
-
def _parse_schema_metadata(schema: Schema) ->
|
|
332
|
+
def _parse_schema_metadata(schema: Schema) -> tuple[UUID, str, type[Evaluations]]:
|
|
330
333
|
"""
|
|
331
334
|
Validates and parses the pyarrow schema metadata.
|
|
332
335
|
"""
|
|
@@ -335,8 +338,10 @@ def _parse_schema_metadata(schema: Schema) -> Tuple[UUID, str, Type[Evaluations]
|
|
|
335
338
|
arize_metadata = json.loads(metadata[b"arize"])
|
|
336
339
|
eval_classes = {subclass.__name__: subclass for subclass in Evaluations.__subclasses__()}
|
|
337
340
|
eval_id = UUID(arize_metadata["eval_id"])
|
|
338
|
-
if not isinstance((eval_name := arize_metadata["eval_name"]), str):
|
|
339
|
-
raise
|
|
341
|
+
if not isinstance((eval_name := arize_metadata["eval_name"]), str) or not eval_name.strip():
|
|
342
|
+
raise PhoenixEvaluationNameIsMissing(
|
|
343
|
+
'Arize metadata must contain a non-empty string value for key "eval_name"'
|
|
344
|
+
)
|
|
340
345
|
evaluations_cls = eval_classes[arize_metadata["eval_type"]]
|
|
341
346
|
return eval_id, eval_name, evaluations_cls
|
|
342
347
|
except Exception as err:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from datetime import datetime
|
|
3
|
-
from typing import Any,
|
|
3
|
+
from typing import Any, Optional
|
|
4
4
|
|
|
5
5
|
from openinference.semconv.trace import SpanAttributes
|
|
6
6
|
|
|
@@ -22,7 +22,7 @@ INPUT_MIME_TYPE = SpanAttributes.INPUT_MIME_TYPE
|
|
|
22
22
|
OUTPUT_MIME_TYPE = SpanAttributes.OUTPUT_MIME_TYPE
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
def json_to_attributes(obj: Optional[
|
|
25
|
+
def json_to_attributes(obj: Optional[dict[str, Any]]) -> dict[str, Any]:
|
|
26
26
|
if obj is None:
|
|
27
27
|
return {}
|
|
28
28
|
if not isinstance(obj, dict):
|
|
@@ -34,7 +34,7 @@ def json_to_attributes(obj: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
|
|
34
34
|
return obj
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def json_to_span(data:
|
|
37
|
+
def json_to_span(data: dict[str, Any]) -> Any:
|
|
38
38
|
"""
|
|
39
39
|
A hook for json.loads to convert a dict to a Span object.
|
|
40
40
|
"""
|
|
@@ -80,7 +80,9 @@ def json_to_span(data: Dict[str, Any]) -> Any:
|
|
|
80
80
|
attributes=event.get("attributes") or {},
|
|
81
81
|
timestamp=datetime.fromisoformat(event["timestamp"]),
|
|
82
82
|
)
|
|
83
|
-
for event in
|
|
83
|
+
for event in (
|
|
84
|
+
json.loads(data["events"]) if isinstance(data["events"], str) else data["events"]
|
|
85
|
+
)
|
|
84
86
|
]
|
|
85
87
|
data["conversation"] = (
|
|
86
88
|
SpanConversationAttributes(**data["conversation"])
|