langgraph-api 0.0.26__py3-none-any.whl → 0.0.28__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 langgraph-api might be problematic. Click here for more details.

Files changed (53) hide show
  1. langgraph_api/api/__init__.py +2 -0
  2. langgraph_api/api/assistants.py +43 -13
  3. langgraph_api/api/meta.py +2 -1
  4. langgraph_api/api/runs.py +14 -1
  5. langgraph_api/api/ui.py +68 -0
  6. langgraph_api/asyncio.py +43 -4
  7. langgraph_api/auth/middleware.py +2 -2
  8. langgraph_api/cli.py +72 -57
  9. langgraph_api/config.py +23 -1
  10. langgraph_api/cron_scheduler.py +1 -1
  11. langgraph_api/graph.py +5 -0
  12. langgraph_api/http.py +24 -7
  13. langgraph_api/js/.gitignore +2 -0
  14. langgraph_api/js/build.mts +49 -3
  15. langgraph_api/js/client.mts +84 -40
  16. langgraph_api/js/global.d.ts +1 -0
  17. langgraph_api/js/package.json +15 -7
  18. langgraph_api/js/remote.py +662 -16
  19. langgraph_api/js/src/graph.mts +5 -4
  20. langgraph_api/js/sse.py +138 -0
  21. langgraph_api/js/tests/api.test.mts +28 -0
  22. langgraph_api/js/tests/compose-postgres.yml +2 -2
  23. langgraph_api/js/tests/graphs/agent.css +1 -0
  24. langgraph_api/js/tests/graphs/agent.ui.tsx +10 -0
  25. langgraph_api/js/tests/graphs/package.json +2 -2
  26. langgraph_api/js/tests/graphs/yarn.lock +13 -13
  27. langgraph_api/js/yarn.lock +710 -1187
  28. langgraph_api/lifespan.py +15 -5
  29. langgraph_api/logging.py +9 -0
  30. langgraph_api/metadata.py +5 -1
  31. langgraph_api/middleware/http_logger.py +1 -1
  32. langgraph_api/patch.py +2 -0
  33. langgraph_api/queue_entrypoint.py +63 -0
  34. langgraph_api/schema.py +2 -0
  35. langgraph_api/stream.py +1 -0
  36. langgraph_api/webhook.py +42 -0
  37. langgraph_api/{queue.py → worker.py} +52 -166
  38. {langgraph_api-0.0.26.dist-info → langgraph_api-0.0.28.dist-info}/METADATA +8 -8
  39. {langgraph_api-0.0.26.dist-info → langgraph_api-0.0.28.dist-info}/RECORD +49 -46
  40. langgraph_storage/database.py +8 -22
  41. langgraph_storage/inmem_stream.py +108 -0
  42. langgraph_storage/ops.py +80 -57
  43. langgraph_storage/queue.py +126 -103
  44. langgraph_storage/retry.py +5 -1
  45. langgraph_storage/store.py +5 -1
  46. openapi.json +3 -3
  47. langgraph_api/js/client.new.mts +0 -861
  48. langgraph_api/js/remote_new.py +0 -694
  49. langgraph_api/js/remote_old.py +0 -667
  50. langgraph_api/js/server_sent_events.py +0 -126
  51. {langgraph_api-0.0.26.dist-info → langgraph_api-0.0.28.dist-info}/LICENSE +0 -0
  52. {langgraph_api-0.0.26.dist-info → langgraph_api-0.0.28.dist-info}/WHEEL +0 -0
  53. {langgraph_api-0.0.26.dist-info → langgraph_api-0.0.28.dist-info}/entry_points.txt +0 -0
@@ -1,41 +1,38 @@
1
1
  LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
2
2
  langgraph_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- langgraph_api/api/__init__.py,sha256=WBw9xUm5JkvgWVbwHs8vhR5jOf3_zhMjktB96zKNI58,4962
4
- langgraph_api/api/assistants.py,sha256=9wngelDC9vnSs2vYGTHDSCLf5KNds-6mgP1BWnfoY2M,12865
5
- langgraph_api/api/meta.py,sha256=hueasWpTDQ6xYLo9Bzt2jhNH8XQRzreH8FTeFfnRoxQ,2700
3
+ langgraph_api/api/__init__.py,sha256=vwV4VnV7wA7tHqgcsSzeaHHQz9aTNZmTKLO6yT5l9UE,5044
4
+ langgraph_api/api/assistants.py,sha256=6K5HTUjl7lQv0sVRe4RURPrLzTW-BblUjbSYHeN2PfQ,13992
5
+ langgraph_api/api/meta.py,sha256=ifJ_Ki0Qf2DYbmY6OKlqKhLGxbt55gm0lEqH1A0cJbw,2790
6
6
  langgraph_api/api/openapi.py,sha256=f9gfmWN2AMKNUpLCpSgZuw_aeOF9jCXPdOtFT5PaTWM,10960
7
- langgraph_api/api/runs.py,sha256=wAzPXi_kcYB9BcLBL4FXgkBohWwCPIpe4XERnsnWnsA,16042
7
+ langgraph_api/api/runs.py,sha256=HWZ7QPRJwPlvtFtC8cyG5mKd3GJIjRkdR0KWlMssQ84,16574
8
8
  langgraph_api/api/store.py,sha256=VzAJVOwO0IxosBB7km5TTf2rhlWGyPkVz_LpvbxetVY,5437
9
9
  langgraph_api/api/threads.py,sha256=taU61XPcCEhBPCYPZcMDsgVDwwWUWJs8p-PrXFXWY48,8661
10
- langgraph_api/asyncio.py,sha256=2fOlx-cZvuj1gQ867Kw1R_wsBsl9jdHYHcUtK2a-x-U,6264
10
+ langgraph_api/api/ui.py,sha256=LiOZVewKOPbKEykCm30hCEaOA7vuS_Ti5hB32EEy4vw,2082
11
+ langgraph_api/asyncio.py,sha256=ipxOGL0CuKZeHw8895ojtfoBU2fj0iJOp48uhiLAmss,7786
11
12
  langgraph_api/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
