arize-phoenix 4.10.2rc2__py3-none-any.whl → 4.11.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-4.10.2rc2.dist-info → arize_phoenix-4.11.0.dist-info}/METADATA +3 -4
- {arize_phoenix-4.10.2rc2.dist-info → arize_phoenix-4.11.0.dist-info}/RECORD +26 -26
- phoenix/server/api/context.py +7 -3
- phoenix/server/api/openapi/main.py +2 -18
- phoenix/server/api/openapi/schema.py +12 -12
- phoenix/server/api/routers/v1/__init__.py +83 -36
- phoenix/server/api/routers/v1/dataset_examples.py +123 -102
- phoenix/server/api/routers/v1/datasets.py +507 -389
- phoenix/server/api/routers/v1/evaluations.py +66 -73
- phoenix/server/api/routers/v1/experiment_evaluations.py +91 -67
- phoenix/server/api/routers/v1/experiment_runs.py +155 -97
- phoenix/server/api/routers/v1/experiments.py +181 -131
- phoenix/server/api/routers/v1/spans.py +173 -143
- phoenix/server/api/routers/v1/traces.py +128 -114
- phoenix/server/api/types/Span.py +1 -0
- phoenix/server/app.py +176 -148
- phoenix/server/openapi/docs.py +221 -0
- phoenix/server/static/index.js +574 -573
- phoenix/server/thread_server.py +2 -2
- phoenix/trace/attributes.py +2 -1
- phoenix/trace/schemas.py +1 -0
- phoenix/trace/span_json_decoder.py +1 -1
- phoenix/version.py +1 -1
- phoenix/server/api/routers/v1/utils.py +0 -94
- {arize_phoenix-4.10.2rc2.dist-info → arize_phoenix-4.11.0.dist-info}/WHEEL +0 -0
- {arize_phoenix-4.10.2rc2.dist-info → arize_phoenix-4.11.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-4.10.2rc2.dist-info → arize_phoenix-4.11.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
1
|
from random import getrandbits
|
|
3
|
-
from typing import Any, Dict, Optional
|
|
4
2
|
|
|
5
|
-
from fastapi import APIRouter, HTTPException
|
|
6
|
-
from pydantic import BaseModel, Field
|
|
7
3
|
from sqlalchemy import select
|
|
8
4
|
from starlette.requests import Request
|
|
5
|
+
from starlette.responses import JSONResponse, Response
|
|
9
6
|
from starlette.status import HTTP_404_NOT_FOUND
|
|
10
7
|
from strawberry.relay import GlobalID
|
|
11
8
|
|
|
@@ -14,10 +11,6 @@ from phoenix.db.helpers import SupportedSQLDialect
|
|
|
14
11
|
from phoenix.db.insertion.helpers import insert_on_conflict
|
|
15
12
|
from phoenix.server.api.types.node import from_global_id_with_expected_type
|
|
16
13
|
|
|
17
|
-
from .utils import ResponseBody, add_errors_to_responses
|
|
18
|
-
|
|
19
|
-
router = APIRouter(tags=["experiments"], include_in_schema=False)
|
|
20
|
-
|
|
21
14
|
|
|
22
15
|
def _short_uuid() -> str:
|
|
23
16
|
return str(getrandbits(32).to_bytes(4, "big").hex())
|
|
@@ -31,76 +24,94 @@ def _generate_experiment_name(dataset_name: str) -> str:
|
|
|
31
24
|
return f"{short_ds_name}-{_short_uuid()}"
|
|
32
25
|
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
id: str = Field(description="The ID of the experiment")
|
|
36
|
-
dataset_id: str = Field(description="The ID of the dataset associated with the experiment")
|
|
37
|
-
dataset_version_id: str = Field(
|
|
38
|
-
description="The ID of the dataset version associated with the experiment"
|
|
39
|
-
)
|
|
40
|
-
repetitions: int = Field(description="Number of times the experiment is repeated")
|
|
41
|
-
metadata: Dict[str, Any] = Field(description="Metadata of the experiment")
|
|
42
|
-
project_name: Optional[str] = Field(
|
|
43
|
-
description="The name of the project associated with the experiment"
|
|
44
|
-
)
|
|
45
|
-
created_at: datetime = Field(description="The creation timestamp of the experiment")
|
|
46
|
-
updated_at: datetime = Field(description="The last update timestamp of the experiment")
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class CreateExperimentRequestBody(BaseModel):
|
|
27
|
+
async def create_experiment(request: Request) -> Response:
|
|
50
28
|
"""
|
|
51
|
-
|
|
29
|
+
summary: Create an experiment using a dataset
|
|
30
|
+
operationId: createExperiment
|
|
31
|
+
tags:
|
|
32
|
+
- private
|
|
33
|
+
parameters:
|
|
34
|
+
- in: path
|
|
35
|
+
name: dataset_id
|
|
36
|
+
required: true
|
|
37
|
+
description: The ID of the dataset to create an experiment for
|
|
38
|
+
schema:
|
|
39
|
+
type: string
|
|
40
|
+
requestBody:
|
|
41
|
+
description: Details of the experiment to be created
|
|
42
|
+
required: true
|
|
43
|
+
content:
|
|
44
|
+
application/json:
|
|
45
|
+
schema:
|
|
46
|
+
type: object
|
|
47
|
+
properties:
|
|
48
|
+
repetitions:
|
|
49
|
+
type: integer
|
|
50
|
+
description: Number of times the experiment should be repeated for each example
|
|
51
|
+
default: 1
|
|
52
|
+
metadata:
|
|
53
|
+
type: object
|
|
54
|
+
description: Metadata for the experiment
|
|
55
|
+
additionalProperties:
|
|
56
|
+
type: string
|
|
57
|
+
version_id:
|
|
58
|
+
type: string
|
|
59
|
+
description: ID of the dataset version to use
|
|
60
|
+
responses:
|
|
61
|
+
200:
|
|
62
|
+
description: Experiment retrieved successfully
|
|
63
|
+
content:
|
|
64
|
+
application/json:
|
|
65
|
+
schema:
|
|
66
|
+
type: object
|
|
67
|
+
properties:
|
|
68
|
+
data:
|
|
69
|
+
type: object
|
|
70
|
+
properties:
|
|
71
|
+
id:
|
|
72
|
+
type: string
|
|
73
|
+
description: The ID of the experiment
|
|
74
|
+
dataset_id:
|
|
75
|
+
type: string
|
|
76
|
+
description: The ID of the dataset associated with the experiment
|
|
77
|
+
dataset_version_id:
|
|
78
|
+
type: string
|
|
79
|
+
description: The ID of the dataset version associated with the experiment
|
|
80
|
+
repetitions:
|
|
81
|
+
type: integer
|
|
82
|
+
description: Number of times the experiment is repeated
|
|
83
|
+
metadata:
|
|
84
|
+
type: object
|
|
85
|
+
description: Metadata of the experiment
|
|
86
|
+
additionalProperties:
|
|
87
|
+
type: string
|
|
88
|
+
project_name:
|
|
89
|
+
type: string
|
|
90
|
+
description: The name of the project associated with the experiment
|
|
91
|
+
created_at:
|
|
92
|
+
type: string
|
|
93
|
+
format: date-time
|
|
94
|
+
description: The creation timestamp of the experiment
|
|
95
|
+
updated_at:
|
|
96
|
+
type: string
|
|
97
|
+
format: date-time
|
|
98
|
+
description: The last update timestamp of the experiment
|
|
99
|
+
404:
|
|
100
|
+
description: Dataset or DatasetVersion not found
|
|
52
101
|
"""
|
|
53
|
-
|
|
54
|
-
name: Optional[str] = Field(
|
|
55
|
-
default=None,
|
|
56
|
-
description=("Name of the experiment (if omitted, a random name will be generated)"),
|
|
57
|
-
)
|
|
58
|
-
description: Optional[str] = Field(
|
|
59
|
-
default=None, description="An optional description of the experiment"
|
|
60
|
-
)
|
|
61
|
-
metadata: Optional[Dict[str, Any]] = Field(
|
|
62
|
-
default=None, description="Metadata for the experiment"
|
|
63
|
-
)
|
|
64
|
-
version_id: Optional[str] = Field(
|
|
65
|
-
default=None,
|
|
66
|
-
description=(
|
|
67
|
-
"ID of the dataset version over which the experiment will be run "
|
|
68
|
-
"(if omitted, the latest version will be used)"
|
|
69
|
-
),
|
|
70
|
-
)
|
|
71
|
-
repetitions: int = Field(
|
|
72
|
-
default=1, description="Number of times the experiment should be repeated for each example"
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
class CreateExperimentResponseBody(ResponseBody[Experiment]):
|
|
77
|
-
pass
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
@router.post(
|
|
81
|
-
"/datasets/{dataset_id}/experiments",
|
|
82
|
-
operation_id="createExperiment",
|
|
83
|
-
summary="Create experiment on a dataset",
|
|
84
|
-
responses=add_errors_to_responses(
|
|
85
|
-
[{"status_code": HTTP_404_NOT_FOUND, "description": "Dataset or DatasetVersion not found"}]
|
|
86
|
-
),
|
|
87
|
-
response_description="Experiment retrieved successfully",
|
|
88
|
-
)
|
|
89
|
-
async def create_experiment(
|
|
90
|
-
request: Request,
|
|
91
|
-
dataset_id: str,
|
|
92
|
-
request_body: CreateExperimentRequestBody,
|
|
93
|
-
) -> CreateExperimentResponseBody:
|
|
94
|
-
dataset_globalid = GlobalID.from_id(dataset_id)
|
|
102
|
+
dataset_globalid = GlobalID.from_id(request.path_params["dataset_id"])
|
|
95
103
|
try:
|
|
96
|
-
|
|
104
|
+
dataset_id = from_global_id_with_expected_type(dataset_globalid, "Dataset")
|
|
97
105
|
except ValueError:
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
return Response(
|
|
107
|
+
content="Dataset with ID {dataset_globalid} does not exist",
|
|
100
108
|
status_code=HTTP_404_NOT_FOUND,
|
|
101
109
|
)
|
|
102
110
|
|
|
103
|
-
|
|
111
|
+
payload = await request.json()
|
|
112
|
+
repetitions = payload.get("repetitions", 1)
|
|
113
|
+
metadata = payload.get("metadata") or {}
|
|
114
|
+
dataset_version_globalid_str = payload.get("version_id")
|
|
104
115
|
if dataset_version_globalid_str is not None:
|
|
105
116
|
try:
|
|
106
117
|
dataset_version_globalid = GlobalID.from_id(dataset_version_globalid_str)
|
|
@@ -108,31 +119,31 @@ async def create_experiment(
|
|
|
108
119
|
dataset_version_globalid, "DatasetVersion"
|
|
109
120
|
)
|
|
110
121
|
except ValueError:
|
|
111
|
-
|
|
112
|
-
|
|
122
|
+
return Response(
|
|
123
|
+
content="DatasetVersion with ID {dataset_version_globalid} does not exist",
|
|
113
124
|
status_code=HTTP_404_NOT_FOUND,
|
|
114
125
|
)
|
|
115
126
|
|
|
116
127
|
async with request.app.state.db() as session:
|
|
117
128
|
result = (
|
|
118
|
-
await session.execute(select(models.Dataset).where(models.Dataset.id ==
|
|
129
|
+
await session.execute(select(models.Dataset).where(models.Dataset.id == dataset_id))
|
|
119
130
|
).scalar()
|
|
120
131
|
if result is None:
|
|
121
|
-
|
|
122
|
-
|
|
132
|
+
return Response(
|
|
133
|
+
content=f"Dataset with ID {dataset_globalid} does not exist",
|
|
123
134
|
status_code=HTTP_404_NOT_FOUND,
|
|
124
135
|
)
|
|
125
136
|
dataset_name = result.name
|
|
126
137
|
if dataset_version_globalid_str is None:
|
|
127
138
|
dataset_version_result = await session.execute(
|
|
128
139
|
select(models.DatasetVersion)
|
|
129
|
-
.where(models.DatasetVersion.dataset_id ==
|
|
140
|
+
.where(models.DatasetVersion.dataset_id == dataset_id)
|
|
130
141
|
.order_by(models.DatasetVersion.id.desc())
|
|
131
142
|
)
|
|
132
143
|
dataset_version = dataset_version_result.scalar()
|
|
133
144
|
if not dataset_version:
|
|
134
|
-
|
|
135
|
-
|
|
145
|
+
return Response(
|
|
146
|
+
content=f"Dataset {dataset_globalid} does not have any versions",
|
|
136
147
|
status_code=HTTP_404_NOT_FOUND,
|
|
137
148
|
)
|
|
138
149
|
dataset_version_id = dataset_version.id
|
|
@@ -143,24 +154,24 @@ async def create_experiment(
|
|
|
143
154
|
)
|
|
144
155
|
dataset_version = dataset_version.scalar()
|
|
145
156
|
if not dataset_version:
|
|
146
|
-
|
|
147
|
-
|
|
157
|
+
return Response(
|
|
158
|
+
content=f"DatasetVersion with ID {dataset_version_globalid} does not exist",
|
|
148
159
|
status_code=HTTP_404_NOT_FOUND,
|
|
149
160
|
)
|
|
150
161
|
|
|
151
162
|
# generate a semi-unique name for the experiment
|
|
152
|
-
experiment_name =
|
|
163
|
+
experiment_name = payload.get("name") or _generate_experiment_name(dataset_name)
|
|
153
164
|
project_name = f"Experiment-{getrandbits(96).to_bytes(12, 'big').hex()}"
|
|
154
165
|
project_description = (
|
|
155
166
|
f"dataset_id: {dataset_globalid}\ndataset_version_id: {dataset_version_globalid}"
|
|
156
167
|
)
|
|
157
168
|
experiment = models.Experiment(
|
|
158
|
-
dataset_id=int(
|
|
169
|
+
dataset_id=int(dataset_id),
|
|
159
170
|
dataset_version_id=int(dataset_version_id),
|
|
160
171
|
name=experiment_name,
|
|
161
|
-
description=
|
|
162
|
-
repetitions=
|
|
163
|
-
metadata_=
|
|
172
|
+
description=payload.get("description"),
|
|
173
|
+
repetitions=repetitions,
|
|
174
|
+
metadata_=metadata,
|
|
164
175
|
project_name=project_name,
|
|
165
176
|
)
|
|
166
177
|
session.add(experiment)
|
|
@@ -187,65 +198,104 @@ async def create_experiment(
|
|
|
187
198
|
dataset_version_globalid = GlobalID(
|
|
188
199
|
"DatasetVersion", str(experiment.dataset_version_id)
|
|
189
200
|
)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
)
|
|
201
|
+
experiment_payload = {
|
|
202
|
+
"id": str(experiment_globalid),
|
|
203
|
+
"dataset_id": str(dataset_globalid),
|
|
204
|
+
"dataset_version_id": str(dataset_version_globalid),
|
|
205
|
+
"repetitions": experiment.repetitions,
|
|
206
|
+
"metadata": experiment.metadata_,
|
|
207
|
+
"project_name": experiment.project_name,
|
|
208
|
+
"created_at": experiment.created_at.isoformat(),
|
|
209
|
+
"updated_at": experiment.updated_at.isoformat(),
|
|
210
|
+
}
|
|
211
|
+
return JSONResponse(content={"data": experiment_payload})
|
|
202
212
|
|
|
203
213
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
214
|
+
async def read_experiment(request: Request) -> Response:
|
|
215
|
+
"""
|
|
216
|
+
summary: Get details of a specific experiment
|
|
217
|
+
operationId: getExperiment
|
|
218
|
+
tags:
|
|
219
|
+
- private
|
|
220
|
+
parameters:
|
|
221
|
+
- in: path
|
|
222
|
+
name: experiment_id
|
|
223
|
+
required: true
|
|
224
|
+
description: The ID of the experiment to retrieve
|
|
225
|
+
schema:
|
|
226
|
+
type: string
|
|
227
|
+
responses:
|
|
228
|
+
200:
|
|
229
|
+
description: Experiment retrieved successfully
|
|
230
|
+
content:
|
|
231
|
+
application/json:
|
|
232
|
+
schema:
|
|
233
|
+
type: object
|
|
234
|
+
properties:
|
|
235
|
+
data:
|
|
236
|
+
type: object
|
|
237
|
+
properties:
|
|
238
|
+
id:
|
|
239
|
+
type: string
|
|
240
|
+
description: The ID of the experiment
|
|
241
|
+
dataset_id:
|
|
242
|
+
type: string
|
|
243
|
+
description: The ID of the dataset associated with the experiment
|
|
244
|
+
dataset_version_id:
|
|
245
|
+
type: string
|
|
246
|
+
description: The ID of the dataset version associated with the experiment
|
|
247
|
+
repetitions:
|
|
248
|
+
type: integer
|
|
249
|
+
description: Number of times the experiment is repeated
|
|
250
|
+
metadata:
|
|
251
|
+
type: object
|
|
252
|
+
description: Metadata of the experiment
|
|
253
|
+
additionalProperties:
|
|
254
|
+
type: string
|
|
255
|
+
project_name:
|
|
256
|
+
type: string
|
|
257
|
+
description: The name of the project associated with the experiment
|
|
258
|
+
created_at:
|
|
259
|
+
type: string
|
|
260
|
+
format: date-time
|
|
261
|
+
description: The creation timestamp of the experiment
|
|
262
|
+
updated_at:
|
|
263
|
+
type: string
|
|
264
|
+
format: date-time
|
|
265
|
+
description: The last update timestamp of the experiment
|
|
266
|
+
404:
|
|
267
|
+
description: Experiment not found
|
|
268
|
+
"""
|
|
269
|
+
experiment_globalid = GlobalID.from_id(request.path_params["experiment_id"])
|
|
219
270
|
try:
|
|
220
|
-
|
|
271
|
+
experiment_id = from_global_id_with_expected_type(experiment_globalid, "Experiment")
|
|
221
272
|
except ValueError:
|
|
222
|
-
|
|
223
|
-
|
|
273
|
+
return Response(
|
|
274
|
+
content="Experiment with ID {experiment_globalid} does not exist",
|
|
224
275
|
status_code=HTTP_404_NOT_FOUND,
|
|
225
276
|
)
|
|
226
277
|
|
|
227
278
|
async with request.app.state.db() as session:
|
|
228
279
|
experiment = await session.execute(
|
|
229
|
-
select(models.Experiment).where(models.Experiment.id ==
|
|
280
|
+
select(models.Experiment).where(models.Experiment.id == experiment_id)
|
|
230
281
|
)
|
|
231
282
|
experiment = experiment.scalar()
|
|
232
283
|
if not experiment:
|
|
233
|
-
|
|
234
|
-
|
|
284
|
+
return Response(
|
|
285
|
+
content=f"Experiment with ID {experiment_globalid} does not exist",
|
|
235
286
|
status_code=HTTP_404_NOT_FOUND,
|
|
236
287
|
)
|
|
237
288
|
|
|
238
289
|
dataset_globalid = GlobalID("Dataset", str(experiment.dataset_id))
|
|
239
290
|
dataset_version_globalid = GlobalID("DatasetVersion", str(experiment.dataset_version_id))
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
)
|
|
291
|
+
experiment_payload = {
|
|
292
|
+
"id": str(experiment_globalid),
|
|
293
|
+
"dataset_id": str(dataset_globalid),
|
|
294
|
+
"dataset_version_id": str(dataset_version_globalid),
|
|
295
|
+
"repetitions": experiment.repetitions,
|
|
296
|
+
"metadata": experiment.metadata_,
|
|
297
|
+
"project_name": experiment.project_name,
|
|
298
|
+
"created_at": experiment.created_at.isoformat(),
|
|
299
|
+
"updated_at": experiment.updated_at.isoformat(),
|
|
300
|
+
}
|
|
301
|
+
return JSONResponse(content={"data": experiment_payload})
|