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
|
@@ -1,234 +1,483 @@
|
|
|
1
|
+
import operator
|
|
1
2
|
from datetime import datetime
|
|
2
|
-
from
|
|
3
|
-
from typing import List, Optional
|
|
3
|
+
from typing import Any, ClassVar, Optional
|
|
4
4
|
|
|
5
5
|
import strawberry
|
|
6
|
+
from aioitertools.itertools import islice
|
|
7
|
+
from openinference.semconv.trace import SpanAttributes
|
|
8
|
+
from sqlalchemy import and_, desc, distinct, func, or_, select
|
|
9
|
+
from sqlalchemy.orm import contains_eager
|
|
10
|
+
from sqlalchemy.sql.elements import ColumnElement
|
|
11
|
+
from sqlalchemy.sql.expression import tuple_
|
|
6
12
|
from strawberry import ID, UNSET
|
|
13
|
+
from strawberry.relay import Connection, Node, NodeID
|
|
14
|
+
from strawberry.types import Info
|
|
15
|
+
from typing_extensions import assert_never
|
|
7
16
|
|
|
8
|
-
from phoenix.
|
|
9
|
-
from phoenix.
|
|
10
|
-
from phoenix.server.api.
|
|
17
|
+
from phoenix.datetime_utils import right_open_time_range
|
|
18
|
+
from phoenix.db import models
|
|
19
|
+
from phoenix.server.api.context import Context
|
|
20
|
+
from phoenix.server.api.input_types.ProjectSessionSort import (
|
|
21
|
+
ProjectSessionColumn,
|
|
22
|
+
ProjectSessionSort,
|
|
23
|
+
)
|
|
24
|
+
from phoenix.server.api.input_types.SpanSort import SpanSort, SpanSortConfig
|
|
11
25
|
from phoenix.server.api.input_types.TimeRange import TimeRange
|
|
26
|
+
from phoenix.server.api.types.AnnotationSummary import AnnotationSummary
|
|
12
27
|
from phoenix.server.api.types.DocumentEvaluationSummary import DocumentEvaluationSummary
|
|
13
|
-
from phoenix.server.api.types.EvaluationSummary import EvaluationSummary
|
|
14
|
-
from phoenix.server.api.types.node import Node
|
|
15
28
|
from phoenix.server.api.types.pagination import (
|
|
16
|
-
Connection,
|
|
17
|
-
ConnectionArgs,
|
|
18
29
|
Cursor,
|
|
19
|
-
|
|
30
|
+
CursorSortColumn,
|
|
31
|
+
CursorString,
|
|
32
|
+
connection_from_cursors_and_nodes,
|
|
20
33
|
)
|
|
34
|
+
from phoenix.server.api.types.ProjectSession import ProjectSession, to_gql_project_session
|
|
35
|
+
from phoenix.server.api.types.SortDir import SortDir
|
|
21
36
|
from phoenix.server.api.types.Span import Span, to_gql_span
|
|
37
|
+
from phoenix.server.api.types.Trace import Trace, to_gql_trace
|
|
22
38
|
from phoenix.server.api.types.ValidationResult import ValidationResult
|
|
23
39
|
from phoenix.trace.dsl import SpanFilter
|
|
24
|
-
from phoenix.trace.schemas import SpanID, TraceID
|
|
25
40
|
|
|
26
41
|
|
|
27
42
|
@strawberry.type
|
|
28
43
|
class Project(Node):
|
|
44
|
+
_table: ClassVar[type[models.Base]] = models.Project
|
|
45
|
+
id_attr: NodeID[int]
|
|
29
46
|
name: str
|
|
30
|
-
|
|
47
|
+
gradient_start_color: str
|
|
48
|
+
gradient_end_color: str
|
|
31
49
|
|
|
32
50
|
@strawberry.field
|
|
33
|
-
def start_time(
|
|
34
|
-
|
|
51
|
+
async def start_time(
|
|
52
|
+
self,
|
|
53
|
+
info: Info[Context, None],
|
|
54
|
+
) -> Optional[datetime]:
|
|
55
|
+
start_time = await info.context.data_loaders.min_start_or_max_end_times.load(
|
|
56
|
+
(self.id_attr, "start"),
|
|
57
|
+
)
|
|
58
|
+
start_time, _ = right_open_time_range(start_time, None)
|
|
35
59
|
return start_time
|
|
36
60
|
|
|
37
61
|
@strawberry.field
|
|
38
|
-
def end_time(
|
|
39
|
-
|
|
62
|
+
async def end_time(
|
|
63
|
+
self,
|
|
64
|
+
info: Info[Context, None],
|
|
65
|
+
) -> Optional[datetime]:
|
|
66
|
+
end_time = await info.context.data_loaders.min_start_or_max_end_times.load(
|
|
67
|
+
(self.id_attr, "end"),
|
|
68
|
+
)
|
|
69
|
+
_, end_time = right_open_time_range(None, end_time)
|
|
40
70
|
return end_time
|
|
41
71
|
|
|
42
72
|
@strawberry.field
|
|
43
|
-
def record_count(
|
|
73
|
+
async def record_count(
|
|
74
|
+
self,
|
|
75
|
+
info: Info[Context, None],
|
|
76
|
+
time_range: Optional[TimeRange] = UNSET,
|
|
77
|
+
filter_condition: Optional[str] = UNSET,
|
|
78
|
+
) -> int:
|
|
79
|
+
return await info.context.data_loaders.record_counts.load(
|
|
80
|
+
("span", self.id_attr, time_range, filter_condition),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
@strawberry.field
|
|
84
|
+
async def trace_count(
|
|
85
|
+
self,
|
|
86
|
+
info: Info[Context, None],
|
|
87
|
+
time_range: Optional[TimeRange] = UNSET,
|
|
88
|
+
) -> int:
|
|
89
|
+
return await info.context.data_loaders.record_counts.load(
|
|
90
|
+
("trace", self.id_attr, time_range, None),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
@strawberry.field
|
|
94
|
+
async def token_count_total(
|
|
44
95
|
self,
|
|
96
|
+
info: Info[Context, None],
|
|
45
97
|
time_range: Optional[TimeRange] = UNSET,
|
|
98
|
+
filter_condition: Optional[str] = UNSET,
|
|
99
|
+
) -> int:
|
|
100
|
+
return await info.context.data_loaders.token_counts.load(
|
|
101
|
+
("total", self.id_attr, time_range, filter_condition),
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
@strawberry.field
|
|
105
|
+
async def token_count_prompt(
|
|
106
|
+
self,
|
|
107
|
+
info: Info[Context, None],
|
|
108
|
+
time_range: Optional[TimeRange] = UNSET,
|
|
109
|
+
filter_condition: Optional[str] = UNSET,
|
|
46
110
|
) -> int:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
111
|
+
return await info.context.data_loaders.token_counts.load(
|
|
112
|
+
("prompt", self.id_attr, time_range, filter_condition),
|
|
113
|
+
)
|
|
50
114
|
|
|
51
115
|
@strawberry.field
|
|
52
|
-
def
|
|
116
|
+
async def token_count_completion(
|
|
53
117
|
self,
|
|
118
|
+
info: Info[Context, None],
|
|
54
119
|
time_range: Optional[TimeRange] = UNSET,
|
|
120
|
+
filter_condition: Optional[str] = UNSET,
|
|
55
121
|
) -> int:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
122
|
+
return await info.context.data_loaders.token_counts.load(
|
|
123
|
+
("completion", self.id_attr, time_range, filter_condition),
|
|
124
|
+
)
|
|
59
125
|
|
|
60
126
|
@strawberry.field
|
|
61
|
-
def
|
|
62
|
-
|
|
127
|
+
async def latency_ms_quantile(
|
|
128
|
+
self,
|
|
129
|
+
info: Info[Context, None],
|
|
130
|
+
probability: float,
|
|
131
|
+
time_range: Optional[TimeRange] = UNSET,
|
|
132
|
+
) -> Optional[float]:
|
|
133
|
+
return await info.context.data_loaders.latency_ms_quantile.load(
|
|
134
|
+
(
|
|
135
|
+
"trace",
|
|
136
|
+
self.id_attr,
|
|
137
|
+
time_range,
|
|
138
|
+
None,
|
|
139
|
+
probability,
|
|
140
|
+
),
|
|
141
|
+
)
|
|
63
142
|
|
|
64
143
|
@strawberry.field
|
|
65
|
-
def
|
|
66
|
-
|
|
144
|
+
async def span_latency_ms_quantile(
|
|
145
|
+
self,
|
|
146
|
+
info: Info[Context, None],
|
|
147
|
+
probability: float,
|
|
148
|
+
time_range: Optional[TimeRange] = UNSET,
|
|
149
|
+
filter_condition: Optional[str] = UNSET,
|
|
150
|
+
) -> Optional[float]:
|
|
151
|
+
return await info.context.data_loaders.latency_ms_quantile.load(
|
|
152
|
+
(
|
|
153
|
+
"span",
|
|
154
|
+
self.id_attr,
|
|
155
|
+
time_range,
|
|
156
|
+
filter_condition,
|
|
157
|
+
probability,
|
|
158
|
+
),
|
|
159
|
+
)
|
|
67
160
|
|
|
68
161
|
@strawberry.field
|
|
69
|
-
def
|
|
70
|
-
|
|
162
|
+
async def trace(self, trace_id: ID, info: Info[Context, None]) -> Optional[Trace]:
|
|
163
|
+
stmt = (
|
|
164
|
+
select(models.Trace)
|
|
165
|
+
.where(models.Trace.trace_id == str(trace_id))
|
|
166
|
+
.where(models.Trace.project_rowid == self.id_attr)
|
|
167
|
+
)
|
|
168
|
+
async with info.context.db() as session:
|
|
169
|
+
if (trace := await session.scalar(stmt)) is None:
|
|
170
|
+
return None
|
|
171
|
+
return to_gql_trace(trace)
|
|
71
172
|
|
|
72
173
|
@strawberry.field
|
|
73
|
-
def spans(
|
|
174
|
+
async def spans(
|
|
74
175
|
self,
|
|
176
|
+
info: Info[Context, None],
|
|
75
177
|
time_range: Optional[TimeRange] = UNSET,
|
|
76
|
-
trace_ids: Optional[List[ID]] = UNSET,
|
|
77
178
|
first: Optional[int] = 50,
|
|
78
179
|
last: Optional[int] = UNSET,
|
|
79
|
-
after: Optional[
|
|
80
|
-
before: Optional[
|
|
180
|
+
after: Optional[CursorString] = UNSET,
|
|
181
|
+
before: Optional[CursorString] = UNSET,
|
|
81
182
|
sort: Optional[SpanSort] = UNSET,
|
|
82
183
|
root_spans_only: Optional[bool] = UNSET,
|
|
83
184
|
filter_condition: Optional[str] = UNSET,
|
|
84
185
|
) -> Connection[Span]:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
)
|
|
91
|
-
if
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
186
|
+
stmt = (
|
|
187
|
+
select(models.Span)
|
|
188
|
+
.join(models.Trace)
|
|
189
|
+
.where(models.Trace.project_rowid == self.id_attr)
|
|
190
|
+
.options(contains_eager(models.Span.trace).load_only(models.Trace.trace_id))
|
|
191
|
+
)
|
|
192
|
+
if time_range:
|
|
193
|
+
stmt = stmt.where(
|
|
194
|
+
and_(
|
|
195
|
+
time_range.start <= models.Span.start_time,
|
|
196
|
+
models.Span.start_time < time_range.end,
|
|
197
|
+
)
|
|
97
198
|
)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
199
|
+
if root_spans_only:
|
|
200
|
+
# A root span is any span whose parent span is missing in the
|
|
201
|
+
# database, even if its `parent_span_id` may not be NULL.
|
|
202
|
+
parent = select(models.Span.span_id).alias()
|
|
203
|
+
stmt = stmt.outerjoin(
|
|
204
|
+
parent,
|
|
205
|
+
models.Span.parent_id == parent.c.span_id,
|
|
206
|
+
).where(parent.c.span_id.is_(None))
|
|
207
|
+
if filter_condition:
|
|
208
|
+
span_filter = SpanFilter(condition=filter_condition)
|
|
209
|
+
stmt = span_filter(stmt)
|
|
210
|
+
sort_config: Optional[SpanSortConfig] = None
|
|
211
|
+
cursor_rowid_column: Any = models.Span.id
|
|
212
|
+
if sort:
|
|
213
|
+
sort_config = sort.update_orm_expr(stmt)
|
|
214
|
+
stmt = sort_config.stmt
|
|
215
|
+
if sort_config.dir is SortDir.desc:
|
|
216
|
+
cursor_rowid_column = desc(cursor_rowid_column)
|
|
217
|
+
if after:
|
|
218
|
+
cursor = Cursor.from_string(after)
|
|
219
|
+
if sort_config and cursor.sort_column:
|
|
220
|
+
sort_column = cursor.sort_column
|
|
221
|
+
compare = operator.lt if sort_config.dir is SortDir.desc else operator.gt
|
|
222
|
+
stmt = stmt.where(
|
|
223
|
+
compare(
|
|
224
|
+
tuple_(sort_config.orm_expression, models.Span.id),
|
|
225
|
+
(sort_column.value, cursor.rowid),
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
else:
|
|
229
|
+
stmt = stmt.where(models.Span.id > cursor.rowid)
|
|
230
|
+
if first:
|
|
231
|
+
stmt = stmt.limit(
|
|
232
|
+
first + 1 # overfetch by one to determine whether there's a next page
|
|
106
233
|
)
|
|
234
|
+
stmt = stmt.order_by(cursor_rowid_column)
|
|
235
|
+
cursors_and_nodes = []
|
|
236
|
+
async with info.context.db() as session:
|
|
237
|
+
span_records = await session.execute(stmt)
|
|
238
|
+
async for span_record in islice(span_records, first):
|
|
239
|
+
span = span_record[0]
|
|
240
|
+
cursor = Cursor(rowid=span.id)
|
|
241
|
+
if sort_config:
|
|
242
|
+
assert len(span_record) > 1
|
|
243
|
+
cursor.sort_column = CursorSortColumn(
|
|
244
|
+
type=sort_config.column_data_type,
|
|
245
|
+
value=span_record[1],
|
|
246
|
+
)
|
|
247
|
+
cursors_and_nodes.append((cursor, to_gql_span(span)))
|
|
248
|
+
has_next_page = True
|
|
249
|
+
try:
|
|
250
|
+
next(span_records)
|
|
251
|
+
except StopIteration:
|
|
252
|
+
has_next_page = False
|
|
253
|
+
|
|
254
|
+
return connection_from_cursors_and_nodes(
|
|
255
|
+
cursors_and_nodes,
|
|
256
|
+
has_previous_page=False,
|
|
257
|
+
has_next_page=has_next_page,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
@strawberry.field
|
|
261
|
+
async def sessions(
|
|
262
|
+
self,
|
|
263
|
+
info: Info[Context, None],
|
|
264
|
+
time_range: Optional[TimeRange] = UNSET,
|
|
265
|
+
first: Optional[int] = 50,
|
|
266
|
+
after: Optional[CursorString] = UNSET,
|
|
267
|
+
sort: Optional[ProjectSessionSort] = UNSET,
|
|
268
|
+
filter_io_substring: Optional[str] = UNSET,
|
|
269
|
+
) -> Connection[ProjectSession]:
|
|
270
|
+
table = models.ProjectSession
|
|
271
|
+
stmt = select(table).filter_by(project_id=self.id_attr)
|
|
272
|
+
if time_range:
|
|
273
|
+
if time_range.start:
|
|
274
|
+
stmt = stmt.where(time_range.start <= table.start_time)
|
|
275
|
+
if time_range.end:
|
|
276
|
+
stmt = stmt.where(table.start_time < time_range.end)
|
|
277
|
+
if filter_io_substring:
|
|
278
|
+
filter_subq = (
|
|
279
|
+
stmt.with_only_columns(distinct(table.id).label("id"))
|
|
280
|
+
.join_from(table, models.Trace)
|
|
281
|
+
.join_from(models.Trace, models.Span)
|
|
282
|
+
.where(models.Span.parent_id.is_(None))
|
|
283
|
+
.where(
|
|
284
|
+
or_(
|
|
285
|
+
models.TextContains(
|
|
286
|
+
models.Span.attributes[INPUT_VALUE].as_string(),
|
|
287
|
+
filter_io_substring,
|
|
288
|
+
),
|
|
289
|
+
models.TextContains(
|
|
290
|
+
models.Span.attributes[OUTPUT_VALUE].as_string(),
|
|
291
|
+
filter_io_substring,
|
|
292
|
+
),
|
|
293
|
+
)
|
|
294
|
+
)
|
|
295
|
+
).subquery()
|
|
296
|
+
stmt = stmt.join(filter_subq, table.id == filter_subq.c.id)
|
|
297
|
+
if sort:
|
|
298
|
+
key: ColumnElement[Any]
|
|
299
|
+
if sort.col is ProjectSessionColumn.startTime:
|
|
300
|
+
key = table.start_time.label("key")
|
|
301
|
+
elif sort.col is ProjectSessionColumn.endTime:
|
|
302
|
+
key = table.end_time.label("key")
|
|
303
|
+
elif (
|
|
304
|
+
sort.col is ProjectSessionColumn.tokenCountTotal
|
|
305
|
+
or sort.col is ProjectSessionColumn.numTraces
|
|
306
|
+
):
|
|
307
|
+
if sort.col is ProjectSessionColumn.tokenCountTotal:
|
|
308
|
+
sort_subq = (
|
|
309
|
+
select(
|
|
310
|
+
models.Trace.project_session_rowid.label("id"),
|
|
311
|
+
func.sum(models.Span.cumulative_llm_token_count_total).label("key"),
|
|
312
|
+
)
|
|
313
|
+
.join_from(models.Trace, models.Span)
|
|
314
|
+
.where(models.Span.parent_id.is_(None))
|
|
315
|
+
.group_by(models.Trace.project_session_rowid)
|
|
316
|
+
).subquery()
|
|
317
|
+
elif sort.col is ProjectSessionColumn.numTraces:
|
|
318
|
+
sort_subq = (
|
|
319
|
+
select(
|
|
320
|
+
models.Trace.project_session_rowid.label("id"),
|
|
321
|
+
func.count(models.Trace.id).label("key"),
|
|
322
|
+
).group_by(models.Trace.project_session_rowid)
|
|
323
|
+
).subquery()
|
|
324
|
+
else:
|
|
325
|
+
assert_never(sort.col)
|
|
326
|
+
key = sort_subq.c.key
|
|
327
|
+
stmt = stmt.join(sort_subq, table.id == sort_subq.c.id)
|
|
328
|
+
else:
|
|
329
|
+
assert_never(sort.col)
|
|
330
|
+
stmt = stmt.add_columns(key)
|
|
331
|
+
if sort.dir is SortDir.asc:
|
|
332
|
+
stmt = stmt.order_by(key.asc(), table.id.asc())
|
|
333
|
+
else:
|
|
334
|
+
stmt = stmt.order_by(key.desc(), table.id.desc())
|
|
335
|
+
if after:
|
|
336
|
+
cursor = Cursor.from_string(after)
|
|
337
|
+
assert cursor.sort_column is not None
|
|
338
|
+
compare = operator.lt if sort.dir is SortDir.desc else operator.gt
|
|
339
|
+
stmt = stmt.where(
|
|
340
|
+
compare(
|
|
341
|
+
tuple_(key, table.id),
|
|
342
|
+
(cursor.sort_column.value, cursor.rowid),
|
|
343
|
+
)
|
|
344
|
+
)
|
|
107
345
|
else:
|
|
108
|
-
|
|
109
|
-
|
|
346
|
+
stmt = stmt.order_by(table.id.desc())
|
|
347
|
+
if after:
|
|
348
|
+
cursor = Cursor.from_string(after)
|
|
349
|
+
stmt = stmt.where(table.id < cursor.rowid)
|
|
350
|
+
if first:
|
|
351
|
+
stmt = stmt.limit(
|
|
352
|
+
first + 1 # over-fetch by one to determine whether there's a next page
|
|
110
353
|
)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
354
|
+
cursors_and_nodes = []
|
|
355
|
+
async with info.context.db() as session:
|
|
356
|
+
records = await session.stream(stmt)
|
|
357
|
+
async for record in islice(records, first):
|
|
358
|
+
project_session = record[0]
|
|
359
|
+
cursor = Cursor(rowid=project_session.id)
|
|
360
|
+
if sort:
|
|
361
|
+
assert len(record) > 1
|
|
362
|
+
cursor.sort_column = CursorSortColumn(
|
|
363
|
+
type=sort.col.data_type,
|
|
364
|
+
value=record[1],
|
|
365
|
+
)
|
|
366
|
+
cursors_and_nodes.append((cursor, to_gql_project_session(project_session)))
|
|
367
|
+
has_next_page = True
|
|
368
|
+
try:
|
|
369
|
+
await records.__anext__()
|
|
370
|
+
except StopAsyncIteration:
|
|
371
|
+
has_next_page = False
|
|
372
|
+
return connection_from_cursors_and_nodes(
|
|
373
|
+
cursors_and_nodes,
|
|
374
|
+
has_previous_page=False,
|
|
375
|
+
has_next_page=has_next_page,
|
|
376
|
+
)
|
|
117
377
|
|
|
118
378
|
@strawberry.field(
|
|
119
|
-
description="Names of all available
|
|
379
|
+
description="Names of all available annotations for traces. "
|
|
120
380
|
"(The list contains no duplicates.)"
|
|
121
381
|
) # type: ignore
|
|
122
|
-
def
|
|
123
|
-
|
|
382
|
+
async def trace_annotations_names(
|
|
383
|
+
self,
|
|
384
|
+
info: Info[Context, None],
|
|
385
|
+
) -> list[str]:
|
|
386
|
+
stmt = (
|
|
387
|
+
select(distinct(models.TraceAnnotation.name))
|
|
388
|
+
.join(models.Trace)
|
|
389
|
+
.where(models.Trace.project_rowid == self.id_attr)
|
|
390
|
+
)
|
|
391
|
+
async with info.context.db() as session:
|
|
392
|
+
return list(await session.scalars(stmt))
|
|
393
|
+
|
|
394
|
+
@strawberry.field(
|
|
395
|
+
description="Names of all available annotations for spans. "
|
|
396
|
+
"(The list contains no duplicates.)"
|
|
397
|
+
) # type: ignore
|
|
398
|
+
async def span_annotation_names(
|
|
399
|
+
self,
|
|
400
|
+
info: Info[Context, None],
|
|
401
|
+
) -> list[str]:
|
|
402
|
+
stmt = (
|
|
403
|
+
select(distinct(models.SpanAnnotation.name))
|
|
404
|
+
.join(models.Span)
|
|
405
|
+
.join(models.Trace, models.Span.trace_rowid == models.Trace.id)
|
|
406
|
+
.where(models.Trace.project_rowid == self.id_attr)
|
|
407
|
+
)
|
|
408
|
+
async with info.context.db() as session:
|
|
409
|
+
return list(await session.scalars(stmt))
|
|
124
410
|
|
|
125
411
|
@strawberry.field(
|
|
126
412
|
description="Names of available document evaluations.",
|
|
127
413
|
) # type: ignore
|
|
128
|
-
def document_evaluation_names(
|
|
414
|
+
async def document_evaluation_names(
|
|
129
415
|
self,
|
|
416
|
+
info: Info[Context, None],
|
|
130
417
|
span_id: Optional[ID] = UNSET,
|
|
131
|
-
) ->
|
|
132
|
-
|
|
133
|
-
|
|
418
|
+
) -> list[str]:
|
|
419
|
+
stmt = (
|
|
420
|
+
select(distinct(models.DocumentAnnotation.name))
|
|
421
|
+
.join(models.Span)
|
|
422
|
+
.join(models.Trace, models.Span.trace_rowid == models.Trace.id)
|
|
423
|
+
.where(models.Trace.project_rowid == self.id_attr)
|
|
424
|
+
.where(models.DocumentAnnotation.annotator_kind == "LLM")
|
|
134
425
|
)
|
|
426
|
+
if span_id:
|
|
427
|
+
stmt = stmt.where(models.Span.span_id == str(span_id))
|
|
428
|
+
async with info.context.db() as session:
|
|
429
|
+
return list(await session.scalars(stmt))
|
|
135
430
|
|
|
136
431
|
@strawberry.field
|
|
137
|
-
def
|
|
432
|
+
async def trace_annotation_summary(
|
|
138
433
|
self,
|
|
139
|
-
|
|
434
|
+
info: Info[Context, None],
|
|
435
|
+
annotation_name: str,
|
|
436
|
+
time_range: Optional[TimeRange] = UNSET,
|
|
437
|
+
) -> Optional[AnnotationSummary]:
|
|
438
|
+
return await info.context.data_loaders.annotation_summaries.load(
|
|
439
|
+
("trace", self.id_attr, time_range, None, annotation_name),
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
@strawberry.field
|
|
443
|
+
async def span_annotation_summary(
|
|
444
|
+
self,
|
|
445
|
+
info: Info[Context, None],
|
|
446
|
+
annotation_name: str,
|
|
140
447
|
time_range: Optional[TimeRange] = UNSET,
|
|
141
448
|
filter_condition: Optional[str] = UNSET,
|
|
142
|
-
) -> Optional[
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
SpanFilter(
|
|
146
|
-
condition=filter_condition,
|
|
147
|
-
evals=project,
|
|
148
|
-
)
|
|
149
|
-
if filter_condition
|
|
150
|
-
else None
|
|
151
|
-
)
|
|
152
|
-
span_ids = project.get_span_evaluation_span_ids(evaluation_name)
|
|
153
|
-
if not span_ids:
|
|
154
|
-
return None
|
|
155
|
-
spans = project.get_spans(
|
|
156
|
-
start_time=time_range.start if time_range else None,
|
|
157
|
-
stop_time=time_range.end if time_range else None,
|
|
158
|
-
span_ids=span_ids,
|
|
159
|
-
)
|
|
160
|
-
if predicate:
|
|
161
|
-
spans = filter(predicate, spans)
|
|
162
|
-
evaluations = tuple(
|
|
163
|
-
evaluation
|
|
164
|
-
for span in spans
|
|
165
|
-
if (
|
|
166
|
-
evaluation := project.get_span_evaluation(
|
|
167
|
-
span.context.span_id,
|
|
168
|
-
evaluation_name,
|
|
169
|
-
)
|
|
170
|
-
)
|
|
171
|
-
is not None
|
|
449
|
+
) -> Optional[AnnotationSummary]:
|
|
450
|
+
return await info.context.data_loaders.annotation_summaries.load(
|
|
451
|
+
("span", self.id_attr, time_range, filter_condition, annotation_name),
|
|
172
452
|
)
|
|
173
|
-
if not evaluations:
|
|
174
|
-
return None
|
|
175
|
-
labels = project.get_span_evaluation_labels(evaluation_name)
|
|
176
|
-
return EvaluationSummary(evaluations, labels)
|
|
177
453
|
|
|
178
454
|
@strawberry.field
|
|
179
|
-
def document_evaluation_summary(
|
|
455
|
+
async def document_evaluation_summary(
|
|
180
456
|
self,
|
|
457
|
+
info: Info[Context, None],
|
|
181
458
|
evaluation_name: str,
|
|
182
459
|
time_range: Optional[TimeRange] = UNSET,
|
|
183
460
|
filter_condition: Optional[str] = UNSET,
|
|
184
461
|
) -> Optional[DocumentEvaluationSummary]:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
SpanFilter(condition=filter_condition, evals=project) if filter_condition else None
|
|
188
|
-
)
|
|
189
|
-
span_ids = project.get_document_evaluation_span_ids(evaluation_name)
|
|
190
|
-
if not span_ids:
|
|
191
|
-
return None
|
|
192
|
-
spans = project.get_spans(
|
|
193
|
-
start_time=time_range.start if time_range else None,
|
|
194
|
-
stop_time=time_range.end if time_range else None,
|
|
195
|
-
span_ids=span_ids,
|
|
196
|
-
)
|
|
197
|
-
if predicate:
|
|
198
|
-
spans = filter(predicate, spans)
|
|
199
|
-
metrics_collection = []
|
|
200
|
-
for span in spans:
|
|
201
|
-
span_id = span.context.span_id
|
|
202
|
-
num_documents = project.get_num_documents(span_id)
|
|
203
|
-
if not num_documents:
|
|
204
|
-
continue
|
|
205
|
-
evaluation_scores = project.get_document_evaluation_scores(
|
|
206
|
-
span_id=span_id,
|
|
207
|
-
evaluation_name=evaluation_name,
|
|
208
|
-
num_documents=num_documents,
|
|
209
|
-
)
|
|
210
|
-
metrics_collection.append(RetrievalMetrics(evaluation_scores))
|
|
211
|
-
if not metrics_collection:
|
|
212
|
-
return None
|
|
213
|
-
return DocumentEvaluationSummary(
|
|
214
|
-
evaluation_name=evaluation_name,
|
|
215
|
-
metrics_collection=metrics_collection,
|
|
462
|
+
return await info.context.data_loaders.document_evaluation_summaries.load(
|
|
463
|
+
(self.id_attr, time_range, filter_condition, evaluation_name),
|
|
216
464
|
)
|
|
217
465
|
|
|
218
466
|
@strawberry.field
|
|
219
467
|
def streaming_last_updated_at(
|
|
220
468
|
self,
|
|
469
|
+
info: Info[Context, None],
|
|
221
470
|
) -> Optional[datetime]:
|
|
222
|
-
return self.
|
|
471
|
+
return info.context.last_updated_at.get(self._table, self.id_attr)
|
|
223
472
|
|
|
224
473
|
@strawberry.field
|
|
225
|
-
def validate_span_filter_condition(self, condition: str) -> ValidationResult:
|
|
226
|
-
|
|
474
|
+
async def validate_span_filter_condition(self, condition: str) -> ValidationResult:
|
|
475
|
+
# This query is too expensive to run on every validation
|
|
476
|
+
# valid_eval_names = await self.span_annotation_names()
|
|
227
477
|
try:
|
|
228
478
|
SpanFilter(
|
|
229
479
|
condition=condition,
|
|
230
|
-
|
|
231
|
-
valid_eval_names=valid_eval_names,
|
|
480
|
+
# valid_eval_names=valid_eval_names,
|
|
232
481
|
)
|
|
233
482
|
return ValidationResult(is_valid=True, error_message=None)
|
|
234
483
|
except SyntaxError as e:
|
|
@@ -236,3 +485,19 @@ class Project(Node):
|
|
|
236
485
|
is_valid=False,
|
|
237
486
|
error_message=e.msg,
|
|
238
487
|
)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def to_gql_project(project: models.Project) -> Project:
|
|
491
|
+
"""
|
|
492
|
+
Converts an ORM project to a GraphQL Project.
|
|
493
|
+
"""
|
|
494
|
+
return Project(
|
|
495
|
+
id_attr=project.id,
|
|
496
|
+
name=project.name,
|
|
497
|
+
gradient_start_color=project.gradient_start_color,
|
|
498
|
+
gradient_end_color=project.gradient_end_color,
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
INPUT_VALUE = SpanAttributes.INPUT_VALUE.split(".")
|
|
503
|
+
OUTPUT_VALUE = SpanAttributes.OUTPUT_VALUE.split(".")
|