13
  langgraph_api/auth/custom.py,sha256=oyXz7UONY1YeTJ5IBGRAbFUz_K6krkptNxW8zzjb0uk,21088
13
14
  langgraph_api/auth/langsmith/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
15
  langgraph_api/auth/langsmith/backend.py,sha256=InScaL-HYCnxYEauhxU198gRZV9pJn9SzzBoR9Edn7g,2654
15
16
  langgraph_api/auth/langsmith/client.py,sha256=eKchvAom7hdkUXauD8vHNceBDDUijrFgdTV8bKd7x4Q,3998
16
- langgraph_api/auth/middleware.py,sha256=qc7SbaFoeWaqxS1wbjZ2PPQ4iI2p9T0shWL7c6g0ed4,1636
17
+ langgraph_api/auth/middleware.py,sha256=5VEC3KE0DrEgXHbEta95zpL1S6PYyD9ETXn3lWKVhqI,1684
17
18
  langgraph_api/auth/noop.py,sha256=Bk6Nf3p8D_iMVy_OyfPlyiJp_aEwzL-sHrbxoXpCbac,586
18
19
  langgraph_api/auth/studio_user.py,sha256=FzFQRROKDlA9JjtBuwyZvk6Mbwno5M9RVYjDO6FU3F8,186
19
- langgraph_api/cli.py,sha256=PKZDWjb1NjGXT1qRN0ogsFw2ANlyq7VvXI6QeTPaFhI,12231
20
- langgraph_api/config.py,sha256=Alb7vm8t13Y1oM9W6JaplApMwTLL1qVZZuXHG08FtgE,5104
21
- langgraph_api/cron_scheduler.py,sha256=MW41-TSGUe5OuXycFTy7Ax7ypxHVAv-0ImLonRT8h8o,2629
20
+ langgraph_api/cli.py,sha256=YIiuS-2D0fyqLd8BzJBIUAM3mmAJPtLUdPEMS6Awj9o,12752
21
+ langgraph_api/config.py,sha256=iIekoGAQL-v--hVrCzSd2StrelzuHqb1vbpJAoHgxkU,5809
22
+ langgraph_api/cron_scheduler.py,sha256=9yzbbGxzNgJdIg4ZT7yu2oTwT_wRuPxD1c2sbbd52xs,2630
22
23
  langgraph_api/errors.py,sha256=Bu_i5drgNTyJcLiyrwVE_6-XrSU50BHf9TDpttki9wQ,1690
23
- langgraph_api/graph.py,sha256=FombjYQkqj8jrXJFEVkl3m2UyFcq5nSVNswR2HoRsQY,16385
24
- langgraph_api/http.py,sha256=XrbyxpjtfSvnaWWh5ZLGpgZmY83WoDCrP_1GPguNiXI,4712
25
- langgraph_api/js/.gitignore,sha256=qAah3Fq0HWAlfRj5ktZyC6QRQIsAolGLRGcRukA1XJI,33
24
+ langgraph_api/graph.py,sha256=viFyQa8-BRRNRZqNNUzLB31cB4IypEMkMucQEJQYLWY,16556
25
+ langgraph_api/http.py,sha256=gYbxxjY8aLnsXeJymcJ7G7Nj_yToOGpPYQqmZ1_ggfA,5240
26
+ langgraph_api/js/.gitignore,sha256=l5yI6G_V6F1600I1IjiUKn87f4uYIrBAYU1MOyBBhg4,59
26
27
  langgraph_api/js/base.py,sha256=BpE8-xkUp8HFPRjSKx1tfUQubvoV4jYl6OwZdre3veI,209
27
- langgraph_api/js/build.mts,sha256=43_LQDjmtEyV6Sj6IZo7Mp6Y8zrsBbMP_5sWXQ_xBsY,1372
28
- langgraph_api/js/client.mts,sha256=5cJgJLmER3fpf-QQU8v2cjR593ROXcQk6vXd71cntsw,22772
29
- langgraph_api/js/client.new.mts,sha256=FskIvfdS4MvzoD07fJuHMoz26Puh_Mkwh2Lh6jKFj60,23658
28
+ langgraph_api/js/build.mts,sha256=ArAEn74HHY_KXt0JCne76FTyGP34VQCHMe6OfPyxLM8,2915
29
+ langgraph_api/js/client.mts,sha256=heQ5LMpK-hWfSrByNeINZn0JTJ3EYaJKCepzfpiXccU,24356
30
30
  langgraph_api/js/errors.py,sha256=Cm1TKWlUCwZReDC5AQ6SgNIVGD27Qov2xcgHyf8-GXo,361
31
- langgraph_api/js/global.d.ts,sha256=zR_zLYfpzyPfxpEFth5RgZoyfGulIXyZYPRf7cU0K0Y,106
32
- langgraph_api/js/package.json,sha256=AmpkMzr96yF9xZ7bCrSApF-j7PJH6WeALn9HpPGBnmQ,840
33
- langgraph_api/js/remote.py,sha256=D9cqcEgXau-fm_trpNwCHMra5BXntgUa469lgs_a9JQ,622
34
- langgraph_api/js/remote_new.py,sha256=-9gsJeV32cHPXd-EESFVfPoBoPC7pqR7XKhkb_q9cHA,22781
35
- langgraph_api/js/remote_old.py,sha256=A28NMSvLGPfg044NDTHSv63pcujYZQXZujDxryGGhOw,22652
31
+ langgraph_api/js/global.d.ts,sha256=cLJRZfYVGmgQ6o_xFevVNNTIi918ZUdxVRnpLVSjiAY,133
32
+ langgraph_api/js/package.json,sha256=j6DMoVgwRqWqTwdd7R1f-kvmiTUAbO3HaUhM8K64lbE,1224
33
+ langgraph_api/js/remote.py,sha256=jUUgdskUbDR_tuhZFrJo4OewbndQ76joczBv-mZHG9k,22637
36
34
  langgraph_api/js/schema.py,sha256=7idnv7URlYUdSNMBXQcw7E4SxaPxCq_Oxwnlml8q5ik,408
