arize-phoenix 4.36.0__py3-none-any.whl → 5.1.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 (81) hide show
  1. {arize_phoenix-4.36.0.dist-info → arize_phoenix-5.1.0.dist-info}/METADATA +10 -12
  2. {arize_phoenix-4.36.0.dist-info → arize_phoenix-5.1.0.dist-info}/RECORD +69 -60
  3. phoenix/__init__.py +86 -0
  4. phoenix/auth.py +275 -14
  5. phoenix/config.py +277 -25
  6. phoenix/db/enums.py +20 -0
  7. phoenix/db/facilitator.py +112 -0
  8. phoenix/db/migrations/versions/cd164e83824f_users_and_tokens.py +157 -0
  9. phoenix/db/models.py +145 -60
  10. phoenix/experiments/evaluators/code_evaluators.py +9 -3
  11. phoenix/experiments/functions.py +1 -4
  12. phoenix/server/api/README.md +28 -0
  13. phoenix/server/api/auth.py +32 -0
  14. phoenix/server/api/context.py +50 -2
  15. phoenix/server/api/dataloaders/__init__.py +4 -0
  16. phoenix/server/api/dataloaders/user_roles.py +30 -0
  17. phoenix/server/api/dataloaders/users.py +33 -0
  18. phoenix/server/api/exceptions.py +7 -0
  19. phoenix/server/api/mutations/__init__.py +0 -2
  20. phoenix/server/api/mutations/api_key_mutations.py +104 -86
  21. phoenix/server/api/mutations/dataset_mutations.py +8 -8
  22. phoenix/server/api/mutations/experiment_mutations.py +2 -2
  23. phoenix/server/api/mutations/export_events_mutations.py +3 -3
  24. phoenix/server/api/mutations/project_mutations.py +3 -3
  25. phoenix/server/api/mutations/span_annotations_mutations.py +4 -4
  26. phoenix/server/api/mutations/trace_annotations_mutations.py +4 -4
  27. phoenix/server/api/mutations/user_mutations.py +282 -42
  28. phoenix/server/api/openapi/schema.py +2 -2
  29. phoenix/server/api/queries.py +48 -39
  30. phoenix/server/api/routers/__init__.py +11 -0
  31. phoenix/server/api/routers/auth.py +284 -0
  32. phoenix/server/api/routers/embeddings.py +26 -0
  33. phoenix/server/api/routers/oauth2.py +456 -0
  34. phoenix/server/api/routers/v1/__init__.py +38 -16
  35. phoenix/server/api/types/ApiKey.py +11 -0
  36. phoenix/server/api/types/AuthMethod.py +9 -0
  37. phoenix/server/api/types/User.py +48 -4
  38. phoenix/server/api/types/UserApiKey.py +35 -1
  39. phoenix/server/api/types/UserRole.py +7 -0
  40. phoenix/server/app.py +103 -31
  41. phoenix/server/bearer_auth.py +161 -0
  42. phoenix/server/email/__init__.py +0 -0
  43. phoenix/server/email/sender.py +26 -0
  44. phoenix/server/email/templates/__init__.py +0 -0
  45. phoenix/server/email/templates/password_reset.html +19 -0
  46. phoenix/server/email/types.py +11 -0
  47. phoenix/server/grpc_server.py +6 -0
  48. phoenix/server/jwt_store.py +504 -0
  49. phoenix/server/main.py +40 -9
  50. phoenix/server/oauth2.py +51 -0
  51. phoenix/server/prometheus.py +20 -0
  52. phoenix/server/rate_limiters.py +191 -0
  53. phoenix/server/static/.vite/manifest.json +31 -31
  54. phoenix/server/static/assets/{components-Dte7_KRd.js → components-REunxTt6.js} +348 -286
  55. phoenix/server/static/assets/index-DAPJxlCw.js +101 -0
  56. phoenix/server/static/assets/{pages-CnTvEGEN.js → pages-1VrMk2pW.js} +559 -291
  57. phoenix/server/static/assets/{vendor-BC3OPQuM.js → vendor-B5IC0ivG.js} +5 -5
  58. phoenix/server/static/assets/{vendor-arizeai-NjB3cZzD.js → vendor-arizeai-aFbT4kl1.js} +2 -2
  59. phoenix/server/static/assets/{vendor-codemirror-gE_JCOgX.js → vendor-codemirror-BEGorXSV.js} +1 -1
  60. phoenix/server/static/assets/{vendor-recharts-BXLYwcXF.js → vendor-recharts-6nUU7gU_.js} +1 -1
  61. phoenix/server/templates/index.html +1 -0
  62. phoenix/server/types.py +157 -1
  63. phoenix/session/client.py +7 -2
  64. phoenix/trace/fixtures.py +24 -0
  65. phoenix/utilities/client.py +16 -0
  66. phoenix/version.py +1 -1
  67. phoenix/db/migrations/future_versions/README.md +0 -4
  68. phoenix/db/migrations/future_versions/cd164e83824f_users_and_tokens.py +0 -293
  69. phoenix/db/migrations/versions/.gitignore +0 -1
  70. phoenix/server/api/mutations/auth.py +0 -18
  71. phoenix/server/api/mutations/auth_mutations.py +0 -65
  72. phoenix/server/static/assets/index-fq1-hCK4.js +0 -100
  73. phoenix/trace/langchain/__init__.py +0 -3
  74. phoenix/trace/langchain/instrumentor.py +0 -34
  75. phoenix/trace/llama_index/__init__.py +0 -3
  76. phoenix/trace/llama_index/callback.py +0 -102
  77. phoenix/trace/openai/__init__.py +0 -3
  78. phoenix/trace/openai/instrumentor.py +0 -30
  79. {arize_phoenix-4.36.0.dist-info → arize_phoenix-5.1.0.dist-info}/WHEEL +0 -0
  80. {arize_phoenix-4.36.0.dist-info → arize_phoenix-5.1.0.dist-info}/licenses/IP_NOTICE +0 -0
  81. {arize_phoenix-4.36.0.dist-info → arize_phoenix-5.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,191 @@
1
+ import re
2
+ import time
3
+ from collections import defaultdict
4
+ from functools import partial
5
+ from typing import (
6
+ Any,
7
+ Callable,
8
+ Coroutine,
9
+ DefaultDict,
10
+ List,
11
+ Optional,
12
+ Pattern, # import from re module when we drop support for 3.8
13
+ Union,
14
+ )
15
+
16
+ from fastapi import HTTPException, Request
17
+
18
+ from phoenix.config import get_env_enable_prometheus
19
+ from phoenix.exceptions import PhoenixException
20
+
21
+
22
+ class UnavailableTokensError(PhoenixException):
23
+ pass
24
+
25
+
26
+ class TokenBucket:
27
+ """
28
+ An implementation of the token-bucket algorithm for use as a rate limiter.
29
+
30
+ Args:
31
+ per_second_request_rate (float): The allowed request rate.
32
+ enforcement_window_minutes (float): The time window over which the rate limit is enforced.
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ per_second_request_rate: float,
38
+ enforcement_window_seconds: float = 1,
39
+ ):
40
+ self.enforcement_window = enforcement_window_seconds
41
+ self.rate = per_second_request_rate
42
+
43
+ now = time.time()
44
+ self.last_checked = now
45
+ self.tokens = self.max_tokens()
46
+
47
+ def max_tokens(self) -> float:
48
+ return self.rate * self.enforcement_window
49
+
50
+ def available_tokens(self) -> float:
51
+ now = time.time()
52
+ time_since_last_checked = now - self.last_checked
53
+ self.tokens = min(self.max_tokens(), self.rate * time_since_last_checked + self.tokens)
54
+ self.last_checked = now
55
+ return self.tokens
56
+
57
+ def make_request_if_ready(self) -> None:
58
+ if self.available_tokens() < 1:
59
+ if get_env_enable_prometheus():
60
+ from phoenix.server.prometheus import RATE_LIMITER_THROTTLES
61
+
62
+ RATE_LIMITER_THROTTLES.inc()
63
+ raise UnavailableTokensError
64
+ self.tokens -= 1
65
+
66
+
67
+ class ServerRateLimiter:
68
+ """
69
+ This rate limiter holds a cache of token buckets that enforce rate limits.
70
+
71
+ The cache is kept in partitions that rotate every `partition_seconds`. Each user's rate limiter
72
+ can be accessed from all active partitions, the number of active partitions is set with
73
+ `active_partitions`. This guarantees that a user's rate limiter will sit in the cache for at
74
+ least:
75
+
76
+ minimum_cache_lifetime = (active_partitions - 1) * partition_seconds
77
+
78
+ Every time the cache is accessed, inactive partitions are purged. If enough time has passed,
79
+ the entire cache is purged.
80
+ """
81
+
82
+ def __init__(
83
+ self,
84
+ per_second_rate_limit: float = 0.5,
85
+ enforcement_window_seconds: float = 5,
86
+ partition_seconds: float = 60,
87
+ active_partitions: int = 2,
88
+ ):
89
+ self.bucket_factory = partial(
90
+ TokenBucket,
91
+ per_second_request_rate=per_second_rate_limit,
92
+ enforcement_window_seconds=enforcement_window_seconds,
93
+ )
94
+ self.partition_seconds = partition_seconds
95
+ self.active_partitions = active_partitions
96
+ self.num_partitions = active_partitions + 2 # two overflow partitions to avoid edge cases
97
+ self._reset_rate_limiters()
98
+ self._last_cleanup_time = time.time()
99
+
100
+ def _reset_rate_limiters(self) -> None:
101
+ self.cache_partitions: List[DefaultDict[Any, TokenBucket]] = [
102
+ defaultdict(self.bucket_factory) for _ in range(self.num_partitions)
103
+ ]
104
+
105
+ def _current_partition_index(self, timestamp: float) -> int:
106
+ return (
107
+ int(timestamp // self.partition_seconds) % self.num_partitions
108
+ ) # a cyclic bucket index
109
+
110
+ def _active_partition_indices(self, current_index: int) -> List[int]:
111
+ return [(current_index - ii) % self.num_partitions for ii in range(self.active_partitions)]
112
+
113
+ def _inactive_partition_indices(self, current_index: int) -> List[int]:
114
+ active_indices = set(self._active_partition_indices(current_index))
115
+ all_indices = set(range(self.num_partitions))
116
+ return list(all_indices - active_indices)
117
+
118
+ def _cleanup_expired_limiters(self, request_time: float) -> None:
119
+ time_since_last_cleanup = request_time - self._last_cleanup_time
120
+ if time_since_last_cleanup >= ((self.num_partitions - 1) * self.partition_seconds):
121
+ # Reset the cache to avoid "looping" back to the same partitions
122
+ self._reset_rate_limiters()
123
+ self._last_cleanup_time = request_time
124
+ return
125
+
126
+ current_partition_index = self._current_partition_index(request_time)
127
+ inactive_indices = self._inactive_partition_indices(current_partition_index)
128
+ for ii in inactive_indices:
129
+ self.cache_partitions[ii] = defaultdict(self.bucket_factory)
130
+ self._last_cleanup_time = request_time
131
+
132
+ def _fetch_token_bucket(self, key: str, request_time: float) -> TokenBucket:
133
+ current_partition_index = self._current_partition_index(request_time)
134
+ active_indices = self._active_partition_indices(current_partition_index)
135
+ bucket: Optional[TokenBucket] = None
136
+ for ii in active_indices:
137
+ partition = self.cache_partitions[ii]
138
+ if key in partition:
139
+ bucket = partition.pop(key)
140
+ break
141
+
142
+ current_partition = self.cache_partitions[current_partition_index]
143
+ if key not in current_partition and bucket is not None:
144
+ current_partition[key] = bucket
145
+ return current_partition[key]
146
+
147
+ def make_request(self, key: str) -> None:
148
+ request_time = time.time()
149
+ self._cleanup_expired_limiters(request_time)
150
+ rate_limiter = self._fetch_token_bucket(key, request_time)
151
+ rate_limiter.make_request_if_ready()
152
+ if get_env_enable_prometheus():
153
+ from phoenix.server.prometheus import RATE_LIMITER_CACHE_SIZE
154
+
155
+ RATE_LIMITER_CACHE_SIZE.set(sum(len(partition) for partition in self.cache_partitions))
156
+
157
+
158
+ def fastapi_ip_rate_limiter(
159
+ rate_limiter: ServerRateLimiter, paths: Optional[List[Union[str, Pattern[str]]]] = None
160
+ ) -> Callable[[Request], Coroutine[Any, Any, Request]]:
161
+ async def dependency(request: Request) -> Request:
162
+ if paths is None or any(path_match(request.url.path, path) for path in paths):
163
+ client = request.client
164
+ if client: # bypasses rate limiter if no client
165
+ client_ip = client.host
166
+ try:
167
+ rate_limiter.make_request(client_ip)
168
+ except UnavailableTokensError:
169
+ raise HTTPException(status_code=429, detail="Too Many Requests")
170
+ return request
171
+
172
+ return dependency
173
+
174
+
175
+ def fastapi_route_rate_limiter(
176
+ rate_limiter: ServerRateLimiter,
177
+ ) -> Callable[[Request], Coroutine[Any, Any, Request]]:
178
+ async def dependency(request: Request) -> Request:
179
+ try:
180
+ rate_limiter.make_request(request.url.path)
181
+ except UnavailableTokensError:
182
+ raise HTTPException(status_code=429, detail="Too Many Requests")
183
+ return request
184
+
185
+ return dependency
186
+
187
+
188
+ def path_match(path: str, match_pattern: Union[str, Pattern[str]]) -> bool:
189
+ if isinstance(match_pattern, re.Pattern):
190
+ return bool(match_pattern.match(path))
191
+ return path == match_pattern
@@ -1,32 +1,32 @@
1
1
  {
2
- "_components-Dte7_KRd.js": {
3
- "file": "assets/components-Dte7_KRd.js",
2
+ "_components-REunxTt6.js": {
3
+ "file": "assets/components-REunxTt6.js",
4
4
  "name": "components",
5
5
  "imports": [
6
- "_vendor-BC3OPQuM.js",
7
- "_vendor-arizeai-NjB3cZzD.js",
8
- "_pages-CnTvEGEN.js",
6
+ "_vendor-B5IC0ivG.js",
7
+ "_vendor-arizeai-aFbT4kl1.js",
8
+ "_pages-1VrMk2pW.js",
9
9
  "_vendor-three-DwGkEfCM.js",
10
- "_vendor-codemirror-gE_JCOgX.js"
10
+ "_vendor-codemirror-BEGorXSV.js"
11
11
  ]
12
12
  },
13
- "_pages-CnTvEGEN.js": {
14
- "file": "assets/pages-CnTvEGEN.js",
13
+ "_pages-1VrMk2pW.js": {
14
+ "file": "assets/pages-1VrMk2pW.js",
15
15
  "name": "pages",
16
16
  "imports": [
17
- "_vendor-BC3OPQuM.js",
18
- "_components-Dte7_KRd.js",
19
- "_vendor-arizeai-NjB3cZzD.js",
20
- "_vendor-recharts-BXLYwcXF.js",
21
- "_vendor-codemirror-gE_JCOgX.js"
17
+ "_vendor-B5IC0ivG.js",
18
+ "_vendor-arizeai-aFbT4kl1.js",
19
+ "_components-REunxTt6.js",
20
+ "_vendor-recharts-6nUU7gU_.js",
21
+ "_vendor-codemirror-BEGorXSV.js"
22
22
  ]
23
23
  },
24
24
  "_vendor-!~{003}~.js": {
25
25
  "file": "assets/vendor-DxkFTwjz.css",
26
26
  "src": "_vendor-!~{003}~.js"
27
27
  },
28
- "_vendor-BC3OPQuM.js": {
29
- "file": "assets/vendor-BC3OPQuM.js",
28
+ "_vendor-B5IC0ivG.js": {
29
+ "file": "assets/vendor-B5IC0ivG.js",
30
30
  "name": "vendor",
31
31
  "imports": [
32
32
  "_vendor-three-DwGkEfCM.js"
@@ -35,25 +35,25 @@
35
35
  "assets/vendor-DxkFTwjz.css"
36
36
  ]
37
37
  },
38
- "_vendor-arizeai-NjB3cZzD.js": {
39
- "file": "assets/vendor-arizeai-NjB3cZzD.js",
38
+ "_vendor-arizeai-aFbT4kl1.js": {
39
+ "file": "assets/vendor-arizeai-aFbT4kl1.js",
40
40
  "name": "vendor-arizeai",
41
41
  "imports": [
42
- "_vendor-BC3OPQuM.js"
42
+ "_vendor-B5IC0ivG.js"
43
43
  ]
44
44
  },
45
- "_vendor-codemirror-gE_JCOgX.js": {
46
- "file": "assets/vendor-codemirror-gE_JCOgX.js",
45
+ "_vendor-codemirror-BEGorXSV.js": {
46
+ "file": "assets/vendor-codemirror-BEGorXSV.js",
47
47
  "name": "vendor-codemirror",
48
48
  "imports": [
49
- "_vendor-BC3OPQuM.js"
49
+ "_vendor-B5IC0ivG.js"
50
50
  ]
51
51
  },
52
- "_vendor-recharts-BXLYwcXF.js": {
53
- "file": "assets/vendor-recharts-BXLYwcXF.js",
52
+ "_vendor-recharts-6nUU7gU_.js": {
53
+ "file": "assets/vendor-recharts-6nUU7gU_.js",
54
54
  "name": "vendor-recharts",
55
55
  "imports": [
56
- "_vendor-BC3OPQuM.js"
56
+ "_vendor-B5IC0ivG.js"
57
57
  ]
58
58
  },
59
59
  "_vendor-three-DwGkEfCM.js": {
@@ -61,18 +61,18 @@
61
61
  "name": "vendor-three"
62
62
  },
63
63
  "index.tsx": {
64
- "file": "assets/index-fq1-hCK4.js",
64
+ "file": "assets/index-DAPJxlCw.js",
65
65
  "name": "index",
66
66
  "src": "index.tsx",
67
67
  "isEntry": true,
68
68
  "imports": [
69
- "_vendor-BC3OPQuM.js",
70
- "_vendor-arizeai-NjB3cZzD.js",
71
- "_pages-CnTvEGEN.js",
72
- "_components-Dte7_KRd.js",
69
+ "_vendor-B5IC0ivG.js",
70
+ "_vendor-arizeai-aFbT4kl1.js",
71
+ "_pages-1VrMk2pW.js",
72
+ "_components-REunxTt6.js",
73
73
  "_vendor-three-DwGkEfCM.js",
74
- "_vendor-recharts-BXLYwcXF.js",
75
- "_vendor-codemirror-gE_JCOgX.js"
74
+ "_vendor-recharts-6nUU7gU_.js",
75
+ "_vendor-codemirror-BEGorXSV.js"
76
76
  ]
77
77
  }
78
78
  }