arize-phoenix 10.5.0__py3-none-any.whl → 10.6.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-10.5.0.dist-info → arize_phoenix-10.6.1.dist-info}/METADATA +3 -3
- {arize_phoenix-10.5.0.dist-info → arize_phoenix-10.6.1.dist-info}/RECORD +18 -18
- phoenix/server/api/routers/v1/annotations.py +26 -4
- phoenix/server/api/routers/v1/spans.py +180 -35
- phoenix/server/static/.vite/manifest.json +36 -36
- phoenix/server/static/assets/{components-i0MTMvxr.js → components-J06J_j9O.js} +303 -247
- phoenix/server/static/assets/{index-BBU9ybeN.js → index-DfT39tc3.js} +22 -8
- phoenix/server/static/assets/{pages-Dmq64JIh.js → pages-nxs-tDxQ.js} +389 -406
- phoenix/server/static/assets/vendor-B52WHALA.js +911 -0
- phoenix/server/static/assets/vendor-arizeai-DGHetzZW.js +642 -0
- phoenix/server/static/assets/{vendor-codemirror-c6BZvTq5.js → vendor-codemirror-QIdVJrP_.js} +1 -1
- phoenix/server/static/assets/{vendor-recharts-ButLmGII.js → vendor-recharts-GmWamXB4.js} +1 -1
- phoenix/server/static/assets/{vendor-shiki-BDqcZGjN.js → vendor-shiki-C8cTrXI5.js} +1 -1
- phoenix/version.py +1 -1
- phoenix/server/static/assets/vendor-Cm32tkMA.js +0 -905
- phoenix/server/static/assets/vendor-arizeai-CB-pngCZ.js +0 -649
- {arize_phoenix-10.5.0.dist-info → arize_phoenix-10.6.1.dist-info}/WHEEL +0 -0
- {arize_phoenix-10.5.0.dist-info → arize_phoenix-10.6.1.dist-info}/entry_points.txt +0 -0
- {arize_phoenix-10.5.0.dist-info → arize_phoenix-10.6.1.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-10.5.0.dist-info → arize_phoenix-10.6.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arize-phoenix
|
|
3
|
-
Version: 10.
|
|
3
|
+
Version: 10.6.1
|
|
4
4
|
Summary: AI Observability and Evaluation
|
|
5
5
|
Project-URL: Documentation, https://arize.com/docs/phoenix/
|
|
6
6
|
Project-URL: Issues, https://github.com/Arize-ai/phoenix/issues
|
|
@@ -32,8 +32,8 @@ Requires-Dist: grpcio
|
|
|
32
32
|
Requires-Dist: httpx
|
|
33
33
|
Requires-Dist: jinja2
|
|
34
34
|
Requires-Dist: numpy!=2.0.0
|
|
35
|
-
Requires-Dist: openinference-instrumentation>=0.1.
|
|
36
|
-
Requires-Dist: openinference-semantic-conventions>=0.1.
|
|
35
|
+
Requires-Dist: openinference-instrumentation>=0.1.32
|
|
36
|
+
Requires-Dist: openinference-semantic-conventions>=0.1.17
|
|
37
37
|
Requires-Dist: opentelemetry-exporter-otlp
|
|
38
38
|
Requires-Dist: opentelemetry-proto>=1.12.0
|
|
39
39
|
Requires-Dist: opentelemetry-sdk
|
|
@@ -6,7 +6,7 @@ phoenix/exceptions.py,sha256=n2L2KKuecrdflB9MsCdAYCiSEvGJptIsfRkXMoJle7A,169
|
|
|
6
6
|
phoenix/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
7
7
|
phoenix/services.py,sha256=ngkyKGVatX3cO2WJdo2hKdaVKP-xJCMvqthvga6kJss,5196
|
|
8
8
|
phoenix/settings.py,sha256=x87BX7hWGQQZbrW_vrYqFR_izCGfO9gFc--JXUG4Tdk,754
|
|
9
|
-
phoenix/version.py,sha256=
|
|
9
|
+
phoenix/version.py,sha256=hmMNQcyOVrQB5x2nAaJLi4IxHK6TvSgxZcMlUMDDDi8,23
|
|
10
10
|
phoenix/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
phoenix/core/embedding_dimension.py,sha256=zKGbcvwOXgLf-yrJBpQyKtd-LEOPRKHnUToyAU8Owis,87
|
|
12
12
|
phoenix/core/model.py,sha256=qBFraOtmwCCnWJltKNP18DDG0mULXigytlFsa6YOz6k,4837
|
|
@@ -231,7 +231,7 @@ phoenix/server/api/routers/oauth2.py,sha256=EUoBeh4Ix-Uwt_q_RD75xbMcdVdd0xJLDYjE
|
|
|
231
231
|
phoenix/server/api/routers/utils.py,sha256=M41BoH-fl37izhRuN2aX7lWm7jOC20A_3uClv9TVUUY,583
|
|
232
232
|
phoenix/server/api/routers/v1/__init__.py,sha256=ngLMPjC7lgZxgKy_Is33KxTRnMzSqy25qTTChCVx_Mo,2696
|
|
233
233
|
phoenix/server/api/routers/v1/annotation_configs.py,sha256=rZ3yJm7m75BVegSjSHqsdqf7n26roGg7vYYiiKfWA3A,15898
|
|
234
|
-
phoenix/server/api/routers/v1/annotations.py,sha256=
|
|
234
|
+
phoenix/server/api/routers/v1/annotations.py,sha256=fVl2qeh_ZbWXGvFBTZgeL7aGkkINIScdjuyxnOoSzNM,6817
|
|
235
235
|
phoenix/server/api/routers/v1/datasets.py,sha256=Xh-M8bnCmjflmPcgv8dAG8Cek88sApuqQlvNBuSnrYc,37534
|
|
236
236
|
phoenix/server/api/routers/v1/evaluations.py,sha256=GFTo42aIEX0Htn0EjjoE1JZDYlvryeZ_CK9kowhwzGw,12830
|
|
237
237
|
phoenix/server/api/routers/v1/experiment_evaluations.py,sha256=xSs004jNYsOl3eg-6Zjo2tt9TefTd7WR3twWYrsNQNk,4828
|
|
@@ -240,7 +240,7 @@ phoenix/server/api/routers/v1/experiments.py,sha256=V9_sxqLTE1MKGFu9H3FEdGKr70lY
|
|
|
240
240
|
phoenix/server/api/routers/v1/models.py,sha256=p3gJN-9SWiUYTUTft4bZMsZVCBNTb4nN1Foy68eRZzQ,1997
|
|
241
241
|
phoenix/server/api/routers/v1/projects.py,sha256=LFWxHYPRZy-1EvroNylL635vU1UuDbcuRo1oD26yBnw,12551
|
|
242
242
|
phoenix/server/api/routers/v1/prompts.py,sha256=aBOUBwLDzZDIzJQkxJcR8ZKnakNJOLMwzsLKINSs1mA,26545
|
|
243
|
-
phoenix/server/api/routers/v1/spans.py,sha256=
|
|
243
|
+
phoenix/server/api/routers/v1/spans.py,sha256=MxZ9RvZqc8JGgcDAgH_S9rTc9YfXOEF_j9kqBf3z4qY,38379
|
|
244
244
|
phoenix/server/api/routers/v1/traces.py,sha256=DfzeszQvtlrVxvurJLaWJJAhkCZ4BodLwpFuBYPwN5Q,8206
|
|
245
245
|
phoenix/server/api/routers/v1/users.py,sha256=ZcW3if0L8-lUusTzET7fhlhvnmiCICDrGimzB7i3irc,11947
|
|
246
246
|
phoenix/server/api/routers/v1/utils.py,sha256=oXIOGPzPTkE0ZWUTRCoRIQQ7wTzoSwtWFaUSjlGBqts,4960
|
|
@@ -348,16 +348,16 @@ phoenix/server/static/apple-touch-icon-76x76.png,sha256=CT_xT12I0u2i0WU8JzBZBuOQ
|
|
|
348
348
|
phoenix/server/static/apple-touch-icon.png,sha256=fOfpjqGpWYbJ0eAurKsyoZP1EAs6ZVooBJ_SGk2ZkDs,3801
|
|
349
349
|
phoenix/server/static/favicon.ico,sha256=bY0vvCKRftemZfPShwZtE93DiiQdaYaozkPGwNFr6H8,34494
|
|
350
350
|
phoenix/server/static/modernizr.js,sha256=mvK-XtkNqjOral-QvzoqsyOMECXIMu5BQwSVN_wcU9c,2564
|
|
351
|
-
phoenix/server/static/.vite/manifest.json,sha256=
|
|
352
|
-
phoenix/server/static/assets/components-
|
|
353
|
-
phoenix/server/static/assets/index-
|
|
354
|
-
phoenix/server/static/assets/pages-
|
|
355
|
-
phoenix/server/static/assets/vendor-
|
|
351
|
+
phoenix/server/static/.vite/manifest.json,sha256=E-0Lfjo3ifvH_nTkOfxSUD71-14G-h4J_SPkp45W6i4,2165
|
|
352
|
+
phoenix/server/static/assets/components-J06J_j9O.js,sha256=Fc_oqiCnUDql98GSn9T4Ye1t5NCQHLx1C5lfVMJgH3o,560166
|
|
353
|
+
phoenix/server/static/assets/index-DfT39tc3.js,sha256=ZZMrxzzP1lR_4_LbrTY6AzlU8jmXPaeLx1XU3NpNDMM,61125
|
|
354
|
+
phoenix/server/static/assets/pages-nxs-tDxQ.js,sha256=3eEO9a8WoKg9IsVzOtxigo5dEwjkSoa0_Sb87JGf6HU,1032945
|
|
355
|
+
phoenix/server/static/assets/vendor-B52WHALA.js,sha256=qNJdtbj4rc-YYJc4JF9crIVk9G3iSnPB5NbDhXzS4VM,2731862
|
|
356
356
|
phoenix/server/static/assets/vendor-WIZid84E.css,sha256=spZD2r7XL5GfLO13ln-IuXfnjAref8l6g_n_AvxxOlI,5517
|
|
357
|
-
phoenix/server/static/assets/vendor-arizeai-
|
|
358
|
-
phoenix/server/static/assets/vendor-codemirror-
|
|
359
|
-
phoenix/server/static/assets/vendor-recharts-
|
|
360
|
-
phoenix/server/static/assets/vendor-shiki-
|
|
357
|
+
phoenix/server/static/assets/vendor-arizeai-DGHetzZW.js,sha256=0GqwRIhagIyYxB33qbBQaHZaxLVExlvEXiOTCy7Ybc4,181777
|
|
358
|
+
phoenix/server/static/assets/vendor-codemirror-QIdVJrP_.js,sha256=kMwrQyDFj51M4Wz8PxZUtYKu9jfcxZQaSFVH18zQcKE,781264
|
|
359
|
+
phoenix/server/static/assets/vendor-recharts-GmWamXB4.js,sha256=4Qkqq7rtva-XKz0cYfvNWuArDKJYr41ohfwmcct6D44,282150
|
|
360
|
+
phoenix/server/static/assets/vendor-shiki-C8cTrXI5.js,sha256=Op8ln0V7Vo23uJvqEzcXw9irmK8k2_mQiKLFaXLk8HU,8980312
|
|
361
361
|
phoenix/server/static/assets/vendor-three-C5WAXd5r.js,sha256=ELkg06u70N7h8oFmvqdoHyPuUf9VgGEWeT4LKFx4VWo,620975
|
|
362
362
|
phoenix/server/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
363
363
|
phoenix/server/templates/index.html,sha256=NpJ83DULqcStXFbShNamX4_NPDtnnucuBxppvUYjJa8,4409
|
|
@@ -398,9 +398,9 @@ phoenix/utilities/project.py,sha256=auVpARXkDb-JgeX5f2aStyFIkeKvGwN9l7qrFeJMVxI,
|
|
|
398
398
|
phoenix/utilities/re.py,sha256=6YyUWIkv0zc2SigsxfOWIHzdpjKA_TZo2iqKq7zJKvw,2081
|
|
399
399
|
phoenix/utilities/span_store.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
400
400
|
phoenix/utilities/template_formatters.py,sha256=gh9PJD6WEGw7TEYXfSst1UR4pWWwmjxMLrDVQ_CkpkQ,2779
|
|
401
|
-
arize_phoenix-10.
|
|
402
|
-
arize_phoenix-10.
|
|
403
|
-
arize_phoenix-10.
|
|
404
|
-
arize_phoenix-10.
|
|
405
|
-
arize_phoenix-10.
|
|
406
|
-
arize_phoenix-10.
|
|
401
|
+
arize_phoenix-10.6.1.dist-info/METADATA,sha256=bKdR8tOlMRt59PTNBZ6WoNYnNd4oSzSaLW9lsdPkGqg,27004
|
|
402
|
+
arize_phoenix-10.6.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
403
|
+
arize_phoenix-10.6.1.dist-info/entry_points.txt,sha256=Pgpn8Upxx9P8z8joPXZWl2LlnAlGc3gcQoVchb06X1Q,94
|
|
404
|
+
arize_phoenix-10.6.1.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
|
|
405
|
+
arize_phoenix-10.6.1.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
|
|
406
|
+
arize_phoenix-10.6.1.dist-info/RECORD,,
|
|
@@ -62,6 +62,17 @@ async def list_span_annotations(
|
|
|
62
62
|
span_ids: list[str] = Query(
|
|
63
63
|
..., min_length=1, description="One or more span id to fetch annotations for"
|
|
64
64
|
),
|
|
65
|
+
include_annotation_names: Optional[list[str]] = Query(
|
|
66
|
+
default=None,
|
|
67
|
+
description=(
|
|
68
|
+
"Optional list of annotation names to include. If provided, only annotations with "
|
|
69
|
+
"these names will be returned. 'note' annotations are excluded by default unless "
|
|
70
|
+
"explicitly included in this list."
|
|
71
|
+
),
|
|
72
|
+
),
|
|
73
|
+
exclude_annotation_names: Optional[list[str]] = Query(
|
|
74
|
+
default=None, description="Optional list of annotation names to exclude from results."
|
|
75
|
+
),
|
|
65
76
|
cursor: Optional[str] = Query(default=None, description="A cursor for pagination"),
|
|
66
77
|
limit: int = Query(
|
|
67
78
|
default=10,
|
|
@@ -84,15 +95,26 @@ async def list_span_annotations(
|
|
|
84
95
|
status_code=HTTP_404_NOT_FOUND,
|
|
85
96
|
detail=f"Project with identifier {project_identifier} not found",
|
|
86
97
|
)
|
|
98
|
+
|
|
99
|
+
# Build the base query
|
|
100
|
+
where_conditions = [
|
|
101
|
+
models.Project.id == project.id,
|
|
102
|
+
models.Span.span_id.in_(span_ids),
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
# Add annotation name filtering
|
|
106
|
+
if include_annotation_names:
|
|
107
|
+
where_conditions.append(models.SpanAnnotation.name.in_(include_annotation_names))
|
|
108
|
+
|
|
109
|
+
if exclude_annotation_names:
|
|
110
|
+
where_conditions.append(models.SpanAnnotation.name.not_in(exclude_annotation_names))
|
|
111
|
+
|
|
87
112
|
stmt = (
|
|
88
113
|
select(models.Span.span_id, models.SpanAnnotation)
|
|
89
114
|
.join(models.Trace, models.Span.trace_rowid == models.Trace.id)
|
|
90
115
|
.join(models.Project, models.Trace.project_rowid == models.Project.id)
|
|
91
116
|
.join(models.SpanAnnotation, models.SpanAnnotation.span_rowid == models.Span.id)
|
|
92
|
-
.where(
|
|
93
|
-
models.Project.id == project.id,
|
|
94
|
-
models.Span.span_id.in_(span_ids),
|
|
95
|
-
)
|
|
117
|
+
.where(*where_conditions)
|
|
96
118
|
.order_by(models.SpanAnnotation.id.desc())
|
|
97
119
|
.limit(limit + 1)
|
|
98
120
|
)
|
|
@@ -370,12 +370,48 @@ class OtlpSpan(BaseModel):
|
|
|
370
370
|
)
|
|
371
371
|
|
|
372
372
|
|
|
373
|
-
class
|
|
373
|
+
class OtlpSpansResponseBody(PaginatedResponseBody[OtlpSpan]):
|
|
374
374
|
"""Paginated response where each span follows OTLP JSON structure."""
|
|
375
375
|
|
|
376
376
|
pass
|
|
377
377
|
|
|
378
378
|
|
|
379
|
+
################################################################################
|
|
380
|
+
# Phoenix Span Models
|
|
381
|
+
################################################################################
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class SpanContext(V1RoutesBaseModel):
|
|
385
|
+
trace_id: str = Field(description="OpenTelemetry trace ID")
|
|
386
|
+
span_id: str = Field(description="OpenTelemetry span ID")
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class SpanEvent(V1RoutesBaseModel):
|
|
390
|
+
name: str = Field(description="Name of the event")
|
|
391
|
+
timestamp: datetime = Field(description="When the event occurred")
|
|
392
|
+
attributes: dict[str, Any] = Field(default_factory=dict, description="Event attributes")
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
class Span(V1RoutesBaseModel):
|
|
396
|
+
id: str = Field(description="Span Global ID, distinct from the OpenTelemetry span ID")
|
|
397
|
+
name: str = Field(description="Name of the span operation")
|
|
398
|
+
context: SpanContext = Field(description="Span context containing trace_id and span_id")
|
|
399
|
+
span_kind: str = Field(description="Type of work that the span encapsulates")
|
|
400
|
+
parent_id: Optional[str] = Field(
|
|
401
|
+
default=None, description="OpenTelemetry span ID of the parent span"
|
|
402
|
+
)
|
|
403
|
+
start_time: datetime = Field(description="Start time of the span")
|
|
404
|
+
end_time: datetime = Field(description="End time of the span")
|
|
405
|
+
status_code: str = Field(description="Status code of the span")
|
|
406
|
+
status_message: str = Field(default="", description="Status message")
|
|
407
|
+
attributes: dict[str, Any] = Field(default_factory=dict, description="Span attributes")
|
|
408
|
+
events: list[SpanEvent] = Field(default_factory=list, description="Span events")
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
class SpansResponseBody(PaginatedResponseBody[Span]):
|
|
412
|
+
pass
|
|
413
|
+
|
|
414
|
+
|
|
379
415
|
# TODO: Add property details to SpanQuery schema
|
|
380
416
|
@router.post(
|
|
381
417
|
"/spans",
|
|
@@ -513,11 +549,11 @@ def _to_any_value(value: Any) -> OtlpAnyValue:
|
|
|
513
549
|
"/projects/{project_identifier}/spans/otlpv1",
|
|
514
550
|
operation_id="spanSearch",
|
|
515
551
|
summary="Search spans with simple filters (no DSL)",
|
|
516
|
-
description="Return spans within a project filtered by time range
|
|
517
|
-
"
|
|
552
|
+
description="Return spans within a project filtered by time range. "
|
|
553
|
+
"Supports cursor-based pagination.",
|
|
518
554
|
responses=add_errors_to_responses([HTTP_404_NOT_FOUND, HTTP_422_UNPROCESSABLE_ENTITY]),
|
|
519
555
|
)
|
|
520
|
-
async def
|
|
556
|
+
async def span_search_otlpv1(
|
|
521
557
|
request: Request,
|
|
522
558
|
project_identifier: str = Path(
|
|
523
559
|
description=(
|
|
@@ -525,30 +561,18 @@ async def span_search(
|
|
|
525
561
|
"it cannot contain slash (/), question mark (?), or pound sign (#) characters."
|
|
526
562
|
)
|
|
527
563
|
),
|
|
528
|
-
cursor: Optional[str] = Query(default=None, description="Pagination cursor (
|
|
564
|
+
cursor: Optional[str] = Query(default=None, description="Pagination cursor (Span Global ID)"),
|
|
529
565
|
limit: int = Query(default=100, gt=0, le=1000, description="Maximum number of spans to return"),
|
|
530
|
-
sort_direction: Literal["asc", "desc"] = Query(
|
|
531
|
-
default="desc",
|
|
532
|
-
description="Sort direction for the sort field",
|
|
533
|
-
),
|
|
534
566
|
start_time: Optional[datetime] = Query(default=None, description="Inclusive lower bound time"),
|
|
535
567
|
end_time: Optional[datetime] = Query(default=None, description="Exclusive upper bound time"),
|
|
536
|
-
|
|
537
|
-
default=None,
|
|
538
|
-
description=(
|
|
539
|
-
"If provided, only include spans that have at least one annotation with one "
|
|
540
|
-
"of these names."
|
|
541
|
-
),
|
|
542
|
-
alias="annotationNames",
|
|
543
|
-
),
|
|
544
|
-
) -> SpanSearchResponseBody:
|
|
568
|
+
) -> OtlpSpansResponseBody:
|
|
545
569
|
"""Search spans with minimal filters instead of the old SpanQuery DSL."""
|
|
546
570
|
|
|
547
571
|
async with request.app.state.db() as session:
|
|
548
572
|
project = await _get_project_by_identifier(session, project_identifier)
|
|
549
573
|
|
|
550
574
|
project_id: int = project.id
|
|
551
|
-
order_by = [models.Span.id.
|
|
575
|
+
order_by = [models.Span.id.desc()]
|
|
552
576
|
|
|
553
577
|
stmt = (
|
|
554
578
|
select(
|
|
@@ -565,23 +589,10 @@ async def span_search(
|
|
|
565
589
|
if end_time:
|
|
566
590
|
stmt = stmt.where(models.Span.start_time < normalize_datetime(end_time, timezone.utc))
|
|
567
591
|
|
|
568
|
-
if annotation_names:
|
|
569
|
-
stmt = (
|
|
570
|
-
stmt.join(
|
|
571
|
-
models.SpanAnnotation,
|
|
572
|
-
onclause=models.SpanAnnotation.span_rowid == models.Span.id,
|
|
573
|
-
)
|
|
574
|
-
.where(models.SpanAnnotation.name.in_(annotation_names))
|
|
575
|
-
.group_by(models.Span.id, models.Trace.trace_id)
|
|
576
|
-
)
|
|
577
|
-
|
|
578
592
|
if cursor:
|
|
579
593
|
try:
|
|
580
594
|
cursor_rowid = int(GlobalID.from_id(cursor).node_id)
|
|
581
|
-
|
|
582
|
-
stmt = stmt.where(models.Span.id >= cursor_rowid)
|
|
583
|
-
else:
|
|
584
|
-
stmt = stmt.where(models.Span.id <= cursor_rowid)
|
|
595
|
+
stmt = stmt.where(models.Span.id <= cursor_rowid)
|
|
585
596
|
except Exception:
|
|
586
597
|
raise HTTPException(status_code=HTTP_422_UNPROCESSABLE_ENTITY, detail="Invalid cursor")
|
|
587
598
|
|
|
@@ -591,7 +602,7 @@ async def span_search(
|
|
|
591
602
|
rows: list[tuple[models.Span, str]] = [r async for r in await session.stream(stmt)]
|
|
592
603
|
|
|
593
604
|
if not rows:
|
|
594
|
-
return
|
|
605
|
+
return OtlpSpansResponseBody(next_cursor=None, data=[])
|
|
595
606
|
|
|
596
607
|
next_cursor: Optional[str] = None
|
|
597
608
|
if len(rows) == limit + 1:
|
|
@@ -668,7 +679,141 @@ async def span_search(
|
|
|
668
679
|
)
|
|
669
680
|
)
|
|
670
681
|
|
|
671
|
-
return
|
|
682
|
+
return OtlpSpansResponseBody(next_cursor=next_cursor, data=result_spans)
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
@router.get(
|
|
686
|
+
"/projects/{project_identifier}/spans",
|
|
687
|
+
operation_id="getSpans",
|
|
688
|
+
summary="List spans with simple filters (no DSL)",
|
|
689
|
+
description="Return spans within a project filtered by time range. "
|
|
690
|
+
"Supports cursor-based pagination.",
|
|
691
|
+
responses=add_errors_to_responses([HTTP_404_NOT_FOUND, HTTP_422_UNPROCESSABLE_ENTITY]),
|
|
692
|
+
)
|
|
693
|
+
async def span_search(
|
|
694
|
+
request: Request,
|
|
695
|
+
project_identifier: str = Path(
|
|
696
|
+
description=(
|
|
697
|
+
"The project identifier: either project ID or project name. If using a project name, "
|
|
698
|
+
"it cannot contain slash (/), question mark (?), or pound sign (#) characters."
|
|
699
|
+
)
|
|
700
|
+
),
|
|
701
|
+
cursor: Optional[str] = Query(default=None, description="Pagination cursor (Span Global ID)"),
|
|
702
|
+
limit: int = Query(default=100, gt=0, le=1000, description="Maximum number of spans to return"),
|
|
703
|
+
start_time: Optional[datetime] = Query(default=None, description="Inclusive lower bound time"),
|
|
704
|
+
end_time: Optional[datetime] = Query(default=None, description="Exclusive upper bound time"),
|
|
705
|
+
) -> SpansResponseBody:
|
|
706
|
+
async with request.app.state.db() as session:
|
|
707
|
+
project = await _get_project_by_identifier(session, project_identifier)
|
|
708
|
+
|
|
709
|
+
project_id: int = project.id
|
|
710
|
+
order_by = [models.Span.id.desc()]
|
|
711
|
+
|
|
712
|
+
stmt = (
|
|
713
|
+
select(
|
|
714
|
+
models.Span,
|
|
715
|
+
models.Trace.trace_id,
|
|
716
|
+
)
|
|
717
|
+
.join(models.Trace, onclause=models.Trace.id == models.Span.trace_rowid)
|
|
718
|
+
.join(models.Project, onclause=models.Project.id == project_id)
|
|
719
|
+
.order_by(*order_by)
|
|
720
|
+
)
|
|
721
|
+
|
|
722
|
+
if start_time:
|
|
723
|
+
stmt = stmt.where(models.Span.start_time >= normalize_datetime(start_time, timezone.utc))
|
|
724
|
+
if end_time:
|
|
725
|
+
stmt = stmt.where(models.Span.start_time < normalize_datetime(end_time, timezone.utc))
|
|
726
|
+
|
|
727
|
+
if cursor:
|
|
728
|
+
try:
|
|
729
|
+
cursor_rowid = int(GlobalID.from_id(cursor).node_id)
|
|
730
|
+
except Exception:
|
|
731
|
+
raise HTTPException(status_code=HTTP_422_UNPROCESSABLE_ENTITY, detail="Invalid cursor")
|
|
732
|
+
stmt = stmt.where(models.Span.id <= cursor_rowid)
|
|
733
|
+
|
|
734
|
+
stmt = stmt.limit(limit + 1)
|
|
735
|
+
|
|
736
|
+
async with request.app.state.db() as session:
|
|
737
|
+
rows: list[tuple[models.Span, str]] = [r async for r in await session.stream(stmt)]
|
|
738
|
+
|
|
739
|
+
if not rows:
|
|
740
|
+
return SpansResponseBody(next_cursor=None, data=[])
|
|
741
|
+
|
|
742
|
+
next_cursor: Optional[str] = None
|
|
743
|
+
if len(rows) == limit + 1:
|
|
744
|
+
*rows, extra = rows # extra is first item of next page
|
|
745
|
+
span_extra, _ = extra
|
|
746
|
+
next_cursor = str(GlobalID("Span", str(span_extra.id)))
|
|
747
|
+
|
|
748
|
+
# Convert ORM rows -> Phoenix spans
|
|
749
|
+
result_spans: list[Span] = []
|
|
750
|
+
for span_orm, trace_id in rows:
|
|
751
|
+
# Convert events to Phoenix Event list
|
|
752
|
+
events: list[SpanEvent] = []
|
|
753
|
+
for event in span_orm.events:
|
|
754
|
+
event_time = event.get("timestamp")
|
|
755
|
+
parsed_time = None
|
|
756
|
+
|
|
757
|
+
if event_time:
|
|
758
|
+
if isinstance(event_time, datetime):
|
|
759
|
+
parsed_time = normalize_datetime(event_time, timezone.utc)
|
|
760
|
+
elif isinstance(event_time, str):
|
|
761
|
+
try:
|
|
762
|
+
naive_time = datetime.fromisoformat(event_time)
|
|
763
|
+
parsed_time = normalize_datetime(naive_time, timezone.utc)
|
|
764
|
+
except ValueError:
|
|
765
|
+
# If ISO format fails, try to parse as timestamp
|
|
766
|
+
try:
|
|
767
|
+
parsed_time = datetime.fromtimestamp(float(event_time), tz=timezone.utc)
|
|
768
|
+
except (ValueError, TypeError):
|
|
769
|
+
parsed_time = datetime.now(timezone.utc) # fallback
|
|
770
|
+
elif isinstance(event_time, (int, float)):
|
|
771
|
+
try:
|
|
772
|
+
# Assume nanoseconds if very large, otherwise seconds
|
|
773
|
+
if event_time > 1e12: # nanoseconds
|
|
774
|
+
parsed_time = datetime.fromtimestamp(
|
|
775
|
+
event_time / 1_000_000_000, tz=timezone.utc
|
|
776
|
+
)
|
|
777
|
+
else: # seconds
|
|
778
|
+
parsed_time = datetime.fromtimestamp(event_time, tz=timezone.utc)
|
|
779
|
+
except (ValueError, OSError):
|
|
780
|
+
parsed_time = datetime.now(timezone.utc) # fallback
|
|
781
|
+
else:
|
|
782
|
+
parsed_time = datetime.now(timezone.utc) # fallback
|
|
783
|
+
|
|
784
|
+
events.append(
|
|
785
|
+
SpanEvent(
|
|
786
|
+
name=event.get("name", ""),
|
|
787
|
+
timestamp=parsed_time,
|
|
788
|
+
attributes=event.get("attributes", {}),
|
|
789
|
+
)
|
|
790
|
+
)
|
|
791
|
+
|
|
792
|
+
attributes = {
|
|
793
|
+
k: v for k, v in flatten(span_orm.attributes or dict(), recurse_on_sequence=True)
|
|
794
|
+
}
|
|
795
|
+
openinference_span_kind = attributes.pop("openinference.span.kind", "UNKNOWN")
|
|
796
|
+
|
|
797
|
+
result_spans.append(
|
|
798
|
+
Span(
|
|
799
|
+
id=str(GlobalID("Span", str(span_orm.id))),
|
|
800
|
+
name=span_orm.name or "",
|
|
801
|
+
context=SpanContext(
|
|
802
|
+
trace_id=trace_id,
|
|
803
|
+
span_id=span_orm.span_id or "",
|
|
804
|
+
),
|
|
805
|
+
span_kind=openinference_span_kind,
|
|
806
|
+
parent_id=span_orm.parent_id,
|
|
807
|
+
start_time=span_orm.start_time,
|
|
808
|
+
end_time=span_orm.end_time,
|
|
809
|
+
status_code=span_orm.status_code,
|
|
810
|
+
status_message=span_orm.status_message or "",
|
|
811
|
+
attributes=attributes,
|
|
812
|
+
events=events,
|
|
813
|
+
)
|
|
814
|
+
)
|
|
815
|
+
|
|
816
|
+
return SpansResponseBody(next_cursor=next_cursor, data=result_spans)
|
|
672
817
|
|
|
673
818
|
|
|
674
819
|
@router.get("/spans", include_in_schema=False, deprecated=True)
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_components-
|
|
3
|
-
"file": "assets/components-
|
|
2
|
+
"_components-J06J_j9O.js": {
|
|
3
|
+
"file": "assets/components-J06J_j9O.js",
|
|
4
4
|
"name": "components",
|
|
5
5
|
"imports": [
|
|
6
|
-
"_vendor-
|
|
7
|
-
"_pages-
|
|
8
|
-
"_vendor-arizeai-
|
|
9
|
-
"_vendor-codemirror-
|
|
6
|
+
"_vendor-B52WHALA.js",
|
|
7
|
+
"_pages-nxs-tDxQ.js",
|
|
8
|
+
"_vendor-arizeai-DGHetzZW.js",
|
|
9
|
+
"_vendor-codemirror-QIdVJrP_.js",
|
|
10
10
|
"_vendor-three-C5WAXd5r.js"
|
|
11
11
|
]
|
|
12
12
|
},
|
|
13
|
-
"_pages-
|
|
14
|
-
"file": "assets/pages-
|
|
13
|
+
"_pages-nxs-tDxQ.js": {
|
|
14
|
+
"file": "assets/pages-nxs-tDxQ.js",
|
|
15
15
|
"name": "pages",
|
|
16
16
|
"imports": [
|
|
17
|
-
"_vendor-
|
|
18
|
-
"_vendor-arizeai-
|
|
19
|
-
"_components-
|
|
20
|
-
"_vendor-codemirror-
|
|
21
|
-
"_vendor-recharts-
|
|
17
|
+
"_vendor-B52WHALA.js",
|
|
18
|
+
"_vendor-arizeai-DGHetzZW.js",
|
|
19
|
+
"_components-J06J_j9O.js",
|
|
20
|
+
"_vendor-codemirror-QIdVJrP_.js",
|
|
21
|
+
"_vendor-recharts-GmWamXB4.js"
|
|
22
22
|
]
|
|
23
23
|
},
|
|
24
|
-
"_vendor-
|
|
25
|
-
"file": "assets/vendor-
|
|
24
|
+
"_vendor-B52WHALA.js": {
|
|
25
|
+
"file": "assets/vendor-B52WHALA.js",
|
|
26
26
|
"name": "vendor",
|
|
27
27
|
"imports": [
|
|
28
28
|
"_vendor-three-C5WAXd5r.js"
|
|
@@ -35,33 +35,33 @@
|
|
|
35
35
|
"file": "assets/vendor-WIZid84E.css",
|
|
36
36
|
"src": "_vendor-WIZid84E.css"
|
|
37
37
|
},
|
|
38
|
-
"_vendor-arizeai-
|
|
39
|
-
"file": "assets/vendor-arizeai-
|
|
38
|
+
"_vendor-arizeai-DGHetzZW.js": {
|
|
39
|
+
"file": "assets/vendor-arizeai-DGHetzZW.js",
|
|
40
40
|
"name": "vendor-arizeai",
|
|
41
41
|
"imports": [
|
|
42
|
-
"_vendor-
|
|
42
|
+
"_vendor-B52WHALA.js"
|
|
43
43
|
]
|
|
44
44
|
},
|
|
45
|
-
"_vendor-codemirror-
|
|
46
|
-
"file": "assets/vendor-codemirror-
|
|
45
|
+
"_vendor-codemirror-QIdVJrP_.js": {
|
|
46
|
+
"file": "assets/vendor-codemirror-QIdVJrP_.js",
|
|
47
47
|
"name": "vendor-codemirror",
|
|
48
48
|
"imports": [
|
|
49
|
-
"_vendor-
|
|
50
|
-
"_vendor-shiki-
|
|
49
|
+
"_vendor-B52WHALA.js",
|
|
50
|
+
"_vendor-shiki-C8cTrXI5.js"
|
|
51
51
|
]
|
|
52
52
|
},
|
|
53
|
-
"_vendor-recharts-
|
|
54
|
-
"file": "assets/vendor-recharts-
|
|
53
|
+
"_vendor-recharts-GmWamXB4.js": {
|
|
54
|
+
"file": "assets/vendor-recharts-GmWamXB4.js",
|
|
55
55
|
"name": "vendor-recharts",
|
|
56
56
|
"imports": [
|
|
57
|
-
"_vendor-
|
|
57
|
+
"_vendor-B52WHALA.js"
|
|
58
58
|
]
|
|
59
59
|
},
|
|
60
|
-
"_vendor-shiki-
|
|
61
|
-
"file": "assets/vendor-shiki-
|
|
60
|
+
"_vendor-shiki-C8cTrXI5.js": {
|
|
61
|
+
"file": "assets/vendor-shiki-C8cTrXI5.js",
|
|
62
62
|
"name": "vendor-shiki",
|
|
63
63
|
"imports": [
|
|
64
|
-
"_vendor-
|
|
64
|
+
"_vendor-B52WHALA.js"
|
|
65
65
|
]
|
|
66
66
|
},
|
|
67
67
|
"_vendor-three-C5WAXd5r.js": {
|
|
@@ -69,19 +69,19 @@
|
|
|
69
69
|
"name": "vendor-three"
|
|
70
70
|
},
|
|
71
71
|
"index.tsx": {
|
|
72
|
-
"file": "assets/index-
|
|
72
|
+
"file": "assets/index-DfT39tc3.js",
|
|
73
73
|
"name": "index",
|
|
74
74
|
"src": "index.tsx",
|
|
75
75
|
"isEntry": true,
|
|
76
76
|
"imports": [
|
|
77
|
-
"_vendor-
|
|
78
|
-
"_vendor-arizeai-
|
|
79
|
-
"_pages-
|
|
80
|
-
"_components-
|
|
77
|
+
"_vendor-B52WHALA.js",
|
|
78
|
+
"_vendor-arizeai-DGHetzZW.js",
|
|
79
|
+
"_pages-nxs-tDxQ.js",
|
|
80
|
+
"_components-J06J_j9O.js",
|
|
81
81
|
"_vendor-three-C5WAXd5r.js",
|
|
82
|
-
"_vendor-codemirror-
|
|
83
|
-
"_vendor-shiki-
|
|
84
|
-
"_vendor-recharts-
|
|
82
|
+
"_vendor-codemirror-QIdVJrP_.js",
|
|
83
|
+
"_vendor-shiki-C8cTrXI5.js",
|
|
84
|
+
"_vendor-recharts-GmWamXB4.js"
|
|
85
85
|
]
|
|
86
86
|
}
|
|
87
87
|
}
|