arize-phoenix 8.1.0__py3-none-any.whl → 8.2.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.

Files changed (36) hide show
  1. {arize_phoenix-8.1.0.dist-info → arize_phoenix-8.2.1.dist-info}/METADATA +1 -1
  2. {arize_phoenix-8.1.0.dist-info → arize_phoenix-8.2.1.dist-info}/RECORD +36 -34
  3. phoenix/db/models.py +151 -11
  4. phoenix/server/api/context.py +4 -0
  5. phoenix/server/api/dataloaders/__init__.py +4 -0
  6. phoenix/server/api/dataloaders/span_by_id.py +29 -0
  7. phoenix/server/api/dataloaders/span_descendants.py +24 -15
  8. phoenix/server/api/dataloaders/span_fields.py +76 -0
  9. phoenix/server/api/dataloaders/trace_root_spans.py +9 -10
  10. phoenix/server/api/mutations/chat_mutations.py +10 -7
  11. phoenix/server/api/queries.py +2 -2
  12. phoenix/server/api/subscriptions.py +3 -3
  13. phoenix/server/api/types/Annotation.py +4 -1
  14. phoenix/server/api/types/DatasetExample.py +2 -2
  15. phoenix/server/api/types/Project.py +8 -10
  16. phoenix/server/api/types/ProjectSession.py +2 -2
  17. phoenix/server/api/types/Span.py +377 -120
  18. phoenix/server/api/types/SpanIOValue.py +39 -6
  19. phoenix/server/api/types/Trace.py +17 -15
  20. phoenix/server/app.py +4 -0
  21. phoenix/server/prometheus.py +113 -7
  22. phoenix/server/static/.vite/manifest.json +36 -36
  23. phoenix/server/static/assets/{components-B-qgPyHv.js → components-CgcYOKnv.js} +175 -170
  24. phoenix/server/static/assets/{index-D4KO1IcF.js → index-B_Nkd6Rh.js} +2 -2
  25. phoenix/server/static/assets/{pages-DdcuL3Rh.js → pages-Cz-JsoAE.js} +327 -327
  26. phoenix/server/static/assets/{vendor-DQp7CrDA.js → vendor-Cqfydjep.js} +117 -117
  27. phoenix/server/static/assets/{vendor-arizeai-C1nEIEQq.js → vendor-arizeai-WnerlUPN.js} +1 -1
  28. phoenix/server/static/assets/{vendor-codemirror-BZXYUIkP.js → vendor-codemirror-D-ZZKLFq.js} +1 -1
  29. phoenix/server/static/assets/{vendor-recharts-BUFpwCVD.js → vendor-recharts-KY97ZPfK.js} +1 -1
  30. phoenix/server/static/assets/{vendor-shiki-C8L-c9jT.js → vendor-shiki-D5K9GnFn.js} +1 -1
  31. phoenix/trace/attributes.py +7 -2
  32. phoenix/version.py +1 -1
  33. {arize_phoenix-8.1.0.dist-info → arize_phoenix-8.2.1.dist-info}/WHEEL +0 -0
  34. {arize_phoenix-8.1.0.dist-info → arize_phoenix-8.2.1.dist-info}/entry_points.txt +0 -0
  35. {arize_phoenix-8.1.0.dist-info → arize_phoenix-8.2.1.dist-info}/licenses/IP_NOTICE +0 -0
  36. {arize_phoenix-8.1.0.dist-info → arize_phoenix-8.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -6,10 +6,10 @@ from typing import TYPE_CHECKING, Annotated, Optional, Union
6
6
  import strawberry
7
7
  from openinference.semconv.trace import SpanAttributes
8
8
  from sqlalchemy import desc, select
9
- from sqlalchemy.orm import contains_eager
10
9
  from strawberry import UNSET, Private, lazy
11
10
  from strawberry.relay import Connection, GlobalID, Node, NodeID
12
11
  from strawberry.types import Info
12
+ from typing_extensions import TypeAlias
13
13
 
14
14
  from phoenix.db import models
15
15
  from phoenix.server.api.context import Context
@@ -20,17 +20,20 @@ from phoenix.server.api.types.pagination import (
20
20
  connection_from_list,
21
21
  )
22
22
  from phoenix.server.api.types.SortDir import SortDir
23
- from phoenix.server.api.types.Span import Span, to_gql_span
23
+ from phoenix.server.api.types.Span import Span
24
24
  from phoenix.server.api.types.TraceAnnotation import TraceAnnotation, to_gql_trace_annotation
25
25
 
26
26
  if TYPE_CHECKING:
27
27
  from phoenix.server.api.types.ProjectSession import ProjectSession
28
28
 
29
+ ProjectRowId: TypeAlias = int
30
+ TraceRowId: TypeAlias = int
31
+
29
32
 
30
33
  @strawberry.type
31
34
  class Trace(Node):
32
- id_attr: NodeID[int]
33
- project_rowid: Private[int]
35
+ trace_rowid: NodeID[TraceRowId]
36
+ project_rowid: Private[ProjectRowId]
34
37
  project_session_rowid: Private[Optional[int]]
35
38
  trace_id: str
36
39
  start_time: datetime
@@ -45,7 +48,7 @@ class Trace(Node):
45
48
  latency = await session.scalar(
46
49
  select(
47
50
  models.Trace.latency_ms,
48
- ).where(models.Trace.id == self.id_attr)
51
+ ).where(models.Trace.id == self.trace_rowid)
49
52
  )
