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

Files changed (32) hide show
  1. {arize_phoenix-11.7.0.dist-info → arize_phoenix-11.8.0.dist-info}/METADATA +14 -2
  2. {arize_phoenix-11.7.0.dist-info → arize_phoenix-11.8.0.dist-info}/RECORD +32 -31
  3. phoenix/config.py +33 -0
  4. phoenix/datetime_utils.py +112 -1
  5. phoenix/db/helpers.py +156 -1
  6. phoenix/server/api/auth.py +28 -6
  7. phoenix/server/api/dataloaders/span_cost_summary_by_experiment.py +6 -7
  8. phoenix/server/api/exceptions.py +6 -0
  9. phoenix/server/api/input_types/TimeBinConfig.py +23 -0
  10. phoenix/server/api/routers/oauth2.py +19 -2
  11. phoenix/server/api/types/CostBreakdown.py +4 -7
  12. phoenix/server/api/types/Project.py +341 -73
  13. phoenix/server/app.py +7 -3
  14. phoenix/server/authorization.py +27 -2
  15. phoenix/server/cost_tracking/cost_details_calculator.py +22 -16
  16. phoenix/server/daemons/span_cost_calculator.py +2 -8
  17. phoenix/server/email/sender.py +2 -1
  18. phoenix/server/email/templates/db_disk_usage_notification.html +3 -0
  19. phoenix/server/static/.vite/manifest.json +36 -36
  20. phoenix/server/static/assets/{components-J3qjrjBf.js → components-5M9nebi4.js} +344 -263
  21. phoenix/server/static/assets/{index-CEObsQf_.js → index-OU2WTnGN.js} +11 -11
  22. phoenix/server/static/assets/{pages-CW1UdBht.js → pages-DF8rqxJ4.js} +451 -444
  23. phoenix/server/static/assets/{vendor-BnPh9i9e.js → vendor-Bl7CyFDw.js} +147 -147
  24. phoenix/server/static/assets/{vendor-arizeai-Cr9o_Iu_.js → vendor-arizeai-B_viEUUA.js} +18 -480
  25. phoenix/server/static/assets/{vendor-codemirror-k3zCIjlN.js → vendor-codemirror-vlcH1_iR.js} +1 -1
  26. phoenix/server/static/assets/{vendor-recharts-BdblEuGB.js → vendor-recharts-C9cQu72o.js} +25 -25
  27. phoenix/server/static/assets/{vendor-shiki-DPtuv2M4.js → vendor-shiki-BsknB7bv.js} +1 -1
  28. phoenix/version.py +1 -1
  29. {arize_phoenix-11.7.0.dist-info → arize_phoenix-11.8.0.dist-info}/WHEEL +0 -0
  30. {arize_phoenix-11.7.0.dist-info → arize_phoenix-11.8.0.dist-info}/entry_points.txt +0 -0
  31. {arize_phoenix-11.7.0.dist-info → arize_phoenix-11.8.0.dist-info}/licenses/IP_NOTICE +0 -0
  32. {arize_phoenix-11.7.0.dist-info → arize_phoenix-11.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -25,6 +25,7 @@ Usage:
25
25
  from fastapi import HTTPException, Request
26
26
  from fastapi import status as fastapi_status
27
27
 
28
+ from phoenix.config import get_env_support_email
28
29
  from phoenix.server.bearer_auth import PhoenixUser
29
30
 
30
31
 
@@ -54,9 +55,33 @@ def require_admin(request: Request) -> None:
54
55
 
55
56
 
56
57
  def is_not_locked(request: Request) -> None:
58
+ """
59
+ FastAPI dependency to ensure database operations are not locked due to insufficient storage.
60
+
61
+ This dependency checks if data insertion and update operations are disabled due to
62
+ storage capacity limits. When storage thresholds are exceeded, it raises an HTTP 507
63
+ error with actionable guidance for users.
64
+
65
+ Usage:
66
+ Add as a dependency to any route that modifies data:
67
+
68
+ @router.post("/create-data", dependencies=[Depends(is_not_locked)])
69
+ async def create_data(...):
70
+ ...
71
+
72
+ Raises:
73
+ HTTPException: HTTP 507 Insufficient Storage when database operations are locked.
74
+ The error includes guidance on resolving storage issues and support contact
75
+ information if configured.
76
+ """
57
77
  if request.app.state.db.should_not_insert_or_update:
78
+ detail = (
79
+ "Database operations are disabled due to insufficient storage. "
80
+ "Please delete old data or increase storage."
81
+ )
82
+ if support_email := get_env_support_email():
83
+ detail += f" Need help? Contact us at {support_email}"
58
84
  raise HTTPException(
59
85
  status_code=fastapi_status.HTTP_507_INSUFFICIENT_STORAGE,
60
- detail="Operations that insert or update database "
61
- "records are currently not allowed.",
86
+ detail=detail,
62
87
  )
@@ -1,5 +1,5 @@
1
1
  from itertools import chain
2
- from typing import Any, Iterable, Mapping
2
+ from typing import Any, Iterable, Mapping, Optional
3
3
 
4
4
  from typing_extensions import TypeAlias
5
5
 
@@ -60,7 +60,7 @@ class SpanCostDetailsCalculator:
60
60
  for p in prices
61
61
  if p.is_prompt
62
62
  }