37
- langgraph_api/js/server_sent_events.py,sha256=DLgXOHauemt7706vnfDUCG1GI3TidKycSizccdz9KgA,3702
38
- langgraph_api/js/src/graph.mts,sha256=J-M-vYHj1G5tyonPUym3ePNGqGYtspPCrZOgr92xKb4,3171
35
+ langgraph_api/js/src/graph.mts,sha256=mRyMUp03Fwd5DlmNIFl3RiUCQuJ5XwmFp1AfAeKDfVc,3169
39
36
  langgraph_api/js/src/hooks.mjs,sha256=XtktgmIHlls_DsknAuwib-z7TqCm0haRoTXvnkgzMuo,601
40
37
  langgraph_api/js/src/parser/parser.mts,sha256=wXre7teh8N8RYmGcyhZp4vMJd0kNnnFgoSGEyMVPzpQ,13007
41
38
  langgraph_api/js/src/parser/parser.worker.mjs,sha256=2K6D0GlUmkk7LE39I8mryB8VZVE3-N9Cblji-ArPhFo,386
@@ -44,54 +41,60 @@ langgraph_api/js/src/schema/types.template.mts,sha256=oAwnwWOgkEAQ3EouB8dG5Mdg4H
44
41
  langgraph_api/js/src/utils/importMap.mts,sha256=pX4TGOyUpuuWF82kXcxcv3-8mgusRezOGe6Uklm2O5A,1644
45
42
  langgraph_api/js/src/utils/pythonSchemas.mts,sha256=98IW7Z_VP7L_CHNRMb3_MsiV3BgLE2JsWQY_PQcRR3o,685
46
43
  langgraph_api/js/src/utils/serde.mts,sha256=OuyyO9btvwWd55rU_H4x91dFEJiaPxL-lL9O6Zgo908,742
47
- langgraph_api/js/tests/api.test.mts,sha256=fiQDSdfJj6DhOqQGghFq0RknIYnTPQ7nH_QfBjs7kAk,56931
48
- langgraph_api/js/tests/compose-postgres.yml,sha256=pbNfeqVUqhWILBuUdwAgQOYsVU_fgkCVm0YlTgU8og8,1721
44
+ langgraph_api/js/sse.py,sha256=lsfp4nyJyA1COmlKG9e2gJnTttf_HGCB5wyH8OZBER8,4105
45
+ langgraph_api/js/tests/api.test.mts,sha256=7B7uIiUsi4X3tN5ji7loaU4PGkoJyHhTIbqPN2V-7kY,57709
46
+ langgraph_api/js/tests/compose-postgres.yml,sha256=wV1Kws7WwUWVIudPkB--v58MOPL9hOcV0MUK-cvNrpA,1738
49
47
  langgraph_api/js/tests/graphs/.gitignore,sha256=26J8MarZNXh7snXD5eTpV3CPFTht5Znv8dtHYCLNfkw,12
48
+ langgraph_api/js/tests/graphs/agent.css,sha256=QgcOC0W7IBsrg4pSqqpull-WTgtULZfx_lF_5ZxLdag,23
50
49
  langgraph_api/js/tests/graphs/agent.mts,sha256=E9WMv0alMv0njUEECqEsqoRk9NXJUgXW7SyQJ3GOZ8k,5396
50
+ langgraph_api/js/tests/graphs/agent.ui.tsx,sha256=JDFJdpdIS6rglkXTaROSb1Is0j1kt5wN9ML8W4cuht8,175
51
51
  langgraph_api/js/tests/graphs/delay.mts,sha256=CFneKxqI4bGGK0lYjSbe80QirowPQlsRSuhDUKfydhk,703
52
52
  langgraph_api/js/tests/graphs/error.mts,sha256=l4tk89449dj1BnEF_0ZcfPt0Ikk1gl8L1RaSnRfr3xo,487
53
53
  langgraph_api/js/tests/graphs/langgraph.json,sha256=frxd7ZWILdeMYSZgUBH6UO-IR7I2YJSOfOlx2mnO1sI,189
54
54
  langgraph_api/js/tests/graphs/nested.mts,sha256=4G7jSOSaFVQAza-_ARbK-Iai1biLlF2DIPDZXf7PLIY,1245
55
- langgraph_api/js/tests/graphs/package.json,sha256=U1e03aGujf7LHM9j_01fwbD7hcC59kxJM4ZvUECVF3o,118
55
+ langgraph_api/js/tests/graphs/package.json,sha256=Kv2kdlTNeWl00vYQAhngorQ6rLab4SMc7g1AgZslrHQ,118
56
56
  langgraph_api/js/tests/graphs/weather.mts,sha256=A7mLK3xW8h5B-ZyJNAyX2M2fJJwzPJzXs4DYesJwreQ,1655
57
- langgraph_api/js/tests/graphs/yarn.lock,sha256=N-LBwqNAve0B_VvVZvSAGVkCCo9AQO6VDM-AznGVVLQ,10407
57
+ langgraph_api/js/tests/graphs/yarn.lock,sha256=i2AAIgXA3XBLM8-oU45wgUefCSG-Tne4ghWHmUCUKVk,10407
58
58
  langgraph_api/js/tests/parser.test.mts,sha256=dEC8KTqKygeb1u39ZvpPqCT4HtfPD947nLmITt2buxA,27883
59
59
  langgraph_api/js/tests/utils.mts,sha256=2kTybJ3O7Yfe1q3ehDouqV54ibXkNzsPZ_wBZLJvY-4,421
