arize-phoenix 8.22.1__py3-none-any.whl → 8.24.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.
- {arize_phoenix-8.22.1.dist-info → arize_phoenix-8.24.0.dist-info}/METADATA +22 -2
- {arize_phoenix-8.22.1.dist-info → arize_phoenix-8.24.0.dist-info}/RECORD +31 -29
- phoenix/config.py +65 -3
- phoenix/db/facilitator.py +118 -85
- phoenix/db/helpers.py +16 -0
- phoenix/server/api/context.py +2 -0
- phoenix/server/api/mutations/user_mutations.py +10 -0
- phoenix/server/api/queries.py +3 -14
- phoenix/server/api/routers/v1/__init__.py +2 -0
- phoenix/server/api/routers/v1/projects.py +393 -0
- phoenix/server/api/subscriptions.py +1 -1
- phoenix/server/app.py +17 -0
- phoenix/server/email/sender.py +74 -47
- phoenix/server/email/templates/welcome.html +12 -0
- phoenix/server/email/types.py +16 -1
- phoenix/server/main.py +8 -1
- phoenix/server/static/.vite/manifest.json +36 -36
- phoenix/server/static/assets/{components-BAc4OPED.js → components-B6cljCxu.js} +82 -82
- phoenix/server/static/assets/{index-Du53xkjY.js → index-DfHKoAV9.js} +2 -2
- phoenix/server/static/assets/{pages-Dz-gbBPF.js → pages-Dhitcl5V.js} +342 -339
- phoenix/server/static/assets/{vendor-CEisxXSv.js → vendor-C3H3sezv.js} +1 -1
- phoenix/server/static/assets/{vendor-arizeai-BCTsSnvS.js → vendor-arizeai-DT8pwHfH.js} +1 -1
- phoenix/server/static/assets/{vendor-codemirror-DIWnRs_7.js → vendor-codemirror-DvimrGxD.js} +1 -1
- phoenix/server/static/assets/{vendor-recharts-Bame54mG.js → vendor-recharts-DuSQBcYW.js} +1 -1
- phoenix/server/static/assets/{vendor-shiki-Cc73E4D-.js → vendor-shiki-i05Hmswh.js} +1 -1
- phoenix/session/session.py +10 -0
- phoenix/version.py +1 -1
- {arize_phoenix-8.22.1.dist-info → arize_phoenix-8.24.0.dist-info}/WHEEL +0 -0
- {arize_phoenix-8.22.1.dist-info → arize_phoenix-8.24.0.dist-info}/entry_points.txt +0 -0
- {arize_phoenix-8.22.1.dist-info → arize_phoenix-8.24.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-8.22.1.dist-info → arize_phoenix-8.24.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arize-phoenix
|
|
3
|
-
Version: 8.
|
|
3
|
+
Version: 8.24.0
|
|
4
4
|
Summary: AI Observability and Evaluation
|
|
5
5
|
Project-URL: Documentation, https://docs.arize.com/phoenix/
|
|
6
6
|
Project-URL: Issues, https://github.com/Arize-ai/phoenix/issues
|
|
@@ -141,7 +141,10 @@ Description-Content-Type: text/markdown
|
|
|
141
141
|
<a target="_blank" href="https://arize-ai.slack.com/join/shared_invite/zt-11t1vbu4x-xkBIHmOREQnYnYDH1GDfCg?__hstc=259489365.a667dfafcfa0169c8aee4178d115dc81.1733501603539.1733501603539.1733501603539.1&__hssc=259489365.1.1733501603539&__hsfp=3822854628&submissionGuid=381a0676-8f38-437b-96f2-fc10875658df#/shared-invite/email">
|
|
142
142
|
<img src="https://img.shields.io/static/v1?message=Community&logo=slack&labelColor=grey&color=blue&logoColor=white&label=%20"/>
|
|
143
143
|
</a>
|
|
144
|
-
|
|
144
|
+
<a target="_blank" href="https://bsky.app/profile/arize-phoenix.bsky.social">
|
|
145
|
+
<img src="https://img.shields.io/badge/-phoenix-blue.svg?color=blue&labelColor=gray&logo=bluesky">
|
|
146
|
+
</a>
|
|
147
|
+
<a target="_blank" href="https://x.com/ArizePhoenix">
|
|
145
148
|
<img src="https://img.shields.io/badge/-ArizePhoenix-blue.svg?color=blue&labelColor=gray&logo=x">
|
|
146
149
|
</a>
|
|
147
150
|
<a target="_blank" href="https://pypi.org/project/arize-phoenix/">
|
|
@@ -156,6 +159,9 @@ Description-Content-Type: text/markdown
|
|
|
156
159
|
<a target="_blank" href="https://hub.docker.com/r/arizephoenix/phoenix/tags">
|
|
157
160
|
<img src="https://img.shields.io/docker/v/arizephoenix/phoenix?sort=semver&logo=docker&label=image&color=blue">
|
|
158
161
|
</a>
|
|
162
|
+
<a target="_blank" href="https://github.com/Arize-ai/phoenix/tree/main/js/packages/phoenix-mcp">
|
|
163
|
+
<img src="https://badge.mcpx.dev?status=on" title="MCP Enabled"/>
|
|
164
|
+
</a>
|
|
159
165
|
</p>
|
|
160
166
|
|
|
161
167
|
Phoenix is an open-source AI observability platform designed for experimentation, evaluation, and troubleshooting. It provides:
|
|
@@ -181,6 +187,20 @@ pip install arize-phoenix
|
|
|
181
187
|
|
|
182
188
|
Phoenix container images are available via [Docker Hub](https://hub.docker.com/r/arizephoenix/phoenix) and can be deployed using Docker or Kubernetes.
|
|
183
189
|
|
|
190
|
+
## Packages
|
|
191
|
+
|
|
192
|
+
The `arize-phoenix` package includes the entire Phoenix platfom. However if you have deployed the Phoenix platform, there are light-weight Python sub-packages and TypeScript packages that can be used in conjunction with the platfrom.
|
|
193
|
+
|
|
194
|
+
### Subpackages
|
|
195
|
+
|
|
196
|
+
| Package | Language | Description |
|
|
197
|
+
| --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
198
|
+
| [arize-phoenix-otel](https://github.com/Arize-ai/phoenix/tree/main/packages/phoenix-otel) | Python [](https://pypi.org/project/arize-phoenix-otel/) | Provides a lightweight wrapper around OpenTelemetry primitives with Phoenix-aware defaults |
|
|
199
|
+
| [arize-phoenix-client](https://github.com/Arize-ai/phoenix/tree/main/packages/phoenix-client) | Python [](https://pypi.org/project/arize-phoenix-client/) | Lightweight client for interacting with the Phoenix server via its OpenAPI REST interface |
|
|
200
|
+
| [arize-phoenix-evals](https://github.com/Arize-ai/phoenix/tree/main/packages/phoenix-evals) | Python [](https://pypi.org/project/arize-phoenix-evals/) | Tooling to evaluate LLM applications including RAG relevance, answer relevance, and more |
|
|
201
|
+
| [@arizeai/phoenix-client](https://github.com/Arize-ai/phoenix/tree/main/js/packages/phoenix-client) | JavaScript [](https://www.npmjs.com/package/@arizeai/phoenix-client) | Client for the Arize Phoenix API |
|
|
202
|
+
| [@arizeai/phoenix-mcp](https://github.com/Arize-ai/phoenix/tree/main/js/packages/phoenix-mcp) | JavaScript [](https://www.npmjs.com/package/@arizeai/phoenix-mcp) | MCP server implementation for Arize Phoenix providing unified interface to Phoenix's capabilities |
|
|
203
|
+
|
|
184
204
|
## Tracing Integrations
|
|
185
205
|
|
|
186
206
|
Phoenix is built on top of OpenTelemetry and is vendor, language, and framework agnostic. For details about tracing integrations and example applications, see the [OpenInference](https://github.com/Arize-ai/openinference) project.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
phoenix/__init__.py,sha256=X3eUEwd2rG8KKWWYVNNDJoqo08ihfjgHhlP29dcdNJE,5481
|
|
2
2
|
phoenix/auth.py,sha256=VVMHrWN31tln3Zo4z6ofecrV4daiqJjLd8r85mqlxek,10939
|
|
3
|
-
phoenix/config.py,sha256=
|
|
3
|
+
phoenix/config.py,sha256=SNBMUEo6lNn5WXP-x6B85UmADZfGEkhfeHDDfW3Af5Y,38090
|
|
4
4
|
phoenix/datetime_utils.py,sha256=iJzNG6YJ6V7_u8B2iA7P2Z26FyxYbOPtx0dhJ7kNDHA,3398
|
|
5
5
|
phoenix/exceptions.py,sha256=n2L2KKuecrdflB9MsCdAYCiSEvGJptIsfRkXMoJle7A,169
|
|
6
6
|
phoenix/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
7
7
|
phoenix/services.py,sha256=kpW1WL0kiB8XJsO6XycvZVJ-lBkNoenhQ7atCvBoSe8,5365
|
|
8
8
|
phoenix/settings.py,sha256=x87BX7hWGQQZbrW_vrYqFR_izCGfO9gFc--JXUG4Tdk,754
|
|
9
|
-
phoenix/version.py,sha256=
|
|
9
|
+
phoenix/version.py,sha256=gGnFGQFB9qEAewyp9uBaFtO7FGPLu10guHACzqxWA_g,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
|
|
@@ -18,8 +18,8 @@ phoenix/db/alembic.ini,sha256=GIS6HpHaKaJbbuahZg1Rc1D2_QqyCkV9r58wdARGf6w,3262
|
|
|
18
18
|
phoenix/db/bulk_inserter.py,sha256=faNjuwLqqsw4ky8sa4D0h9u5TvEDTOjrccUW89L008E,12725
|
|
19
19
|
phoenix/db/engines.py,sha256=gxcP2aNy_JyKHv1MO4d2UM47GTMy1jDcN-FQETZ3iNA,7348
|
|
20
20
|
phoenix/db/enums.py,sha256=tt7iovXLhVTLZ3_LbHNGgcI44SnNjXfkKtLAZG57T54,428
|
|
21
|
-
phoenix/db/facilitator.py,sha256
|
|
22
|
-
phoenix/db/helpers.py,sha256=
|
|
21
|
+
phoenix/db/facilitator.py,sha256=-uzv3Wr4YuUtVff4_0bLTcIFvzWzWOA3IPq8s2RA_bc,6821
|
|
22
|
+
phoenix/db/helpers.py,sha256=rbbHcl-STzcEpcXCYx6jbKzko7r3ggrWHHsXjZ48HsM,5352
|
|
23
23
|
phoenix/db/migrate.py,sha256=oUrXH8yEbcpL4eh09aSCuUiSrhFli0eT5D_j4ZmYChY,2797
|
|
24
24
|
phoenix/db/models.py,sha256=TbHgtT7WWdkQK-OtLsUqp3MwP23HGV1IaSAWTqCf5ac,45707
|
|
25
25
|
phoenix/db/insertion/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -80,13 +80,13 @@ phoenix/pointcloud/pointcloud.py,sha256=SN_1wXZcwKrtSnHGZLDZGx71orqE1WyVF7E-D58d
|
|
|
80
80
|
phoenix/pointcloud/projectors.py,sha256=TQgwc9cJDjJkin1WZyZzgl3HsYrLLiyWD7Czy4jNW3U,1088
|
|
81
81
|
phoenix/pointcloud/umap_parameters.py,sha256=db_WEPoamuWtopZx7tQfAXPnoE0MS8FkAV0_ThjEx_Q,1735
|
|
82
82
|
phoenix/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
83
|
-
phoenix/server/app.py,sha256=
|
|
83
|
+
phoenix/server/app.py,sha256=f9H8ekaGiMwCMbSf2kbQIUFKywY2VmaIuJgPlrC7obU,40221
|
|
84
84
|
phoenix/server/bearer_auth.py,sha256=0UudvkAS_dxna5JEJJhGUYwB6Ny-e22ssX5Mm79QwCk,5907
|
|
85
85
|
phoenix/server/dml_event.py,sha256=MjJmVEKytq75chBOSyvYDusUnEbg1pHpIjR3pZkUaJA,2838
|
|
86
86
|
phoenix/server/dml_event_handler.py,sha256=EZLXmCvx4pJrCkz29gxwKwmvmUkTtPCHw6klR-XM8qE,8258
|
|
87
87
|
phoenix/server/grpc_server.py,sha256=SknR-iLIUqU9swiAyLhbCUREA1obOM6w49xgdK1AQDs,3839
|
|
88
88
|
phoenix/server/jwt_store.py,sha256=asxzY4_ZBM2FWAMstHvhvnKUP_0AA3v3xPTL2IOgNqY,16831
|
|
89
|
-
phoenix/server/main.py,sha256=
|
|
89
|
+
phoenix/server/main.py,sha256=qJcqXudiY65UeOWOaxP1-dqzrV-BBnbZu_IDd-qMKMk,16720
|
|
90
90
|
phoenix/server/oauth2.py,sha256=EV4wcCwG0N7cJRcfGNURdP5rZgRVCeRDvXyle19A27Y,2064
|
|
91
91
|
phoenix/server/prometheus.py,sha256=1KjvSfjSa2-BPjDybVMM_Kag316CsN-Zwt64YNr_snc,7825
|
|
92
92
|
phoenix/server/rate_limiters.py,sha256=cFc73D2NaxqNZZDbwfIDw4So-fRVOJPBtqxOZ8Qky_s,7155
|
|
@@ -96,12 +96,12 @@ phoenix/server/types.py,sha256=gJJPBcDRkQ9VHZIt_aLqG_OBbGt1oWp4e3W3Jp61oKs,7409
|
|
|
96
96
|
phoenix/server/api/README.md,sha256=Pyq1PLPgTzXAswrfIhGXrjI3Skq8it2jTVnanT6Ba4Q,1162
|
|
97
97
|
phoenix/server/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
98
98
|
phoenix/server/api/auth.py,sha256=nywpmfMI1trZTbZRD3oBj4kFjzg_vnxDljcM431T1eY,1246
|
|
99
|
-
phoenix/server/api/context.py,sha256=
|
|
99
|
+
phoenix/server/api/context.py,sha256=gB9bNgdqqD4Bn-6V4zlKeILl6L76jynPtOuM_5UZBNw,6557
|
|
100
100
|
phoenix/server/api/exceptions.py,sha256=TA0JuY2YRnj35qGuMSQ8d0ToHum9gWm9W--3fSKHrX0,1171
|
|
101
101
|
phoenix/server/api/interceptor.py,sha256=ykDnoC_apUd-llVli3m1CW18kNSIgjz2qZ6m5JmPDu8,1294
|
|
102
|
-
phoenix/server/api/queries.py,sha256=
|
|
102
|
+
phoenix/server/api/queries.py,sha256=WNH1LxB7bobfOMdBJts-9_ncfVhv2LThAKu14MRPMpk,35810
|
|
103
103
|
phoenix/server/api/schema.py,sha256=fcs36xQwFF_Qe41_5cWR8wYpDvOrnbcyTeo5WNMbDsA,1702
|
|
104
|
-
phoenix/server/api/subscriptions.py,sha256=
|
|
104
|
+
phoenix/server/api/subscriptions.py,sha256=TnZhdoNHMXp1NkUVLA-eB54woll7FvxtsB2pLt1dO0w,23001
|
|
105
105
|
phoenix/server/api/utils.py,sha256=quCBRcusc6PUq9tJq7M8PgwFZp7nXgVAxtbw8feribY,833
|
|
106
106
|
phoenix/server/api/dataloaders/__init__.py,sha256=LiOF9d95TPOD3pYuE906trLq-_q1VP8JQ9B6SHicbbM,4360
|
|
107
107
|
phoenix/server/api/dataloaders/annotation_summaries.py,sha256=2sHmIDX7n8tuPeBTs9bMKtlMKWn_Ph9awTZqmwn2Owc,5505
|
|
@@ -201,7 +201,7 @@ phoenix/server/api/mutations/prompt_version_tag_mutations.py,sha256=t77osYb5he2A
|
|
|
201
201
|
phoenix/server/api/mutations/span_annotations_mutations.py,sha256=sumBLUqRKlgMASWdWwYItmIJ2l7AyAp_PlIYeXYfguc,5970
|
|
202
202
|
phoenix/server/api/mutations/trace_annotations_mutations.py,sha256=sEcEt8hbMt8YMiRX5o3xcJ5rWWZbDeBPMNz2teZoi3U,5945
|
|
203
203
|
phoenix/server/api/mutations/trace_mutations.py,sha256=D5h2HYdlTo6yYZNq-O-PjaS9GeiZHxxVaOxDdh7fwjw,2957
|
|
204
|
-
phoenix/server/api/mutations/user_mutations.py,sha256=
|
|
204
|
+
phoenix/server/api/mutations/user_mutations.py,sha256=5pgtNKZwOnP1pbuegnNMwPn5u4teeWJ6vZSIcjDSCUg,14055
|
|
205
205
|
phoenix/server/api/openapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
206
206
|
phoenix/server/api/openapi/main.py,sha256=yKdzJYI4cxy_1mFcK4_7YObIcuRviBIfwNjB23RG14k,461
|
|
207
207
|
phoenix/server/api/openapi/schema.py,sha256=WGmHWSIyJhtc5EIh_M3vlXU-EgHkFuTlyVofgS0kj1I,529
|
|
@@ -210,13 +210,14 @@ phoenix/server/api/routers/auth.py,sha256=T774FE5mqrfRSSYo1snpR5NIp3YzAJnsLsY9FJ
|
|
|
210
210
|
phoenix/server/api/routers/embeddings.py,sha256=BpZGJee0pdL0W5Rp1L0b30dEtZTgJeVqXky8LgZ0ZXw,898
|
|
211
211
|
phoenix/server/api/routers/oauth2.py,sha256=qXSUmnHPoRS8extKB8qUD8zbnUerAHeoEyEiVPiCyek,17285
|
|
212
212
|
phoenix/server/api/routers/utils.py,sha256=M41BoH-fl37izhRuN2aX7lWm7jOC20A_3uClv9TVUUY,583
|
|
213
|
-
phoenix/server/api/routers/v1/__init__.py,sha256=
|
|
213
|
+
phoenix/server/api/routers/v1/__init__.py,sha256=_diWcDH9P49f_p0g2E8zkh-eVQkcSYCVdBO_zLccS8o,2393
|
|
214
214
|
phoenix/server/api/routers/v1/datasets.py,sha256=gHlF4x0EmWiJ-8vwJygoh0bO3gvDBmi6vYnLAbSkQw4,37057
|
|
215
215
|
phoenix/server/api/routers/v1/evaluations.py,sha256=RpOkTylp5Da6BvPZGuN8ksnxz_BVXRIwyOvwX9Iko8U,12647
|
|
216
216
|
phoenix/server/api/routers/v1/experiment_evaluations.py,sha256=vx4CKlE84sAL1vtPiM_XWnbfrATQujOSzzduJDYgcyM,4829
|
|
217
217
|
phoenix/server/api/routers/v1/experiment_runs.py,sha256=bInuasRv7ogiYf8fq-LwpJ5tptmMQsBNDlJAqwdymko,6378
|
|
218
218
|
phoenix/server/api/routers/v1/experiments.py,sha256=V9_sxqLTE1MKGFu9H3FEdGKr70lYMbGZx813MGaavfQ,20430
|
|
219
219
|
phoenix/server/api/routers/v1/models.py,sha256=r0nM2kFJ3mxDqgc5vFr1cjNuyOPs3RIKE_DS2VMdF48,1749
|
|
220
|
+
phoenix/server/api/routers/v1/projects.py,sha256=qv4RffYVGCJQuJ3FzaTVkueY8qKY0-GUbG9eSdm7oRY,14181
|
|
220
221
|
phoenix/server/api/routers/v1/prompts.py,sha256=aBOUBwLDzZDIzJQkxJcR8ZKnakNJOLMwzsLKINSs1mA,26545
|
|
221
222
|
phoenix/server/api/routers/v1/spans.py,sha256=uoU_bwIgz86fuvPjP5sX8goDyuCcnsTig-x3f17p60U,9625
|
|
222
223
|
phoenix/server/api/routers/v1/traces.py,sha256=hSv35QIB4mwFgp53rOpz3zWIiSwbZzQnjafD790QuJU,7908
|
|
@@ -299,10 +300,11 @@ phoenix/server/api/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
|
|
|
299
300
|
phoenix/server/api/types/node.py,sha256=BLl_IOFr0zrqUxaAtGLGui5aeM5VNVXFTzGeAKrztr0,822
|
|
300
301
|
phoenix/server/api/types/pagination.py,sha256=BXm46gXZfrBS4hpiLvVSEdsbb29ctUMVJYjKXlOLxUA,9064
|
|
301
302
|
phoenix/server/email/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
302
|
-
phoenix/server/email/sender.py,sha256=
|
|
303
|
-
phoenix/server/email/types.py,sha256=
|
|
303
|
+
phoenix/server/email/sender.py,sha256=PU6YVnsoIT9ClYrTyuii-lklifHjdMmN7dStR4f0ZFE,4066
|
|
304
|
+
phoenix/server/email/types.py,sha256=IO2bTtCh-1cve-xiM4MWnunCCVNOQ3Z2cqTqF7vH-do,466
|
|
304
305
|
phoenix/server/email/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
305
306
|
phoenix/server/email/templates/password_reset.html,sha256=jv0Pe-06JloPZcubRWxPdAFHYEn9eDj_4SjmuoIwshI,441
|
|
307
|
+
phoenix/server/email/templates/welcome.html,sha256=E7RaTUH8VBGMcZ5RYIUukiKigJdLJ3_L6FsgoLkazEE,299
|
|
306
308
|
phoenix/server/openapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
307
309
|
phoenix/server/static/apple-touch-icon-114x114.png,sha256=xtFVXAYQnJkpUApg2D1hltSTuyO4Is4sD4A0ZkikiVU,9486
|
|
308
310
|
phoenix/server/static/apple-touch-icon-120x120.png,sha256=iqZVAk634BbjJMozA8aHYyw15JjhIlIrG41FA2DFFaE,9957
|
|
@@ -314,16 +316,16 @@ phoenix/server/static/apple-touch-icon-76x76.png,sha256=CT_xT12I0u2i0WU8JzBZBuOQ
|
|
|
314
316
|
phoenix/server/static/apple-touch-icon.png,sha256=fOfpjqGpWYbJ0eAurKsyoZP1EAs6ZVooBJ_SGk2ZkDs,3801
|
|
315
317
|
phoenix/server/static/favicon.ico,sha256=bY0vvCKRftemZfPShwZtE93DiiQdaYaozkPGwNFr6H8,34494
|
|
316
318
|
phoenix/server/static/modernizr.js,sha256=mvK-XtkNqjOral-QvzoqsyOMECXIMu5BQwSVN_wcU9c,2564
|
|
317
|
-
phoenix/server/static/.vite/manifest.json,sha256=
|
|
318
|
-
phoenix/server/static/assets/components-
|
|
319
|
-
phoenix/server/static/assets/index-
|
|
320
|
-
phoenix/server/static/assets/pages-
|
|
321
|
-
phoenix/server/static/assets/vendor-
|
|
319
|
+
phoenix/server/static/.vite/manifest.json,sha256=HoxYrYg2l2nq5chl9GKo0pnga42JFMShSC0Kr4wLX5o,2165
|
|
320
|
+
phoenix/server/static/assets/components-B6cljCxu.js,sha256=VQZ_xj8sB55iva4mG-oFIPElEaArjo0-GF8hWOQ7L8c,455620
|
|
321
|
+
phoenix/server/static/assets/index-DfHKoAV9.js,sha256=OmqicccaM0lPOLABn3D6U-igVTGSREXbM4ZFiKWYGMI,60397
|
|
322
|
+
phoenix/server/static/assets/pages-Dhitcl5V.js,sha256=CdjHOTXth5jc1mqFO-ofYgFTglKUtVlBwiYEK8SBRJE,863027
|
|
323
|
+
phoenix/server/static/assets/vendor-C3H3sezv.js,sha256=Ask2DJPiTV2k5gNIopK_nOQFKCysUP5Z7h1gWVsFB3c,2510162
|
|
322
324
|
phoenix/server/static/assets/vendor-Cg6lcjUC.css,sha256=nZrkr0u6NNElFGvpWHk9GTHeGoibCXCli1bE7mXZGZg,1816
|
|
323
|
-
phoenix/server/static/assets/vendor-arizeai-
|
|
324
|
-
phoenix/server/static/assets/vendor-codemirror-
|
|
325
|
-
phoenix/server/static/assets/vendor-recharts-
|
|
326
|
-
phoenix/server/static/assets/vendor-shiki-
|
|
325
|
+
phoenix/server/static/assets/vendor-arizeai-DT8pwHfH.js,sha256=JraS2i2tsj4ToS_KGjq9BWS5zv4_FohPgF-vPV3MxEE,193248
|
|
326
|
+
phoenix/server/static/assets/vendor-codemirror-DvimrGxD.js,sha256=PC19rYrHBq_ICslA4t7DQpbazcNj5o0sA43H-j_Hzy0,781264
|
|
327
|
+
phoenix/server/static/assets/vendor-recharts-DuSQBcYW.js,sha256=FMnvLAuTQLvIU1IrPvWcIQNhSSSmOqf459tvo4kc8bA,282109
|
|
328
|
+
phoenix/server/static/assets/vendor-shiki-i05Hmswh.js,sha256=FQAOrcfIN8lFrk8VXMO6xMcnJdBxXh6CVvyMA8ZXj-M,8980312
|
|
327
329
|
phoenix/server/static/assets/vendor-three-C5WAXd5r.js,sha256=ELkg06u70N7h8oFmvqdoHyPuUf9VgGEWeT4LKFx4VWo,620975
|
|
328
330
|
phoenix/server/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
329
331
|
phoenix/server/templates/index.html,sha256=e8_jdi7Eo19SK7DI_gglkTW094D17E0VAegoMmmmvIc,4330
|
|
@@ -331,7 +333,7 @@ phoenix/session/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
331
333
|
phoenix/session/client.py,sha256=nXSn2Zmf9wTxgSe11Y9kKSLClkG8pNEAJgfEmy4mPm8,35234
|
|
332
334
|
phoenix/session/data_extractor.py,sha256=Y0RzYFaNy9fQj8PEIeQ76TBZ90_E1FW7bXu3K5x0EZY,2782
|
|
333
335
|
phoenix/session/evaluation.py,sha256=Q3fOMNELvqkk-b6a6PKc8pDJdsNQ0ZbTpseUSA2NKqs,5300
|
|
334
|
-
phoenix/session/session.py,sha256=
|
|
336
|
+
phoenix/session/session.py,sha256=paP01LC8g3_ABbiQRbqSaIcXaUa7I0WpbcLf-IeR3-k,28199
|
|
335
337
|
phoenix/trace/__init__.py,sha256=ujk_uYjM8gmm-YqnyXxF-kekfwid0bcaPMTtNNcaw6U,407
|
|
336
338
|
phoenix/trace/attributes.py,sha256=hyEKYZWPCP4NRmW7VmiC2voa3TH7FYKUBR9DYiVfXlw,12627
|
|
337
339
|
phoenix/trace/errors.py,sha256=wB1z8qdPckngdfU-TORToekvg3344oNFAA83_hC2yFY,180
|
|
@@ -364,9 +366,9 @@ phoenix/utilities/project.py,sha256=auVpARXkDb-JgeX5f2aStyFIkeKvGwN9l7qrFeJMVxI,
|
|
|
364
366
|
phoenix/utilities/re.py,sha256=6YyUWIkv0zc2SigsxfOWIHzdpjKA_TZo2iqKq7zJKvw,2081
|
|
365
367
|
phoenix/utilities/span_store.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
366
368
|
phoenix/utilities/template_formatters.py,sha256=gh9PJD6WEGw7TEYXfSst1UR4pWWwmjxMLrDVQ_CkpkQ,2779
|
|
367
|
-
arize_phoenix-8.
|
|
368
|
-
arize_phoenix-8.
|
|
369
|
-
arize_phoenix-8.
|
|
370
|
-
arize_phoenix-8.
|
|
371
|
-
arize_phoenix-8.
|
|
372
|
-
arize_phoenix-8.
|
|
369
|
+
arize_phoenix-8.24.0.dist-info/METADATA,sha256=Pceh1Pt4Y_uZNJas5q6Zyx2syDJg5xmVDP3Krvzk2ho,24495
|
|
370
|
+
arize_phoenix-8.24.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
371
|
+
arize_phoenix-8.24.0.dist-info/entry_points.txt,sha256=Pgpn8Upxx9P8z8joPXZWl2LlnAlGc3gcQoVchb06X1Q,94
|
|
372
|
+
arize_phoenix-8.24.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
|
|
373
|
+
arize_phoenix-8.24.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
|
|
374
|
+
arize_phoenix-8.24.0.dist-info/RECORD,,
|
phoenix/config.py
CHANGED
|
@@ -8,10 +8,11 @@ from enum import Enum
|
|
|
8
8
|
from importlib.metadata import version
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import Any, Optional, Union, cast, overload
|
|
11
|
-
from urllib.parse import quote_plus, urlparse
|
|
11
|
+
from urllib.parse import quote_plus, urljoin, urlparse
|
|
12
12
|
|
|
13
13
|
import wrapt
|
|
14
14
|
from email_validator import EmailNotValidError, validate_email
|
|
15
|
+
from starlette.datastructures import URL
|
|
15
16
|
|
|
16
17
|
from phoenix.utilities.logging import log_a_list
|
|
17
18
|
|
|
@@ -182,6 +183,19 @@ If the username or email address already exists in the database, the user record
|
|
|
182
183
|
modified, e.g., changed from non-admin to admin. Changing this environment variable for the next
|
|
183
184
|
startup will not undo any records created in previous startups.
|
|
184
185
|
"""
|
|
186
|
+
ENV_PHOENIX_ROOT_URL = "PHOENIX_ROOT_URL"
|
|
187
|
+
"""
|
|
188
|
+
This is the full URL used to access Phoenix from a web browser. This setting is important when
|
|
189
|
+
you have a reverse proxy in front of Phoenix. If the reverse proxy exposes Phoenix through a
|
|
190
|
+
sub-path, add that sub-path to the end of this URL setting.
|
|
191
|
+
|
|
192
|
+
WARNING: When a sub-path is needed, you must also specify the sub-path via the environment
|
|
193
|
+
variable PHOENIX_HOST_ROOT_PATH. Setting just this URL setting is not enough.
|
|
194
|
+
|
|
195
|
+
Examples:
|
|
196
|
+
- With a sub-path: "https://example.com/phoenix"
|
|
197
|
+
- Without a sub-path: "https://phoenix.example.com"
|
|
198
|
+
"""
|
|
185
199
|
|
|
186
200
|
|
|
187
201
|
# SMTP settings
|
|
@@ -209,7 +223,11 @@ ENV_PHOENIX_SMTP_VALIDATE_CERTS = "PHOENIX_SMTP_VALIDATE_CERTS"
|
|
|
209
223
|
"""
|
|
210
224
|
Whether to validate SMTP server certificates. Defaults to true.
|
|
211
225
|
"""
|
|
212
|
-
|
|
226
|
+
ENV_PHOENIX_ALLOWED_ORIGINS = "PHOENIX_ALLOWED_ORIGINS"
|
|
227
|
+
"""
|
|
228
|
+
List of allowed origins for CORS. Defaults to None.
|
|
229
|
+
When set to None, CORS is disabled.
|
|
230
|
+
"""
|
|
213
231
|
# API extension settings
|
|
214
232
|
ENV_PHOENIX_FASTAPI_MIDDLEWARE_PATHS = "PHOENIX_FASTAPI_MIDDLEWARE_PATHS"
|
|
215
233
|
ENV_PHOENIX_GQL_EXTENSION_PATHS = "PHOENIX_GQL_EXTENSION_PATHS"
|
|
@@ -824,7 +842,7 @@ def get_env_host() -> str:
|
|
|
824
842
|
|
|
825
843
|
|
|
826
844
|
def get_env_host_root_path() -> str:
|
|
827
|
-
if (host_root_path := getenv(ENV_PHOENIX_HOST_ROOT_PATH))
|
|
845
|
+
if not (host_root_path := getenv(ENV_PHOENIX_HOST_ROOT_PATH)):
|
|
828
846
|
return HOST_ROOT_PATH
|
|
829
847
|
if not host_root_path.startswith("/"):
|
|
830
848
|
raise ValueError(
|
|
@@ -890,7 +908,33 @@ def get_env_client_headers() -> dict[str, str]:
|
|
|
890
908
|
return headers
|
|
891
909
|
|
|
892
910
|
|
|
911
|
+
def get_env_root_url() -> URL:
|
|
912
|
+
"""
|
|
913
|
+
Get the URL used to access Phoenix from a web browser
|
|
914
|
+
|
|
915
|
+
Returns:
|
|
916
|
+
URL: The root URL of the Phoenix server
|
|
917
|
+
|
|
918
|
+
Note:
|
|
919
|
+
This is intended to replace the legacy `get_base_url()` helper function. In
|
|
920
|
+
particular, `get_env_collector_endpoint()` is really for the client and should be
|
|
921
|
+
deprecated on the server side.
|
|
922
|
+
"""
|
|
923
|
+
if root_url := getenv(ENV_PHOENIX_ROOT_URL):
|
|
924
|
+
result = urlparse(root_url)
|
|
925
|
+
if not result.scheme or not result.netloc:
|
|
926
|
+
raise ValueError(
|
|
927
|
+
f"The environment variable `{ENV_PHOENIX_ROOT_URL}` must be a valid URL."
|
|
928
|
+
)
|
|
929
|
+
return URL(root_url)
|
|
930
|
+
host = get_env_host()
|
|
931
|
+
if host == "0.0.0.0":
|
|
932
|
+
host = "127.0.0.1"
|
|
933
|
+
return URL(urljoin(f"http://{host}:{get_env_port()}", get_env_host_root_path()))
|
|
934
|
+
|
|
935
|
+
|
|
893
936
|
def get_base_url() -> str:
|
|
937
|
+
"""Deprecated: Use get_env_root_url() instead, but note the difference in behavior."""
|
|
894
938
|
host = get_env_host()
|
|
895
939
|
if host == "0.0.0.0":
|
|
896
940
|
host = "127.0.0.1"
|
|
@@ -1049,4 +1093,22 @@ def get_env_disable_migrations() -> bool:
|
|
|
1049
1093
|
DEFAULT_PROJECT_NAME = "default"
|
|
1050
1094
|
_KUBERNETES_PHOENIX_PORT_PATTERN = re.compile(r"^tcp://\d{1,3}[.]\d{1,3}[.]\d{1,3}[.]\d{1,3}:\d+$")
|
|
1051
1095
|
|
|
1096
|
+
|
|
1097
|
+
def get_env_allowed_origins() -> Optional[list[str]]:
|
|
1098
|
+
"""
|
|
1099
|
+
Gets the value of the PHOENIX_ALLOWED_ORIGINS environment variable.
|
|
1100
|
+
"""
|
|
1101
|
+
allowed_origins = getenv(ENV_PHOENIX_ALLOWED_ORIGINS)
|
|
1102
|
+
if allowed_origins is None:
|
|
1103
|
+
return None
|
|
1104
|
+
|
|
1105
|
+
return allowed_origins.split(",")
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
def verify_server_environment_variables() -> None:
|
|
1109
|
+
"""Verify that the environment variables are set correctly. Raises an error otherwise."""
|
|
1110
|
+
get_env_root_url()
|
|
1111
|
+
|
|
1112
|
+
|
|
1052
1113
|
SKLEARN_VERSION = cast(tuple[int, int], tuple(map(int, version("scikit-learn").split(".", 2)[:2])))
|
|
1114
|
+
PLAYGROUND_PROJECT_NAME = "playground"
|
phoenix/db/facilitator.py
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import logging
|
|
4
5
|
import secrets
|
|
6
|
+
from asyncio import gather
|
|
5
7
|
from functools import partial
|
|
8
|
+
from typing import Optional
|
|
6
9
|
|
|
7
10
|
from sqlalchemy import (
|
|
8
11
|
distinct,
|
|
9
12
|
insert,
|
|
10
13
|
select,
|
|
11
14
|
)
|
|
12
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
|
13
15
|
|
|
14
16
|
from phoenix.auth import (
|
|
15
17
|
DEFAULT_ADMIN_EMAIL,
|
|
@@ -25,8 +27,11 @@ from phoenix.config import (
|
|
|
25
27
|
)
|
|
26
28
|
from phoenix.db import models
|
|
27
29
|
from phoenix.db.enums import COLUMN_ENUMS, UserRole
|
|
30
|
+
from phoenix.server.email.types import WelcomeEmailSender
|
|
28
31
|
from phoenix.server.types import DbSessionFactory
|
|
29
32
|
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
30
35
|
|
|
31
36
|
class Facilitator:
|
|
32
37
|
"""
|
|
@@ -36,20 +41,25 @@ class Facilitator:
|
|
|
36
41
|
carried out as callbacks at the very beginning of Starlette's lifespan process.
|
|
37
42
|
"""
|
|
38
43
|
|
|
39
|
-
def __init__(
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
*,
|
|
47
|
+
db: DbSessionFactory,
|
|
48
|
+
email_sender: Optional[WelcomeEmailSender] = None,
|
|
49
|
+
) -> None:
|
|
40
50
|
self._db = db
|
|
51
|
+
self._email_sender = email_sender
|
|
41
52
|
|
|
42
53
|
async def __call__(self) -> None:
|
|
43
54
|
for fn in (
|
|
44
55
|
_ensure_enums,
|
|
45
56
|
_ensure_user_roles,
|
|
46
|
-
_ensure_admins,
|
|
57
|
+
partial(_ensure_admins, email_sender=self._email_sender),
|
|
47
58
|
):
|
|
48
|
-
|
|
49
|
-
await fn(session)
|
|
59
|
+
await fn(self._db)
|
|
50
60
|
|
|
51
61
|
|
|
52
|
-
async def _ensure_enums(
|
|
62
|
+
async def _ensure_enums(db: DbSessionFactory) -> None:
|
|
53
63
|
"""
|
|
54
64
|
Ensure that all enum values are present in their respective tables. If any values are missing,
|
|
55
65
|
they will be added. If any values are present in the database but not in the enum, an error will
|
|
@@ -57,99 +67,122 @@ async def _ensure_enums(session: AsyncSession) -> None:
|
|
|
57
67
|
"""
|
|
58
68
|
for column, enum in COLUMN_ENUMS.items():
|
|
59
69
|
table = column.class_
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
async with db() as session:
|
|
71
|
+
existing = set(
|
|
72
|
+
[_ async for _ in await session.stream_scalars(select(distinct(column)))]
|
|
73
|
+
)
|
|
74
|
+
expected = set(e.value for e in enum)
|
|
75
|
+
if unexpected := existing - expected:
|
|
76
|
+
raise ValueError(f"Unexpected values in {table.name}.{column.key}: {unexpected}")
|
|
77
|
+
if not (missing := expected - existing):
|
|
78
|
+
continue
|
|
79
|
+
await session.execute(insert(table), [{column.key: v} for v in missing])
|
|
67
80
|
|
|
68
81
|
|
|
69
|
-
async def _ensure_user_roles(
|
|
82
|
+
async def _ensure_user_roles(db: DbSessionFactory) -> None:
|
|
70
83
|
"""
|
|
71
84
|
Ensure that the system and admin roles are present in the database. If they are not, they will
|
|
72
85
|
be added. The system user will have the email "system@localhost" and the admin user will have
|
|
73
86
|
the email "admin@localhost".
|
|
74
87
|
"""
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
88
|
+
async with db() as session:
|
|
89
|
+
role_ids = {
|
|
90
|
+
name: id_
|
|
91
|
+
async for name, id_ in await session.stream(
|
|
92
|
+
select(models.UserRole.name, models.UserRole.id)
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
existing_roles = [
|
|
96
|
+
name
|
|
97
|
+
async for name in await session.stream_scalars(
|
|
98
|
+
select(distinct(models.UserRole.name)).join_from(models.User, models.UserRole)
|
|
99
|
+
)
|
|
100
|
+
]
|
|
101
|
+
if (system_role := UserRole.SYSTEM.value) not in existing_roles and (
|
|
102
|
+
system_role_id := role_ids.get(system_role)
|
|
103
|
+
) is not None:
|
|
104
|
+
system_user = models.User(
|
|
105
|
+
user_role_id=system_role_id,
|
|
106
|
+
username=DEFAULT_SYSTEM_USERNAME,
|
|
107
|
+
email=DEFAULT_SYSTEM_EMAIL,
|
|
108
|
+
reset_password=False,
|
|
109
|
+
password_salt=secrets.token_bytes(DEFAULT_SECRET_LENGTH),
|
|
110
|
+
password_hash=secrets.token_bytes(DEFAULT_SECRET_LENGTH),
|
|
111
|
+
)
|
|
112
|
+
session.add(system_user)
|
|
113
|
+
if (admin_role := UserRole.ADMIN.value) not in existing_roles and (
|
|
114
|
+
admin_role_id := role_ids.get(admin_role)
|
|
115
|
+
) is not None:
|
|
116
|
+
salt = secrets.token_bytes(DEFAULT_SECRET_LENGTH)
|
|
117
|
+
password = get_env_default_admin_initial_password()
|
|
118
|
+
compute = partial(compute_password_hash, password=password, salt=salt)
|
|
119
|
+
loop = asyncio.get_running_loop()
|
|
120
|
+
hash_ = await loop.run_in_executor(None, compute)
|
|
121
|
+
admin_user = models.User(
|
|
122
|
+
user_role_id=admin_role_id,
|
|
123
|
+
username=DEFAULT_ADMIN_USERNAME,
|
|
124
|
+
email=DEFAULT_ADMIN_EMAIL,
|
|
125
|
+
password_salt=salt,
|
|
126
|
+
password_hash=hash_,
|
|
127
|
+
reset_password=True,
|
|
128
|
+
)
|
|
129
|
+
session.add(admin_user)
|
|
130
|
+
await session.flush()
|
|
117
131
|
|
|
118
132
|
|
|
119
|
-
async def _ensure_admins(
|
|
133
|
+
async def _ensure_admins(
|
|
134
|
+
db: DbSessionFactory,
|
|
135
|
+
*,
|
|
136
|
+
email_sender: Optional[WelcomeEmailSender] = None,
|
|
137
|
+
) -> None:
|
|
120
138
|
"""
|
|
121
139
|
Ensure that all startup admin users are present in the database. If any are missing, they will
|
|
122
140
|
be added. Existing records will not be modified.
|
|
123
141
|
"""
|
|
124
142
|
if not (admins := get_env_admins()):
|
|
125
143
|
return
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return
|
|
132
|
-
existing_usernames = set(
|
|
133
|
-
await session.scalars(
|
|
134
|
-
select(models.User.username).where(models.User.username.in_(admins.values()))
|
|
144
|
+
async with db() as session:
|
|
145
|
+
existing_emails = set(
|
|
146
|
+
await session.scalars(
|
|
147
|
+
select(models.User.email).where(models.User.email.in_(admins.keys()))
|
|
148
|
+
)
|
|
135
149
|
)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
assert admin_role_id is not None, "Admin role not found in database"
|
|
146
|
-
for email, username in admins.items():
|
|
147
|
-
values = dict(
|
|
148
|
-
user_role_id=admin_role_id,
|
|
149
|
-
username=username,
|
|
150
|
-
email=email,
|
|
151
|
-
password_salt=secrets.token_bytes(DEFAULT_SECRET_LENGTH),
|
|
152
|
-
password_hash=secrets.token_bytes(DEFAULT_SECRET_LENGTH),
|
|
153
|
-
reset_password=True,
|
|
150
|
+
admins = {
|
|
151
|
+
email: username for email, username in admins.items() if email not in existing_emails
|
|
152
|
+
}
|
|
153
|
+
if not admins:
|
|
154
|
+
return
|
|
155
|
+
existing_usernames = set(
|
|
156
|
+
await session.scalars(
|
|
157
|
+
select(models.User.username).where(models.User.username.in_(admins.values()))
|
|
158
|
+
)
|
|
154
159
|
)
|
|
155
|
-
|
|
160
|
+
admins = {
|
|
161
|
+
email: username
|
|
162
|
+
for email, username in admins.items()
|
|
163
|
+
if username not in existing_usernames
|
|
164
|
+
}
|
|
165
|
+
if not admins:
|
|
166
|
+
return
|
|
167
|
+
admin_role_id = await session.scalar(
|
|
168
|
+
select(models.UserRole.id).filter_by(name=UserRole.ADMIN.value)
|
|
169
|
+
)
|
|
170
|
+
assert admin_role_id is not None, "Admin role not found in database"
|
|
171
|
+
for email, username in admins.items():
|
|
172
|
+
values = dict(
|
|
173
|
+
user_role_id=admin_role_id,
|
|
174
|
+
username=username,
|
|
175
|
+
email=email,
|
|
176
|
+
password_salt=secrets.token_bytes(DEFAULT_SECRET_LENGTH),
|
|
177
|
+
password_hash=secrets.token_bytes(DEFAULT_SECRET_LENGTH),
|
|
178
|
+
reset_password=True,
|
|
179
|
+
)
|
|
180
|
+
await session.execute(insert(models.User).values(values))
|
|
181
|
+
if email_sender is None:
|
|
182
|
+
return
|
|
183
|
+
for exc in await gather(
|
|
184
|
+
*(email_sender.send_welcome_email(email, username) for email, username in admins.items()),
|
|
185
|
+
return_exceptions=True,
|
|
186
|
+
):
|
|
187
|
+
if isinstance(exc, Exception):
|
|
188
|
+
logger.error(f"Failed to send welcome email: {exc}")
|
phoenix/db/helpers.py
CHANGED
|
@@ -19,6 +19,7 @@ from sqlalchemy import (
|
|
|
19
19
|
)
|
|
20
20
|
from typing_extensions import assert_never
|
|
21
21
|
|
|
22
|
+
from phoenix.config import PLAYGROUND_PROJECT_NAME
|
|
22
23
|
from phoenix.db import models
|
|
23
24
|
|
|
24
25
|
|
|
@@ -157,3 +158,18 @@ def get_dataset_example_revisions(
|
|
|
157
158
|
),
|
|
158
159
|
)
|
|
159
160
|
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
_AnyTuple = TypeVar("_AnyTuple", bound=tuple[Any, ...])
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def exclude_experiment_projects(
|
|
167
|
+
stmt: Select[_AnyTuple],
|
|
168
|
+
) -> Select[_AnyTuple]:
|
|
169
|
+
return stmt.outerjoin(
|
|
170
|
+
models.Experiment,
|
|
171
|
+
and_(
|
|
172
|
+
models.Project.name == models.Experiment.project_name,
|
|
173
|
+
models.Experiment.project_name != PLAYGROUND_PROJECT_NAME,
|
|
174
|
+
),
|
|
175
|
+
).where(models.Experiment.project_name.is_(None))
|
phoenix/server/api/context.py
CHANGED
|
@@ -53,6 +53,7 @@ from phoenix.server.api.dataloaders import (
|
|
|
53
53
|
)
|
|
54
54
|
from phoenix.server.bearer_auth import PhoenixUser
|
|
55
55
|
from phoenix.server.dml_event import DmlEvent
|
|
56
|
+
from phoenix.server.email.types import EmailSender
|
|
56
57
|
from phoenix.server.types import (
|
|
57
58
|
CanGetLastUpdatedAt,
|
|
58
59
|
CanPutItem,
|
|
@@ -124,6 +125,7 @@ class Context(BaseContext):
|
|
|
124
125
|
auth_enabled: bool = False
|
|
125
126
|
secret: Optional[str] = None
|
|
126
127
|
token_store: Optional[TokenStore] = None
|
|
128
|
+
email_sender: Optional[EmailSender] = None
|
|
127
129
|
|
|
128
130
|
def get_secret(self) -> str:
|
|
129
131
|
"""A type-safe way to get the application secret. Throws an error if the secret is not set.
|