arize-phoenix 4.12.1rc1__py3-none-any.whl → 4.14.1__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 (51) hide show
  1. {arize_phoenix-4.12.1rc1.dist-info → arize_phoenix-4.14.1.dist-info}/METADATA +12 -9
  2. {arize_phoenix-4.12.1rc1.dist-info → arize_phoenix-4.14.1.dist-info}/RECORD +48 -49
  3. phoenix/db/bulk_inserter.py +3 -1
  4. phoenix/experiments/evaluators/base.py +4 -0
  5. phoenix/experiments/evaluators/code_evaluators.py +80 -0
  6. phoenix/experiments/evaluators/llm_evaluators.py +77 -1
  7. phoenix/experiments/evaluators/utils.py +70 -21
  8. phoenix/experiments/functions.py +14 -14
  9. phoenix/server/api/context.py +7 -3
  10. phoenix/server/api/dataloaders/average_experiment_run_latency.py +23 -23
  11. phoenix/server/api/dataloaders/experiment_error_rates.py +30 -10
  12. phoenix/server/api/dataloaders/experiment_run_counts.py +18 -5
  13. phoenix/server/api/input_types/{CreateSpanAnnotationsInput.py → CreateSpanAnnotationInput.py} +4 -2
  14. phoenix/server/api/input_types/{CreateTraceAnnotationsInput.py → CreateTraceAnnotationInput.py} +4 -2
  15. phoenix/server/api/input_types/{PatchAnnotationsInput.py → PatchAnnotationInput.py} +4 -2
  16. phoenix/server/api/mutations/span_annotations_mutations.py +12 -6
  17. phoenix/server/api/mutations/trace_annotations_mutations.py +12 -6
  18. phoenix/server/api/openapi/main.py +2 -18
  19. phoenix/server/api/openapi/schema.py +12 -12
  20. phoenix/server/api/routers/v1/__init__.py +83 -36
  21. phoenix/server/api/routers/v1/dataset_examples.py +123 -102
  22. phoenix/server/api/routers/v1/datasets.py +506 -390
  23. phoenix/server/api/routers/v1/evaluations.py +66 -73
  24. phoenix/server/api/routers/v1/experiment_evaluations.py +91 -68
  25. phoenix/server/api/routers/v1/experiment_runs.py +155 -98
  26. phoenix/server/api/routers/v1/experiments.py +181 -132
  27. phoenix/server/api/routers/v1/spans.py +173 -144
  28. phoenix/server/api/routers/v1/traces.py +128 -115
  29. phoenix/server/api/types/Experiment.py +2 -2
  30. phoenix/server/api/types/Inferences.py +1 -2
  31. phoenix/server/api/types/Model.py +1 -2
  32. phoenix/server/app.py +177 -152
  33. phoenix/server/openapi/docs.py +221 -0
  34. phoenix/server/static/.vite/manifest.json +31 -31
  35. phoenix/server/static/assets/{components-C8sm_r1F.js → components-DeS0YEmv.js} +2 -2
  36. phoenix/server/static/assets/index-CQgXRwU0.js +100 -0
  37. phoenix/server/static/assets/{pages-bN7juCjh.js → pages-hdjlFZhO.js} +275 -198
  38. phoenix/server/static/assets/{vendor-CUDAPm8e.js → vendor-DPvSDRn3.js} +1 -1
  39. phoenix/server/static/assets/{vendor-arizeai-Do2HOmcL.js → vendor-arizeai-CkvPT67c.js} +2 -2
  40. phoenix/server/static/assets/{vendor-codemirror-CrdxOlMs.js → vendor-codemirror-Cqwpwlua.js} +1 -1
  41. phoenix/server/static/assets/{vendor-recharts-PKRvByVe.js → vendor-recharts-5jlNaZuF.js} +1 -1
  42. phoenix/server/thread_server.py +2 -2
  43. phoenix/session/client.py +9 -8
  44. phoenix/trace/dsl/filter.py +40 -25
  45. phoenix/version.py +1 -1
  46. phoenix/server/api/routers/v1/pydantic_compat.py +0 -78
  47. phoenix/server/api/routers/v1/utils.py +0 -95
  48. phoenix/server/static/assets/index-BEKPzgQs.js +0 -100
  49. {arize_phoenix-4.12.1rc1.dist-info → arize_phoenix-4.14.1.dist-info}/WHEEL +0 -0
  50. {arize_phoenix-4.12.1rc1.dist-info → arize_phoenix-4.14.1.dist-info}/licenses/IP_NOTICE +0 -0
  51. {arize_phoenix-4.12.1rc1.dist-info → arize_phoenix-4.14.1.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 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,11 +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 .pydantic_compat import V1RoutesBaseModel
18
- from .utils import ResponseBody, add_errors_to_responses
19
-
20
- router = APIRouter(tags=["experiments"], include_in_schema=False)
21
-
22
14
 
23
15
  def _short_uuid() -> str:
24
16
  return str(getrandbits(32).to_bytes(4, "big").hex())
@@ -32,76 +24,94 @@ def _generate_experiment_name(dataset_name: str) -> str:
32
24
  return f"{short_ds_name}-{_short_uuid()}"
33
25
 
34
26
 
35
- class Experiment(V1RoutesBaseModel):
36
- id: str = Field(description="The ID of the experiment")
37
- dataset_id: str = Field(description="The ID of the dataset associated with the experiment")
38
- dataset_version_id: str = Field(
39
- description="The ID of the dataset version associated with the experiment"
40
- )
41
- repetitions: int = Field(description="Number of times the experiment is repeated")
42
- metadata: Dict[str, Any] = Field(description="Metadata of the experiment")
43
- project_name: Optional[str] = Field(
44
- description="The name of the project associated with the experiment"
45
- )
46
- created_at: datetime = Field(description="The creation timestamp of the experiment")
47
- updated_at: datetime = Field(description="The last update timestamp of the experiment")
48
-
49
-
50
- class CreateExperimentRequestBody(V1RoutesBaseModel):
27
+ async def create_experiment(request: Request) -> Response:
51
28
  """
52
- 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
53
101
  """
54
-
55
- name: Optional[str] = Field(
56
- default=None,
57
- description=("Name of the experiment (if omitted, a random name will be generated)"),
58
- )
59
- description: Optional[str] = Field(
60
- default=None, description="An optional description of the experiment"
61
- )
62
- metadata: Optional[Dict[str, Any]] = Field(
63
- default=None, description="Metadata for the experiment"
64
- )
65
- version_id: Optional[str] = Field(
66
- default=None,
67
- description=(
68
- "ID of the dataset version over which the experiment will be run "
69
- "(if omitted, the latest version will be used)"
70
- ),
71
- )
72
- repetitions: int = Field(
73
- default=1, description="Number of times the experiment should be repeated for each example"
74
- )
75
-
76
-
77
- class CreateExperimentResponseBody(ResponseBody[Experiment]):
78
- pass
79
-
80
-
81
- @router.post(
82
- "/datasets/{dataset_id}/experiments",
83
- operation_id="createExperiment",
84
- summary="Create experiment on a dataset",
85
- responses=add_errors_to_responses(
86
- [{"status_code": HTTP_404_NOT_FOUND, "description": "Dataset or DatasetVersion not found"}]
87
- ),
88
- response_description="Experiment retrieved successfully",
89
- )
90
- async def create_experiment(
91
- request: Request,
92
- dataset_id: str,
93
- request_body: CreateExperimentRequestBody,
94
- ) -> CreateExperimentResponseBody:
95
- dataset_globalid = GlobalID.from_id(dataset_id)
102
+ dataset_globalid = GlobalID.from_id(request.path_params["dataset_id"])
96
103
  try:
97
- dataset_rowid = from_global_id_with_expected_type(dataset_globalid, "Dataset")
104
+ dataset_id = from_global_id_with_expected_type(dataset_globalid, "Dataset")
98
105
  except ValueError:
99
- raise HTTPException(
100
- detail="Dataset with ID {dataset_globalid} does not exist",
106
+ return Response(
107
+ content="Dataset with ID {dataset_globalid} does not exist",
101
108
  status_code=HTTP_404_NOT_FOUND,
102
109
  )
103
110
 
104
- 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")
105
115
  if dataset_version_globalid_str is not None:
106
116
  try:
107
117
  dataset_version_globalid = GlobalID.from_id(dataset_version_globalid_str)
@@ -109,31 +119,31 @@ async def create_experiment(
109
119
  dataset_version_globalid, "DatasetVersion"
110
120
  )
111
121
  except ValueError:
112
- raise HTTPException(
113
- detail="DatasetVersion with ID {dataset_version_globalid} does not exist",
122
+ return Response(
123
+ content="DatasetVersion with ID {dataset_version_globalid} does not exist",
114
124
  status_code=HTTP_404_NOT_FOUND,
115
125
  )
116
126
 
117
127
  async with request.app.state.db() as session:
118
128
  result = (
119
- 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))
120
130
  ).scalar()
121
131
  if result is None:
122
- raise HTTPException(
123
- detail=f"Dataset with ID {dataset_globalid} does not exist",
132
+ return Response(
133
+ content=f"Dataset with ID {dataset_globalid} does not exist",
124
134
  status_code=HTTP_404_NOT_FOUND,
125
135
  )
126
136
  dataset_name = result.name
127
137
  if dataset_version_globalid_str is None:
128
138
  dataset_version_result = await session.execute(
129
139
  select(models.DatasetVersion)
130
- .where(models.DatasetVersion.dataset_id == dataset_rowid)
140
+ .where(models.DatasetVersion.dataset_id == dataset_id)
131
141
  .order_by(models.DatasetVersion.id.desc())
132
142
  )
133
143
  dataset_version = dataset_version_result.scalar()
134
144
  if not dataset_version:
135
- raise HTTPException(
136
- detail=f"Dataset {dataset_globalid} does not have any versions",
145
+ return Response(
146
+ content=f"Dataset {dataset_globalid} does not have any versions",
137
147
  status_code=HTTP_404_NOT_FOUND,
138
148
  )
139
149
  dataset_version_id = dataset_version.id
@@ -144,24 +154,24 @@ async def create_experiment(
144
154
  )
145
155
  dataset_version = dataset_version.scalar()
146
156
  if not dataset_version:
147
- raise HTTPException(
148
- 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",
149
159
  status_code=HTTP_404_NOT_FOUND,
150
160
  )
151
161
 
152
162
  # generate a semi-unique name for the experiment
153
- experiment_name = request_body.name or _generate_experiment_name(dataset_name)
163
+ experiment_name = payload.get("name") or _generate_experiment_name(dataset_name)
154
164
  project_name = f"Experiment-{getrandbits(96).to_bytes(12, 'big').hex()}"
155
165
  project_description = (
156
166
  f"dataset_id: {dataset_globalid}\ndataset_version_id: {dataset_version_globalid}"
157
167
  )
158
168
  experiment = models.Experiment(
159
- dataset_id=int(dataset_rowid),
169
+ dataset_id=int(dataset_id),
160
170
  dataset_version_id=int(dataset_version_id),
161
171
  name=experiment_name,
162
- description=request_body.description,
163
- repetitions=request_body.repetitions,
164
- metadata_=request_body.metadata or {},
172
+ description=payload.get("description"),
173
+ repetitions=repetitions,
174
+ metadata_=metadata,
165
175
  project_name=project_name,
166
176
  )
167
177
  session.add(experiment)
@@ -188,65 +198,104 @@ async def create_experiment(
188
198
  dataset_version_globalid = GlobalID(
189
199
  "DatasetVersion", str(experiment.dataset_version_id)
190
200
  )
191
- return CreateExperimentResponseBody(
192
- data=Experiment(
193
- id=str(experiment_globalid),
194
- dataset_id=str(dataset_globalid),
195
- dataset_version_id=str(dataset_version_globalid),
196
- repetitions=experiment.repetitions,
197
- metadata=experiment.metadata_,
198
- project_name=experiment.project_name,
199
- created_at=experiment.created_at,
200
- updated_at=experiment.updated_at,
201
- )
202
- )
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})
203
212
 
204
213
 
205
- class GetExperimentResponseBody(ResponseBody[Experiment]):
206
- pass
207
-
208
-
209
- @router.get(
210
- "/experiments/{experiment_id}",
211
- operation_id="getExperiment",
212
- summary="Get experiment by ID",
213
- responses=add_errors_to_responses(
214
- [{"status_code": HTTP_404_NOT_FOUND, "description": "Experiment not found"}]
215
- ),
216
- response_description="Experiment retrieved successfully",
217
- )
218
- async def get_experiment(request: Request, experiment_id: str) -> GetExperimentResponseBody:
219
- 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"])
220
270
  try:
221
- experiment_rowid = from_global_id_with_expected_type(experiment_globalid, "Experiment")
271
+ experiment_id = from_global_id_with_expected_type(experiment_globalid, "Experiment")
222
272
  except ValueError:
223
- raise HTTPException(
224
- detail="Experiment with ID {experiment_globalid} does not exist",
273
+ return Response(
274
+ content="Experiment with ID {experiment_globalid} does not exist",
225
275
  status_code=HTTP_404_NOT_FOUND,
226
276
  )
227
277
 
228
278
  async with request.app.state.db() as session:
229
279
  experiment = await session.execute(
230
- select(models.Experiment).where(models.Experiment.id == experiment_rowid)
280
+ select(models.Experiment).where(models.Experiment.id == experiment_id)
231
281
  )
232
282
  experiment = experiment.scalar()
233
283
  if not experiment:
234
- raise HTTPException(
235
- detail=f"Experiment with ID {experiment_globalid} does not exist",
284
+ return Response(
285
+ content=f"Experiment with ID {experiment_globalid} does not exist",
236
286
  status_code=HTTP_404_NOT_FOUND,
237
287
  )
238
288
 
239
289
  dataset_globalid = GlobalID("Dataset", str(experiment.dataset_id))
240
290
  dataset_version_globalid = GlobalID("DatasetVersion", str(experiment.dataset_version_id))
241
- return GetExperimentResponseBody(
242
- data=Experiment(
243
- id=str(experiment_globalid),
244
- dataset_id=str(dataset_globalid),
245
- dataset_version_id=str(dataset_version_globalid),
246
- repetitions=experiment.repetitions,
247
- metadata=experiment.metadata_,
248
- project_name=experiment.project_name,
249
- created_at=experiment.created_at,
250
- updated_at=experiment.updated_at,
251
- )
252
- )
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})