60
- langgraph_api/js/yarn.lock,sha256=SaJYDS1TmC5J94zGeoi2gHC3_lnF-h8xQcVND6wWd1c,102827
61
- langgraph_api/lifespan.py,sha256=Uj7NV-NqxxD1fgx_umM9pVqclcy-VlqrIxDljyj2he0,1820
62
- langgraph_api/logging.py,sha256=tiDNrEFwqaIdL5ywZv908OXlzzfXsPCws9GXeoFtBV8,3367
63
- langgraph_api/metadata.py,sha256=wvNCHvejBiO_VaAF12kbdlNDn1QXH4Fh_buvHD9J60U,3276
60
+ langgraph_api/js/yarn.lock,sha256=W89dVYZMThcec08lJMcYnvEEnQK7VM5cPglvwpIdRv0,82773
61
+ langgraph_api/lifespan.py,sha256=tcW68-Vxh10y15NDn0TJG2qAbU9RwsrQmumT5xs2HBA,2046
62
+ langgraph_api/logging.py,sha256=rnd_pJsnTd-QbxVJQq-FswEDkd9tNzhoqp7g3_ReIzA,3598
63
+ langgraph_api/metadata.py,sha256=jPLNIRxHi7taZ0g60UdOEXenkvDwoYdI11tsmHenb28,3443
64
64
  langgraph_api/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
65
- langgraph_api/middleware/http_logger.py,sha256=Sxo_q-65tElauRvkzVLt9lJojgNdgtcHGBYD0IRyX7M,3146
65
+ langgraph_api/middleware/http_logger.py,sha256=yuFPNFIWwn-4AE1CogBfWlo8KytzywLi_Bd4ccsyVQE,3150
66
66
  langgraph_api/middleware/private_network.py,sha256=eYgdyU8AzU2XJu362i1L8aSFoQRiV7_aLBPw7_EgeqI,2111
67
67
  langgraph_api/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
68
  langgraph_api/models/run.py,sha256=1TzTmagDXFQD_LhIMRdguZHmrPSzztq1wiMjF63d2fc,9843
69
- langgraph_api/patch.py,sha256=94ddcTSZJe22JcpjxiSNjFZdYVnmeoWjk4IX4iBSoyk,1249
70
- langgraph_api/queue.py,sha256=2sw9HB2cYVBhYUNA3F7lcJAgRjhQJXhA_HNGhFt2BW8,14508
69
+ langgraph_api/patch.py,sha256=82xjuFqY7tgrUm-k1XWHI6k8S6QovSD0zhe--8_xW4o,1296
70
+ langgraph_api/queue_entrypoint.py,sha256=4xICUxXarNV8DhnaqAMhVi3xCmyVKCL3J5NzHxPA9Xc,1835
71
71
  langgraph_api/route.py,sha256=fM4qYCGbmH0a3_cV8uKocb1sLklehxO6HhdRXqLK6OM,4421
72
- langgraph_api/schema.py,sha256=mgam5lpuqZnrNWMm_0nQ95683gCnCvQNRKbiuFj7z8Q,5310
72
+ langgraph_api/schema.py,sha256=hNbg6ep2wiGBBtBJVNBgMYA8uC33AfaqhRXXVUY_At8,5361
73
73
  langgraph_api/serde.py,sha256=VoJ7Z1IuqrQGXFzEP1qijAITtWCrmjtVqlCRuScjXJI,3533
74
74
  langgraph_api/server.py,sha256=CiNK327zTsEpoVGeJK1JOtZHvOBYRoz0CnBTZUmsC7c,4567
75
75
  langgraph_api/sse.py,sha256=2wNodCOP2eg7a9mpSu0S3FQ0CHk2BBV_vv0UtIgJIcc,4034
76
76
  langgraph_api/state.py,sha256=8jx4IoTCOjTJuwzuXJKKFwo1VseHjNnw_CCq4x1SW14,2284
77
- langgraph_api/stream.py,sha256=MUYYNgwtLs1Mhq1dm12zda7j8uFYir49umigK6CnuXU,11944
77
+ langgraph_api/stream.py,sha256=LKXxol0y9xEsrgnbsDwyeKIPBDE-75uVz01OcLI-_QE,11976
78
78
  langgraph_api/utils.py,sha256=92mSti9GfGdMRRWyESKQW5yV-75Z9icGHnIrBYvdypU,3619
79
79
  langgraph_api/validation.py,sha256=McizHlz-Ez8Jhdbc79mbPSde7GIuf2Jlbjx2yv_l6dA,4475
80
+ langgraph_api/webhook.py,sha256=1ncwO0rIZcj-Df9sxSnFEzd1gP1bfS4okeZQS8NSRoE,1382
81
+ langgraph_api/worker.py,sha256=7yQfZBANt1kgJDOEs5B5c3Xy65lzNMmngVbBqLs-r5s,9802
80
82
  langgraph_license/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
81
83
  langgraph_license/middleware.py,sha256=_ODIYzQkymr6W9_Fp9wtf1kAQspnpsmr53xuzyF2GA0,612
82
84
  langgraph_license/validation.py,sha256=Uu_G8UGO_WTlLsBEY0gTVWjRR4czYGfw5YAD3HLZoj0,203
83
85
  langgraph_storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
84
86
  langgraph_storage/checkpoint.py,sha256=V4t2GwYEJdPCHbhq_4Udhlv0TWKDzlMu_rlNPdTDc50,3589
