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