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,18 +1,19 @@
|
|
|
1
1
|
import gzip
|
|
2
2
|
import zlib
|
|
3
|
-
from typing import Any, Dict, List
|
|
3
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
4
4
|
|
|
5
|
+
from fastapi import APIRouter, BackgroundTasks, Header, HTTPException
|
|
5
6
|
from google.protobuf.message import DecodeError
|
|
6
7
|
from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import (
|
|
7
8
|
ExportTraceServiceRequest,
|
|
8
9
|
)
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
9
11
|
from sqlalchemy import select
|
|
10
|
-
from starlette.background import BackgroundTask
|
|
11
12
|
from starlette.concurrency import run_in_threadpool
|
|
12
13
|
from starlette.datastructures import State
|
|
13
14
|
from starlette.requests import Request
|
|
14
|
-
from starlette.responses import JSONResponse, Response
|
|
15
15
|
from starlette.status import (
|
|
16
|
+
HTTP_204_NO_CONTENT,
|
|
16
17
|
HTTP_404_NOT_FOUND,
|
|
17
18
|
HTTP_415_UNSUPPORTED_MEDIA_TYPE,
|
|
18
19
|
HTTP_422_UNPROCESSABLE_ENTITY,
|
|
@@ -26,40 +27,49 @@ from phoenix.server.api.types.node import from_global_id_with_expected_type
|
|
|
26
27
|
from phoenix.trace.otel import decode_otlp_span
|
|
27
28
|
from phoenix.utilities.project import get_project_name
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
30
|
+
from .utils import RequestBody, ResponseBody, add_errors_to_responses
|
|
31
|
+
|
|
32
|
+
router = APIRouter(tags=["traces"], include_in_schema=False)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@router.post(
|
|
36
|
+
"/traces",
|
|
37
|
+
operation_id="addTraces",
|
|
38
|
+
summary="Send traces",
|
|
39
|
+
status_code=HTTP_204_NO_CONTENT,
|
|
40
|
+
responses=add_errors_to_responses(
|
|
41
|
+
[
|
|
42
|
+
{
|
|
43
|
+
"status_code": HTTP_415_UNSUPPORTED_MEDIA_TYPE,
|
|
44
|
+
"description": (
|
|
45
|
+
"Unsupported content type (only `application/x-protobuf` is supported)"
|
|
46
|
+
),
|
|
47
|
+
},
|
|
48
|
+
{"status_code": HTTP_422_UNPROCESSABLE_ENTITY, "description": "Invalid request body"},
|
|
49
|
+
]
|
|
50
|
+
),
|
|
51
|
+
openapi_extra={
|
|
52
|
+
"requestBody": {
|
|
53
|
+
"required": True,
|
|
54
|
+
"content": {
|
|
55
|
+
"application/x-protobuf": {"schema": {"type": "string", "format": "binary"}}
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
)
|
|
60
|
+
async def post_traces(
|
|
61
|
+
request: Request,
|
|
62
|
+
content_type: Optional[str] = Header(default=None),
|
|
63
|
+
content_encoding: Optional[str] = Header(default=None),
|
|
64
|
+
) -> None:
|
|
54
65
|
if content_type != "application/x-protobuf":
|
|
55
|
-
|
|
56
|
-
|
|
66
|
+
raise HTTPException(
|
|
67
|
+
detail=f"Unsupported content type: {content_type}",
|
|
57
68
|
status_code=HTTP_415_UNSUPPORTED_MEDIA_TYPE,
|
|
58
69
|
)
|
|
59
|
-
content_encoding = request.headers.get("content-encoding")
|
|
60
70
|
if content_encoding and content_encoding not in ("gzip", "deflate"):
|
|
61
|
-
|
|
62
|
-
|
|
71
|
+
raise HTTPException(
|
|
72
|
+
detail=f"Unsupported content encoding: {content_encoding}",
|
|
63
73
|
status_code=HTTP_415_UNSUPPORTED_MEDIA_TYPE,
|
|
64
74
|
)
|
|
65
75
|
body = await request.body()
|
|
@@ -71,96 +81,69 @@ async def post_traces(request: Request) -> Response:
|
|
|
71
81
|
try:
|
|
72
82
|
await run_in_threadpool(req.ParseFromString, body)
|
|
73
83
|
except DecodeError:
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
raise HTTPException(
|
|
85
|
+
detail="Request body is invalid ExportTraceServiceRequest",
|
|
76
86
|
status_code=HTTP_422_UNPROCESSABLE_ENTITY,
|
|
77
87
|
)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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]
|
|
88
|
+
BackgroundTasks().add_task(_add_spans, req, request.state)
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class AnnotationResult(BaseModel):
|
|
93
|
+
label: Optional[str] = Field(default=None, description="The label assigned by the annotation")
|
|
94
|
+
score: Optional[float] = Field(default=None, description="The score assigned by the annotation")
|
|
95
|
+
explanation: Optional[str] = Field(
|
|
96
|
+
default=None, description="Explanation of the annotation result"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class TraceAnnotation(BaseModel):
|
|
101
|
+
trace_id: str = Field(description="The ID of the trace being annotated")
|
|
102
|
+
name: str = Field(description="The name of the annotation")
|
|
103
|
+
annotator_kind: Literal["LLM", "HUMAN"] = Field(
|
|
104
|
+
description="The kind of annotator used for the annotation"
|
|
105
|
+
)
|
|
106
|
+
result: Optional[AnnotationResult] = Field(
|
|
107
|
+
default=None, description="The result of the annotation"
|
|
108
|
+
)
|
|
109
|
+
metadata: Optional[Dict[str, Any]] = Field(
|
|
110
|
+
default=None, description="Metadata for the annotation"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class AnnotateTracesRequestBody(RequestBody[List[TraceAnnotation]]):
|
|
115
|
+
data: List[TraceAnnotation] = Field(description="The trace annotations to be upserted")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class InsertedTraceAnnotation(BaseModel):
|
|
119
|
+
id: str = Field(description="The ID of the inserted trace annotation")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class AnnotateTracesResponseBody(ResponseBody[List[InsertedTraceAnnotation]]):
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@router.post(
|
|
127
|
+
"/trace_annotations",
|
|
128
|
+
operation_id="annotateTraces",
|
|
129
|
+
summary="Create or update trace annotations",
|
|
130
|
+
responses=add_errors_to_responses(
|
|
131
|
+
[{"status_code": HTTP_404_NOT_FOUND, "description": "Trace not found"}]
|
|
132
|
+
),
|
|
133
|
+
)
|
|
134
|
+
async def annotate_traces(
|
|
135
|
+
request: Request, request_body: AnnotateTracesRequestBody
|
|
136
|
+
) -> AnnotateTracesResponseBody:
|
|
137
|
+
trace_annotations = request_body.data
|
|
138
|
+
trace_gids = [GlobalID.from_id(annotation.trace_id) for annotation in trace_annotations]
|
|
156
139
|
|
|
157
140
|
resolved_trace_ids = []
|
|
158
141
|
for trace_gid in trace_gids:
|
|
159
142
|
try:
|
|
160
143
|
resolved_trace_ids.append(from_global_id_with_expected_type(trace_gid, "Trace"))
|
|
161
144
|
except ValueError:
|
|
162
|
-
|
|
163
|
-
|
|
145
|
+
raise HTTPException(
|
|
146
|
+
detail="Trace with ID {trace_gid} does not exist",
|
|
164
147
|
status_code=HTTP_404_NOT_FOUND,
|
|
165
148
|
)
|
|
166
149
|
|
|
@@ -175,24 +158,24 @@ async def annotate_traces(request: Request) -> Response:
|
|
|
175
158
|
missing_trace_gids = [
|
|
176
159
|
str(GlobalID("Trace", str(trace_gid))) for trace_gid in missing_trace_ids
|
|
177
160
|
]
|
|
178
|
-
|
|
179
|
-
|
|
161
|
+
raise HTTPException(
|
|
162
|
+
detail=f"Traces with IDs {', '.join(missing_trace_gids)} do not exist.",
|
|
180
163
|
status_code=HTTP_404_NOT_FOUND,
|
|
181
164
|
)
|
|
182
165
|
|
|
183
166
|
inserted_annotations = []
|
|
184
167
|
|
|
185
|
-
for annotation in
|
|
186
|
-
trace_gid = GlobalID.from_id(annotation
|
|
168
|
+
for annotation in trace_annotations:
|
|
169
|
+
trace_gid = GlobalID.from_id(annotation.trace_id)
|
|
187
170
|
trace_id = from_global_id_with_expected_type(trace_gid, "Trace")
|
|
188
171
|
|
|
189
|
-
name = annotation
|
|
190
|
-
annotator_kind = annotation
|
|
191
|
-
result = annotation.
|
|
192
|
-
label = result.
|
|
193
|
-
score = result.
|
|
194
|
-
explanation = result.
|
|
195
|
-
metadata = annotation.
|
|
172
|
+
name = annotation.name
|
|
173
|
+
annotator_kind = annotation.annotator_kind
|
|
174
|
+
result = annotation.result
|
|
175
|
+
label = result.label if result else None
|
|
176
|
+
score = result.score if result else None
|
|
177
|
+
explanation = result.explanation if result else None
|
|
178
|
+
metadata = annotation.metadata or {}
|
|
196
179
|
|
|
197
180
|
values = dict(
|
|
198
181
|
trace_rowid=trace_id,
|
|
@@ -213,10 +196,12 @@ async def annotate_traces(request: Request) -> Response:
|
|
|
213
196
|
).returning(models.TraceAnnotation.id)
|
|
214
197
|
)
|
|
215
198
|
inserted_annotations.append(
|
|
216
|
-
|
|
199
|
+
InsertedTraceAnnotation(
|
|
200
|
+
id=str(GlobalID("TraceAnnotation", str(trace_annotation_id)))
|
|
201
|
+
)
|
|
217
202
|
)
|
|
218
203
|
|
|
219
|
-
return
|
|
204
|
+
return AnnotateTracesResponseBody(data=inserted_annotations)
|
|
220
205
|
|
|
221
206
|
|
|
222
207
|
async def _add_spans(req: ExportTraceServiceRequest, state: State) -> None:
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from typing import Any, Dict, Generic, List, Optional, TypedDict, Union
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from typing_extensions import TypeAlias, TypeVar, assert_never
|
|
5
|
+
|
|
6
|
+
StatusCode: TypeAlias = int
|
|
7
|
+
DataType = TypeVar("DataType")
|
|
8
|
+
Responses: TypeAlias = Dict[
|
|
9
|
+
Union[int, str], Dict[str, Any]
|
|
10
|
+
] # input type for the `responses` parameter of a fastapi route
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class StatusCodeWithDescription(TypedDict):
|
|
14
|
+
"""
|
|
15
|
+
A duck type for a status code with a description detailing under what
|
|
16
|
+
conditions the status code is raised.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
status_code: StatusCode
|
|
20
|
+
description: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RequestBody(BaseModel, Generic[DataType]):
|
|
24
|
+
# A generic request type accepted by V1 routes.
|
|
25
|
+
#
|
|
26
|
+
# Don't use """ for this docstring or it will be included as a description
|
|
27
|
+
# in the generated OpenAPI schema.
|
|
28
|
+
data: DataType
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ResponseBody(BaseModel, Generic[DataType]):
|
|
32
|
+
# A generic response type returned by V1 routes.
|
|
33
|
+
#
|
|
34
|
+
# Don't use """ for this docstring or it will be included as a description
|
|
35
|
+
# in the generated OpenAPI schema.
|
|
36
|
+
|
|
37
|
+
data: DataType
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class PaginatedResponseBody(BaseModel, Generic[DataType]):
|
|
41
|
+
# A generic paginated response type returned by V1 routes.
|
|
42
|
+
#
|
|
43
|
+
# Don't use """ for this docstring or it will be included as a description
|
|
44
|
+
# in the generated OpenAPI schema.
|
|
45
|
+
|
|
46
|
+
data: List[DataType]
|
|
47
|
+
next_cursor: Optional[str]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def add_errors_to_responses(
|
|
51
|
+
errors: List[Union[StatusCode, StatusCodeWithDescription]],
|
|
52
|
+
/,
|
|
53
|
+
*,
|
|
54
|
+
responses: Optional[Responses] = None,
|
|
55
|
+
) -> Responses:
|
|
56
|
+
"""
|
|
57
|
+
Creates or updates a patch for an OpenAPI schema's `responses` section to
|
|
58
|
+
include status codes in the generated OpenAPI schema.
|
|
59
|
+
"""
|
|
60
|
+
output_responses: Responses = responses or {}
|
|
61
|
+
for error in errors:
|
|
62
|
+
status_code: int
|
|
63
|
+
description: Optional[str] = None
|
|
64
|
+
if isinstance(error, StatusCode):
|
|
65
|
+
status_code = error
|
|
66
|
+
elif isinstance(error, dict):
|
|
67
|
+
status_code = error["status_code"]
|
|
68
|
+
description = error["description"]
|
|
69
|
+
else:
|
|
70
|
+
assert_never(error)
|
|
71
|
+
if status_code not in output_responses:
|
|
72
|
+
output_responses[status_code] = {
|
|
73
|
+
"content": {"text/plain": {"schema": {"type": "string"}}}
|
|
74
|
+
}
|
|
75
|
+
if description:
|
|
76
|
+
output_responses[status_code]["description"] = description
|
|
77
|
+
return output_responses
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def add_text_csv_content_to_responses(
|
|
81
|
+
status_code: StatusCode, /, *, responses: Optional[Responses] = None
|
|
82
|
+
) -> Responses:
|
|
83
|
+
"""
|
|
84
|
+
Creates or updates a patch for an OpenAPI schema's `responses` section to
|
|
85
|
+
ensure that the response for the given status code is marked as text/csv in
|
|
86
|
+
the generated OpenAPI schema.
|
|
87
|
+
"""
|
|
88
|
+
output_responses: Responses = responses or {}
|
|
89
|
+
if status_code not in output_responses:
|
|
90
|
+
output_responses[status_code] = {}
|
|
91
|
+
output_responses[status_code]["content"] = {
|
|
92
|
+
"text/csv": {"schema": {"type": "string", "contentMediaType": "text/csv"}}
|
|
93
|
+
}
|
|
94
|
+
return output_responses
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
import strawberry
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@strawberry.interface
|
|
7
|
+
class Annotation:
|
|
8
|
+
name: str = strawberry.field(
|
|
9
|
+
description="Name of the annotation, e.g. 'helpfulness' or 'relevance'."
|
|
10
|
+
)
|
|
11
|
+
score: Optional[float] = strawberry.field(
|
|
12
|
+
description="Value of the annotation in the form of a numeric score."
|
|
13
|
+
)
|
|
14
|
+
label: Optional[str] = strawberry.field(
|
|
15
|
+
description="Value of the annotation in the form of a string, e.g. "
|
|
16
|
+
"'helpful' or 'not helpful'. Note that the label is not necessarily binary."
|
|
17
|
+
)
|
|
18
|
+
explanation: Optional[str] = strawberry.field(
|
|
19
|
+
description="The annotator's explanation for the annotation result (i.e. "
|
|
20
|
+
"score or label, or both) given to the subject."
|
|
21
|
+
)
|
|
@@ -1,31 +1,13 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
1
|
import strawberry
|
|
4
2
|
|
|
5
3
|
import phoenix.trace.v1 as pb
|
|
6
4
|
from phoenix.db.models import DocumentAnnotation, SpanAnnotation, TraceAnnotation
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
@strawberry.interface
|
|
10
|
-
class Evaluation:
|
|
11
|
-
name: str = strawberry.field(
|
|
12
|
-
description="Name of the evaluation, e.g. 'helpfulness' or 'relevance'."
|
|
13
|
-
)
|
|
14
|
-
score: Optional[float] = strawberry.field(
|
|
15
|
-
description="Result of the evaluation in the form of a numeric score."
|
|
16
|
-
)
|
|
17
|
-
label: Optional[str] = strawberry.field(
|
|
18
|
-
description="Result of the evaluation in the form of a string, e.g. "
|
|
19
|
-
"'helpful' or 'not helpful'. Note that the label is not necessarily binary."
|
|
20
|
-
)
|
|
21
|
-
explanation: Optional[str] = strawberry.field(
|
|
22
|
-
description="The evaluator's explanation for the evaluation result (i.e. "
|
|
23
|
-
"score or label, or both) given to the subject."
|
|
24
|
-
)
|
|
6
|
+
from .Annotation import Annotation
|
|
25
7
|
|
|
26
8
|
|
|
27
9
|
@strawberry.type
|
|
28
|
-
class TraceEvaluation(
|
|
10
|
+
class TraceEvaluation(Annotation):
|
|
29
11
|
@staticmethod
|
|
30
12
|
def from_pb_evaluation(evaluation: pb.Evaluation) -> "TraceEvaluation":
|
|
31
13
|
result = evaluation.result
|
|
@@ -50,7 +32,7 @@ class TraceEvaluation(Evaluation):
|
|
|
50
32
|
|
|
51
33
|
|
|
52
34
|
@strawberry.type
|
|
53
|
-
class SpanEvaluation(
|
|
35
|
+
class SpanEvaluation(Annotation):
|
|
54
36
|
@staticmethod
|
|
55
37
|
def from_pb_evaluation(evaluation: pb.Evaluation) -> "SpanEvaluation":
|
|
56
38
|
result = evaluation.result
|
|
@@ -75,7 +57,7 @@ class SpanEvaluation(Evaluation):
|
|
|
75
57
|
|
|
76
58
|
|
|
77
59
|
@strawberry.type
|
|
78
|
-
class DocumentEvaluation(
|
|
60
|
+
class DocumentEvaluation(Annotation):
|
|
79
61
|
document_position: int = strawberry.field(
|
|
80
62
|
description="The zero-based index among retrieved documents, which "
|
|
81
63
|
"is collected as a list (even when ordering is not inherently meaningful)."
|
phoenix/server/api/types/Span.py
CHANGED
|
@@ -19,12 +19,14 @@ from phoenix.server.api.helpers.dataset_helpers import (
|
|
|
19
19
|
get_dataset_example_input,
|
|
20
20
|
get_dataset_example_output,
|
|
21
21
|
)
|
|
22
|
-
from phoenix.server.api.types.DocumentRetrievalMetrics import DocumentRetrievalMetrics
|
|
23
|
-
from phoenix.server.api.types.Evaluation import DocumentEvaluation, SpanEvaluation
|
|
24
|
-
from phoenix.server.api.types.ExampleRevisionInterface import ExampleRevision
|
|
25
|
-
from phoenix.server.api.types.MimeType import MimeType
|
|
26
22
|
from phoenix.trace.attributes import get_attribute_value
|
|
27
23
|
|
|
24
|
+
from .DocumentRetrievalMetrics import DocumentRetrievalMetrics
|
|
25
|
+
from .Evaluation import DocumentEvaluation, SpanEvaluation
|
|
26
|
+
from .ExampleRevisionInterface import ExampleRevision
|
|
27
|
+
from .MimeType import MimeType
|
|
28
|
+
from .SpanAnnotation import SpanAnnotation
|
|
29
|
+
|
|
28
30
|
if TYPE_CHECKING:
|
|
29
31
|
from phoenix.server.api.types.Project import Project
|
|
30
32
|
|
|
@@ -172,6 +174,15 @@ class Span(Node):
|
|
|
172
174
|
async def span_evaluations(self, info: Info[Context, None]) -> List[SpanEvaluation]:
|
|
173
175
|
return await info.context.data_loaders.span_evaluations.load(self.id_attr)
|
|
174
176
|
|
|
177
|
+
@strawberry.field(
|
|
178
|
+
description=(
|
|
179
|
+
"Annotations of the span's parent span. This encompasses both "
|
|
180
|
+
"LLM and human annotations."
|
|
181
|
+
)
|
|
182
|
+
) # type: ignore
|
|
183
|
+
async def span_annotations(self, info: Info[Context, None]) -> List[SpanAnnotation]:
|
|
184
|
+
return await info.context.data_loaders.span_annotations.load(self.id_attr)
|
|
185
|
+
|
|
175
186
|
@strawberry.field(
|
|
176
187
|
description="Evaluations of the documents associated with the span, e.g. "
|
|
177
188
|
"if the span is a RETRIEVER with a list of documents in its RETRIEVAL_DOCUMENTS "
|
|
@@ -6,17 +6,15 @@ from strawberry.relay import GlobalID, Node, NodeID
|
|
|
6
6
|
from strawberry.scalars import JSON
|
|
7
7
|
|
|
8
8
|
from phoenix.db import models
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
from .Annotation import Annotation
|
|
11
|
+
from .AnnotatorKind import AnnotatorKind
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
@strawberry.type
|
|
13
|
-
class SpanAnnotation(Node):
|
|
15
|
+
class SpanAnnotation(Node, Annotation):
|
|
14
16
|
id_attr: NodeID[int]
|
|
15
|
-
name: str
|
|
16
17
|
annotator_kind: AnnotatorKind
|
|
17
|
-
label: Optional[str]
|
|
18
|
-
score: Optional[float]
|
|
19
|
-
explanation: Optional[str]
|
|
20
18
|
metadata: JSON
|
|
21
19
|
span_rowid: Private[Optional[int]]
|
|
22
20
|
|