arize-phoenix 11.23.1__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-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/METADATA +61 -36
- {arize_phoenix-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/RECORD +212 -162
- {arize_phoenix-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/WHEEL +1 -1
- {arize_phoenix-11.23.1.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 +2 -1
- phoenix/auth.py +27 -2
- phoenix/config.py +1594 -81
- phoenix/db/README.md +546 -28
- phoenix/db/bulk_inserter.py +119 -116
- phoenix/db/engines.py +140 -33
- phoenix/db/facilitator.py +22 -1
- phoenix/db/helpers.py +818 -65
- phoenix/db/iam_auth.py +64 -0
- phoenix/db/insertion/dataset.py +133 -1
- phoenix/db/insertion/document_annotation.py +9 -6
- phoenix/db/insertion/evaluation.py +2 -3
- phoenix/db/insertion/helpers.py +2 -2
- phoenix/db/insertion/session_annotation.py +176 -0
- phoenix/db/insertion/span_annotation.py +3 -4
- phoenix/db/insertion/trace_annotation.py +3 -4
- phoenix/db/insertion/types.py +41 -18
- 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/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 +364 -56
- phoenix/db/pg_config.py +10 -0
- phoenix/db/types/trace_retention.py +7 -6
- phoenix/experiments/functions.py +69 -19
- phoenix/inferences/inferences.py +1 -2
- phoenix/server/api/auth.py +9 -0
- phoenix/server/api/auth_messages.py +46 -0
- phoenix/server/api/context.py +60 -0
- phoenix/server/api/dataloaders/__init__.py +36 -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/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_summary_by_experiment_repeated_run_group.py +64 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_project.py +28 -14
- phoenix/server/api/dataloaders/span_costs.py +3 -9
- 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/exceptions.py +5 -1
- phoenix/server/api/helpers/playground_clients.py +263 -83
- phoenix/server/api/helpers/playground_spans.py +2 -1
- phoenix/server/api/helpers/playground_users.py +26 -0
- phoenix/server/api/helpers/prompts/conversions/google.py +103 -0
- phoenix/server/api/helpers/prompts/models.py +61 -19
- phoenix/server/api/input_types/{SpanAnnotationFilter.py → AnnotationFilter.py} +22 -14
- phoenix/server/api/input_types/ChatCompletionInput.py +3 -0
- phoenix/server/api/input_types/CreateProjectSessionAnnotationInput.py +37 -0
- phoenix/server/api/input_types/DatasetFilter.py +5 -2
- phoenix/server/api/input_types/ExperimentRunSort.py +237 -0
- phoenix/server/api/input_types/GenerativeModelInput.py +3 -0
- phoenix/server/api/input_types/ProjectSessionSort.py +158 -1
- phoenix/server/api/input_types/PromptVersionInput.py +47 -1
- phoenix/server/api/input_types/SpanSort.py +3 -2
- phoenix/server/api/input_types/UpdateAnnotationInput.py +34 -0
- phoenix/server/api/input_types/UserRoleInput.py +1 -0
- phoenix/server/api/mutations/__init__.py +8 -0
- phoenix/server/api/mutations/annotation_config_mutations.py +8 -8
- phoenix/server/api/mutations/api_key_mutations.py +15 -20
- phoenix/server/api/mutations/chat_mutations.py +106 -37
- 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 +11 -9
- phoenix/server/api/mutations/project_mutations.py +4 -4
- 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 +13 -8
- phoenix/server/api/mutations/trace_mutations.py +3 -3
- phoenix/server/api/mutations/user_mutations.py +55 -26
- phoenix/server/api/queries.py +501 -617
- phoenix/server/api/routers/__init__.py +2 -2
- phoenix/server/api/routers/auth.py +141 -87
- phoenix/server/api/routers/ldap.py +229 -0
- phoenix/server/api/routers/oauth2.py +349 -101
- phoenix/server/api/routers/v1/__init__.py +22 -4
- phoenix/server/api/routers/v1/annotation_configs.py +19 -30
- phoenix/server/api/routers/v1/annotations.py +455 -13
- phoenix/server/api/routers/v1/datasets.py +355 -68
- phoenix/server/api/routers/v1/documents.py +142 -0
- phoenix/server/api/routers/v1/evaluations.py +20 -28
- phoenix/server/api/routers/v1/experiment_evaluations.py +16 -6
- phoenix/server/api/routers/v1/experiment_runs.py +335 -59
- phoenix/server/api/routers/v1/experiments.py +475 -47
- phoenix/server/api/routers/v1/projects.py +16 -50
- phoenix/server/api/routers/v1/prompts.py +50 -39
- phoenix/server/api/routers/v1/sessions.py +108 -0
- phoenix/server/api/routers/v1/spans.py +156 -96
- phoenix/server/api/routers/v1/traces.py +51 -77
- phoenix/server/api/routers/v1/users.py +64 -24
- phoenix/server/api/routers/v1/utils.py +3 -7
- phoenix/server/api/subscriptions.py +257 -93
- 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/Dataset.py +199 -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 +215 -68
- phoenix/server/api/types/ExperimentComparison.py +3 -9
- phoenix/server/api/types/ExperimentRepeatedRunGroup.py +155 -0
- phoenix/server/api/types/ExperimentRepeatedRunGroupAnnotationSummary.py +9 -0
- phoenix/server/api/types/ExperimentRun.py +120 -70
- phoenix/server/api/types/ExperimentRunAnnotation.py +158 -39
- phoenix/server/api/types/GenerativeModel.py +95 -42
- phoenix/server/api/types/GenerativeProvider.py +1 -1
- phoenix/server/api/types/ModelInterface.py +7 -2
- phoenix/server/api/types/PlaygroundModel.py +12 -2
- phoenix/server/api/types/Project.py +218 -185
- phoenix/server/api/types/ProjectSession.py +146 -29
- 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/Span.py +130 -123
- phoenix/server/api/types/SpanAnnotation.py +189 -42
- phoenix/server/api/types/SystemApiKey.py +65 -1
- phoenix/server/api/types/Trace.py +184 -53
- phoenix/server/api/types/TraceAnnotation.py +149 -50
- phoenix/server/api/types/User.py +128 -33
- 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 +154 -36
- phoenix/server/authorization.py +5 -4
- phoenix/server/bearer_auth.py +13 -5
- phoenix/server/cost_tracking/cost_model_lookup.py +42 -14
- phoenix/server/cost_tracking/model_cost_manifest.json +1085 -194
- phoenix/server/daemons/generative_model_store.py +61 -9
- phoenix/server/daemons/span_cost_calculator.py +10 -8
- phoenix/server/dml_event.py +13 -0
- phoenix/server/email/sender.py +29 -2
- phoenix/server/grpc_server.py +9 -9
- phoenix/server/jwt_store.py +8 -6
- phoenix/server/ldap.py +1449 -0
- phoenix/server/main.py +9 -3
- phoenix/server/oauth2.py +330 -12
- phoenix/server/prometheus.py +43 -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 +51 -53
- phoenix/server/static/assets/components-BreFUQQa.js +6702 -0
- phoenix/server/static/assets/{index-BPCwGQr8.js → index-CTQoemZv.js} +42 -35
- 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-Bw30oz1A.js → vendor-recharts-V9cwpXsm.js} +7 -7
- phoenix/server/static/assets/{vendor-shiki-DZajAPeq.js → vendor-shiki-Do--csgv.js} +1 -1
- phoenix/server/static/assets/vendor-three-CmB8bl_y.js +3840 -0
- phoenix/server/templates/index.html +7 -1
- phoenix/server/thread_server.py +1 -2
- phoenix/server/utils.py +74 -0
- phoenix/session/client.py +55 -1
- phoenix/session/data_extractor.py +5 -0
- phoenix/session/evaluation.py +8 -4
- phoenix/session/session.py +44 -8
- phoenix/settings.py +2 -0
- phoenix/trace/attributes.py +80 -13
- phoenix/trace/dsl/query.py +2 -0
- phoenix/trace/projects.py +5 -0
- phoenix/utilities/template_formatters.py +1 -1
- phoenix/version.py +1 -1
- phoenix/server/api/types/Evaluation.py +0 -39
- phoenix/server/static/assets/components-D0DWAf0l.js +0 -5650
- phoenix/server/static/assets/pages-Creyamao.js +0 -8612
- phoenix/server/static/assets/vendor-CU36oj8y.js +0 -905
- phoenix/server/static/assets/vendor-CqDb5u4o.css +0 -1
- phoenix/server/static/assets/vendor-arizeai-Ctgw0e1G.js +0 -168
- phoenix/server/static/assets/vendor-codemirror-Cojjzqb9.js +0 -25
- phoenix/server/static/assets/vendor-three-BLWp5bic.js +0 -2998
- phoenix/utilities/deprecation.py +0 -31
- {arize_phoenix-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/entry_points.txt +0 -0
- {arize_phoenix-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import operator
|
|
2
|
+
from enum import Enum, auto
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
import strawberry
|
|
6
|
+
from sqlalchemy import ColumnElement, Select, and_, func, literal, or_, select, tuple_
|
|
7
|
+
from sqlalchemy.sql.selectable import NamedFromClause
|
|
8
|
+
from strawberry import Maybe
|
|
9
|
+
from typing_extensions import assert_never
|
|
10
|
+
|
|
11
|
+
from phoenix.db import models
|
|
12
|
+
from phoenix.server.api.types.pagination import (
|
|
13
|
+
Cursor,
|
|
14
|
+
CursorSortColumn,
|
|
15
|
+
CursorSortColumnDataType,
|
|
16
|
+
CursorSortColumnValue,
|
|
17
|
+
)
|
|
18
|
+
from phoenix.server.api.types.SortDir import SortDir
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@strawberry.enum
|
|
22
|
+
class ExperimentRunMetric(Enum):
|
|
23
|
+
latencyMs = auto()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@strawberry.input(one_of=True)
|
|
27
|
+
class ExperimentRunColumn:
|
|
28
|
+
metric: Maybe[ExperimentRunMetric]
|
|
29
|
+
annotation_name: Maybe[str]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@strawberry.input(description="The sort key and direction for experiment run connections")
|
|
33
|
+
class ExperimentRunSort:
|
|
34
|
+
col: ExperimentRunColumn
|
|
35
|
+
dir: SortDir
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_experiment_run_cursor(
|
|
39
|
+
run: models.ExperimentRun, annotation_score: Optional[float], sort: Optional[ExperimentRunSort]
|
|
40
|
+
) -> Cursor:
|
|
41
|
+
sort_column: Optional[CursorSortColumn] = None
|
|
42
|
+
if sort:
|
|
43
|
+
if sort.col.metric:
|
|
44
|
+
metric = sort.col.metric.value
|
|
45
|
+
assert metric is not None
|
|
46
|
+
if metric is ExperimentRunMetric.latencyMs:
|
|
47
|
+
sort_column = CursorSortColumn(
|
|
48
|
+
type=CursorSortColumnDataType.FLOAT,
|
|
49
|
+
value=run.latency_ms,
|
|
50
|
+
)
|
|
51
|
+
else:
|
|
52
|
+
assert_never(metric)
|
|
53
|
+
elif sort.col.annotation_name:
|
|
54
|
+
data_type = (
|
|
55
|
+
CursorSortColumnDataType.FLOAT
|
|
56
|
+
if annotation_score is not None
|
|
57
|
+
else CursorSortColumnDataType.NULL
|
|
58
|
+
)
|
|
59
|
+
sort_column = CursorSortColumn(
|
|
60
|
+
type=data_type,
|
|
61
|
+
value=annotation_score,
|
|
62
|
+
)
|
|
63
|
+
return Cursor(rowid=run.id, sort_column=sort_column)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def add_order_by_and_page_start_to_query(
|
|
67
|
+
query: Select[Any],
|
|
68
|
+
sort: Optional[ExperimentRunSort],
|
|
69
|
+
experiment_rowid: int,
|
|
70
|
+
after_experiment_run_rowid: Optional[int],
|
|
71
|
+
after_sort_column_value: Optional[CursorSortColumnValue] = None,
|
|
72
|
+
) -> Select[Any]:
|
|
73
|
+
mean_annotation_scores: Optional[NamedFromClause] = None
|
|
74
|
+
if sort and sort.col.annotation_name:
|
|
75
|
+
annotation_name = sort.col.annotation_name.value
|
|
76
|
+
assert annotation_name is not None
|
|
77
|
+
mean_annotation_scores = _get_mean_annotation_scores_subquery(annotation_name)
|
|
78
|
+
order_by_columns = _get_order_by_columns(
|
|
79
|
+
sort=sort, experiment_rowid=experiment_rowid, mean_annotation_scores=mean_annotation_scores
|
|
80
|
+
)
|
|
81
|
+
query = query.order_by(*order_by_columns)
|
|
82
|
+
if after_experiment_run_rowid is not None:
|
|
83
|
+
query = _add_after_expression(
|
|
84
|
+
query=query,
|
|
85
|
+
sort=sort,
|
|
86
|
+
experiment_run_rowid=after_experiment_run_rowid,
|
|
87
|
+
after_sort_column_value=after_sort_column_value,
|
|
88
|
+
mean_annotation_scores=mean_annotation_scores,
|
|
89
|
+
)
|
|
90
|
+
query = _add_joins_and_selects_to_query(
|
|
91
|
+
query=query,
|
|
92
|
+
sort=sort,
|
|
93
|
+
mean_annotation_scores=mean_annotation_scores,
|
|
94
|
+
)
|
|
95
|
+
return query
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _get_order_by_columns(
|
|
99
|
+
sort: Optional[ExperimentRunSort],
|
|
100
|
+
experiment_rowid: int,
|
|
101
|
+
mean_annotation_scores: Optional[NamedFromClause],
|
|
102
|
+
) -> tuple[ColumnElement[Any], ...]:
|
|
103
|
+
if not sort:
|
|
104
|
+
# Ideally, this would sort the runs by (example_id, repetition_number),
|
|
105
|
+
# but this would require making the cursor more complex or adding an additional query
|
|
106
|
+
# to handle the after cursor.
|
|
107
|
+
return (models.ExperimentRun.id.asc(),)
|
|
108
|
+
sort_direction = sort.dir
|
|
109
|
+
if sort.col.metric:
|
|
110
|
+
metric = sort.col.metric.value
|
|
111
|
+
assert metric is not None
|
|
112
|
+
if metric is ExperimentRunMetric.latencyMs:
|
|
113
|
+
if sort_direction is SortDir.asc:
|
|
114
|
+
return (models.ExperimentRun.latency_ms.asc(), models.ExperimentRun.id.asc())
|
|
115
|
+
else:
|
|
116
|
+
return (models.ExperimentRun.latency_ms.desc(), models.ExperimentRun.id.desc())
|
|
117
|
+
else:
|
|
118
|
+
assert_never(metric)
|
|
119
|
+
elif sort.col.annotation_name:
|
|
120
|
+
annotation_name = sort.col.annotation_name.value
|
|
121
|
+
assert annotation_name is not None
|
|
122
|
+
assert mean_annotation_scores is not None
|
|
123
|
+
if sort_direction is SortDir.asc:
|
|
124
|
+
return (
|
|
125
|
+
mean_annotation_scores.c.score.asc().nulls_last(),
|
|
126
|
+
models.ExperimentRun.id.asc(),
|
|
127
|
+
)
|
|
128
|
+
else:
|
|
129
|
+
return (
|
|
130
|
+
mean_annotation_scores.c.score.desc().nulls_last(),
|
|
131
|
+
models.ExperimentRun.id.desc(),
|
|
132
|
+
)
|
|
133
|
+
raise NotImplementedError
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _add_after_expression(
|
|
137
|
+
query: Select[Any],
|
|
138
|
+
sort: Optional[ExperimentRunSort],
|
|
139
|
+
experiment_run_rowid: int,
|
|
140
|
+
after_sort_column_value: Optional[CursorSortColumnValue],
|
|
141
|
+
mean_annotation_scores: Optional[NamedFromClause],
|
|
142
|
+
) -> Select[Any]:
|
|
143
|
+
if not sort:
|
|
144
|
+
# Ideally, this would return the runs sorted by (example_id, repetition_number),
|
|
145
|
+
# but this would require making the cursor more complex or adding an additional query.
|
|
146
|
+
return query.where(models.ExperimentRun.id > literal(experiment_run_rowid))
|
|
147
|
+
sort_direction = sort.dir
|
|
148
|
+
compare_fn = operator.gt if sort_direction is SortDir.asc else operator.lt
|
|
149
|
+
if sort.col.metric:
|
|
150
|
+
metric = sort.col.metric.value
|
|
151
|
+
assert metric is not None
|
|
152
|
+
if metric is ExperimentRunMetric.latencyMs:
|
|
153
|
+
assert after_sort_column_value is not None
|
|
154
|
+
return query.where(
|
|
155
|
+
compare_fn(
|
|
156
|
+
tuple_(models.ExperimentRun.latency_ms, models.ExperimentRun.id),
|
|
157
|
+
tuple_(
|
|
158
|
+
literal(after_sort_column_value),
|
|
159
|
+
literal(experiment_run_rowid),
|
|
160
|
+
),
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
else:
|
|
164
|
+
assert_never(metric)
|
|
165
|
+
elif sort.col.annotation_name:
|
|
166
|
+
annotation_name = sort.col.annotation_name.value
|
|
167
|
+
assert annotation_name is not None
|
|
168
|
+
assert mean_annotation_scores is not None
|
|
169
|
+
if after_sort_column_value is None:
|
|
170
|
+
return query.where(
|
|
171
|
+
and_(
|
|
172
|
+
compare_fn(models.ExperimentRun.id, literal(experiment_run_rowid)),
|
|
173
|
+
mean_annotation_scores.c.score.is_(None),
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
else:
|
|
177
|
+
return query.where(
|
|
178
|
+
or_(
|
|
179
|
+
compare_fn(
|
|
180
|
+
tuple_(mean_annotation_scores.c.score, models.ExperimentRun.id),
|
|
181
|
+
tuple_(
|
|
182
|
+
literal(after_sort_column_value),
|
|
183
|
+
literal(experiment_run_rowid),
|
|
184
|
+
),
|
|
185
|
+
),
|
|
186
|
+
mean_annotation_scores.c.score.is_(None),
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
raise NotImplementedError
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _get_mean_annotation_scores_subquery(annotation_name: str) -> NamedFromClause:
|
|
193
|
+
return (
|
|
194
|
+
select(
|
|
195
|
+
func.avg(models.ExperimentRunAnnotation.score).label("score"),
|
|
196
|
+
models.ExperimentRunAnnotation.experiment_run_id.label("experiment_run_id"),
|
|
197
|
+
)
|
|
198
|
+
.select_from(models.ExperimentRunAnnotation)
|
|
199
|
+
.join(
|
|
200
|
+
models.ExperimentRun,
|
|
201
|
+
models.ExperimentRunAnnotation.experiment_run_id == models.ExperimentRun.id,
|
|
202
|
+
)
|
|
203
|
+
.where(models.ExperimentRunAnnotation.name == annotation_name)
|
|
204
|
+
.group_by(models.ExperimentRunAnnotation.experiment_run_id)
|
|
205
|
+
.subquery()
|
|
206
|
+
.alias("mean_annotation_scores")
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _add_joins_and_selects_to_query(
|
|
211
|
+
query: Select[tuple[models.ExperimentRun]],
|
|
212
|
+
sort: Optional[ExperimentRunSort],
|
|
213
|
+
mean_annotation_scores: Optional[NamedFromClause],
|
|
214
|
+
) -> Select[tuple[models.ExperimentRun]]:
|
|
215
|
+
if not sort:
|
|
216
|
+
return query
|
|
217
|
+
if sort.col.metric:
|
|
218
|
+
metric = sort.col.metric.value
|
|
219
|
+
assert metric is not None
|
|
220
|
+
if metric is ExperimentRunMetric.latencyMs:
|
|
221
|
+
return query
|
|
222
|
+
else:
|
|
223
|
+
assert_never(metric)
|
|
224
|
+
elif sort.col.annotation_name:
|
|
225
|
+
annotation_name = sort.col.annotation_name.value
|
|
226
|
+
assert annotation_name is not None
|
|
227
|
+
assert mean_annotation_scores is not None
|
|
228
|
+
query = query.join(
|
|
229
|
+
mean_annotation_scores,
|
|
230
|
+
mean_annotation_scores.c.experiment_run_id == models.ExperimentRun.id,
|
|
231
|
+
isouter=True,
|
|
232
|
+
)
|
|
233
|
+
query = query.add_columns(
|
|
234
|
+
mean_annotation_scores.c.score.label("score")
|
|
235
|
+
) # the score must be in the select so that the value can be included in the cursor
|
|
236
|
+
return query
|
|
237
|
+
raise NotImplementedError
|
|
@@ -2,6 +2,7 @@ from typing import Optional
|
|
|
2
2
|
|
|
3
3
|
import strawberry
|
|
4
4
|
from strawberry import UNSET
|
|
5
|
+
from strawberry.scalars import JSON
|
|
5
6
|
|
|
6
7
|
from phoenix.server.api.types.GenerativeProvider import GenerativeProviderKey
|
|
7
8
|
|
|
@@ -19,3 +20,5 @@ class GenerativeModelInput:
|
|
|
19
20
|
""" The API version to use for the model. """
|
|
20
21
|
region: Optional[str] = UNSET
|
|
21
22
|
""" The region to use for the model. """
|
|
23
|
+
custom_headers: Optional[JSON] = UNSET
|
|
24
|
+
""" Custom headers to use for the model. """
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
1
2
|
from enum import Enum, auto
|
|
3
|
+
from typing import Any, Optional
|
|
2
4
|
|
|
3
5
|
import strawberry
|
|
6
|
+
from sqlalchemy import and_, desc, func, nulls_last, select
|
|
7
|
+
from sqlalchemy.orm import InstrumentedAttribute
|
|
8
|
+
from sqlalchemy.sql.expression import Select
|
|
9
|
+
from strawberry import UNSET
|
|
4
10
|
from typing_extensions import assert_never
|
|
5
11
|
|
|
12
|
+
from phoenix.db import models
|
|
13
|
+
from phoenix.db.helpers import truncate_name
|
|
6
14
|
from phoenix.server.api.types.pagination import CursorSortColumnDataType
|
|
7
15
|
from phoenix.server.api.types.SortDir import SortDir
|
|
8
16
|
|
|
@@ -15,6 +23,29 @@ class ProjectSessionColumn(Enum):
|
|
|
15
23
|
numTraces = auto()
|
|
16
24
|
costTotal = auto()
|
|
17
25
|
|
|
26
|
+
@property
|
|
27
|
+
def column_name(self) -> str:
|
|
28
|
+
return truncate_name(f"{self.name}_project_session_sort_column")
|
|
29
|
+
|
|
30
|
+
def as_orm_expression(self, joined_table: Optional[Any] = None) -> Any:
|
|
31
|
+
expr: Any
|
|
32
|
+
if self is ProjectSessionColumn.startTime:
|
|
33
|
+
expr = models.ProjectSession.start_time
|
|
34
|
+
elif self is ProjectSessionColumn.endTime:
|
|
35
|
+
expr = models.ProjectSession.end_time
|
|
36
|
+
elif self is ProjectSessionColumn.tokenCountTotal:
|
|
37
|
+
assert joined_table is not None
|
|
38
|
+
expr = joined_table.c.key
|
|
39
|
+
elif self is ProjectSessionColumn.numTraces:
|
|
40
|
+
assert joined_table is not None
|
|
41
|
+
expr = joined_table.c.key
|
|
42
|
+
elif self is ProjectSessionColumn.costTotal:
|
|
43
|
+
assert joined_table is not None
|
|
44
|
+
expr = joined_table.c.key
|
|
45
|
+
else:
|
|
46
|
+
assert_never(self)
|
|
47
|
+
return expr.label(self.column_name)
|
|
48
|
+
|
|
18
49
|
@property
|
|
19
50
|
def data_type(self) -> CursorSortColumnDataType:
|
|
20
51
|
if self is ProjectSessionColumn.tokenCountTotal or self is ProjectSessionColumn.numTraces:
|
|
@@ -25,8 +56,134 @@ class ProjectSessionColumn(Enum):
|
|
|
25
56
|
return CursorSortColumnDataType.FLOAT
|
|
26
57
|
assert_never(self)
|
|
27
58
|
|
|
59
|
+
def join_tables(self, stmt: Select[Any]) -> tuple[Select[Any], Any]:
|
|
60
|
+
"""
|
|
61
|
+
If needed, joins tables required for the sort column.
|
|
62
|
+
"""
|
|
63
|
+
if self is ProjectSessionColumn.tokenCountTotal:
|
|
64
|
+
sort_subq = (
|
|
65
|
+
select(
|
|
66
|
+
models.Trace.project_session_rowid.label("id"),
|
|
67
|
+
func.sum(models.Span.cumulative_llm_token_count_total).label("key"),
|
|
68
|
+
)
|
|
69
|
+
.join_from(models.Trace, models.Span)
|
|
70
|
+
.where(models.Span.parent_id.is_(None))
|
|
71
|
+
.group_by(models.Trace.project_session_rowid)
|
|
72
|
+
).subquery()
|
|
73
|
+
stmt = stmt.join(sort_subq, models.ProjectSession.id == sort_subq.c.id)
|
|
74
|
+
return stmt, sort_subq
|
|
75
|
+
if self is ProjectSessionColumn.numTraces:
|
|
76
|
+
sort_subq = (
|
|
77
|
+
select(
|
|
78
|
+
models.Trace.project_session_rowid.label("id"),
|
|
79
|
+
func.count(models.Trace.id).label("key"),
|
|
80
|
+
).group_by(models.Trace.project_session_rowid)
|
|
81
|
+
).subquery()
|
|
82
|
+
stmt = stmt.join(sort_subq, models.ProjectSession.id == sort_subq.c.id)
|
|
83
|
+
return stmt, sort_subq
|
|
84
|
+
if self is ProjectSessionColumn.costTotal:
|
|
85
|
+
sort_subq = (
|
|
86
|
+
select(
|
|
87
|
+
models.Trace.project_session_rowid.label("id"),
|
|
88
|
+
func.sum(models.SpanCost.total_cost).label("key"),
|
|
89
|
+
)
|
|
90
|
+
.join_from(
|
|
91
|
+
models.Trace,
|
|
92
|
+
models.SpanCost,
|
|
93
|
+
models.Trace.id == models.SpanCost.trace_rowid,
|
|
94
|
+
)
|
|
95
|
+
.group_by(models.Trace.project_session_rowid)
|
|
96
|
+
).subquery()
|
|
97
|
+
stmt = stmt.join(sort_subq, models.ProjectSession.id == sort_subq.c.id)
|
|
98
|
+
return stmt, sort_subq
|
|
99
|
+
return stmt, None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@strawberry.enum
|
|
103
|
+
class ProjectSessionAnnoAttr(Enum):
|
|
104
|
+
score = "score"
|
|
105
|
+
label = "label"
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def column_name(self) -> str:
|
|
109
|
+
return f"{self.value}_anno_sort_column"
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def orm_expression(self) -> Any:
|
|
113
|
+
expr: InstrumentedAttribute[Any]
|
|
114
|
+
if self is ProjectSessionAnnoAttr.score:
|
|
115
|
+
expr = models.ProjectSessionAnnotation.score
|
|
116
|
+
elif self is ProjectSessionAnnoAttr.label:
|
|
117
|
+
expr = models.ProjectSessionAnnotation.label
|
|
118
|
+
else:
|
|
119
|
+
assert_never(self)
|
|
120
|
+
return expr.label(self.column_name)
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def data_type(self) -> CursorSortColumnDataType:
|
|
124
|
+
if self is ProjectSessionAnnoAttr.label:
|
|
125
|
+
return CursorSortColumnDataType.STRING
|
|
126
|
+
if self is ProjectSessionAnnoAttr.score:
|
|
127
|
+
return CursorSortColumnDataType.FLOAT
|
|
128
|
+
assert_never(self)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@strawberry.input
|
|
132
|
+
class ProjectSessionAnnoResultKey:
|
|
133
|
+
name: str
|
|
134
|
+
attr: ProjectSessionAnnoAttr
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@dataclass(frozen=True)
|
|
138
|
+
class ProjectSessionSortConfig:
|
|
139
|
+
stmt: Select[Any]
|
|
140
|
+
orm_expression: Any
|
|
141
|
+
dir: SortDir
|
|
142
|
+
column_name: str
|
|
143
|
+
column_data_type: CursorSortColumnDataType
|
|
144
|
+
|
|
28
145
|
|
|
29
146
|
@strawberry.input(description="The sort key and direction for ProjectSession connections.")
|
|
30
147
|
class ProjectSessionSort:
|
|
31
|
-
col: ProjectSessionColumn
|
|
148
|
+
col: Optional[ProjectSessionColumn] = UNSET
|
|
149
|
+
anno_result_key: Optional[ProjectSessionAnnoResultKey] = UNSET
|
|
32
150
|
dir: SortDir
|
|
151
|
+
|
|
152
|
+
def update_orm_expr(self, stmt: Select[Any]) -> ProjectSessionSortConfig:
|
|
153
|
+
if (col := self.col) and not self.anno_result_key:
|
|
154
|
+
stmt, joined_table = col.join_tables(stmt)
|
|
155
|
+
expr = col.as_orm_expression(joined_table)
|
|
156
|
+
stmt = stmt.add_columns(expr)
|
|
157
|
+
if self.dir == SortDir.desc:
|
|
158
|
+
expr = desc(expr)
|
|
159
|
+
return ProjectSessionSortConfig(
|
|
160
|
+
stmt=stmt.order_by(nulls_last(expr)),
|
|
161
|
+
orm_expression=col.as_orm_expression(joined_table),
|
|
162
|
+
dir=self.dir,
|
|
163
|
+
column_name=col.column_name,
|
|
164
|
+
column_data_type=col.data_type,
|
|
165
|
+
)
|
|
166
|
+
if (anno_result_key := self.anno_result_key) and not col:
|
|
167
|
+
anno_name = anno_result_key.name
|
|
168
|
+
anno_attr = anno_result_key.attr
|
|
169
|
+
expr = anno_result_key.attr.orm_expression
|
|
170
|
+
stmt = stmt.add_columns(expr)
|
|
171
|
+
if self.dir == SortDir.desc:
|
|
172
|
+
expr = desc(expr)
|
|
173
|
+
stmt = stmt.join(
|
|
174
|
+
models.ProjectSessionAnnotation,
|
|
175
|
+
onclause=and_(
|
|
176
|
+
models.ProjectSessionAnnotation.project_session_id == models.ProjectSession.id,
|
|
177
|
+
models.ProjectSessionAnnotation.name == anno_name,
|
|
178
|
+
),
|
|
179
|
+
).order_by(nulls_last(expr))
|
|
180
|
+
return ProjectSessionSortConfig(
|
|
181
|
+
stmt=stmt,
|
|
182
|
+
orm_expression=anno_result_key.attr.orm_expression,
|
|
183
|
+
dir=self.dir,
|
|
184
|
+
column_name=anno_attr.column_name,
|
|
185
|
+
column_data_type=anno_attr.data_type,
|
|
186
|
+
)
|
|
187
|
+
raise ValueError(
|
|
188
|
+
"Exactly one of `col` or `annoResultKey` must be specified on `ProjectSessionSort`."
|
|
189
|
+
)
|
|
@@ -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
|
|
|
@@ -88,6 +93,47 @@ class ChatPromptVersionInput:
|
|
|
88
93
|
k: v for k, v in self.invocation_parameters.items() if v is not None
|
|
89
94
|
}
|
|
90
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
|
+
|
|
91
137
|
|
|
92
138
|
def to_pydantic_prompt_chat_template_v1(
|
|
93
139
|
prompt_chat_template_input: PromptChatTemplateInput,
|
|
@@ -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
|
|
@@ -32,7 +33,7 @@ class SpanColumn(Enum):
|
|
|
32
33
|
|
|
33
34
|
@property
|
|
34
35
|
def column_name(self) -> str:
|
|
35
|
-
return f"{self.name}_span_sort_column"
|
|
36
|
+
return truncate_name(f"{self.name}_span_sort_column")
|
|
36
37
|
|
|
37
38
|
def as_orm_expression(self, joined_table: Optional[Any] = None) -> Any:
|
|
38
39
|
expr: Any
|
|
@@ -199,7 +200,7 @@ class SpanSort:
|
|
|
199
200
|
models.SpanAnnotation.span_rowid == models.Span.id,
|
|
200
201
|
models.SpanAnnotation.name == eval_name,
|
|
201
202
|
),
|
|
202
|
-
).order_by(expr)
|
|
203
|
+
).order_by(nulls_last(expr))
|
|
203
204
|
return SpanSortConfig(
|
|
204
205
|
stmt=stmt,
|
|
205
206
|
orm_expression=eval_result_key.attr.orm_expression,
|
|
@@ -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,11 +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
|
|
11
13
|
from phoenix.server.api.mutations.model_mutations import ModelMutationMixin
|
|
12
14
|
from phoenix.server.api.mutations.project_mutations import ProjectMutationMixin
|
|
15
|
+
from phoenix.server.api.mutations.project_session_annotations_mutations import (
|
|
16
|
+
ProjectSessionAnnotationMutationMixin,
|
|
17
|
+
)
|
|
13
18
|
from phoenix.server.api.mutations.project_trace_retention_policy_mutations import (
|
|
14
19
|
ProjectTraceRetentionPolicyMutationMixin,
|
|
15
20
|
)
|
|
@@ -27,7 +32,9 @@ class Mutation(
|
|
|
27
32
|
AnnotationConfigMutationMixin,
|
|
28
33
|
ApiKeyMutationMixin,
|
|
29
34
|
ChatCompletionMutationMixin,
|
|
35
|
+
DatasetLabelMutationMixin,
|
|
30
36
|
DatasetMutationMixin,
|
|
37
|
+
DatasetSplitMutationMixin,
|
|
31
38
|
ExperimentMutationMixin,
|
|
32
39
|
ExportEventsMutationMixin,
|
|
33
40
|
ModelMutationMixin,
|
|
@@ -37,6 +44,7 @@ class Mutation(
|
|
|
37
44
|
PromptVersionTagMutationMixin,
|
|
38
45
|
PromptLabelMutationMixin,
|
|
39
46
|
SpanAnnotationMutationMixin,
|
|
47
|
+
ProjectSessionAnnotationMutationMixin,
|
|
40
48
|
TraceAnnotationMutationMixin,
|
|
41
49
|
TraceMutationMixin,
|
|
42
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
|
)
|