85
- langgraph_storage/database.py,sha256=Nr5zE9Fur3-tESkqe7xNXMf2QlBuw3H0CUie7jVa6Q4,6003
86
- langgraph_storage/ops.py,sha256=Lp6ICiPq-Td8mE7dxBRRvkw6vLh1-hkIJBw-b6XrN84,68533
87
- langgraph_storage/queue.py,sha256=6cTZ0ubHu3S1T43yxHMVOwsQsDaJupByiU0sTUFFls8,3261
88
- langgraph_storage/retry.py,sha256=uvYFuXJ-T6S1QY1ZwkZHyZQbsvS-Ab68LSbzbUUSI2E,696
89
- langgraph_storage/store.py,sha256=D-p3cWc_umamkKp-6Cz3cAriSACpvM5nxUIvND6PuxE,2710
87
+ langgraph_storage/database.py,sha256=I0AgFeJ-NSTT34vxKxQBUf1z2syFP0S8QpKCqTixrzY,5652
88
+ langgraph_storage/inmem_stream.py,sha256=8bxkILIuFpr7P7RQ37SQAxrpRKvmbHdRB_nbfFiomlk,3263
89
+ langgraph_storage/ops.py,sha256=BB1UabucGRPg_j-S-KktUbBypvrIj9xWQ596mkZbaQQ,69504
90
+ langgraph_storage/queue.py,sha256=UDgsUTtUMfBSRDrQ8Onis-FJO4n7KTsX6sdpbY8Hs0A,5055
91
+ langgraph_storage/retry.py,sha256=XmldOP4e_H5s264CagJRVnQMDFcEJR_dldVR1Hm5XvM,763
92
+ langgraph_storage/store.py,sha256=SL7VYCO1wI3O1WP20A9jD3lsxXIN990XuuyVrrj6RVY,2855
90
93
  langgraph_storage/ttl_dict.py,sha256=FlpEY8EANeXWKo_G5nmIotPquABZGyIJyk6HD9u6vqY,1533
91
94
  logging.json,sha256=3RNjSADZmDq38eHePMm1CbP6qZ71AmpBtLwCmKU9Zgo,379
92
- openapi.json,sha256=DqXpD6JD4tvSOhUgDN_6f919F8YmOSAK4CsGi1NDoiI,125252
93
- langgraph_api-0.0.26.dist-info/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
94
- langgraph_api-0.0.26.dist-info/METADATA,sha256=VFGg6OyMT4eyXHQgQiRPD9aWiHWlAjIKqTqQCX34CSc,4039
95
- langgraph_api-0.0.26.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
96
- langgraph_api-0.0.26.dist-info/entry_points.txt,sha256=3EYLgj89DfzqJHHYGxPH4A_fEtClvlRbWRUHaXO7hj4,77
97
- langgraph_api-0.0.26.dist-info/RECORD,,
95
+ openapi.json,sha256=8NhoZn3Dse6Ia0i7fXCB46sdGnEg5qAhov_8of-6m0o,125282
96
+ langgraph_api-0.0.28.dist-info/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
97
+ langgraph_api-0.0.28.dist-info/METADATA,sha256=mDuNiHWUtLvJaA51ixXV2c6lIScZm-_uHqTP6MLa-7g,4029
98
+ langgraph_api-0.0.28.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
99
+ langgraph_api-0.0.28.dist-info/entry_points.txt,sha256=3EYLgj89DfzqJHHYGxPH4A_fEtClvlRbWRUHaXO7hj4,77
100
+ langgraph_api-0.0.28.dist-info/RECORD,,
@@ -1,7 +1,5 @@
1
1
  import asyncio
2
- import json
3
2
  import os
4
- import threading
5
3
  import uuid
6
4
  from collections import defaultdict
7
5
  from collections.abc import AsyncIterator
@@ -13,9 +11,10 @@ from uuid import UUID
13
11
  import structlog
14
12
  from langgraph.checkpoint.memory import PersistentDict
15
13
 
14
+ from langgraph_api import config as langgraph_config
16
15
  from langgraph_api.utils import AsyncConnectionProto
17
16
  from langgraph_storage import store
18
- from langgraph_storage.queue import start_queue, stop_queue
17
+ from langgraph_storage.inmem_stream import start_stream, stop_stream
19
18
 
20
19
  logger = structlog.stdlib.get_logger(__name__)
21
20
 
@@ -23,6 +22,7 @@ logger = structlog.stdlib.get_logger(__name__)
23
22
  class Assistant(TypedDict):
24
23
  assistant_id: UUID
25
24
  graph_id: str
25
+ name: str
26
26
  created_at: NotRequired[datetime]
27
27
  updated_at: NotRequired[datetime]
28
28
  config: dict[str, Any]
@@ -66,6 +66,7 @@ class AssistantVersion(TypedDict):
66
66
  config: dict[str, Any]
67
67
  metadata: dict[str, Any]
68
68
  created_at: NotRequired[datetime]
69
+ name: str
69
70
 
70
71
 
71
72
  class GlobalStore(PersistentDict):
@@ -109,26 +110,12 @@ GLOBAL_RETRY_COUNTER = InMemoryRetryCounter()
109
110
  GLOBAL_STORE = GlobalStore(filename=OPS_FILENAME)
110
111
 
111
112
 
112
- class LockDict(defaultdict):
113
- def __init__(self, *args, **kwargs):
114
- self.__masterlock__ = threading.Lock()
115
- super().__init__(*args, **kwargs)
116
-
117
- def __getitem__(self, key):
118
- with self.__masterlock__:
119
- return super().__getitem__(key)
120
-
121
-
122
- GLOBAL_LOCKS = LockDict(threading.Lock)
123
-
124
-
125
113
  class InMemConnectionProto:
126
114
  def __init__(self):
127
115
  self.filename = OPS_FILENAME
128
116
  self.store = GLOBAL_STORE
129
117
  self.retry_counter = GLOBAL_RETRY_COUNTER
130
118
  self.can_execute = False
131
- self.locks = GLOBAL_LOCKS
132
119
 
133
120
  @asynccontextmanager
134
121
  async def pipeline(self):
@@ -139,7 +126,6 @@ class InMemConnectionProto:
139
126
 
140
127
  def clear(self):
141
128
  self.store.clear()
142
- self.locks.clear()
143
129
  keys = list(self.retry_counter._counters)
144
130
  for key in keys:
145
131
  del self.retry_counter._counters[key]
