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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, Optional
|
|
1
|
+
from typing import Any, Optional
|
|
2
2
|
|
|
3
3
|
import strawberry
|
|
4
4
|
from fastapi import Request
|
|
@@ -7,23 +7,17 @@ from sqlalchemy import delete, select, update
|
|
|
7
7
|
from sqlalchemy.exc import IntegrityError as PostgreSQLIntegrityError
|
|
8
8
|
from sqlalchemy.orm import joinedload
|
|
9
9
|
from sqlean.dbapi2 import IntegrityError as SQLiteIntegrityError # type: ignore[import-untyped]
|
|
10
|
+
from strawberry import UNSET
|
|
10
11
|
from strawberry.relay.types import GlobalID
|
|
11
12
|
from strawberry.types import Info
|
|
12
13
|
|
|
13
14
|
from phoenix.db import models
|
|
14
15
|
from phoenix.db.types.identifier import Identifier as IdentifierModel
|
|
15
|
-
from phoenix.
|
|
16
|
-
from phoenix.server.api.auth import IsLocked, IsNotReadOnly
|
|
16
|
+
from phoenix.server.api.auth import IsLocked, IsNotReadOnly, IsNotViewer
|
|
17
17
|
from phoenix.server.api.context import Context
|
|
18
18
|
from phoenix.server.api.exceptions import BadRequest, Conflict, NotFound
|
|
19
|
-
from phoenix.server.api.helpers.prompts.models import (
|
|
20
|
-
normalize_response_format,
|
|
21
|
-
normalize_tools,
|
|
22
|
-
validate_invocation_parameters,
|
|
23
|
-
)
|
|
24
19
|
from phoenix.server.api.input_types.PromptVersionInput import (
|
|
25
20
|
ChatPromptVersionInput,
|
|
26
|
-
to_pydantic_prompt_chat_template_v1,
|
|
27
21
|
)
|
|
28
22
|
from phoenix.server.api.mutations.prompt_version_tag_mutations import (
|
|
29
23
|
SetPromptVersionTagInput,
|
|
@@ -32,7 +26,7 @@ from phoenix.server.api.mutations.prompt_version_tag_mutations import (
|
|
|
32
26
|
from phoenix.server.api.queries import Query
|
|
33
27
|
from phoenix.server.api.types.Identifier import Identifier
|
|
34
28
|
from phoenix.server.api.types.node import from_global_id_with_expected_type
|
|
35
|
-
from phoenix.server.api.types.Prompt import Prompt
|
|
29
|
+
from phoenix.server.api.types.Prompt import Prompt
|
|
36
30
|
from phoenix.server.bearer_auth import PhoenixUser
|
|
37
31
|
|
|
38
32
|
|
|
@@ -41,6 +35,7 @@ class CreateChatPromptInput:
|
|
|
41
35
|
name: Identifier
|
|
42
36
|
description: Optional[str] = None
|
|
43
37
|
prompt_version: ChatPromptVersionInput
|
|
38
|
+
metadata: Optional[strawberry.scalars.JSON] = None
|
|
44
39
|
|
|
45
40
|
|
|
46
41
|
@strawberry.input
|
|
@@ -58,14 +53,16 @@ class DeletePromptInput:
|
|
|
58
53
|
@strawberry.input
|
|
59
54
|
class ClonePromptInput:
|
|
60
55
|
name: Identifier
|
|
61
|
-
description: Optional[str] = None
|
|
62
56
|
prompt_id: GlobalID
|
|
57
|
+
description: Optional[str] = UNSET
|
|
58
|
+
metadata: Optional[strawberry.scalars.JSON] = UNSET
|
|
63
59
|
|
|
64
60
|
|
|
65
61
|
@strawberry.input
|
|
66
62
|
class PatchPromptInput:
|
|
67
63
|
prompt_id: GlobalID
|
|
68
|
-
description: str
|
|
64
|
+
description: Optional[str] = UNSET
|
|
65
|
+
metadata: Optional[strawberry.scalars.JSON] = UNSET
|
|
69
66
|
|
|
70
67
|
|
|
71
68
|
@strawberry.type
|
|
@@ -75,7 +72,7 @@ class DeletePromptMutationPayload:
|
|
|
75
72
|
|
|
76
73
|
@strawberry.type
|
|
77
74
|
class PromptMutationMixin:
|
|
78
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsLocked]) # type: ignore
|
|
75
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer, IsLocked]) # type: ignore
|
|
79
76
|
async def create_chat_prompt(
|
|
80
77
|
self, info: Info[Context, None], input: CreateChatPromptInput
|
|
81
78
|
) -> Prompt:
|
|
@@ -84,65 +81,26 @@ class PromptMutationMixin:
|
|
|
84
81
|
if "user" in request.scope:
|
|
85
82
|
assert isinstance(user := request.user, PhoenixUser)
|
|
86
83
|
user_id = int(user.identity)
|
|
87
|
-
|
|
88
|
-
input_prompt_version = input.prompt_version
|
|
89
|
-
tool_definitions = [tool.definition for tool in input_prompt_version.tools]
|
|
90
|
-
tool_choice = cast(
|
|
91
|
-
Optional[Union[str, dict[str, Any]]],
|
|
92
|
-
cast(dict[str, Any], input.prompt_version.invocation_parameters).pop(
|
|
93
|
-
"tool_choice", None
|
|
94
|
-
),
|
|
95
|
-
)
|
|
96
|
-
model_provider = ModelProvider(input_prompt_version.model_provider)
|
|
97
84
|
try:
|
|
98
|
-
|
|
99
|
-
normalize_tools(tool_definitions, model_provider, tool_choice)
|
|
100
|
-
if tool_definitions
|
|
101
|
-
else None
|
|
102
|
-
)
|
|
103
|
-
template = to_pydantic_prompt_chat_template_v1(input_prompt_version.template)
|
|
104
|
-
response_format = (
|
|
105
|
-
normalize_response_format(
|
|
106
|
-
input_prompt_version.response_format.definition,
|
|
107
|
-
model_provider,
|
|
108
|
-
)
|
|
109
|
-
if input_prompt_version.response_format
|
|
110
|
-
else None
|
|
111
|
-
)
|
|
112
|
-
invocation_parameters = validate_invocation_parameters(
|
|
113
|
-
input_prompt_version.invocation_parameters,
|
|
114
|
-
model_provider,
|
|
115
|
-
)
|
|
85
|
+
prompt_version = input.prompt_version.to_orm_prompt_version(user_id)
|
|
116
86
|
except ValidationError as error:
|
|
117
87
|
raise BadRequest(str(error))
|
|
118
|
-
|
|
88
|
+
name = IdentifierModel.model_validate(str(input.name))
|
|
89
|
+
prompt = models.Prompt(
|
|
90
|
+
name=name,
|
|
91
|
+
description=input.description,
|
|
92
|
+
metadata_=input.metadata or {},
|
|
93
|
+
prompt_versions=[prompt_version],
|
|
94
|
+
)
|
|
119
95
|
async with info.context.db() as session:
|
|
120
|
-
prompt_version = models.PromptVersion(
|
|
121
|
-
description=input_prompt_version.description,
|
|
122
|
-
user_id=user_id,
|
|
123
|
-
template_type="CHAT",
|
|
124
|
-
template_format=input_prompt_version.template_format,
|
|
125
|
-
template=template,
|
|
126
|
-
invocation_parameters=invocation_parameters,
|
|
127
|
-
tools=tools,
|
|
128
|
-
response_format=response_format,
|
|
129
|
-
model_provider=input_prompt_version.model_provider,
|
|
130
|
-
model_name=input_prompt_version.model_name,
|
|
131
|
-
)
|
|
132
|
-
name = IdentifierModel.model_validate(str(input.name))
|
|
133
|
-
prompt = models.Prompt(
|
|
134
|
-
name=name,
|
|
135
|
-
description=input.description,
|
|
136
|
-
prompt_versions=[prompt_version],
|
|
137
|
-
)
|
|
138
96
|
session.add(prompt)
|
|
139
97
|
try:
|
|
140
98
|
await session.commit()
|
|
141
99
|
except (PostgreSQLIntegrityError, SQLiteIntegrityError):
|
|
142
100
|
raise Conflict(f"A prompt named '{input.name}' already exists")
|
|
143
|
-
return
|
|
101
|
+
return Prompt(id=prompt.id, db_record=prompt)
|
|
144
102
|
|
|
145
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsLocked]) # type: ignore
|
|
103
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer, IsLocked]) # type: ignore
|
|
146
104
|
async def create_chat_prompt_version(
|
|
147
105
|
self,
|
|
148
106
|
info: Info[Context, None],
|
|
@@ -153,74 +111,28 @@ class PromptMutationMixin:
|
|
|
153
111
|
if "user" in request.scope:
|
|
154
112
|
assert isinstance(user := request.user, PhoenixUser)
|
|
155
113
|
user_id = int(user.identity)
|
|
156
|
-
|
|
157
|
-
input_prompt_version = input.prompt_version
|
|
158
|
-
tool_definitions = [tool.definition for tool in input.prompt_version.tools]
|
|
159
|
-
tool_choice = cast(
|
|
160
|
-
Optional[Union[str, dict[str, Any]]],
|
|
161
|
-
cast(dict[str, Any], input.prompt_version.invocation_parameters).pop(
|
|
162
|
-
"tool_choice", None
|
|
163
|
-
),
|
|
164
|
-
)
|
|
165
|
-
model_provider = ModelProvider(input_prompt_version.model_provider)
|
|
166
114
|
try:
|
|
167
|
-
|
|
168
|
-
normalize_tools(tool_definitions, model_provider, tool_choice)
|
|
169
|
-
if tool_definitions
|
|
170
|
-
else None
|
|
171
|
-
)
|
|
172
|
-
template = to_pydantic_prompt_chat_template_v1(input_prompt_version.template)
|
|
173
|
-
response_format = (
|
|
174
|
-
normalize_response_format(
|
|
175
|
-
input_prompt_version.response_format.definition,
|
|
176
|
-
model_provider,
|
|
177
|
-
)
|
|
178
|
-
if input_prompt_version.response_format
|
|
179
|
-
else None
|
|
180
|
-
)
|
|
181
|
-
invocation_parameters = validate_invocation_parameters(
|
|
182
|
-
input_prompt_version.invocation_parameters,
|
|
183
|
-
model_provider,
|
|
184
|
-
)
|
|
115
|
+
prompt_version = input.prompt_version.to_orm_prompt_version(user_id)
|
|
185
116
|
except ValidationError as error:
|
|
186
117
|
raise BadRequest(str(error))
|
|
187
|
-
|
|
188
118
|
prompt_id = from_global_id_with_expected_type(
|
|
189
119
|
global_id=input.prompt_id, expected_type_name=Prompt.__name__
|
|
190
120
|
)
|
|
121
|
+
prompt_version.prompt_id = prompt_id
|
|
191
122
|
async with info.context.db() as session:
|
|
192
|
-
prompt = await session.get(models.Prompt, prompt_id)
|
|
193
|
-
if not prompt:
|
|
194
|
-
raise NotFound(f"Prompt with ID '{input.prompt_id}' not found")
|
|
195
|
-
|
|
196
|
-
prompt_version = models.PromptVersion(
|
|
197
|
-
prompt_id=prompt_id,
|
|
198
|
-
description=input.prompt_version.description,
|
|
199
|
-
user_id=user_id,
|
|
200
|
-
template_type="CHAT",
|
|
201
|
-
template_format=input.prompt_version.template_format,
|
|
202
|
-
template=template,
|
|
203
|
-
invocation_parameters=invocation_parameters,
|
|
204
|
-
tools=tools,
|
|
205
|
-
response_format=response_format,
|
|
206
|
-
model_provider=input.prompt_version.model_provider,
|
|
207
|
-
model_name=input.prompt_version.model_name,
|
|
208
|
-
)
|
|
209
123
|
session.add(prompt_version)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
|
|
124
|
+
try:
|
|
125
|
+
await session.flush()
|
|
126
|
+
except (PostgreSQLIntegrityError, SQLiteIntegrityError):
|
|
127
|
+
raise NotFound(f"Prompt with ID '{input.prompt_id}' not found")
|
|
128
|
+
if input.tags:
|
|
129
|
+
for tag in input.tags:
|
|
130
|
+
await upsert_prompt_version_tag(
|
|
131
|
+
session, prompt_id, prompt_version.id, tag.name, tag.description
|
|
132
|
+
)
|
|
133
|
+
return Prompt(id=prompt_id)
|
|
134
|
+
|
|
135
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer]) # type: ignore
|
|
224
136
|
async def delete_prompt(
|
|
225
137
|
self, info: Info[Context, None], input: DeletePromptInput
|
|
226
138
|
) -> DeletePromptMutationPayload:
|
|
@@ -231,13 +143,13 @@ class PromptMutationMixin:
|
|
|
231
143
|
stmt = delete(models.Prompt).where(models.Prompt.id == prompt_id)
|
|
232
144
|
result = await session.execute(stmt)
|
|
233
145
|
|
|
234
|
-
if result.rowcount == 0:
|
|
146
|
+
if result.rowcount == 0: # type: ignore[attr-defined]
|
|
235
147
|
raise NotFound(f"Prompt with ID '{input.prompt_id}' not found")
|
|
236
148
|
|
|
237
149
|
await session.commit()
|
|
238
150
|
return DeletePromptMutationPayload(query=Query())
|
|
239
151
|
|
|
240
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsLocked]) # type: ignore
|
|
152
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer, IsLocked]) # type: ignore
|
|
241
153
|
async def clone_prompt(self, info: Info[Context, None], input: ClonePromptInput) -> Prompt:
|
|
242
154
|
prompt_id = from_global_id_with_expected_type(
|
|
243
155
|
global_id=input.prompt_id, expected_type_name=Prompt.__name__
|
|
@@ -256,10 +168,23 @@ class PromptMutationMixin:
|
|
|
256
168
|
|
|
257
169
|
# Create new prompt
|
|
258
170
|
name = IdentifierModel.model_validate(str(input.name))
|
|
171
|
+
# Handle description: inherit if UNSET, otherwise use value (can be None)
|
|
172
|
+
if input.description is UNSET:
|
|
173
|
+
description = prompt.description
|
|
174
|
+
else:
|
|
175
|
+
description = input.description.strip() if input.description is not None else None
|
|
176
|
+
|
|
177
|
+
# Handle metadata: inherit if UNSET, clear to empty dict if None, or use value
|
|
178
|
+
if input.metadata is UNSET:
|
|
179
|
+
metadata = prompt.metadata_
|
|
180
|
+
else:
|
|
181
|
+
metadata = input.metadata or {}
|
|
182
|
+
|
|
259
183
|
new_prompt = models.Prompt(
|
|
260
184
|
name=name,
|
|
261
|
-
description=input.description,
|
|
262
185
|
source_prompt_id=prompt_id,
|
|
186
|
+
description=description,
|
|
187
|
+
metadata_=metadata,
|
|
263
188
|
)
|
|
264
189
|
|
|
265
190
|
# Create copies of all versions
|
|
@@ -288,19 +213,30 @@ class PromptMutationMixin:
|
|
|
288
213
|
await session.commit()
|
|
289
214
|
except (PostgreSQLIntegrityError, SQLiteIntegrityError):
|
|
290
215
|
raise Conflict(f"A prompt named '{input.name}' already exists")
|
|
291
|
-
return
|
|
216
|
+
return Prompt(id=new_prompt.id, db_record=new_prompt)
|
|
292
217
|
|
|
293
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsLocked]) # type: ignore
|
|
218
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer, IsLocked]) # type: ignore
|
|
294
219
|
async def patch_prompt(self, info: Info[Context, None], input: PatchPromptInput) -> Prompt:
|
|
295
220
|
prompt_id = from_global_id_with_expected_type(
|
|
296
221
|
global_id=input.prompt_id, expected_type_name=Prompt.__name__
|
|
297
222
|
)
|
|
298
223
|
|
|
224
|
+
values: dict[str, Any] = {}
|
|
225
|
+
if input.description is not UNSET:
|
|
226
|
+
values["description"] = (
|
|
227
|
+
input.description.strip() if input.description is not None else None
|
|
228
|
+
)
|
|
229
|
+
if input.metadata is not UNSET:
|
|
230
|
+
values["metadata_"] = input.metadata or {}
|
|
231
|
+
|
|
232
|
+
if not values:
|
|
233
|
+
raise BadRequest("No fields provided to update")
|
|
234
|
+
|
|
299
235
|
async with info.context.db() as session:
|
|
300
236
|
stmt = (
|
|
301
237
|
update(models.Prompt)
|
|
302
238
|
.where(models.Prompt.id == prompt_id)
|
|
303
|
-
.values(
|
|
239
|
+
.values(**values)
|
|
304
240
|
.returning(models.Prompt)
|
|
305
241
|
)
|
|
306
242
|
|
|
@@ -310,4 +246,4 @@ class PromptMutationMixin:
|
|
|
310
246
|
if prompt is None:
|
|
311
247
|
raise NotFound(f"Prompt with ID '{input.prompt_id}' not found")
|
|
312
248
|
|
|
313
|
-
return
|
|
249
|
+
return Prompt(id=prompt.id, db_record=prompt)
|
|
@@ -10,15 +10,15 @@ from strawberry.types import Info
|
|
|
10
10
|
|
|
11
11
|
from phoenix.db import models
|
|
12
12
|
from phoenix.db.types.identifier import Identifier as IdentifierModel
|
|
13
|
-
from phoenix.server.api.auth import IsLocked, IsNotReadOnly
|
|
13
|
+
from phoenix.server.api.auth import IsLocked, IsNotReadOnly, IsNotViewer
|
|
14
14
|
from phoenix.server.api.context import Context
|
|
15
15
|
from phoenix.server.api.exceptions import BadRequest, Conflict, NotFound
|
|
16
16
|
from phoenix.server.api.queries import Query
|
|
17
17
|
from phoenix.server.api.types.Identifier import Identifier
|
|
18
18
|
from phoenix.server.api.types.node import from_global_id_with_expected_type
|
|
19
|
-
from phoenix.server.api.types.Prompt import Prompt
|
|
19
|
+
from phoenix.server.api.types.Prompt import Prompt
|
|
20
20
|
from phoenix.server.api.types.PromptVersion import PromptVersion
|
|
21
|
-
from phoenix.server.api.types.PromptVersionTag import PromptVersionTag
|
|
21
|
+
from phoenix.server.api.types.PromptVersionTag import PromptVersionTag
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
@strawberry.input
|
|
@@ -42,7 +42,7 @@ class PromptVersionTagMutationPayload:
|
|
|
42
42
|
|
|
43
43
|
@strawberry.type
|
|
44
44
|
class PromptVersionTagMutationMixin:
|
|
45
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
|
|
45
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer]) # type: ignore
|
|
46
46
|
async def delete_prompt_version_tag(
|
|
47
47
|
self, info: Info[Context, None], input: DeletePromptVersionTagInput
|
|
48
48
|
) -> PromptVersionTagMutationPayload:
|
|
@@ -75,10 +75,12 @@ class PromptVersionTagMutationMixin:
|
|
|
75
75
|
await session.delete(prompt_version_tag)
|
|
76
76
|
await session.commit()
|
|
77
77
|
return PromptVersionTagMutationPayload(
|
|
78
|
-
prompt_version_tag=None,
|
|
78
|
+
prompt_version_tag=None,
|
|
79
|
+
query=Query(),
|
|
80
|
+
prompt=Prompt(id=prompt.id, db_record=prompt),
|
|
79
81
|
)
|
|
80
82
|
|
|
81
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsLocked]) # type: ignore
|
|
83
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer, IsLocked]) # type: ignore
|
|
82
84
|
async def set_prompt_version_tag(
|
|
83
85
|
self, info: Info[Context, None], input: SetPromptVersionTagInput
|
|
84
86
|
) -> PromptVersionTagMutationPayload:
|
|
@@ -111,9 +113,10 @@ class PromptVersionTagMutationMixin:
|
|
|
111
113
|
except (PostgreSQLIntegrityError, SQLiteIntegrityError):
|
|
112
114
|
raise Conflict("Failed to update PromptVersionTag.")
|
|
113
115
|
|
|
114
|
-
version_tag = to_gql_prompt_version_tag(updated_tag)
|
|
115
116
|
return PromptVersionTagMutationPayload(
|
|
116
|
-
prompt_version_tag=
|
|
117
|
+
prompt_version_tag=PromptVersionTag(id=updated_tag.id, db_record=updated_tag),
|
|
118
|
+
prompt=Prompt(id=prompt.id, db_record=prompt),
|
|
119
|
+
query=Query(),
|
|
117
120
|
)
|
|
118
121
|
|
|
119
122
|
|
|
@@ -7,7 +7,7 @@ from starlette.requests import Request
|
|
|
7
7
|
from strawberry import UNSET, Info
|
|
8
8
|
|
|
9
9
|
from phoenix.db import models
|
|
10
|
-
from phoenix.server.api.auth import IsLocked, IsNotReadOnly
|
|
10
|
+
from phoenix.server.api.auth import IsLocked, IsNotReadOnly, IsNotViewer
|
|
11
11
|
from phoenix.server.api.context import Context
|
|
12
12
|
from phoenix.server.api.exceptions import BadRequest, NotFound, Unauthorized
|
|
13
13
|
from phoenix.server.api.helpers.annotations import get_user_identifier
|
|
@@ -21,7 +21,7 @@ from phoenix.server.api.queries import Query
|
|
|
21
21
|
from phoenix.server.api.types.AnnotationSource import AnnotationSource
|
|
22
22
|
from phoenix.server.api.types.AnnotatorKind import AnnotatorKind
|
|
23
23
|
from phoenix.server.api.types.node import from_global_id_with_expected_type
|
|
24
|
-
from phoenix.server.api.types.SpanAnnotation import SpanAnnotation
|
|
24
|
+
from phoenix.server.api.types.SpanAnnotation import SpanAnnotation
|
|
25
25
|
from phoenix.server.bearer_auth import PhoenixUser
|
|
26
26
|
from phoenix.server.dml_event import SpanAnnotationDeleteEvent, SpanAnnotationInsertEvent
|
|
27
27
|
|
|
@@ -34,7 +34,7 @@ class SpanAnnotationMutationPayload:
|
|
|
34
34
|
|
|
35
35
|
@strawberry.type
|
|
36
36
|
class SpanAnnotationMutationMixin:
|
|
37
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsLocked]) # type: ignore
|
|
37
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer, IsLocked]) # type: ignore
|
|
38
38
|
async def create_span_annotations(
|
|
39
39
|
self, info: Info[Context, None], input: list[CreateSpanAnnotationInput]
|
|
40
40
|
) -> SpanAnnotationMutationPayload:
|
|
@@ -138,7 +138,7 @@ class SpanAnnotationMutationMixin:
|
|
|
138
138
|
|
|
139
139
|
# Convert the fully loaded annotations to GQL types
|
|
140
140
|
returned_annotations = [
|
|
141
|
-
|
|
141
|
+
SpanAnnotation(id=anno.id, db_record=anno) for anno in ordered_final_annotations
|
|
142
142
|
]
|
|
143
143
|
|
|
144
144
|
await session.commit()
|
|
@@ -148,7 +148,7 @@ class SpanAnnotationMutationMixin:
|
|
|
148
148
|
query=Query(),
|
|
149
149
|
)
|
|
150
150
|
|
|
151
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsLocked]) # type: ignore
|
|
151
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer, IsLocked]) # type: ignore
|
|
152
152
|
async def create_span_note(
|
|
153
153
|
self, info: Info[Context, None], annotation_input: CreateSpanNoteInput
|
|
154
154
|
) -> SpanAnnotationMutationPayload:
|
|
@@ -184,14 +184,16 @@ class SpanAnnotationMutationMixin:
|
|
|
184
184
|
processed_annotation = result.one()
|
|
185
185
|
|
|
186
186
|
info.context.event_queue.put(SpanAnnotationInsertEvent((processed_annotation.id,)))
|
|
187
|
-
returned_annotation =
|
|
187
|
+
returned_annotation = SpanAnnotation(
|
|
188
|
+
id=processed_annotation.id, db_record=processed_annotation
|
|
189
|
+
)
|
|
188
190
|
await session.commit()
|
|
189
191
|
return SpanAnnotationMutationPayload(
|
|
190
192
|
span_annotations=[returned_annotation],
|
|
191
193
|
query=Query(),
|
|
192
194
|
)
|
|
193
195
|
|
|
194
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsLocked]) # type: ignore
|
|
196
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer, IsLocked]) # type: ignore
|
|
195
197
|
async def patch_span_annotations(
|
|
196
198
|
self, info: Info[Context, None], input: list[PatchAnnotationInput]
|
|
197
199
|
) -> SpanAnnotationMutationPayload:
|
|
@@ -256,7 +258,7 @@ class SpanAnnotationMutationMixin:
|
|
|
256
258
|
session.add(span_annotation)
|
|
257
259
|
|
|
258
260
|
patched_annotations = [
|
|
259
|
-
|
|
261
|
+
SpanAnnotation(id=span_annotation.id, db_record=span_annotation)
|
|
260
262
|
for span_annotation in span_annotations_by_id.values()
|
|
261
263
|
]
|
|
262
264
|
|
|
@@ -268,7 +270,7 @@ class SpanAnnotationMutationMixin:
|
|
|
268
270
|
query=Query(),
|
|
269
271
|
)
|
|
270
272
|
|
|
271
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
|
|
273
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer]) # type: ignore
|
|
272
274
|
async def delete_span_annotations(
|
|
273
275
|
self, info: Info[Context, None], input: DeleteAnnotationsInput
|
|
274
276
|
) -> SpanAnnotationMutationPayload:
|
|
@@ -320,7 +322,10 @@ class SpanAnnotationMutationMixin:
|
|
|
320
322
|
)
|
|
321
323
|
|
|
322
324
|
deleted_annotations_gql = [
|
|
323
|
-
|
|
325
|
+
SpanAnnotation(
|
|
326
|
+
id=deleted_annotations_by_id[id].id, db_record=deleted_annotations_by_id[id]
|
|
327
|
+
)
|
|
328
|
+
for id in span_annotation_ids
|
|
324
329
|
]
|
|
325
330
|
info.context.event_queue.put(
|
|
326
331
|
SpanAnnotationDeleteEvent(tuple(deleted_annotations_by_id.keys()))
|
|
@@ -6,7 +6,7 @@ from starlette.requests import Request
|
|
|
6
6
|
from strawberry import UNSET, Info
|
|
7
7
|
|
|
8
8
|
from phoenix.db import models
|
|
9
|
-
from phoenix.server.api.auth import IsLocked, IsNotReadOnly
|
|
9
|
+
from phoenix.server.api.auth import IsLocked, IsNotReadOnly, IsNotViewer
|
|
10
10
|
from phoenix.server.api.context import Context
|
|
11
11
|
from phoenix.server.api.exceptions import BadRequest, NotFound, Unauthorized
|
|
12
12
|
from phoenix.server.api.helpers.annotations import get_user_identifier
|
|
@@ -16,7 +16,7 @@ from phoenix.server.api.input_types.PatchAnnotationInput import PatchAnnotationI
|
|
|
16
16
|
from phoenix.server.api.queries import Query
|
|
17
17
|
from phoenix.server.api.types.AnnotationSource import AnnotationSource
|
|
18
18
|
from phoenix.server.api.types.node import from_global_id_with_expected_type
|
|
19
|
-
from phoenix.server.api.types.TraceAnnotation import TraceAnnotation
|
|
19
|
+
from phoenix.server.api.types.TraceAnnotation import TraceAnnotation
|
|
20
20
|
from phoenix.server.bearer_auth import PhoenixUser
|
|
21
21
|
from phoenix.server.dml_event import TraceAnnotationDeleteEvent, TraceAnnotationInsertEvent
|
|
22
22
|
|
|
@@ -29,7 +29,7 @@ class TraceAnnotationMutationPayload:
|
|
|
29
29
|
|
|
30
30
|
@strawberry.type
|
|
31
31
|
class TraceAnnotationMutationMixin:
|
|
32
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsLocked]) # type: ignore
|
|
32
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer, IsLocked]) # type: ignore
|
|
33
33
|
async def create_trace_annotations(
|
|
34
34
|
self, info: Info[Context, None], input: list[CreateTraceAnnotationInput]
|
|
35
35
|
) -> TraceAnnotationMutationPayload:
|
|
@@ -111,7 +111,9 @@ class TraceAnnotationMutationMixin:
|
|
|
111
111
|
info.context.event_queue.put(TraceAnnotationInsertEvent(inserted_annotation_ids))
|
|
112
112
|
|
|
113
113
|
returned_annotations = [
|
|
114
|
-
|
|
114
|
+
TraceAnnotation(
|
|
115
|
+
id=processed_annotations_map[i].id, db_record=processed_annotations_map[i]
|
|
116
|
+
)
|
|
115
117
|
for i in sorted(processed_annotations_map.keys())
|
|
116
118
|
]
|
|
117
119
|
|
|
@@ -120,7 +122,7 @@ class TraceAnnotationMutationMixin:
|
|
|
120
122
|
query=Query(),
|
|
121
123
|
)
|
|
122
124
|
|
|
123
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsLocked]) # type: ignore
|
|
125
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer, IsLocked]) # type: ignore
|
|
124
126
|
async def patch_trace_annotations(
|
|
125
127
|
self, info: Info[Context, None], input: list[PatchAnnotationInput]
|
|
126
128
|
) -> TraceAnnotationMutationPayload:
|
|
@@ -186,7 +188,7 @@ class TraceAnnotationMutationMixin:
|
|
|
186
188
|
await session.commit()
|
|
187
189
|
|
|
188
190
|
patched_annotations = [
|
|
189
|
-
|
|
191
|
+
TraceAnnotation(id=trace_annotation.id, db_record=trace_annotation)
|
|
190
192
|
for trace_annotation in trace_annotations_by_id.values()
|
|
191
193
|
]
|
|
192
194
|
info.context.event_queue.put(TraceAnnotationInsertEvent(tuple(patch_by_id.keys())))
|
|
@@ -195,7 +197,7 @@ class TraceAnnotationMutationMixin:
|
|
|
195
197
|
query=Query(),
|
|
196
198
|
)
|
|
197
199
|
|
|
198
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
|
|
200
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer]) # type: ignore
|
|
199
201
|
async def delete_trace_annotations(
|
|
200
202
|
self, info: Info[Context, None], input: DeleteAnnotationsInput
|
|
201
203
|
) -> TraceAnnotationMutationPayload:
|
|
@@ -245,7 +247,10 @@ class TraceAnnotationMutationMixin:
|
|
|
245
247
|
)
|
|
246
248
|
|
|
247
249
|
deleted_gql_annotations = [
|
|
248
|
-
|
|
250
|
+
TraceAnnotation(
|
|
251
|
+
id=deleted_annotations_by_id[id].id, db_record=deleted_annotations_by_id[id]
|
|
252
|
+
)
|
|
253
|
+
for id in trace_annotation_ids
|
|
249
254
|
]
|
|
250
255
|
info.context.event_queue.put(
|
|
251
256
|
TraceAnnotationDeleteEvent(tuple(deleted_annotations_by_id.keys()))
|
|
@@ -6,7 +6,7 @@ from strawberry.relay import GlobalID
|
|
|
6
6
|
from strawberry.types import Info
|
|
7
7
|
|
|
8
8
|
from phoenix.db import models
|
|
9
|
-
from phoenix.server.api.auth import IsNotReadOnly
|
|
9
|
+
from phoenix.server.api.auth import IsNotReadOnly, IsNotViewer
|
|
10
10
|
from phoenix.server.api.context import Context
|
|
11
11
|
from phoenix.server.api.exceptions import BadRequest
|
|
12
12
|
from phoenix.server.api.queries import Query
|
|
@@ -16,7 +16,7 @@ from phoenix.server.dml_event import SpanDeleteEvent
|
|
|
16
16
|
|
|
17
17
|
@strawberry.type
|
|
18
18
|
class TraceMutationMixin:
|
|
19
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
|
|
19
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer]) # type: ignore
|
|
20
20
|
async def delete_traces(
|
|
21
21
|
self,
|
|
22
22
|
info: Info[Context, None],
|
|
@@ -73,7 +73,7 @@ class TraceMutationMixin:
|
|
|
73
73
|
info.context.event_queue.put(SpanDeleteEvent(project_ids))
|
|
74
74
|
return Query()
|
|
75
75
|
|
|
76
|
-
@strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
|
|
76
|
+
@strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer]) # type: ignore
|
|
77
77
|
async def transfer_traces_to_project(
|
|
78
78
|
self,
|
|
79
79
|
info: Info[Context, None],
|