arize-phoenix 10.0.4__py3-none-any.whl → 12.28.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/METADATA +124 -72
- arize_phoenix-12.28.1.dist-info/RECORD +499 -0
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/WHEEL +1 -1
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/licenses/IP_NOTICE +1 -1
- phoenix/__generated__/__init__.py +0 -0
- phoenix/__generated__/classification_evaluator_configs/__init__.py +20 -0
- phoenix/__generated__/classification_evaluator_configs/_document_relevance_classification_evaluator_config.py +17 -0
- phoenix/__generated__/classification_evaluator_configs/_hallucination_classification_evaluator_config.py +17 -0
- phoenix/__generated__/classification_evaluator_configs/_models.py +18 -0
- phoenix/__generated__/classification_evaluator_configs/_tool_selection_classification_evaluator_config.py +17 -0
- phoenix/__init__.py +5 -4
- phoenix/auth.py +39 -2
- phoenix/config.py +1763 -91
- phoenix/datetime_utils.py +120 -2
- phoenix/db/README.md +595 -25
- phoenix/db/bulk_inserter.py +145 -103
- phoenix/db/engines.py +140 -33
- phoenix/db/enums.py +3 -12
- phoenix/db/facilitator.py +302 -35
- phoenix/db/helpers.py +1000 -65
- phoenix/db/iam_auth.py +64 -0
- phoenix/db/insertion/dataset.py +135 -2
- phoenix/db/insertion/document_annotation.py +9 -6
- phoenix/db/insertion/evaluation.py +2 -3
- phoenix/db/insertion/helpers.py +17 -2
- phoenix/db/insertion/session_annotation.py +176 -0
- phoenix/db/insertion/span.py +15 -11
- phoenix/db/insertion/span_annotation.py +3 -4
- phoenix/db/insertion/trace_annotation.py +3 -4
- phoenix/db/insertion/types.py +50 -20
- phoenix/db/migrations/versions/01a8342c9cdf_add_user_id_on_datasets.py +40 -0
- phoenix/db/migrations/versions/0df286449799_add_session_annotations_table.py +105 -0
- phoenix/db/migrations/versions/272b66ff50f8_drop_single_indices.py +119 -0
- phoenix/db/migrations/versions/58228d933c91_dataset_labels.py +67 -0
- phoenix/db/migrations/versions/699f655af132_experiment_tags.py +57 -0
- phoenix/db/migrations/versions/735d3d93c33e_add_composite_indices.py +41 -0
- phoenix/db/migrations/versions/a20694b15f82_cost.py +196 -0
- phoenix/db/migrations/versions/ab513d89518b_add_user_id_on_dataset_versions.py +40 -0
- phoenix/db/migrations/versions/d0690a79ea51_users_on_experiments.py +40 -0
- phoenix/db/migrations/versions/deb2c81c0bb2_dataset_splits.py +139 -0
- phoenix/db/migrations/versions/e76cbd66ffc3_add_experiments_dataset_examples.py +87 -0
- phoenix/db/models.py +669 -56
- phoenix/db/pg_config.py +10 -0
- phoenix/db/types/model_provider.py +4 -0
- phoenix/db/types/token_price_customization.py +29 -0
- phoenix/db/types/trace_retention.py +23 -15
- phoenix/experiments/evaluators/utils.py +3 -3
- phoenix/experiments/functions.py +160 -52
- phoenix/experiments/tracing.py +2 -2
- phoenix/experiments/types.py +1 -1
- phoenix/inferences/inferences.py +1 -2
- phoenix/server/api/auth.py +38 -7
- phoenix/server/api/auth_messages.py +46 -0
- phoenix/server/api/context.py +100 -4
- phoenix/server/api/dataloaders/__init__.py +79 -5
- phoenix/server/api/dataloaders/annotation_configs_by_project.py +31 -0
- phoenix/server/api/dataloaders/annotation_summaries.py +60 -8
- phoenix/server/api/dataloaders/average_experiment_repeated_run_group_latency.py +50 -0
- phoenix/server/api/dataloaders/average_experiment_run_latency.py +17 -24
- phoenix/server/api/dataloaders/cache/two_tier_cache.py +1 -2
- phoenix/server/api/dataloaders/dataset_dataset_splits.py +52 -0
- phoenix/server/api/dataloaders/dataset_example_revisions.py +0 -1
- phoenix/server/api/dataloaders/dataset_example_splits.py +40 -0
- phoenix/server/api/dataloaders/dataset_examples_and_versions_by_experiment_run.py +47 -0
- phoenix/server/api/dataloaders/dataset_labels.py +36 -0
- phoenix/server/api/dataloaders/document_evaluation_summaries.py +2 -2
- phoenix/server/api/dataloaders/document_evaluations.py +6 -9
- phoenix/server/api/dataloaders/experiment_annotation_summaries.py +88 -34
- phoenix/server/api/dataloaders/experiment_dataset_splits.py +43 -0
- phoenix/server/api/dataloaders/experiment_error_rates.py +21 -28
- phoenix/server/api/dataloaders/experiment_repeated_run_group_annotation_summaries.py +77 -0
- phoenix/server/api/dataloaders/experiment_repeated_run_groups.py +57 -0
- phoenix/server/api/dataloaders/experiment_runs_by_experiment_and_example.py +44 -0
- phoenix/server/api/dataloaders/last_used_times_by_generative_model_id.py +35 -0
- phoenix/server/api/dataloaders/latency_ms_quantile.py +40 -8
- phoenix/server/api/dataloaders/record_counts.py +37 -10
- phoenix/server/api/dataloaders/session_annotations_by_session.py +29 -0
- phoenix/server/api/dataloaders/span_cost_by_span.py +24 -0
- phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_generative_model.py +56 -0
- phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_project_session.py +57 -0
- phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_span.py +43 -0
- phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_trace.py +56 -0
- phoenix/server/api/dataloaders/span_cost_details_by_span_cost.py +27 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_experiment.py +57 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_experiment_repeated_run_group.py +64 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_experiment_run.py +58 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_generative_model.py +55 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_project.py +152 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_project_session.py +56 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_trace.py +55 -0
- phoenix/server/api/dataloaders/span_costs.py +29 -0
- phoenix/server/api/dataloaders/table_fields.py +2 -2
- phoenix/server/api/dataloaders/token_prices_by_model.py +30 -0
- phoenix/server/api/dataloaders/trace_annotations_by_trace.py +27 -0
- phoenix/server/api/dataloaders/types.py +29 -0
- phoenix/server/api/exceptions.py +11 -1
- phoenix/server/api/helpers/dataset_helpers.py +5 -1
- phoenix/server/api/helpers/playground_clients.py +1243 -292
- phoenix/server/api/helpers/playground_registry.py +2 -2
- phoenix/server/api/helpers/playground_spans.py +8 -4
- phoenix/server/api/helpers/playground_users.py +26 -0
- phoenix/server/api/helpers/prompts/conversions/aws.py +83 -0
- phoenix/server/api/helpers/prompts/conversions/google.py +103 -0
- phoenix/server/api/helpers/prompts/models.py +205 -22
- phoenix/server/api/input_types/{SpanAnnotationFilter.py → AnnotationFilter.py} +22 -14
- phoenix/server/api/input_types/ChatCompletionInput.py +6 -2
- phoenix/server/api/input_types/CreateProjectInput.py +27 -0
- phoenix/server/api/input_types/CreateProjectSessionAnnotationInput.py +37 -0
- phoenix/server/api/input_types/DatasetFilter.py +17 -0
- phoenix/server/api/input_types/ExperimentRunSort.py +237 -0
- phoenix/server/api/input_types/GenerativeCredentialInput.py +9 -0
- phoenix/server/api/input_types/GenerativeModelInput.py +5 -0
- phoenix/server/api/input_types/ProjectSessionSort.py +161 -1
- phoenix/server/api/input_types/PromptFilter.py +14 -0
- phoenix/server/api/input_types/PromptVersionInput.py +52 -1
- phoenix/server/api/input_types/SpanSort.py +44 -7
- phoenix/server/api/input_types/TimeBinConfig.py +23 -0
- phoenix/server/api/input_types/UpdateAnnotationInput.py +34 -0
- phoenix/server/api/input_types/UserRoleInput.py +1 -0
- phoenix/server/api/mutations/__init__.py +10 -0
- phoenix/server/api/mutations/annotation_config_mutations.py +8 -8
- phoenix/server/api/mutations/api_key_mutations.py +19 -23
- phoenix/server/api/mutations/chat_mutations.py +154 -47
- phoenix/server/api/mutations/dataset_label_mutations.py +243 -0
- phoenix/server/api/mutations/dataset_mutations.py +21 -16
- phoenix/server/api/mutations/dataset_split_mutations.py +351 -0
- phoenix/server/api/mutations/experiment_mutations.py +2 -2
- phoenix/server/api/mutations/export_events_mutations.py +3 -3
- phoenix/server/api/mutations/model_mutations.py +210 -0
- phoenix/server/api/mutations/project_mutations.py +49 -10
- phoenix/server/api/mutations/project_session_annotations_mutations.py +158 -0
- phoenix/server/api/mutations/project_trace_retention_policy_mutations.py +8 -4
- phoenix/server/api/mutations/prompt_label_mutations.py +74 -65
- phoenix/server/api/mutations/prompt_mutations.py +65 -129
- phoenix/server/api/mutations/prompt_version_tag_mutations.py +11 -8
- phoenix/server/api/mutations/span_annotations_mutations.py +15 -10
- phoenix/server/api/mutations/trace_annotations_mutations.py +14 -10
- phoenix/server/api/mutations/trace_mutations.py +47 -3
- phoenix/server/api/mutations/user_mutations.py +66 -41
- phoenix/server/api/queries.py +768 -293
- phoenix/server/api/routers/__init__.py +2 -2
- phoenix/server/api/routers/auth.py +154 -88
- phoenix/server/api/routers/ldap.py +229 -0
- phoenix/server/api/routers/oauth2.py +369 -106
- phoenix/server/api/routers/v1/__init__.py +24 -4
- phoenix/server/api/routers/v1/annotation_configs.py +23 -31
- phoenix/server/api/routers/v1/annotations.py +481 -17
- phoenix/server/api/routers/v1/datasets.py +395 -81
- phoenix/server/api/routers/v1/documents.py +142 -0
- phoenix/server/api/routers/v1/evaluations.py +24 -31
- phoenix/server/api/routers/v1/experiment_evaluations.py +19 -8
- phoenix/server/api/routers/v1/experiment_runs.py +337 -59
- phoenix/server/api/routers/v1/experiments.py +479 -48
- phoenix/server/api/routers/v1/models.py +7 -0
- phoenix/server/api/routers/v1/projects.py +18 -49
- phoenix/server/api/routers/v1/prompts.py +54 -40
- phoenix/server/api/routers/v1/sessions.py +108 -0
- phoenix/server/api/routers/v1/spans.py +1091 -81
- phoenix/server/api/routers/v1/traces.py +132 -78
- phoenix/server/api/routers/v1/users.py +389 -0
- phoenix/server/api/routers/v1/utils.py +3 -7
- phoenix/server/api/subscriptions.py +305 -88
- phoenix/server/api/types/Annotation.py +90 -23
- phoenix/server/api/types/ApiKey.py +13 -17
- phoenix/server/api/types/AuthMethod.py +1 -0
- phoenix/server/api/types/ChatCompletionSubscriptionPayload.py +1 -0
- phoenix/server/api/types/CostBreakdown.py +12 -0
- phoenix/server/api/types/Dataset.py +226 -72
- phoenix/server/api/types/DatasetExample.py +88 -18
- phoenix/server/api/types/DatasetExperimentAnnotationSummary.py +10 -0
- phoenix/server/api/types/DatasetLabel.py +57 -0
- phoenix/server/api/types/DatasetSplit.py +98 -0
- phoenix/server/api/types/DatasetVersion.py +49 -4
- phoenix/server/api/types/DocumentAnnotation.py +212 -0
- phoenix/server/api/types/Experiment.py +264 -59
- phoenix/server/api/types/ExperimentComparison.py +5 -10
- phoenix/server/api/types/ExperimentRepeatedRunGroup.py +155 -0
- phoenix/server/api/types/ExperimentRepeatedRunGroupAnnotationSummary.py +9 -0
- phoenix/server/api/types/ExperimentRun.py +169 -65
- phoenix/server/api/types/ExperimentRunAnnotation.py +158 -39
- phoenix/server/api/types/GenerativeModel.py +245 -3
- phoenix/server/api/types/GenerativeProvider.py +70 -11
- phoenix/server/api/types/{Model.py → InferenceModel.py} +1 -1
- phoenix/server/api/types/ModelInterface.py +16 -0
- phoenix/server/api/types/PlaygroundModel.py +20 -0
- phoenix/server/api/types/Project.py +1278 -216
- phoenix/server/api/types/ProjectSession.py +188 -28
- phoenix/server/api/types/ProjectSessionAnnotation.py +187 -0
- phoenix/server/api/types/ProjectTraceRetentionPolicy.py +1 -1
- phoenix/server/api/types/Prompt.py +119 -39
- phoenix/server/api/types/PromptLabel.py +42 -25
- phoenix/server/api/types/PromptVersion.py +11 -8
- phoenix/server/api/types/PromptVersionTag.py +65 -25
- phoenix/server/api/types/ServerStatus.py +6 -0
- phoenix/server/api/types/Span.py +167 -123
- phoenix/server/api/types/SpanAnnotation.py +189 -42
- phoenix/server/api/types/SpanCostDetailSummaryEntry.py +10 -0
- phoenix/server/api/types/SpanCostSummary.py +10 -0
- phoenix/server/api/types/SystemApiKey.py +65 -1
- phoenix/server/api/types/TokenPrice.py +16 -0
- phoenix/server/api/types/TokenUsage.py +3 -3
- phoenix/server/api/types/Trace.py +223 -51
- phoenix/server/api/types/TraceAnnotation.py +149 -50
- phoenix/server/api/types/User.py +137 -32
- phoenix/server/api/types/UserApiKey.py +73 -26
- phoenix/server/api/types/node.py +10 -0
- phoenix/server/api/types/pagination.py +11 -2
- phoenix/server/app.py +290 -45
- phoenix/server/authorization.py +38 -3
- phoenix/server/bearer_auth.py +34 -24
- phoenix/server/cost_tracking/cost_details_calculator.py +196 -0
- phoenix/server/cost_tracking/cost_model_lookup.py +179 -0
- phoenix/server/cost_tracking/helpers.py +68 -0
- phoenix/server/cost_tracking/model_cost_manifest.json +3657 -830
- phoenix/server/cost_tracking/regex_specificity.py +397 -0
- phoenix/server/cost_tracking/token_cost_calculator.py +57 -0
- phoenix/server/daemons/__init__.py +0 -0
- phoenix/server/daemons/db_disk_usage_monitor.py +214 -0
- phoenix/server/daemons/generative_model_store.py +103 -0
- phoenix/server/daemons/span_cost_calculator.py +99 -0
- phoenix/server/dml_event.py +17 -0
- phoenix/server/dml_event_handler.py +5 -0
- phoenix/server/email/sender.py +56 -3
- phoenix/server/email/templates/db_disk_usage_notification.html +19 -0
- phoenix/server/email/types.py +11 -0
- phoenix/server/experiments/__init__.py +0 -0
- phoenix/server/experiments/utils.py +14 -0
- phoenix/server/grpc_server.py +11 -11
- phoenix/server/jwt_store.py +17 -15
- phoenix/server/ldap.py +1449 -0
- phoenix/server/main.py +26 -10
- phoenix/server/oauth2.py +330 -12
- phoenix/server/prometheus.py +66 -6
- phoenix/server/rate_limiters.py +4 -9
- phoenix/server/retention.py +33 -20
- phoenix/server/session_filters.py +49 -0
- phoenix/server/static/.vite/manifest.json +55 -51
- phoenix/server/static/assets/components-BreFUQQa.js +6702 -0
- phoenix/server/static/assets/{index-E0M82BdE.js → index-CTQoemZv.js} +140 -56
- phoenix/server/static/assets/pages-DBE5iYM3.js +9524 -0
- phoenix/server/static/assets/vendor-BGzfc4EU.css +1 -0
- phoenix/server/static/assets/vendor-DCE4v-Ot.js +920 -0
- phoenix/server/static/assets/vendor-codemirror-D5f205eT.js +25 -0
- phoenix/server/static/assets/vendor-recharts-V9cwpXsm.js +37 -0
- phoenix/server/static/assets/vendor-shiki-Do--csgv.js +5 -0
- phoenix/server/static/assets/vendor-three-CmB8bl_y.js +3840 -0
- phoenix/server/templates/index.html +40 -6
- phoenix/server/thread_server.py +1 -2
- phoenix/server/types.py +14 -4
- phoenix/server/utils.py +74 -0
- phoenix/session/client.py +56 -3
- phoenix/session/data_extractor.py +5 -0
- phoenix/session/evaluation.py +14 -5
- phoenix/session/session.py +45 -9
- phoenix/settings.py +5 -0
- phoenix/trace/attributes.py +80 -13
- phoenix/trace/dsl/helpers.py +90 -1
- phoenix/trace/dsl/query.py +8 -6
- phoenix/trace/projects.py +5 -0
- phoenix/utilities/template_formatters.py +1 -1
- phoenix/version.py +1 -1
- arize_phoenix-10.0.4.dist-info/RECORD +0 -405
- phoenix/server/api/types/Evaluation.py +0 -39
- phoenix/server/cost_tracking/cost_lookup.py +0 -255
- phoenix/server/static/assets/components-DULKeDfL.js +0 -4365
- phoenix/server/static/assets/pages-Cl0A-0U2.js +0 -7430
- phoenix/server/static/assets/vendor-WIZid84E.css +0 -1
- phoenix/server/static/assets/vendor-arizeai-Dy-0mSNw.js +0 -649
- phoenix/server/static/assets/vendor-codemirror-DBtifKNr.js +0 -33
- phoenix/server/static/assets/vendor-oB4u9zuV.js +0 -905
- phoenix/server/static/assets/vendor-recharts-D-T4KPz2.js +0 -59
- phoenix/server/static/assets/vendor-shiki-BMn4O_9F.js +0 -5
- phoenix/server/static/assets/vendor-three-C5WAXd5r.js +0 -2998
- phoenix/utilities/deprecation.py +0 -31
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/entry_points.txt +0 -0
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,58 +1,116 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from dataclasses import asdict, dataclass
|
|
1
3
|
from datetime import datetime
|
|
2
|
-
from typing import TYPE_CHECKING, Annotated,
|
|
4
|
+
from typing import TYPE_CHECKING, Annotated, Optional
|
|
3
5
|
|
|
6
|
+
import pandas as pd
|
|
4
7
|
import strawberry
|
|
5
8
|
from openinference.semconv.trace import SpanAttributes
|
|
6
9
|
from sqlalchemy import select
|
|
7
|
-
from strawberry import UNSET, Info,
|
|
8
|
-
from strawberry.relay import Connection,
|
|
10
|
+
from strawberry import UNSET, Info, lazy
|
|
11
|
+
from strawberry.relay import Connection, Node, NodeID
|
|
9
12
|
|
|
10
13
|
from phoenix.db import models
|
|
11
14
|
from phoenix.server.api.context import Context
|
|
15
|
+
from phoenix.server.api.input_types.AnnotationFilter import AnnotationFilter, satisfies_filter
|
|
16
|
+
from phoenix.server.api.types.AnnotationSummary import AnnotationSummary
|
|
17
|
+
from phoenix.server.api.types.CostBreakdown import CostBreakdown
|
|
12
18
|
from phoenix.server.api.types.MimeType import MimeType
|
|
13
19
|
from phoenix.server.api.types.pagination import ConnectionArgs, CursorString, connection_from_list
|
|
20
|
+
from phoenix.server.api.types.SpanCostDetailSummaryEntry import SpanCostDetailSummaryEntry
|
|
21
|
+
from phoenix.server.api.types.SpanCostSummary import SpanCostSummary
|
|
14
22
|
from phoenix.server.api.types.SpanIOValue import SpanIOValue
|
|
15
23
|
from phoenix.server.api.types.TokenUsage import TokenUsage
|
|
16
24
|
|
|
17
25
|
if TYPE_CHECKING:
|
|
26
|
+
from phoenix.server.api.types.Project import Project
|
|
27
|
+
from phoenix.server.api.types.ProjectSessionAnnotation import ProjectSessionAnnotation
|
|
18
28
|
from phoenix.server.api.types.Trace import Trace
|
|
19
29
|
|
|
20
30
|
|
|
21
31
|
@strawberry.type
|
|
22
32
|
class ProjectSession(Node):
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
id: NodeID[int]
|
|
34
|
+
db_record: strawberry.Private[Optional[models.ProjectSession]] = None
|
|
35
|
+
|
|
36
|
+
def __post_init__(self) -> None:
|
|
37
|
+
if self.db_record and self.id != self.db_record.id:
|
|
38
|
+
raise ValueError("ProjectSession ID mismatch")
|
|
39
|
+
|
|
40
|
+
@strawberry.field
|
|
41
|
+
async def session_id(
|
|
42
|
+
self,
|
|
43
|
+
info: Info[Context, None],
|
|
44
|
+
) -> str:
|
|
45
|
+
if self.db_record:
|
|
46
|
+
val = self.db_record.session_id
|
|
47
|
+
else:
|
|
48
|
+
val = await info.context.data_loaders.project_session_fields.load(
|
|
49
|
+
(self.id, models.ProjectSession.session_id),
|
|
50
|
+
)
|
|
51
|
+
return val
|
|
52
|
+
|
|
53
|
+
@strawberry.field
|
|
54
|
+
async def start_time(
|
|
55
|
+
self,
|
|
56
|
+
info: Info[Context, None],
|
|
57
|
+
) -> datetime:
|
|
58
|
+
if self.db_record:
|
|
59
|
+
val = self.db_record.start_time
|
|
60
|
+
else:
|
|
61
|
+
val = await info.context.data_loaders.project_session_fields.load(
|
|
62
|
+
(self.id, models.ProjectSession.start_time),
|
|
63
|
+
)
|
|
64
|
+
return val
|
|
65
|
+
|
|
66
|
+
@strawberry.field
|
|
67
|
+
async def end_time(
|
|
68
|
+
self,
|
|
69
|
+
info: Info[Context, None],
|
|
70
|
+
) -> datetime:
|
|
71
|
+
if self.db_record:
|
|
72
|
+
val = self.db_record.end_time
|
|
73
|
+
else:
|
|
74
|
+
val = await info.context.data_loaders.project_session_fields.load(
|
|
75
|
+
(self.id, models.ProjectSession.end_time),
|
|
76
|
+
)
|
|
77
|
+
return val
|
|
29
78
|
|
|
30
79
|
@strawberry.field
|
|
31
|
-
async def
|
|
80
|
+
async def project(
|
|
81
|
+
self,
|
|
82
|
+
info: Info[Context, None],
|
|
83
|
+
) -> Annotated["Project", lazy(".Project")]:
|
|
32
84
|
from phoenix.server.api.types.Project import Project
|
|
33
85
|
|
|
34
|
-
|
|
86
|
+
if self.db_record:
|
|
87
|
+
project_rowid = self.db_record.project_id
|
|
88
|
+
else:
|
|
89
|
+
project_rowid = await info.context.data_loaders.project_session_fields.load(
|
|
90
|
+
(self.id, models.ProjectSession.project_id),
|
|
91
|
+
)
|
|
92
|
+
return Project(id=project_rowid)
|
|
35
93
|
|
|
36
94
|
@strawberry.field
|
|
37
95
|
async def num_traces(
|
|
38
96
|
self,
|
|
39
97
|
info: Info[Context, None],
|
|
40
98
|
) -> int:
|
|
41
|
-
return await info.context.data_loaders.session_num_traces.load(self.
|
|
99
|
+
return await info.context.data_loaders.session_num_traces.load(self.id)
|
|
42
100
|
|
|
43
101
|
@strawberry.field
|
|
44
102
|
async def num_traces_with_error(
|
|
45
103
|
self,
|
|
46
104
|
info: Info[Context, None],
|
|
47
105
|
) -> int:
|
|
48
|
-
return await info.context.data_loaders.session_num_traces_with_error.load(self.
|
|
106
|
+
return await info.context.data_loaders.session_num_traces_with_error.load(self.id)
|
|
49
107
|
|
|
50
108
|
@strawberry.field
|
|
51
109
|
async def first_input(
|
|
52
110
|
self,
|
|
53
111
|
info: Info[Context, None],
|
|
54
112
|
) -> Optional[SpanIOValue]:
|
|
55
|
-
record = await info.context.data_loaders.session_first_inputs.load(self.
|
|
113
|
+
record = await info.context.data_loaders.session_first_inputs.load(self.id)
|
|
56
114
|
if record is None:
|
|
57
115
|
return None
|
|
58
116
|
return SpanIOValue(
|
|
@@ -65,7 +123,7 @@ class ProjectSession(Node):
|
|
|
65
123
|
self,
|
|
66
124
|
info: Info[Context, None],
|
|
67
125
|
) -> Optional[SpanIOValue]:
|
|
68
|
-
record = await info.context.data_loaders.session_last_outputs.load(self.
|
|
126
|
+
record = await info.context.data_loaders.session_last_outputs.load(self.id)
|
|
69
127
|
if record is None:
|
|
70
128
|
return None
|
|
71
129
|
return SpanIOValue(
|
|
@@ -78,7 +136,7 @@ class ProjectSession(Node):
|
|
|
78
136
|
self,
|
|
79
137
|
info: Info[Context, None],
|
|
80
138
|
) -> TokenUsage:
|
|
81
|
-
usage = await info.context.data_loaders.session_token_usages.load(self.
|
|
139
|
+
usage = await info.context.data_loaders.session_token_usages.load(self.id)
|
|
82
140
|
return TokenUsage(
|
|
83
141
|
prompt=usage.prompt,
|
|
84
142
|
completion=usage.completion,
|
|
@@ -103,13 +161,12 @@ class ProjectSession(Node):
|
|
|
103
161
|
)
|
|
104
162
|
stmt = (
|
|
105
163
|
select(models.Trace)
|
|
106
|
-
.filter_by(project_session_rowid=self.
|
|
164
|
+
.filter_by(project_session_rowid=self.id)
|
|
107
165
|
.order_by(models.Trace.start_time)
|
|
108
|
-
.limit(first)
|
|
109
166
|
)
|
|
110
167
|
async with info.context.db() as session:
|
|
111
168
|
traces = await session.stream_scalars(stmt)
|
|
112
|
-
data = [Trace(
|
|
169
|
+
data = [Trace(id=trace.id, db_record=trace) async for trace in traces]
|
|
113
170
|
return connection_from_list(data=data, args=args)
|
|
114
171
|
|
|
115
172
|
@strawberry.field
|
|
@@ -119,18 +176,121 @@ class ProjectSession(Node):
|
|
|
119
176
|
probability: float,
|
|
120
177
|
) -> Optional[float]:
|
|
121
178
|
return await info.context.data_loaders.session_trace_latency_ms_quantile.load(
|
|
122
|
-
(self.
|
|
179
|
+
(self.id, probability)
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
@strawberry.field
|
|
183
|
+
async def cost_summary(
|
|
184
|
+
self,
|
|
185
|
+
info: Info[Context, None],
|
|
186
|
+
) -> SpanCostSummary:
|
|
187
|
+
loader = info.context.data_loaders.span_cost_summary_by_project_session
|
|
188
|
+
summary = await loader.load(self.id)
|
|
189
|
+
return SpanCostSummary(
|
|
190
|
+
prompt=CostBreakdown(
|
|
191
|
+
tokens=summary.prompt.tokens,
|
|
192
|
+
cost=summary.prompt.cost,
|
|
193
|
+
),
|
|
194
|
+
completion=CostBreakdown(
|
|
195
|
+
tokens=summary.completion.tokens,
|
|
196
|
+
cost=summary.completion.cost,
|
|
197
|
+
),
|
|
198
|
+
total=CostBreakdown(
|
|
199
|
+
tokens=summary.total.tokens,
|
|
200
|
+
cost=summary.total.cost,
|
|
201
|
+
),
|
|
123
202
|
)
|
|
124
203
|
|
|
204
|
+
@strawberry.field
|
|
205
|
+
async def cost_detail_summary_entries(
|
|
206
|
+
self,
|
|
207
|
+
info: Info[Context, None],
|
|
208
|
+
) -> list[SpanCostDetailSummaryEntry]:
|
|
209
|
+
loader = info.context.data_loaders.span_cost_detail_summary_entries_by_project_session
|
|
210
|
+
summary = await loader.load(self.id)
|
|
211
|
+
return [
|
|
212
|
+
SpanCostDetailSummaryEntry(
|
|
213
|
+
token_type=entry.token_type,
|
|
214
|
+
is_prompt=entry.is_prompt,
|
|
215
|
+
value=CostBreakdown(
|
|
216
|
+
tokens=entry.value.tokens,
|
|
217
|
+
cost=entry.value.cost,
|
|
218
|
+
),
|
|
219
|
+
)
|
|
220
|
+
for entry in summary
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
@strawberry.field
|
|
224
|
+
async def session_annotations(
|
|
225
|
+
self,
|
|
226
|
+
info: Info[Context, None],
|
|
227
|
+
) -> list[Annotated["ProjectSessionAnnotation", lazy(".ProjectSessionAnnotation")]]:
|
|
228
|
+
"""Get all annotations for this session."""
|
|
229
|
+
from .ProjectSessionAnnotation import ProjectSessionAnnotation
|
|
230
|
+
|
|
231
|
+
stmt = select(models.ProjectSessionAnnotation).filter_by(project_session_id=self.id)
|
|
232
|
+
async with info.context.db() as session:
|
|
233
|
+
annotations = await session.stream_scalars(stmt)
|
|
234
|
+
return [
|
|
235
|
+
ProjectSessionAnnotation(id=annotation.id, db_record=annotation)
|
|
236
|
+
async for annotation in annotations
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
@strawberry.field(
|
|
240
|
+
description="Summarizes each annotation (by name) associated with the session"
|
|
241
|
+
) # type: ignore
|
|
242
|
+
async def session_annotation_summaries(
|
|
243
|
+
self,
|
|
244
|
+
info: Info[Context, None],
|
|
245
|
+
filter: Optional[AnnotationFilter] = None,
|
|
246
|
+
) -> list[AnnotationSummary]:
|
|
247
|
+
"""
|
|
248
|
+
Retrieves and summarizes annotations associated with this span.
|
|
249
|
+
|
|
250
|
+
This method aggregates annotation data by name and label, calculating metrics
|
|
251
|
+
such as count of occurrences and sum of scores. The results are organized
|
|
252
|
+
into a structured format that can be easily converted to a DataFrame.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
info: GraphQL context information
|
|
256
|
+
filter: Optional filter to apply to annotations before processing
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
A list of AnnotationSummary objects, each containing:
|
|
260
|
+
- name: The name of the annotation
|
|
261
|
+
- data: A list of dictionaries with label statistics
|
|
262
|
+
"""
|
|
263
|
+
# Load all annotations for this span from the data loader
|
|
264
|
+
annotations = await info.context.data_loaders.session_annotations_by_session.load(self.id)
|
|
265
|
+
|
|
266
|
+
# Apply filter if provided to narrow down the annotations
|
|
267
|
+
if filter:
|
|
268
|
+
annotations = [
|
|
269
|
+
annotation for annotation in annotations if satisfies_filter(annotation, filter)
|
|
270
|
+
]
|
|
271
|
+
|
|
272
|
+
@dataclass
|
|
273
|
+
class Metrics:
|
|
274
|
+
record_count: int = 0
|
|
275
|
+
label_count: int = 0
|
|
276
|
+
score_sum: float = 0
|
|
277
|
+
score_count: int = 0
|
|
278
|
+
|
|
279
|
+
summaries: defaultdict[str, defaultdict[Optional[str], Metrics]] = defaultdict(
|
|
280
|
+
lambda: defaultdict(Metrics)
|
|
281
|
+
)
|
|
282
|
+
for annotation in annotations:
|
|
283
|
+
metrics = summaries[annotation.name][annotation.label]
|
|
284
|
+
metrics.record_count += 1
|
|
285
|
+
metrics.label_count += int(annotation.label is not None)
|
|
286
|
+
metrics.score_sum += annotation.score or 0
|
|
287
|
+
metrics.score_count += int(annotation.score is not None)
|
|
125
288
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
project_rowid=project_session.project_id,
|
|
132
|
-
end_time=project_session.end_time,
|
|
133
|
-
)
|
|
289
|
+
result: list[AnnotationSummary] = []
|
|
290
|
+
for name, label_metrics in summaries.items():
|
|
291
|
+
rows = [{"label": label, **asdict(metrics)} for label, metrics in label_metrics.items()]
|
|
292
|
+
result.append(AnnotationSummary(name=name, df=pd.DataFrame(rows), simple_avg=True))
|
|
293
|
+
return result
|
|
134
294
|
|
|
135
295
|
|
|
136
296
|
INPUT_VALUE = SpanAttributes.INPUT_VALUE.split(".")
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
from math import isfinite
|
|
2
|
+
from typing import TYPE_CHECKING, Annotated, Optional
|
|
3
|
+
|
|
4
|
+
import strawberry
|
|
5
|
+
from strawberry.relay import GlobalID, Node, NodeID
|
|
6
|
+
from strawberry.scalars import JSON
|
|
7
|
+
from strawberry.types import Info
|
|
8
|
+
|
|
9
|
+
from phoenix.db import models
|
|
10
|
+
from phoenix.server.api.context import Context
|
|
11
|
+
from phoenix.server.api.types.AnnotatorKind import AnnotatorKind
|
|
12
|
+
|
|
13
|
+
from .Annotation import Annotation
|
|
14
|
+
from .AnnotationSource import AnnotationSource
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from .ProjectSession import ProjectSession
|
|
18
|
+
from .User import User
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@strawberry.type
|
|
22
|
+
class ProjectSessionAnnotation(Node, Annotation):
|
|
23
|
+
id: NodeID[int]
|
|
24
|
+
db_record: strawberry.Private[Optional[models.ProjectSessionAnnotation]] = None
|
|
25
|
+
|
|
26
|
+
def __post_init__(self) -> None:
|
|
27
|
+
if self.db_record and self.id != self.db_record.id:
|
|
28
|
+
raise ValueError("ProjectSessionAnnotation ID mismatch")
|
|
29
|
+
|
|
30
|
+
@strawberry.field(description="Name of the annotation, e.g. 'helpfulness' or 'relevance'.") # type: ignore
|
|
31
|
+
async def name(
|
|
32
|
+
self,
|
|
33
|
+
info: Info[Context, None],
|
|
34
|
+
) -> str:
|
|
35
|
+
if self.db_record:
|
|
36
|
+
val = self.db_record.name
|
|
37
|
+
else:
|
|
38
|
+
val = await info.context.data_loaders.project_session_annotation_fields.load(
|
|
39
|
+
(self.id, models.ProjectSessionAnnotation.name),
|
|
40
|
+
)
|
|
41
|
+
return val
|
|
42
|
+
|
|
43
|
+
@strawberry.field(description="The kind of annotator that produced the annotation.") # type: ignore
|
|
44
|
+
async def annotator_kind(
|
|
45
|
+
self,
|
|
46
|
+
info: Info[Context, None],
|
|
47
|
+
) -> AnnotatorKind:
|
|
48
|
+
if self.db_record:
|
|
49
|
+
val = self.db_record.annotator_kind
|
|
50
|
+
else:
|
|
51
|
+
val = await info.context.data_loaders.project_session_annotation_fields.load(
|
|
52
|
+
(self.id, models.ProjectSessionAnnotation.annotator_kind),
|
|
53
|
+
)
|
|
54
|
+
return AnnotatorKind(val)
|
|
55
|
+
|
|
56
|
+
@strawberry.field(
|
|
57
|
+
description="Value of the annotation in the form of a string, e.g. 'helpful' or 'not helpful'. Note that the label is not necessarily binary." # noqa: E501
|
|
58
|
+
) # type: ignore
|
|
59
|
+
async def label(
|
|
60
|
+
self,
|
|
61
|
+
info: Info[Context, None],
|
|
62
|
+
) -> Optional[str]:
|
|
63
|
+
if self.db_record:
|
|
64
|
+
val = self.db_record.label
|
|
65
|
+
else:
|
|
66
|
+
val = await info.context.data_loaders.project_session_annotation_fields.load(
|
|
67
|
+
(self.id, models.ProjectSessionAnnotation.label),
|
|
68
|
+
)
|
|
69
|
+
return val
|
|
70
|
+
|
|
71
|
+
@strawberry.field(description="Value of the annotation in the form of a numeric score.") # type: ignore
|
|
72
|
+
async def score(
|
|
73
|
+
self,
|
|
74
|
+
info: Info[Context, None],
|
|
75
|
+
) -> Optional[float]:
|
|
76
|
+
if self.db_record:
|
|
77
|
+
val = self.db_record.score
|
|
78
|
+
else:
|
|
79
|
+
val = await info.context.data_loaders.project_session_annotation_fields.load(
|
|
80
|
+
(self.id, models.ProjectSessionAnnotation.score),
|
|
81
|
+
)
|
|
82
|
+
return val if val is not None and isfinite(val) else None
|
|
83
|
+
|
|
84
|
+
@strawberry.field(
|
|
85
|
+
description="The annotator's explanation for the annotation result (i.e. score or label, or both) given to the subject." # noqa: E501
|
|
86
|
+
) # type: ignore
|
|
87
|
+
async def explanation(
|
|
88
|
+
self,
|
|
89
|
+
info: Info[Context, None],
|
|
90
|
+
) -> Optional[str]:
|
|
91
|
+
if self.db_record:
|
|
92
|
+
val = self.db_record.explanation
|
|
93
|
+
else:
|
|
94
|
+
val = await info.context.data_loaders.project_session_annotation_fields.load(
|
|
95
|
+
(self.id, models.ProjectSessionAnnotation.explanation),
|
|
96
|
+
)
|
|
97
|
+
return val
|
|
98
|
+
|
|
99
|
+
@strawberry.field(description="Metadata about the annotation.") # type: ignore
|
|
100
|
+
async def metadata(
|
|
101
|
+
self,
|
|
102
|
+
info: Info[Context, None],
|
|
103
|
+
) -> JSON:
|
|
104
|
+
if self.db_record:
|
|
105
|
+
val = self.db_record.metadata_
|
|
106
|
+
else:
|
|
107
|
+
val = await info.context.data_loaders.project_session_annotation_fields.load(
|
|
108
|
+
(self.id, models.ProjectSessionAnnotation.metadata_),
|
|
109
|
+
)
|
|
110
|
+
return val
|
|
111
|
+
|
|
112
|
+
@strawberry.field(description="The identifier of the annotation.") # type: ignore
|
|
113
|
+
async def identifier(
|
|
114
|
+
self,
|
|
115
|
+
info: Info[Context, None],
|
|
116
|
+
) -> str:
|
|
117
|
+
if self.db_record:
|
|
118
|
+
val = self.db_record.identifier
|
|
119
|
+
else:
|
|
120
|
+
val = await info.context.data_loaders.project_session_annotation_fields.load(
|
|
121
|
+
(self.id, models.ProjectSessionAnnotation.identifier),
|
|
122
|
+
)
|
|
123
|
+
return val
|
|
124
|
+
|
|
125
|
+
@strawberry.field(description="The source of the annotation.") # type: ignore
|
|
126
|
+
async def source(
|
|
127
|
+
self,
|
|
128
|
+
info: Info[Context, None],
|
|
129
|
+
) -> AnnotationSource:
|
|
130
|
+
if self.db_record:
|
|
131
|
+
val = self.db_record.source
|
|
132
|
+
else:
|
|
133
|
+
val = await info.context.data_loaders.project_session_annotation_fields.load(
|
|
134
|
+
(self.id, models.ProjectSessionAnnotation.source),
|
|
135
|
+
)
|
|
136
|
+
return AnnotationSource(val)
|
|
137
|
+
|
|
138
|
+
@strawberry.field(description="The project session associated with the annotation.") # type: ignore
|
|
139
|
+
async def project_session_id(
|
|
140
|
+
self,
|
|
141
|
+
info: Info[Context, None],
|
|
142
|
+
) -> GlobalID:
|
|
143
|
+
from phoenix.server.api.types.ProjectSession import ProjectSession
|
|
144
|
+
|
|
145
|
+
if self.db_record:
|
|
146
|
+
project_session_id = self.db_record.project_session_id
|
|
147
|
+
else:
|
|
148
|
+
project_session_id = (
|
|
149
|
+
await info.context.data_loaders.project_session_annotation_fields.load(
|
|
150
|
+
(self.id, models.ProjectSessionAnnotation.project_session_id),
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
return GlobalID(type_name=ProjectSession.__name__, node_id=str(project_session_id))
|
|
154
|
+
|
|
155
|
+
@strawberry.field(description="The project session associated with the annotation.") # type: ignore
|
|
156
|
+
async def project_session(
|
|
157
|
+
self,
|
|
158
|
+
info: Info[Context, None],
|
|
159
|
+
) -> Annotated["ProjectSession", strawberry.lazy(".ProjectSession")]:
|
|
160
|
+
if self.db_record:
|
|
161
|
+
project_session_id = self.db_record.project_session_id
|
|
162
|
+
else:
|
|
163
|
+
project_session_id = (
|
|
164
|
+
await info.context.data_loaders.project_session_annotation_fields.load(
|
|
165
|
+
(self.id, models.ProjectSessionAnnotation.project_session_id),
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
from .ProjectSession import ProjectSession
|
|
169
|
+
|
|
170
|
+
return ProjectSession(id=project_session_id)
|
|
171
|
+
|
|
172
|
+
@strawberry.field(description="The user that produced the annotation.") # type: ignore
|
|
173
|
+
async def user(
|
|
174
|
+
self,
|
|
175
|
+
info: Info[Context, None],
|
|
176
|
+
) -> Optional[Annotated["User", strawberry.lazy(".User")]]:
|
|
177
|
+
if self.db_record:
|
|
178
|
+
user_id = self.db_record.user_id
|
|
179
|
+
else:
|
|
180
|
+
user_id = await info.context.data_loaders.project_session_annotation_fields.load(
|
|
181
|
+
(self.id, models.ProjectSessionAnnotation.user_id),
|
|
182
|
+
)
|
|
183
|
+
if user_id is None:
|
|
184
|
+
return None
|
|
185
|
+
from .User import User
|
|
186
|
+
|
|
187
|
+
return User(id=user_id)
|
|
@@ -106,5 +106,5 @@ class ProjectTraceRetentionPolicy(Node):
|
|
|
106
106
|
project_rowids = await info.context.data_loaders.projects_by_trace_retention_policy_id.load(
|
|
107
107
|
self.id
|
|
108
108
|
)
|
|
109
|
-
data = [Project(
|
|
109
|
+
data = [Project(id=project_rowid) for project_rowid in project_rowids]
|
|
110
110
|
return connection_from_list(data=data, args=args)
|