@@ -157,8 +143,8 @@ async def connect(*, __test__: bool = False) -> AsyncIterator[AsyncConnectionPro
157
143
 
158
144
  async def start_pool() -> None:
159
145
  if store._STORE_CONFIG is None:
160
- if (config_val := os.getenv("LANGGRAPH_STORE")) and config_val.strip():
161
- config_ = json.loads(config_val.strip())
146
+ if langgraph_config.STORE_CONFIG:
147
+ config_ = langgraph_config.STORE_CONFIG
162
148
  store.set_store_config(config_)
163
149
 
164
150
  if not os.path.exists(".langgraph_api"):
@@ -187,7 +173,7 @@ async def start_pool() -> None:
187
173
  for k in ["crons"]:
188
174
  if not GLOBAL_STORE.get(k):
189
175
  GLOBAL_STORE[k] = {}
190
- await start_queue()
176
+ await start_stream()
191
177
 
192
178
 
193
179
  async def stop_pool() -> None:
@@ -200,7 +186,7 @@ async def stop_pool() -> None:
200
186
 
201
187
  async with Checkpointer():
202
188
  pass
203
- await stop_queue()
189
+ await stop_stream()
204
190
 
205
191
 
206
192
  async def healthcheck() -> None:
@@ -0,0 +1,108 @@
1
+ import asyncio
2
+ import logging
3
+ from collections import defaultdict
4
+ from dataclasses import dataclass
5
+ from uuid import UUID
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ @dataclass
11
+ class Message:
12
+ topic: bytes
13
+ data: bytes
14
+
15
+
16
+ class ContextQueue(asyncio.Queue):
17
+ """Queue that supports async context manager protocol"""
18
+
19
+ async def __aenter__(self):
20
+ return self
21
+
22
+ async def __aexit__(
23
+ self,
24
+ exc_type: type[BaseException] | None,
25
+ exc_val: BaseException | None,
26
+ exc_tb: object | None,
27
+ ) -> None:
28
+ # Clear the queue
29
+ while not self.empty():
30
+ try:
31
+ self.get_nowait()
32
+ except asyncio.QueueEmpty:
33
+ break
34
+
35
+
36
+ class StreamManager:
37
+ def __init__(self):
38
+ self.queues = defaultdict(list) # Dict[UUID, List[asyncio.Queue]]
39
+ self.control_queues = defaultdict(list)
40
+
41
+ def get_queues(self, run_id: UUID) -> list[asyncio.Queue]:
42
+ return self.queues[run_id]
43
+
44
+ async def put(self, run_id: UUID, message: Message) -> None:
45
+ topic = message.topic.decode()
46
+ if "control" in topic:
47
+ self.control_queues[run_id].append(message)
48
+ queues = self.queues.get(run_id, [])
49
+ coros = [queue.put(message) for queue in queues]
50
+ results = await asyncio.gather(*coros, return_exceptions=True)
51
+ for result in results:
52
+ if isinstance(result, Exception):
53
+ logger.exception(f"Failed to put message in queue: {result}")
54
+
55
+ async def add_queue(self, run_id: UUID) -> asyncio.Queue:
56
+ queue = ContextQueue()
57
+ self.queues[run_id].append(queue)
58
+ for control_msg in self.control_queues[run_id]:
59
+ try:
60
+ await queue.put(control_msg)
61
+ except Exception:
62
+ logger.exception(
63
+ f"Failed to put control message in queue: {control_msg}"
64
+ )
65
+
66
+ return queue
67
+
68
+ async def remove_queue(self, run_id: UUID, queue: asyncio.Queue):
69
+ if run_id in self.queues:
70
+ self.queues[run_id].remove(queue)
71
+ if not self.queues[run_id]:
72
+ del self.queues[run_id]
73
+
74
+
75
+ # Global instance
76
+ stream_manager = StreamManager()
77
+
78
+
79
+ async def start_stream() -> None:
80
+ """Initialize the queue system.
81
+ In this in-memory implementation, we just need to ensure we have a clean StreamManager instance.
82
+ """
83
+ global stream_manager
84
+ stream_manager = StreamManager()
85
+
86
+
87
+ async def stop_stream() -> None:
88
+ """Clean up the queue system.
89
+ Clear all queues and stored control messages."""
90
+ global stream_manager
91
+
92
+ # Send 'done' message to all active queues before clearing
93
+ for run_id in list(stream_manager.queues.keys()):
94
+ control_message = Message(topic=f"run:{run_id}:control".encode(), data=b"done")
95
+ for queue in stream_manager.queues[run_id]:
96
+ try:
97
+ await queue.put(control_message)
98
+ except (Exception, RuntimeError):
99
+ pass # Ignore errors during shutdown
100
+
101
+ # Clear all stored data
102
+ stream_manager.queues.clear()
103
+ stream_manager.control_queues.clear()
104
+
105
+
106
+ def get_stream_manager() -> StreamManager:
107
+ """Get the global stream manager instance."""
108
+ return stream_manager
langgraph_storage/ops.py CHANGED
@@ -46,7 +46,7 @@ from langgraph_api.serde import Fragment
46
46
  from langgraph_api.utils import fetchone, get_auth_ctx
47
47
  from langgraph_storage.checkpoint import Checkpointer
48
48
  from langgraph_storage.database import InMemConnectionProto, connect
49
- from langgraph_storage.queue import Message, get_stream_manager
49
+ from langgraph_storage.inmem_stream import Message, get_stream_manager
50
50
 
51
51
  logger = structlog.stdlib.get_logger(__name__)
52
52
 
@@ -229,6 +229,7 @@ class Assistants(Authenticated):
229
229
  "config": config or {},
230
230
  "metadata": metadata or {},
231
231
  "created_at": now,
232
+ "name": name,
232
233
  }
233
234
  conn.store["assistants"].append(new_assistant)
234
235
  conn.store["assistant_versions"].append(new_version)
@@ -312,6 +313,7 @@ class Assistants(Authenticated):
312
313
  "config": config if config is not None else assistant["config"],
313
314
  "metadata": metadata if metadata is not None else assistant["metadata"],
314
315
  "created_at": now,
316
+ "name": name if name is not None else assistant["name"],
315
317
  }
316
318
  conn.store["assistant_versions"].append(new_version_entry)
317
319
 
