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.

Files changed (29) hide show
  1. {arize_phoenix-4.15.0.dist-info → arize_phoenix-4.16.0.dist-info}/METADATA +2 -1
  2. {arize_phoenix-4.15.0.dist-info → arize_phoenix-4.16.0.dist-info}/RECORD +29 -22
  3. phoenix/db/bulk_inserter.py +129 -2
  4. phoenix/db/helpers.py +23 -1
  5. phoenix/db/insertion/constants.py +2 -0
  6. phoenix/db/insertion/document_annotation.py +157 -0
  7. phoenix/db/insertion/helpers.py +13 -0
  8. phoenix/db/insertion/span_annotation.py +144 -0
  9. phoenix/db/insertion/trace_annotation.py +144 -0
  10. phoenix/db/insertion/types.py +261 -0
  11. phoenix/experiments/types.py +3 -3
  12. phoenix/server/api/input_types/SpanAnnotationSort.py +17 -0
  13. phoenix/server/api/input_types/TraceAnnotationSort.py +17 -0
  14. phoenix/server/api/routers/v1/evaluations.py +90 -4
  15. phoenix/server/api/routers/v1/spans.py +36 -46
  16. phoenix/server/api/routers/v1/traces.py +36 -48
  17. phoenix/server/api/types/Span.py +22 -3
  18. phoenix/server/api/types/Trace.py +21 -4
  19. phoenix/server/app.py +2 -0
  20. phoenix/server/static/.vite/manifest.json +14 -14
  21. phoenix/server/static/assets/{components-kGgeFkHp.js → components-Ci5kMOk5.js} +119 -126
  22. phoenix/server/static/assets/{index-BctFO6S7.js → index-BQG5WVX7.js} +2 -2
  23. phoenix/server/static/assets/{pages-DabDCmVd.js → pages-BrevprVW.js} +289 -213
  24. phoenix/server/static/assets/{vendor-arizeai-B5Hti8OB.js → vendor-arizeai-DTbiPGp6.js} +1 -1
  25. phoenix/trace/dsl/filter.py +2 -6
  26. phoenix/version.py +1 -1
  27. {arize_phoenix-4.15.0.dist-info → arize_phoenix-4.16.0.dist-info}/WHEEL +0 -0
  28. {arize_phoenix-4.15.0.dist-info → arize_phoenix-4.16.0.dist-info}/licenses/IP_NOTICE +0 -0
  29. {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.15.0
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=n9j59G6rZwNZUyNaaFWTiRnQ1XtoroCAMfk4OtFuagc,23
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=Hagqne0MiHXi9Y1uVsXpzIqNF9HpSZcmqv00TuHChTc,11285
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=mTBhPzdy_aU9gD7hNzUZJkAnV77ko5CdaXyoWH3snPA,2982
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=v8ONIWZu_x1XNWKmz3gNu3839LqlZbFv4gyAYrLKMts,3068
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=VuvDCcvUGeHIQuXS_xpz7Jq5xHdt3qu-O_C7IQ3DvF8,23397
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=DaYK3-2YJVB6AWE_oJ1pX96H20pap9D60DoCm20p12A,19041
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=xfBoieGa9bmkvomJFfveGIS6LcC4AgjS4OeoUsTlJWk,9484
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=yMs3Sm6s6JLV2nlmpnQ-ZJ_In0aTY5M3uOlLi-Wzb9c,8967
151
- phoenix/server/api/routers/v1/traces.py,sha256=Zl_hGHd-4rA0tXegH_GVoN9Ij84vbPB8oHu28fzGHA8,8029
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=0Ka8usQ8RscoeqZfz_AEHpfI2BXy0Q7caIAyo8LBc38,14511
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=ep-mPexub1ijxAnBvc2KrGsNVXO2SfDR1WxqER2wcD8,2376
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=9PYR8RFuxdsTxW7Ofs1P8Ehx2DktABRYGTx0jGtAX84,1929
219
- phoenix/server/static/assets/components-kGgeFkHp.js,sha256=xUfFn5d7Q4ZltPMMQYFCHfXwGTeclBTSKjRQID0Fz9c,165411
220
- phoenix/server/static/assets/index-BctFO6S7.js,sha256=Y3BkQU18R8sJnJAuq0cU7sVPFjDcqhskG0AmAtv5Q8k,6337
221
- phoenix/server/static/assets/pages-DabDCmVd.js,sha256=cAiGKj0vFph9REng2JjQAVOax5IhgdSxkEBVs8u1Hh0,433015
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-B5Hti8OB.js,sha256=H1iwTiHB40STvEBQe1BnZpej-9slXsuOgEZvvVvnz3Y,296355
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=DTDERSAexxDbTy5QvC48NSKhnr2pfIRDZ4PS-s3ZW80,32642
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.15.0.dist-info/METADATA,sha256=dGRIIc2CR5BuCgVqrzfF5kJphORO_AmyoqhVX7mVrS0,11775
273
- arize_phoenix-4.15.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
274
- arize_phoenix-4.15.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
275
- arize_phoenix-4.15.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
276
- arize_phoenix-4.15.0.dist-info/RECORD,,
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,,
@@ -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 self._running or not self._operations.empty() or self._spans or self._evaluations:
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,2 @@
1
+ DEFAULT_RETRY_DELAY_SEC: float = 60
2
+ DEFAULT_RETRY_ALLOWANCE: int = 10
@@ -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
@@ -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