50
53
  return latency
51
54
 
@@ -84,10 +87,10 @@ class Trace(Node):
84
87
  self,
85
88
  info: Info[Context, None],
86
89
  ) -> Optional[Span]:
87
- span = await info.context.data_loaders.trace_root_spans.load(self.id_attr)
88
- if span is None:
90
+ span_rowid = await info.context.data_loaders.trace_root_spans.load(self.trace_rowid)
91
+ if span_rowid is None:
89
92
  return None
90
- return to_gql_span(span)
93
+ return Span(span_rowid=span_rowid)
91
94
 
92
95
  @strawberry.field
93
96
  async def spans(
@@ -105,18 +108,17 @@ class Trace(Node):
105
108
  before=before if isinstance(before, CursorString) else None,
106
109
  )
107
110
  stmt = (
108
- select(models.Span)
111
+ select(models.Span.id)
109
112
  .join(models.Trace)
110
- .where(models.Trace.id == self.id_attr)
111
- .options(contains_eager(models.Span.trace).load_only(models.Trace.trace_id))
113
+ .where(models.Trace.id == self.trace_rowid)
112
114
  # Sort descending because the root span tends to show up later
113
115
  # in the ingestion process.
114
116
  .order_by(desc(models.Span.id))
115
117
  .limit(first)
116
118
  )
117
119
  async with info.context.db() as session:
118
- spans = await session.stream_scalars(stmt)
119
- data = [to_gql_span(span) async for span in spans]
120
+ span_rowids = await session.stream_scalars(stmt)
121
+ data = [Span(span_rowid=span_rowid) async for span_rowid in span_rowids]
120
122
  return connection_from_list(data=data, args=args)
121
123
 
122
124
  @strawberry.field(description="Annotations associated with the trace.") # type: ignore
@@ -126,7 +128,7 @@ class Trace(Node):
126
128
  sort: Optional[TraceAnnotationSort] = None,
127
129
  ) -> list[TraceAnnotation]:
128
130
  async with info.context.db() as session:
129
- stmt = select(models.TraceAnnotation).filter_by(span_rowid=self.id_attr)
131
+ stmt = select(models.TraceAnnotation).filter_by(span_rowid=self.trace_rowid)
130
132
  if sort:
131
133
  sort_col = getattr(models.TraceAnnotation, sort.col.value)
132
134
  if sort.dir is SortDir.desc:
@@ -141,7 +143,7 @@ class Trace(Node):
141
143
 
142
144
  def to_gql_trace(trace: models.Trace) -> Trace:
143
145
  return Trace(
144
- id_attr=trace.id,
146
+ trace_rowid=trace.id,
145
147
  project_rowid=trace.project_rowid,
146
148
  project_session_rowid=trace.project_session_rowid,
147
149
  trace_id=trace.trace_id,
phoenix/server/app.py CHANGED
@@ -94,8 +94,10 @@ from phoenix.server.api.dataloaders import (
94
94
  SessionTokenUsagesDataLoader,
95
95
  SessionTraceLatencyMsQuantileDataLoader,
96
96
  SpanAnnotationsDataLoader,
97
+ SpanByIdDataLoader,
97
98
  SpanDatasetExamplesDataLoader,
98
99
  SpanDescendantsDataLoader,
100
+ SpanFieldsDataLoader,
99
101
  SpanProjectsDataLoader,
100
102
  TokenCountDataLoader,
101
103
  TraceByTraceIdsDataLoader,
@@ -624,6 +626,8 @@ def create_graphql_router(
624
626
  session_token_usages=SessionTokenUsagesDataLoader(db),
625
627
  session_trace_latency_ms_quantile=SessionTraceLatencyMsQuantileDataLoader(db),
626
628
  span_annotations=SpanAnnotationsDataLoader(db),
629
+ span_fields=SpanFieldsDataLoader(db),
630
+ span_by_id=SpanByIdDataLoader(db),
627
631
  span_dataset_examples=SpanDatasetExamplesDataLoader(db),
628
632
  span_descendants=SpanDescendantsDataLoader(db),
629
633
  span_projects=SpanProjectsDataLoader(db),
@@ -1,5 +1,9 @@
1
+ import os
2
+ import sys
1
3
  import time
4
+ from functools import lru_cache
2
5
  from threading import Thread
6
+ from typing import Optional
3
7
 
4
8
  import psutil
5
9
  from prometheus_client import (
@@ -31,7 +35,6 @@ RAM_METRIC = Gauge(
31
35
  CPU_METRIC = Gauge(
32
36
  name="cpu_usage_percent",
33
37
  documentation="CPU usage percent",
34
- labelnames=["core"],
35
38
  )
36
39
  BULK_LOADER_INSERTION_TIME = Summary(
37
40
  name="bulk_loader_insertion_time_seconds_summary",
@@ -101,11 +104,114 @@ def gather_system_data() -> None:
101
104
  while True:
102
105
  time.sleep(1)
103
106
 
104
- ram = psutil.virtual_memory()
105
- swap = psutil.swap_memory()
107
+ RAM_METRIC.labels(type="virtual").set(estimate_memory_usage_bytes())
108
+ RAM_METRIC.labels(type="swap").set(estimate_swap_usage_bytes())
109
+ if cpu_metric := estimate_cpu_usage_percent():
110
+ CPU_METRIC.set(cpu_metric)
106
111
 
107
- RAM_METRIC.labels(type="virtual").set(ram.used)
108
- RAM_METRIC.labels(type="swap").set(swap.used)
109
112
 
110
- for core, percent in enumerate(psutil.cpu_percent(interval=1, percpu=True)):
111
- CPU_METRIC.labels(core=core).set(percent)
113
+ def estimate_memory_usage_bytes() -> int:
114
+ # https://docs.docker.com/engine/containers/runmetrics/
115
+ # psutil reports host-level metrics, use cgroups if running on linux
116
+
117
+ if sys.platform == "linux":
118
+ cgroup_v1_file = "/sys/fs/cgroup/memory/memory.usage_in_bytes"
119
+ cgroup_v2_file = "/sys/fs/cgroup/memory.current"
120
+
121
+ if is_cgroup_v2():
122
+ try:
123
+ with open(cgroup_v2_file, "r") as f:
124
+ return int(f.read().strip())
125
+ except Exception:
126
+ pass
127
+ else:
128
+ try:
129
+ with open(cgroup_v1_file, "r") as f:
130
+ return int(f.read().strip())
131
+ except Exception:
132
+ pass
133
+ return psutil.virtual_memory().used
134
+
135
+
136
+ def estimate_swap_usage_bytes() -> int:
137
+ if sys.platform == "linux":
138
+ # cgroup v2: swap usage file (if swap accounting is enabled).
139
+ cgroup_v2_swap_file = "/sys/fs/cgroup/memory.swap.current"
140
+ cgroup_v1_swap_file = "/sys/fs/cgroup/memory/memory.memsw.usage_in_bytes"
141
+
142
+ if is_cgroup_v2() and os.path.exists(cgroup_v2_swap_file):
143
+ try:
144
+ with open(cgroup_v2_swap_file, "r") as f:
145
+ return int(f.read().strip())
146
+ except Exception:
147
+ pass
148
+ elif os.path.exists(cgroup_v1_swap_file):
149
+ try:
150
+ with open(cgroup_v1_swap_file, "r") as f:
151
+ return int(f.read().strip())
152
+ except Exception:
153
+ pass
154
+ return psutil.swap_memory().used
155
+
156
+
157
+ _previous_cpu_sample: dict[str, tuple[float, float]] = {} # cache for previous cpu usage sample
158
+
159
+
160
+ def estimate_cpu_usage_percent() -> Optional[float]:
161
+ # https://docs.docker.com/engine/containers/runmetrics/
162
+ # psutil reports host-level metrics, use cgroups if running on linux
163
+
164
+ current_time = time.time()
165
+
166
+ if sys.platform.startswith("linux"):
167
+ cgroup_v2_cpu_stat = "/sys/fs/cgroup/cpu.stat"
168
+ cgroup_v1_cpu_usage = "/sys/fs/cgroup/cpuacct/cpuacct.usage"
169
+ if is_cgroup_v2():
170
+ try:
171
+ with open(cgroup_v2_cpu_stat, "r") as f:
172
+ lines = f.readlines()
173
+ stats = {}
174
+ for line in lines:
175
+ parts = line.strip().split()
176
+ if len(parts) == 2:
177
+ stats[parts[0]] = float(parts[1])
178
+ if "usage_usec" in stats:
179
+ usage = stats["usage_usec"]
180
+ key = "cgroup_v2"
181
+ if key in _previous_cpu_sample:
182
+ prev_usage, prev_time = _previous_cpu_sample[key]
183
+ delta_usage = usage - prev_usage # in microseconds
184
+ delta_time = current_time - prev_time
185
+ _previous_cpu_sample[key] = (usage, current_time)
186
+ if delta_time > 0:
187
+ # Convert microseconds to seconds.
188
+ return round((delta_usage / (delta_time * 1e6)) * 100, 2)
189
+ else:
190
+ _previous_cpu_sample[key] = (usage, current_time)
191
+ return None # No previous sample yet.
192
+ except Exception:
193
+ pass
194
+ else:
195
+ try:
196
+ with open(cgroup_v1_cpu_usage, "r") as f:
197
+ usage = int(f.read().strip())
198
+ key = "cgroup_v1"
199
+ if key in _previous_cpu_sample:
200
+ prev_usage, prev_time = _previous_cpu_sample[key]
201
+ delta_usage = usage - prev_usage # in nanoseconds
202
+ delta_time = current_time - prev_time
203
+ _previous_cpu_sample[key] = (usage, current_time)
204
+ if delta_time > 0:
205
+ # Convert nanoseconds to seconds.
206
+ return round((delta_usage / (delta_time * 1e9)) * 100, 2)
207
+ else:
208
+ _previous_cpu_sample[key] = (usage, current_time)
209
+ return None # No previous sample yet.
210
+ except Exception:
211
+ pass
212
+ return psutil.cpu_percent(interval=None)
213
+
214
+
215
+ @lru_cache(maxsize=1)
216
+ def is_cgroup_v2() -> bool:
217
+ return os.path.exists("/sys/fs/cgroup/cgroup.controllers")
@@ -1,32 +1,32 @@
1
1
  {
2
- "_components-B-qgPyHv.js": {
3
- "file": "assets/components-B-qgPyHv.js",
2
+ "_components-CgcYOKnv.js": {
3
+ "file": "assets/components-CgcYOKnv.js",
4
4
  "name": "components",
5
5
  "imports": [
6
- "_vendor-DQp7CrDA.js",
7
- "_pages-DdcuL3Rh.js",
8
- "_vendor-arizeai-C1nEIEQq.js",
9
- "_vendor-codemirror-BZXYUIkP.js",
6
+ "_vendor-Cqfydjep.js",
7
+ "_pages-Cz-JsoAE.js",
8
+ "_vendor-arizeai-WnerlUPN.js",
9
+ "_vendor-codemirror-D-ZZKLFq.js",
10
10
  "_vendor-three-C-AGeJYv.js"
11
11
  ]
12
12
  },
13
- "_pages-DdcuL3Rh.js": {
14
- "file": "assets/pages-DdcuL3Rh.js",
13
+ "_pages-Cz-JsoAE.js": {
14
+ "file": "assets/pages-Cz-JsoAE.js",
15
15
  "name": "pages",
16
16
  "imports": [
17
- "_vendor-DQp7CrDA.js",
18
- "_vendor-arizeai-C1nEIEQq.js",
19
- "_components-B-qgPyHv.js",
20
- "_vendor-codemirror-BZXYUIkP.js",
21
- "_vendor-recharts-BUFpwCVD.js"
17
+ "_vendor-Cqfydjep.js",
18
+ "_vendor-arizeai-WnerlUPN.js",
19
+ "_components-CgcYOKnv.js",
20
+ "_vendor-codemirror-D-ZZKLFq.js",
21
+ "_vendor-recharts-KY97ZPfK.js"
22
22
  ]
23
23
  },
24
24
  "_vendor-Cg6lcjUC.css": {
25
25
  "file": "assets/vendor-Cg6lcjUC.css",
26
26
  "src": "_vendor-Cg6lcjUC.css"
27
27
  },
28
- "_vendor-DQp7CrDA.js": {
29
- "file": "assets/vendor-DQp7CrDA.js",
28
+ "_vendor-Cqfydjep.js": {
29
+ "file": "assets/vendor-Cqfydjep.js",
30
30
  "name": "vendor",
31
31
  "imports": [
32
32
  "_vendor-three-C-AGeJYv.js"
@@ -35,33 +35,33 @@
35
35
  "assets/vendor-Cg6lcjUC.css"
36
36
  ]
37
37
  },
38
- "_vendor-arizeai-C1nEIEQq.js": {
39
- "file": "assets/vendor-arizeai-C1nEIEQq.js",
38
+ "_vendor-arizeai-WnerlUPN.js": {
39
+ "file": "assets/vendor-arizeai-WnerlUPN.js",
40
40
  "name": "vendor-arizeai",
41
41
  "imports": [
42
- "_vendor-DQp7CrDA.js"
42
+ "_vendor-Cqfydjep.js"
43
43
  ]
44
44
  },
45
- "_vendor-codemirror-BZXYUIkP.js": {
46
- "file": "assets/vendor-codemirror-BZXYUIkP.js",
45
+ "_vendor-codemirror-D-ZZKLFq.js": {
46
+ "file": "assets/vendor-codemirror-D-ZZKLFq.js",
47
47
  "name": "vendor-codemirror",
48
48
  "imports": [
49
- "_vendor-DQp7CrDA.js",
50
- "_vendor-shiki-C8L-c9jT.js"
49
+ "_vendor-Cqfydjep.js",
50
+ "_vendor-shiki-D5K9GnFn.js"
51
51
  ]
52
52
  },
53
- "_vendor-recharts-BUFpwCVD.js": {
54
- "file": "assets/vendor-recharts-BUFpwCVD.js",
53
+ "_vendor-recharts-KY97ZPfK.js": {
54
+ "file": "assets/vendor-recharts-KY97ZPfK.js",
55
55
  "name": "vendor-recharts",
56
56
  "imports": [
57
- "_vendor-DQp7CrDA.js"
57
+ "_vendor-Cqfydjep.js"
58
58
  ]
59
59
  },
60
- "_vendor-shiki-C8L-c9jT.js": {
61
- "file": "assets/vendor-shiki-C8L-c9jT.js",
60
+ "_vendor-shiki-D5K9GnFn.js": {
61
+ "file": "assets/vendor-shiki-D5K9GnFn.js",
62
62
  "name": "vendor-shiki",
63
63
  "imports": [
64
- "_vendor-DQp7CrDA.js"
64
+ "_vendor-Cqfydjep.js"
65
65
  ]
66
66
  },
67
67
  "_vendor-three-C-AGeJYv.js": {
@@ -69,19 +69,19 @@
69
69
  "name": "vendor-three"
70
70
  },
71
71
  "index.tsx": {
72
- "file": "assets/index-D4KO1IcF.js",
72
+ "file": "assets/index-B_Nkd6Rh.js",
73
73
  "name": "index",
74
74
  "src": "index.tsx",
75
75
  "isEntry": true,
76
76
  "imports": [
77
- "_vendor-DQp7CrDA.js",
78
- "_vendor-arizeai-C1nEIEQq.js",
79
- "_pages-DdcuL3Rh.js",
80
- "_components-B-qgPyHv.js",
77
+ "_vendor-Cqfydjep.js",
78
+ "_vendor-arizeai-WnerlUPN.js",
79
+ "_pages-Cz-JsoAE.js",
80
+ "_components-CgcYOKnv.js",
81
81
  "_vendor-three-C-AGeJYv.js",
82
- "_vendor-codemirror-BZXYUIkP.js",
83
- "_vendor-shiki-C8L-c9jT.js",
84
- "_vendor-recharts-BUFpwCVD.js"
82
+ "_vendor-codemirror-D-ZZKLFq.js",
83
+ "_vendor-shiki-D5K9GnFn.js",
84
+ "_vendor-recharts-KY97ZPfK.js"
85
85
  ]
86
86
  }
87
87
  }