arize-phoenix 4.15.0__py3-none-any.whl → 4.16.0__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.15.0.dist-info → arize_phoenix-4.16.0.dist-info}/METADATA +2 -1
- {arize_phoenix-4.15.0.dist-info → arize_phoenix-4.16.0.dist-info}/RECORD +29 -22
- phoenix/db/bulk_inserter.py +129 -2
- phoenix/db/helpers.py +23 -1
- phoenix/db/insertion/constants.py +2 -0
- phoenix/db/insertion/document_annotation.py +157 -0
- phoenix/db/insertion/helpers.py +13 -0
- phoenix/db/insertion/span_annotation.py +144 -0
- phoenix/db/insertion/trace_annotation.py +144 -0
- phoenix/db/insertion/types.py +261 -0
- phoenix/experiments/types.py +3 -3
- phoenix/server/api/input_types/SpanAnnotationSort.py +17 -0
- phoenix/server/api/input_types/TraceAnnotationSort.py +17 -0
- phoenix/server/api/routers/v1/evaluations.py +90 -4
- phoenix/server/api/routers/v1/spans.py +36 -46
- phoenix/server/api/routers/v1/traces.py +36 -48
- phoenix/server/api/types/Span.py +22 -3
- phoenix/server/api/types/Trace.py +21 -4
- phoenix/server/app.py +2 -0
- phoenix/server/static/.vite/manifest.json +14 -14
- phoenix/server/static/assets/{components-kGgeFkHp.js → components-Ci5kMOk5.js} +119 -126
- phoenix/server/static/assets/{index-BctFO6S7.js → index-BQG5WVX7.js} +2 -2
- phoenix/server/static/assets/{pages-DabDCmVd.js → pages-BrevprVW.js} +289 -213
- phoenix/server/static/assets/{vendor-arizeai-B5Hti8OB.js → vendor-arizeai-DTbiPGp6.js} +1 -1
- phoenix/trace/dsl/filter.py +2 -6
- phoenix/version.py +1 -1
- {arize_phoenix-4.15.0.dist-info → arize_phoenix-4.16.0.dist-info}/WHEEL +0 -0
- {arize_phoenix-4.15.0.dist-info → arize_phoenix-4.16.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-4.15.0.dist-info → arize_phoenix-4.16.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import gzip
|
|
2
2
|
from itertools import chain
|
|
3
|
-
from typing import Iterator, Optional, Tuple
|
|
3
|
+
from typing import Any, Callable, Iterator, Optional, Tuple, Union, cast
|
|
4
4
|
|
|
5
5
|
import pandas as pd
|
|
6
6
|
import pyarrow as pa
|
|
@@ -24,10 +24,10 @@ from typing_extensions import TypeAlias
|
|
|
24
24
|
import phoenix.trace.v1 as pb
|
|
25
25
|
from phoenix.config import DEFAULT_PROJECT_NAME
|
|
26
26
|
from phoenix.db import models
|
|
27
|
+
from phoenix.db.insertion.types import Precursors
|
|
27
28
|
from phoenix.exceptions import PhoenixEvaluationNameIsMissing
|
|
28
29
|
from phoenix.server.api.routers.utils import table_to_bytes
|
|
29
30
|
from phoenix.server.types import DbSessionFactory
|
|
30
|
-
from phoenix.session.evaluation import encode_evaluations
|
|
31
31
|
from phoenix.trace.span_evaluations import (
|
|
32
32
|
DocumentEvaluations,
|
|
33
33
|
Evaluations,
|
|
@@ -194,8 +194,94 @@ async def _process_pyarrow(request: Request) -> Response:
|
|
|
194
194
|
|
|
195
195
|
|
|
196
196
|
async def _add_evaluations(state: State, evaluations: Evaluations) -> None:
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
dataframe = evaluations.dataframe
|
|
198
|
+
eval_name = evaluations.eval_name
|
|
199
|
+
names = dataframe.index.names
|
|
200
|
+
if (
|
|
201
|
+
len(names) == 2
|
|
202
|
+
and "document_position" in names
|
|
203
|
+
and ("context.span_id" in names or "span_id" in names)
|
|
204
|
+
):
|
|
205
|
+
cls = _document_annotation_factory(
|
|
206
|
+
names.index("span_id") if "span_id" in names else names.index("context.span_id"),
|
|
207
|
+
names.index("document_position"),
|
|
208
|
+
)
|
|
209
|
+
for index, row in dataframe.iterrows():
|
|
210
|
+
score, label, explanation = _get_annotation_result(row)
|
|
211
|
+
document_annotation = cls(cast(Union[Tuple[str, int], Tuple[int, str]], index))(
|
|
212
|
+
name=eval_name,
|
|
213
|
+
annotator_kind="LLM",
|
|
214
|
+
score=score,
|
|
215
|
+
label=label,
|
|
216
|
+
explanation=explanation,
|
|
217
|
+
metadata_={},
|
|
218
|
+
)
|
|
219
|
+
await state.enqueue(document_annotation)
|
|
220
|
+
elif len(names) == 1 and names[0] in ("context.span_id", "span_id"):
|
|
221
|
+
for index, row in dataframe.iterrows():
|
|
222
|
+
score, label, explanation = _get_annotation_result(row)
|
|
223
|
+
span_annotation = _span_annotation_factory(cast(str, index))(
|
|
224
|
+
name=eval_name,
|
|
225
|
+
annotator_kind="LLM",
|
|
226
|
+
score=score,
|
|
227
|
+
label=label,
|
|
228
|
+
explanation=explanation,
|
|
229
|
+
metadata_={},
|
|
230
|
+
)
|
|
231
|
+
await state.enqueue(span_annotation)
|
|
232
|
+
elif len(names) == 1 and names[0] in ("context.trace_id", "trace_id"):
|
|
233
|
+
for index, row in dataframe.iterrows():
|
|
234
|
+
score, label, explanation = _get_annotation_result(row)
|
|
235
|
+
trace_annotation = _trace_annotation_factory(cast(str, index))(
|
|
236
|
+
name=eval_name,
|
|
237
|
+
annotator_kind="LLM",
|
|
238
|
+
score=score,
|
|
239
|
+
label=label,
|
|
240
|
+
explanation=explanation,
|
|
241
|
+
metadata_={},
|
|
242
|
+
)
|
|
243
|
+
await state.enqueue(trace_annotation)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _get_annotation_result(
|
|
247
|
+
row: "pd.Series[Any]",
|
|
248
|
+
) -> Tuple[Optional[float], Optional[str], Optional[str]]:
|
|
249
|
+
return (
|
|
250
|
+
cast(Optional[float], row.get("score")),
|
|
251
|
+
cast(Optional[str], row.get("label")),
|
|
252
|
+
cast(Optional[str], row.get("explanation")),
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _document_annotation_factory(
|
|
257
|
+
span_id_idx: int,
|
|
258
|
+
document_position_idx: int,
|
|
259
|
+
) -> Callable[
|
|
260
|
+
[Union[Tuple[str, int], Tuple[int, str]]],
|
|
261
|
+
Callable[..., Precursors.DocumentAnnotation],
|
|
262
|
+
]:
|
|
263
|
+
return lambda index: lambda **kwargs: Precursors.DocumentAnnotation(
|
|
264
|
+
span_id=str(index[span_id_idx]),
|
|
265
|
+
document_position=int(index[document_position_idx]),
|
|
266
|
+
obj=models.DocumentAnnotation(
|
|
267
|
+
document_position=int(index[document_position_idx]),
|
|
268
|
+
**kwargs,
|
|
269
|
+
),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _span_annotation_factory(span_id: str) -> Callable[..., Precursors.SpanAnnotation]:
|
|
274
|
+
return lambda **kwargs: Precursors.SpanAnnotation(
|
|
275
|
+
span_id=str(span_id),
|
|
276
|
+
obj=models.SpanAnnotation(**kwargs),
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _trace_annotation_factory(trace_id: str) -> Callable[..., Precursors.TraceAnnotation]:
|
|
281
|
+
return lambda **kwargs: Precursors.TraceAnnotation(
|
|
282
|
+
trace_id=str(trace_id),
|
|
283
|
+
obj=models.TraceAnnotation(**kwargs),
|
|
284
|
+
)
|
|
199
285
|
|
|
200
286
|
|
|
201
287
|
def _read_sql_trace_evaluations_into_dataframe(
|
|
@@ -13,9 +13,9 @@ from phoenix.config import DEFAULT_PROJECT_NAME
|
|
|
13
13
|
from phoenix.datetime_utils import normalize_datetime
|
|
14
14
|
from phoenix.db import models
|
|
15
15
|
from phoenix.db.helpers import SupportedSQLDialect
|
|
16
|
-
from phoenix.db.insertion.helpers import insert_on_conflict
|
|
16
|
+
from phoenix.db.insertion.helpers import as_kv, insert_on_conflict
|
|
17
|
+
from phoenix.db.insertion.types import Precursors
|
|
17
18
|
from phoenix.server.api.routers.utils import df_to_bytes
|
|
18
|
-
from phoenix.server.api.types.node import from_global_id_with_expected_type
|
|
19
19
|
from phoenix.trace.dsl import SpanQuery as SpanQuery_
|
|
20
20
|
|
|
21
21
|
from .pydantic_compat import V1RoutesBaseModel
|
|
@@ -143,7 +143,7 @@ class SpanAnnotationResult(V1RoutesBaseModel):
|
|
|
143
143
|
|
|
144
144
|
|
|
145
145
|
class SpanAnnotation(V1RoutesBaseModel):
|
|
146
|
-
span_id: str = Field(description="
|
|
146
|
+
span_id: str = Field(description="OpenTelemetry Span ID (hex format w/o 0x prefix)")
|
|
147
147
|
name: str = Field(description="The name of the annotation")
|
|
148
148
|
annotator_kind: Literal["LLM", "HUMAN"] = Field(
|
|
149
149
|
description="The kind of annotator used for the annotation"
|
|
@@ -155,6 +155,19 @@ class SpanAnnotation(V1RoutesBaseModel):
|
|
|
155
155
|
default=None, description="Metadata for the annotation"
|
|
156
156
|
)
|
|
157
157
|
|
|
158
|
+
def as_precursor(self) -> Precursors.SpanAnnotation:
|
|
159
|
+
return Precursors.SpanAnnotation(
|
|
160
|
+
self.span_id,
|
|
161
|
+
models.SpanAnnotation(
|
|
162
|
+
name=self.name,
|
|
163
|
+
annotator_kind=self.annotator_kind,
|
|
164
|
+
score=self.result.score if self.result else None,
|
|
165
|
+
label=self.result.label if self.result else None,
|
|
166
|
+
explanation=self.result.explanation if self.result else None,
|
|
167
|
+
metadata_=self.metadata or {},
|
|
168
|
+
),
|
|
169
|
+
)
|
|
170
|
+
|
|
158
171
|
|
|
159
172
|
class AnnotateSpansRequestBody(RequestBody[List[SpanAnnotation]]):
|
|
160
173
|
data: List[SpanAnnotation]
|
|
@@ -178,59 +191,36 @@ class AnnotateSpansResponseBody(ResponseBody[List[InsertedSpanAnnotation]]):
|
|
|
178
191
|
response_description="Span annotations inserted successfully",
|
|
179
192
|
)
|
|
180
193
|
async def annotate_spans(
|
|
181
|
-
request: Request,
|
|
194
|
+
request: Request,
|
|
195
|
+
request_body: AnnotateSpansRequestBody,
|
|
196
|
+
sync: bool = Query(default=True, description="If true, fulfill request synchronously."),
|
|
182
197
|
) -> AnnotateSpansResponseBody:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
for span_gid in span_gids:
|
|
188
|
-
try:
|
|
189
|
-
resolved_span_ids.append(from_global_id_with_expected_type(span_gid, "Span"))
|
|
190
|
-
except ValueError:
|
|
191
|
-
raise HTTPException(
|
|
192
|
-
detail="Span with ID {span_gid} does not exist",
|
|
193
|
-
status_code=HTTP_404_NOT_FOUND,
|
|
194
|
-
)
|
|
198
|
+
precursors = [d.as_precursor() for d in request_body.data]
|
|
199
|
+
if not sync:
|
|
200
|
+
await request.state.enqueue(*precursors)
|
|
201
|
+
return AnnotateSpansResponseBody(data=[])
|
|
195
202
|
|
|
203
|
+
span_ids = {p.span_id for p in precursors}
|
|
196
204
|
async with request.app.state.db() as session:
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
205
|
+
existing_spans = {
|
|
206
|
+
span.span_id: span.id
|
|
207
|
+
async for span in await session.stream_scalars(
|
|
208
|
+
select(models.Span).filter(models.Span.span_id.in_(span_ids))
|
|
209
|
+
)
|
|
210
|
+
}
|
|
201
211
|
|
|
202
|
-
missing_span_ids = set(
|
|
212
|
+
missing_span_ids = span_ids - set(existing_spans.keys())
|
|
203
213
|
if missing_span_ids:
|
|
204
|
-
missing_span_gids = [
|
|
205
|
-
str(GlobalID("Span", str(span_gid))) for span_gid in missing_span_ids
|
|
206
|
-
]
|
|
207
214
|
raise HTTPException(
|
|
208
|
-
detail=f"Spans with IDs {', '.join(
|
|
215
|
+
detail=f"Spans with IDs {', '.join(missing_span_ids)} do not exist.",
|
|
209
216
|
status_code=HTTP_404_NOT_FOUND,
|
|
210
217
|
)
|
|
211
218
|
|
|
212
219
|
inserted_annotations = []
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
annotator_kind = annotation.annotator_kind
|
|
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 {}
|
|
223
|
-
|
|
224
|
-
values = dict(
|
|
225
|
-
span_rowid=span_id,
|
|
226
|
-
name=name,
|
|
227
|
-
label=label,
|
|
228
|
-
score=score,
|
|
229
|
-
explanation=explanation,
|
|
230
|
-
annotator_kind=annotator_kind,
|
|
231
|
-
metadata_=metadata,
|
|
232
|
-
)
|
|
233
|
-
dialect = SupportedSQLDialect(session.bind.dialect.name)
|
|
220
|
+
|
|
221
|
+
dialect = SupportedSQLDialect(session.bind.dialect.name)
|
|
222
|
+
for p in precursors:
|
|
223
|
+
values = dict(as_kv(p.as_insertable(existing_spans[p.span_id]).row))
|
|
234
224
|
span_annotation_id = await session.scalar(
|
|
235
225
|
insert_on_conflict(
|
|
236
226
|
values,
|
|
@@ -2,7 +2,7 @@ import gzip
|
|
|
2
2
|
import zlib
|
|
3
3
|
from typing import Any, Dict, List, Literal, Optional
|
|
4
4
|
|
|
5
|
-
from fastapi import APIRouter, BackgroundTasks, Header, HTTPException
|
|
5
|
+
from fastapi import APIRouter, BackgroundTasks, Header, HTTPException, Query
|
|
6
6
|
from google.protobuf.message import DecodeError
|
|
7
7
|
from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import (
|
|
8
8
|
ExportTraceServiceRequest,
|
|
@@ -22,8 +22,8 @@ from strawberry.relay import GlobalID
|
|
|
22
22
|
|
|
23
23
|
from phoenix.db import models
|
|
24
24
|
from phoenix.db.helpers import SupportedSQLDialect
|
|
25
|
-
from phoenix.db.insertion.helpers import insert_on_conflict
|
|
26
|
-
from phoenix.
|
|
25
|
+
from phoenix.db.insertion.helpers import as_kv, insert_on_conflict
|
|
26
|
+
from phoenix.db.insertion.types import Precursors
|
|
27
27
|
from phoenix.trace.otel import decode_otlp_span
|
|
28
28
|
from phoenix.utilities.project import get_project_name
|
|
29
29
|
|
|
@@ -100,7 +100,7 @@ class TraceAnnotationResult(V1RoutesBaseModel):
|
|
|
100
100
|
|
|
101
101
|
|
|
102
102
|
class TraceAnnotation(V1RoutesBaseModel):
|
|
103
|
-
trace_id: str = Field(description="
|
|
103
|
+
trace_id: str = Field(description="OpenTelemetry Trace ID (hex format w/o 0x prefix)")
|
|
104
104
|
name: str = Field(description="The name of the annotation")
|
|
105
105
|
annotator_kind: Literal["LLM", "HUMAN"] = Field(
|
|
106
106
|
description="The kind of annotator used for the annotation"
|
|
@@ -112,6 +112,19 @@ class TraceAnnotation(V1RoutesBaseModel):
|
|
|
112
112
|
default=None, description="Metadata for the annotation"
|
|
113
113
|
)
|
|
114
114
|
|
|
115
|
+
def as_precursor(self) -> Precursors.TraceAnnotation:
|
|
116
|
+
return Precursors.TraceAnnotation(
|
|
117
|
+
self.trace_id,
|
|
118
|
+
models.TraceAnnotation(
|
|
119
|
+
name=self.name,
|
|
120
|
+
annotator_kind=self.annotator_kind,
|
|
121
|
+
score=self.result.score if self.result else None,
|
|
122
|
+
label=self.result.label if self.result else None,
|
|
123
|
+
explanation=self.result.explanation if self.result else None,
|
|
124
|
+
metadata_=self.metadata or {},
|
|
125
|
+
),
|
|
126
|
+
)
|
|
127
|
+
|
|
115
128
|
|
|
116
129
|
class AnnotateTracesRequestBody(RequestBody[List[TraceAnnotation]]):
|
|
117
130
|
data: List[TraceAnnotation] = Field(description="The trace annotations to be upserted")
|
|
@@ -134,61 +147,36 @@ class AnnotateTracesResponseBody(ResponseBody[List[InsertedTraceAnnotation]]):
|
|
|
134
147
|
),
|
|
135
148
|
)
|
|
136
149
|
async def annotate_traces(
|
|
137
|
-
request: Request,
|
|
150
|
+
request: Request,
|
|
151
|
+
request_body: AnnotateTracesRequestBody,
|
|
152
|
+
sync: bool = Query(default=True, description="If true, fulfill request synchronously."),
|
|
138
153
|
) -> AnnotateTracesResponseBody:
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
for trace_gid in trace_gids:
|
|
144
|
-
try:
|
|
145
|
-
resolved_trace_ids.append(from_global_id_with_expected_type(trace_gid, "Trace"))
|
|
146
|
-
except ValueError:
|
|
147
|
-
raise HTTPException(
|
|
148
|
-
detail="Trace with ID {trace_gid} does not exist",
|
|
149
|
-
status_code=HTTP_404_NOT_FOUND,
|
|
150
|
-
)
|
|
154
|
+
precursors = [d.as_precursor() for d in request_body.data]
|
|
155
|
+
if not sync:
|
|
156
|
+
await request.state.enqueue(*precursors)
|
|
157
|
+
return AnnotateTracesResponseBody(data=[])
|
|
151
158
|
|
|
159
|
+
trace_ids = {p.trace_id for p in precursors}
|
|
152
160
|
async with request.app.state.db() as session:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
161
|
+
existing_traces = {
|
|
162
|
+
trace.trace_id: trace.id
|
|
163
|
+
async for trace in await session.stream_scalars(
|
|
164
|
+
select(models.Trace).filter(models.Trace.trace_id.in_(trace_ids))
|
|
165
|
+
)
|
|
166
|
+
}
|
|
157
167
|
|
|
158
|
-
missing_trace_ids = set(
|
|
168
|
+
missing_trace_ids = trace_ids - set(existing_traces.keys())
|
|
159
169
|
if missing_trace_ids:
|
|
160
|
-
missing_trace_gids = [
|
|
161
|
-
str(GlobalID("Trace", str(trace_gid))) for trace_gid in missing_trace_ids
|
|
162
|
-
]
|
|
163
170
|
raise HTTPException(
|
|
164
|
-
detail=f"Traces with IDs {', '.join(
|
|
171
|
+
detail=f"Traces with IDs {', '.join(missing_trace_ids)} do not exist.",
|
|
165
172
|
status_code=HTTP_404_NOT_FOUND,
|
|
166
173
|
)
|
|
167
174
|
|
|
168
175
|
inserted_annotations = []
|
|
169
176
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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 {}
|
|
181
|
-
|
|
182
|
-
values = dict(
|
|
183
|
-
trace_rowid=trace_id,
|
|
184
|
-
name=name,
|
|
185
|
-
label=label,
|
|
186
|
-
score=score,
|
|
187
|
-
explanation=explanation,
|
|
188
|
-
annotator_kind=annotator_kind,
|
|
189
|
-
metadata_=metadata,
|
|
190
|
-
)
|
|
191
|
-
dialect = SupportedSQLDialect(session.bind.dialect.name)
|
|
177
|
+
dialect = SupportedSQLDialect(session.bind.dialect.name)
|
|
178
|
+
for p in precursors:
|
|
179
|
+
values = dict(as_kv(p.as_insertable(existing_traces[p.trace_id]).row))
|
|
192
180
|
trace_annotation_id = await session.scalar(
|
|
193
181
|
insert_on_conflict(
|
|
194
182
|
values,
|
phoenix/server/api/types/Span.py
CHANGED
|
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Any, List, Mapping, Optional, Sized, cast
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
import strawberry
|
|
9
9
|
from openinference.semconv.trace import EmbeddingAttributes, SpanAttributes
|
|
10
|
+
from sqlalchemy import select
|
|
10
11
|
from strawberry import ID, UNSET
|
|
11
12
|
from strawberry.relay import Node, NodeID
|
|
12
13
|
from strawberry.types import Info
|
|
@@ -19,6 +20,9 @@ from phoenix.server.api.helpers.dataset_helpers import (
|
|
|
19
20
|
get_dataset_example_input,
|
|
20
21
|
get_dataset_example_output,
|
|
21
22
|
)
|
|
23
|
+
from phoenix.server.api.input_types.SpanAnnotationSort import SpanAnnotationSort
|
|
24
|
+
from phoenix.server.api.types.SortDir import SortDir
|
|
25
|
+
from phoenix.server.api.types.SpanAnnotation import to_gql_span_annotation
|
|
22
26
|
from phoenix.trace.attributes import get_attribute_value
|
|
23
27
|
|
|
24
28
|
from .DocumentRetrievalMetrics import DocumentRetrievalMetrics
|
|
@@ -177,12 +181,27 @@ class Span(Node):
|
|
|
177
181
|
|
|
178
182
|
@strawberry.field(
|
|
179
183
|
description=(
|
|
180
|
-
"Annotations
|
|
184
|
+
"Annotations associated with the span. This encompasses both "
|
|
181
185
|
"LLM and human annotations."
|
|
182
186
|
)
|
|
183
187
|
) # type: ignore
|
|
184
|
-
async def span_annotations(
|
|
185
|
-
|
|
188
|
+
async def span_annotations(
|
|
189
|
+
self,
|
|
190
|
+
info: Info[Context, None],
|
|
191
|
+
sort: Optional[SpanAnnotationSort] = UNSET,
|
|
192
|
+
) -> List[SpanAnnotation]:
|
|
193
|
+
async with info.context.db() as session:
|
|
194
|
+
stmt = select(models.SpanAnnotation).filter_by(span_rowid=self.id_attr)
|
|
195
|
+
if sort:
|
|
196
|
+
sort_col = getattr(models.SpanAnnotation, sort.col.value)
|
|
197
|
+
if sort.dir is SortDir.desc:
|
|
198
|
+
stmt = stmt.order_by(sort_col.desc(), models.SpanAnnotation.id.desc())
|
|
199
|
+
else:
|
|
200
|
+
stmt = stmt.order_by(sort_col.asc(), models.SpanAnnotation.id.asc())
|
|
201
|
+
else:
|
|
202
|
+
stmt = stmt.order_by(models.SpanAnnotation.created_at.desc())
|
|
203
|
+
annotations = await session.scalars(stmt)
|
|
204
|
+
return [to_gql_span_annotation(annotation) for annotation in annotations]
|
|
186
205
|
|
|
187
206
|
@strawberry.field(
|
|
188
207
|
description="Evaluations of the documents associated with the span, e.g. "
|
|
@@ -11,13 +11,15 @@ from strawberry.types import Info
|
|
|
11
11
|
|
|
12
12
|
from phoenix.db import models
|
|
13
13
|
from phoenix.server.api.context import Context
|
|
14
|
-
from phoenix.server.api.
|
|
14
|
+
from phoenix.server.api.input_types.TraceAnnotationSort import TraceAnnotationSort
|
|
15
15
|
from phoenix.server.api.types.pagination import (
|
|
16
16
|
ConnectionArgs,
|
|
17
17
|
CursorString,
|
|
18
18
|
connection_from_list,
|
|
19
19
|
)
|
|
20
|
+
from phoenix.server.api.types.SortDir import SortDir
|
|
20
21
|
from phoenix.server.api.types.Span import Span, to_gql_span
|
|
22
|
+
from phoenix.server.api.types.TraceAnnotation import TraceAnnotation, to_gql_trace_annotation
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
@strawberry.type
|
|
@@ -62,6 +64,21 @@ class Trace(Node):
|
|
|
62
64
|
data = [to_gql_span(span) async for span in spans]
|
|
63
65
|
return connection_from_list(data=data, args=args)
|
|
64
66
|
|
|
65
|
-
@strawberry.field(description="
|
|
66
|
-
async def
|
|
67
|
-
|
|
67
|
+
@strawberry.field(description="Annotations associated with the trace.") # type: ignore
|
|
68
|
+
async def span_annotations(
|
|
69
|
+
self,
|
|
70
|
+
info: Info[Context, None],
|
|
71
|
+
sort: Optional[TraceAnnotationSort] = None,
|
|
72
|
+
) -> List[TraceAnnotation]:
|
|
73
|
+
async with info.context.db() as session:
|
|
74
|
+
stmt = select(models.TraceAnnotation).filter_by(span_rowid=self.id_attr)
|
|
75
|
+
if sort:
|
|
76
|
+
sort_col = getattr(models.TraceAnnotation, sort.col.value)
|
|
77
|
+
if sort.dir is SortDir.desc:
|
|
78
|
+
stmt = stmt.order_by(sort_col.desc(), models.TraceAnnotation.id.desc())
|
|
79
|
+
else:
|
|
80
|
+
stmt = stmt.order_by(sort_col.asc(), models.TraceAnnotation.id.asc())
|
|
81
|
+
else:
|
|
82
|
+
stmt = stmt.order_by(models.TraceAnnotation.created_at.desc())
|
|
83
|
+
annotations = await session.scalars(stmt)
|
|
84
|
+
return [to_gql_trace_annotation(annotation) for annotation in annotations]
|
phoenix/server/app.py
CHANGED
|
@@ -229,6 +229,7 @@ def _lifespan(
|
|
|
229
229
|
global DB_MUTEX
|
|
230
230
|
DB_MUTEX = asyncio.Lock() if dialect is SupportedSQLDialect.SQLITE else None
|
|
231
231
|
async with bulk_inserter as (
|
|
232
|
+
enqueue,
|
|
232
233
|
queue_span,
|
|
233
234
|
queue_evaluation,
|
|
234
235
|
enqueue_operation,
|
|
@@ -239,6 +240,7 @@ def _lifespan(
|
|
|
239
240
|
enable_prometheus=enable_prometheus,
|
|
240
241
|
):
|
|
241
242
|
yield {
|
|
243
|
+
"enqueue": enqueue,
|
|
242
244
|
"queue_span_for_bulk_insert": queue_span,
|
|
243
245
|
"queue_evaluation_for_bulk_insert": queue_evaluation,
|
|
244
246
|
"enqueue_operation": enqueue_operation,
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_components-
|
|
3
|
-
"file": "assets/components-
|
|
2
|
+
"_components-Ci5kMOk5.js": {
|
|
3
|
+
"file": "assets/components-Ci5kMOk5.js",
|
|
4
4
|
"name": "components",
|
|
5
5
|
"imports": [
|
|
6
6
|
"_vendor-CP0b0YG0.js",
|
|
7
|
-
"_vendor-arizeai-
|
|
8
|
-
"_pages-
|
|
7
|
+
"_vendor-arizeai-DTbiPGp6.js",
|
|
8
|
+
"_pages-BrevprVW.js",
|
|
9
9
|
"_vendor-three-DwGkEfCM.js",
|
|
10
10
|
"_vendor-codemirror-DtdPDzrv.js"
|
|
11
11
|
]
|
|
12
12
|
},
|
|
13
|
-
"_pages-
|
|
14
|
-
"file": "assets/pages-
|
|
13
|
+
"_pages-BrevprVW.js": {
|
|
14
|
+
"file": "assets/pages-BrevprVW.js",
|
|
15
15
|
"name": "pages",
|
|
16
16
|
"imports": [
|
|
17
17
|
"_vendor-CP0b0YG0.js",
|
|
18
|
-
"_components-
|
|
19
|
-
"_vendor-arizeai-
|
|
18
|
+
"_components-Ci5kMOk5.js",
|
|
19
|
+
"_vendor-arizeai-DTbiPGp6.js",
|
|
20
20
|
"_vendor-recharts-A0DA1O99.js",
|
|
21
21
|
"_vendor-codemirror-DtdPDzrv.js"
|
|
22
22
|
]
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"assets/vendor-DxkFTwjz.css"
|
|
36
36
|
]
|
|
37
37
|
},
|
|
38
|
-
"_vendor-arizeai-
|
|
39
|
-
"file": "assets/vendor-arizeai-
|
|
38
|
+
"_vendor-arizeai-DTbiPGp6.js": {
|
|
39
|
+
"file": "assets/vendor-arizeai-DTbiPGp6.js",
|
|
40
40
|
"name": "vendor-arizeai",
|
|
41
41
|
"imports": [
|
|
42
42
|
"_vendor-CP0b0YG0.js"
|
|
@@ -61,15 +61,15 @@
|
|
|
61
61
|
"name": "vendor-three"
|
|
62
62
|
},
|
|
63
63
|
"index.tsx": {
|
|
64
|
-
"file": "assets/index-
|
|
64
|
+
"file": "assets/index-BQG5WVX7.js",
|
|
65
65
|
"name": "index",
|
|
66
66
|
"src": "index.tsx",
|
|
67
67
|
"isEntry": true,
|
|
68
68
|
"imports": [
|
|
69
69
|
"_vendor-CP0b0YG0.js",
|
|
70
|
-
"_vendor-arizeai-
|
|
71
|
-
"_pages-
|
|
72
|
-
"_components-
|
|
70
|
+
"_vendor-arizeai-DTbiPGp6.js",
|
|
71
|
+
"_pages-BrevprVW.js",
|
|
72
|
+
"_components-Ci5kMOk5.js",
|
|
73
73
|
"_vendor-three-DwGkEfCM.js",
|
|
74
74
|
"_vendor-recharts-A0DA1O99.js",
|
|
75
75
|
"_vendor-codemirror-DtdPDzrv.js"
|