@@ -459,6 +461,14 @@ class Assistants(Authenticated):
459
461
  "read",
460
462
  Auth.types.AssistantsRead(assistant_id=assistant_id),
461
463
  )
464
+ assistant = next(
465
+ (a for a in conn.store["assistants"] if a["assistant_id"] == assistant_id),
466
+ None,
467
+ )
468
+ if not assistant:
469
+ raise HTTPException(
470
+ status_code=404, detail=f"Assistant {assistant_id} not found"
471
+ )
462
472
  versions = [
463
473
  v
464
474
  for v in conn.store["assistant_versions"]
@@ -466,6 +476,12 @@ class Assistants(Authenticated):
466
476
  and (not metadata or is_jsonb_contained(v["metadata"], metadata))
467
477
  and (not filters or _check_filter_match(v["metadata"], filters))
468
478
  ]
479
+
480
+ # Previously, the name was not included in the assistant_versions table. So we should add them here.
481
+ for v in versions:
482
+ if "name" not in v:
483
+ v["name"] = assistant["name"]
484
+
469
485
  versions.sort(key=lambda x: x["version"], reverse=True)
470
486
 
471
487
  async def _yield_versions():
@@ -767,7 +783,7 @@ class Threads(Authenticated):
767
783
  async def has_pending_runs(conn_: InMemConnectionProto, tid: UUID) -> bool:
768
784
  """Check if thread has any pending runs."""
769
785
  return any(
770
- run["status"] == "pending" and run["thread_id"] == tid
786
+ run["status"] in ("pending", "running") and run["thread_id"] == tid
771
787
  for run in conn_.store["runs"]
772
788
  )
773
789
 
@@ -846,8 +862,6 @@ class Threads(Authenticated):
846
862
  raise HTTPException(
847
863
  status_code=404, detail=f"Thread with ID {thread_id} not found"
848
864
  )
849
- # Delete the thread
850
- conn.locks.pop(thread_id, None)
851
865
  # Cascade delete all runs associated with this thread
