arize-phoenix 4.10.1__py3-none-any.whl → 4.10.2rc1__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.
- {arize_phoenix-4.10.1.dist-info → arize_phoenix-4.10.2rc1.dist-info}/METADATA +4 -3
- {arize_phoenix-4.10.1.dist-info → arize_phoenix-4.10.2rc1.dist-info}/RECORD +27 -25
- phoenix/server/api/context.py +5 -7
- phoenix/server/api/dataloaders/__init__.py +2 -0
- phoenix/server/api/dataloaders/span_annotations.py +35 -0
- phoenix/server/api/openapi/main.py +18 -2
- phoenix/server/api/openapi/schema.py +12 -12
- phoenix/server/api/routers/v1/__init__.py +36 -83
- phoenix/server/api/routers/v1/dataset_examples.py +102 -123
- phoenix/server/api/routers/v1/datasets.py +389 -507
- phoenix/server/api/routers/v1/evaluations.py +74 -64
- phoenix/server/api/routers/v1/experiment_evaluations.py +67 -91
- phoenix/server/api/routers/v1/experiment_runs.py +97 -155
- phoenix/server/api/routers/v1/experiments.py +131 -181
- phoenix/server/api/routers/v1/spans.py +141 -173
- phoenix/server/api/routers/v1/traces.py +113 -128
- phoenix/server/api/routers/v1/utils.py +94 -0
- phoenix/server/api/types/Annotation.py +21 -0
- phoenix/server/api/types/Evaluation.py +4 -22
- phoenix/server/api/types/Span.py +15 -4
- phoenix/server/api/types/SpanAnnotation.py +4 -6
- phoenix/server/app.py +149 -174
- phoenix/server/thread_server.py +2 -2
- phoenix/version.py +1 -1
- phoenix/server/openapi/docs.py +0 -221
- {arize_phoenix-4.10.1.dist-info → arize_phoenix-4.10.2rc1.dist-info}/WHEEL +0 -0
- {arize_phoenix-4.10.1.dist-info → arize_phoenix-4.10.2rc1.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-4.10.1.dist-info → arize_phoenix-4.10.2rc1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
from datetime import timezone
|
|
2
|
-
from typing import Any, AsyncIterator, Dict, List
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
from typing import Any, AsyncIterator, Dict, List, Literal, Optional
|
|
3
3
|
|
|
4
|
+
from fastapi import APIRouter, HTTPException, Query
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
4
6
|
from sqlalchemy import select
|
|
5
7
|
from starlette.requests import Request
|
|
6
|
-
from starlette.responses import
|
|
8
|
+
from starlette.responses import Response, StreamingResponse
|
|
7
9
|
from starlette.status import HTTP_404_NOT_FOUND, HTTP_422_UNPROCESSABLE_ENTITY
|
|
8
10
|
from strawberry.relay import GlobalID
|
|
9
11
|
|
|
@@ -12,93 +14,81 @@ from phoenix.datetime_utils import normalize_datetime
|
|
|
12
14
|
from phoenix.db import models
|
|
13
15
|
from phoenix.db.helpers import SupportedSQLDialect
|
|
14
16
|
from phoenix.db.insertion.helpers import insert_on_conflict
|
|
15
|
-
from phoenix.server.api.routers.utils import df_to_bytes
|
|
17
|
+
from phoenix.server.api.routers.utils import df_to_bytes
|
|
16
18
|
from phoenix.server.api.types.node import from_global_id_with_expected_type
|
|
17
|
-
from phoenix.trace.dsl import SpanQuery
|
|
19
|
+
from phoenix.trace.dsl import SpanQuery as SpanQuery_
|
|
20
|
+
|
|
21
|
+
from .utils import RequestBody, ResponseBody, add_errors_to_responses
|
|
18
22
|
|
|
19
23
|
DEFAULT_SPAN_LIMIT = 1000
|
|
20
24
|
|
|
25
|
+
router = APIRouter(tags=["traces"], include_in_schema=False)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SpanQuery(BaseModel):
|
|
29
|
+
select: Dict[str, Any]
|
|
30
|
+
filter: Dict[str, Any]
|
|
31
|
+
explode: Dict[str, Any]
|
|
32
|
+
concat: Dict[str, Any]
|
|
33
|
+
rename: Dict[str, Any]
|
|
34
|
+
index: Dict[str, Any]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class QuerySpansRequestBody(BaseModel):
|
|
38
|
+
queries: List[SpanQuery]
|
|
39
|
+
start_time: datetime
|
|
40
|
+
end_time: Optional[datetime] = None
|
|
41
|
+
limit: int = DEFAULT_SPAN_LIMIT
|
|
42
|
+
root_spans_only: Optional[bool] = None
|
|
43
|
+
project_name: Optional[str] = Field(
|
|
44
|
+
default=None,
|
|
45
|
+
description=(
|
|
46
|
+
"The name of the project to query. "
|
|
47
|
+
"This parameter has been deprecated, use the project_name query parameter instead."
|
|
48
|
+
),
|
|
49
|
+
deprecated=True,
|
|
50
|
+
)
|
|
51
|
+
stop_time: Optional[datetime] = Field(
|
|
52
|
+
default=None,
|
|
53
|
+
description=(
|
|
54
|
+
"An upper bound on the time to query for. "
|
|
55
|
+
"This parameter has been deprecated, use the end_time parameter instead."
|
|
56
|
+
),
|
|
57
|
+
deprecated=True,
|
|
58
|
+
)
|
|
59
|
+
|
|
21
60
|
|
|
22
61
|
# TODO: Add property details to SpanQuery schema
|
|
23
|
-
|
|
24
|
-
""
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
required: true
|
|
38
|
-
content:
|
|
39
|
-
application/json:
|
|
40
|
-
schema:
|
|
41
|
-
type: object
|
|
42
|
-
properties:
|
|
43
|
-
queries:
|
|
44
|
-
type: array
|
|
45
|
-
items:
|
|
46
|
-
type: object
|
|
47
|
-
properties:
|
|
48
|
-
select:
|
|
49
|
-
type: object
|
|
50
|
-
filter:
|
|
51
|
-
type: object
|
|
52
|
-
explode:
|
|
53
|
-
type: object
|
|
54
|
-
concat:
|
|
55
|
-
type: object
|
|
56
|
-
rename:
|
|
57
|
-
type: object
|
|
58
|
-
index:
|
|
59
|
-
type: object
|
|
60
|
-
start_time:
|
|
61
|
-
type: string
|
|
62
|
-
format: date-time
|
|
63
|
-
end_time:
|
|
64
|
-
type: string
|
|
65
|
-
format: date-time
|
|
66
|
-
nullable: true
|
|
67
|
-
limit:
|
|
68
|
-
type: integer
|
|
69
|
-
nullable: true
|
|
70
|
-
default: 1000
|
|
71
|
-
root_spans_only:
|
|
72
|
-
type: boolean
|
|
73
|
-
nullable: true
|
|
74
|
-
responses:
|
|
75
|
-
200:
|
|
76
|
-
description: Success
|
|
77
|
-
403:
|
|
78
|
-
description: Forbidden
|
|
79
|
-
404:
|
|
80
|
-
description: Not found
|
|
81
|
-
422:
|
|
82
|
-
description: Request body is invalid
|
|
83
|
-
"""
|
|
84
|
-
payload = await request.json()
|
|
85
|
-
queries = payload.pop("queries", [])
|
|
62
|
+
@router.post(
|
|
63
|
+
"/spans",
|
|
64
|
+
operation_id="querySpans",
|
|
65
|
+
summary="Query spans with query DSL",
|
|
66
|
+
responses=add_errors_to_responses([HTTP_404_NOT_FOUND, HTTP_422_UNPROCESSABLE_ENTITY]),
|
|
67
|
+
)
|
|
68
|
+
async def query_spans_handler(
|
|
69
|
+
request: Request,
|
|
70
|
+
request_body: QuerySpansRequestBody,
|
|
71
|
+
project_name: Optional[str] = Query(
|
|
72
|
+
default=None, description="The project name to get evaluations from"
|
|
73
|
+
),
|
|
74
|
+
) -> Response:
|
|
75
|
+
queries = request_body.queries
|
|
86
76
|
project_name = (
|
|
87
|
-
|
|
77
|
+
project_name
|
|
88
78
|
or request.query_params.get("project-name") # for backward compatibility
|
|
89
79
|
or request.headers.get(
|
|
90
80
|
"project-name"
|
|
91
81
|
) # read from headers/payload for backward-compatibility
|
|
92
|
-
or
|
|
82
|
+
or request_body.project_name
|
|
93
83
|
or DEFAULT_PROJECT_NAME
|
|
94
84
|
)
|
|
95
|
-
end_time =
|
|
85
|
+
end_time = request_body.end_time or request_body.stop_time
|
|
96
86
|
try:
|
|
97
|
-
span_queries = [
|
|
87
|
+
span_queries = [SpanQuery_.from_dict(query.dict()) for query in queries]
|
|
98
88
|
except Exception as e:
|
|
99
|
-
|
|
89
|
+
raise HTTPException(
|
|
90
|
+
detail=f"Invalid query: {e}",
|
|
100
91
|
status_code=HTTP_422_UNPROCESSABLE_ENTITY,
|
|
101
|
-
content=f"Invalid query: {e}",
|
|
102
92
|
)
|
|
103
93
|
async with request.app.state.db() as session:
|
|
104
94
|
results = []
|
|
@@ -108,19 +98,19 @@ async def query_spans_handler(request: Request) -> Response:
|
|
|
108
98
|
query,
|
|
109
99
|
project_name=project_name,
|
|
110
100
|
start_time=normalize_datetime(
|
|
111
|
-
|
|
101
|
+
request_body.start_time,
|
|
112
102
|
timezone.utc,
|
|
113
103
|
),
|
|
114
104
|
end_time=normalize_datetime(
|
|
115
|
-
|
|
105
|
+
end_time,
|
|
116
106
|
timezone.utc,
|
|
117
107
|
),
|
|
118
|
-
limit=
|
|
119
|
-
root_spans_only=
|
|
108
|
+
limit=request_body.limit,
|
|
109
|
+
root_spans_only=request_body.root_spans_only,
|
|
120
110
|
)
|
|
121
111
|
)
|
|
122
112
|
if not results:
|
|
123
|
-
|
|
113
|
+
raise HTTPException(status_code=HTTP_404_NOT_FOUND)
|
|
124
114
|
|
|
125
115
|
async def content() -> AsyncIterator[bytes]:
|
|
126
116
|
for result in results:
|
|
@@ -132,93 +122,71 @@ async def query_spans_handler(request: Request) -> Response:
|
|
|
132
122
|
)
|
|
133
123
|
|
|
134
124
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
- annotator_kind
|
|
193
|
-
responses:
|
|
194
|
-
200:
|
|
195
|
-
description: Span annotations inserted successfully
|
|
196
|
-
content:
|
|
197
|
-
application/json:
|
|
198
|
-
schema:
|
|
199
|
-
type: object
|
|
200
|
-
properties:
|
|
201
|
-
data:
|
|
202
|
-
type: array
|
|
203
|
-
items:
|
|
204
|
-
type: object
|
|
205
|
-
properties:
|
|
206
|
-
id:
|
|
207
|
-
type: string
|
|
208
|
-
description: The ID of the inserted span annotation
|
|
209
|
-
404:
|
|
210
|
-
description: Span not found
|
|
211
|
-
"""
|
|
212
|
-
payload: List[Dict[str, Any]] = (await request.json()).get("data", [])
|
|
213
|
-
span_gids = [GlobalID.from_id(annotation["span_id"]) for annotation in payload]
|
|
125
|
+
@router.get("/spans", include_in_schema=False, deprecated=True)
|
|
126
|
+
async def get_spans_handler(
|
|
127
|
+
request: Request,
|
|
128
|
+
request_body: QuerySpansRequestBody,
|
|
129
|
+
project_name: Optional[str] = Query(
|
|
130
|
+
default=None, description="The project name to get evaluations from"
|
|
131
|
+
),
|
|
132
|
+
) -> Response:
|
|
133
|
+
return await query_spans_handler(request, request_body, project_name)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class Result(BaseModel):
|
|
137
|
+
label: Optional[str] = Field(default=None, description="The label assigned by the annotation")
|
|
138
|
+
score: Optional[float] = Field(default=None, description="The score assigned by the annotation")
|
|
139
|
+
explanation: Optional[str] = Field(
|
|
140
|
+
default=None, description="Explanation of the annotation result"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class SpanAnnotation(BaseModel):
|
|
145
|
+
span_id: str = Field(description="The ID of the span being annotated")
|
|
146
|
+
name: str = Field(description="The name of the annotation")
|
|
147
|
+
annotator_kind: Literal["LLM", "HUMAN"] = Field(
|
|
148
|
+
description="The kind of annotator used for the annotation"
|
|
149
|
+
)
|
|
150
|
+
result: Optional[Result] = Field(default=None, description="The result of the annotation")
|
|
151
|
+
metadata: Optional[Dict[str, Any]] = Field(
|
|
152
|
+
default=None, description="Metadata for the annotation"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class AnnotateSpansRequestBody(RequestBody[List[SpanAnnotation]]):
|
|
157
|
+
data: List[SpanAnnotation]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class InsertedSpanAnnotation(BaseModel):
|
|
161
|
+
id: str = Field(description="The ID of the inserted span annotation")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class AnnotateSpansResponseBody(ResponseBody[InsertedSpanAnnotation]):
|
|
165
|
+
pass
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@router.post(
|
|
169
|
+
"/span_annotations",
|
|
170
|
+
operation_id="annotateSpans",
|
|
171
|
+
summary="Create or update span annotations",
|
|
172
|
+
responses=add_errors_to_responses(
|
|
173
|
+
[{"status_code": HTTP_404_NOT_FOUND, "description": "Span not found"}]
|
|
174
|
+
),
|
|
175
|
+
response_description="Span annotations inserted successfully",
|
|
176
|
+
)
|
|
177
|
+
async def annotate_spans(
|
|
178
|
+
request: Request, request_body: AnnotateSpansRequestBody
|
|
179
|
+
) -> AnnotateSpansResponseBody:
|
|
180
|
+
span_annotations = request_body.data
|
|
181
|
+
span_gids = [GlobalID.from_id(annotation.span_id) for annotation in span_annotations]
|
|
214
182
|
|
|
215
183
|
resolved_span_ids = []
|
|
216
184
|
for span_gid in span_gids:
|
|
217
185
|
try:
|
|
218
186
|
resolved_span_ids.append(from_global_id_with_expected_type(span_gid, "Span"))
|
|
219
187
|
except ValueError:
|
|
220
|
-
|
|
221
|
-
|
|
188
|
+
raise HTTPException(
|
|
189
|
+
detail="Span with ID {span_gid} does not exist",
|
|
222
190
|
status_code=HTTP_404_NOT_FOUND,
|
|
223
191
|
)
|
|
224
192
|
|
|
@@ -233,22 +201,22 @@ async def annotate_spans(request: Request) -> Response:
|
|
|
233
201
|
missing_span_gids = [
|
|
234
202
|
str(GlobalID("Span", str(span_gid))) for span_gid in missing_span_ids
|
|
235
203
|
]
|
|
236
|
-
|
|
237
|
-
|
|
204
|
+
raise HTTPException(
|
|
205
|
+
detail=f"Spans with IDs {', '.join(missing_span_gids)} do not exist.",
|
|
238
206
|
status_code=HTTP_404_NOT_FOUND,
|
|
239
207
|
)
|
|
240
208
|
|
|
241
209
|
inserted_annotations = []
|
|
242
|
-
for annotation in
|
|
243
|
-
span_gid = GlobalID.from_id(annotation
|
|
210
|
+
for annotation in span_annotations:
|
|
211
|
+
span_gid = GlobalID.from_id(annotation.span_id)
|
|
244
212
|
span_id = from_global_id_with_expected_type(span_gid, "Span")
|
|
245
|
-
name = annotation
|
|
246
|
-
annotator_kind = annotation
|
|
247
|
-
result = annotation.
|
|
248
|
-
label = result.
|
|
249
|
-
score = result.
|
|
250
|
-
explanation = result.
|
|
251
|
-
metadata = annotation.
|
|
213
|
+
name = annotation.name
|
|
214
|
+
annotator_kind = annotation.annotator_kind
|
|
215
|
+
result = annotation.result
|
|
216
|
+
label = result.label if result else None
|
|
217
|
+
score = result.score if result else None
|
|
218
|
+
explanation = result.explanation if result else None
|
|
219
|
+
metadata = annotation.metadata or {}
|
|
252
220
|
|
|
253
221
|
values = dict(
|
|
254
222
|
span_rowid=span_id,
|
|
@@ -269,7 +237,7 @@ async def annotate_spans(request: Request) -> Response:
|
|
|
269
237
|
).returning(models.SpanAnnotation.id)
|
|
270
238
|
)
|
|
271
239
|
inserted_annotations.append(
|
|
272
|
-
|
|
240
|
+
InsertedSpanAnnotation(id=str(GlobalID("SpanAnnotation", str(span_annotation_id))))
|
|
273
241
|
)
|
|
274
242
|
|
|
275
|
-
return
|
|
243
|
+
return AnnotateSpansResponseBody(data=inserted_annotations)
|