arize-phoenix 4.10.2rc0__py3-none-any.whl → 4.10.2rc2__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 (38) hide show
  1. {arize_phoenix-4.10.2rc0.dist-info → arize_phoenix-4.10.2rc2.dist-info}/METADATA +4 -3
  2. {arize_phoenix-4.10.2rc0.dist-info → arize_phoenix-4.10.2rc2.dist-info}/RECORD +27 -35
  3. phoenix/server/api/context.py +3 -7
  4. phoenix/server/api/openapi/main.py +18 -2
  5. phoenix/server/api/openapi/schema.py +12 -12
  6. phoenix/server/api/routers/v1/__init__.py +36 -83
  7. phoenix/server/api/routers/v1/dataset_examples.py +102 -123
  8. phoenix/server/api/routers/v1/datasets.py +389 -507
  9. phoenix/server/api/routers/v1/evaluations.py +73 -66
  10. phoenix/server/api/routers/v1/experiment_evaluations.py +67 -91
  11. phoenix/server/api/routers/v1/experiment_runs.py +97 -155
  12. phoenix/server/api/routers/v1/experiments.py +131 -181
  13. phoenix/server/api/routers/v1/spans.py +143 -173
  14. phoenix/server/api/routers/v1/traces.py +114 -128
  15. phoenix/server/api/routers/v1/utils.py +94 -0
  16. phoenix/server/api/types/Span.py +0 -1
  17. phoenix/server/app.py +148 -192
  18. phoenix/server/main.py +0 -3
  19. phoenix/server/static/index.css +6 -0
  20. phoenix/server/static/index.js +8547 -0
  21. phoenix/server/templates/index.html +25 -76
  22. phoenix/server/thread_server.py +2 -2
  23. phoenix/trace/schemas.py +0 -1
  24. phoenix/version.py +1 -1
  25. phoenix/server/openapi/docs.py +0 -221
  26. phoenix/server/static/.vite/manifest.json +0 -78
  27. phoenix/server/static/assets/components-C8sm_r1F.js +0 -1142
  28. phoenix/server/static/assets/index-BEKPzgQs.js +0 -100
  29. phoenix/server/static/assets/pages-bN7juCjh.js +0 -2885
  30. phoenix/server/static/assets/vendor-CUDAPm8e.js +0 -641
  31. phoenix/server/static/assets/vendor-DxkFTwjz.css +0 -1
  32. phoenix/server/static/assets/vendor-arizeai-Do2HOmcL.js +0 -662
  33. phoenix/server/static/assets/vendor-codemirror-CrdxOlMs.js +0 -12
  34. phoenix/server/static/assets/vendor-recharts-PKRvByVe.js +0 -59
  35. phoenix/server/static/assets/vendor-three-DwGkEfCM.js +0 -2998
  36. {arize_phoenix-4.10.2rc0.dist-info → arize_phoenix-4.10.2rc2.dist-info}/WHEEL +0 -0
  37. {arize_phoenix-4.10.2rc0.dist-info → arize_phoenix-4.10.2rc2.dist-info}/licenses/IP_NOTICE +0 -0
  38. {arize_phoenix-4.10.2rc0.dist-info → arize_phoenix-4.10.2rc2.dist-info}/licenses/LICENSE +0 -0
@@ -1,8 +1,11 @@
1
+ from datetime import datetime
1
2
  from random import getrandbits
3
+ from typing import Any, Dict, Optional
2
4
 
5
+ from fastapi import APIRouter, HTTPException
6
+ from pydantic import BaseModel, Field
3
7
  from sqlalchemy import select
4
8
  from starlette.requests import Request
5
- from starlette.responses import JSONResponse, Response
6
9
  from starlette.status import HTTP_404_NOT_FOUND
7
10
  from strawberry.relay import GlobalID
8
11
 
@@ -11,6 +14,10 @@ from phoenix.db.helpers import SupportedSQLDialect
11
14
  from phoenix.db.insertion.helpers import insert_on_conflict
12
15
  from phoenix.server.api.types.node import from_global_id_with_expected_type
13
16
 
17
+ from .utils import ResponseBody, add_errors_to_responses
18
+
19
+ router = APIRouter(tags=["experiments"], include_in_schema=False)
20
+
14
21
 
15
22
  def _short_uuid() -> str:
16
23
  return str(getrandbits(32).to_bytes(4, "big").hex())
@@ -24,94 +31,76 @@ def _generate_experiment_name(dataset_name: str) -> str:
24
31
  return f"{short_ds_name}-{_short_uuid()}"
25
32
 
26
33
 
27
- async def create_experiment(request: Request) -> Response:
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):
28
50
  """
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
51
+ Details of the experiment to be created
101
52
  """
102
- dataset_globalid = GlobalID.from_id(request.path_params["dataset_id"])
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)
103
95
  try:
104
- dataset_id = from_global_id_with_expected_type(dataset_globalid, "Dataset")
96
+ dataset_rowid = from_global_id_with_expected_type(dataset_globalid, "Dataset")
105
97
  except ValueError:
106
- return Response(
107
- content="Dataset with ID {dataset_globalid} does not exist",
98
+ raise HTTPException(
99
+ detail="Dataset with ID {dataset_globalid} does not exist",
108
100
  status_code=HTTP_404_NOT_FOUND,
109
101
  )
110
102
 
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")
103
+ dataset_version_globalid_str = request_body.version_id
115
104
  if dataset_version_globalid_str is not None:
116
105
  try:
117
106
  dataset_version_globalid = GlobalID.from_id(dataset_version_globalid_str)
@@ -119,31 +108,31 @@ async def create_experiment(request: Request) -> Response:
119
108
  dataset_version_globalid, "DatasetVersion"
120
109
  )
