arize-phoenix 12.3.0__py3-none-any.whl → 12.4.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-12.3.0.dist-info → arize_phoenix-12.4.0.dist-info}/METADATA +2 -1
- {arize_phoenix-12.3.0.dist-info → arize_phoenix-12.4.0.dist-info}/RECORD +37 -37
- phoenix/auth.py +19 -0
- phoenix/config.py +302 -53
- phoenix/db/README.md +546 -28
- phoenix/server/api/routers/auth.py +21 -30
- phoenix/server/api/routers/oauth2.py +213 -24
- phoenix/server/api/routers/v1/__init__.py +2 -3
- phoenix/server/api/routers/v1/annotation_configs.py +12 -29
- phoenix/server/api/routers/v1/annotations.py +21 -22
- phoenix/server/api/routers/v1/datasets.py +38 -56
- phoenix/server/api/routers/v1/documents.py +2 -3
- phoenix/server/api/routers/v1/evaluations.py +12 -24
- phoenix/server/api/routers/v1/experiment_evaluations.py +2 -3
- phoenix/server/api/routers/v1/experiment_runs.py +9 -10
- phoenix/server/api/routers/v1/experiments.py +16 -17
- phoenix/server/api/routers/v1/projects.py +15 -21
- phoenix/server/api/routers/v1/prompts.py +30 -31
- phoenix/server/api/routers/v1/sessions.py +2 -5
- phoenix/server/api/routers/v1/spans.py +35 -26
- phoenix/server/api/routers/v1/traces.py +11 -19
- phoenix/server/api/routers/v1/users.py +14 -23
- phoenix/server/api/routers/v1/utils.py +3 -7
- phoenix/server/app.py +1 -2
- phoenix/server/authorization.py +2 -3
- phoenix/server/bearer_auth.py +4 -5
- phoenix/server/oauth2.py +172 -5
- phoenix/server/static/.vite/manifest.json +9 -9
- phoenix/server/static/assets/{components-Bs8eJEpU.js → components-BvsExS75.js} +110 -120
- phoenix/server/static/assets/{index-C6WEu5UP.js → index-iq8WDxat.js} +1 -1
- phoenix/server/static/assets/{pages-D-n2pkoG.js → pages-Ckg4SLQ9.js} +4 -4
- phoenix/trace/attributes.py +80 -13
- phoenix/version.py +1 -1
- {arize_phoenix-12.3.0.dist-info → arize_phoenix-12.4.0.dist-info}/WHEEL +0 -0
- {arize_phoenix-12.3.0.dist-info → arize_phoenix-12.4.0.dist-info}/entry_points.txt +0 -0
- {arize_phoenix-12.3.0.dist-info → arize_phoenix-12.4.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-12.3.0.dist-info → arize_phoenix-12.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -7,11 +7,6 @@ from sqlalchemy import delete, select
|
|
|
7
7
|
from sqlalchemy.exc import IntegrityError as PostgreSQLIntegrityError
|
|
8
8
|
from sqlean.dbapi2 import IntegrityError as SQLiteIntegrityError # type: ignore[import-untyped]
|
|
9
9
|
from starlette.requests import Request
|
|
10
|
-
from starlette.status import (
|
|
11
|
-
HTTP_400_BAD_REQUEST,
|
|
12
|
-
HTTP_404_NOT_FOUND,
|
|
13
|
-
HTTP_409_CONFLICT,
|
|
14
|
-
)
|
|
15
10
|
from strawberry.relay import GlobalID
|
|
16
11
|
from typing_extensions import TypeAlias, assert_never
|
|
17
12
|
|
|
@@ -206,7 +201,7 @@ async def list_annotation_configs(
|
|
|
206
201
|
except ValueError:
|
|
207
202
|
raise HTTPException(
|
|
208
203
|
detail=f"Invalid cursor: {cursor}",
|
|
209
|
-
status_code=
|
|
204
|
+
status_code=400,
|
|
210
205
|
)
|
|
211
206
|
if cursor_gid.type_name not in (
|
|
212
207
|
CategoricalAnnotationConfigType.__name__,
|
|
@@ -215,7 +210,7 @@ async def list_annotation_configs(
|
|
|
215
210
|
):
|
|
216
211
|
raise HTTPException(
|
|
217
212
|
detail=f"Invalid cursor: {cursor}",
|
|
218
|
-
status_code=
|
|
213
|
+
status_code=400,
|
|
219
214
|
)
|
|
220
215
|
cursor_id = int(cursor_gid.node_id)
|
|
221
216
|
|
|
@@ -261,9 +256,7 @@ async def get_annotation_config_by_name_or_id(
|
|
|
261
256
|
query = query.where(models.AnnotationConfig.name == config_identifier)
|
|
262
257
|
config = await session.scalar(query)
|
|
263
258
|
if not config:
|
|
264
|
-
raise HTTPException(
|
|
265
|
-
status_code=HTTP_404_NOT_FOUND, detail="Annotation configuration not found"
|
|
266
|
-
)
|
|
259
|
+
raise HTTPException(status_code=404, detail="Annotation configuration not found")
|
|
267
260
|
return GetAnnotationConfigResponseBody(data=db_to_api_annotation_config(config))
|
|
268
261
|
|
|
269
262
|
|
|
@@ -282,7 +275,7 @@ async def create_annotation_config(
|
|
|
282
275
|
try:
|
|
283
276
|
db_config = _to_db_annotation_config(input_config)
|
|
284
277
|
except ValueError as error:
|
|
285
|
-
raise HTTPException(status_code=
|
|
278
|
+
raise HTTPException(status_code=400, detail=str(error))
|
|
286
279
|
|
|
287
280
|
async with request.app.state.db() as session:
|
|
288
281
|
annotation_config = models.AnnotationConfig(
|
|
@@ -294,7 +287,7 @@ async def create_annotation_config(
|
|
|
294
287
|
await session.commit()
|
|
295
288
|
except (PostgreSQLIntegrityError, SQLiteIntegrityError):
|
|
296
289
|
raise HTTPException(
|
|
297
|
-
status_code=
|
|
290
|
+
status_code=409,
|
|
298
291
|
detail="The name of the annotation configuration is already taken",
|
|
299
292
|
)
|
|
300
293
|
return CreateAnnotationConfigResponseBody(
|
|
@@ -321,22 +314,18 @@ async def update_annotation_config(
|
|
|
321
314
|
ContinuousAnnotationConfigType.__name__,
|
|
322
315
|
FreeformAnnotationConfigType.__name__,
|
|
323
316
|
):
|
|
324
|
-
raise HTTPException(
|
|
325
|
-
status_code=HTTP_400_BAD_REQUEST, detail="Invalid annotation configuration ID"
|
|
326
|
-
)
|
|
317
|
+
raise HTTPException(status_code=400, detail="Invalid annotation configuration ID")
|
|
327
318
|
config_rowid = int(config_gid.node_id)
|
|
328
319
|
|
|
329
320
|
try:
|
|
330
321
|
db_config = _to_db_annotation_config(input_config)
|
|
331
322
|
except ValueError as error:
|
|
332
|
-
raise HTTPException(status_code=
|
|
323
|
+
raise HTTPException(status_code=400, detail=str(error))
|
|
333
324
|
|
|
334
325
|
async with request.app.state.db() as session:
|
|
335
326
|
existing_config = await session.get(models.AnnotationConfig, config_rowid)
|
|
336
327
|
if not existing_config:
|
|
337
|
-
raise HTTPException(
|
|
338
|
-
status_code=HTTP_404_NOT_FOUND, detail="Annotation configuration not found"
|
|
339
|
-
)
|
|
328
|
+
raise HTTPException(status_code=404, detail="Annotation configuration not found")
|
|
340
329
|
|
|
341
330
|
existing_config.name = input_config.name
|
|
342
331
|
existing_config.config = db_config
|
|
@@ -345,7 +334,7 @@ async def update_annotation_config(
|
|
|
345
334
|
await session.commit()
|
|
346
335
|
except (PostgreSQLIntegrityError, SQLiteIntegrityError):
|
|
347
336
|
raise HTTPException(
|
|
348
|
-
status_code=
|
|
337
|
+
status_code=409,
|
|
349
338
|
detail="The name of the annotation configuration is already taken",
|
|
350
339
|
)
|
|
351
340
|
|
|
@@ -366,9 +355,7 @@ async def delete_annotation_config(
|
|
|
366
355
|
ContinuousAnnotationConfigType.__name__,
|
|
367
356
|
FreeformAnnotationConfigType.__name__,
|
|
368
357
|
):
|
|
369
|
-
raise HTTPException(
|
|
370
|
-
status_code=HTTP_400_BAD_REQUEST, detail="Invalid annotation configuration ID"
|
|
371
|
-
)
|
|
358
|
+
raise HTTPException(status_code=400, detail="Invalid annotation configuration ID")
|
|
372
359
|
config_rowid = int(config_gid.node_id)
|
|
373
360
|
async with request.app.state.db() as session:
|
|
374
361
|
stmt = (
|
|
@@ -378,9 +365,7 @@ async def delete_annotation_config(
|
|
|
378
365
|
)
|
|
379
366
|
annotation_config = await session.scalar(stmt)
|
|
380
367
|
if annotation_config is None:
|
|
381
|
-
raise HTTPException(
|
|
382
|
-
status_code=HTTP_404_NOT_FOUND, detail="Annotation configuration not found"
|
|
383
|
-
)
|
|
368
|
+
raise HTTPException(status_code=404, detail="Annotation configuration not found")
|
|
384
369
|
await session.commit()
|
|
385
370
|
return DeleteAnnotationConfigResponseBody(data=db_to_api_annotation_config(annotation_config))
|
|
386
371
|
|
|
@@ -400,9 +385,7 @@ def _get_annotation_config_db_id(config_gid: str) -> int:
|
|
|
400
385
|
def _reserve_note_annotation_name(data: AnnotationConfigData) -> str:
|
|
401
386
|
name = data.name
|
|
402
387
|
if name == "note":
|
|
403
|
-
raise HTTPException(
|
|
404
|
-
status_code=HTTP_409_CONFLICT, detail="The name 'note' is reserved for span notes"
|
|
405
|
-
)
|
|
388
|
+
raise HTTPException(status_code=409, detail="The name 'note' is reserved for span notes")
|
|
406
389
|
return name
|
|
407
390
|
|
|
408
391
|
|
|
@@ -8,7 +8,6 @@ from fastapi import APIRouter, HTTPException, Path, Query
|
|
|
8
8
|
from pydantic import Field
|
|
9
9
|
from sqlalchemy import exists, select
|
|
10
10
|
from starlette.requests import Request
|
|
11
|
-
from starlette.status import HTTP_200_OK, HTTP_404_NOT_FOUND, HTTP_422_UNPROCESSABLE_ENTITY
|
|
12
11
|
from strawberry.relay import GlobalID
|
|
13
12
|
|
|
14
13
|
from phoenix.db import models
|
|
@@ -198,11 +197,11 @@ class SessionAnnotationsResponseBody(PaginatedResponseBody[SessionAnnotation]):
|
|
|
198
197
|
"/projects/{project_identifier}/span_annotations",
|
|
199
198
|
operation_id="listSpanAnnotationsBySpanIds",
|
|
200
199
|
summary="Get span annotations for a list of span_ids.",
|
|
201
|
-
status_code=
|
|
200
|
+
status_code=200,
|
|
202
201
|
responses=add_errors_to_responses(
|
|
203
202
|
[
|
|
204
|
-
{"status_code":
|
|
205
|
-
{"status_code":
|
|
203
|
+
{"status_code": 404, "description": "Project or spans not found"},
|
|
204
|
+
{"status_code": 422, "description": "Invalid parameters"},
|
|
206
205
|
]
|
|
207
206
|
),
|
|
208
207
|
)
|
|
@@ -240,7 +239,7 @@ async def list_span_annotations(
|
|
|
240
239
|
span_ids = list({*span_ids})
|
|
241
240
|
if len(span_ids) > MAX_SPAN_IDS:
|
|
242
241
|
raise HTTPException(
|
|
243
|
-
status_code=
|
|
242
|
+
status_code=422,
|
|
244
243
|
detail=f"Too many span_ids supplied: {len(span_ids)} (max {MAX_SPAN_IDS})",
|
|
245
244
|
)
|
|
246
245
|
|
|
@@ -248,7 +247,7 @@ async def list_span_annotations(
|
|
|
248
247
|
project = await _get_project_by_identifier(session, project_identifier)
|
|
249
248
|
if not project:
|
|
250
249
|
raise HTTPException(
|
|
251
|
-
status_code=
|
|
250
|
+
status_code=404,
|
|
252
251
|
detail=f"Project with identifier {project_identifier} not found",
|
|
253
252
|
)
|
|
254
253
|
|
|
@@ -280,7 +279,7 @@ async def list_span_annotations(
|
|
|
280
279
|
cursor_id = int(GlobalID.from_id(cursor).node_id)
|
|
281
280
|
except ValueError:
|
|
282
281
|
raise HTTPException(
|
|
283
|
-
status_code=
|
|
282
|
+
status_code=422,
|
|
284
283
|
detail="Invalid cursor value",
|
|
285
284
|
)
|
|
286
285
|
stmt = stmt.where(models.SpanAnnotation.id <= cursor_id)
|
|
@@ -310,7 +309,7 @@ async def list_span_annotations(
|
|
|
310
309
|
if not spans_exist:
|
|
311
310
|
raise HTTPException(
|
|
312
311
|
detail="None of the supplied span_ids exist in this project",
|
|
313
|
-
status_code=
|
|
312
|
+
status_code=404,
|
|
314
313
|
)
|
|
315
314
|
|
|
316
315
|
return SpanAnnotationsResponseBody(data=[], next_cursor=None)
|
|
@@ -343,11 +342,11 @@ async def list_span_annotations(
|
|
|
343
342
|
"/projects/{project_identifier}/trace_annotations",
|
|
344
343
|
operation_id="listTraceAnnotationsByTraceIds",
|
|
345
344
|
summary="Get trace annotations for a list of trace_ids.",
|
|
346
|
-
status_code=
|
|
345
|
+
status_code=200,
|
|
347
346
|
responses=add_errors_to_responses(
|
|
348
347
|
[
|
|
349
|
-
{"status_code":
|
|
350
|
-
{"status_code":
|
|
348
|
+
{"status_code": 404, "description": "Project or traces not found"},
|
|
349
|
+
{"status_code": 422, "description": "Invalid parameters"},
|
|
351
350
|
]
|
|
352
351
|
),
|
|
353
352
|
)
|
|
@@ -385,7 +384,7 @@ async def list_trace_annotations(
|
|
|
385
384
|
trace_ids = list({*trace_ids})
|
|
386
385
|
if len(trace_ids) > MAX_TRACE_IDS:
|
|
387
386
|
raise HTTPException(
|
|
388
|
-
status_code=
|
|
387
|
+
status_code=422,
|
|
389
388
|
detail=f"Too many trace_ids supplied: {len(trace_ids)} (max {MAX_TRACE_IDS})",
|
|
390
389
|
)
|
|
391
390
|
|
|
@@ -393,7 +392,7 @@ async def list_trace_annotations(
|
|
|
393
392
|
project = await _get_project_by_identifier(session, project_identifier)
|
|
394
393
|
if not project:
|
|
395
394
|
raise HTTPException(
|
|
396
|
-
status_code=
|
|
395
|
+
status_code=404,
|
|
397
396
|
detail=f"Project with identifier {project_identifier} not found",
|
|
398
397
|
)
|
|
399
398
|
|
|
@@ -424,7 +423,7 @@ async def list_trace_annotations(
|
|
|
424
423
|
cursor_id = int(GlobalID.from_id(cursor).node_id)
|
|
425
424
|
except ValueError:
|
|
426
425
|
raise HTTPException(
|
|
427
|
-
status_code=
|
|
426
|
+
status_code=422,
|
|
428
427
|
detail="Invalid cursor value",
|
|
429
428
|
)
|
|
430
429
|
stmt = stmt.where(models.TraceAnnotation.id <= cursor_id)
|
|
@@ -450,7 +449,7 @@ async def list_trace_annotations(
|
|
|
450
449
|
if not traces_exist:
|
|
451
450
|
raise HTTPException(
|
|
452
451
|
detail="None of the supplied trace_ids exist in this project",
|
|
453
|
-
status_code=
|
|
452
|
+
status_code=404,
|
|
454
453
|
)
|
|
455
454
|
|
|
456
455
|
return TraceAnnotationsResponseBody(data=[], next_cursor=None)
|
|
@@ -483,11 +482,11 @@ async def list_trace_annotations(
|
|
|
483
482
|
"/projects/{project_identifier}/session_annotations",
|
|
484
483
|
operation_id="listSessionAnnotationsBySessionIds",
|
|
485
484
|
summary="Get session annotations for a list of session_ids.",
|
|
486
|
-
status_code=
|
|
485
|
+
status_code=200,
|
|
487
486
|
responses=add_errors_to_responses(
|
|
488
487
|
[
|
|
489
|
-
{"status_code":
|
|
490
|
-
{"status_code":
|
|
488
|
+
{"status_code": 404, "description": "Project or sessions not found"},
|
|
489
|
+
{"status_code": 422, "description": "Invalid parameters"},
|
|
491
490
|
]
|
|
492
491
|
),
|
|
493
492
|
)
|
|
@@ -525,7 +524,7 @@ async def list_session_annotations(
|
|
|
525
524
|
session_ids = list({*session_ids})
|
|
526
525
|
if len(session_ids) > MAX_SESSION_IDS:
|
|
527
526
|
raise HTTPException(
|
|
528
|
-
status_code=
|
|
527
|
+
status_code=422,
|
|
529
528
|
detail=f"Too many session_ids supplied: {len(session_ids)} (max {MAX_SESSION_IDS})",
|
|
530
529
|
)
|
|
531
530
|
|
|
@@ -533,7 +532,7 @@ async def list_session_annotations(
|
|
|
533
532
|
project = await _get_project_by_identifier(session, project_identifier)
|
|
534
533
|
if not project:
|
|
535
534
|
raise HTTPException(
|
|
536
|
-
status_code=
|
|
535
|
+
status_code=404,
|
|
537
536
|
detail=f"Project with identifier {project_identifier} not found",
|
|
538
537
|
)
|
|
539
538
|
|
|
@@ -571,7 +570,7 @@ async def list_session_annotations(
|
|
|
571
570
|
cursor_id = int(GlobalID.from_id(cursor).node_id)
|
|
572
571
|
except ValueError:
|
|
573
572
|
raise HTTPException(
|
|
574
|
-
status_code=
|
|
573
|
+
status_code=422,
|
|
575
574
|
detail="Invalid cursor value",
|
|
576
575
|
)
|
|
577
576
|
stmt = stmt.where(models.ProjectSessionAnnotation.id <= cursor_id)
|
|
@@ -597,7 +596,7 @@ async def list_session_annotations(
|
|
|
597
596
|
if not sessions_exist:
|
|
598
597
|
raise HTTPException(
|
|
599
598
|
detail="None of the supplied session_ids exist in this project",
|
|
600
|
-
status_code=
|
|
599
|
+
status_code=404,
|
|
601
600
|
)
|
|
602
601
|
|
|
603
602
|
return SessionAnnotationsResponseBody(data=[], next_cursor=None)
|
|
@@ -23,14 +23,6 @@ from starlette.concurrency import run_in_threadpool
|
|
|
23
23
|
from starlette.datastructures import FormData, UploadFile
|
|
24
24
|
from starlette.requests import Request
|
|
25
25
|
from starlette.responses import Response
|
|
26
|
-
from starlette.status import (
|
|
27
|
-
HTTP_200_OK,
|
|
28
|
-
HTTP_204_NO_CONTENT,
|
|
29
|
-
HTTP_404_NOT_FOUND,
|
|
30
|
-
HTTP_409_CONFLICT,
|
|
31
|
-
HTTP_422_UNPROCESSABLE_ENTITY,
|
|
32
|
-
HTTP_429_TOO_MANY_REQUESTS,
|
|
33
|
-
)
|
|
34
26
|
from strawberry.relay import GlobalID
|
|
35
27
|
from typing_extensions import TypeAlias, assert_never
|
|
36
28
|
|
|
@@ -91,7 +83,7 @@ class ListDatasetsResponseBody(PaginatedResponseBody[Dataset]):
|
|
|
91
83
|
"/datasets",
|
|
92
84
|
operation_id="listDatasets",
|
|
93
85
|
summary="List datasets",
|
|
94
|
-
responses=add_errors_to_responses([
|
|
86
|
+
responses=add_errors_to_responses([422]),
|
|
95
87
|
)
|
|
96
88
|
async def list_datasets(
|
|
97
89
|
request: Request,
|
|
@@ -125,7 +117,7 @@ async def list_datasets(
|
|
|
125
117
|
except ValueError:
|
|
126
118
|
raise HTTPException(
|
|
127
119
|
detail=f"Invalid cursor format: {cursor}",
|
|
128
|
-
status_code=
|
|
120
|
+
status_code=422,
|
|
129
121
|
)
|
|
130
122
|
if name:
|
|
131
123
|
query = query.filter(models.Dataset.name == name)
|
|
@@ -164,11 +156,11 @@ async def list_datasets(
|
|
|
164
156
|
"/datasets/{id}",
|
|
165
157
|
operation_id="deleteDatasetById",
|
|
166
158
|
summary="Delete dataset by ID",
|
|
167
|
-
status_code=
|
|
159
|
+
status_code=204,
|
|
168
160
|
responses=add_errors_to_responses(
|
|
169
161
|
[
|
|
170
|
-
{"status_code":
|
|
171
|
-
{"status_code":
|
|
162
|
+
{"status_code": 404, "description": "Dataset not found"},
|
|
163
|
+
{"status_code": 422, "description": "Invalid dataset ID"},
|
|
172
164
|
]
|
|
173
165
|
),
|
|
174
166
|
)
|
|
@@ -182,11 +174,9 @@ async def delete_dataset(
|
|
|
182
174
|
DATASET_NODE_NAME,
|
|
183
175
|
)
|
|
184
176
|
except ValueError:
|
|
185
|
-
raise HTTPException(
|
|
186
|
-
detail=f"Invalid Dataset ID: {id}", status_code=HTTP_422_UNPROCESSABLE_ENTITY
|
|
187
|
-
)
|
|
177
|
+
raise HTTPException(detail=f"Invalid Dataset ID: {id}", status_code=422)
|
|
188
178
|
else:
|
|
189
|
-
raise HTTPException(detail="Missing Dataset ID", status_code=
|
|
179
|
+
raise HTTPException(detail="Missing Dataset ID", status_code=422)
|
|
190
180
|
project_names_stmt = get_project_names_for_datasets(dataset_id)
|
|
191
181
|
eval_trace_ids_stmt = get_eval_trace_ids_for_datasets(dataset_id)
|
|
192
182
|
stmt = (
|
|
@@ -196,7 +186,7 @@ async def delete_dataset(
|
|
|
196
186
|
project_names = await session.scalars(project_names_stmt)
|
|
197
187
|
eval_trace_ids = await session.scalars(eval_trace_ids_stmt)
|
|
198
188
|
if (await session.scalar(stmt)) is None:
|
|
199
|
-
raise HTTPException(detail="Dataset does not exist", status_code=
|
|
189
|
+
raise HTTPException(detail="Dataset does not exist", status_code=404)
|
|
200
190
|
tasks = BackgroundTasks()
|
|
201
191
|
tasks.add_task(delete_projects, request.app.state.db, *project_names)
|
|
202
192
|
tasks.add_task(delete_traces, request.app.state.db, *eval_trace_ids)
|
|
@@ -214,7 +204,7 @@ class GetDatasetResponseBody(ResponseBody[DatasetWithExampleCount]):
|
|
|
214
204
|
"/datasets/{id}",
|
|
215
205
|
operation_id="getDataset",
|
|
216
206
|
summary="Get dataset by ID",
|
|
217
|
-
responses=add_errors_to_responses([
|
|
207
|
+
responses=add_errors_to_responses([404]),
|
|
218
208
|
)
|
|
219
209
|
async def get_dataset(
|
|
220
210
|
request: Request, id: str = Path(description="The ID of the dataset")
|
|
@@ -222,9 +212,7 @@ async def get_dataset(
|
|
|
222
212
|
dataset_id = GlobalID.from_id(id)
|
|
223
213
|
|
|
224
214
|
if (type_name := dataset_id.type_name) != DATASET_NODE_NAME:
|
|
225
|
-
raise HTTPException(
|
|
226
|
-
detail=f"ID {dataset_id} refers to a f{type_name}", status_code=HTTP_404_NOT_FOUND
|
|
227
|
-
)
|
|
215
|
+
raise HTTPException(detail=f"ID {dataset_id} refers to a f{type_name}", status_code=404)
|
|
228
216
|
async with request.app.state.db() as session:
|
|
229
217
|
result = await session.execute(
|
|
230
218
|
select(models.Dataset, models.Dataset.example_count).filter(
|
|
@@ -235,9 +223,7 @@ async def get_dataset(
|
|
|
235
223
|
dataset = dataset_query[0] if dataset_query else None
|
|
236
224
|
example_count = dataset_query[1] if dataset_query else 0
|
|
237
225
|
if dataset is None:
|
|
238
|
-
raise HTTPException(
|
|
239
|
-
detail=f"Dataset with ID {dataset_id} not found", status_code=HTTP_404_NOT_FOUND
|
|
240
|
-
)
|
|
226
|
+
raise HTTPException(detail=f"Dataset with ID {dataset_id} not found", status_code=404)
|
|
241
227
|
|
|
242
228
|
dataset = DatasetWithExampleCount(
|
|
243
229
|
id=str(dataset_id),
|
|
@@ -266,7 +252,7 @@ class ListDatasetVersionsResponseBody(PaginatedResponseBody[DatasetVersion]):
|
|
|
266
252
|
"/datasets/{id}/versions",
|
|
267
253
|
operation_id="listDatasetVersionsByDatasetId",
|
|
268
254
|
summary="List dataset versions",
|
|
269
|
-
responses=add_errors_to_responses([
|
|
255
|
+
responses=add_errors_to_responses([422]),
|
|
270
256
|
)
|
|
271
257
|
async def list_dataset_versions(
|
|
272
258
|
request: Request,
|
|
@@ -288,12 +274,12 @@ async def list_dataset_versions(
|
|
|
288
274
|
except ValueError:
|
|
289
275
|
raise HTTPException(
|
|
290
276
|
detail=f"Invalid Dataset ID: {id}",
|
|
291
|
-
status_code=
|
|
277
|
+
status_code=422,
|
|
292
278
|
)
|
|
293
279
|
else:
|
|
294
280
|
raise HTTPException(
|
|
295
281
|
detail="Missing Dataset ID",
|
|
296
|
-
status_code=
|
|
282
|
+
status_code=422,
|
|
297
283
|
)
|
|
298
284
|
stmt = (
|
|
299
285
|
select(models.DatasetVersion)
|
|
@@ -309,7 +295,7 @@ async def list_dataset_versions(
|
|
|
309
295
|
except ValueError:
|
|
310
296
|
raise HTTPException(
|
|
311
297
|
detail=f"Invalid cursor: {cursor}",
|
|
312
|
-
status_code=
|
|
298
|
+
status_code=422,
|
|
313
299
|
)
|
|
314
300
|
max_dataset_version_id = (
|
|
315
301
|
select(models.DatasetVersion.id)
|
|
@@ -348,10 +334,10 @@ class UploadDatasetResponseBody(ResponseBody[UploadDatasetData]):
|
|
|
348
334
|
responses=add_errors_to_responses(
|
|
349
335
|
[
|
|
350
336
|
{
|
|
351
|
-
"status_code":
|
|
337
|
+
"status_code": 409,
|
|
352
338
|
"description": "Dataset of the same name already exists",
|
|
353
339
|
},
|
|
354
|
-
{"status_code":
|
|
340
|
+
{"status_code": 422, "description": "Invalid request body"},
|
|
355
341
|
]
|
|
356
342
|
),
|
|
357
343
|
# FastAPI cannot generate the request body portion of the OpenAPI schema for
|
|
@@ -424,14 +410,14 @@ async def upload_dataset(
|
|
|
424
410
|
except ValueError as e:
|
|
425
411
|
raise HTTPException(
|
|
426
412
|
detail=str(e),
|
|
427
|
-
status_code=
|
|
413
|
+
status_code=422,
|
|
428
414
|
)
|
|
429
415
|
if action is DatasetAction.CREATE:
|
|
430
416
|
async with request.app.state.db() as session:
|
|
431
417
|
if await _check_table_exists(session, name):
|
|
432
418
|
raise HTTPException(
|
|
433
419
|
detail=f"Dataset with the same name already exists: {name=}",
|
|
434
|
-
status_code=
|
|
420
|
+
status_code=409,
|
|
435
421
|
)
|
|
436
422
|
elif request_content_type.startswith("multipart/form-data"):
|
|
437
423
|
async with request.form() as form:
|
|
@@ -448,14 +434,14 @@ async def upload_dataset(
|
|
|
448
434
|
except ValueError as e:
|
|
449
435
|
raise HTTPException(
|
|
450
436
|
detail=str(e),
|
|
451
|
-
status_code=
|
|
437
|
+
status_code=422,
|
|
452
438
|
)
|
|
453
439
|
if action is DatasetAction.CREATE:
|
|
454
440
|
async with request.app.state.db() as session:
|
|
455
441
|
if await _check_table_exists(session, name):
|
|
456
442
|
raise HTTPException(
|
|
457
443
|
detail=f"Dataset with the same name already exists: {name=}",
|
|
458
|
-
status_code=
|
|
444
|
+
status_code=409,
|
|
459
445
|
)
|
|
460
446
|
content = await file.read()
|
|
461
447
|
try:
|
|
@@ -472,12 +458,12 @@ async def upload_dataset(
|
|
|
472
458
|
except ValueError as e:
|
|
473
459
|
raise HTTPException(
|
|
474
460
|
detail=str(e),
|
|
475
|
-
status_code=
|
|
461
|
+
status_code=422,
|
|
476
462
|
)
|
|
477
463
|
else:
|
|
478
464
|
raise HTTPException(
|
|
479
465
|
detail="Invalid request Content-Type",
|
|
480
|
-
status_code=
|
|
466
|
+
status_code=422,
|
|
481
467
|
)
|
|
482
468
|
user_id: Optional[int] = None
|
|
483
469
|
if request.app.state.authentication_enabled and isinstance(request.user, PhoenixUser):
|
|
@@ -510,7 +496,7 @@ async def upload_dataset(
|
|
|
510
496
|
except QueueFull:
|
|
511
497
|
if isinstance(examples, Coroutine):
|
|
512
498
|
examples.close()
|
|
513
|
-
raise HTTPException(detail="Too many requests.", status_code=
|
|
499
|
+
raise HTTPException(detail="Too many requests.", status_code=429)
|
|
514
500
|
return None
|
|
515
501
|
|
|
516
502
|
|
|
@@ -711,7 +697,7 @@ class ListDatasetExamplesResponseBody(ResponseBody[ListDatasetExamplesData]):
|
|
|
711
697
|
"/datasets/{id}/examples",
|
|
712
698
|
operation_id="getDatasetExamples",
|
|
713
699
|
summary="Get examples from a dataset",
|
|
714
|
-
responses=add_errors_to_responses([
|
|
700
|
+
responses=add_errors_to_responses([404]),
|
|
715
701
|
)
|
|
716
702
|
async def get_dataset_examples(
|
|
717
703
|
request: Request,
|
|
@@ -727,14 +713,10 @@ async def get_dataset_examples(
|
|
|
727
713
|
version_gid = GlobalID.from_id(version_id) if version_id else None
|
|
728
714
|
|
|
729
715
|
if (dataset_type := dataset_gid.type_name) != "Dataset":
|
|
730
|
-
raise HTTPException(
|
|
731
|
-
detail=f"ID {dataset_gid} refers to a {dataset_type}", status_code=HTTP_404_NOT_FOUND
|
|
732
|
-
)
|
|
716
|
+
raise HTTPException(detail=f"ID {dataset_gid} refers to a {dataset_type}", status_code=404)
|
|
733
717
|
|
|
734
718
|
if version_gid and (version_type := version_gid.type_name) != "DatasetVersion":
|
|
735
|
-
raise HTTPException(
|
|
736
|
-
detail=f"ID {version_gid} refers to a {version_type}", status_code=HTTP_404_NOT_FOUND
|
|
737
|
-
)
|
|
719
|
+
raise HTTPException(detail=f"ID {version_gid} refers to a {version_type}", status_code=404)
|
|
738
720
|
|
|
739
721
|
async with request.app.state.db() as session:
|
|
740
722
|
if (
|
|
@@ -744,7 +726,7 @@ async def get_dataset_examples(
|
|
|
744
726
|
) is None:
|
|
745
727
|
raise HTTPException(
|
|
746
728
|
detail=f"No dataset with id {dataset_gid} can be found.",
|
|
747
|
-
status_code=
|
|
729
|
+
status_code=404,
|
|
748
730
|
)
|
|
749
731
|
|
|
750
732
|
# Subquery to find the maximum created_at for each dataset_example_id
|
|
@@ -766,7 +748,7 @@ async def get_dataset_examples(
|
|
|
766
748
|
) is None:
|
|
767
749
|
raise HTTPException(
|
|
768
750
|
detail=f"No dataset version with id {version_id} can be found.",
|
|
769
|
-
status_code=
|
|
751
|
+
status_code=404,
|
|
770
752
|
)
|
|
771
753
|
# if a version_id is provided, filter the subquery to only include revisions from that
|
|
772
754
|
partial_subquery = partial_subquery.filter(
|
|
@@ -782,7 +764,7 @@ async def get_dataset_examples(
|
|
|
782
764
|
) is None:
|
|
783
765
|
raise HTTPException(
|
|
784
766
|
detail="Dataset has no versions.",
|
|
785
|
-
status_code=
|
|
767
|
+
status_code=404,
|
|
786
768
|
)
|
|
787
769
|
|
|
788
770
|
subquery = partial_subquery.subquery()
|
|
@@ -825,10 +807,10 @@ async def get_dataset_examples(
|
|
|
825
807
|
operation_id="getDatasetCsv",
|
|
826
808
|
summary="Download dataset examples as CSV file",
|
|
827
809
|
response_class=StreamingResponse,
|
|
828
|
-
status_code=
|
|
810
|
+
status_code=200,
|
|
829
811
|
responses={
|
|
830
|
-
**add_errors_to_responses([
|
|
831
|
-
**add_text_csv_content_to_responses(
|
|
812
|
+
**add_errors_to_responses([422]),
|
|
813
|
+
**add_text_csv_content_to_responses(200),
|
|
832
814
|
},
|
|
833
815
|
)
|
|
834
816
|
async def get_dataset_csv(
|
|
@@ -848,7 +830,7 @@ async def get_dataset_csv(
|
|
|
848
830
|
session=session, id=id, version_id=version_id
|
|
849
831
|
)
|
|
850
832
|
except ValueError as e:
|
|
851
|
-
raise HTTPException(detail=str(e), status_code=
|
|
833
|
+
raise HTTPException(detail=str(e), status_code=422)
|
|
852
834
|
content = await run_in_threadpool(_get_content_csv, examples)
|
|
853
835
|
encoded_dataset_name = urllib.parse.quote(dataset_name)
|
|
854
836
|
return Response(
|
|
@@ -868,7 +850,7 @@ async def get_dataset_csv(
|
|
|
868
850
|
responses=add_errors_to_responses(
|
|
869
851
|
[
|
|
870
852
|
{
|
|
871
|
-
"status_code":
|
|
853
|
+
"status_code": 422,
|
|
872
854
|
"description": "Invalid dataset or version ID",
|
|
873
855
|
}
|
|
874
856
|
]
|
|
@@ -891,7 +873,7 @@ async def get_dataset_jsonl_openai_ft(
|
|
|
891
873
|
session=session, id=id, version_id=version_id
|
|
892
874
|
)
|
|
893
875
|
except ValueError as e:
|
|
894
|
-
raise HTTPException(detail=str(e), status_code=
|
|
876
|
+
raise HTTPException(detail=str(e), status_code=422)
|
|
895
877
|
content = await run_in_threadpool(_get_content_jsonl_openai_ft, examples)
|
|
896
878
|
encoded_dataset_name = urllib.parse.quote(dataset_name)
|
|
897
879
|
response.headers["content-disposition"] = (
|
|
@@ -908,7 +890,7 @@ async def get_dataset_jsonl_openai_ft(
|
|
|
908
890
|
responses=add_errors_to_responses(
|
|
909
891
|
[
|
|
910
892
|
{
|
|
911
|
-
"status_code":
|
|
893
|
+
"status_code": 422,
|
|
912
894
|
"description": "Invalid dataset or version ID",
|
|
913
895
|
}
|
|
914
896
|
]
|
|
@@ -931,7 +913,7 @@ async def get_dataset_jsonl_openai_evals(
|
|
|
931
913
|
session=session, id=id, version_id=version_id
|
|
932
914
|
)
|
|
933
915
|
except ValueError as e:
|
|
934
|
-
raise HTTPException(detail=str(e), status_code=
|
|
916
|
+
raise HTTPException(detail=str(e), status_code=422)
|
|
935
917
|
content = await run_in_threadpool(_get_content_jsonl_openai_evals, examples)
|
|
936
918
|
encoded_dataset_name = urllib.parse.quote(dataset_name)
|
|
937
919
|
response.headers["content-disposition"] = (
|
|
@@ -4,7 +4,6 @@ from fastapi import APIRouter, Depends, HTTPException, Query
|
|
|
4
4
|
from pydantic import Field
|
|
5
5
|
from sqlalchemy import select
|
|
6
6
|
from starlette.requests import Request
|
|
7
|
-
from starlette.status import HTTP_404_NOT_FOUND
|
|
8
7
|
from strawberry.relay import GlobalID
|
|
9
8
|
|
|
10
9
|
from phoenix.db import models
|
|
@@ -42,7 +41,7 @@ class AnnotateSpanDocumentsResponseBody(ResponseBody[list[InsertedSpanDocumentAn
|
|
|
42
41
|
responses=add_errors_to_responses(
|
|
43
42
|
[
|
|
44
43
|
{
|
|
45
|
-
"status_code":
|
|
44
|
+
"status_code": 404,
|
|
46
45
|
"description": "Span not found",
|
|
47
46
|
},
|
|
48
47
|
{
|
|
@@ -102,7 +101,7 @@ async def annotate_span_documents(
|
|
|
102
101
|
if missing_span_ids:
|
|
103
102
|
raise HTTPException(
|
|
104
103
|
detail=f"Spans with IDs {', '.join(missing_span_ids)} do not exist.",
|
|
105
|
-
status_code=
|
|
104
|
+
status_code=404,
|
|
106
105
|
)
|
|
107
106
|
|
|
108
107
|
# Validate that document positions are within bounds
|