arize-phoenix 8.32.1__py3-none-any.whl → 9.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of arize-phoenix might be problematic. Click here for more details.
- {arize_phoenix-8.32.1.dist-info → arize_phoenix-9.0.0.dist-info}/METADATA +2 -2
- {arize_phoenix-8.32.1.dist-info → arize_phoenix-9.0.0.dist-info}/RECORD +76 -56
- phoenix/db/constants.py +1 -0
- phoenix/db/facilitator.py +55 -0
- phoenix/db/insertion/document_annotation.py +31 -13
- phoenix/db/insertion/evaluation.py +15 -3
- phoenix/db/insertion/helpers.py +2 -1
- phoenix/db/insertion/span_annotation.py +26 -9
- phoenix/db/insertion/trace_annotation.py +25 -9
- phoenix/db/insertion/types.py +7 -0
- phoenix/db/migrations/versions/2f9d1a65945f_annotation_config_migration.py +322 -0
- phoenix/db/migrations/versions/8a3764fe7f1a_change_jsonb_to_json_for_prompts.py +76 -0
- phoenix/db/migrations/versions/bb8139330879_create_project_trace_retention_policies_table.py +77 -0
- phoenix/db/models.py +151 -10
- phoenix/db/types/annotation_configs.py +97 -0
- phoenix/db/types/db_models.py +41 -0
- phoenix/db/types/trace_retention.py +267 -0
- phoenix/experiments/functions.py +5 -1
- phoenix/server/api/auth.py +9 -0
- phoenix/server/api/context.py +5 -0
- phoenix/server/api/dataloaders/__init__.py +4 -0
- phoenix/server/api/dataloaders/annotation_summaries.py +203 -24
- phoenix/server/api/dataloaders/project_ids_by_trace_retention_policy_id.py +42 -0
- phoenix/server/api/dataloaders/trace_retention_policy_id_by_project_id.py +34 -0
- phoenix/server/api/helpers/annotations.py +9 -0
- phoenix/server/api/helpers/prompts/models.py +34 -67
- phoenix/server/api/input_types/CreateSpanAnnotationInput.py +9 -0
- phoenix/server/api/input_types/CreateTraceAnnotationInput.py +3 -0
- phoenix/server/api/input_types/PatchAnnotationInput.py +3 -0
- phoenix/server/api/input_types/SpanAnnotationFilter.py +67 -0
- phoenix/server/api/mutations/__init__.py +6 -0
- phoenix/server/api/mutations/annotation_config_mutations.py +413 -0
- phoenix/server/api/mutations/dataset_mutations.py +62 -39
- phoenix/server/api/mutations/project_trace_retention_policy_mutations.py +245 -0
- phoenix/server/api/mutations/span_annotations_mutations.py +272 -70
- phoenix/server/api/mutations/trace_annotations_mutations.py +203 -74
- phoenix/server/api/queries.py +86 -0
- phoenix/server/api/routers/v1/__init__.py +4 -0
- phoenix/server/api/routers/v1/annotation_configs.py +449 -0
- phoenix/server/api/routers/v1/annotations.py +161 -0
- phoenix/server/api/routers/v1/evaluations.py +6 -0
- phoenix/server/api/routers/v1/projects.py +1 -50
- phoenix/server/api/routers/v1/spans.py +35 -8
- phoenix/server/api/routers/v1/traces.py +22 -13
- phoenix/server/api/routers/v1/utils.py +60 -0
- phoenix/server/api/types/Annotation.py +7 -0
- phoenix/server/api/types/AnnotationConfig.py +124 -0
- phoenix/server/api/types/AnnotationSource.py +9 -0
- phoenix/server/api/types/AnnotationSummary.py +28 -14
- phoenix/server/api/types/AnnotatorKind.py +1 -0
- phoenix/server/api/types/CronExpression.py +15 -0
- phoenix/server/api/types/Evaluation.py +4 -30
- phoenix/server/api/types/Project.py +50 -2
- phoenix/server/api/types/ProjectTraceRetentionPolicy.py +110 -0
- phoenix/server/api/types/Span.py +78 -0
- phoenix/server/api/types/SpanAnnotation.py +24 -0
- phoenix/server/api/types/Trace.py +2 -2
- phoenix/server/api/types/TraceAnnotation.py +23 -0
- phoenix/server/app.py +20 -0
- phoenix/server/retention.py +76 -0
- phoenix/server/static/.vite/manifest.json +36 -36
- phoenix/server/static/assets/components-B2MWTXnm.js +4326 -0
- phoenix/server/static/assets/{index-B0CbpsxD.js → index-Bfvpea_-.js} +10 -10
- phoenix/server/static/assets/pages-CZ2vKu8H.js +7268 -0
- phoenix/server/static/assets/vendor-BRDkBC5J.js +903 -0
- phoenix/server/static/assets/{vendor-arizeai-CxXYQNUl.js → vendor-arizeai-BvTqp_W8.js} +3 -3
- phoenix/server/static/assets/{vendor-codemirror-B0NIFPOL.js → vendor-codemirror-COt9UfW7.js} +1 -1
- phoenix/server/static/assets/{vendor-recharts-CrrDFWK1.js → vendor-recharts-BoHX9Hvs.js} +2 -2
- phoenix/server/static/assets/{vendor-shiki-C5bJ-RPf.js → vendor-shiki-Cw1dsDAz.js} +1 -1
- phoenix/trace/dsl/filter.py +25 -5
- phoenix/utilities/__init__.py +18 -0
- phoenix/version.py +1 -1
- phoenix/server/static/assets/components-x-gKFJ8C.js +0 -3414
- phoenix/server/static/assets/pages-BU4VdyeH.js +0 -5867
- phoenix/server/static/assets/vendor-BfhM_F1u.js +0 -902
- {arize_phoenix-8.32.1.dist-info → arize_phoenix-9.0.0.dist-info}/WHEEL +0 -0
- {arize_phoenix-8.32.1.dist-info → arize_phoenix-9.0.0.dist-info}/entry_points.txt +0 -0
- {arize_phoenix-8.32.1.dist-info → arize_phoenix-9.0.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-8.32.1.dist-info → arize_phoenix-9.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
from
|
|
1
|
+
from typing import Optional
|
|
2
2
|
|
|
3
3
|
import strawberry
|
|
4
|
-
from sqlalchemy import delete, insert,
|
|
5
|
-
from
|
|
6
|
-
from strawberry
|
|
4
|
+
from sqlalchemy import delete, insert, select
|
|
5
|
+
from starlette.requests import Request
|
|
6
|
+
from strawberry import UNSET, Info
|
|
7
7
|
|
|
8
8
|
from phoenix.db import models
|
|
9
9
|
from phoenix.server.api.auth import IsLocked, IsNotReadOnly
|
|
10
10
|
from phoenix.server.api.context import Context
|
|
11
|
+
from phoenix.server.api.exceptions import BadRequest, NotFound, Unauthorized
|
|
12
|
+
from phoenix.server.api.helpers.annotations import get_user_identifier
|
|
11
13
|
from phoenix.server.api.input_types.CreateTraceAnnotationInput import CreateTraceAnnotationInput
|
|
12
14
|
from phoenix.server.api.input_types.DeleteAnnotationsInput import DeleteAnnotationsInput
|
|
13
15
|
from phoenix.server.api.input_types.PatchAnnotationInput import PatchAnnotationInput
|
|
14
16
|
from phoenix.server.api.queries import Query
|
|
17
|
+
from phoenix.server.api.types.AnnotationSource import AnnotationSource
|
|
15
18
|
from phoenix.server.api.types.node import from_global_id_with_expected_type
|
|
16
19
|
from phoenix.server.api.types.TraceAnnotation import TraceAnnotation, to_gql_trace_annotation
|
|
20
|
+
from phoenix.server.bearer_auth import PhoenixUser
|
|
17
21
|
from phoenix.server.dml_event import TraceAnnotationDeleteEvent, TraceAnnotationInsertEvent
|
|
18
22
|
|
|
19
23
|
|
|
@@ -29,33 +33,91 @@ class TraceAnnotationMutationMixin:
|
|
|
29
33
|
async def create_trace_annotations(
|
|
30
34
|
self, info: Info[Context, None], input: list[CreateTraceAnnotationInput]
|
|
31
35
|
) -> TraceAnnotationMutationPayload:
|
|
32
|
-
|
|
36
|
+
if not input:
|
|
37
|
+
raise BadRequest("No trace annotations provided.")
|
|
38
|
+
|
|
39
|
+
assert isinstance(request := info.context.request, Request)
|
|
40
|
+
user_id: Optional[int] = None
|
|
41
|
+
if "user" in request.scope and isinstance((user := info.context.user), PhoenixUser):
|
|
42
|
+
user_id = int(user.identity)
|
|
43
|
+
|
|
44
|
+
processed_annotations_map: dict[int, models.TraceAnnotation] = {}
|
|
45
|
+
|
|
46
|
+
trace_rowids = []
|
|
47
|
+
for idx, annotation_input in enumerate(input):
|
|
48
|
+
try:
|
|
49
|
+
trace_rowid = from_global_id_with_expected_type(annotation_input.trace_id, "Trace")
|
|
50
|
+
except ValueError:
|
|
51
|
+
raise BadRequest(
|
|
52
|
+
f"Invalid trace ID for annotation at index {idx}: "
|
|
53
|
+
f"{annotation_input.trace_id}"
|
|
54
|
+
)
|
|
55
|
+
trace_rowids.append(trace_rowid)
|
|
56
|
+
|
|
33
57
|
async with info.context.db() as session:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
58
|
+
for idx, (trace_rowid, annotation_input) in enumerate(zip(trace_rowids, input)):
|
|
59
|
+
resolved_identifier = ""
|
|
60
|
+
if isinstance(annotation_input.identifier, str):
|
|
61
|
+
resolved_identifier = annotation_input.identifier
|
|
62
|
+
elif annotation_input.source == AnnotationSource.APP and user_id is not None:
|
|
63
|
+
resolved_identifier = get_user_identifier(user_id)
|
|
64
|
+
values = {
|
|
65
|
+
"trace_rowid": trace_rowid,
|
|
66
|
+
"name": annotation_input.name,
|
|
67
|
+
"label": annotation_input.label,
|
|
68
|
+
"score": annotation_input.score,
|
|
69
|
+
"explanation": annotation_input.explanation,
|
|
70
|
+
"annotator_kind": annotation_input.annotator_kind.value,
|
|
71
|
+
"metadata_": annotation_input.metadata,
|
|
72
|
+
"identifier": resolved_identifier,
|
|
73
|
+
"source": annotation_input.source.value,
|
|
74
|
+
"user_id": user_id,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
processed_annotation: Optional[models.TraceAnnotation] = None
|
|
78
|
+
|
|
79
|
+
# Check if an annotation with this trace_rowid, name, and identifier already exists
|
|
80
|
+
q = select(models.TraceAnnotation).where(
|
|
81
|
+
models.TraceAnnotation.trace_rowid == trace_rowid,
|
|
82
|
+
models.TraceAnnotation.name == annotation_input.name,
|
|
83
|
+
models.TraceAnnotation.identifier == resolved_identifier,
|
|
43
84
|
)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
85
|
+
existing_annotation = await session.scalar(q)
|
|
86
|
+
|
|
87
|
+
if existing_annotation:
|
|
88
|
+
# Update existing annotation
|
|
89
|
+
existing_annotation.name = values["name"]
|
|
90
|
+
existing_annotation.label = values["label"]
|
|
91
|
+
existing_annotation.score = values["score"]
|
|
92
|
+
existing_annotation.explanation = values["explanation"]
|
|
93
|
+
existing_annotation.metadata_ = values["metadata_"]
|
|
94
|
+
existing_annotation.annotator_kind = values["annotator_kind"]
|
|
95
|
+
existing_annotation.source = values["source"]
|
|
96
|
+
existing_annotation.user_id = values["user_id"]
|
|
97
|
+
session.add(existing_annotation)
|
|
98
|
+
processed_annotation = existing_annotation
|
|
99
|
+
|
|
100
|
+
if processed_annotation is None:
|
|
101
|
+
stmt = insert(models.TraceAnnotation).values(**values)
|
|
102
|
+
stmt = stmt.returning(models.TraceAnnotation)
|
|
103
|
+
result = await session.scalars(stmt)
|
|
104
|
+
processed_annotation = result.one()
|
|
105
|
+
|
|
106
|
+
processed_annotations_map[idx] = processed_annotation
|
|
107
|
+
|
|
108
|
+
await session.commit()
|
|
109
|
+
|
|
110
|
+
inserted_annotation_ids = tuple(anno.id for anno in processed_annotations_map.values())
|
|
111
|
+
if inserted_annotation_ids:
|
|
112
|
+
info.context.event_queue.put(TraceAnnotationInsertEvent(inserted_annotation_ids))
|
|
113
|
+
|
|
114
|
+
returned_annotations = [
|
|
115
|
+
to_gql_trace_annotation(processed_annotations_map[i])
|
|
116
|
+
for i in sorted(processed_annotations_map.keys())
|
|
117
|
+
]
|
|
118
|
+
|
|
55
119
|
return TraceAnnotationMutationPayload(
|
|
56
|
-
trace_annotations=
|
|
57
|
-
to_gql_trace_annotation(annotation) for annotation in inserted_annotations
|
|
58
|
-
],
|
|
120
|
+
trace_annotations=returned_annotations,
|
|
59
121
|
query=Query(),
|
|
60
122
|
)
|
|
61
123
|
|
|
@@ -63,65 +125,132 @@ class TraceAnnotationMutationMixin:
|
|
|
63
125
|
async def patch_trace_annotations(
|
|
64
126
|
self, info: Info[Context, None], input: list[PatchAnnotationInput]
|
|
65
127
|
) -> TraceAnnotationMutationPayload:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
128
|
+
if not input:
|
|
129
|
+
raise BadRequest("No trace annotations provided.")
|
|
130
|
+
|
|
131
|
+
assert isinstance(request := info.context.request, Request)
|
|
132
|
+
user_id: Optional[int] = None
|
|
133
|
+
if "user" in request.scope and isinstance((user := info.context.user), PhoenixUser):
|
|
134
|
+
user_id = int(user.identity)
|
|
135
|
+
|
|
136
|
+
patch_by_id = {}
|
|
137
|
+
for patch in input:
|
|
138
|
+
try:
|
|
69
139
|
trace_annotation_id = from_global_id_with_expected_type(
|
|
70
|
-
|
|
140
|
+
patch.annotation_id, "TraceAnnotation"
|
|
141
|
+
)
|
|
142
|
+
except ValueError:
|
|
143
|
+
raise BadRequest(f"Invalid trace annotation ID: {patch.annotation_id}")
|
|
144
|
+
if trace_annotation_id in patch_by_id:
|
|
145
|
+
raise BadRequest(f"Duplicate patch for trace annotation ID: {trace_annotation_id}")
|
|
146
|
+
patch_by_id[trace_annotation_id] = patch
|
|
147
|
+
|
|
148
|
+
async with info.context.db() as session:
|
|
149
|
+
trace_annotations_by_id = {}
|
|
150
|
+
for trace_annotation in await session.scalars(
|
|
151
|
+
select(models.TraceAnnotation).where(
|
|
152
|
+
models.TraceAnnotation.id.in_(patch_by_id.keys())
|
|
71
153
|
)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
(
|
|
77
|
-
models.TraceAnnotation.annotator_kind,
|
|
78
|
-
annotation.annotator_kind.value
|
|
79
|
-
if annotation.annotator_kind is not None
|
|
80
|
-
else None,
|
|
81
|
-
False,
|
|
82
|
-
),
|
|
83
|
-
(models.TraceAnnotation.label, annotation.label, True),
|
|
84
|
-
(models.TraceAnnotation.score, annotation.score, True),
|
|
85
|
-
(models.TraceAnnotation.explanation, annotation.explanation, True),
|
|
86
|
-
(models.TraceAnnotation.metadata_, annotation.metadata, False),
|
|
154
|
+
):
|
|
155
|
+
if trace_annotation.user_id != user_id:
|
|
156
|
+
raise Unauthorized(
|
|
157
|
+
"At least one trace annotation is not associated with the current user."
|
|
87
158
|
)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
159
|
+
trace_annotations_by_id[trace_annotation.id] = trace_annotation
|
|
160
|
+
|
|
161
|
+
missing_trace_annotation_ids = set(patch_by_id.keys()) - set(
|
|
162
|
+
trace_annotations_by_id.keys()
|
|
163
|
+
)
|
|
164
|
+
if missing_trace_annotation_ids:
|
|
165
|
+
raise NotFound(
|
|
166
|
+
f"Could not find trace annotations with IDs: {missing_trace_annotation_ids}"
|
|
95
167
|
)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
168
|
+
|
|
169
|
+
for trace_annotation_id, patch in patch_by_id.items():
|
|
170
|
+
trace_annotation = trace_annotations_by_id[trace_annotation_id]
|
|
171
|
+
if patch.name:
|
|
172
|
+
trace_annotation.name = patch.name
|
|
173
|
+
if patch.annotator_kind:
|
|
174
|
+
trace_annotation.annotator_kind = patch.annotator_kind.value
|
|
175
|
+
if patch.label is not UNSET:
|
|
176
|
+
trace_annotation.label = patch.label
|
|
177
|
+
if patch.score is not UNSET:
|
|
178
|
+
trace_annotation.score = patch.score
|
|
179
|
+
if patch.explanation is not UNSET:
|
|
180
|
+
trace_annotation.explanation = patch.explanation
|
|
181
|
+
if patch.metadata is not UNSET:
|
|
182
|
+
assert isinstance(patch.metadata, dict)
|
|
183
|
+
trace_annotation.metadata_ = patch.metadata
|
|
184
|
+
if patch.identifier is not UNSET:
|
|
185
|
+
trace_annotation.identifier = patch.identifier or ""
|
|
186
|
+
session.add(trace_annotation)
|
|
187
|
+
await session.commit()
|
|
188
|
+
|
|
189
|
+
patched_annotations = [
|
|
190
|
+
to_gql_trace_annotation(trace_annotation)
|
|
191
|
+
for trace_annotation in trace_annotations_by_id.values()
|
|
192
|
+
]
|
|
193
|
+
info.context.event_queue.put(TraceAnnotationInsertEvent(tuple(patch_by_id.keys())))
|
|
194
|
+
return TraceAnnotationMutationPayload(
|
|
195
|
+
trace_annotations=patched_annotations,
|
|
196
|
+
query=Query(),
|
|
197
|
+
)
|
|
100
198
|
|
|
101
199
|
@strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
|
|
102
200
|
async def delete_trace_annotations(
|
|
103
201
|
self, info: Info[Context, None], input: DeleteAnnotationsInput
|
|
104
202
|
) -> TraceAnnotationMutationPayload:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
]
|
|
203
|
+
if not input.annotation_ids:
|
|
204
|
+
raise BadRequest("No trace annotation IDs provided.")
|
|
205
|
+
|
|
206
|
+
trace_annotation_ids: dict[int, None] = {} # use dict to preserve order
|
|
207
|
+
for annotation_gid in input.annotation_ids:
|
|
208
|
+
try:
|
|
209
|
+
annotation_id = from_global_id_with_expected_type(annotation_gid, "TraceAnnotation")
|
|
210
|
+
except ValueError:
|
|
211
|
+
raise BadRequest(f"Invalid trace annotation ID: {annotation_gid}")
|
|
212
|
+
if annotation_id in trace_annotation_ids:
|
|
213
|
+
raise BadRequest(f"Duplicate trace annotation ID: {annotation_id}")
|
|
214
|
+
trace_annotation_ids[annotation_id] = None
|
|
215
|
+
|
|
216
|
+
assert isinstance(request := info.context.request, Request)
|
|
217
|
+
user_id: Optional[int] = None
|
|
218
|
+
user_is_admin = False
|
|
219
|
+
if "user" in request.scope and isinstance((user := info.context.user), PhoenixUser):
|
|
220
|
+
user_id = int(user.identity)
|
|
221
|
+
user_is_admin = user.is_admin
|
|
222
|
+
|
|
109
223
|
async with info.context.db() as session:
|
|
110
|
-
|
|
224
|
+
result = await session.scalars(
|
|
111
225
|
delete(models.TraceAnnotation)
|
|
112
|
-
.where(models.TraceAnnotation.id.in_(trace_annotation_ids))
|
|
226
|
+
.where(models.TraceAnnotation.id.in_(trace_annotation_ids.keys()))
|
|
113
227
|
.returning(models.TraceAnnotation)
|
|
114
228
|
)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
229
|
+
deleted_annotations_by_id = {annotation.id: annotation for annotation in result.all()}
|
|
230
|
+
|
|
231
|
+
if not user_is_admin and any(
|
|
232
|
+
annotation.user_id != user_id for annotation in deleted_annotations_by_id.values()
|
|
233
|
+
):
|
|
234
|
+
await session.rollback()
|
|
235
|
+
raise Unauthorized(
|
|
236
|
+
"At least one trace annotation is not associated with the current user "
|
|
237
|
+
"and the current user is not an admin."
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
missing_trace_annotation_ids = set(trace_annotation_ids.keys()) - set(
|
|
241
|
+
deleted_annotations_by_id.keys()
|
|
124
242
|
)
|
|
243
|
+
if missing_trace_annotation_ids:
|
|
244
|
+
raise NotFound(
|
|
245
|
+
f"Could not find trace annotations with IDs: {missing_trace_annotation_ids}"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
deleted_gql_annotations = [
|
|
249
|
+
to_gql_trace_annotation(deleted_annotations_by_id[id]) for id in trace_annotation_ids
|
|
250
|
+
]
|
|
251
|
+
info.context.event_queue.put(
|
|
252
|
+
TraceAnnotationDeleteEvent(tuple(deleted_annotations_by_id.keys()))
|
|
253
|
+
)
|
|
125
254
|
return TraceAnnotationMutationPayload(
|
|
126
|
-
trace_annotations=
|
|
255
|
+
trace_annotations=deleted_gql_annotations, query=Query()
|
|
127
256
|
)
|
phoenix/server/api/queries.py
CHANGED
|
@@ -19,6 +19,7 @@ from phoenix.config import (
|
|
|
19
19
|
getenv,
|
|
20
20
|
)
|
|
21
21
|
from phoenix.db import enums, models
|
|
22
|
+
from phoenix.db.constants import DEFAULT_PROJECT_TRACE_RETENTION_POLICY_ID
|
|
22
23
|
from phoenix.db.helpers import SupportedSQLDialect, exclude_experiment_projects
|
|
23
24
|
from phoenix.db.models import DatasetExample as OrmExample
|
|
24
25
|
from phoenix.db.models import DatasetExampleRevision as OrmRevision
|
|
@@ -42,6 +43,7 @@ from phoenix.server.api.input_types.ClusterInput import ClusterInput
|
|
|
42
43
|
from phoenix.server.api.input_types.Coordinates import InputCoordinate2D, InputCoordinate3D
|
|
43
44
|
from phoenix.server.api.input_types.DatasetSort import DatasetSort
|
|
44
45
|
from phoenix.server.api.input_types.InvocationParameters import InvocationParameter
|
|
46
|
+
from phoenix.server.api.types.AnnotationConfig import AnnotationConfig, to_gql_annotation_config
|
|
45
47
|
from phoenix.server.api.types.Cluster import Cluster, to_gql_clusters
|
|
46
48
|
from phoenix.server.api.types.Dataset import Dataset, to_gql_dataset
|
|
47
49
|
from phoenix.server.api.types.DatasetExample import DatasetExample
|
|
@@ -65,14 +67,17 @@ from phoenix.server.api.types.node import from_global_id, from_global_id_with_ex
|
|
|
65
67
|
from phoenix.server.api.types.pagination import ConnectionArgs, CursorString, connection_from_list
|
|
66
68
|
from phoenix.server.api.types.Project import Project
|
|
67
69
|
from phoenix.server.api.types.ProjectSession import ProjectSession, to_gql_project_session
|
|
70
|
+
from phoenix.server.api.types.ProjectTraceRetentionPolicy import ProjectTraceRetentionPolicy
|
|
68
71
|
from phoenix.server.api.types.Prompt import Prompt, to_gql_prompt_from_orm
|
|
69
72
|
from phoenix.server.api.types.PromptLabel import PromptLabel, to_gql_prompt_label
|
|
70
73
|
from phoenix.server.api.types.PromptVersion import PromptVersion, to_gql_prompt_version
|
|
71
74
|
from phoenix.server.api.types.PromptVersionTag import PromptVersionTag, to_gql_prompt_version_tag
|
|
72
75
|
from phoenix.server.api.types.SortDir import SortDir
|
|
73
76
|
from phoenix.server.api.types.Span import Span
|
|
77
|
+
from phoenix.server.api.types.SpanAnnotation import SpanAnnotation, to_gql_span_annotation
|
|
74
78
|
from phoenix.server.api.types.SystemApiKey import SystemApiKey
|
|
75
79
|
from phoenix.server.api.types.Trace import Trace
|
|
80
|
+
from phoenix.server.api.types.TraceAnnotation import TraceAnnotation, to_gql_trace_annotation
|
|
76
81
|
from phoenix.server.api.types.User import User, to_gql_user
|
|
77
82
|
from phoenix.server.api.types.UserApiKey import UserApiKey, to_gql_api_key
|
|
78
83
|
from phoenix.server.api.types.UserRole import UserRole
|
|
@@ -588,6 +593,26 @@ class Query:
|
|
|
588
593
|
if not (prompt_version_tag := await session.get(models.PromptVersionTag, node_id)):
|
|
589
594
|
raise NotFound(f"Unknown prompt version tag: {id}")
|
|
590
595
|
return to_gql_prompt_version_tag(prompt_version_tag)
|
|
596
|
+
elif type_name == ProjectTraceRetentionPolicy.__name__:
|
|
597
|
+
async with info.context.db() as session:
|
|
598
|
+
db_policy = await session.scalar(
|
|
599
|
+
select(models.ProjectTraceRetentionPolicy).filter_by(id=node_id)
|
|
600
|
+
)
|
|
601
|
+
if not db_policy:
|
|
602
|
+
raise NotFound(f"Unknown project trace retention policy: {id}")
|
|
603
|
+
return ProjectTraceRetentionPolicy(id=db_policy.id, db_policy=db_policy)
|
|
604
|
+
elif type_name == SpanAnnotation.__name__:
|
|
605
|
+
async with info.context.db() as session:
|
|
606
|
+
span_annotation = await session.get(models.SpanAnnotation, node_id)
|
|
607
|
+
if not span_annotation:
|
|
608
|
+
raise NotFound(f"Unknown span annotation: {id}")
|
|
609
|
+
return to_gql_span_annotation(span_annotation)
|
|
610
|
+
elif type_name == TraceAnnotation.__name__:
|
|
611
|
+
async with info.context.db() as session:
|
|
612
|
+
trace_annotation = await session.get(models.TraceAnnotation, node_id)
|
|
613
|
+
if not trace_annotation:
|
|
614
|
+
raise NotFound(f"Unknown trace annotation: {id}")
|
|
615
|
+
return to_gql_trace_annotation(trace_annotation)
|
|
591
616
|
raise NotFound(f"Unknown node type: {type_name}")
|
|
592
617
|
|
|
593
618
|
@strawberry.field
|
|
@@ -657,6 +682,28 @@ class Query:
|
|
|
657
682
|
args=args,
|
|
658
683
|
)
|
|
659
684
|
|
|
685
|
+
@strawberry.field
|
|
686
|
+
async def annotation_configs(
|
|
687
|
+
self,
|
|
688
|
+
info: Info[Context, None],
|
|
689
|
+
first: Optional[int] = 50,
|
|
690
|
+
last: Optional[int] = None,
|
|
691
|
+
after: Optional[str] = None,
|
|
692
|
+
before: Optional[str] = None,
|
|
693
|
+
) -> Connection[AnnotationConfig]:
|
|
694
|
+
args = ConnectionArgs(
|
|
695
|
+
first=first,
|
|
696
|
+
after=after if isinstance(after, CursorString) else None,
|
|
697
|
+
last=last,
|
|
698
|
+
before=before if isinstance(before, CursorString) else None,
|
|
699
|
+
)
|
|
700
|
+
async with info.context.db() as session:
|
|
701
|
+
configs = await session.stream_scalars(
|
|
702
|
+
select(models.AnnotationConfig).order_by(models.AnnotationConfig.name)
|
|
703
|
+
)
|
|
704
|
+
data = [to_gql_annotation_config(config) async for config in configs]
|
|
705
|
+
return connection_from_list(data=data, args=args)
|
|
706
|
+
|
|
660
707
|
@strawberry.field
|
|
661
708
|
def clusters(
|
|
662
709
|
self,
|
|
@@ -785,6 +832,45 @@ class Query:
|
|
|
785
832
|
clustered_events=clustered_events,
|
|
786
833
|
)
|
|
787
834
|
|
|
835
|
+
@strawberry.field
|
|
836
|
+
async def default_project_trace_retention_policy(
|
|
837
|
+
self,
|
|
838
|
+
info: Info[Context, None],
|
|
839
|
+
) -> ProjectTraceRetentionPolicy:
|
|
840
|
+
stmt = select(models.ProjectTraceRetentionPolicy).filter_by(
|
|
841
|
+
id=DEFAULT_PROJECT_TRACE_RETENTION_POLICY_ID
|
|
842
|
+
)
|
|
843
|
+
async with info.context.db() as session:
|
|
844
|
+
db_policy = await session.scalar(stmt)
|
|
845
|
+
assert db_policy
|
|
846
|
+
return ProjectTraceRetentionPolicy(id=db_policy.id, db_policy=db_policy)
|
|
847
|
+
|
|
848
|
+
@strawberry.field
|
|
849
|
+
async def project_trace_retention_policies(
|
|
850
|
+
self,
|
|
851
|
+
info: Info[Context, None],
|
|
852
|
+
first: Optional[int] = 100,
|
|
853
|
+
last: Optional[int] = UNSET,
|
|
854
|
+
after: Optional[CursorString] = UNSET,
|
|
855
|
+
before: Optional[CursorString] = UNSET,
|
|
856
|
+
) -> Connection[ProjectTraceRetentionPolicy]:
|
|
857
|
+
args = ConnectionArgs(
|
|
858
|
+
first=first,
|
|
859
|
+
after=after if isinstance(after, CursorString) else None,
|
|
860
|
+
last=last,
|
|
861
|
+
before=before if isinstance(before, CursorString) else None,
|
|
862
|
+
)
|
|
863
|
+
stmt = select(models.ProjectTraceRetentionPolicy).order_by(
|
|
864
|
+
models.ProjectTraceRetentionPolicy.id
|
|
865
|
+
)
|
|
866
|
+
async with info.context.db() as session:
|
|
867
|
+
result = await session.stream_scalars(stmt)
|
|
868
|
+
data = [
|
|
869
|
+
ProjectTraceRetentionPolicy(id=db_policy.id, db_policy=db_policy)
|
|
870
|
+
async for db_policy in result
|
|
871
|
+
]
|
|
872
|
+
return connection_from_list(data=data, args=args)
|
|
873
|
+
|
|
788
874
|
@strawberry.field(
|
|
789
875
|
description="The allocated storage capacity of the database in bytes. "
|
|
790
876
|
"Return None if this information is unavailable.",
|
|
@@ -4,6 +4,8 @@ from starlette.status import HTTP_403_FORBIDDEN
|
|
|
4
4
|
|
|
5
5
|
from phoenix.server.bearer_auth import is_authenticated
|
|
6
6
|
|
|
7
|
+
from .annotation_configs import router as annotation_configs_router
|
|
8
|
+
from .annotations import router as annotations_router
|
|
7
9
|
from .datasets import router as datasets_router
|
|
8
10
|
from .evaluations import router as evaluations_router
|
|
9
11
|
from .experiment_evaluations import router as experiment_evaluations_router
|
|
@@ -56,6 +58,8 @@ def create_v1_router(authentication_enabled: bool) -> APIRouter:
|
|
|
56
58
|
]
|
|
57
59
|
),
|
|
58
60
|
)
|
|
61
|
+
router.include_router(annotation_configs_router)
|
|
62
|
+
router.include_router(annotations_router)
|
|
59
63
|
router.include_router(datasets_router)
|
|
60
64
|
router.include_router(experiments_router)
|
|
61
65
|
router.include_router(experiment_runs_router)
|