121
110
  except ValueError:
122
- return Response(
123
- content="DatasetVersion with ID {dataset_version_globalid} does not exist",
111
+ raise HTTPException(
112
+ detail="DatasetVersion with ID {dataset_version_globalid} does not exist",
124
113
  status_code=HTTP_404_NOT_FOUND,
125
114
  )
126
115
 
127
116
  async with request.app.state.db() as session:
128
117
  result = (
129
- await session.execute(select(models.Dataset).where(models.Dataset.id == dataset_id))
118
+ await session.execute(select(models.Dataset).where(models.Dataset.id == dataset_rowid))
130
119
  ).scalar()
131
120
  if result is None:
132
- return Response(
133
- content=f"Dataset with ID {dataset_globalid} does not exist",
121
+ raise HTTPException(
122
+ detail=f"Dataset with ID {dataset_globalid} does not exist",
134
123
  status_code=HTTP_404_NOT_FOUND,
135
124
  )
136
125
  dataset_name = result.name
137
126
  if dataset_version_globalid_str is None:
138
127
  dataset_version_result = await session.execute(
139
128
  select(models.DatasetVersion)
140
- .where(models.DatasetVersion.dataset_id == dataset_id)
129
+ .where(models.DatasetVersion.dataset_id == dataset_rowid)
141
130
  .order_by(models.DatasetVersion.id.desc())
142
131
  )
143
132
  dataset_version = dataset_version_result.scalar()
144
133
  if not dataset_version:
145
- return Response(
146
- content=f"Dataset {dataset_globalid} does not have any versions",
134
+ raise HTTPException(
135
+ detail=f"Dataset {dataset_globalid} does not have any versions",
147
136
  status_code=HTTP_404_NOT_FOUND,
148
137
  )
149
138
  dataset_version_id = dataset_version.id
@@ -154,24 +143,24 @@ async def create_experiment(request: Request) -> Response:
154
143
  )
155
144
  dataset_version = dataset_version.scalar()
156
145
  if not dataset_version:
157
- return Response(
158
- content=f"DatasetVersion with ID {dataset_version_globalid} does not exist",
146
+ raise HTTPException(
147
+ detail=f"DatasetVersion with ID {dataset_version_globalid} does not exist",
159
148
  status_code=HTTP_404_NOT_FOUND,
160
149
  )
161
150
 
162
151
  # generate a semi-unique name for the experiment
163
- experiment_name = payload.get("name") or _generate_experiment_name(dataset_name)
152
+ experiment_name = request_body.name or _generate_experiment_name(dataset_name)
164
153
  project_name = f"Experiment-{getrandbits(96).to_bytes(12, 'big').hex()}"
165
154
  project_description = (
166
155
  f"dataset_id: {dataset_globalid}\ndataset_version_id: {dataset_version_globalid}"
167
156
  )
168
157
  experiment = models.Experiment(
169
- dataset_id=int(dataset_id),
158
+ dataset_id=int(dataset_rowid),
170
159
  dataset_version_id=int(dataset_version_id),
171
160
  name=experiment_name,
172
- description=payload.get("description"),
173
- repetitions=repetitions,
174
- metadata_=metadata,
161
+ description=request_body.description,
162
+ repetitions=request_body.repetitions,
163
+ metadata_=request_body.metadata or {},
175
164
  project_name=project_name,
176
165
  )
177
166
  session.add(experiment)
@@ -198,104 +187,65 @@ async def create_experiment(request: Request) -> Response:
198
187
  dataset_version_globalid = GlobalID(
199
188
  "DatasetVersion", str(experiment.dataset_version_id)
200
189
  )
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})
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
+ )
212
202
 
213
203
 
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"])
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)
270
219
  try:
271
- experiment_id = from_global_id_with_expected_type(experiment_globalid, "Experiment")
220
+ experiment_rowid = from_global_id_with_expected_type(experiment_globalid, "Experiment")
272
221
  except ValueError:
273
- return Response(
274
- content="Experiment with ID {experiment_globalid} does not exist",
222
+ raise HTTPException(
223
+ detail="Experiment with ID {experiment_globalid} does not exist",
275
224
  status_code=HTTP_404_NOT_FOUND,
276
225
  )
277
226
 
278
227
  async with request.app.state.db() as session:
279
228
  experiment = await session.execute(
280
- select(models.Experiment).where(models.Experiment.id == experiment_id)
229
+ select(models.Experiment).where(models.Experiment.id == experiment_rowid)
281
230
  )
282
231
  experiment = experiment.scalar()
283
232
  if not experiment:
284
- return Response(
285
- content=f"Experiment with ID {experiment_globalid} does not exist",
233
+ raise HTTPException(
234
+ detail=f"Experiment with ID {experiment_globalid} does not exist",
286
235
  status_code=HTTP_404_NOT_FOUND,
287
236
  )
288
237
 
289
238
  dataset_globalid = GlobalID("Dataset", str(experiment.dataset_id))
290
239
  dataset_version_globalid = GlobalID("DatasetVersion", str(experiment.dataset_version_id))
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})
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
+ )