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
phoenix/settings.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
|
|
4
|
+
from phoenix.config import LoggingMode
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class _Settings:
|
|
9
|
+
"""Settings for Phoenix, lazily initialized."""
|
|
10
|
+
|
|
11
|
+
# By default, don't log migrations
|
|
12
|
+
log_migrations: bool = field(default=False)
|
|
13
|
+
# By default, Phoenix does not configure its loggers and acts as a library
|
|
14
|
+
logging_mode: LoggingMode = field(default=LoggingMode.DEFAULT)
|
|
15
|
+
# By default, log level is INFO
|
|
16
|
+
logging_level: int = field(default=logging.INFO)
|
|
17
|
+
# By default, log level is WARNING
|
|
18
|
+
db_logging_level: int = field(default=logging.WARNING)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Singleton instance of the settings
|
|
22
|
+
Settings = _Settings()
|
phoenix/trace/__init__.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
from typing import Iterator
|
|
1
|
+
from openinference.instrumentation import suppress_tracing
|
|
3
2
|
|
|
4
3
|
from .projects import using_project
|
|
5
4
|
from .span_evaluations import DocumentEvaluations, Evaluations, SpanEvaluations, TraceEvaluations
|
|
@@ -12,24 +11,5 @@ __all__ = [
|
|
|
12
11
|
"DocumentEvaluations",
|
|
13
12
|
"TraceEvaluations",
|
|
14
13
|
"using_project",
|
|
14
|
+
"suppress_tracing",
|
|
15
15
|
]
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@contextlib.contextmanager
|
|
19
|
-
def suppress_tracing() -> Iterator[None]:
|
|
20
|
-
"""
|
|
21
|
-
Context manager to pause OpenTelemetry instrumentation.
|
|
22
|
-
|
|
23
|
-
Examples:
|
|
24
|
-
with suppress_tracing():
|
|
25
|
-
# No tracing will occur within this block
|
|
26
|
-
...
|
|
27
|
-
"""
|
|
28
|
-
try:
|
|
29
|
-
from opentelemetry.context import _SUPPRESS_INSTRUMENTATION_KEY, attach, detach, set_value
|
|
30
|
-
except ImportError:
|
|
31
|
-
yield
|
|
32
|
-
return
|
|
33
|
-
token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
|
|
34
|
-
yield
|
|
35
|
-
detach(token)
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Span attribute keys have a special relationship with the `.` separator. When
|
|
3
|
+
a span attribute is ingested from protobuf, it's in the form of a key value
|
|
4
|
+
pair such as `("llm.token_count.completion", 123)`. What we need to do is to split
|
|
5
|
+
the key by the `.` separator and turn it into part of a nested dictionary such
|
|
6
|
+
as {"llm": {"token_count": {"completion": 123}}}. We also need to reverse this
|
|
7
|
+
process, which is to flatten the nested dictionary into a list of key value
|
|
8
|
+
pairs. This module provides functions to do both of these operations.
|
|
9
|
+
|
|
10
|
+
Note that digit keys are treated as indices of a nested array. For example,
|
|
11
|
+
the digits inside `("retrieval.documents.0.document.content", 'A')` and
|
|
12
|
+
`("retrieval.documents.1.document.content": 'B')` turn the sub-keys following
|
|
13
|
+
them into a nested list of dictionaries i.e.
|
|
14
|
+
{`retrieval: {"documents": [{"document": {"content": "A"}}, {"document":
|
|
15
|
+
{"content": "B"}}]}`.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import inspect
|
|
19
|
+
import json
|
|
20
|
+
from collections import defaultdict
|
|
21
|
+
from collections.abc import Iterable, Iterator, Mapping, Sequence
|
|
22
|
+
from typing import Any, Optional, Union, cast
|
|
23
|
+
|
|
24
|
+
import numpy as np
|
|
25
|
+
from openinference.semconv import trace
|
|
26
|
+
from openinference.semconv.trace import DocumentAttributes, SpanAttributes
|
|
27
|
+
from typing_extensions import assert_never
|
|
28
|
+
|
|
29
|
+
DOCUMENT_METADATA = DocumentAttributes.DOCUMENT_METADATA
|
|
30
|
+
LLM_PROMPT_TEMPLATE_VARIABLES = SpanAttributes.LLM_PROMPT_TEMPLATE_VARIABLES
|
|
31
|
+
METADATA = SpanAttributes.METADATA
|
|
32
|
+
TOOL_PARAMETERS = SpanAttributes.TOOL_PARAMETERS
|
|
33
|
+
|
|
34
|
+
# attributes interpreted as JSON strings during ingestion
|
|
35
|
+
JSON_STRING_ATTRIBUTES = (
|
|
36
|
+
DOCUMENT_METADATA,
|
|
37
|
+
LLM_PROMPT_TEMPLATE_VARIABLES,
|
|
38
|
+
METADATA,
|
|
39
|
+
TOOL_PARAMETERS,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
SEMANTIC_CONVENTIONS: list[str] = sorted(
|
|
43
|
+
# e.g. "input.value", "llm.token_count.total", etc.
|
|
44
|
+
(
|
|
45
|
+
cast(str, getattr(klass, attr))
|
|
46
|
+
for name in dir(trace)
|
|
47
|
+
if name.endswith("Attributes") and inspect.isclass(klass := getattr(trace, name))
|
|
48
|
+
for attr in dir(klass)
|
|
49
|
+
if attr.isupper()
|
|
50
|
+
),
|
|
51
|
+
key=len,
|
|
52
|
+
reverse=True,
|
|
53
|
+
) # sorted so the longer strings go first
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def unflatten(
|
|
57
|
+
key_value_pairs: Iterable[tuple[str, Any]],
|
|
58
|
+
*,
|
|
59
|
+
prefix_exclusions: Sequence[str] = (),
|
|
60
|
+
separator: str = ".",
|
|
61
|
+
) -> dict[str, Any]:
|
|
62
|
+
# `prefix_exclusions` is intended to contain the semantic conventions
|
|
63
|
+
trie = _build_trie(key_value_pairs, separator=separator, prefix_exclusions=prefix_exclusions)
|
|
64
|
+
return dict(_walk(trie, separator=separator))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def flatten(
|
|
68
|
+
obj: Union[Mapping[str, Any], Iterable[Any]],
|
|
69
|
+
*,
|
|
70
|
+
prefix: str = "",
|
|
71
|
+
separator: str = ".",
|
|
72
|
+
recurse_on_sequence: bool = False,
|
|
73
|
+
json_string_attributes: Optional[Sequence[str]] = None,
|
|
74
|
+
) -> Iterator[tuple[str, Any]]:
|
|
75
|
+
"""
|
|
76
|
+
Flatten a nested dictionary or a sequence of dictionaries into a list of
|
|
77
|
+
key value pairs. If `recurse_on_sequence` is True, then the function will
|
|
78
|
+
also recursively flatten nested sequences of dictionaries. If
|
|
79
|
+
`json_string_attributes` is provided, then the function will interpret the
|
|
80
|
+
attributes in the list as JSON strings and convert them into dictionaries.
|
|
81
|
+
The `prefix` argument is used to prefix the keys in the output list, but
|
|
82
|
+
it's mostly used internally to facilitate recursion.
|
|
83
|
+
"""
|
|
84
|
+
if isinstance(obj, Mapping):
|
|
85
|
+
yield from _flatten_mapping(
|
|
86
|
+
obj,
|
|
87
|
+
prefix=prefix,
|
|
88
|
+
recurse_on_sequence=recurse_on_sequence,
|
|
89
|
+
json_string_attributes=json_string_attributes,
|
|
90
|
+
separator=separator,
|
|
91
|
+
)
|
|
92
|
+
elif isinstance(obj, Iterable):
|
|
93
|
+
yield from _flatten_sequence(
|
|
94
|
+
obj,
|
|
95
|
+
prefix=prefix,
|
|
96
|
+
recurse_on_sequence=recurse_on_sequence,
|
|
97
|
+
json_string_attributes=json_string_attributes,
|
|
98
|
+
separator=separator,
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
assert_never(obj)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def has_mapping(sequence: Iterable[Any]) -> bool:
|
|
105
|
+
"""
|
|
106
|
+
Check if a sequence contains a dictionary. We don't flatten sequences that
|
|
107
|
+
only contain primitive types, such as strings, integers, etc. Conversely,
|
|
108
|
+
we'll only un-flatten digit sub-keys if it can be interpreted the index of
|
|
109
|
+
an array of dictionaries.
|
|
110
|
+
"""
|
|
111
|
+
for item in sequence:
|
|
112
|
+
if isinstance(item, Mapping):
|
|
113
|
+
return True
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def get_attribute_value(
|
|
118
|
+
attributes: Optional[Mapping[str, Any]],
|
|
119
|
+
key: str,
|
|
120
|
+
separator: str = ".",
|
|
121
|
+
) -> Optional[Any]:
|
|
122
|
+
"""
|
|
123
|
+
Get the value of a nested attribute from a dictionary. The `key` is a
|
|
124
|
+
string that represents the path to the attribute, where each level is
|
|
125
|
+
separated by the `separator`. For example, if the dictionary is
|
|
126
|
+
`{"a": {"b": {"c": 1}}}` and the key is `"a.b.c"`, then the function
|
|
127
|
+
will return `1`. If the key is `"a.b"`, then the function will return
|
|
128
|
+
`{"c": 1}`.
|
|
129
|
+
"""
|
|
130
|
+
if not (attributes and isinstance(attributes, dict)):
|
|
131
|
+
return None
|
|
132
|
+
sub_keys = key.split(separator)
|
|
133
|
+
for sub_key in sub_keys[:-1]:
|
|
134
|
+
attributes = attributes.get(sub_key)
|
|
135
|
+
if not (attributes and isinstance(attributes, dict)):
|
|
136
|
+
return None
|
|
137
|
+
return attributes.get(sub_keys[-1])
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def load_json_strings(key_values: Iterable[tuple[str, Any]]) -> Iterator[tuple[str, Any]]:
|
|
141
|
+
for key, value in key_values:
|
|
142
|
+
if key.endswith(JSON_STRING_ATTRIBUTES):
|
|
143
|
+
try:
|
|
144
|
+
dict_value = json.loads(value)
|
|
145
|
+
except Exception:
|
|
146
|
+
yield key, value
|
|
147
|
+
else:
|
|
148
|
+
if dict_value:
|
|
149
|
+
yield key, dict_value
|
|
150
|
+
else:
|
|
151
|
+
yield key, value
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _partition_with_prefix_exclusion(
|
|
155
|
+
key: str,
|
|
156
|
+
separator: str = ".",
|
|
157
|
+
prefix_exclusions: Sequence[str] = (),
|
|
158
|
+
) -> tuple[str, str, str]:
|
|
159
|
+
"""
|
|
160
|
+
Partition `key` by `separator`, but exclude prefixes in `prefix_exclusions`,
|
|
161
|
+
which is usually the list of semantic conventions. `prefix_exclusions` should
|
|
162
|
+
be sorted by length from the longest to the shortest
|
|
163
|
+
"""
|
|
164
|
+
for prefix in prefix_exclusions:
|
|
165
|
+
if key.startswith(prefix) and (
|
|
166
|
+
len(key) == len(prefix) or key[len(prefix) :].startswith(separator)
|
|
167
|
+
):
|
|
168
|
+
return prefix, separator, key[len(prefix) + len(separator) :]
|
|
169
|
+
return key.partition(separator)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class _Trie(defaultdict[Union[str, int], "_Trie"]):
|
|
173
|
+
"""
|
|
174
|
+
Prefix Tree with special handling for indices (i.e. all-digit keys). Indices
|
|
175
|
+
represent the position of an element in a nested list, while branches represent
|
|
176
|
+
the keys of a nested dictionary.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
def __init__(self) -> None:
|
|
180
|
+
super().__init__(_Trie)
|
|
181
|
+
self.value: Any = None
|
|
182
|
+
self.indices: set[int] = set()
|
|
183
|
+
self.branches: set[Union[str, int]] = set()
|
|
184
|
+
|
|
185
|
+
def set_value(self, value: Any) -> None:
|
|
186
|
+
self.value = value
|
|
187
|
+
# value and indices must not coexist
|
|
188
|
+
self.branches.update(self.indices)
|
|
189
|
+
self.indices.clear()
|
|
190
|
+
|
|
191
|
+
def add_index(self, index: int) -> "_Trie":
|
|
192
|
+
if self.value is not None:
|
|
193
|
+
self.branches.add(index)
|
|
194
|
+
elif index not in self.branches:
|
|
195
|
+
self.indices.add(index)
|
|
196
|
+
return self[index]
|
|
197
|
+
|
|
198
|
+
def add_branch(self, branch: Union[str, int]) -> "_Trie":
|
|
199
|
+
if branch in self.indices:
|
|
200
|
+
self.indices.discard(cast(int, branch))
|
|
201
|
+
self.branches.add(branch)
|
|
202
|
+
return self[branch]
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _build_trie(
|
|
206
|
+
key_value_pairs: Iterable[tuple[str, Any]],
|
|
207
|
+
*,
|
|
208
|
+
prefix_exclusions: Sequence[str] = (),
|
|
209
|
+
separator: str = ".",
|
|
210
|
+
) -> _Trie:
|
|
211
|
+
"""
|
|
212
|
+
Build a Trie (a.k.a. prefix tree) from `key_value_pairs`, by partitioning the keys by
|
|
213
|
+
separator. Each partition is a branch in the Trie. Special handling is done for partitions
|
|
214
|
+
that are all digits, e.g. "0", "12", etc., which are converted to integers and collected
|
|
215
|
+
as indices.
|
|
216
|
+
"""
|
|
217
|
+
trie = _Trie()
|
|
218
|
+
for key, value in key_value_pairs:
|
|
219
|
+
if value is None:
|
|
220
|
+
continue
|
|
221
|
+
t = trie
|
|
222
|
+
while True:
|
|
223
|
+
prefix, _, suffix = _partition_with_prefix_exclusion(
|
|
224
|
+
key,
|
|
225
|
+
separator,
|
|
226
|
+
prefix_exclusions,
|
|
227
|
+
)
|
|
228
|
+
if prefix.isdigit():
|
|
229
|
+
index = int(prefix)
|
|
230
|
+
t = t.add_index(index) if suffix else t.add_branch(index)
|
|
231
|
+
else:
|
|
232
|
+
t = t.add_branch(prefix)
|
|
233
|
+
if not suffix:
|
|
234
|
+
break
|
|
235
|
+
key = suffix
|
|
236
|
+
t.set_value(value)
|
|
237
|
+
return trie
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _walk(
|
|
241
|
+
trie: _Trie,
|
|
242
|
+
*,
|
|
243
|
+
prefix: str = "",
|
|
244
|
+
separator: str = ".",
|
|
245
|
+
) -> Iterator[tuple[str, Any]]:
|
|
246
|
+
"""
|
|
247
|
+
Walk the Trie and yield key value pairs. If the Trie node has a value, then
|
|
248
|
+
yield the prefix and the value. If the Trie node has indices, then yield the
|
|
249
|
+
prefix and a list of dictionaries. If the Trie node has branches, then yield
|
|
250
|
+
the prefix and a dictionary.
|
|
251
|
+
"""
|
|
252
|
+
if trie.value is not None:
|
|
253
|
+
yield prefix, trie.value
|
|
254
|
+
elif prefix and trie.indices:
|
|
255
|
+
yield (
|
|
256
|
+
prefix,
|
|
257
|
+
[dict(_walk(trie[index], separator=separator)) for index in sorted(trie.indices)],
|
|
258
|
+
)
|
|
259
|
+
elif trie.indices:
|
|
260
|
+
for index in trie.indices:
|
|
261
|
+
yield from _walk(trie[index], prefix=f"{index}", separator=separator)
|
|
262
|
+
elif prefix:
|
|
263
|
+
yield prefix, dict(_walk(trie, separator=separator))
|
|
264
|
+
return
|
|
265
|
+
for branch in trie.branches:
|
|
266
|
+
new_prefix = f"{prefix}{separator}{branch}" if prefix else f"{branch}"
|
|
267
|
+
yield from _walk(trie[branch], prefix=new_prefix, separator=separator)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _flatten_mapping(
|
|
271
|
+
mapping: Mapping[str, Any],
|
|
272
|
+
*,
|
|
273
|
+
prefix: str = "",
|
|
274
|
+
recurse_on_sequence: bool = False,
|
|
275
|
+
json_string_attributes: Optional[Sequence[str]] = None,
|
|
276
|
+
separator: str = ".",
|
|
277
|
+
) -> Iterator[tuple[str, Any]]:
|
|
278
|
+
"""
|
|
279
|
+
Flatten a nested dictionary into a list of key value pairs. If `recurse_on_sequence`
|
|
280
|
+
is True, then the function will also recursively flatten nested sequences of dictionaries.
|
|
281
|
+
If `json_string_attributes` is provided, then the function will interpret the attributes
|
|
282
|
+
in the list as JSON strings and convert them into dictionaries. The `prefix` argument is
|
|
283
|
+
used to prefix the keys in the output list, but it's mostly used internally to facilitate
|
|
284
|
+
recursion.
|
|
285
|
+
"""
|
|
286
|
+
for key, value in mapping.items():
|
|
287
|
+
prefixed_key = f"{prefix}{separator}{key}" if prefix else key
|
|
288
|
+
if isinstance(value, Mapping):
|
|
289
|
+
if json_string_attributes and prefixed_key.endswith(JSON_STRING_ATTRIBUTES):
|
|
290
|
+
yield prefixed_key, json.dumps(value)
|
|
291
|
+
else:
|
|
292
|
+
yield from _flatten_mapping(
|
|
293
|
+
value,
|
|
294
|
+
prefix=prefixed_key,
|
|
295
|
+
recurse_on_sequence=recurse_on_sequence,
|
|
296
|
+
json_string_attributes=json_string_attributes,
|
|
297
|
+
separator=separator,
|
|
298
|
+
)
|
|
299
|
+
elif (isinstance(value, Sequence) or isinstance(value, np.ndarray)) and recurse_on_sequence:
|
|
300
|
+
yield from _flatten_sequence(
|
|
301
|
+
value,
|
|
302
|
+
prefix=prefixed_key,
|
|
303
|
+
recurse_on_sequence=recurse_on_sequence,
|
|
304
|
+
json_string_attributes=json_string_attributes,
|
|
305
|
+
separator=separator,
|
|
306
|
+
)
|
|
307
|
+
elif value is not None:
|
|
308
|
+
yield prefixed_key, value
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _flatten_sequence(
|
|
312
|
+
sequence: Iterable[Any],
|
|
313
|
+
*,
|
|
314
|
+
prefix: str = "",
|
|
315
|
+
recurse_on_sequence: bool = False,
|
|
316
|
+
json_string_attributes: Optional[Sequence[str]] = None,
|
|
317
|
+
separator: str = ".",
|
|
318
|
+
) -> Iterator[tuple[str, Any]]:
|
|
319
|
+
"""
|
|
320
|
+
Flatten a sequence of dictionaries into a list of key value pairs. If `recurse_on_sequence`
|
|
321
|
+
is True, then the function will also recursively flatten nested sequences of dictionaries.
|
|
322
|
+
If `json_string_attributes` is provided, then the function will interpret the attributes
|
|
323
|
+
in the list as JSON strings and convert them into dictionaries. The `prefix` argument is
|
|
324
|
+
used to prefix the keys in the output list, but it's mostly used internally to facilitate
|
|
325
|
+
recursion.
|
|
326
|
+
"""
|
|
327
|
+
if isinstance(sequence, str) or not has_mapping(sequence):
|
|
328
|
+
yield prefix, sequence
|
|
329
|
+
for idx, obj in enumerate(sequence):
|
|
330
|
+
if not isinstance(obj, Mapping):
|
|
331
|
+
continue
|
|
332
|
+
yield from _flatten_mapping(
|
|
333
|
+
obj,
|
|
334
|
+
prefix=f"{prefix}{separator}{idx}" if prefix else f"{idx}",
|
|
335
|
+
recurse_on_sequence=recurse_on_sequence,
|
|
336
|
+
json_string_attributes=json_string_attributes,
|
|
337
|
+
separator=separator,
|
|
338
|
+
)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
This Phoenix module uses Python's `ast` module. The code snippets below provides a basic introduction to the `ast` module.
|
|
2
|
+
|
|
3
|
+
# Abstract Syntax Tree (AST)
|
|
4
|
+
|
|
5
|
+
The idea is that any Python expression can be parsed into an AST, and then transformed into a different one. The new AST can then be compiled back into a Python expression and evaluated at runtime.
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
import ast
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
# Constant
|
|
12
|
+
https://docs.python.org/3/library/ast.html#ast.Constant
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
print(ast.dump(ast.parse("None", mode="eval").body, indent=4))
|
|
16
|
+
print(ast.dump(ast.parse("1", mode="eval").body, indent=4))
|
|
17
|
+
print(ast.dump(ast.parse("'xyz'", mode="eval").body, indent=4))
|
|
18
|
+
```
|
|
19
|
+
### Output
|
|
20
|
+
```python
|
|
21
|
+
Constant(value=None)
|
|
22
|
+
Constant(value=1)
|
|
23
|
+
Constant(value='xyz')
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
# Name
|
|
27
|
+
https://docs.python.org/3/library/ast.html#ast.Name
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
print(ast.dump(ast.parse("xyz", mode="eval").body, indent=4))
|
|
31
|
+
```
|
|
32
|
+
### Output
|
|
33
|
+
```python
|
|
34
|
+
Name(id='xyz', ctx=Load())
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
# Compilation and Evaluation
|
|
38
|
+
https://docs.python.org/3/library/functions.html#compile
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
parsed = ast.parse("xyz", mode="eval")
|
|
42
|
+
compiled = compile(parsed, filename="", mode="eval")
|
|
43
|
+
|
|
44
|
+
eval(compiled, {"xyz": 42})
|
|
45
|
+
```
|
|
46
|
+
### Output
|
|
47
|
+
```python
|
|
48
|
+
42
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
# Attribute
|
|
52
|
+
https://docs.python.org/3/library/ast.html#ast.Attribute
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
print(ast.dump(ast.parse("llm.token_count.completion", mode="eval").body, indent=4))
|
|
56
|
+
```
|
|
57
|
+
### Output
|
|
58
|
+
```python
|
|
59
|
+
Attribute(
|
|
60
|
+
value=Attribute(
|
|
61
|
+
value=Name(id='llm', ctx=Load()),
|
|
62
|
+
attr='token_count',
|
|
63
|
+
ctx=Load()),
|
|
64
|
+
attr='completion',
|
|
65
|
+
ctx=Load())
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
# Subscript
|
|
69
|
+
https://docs.python.org/3/library/ast.html#ast.Subscript
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
print(ast.dump(ast.parse("attributes[['llm', 'token_count', 'completion']]", mode="eval").body, indent=4))
|
|
73
|
+
```
|
|
74
|
+
### Output
|
|
75
|
+
```python
|
|
76
|
+
Subscript(
|
|
77
|
+
value=Name(id='attributes', ctx=Load()),
|
|
78
|
+
slice=List(
|
|
79
|
+
elts=[
|
|
80
|
+
Constant(value='llm'),
|
|
81
|
+
Constant(value='token_count'),
|
|
82
|
+
Constant(value='completion')],
|
|
83
|
+
ctx=Load()),
|
|
84
|
+
ctx=Load())
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
# Translation of Attribute to Subscript
|
|
88
|
+
https://docs.python.org/3/library/ast.html#ast.NodeTransformer
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
class Translator(ast.NodeTransformer):
|
|
92
|
+
def visit_Attribute(self, node):
|
|
93
|
+
path = []
|
|
94
|
+
while isinstance(node, ast.Attribute):
|
|
95
|
+
path.append(node.attr)
|
|
96
|
+
node = node.value
|
|
97
|
+
if isinstance(node, ast.Name):
|
|
98
|
+
path.append(node.id)
|
|
99
|
+
break
|
|
100
|
+
return ast.Subscript(
|
|
101
|
+
value=ast.Name(id='attributes', ctx=ast.Load()),
|
|
102
|
+
slice=ast.List(
|
|
103
|
+
elts=[ast.Constant(value=p) for p in reversed(path)],
|
|
104
|
+
ctx=ast.Load(),
|
|
105
|
+
),
|
|
106
|
+
ctx=ast.Load(),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
parsed = ast.parse("llm.token_count.completion", mode="eval")
|
|
110
|
+
translated = Translator().visit(parsed)
|
|
111
|
+
print(ast.unparse(translated))
|
|
112
|
+
```
|
|
113
|
+
### Output
|
|
114
|
+
```python
|
|
115
|
+
attributes[['llm', 'token_count', 'completion']]
|
|
116
|
+
```
|