arize-phoenix 4.15.0__py3-none-any.whl → 4.16.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-4.15.0.dist-info → arize_phoenix-4.16.0.dist-info}/METADATA +2 -1
- {arize_phoenix-4.15.0.dist-info → arize_phoenix-4.16.0.dist-info}/RECORD +29 -22
- phoenix/db/bulk_inserter.py +129 -2
- phoenix/db/helpers.py +23 -1
- phoenix/db/insertion/constants.py +2 -0
- phoenix/db/insertion/document_annotation.py +157 -0
- phoenix/db/insertion/helpers.py +13 -0
- phoenix/db/insertion/span_annotation.py +144 -0
- phoenix/db/insertion/trace_annotation.py +144 -0
- phoenix/db/insertion/types.py +261 -0
- phoenix/experiments/types.py +3 -3
- phoenix/server/api/input_types/SpanAnnotationSort.py +17 -0
- phoenix/server/api/input_types/TraceAnnotationSort.py +17 -0
- phoenix/server/api/routers/v1/evaluations.py +90 -4
- phoenix/server/api/routers/v1/spans.py +36 -46
- phoenix/server/api/routers/v1/traces.py +36 -48
- phoenix/server/api/types/Span.py +22 -3
- phoenix/server/api/types/Trace.py +21 -4
- phoenix/server/app.py +2 -0
- phoenix/server/static/.vite/manifest.json +14 -14
- phoenix/server/static/assets/{components-kGgeFkHp.js → components-Ci5kMOk5.js} +119 -126
- phoenix/server/static/assets/{index-BctFO6S7.js → index-BQG5WVX7.js} +2 -2
- phoenix/server/static/assets/{pages-DabDCmVd.js → pages-BrevprVW.js} +289 -213
- phoenix/server/static/assets/{vendor-arizeai-B5Hti8OB.js → vendor-arizeai-DTbiPGp6.js} +1 -1
- phoenix/trace/dsl/filter.py +2 -6
- phoenix/version.py +1 -1
- {arize_phoenix-4.15.0.dist-info → arize_phoenix-4.16.0.dist-info}/WHEEL +0 -0
- {arize_phoenix-4.15.0.dist-info → arize_phoenix-4.16.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-4.15.0.dist-info → arize_phoenix-4.16.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: arize-phoenix
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.16.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
|
|
@@ -72,6 +72,7 @@ Requires-Dist: anthropic; extra == 'dev'
|
|
|
72
72
|
Requires-Dist: arize[autoembeddings,llm-evaluation]; extra == 'dev'
|
|
73
73
|
Requires-Dist: asgi-lifespan; extra == 'dev'
|
|
74
74
|
Requires-Dist: asyncpg; extra == 'dev'
|
|
75
|
+
Requires-Dist: faker>=26.0.0; extra == 'dev'
|
|
75
76
|
Requires-Dist: gcsfs; extra == 'dev'
|
|
76
77
|
Requires-Dist: google-cloud-aiplatform>=1.3; extra == 'dev'
|
|
77
78
|
Requires-Dist: hatch; extra == 'dev'
|
|
@@ -5,7 +5,7 @@ phoenix/exceptions.py,sha256=n2L2KKuecrdflB9MsCdAYCiSEvGJptIsfRkXMoJle7A,169
|
|
|
5
5
|
phoenix/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
6
6
|
phoenix/services.py,sha256=aTxhcOA1pZHB6U-B3TEcp6fqDF5oT0xCUvEUNMZVTUQ,5175
|
|
7
7
|
phoenix/settings.py,sha256=cO-qgis_S27nHirTobYI9hHPfZH18R--WMmxNdsVUwc,273
|
|
8
|
-
phoenix/version.py,sha256=
|
|
8
|
+
phoenix/version.py,sha256=jqZFarxQPRfyIPY1ojpY1Nm-BNzQbn-cIh6gBXR-dN8,23
|
|
9
9
|
phoenix/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
phoenix/core/embedding_dimension.py,sha256=zKGbcvwOXgLf-yrJBpQyKtd-LEOPRKHnUToyAU8Owis,87
|
|
11
11
|
phoenix/core/model.py,sha256=km_a--PBHOuA337ClRw9xqhOHhrUT6Rl9pz_zV0JYkQ,4843
|
|
@@ -14,16 +14,21 @@ phoenix/core/model_schema_adapter.py,sha256=0Tm_Y_gV-WED8fKBCaFXAEFwE3CTEZS1dowq
|
|
|
14
14
|
phoenix/db/README.md,sha256=IvKaZyf9ECbGBYYePaRhBveKZwDbxAc-c7BMxJYZh6Q,595
|
|
15
15
|
phoenix/db/__init__.py,sha256=pDjEFXukHmJBM-1D8RjmXkvLsz85YWNxMQczt81ec3A,118
|
|
16
16
|
phoenix/db/alembic.ini,sha256=p8DjVqGUs_tTx8oU56JP7qj-rMUebNFizItUSv_hPhs,3763
|
|
17
|
-
phoenix/db/bulk_inserter.py,sha256=
|
|
17
|
+
phoenix/db/bulk_inserter.py,sha256=kBgR-4kr8LnqrN03UfTtXYOhwMf7IxUks4OViGo84h8,16418
|
|
18
18
|
phoenix/db/engines.py,sha256=R3btYTSOSd6BwRA59EmhhojL0HCQ7NnzFIXQrPYS0iU,4812
|
|
19
|
-
phoenix/db/helpers.py,sha256=
|
|
19
|
+
phoenix/db/helpers.py,sha256=2zSc4n5IJfu-CaOFoBfqTB35M1nTFcAc8tqLsNtF2Jw,3488
|
|
20
20
|
phoenix/db/migrate.py,sha256=MuhtNWnR24riROvarvKfbRb4_D5xuQi6P760vBUKl1E,2270
|
|
21
21
|
phoenix/db/models.py,sha256=7DBWbxY3cx3ve2P1I0kkDKXzlt04zEFJuRPJWsVpH-I,20422
|
|
22
22
|
phoenix/db/insertion/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
+
phoenix/db/insertion/constants.py,sha256=oLtCd7KxC6nQlGkhUoguF5PlomEVoZTddxc-huD3f2A,70
|
|
23
24
|
phoenix/db/insertion/dataset.py,sha256=_vxy5e6W5jEuvO2fMKbbNCn9JvHkwI4LRKk_10eKFVg,7171
|
|
25
|
+
phoenix/db/insertion/document_annotation.py,sha256=vMP1OkQ0GOC57aHLTErwF6cu6qHdBkjQFXbCwUtYYAs,5409
|
|
24
26
|
phoenix/db/insertion/evaluation.py,sha256=SoI85N3MYUSeNgjKa5WzFw14OfNjNTjExv-2m3sxaR8,6371
|
|
25
|
-
phoenix/db/insertion/helpers.py,sha256=
|
|
27
|
+
phoenix/db/insertion/helpers.py,sha256=z_Wnckhdf-F7xadWgaAV5ScXnLft8EtaYJCuIkma2Vw,3486
|
|
26
28
|
phoenix/db/insertion/span.py,sha256=T9jOW3lyWte-JGD7wlP2ZqtO0-V57d8z6U_TldnuGuk,5527
|
|
29
|
+
phoenix/db/insertion/span_annotation.py,sha256=Aa33nhEsq6NlXbRZ46fl2rVVehityRtxi6NYDXzE9vU,4705
|
|
30
|
+
phoenix/db/insertion/trace_annotation.py,sha256=eIza4k4ImOPu9iwlAt8gv-A0ESw5XLGEa4dKGVqNSxk,4765
|
|
31
|
+
phoenix/db/insertion/types.py,sha256=H9o_LJ1DabMVyVv3NZYtmJ26WusyihwlzV7AA-TOgGY,8272
|
|
27
32
|
phoenix/db/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
33
|
phoenix/db/migrations/env.py,sha256=QbzB5zrRs6XQQmrYeUpuzeilcMlM-MsbaAgHHYcIHTI,3626
|
|
29
34
|
phoenix/db/migrations/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635
|
|
@@ -33,7 +38,7 @@ phoenix/db/migrations/versions/cf03bd6bae1d_init.py,sha256=CbWT3ZTR0CZqeT3zWLoTW
|
|
|
33
38
|
phoenix/experiments/__init__.py,sha256=6JGwgUd7xCbGpuHqYZlsmErmYvVgv7N_j43bn3dUqsk,123
|
|
34
39
|
phoenix/experiments/functions.py,sha256=4XaOLE1Co9sW_yjM1sypQClmOLtt9kwoxmhIEJ3f_rk,32209
|
|
35
40
|
phoenix/experiments/tracing.py,sha256=wVpt8Ie9WNPoi1djJdcrkwCokHdTO0bicXViLg3O-1Y,2831
|
|
36
|
-
phoenix/experiments/types.py,sha256=
|
|
41
|
+
phoenix/experiments/types.py,sha256=SQqI-8CqA-bDTVIOUC1NOMi9bjEcDSlcG7o3Li1IBFY,23415
|
|
37
42
|
phoenix/experiments/utils.py,sha256=wLu5Kvt1b4a8rGPRWq5G8RQ9XSiV8fCIVm51zWBI3-g,758
|
|
38
43
|
phoenix/experiments/evaluators/__init__.py,sha256=j63fi3fa3U7-itVPHa82GowhjQRU-wO6yhO34u_lhsA,714
|
|
39
44
|
phoenix/experiments/evaluators/base.py,sha256=jAwJs-V7jCp2UBChL0S3813Xyd9GN4rU4IEhX0nkFGs,5549
|
|
@@ -60,7 +65,7 @@ phoenix/pointcloud/pointcloud.py,sha256=4zAIkKs2xOUbchpj4XDAV-iPMXrfAJ15TG6rlIYG
|
|
|
60
65
|
phoenix/pointcloud/projectors.py,sha256=zO_RrtDYSv2rqVOfIP2_9Cv11Dc8EmcZR94xhFcBYPU,1057
|
|
61
66
|
phoenix/pointcloud/umap_parameters.py,sha256=3UQSjrysVOvq2V4KNpTMqNqNiK0BsTZnPBHWZ4fyJtQ,1708
|
|
62
67
|
phoenix/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
63
|
-
phoenix/server/app.py,sha256=
|
|
68
|
+
phoenix/server/app.py,sha256=gAmGzOAhGO05IBEnBNfOc8Suw15_0_h0OCp-9RP0udQ,19098
|
|
64
69
|
phoenix/server/grpc_server.py,sha256=jllxDNkpLQxDkvej4RhTokobowbvydF-SU8gSw1MTCc,3378
|
|
65
70
|
phoenix/server/main.py,sha256=dvjv3g8ANpkvSGCUN02S2Yse643Nlwrp_bj4iXBSVTE,11082
|
|
66
71
|
phoenix/server/prometheus.py,sha256=j9DHB2fERuq_ZKmwVaqR-9wx5WcPPuU1Cm5Bhg5241Y,2996
|
|
@@ -124,8 +129,10 @@ phoenix/server/api/input_types/PatchAnnotationInput.py,sha256=NWhkcbcGNPwfOYsN3w
|
|
|
124
129
|
phoenix/server/api/input_types/PatchDatasetExamplesInput.py,sha256=E86aBGXDBC83jiEGwV5rilnoeQf6eqCfZ0aAVeIt2VI,890
|
|
125
130
|
phoenix/server/api/input_types/PatchDatasetInput.py,sha256=OURtTVY8Z_oFEDtKwT1LCMaOK5D4QYo5TVQ6mDrex-g,328
|
|
126
131
|
phoenix/server/api/input_types/PerformanceMetricInput.py,sha256=fElsLTSEYYgGFGMYTEGcYid39tXUKFdV_JkdHavMcbA,591
|
|
132
|
+
phoenix/server/api/input_types/SpanAnnotationSort.py,sha256=T5pAGzmh4MiJp9JMAzNDByFVTczfw02FH4WFWwFezyI,361
|
|
127
133
|
phoenix/server/api/input_types/SpanSort.py,sha256=BqV0DjI4m3RKFbzsHN47XNhJfFmErFIYg64c8EJAVMo,6312
|
|
128
134
|
phoenix/server/api/input_types/TimeRange.py,sha256=yzx-gxj8mDeGLft1FzU_x1MVEgIG5Pt6-f8PUVDgipQ,522
|
|
135
|
+
phoenix/server/api/input_types/TraceAnnotationSort.py,sha256=BzwiUnMh2VsgQYnhDlbJ6ljHugqIS4YDUlYzvq_tl3o,365
|
|
129
136
|
phoenix/server/api/input_types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
130
137
|
phoenix/server/api/mutations/__init__.py,sha256=UKUAhD5NY-ZI7XONnRRkaHoFuuU3idmE4fk6Sjgy18M,776
|
|
131
138
|
phoenix/server/api/mutations/auth.py,sha256=vPRFoj7J6PV6QeODewG4K0PhoOebS5AfMRpbi_wuhyQ,311
|
|
@@ -142,13 +149,13 @@ phoenix/server/api/routers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
|
|
|
142
149
|
phoenix/server/api/routers/utils.py,sha256=M41BoH-fl37izhRuN2aX7lWm7jOC20A_3uClv9TVUUY,583
|
|
143
150
|
phoenix/server/api/routers/v1/__init__.py,sha256=nb49zcOdAi3DSGuC9gUubN9Yri-o7-WFdlGak4jGuFw,1462
|
|
144
151
|
phoenix/server/api/routers/v1/datasets.py,sha256=QG6QeyfPi1tfBvVoa3jpNOcwtR5ntNfd3IW9rzP735Q,36920
|
|
145
|
-
phoenix/server/api/routers/v1/evaluations.py,sha256=
|
|
152
|
+
phoenix/server/api/routers/v1/evaluations.py,sha256=FSfz9MTi8s65F07abDXlb9-y97fDZSYbqsCXpimwO7g,12628
|
|
146
153
|
phoenix/server/api/routers/v1/experiment_evaluations.py,sha256=xoVhU71U3c1QJSXiKsAa4yiH-UAqkyVc7uKPyIHjrYY,4682
|
|
147
154
|
phoenix/server/api/routers/v1/experiment_runs.py,sha256=7qvLYgqH58nxqhTnJ0hPf0PBfVmZnbxquodSnEGoQxk,6059
|
|
148
155
|
phoenix/server/api/routers/v1/experiments.py,sha256=AFJb8Pi6SDKbIbkDRWUtPjikZIoRhc7cFLWm40l3mPk,9817
|
|
149
156
|
phoenix/server/api/routers/v1/pydantic_compat.py,sha256=FeK8oe2brqu-djsoqRxiKL4tw5cHmi89OHVfCFxYsAo,2890
|
|
150
|
-
phoenix/server/api/routers/v1/spans.py,sha256
|
|
151
|
-
phoenix/server/api/routers/v1/traces.py,sha256=
|
|
157
|
+
phoenix/server/api/routers/v1/spans.py,sha256=-nDcr0AjynS-0U6A2nxEQiUTLoNyfzE-3NiFObyfyVQ,8516
|
|
158
|
+
phoenix/server/api/routers/v1/traces.py,sha256=4k_57UICPfRcyQsdPBK-Xo_n6oZ1p41XUwXhZEQpXwE,7568
|
|
152
159
|
phoenix/server/api/routers/v1/utils.py,sha256=xvl2v-BKUkqmFVMmgmmWGFKuRBTrUdoiAeT3mCYEE68,3086
|
|
153
160
|
phoenix/server/api/types/Annotation.py,sha256=7Ym7iuVcbwHlw2yIRylz4nATAF_Cm-Z17qcjiooj1cc,751
|
|
154
161
|
phoenix/server/api/types/AnnotatorKind.py,sha256=rPgGdbN1Gvc109sGQ_ZH-gfJbp93V9wlarzTEJNtUwI,236
|
|
@@ -193,10 +200,10 @@ phoenix/server/api/types/Retrieval.py,sha256=OhMK2ncjoyp5h1yjKhjlKpoTbQrMHuxmgSF
|
|
|
193
200
|
phoenix/server/api/types/ScalarDriftMetricEnum.py,sha256=IUAcRPpgL41WdoIgK6cNk2Te38SspXGyEs-S1fY23_A,232
|
|
194
201
|
phoenix/server/api/types/Segments.py,sha256=m2yoegrxA1Tn7ZAy1rMjjD1isc752MaAXMoffkBlvrM,2921
|
|
195
202
|
phoenix/server/api/types/SortDir.py,sha256=OUpXhlCzCxPoXSDkJJygEs9Rw9pMymfaZUG5zPTrw4Y,152
|
|
196
|
-
phoenix/server/api/types/Span.py,sha256=
|
|
203
|
+
phoenix/server/api/types/Span.py,sha256=PRVjcC79qyxz8FW-YT90rDgcW8dkVO9_5pXxUXeH31g,15448
|
|
197
204
|
phoenix/server/api/types/SpanAnnotation.py,sha256=6b5G-b_OoRvDL2ayWk7MkbqarLK-F-pQMx21CpUuNGY,1168
|
|
198
205
|
phoenix/server/api/types/TimeSeries.py,sha256=wjzuxHFqCey0O7Ys25qiXyuqXK8an-osyNWUE8A_8G4,5227
|
|
199
|
-
phoenix/server/api/types/Trace.py,sha256
|
|
206
|
+
phoenix/server/api/types/Trace.py,sha256=-nh3A-S_BlQK1VSSOTWqM85l-WwJsRHifxeDi0sFWZE,3246
|
|
200
207
|
phoenix/server/api/types/TraceAnnotation.py,sha256=OW6A2zr1gomOuG0XQe55dk15XXX2DSM0DzatRbHWH5A,1256
|
|
201
208
|
phoenix/server/api/types/UMAPPoints.py,sha256=5sOuruzM8saXa8C2XiyUfk2XPrkVGmhqKpclMYRw1dk,1656
|
|
202
209
|
phoenix/server/api/types/ValidationResult.py,sha256=pHwdYk4J7SJ5xhlWWHg_6qWkfk4rjOx-bSkGHvkDE3Q,142
|
|
@@ -215,13 +222,13 @@ phoenix/server/static/apple-touch-icon-76x76.png,sha256=CT_xT12I0u2i0WU8JzBZBuOQ
|
|
|
215
222
|
phoenix/server/static/apple-touch-icon.png,sha256=fOfpjqGpWYbJ0eAurKsyoZP1EAs6ZVooBJ_SGk2ZkDs,3801
|
|
216
223
|
phoenix/server/static/favicon.ico,sha256=bY0vvCKRftemZfPShwZtE93DiiQdaYaozkPGwNFr6H8,34494
|
|
217
224
|
phoenix/server/static/modernizr.js,sha256=mvK-XtkNqjOral-QvzoqsyOMECXIMu5BQwSVN_wcU9c,2564
|
|
218
|
-
phoenix/server/static/.vite/manifest.json,sha256=
|
|
219
|
-
phoenix/server/static/assets/components-
|
|
220
|
-
phoenix/server/static/assets/index-
|
|
221
|
-
phoenix/server/static/assets/pages-
|
|
225
|
+
phoenix/server/static/.vite/manifest.json,sha256=Y5Vxm1VUdmt2c5ANmtD1Z0r2Q1mnApb-UhzLnyJxtU0,1929
|
|
226
|
+
phoenix/server/static/assets/components-Ci5kMOk5.js,sha256=WxnZ4voqivS6S9USAF9zhC5Q2AdvhaJTGFbkTwDLiUI,165385
|
|
227
|
+
phoenix/server/static/assets/index-BQG5WVX7.js,sha256=7RjUXpSAeTK-Q_mLrs4cWscpGyhtSIMI0RhsNn6SwMg,6347
|
|
228
|
+
phoenix/server/static/assets/pages-BrevprVW.js,sha256=GZiR--twaLN-wYV1jvIM-pJiuPbae-HReCjkXNZ5O_U,441239
|
|
222
229
|
phoenix/server/static/assets/vendor-CP0b0YG0.js,sha256=hoxXvVgcHofQrVahK5Q4hAMDaQiH6GygbQvdiXlhQAo,1355423
|
|
223
230
|
phoenix/server/static/assets/vendor-DxkFTwjz.css,sha256=nZrkr0u6NNElFGvpWHk9GTHeGoibCXCli1bE7mXZGZg,1816
|
|
224
|
-
phoenix/server/static/assets/vendor-arizeai-
|
|
231
|
+
phoenix/server/static/assets/vendor-arizeai-DTbiPGp6.js,sha256=-o-2f0ByoAbbFXyNrOogVNfdqmXKVMsuxhnuwTJqVLc,296355
|
|
225
232
|
phoenix/server/static/assets/vendor-codemirror-DtdPDzrv.js,sha256=ngcX7xxWUANIvplBuEqkDowqRkknIoX3gXUeQk-jtMQ,503031
|
|
226
233
|
phoenix/server/static/assets/vendor-recharts-A0DA1O99.js,sha256=Zp6oIbsFZPDN-M7VNovxGfJkg9XUISg1fM7eRMtHCaw,282859
|
|
227
234
|
phoenix/server/static/assets/vendor-three-DwGkEfCM.js,sha256=0D12ZgKzfKCTSdSTKJBFR2RZO_xxeMXrqDp0AszZqHY,620972
|
|
@@ -248,7 +255,7 @@ phoenix/trace/trace_dataset.py,sha256=Wq89jJ4hYQ1Qt-Uj11ZNzKQYQeKmGY6NqWStQiiTlM
|
|
|
248
255
|
phoenix/trace/utils.py,sha256=1SEQr37cdHOM0P3BdL1dszArj3Zm-VJQyb1BcJs_qO8,1833
|
|
249
256
|
phoenix/trace/dsl/README.md,sha256=ihmP9zGUC5V-TDbzKla76LuyDqPDQIBUH2BORwxNI68,2902
|
|
250
257
|
phoenix/trace/dsl/__init__.py,sha256=WIQIjJg362XD3s50OsPJJ0xbDsGp41bSv7vDllLrPuA,144
|
|
251
|
-
phoenix/trace/dsl/filter.py,sha256=
|
|
258
|
+
phoenix/trace/dsl/filter.py,sha256=9NwATCUOgJ4Pms8XsEcinROUuxZ9UW-ISV09o65Ms70,32600
|
|
252
259
|
phoenix/trace/dsl/helpers.py,sha256=ULAhqWULPqYWCSNX7y50DVKIqfySx86nqb6hDvZPnVk,3896
|
|
253
260
|
phoenix/trace/dsl/query.py,sha256=W0t-tiXh2WIVb96lzFAGQOQ-U46uKux78d4KL3rW-PE,30316
|
|
254
261
|
phoenix/trace/langchain/__init__.py,sha256=F37GfD1pd5Kuw7R7iRUM1zXXpO8xEcycNZh5dwqBXNk,109
|
|
@@ -269,8 +276,8 @@ phoenix/utilities/logging.py,sha256=lDXd6EGaamBNcQxL4vP1au9-i_SXe0OraUDiJOcszSw,
|
|
|
269
276
|
phoenix/utilities/project.py,sha256=8IJuMM4yUMoooPi37sictGj8Etu9rGmq6RFtc9848cQ,436
|
|
270
277
|
phoenix/utilities/re.py,sha256=PDve_OLjRTM8yQQJHC8-n3HdIONi7aNils3ZKRZ5uBM,2045
|
|
271
278
|
phoenix/utilities/span_store.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
272
|
-
arize_phoenix-4.
|
|
273
|
-
arize_phoenix-4.
|
|
274
|
-
arize_phoenix-4.
|
|
275
|
-
arize_phoenix-4.
|
|
276
|
-
arize_phoenix-4.
|
|
279
|
+
arize_phoenix-4.16.0.dist-info/METADATA,sha256=GEUUxZhBhXAU_6ikOQlpm6SN8-ECGptFZtdWKUQ553Y,11820
|
|
280
|
+
arize_phoenix-4.16.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
281
|
+
arize_phoenix-4.16.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
|
|
282
|
+
arize_phoenix-4.16.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
|
|
283
|
+
arize_phoenix-4.16.0.dist-info/RECORD,,
|
phoenix/db/bulk_inserter.py
CHANGED
|
@@ -1,26 +1,36 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
|
-
from asyncio import Queue
|
|
3
|
+
from asyncio import Queue, as_completed
|
|
4
|
+
from collections import defaultdict
|
|
4
5
|
from dataclasses import dataclass, field
|
|
5
6
|
from datetime import datetime, timezone
|
|
7
|
+
from functools import singledispatchmethod
|
|
6
8
|
from itertools import islice
|
|
7
9
|
from time import perf_counter
|
|
8
10
|
from typing import (
|
|
9
11
|
Any,
|
|
10
12
|
Awaitable,
|
|
11
13
|
Callable,
|
|
14
|
+
DefaultDict,
|
|
15
|
+
Dict,
|
|
12
16
|
Iterable,
|
|
13
17
|
List,
|
|
18
|
+
Mapping,
|
|
14
19
|
Optional,
|
|
15
20
|
Set,
|
|
16
21
|
Tuple,
|
|
22
|
+
Type,
|
|
17
23
|
cast,
|
|
18
24
|
)
|
|
19
25
|
|
|
20
26
|
from cachetools import LRUCache
|
|
27
|
+
from sqlalchemy import Select, select
|
|
21
28
|
from typing_extensions import TypeAlias
|
|
22
29
|
|
|
23
30
|
import phoenix.trace.v1 as pb
|
|
31
|
+
from phoenix.db import models
|
|
32
|
+
from phoenix.db.insertion.constants import DEFAULT_RETRY_ALLOWANCE, DEFAULT_RETRY_DELAY_SEC
|
|
33
|
+
from phoenix.db.insertion.document_annotation import DocumentAnnotationQueueInserter
|
|
24
34
|
from phoenix.db.insertion.evaluation import (
|
|
25
35
|
EvaluationInsertionEvent,
|
|
26
36
|
InsertEvaluationError,
|
|
@@ -28,6 +38,9 @@ from phoenix.db.insertion.evaluation import (
|
|
|
28
38
|
)
|
|
29
39
|
from phoenix.db.insertion.helpers import DataManipulation, DataManipulationEvent
|
|
30
40
|
from phoenix.db.insertion.span import SpanInsertionEvent, insert_span
|
|
41
|
+
from phoenix.db.insertion.span_annotation import SpanAnnotationQueueInserter
|
|
42
|
+
from phoenix.db.insertion.trace_annotation import TraceAnnotationQueueInserter
|
|
43
|
+
from phoenix.db.insertion.types import Insertables, Precursors
|
|
31
44
|
from phoenix.server.api.dataloaders import CacheForDataLoaders
|
|
32
45
|
from phoenix.server.types import DbSessionFactory
|
|
33
46
|
from phoenix.trace.schemas import Span
|
|
@@ -55,6 +68,8 @@ class BulkInserter:
|
|
|
55
68
|
max_ops_per_transaction: int = 1000,
|
|
56
69
|
max_queue_size: int = 1000,
|
|
57
70
|
enable_prometheus: bool = False,
|
|
71
|
+
retry_delay_sec: float = DEFAULT_RETRY_DELAY_SEC,
|
|
72
|
+
retry_allowance: int = DEFAULT_RETRY_ALLOWANCE,
|
|
58
73
|
) -> None:
|
|
59
74
|
"""
|
|
60
75
|
:param db: A function to initiate a new database session.
|
|
@@ -81,6 +96,9 @@ class BulkInserter:
|
|
|
81
96
|
self._last_updated_at_by_project: LRUCache[ProjectRowId, datetime] = LRUCache(maxsize=100)
|
|
82
97
|
self._cache_for_dataloaders = cache_for_dataloaders
|
|
83
98
|
self._enable_prometheus = enable_prometheus
|
|
99
|
+
self._retry_delay_sec = retry_delay_sec
|
|
100
|
+
self._retry_allowance = retry_allowance
|
|
101
|
+
self._queue_inserters = _QueueInserters(db, self._retry_delay_sec, self._retry_allowance)
|
|
84
102
|
|
|
85
103
|
def last_updated_at(self, project_rowid: Optional[ProjectRowId] = None) -> Optional[datetime]:
|
|
86
104
|
if isinstance(project_rowid, ProjectRowId):
|
|
@@ -90,6 +108,7 @@ class BulkInserter:
|
|
|
90
108
|
async def __aenter__(
|
|
91
109
|
self,
|
|
92
110
|
) -> Tuple[
|
|
111
|
+
Callable[[Any], Awaitable[None]],
|
|
93
112
|
Callable[[Span, str], Awaitable[None]],
|
|
94
113
|
Callable[[pb.Evaluation], Awaitable[None]],
|
|
95
114
|
Callable[[DataManipulation], None],
|
|
@@ -98,6 +117,7 @@ class BulkInserter:
|
|
|
98
117
|
self._operations = Queue(maxsize=self._max_queue_size)
|
|
99
118
|
self._task = asyncio.create_task(self._bulk_insert())
|
|
100
119
|
return (
|
|
120
|
+
self._enqueue,
|
|
101
121
|
self._queue_span,
|
|
102
122
|
self._queue_evaluation,
|
|
103
123
|
self._enqueue_operation,
|
|
@@ -109,6 +129,9 @@ class BulkInserter:
|
|
|
109
129
|
self._task.cancel()
|
|
110
130
|
self._task = None
|
|
111
131
|
|
|
132
|
+
async def _enqueue(self, *items: Any) -> None:
|
|
133
|
+
await self._queue_inserters.enqueue(*items)
|
|
134
|
+
|
|
112
135
|
def _enqueue_operation(self, operation: DataManipulation) -> None:
|
|
113
136
|
cast("Queue[DataManipulation]", self._operations).put_nowait(operation)
|
|
114
137
|
|
|
@@ -124,7 +147,17 @@ class BulkInserter:
|
|
|
124
147
|
assert isinstance(self._operations, Queue)
|
|
125
148
|
spans_buffer, evaluations_buffer = None, None
|
|
126
149
|
# start first insert immediately if the inserter has not run recently
|
|
127
|
-
while
|
|
150
|
+
while (
|
|
151
|
+
self._running
|
|
152
|
+
or not self._queue_inserters.empty
|
|
153
|
+
or not self._operations.empty()
|
|
154
|
+
or self._spans
|
|
155
|
+
or self._evaluations
|
|
156
|
+
):
|
|
157
|
+
if not self._queue_inserters.empty:
|
|
158
|
+
if inserted_ids := await self._queue_inserters.insert():
|
|
159
|
+
for project_rowid in await self._get_project_rowids(inserted_ids):
|
|
160
|
+
self._last_updated_at_by_project[project_rowid] = datetime.now(timezone.utc)
|
|
128
161
|
if self._operations.empty() and not (self._spans or self._evaluations):
|
|
129
162
|
await asyncio.sleep(self._sleep)
|
|
130
163
|
continue
|
|
@@ -244,3 +277,97 @@ class BulkInserter:
|
|
|
244
277
|
BULK_LOADER_EXCEPTIONS.inc()
|
|
245
278
|
logger.exception("Failed to insert evaluations")
|
|
246
279
|
return transaction_result
|
|
280
|
+
|
|
281
|
+
async def _get_project_rowids(
|
|
282
|
+
self,
|
|
283
|
+
inserted_ids: Mapping[Type[models.Base], List[int]],
|
|
284
|
+
) -> Set[int]:
|
|
285
|
+
ans: Set[int] = set()
|
|
286
|
+
if not inserted_ids:
|
|
287
|
+
return ans
|
|
288
|
+
stmt: Select[Tuple[int]]
|
|
289
|
+
for table, ids in inserted_ids.items():
|
|
290
|
+
if not ids:
|
|
291
|
+
continue
|
|
292
|
+
if issubclass(table, models.SpanAnnotation):
|
|
293
|
+
stmt = (
|
|
294
|
+
select(models.Project.id)
|
|
295
|
+
.join(models.Trace)
|
|
296
|
+
.join_from(models.Trace, models.Span)
|
|
297
|
+
.join_from(models.Span, models.SpanAnnotation)
|
|
298
|
+
.where(models.SpanAnnotation.id.in_(ids))
|
|
299
|
+
)
|
|
300
|
+
elif issubclass(table, models.DocumentAnnotation):
|
|
301
|
+
stmt = (
|
|
302
|
+
select(models.Project.id)
|
|
303
|
+
.join(models.Trace)
|
|
304
|
+
.join_from(models.Trace, models.Span)
|
|
305
|
+
.join_from(models.Span, models.DocumentAnnotation)
|
|
306
|
+
.where(models.DocumentAnnotation.id.in_(ids))
|
|
307
|
+
)
|
|
308
|
+
elif issubclass(table, models.TraceAnnotation):
|
|
309
|
+
stmt = (
|
|
310
|
+
select(models.Project.id)
|
|
311
|
+
.join(models.Trace)
|
|
312
|
+
.join_from(models.Trace, models.TraceAnnotation)
|
|
313
|
+
.where(models.TraceAnnotation.id.in_(ids))
|
|
314
|
+
)
|
|
315
|
+
else:
|
|
316
|
+
continue
|
|
317
|
+
async with self._db() as session:
|
|
318
|
+
project_rowids = [_ async for _ in await session.stream_scalars(stmt)]
|
|
319
|
+
ans.update(project_rowids)
|
|
320
|
+
return ans
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class _QueueInserters:
|
|
324
|
+
def __init__(
|
|
325
|
+
self,
|
|
326
|
+
db: DbSessionFactory,
|
|
327
|
+
retry_delay_sec: float = DEFAULT_RETRY_DELAY_SEC,
|
|
328
|
+
retry_allowance: int = DEFAULT_RETRY_ALLOWANCE,
|
|
329
|
+
) -> None:
|
|
330
|
+
self._db = db
|
|
331
|
+
args = (db, retry_delay_sec, retry_allowance)
|
|
332
|
+
self._span_annotations = SpanAnnotationQueueInserter(*args)
|
|
333
|
+
self._trace_annotations = TraceAnnotationQueueInserter(*args)
|
|
334
|
+
self._document_annotations = DocumentAnnotationQueueInserter(*args)
|
|
335
|
+
self._queues = (
|
|
336
|
+
self._span_annotations,
|
|
337
|
+
self._trace_annotations,
|
|
338
|
+
self._document_annotations,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
async def insert(self) -> Dict[Type[models.Base], List[int]]:
|
|
342
|
+
ans: DefaultDict[Type[models.Base], List[int]] = defaultdict(list)
|
|
343
|
+
for coro in as_completed([q.insert() for q in self._queues]):
|
|
344
|
+
table, inserted_ids = await coro
|
|
345
|
+
if inserted_ids:
|
|
346
|
+
ans[table].extend(inserted_ids)
|
|
347
|
+
return ans
|
|
348
|
+
|
|
349
|
+
@property
|
|
350
|
+
def empty(self) -> bool:
|
|
351
|
+
return all(q.empty for q in self._queues)
|
|
352
|
+
|
|
353
|
+
async def enqueue(self, *items: Any) -> None:
|
|
354
|
+
for item in items:
|
|
355
|
+
await self._enqueue(item)
|
|
356
|
+
|
|
357
|
+
@singledispatchmethod
|
|
358
|
+
async def _enqueue(self, item: Any) -> None: ...
|
|
359
|
+
|
|
360
|
+
@_enqueue.register(Precursors.SpanAnnotation)
|
|
361
|
+
@_enqueue.register(Insertables.SpanAnnotation)
|
|
362
|
+
async def _(self, item: Precursors.SpanAnnotation) -> None:
|
|
363
|
+
await self._span_annotations.enqueue(item)
|
|
364
|
+
|
|
365
|
+
@_enqueue.register(Precursors.TraceAnnotation)
|
|
366
|
+
@_enqueue.register(Insertables.TraceAnnotation)
|
|
367
|
+
async def _(self, item: Precursors.TraceAnnotation) -> None:
|
|
368
|
+
await self._trace_annotations.enqueue(item)
|
|
369
|
+
|
|
370
|
+
@_enqueue.register(Precursors.DocumentAnnotation)
|
|
371
|
+
@_enqueue.register(Insertables.DocumentAnnotation)
|
|
372
|
+
async def _(self, item: Precursors.DocumentAnnotation) -> None:
|
|
373
|
+
await self._document_annotations.enqueue(item)
|
phoenix/db/helpers.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
|
-
from typing import Any, Optional, Tuple
|
|
2
|
+
from typing import Any, Callable, Hashable, Iterable, List, Optional, Set, Tuple, TypeVar
|
|
3
3
|
|
|
4
4
|
from openinference.semconv.trace import (
|
|
5
5
|
OpenInferenceSpanKindValues,
|
|
@@ -80,3 +80,25 @@ def get_project_names_for_experiments(*experiment_ids: int) -> Select[Tuple[Opti
|
|
|
80
80
|
.where(models.Experiment.id.in_(set(experiment_ids)))
|
|
81
81
|
.where(models.Experiment.project_name.isnot(None))
|
|
82
82
|
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
_AnyT = TypeVar("_AnyT")
|
|
86
|
+
_KeyT = TypeVar("_KeyT", bound=Hashable)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def dedup(
|
|
90
|
+
items: Iterable[_AnyT],
|
|
91
|
+
key: Callable[[_AnyT], _KeyT],
|
|
92
|
+
) -> List[_AnyT]:
|
|
93
|
+
"""
|
|
94
|
+
Discard subsequent duplicates after the first appearance in `items`.
|
|
95
|
+
"""
|
|
96
|
+
ans = []
|
|
97
|
+
seen: Set[_KeyT] = set()
|
|
98
|
+
for item in items:
|
|
99
|
+
if (k := key(item)) in seen:
|
|
100
|
+
continue
|
|
101
|
+
else:
|
|
102
|
+
ans.append(item)
|
|
103
|
+
seen.add(k)
|
|
104
|
+
return ans
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Any, List, Mapping, NamedTuple, Optional, Tuple
|
|
3
|
+
|
|
4
|
+
from sqlalchemy import Row, Select, and_, select, tuple_
|
|
5
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
6
|
+
from typing_extensions import TypeAlias
|
|
7
|
+
|
|
8
|
+
from phoenix.db import models
|
|
9
|
+
from phoenix.db.helpers import dedup, num_docs_col
|
|
10
|
+
from phoenix.db.insertion.types import (
|
|
11
|
+
Insertables,
|
|
12
|
+
Postponed,
|
|
13
|
+
Precursors,
|
|
14
|
+
QueueInserter,
|
|
15
|
+
Received,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
_Name: TypeAlias = str
|
|
19
|
+
_SpanId: TypeAlias = str
|
|
20
|
+
_SpanRowId: TypeAlias = int
|
|
21
|
+
_DocumentPosition: TypeAlias = int
|
|
22
|
+
_AnnoRowId: TypeAlias = int
|
|
23
|
+
_NumDocs: TypeAlias = int
|
|
24
|
+
|
|
25
|
+
_Key: TypeAlias = Tuple[_Name, _SpanId, _DocumentPosition]
|
|
26
|
+
_UniqueBy: TypeAlias = Tuple[_Name, _SpanRowId, _DocumentPosition]
|
|
27
|
+
_Existing: TypeAlias = Tuple[
|
|
28
|
+
_SpanRowId,
|
|
29
|
+
_SpanId,
|
|
30
|
+
_NumDocs,
|
|
31
|
+
Optional[_AnnoRowId],
|
|
32
|
+
Optional[_Name],
|
|
33
|
+
Optional[_DocumentPosition],
|
|
34
|
+
Optional[datetime],
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DocumentAnnotationQueueInserter(
|
|
39
|
+
QueueInserter[
|
|
40
|
+
Precursors.DocumentAnnotation,
|
|
41
|
+
Insertables.DocumentAnnotation,
|
|
42
|
+
models.DocumentAnnotation,
|
|
43
|
+
],
|
|
44
|
+
table=models.DocumentAnnotation,
|
|
45
|
+
unique_by=("name", "span_rowid", "document_position"),
|
|
46
|
+
):
|
|
47
|
+
async def _partition(
|
|
48
|
+
self,
|
|
49
|
+
session: AsyncSession,
|
|
50
|
+
*parcels: Received[Precursors.DocumentAnnotation],
|
|
51
|
+
) -> Tuple[
|
|
52
|
+
List[Received[Insertables.DocumentAnnotation]],
|
|
53
|
+
List[Postponed[Precursors.DocumentAnnotation]],
|
|
54
|
+
List[Received[Precursors.DocumentAnnotation]],
|
|
55
|
+
]:
|
|
56
|
+
to_insert: List[Received[Insertables.DocumentAnnotation]] = []
|
|
57
|
+
to_postpone: List[Postponed[Precursors.DocumentAnnotation]] = []
|
|
58
|
+
to_discard: List[Received[Precursors.DocumentAnnotation]] = []
|
|
59
|
+
|
|
60
|
+
stmt = self._select_existing(*map(_key, parcels))
|
|
61
|
+
existing: List[Row[_Existing]] = [_ async for _ in await session.stream(stmt)]
|
|
62
|
+
existing_spans: Mapping[str, _SpanAttr] = {
|
|
63
|
+
e.span_id: _SpanAttr(e.span_rowid, e.num_docs) for e in existing
|
|
64
|
+
}
|
|
65
|
+
existing_annos: Mapping[_Key, _AnnoAttr] = {
|
|
66
|
+
(e.name, e.span_id, e.document_position): _AnnoAttr(e.span_rowid, e.id, e.updated_at)
|
|
67
|
+
for e in existing
|
|
68
|
+
if e.id is not None
|
|
69
|
+
and e.name is not None
|
|
70
|
+
and e.document_position is not None
|
|
71
|
+
and e.updated_at is not None
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for p in parcels:
|
|
75
|
+
if (anno := existing_annos.get(_key(p))) is not None:
|
|
76
|
+
if p.received_at <= anno.updated_at:
|
|
77
|
+
to_discard.append(p)
|
|
78
|
+
else:
|
|
79
|
+
to_insert.append(
|
|
80
|
+
Received(
|
|
81
|
+
received_at=p.received_at,
|
|
82
|
+
item=p.item.as_insertable(
|
|
83
|
+
span_rowid=anno.span_rowid,
|
|
84
|
+
id_=anno.id_,
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
elif (span := existing_spans.get(p.item.span_id)) is not None:
|
|
89
|
+
if 0 <= p.item.document_position < span.num_docs:
|
|
90
|
+
to_insert.append(
|
|
91
|
+
Received(
|
|
92
|
+
received_at=p.received_at,
|
|
93
|
+
item=p.item.as_insertable(
|
|
94
|
+
span_rowid=span.span_rowid,
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
else:
|
|
99
|
+
to_discard.append(p)
|
|
100
|
+
elif isinstance(p, Postponed):
|
|
101
|
+
if p.retries_left > 1:
|
|
102
|
+
to_postpone.append(p.postpone(p.retries_left - 1))
|
|
103
|
+
else:
|
|
104
|
+
to_discard.append(p)
|
|
105
|
+
elif isinstance(p, Received):
|
|
106
|
+
to_postpone.append(p.postpone(self._retry_allowance))
|
|
107
|
+
else:
|
|
108
|
+
to_discard.append(p)
|
|
109
|
+
|
|
110
|
+
assert len(to_insert) + len(to_postpone) + len(to_discard) == len(parcels)
|
|
111
|
+
to_insert = dedup(sorted(to_insert, key=_time, reverse=True), _unique_by)[::-1]
|
|
112
|
+
return to_insert, to_postpone, to_discard
|
|
113
|
+
|
|
114
|
+
def _select_existing(self, *keys: _Key) -> Select[_Existing]:
|
|
115
|
+
anno = self.table
|
|
116
|
+
span = (
|
|
117
|
+
select(models.Span.id, models.Span.span_id, num_docs_col(self._db.dialect))
|
|
118
|
+
.where(models.Span.span_id.in_({span_id for _, span_id, *_ in keys}))
|
|
119
|
+
.cte()
|
|
120
|
+
)
|
|
121
|
+
onclause = and_(
|
|
122
|
+
span.c.id == anno.span_rowid,
|
|
123
|
+
anno.name.in_({name for name, *_ in keys}),
|
|
124
|
+
tuple_(anno.name, span.c.span_id, anno.document_position).in_(keys),
|
|
125
|
+
)
|
|
126
|
+
return select(
|
|
127
|
+
span.c.id.label("span_rowid"),
|
|
128
|
+
span.c.span_id,
|
|
129
|
+
span.c.num_docs,
|
|
130
|
+
anno.id,
|
|
131
|
+
anno.name,
|
|
132
|
+
anno.document_position,
|
|
133
|
+
anno.updated_at,
|
|
134
|
+
).outerjoin_from(span, anno, onclause)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class _SpanAttr(NamedTuple):
|
|
138
|
+
span_rowid: _SpanRowId
|
|
139
|
+
num_docs: _NumDocs
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class _AnnoAttr(NamedTuple):
|
|
143
|
+
span_rowid: _SpanRowId
|
|
144
|
+
id_: _AnnoRowId
|
|
145
|
+
updated_at: datetime
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _key(p: Received[Precursors.DocumentAnnotation]) -> _Key:
|
|
149
|
+
return p.item.obj.name, p.item.span_id, p.item.document_position
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _unique_by(p: Received[Insertables.DocumentAnnotation]) -> _UniqueBy:
|
|
153
|
+
return p.item.obj.name, p.item.span_rowid, p.item.document_position
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _time(p: Received[Any]) -> datetime:
|
|
157
|
+
return p.received_at
|
phoenix/db/insertion/helpers.py
CHANGED
|
@@ -20,6 +20,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
20
20
|
from sqlalchemy.sql.elements import KeyedColumnElement
|
|
21
21
|
from typing_extensions import TypeAlias, assert_never
|
|
22
22
|
|
|
23
|
+
from phoenix.db import models
|
|
23
24
|
from phoenix.db.helpers import SupportedSQLDialect
|
|
24
25
|
from phoenix.db.models import Base
|
|
25
26
|
|
|
@@ -93,3 +94,15 @@ def _clean(
|
|
|
93
94
|
yield "metadata", v
|
|
94
95
|
else:
|
|
95
96
|
yield k, v
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def as_kv(obj: models.Base) -> Iterator[Tuple[str, Any]]:
|
|
100
|
+
for k, c in obj.__table__.c.items():
|
|
101
|
+
if k in ["created_at", "updated_at"]:
|
|
102
|
+
continue
|
|
103
|
+
k = "metadata_" if k == "metadata" else k
|
|
104
|
+
v = getattr(obj, k, None)
|
|
105
|
+
if c.primary_key and v is None:
|
|
106
|
+
# postgresql disallows None for primary key
|
|
107
|
+
continue
|
|
108
|
+
yield k, v
|