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,9 +1,10 @@
1
1
  import gzip
2
2
  from itertools import chain
3
- from typing import AsyncContextManager, Callable, Iterator, Tuple
3
+ from typing import AsyncContextManager, Callable, Iterator, Optional, Tuple
4
4
 
5
5
  import pandas as pd
6
6
  import pyarrow as pa
7
+ from fastapi import APIRouter, Header, HTTPException, Query
7
8
  from google.protobuf.message import DecodeError
8
9
  from pandas import DataFrame
9
10
  from sqlalchemy import select
@@ -16,7 +17,7 @@ from starlette.datastructures import State
16
17
  from starlette.requests import Request
17
18
  from starlette.responses import Response, StreamingResponse
18
19
  from starlette.status import (
19
- HTTP_403_FORBIDDEN,
20
+ HTTP_204_NO_CONTENT,
20
21
  HTTP_404_NOT_FOUND,
21
22
  HTTP_415_UNSUPPORTED_MEDIA_TYPE,
22
23
  HTTP_422_UNPROCESSABLE_ENTITY,
@@ -36,86 +37,92 @@ from phoenix.trace.span_evaluations import (
36
37
  TraceEvaluations,
37
38
  )
38
39
 
40
+ from .utils import add_errors_to_responses
41
+
39
42
  EvaluationName: TypeAlias = str
40
43
 
44
+ router = APIRouter(tags=["traces"], include_in_schema=False)
41
45
 
42
- async def post_evaluations(request: Request) -> Response:
43
- """
44
- summary: Add evaluations to a span, trace, or document
45
- operationId: addEvaluations
46
- tags:
47
- - private
48
- requestBody:
49
- required: true
50
- content:
51
- application/x-protobuf:
52
- schema:
53
- type: string
54
- format: binary
55
- application/x-pandas-arrow:
56
- schema:
57
- type: string
58
- format: binary
59
- responses:
60
- 200:
61
- description: Success
62
- 403:
63
- description: Forbidden
64
- 415:
65
- description: Unsupported content type, only gzipped protobuf and pandas-arrow are supported
66
- 422:
67
- description: Request body is invalid
68
- """
69
- if request.app.state.read_only:
70
- return Response(status_code=HTTP_403_FORBIDDEN)
71
- content_type = request.headers.get("content-type")
46
+
47
+ @router.post(
48
+ "/evaluations",
49
+ operation_id="addEvaluations",
50
+ summary="Add span, trace, or document evaluations",
51
+ status_code=HTTP_204_NO_CONTENT,
52
+ responses=add_errors_to_responses(
53
+ [
54
+ {
55
+ "status_code": HTTP_415_UNSUPPORTED_MEDIA_TYPE,
56
+ "description": (
57
+ "Unsupported content type, "
58
+ "only gzipped protobuf and pandas-arrow are supported"
59
+ ),
60
+ },
61
+ HTTP_422_UNPROCESSABLE_ENTITY,
62
+ ]
63
+ ),
64
+ openapi_extra={
65
+ "requestBody": {
66
+ "required": True,
67
+ "content": {
68
+ "application/x-protobuf": {"schema": {"type": "string", "format": "binary"}},
69
+ "application/x-pandas-arrow": {"schema": {"type": "string", "format": "binary"}},
70
+ },
71
+ },
72
+ },
73
+ )
74
+ async def post_evaluations(
75
+ request: Request,
76
+ content_type: Optional[str] = Header(default=None),
77
+ content_encoding: Optional[str] = Header(default=None),
78
+ ) -> Response:
72
79
  if content_type == "application/x-pandas-arrow":
73
80
  return await _process_pyarrow(request)
74
81
  if content_type != "application/x-protobuf":
75
- return Response("Unsupported content type", status_code=HTTP_415_UNSUPPORTED_MEDIA_TYPE)
82
+ raise HTTPException(
83
+ detail="Unsupported content type", status_code=HTTP_415_UNSUPPORTED_MEDIA_TYPE
84
+ )
76
85
  body = await request.body()
77
- content_encoding = request.headers.get("content-encoding")
78
86
  if content_encoding == "gzip":
79
87
  body = gzip.decompress(body)
80
88
  elif content_encoding:
81
- return Response("Unsupported content encoding", status_code=HTTP_415_UNSUPPORTED_MEDIA_TYPE)
89
+ raise HTTPException(
90
+ detail="Unsupported content encoding", status_code=HTTP_415_UNSUPPORTED_MEDIA_TYPE
91
+ )
82
92
  evaluation = pb.Evaluation()
83
93
  try:
84
94
  evaluation.ParseFromString(body)
85
95
  except DecodeError:
86
- return Response("Request body is invalid", status_code=HTTP_422_UNPROCESSABLE_ENTITY)
96
+ raise HTTPException(
97
+ detail="Request body is invalid", status_code=HTTP_422_UNPROCESSABLE_ENTITY
98
+ )
87
99
  if not evaluation.name.strip():
88
- return Response(
89
- "Evaluation name must not be blank/empty",
100
+ raise HTTPException(
101
+ detail="Evaluation name must not be blank/empty",
90
102
  status_code=HTTP_422_UNPROCESSABLE_ENTITY,
91
103
  )
92
104
  await request.state.queue_evaluation_for_bulk_insert(evaluation)
93
105
  return Response()
94
106
 
95
107
 
96
- async def get_evaluations(request: Request) -> Response:
97
- """
98
- summary: Get evaluations from Phoenix
99
- operationId: getEvaluation
100
- tags:
101
- - private
102
- parameters:
103
- - name: project_name
104
- in: query
105
- schema:
106
- type: string
107
- default: default
108
- description: The project name to get evaluations from
109
- responses:
110
- 200:
111
- description: Success
112
- 403:
113
- description: Forbidden
114
- 404:
115
- description: Not found
116
- """
108
+ @router.get(
109
+ "/evaluations",
110
+ operation_id="getEvaluations",
111
+ summary="Get span, trace, or document evaluations from a project",
112
+ responses=add_errors_to_responses([HTTP_404_NOT_FOUND]),
113
+ )
114
+ async def get_evaluations(
115
+ request: Request,
116
+ project_name: Optional[str] = Query(
117
+ default=None,
118
+ description=(
119
+ "The name of the project to get evaluations from (if omitted, "
120
+ f"evaluations will be drawn from the `{DEFAULT_PROJECT_NAME}` project)"
121
+ ),
122
+ ),
123
+ ) -> Response:
117
124
  project_name = (
118
- request.query_params.get("project_name")
125
+ project_name
119
126
  or request.query_params.get("project-name") # for backward compatibility
120
127
  or request.headers.get("project-name") # read from headers for backwards compatibility
121
128
  or DEFAULT_PROJECT_NAME
@@ -169,20 +176,20 @@ async def _process_pyarrow(request: Request) -> Response:
169
176
  try:
170
177
  reader = pa.ipc.open_stream(body)
171
178
  except pa.ArrowInvalid:
172
- return Response(
173
- content="Request body is not valid pyarrow",
179
+ raise HTTPException(
180
+ detail="Request body is not valid pyarrow",
174
181
  status_code=HTTP_422_UNPROCESSABLE_ENTITY,
175
182
  )
176
183
  try:
177
184
  evaluations = Evaluations.from_pyarrow_reader(reader)
178
185
  except Exception as e:
179
186
  if isinstance(e, PhoenixEvaluationNameIsMissing):
180
- return Response(
181
- "Evaluation name must not be blank/empty",
187
+ raise HTTPException(
188
+ detail="Evaluation name must not be blank/empty",
182
189
  status_code=HTTP_422_UNPROCESSABLE_ENTITY,
183
190
  )
184
- return Response(
185
- content="Invalid data in request body",
191
+ raise HTTPException(
192
+ detail="Invalid data in request body",
186
193
  status_code=HTTP_422_UNPROCESSABLE_ENTITY,
187
194
  )
188
195
  return Response(background=BackgroundTask(_add_evaluations, request.state, evaluations))
@@ -1,7 +1,9 @@
1
1
  from datetime import datetime
2
+ from typing import Any, Dict, Literal, Optional
2
3
 
4
+ from fastapi import APIRouter, HTTPException
5
+ from pydantic import BaseModel, Field
3
6
  from starlette.requests import Request
4
- from starlette.responses import JSONResponse, Response
5
7
  from starlette.status import HTTP_404_NOT_FOUND
6
8
  from strawberry.relay import GlobalID
7
9
 
@@ -10,103 +12,75 @@ from phoenix.db.helpers import SupportedSQLDialect
10
12
  from phoenix.db.insertion.helpers import insert_on_conflict
11
13
  from phoenix.server.api.types.node import from_global_id_with_expected_type
12
14
 
15
+ from .utils import ResponseBody, add_errors_to_responses
13
16
 
14
- async def upsert_experiment_evaluation(request: Request) -> Response:
15
- """
16
- summary: Create an evaluation for a specific experiment run
17
- operationId: upsertExperimentEvaluation
18
- tags:
19
- - private
20
- requestBody:
21
- description: Details of the experiment evaluation to be upserted
22
- required: true
23
- content:
24
- application/json:
25
- schema:
26
- type: object
27
- properties:
28
- experiment_run_id:
29
- type: string
30
- description: The ID of the experiment run being evaluated
31
- name:
32
- type: string
33
- description: The name of the evaluation
34
- annotator_kind:
35
- type: string
36
- description: The kind of annotator used for the evaluation
37
- result:
38
- type: object
39
- description: The result of the evaluation
40
- properties:
41
- label:
42
- type: string
43
- description: The label assigned by the evaluation
44
- score:
45
- type: number
46
- format: float
47
- description: The score assigned by the evaluation
48
- explanation:
49
- type: string
50
- description: Explanation of the evaluation result
51
- error:
52
- type: string
53
- description: Optional error message if the evaluation encountered an error
54
- metadata:
55
- type: object
56
- description: Metadata for the evaluation
57
- additionalProperties:
58
- type: string
59
- start_time:
60
- type: string
61
- format: date-time
62
- description: The start time of the evaluation in ISO format
63
- end_time:
64
- type: string
65
- format: date-time
66
- description: The end time of the evaluation in ISO format
67
- trace_id:
68
- type: string
69
- description: Optional trace ID for tracking
70
- required:
71
- - experiment_run_id
72
- - name
73
- - annotator_kind
74
- - start_time
75
- - end_time
76
- responses:
77
- 200:
78
- description: Experiment evaluation upserted successfully
79
- content:
80
- application/json:
81
- schema:
82
- type: object
83
- properties:
84
- data:
85
- type: object
86
- properties:
87
- id:
88
- type: string
89
- description: The ID of the upserted experiment evaluation
90
- 404:
91
- description: ExperimentRun not found
92
- """
17
+ router = APIRouter(tags=["experiments"], include_in_schema=False)
18
+
19
+
20
+ class ExperimentEvaluationResult(BaseModel):
21
+ label: Optional[str] = Field(default=None, description="The label assigned by the evaluation")
22
+ score: Optional[float] = Field(default=None, description="The score assigned by the evaluation")
23
+ explanation: Optional[str] = Field(
24
+ default=None, description="Explanation of the evaluation result"
25
+ )
26
+
27
+
28
+ class UpsertExperimentEvaluationRequestBody(BaseModel):
29
+ experiment_run_id: str = Field(description="The ID of the experiment run being evaluated")
30
+ name: str = Field(description="The name of the evaluation")
31
+ annotator_kind: Literal["LLM", "CODE", "HUMAN"] = Field(
32
+ description="The kind of annotator used for the evaluation"
33
+ )
34
+ start_time: datetime = Field(description="The start time of the evaluation in ISO format")
35
+ end_time: datetime = Field(description="The end time of the evaluation in ISO format")
36
+ result: ExperimentEvaluationResult = Field(description="The result of the evaluation")
37
+ error: Optional[str] = Field(
38
+ None, description="Optional error message if the evaluation encountered an error"
39
+ )
40
+ metadata: Optional[Dict[str, Any]] = Field(
41
+ default=None, description="Metadata for the evaluation"
42
+ )
43
+ trace_id: Optional[str] = Field(default=None, description="Optional trace ID for tracking")
44
+
45
+
46
+ class UpsertExperimentEvaluationResponseBodyData(BaseModel):
47
+ id: str = Field(description="The ID of the upserted experiment evaluation")
48
+
49
+
50
+ class UpsertExperimentEvaluationResponseBody(
51
+ ResponseBody[UpsertExperimentEvaluationResponseBodyData]
52
+ ):
53
+ pass
54
+
55
+
56
+ @router.post(
57
+ "/experiment_evaluations",
58
+ operation_id="upsertExperimentEvaluation",
59
+ summary="Create or update evaluation for an experiment run",
60
+ responses=add_errors_to_responses(
61
+ [{"status_code": HTTP_404_NOT_FOUND, "description": "Experiment run not found"}]
62
+ ),
63
+ )
64
+ async def upsert_experiment_evaluation(
65
+ request: Request, request_body: UpsertExperimentEvaluationRequestBody
66
+ ) -> UpsertExperimentEvaluationResponseBody:
93
67
  payload = await request.json()
94
68
  experiment_run_gid = GlobalID.from_id(payload["experiment_run_id"])
95
69
  try:
96
70
  experiment_run_id = from_global_id_with_expected_type(experiment_run_gid, "ExperimentRun")
97
71
  except ValueError:
98
- return Response(
99
- content=f"ExperimentRun with ID {experiment_run_gid} does not exist",
72
+ raise HTTPException(
73
+ detail=f"ExperimentRun with ID {experiment_run_gid} does not exist",
100
74
  status_code=HTTP_404_NOT_FOUND,
101
75
  )
102
- name = payload["name"]
103
- annotator_kind = payload["annotator_kind"]
104
- result = payload.get("result")
105
- label = result.get("label") if result else None
106
- score = result.get("score") if result else None
107
- explanation = result.get("explanation") if result else None
108
- error = payload.get("error")
109
- metadata = payload.get("metadata") or {}
76
+ name = request_body.name
77
+ annotator_kind = request_body.annotator_kind
78
+ result = request_body.result
79
+ label = result.label if result else None
80
+ score = result.score if result else None
81
+ explanation = result.explanation if result else None
82
+ error = request_body.error
83
+ metadata = request_body.metadata or {}
110
84
  start_time = payload["start_time"]
111
85
  end_time = payload["end_time"]
112
86
  async with request.app.state.db() as session:
@@ -133,4 +107,6 @@ async def upsert_experiment_evaluation(request: Request) -> Response:
133
107
  ).returning(models.ExperimentRunAnnotation)
134
108
  )
135
109
  evaluation_gid = GlobalID("ExperimentEvaluation", str(exp_eval_run.id))
136
- return JSONResponse(content={"data": {"id": str(evaluation_gid)}})
110
+ return UpsertExperimentEvaluationResponseBody(
111
+ data=UpsertExperimentEvaluationResponseBodyData(id=str(evaluation_gid))
112
+ )