63
- if "input" not in self._prompt:
63
+ if self._prompt and "input" not in self._prompt:
64
64
  raise ValueError("Token prices for prompt must include an 'input' token type")
65
65
 
66
66
  # Create calculators for completion token types (is_prompt=False)
@@ -69,7 +69,7 @@ class SpanCostDetailsCalculator:
69
69
  for p in prices
70
70
  if not p.is_prompt
71
71
  }
72
- if "output" not in self._completion:
72
+ if self._completion and "output" not in self._completion:
73
73
  raise ValueError("Token prices for completion must include an 'output' token type")
74
74
 
75
75
  def calculate_details(
@@ -112,6 +112,9 @@ class SpanCostDetailsCalculator:
112
112
  """
113
113
  prompt_details: dict[_TokenType, models.SpanCostDetail] = {}
114
114
  completion_details: dict[_TokenType, models.SpanCostDetail] = {}
115
+ calculator: Optional[TokenCostCalculator]
116
+ cost: Optional[float]
117
+ cost_per_token: Optional[float]
115
118
 
116
119
  # Phase 1: Process detailed token counts from span attributes
117
120
  for is_prompt, prefix, calculators, results in (
@@ -128,18 +131,19 @@ class SpanCostDetailsCalculator:
128
131
  tokens = max(0, int(token_count))
129
132
 
130
133
  # Calculate cost using specific calculator or fallback to default
134
+ calculator = None
135
+ calculator_key = "input" if is_prompt else "output"
131
136
  if token_type in calculators:
132
137
  # Use specific calculator for this token type
133
138
  calculator = calculators[token_type]
134
- else:
135
- # Fallback to default calculator: "input" for prompts,
136
- # "output" for completions
137
- key = "input" if is_prompt else "output"
138
- calculator = calculators[key]
139
- cost = calculator.calculate_cost(attributes, tokens)
139
+ elif calculator_key in calculators:
140
+ calculator = calculators[calculator_key]
140
141
 
141
- # Calculate cost per token (avoid division by zero)
142
- cost_per_token = cost / tokens if tokens else None
142
+ cost = None
143
+ cost_per_token = None
144
+ if calculator:
145
+ cost = calculator.calculate_cost(attributes, tokens)
146
+ cost_per_token = cost / tokens if tokens else None
143
147
 
144
148
  detail = models.SpanCostDetail(
145
149
  token_type=token_type,
@@ -171,11 +175,13 @@ class SpanCostDetailsCalculator:
171
175
  if tokens <= 0:
172
176
  continue
173
177
 
174
- # Calculate cost using guaranteed default calculator (input/output are required)
175
- cost = calculators[token_type].calculate_cost(attributes, tokens)
176
-
177
- # Calculate cost per token (avoid division by zero)
178
- cost_per_token = cost / tokens if cost and tokens else None
178
+ # Calculate cost using calculator if available
179
+ cost = None
180
+ cost_per_token = None
181
+ if token_type in calculators:
182
+ calculator = calculators[token_type]
183
+ cost = calculator.calculate_cost(attributes, tokens)
184
+ cost_per_token = cost / tokens if tokens else None
179
185
 
180
186
  detail = models.SpanCostDetail(
181
187
  token_type=token_type,
@@ -5,7 +5,6 @@ from asyncio import sleep
5
5
  from datetime import datetime
6
6
  from typing import Any, Mapping, NamedTuple, Optional
7
7
 
8
- from sqlalchemy import inspect
9
8
  from typing_extensions import TypeAlias
10
9
 
11
10
  from phoenix.db import models
@@ -84,18 +83,13 @@ class SpanCostCalculator(DaemonTask):
84
83
  start_time=start_time,
85
84
  attributes=attributes,
86
85
  )
87
- if not cost_model:
88
- return None
89
- if not isinstance(inspect(cost_model).attrs.token_prices.loaded_value, list):
90
- return None
91
-
92
- calculator = SpanCostDetailsCalculator(cost_model.token_prices)
86
+ calculator = SpanCostDetailsCalculator(cost_model.token_prices if cost_model else [])
93
87
  details = calculator.calculate_details(attributes)
94
88
  if not details:
95
89
  return None
96
90
 
97
91
  cost = models.SpanCost(
98
- model_id=cost_model.id,
92
+ model_id=cost_model.id if cost_model else None,
99
93
  span_start_time=start_time,
100
94
  )
101
95
  for detail in details:
@@ -8,7 +8,7 @@ from anyio import to_thread
8
8
  from jinja2 import Environment, FileSystemLoader, select_autoescape
9
9
  from typing_extensions import TypeAlias
10
10
 
11
- from phoenix.config import get_env_root_url
11
+ from phoenix.config import get_env_root_url, get_env_support_email
12
12
 
13
13
  EMAIL_TEMPLATE_FOLDER = Path(__file__).parent / "templates"
14
14
 
@@ -96,6 +96,7 @@ class SimpleEmailSender:
96
96
  current_usage_gibibytes=current_usage_gibibytes,
97
97
  allocated_storage_gibibytes=allocated_storage_gibibytes,
98
98
  notification_threshold_percentage=notification_threshold_percentage,
99
+ support_email=get_env_support_email(),
99
100
  )
100
101
 
101
102
  msg = EmailMessage()
@@ -12,5 +12,8 @@
12
12
  <p><strong>Usage Percentage:</strong> {{ ((current_usage_gibibytes / allocated_storage_gibibytes) * 100)|round(1) }}%</p>
13
13
  <p><strong>Notification Threshold:</strong> {{ notification_threshold_percentage }}%</p>
14
14
  <p>Please consider removing old data or increasing your storage allocation to prevent interruption.</p>
15
+ {% if support_email %}
16
+ <p>If you need assistance, please contact support at <a id="support-email" href="mailto:{{ support_email }}">{{ support_email }}</a>.</p>
17
+ {% endif %}
15
18
  </body>
16
19
  </html>
@@ -1,28 +1,28 @@
1
1
  {
2
- "_components-J3qjrjBf.js": {
3
- "file": "assets/components-J3qjrjBf.js",
2
+ "_components-5M9nebi4.js": {
3
+ "file": "assets/components-5M9nebi4.js",
4
4
  "name": "components",
5
5
  "imports": [
6
- "_vendor-BnPh9i9e.js",
7
- "_pages-CW1UdBht.js",
8
- "_vendor-arizeai-Cr9o_Iu_.js",
9
- "_vendor-codemirror-k3zCIjlN.js",
6
+ "_vendor-Bl7CyFDw.js",
7
+ "_pages-DF8rqxJ4.js",
8
+ "_vendor-arizeai-B_viEUUA.js",
9
+ "_vendor-codemirror-vlcH1_iR.js",
10
10
  "_vendor-three-C5WAXd5r.js"
11
11
  ]
12
12
  },
13
- "_pages-CW1UdBht.js": {
14
- "file": "assets/pages-CW1UdBht.js",
13
+ "_pages-DF8rqxJ4.js": {
14
+ "file": "assets/pages-DF8rqxJ4.js",
15
15
  "name": "pages",
16
16
  "imports": [
17
- "_vendor-BnPh9i9e.js",
18
- "_vendor-arizeai-Cr9o_Iu_.js",
19
- "_components-J3qjrjBf.js",
20
- "_vendor-codemirror-k3zCIjlN.js",
21
- "_vendor-recharts-BdblEuGB.js"
17
+ "_vendor-Bl7CyFDw.js",
18
+ "_vendor-arizeai-B_viEUUA.js",
19
+ "_components-5M9nebi4.js",
20
+ "_vendor-codemirror-vlcH1_iR.js",
21
+ "_vendor-recharts-C9cQu72o.js"
22
22
  ]
23
23
  },
24
- "_vendor-BnPh9i9e.js": {
25
- "file": "assets/vendor-BnPh9i9e.js",
24
+ "_vendor-Bl7CyFDw.js": {
25
+ "file": "assets/vendor-Bl7CyFDw.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-Cr9o_Iu_.js": {
39
- "file": "assets/vendor-arizeai-Cr9o_Iu_.js",
38
+ "_vendor-arizeai-B_viEUUA.js": {
39
+ "file": "assets/vendor-arizeai-B_viEUUA.js",
40
40
  "name": "vendor-arizeai",
41
41
  "imports": [
42
- "_vendor-BnPh9i9e.js"
42
+ "_vendor-Bl7CyFDw.js"
43
43
  ]
44
44
  },
45
- "_vendor-codemirror-k3zCIjlN.js": {
46
- "file": "assets/vendor-codemirror-k3zCIjlN.js",
45
+ "_vendor-codemirror-vlcH1_iR.js": {
46
+ "file": "assets/vendor-codemirror-vlcH1_iR.js",
47
47
  "name": "vendor-codemirror",
48
48
  "imports": [
49
- "_vendor-BnPh9i9e.js",
50
- "_vendor-shiki-DPtuv2M4.js"
49
+ "_vendor-Bl7CyFDw.js",
50
+ "_vendor-shiki-BsknB7bv.js"
51
51
  ]
52
52
  },
53
- "_vendor-recharts-BdblEuGB.js": {
54
- "file": "assets/vendor-recharts-BdblEuGB.js",
53
+ "_vendor-recharts-C9cQu72o.js": {
54
+ "file": "assets/vendor-recharts-C9cQu72o.js",
55
55
  "name": "vendor-recharts",
56
56
  "imports": [
57
- "_vendor-BnPh9i9e.js"
57
+ "_vendor-Bl7CyFDw.js"
58
58
  ]
59
59
  },
60
- "_vendor-shiki-DPtuv2M4.js": {
61
- "file": "assets/vendor-shiki-DPtuv2M4.js",
60
+ "_vendor-shiki-BsknB7bv.js": {
61
+ "file": "assets/vendor-shiki-BsknB7bv.js",
62
62
  "name": "vendor-shiki",
63
63
  "imports": [
64
- "_vendor-BnPh9i9e.js"
64
+ "_vendor-Bl7CyFDw.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-CEObsQf_.js",
72
+ "file": "assets/index-OU2WTnGN.js",
73
73
  "name": "index",
74
74
  "src": "index.tsx",
75
75
  "isEntry": true,
76
76
  "imports": [
77
- "_vendor-BnPh9i9e.js",
78
- "_vendor-arizeai-Cr9o_Iu_.js",
79
- "_pages-CW1UdBht.js",
80
- "_components-J3qjrjBf.js",
77
+ "_vendor-Bl7CyFDw.js",
78
+ "_vendor-arizeai-B_viEUUA.js",
79
+ "_pages-DF8rqxJ4.js",
80
+ "_components-5M9nebi4.js",
81
81
  "_vendor-three-C5WAXd5r.js",
82
- "_vendor-codemirror-k3zCIjlN.js",
83
- "_vendor-shiki-DPtuv2M4.js",
84
- "_vendor-recharts-BdblEuGB.js"
82
+ "_vendor-codemirror-vlcH1_iR.js",
83
+ "_vendor-shiki-BsknB7bv.js",
84
+ "_vendor-recharts-C9cQu72o.js"
85
85
  ]
86
86
  }
87
87
  }