arize-phoenix 4.10.2rc2__py3-none-any.whl → 4.12.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.

Files changed (30) hide show
  1. {arize_phoenix-4.10.2rc2.dist-info → arize_phoenix-4.12.0.dist-info}/METADATA +3 -4
  2. {arize_phoenix-4.10.2rc2.dist-info → arize_phoenix-4.12.0.dist-info}/RECORD +29 -29
  3. phoenix/server/api/context.py +7 -3
  4. phoenix/server/api/openapi/main.py +2 -18
  5. phoenix/server/api/openapi/schema.py +12 -12
  6. phoenix/server/api/routers/v1/__init__.py +83 -36
  7. phoenix/server/api/routers/v1/dataset_examples.py +123 -102
  8. phoenix/server/api/routers/v1/datasets.py +507 -389
  9. phoenix/server/api/routers/v1/evaluations.py +66 -73
  10. phoenix/server/api/routers/v1/experiment_evaluations.py +91 -67
  11. phoenix/server/api/routers/v1/experiment_runs.py +155 -97
  12. phoenix/server/api/routers/v1/experiments.py +181 -131
  13. phoenix/server/api/routers/v1/spans.py +173 -143
  14. phoenix/server/api/routers/v1/traces.py +128 -114
  15. phoenix/server/api/types/Span.py +1 -0
  16. phoenix/server/app.py +176 -148
  17. phoenix/server/openapi/docs.py +221 -0
  18. phoenix/server/static/index.js +574 -573
  19. phoenix/server/thread_server.py +2 -2
  20. phoenix/session/client.py +5 -0
  21. phoenix/session/data_extractor.py +20 -1
  22. phoenix/session/session.py +4 -0
  23. phoenix/trace/attributes.py +2 -1
  24. phoenix/trace/schemas.py +1 -0
  25. phoenix/trace/span_json_decoder.py +1 -1
  26. phoenix/version.py +1 -1
  27. phoenix/server/api/routers/v1/utils.py +0 -94
  28. {arize_phoenix-4.10.2rc2.dist-info → arize_phoenix-4.12.0.dist-info}/WHEEL +0 -0
  29. {arize_phoenix-4.10.2rc2.dist-info → arize_phoenix-4.12.0.dist-info}/licenses/IP_NOTICE +0 -0
  30. {arize_phoenix-4.10.2rc2.dist-info → arize_phoenix-4.12.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
- class Experiment(BaseModel):
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
- Details of the experiment to be created
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
- dataset_rowid = from_global_id_with_expected_type(dataset_globalid, "Dataset")
104
+ dataset_id = from_global_id_with_expected_type(dataset_globalid, "Dataset")
97
105
  except ValueError:
98
- raise HTTPException(
99
- detail="Dataset with ID {dataset_globalid} does not exist",
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
- dataset_version_globalid_str = request_body.version_id
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
- raise HTTPException(
112
- detail="DatasetVersion with ID {dataset_version_globalid} does not exist",
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 == dataset_rowid))
129
+ await session.execute(select(models.Dataset).where(models.Dataset.id == dataset_id))
119
130
  ).scalar()
120
131
  if result is None:
121
- raise HTTPException(
122
- detail=f"Dataset with ID {dataset_globalid} does not exist",
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 == dataset_rowid)
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
- raise HTTPException(
135
- detail=f"Dataset {dataset_globalid} does not have any versions",
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
- raise HTTPException(
147
- detail=f"DatasetVersion with ID {dataset_version_globalid} does not exist",
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 = request_body.name or _generate_experiment_name(dataset_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(dataset_rowid),
169
+ dataset_id=int(dataset_id),
159
170
  dataset_version_id=int(dataset_version_id),
160
171
  name=experiment_name,
161
- description=request_body.description,
162
- repetitions=request_body.repetitions,
163
- metadata_=request_body.metadata or {},
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
- return CreateExperimentResponseBody(
191
- data=Experiment(
192
- id=str(experiment_globalid),
193
- dataset_id=str(dataset_globalid),
194
- dataset_version_id=str(dataset_version_globalid),
195
- repetitions=experiment.repetitions,
196
- metadata=experiment.metadata_,
197
- project_name=experiment.project_name,
198
- created_at=experiment.created_at,
199
- updated_at=experiment.updated_at,
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
- class GetExperimentResponseBody(ResponseBody[Experiment]):
205
- pass
206
-
207
-
208
- @router.get(
209
- "/experiments/{experiment_id}",
210
- operation_id="getExperiment",
211
- summary="Get experiment by ID",
212
- responses=add_errors_to_responses(
213
- [{"status_code": HTTP_404_NOT_FOUND, "description": "Experiment not found"}]
214
- ),
215
- response_description="Experiment retrieved successfully",
216
- )
217
- async def get_experiment(request: Request, experiment_id: str) -> GetExperimentResponseBody:
218
- experiment_globalid = GlobalID.from_id(experiment_id)
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
- experiment_rowid = from_global_id_with_expected_type(experiment_globalid, "Experiment")
271
+ experiment_id = from_global_id_with_expected_type(experiment_globalid, "Experiment")
221
272
  except ValueError:
222
- raise HTTPException(
223
- detail="Experiment with ID {experiment_globalid} does not exist",
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 == experiment_rowid)
280
+ select(models.Experiment).where(models.Experiment.id == experiment_id)
230
281
  )
231
282
  experiment = experiment.scalar()
232
283
  if not experiment:
233
- raise HTTPException(
234
- detail=f"Experiment with ID {experiment_globalid} does not exist",
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
- return GetExperimentResponseBody(
241
- data=Experiment(
242
- id=str(experiment_globalid),
243
- dataset_id=str(dataset_globalid),
244
- dataset_version_id=str(dataset_version_globalid),
245
- repetitions=experiment.repetitions,
246
- metadata=experiment.metadata_,
247
- project_name=experiment.project_name,
248
- created_at=experiment.created_at,
249
- updated_at=experiment.updated_at,
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})