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,19 +1,18 @@
1
1
  import gzip
2
2
  import zlib
3
- from typing import Any, Dict, List, Literal, Optional
3
+ from typing import Any, Dict, List
4
4
 
5
- from fastapi import APIRouter, BackgroundTasks, Header, HTTPException
6
5
  from google.protobuf.message import DecodeError
7
6
  from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import (
8
7
  ExportTraceServiceRequest,
9
8
  )
10
- from pydantic import Field
11
9
  from sqlalchemy import select
10
+ from starlette.background import BackgroundTask
12
11
  from starlette.concurrency import run_in_threadpool
13
12
  from starlette.datastructures import State
14
13
  from starlette.requests import Request
14
+ from starlette.responses import JSONResponse, Response
15
15
  from starlette.status import (
16
- HTTP_204_NO_CONTENT,
17
16
  HTTP_404_NOT_FOUND,
18
17
  HTTP_415_UNSUPPORTED_MEDIA_TYPE,
19
18
  HTTP_422_UNPROCESSABLE_ENTITY,
@@ -27,51 +26,40 @@ from phoenix.server.api.types.node import from_global_id_with_expected_type
27
26
  from phoenix.trace.otel import decode_otlp_span
28
27
  from phoenix.utilities.project import get_project_name
29
28
 
30
- from .pydantic_compat import V1RoutesBaseModel
31
- from .utils import RequestBody, ResponseBody, add_errors_to_responses
32
-
33
- router = APIRouter(tags=["traces"], include_in_schema=False)
34
-
35
-
36
- @router.post(
37
- "/traces",
38
- operation_id="addTraces",
39
- summary="Send traces",
40
- status_code=HTTP_204_NO_CONTENT,
41
- responses=add_errors_to_responses(
42
- [
43
- {
44
- "status_code": HTTP_415_UNSUPPORTED_MEDIA_TYPE,
45
- "description": (
46
- "Unsupported content type (only `application/x-protobuf` is supported)"
47
- ),
48
- },
49
- {"status_code": HTTP_422_UNPROCESSABLE_ENTITY, "description": "Invalid request body"},
50
- ]
51
- ),
52
- openapi_extra={
53
- "requestBody": {
54
- "required": True,
55
- "content": {
56
- "application/x-protobuf": {"schema": {"type": "string", "format": "binary"}}
57
- },
58
- }
59
- },
60
- )
61
- async def post_traces(
62
- request: Request,
63
- background_tasks: BackgroundTasks,
64
- content_type: Optional[str] = Header(default=None),
65
- content_encoding: Optional[str] = Header(default=None),
66
- ) -> None:
29
+
30
+ async def post_traces(request: Request) -> Response:
31
+ """
32
+ summary: Send traces to Phoenix
33
+ operationId: addTraces
34
+ tags:
35
+ - private
36
+ requestBody:
37
+ required: true
38
+ content:
39
+ application/x-protobuf:
40
+ schema:
41
+ type: string
42
+ format: binary
43
+ responses:
44
+ 200:
45
+ description: Success
46
+ 403:
47
+ description: Forbidden
48
+ 415:
49
+ description: Unsupported content type, only gzipped protobuf
50
+ 422:
51
+ description: Request body is invalid
52
+ """
53
+ content_type = request.headers.get("content-type")
67
54
  if content_type != "application/x-protobuf":
68
- raise HTTPException(
69
- detail=f"Unsupported content type: {content_type}",
55
+ return Response(
56
+ content=f"Unsupported content type: {content_type}",
70
57
  status_code=HTTP_415_UNSUPPORTED_MEDIA_TYPE,
71
58
  )
59
+ content_encoding = request.headers.get("content-encoding")
72
60
  if content_encoding and content_encoding not in ("gzip", "deflate"):
73
- raise HTTPException(
74
- detail=f"Unsupported content encoding: {content_encoding}",
61
+ return Response(
62
+ content=f"Unsupported content encoding: {content_encoding}",
75
63
  status_code=HTTP_415_UNSUPPORTED_MEDIA_TYPE,
76
64
  )
77
65
  body = await request.body()
@@ -83,69 +71,96 @@ async def post_traces(
83
71
  try:
84
72
  await run_in_threadpool(req.ParseFromString, body)
85
73
  except DecodeError:
86
- raise HTTPException(
87
- detail="Request body is invalid ExportTraceServiceRequest",
74
+ return Response(
75
+ content="Request body is invalid ExportTraceServiceRequest",
88
76
  status_code=HTTP_422_UNPROCESSABLE_ENTITY,
89
77
  )
90
- background_tasks.add_task(_add_spans, req, request.state)
91
- return None
92
-
93
-
94
- class TraceAnnotationResult(V1RoutesBaseModel):
95
- label: Optional[str] = Field(default=None, description="The label assigned by the annotation")
96
- score: Optional[float] = Field(default=None, description="The score assigned by the annotation")
97
- explanation: Optional[str] = Field(
98
- default=None, description="Explanation of the annotation result"
99
- )
100
-
101
-
102
- class TraceAnnotation(V1RoutesBaseModel):
103
- trace_id: str = Field(description="The ID of the trace being annotated")
104
- name: str = Field(description="The name of the annotation")
105
- annotator_kind: Literal["LLM", "HUMAN"] = Field(
106
- description="The kind of annotator used for the annotation"
107
- )
108
- result: Optional[TraceAnnotationResult] = Field(
109
- default=None, description="The result of the annotation"
110
- )
111
- metadata: Optional[Dict[str, Any]] = Field(
112
- default=None, description="Metadata for the annotation"
113
- )
114
-
115
-
116
- class AnnotateTracesRequestBody(RequestBody[List[TraceAnnotation]]):
117
- data: List[TraceAnnotation] = Field(description="The trace annotations to be upserted")
118
-
119
-
120
- class InsertedTraceAnnotation(V1RoutesBaseModel):
121
- id: str = Field(description="The ID of the inserted trace annotation")
122
-
123
-
124
- class AnnotateTracesResponseBody(ResponseBody[List[InsertedTraceAnnotation]]):
125
- pass
126
-
127
-
128
- @router.post(
129
- "/trace_annotations",
130
- operation_id="annotateTraces",
131
- summary="Create or update trace annotations",
132
- responses=add_errors_to_responses(
133
- [{"status_code": HTTP_404_NOT_FOUND, "description": "Trace not found"}]
134
- ),
135
- )
136
- async def annotate_traces(
137
- request: Request, request_body: AnnotateTracesRequestBody
138
- ) -> AnnotateTracesResponseBody:
139
- trace_annotations = request_body.data
140
- trace_gids = [GlobalID.from_id(annotation.trace_id) for annotation in trace_annotations]
78
+ return Response(background=BackgroundTask(_add_spans, req, request.state))
79
+
80
+
81
+ async def annotate_traces(request: Request) -> Response:
82
+ """
83
+ summary: Upsert annotations for traces
84
+ operationId: annotateTraces
85
+ tags:
86
+ - private
87
+ requestBody:
88
+ description: List of trace annotations to be inserted
89
+ required: true
90
+ content:
91
+ application/json:
92
+ schema:
93
+ type: object
94
+ properties:
95
+ data:
96
+ type: array
97
+ items:
98
+ type: object
99
+ properties:
100
+ trace_id:
101
+ type: string
102
+ description: The ID of the trace being annotated
103
+ name:
104
+ type: string
105
+ description: The name of the annotation
106
+ annotator_kind:
107
+ type: string
108
+ description: The kind of annotator used for the annotation ("LLM" or "HUMAN")
109
+ result:
110
+ type: object
111
+ description: The result of the annotation
112
+ properties:
113
+ label:
114
+ type: string
115
+ description: The label assigned by the annotation
116
+ score:
117
+ type: number
118
+ format: float
119
+ description: The score assigned by the annotation
120
+ explanation:
121
+ type: string
122
+ description: Explanation of the annotation result
123
+ error:
124
+ type: string
125
+ description: Optional error message if the annotation encountered an error
126
+ metadata:
127
+ type: object
128
+ description: Metadata for the annotation
129
+ additionalProperties:
130
+ type: string
131
+ required:
132
+ - trace_id
133
+ - name
134
+ - annotator_kind
135
+ responses:
136
+ 200:
137
+ description: Trace annotations inserted successfully
138
+ content:
139
+ application/json:
140
+ schema:
141
+ type: object
142
+ properties:
143
+ data:
144
+ type: array
145
+ items:
146
+ type: object
147
+ properties:
148
+ id:
149
+ type: string
150
+ description: The ID of the inserted trace annotation
151
+ 404:
152
+ description: Trace not found
153
+ """
154
+ payload: List[Dict[str, Any]] = (await request.json()).get("data", [])
155
+ trace_gids = [GlobalID.from_id(annotation["trace_id"]) for annotation in payload]
141
156
 
142
157
  resolved_trace_ids = []
143
158
  for trace_gid in trace_gids:
144
159
  try:
145
160
  resolved_trace_ids.append(from_global_id_with_expected_type(trace_gid, "Trace"))
146
161
  except ValueError:
147
- raise HTTPException(
148
- detail="Trace with ID {trace_gid} does not exist",
162
+ return Response(
163
+ content="Trace with ID {trace_gid} does not exist",
149
164
  status_code=HTTP_404_NOT_FOUND,
150
165
  )
151
166
 
@@ -160,24 +175,24 @@ async def annotate_traces(
160
175
  missing_trace_gids = [
161
176
  str(GlobalID("Trace", str(trace_gid))) for trace_gid in missing_trace_ids
162
177
  ]
163
- raise HTTPException(
164
- detail=f"Traces with IDs {', '.join(missing_trace_gids)} do not exist.",
178
+ return Response(
179
+ content=f"Traces with IDs {', '.join(missing_trace_gids)} do not exist.",
165
180
  status_code=HTTP_404_NOT_FOUND,
166
181
  )
167
182
 
168
183
  inserted_annotations = []
169
184
 
170
- for annotation in trace_annotations:
171
- trace_gid = GlobalID.from_id(annotation.trace_id)
185
+ for annotation in payload:
186
+ trace_gid = GlobalID.from_id(annotation["trace_id"])
172
187
  trace_id = from_global_id_with_expected_type(trace_gid, "Trace")
173
188
 
174
- name = annotation.name
175
- annotator_kind = annotation.annotator_kind
176
- result = annotation.result
177
- label = result.label if result else None
178
- score = result.score if result else None
179
- explanation = result.explanation if result else None
180
- metadata = annotation.metadata or {}
189
+ name = annotation["name"]
190
+ annotator_kind = annotation["annotator_kind"]
191
+ result = annotation.get("result")
192
+ label = result.get("label") if result else None
193
+ score = result.get("score") if result else None
194
+ explanation = result.get("explanation") if result else None
195
+ metadata = annotation.get("metadata") or {}
181
196
 
182
197
  values = dict(
183
198
  trace_rowid=trace_id,
@@ -198,12 +213,10 @@ async def annotate_traces(
198
213
  ).returning(models.TraceAnnotation.id)
199
214
  )
200
215
  inserted_annotations.append(
201
- InsertedTraceAnnotation(
202
- id=str(GlobalID("TraceAnnotation", str(trace_annotation_id)))
203
- )
216
+ {"id": str(GlobalID("TraceAnnotation", str(trace_annotation_id)))}
204
217
  )
205
218
 
206
- return AnnotateTracesResponseBody(data=inserted_annotations)
219
+ return JSONResponse(content={"data": inserted_annotations})
207
220
 
208
221
 
209
222
  async def _add_spans(req: ExportTraceServiceRequest, state: State) -> None:
@@ -104,11 +104,11 @@ class Experiment(Node):
104
104
  return await info.context.data_loaders.experiment_error_rates.load(self.id_attr)
105
105
 
106
106
  @strawberry.field
107
- async def average_run_latency_ms(self, info: Info[Context, None]) -> float:
107
+ async def average_run_latency_ms(self, info: Info[Context, None]) -> Optional[float]:
108
108
  latency_seconds = await info.context.data_loaders.average_experiment_run_latency.load(
109
109
  self.id_attr
110
110
  )
111
- return latency_seconds * 1000
111
+ return latency_seconds * 1000 if latency_seconds is not None else None
112
112
 
113
113
  @strawberry.field
114
114
  async def project(self, info: Info[Context, None]) -> Optional[Project]:
@@ -2,8 +2,7 @@ from datetime import datetime
2
2
  from typing import Iterable, List, Optional, Set, Union
3
3
 
4
4
  import strawberry
5
- from strawberry.scalars import ID
6
- from strawberry.unset import UNSET
5
+ from strawberry import ID, UNSET
7
6
 
8
7
  import phoenix.core.model_schema as ms
9
8
  from phoenix.core.model_schema import FEATURE, TAG, ScalarDimension
@@ -2,9 +2,8 @@ import asyncio
2
2
  from typing import List, Optional
3
3
 
4
4
  import strawberry
5
+ from strawberry import UNSET, Info
5
6
  from strawberry.relay import Connection
6
- from strawberry.types import Info
7
- from strawberry.unset import UNSET
8
7
  from typing_extensions import Annotated
9
8
 
10
9
  from phoenix.config import get_exported_files