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,10 +1,11 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from typing import Optional, cast
|
|
2
|
+
from typing import Any, Optional, Union, cast
|
|
3
3
|
|
|
4
4
|
import strawberry
|
|
5
5
|
from strawberry import UNSET
|
|
6
6
|
from strawberry.scalars import JSON
|
|
7
7
|
|
|
8
|
+
from phoenix.db import models
|
|
8
9
|
from phoenix.db.types.model_provider import ModelProvider
|
|
9
10
|
from phoenix.server.api.helpers.prompts.models import (
|
|
10
11
|
ContentPart,
|
|
@@ -12,11 +13,15 @@ from phoenix.server.api.helpers.prompts.models import (
|
|
|
12
13
|
PromptMessage,
|
|
13
14
|
PromptMessageRole,
|
|
14
15
|
PromptTemplateFormat,
|
|
16
|
+
PromptTemplateType,
|
|
15
17
|
RoleConversion,
|
|
16
18
|
TextContentPart,
|
|
17
19
|
ToolCallContentPart,
|
|
18
20
|
ToolCallFunction,
|
|
19
21
|
ToolResultContentPart,
|
|
22
|
+
normalize_response_format,
|
|
23
|
+
normalize_tools,
|
|
24
|
+
validate_invocation_parameters,
|
|
20
25
|
)
|
|
21
26
|
|
|
22
27
|
|
|
@@ -83,6 +88,52 @@ class ChatPromptVersionInput:
|
|
|
83
88
|
model_provider: ModelProvider
|
|
84
89
|
model_name: str
|
|
85
90
|
|
|
91
|
+
def __post_init__(self) -> None:
|
|
92
|
+
self.invocation_parameters = {
|
|
93
|
+
k: v for k, v in self.invocation_parameters.items() if v is not None
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
def to_orm_prompt_version(
|
|
97
|
+
self,
|
|
98
|
+
user_id: Optional[int],
|
|
99
|
+
) -> models.PromptVersion:
|
|
100
|
+
tool_definitions = [tool.definition for tool in self.tools]
|
|
101
|
+
tool_choice = cast(
|
|
102
|
+
Optional[Union[str, dict[str, Any]]],
|
|
103
|
+
cast(dict[str, Any], self.invocation_parameters).pop("tool_choice", None),
|
|
104
|
+
)
|
|
105
|
+
model_provider = ModelProvider(self.model_provider)
|
|
106
|
+
tools = (
|
|
107
|
+
normalize_tools(tool_definitions, model_provider, tool_choice)
|
|
108
|
+
if tool_definitions
|
|
109
|
+
else None
|
|
110
|
+
)
|
|
111
|
+
template = to_pydantic_prompt_chat_template_v1(self.template)
|
|
112
|
+
response_format = (
|
|
113
|
+
normalize_response_format(
|
|
114
|
+
self.response_format.definition,
|
|
115
|
+
model_provider,
|
|
116
|
+
)
|
|
117
|
+
if self.response_format
|
|
118
|
+
else None
|
|
119
|
+
)
|
|
120
|
+
invocation_parameters = validate_invocation_parameters(
|
|
121
|
+
self.invocation_parameters,
|
|
122
|
+
model_provider,
|
|
123
|
+
)
|
|
124
|
+
return models.PromptVersion(
|
|
125
|
+
description=self.description,
|
|
126
|
+
user_id=user_id,
|
|
127
|
+
template_type=PromptTemplateType.CHAT,
|
|
128
|
+
template_format=self.template_format,
|
|
129
|
+
template=template,
|
|
130
|
+
invocation_parameters=invocation_parameters,
|
|
131
|
+
tools=tools,
|
|
132
|
+
response_format=response_format,
|
|
133
|
+
model_provider=ModelProvider(self.model_provider),
|
|
134
|
+
model_name=self.model_name,
|
|
135
|
+
)
|
|
136
|
+
|
|
86
137
|
|
|
87
138
|
def to_pydantic_prompt_chat_template_v1(
|
|
88
139
|
prompt_chat_template_input: PromptChatTemplateInput,
|
|
@@ -3,7 +3,7 @@ from enum import Enum, auto
|
|
|
3
3
|
from typing import Any, Optional, Protocol
|
|
4
4
|
|
|
5
5
|
import strawberry
|
|
6
|
-
from sqlalchemy import and_, desc, nulls_last
|
|
6
|
+
from sqlalchemy import and_, desc, func, nulls_last, select
|
|
7
7
|
from sqlalchemy.orm import InstrumentedAttribute
|
|
8
8
|
from sqlalchemy.sql.expression import Select
|
|
9
9
|
from strawberry import UNSET
|
|
@@ -11,6 +11,7 @@ from typing_extensions import assert_never
|
|
|
11
11
|
|
|
12
12
|
import phoenix.trace.v1 as pb
|
|
13
13
|
from phoenix.db import models
|
|
14
|
+
from phoenix.db.helpers import truncate_name
|
|
14
15
|
from phoenix.server.api.types.pagination import CursorSortColumnDataType
|
|
15
16
|
from phoenix.server.api.types.SortDir import SortDir
|
|
16
17
|
from phoenix.trace.schemas import SpanID
|
|
@@ -27,13 +28,14 @@ class SpanColumn(Enum):
|
|
|
27
28
|
cumulativeTokenCountTotal = auto()
|
|
28
29
|
cumulativeTokenCountPrompt = auto()
|
|
29
30
|
cumulativeTokenCountCompletion = auto()
|
|
31
|
+
cumulativeTokenCostTotal = auto()
|
|
32
|
+
tokenCostTotal = auto()
|
|
30
33
|
|
|
31
34
|
@property
|
|
32
35
|
def column_name(self) -> str:
|
|
33
|
-
return f"{self.name}_span_sort_column"
|
|
36
|
+
return truncate_name(f"{self.name}_span_sort_column")
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
def orm_expression(self) -> Any:
|
|
38
|
+
def as_orm_expression(self, joined_table: Optional[Any] = None) -> Any:
|
|
37
39
|
expr: Any
|
|
38
40
|
if self is SpanColumn.startTime:
|
|
39
41
|
expr = models.Span.start_time
|
|
@@ -56,6 +58,11 @@ class SpanColumn(Enum):
|
|
|
56
58
|
expr = models.Span.cumulative_llm_token_count_prompt
|
|
57
59
|
elif self is SpanColumn.cumulativeTokenCountCompletion:
|
|
58
60
|
expr = models.Span.cumulative_llm_token_count_completion
|
|
61
|
+
elif self is SpanColumn.tokenCostTotal:
|
|
62
|
+
expr = models.SpanCost.total_cost
|
|
63
|
+
elif self is SpanColumn.cumulativeTokenCostTotal:
|
|
64
|
+
assert joined_table is not None
|
|
65
|
+
expr = joined_table.c.cumulative_total_cost
|
|
59
66
|
else:
|
|
60
67
|
assert_never(self)
|
|
61
68
|
return expr.label(self.column_name)
|
|
@@ -73,12 +80,41 @@ class SpanColumn(Enum):
|
|
|
73
80
|
or self is SpanColumn.tokenCountTotal
|
|
74
81
|
or self is SpanColumn.tokenCountPrompt
|
|
75
82
|
or self is SpanColumn.tokenCountCompletion
|
|
83
|
+
or self is SpanColumn.tokenCostTotal
|
|
84
|
+
or self is SpanColumn.cumulativeTokenCostTotal
|
|
76
85
|
):
|
|
77
86
|
return CursorSortColumnDataType.FLOAT
|
|
78
87
|
if self is SpanColumn.startTime or self is SpanColumn.endTime:
|
|
79
88
|
return CursorSortColumnDataType.DATETIME
|
|
80
89
|
assert_never(self)
|
|
81
90
|
|
|
91
|
+
def join_tables(self, stmt: Select[Any]) -> tuple[Select[Any], Any]:
|
|
92
|
+
"""
|
|
93
|
+
If needed, joins tables required for the sort column.
|
|
94
|
+
"""
|
|
95
|
+
if self is SpanColumn.tokenCostTotal:
|
|
96
|
+
return stmt.join_from(
|
|
97
|
+
models.Span,
|
|
98
|
+
models.SpanCost,
|
|
99
|
+
onclause=models.SpanCost.span_rowid == models.Span.id,
|
|
100
|
+
), models.SpanCost
|
|
101
|
+
if self is SpanColumn.cumulativeTokenCostTotal:
|
|
102
|
+
trace_costs = (
|
|
103
|
+
select(
|
|
104
|
+
func.sum(models.SpanCost.total_cost).label("cumulative_total_cost"),
|
|
105
|
+
models.SpanCost.trace_rowid,
|
|
106
|
+
)
|
|
107
|
+
.select_from(models.SpanCost)
|
|
108
|
+
.group_by(models.SpanCost.trace_rowid)
|
|
109
|
+
.subquery()
|
|
110
|
+
)
|
|
111
|
+
stmt = stmt.join(
|
|
112
|
+
trace_costs,
|
|
113
|
+
onclause=models.Span.trace_rowid == trace_costs.c.trace_rowid,
|
|
114
|
+
)
|
|
115
|
+
return stmt, trace_costs
|
|
116
|
+
return stmt, None
|
|
117
|
+
|
|
82
118
|
|
|
83
119
|
@strawberry.enum
|
|
84
120
|
class EvalAttr(Enum):
|
|
@@ -139,13 +175,14 @@ class SpanSort:
|
|
|
139
175
|
|
|
140
176
|
def update_orm_expr(self, stmt: Select[Any]) -> SpanSortConfig:
|
|
141
177
|
if (col := self.col) and not self.eval_result_key:
|
|
142
|
-
|
|
178
|
+
stmt, joined_table = col.join_tables(stmt)
|
|
179
|
+
expr = col.as_orm_expression(joined_table)
|
|
143
180
|
stmt = stmt.add_columns(expr)
|
|
144
181
|
if self.dir == SortDir.desc:
|
|
145
182
|
expr = desc(expr)
|
|
146
183
|
return SpanSortConfig(
|
|
147
184
|
stmt=stmt.order_by(nulls_last(expr)),
|
|
148
|
-
orm_expression=col.
|
|
185
|
+
orm_expression=col.as_orm_expression(joined_table),
|
|
149
186
|
dir=self.dir,
|
|
150
187
|
column_name=col.column_name,
|
|
151
188
|
column_data_type=col.data_type,
|
|
@@ -163,7 +200,7 @@ class SpanSort:
|
|
|
163
200
|
models.SpanAnnotation.span_rowid == models.Span.id,
|
|
164
201
|
models.SpanAnnotation.name == eval_name,
|
|
165
202
|
),
|
|
166
|
-
).order_by(expr)
|
|
203
|
+
).order_by(nulls_last(expr))
|
|
167
204
|
return SpanSortConfig(
|
|
168
205
|
stmt=stmt,
|
|
169
206
|
orm_expression=eval_result_key.attr.orm_expression,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
import strawberry
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@strawberry.enum
|
|
7
|
+
class TimeBinScale(Enum):
|
|
8
|
+
MINUTE = "minute"
|
|
9
|
+
HOUR = "hour"
|
|
10
|
+
DAY = "day"
|
|
11
|
+
WEEK = "week"
|
|
12
|
+
MONTH = "month"
|
|
13
|
+
YEAR = "year"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@strawberry.input
|
|
17
|
+
class TimeBinConfig:
|
|
18
|
+
scale: TimeBinScale = strawberry.field(
|
|
19
|
+
default=TimeBinScale.HOUR, description="The scale of time bins for aggregation."
|
|
20
|
+
)
|
|
21
|
+
utc_offset_minutes: int = strawberry.field(
|
|
22
|
+
default=0, description="Offset in minutes from UTC for local time binning."
|
|
23
|
+
)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
import strawberry
|
|
4
|
+
from strawberry.relay import GlobalID
|
|
5
|
+
from strawberry.scalars import JSON
|
|
6
|
+
|
|
7
|
+
from phoenix.server.api.exceptions import BadRequest
|
|
8
|
+
from phoenix.server.api.types.AnnotationSource import AnnotationSource
|
|
9
|
+
from phoenix.server.api.types.AnnotatorKind import AnnotatorKind
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@strawberry.input
|
|
13
|
+
class UpdateAnnotationInput:
|
|
14
|
+
id: GlobalID
|
|
15
|
+
name: str
|
|
16
|
+
annotator_kind: AnnotatorKind = AnnotatorKind.HUMAN
|
|
17
|
+
label: Optional[str] = None
|
|
18
|
+
score: Optional[float] = None
|
|
19
|
+
explanation: Optional[str] = None
|
|
20
|
+
metadata: JSON = strawberry.field(default_factory=dict)
|
|
21
|
+
source: AnnotationSource = AnnotationSource.APP
|
|
22
|
+
|
|
23
|
+
def __post_init__(self) -> None:
|
|
24
|
+
self.name = self.name.strip()
|
|
25
|
+
if isinstance(self.label, str):
|
|
26
|
+
self.label = self.label.strip()
|
|
27
|
+
if not self.label:
|
|
28
|
+
self.label = None
|
|
29
|
+
if isinstance(self.explanation, str):
|
|
30
|
+
self.explanation = self.explanation.strip()
|
|
31
|
+
if not self.explanation:
|
|
32
|
+
self.explanation = None
|
|
33
|
+
if self.score is None and not self.label and not self.explanation:
|
|
34
|
+
raise BadRequest("At least one of score, label, or explanation must be not null/empty.")
|
|
@@ -5,10 +5,16 @@ from phoenix.server.api.mutations.api_key_mutations import ApiKeyMutationMixin
|
|
|
5
5
|
from phoenix.server.api.mutations.chat_mutations import (
|
|
6
6
|
ChatCompletionMutationMixin,
|
|
7
7
|
)
|
|
8
|
+
from phoenix.server.api.mutations.dataset_label_mutations import DatasetLabelMutationMixin
|
|
8
9
|
from phoenix.server.api.mutations.dataset_mutations import DatasetMutationMixin
|
|
10
|
+
from phoenix.server.api.mutations.dataset_split_mutations import DatasetSplitMutationMixin
|
|
9
11
|
from phoenix.server.api.mutations.experiment_mutations import ExperimentMutationMixin
|
|
10
12
|
from phoenix.server.api.mutations.export_events_mutations import ExportEventsMutationMixin
|
|
13
|
+
from phoenix.server.api.mutations.model_mutations import ModelMutationMixin
|
|
11
14
|
from phoenix.server.api.mutations.project_mutations import ProjectMutationMixin
|
|
15
|
+
from phoenix.server.api.mutations.project_session_annotations_mutations import (
|
|
16
|
+
ProjectSessionAnnotationMutationMixin,
|
|
17
|
+
)
|
|
12
18
|
from phoenix.server.api.mutations.project_trace_retention_policy_mutations import (
|
|
13
19
|
ProjectTraceRetentionPolicyMutationMixin,
|
|
14
20
|
)
|
|
@@ -26,15 +32,19 @@ class Mutation(
|
|
|
26
32
|
AnnotationConfigMutationMixin,
|
|
27
33
|
ApiKeyMutationMixin,
|
|
28
34
|
ChatCompletionMutationMixin,
|
|
35
|
+
DatasetLabelMutationMixin,
|
|
29
36
|
DatasetMutationMixin,
|
|
37
|
+
DatasetSplitMutationMixin,
|
|
30
38
|
ExperimentMutationMixin,
|
|
31
39
|
ExportEventsMutationMixin,
|
|
40
|
+
ModelMutationMixin,
|
|
32
41
|
ProjectMutationMixin,
|
|
33
42
|
ProjectTraceRetentionPolicyMutationMixin,
|
|
34
43
|
PromptMutationMixin,
|
|
35
44
|
PromptVersionTagMutationMixin,
|
|
36
45
|
PromptLabelMutationMixin,
|
|
37
46
|
SpanAnnotationMutationMixin,
|
|
47
|
+
ProjectSessionAnnotationMutationMixin,
|
|
38
48
|
TraceAnnotationMutationMixin,
|
|
39
49
|
TraceMutationMixin,
|
|
40
50
|
UserMutationMixin,
|
|
@@ -23,7 +23,7 @@ from phoenix.db.types.annotation_configs import (
|
|
|
23
23
|
from phoenix.db.types.annotation_configs import (
|
|
24
24
|
FreeformAnnotationConfig as FreeformAnnotationConfigModel,
|
|
25
25
|
)
|
|
26
|
-
from phoenix.server.api.auth import IsNotReadOnly
|
|
26
|
+
from phoenix.server.api.auth import IsNotReadOnly, IsNotViewer
|
|
27
27
|
from phoenix.server.api.context import Context
|
|
28
28
|
from phoenix.server.api.exceptions import BadRequest, Conflict, NotFound
|
|
29
29
|
from phoenix.server.api.queries import Query
|
|
@@ -197,7 +197,7 @@ def _to_pydantic_freeform_annotation_config(
|
|
|
197
197
|
|
|
198
198
|
@strawberry.type
|
|
199
199
|
class AnnotationConfigMutationMixin:
|
|
200
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore[misc]
|
|
200
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer]) # type: ignore[misc]
|
|
201
201
|
async def create_annotation_config(
|
|
202
202
|
self,
|
|
203
203
|
info: Info[Context, None],
|
|
@@ -236,7 +236,7 @@ class AnnotationConfigMutationMixin:
|
|
|
236
236
|
annotation_config=to_gql_annotation_config(annotation_config),
|
|
237
237
|
)
|
|
238
238
|
|
|
239
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore[misc]
|
|
239
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer]) # type: ignore[misc]
|
|
240
240
|
async def update_annotation_config(
|
|
241
241
|
self,
|
|
242
242
|
info: Info[Context, None],
|
|
@@ -285,7 +285,7 @@ class AnnotationConfigMutationMixin:
|
|
|
285
285
|
annotation_config=to_gql_annotation_config(annotation_config),
|
|
286
286
|
)
|
|
287
287
|
|
|
288
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore[misc]
|
|
288
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer]) # type: ignore[misc]
|
|
289
289
|
async def delete_annotation_configs(
|
|
290
290
|
self,
|
|
291
291
|
info: Info[Context, None],
|
|
@@ -317,7 +317,7 @@ class AnnotationConfigMutationMixin:
|
|
|
317
317
|
],
|
|
318
318
|
)
|
|
319
319
|
|
|
320
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore[misc]
|
|
320
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer]) # type: ignore[misc]
|
|
321
321
|
async def add_annotation_config_to_project(
|
|
322
322
|
self,
|
|
323
323
|
info: Info[Context, None],
|
|
@@ -374,10 +374,10 @@ class AnnotationConfigMutationMixin:
|
|
|
374
374
|
)
|
|
375
375
|
return AddAnnotationConfigToProjectPayload(
|
|
376
376
|
query=Query(),
|
|
377
|
-
project=Project(
|
|
377
|
+
project=Project(id=project_id),
|
|
378
378
|
)
|
|
379
379
|
|
|
380
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore[misc]
|
|
380
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer]) # type: ignore[misc]
|
|
381
381
|
async def remove_annotation_config_from_project(
|
|
382
382
|
self,
|
|
383
383
|
info: Info[Context, None],
|
|
@@ -409,5 +409,5 @@ class AnnotationConfigMutationMixin:
|
|
|
409
409
|
raise NotFound("Could not find one or more input project annotation configs")
|
|
410
410
|
return RemoveAnnotationConfigFromProjectPayload(
|
|
411
411
|
query=Query(),
|
|
412
|
-
project=Project(
|
|
412
|
+
project=Project(id=project_id),
|
|
413
413
|
)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from datetime import datetime, timezone
|
|
2
|
-
from typing import Optional
|
|
2
|
+
from typing import Literal, Optional
|
|
3
3
|
|
|
4
4
|
import strawberry
|
|
5
5
|
from sqlalchemy import select
|
|
@@ -7,8 +7,9 @@ from strawberry import UNSET
|
|
|
7
7
|
from strawberry.relay import GlobalID
|
|
8
8
|
from strawberry.types import Info
|
|
9
9
|
|
|
10
|
-
from phoenix.db import
|
|
11
|
-
from phoenix.
|
|
10
|
+
from phoenix.db import models
|
|
11
|
+
from phoenix.db.models import UserRoleName
|
|
12
|
+
from phoenix.server.api.auth import IsAdmin, IsLocked, IsNotReadOnly, IsNotViewer
|
|
12
13
|
from phoenix.server.api.context import Context
|
|
13
14
|
from phoenix.server.api.exceptions import Unauthorized
|
|
14
15
|
from phoenix.server.api.queries import Query
|
|
@@ -60,18 +61,18 @@ class DeleteApiKeyMutationPayload:
|
|
|
60
61
|
|
|
61
62
|
@strawberry.type
|
|
62
63
|
class ApiKeyMutationMixin:
|
|
63
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsAdmin, IsLocked]) # type: ignore
|
|
64
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer, IsAdmin, IsLocked]) # type: ignore
|
|
64
65
|
async def create_system_api_key(
|
|
65
66
|
self, info: Info[Context, None], input: CreateApiKeyInput
|
|
66
67
|
) -> CreateSystemApiKeyMutationPayload:
|
|
67
68
|
assert (token_store := info.context.token_store) is not None
|
|
68
|
-
user_role =
|
|
69
|
+
user_role: UserRoleName = "SYSTEM"
|
|
69
70
|
async with info.context.db() as session:
|
|
70
71
|
# Get the system user - note this could be pushed into a dataloader
|
|
71
72
|
system_user = await session.scalar(
|
|
72
73
|
select(models.User)
|
|
73
74
|
.join(models.UserRole) # Join User with UserRole
|
|
74
|
-
.where(models.UserRole.name == user_role
|
|
75
|
+
.where(models.UserRole.name == user_role) # Filter where role is SYSTEM
|
|
75
76
|
.order_by(models.User.id)
|
|
76
77
|
.limit(1)
|
|
77
78
|
)
|
|
@@ -91,13 +92,7 @@ class ApiKeyMutationMixin:
|
|
|
91
92
|
token, token_id = await token_store.create_api_key(claims)
|
|
92
93
|
return CreateSystemApiKeyMutationPayload(
|
|
93
94
|
jwt=token,
|
|
94
|
-
api_key=SystemApiKey(
|
|
95
|
-
id_attr=int(token_id),
|
|
96
|
-
name=input.name,
|
|
97
|
-
description=input.description or None,
|
|
98
|
-
created_at=issued_at,
|
|
99
|
-
expires_at=input.expires_at or None,
|
|
100
|
-
),
|
|
95
|
+
api_key=SystemApiKey(id=int(token_id)),
|
|
101
96
|
query=Query(),
|
|
102
97
|
)
|
|
103
98
|
|
|
@@ -112,12 +107,20 @@ class ApiKeyMutationMixin:
|
|
|
112
107
|
except AttributeError:
|
|
113
108
|
raise ValueError("User not found")
|
|
114
109
|
issued_at = datetime.now(timezone.utc)
|
|
110
|
+
# Determine user role for API key
|
|
111
|
+
user_role: Literal["ADMIN", "MEMBER", "VIEWER"]
|
|
112
|
+
if user.is_admin:
|
|
113
|
+
user_role = "ADMIN"
|
|
114
|
+
elif user.is_viewer:
|
|
115
|
+
user_role = "VIEWER"
|
|
116
|
+
else:
|
|
117
|
+
user_role = "MEMBER"
|
|
115
118
|
claims = ApiKeyClaims(
|
|
116
119
|
subject=user.identity,
|
|
117
120
|
issued_at=issued_at,
|
|
118
121
|
expiration_time=input.expires_at or None,
|
|
119
122
|
attributes=ApiKeyAttributes(
|
|
120
|
-
user_role=
|
|
123
|
+
user_role=user_role,
|
|
121
124
|
name=input.name,
|
|
122
125
|
description=input.description,
|
|
123
126
|
),
|
|
@@ -125,18 +128,11 @@ class ApiKeyMutationMixin:
|
|
|
125
128
|
token, token_id = await token_store.create_api_key(claims)
|
|
126
129
|
return CreateUserApiKeyMutationPayload(
|
|
127
130
|
jwt=token,
|
|
128
|
-
api_key=UserApiKey(
|
|
129
|
-
id_attr=int(token_id),
|
|
130
|
-
name=input.name,
|
|
131
|
-
description=input.description or None,
|
|
132
|
-
created_at=issued_at,
|
|
133
|
-
expires_at=input.expires_at or None,
|
|
134
|
-
user_id=int(user.identity),
|
|
135
|
-
),
|
|
131
|
+
api_key=UserApiKey(id=int(token_id)),
|
|
136
132
|
query=Query(),
|
|
137
133
|
)
|
|
138
134
|
|
|
139
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsAdmin]) # type: ignore
|
|
135
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer, IsAdmin]) # type: ignore
|
|
140
136
|
async def delete_system_api_key(
|
|
141
137
|
self, info: Info[Context, None], input: DeleteApiKeyInput
|
|
142
138
|
) -> DeleteApiKeyMutationPayload:
|