852
866
  conn.store["runs"] = [
853
867
  run for run in conn.store["runs"] if run["thread_id"] != thread_id
@@ -1120,6 +1134,9 @@ class Threads(Authenticated):
1120
1134
  return []
1121
1135
 
1122
1136
 
1137
+ RUN_LOCK = asyncio.Lock()
1138
+
1139
+
1123
1140
  class Runs(Authenticated):
1124
1141
  resource = "threads"
1125
1142
 
@@ -1127,12 +1144,18 @@ class Runs(Authenticated):
1127
1144
  async def stats(conn: InMemConnectionProto) -> QueueStats:
1128
1145
  """Get stats about the queue."""
1129
1146
  pending_runs = [run for run in conn.store["runs"] if run["status"] == "pending"]
1147
+ running_runs = [run for run in conn.store["runs"] if run["status"] == "running"]
1130
1148
 
1131
- if not pending_runs:
1132
- return {"n_pending": 0, "max_age_secs": None, "med_age_secs": None}
1149
+ if not pending_runs and not running_runs:
1150
+ return {
1151
+ "n_pending": 0,
1152
+ "max_age_secs": None,
1153
+ "med_age_secs": None,
1154
+ "n_running": 0,
1155
+ }
1133
1156
 
1134
1157
  # Get all creation timestamps
1135
- created_times = [run.get("created_at") for run in pending_runs]
1158
+ created_times = [run.get("created_at") for run in (pending_runs + running_runs)]
1136
1159
  created_times = [
1137
1160
  t for t in created_times if t is not None
1138
1161
  ] # Filter out None values
@@ -1140,6 +1163,7 @@ class Runs(Authenticated):
1140
1163
  if not created_times:
1141
1164
  return {
1142
1165
  "n_pending": len(pending_runs),
1166
+ "n_running": len(running_runs),
1143
1167
  "max_age_secs": None,
1144
1168
  "med_age_secs": None,
1145
1169
  }
@@ -1154,13 +1178,13 @@ class Runs(Authenticated):
1154
1178
 
1155
1179
  return {
1156
1180
  "n_pending": len(pending_runs),
1181
+ "n_running": len(running_runs),
1157
1182
  "max_age_secs": oldest_time,
1158
1183
  "med_age_secs": median_time,
1159
1184
  }
1160
1185
 
1161
- @asynccontextmanager
1162
1186
  @staticmethod
1163
- async def next(wait: bool) -> AsyncIterator[tuple[Run, int] | None]:
1187
+ async def next(wait: bool, limit: int = 1) -> AsyncIterator[tuple[Run, int]]:
1164
1188
  """Get the next run from the queue, and the attempt number.
1165
1189
  1 is the first attempt, 2 is the first retry, etc."""
1166
1190
  now = datetime.now(UTC)
@@ -1170,7 +1194,7 @@ class Runs(Authenticated):
1170
1194
  else:
1171
1195
  await asyncio.sleep(0)
1172
1196
 
1173
- async with connect() as conn:
1197
+ async with connect() as conn, RUN_LOCK:
1174
1198
  pending_runs = sorted(
1175
1199
  [
1176
1200
  run
@@ -1181,48 +1205,47 @@ class Runs(Authenticated):
1181
1205
  )
1182
1206
 
1183
1207
  if not pending_runs:
1184
- yield None
1185
1208
  return
1186
1209
 
1187
1210
  # Try to lock and get the first available run
1188
- for run in pending_runs:
1211
+ for _, run in zip(range(limit), pending_runs, strict=False):
1212
+ if run["status"] != "pending":
1213
+ continue
1214
+
1189
1215
  run_id = run["run_id"]
1190
1216
  thread_id = run["thread_id"]
1191
- lock = conn.locks[thread_id]
1192
- acquired = lock.acquire(blocking=False)
1193
- if not acquired:
1194
- continue
1195
- try:
1196
- if run["status"] != "pending":
1197
- continue
1217
+ thread = next(
1218
+ (t for t in conn.store["threads"] if t["thread_id"] == thread_id),
1219
+ None,
1220
+ )
1198
1221
 
1199
- thread = next(
1200
- (
1201
- t
1202
- for t in conn.store["threads"]
1203
- if t["thread_id"] == run["thread_id"]
1204
- ),
1205
- None,
1222
+ if thread is None:
1223
+ await logger.awarning(
1224
+ "Unexpected missing thread in Runs.next",
1225
+ thread_id=run["thread_id"],
1206
1226
  )
1227
+ continue
1207
1228
 
1208
- if thread is None:
1209
- await logger.awarning(
1210
- "Unexpected missing thread in Runs.next",
1211
- thread_id=run["thread_id"],
1212
- )
1213
- continue
1229
+ if run["status"] != "pending":
1230
+ continue
1214
1231
 
1215
- # Increment attempt counter
1216
- attempt = await conn.retry_counter.increment(run_id)
1217
- yield run, attempt
1218
- finally:
1219
- lock.release()
1220
- return
1221
- yield None
1232
+ if any(
1233
+ run["status"] == "running"
1234
+ for run in conn.store["runs"]
1235
+ if run["thread_id"] == thread_id
1236
+ ):
1237
+ continue
1238
+ # Increment attempt counter
1239
+ attempt = await conn.retry_counter.increment(run_id)
1240
+ # Set run as "running"
1241
+ run["status"] = "running"
1242
+ yield run, attempt
1222
1243
 
1223
1244
  @asynccontextmanager
1224
1245
  @staticmethod
1225
- async def enter(run_id: UUID) -> AsyncIterator[ValueEvent]:
1246
+ async def enter(
1247
+ run_id: UUID, loop: asyncio.AbstractEventLoop
1248
+ ) -> AsyncIterator[ValueEvent]:
1226
1249
  """Enter a run, listen for cancellation while running, signal when done."
1227
1250
  This method should be called as a context manager by a worker executing a run.
1228
1251
  """
@@ -1234,20 +1257,18 @@ class Runs(Authenticated):
1234
1257
  done = ValueEvent()
1235
1258
  tg.create_task(listen_for_cancellation(queue, run_id, done))
1236
1259
 
1237
- try:
1238
- # Give done event to caller
1239
- yield done
1240
- finally:
1241
- # Signal done to all subscribers
1242
- control_message = Message(
1243
- topic=f"run:{run_id}:control".encode(), data=b"done"
1244
- )
1260
+ # Give done event to caller
1261
+ yield done
1262
+ # Signal done to all subscribers
1263
+ control_message = Message(
1264
+ topic=f"run:{run_id}:control".encode(), data=b"done"
1265
+ )
1245
1266
 
1246
- # Store the control message for late subscribers
1247
- await stream_manager.put(run_id, control_message)
1248
- stream_manager.control_queues[run_id].append(control_message)
1249
- # Clean up this queue
1250
- await stream_manager.remove_queue(run_id, queue)
1267
+ # Store the control message for late subscribers
1268
+ await stream_manager.put(run_id, control_message)
1269
+ stream_manager.control_queues[run_id].append(control_message)
1270
+ # Clean up this queue
1271
+ await stream_manager.remove_queue(run_id, queue)
1251
1272
 
1252
1273
  @staticmethod
1253
1274
  async def sweep(conn: InMemConnectionProto) -> list[UUID]:
@@ -1350,6 +1371,7 @@ class Runs(Authenticated):
1350
1371
  created_at=datetime.now(UTC),
1351
1372
  updated_at=datetime.now(UTC),
1352
1373
  )
1374
+ await logger.ainfo("Creating thread", thread_id=thread_id)
1353
1375
  conn.store["threads"].append(thread)
1354
1376
  elif existing_thread:
1355
1377
  # Update existing thread
@@ -1382,7 +1404,7 @@ class Runs(Authenticated):
1382
1404
  inflight_runs = [
1383
1405
  r
1384
1406
  for r in conn.store["runs"]
1385
- if r["thread_id"] == thread_id and r["status"] == "pending"
1407
+ if r["thread_id"] == thread_id and r["status"] in ("pending", "running")
1386
1408
  ]
1387
1409
  if prevent_insert_if_inflight:
1388
1410
  if inflight_runs:
@@ -1521,6 +1543,7 @@ class Runs(Authenticated):
1521
1543
  raise HTTPException(status_code=404, detail="Run not found")
1522
1544
 
1523
1545
  async def _yield_deleted():
1546
+ await logger.ainfo("Run deleted", run_id=run_id)
1524
1547
  yield run_id
1525
1548
 
1526
1549
  return _yield_deleted()
@@ -1545,7 +1568,7 @@ class Runs(Authenticated):
1545
1568
  # wait for the run to complete
1546
1569
  # Rely on this join's auth
1547
1570
  async for mode, chunk in Runs.Stream.join(
1548
- run_id, thread_id=thread_id, stream_mode="values", ctx=ctx
1571
+ run_id, thread_id=thread_id, stream_mode="values", ctx=ctx, ignore_404=True
1549
1572
  ):
1550
1573
  if mode == b"values":
1551
1574
  last_chunk = chunk
@@ -1616,7 +1639,7 @@ class Runs(Authenticated):
1616
1639
  coros.append(stream_manager.put(run_id, control_message))
1617
1640
 
1618
1641
  # Update status for pending runs
1619
- if run["status"] == "pending":
1642
+ if run["status"] in ("pending", "running"):
1620
1643
  if queues or action != "rollback":
1621
1644
  run["status"] = "interrupted"
1622
1645
  run["updated_at"] = datetime.now(tz=UTC)
@@ -1793,7 +1816,7 @@ class Runs(Authenticated):
1793
1816
  ),
1794
1817
  )
1795
1818
  break
1796
- elif run["status"] != "pending":
1819
+ elif run["status"] not in ("pending", "running"):
1797
1820
  break
1798
1821
  except WrappedHTTPException as e:
1799
1822
  raise e.http_exception from None