mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-18 13:45:44 +02:00
Compare commits
542
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05e4efe712 | ||
|
|
c80d93c78a | ||
|
|
87cb509528 | ||
|
|
84023451a2 | ||
|
|
efe78447b3 | ||
|
|
df19173191 | ||
|
|
4d01e69b82 | ||
|
|
e86b5f4da2 | ||
|
|
b70d5aac0e | ||
|
|
297242913f | ||
|
|
02965fb5f5 | ||
|
|
64f0458296 | ||
|
|
76711536f9 | ||
|
|
6c6978918e | ||
|
|
2f1a16006a | ||
|
|
269d08f5d3 | ||
|
|
94698c8a34 | ||
|
|
7d557cb865 | ||
|
|
f211fadc2b | ||
|
|
024468c5dd | ||
|
|
7f08a6dafd | ||
|
|
76203e2c20 | ||
|
|
b6cc022861 | ||
|
|
02bb7a436a | ||
|
|
43d21e9c7d | ||
|
|
55ed7d4b49 | ||
|
|
2b78bf3bad | ||
|
|
630bd9ab95 | ||
|
|
b945b1f21e | ||
|
|
4cb108ccd9 | ||
|
|
23d78c2817 | ||
|
|
f8c1a323cc | ||
|
|
b7e329cf0a | ||
|
|
3f49249e72 | ||
|
|
78910dade0 | ||
|
|
33fbd1bb9c | ||
|
|
d45f52ab0c | ||
|
|
ed07d4a5d4 | ||
|
|
2284a54b64 | ||
|
|
f8006b2fee | ||
|
|
2164b7daa3 | ||
|
|
6d20a0b9c7 | ||
|
|
df8becd5cf | ||
|
|
056ba91a71 | ||
|
|
02300de24c | ||
|
|
ac16bdb795 | ||
|
|
0d4ac836e3 | ||
|
|
201c8015ea | ||
|
|
cf3e8252f5 | ||
|
|
7a5e3c1e79 | ||
|
|
218c60717e | ||
|
|
28b9f578b0 | ||
|
|
bef76b791c | ||
|
|
a1b34efdb0 | ||
|
|
7ab5788f25 | ||
|
|
b0a1029d55 | ||
|
|
c2ef3f3fd3 | ||
|
|
d455bd841d | ||
|
|
69a09adef6 | ||
|
|
35aa98b110 | ||
|
|
0f83d9fafe | ||
|
|
52d66df92c | ||
|
|
4ec92f9fb1 | ||
|
|
2b72953064 | ||
|
|
232014e8ef | ||
|
|
9fd3dfc542 | ||
|
|
ff38f75594 | ||
|
|
bbdd007341 | ||
|
|
3c75e414e5 | ||
|
|
575401c78c | ||
|
|
092c9ecde6 | ||
|
|
8336686e89 | ||
|
|
8b080a091e | ||
|
|
1157ec77b4 | ||
|
|
a10a66cbd1 | ||
|
|
ae525fb74f | ||
|
|
a6dde39be7 | ||
|
|
c1661dd07f | ||
|
|
2d848ffddd | ||
|
|
4c8d965710 | ||
|
|
4ac1c628ee | ||
|
|
41f8e61589 | ||
|
|
57a877279d | ||
|
|
d6dea53323 | ||
|
|
504e91ad5a | ||
|
|
10abf2deb1 | ||
|
|
5796ca9a0a | ||
|
|
fca3e4513c | ||
|
|
c5744f583b | ||
|
|
d298b489b4 | ||
|
|
c4144bb48f | ||
|
|
2c3e380a35 | ||
|
|
cf39fa5a91 | ||
|
|
7e666b58cd | ||
|
|
3f400b38d1 | ||
|
|
6527df688c | ||
|
|
aec841bd2a | ||
|
|
2d3121a17c | ||
|
|
06f9142419 | ||
|
|
a926450601 | ||
|
|
abb96c0e2f | ||
|
|
d9e3d83894 | ||
|
|
cedecd8ed6 | ||
|
|
6bf9a7a4bc | ||
|
|
bce1dcfcd2 | ||
|
|
9b46cba1fb | ||
|
|
a6dab889d1 | ||
|
|
7c6dbb3972 | ||
|
|
e08b8352a8 | ||
|
|
f7fe7c6698 | ||
|
|
c2bc6ab8e9 | ||
|
|
420550501f | ||
|
|
a0599139b8 | ||
|
|
c2f359f708 | ||
|
|
7f78a011fd | ||
|
|
7d166bfb9f | ||
|
|
6cc8899818 | ||
|
|
1ba96f49bf | ||
|
|
b0958115c1 | ||
|
|
04fb14d3ae | ||
|
|
efb0e8c176 | ||
|
|
0584eaa5c4 | ||
|
|
0c73af5624 | ||
|
|
9d1bb9d86c | ||
|
|
7c69cb54a6 | ||
|
|
c2279cbe6f | ||
|
|
3a024cff6d | ||
|
|
4a0b2fa0ef | ||
|
|
36179ab1d2 | ||
|
|
20ddb2b8b4 | ||
|
|
b0a25f2794 | ||
|
|
ea0aebaa2e | ||
|
|
26c68aa528 | ||
|
|
4101aebeea | ||
|
|
90ac06deb6 | ||
|
|
32d66d48eb | ||
|
|
d933d455ec | ||
|
|
c421afba65 | ||
|
|
6139dacef9 | ||
|
|
affaa90d2a | ||
|
|
9f969f5fe1 | ||
|
|
fb531b2473 | ||
|
|
fe4029b3b8 | ||
|
|
7cd9a8e5dd | ||
|
|
5ba02d5b46 | ||
|
|
11834512db | ||
|
|
eeb731c07e | ||
|
|
f0fced262a | ||
|
|
8dc4465d05 | ||
|
|
d0a3eaf601 | ||
|
|
6f45f13952 | ||
|
|
328129e5bd | ||
|
|
2d05a17dfb | ||
|
|
5a36229e38 | ||
|
|
eeadeb282e | ||
|
|
3a22aa0af3 | ||
|
|
9467a0e2bb | ||
|
|
8b55dff7a5 | ||
|
|
a19b74154a | ||
|
|
0607dc4611 | ||
|
|
a3ee814539 | ||
|
|
b65140a892 | ||
|
|
77a63608d1 | ||
|
|
c6179ca9d5 | ||
|
|
f087567853 | ||
|
|
bdef6b3f5d | ||
|
|
677d941bb6 | ||
|
|
a43acc33bd | ||
|
|
326fd55e4f | ||
|
|
6037f0210f | ||
|
|
d9328027f9 | ||
|
|
7170e04aa6 | ||
|
|
20581e61c0 | ||
|
|
4af07942ed | ||
|
|
682f39e0d3 | ||
|
|
7bbe8d8628 | ||
|
|
4dfd1c368c | ||
|
|
b75daf093e | ||
|
|
faacbc1570 | ||
|
|
f5f536ba78 | ||
|
|
7284326160 | ||
|
|
62f7548532 | ||
|
|
e6a9e1d1c1 | ||
|
|
94fa329100 | ||
|
|
264caa0684 | ||
|
|
cd33de2ad1 | ||
|
|
692f177a10 | ||
|
|
14d4be6c4a | ||
|
|
2e133d6189 | ||
|
|
89de950307 | ||
|
|
788b62c0fb | ||
|
|
1539a55d2c | ||
|
|
f3055178f3 | ||
|
|
c0067cd304 | ||
|
|
b90d7c4e58 | ||
|
|
2c14b1d658 | ||
|
|
50ce6badee | ||
|
|
6fc5b3aeda | ||
|
|
b543752878 | ||
|
|
8f6ad0b25a | ||
|
|
ada5d2ecb1 | ||
|
|
eaeafe54ab | ||
|
|
f761116de7 | ||
|
|
36cf353d19 | ||
|
|
d503c0bf33 | ||
|
|
25ba4c3bda | ||
|
|
dfc1c59ebf | ||
|
|
6f4c5fefee | ||
|
|
7cf230defa | ||
|
|
5db65e0281 | ||
|
|
b08c2e092f | ||
|
|
120ae38c12 | ||
|
|
22942d4eec | ||
|
|
1756ce1dd2 | ||
|
|
0b4638269b | ||
|
|
1ebdb1ba31 | ||
|
|
f3423c052e | ||
|
|
b63572ee16 | ||
|
|
ddf4e62bde | ||
|
|
d73902ae76 | ||
|
|
501ba8be34 | ||
|
|
998e194e82 | ||
|
|
ef65d3cf88 | ||
|
|
a692e24a58 | ||
|
|
a566f1f892 | ||
|
|
c0b29a6df5 | ||
|
|
a86eb4c5d0 | ||
|
|
3488eb2a2c | ||
|
|
875f20ba9f | ||
|
|
723d4641b0 | ||
|
|
ae62b8faf2 | ||
|
|
9918488169 | ||
|
|
c37c9cbab3 | ||
|
|
0cd8745aad | ||
|
|
33d13c6f52 | ||
|
|
054e2759ca | ||
|
|
23b71048c1 | ||
|
|
a15f542a1f | ||
|
|
d43eaf1f42 | ||
|
|
16b363fbb0 | ||
|
|
68a75135b0 | ||
|
|
5c0c0fb186 | ||
|
|
4571b708d9 | ||
|
|
e365b2b8bd | ||
|
|
b5504506a7 | ||
|
|
c6ae8d25b9 | ||
|
|
0bd7dd2c52 | ||
|
|
82978a8dd8 | ||
|
|
925150a35d | ||
|
|
2920a9dd19 | ||
|
|
db8ed4e9e4 | ||
|
|
b16fcc8468 | ||
|
|
a2fe4df89b | ||
|
|
69dd20e523 | ||
|
|
5152a96fce | ||
|
|
220314b53a | ||
|
|
38bbd92e01 | ||
|
|
e3cb2dd23b | ||
|
|
2f23d1a30d | ||
|
|
88e195bb78 | ||
|
|
41a4f993b1 | ||
|
|
b8f3f48da9 | ||
|
|
94f7f0632d | ||
|
|
b3e0582255 | ||
|
|
24c7a8db3f | ||
|
|
f8eb4244e0 | ||
|
|
62c3bbc9fe | ||
|
|
76bbb761b4 | ||
|
|
80cd91344f | ||
|
|
9e4c41cbed | ||
|
|
1fda568df9 | ||
|
|
08295ecadb | ||
|
|
4ecbabafe8 | ||
|
|
246efe71f4 | ||
|
|
116121eb3a | ||
|
|
18887e9f86 | ||
|
|
bec28226d0 | ||
|
|
967e368e14 | ||
|
|
9d4dd066e5 | ||
|
|
81027b2b80 | ||
|
|
296bf5f75e | ||
|
|
f43c806736 | ||
|
|
36f444dcb6 | ||
|
|
781a115f92 | ||
|
|
95d056e735 | ||
|
|
64adb2bab3 | ||
|
|
e87f0fb0cd | ||
|
|
c70f283f83 | ||
|
|
1d6b0c36e9 | ||
|
|
e5344d35af | ||
|
|
9f48bb0b61 | ||
|
|
5333fc9c21 | ||
|
|
d59091672f | ||
|
|
da7ff1421d | ||
|
|
72e418e4d0 | ||
|
|
8cea8ae1de | ||
|
|
85d2c7623a | ||
|
|
7436777e7d | ||
|
|
479373bd81 | ||
|
|
dd91819c92 | ||
|
|
b07964c98e | ||
|
|
163d14f812 | ||
|
|
c0185f04e5 | ||
|
|
82b31c9ffd | ||
|
|
aade865727 | ||
|
|
184bcacb53 | ||
|
|
d68bac3865 | ||
|
|
824c309035 | ||
|
|
fdfd06056e | ||
|
|
469ebd3492 | ||
|
|
474fb7b33e | ||
|
|
416da06d6b | ||
|
|
2297271863 | ||
|
|
4910830efe | ||
|
|
027bb0a1b8 | ||
|
|
cd30b9cfea | ||
|
|
5eb9826c46 | ||
|
|
fa43b4694a | ||
|
|
4d80f4b1a5 | ||
|
|
70185d350e | ||
|
|
89efd6e915 | ||
|
|
d88ca6f649 | ||
|
|
fadbe7d710 | ||
|
|
c861614337 | ||
|
|
c020f01425 | ||
|
|
14949c81c8 | ||
|
|
3af857922a | ||
|
|
7ec049e0c7 | ||
|
|
1923ff8d85 | ||
|
|
4b9b7d0b1c | ||
|
|
624247a51f | ||
|
|
b21b595927 | ||
|
|
f4633a0015 | ||
|
|
86017c010c | ||
|
|
2115cffc94 | ||
|
|
03726f9bc6 | ||
|
|
509dfd1f21 | ||
|
|
efca21070d | ||
|
|
dba20d0577 | ||
|
|
5145dac12b | ||
|
|
440c7ff12a | ||
|
|
5eef290c4e | ||
|
|
a8b3746356 | ||
|
|
7541331643 | ||
|
|
76814676c2 | ||
|
|
0804984f9d | ||
|
|
8f11b6a003 | ||
|
|
aa6b122e4c | ||
|
|
23491e5c9a | ||
|
|
1491f30a07 | ||
|
|
f63635d3c8 | ||
|
|
6672032568 | ||
|
|
311ce7b04f | ||
|
|
14b732740e | ||
|
|
370825a48a | ||
|
|
264bae5a7e | ||
|
|
f6aa19709e | ||
|
|
8495f6f95d | ||
|
|
6710908d40 | ||
|
|
d24ad3d980 | ||
|
|
2d8288fd0f | ||
|
|
c1ef10a0ec | ||
|
|
a3d7b6f44e | ||
|
|
bc9d45b476 | ||
|
|
7d3f0089aa | ||
|
|
ed678f4701 | ||
|
|
22411ba0fd | ||
|
|
5b9021ff37 | ||
|
|
aaff464115 | ||
|
|
3a23a256e2 | ||
|
|
f6857395b9 | ||
|
|
cdaa7ba003 | ||
|
|
39745ed794 | ||
|
|
738bb8a343 | ||
|
|
b2dde8d9af | ||
|
|
5312edc830 | ||
|
|
276310e116 | ||
|
|
39977ded8c | ||
|
|
439038fc3a | ||
|
|
2e5445c565 | ||
|
|
d38510ad03 | ||
|
|
951486d107 | ||
|
|
745a1e7a29 | ||
|
|
24731d6a28 | ||
|
|
a4689a5d10 | ||
|
|
b05ce0bf60 | ||
|
|
4501e41991 | ||
|
|
bc83287fc8 | ||
|
|
11547e1990 | ||
|
|
fadd9c4577 | ||
|
|
fd2933a792 | ||
|
|
40fa69f8ee | ||
|
|
0824161984 | ||
|
|
b028f502e1 | ||
|
|
9dc3fed6b8 | ||
|
|
0232201b7b | ||
|
|
869b0f2de4 | ||
|
|
90e3adcd71 | ||
|
|
cb918601d1 | ||
|
|
4a4c8db635 | ||
|
|
56a9ce57b1 | ||
|
|
d1ee1cf1f1 | ||
|
|
29c3a579b3 | ||
|
|
9e3cb1f034 | ||
|
|
508e333220 | ||
|
|
aa1bbe3d01 | ||
|
|
139cad373b | ||
|
|
2a86abb8c4 | ||
|
|
d1f0799002 | ||
|
|
be088801ba | ||
|
|
1ee6bfeb8d | ||
|
|
2153d36726 | ||
|
|
819eae891e | ||
|
|
457edaa75b | ||
|
|
90ba4c5205 | ||
|
|
b3c5298100 | ||
|
|
9d9476e664 | ||
|
|
03bec97767 | ||
|
|
dd9b5c42e8 | ||
|
|
951a3f2d1c | ||
|
|
adaa340c15 | ||
|
|
2c85cba9ca | ||
|
|
cb7b924006 | ||
|
|
c61ac946af | ||
|
|
ec0a30008c | ||
|
|
00c7909c27 | ||
|
|
58d396fcf1 | ||
|
|
9f0abf014d | ||
|
|
8d6cd15669 | ||
|
|
61676b8db0 | ||
|
|
3b85e53360 | ||
|
|
250a17d711 | ||
|
|
f63bec8578 | ||
|
|
dc0f0c5944 | ||
|
|
777fe692d4 | ||
|
|
fdbe31a3aa | ||
|
|
78a9933144 | ||
|
|
5717eefa79 | ||
|
|
5978012619 | ||
|
|
b447a1c7cf | ||
|
|
f87608a16f | ||
|
|
a5fe3316b6 | ||
|
|
e0699fbdaf | ||
|
|
b4eb57da67 | ||
|
|
cec3bef7ea | ||
|
|
adc732272c | ||
|
|
71dc92b349 | ||
|
|
79b4642e55 | ||
|
|
48446bcbd2 | ||
|
|
cf95c870fe | ||
|
|
a34c38a53d | ||
|
|
db5276ded1 | ||
|
|
9c67b9ce4b | ||
|
|
08667fe786 | ||
|
|
c6d674cd3e | ||
|
|
294078adab | ||
|
|
48dabc0538 | ||
|
|
d2cc02d789 | ||
|
|
92c66d13ec | ||
|
|
7f821deded | ||
|
|
12a601c8a3 | ||
|
|
d64447c4c2 | ||
|
|
0d2db35d93 | ||
|
|
6e9e1ca146 | ||
|
|
b290e1ffdc | ||
|
|
a5eb6a75bf | ||
|
|
7a136aaff6 | ||
|
|
e973e936c3 | ||
|
|
066f3b21f8 | ||
|
|
2eecaa8500 | ||
|
|
d935a2d110 | ||
|
+3 |
d5b8733a40 | ||
|
|
40c0e44b9a | ||
|
|
b5afec4b2a | ||
|
|
d674e1bddd | ||
|
|
8c68f739b9 | ||
|
|
1500ebd3d7 | ||
|
|
0837263542 | ||
|
|
18633bc99e | ||
|
|
2558f81889 | ||
|
|
b832fefc58 | ||
|
|
444d699fe8 | ||
|
|
e315fb7397 | ||
|
|
0d8dfa7bba | ||
|
|
2c2ace2a40 | ||
|
|
74218fadad | ||
|
|
7a39e5fc6e | ||
|
|
06144b3b13 | ||
|
|
b19572351e | ||
|
|
a5ce13eb45 | ||
|
|
e91f72dd48 | ||
|
|
3d9cb305b4 | ||
|
|
de1460937a | ||
|
|
e0bf4a7bc3 | ||
|
|
06f311fffb | ||
|
|
5bdb887638 | ||
|
|
6c4034420c | ||
|
|
fa5d93ac2f | ||
|
|
d14a6e2c04 | ||
|
|
54dadadac4 | ||
|
|
d073e1bd8a | ||
|
|
28c276f377 | ||
|
|
a71eb09488 | ||
|
|
579c7831b9 | ||
|
|
16da5f4779 | ||
|
|
f59a1339c9 | ||
|
|
d166e9b8e9 | ||
|
|
4da35babda | ||
|
|
c3d882e87a | ||
|
|
fbade9e300 | ||
|
|
0b6a9e345d | ||
|
|
9b9bf88aee | ||
|
|
5f00938aa2 | ||
|
|
67a86f2dc2 | ||
|
|
ad44d1fe66 | ||
|
|
5c45f7c330 | ||
|
|
bb2f448175 | ||
|
|
8a9f3dbf6f | ||
|
|
63f051ad28 | ||
|
|
e5ded1888b | ||
|
|
0ab9770056 | ||
|
|
8ca5e56f52 | ||
|
|
0733ec65ad | ||
|
|
543cbe9032 | ||
|
|
d1710e2eac | ||
|
|
6eace78c53 | ||
|
|
e5947bcd30 | ||
|
|
1240f8bdca | ||
|
|
4d7c107bb8 | ||
|
|
7a4fd25185 | ||
|
|
7a66213535 | ||
|
|
c8d32f104d | ||
|
|
2fee649980 | ||
|
|
b6dc566ec7 | ||
|
|
a84b744eb6 | ||
|
|
f5b888dd72 | ||
|
|
7a8f29847b | ||
|
|
f001246794 | ||
|
|
b7d11b4141 | ||
|
|
1d3fd9a46b | ||
|
|
8c4e698c5a | ||
|
|
c989f1c898 |
@@ -0,0 +1,6 @@
|
||||
# Contributing to LangGraph
|
||||
|
||||
Hi there! Thank you for even being interested in contributing to LangGraph.
|
||||
As an open-source project in a rapidly developing field, we are extremely open to contributions, whether they involve new features, improved infrastructure, better documentation, or bug fixes.
|
||||
|
||||
To learn how to contribute to LangGraph, please follow the [contribution guide here](https://docs.langchain.com/oss/python/contributing).
|
||||
@@ -1,29 +1,29 @@
|
||||
name: "\U0001F41B Bug Report"
|
||||
description: Report a bug in LangGraph. To report a security issue, please instead use the security option below. For questions, please use the GitHub Discussions.
|
||||
labels: [pending,bug]
|
||||
description: Report a bug in LangGraph. To report a security issue, please instead use the security option below. For questions, please use the LangChain Forum at forum.langchain.com.
|
||||
labels: [pending, bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >
|
||||
value: |
|
||||
Thank you for taking the time to file a bug report.
|
||||
|
||||
Use this to report BUGS in LangGraph. For usage questions, feature requests and general design questions, please use [GitHub Discussions](https://github.com/langchain-ai/langgraph/discussions).
|
||||
|
||||
|
||||
Use this to report BUGS in LangGraph. For usage questions, feature requests and general design questions, please use the [LangChain Forum](https://forum.langchain.com/).
|
||||
|
||||
Relevant links to check before filing a bug report to see if your issue has already been reported, fixed or
|
||||
if there's another way to solve your problem:
|
||||
|
||||
[LangGraph Github Discussions](https://github.com/langchain-ai/langgraph/discussions),
|
||||
[LangGraph Github Issues](https://github.com/langchain-ai/langgraph/issues),
|
||||
[LangGraph how-to guides](https://langchain-ai.github.io/langgraph/how-tos/).
|
||||
[LangChain documentation with the integrated search](https://python.langchain.com/docs/get_started/introduction),
|
||||
[GitHub search](https://github.com/langchain-ai/langgraph),
|
||||
|
||||
* [LangChain Forum](https://forum.langchain.com/),
|
||||
* [LangGraph Github Issues](https://github.com/langchain-ai/langgraph/issues),
|
||||
* [LangGraph how-to guides](https://langchain-ai.github.io/langgraph/how-tos/).
|
||||
* [LangChain documentation with the integrated search](https://docs.langchain.com/),
|
||||
* [GitHub search](https://github.com/langchain-ai/langgraph),
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
attributes:
|
||||
label: Checked other resources
|
||||
description: Before submitting this issue, please confirm that you have completed all the steps below by checking each option. These steps help ensure your issue is well-defined, relevant, and actionable.
|
||||
options:
|
||||
- label: This is a bug, not a usage question. For questions, please use GitHub Discussions.
|
||||
- label: This is a bug, not a usage question. For questions, please use the LangChain Forum (https://forum.langchain.com/).
|
||||
required: true
|
||||
- label: I added a clear and detailed title that summarizes the issue.
|
||||
required: true
|
||||
@@ -38,7 +38,7 @@ body:
|
||||
attributes:
|
||||
label: Example Code
|
||||
description: |
|
||||
Please add a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with your use case.
|
||||
Please add a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with your use case. Replace this code with your own!
|
||||
placeholder: |
|
||||
from langgraph.graph import StateGraph
|
||||
|
||||
@@ -78,7 +78,7 @@ body:
|
||||
attributes:
|
||||
label: System Info
|
||||
description: |
|
||||
python -m langchain_core.sys_info
|
||||
Run on your machine: `python -m langchain_core.sys_info`
|
||||
placeholder: |
|
||||
python -m langchain_core.sys_info
|
||||
validations:
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
blank_issues_enabled: true
|
||||
blank_issues_enabled: false
|
||||
version: 2.1
|
||||
contact_links:
|
||||
- name: 🤔 Question or Problem
|
||||
about: Ask a question or ask about a problem in GitHub Discussions.
|
||||
url: https://github.com/langchain-ai/langgraph/discussions/categories/q-a
|
||||
- name: Feature Request
|
||||
url: https://github.com/langchain-ai/langgraph/discussions/categories/ideas
|
||||
about: Suggest a feature or an idea
|
||||
- name: Show and tell
|
||||
about: Show what you built with LangChain
|
||||
url: https://github.com/langchain-ai/langgraph/discussions/categories/show-and-tell
|
||||
- name: Documentation
|
||||
url: https://github.com/langchain-ai/docs/issues/new?template=langgraph.yml
|
||||
about: Report an issue related to the LangGraph documentation
|
||||
- name: LangChain Forum
|
||||
url: https://forum.langchain.com/
|
||||
about: General community discussions and support
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
name: Documentation
|
||||
description: Report an issue related to the LangGraph documentation.
|
||||
title: "DOC: <Please write a comprehensive title after the 'DOC: ' prefix>"
|
||||
labels: [documentation]
|
||||
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Issue with current documentation:"
|
||||
description: >
|
||||
Please make sure to leave a reference to the document/code you're
|
||||
referring to.
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Idea or request for content:"
|
||||
description: >
|
||||
Please describe as clearly as possible what topics you think are missing
|
||||
from the current documentation.
|
||||
@@ -1,25 +1,29 @@
|
||||
name: 🔒 Privileged
|
||||
description: You are a LangChain maintainer, or was asked directly by a maintainer to create an issue here. If not, check the other options.
|
||||
description: You are a LangGraph maintainer, or was asked directly by a maintainer to create an issue here. If not, check the other options.
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for your interest in LangChain! 🚀
|
||||
|
||||
If you are not a LangChain maintainer or were not asked directly by a maintainer to create an issue, then please start the conversation in a [Question in GitHub Discussions](https://github.com/langchain-ai/langchain/discussions/categories/q-a) instead.
|
||||
|
||||
You are a LangChain maintainer if you maintain any of the packages inside of the LangChain repository
|
||||
or are a regular contributor to LangChain with previous merged merged pull requests.
|
||||
Thanks for your interest in LangGraph! 🚀
|
||||
|
||||
If you are not a LangGraph maintainer or were not asked directly by a maintainer to create an issue, then please start the conversation on the [LangChain Forum](https://forum.langchain.com/) instead.
|
||||
|
||||
You are a LangGraph maintainer if you maintain any of the packages inside of the LangGraph repository
|
||||
or are a regular contributor to LangGraph with previous merged merged pull requests.
|
||||
- type: checkboxes
|
||||
id: privileged
|
||||
attributes:
|
||||
label: Privileged issue
|
||||
description: Confirm that you are allowed to create an issue here.
|
||||
options:
|
||||
- label: I am a LangChain maintainer, or was asked directly by a LangChain maintainer to create an issue here.
|
||||
- label: I am a LangGraph maintainer, or was asked directly by a LangGraph maintainer to create an issue here.
|
||||
required: true
|
||||
- type: textarea
|
||||
id: content
|
||||
attributes:
|
||||
label: Issue Content
|
||||
description: Add the content of the issue here.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Community members should **NOT** work on Privileged issues unless these issues have been explicitly marked with a "help-wanted" tag.
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
Thank you for contributing to LangGraph! Follow these steps to mark your pull request as ready for review. **If any of these steps are not completed, your PR will not be considered for review.**
|
||||
|
||||
- [ ] **PR title**: Follows the format: {TYPE}({SCOPE}): {DESCRIPTION}
|
||||
- Examples:
|
||||
- feat(core): add multi-tenant support
|
||||
- fix(cli): resolve flag parsing error
|
||||
- docs(openai): update API usage examples
|
||||
- Allowed `{TYPE}` values:
|
||||
- feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert, release
|
||||
- Allowed `{SCOPE}` values (optional):
|
||||
- langgraph, docs, cli, checkpoint, checkpoint-postgres, checkpoint-sqlite, prebuilt, scheduler-kafka, sdk-py
|
||||
- Once you've written the title, please delete this checklist item; do not include it in the PR.
|
||||
|
||||
- [ ] **PR message**: ***Delete this entire checklist*** and replace with
|
||||
- **Description:** a description of the change. Include a [closing keyword](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) if applicable.
|
||||
- **Issue:** the issue # it fixes, if applicable
|
||||
- **Dependencies:** any dependencies required for this change
|
||||
- **Twitter handle:** if your PR gets announced, and you'd like a mention, we'll gladly shout you out!
|
||||
|
||||
- [ ] **Add tests and docs**: If you're adding a new integration, you must include:
|
||||
1. A test for the integration, preferably unit tests that do not rely on network access,
|
||||
2. An example notebook showing its use. It lives in `docs/docs/integrations` directory.
|
||||
|
||||
- [ ] **Lint and test**: Run `make format`, `make lint` and `make test` from the root of the package(s) you've modified. We will not consider a PR unless these three are passing in CI. See [contribution guidelines](https://github.com/langchain-ai/langgraph/blob/main/CONTRIBUTING.md) for more.
|
||||
|
||||
Additional guidelines:
|
||||
|
||||
- Make sure optional dependencies are imported within a function.
|
||||
- Please do not add dependencies to `pyproject.toml` files (even optional ones) unless they are **required** for unit tests.
|
||||
- Most PRs should not touch more than one package.
|
||||
- Changes should be backwards compatible.
|
||||
@@ -1,10 +1,15 @@
|
||||
import ast
|
||||
import os
|
||||
from itertools import filterfalse
|
||||
from typing import List, Tuple
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
ROOT_PATH = os.path.abspath(os.path.join(__file__, "..", "..", ".."))
|
||||
CLIENT_PATH = os.path.join(ROOT_PATH, "libs", "sdk-py", "langgraph_sdk", "client.py")
|
||||
ASYNC_TO_SYNC_METHOD_MAP: Dict[str, str] = {
|
||||
"aclose": "close",
|
||||
"__aenter__": "__enter__",
|
||||
"__aexit__": "__exit__",
|
||||
}
|
||||
|
||||
|
||||
def get_class_methods(node: ast.ClassDef) -> List[str]:
|
||||
@@ -22,7 +27,7 @@ def find_classes(tree: ast.AST) -> List[Tuple[str, List[str]]]:
|
||||
|
||||
def compare_sync_async_methods(sync_methods: List[str], async_methods: List[str]) -> List[str]:
|
||||
sync_set = set(sync_methods)
|
||||
async_set = set(async_methods)
|
||||
async_set = {ASYNC_TO_SYNC_METHOD_MAP.get(async_method, async_method) for async_method in async_methods}
|
||||
missing_in_sync = list(async_set - sync_set)
|
||||
missing_in_async = list(sync_set - async_set)
|
||||
return missing_in_sync + missing_in_async
|
||||
@@ -33,7 +38,7 @@ def main():
|
||||
tree = ast.parse(file.read())
|
||||
|
||||
classes = find_classes(tree)
|
||||
|
||||
|
||||
def is_sync(class_spec: Tuple[str, List[str]]) -> bool:
|
||||
return class_spec[0].startswith("Sync")
|
||||
|
||||
|
||||
@@ -1,108 +1,164 @@
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import logging
|
||||
import pathlib
|
||||
import sys
|
||||
import langgraph_cli
|
||||
import langgraph_cli.docker
|
||||
import langgraph_cli.config
|
||||
import time
|
||||
from urllib import error, request
|
||||
|
||||
import langgraph_cli
|
||||
import langgraph_cli.config
|
||||
import langgraph_cli.docker
|
||||
from langgraph_cli.cli import prepare_args_and_stdin
|
||||
from langgraph_cli.constants import DEFAULT_PORT
|
||||
from langgraph_cli.exec import Runner, subp_exec
|
||||
from langgraph_cli.progress import Progress
|
||||
from langgraph_cli.constants import DEFAULT_PORT
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
def test(
|
||||
config: pathlib.Path,
|
||||
port: int,
|
||||
tag: str,
|
||||
verbose: bool,
|
||||
):
|
||||
def test(config: pathlib.Path, port: int, tag: str, verbose: bool):
|
||||
"""Spin up API with Postgres/Redis via docker compose and wait until ready."""
|
||||
logger.info("Starting test...")
|
||||
with Runner() as runner, Progress(message="Pulling...") as set:
|
||||
# check docker available
|
||||
# Detect docker/compose capabilities
|
||||
capabilities = langgraph_cli.docker.check_capabilities(runner)
|
||||
# open config
|
||||
|
||||
# Validate config and prepare compose stdin/args using built image
|
||||
config_json = langgraph_cli.config.validate_config_file(config)
|
||||
args, stdin = prepare_args_and_stdin(
|
||||
capabilities=capabilities,
|
||||
config_path=config,
|
||||
config=config_json,
|
||||
docker_compose=None,
|
||||
port=port,
|
||||
watch=False,
|
||||
debugger_port=None,
|
||||
debugger_base_url=f"http://127.0.0.1:{port}",
|
||||
postgres_uri=None,
|
||||
api_version=None,
|
||||
image=tag,
|
||||
base_image=None,
|
||||
)
|
||||
|
||||
set("Running...")
|
||||
args = [
|
||||
"run",
|
||||
"--rm",
|
||||
"-p",
|
||||
f"{port}:8000",
|
||||
]
|
||||
if isinstance(config_json["env"], str):
|
||||
args.extend(
|
||||
[
|
||||
"--env-file",
|
||||
str(config.parent / config_json["env"]),
|
||||
]
|
||||
)
|
||||
else:
|
||||
for k, v in config_json["env"].items():
|
||||
args.extend(
|
||||
[
|
||||
"-e",
|
||||
f"{k}={v}",
|
||||
]
|
||||
)
|
||||
if capabilities.healthcheck_start_interval:
|
||||
args.extend(
|
||||
[
|
||||
"--health-interval",
|
||||
"5s",
|
||||
"--health-retries",
|
||||
"1",
|
||||
"--health-start-period",
|
||||
"10s",
|
||||
"--health-start-interval",
|
||||
"1s",
|
||||
]
|
||||
)
|
||||
else:
|
||||
args.extend(
|
||||
[
|
||||
"--health-interval",
|
||||
"5s",
|
||||
"--health-retries",
|
||||
"2",
|
||||
]
|
||||
)
|
||||
# Compose up with wait (implies detach), similar to `langgraph up --wait`
|
||||
args_up = [*args, "up", "--remove-orphans", "--wait"]
|
||||
|
||||
_task = None
|
||||
|
||||
def on_stdout(line: str):
|
||||
nonlocal _task
|
||||
if "GET /ok" in line or "Uvicorn running on" in line:
|
||||
set("")
|
||||
sys.stdout.write(
|
||||
f"""Ready!
|
||||
- API: http://localhost:{port}
|
||||
"""
|
||||
)
|
||||
sys.stdout.flush()
|
||||
_task.cancel()
|
||||
return True
|
||||
return False
|
||||
|
||||
async def subp_exec_task(*args, **kwargs):
|
||||
nonlocal _task
|
||||
_task = asyncio.create_task(subp_exec(*args, **kwargs))
|
||||
await _task
|
||||
compose_cmd = ["docker", "compose"]
|
||||
if capabilities.compose_type == "standalone":
|
||||
compose_cmd = ["docker-compose"]
|
||||
|
||||
set("Starting...")
|
||||
try:
|
||||
runner.run(
|
||||
subp_exec_task(
|
||||
"docker",
|
||||
*args,
|
||||
tag,
|
||||
subp_exec(
|
||||
*compose_cmd,
|
||||
*args_up,
|
||||
input=stdin,
|
||||
verbose=verbose,
|
||||
on_stdout=on_stdout,
|
||||
)
|
||||
)
|
||||
except asyncio.CancelledError:
|
||||
except Exception as e: # noqa: BLE001
|
||||
# On failure, show diagnostics then ensure clean teardown
|
||||
sys.stderr.write(f"docker compose up failed: {e}\n")
|
||||
try:
|
||||
sys.stderr.write("\n== docker compose ps ==\n")
|
||||
runner.run(
|
||||
subp_exec(*compose_cmd, *args, "ps", input=stdin, verbose=False)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
sys.stderr.write("\n== docker compose logs (api) ==\n")
|
||||
runner.run(
|
||||
subp_exec(
|
||||
*compose_cmd,
|
||||
*args,
|
||||
"logs",
|
||||
"langgraph-api",
|
||||
input=stdin,
|
||||
verbose=False,
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
try:
|
||||
runner.run(
|
||||
subp_exec(
|
||||
*compose_cmd,
|
||||
*args,
|
||||
"down",
|
||||
"-v",
|
||||
"--remove-orphans",
|
||||
input=stdin,
|
||||
verbose=False,
|
||||
)
|
||||
)
|
||||
finally:
|
||||
raise
|
||||
|
||||
set("")
|
||||
base_url = f"http://localhost:{port}"
|
||||
ok_url = f"{base_url}/ok"
|
||||
logger.info(f"Waiting for {ok_url} to respond with 200...")
|
||||
deadline = time.time() + 30
|
||||
last_err: Exception | None = None
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
with request.urlopen(ok_url, timeout=2) as resp:
|
||||
if resp.status == 200:
|
||||
sys.stdout.write(
|
||||
f"""Ready!\n- API: {base_url}\n- /ok: 200 OK\n"""
|
||||
)
|
||||
sys.stdout.flush()
|
||||
break
|
||||
else:
|
||||
last_err = RuntimeError(f"Unexpected status: {resp.status}")
|
||||
logger.error(f"Unexpected status: {resp.status}")
|
||||
except error.URLError as e:
|
||||
logger.error(f"URLError: {e}")
|
||||
last_err = e
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.error(f"Exception: {e}")
|
||||
last_err = e
|
||||
time.sleep(0.5)
|
||||
else:
|
||||
logger.error("Timeout waiting for /ok to return 200")
|
||||
# Bring stack down before raising
|
||||
args_down = [*args, "down", "-v", "--remove-orphans"]
|
||||
try:
|
||||
runner.run(
|
||||
subp_exec(
|
||||
*compose_cmd,
|
||||
*args_down,
|
||||
input=stdin,
|
||||
verbose=verbose,
|
||||
)
|
||||
)
|
||||
finally:
|
||||
raise SystemExit(
|
||||
f"/ok did not return 202 within timeout. Last error: {last_err}"
|
||||
)
|
||||
|
||||
# Clean up: bring compose stack down to free ports for next test
|
||||
logger.info("Test succeeded. Bringing down compose stack...")
|
||||
try:
|
||||
args_down = [*args, "down", "-v", "--remove-orphans"]
|
||||
runner.run(
|
||||
subp_exec(
|
||||
*compose_cmd,
|
||||
*args_down,
|
||||
input=stdin,
|
||||
verbose=verbose,
|
||||
)
|
||||
)
|
||||
logger.info("Compose stack down. Finishing...")
|
||||
except Exception:
|
||||
logger.exception("Failed to bring down compose stack")
|
||||
pass
|
||||
|
||||
logger.info("Test finished")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
@@ -110,6 +166,12 @@ if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-t", "--tag", type=str)
|
||||
parser.add_argument("-c", "--config", type=str, default="./langgraph.json")
|
||||
parser.add_argument("-p", "--port", default=DEFAULT_PORT)
|
||||
parser.add_argument("-p", "--port", type=int, default=DEFAULT_PORT)
|
||||
args = parser.parse_args()
|
||||
test(pathlib.Path(args.config), args.port, args.tag, verbose=True)
|
||||
try:
|
||||
test(pathlib.Path(args.config), args.port, args.tag, verbose=True)
|
||||
except BaseException:
|
||||
logger.exception("Test failed")
|
||||
raise
|
||||
|
||||
logger.info("Test execution finished")
|
||||
|
||||
@@ -13,13 +13,26 @@ jobs:
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.14"
|
||||
example:
|
||||
- name: A
|
||||
workdir: libs/cli/examples
|
||||
tag: langgraph-test-a
|
||||
- name: B
|
||||
workdir: libs/cli/examples/graphs
|
||||
tag: langgraph-test-b
|
||||
- name: C
|
||||
workdir: libs/cli/examples/graphs_reqs_a
|
||||
tag: langgraph-test-c
|
||||
- name: D
|
||||
workdir: libs/cli/examples/graphs_reqs_b
|
||||
tag: langgraph-test-d
|
||||
name: "CLI integration test"
|
||||
defaults:
|
||||
run:
|
||||
working-directory: libs/cli
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: Ana06/get-changed-files@v2.3.0
|
||||
@@ -27,48 +40,79 @@ jobs:
|
||||
filter: "libs/cli/**"
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
if: steps.changed-files.outputs.all
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
enable-cache: true
|
||||
cache-suffix: "cli-integration-test"
|
||||
ignore-nothing-to-cache: true
|
||||
- name: Setup env
|
||||
if: steps.changed-files.outputs.all
|
||||
working-directory: libs/cli/examples
|
||||
run: cat .env.example > .env
|
||||
- name: Install cli globally
|
||||
if: steps.changed-files.outputs.all
|
||||
run: pip install -e .
|
||||
- name: Build and test service A
|
||||
- name: Build and test service ${{ matrix.example.name }}
|
||||
if: steps.changed-files.outputs.all
|
||||
working-directory: libs/cli/examples
|
||||
working-directory: ${{ matrix.example.workdir }}
|
||||
env:
|
||||
LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }}
|
||||
run: |
|
||||
# The build-arg isn't used; just testing that we accept other args
|
||||
langgraph build -t langgraph-test-a --base-image "langchain/langgraph-trial"
|
||||
cp .env.example .envg
|
||||
timeout 60 python ../../../.github/scripts/run_langgraph_cli_test.py -c langgraph.json -t langgraph-test-a
|
||||
- name: Build and test service B
|
||||
if: steps.changed-files.outputs.all
|
||||
working-directory: libs/cli/examples/graphs
|
||||
run: |
|
||||
langgraph build -t langgraph-test-b --base-image "langchain/langgraph-trial"
|
||||
timeout 60 python ../../../../.github/scripts/run_langgraph_cli_test.py -t langgraph-test-b
|
||||
- name: Build and test service C
|
||||
if: steps.changed-files.outputs.all
|
||||
working-directory: libs/cli/examples/graphs_reqs_a
|
||||
run: |
|
||||
langgraph build -t langgraph-test-c --base-image "langchain/langgraph-trial"
|
||||
timeout 60 python ../../../../.github/scripts/run_langgraph_cli_test.py -t langgraph-test-c
|
||||
- name: Build and test service D
|
||||
if: steps.changed-files.outputs.all
|
||||
working-directory: libs/cli/examples/graphs_reqs_b
|
||||
run: |
|
||||
langgraph build -t langgraph-test-d --base-image "langchain/langgraph-trial"
|
||||
timeout 60 python ../../../../.github/scripts/run_langgraph_cli_test.py -t langgraph-test-d
|
||||
# Build the image for this example
|
||||
langgraph build -t ${{ matrix.example.tag }}
|
||||
# Prepare environment file from local or parent example directory
|
||||
if [ -f .env.example ]; then cp .env.example .env; elif [ -f ../.env.example ]; then cp ../.env.example .env && cp ../.env.example ../.env; fi
|
||||
if [ -n "${{ secrets.LANGSMITH_API_KEY }}" ]; then echo "LANGSMITH_API_KEY=${{ secrets.LANGSMITH_API_KEY }}" >> .env; if [ -f ../.env ]; then echo "LANGSMITH_API_KEY=${{ secrets.LANGSMITH_API_KEY }}" >> ../.env; fi; fi
|
||||
# Run the integration test using the built tag
|
||||
# Compute repo root to reference the shared script robustly
|
||||
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||
timeout 60 python "$REPO_ROOT/.github/scripts/run_langgraph_cli_test.py" -t ${{ matrix.example.tag }}
|
||||
|
||||
- name: Build JS service
|
||||
if: steps.changed-files.outputs.all
|
||||
if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' }}
|
||||
working-directory: libs/cli/js-examples
|
||||
run: |
|
||||
langgraph build -t langgraph-test-e
|
||||
|
||||
- name: Build JS monorepo service
|
||||
if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' }}
|
||||
working-directory: libs/cli/js-monorepo-example
|
||||
run: |
|
||||
langgraph build -t langgraph-test-f -c apps/agent/langgraph.json --build-command "yarn run turbo build" --install-command "yarn install"
|
||||
|
||||
- name: Build Python monorepo service
|
||||
if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' }}
|
||||
working-directory: libs/cli/python-monorepo-example
|
||||
run: |
|
||||
langgraph build -t langgraph-test-g -c apps/agent/langgraph.json
|
||||
cp apps/agent/.env.example apps/agent/.env
|
||||
if [ -n "${{ secrets.LANGSMITH_API_KEY }}" ]; then echo "LANGSMITH_API_KEY=${{ secrets.LANGSMITH_API_KEY }}" >> apps/agent/.env; fi
|
||||
timeout 60 python ../../../.github/scripts/run_langgraph_cli_test.py -t langgraph-test-g -c apps/agent/langgraph.json
|
||||
|
||||
- name: Build and test prerelease reqs service
|
||||
if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' }}
|
||||
working-directory: libs/cli/examples/graph_prerelease_reqs
|
||||
run: |
|
||||
langgraph build -t langgraph-test-h
|
||||
cp ../.env.example .env
|
||||
if [ -n "${{ secrets.LANGSMITH_API_KEY }}" ]; then echo "LANGSMITH_API_KEY=${{ secrets.LANGSMITH_API_KEY }}" >> .env; fi
|
||||
timeout 60 python ../../../../.github/scripts/run_langgraph_cli_test.py -t langgraph-test-h
|
||||
echo "Finished starting up langgraph-test-h"
|
||||
LANGGRAPH_VERSION=$(docker run --rm --entrypoint "" langgraph-test-h python -c "import sys; from importlib.metadata import version; v = version('langgraph'); print(v);")
|
||||
if [ "$LANGGRAPH_VERSION" != "1.0.2" ]; then
|
||||
echo "LANGGRAPH_VERSION != 1.0.2; $LANGGRAPH_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
LANGCHAIN_OPENAI_VERSION=$(docker run --rm --entrypoint "" langgraph-test-h python -c "import sys; from importlib.metadata import version; v = version('langchain-openai'); print(v);")
|
||||
if [ "$LANGCHAIN_OPENAI_VERSION" != "1.0.1" ]; then
|
||||
echo "LANGCHAIN_OPENAI_VERSION != 1.0.1; $LANGCHAIN_OPENAI_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
LANGCHAIN_ANTHROPIC_VERSION=$(docker run --rm --entrypoint "" langgraph-test-h python -c "import sys; from importlib.metadata import version; v = version('langchain-anthropic'); print(v);")
|
||||
if [ "$LANGCHAIN_ANTHROPIC_VERSION" != "1.0.0a5" ]; then
|
||||
echo "LANGCHAIN_ANTHROPIC_VERSION != 1.0.0a5; $LANGCHAIN_ANTHROPIC_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build and test prerelease reqs fail service
|
||||
if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' }}
|
||||
working-directory: libs/cli/examples/graph_prerelease_reqs_fail
|
||||
run: |
|
||||
langgraph build -t langgraph-test-i || [ $? -eq 1 ]
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
- "3.12"
|
||||
name: "lint #${{ matrix.python-version }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: Ana06/get-changed-files@v2.3.0
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
filter: "${{ inputs.working-directory }}/**"
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
if: steps.changed-files.outputs.all
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
enable-cache: true
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
if: steps.changed-files.outputs.all
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: uv sync --frozen --group dev
|
||||
run: uv sync --frozen --group lint
|
||||
|
||||
- name: Get .mypy_cache to speed up mypy
|
||||
if: steps.changed-files.outputs.all
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
- name: Install test dependencies
|
||||
if: steps.changed-files.outputs.all
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: uv sync --group dev
|
||||
run: uv sync --group lint
|
||||
|
||||
- name: Get .mypy_cache_test to speed up mypy
|
||||
if: steps.changed-files.outputs.all
|
||||
|
||||
@@ -17,17 +17,17 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
- "3.13"
|
||||
- "3.14"
|
||||
|
||||
name: "test #${{ matrix.python-version }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
enable-cache: true
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: uv sync --frozen --group dev
|
||||
run: uv sync --frozen --group test --no-dev
|
||||
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
|
||||
@@ -12,20 +12,20 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
- "3.13"
|
||||
- "3.14"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: libs/langgraph
|
||||
name: "test #${{ matrix.python-version }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
enable-cache: true
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: uv sync --frozen --group dev
|
||||
run: uv sync --frozen --group test --no-dev
|
||||
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
|
||||
@@ -16,7 +16,6 @@ permissions:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
outputs:
|
||||
@@ -24,10 +23,10 @@ jobs:
|
||||
version: ${{ steps.check-version.outputs.version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python $${ env.PYTHON_VERSION }}
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
enable-cache: true
|
||||
@@ -49,7 +48,7 @@ jobs:
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: test-dist
|
||||
path: ${{ inputs.working-directory }}/dist/
|
||||
@@ -75,9 +74,9 @@ jobs:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: test-dist
|
||||
path: ${{ inputs.working-directory }}/dist/
|
||||
|
||||
@@ -17,16 +17,16 @@ jobs:
|
||||
run:
|
||||
working-directory: libs/langgraph
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- run: SHA=$(git rev-parse HEAD) && echo "SHA=$SHA" >> $GITHUB_ENV
|
||||
- name: Set up Python 3.11
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: "3.11"
|
||||
enable-cache: true
|
||||
cache-suffix: "bench"
|
||||
- name: Install dependencies
|
||||
run: uv sync --group dev
|
||||
run: uv sync --group test
|
||||
- name: Run benchmarks
|
||||
run: OUTPUT=out/benchmark-baseline.json make -s benchmark
|
||||
- name: Save outputs
|
||||
|
||||
@@ -15,20 +15,20 @@ jobs:
|
||||
run:
|
||||
working-directory: libs/langgraph
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- id: files
|
||||
name: Get changed files
|
||||
uses: Ana06/get-changed-files@v2.3.0
|
||||
with:
|
||||
format: json
|
||||
- name: Set up Python 3.11
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: "3.11"
|
||||
enable-cache: true
|
||||
cache-suffix: "bench"
|
||||
- name: Install dependencies
|
||||
run: uv sync --group dev
|
||||
run: uv sync --group test
|
||||
- name: Download baseline
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
echo EOF
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
- name: Annotation
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const file = JSON.parse(`${{ steps.files.outputs.added_modified_renamed }}`)[0]
|
||||
|
||||
@@ -3,7 +3,8 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
@@ -26,7 +27,7 @@ jobs:
|
||||
python: ${{ steps.filter.outputs.python }}
|
||||
deps: ${{ steps.filter.outputs.deps }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: filter
|
||||
with:
|
||||
@@ -77,6 +78,7 @@ jobs:
|
||||
"libs/checkpoint-sqlite",
|
||||
"libs/checkpoint-postgres",
|
||||
"libs/prebuilt",
|
||||
"libs/sdk-py",
|
||||
]
|
||||
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
|
||||
uses: ./.github/workflows/_test.yml
|
||||
@@ -98,9 +100,9 @@ jobs:
|
||||
name: "Check SDK methods matching"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- name: Run check_sdk_methods script
|
||||
@@ -114,13 +116,13 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.11"
|
||||
- "3.13"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: "3.11"
|
||||
python-version: "3.13"
|
||||
enable-cache: true
|
||||
cache-suffix: "schema-check-cli"
|
||||
- name: Install CLI dependencies
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
LangChain
|
||||
LangGraph
|
||||
LangSmith
|
||||
thead
|
||||
stdio
|
||||
nd
|
||||
jupyter
|
||||
lets
|
||||
lite
|
||||
uis
|
||||
deque
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
@@ -34,10 +34,16 @@
|
||||
id: extract_ignore_words
|
||||
|
||||
- name: Codespell
|
||||
uses: codespell-project/actions-codespell@v2
|
||||
uses: codespell-project/actions-codespell@v2.1
|
||||
with:
|
||||
skip: '*.ambr,*.lock,*.ipynb,*.yaml,*.zlib,*.md'
|
||||
skip: '*.ambr,*.lock,*.ipynb,*.yaml,*.zlib,*.css.map,*.js.map'
|
||||
ignore_words_list: ${{ steps.extract_ignore_words.outputs.ignore_words_list }}
|
||||
# We do this to avoid spellchecking cell outputs
|
||||
- name: Codespell Notebooks
|
||||
run: make codespell
|
||||
run: make codespell
|
||||
|
||||
- name: Codespell LangGraph Library
|
||||
run: |
|
||||
# Change to root directory to check the main LangGraph library
|
||||
cd ..
|
||||
codespell --skip="*.ambr,*.lock,*.ipynb,*.yaml,*.zlib,*.css.map,*.js.map,*.pyc,__pycache__/*" --ignore-words-list="${{ steps.extract_ignore_words.outputs.ignore_words_list }}" libs/langgraph/langgraph/
|
||||
@@ -1,159 +0,0 @@
|
||||
name: Deploy Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs
|
||||
|
||||
jobs:
|
||||
get-changed-files:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
changed-files: ${{ steps.changed-files.outputs.added_modified }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: Ana06/get-changed-files@v2.3.0
|
||||
with:
|
||||
filter: "docs/docs/**"
|
||||
|
||||
# TODO: Uncomment this to run on PRs
|
||||
# run-changed-notebooks:
|
||||
# needs: get-changed-files
|
||||
# uses: ./.github/workflows/run_notebooks.yml
|
||||
# secrets: inherit
|
||||
# with:
|
||||
# changed-files: ${{ needs.get-changed-files.outputs.changed-files }}
|
||||
|
||||
deploy:
|
||||
# needs: run-changed-notebooks
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10 # Job will be cancelled if it runs for more than 10 minutes
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.MKDOCS_GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
enable-cache: true
|
||||
cache-suffix: "docs"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn
|
||||
uv sync --all-groups
|
||||
# we run this installation only for internal PRs
|
||||
# as GITHUB_TOKEN is not available for PRs from outside contributors
|
||||
if [ -n "${GITHUB_TOKEN}" ]; then
|
||||
uv run pip install "git+https://${GITHUB_TOKEN}@github.com/langchain-ai/mkdocs-material-insiders.git"
|
||||
fi
|
||||
|
||||
- name: Run unit tests
|
||||
# Run unit tests on the docs build pipeline
|
||||
run: make tests
|
||||
- name: Lint Docs
|
||||
# This step lints the docs using the existing linting set up.
|
||||
# It should be very fast and should not require any external services.
|
||||
run: make lint-docs
|
||||
- name: Build llms-text
|
||||
run: make llms-text
|
||||
- name: Build site
|
||||
run: |
|
||||
# If this is main branch, then we want to download stats. we do this
|
||||
# with the env variable DOWNLOAD_STATS=true
|
||||
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
|
||||
DOWNLOAD_STATS=true make build-docs
|
||||
else
|
||||
make build-docs
|
||||
fi
|
||||
env:
|
||||
MKDOCS_GIT_COMMITTERS_APIKEY: ${{ secrets.MKDOCS_GIT_COMMITTERS_APIKEY }}
|
||||
OPENAI_API_KEY: sf-proj-1234567890 # fake placeholder, shouldn't actually be used
|
||||
ANTHROPIC_API_KEY: sk-ant-api03-1234567890 # fake placeholder, shouldn't actually be used
|
||||
- name: Check links in notebooks
|
||||
env:
|
||||
LANGCHAIN_API_KEY: test
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "schedule" ]; then
|
||||
echo "Running link check on all HTML files matching notebooks in docs directory..."
|
||||
uv run pytest -v \
|
||||
--check-links-ignore "https://(api|web|docs)\.smith\.langchain\.com/.*" \
|
||||
--check-links-ignore "https://academy\.langchain\.com/.*" \
|
||||
--check-links-ignore "https://x.com/.*" \
|
||||
--check-links-ignore "https://twitter.com/.*" \
|
||||
--check-links-ignore "https://github\.com/.*" \
|
||||
--check-links-ignore "http://localhost:8123/.*" \
|
||||
--check-links-ignore "http://localhost:2024.*" \
|
||||
--check-links-ignore "http://127.0.0.1:.*" \
|
||||
--check-links-ignore "/.*\.(ipynb|html)$" \
|
||||
--check-links-ignore "https://python\.langchain\.com/.*" \
|
||||
--check-links-ignore "https://openai\.com/.*" \
|
||||
--check-links-ignore "https://www\.uber\.com/.*" \
|
||||
--check-links-ignore "https://pepy\.tech/.*" \
|
||||
--check-links-ignore "docs/docs/static/wordmark_*" \
|
||||
--check-links $(find site -name "index.html" | grep -v 'storm/index.html')
|
||||
|
||||
else
|
||||
echo "Fetching changes from origin/main..."
|
||||
git fetch origin main
|
||||
echo "Checking for changed notebook files..."
|
||||
CHANGED_FILES=$(git diff --name-only --diff-filter=d origin/main | grep 'docs/docs/.*\.ipynb$' | grep -v 'storm.ipynb' | sed -E 's|^docs/docs/|site/|; s/\.ipynb$/\/index.html/' || true)
|
||||
echo "Changed files: ${CHANGED_FILES}"
|
||||
if [ -n "${CHANGED_FILES}" ]; then
|
||||
echo "Running link check on HTML files matching changed notebook files..."
|
||||
uv run pytest -v \
|
||||
--check-links-ignore "https://(api|web|docs)\.smith\.langchain\.com/.*" \
|
||||
--check-links-ignore "https://academy\.langchain\.com/.*" \
|
||||
--check-links-ignore "http://localhost:8123/.*" \
|
||||
--check-links-ignore "http://localhost:2024.*" \
|
||||
--check-links-ignore "http://127.0.0.1:.*" \
|
||||
--check-links-ignore "https://x.com/.*" \
|
||||
--check-links-ignore "https://twitter.com/.*" \
|
||||
--check-links-ignore "https://github\.com/.*" \
|
||||
--check-links-ignore "/.*\.(ipynb|html)$" \
|
||||
--check-links-ignore "docs/docs/static/wordmark_*" \
|
||||
--check-links ${CHANGED_FILES} \
|
||||
|| ([ $? = 5 ] && exit 0 || exit $?)
|
||||
else
|
||||
echo "No notebook files changed."
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Configure GitHub Pages
|
||||
if: github.ref == 'refs/heads/main'
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Upload Pages Artifact
|
||||
# if: github.ref == 'refs/heads/main'
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: ./docs/site/
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
if: github.ref == 'refs/heads/main'
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
name: PR Title Lint
|
||||
|
||||
permissions:
|
||||
pull-requests: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, synchronize]
|
||||
|
||||
jobs:
|
||||
lint-pr-title:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Validate PR Title
|
||||
uses: amannn/action-semantic-pull-request@v6
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
types: |
|
||||
feat
|
||||
fix
|
||||
docs
|
||||
style
|
||||
refactor
|
||||
perf
|
||||
test
|
||||
build
|
||||
ci
|
||||
chore
|
||||
revert
|
||||
release
|
||||
scopes: |
|
||||
checkpoint
|
||||
checkpoint-postgres
|
||||
checkpoint-sqlite
|
||||
cli
|
||||
langgraph
|
||||
prebuilt
|
||||
scheduler-kafka
|
||||
sdk-py
|
||||
docs
|
||||
ci
|
||||
deps
|
||||
requireScope: false
|
||||
ignoreLabels: |
|
||||
ignore-lint-pr-title
|
||||
@@ -16,7 +16,6 @@ env:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
outputs:
|
||||
@@ -26,10 +25,10 @@ jobs:
|
||||
tag: ${{ steps.check-version.outputs.tag }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
enable-cache: true
|
||||
@@ -51,7 +50,7 @@ jobs:
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: dist
|
||||
path: ${{ inputs.working-directory }}/dist/
|
||||
@@ -62,7 +61,13 @@ jobs:
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: |
|
||||
PKG_NAME=$(grep -m 1 "^name = " pyproject.toml | cut -d '"' -f 2)
|
||||
VERSION=$(grep -m 1 "^version = " pyproject.toml | cut -d '"' -f 2)
|
||||
if grep -q 'dynamic.*=.*\[.*"version".*\]' pyproject.toml; then
|
||||
# handle dynamic versioning
|
||||
DIR_NAME=$(echo "$PKG_NAME" | tr '-' '_')
|
||||
VERSION=$(grep -m 1 '^__version__' "${DIR_NAME}/__init__.py" | cut -d '"' -f 2)
|
||||
else
|
||||
VERSION=$(grep -m 1 "^version = " pyproject.toml | cut -d '"' -f 2)
|
||||
fi
|
||||
SHORT_PKG_NAME="$(echo "$PKG_NAME" | sed -e 's/langgraph//g' -e 's/-//g')"
|
||||
if [ -z $SHORT_PKG_NAME ]; then
|
||||
TAG="$VERSION"
|
||||
@@ -81,7 +86,7 @@ jobs:
|
||||
outputs:
|
||||
release-body: ${{ steps.generate-release-body.outputs.release-body }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: langchain-ai/langgraph
|
||||
path: langgraph
|
||||
@@ -137,7 +142,9 @@ jobs:
|
||||
needs:
|
||||
- build
|
||||
- release-notes
|
||||
permissions: write-all
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
uses: ./.github/workflows/_test_release.yml
|
||||
with:
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
@@ -150,7 +157,7 @@ jobs:
|
||||
- test-pypi-publish
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
# We explicitly *don't* set up caching here. This ensures our tests are
|
||||
# maximally sensitive to catching breakage.
|
||||
@@ -166,7 +173,7 @@ jobs:
|
||||
# used in the real world.
|
||||
|
||||
- name: Set up Python
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
enable-cache: true
|
||||
@@ -214,7 +221,7 @@ jobs:
|
||||
uv run python -c "import $IMPORT_NAME; print(dir($IMPORT_NAME))"
|
||||
|
||||
- name: Import test dependencies
|
||||
run: uv sync --group dev
|
||||
run: uv sync --group test
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
|
||||
# Overwrite the local version of the package with the test PyPI version.
|
||||
@@ -253,16 +260,16 @@ jobs:
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
enable-cache: true
|
||||
cache-suffix: "release"
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: dist
|
||||
path: ${{ inputs.working-directory }}/dist/
|
||||
@@ -294,16 +301,16 @@ jobs:
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
enable-cache: true
|
||||
cache-suffix: "release"
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: dist
|
||||
path: ${{ inputs.working-directory }}/dist/
|
||||
@@ -315,5 +322,6 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
generateReleaseNotes: false
|
||||
tag: ${{needs.build.outputs.tag}}
|
||||
name: ${{ needs.build.outputs.pkg-name }}==${{ needs.build.outputs.version }}
|
||||
body: ${{ needs.release-notes.outputs.release-body }}
|
||||
commit: ${{ github.sha }}
|
||||
|
||||
@@ -28,9 +28,9 @@ jobs:
|
||||
- "latest"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Python + Poetry
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: "3.11"
|
||||
enable-cache: true
|
||||
|
||||
@@ -16,13 +16,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
# use minimum supported Python version
|
||||
python-version: "3.9"
|
||||
python-version: "3.10"
|
||||
enable-cache: true
|
||||
cache-suffix: "uv-lock-upgrade"
|
||||
|
||||
@@ -33,8 +33,8 @@ jobs:
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: "chore[deps]: upgrade dependencies with `uv lock --upgrade`"
|
||||
title: "chore[deps]: upgrade dependencies with `uv lock --upgrade`"
|
||||
commit-message: "chore(deps): upgrade dependencies with `uv lock --upgrade`"
|
||||
title: "chore(deps): upgrade dependencies with `uv lock --upgrade`"
|
||||
body: |
|
||||
This PR updates the dependencies in all Python packages using `uv lock --upgrade`.
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ Below is a high-level overview:
|
||||
- **langgraph** – core framework for building stateful, multi-actor agents.
|
||||
- **prebuilt** – high-level APIs for creating and running agents and tools.
|
||||
- **sdk-js** – JS/TS SDK for interacting with the LangGraph REST API.
|
||||
- **sdk-py** – Python SDK for the LangGraph Platform API.
|
||||
- **sdk-py** – Python SDK for the LangGraph Server API.
|
||||
|
||||
### Dependency map
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# AGENTS Instructions
|
||||
|
||||
This repository is a monorepo. Each library lives in a subdirectory under `libs/`.
|
||||
|
||||
When you modify code in any library, run the following commands in that library's directory before creating a pull request:
|
||||
|
||||
- `make format` – run code formatters
|
||||
- `make lint` – run the linter
|
||||
- `make test` – execute the test suite
|
||||
|
||||
To run a particular test file or to pass additional pytest options you can specify the `TEST` variable:
|
||||
|
||||
```
|
||||
TEST=path/to/test.py make test
|
||||
```
|
||||
|
||||
Other pytest arguments can also be supplied inside the `TEST` variable.
|
||||
|
||||
## Libraries
|
||||
|
||||
The repository contains several Python and JavaScript/TypeScript libraries.
|
||||
Below is a high-level overview:
|
||||
|
||||
- **checkpoint** – base interfaces for LangGraph checkpointers.
|
||||
- **checkpoint-postgres** – Postgres implementation of the checkpoint saver.
|
||||
- **checkpoint-sqlite** – SQLite implementation of the checkpoint saver.
|
||||
- **cli** – official command-line interface for LangGraph.
|
||||
- **langgraph** – core framework for building stateful, multi-actor agents.
|
||||
- **prebuilt** – high-level APIs for creating and running agents and tools.
|
||||
- **sdk-js** – JS/TS SDK for interacting with the LangGraph REST API.
|
||||
- **sdk-py** – Python SDK for the LangGraph Server API.
|
||||
|
||||
### Dependency map
|
||||
|
||||
The diagram below lists downstream libraries for each production dependency as
|
||||
declared in that library's `pyproject.toml` (or `package.json`).
|
||||
|
||||
```text
|
||||
checkpoint
|
||||
├── checkpoint-postgres
|
||||
├── checkpoint-sqlite
|
||||
├── prebuilt
|
||||
└── langgraph
|
||||
|
||||
prebuilt
|
||||
└── langgraph
|
||||
|
||||
sdk-py
|
||||
├── langgraph
|
||||
└── cli
|
||||
|
||||
sdk-js (standalone)
|
||||
```
|
||||
|
||||
Changes to a library may impact all of its dependents shown above.
|
||||
-294
@@ -1,294 +0,0 @@
|
||||
# Contributing to LangGraph
|
||||
|
||||
Thank you for being interested in contributing to LangGraph!
|
||||
|
||||
## General guidelines
|
||||
|
||||
Here are some things to keep in mind for all types of contributions:
|
||||
|
||||
- Follow the ["fork and pull request"](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project) workflow.
|
||||
- Fill out the checked-in pull request template when opening pull requests. Note related issues and tag relevant maintainers.
|
||||
- Ensure your PR passes formatting, linting, and testing checks before requesting a review.
|
||||
- If you would like comments or feedback, please open an issue or discussion and tag a maintainer.
|
||||
- Backwards compatibility is key. Your changes must not be breaking, except in case of critical bug and security fixes.
|
||||
- Look for duplicate PRs or issues that have already been opened before opening a new one.
|
||||
- Keep scope as isolated as possible. As a general rule, your changes should not affect more than one package at a time.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
For bug fixes, please open up an issue before proposing a fix to ensure the proposal properly addresses the underlying problem. In general, bug fixes should all have an accompanying unit test that fails before the fix.
|
||||
|
||||
### New features
|
||||
|
||||
For new features, please start a new [discussion](https://github.com/langchain-ai/langgraph/discussions), where the maintainers will help with scoping out the necessary changes.
|
||||
|
||||
## Contribute Documentation
|
||||
|
||||
Documentation is a vital part of LangGraph. We welcome both new documentation for new features and
|
||||
community improvements to our current documentation. Please read the resources below before getting started:
|
||||
|
||||
- [Documentation style guide](#documentation-style-guide)
|
||||
- [Documentation setup](#setup)
|
||||
|
||||
## Documentation Style Guide
|
||||
|
||||
As LangGraph continues to grow, the surface area of documentation required to cover it continues to grow too.
|
||||
This page provides guidelines for anyone writing documentation for LangGraph, as well as some of our philosophies around organization and structure.
|
||||
|
||||
## Philosophy
|
||||
|
||||
LangGraph's documentation follows the [Diataxis framework](https://diataxis.fr).
|
||||
Under this framework, all documentation falls under one of four categories: [Tutorials](#tutorials),
|
||||
[How-to guides](#how-to-guides),
|
||||
[References](#references), and [Explanations (aka conceptual guides)](#conceptual-guide).
|
||||
|
||||
### Tutorials
|
||||
|
||||
Tutorials are lessons that take the reader through a practical activity. Their purpose is to help the user
|
||||
gain understanding of concepts and how they interact by showing one way to achieve some goal in a hands-on way.
|
||||
|
||||
They should **avoid** giving
|
||||
multiple permutations of ways to achieve that goal in-depth. Choice is burdensome. Instead, they should guide a new user through a recommended path to accomplishing a concrete goal. While the end result of a tutorial does not necessarily need to
|
||||
be completely production-ready, it should be useful and practically satisfy the goal that you clearly stated in the tutorial's introduction.
|
||||
|
||||
To quote the Diataxis website:
|
||||
|
||||
> A tutorial serves the user’s *acquisition* of skills and knowledge - their study. Its purpose is not to help the user get something done, but to help them learn.
|
||||
|
||||
In LangGraph, these are often higher level guides that show off end-to-end use cases.
|
||||
|
||||
Some examples include:
|
||||
|
||||
- [Build a Customer Support Bot](https://langchain-ai.github.io/langgraph/tutorials/customer-support/customer-support/)
|
||||
- [Build a SQL Agent](https://langchain-ai.github.io/langgraph/tutorials/sql/sql-agent/)
|
||||
|
||||
Here are some high-level tips on writing a good tutorial:
|
||||
|
||||
- Focus on guiding the user to get something done, but keep in mind the end-goal is more to impart principles than to create a perfect production system.
|
||||
- Be specific, not abstract and follow one path.
|
||||
- No need to go deeply into alternative approaches, but it’s ok to reference them, ideally with a link to an appropriate how-to guide.
|
||||
- Get "a point on the board" as soon as possible - something the user can run that outputs something.
|
||||
- You can iterate and expand afterwards.
|
||||
- Try to frequently checkpoint at given steps where the user can run code and see progress.
|
||||
- Focus on results, not technical explanation.
|
||||
- Crosslink heavily to appropriate conceptual/reference pages
|
||||
- The first time you mention a LangGraph concept, use its full name (e.g. "human-in-the-loop"), and link to its conceptual/other documentation page.
|
||||
- It's also helpful to add a prerequisite callout that links to any pages with necessary background information.
|
||||
- End with a recap/next steps section summarizing what the tutorial covered and future reading, such as related how-to guides.
|
||||
- Use phrases like "Next we can run X & Y. We will expect Z.". Then afterwards, use language like "Notice Z" that recalls our expectations and directs the reader's attention to the topic we are trying to teach.
|
||||
- Do not shy away from repetition.
|
||||
|
||||
### How-to guides
|
||||
|
||||
A how-to guide, as the name implies, demonstrates how to do something discrete and specific.
|
||||
It should assume that the user is already familiar with underlying concepts, and is trying to solve an immediate problem, but
|
||||
should still give some background or list the scenarios where the information contained within can be relevant.
|
||||
They can and should discuss alternatives if one approach may be better than another in certain cases.
|
||||
|
||||
To quote the Diataxis website:
|
||||
|
||||
> A how-to guide serves the work of the already-competent user, whom you can assume to know what they want to do, and to be able to follow your instructions correctly.
|
||||
|
||||
Some examples include:
|
||||
|
||||
- [How to add persistence to your graph](https://langchain-ai.github.io/langgraph/how-tos/persistence/)
|
||||
- [How to view and update past graph state](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/time-travel/)
|
||||
|
||||
Here are some high-level tips on writing a good how-to guide:
|
||||
|
||||
- Clearly explain what you are guiding the user through at the start
|
||||
- Assume higher intent than a tutorial and show what the user needs to do to get that task done
|
||||
- Assume familiarity of concepts, but explain why suggested actions are helpful
|
||||
- Crosslink heavily to conceptual/reference pages
|
||||
- Discuss alternatives and responses to real-world tradeoffs that may arise when solving a problem
|
||||
- Use lots of example code, ideally within complete code blocks that the reader can copy and run.
|
||||
- End with a recap/next steps section summarizing what the tutorial covered and future reading, such as other related how-to guides
|
||||
|
||||
### Conceptual guides
|
||||
|
||||
LangGraph's conceptual guides fall under the **Explanation** quadrant of Diataxis. They should cover LangChain terms and concepts
|
||||
in a more abstract way than how-to guides or tutorials, and should be geared towards curious users interested in
|
||||
gaining a deeper understanding of the framework. Try to avoid excessively large code examples. The goal here is to
|
||||
impart perspective to the user rather than to finish a practical project. These guides should cover **why** things work the way they do.
|
||||
|
||||
|
||||
To quote the Diataxis website:
|
||||
|
||||
> The perspective of explanation is higher and wider than that of the other types. It does not take the user’s eye-level view, as in a how-to guide, or a close-up view of the machinery, like reference material. Its scope in each case is a topic - “an area of knowledge”, that somehow has to be bounded in a reasonable, meaningful way.
|
||||
|
||||
Some examples include:
|
||||
|
||||
- [What does it mean to be agentic?](https://langchain-ai.github.io/langgraph/concepts/high_level/)
|
||||
- [Tool calling](https://langchain-ai.github.io/langgraph/concepts/agentic_concepts/#tool-calling)
|
||||
|
||||
Here are some high-level tips on writing a good conceptual guide:
|
||||
|
||||
- Explain design decisions. Why does concept X exist and why was it designed this way?
|
||||
- Use analogies and reference other concepts and alternatives
|
||||
- Avoid blending in too much reference content
|
||||
- You can and should reference content covered in other guides, but make sure to link to them
|
||||
|
||||
### References
|
||||
|
||||
References contain detailed, low-level information that describes exactly what functionality exists and how to use it.
|
||||
In LangGraph, this is mainly our API reference pages, which are populated from docstrings within code.
|
||||
References pages are generally not read end-to-end, but are consulted as necessary when a user needs to know
|
||||
how to use something specific.
|
||||
|
||||
To quote the Diataxis website:
|
||||
|
||||
> The only purpose of a reference guide is to describe, as succinctly as possible, and in an orderly way. Whereas the content of tutorials and how-to guides are led by needs of the user, reference material is led by the product it describes.
|
||||
|
||||
Many of the reference pages in LangChain are automatically generated from code,
|
||||
but here are some high-level tips on writing a good docstring:
|
||||
|
||||
- Be concise
|
||||
- Discuss special cases and deviations from a user's expectations
|
||||
- Go into detail on required inputs and outputs
|
||||
- Light details on when one might use the feature are fine, but in-depth details belong in other sections.
|
||||
|
||||
Each category serves a distinct purpose and requires a specific approach to writing and structuring the content.
|
||||
|
||||
## General guidelines
|
||||
|
||||
Here are some other guidelines you should think about when writing and organizing documentation.
|
||||
|
||||
We generally do not merge new tutorials from outside contributors without an actual need.
|
||||
We welcome updates as well as new integration docs, how-tos, and references.
|
||||
|
||||
### Avoid duplication
|
||||
|
||||
Multiple pages that cover the same material in depth are difficult to maintain and cause confusion. There should
|
||||
be only one (very rarely two), canonical pages for a given concept or feature. Instead, you should link to other guides.
|
||||
|
||||
### Link to other sections
|
||||
|
||||
Because sections of the docs do not exist in a vacuum, it is important to link to other sections as often as possible
|
||||
to allow a developer to learn more about an unfamiliar topic inline.
|
||||
|
||||
This includes linking to the API references as well as conceptual sections!
|
||||
|
||||
### Be concise
|
||||
|
||||
In general, take a less-is-more approach. If a section with a good explanation of a concept already exists, you should link to it rather than
|
||||
re-explain it, unless the concept you are documenting presents some new wrinkle.
|
||||
|
||||
Be concise, including in code samples.
|
||||
|
||||
### General style
|
||||
|
||||
- Use active voice and present tense whenever possible
|
||||
- Use examples and code snippets to illustrate concepts and usage
|
||||
- Use appropriate header levels (`#`, `##`, `###`, etc.) to organize the content hierarchically
|
||||
- Use fewer cells with more code to make copy/paste easier
|
||||
- Use bullet points and numbered lists to break down information into easily digestible chunks
|
||||
- Use tables (especially for **Reference** sections) and diagrams often to present information visually
|
||||
- Include the table of contents for longer documentation pages to help readers navigate the content, but hide it for shorter pages
|
||||
|
||||
## Setup
|
||||
|
||||
LangChain documentation consists of two components:
|
||||
|
||||
1. Main Documentation: Hosted at [https://langchain-ai.github.io](https://langchain-ai.github.io/langgraph/),
|
||||
this comprehensive resource serves as the primary user-facing documentation.
|
||||
It covers a wide array of topics, including tutorials, use cases, integrations,
|
||||
and more, offering extensive guidance on building with LangGraph.
|
||||
The content for this documentation lives in the `/docs` directory of the monorepo.
|
||||
2. In-code Documentation: This is documentation of the codebase itself, which is also
|
||||
used to generate the externally facing [API Reference](https://langchain-ai.github.io/langgraph/reference/graphs/).
|
||||
The content for the API reference is autogenerated by scanning the docstrings in the codebase. For this reason we ask that developers document their code well.
|
||||
|
||||
We appreciate all contributions to the documentation, whether it be fixing a typo,
|
||||
adding a new tutorial or example and whether it be in the main documentation or the API Reference.
|
||||
|
||||
### 📜 Main Documentation
|
||||
|
||||
The content for the main documentation is located in the `/docs` directory of the monorepo.
|
||||
|
||||
The documentation is written using a combination of ipython notebooks (`.ipynb` files)
|
||||
and markdown (`.md` files). The notebooks are converted to markdown
|
||||
and then built using [MkDocs](https://www.mkdocs.org/).
|
||||
|
||||
Feel free to make contributions to the main documentation! 🥰
|
||||
|
||||
After modifying the documentation:
|
||||
|
||||
1. Run the linting and formatting commands (see below) to ensure that the documentation is well-formatted and free of errors.
|
||||
2. Optionally build the documentation locally to verify that the changes look good.
|
||||
3. Make a pull request with the changes.
|
||||
|
||||
### ⚒️ Linting and Building Documentation Locally
|
||||
|
||||
After writing up the documentation, you may want to lint and build the documentation
|
||||
locally to ensure that it looks good and is free of errors.
|
||||
|
||||
If you're unable to build it locally that's okay as well, as you will be able to
|
||||
see a preview of the documentation on the pull request page.
|
||||
|
||||
From the **monorepo root**, run the following command to install the dependencies:
|
||||
|
||||
<!-- TODO -->
|
||||
```bash
|
||||
poetry install --with docs --no-root
|
||||
```
|
||||
|
||||
#### Building
|
||||
|
||||
The code that builds the documentation is located in the `/docs` directory of the monorepo.
|
||||
|
||||
Before building the documentation, it is always a good idea to clean the build directory:
|
||||
|
||||
```bash
|
||||
make clean-docs
|
||||
```
|
||||
|
||||
You can build and preview the documentation as outlined below:
|
||||
|
||||
```bash
|
||||
make serve-docs
|
||||
```
|
||||
|
||||
#### Linting
|
||||
|
||||
The documentation is linted from the **monorepo root**. To lint it, run the following from there:
|
||||
|
||||
```bash
|
||||
make spellcheck
|
||||
```
|
||||
|
||||
### ️In-code Documentation
|
||||
|
||||
The in-code documentation is autogenerated from docstrings.
|
||||
|
||||
For the API reference to be useful, the codebase must be well-documented. This means that all functions, classes, and methods should have a docstring that explains what they do, what the arguments are, and what the return value is. This is a good practice in general, but it is especially important for LangChain because the API reference is the primary resource for developers to understand how to use the codebase.
|
||||
|
||||
We generally follow the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) for docstrings.
|
||||
|
||||
Here is an example of a well-documented function:
|
||||
|
||||
```python
|
||||
|
||||
def my_function(arg1: int, arg2: str) -> float:
|
||||
"""This is a short description of the function. (It should be a single sentence.)
|
||||
|
||||
This is a longer description of the function. It should explain what
|
||||
the function does, what the arguments are, and what the return value is.
|
||||
It should wrap at 88 characters.
|
||||
|
||||
Examples:
|
||||
This is a section for examples of how to use the function.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
my_function(1, "hello")
|
||||
|
||||
Args:
|
||||
arg1: This is a description of arg1. We do not need to specify the type since
|
||||
it is already specified in the function signature.
|
||||
arg2: This is a description of arg2.
|
||||
|
||||
Returns:
|
||||
This is a description of the return value.
|
||||
"""
|
||||
return 3.14
|
||||
```
|
||||
@@ -11,7 +11,7 @@
|
||||
[](https://pypi.org/project/langgraph/)
|
||||
[](https://pepy.tech/project/langgraph)
|
||||
[](https://github.com/langchain-ai/langgraph/issues)
|
||||
[](https://langchain-ai.github.io/langgraph/)
|
||||
[](https://docs.langchain.com/oss/python/langgraph/overview)
|
||||
|
||||
Trusted by companies shaping the future of agents – including Klarna, Replit, Elastic, and more – LangGraph is a low-level orchestration framework for building, managing, and deploying long-running, stateful agents.
|
||||
|
||||
@@ -23,59 +23,67 @@ Install LangGraph:
|
||||
pip install -U langgraph
|
||||
```
|
||||
|
||||
Then, create an agent [using prebuilt components](https://langchain-ai.github.io/langgraph/agents/agents/):
|
||||
Create a simple workflow:
|
||||
|
||||
```python
|
||||
# pip install -qU "langchain[anthropic]" to call the model
|
||||
from langgraph.graph import START, StateGraph
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get weather for a given city."""
|
||||
return f"It's always sunny in {city}!"
|
||||
class State(TypedDict):
|
||||
text: str
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_weather],
|
||||
prompt="You are a helpful assistant"
|
||||
)
|
||||
|
||||
# Run the agent
|
||||
agent.invoke(
|
||||
{"messages": [{"role": "user", "content": "what is the weather in sf"}]}
|
||||
)
|
||||
def node_a(state: State) -> dict:
|
||||
return {"text": state["text"] + "a"}
|
||||
|
||||
|
||||
def node_b(state: State) -> dict:
|
||||
return {"text": state["text"] + "b"}
|
||||
|
||||
|
||||
graph = StateGraph(State)
|
||||
graph.add_node("node_a", node_a)
|
||||
graph.add_node("node_b", node_b)
|
||||
graph.add_edge(START, "node_a")
|
||||
graph.add_edge("node_a", "node_b")
|
||||
|
||||
print(graph.compile().invoke({"text": ""}))
|
||||
# {'text': 'ab'}
|
||||
```
|
||||
|
||||
For more information, see the [Quickstart](https://langchain-ai.github.io/langgraph/agents/agents/). Or, to learn how to build an [agent workflow](https://langchain-ai.github.io/langgraph/concepts/low_level/) with a customizable architecture, long-term memory, and other complex task handling, see the [LangGraph basics tutorials](https://langchain-ai.github.io/langgraph/tutorials/get-started/1-build-basic-chatbot/).
|
||||
Get started with the [LangGraph Quickstart](https://docs.langchain.com/oss/python/langgraph/quickstart).
|
||||
|
||||
To quickly build agents with LangChain's `create_agent` (built on LangGraph), see the [LangChain Agents documentation](https://docs.langchain.com/oss/python/langchain/agents).
|
||||
|
||||
## Core benefits
|
||||
|
||||
LangGraph provides low-level supporting infrastructure for *any* long-running, stateful workflow or agent. LangGraph does not abstract prompts or architecture, and provides the following central benefits:
|
||||
|
||||
- [Durable execution](https://langchain-ai.github.io/langgraph/concepts/durable_execution/): Build agents that persist through failures and can run for extended periods, automatically resuming from exactly where they left off.
|
||||
- [Human-in-the-loop](https://langchain-ai.github.io/langgraph/concepts/human_in_the_loop/): Seamlessly incorporate human oversight by inspecting and modifying agent state at any point during execution.
|
||||
- [Comprehensive memory](https://langchain-ai.github.io/langgraph/concepts/memory/): Create truly stateful agents with both short-term working memory for ongoing reasoning and long-term persistent memory across sessions.
|
||||
- [Durable execution](https://docs.langchain.com/oss/python/langgraph/durable-execution): Build agents that persist through failures and can run for extended periods, automatically resuming from exactly where they left off.
|
||||
- [Human-in-the-loop](https://docs.langchain.com/oss/python/langgraph/interrupts): Seamlessly incorporate human oversight by inspecting and modifying agent state at any point during execution.
|
||||
- [Comprehensive memory](https://docs.langchain.com/oss/python/langgraph/memory): Create truly stateful agents with both short-term working memory for ongoing reasoning and long-term persistent memory across sessions.
|
||||
- [Debugging with LangSmith](http://www.langchain.com/langsmith): Gain deep visibility into complex agent behavior with visualization tools that trace execution paths, capture state transitions, and provide detailed runtime metrics.
|
||||
- [Production-ready deployment](https://langchain-ai.github.io/langgraph/concepts/deployment_options/): Deploy sophisticated agent systems confidently with scalable infrastructure designed to handle the unique challenges of stateful, long-running workflows.
|
||||
- [Production-ready deployment](https://docs.langchain.com/langsmith/app-development): Deploy sophisticated agent systems confidently with scalable infrastructure designed to handle the unique challenges of stateful, long-running workflows.
|
||||
|
||||
## LangGraph’s ecosystem
|
||||
|
||||
While LangGraph can be used standalone, it also integrates seamlessly with any LangChain product, giving developers a full suite of tools for building agents. To improve your LLM application development, pair LangGraph with:
|
||||
|
||||
- [LangSmith](http://www.langchain.com/langsmith) — Helpful for agent evals and observability. Debug poor-performing LLM app runs, evaluate agent trajectories, gain visibility in production, and improve performance over time.
|
||||
- [LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) — Deploy and scale agents effortlessly with a purpose-built deployment platform for long running, stateful workflows. Discover, reuse, configure, and share agents across teams — and iterate quickly with visual prototyping in [LangGraph Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/).
|
||||
- [LangChain](https://python.langchain.com/docs/introduction/) – Provides integrations and composable components to streamline LLM application development.
|
||||
- [LangSmith Deployment](https://docs.langchain.com/langsmith/deployments) — Deploy and scale agents effortlessly with a purpose-built deployment platform for long running, stateful workflows. Discover, reuse, configure, and share agents across teams — and iterate quickly with visual prototyping in [LangGraph Studio](https://docs.langchain.com/oss/python/langgraph/studio).
|
||||
- [LangChain](https://docs.langchain.com/oss/python/langchain/overview) – Provides integrations and composable components to streamline LLM application development.
|
||||
|
||||
> [!NOTE]
|
||||
> Looking for the JS version of LangGraph? See the [JS repo](https://github.com/langchain-ai/langgraphjs) and the [JS docs](https://langchain-ai.github.io/langgraphjs/).
|
||||
> Looking for the JS version of LangGraph? See the [JS repo](https://github.com/langchain-ai/langgraphjs) and the [JS docs](https://docs.langchain.com/oss/javascript/langgraph/overview).
|
||||
|
||||
## Additional resources
|
||||
|
||||
- [Guides](https://langchain-ai.github.io/langgraph/how-tos/): Quick, actionable code snippets for topics such as streaming, adding memory & persistence, and design patterns (e.g. branching, subgraphs, etc.).
|
||||
- [Reference](https://langchain-ai.github.io/langgraph/reference/graphs/): Detailed reference on core classes, methods, how to use the graph and checkpointing APIs, and higher-level prebuilt components.
|
||||
- [Examples](https://langchain-ai.github.io/langgraph/tutorials/overview/): Guided examples on getting started with LangGraph.
|
||||
- [Guides](https://docs.langchain.com/oss/python/langgraph/guides): Quick, actionable code snippets for topics such as streaming, adding memory & persistence, and design patterns (e.g. branching, subgraphs, etc.).
|
||||
- [Reference](https://reference.langchain.com/python/langgraph/): Detailed reference on core classes, methods, how to use the graph and checkpointing APIs, and higher-level prebuilt components.
|
||||
- [Examples](https://docs.langchain.com/oss/python/langgraph/agentic-rag): Guided examples on getting started with LangGraph.
|
||||
- [LangChain Forum](https://forum.langchain.com/): Connect with the community and share all of your technical questions, ideas, and feedback.
|
||||
- [LangChain Academy](https://academy.langchain.com/courses/intro-to-langgraph): Learn the basics of LangGraph in our free, structured course.
|
||||
- [Templates](https://langchain-ai.github.io/langgraph/concepts/template_applications/): Pre-built reference apps for common agentic workflows (e.g. ReAct agent, memory, retrieval etc.) that can be cloned and adapted.
|
||||
- [Case studies](https://www.langchain.com/built-with-langgraph): Hear how industry leaders use LangGraph to ship AI applications at scale.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
+5
-2
@@ -13,10 +13,13 @@ build-prebuilt:
|
||||
uv run python -m _scripts.third_party_page.get_download_stats --fake stats.yml; \
|
||||
set +x; \
|
||||
fi
|
||||
uv run python -m _scripts.third_party_page.create_third_party_page stats.yml docs/agents/prebuilt.md --language python
|
||||
uv run python -m _scripts.third_party_page.create_third_party_page stats.yml docs/agents/prebuilt.md
|
||||
|
||||
build-docs: build-prebuilt
|
||||
uv run python -m mkdocs build --clean -f mkdocs.yml --strict
|
||||
TARGET_LANGUAGE=python uv run python -m mkdocs build --clean -f mkdocs.yml --strict
|
||||
|
||||
build-docs-js: build-prebuilt
|
||||
TARGET_LANGUAGE=js uv run python -m mkdocs build --clean -f mkdocs.yml --strict
|
||||
|
||||
llms-text:
|
||||
uv run python -m _scripts.generate_llms_text docs/llms-full.txt
|
||||
|
||||
+113
-10
@@ -1,24 +1,126 @@
|
||||
# Setup
|
||||
# LangGraph Documentation
|
||||
|
||||
To setup requirements for building docs you can run:
|
||||
For more information on contributing to our documentation, see the [Contributing Guide](../CONTRIBUTING.md).
|
||||
|
||||
```bash
|
||||
uv sync --group test
|
||||
## Structure
|
||||
|
||||
The primary documentation is located in the `docs/` directory. This directory contains both the source files for the main documentation as well as the API reference doc build process.
|
||||
|
||||
### Main Documentation
|
||||
|
||||
Main documentation files are located in `docs/docs/` and are written in Markdown format. The site uses [**MkDocs**](https://www.mkdocs.org/) with the [Material theme](https://squidfunk.github.io/mkdocs-material/) and includes:
|
||||
|
||||
- **Concepts**: Core LangGraph concepts and explanations
|
||||
- **Tutorials**: Step-by-step learning guides
|
||||
- **How-tos**: Task-focused guides for specific use cases
|
||||
- **Examples**: Real-world applications and use cases
|
||||
- **Jupyter Notebooks**: Interactive tutorials that are automatically converted to markdown
|
||||
|
||||
### API Reference
|
||||
|
||||
API reference documentation is defined in `docs/docs/reference/`. Each `.md` file outlines the "template" that each page is built from. Reference content is automatically generated from docstrings in the codebase using the **mkdocstrings** plugin. Once generated, the content is plugged into the corresponding markdown file where it is referenced by using manual directives to specify which classes and/or functions are documented:
|
||||
|
||||
```markdown
|
||||
::: langgraph.graph.state.StateGraph
|
||||
options:
|
||||
show_if_no_docstring: true
|
||||
show_root_heading: true
|
||||
show_root_full_path: false
|
||||
members:
|
||||
- add_node
|
||||
- add_edge
|
||||
- add_conditional_edges
|
||||
- add_sequence
|
||||
- compile
|
||||
```
|
||||
|
||||
## Serving documentation locally
|
||||
## Build Process
|
||||
|
||||
To run the documentation server locally you can run:
|
||||
Docs are built following these steps:
|
||||
|
||||
1. **Content Processing:**
|
||||
- `_scripts/notebook_hooks.py` - Main processing pipeline that:
|
||||
- Converts how-tos/tutorial Jupyter notebooks to markdown using `notebook_convert.py`
|
||||
- Adds automatic API reference links to code blocks using `generate_api_reference_links.py`
|
||||
- Handles conditional rendering for Python/JS versions
|
||||
- Processes highlight comments and custom syntax
|
||||
|
||||
2. **API Reference Generation:**
|
||||
- **mkdocstrings** plugin extracts docstrings from Python source code
|
||||
- Manual `::: module.Class` directives in reference pages (`/docs/docs/*`) specify what to document
|
||||
- Cross-references are automatically generated between docs and API
|
||||
|
||||
3. **Site Generation:**
|
||||
- **MkDocs** processes all markdown files and generates static HTML
|
||||
- Custom hooks handle redirects and inject additional functionality
|
||||
|
||||
4. **Deployment:**
|
||||
- Site is deployed with Vercel
|
||||
- `make build-docs` generates production build (also usable for local testing)
|
||||
- Automatic redirects handle URL changes between versions
|
||||
|
||||
### Local Development
|
||||
|
||||
For local development, use the Makefile targets:
|
||||
|
||||
```bash
|
||||
# Serve docs locally with hot reloading
|
||||
make serve-docs
|
||||
|
||||
# Clean build for production testing
|
||||
make build-docs
|
||||
|
||||
# Serve with clean build
|
||||
make serve-clean-docs
|
||||
```
|
||||
|
||||
This will start the documentation server on [http://127.0.0.1:8000/langgraph/](http://127.0.0.1:8000/langgraph/).
|
||||
The `serve-docs` command:
|
||||
|
||||
- Watches source files for changes
|
||||
- Includes dirty builds for faster iteration
|
||||
- Serves on [http://127.0.0.1:8000/langgraph/](http://127.0.0.1:8000/langgraph/)
|
||||
|
||||
## Standards
|
||||
|
||||
**Docstring Format:**
|
||||
The API reference uses **Google-style docstrings** with Markdown markup. The `mkdocstrings` plugin processes these to generate documentation.
|
||||
|
||||
**Required format:**
|
||||
|
||||
```python
|
||||
def example_function(param1: str, param2: int = 5) -> bool:
|
||||
"""Brief description of the function.
|
||||
|
||||
Longer description can go here. Use Markdown syntax for
|
||||
rich formatting like **bold** and *italic*.
|
||||
|
||||
Args:
|
||||
param1: Description of the first parameter.
|
||||
param2: Description of the second parameter with default value.
|
||||
|
||||
Returns:
|
||||
Description of the return value.
|
||||
|
||||
Raises:
|
||||
ValueError: When param1 is empty.
|
||||
TypeError: When param2 is not an integer.
|
||||
|
||||
!!! warning
|
||||
This function is experimental and may change.
|
||||
|
||||
!!! version-added "Added in version 0.2.0"
|
||||
"""
|
||||
```
|
||||
|
||||
**Special Markers:**
|
||||
|
||||
- **MkDocs admonitions**: `!!! warning`, `!!! note`, `!!! version-added`
|
||||
- **Code blocks**: Standard markdown ``` syntax
|
||||
- **Cross-references**: Automatic linking via `generate_api_reference_links.py`
|
||||
|
||||
## Execute notebooks
|
||||
|
||||
If you would like to automatically execute all of the notebooks, to mimic the "Run notebooks" GHA, you can run:
|
||||
If you would like to automatically execute all of the notebooks, to mimic the "Run notebooks" GitHub action, you can run:
|
||||
|
||||
```bash
|
||||
python _scripts/prepare_notebooks_for_ci.py
|
||||
@@ -33,8 +135,9 @@ python _scripts/prepare_notebooks_for_ci.py --comment-install-cells
|
||||
```
|
||||
|
||||
`prepare_notebooks_for_ci.py` script will add VCR cassette context manager for each cell in the notebook, so that:
|
||||
* when the notebook is run for the first time, cells with network requests will be recorded to a VCR cassette file
|
||||
* when the notebook is run subsequently, the cells with network requests will be replayed from the cassettes
|
||||
|
||||
- when the notebook is run for the first time, cells with network requests will be recorded to a VCR cassette file
|
||||
- when the notebook is run subsequently, the cells with network requests will be replayed from the cassettes
|
||||
|
||||
## Adding new notebooks
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"""Generate API reference links for imports in Python code blocks within markdown files."""
|
||||
|
||||
import ast
|
||||
import importlib
|
||||
import logging
|
||||
@@ -70,8 +72,18 @@ MANUAL_API_REFERENCES_LANGGRAPH = [
|
||||
([], "langgraph.checkpoint.postgres.aio", "AsyncPostgresSaver", "checkpoints"),
|
||||
([], "langgraph.checkpoint.postgres", "PostgresSaver", "checkpoints"),
|
||||
# other prebuilts
|
||||
(["langgraph_supervisor"], "langgraph_supervisor.supervisor", "create_supervisor", "supervisor"),
|
||||
(["langgraph_supervisor"], "langgraph_supervisor.handoff", "create_handoff_tool", "supervisor"),
|
||||
(
|
||||
["langgraph_supervisor"],
|
||||
"langgraph_supervisor.supervisor",
|
||||
"create_supervisor",
|
||||
"supervisor",
|
||||
),
|
||||
(
|
||||
["langgraph_supervisor"],
|
||||
"langgraph_supervisor.handoff",
|
||||
"create_handoff_tool",
|
||||
"supervisor",
|
||||
),
|
||||
([], "langgraph_supervisor.handoff", "create_forward_message_tool", "supervisor"),
|
||||
(["langgraph_swarm"], "langgraph_swarm.swarm", "create_swarm", "swarm"),
|
||||
(["langgraph_swarm"], "langgraph_swarm.swarm", "add_active_agent_router", "swarm"),
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
"""Logic to identify and transform cross-reference links in markdown files.
|
||||
|
||||
This module allows supporting custom markdown syntax for "autolinks". These are links
|
||||
that will be transformed based on the current scope context, such as "global", "python",
|
||||
or "js" into an appropriate markdown link format.
|
||||
|
||||
For example,
|
||||
|
||||
```markdown
|
||||
@[StateGraph]
|
||||
```
|
||||
|
||||
May be transformed into:
|
||||
|
||||
```markdown
|
||||
[StateGraph](some_path/api-reference/state-graph.md)
|
||||
```
|
||||
|
||||
The transformation value depends on the scope in which the link is used.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
from _scripts.link_map import SCOPE_LINK_MAPS
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _transform_link(
|
||||
link_name: str,
|
||||
scope: str,
|
||||
file_path: str,
|
||||
line_number: int,
|
||||
custom_title: Optional[str] = None,
|
||||
) -> Optional[str]:
|
||||
"""Transform a cross-reference link based on the current scope.
|
||||
|
||||
Args:
|
||||
link_name: The name of the link to transform (e.g., "StateGraph").
|
||||
scope: The current scope context ("global", "python", "js", etc.).
|
||||
file_path: The file path for error reporting.
|
||||
line_number: The line number for error reporting.
|
||||
custom_title: Optional custom title for the link. If `None`, uses link_name.
|
||||
|
||||
Returns:
|
||||
A formatted markdown link if the link is found in the scope mapping,
|
||||
None otherwise.
|
||||
|
||||
Example:
|
||||
>>> _transform_link("StateGraph", "python", "file.md", 5)
|
||||
"[StateGraph](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph)"
|
||||
|
||||
>>> _transform_link("StateGraph", "python", "file.md", 5, "Custom Title")
|
||||
"[Custom Title](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph)"
|
||||
|
||||
>>> _transform_link("unknown-link", "python", "file.md", 5)
|
||||
None
|
||||
"""
|
||||
if scope == "global":
|
||||
# Special scope that is composed of both Python and JS links
|
||||
# For now, we will substitute in the python scope!
|
||||
# But we need to add support for handling both scopes.
|
||||
scope = "python"
|
||||
logger.error(
|
||||
"Encountered unhandled 'global' scope. Defaulting to 'python'."
|
||||
"In file: %s, line %d, link_name: %s",
|
||||
file_path,
|
||||
line_number,
|
||||
link_name,
|
||||
)
|
||||
link_map = SCOPE_LINK_MAPS.get(scope, {})
|
||||
url = link_map.get(link_name)
|
||||
|
||||
if url:
|
||||
title = custom_title if custom_title is not None else link_name
|
||||
return f"[{title}]({url})"
|
||||
else:
|
||||
# Log error with file location information
|
||||
logger.info(
|
||||
# Using %s
|
||||
"Link '%s' not found in scope '%s'. "
|
||||
"In file: %s, line %d. Available links in scope: %s",
|
||||
link_name,
|
||||
scope,
|
||||
file_path,
|
||||
line_number,
|
||||
list(link_map.keys() if link_map else []),
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
CONDITIONAL_FENCE_PATTERN = re.compile(
|
||||
r"""
|
||||
^ # Start of line
|
||||
(?P<indent>[ \t]*) # Optional indentation (spaces or tabs)
|
||||
::: # Literal fence marker
|
||||
(?P<language>\w+)? # Optional language identifier (named group: language)
|
||||
\s* # Optional trailing whitespace
|
||||
$ # End of line
|
||||
""",
|
||||
re.VERBOSE,
|
||||
)
|
||||
CROSS_REFERENCE_PATTERN = re.compile(
|
||||
r"""
|
||||
(?: # Non-capturing group for two possible formats:
|
||||
@\[ # @ symbol followed by opening bracket for title
|
||||
(?P<title>[^\]]+) # Custom title - one or more non-bracket characters
|
||||
\] # Closing bracket for title
|
||||
\[ # Opening bracket for link name
|
||||
(?P<link_name_with_title>[^\]]+) # Link name - one or more non-bracket characters
|
||||
\] # Closing bracket for link name
|
||||
| # OR
|
||||
@\[ # @ symbol followed by opening bracket
|
||||
(?P<link_name>[^\]]+) # Link name - one or more non-bracket characters
|
||||
\] # Closing bracket
|
||||
)
|
||||
""",
|
||||
re.VERBOSE,
|
||||
)
|
||||
|
||||
|
||||
def _replace_autolinks(
|
||||
markdown: str, file_path: str, *, default_scope: str = "python"
|
||||
) -> str:
|
||||
"""Preprocess markdown lines to handle @[links] with conditional fence scopes.
|
||||
|
||||
This function processes markdown content to transform @[link_name] references
|
||||
based on the current conditional fence scope. Conditional fences use the
|
||||
syntax :::language to define scope boundaries.
|
||||
|
||||
Args:
|
||||
markdown: The markdown content to process.
|
||||
file_path: The file path for error reporting.
|
||||
default_scope: The default scope to use if no scope is matched.
|
||||
|
||||
Returns:
|
||||
Processed markdown content with @[references] transformed to proper
|
||||
markdown links or left unchanged if not found.
|
||||
|
||||
Example:
|
||||
Input:
|
||||
"@[StateGraph]\\n:::python\\n@[Command]\\n:::\\n"
|
||||
Output:
|
||||
"[StateGraph](url)\\n:::python\\n[Command](url)\\n:::\\n"
|
||||
"""
|
||||
# Track the current scope context
|
||||
current_scope = default_scope
|
||||
lines = markdown.splitlines(keepends=True)
|
||||
processed_lines = []
|
||||
|
||||
for line_number, line in enumerate(lines, 1):
|
||||
line_stripped = line.strip()
|
||||
|
||||
# Check if this line defines a new conditional fence scope
|
||||
fence_match = CONDITIONAL_FENCE_PATTERN.match(line_stripped)
|
||||
if fence_match:
|
||||
language = fence_match.group("language")
|
||||
# Set scope to the specified language, or reset to global if no language
|
||||
current_scope = language.lower() if language else default_scope
|
||||
processed_lines.append(line)
|
||||
continue
|
||||
|
||||
# Transform all @[link_name] references in this line based on current scope
|
||||
def replace_cross_reference(match: re.Match[str]) -> str:
|
||||
"""Replace a single @[link_name] with the scoped equivalent."""
|
||||
# Check if this is the @[title][ref] format or @[ref] format
|
||||
title = match.group("title")
|
||||
if title is not None:
|
||||
# This is @[title][ref] format
|
||||
link_name = match.group("link_name_with_title")
|
||||
custom_title = title
|
||||
else:
|
||||
# This is @[ref] format
|
||||
link_name = match.group("link_name")
|
||||
custom_title = None
|
||||
|
||||
transformed = _transform_link(
|
||||
link_name, current_scope, file_path, line_number, custom_title
|
||||
)
|
||||
return transformed if transformed is not None else match.group(0)
|
||||
|
||||
transformed_line = CROSS_REFERENCE_PATTERN.sub(replace_cross_reference, line)
|
||||
processed_lines.append(transformed_line)
|
||||
|
||||
return "".join(processed_lines)
|
||||
@@ -4,9 +4,11 @@ import argparse
|
||||
|
||||
import requests
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from textwrap import dedent
|
||||
|
||||
|
||||
# Load reference TypeScript snippets
|
||||
URL = "https://gist.githubusercontent.com/eyurtsev/e7486731415463a9bc5b4682358859c8/raw/b5a5fda9c7e3387cfcb781f25082814d43675d50/gistfile1.txt"
|
||||
URL = "https://gist.githubusercontent.com/dqbd/b35d49e2ceec80e654fe1c5ab61ec477/raw/f4768aeedb67628190a4e06d063a938afc8e7672/snippets.md"
|
||||
response = requests.get(URL)
|
||||
response.raise_for_status()
|
||||
reference_snippets = response.text
|
||||
@@ -14,6 +16,80 @@ reference_snippets = response.text
|
||||
# Initialize model
|
||||
model = ChatAnthropic(model="claude-sonnet-4-0", max_tokens=64_000)
|
||||
|
||||
|
||||
FLUENT_INTERFACE_PROMPT = (
|
||||
"CRITICAL: Always use method chaining (fluent interface) for StateGraph operations in TypeScript. "
|
||||
"Never create separate variables for the graph builder or call methods individually. "
|
||||
"The fluent interface provides better type safety and is the preferred pattern.\n\n"
|
||||
"CORRECT examples with fluent interface:\n"
|
||||
+ dedent(
|
||||
"""
|
||||
```typescript
|
||||
const graph = new StateGraph(MyState)
|
||||
.addNode('node1', node1)
|
||||
.addNode('node2', node2)
|
||||
.addEdge(START, 'node1')
|
||||
.addEdge('node1', 'node2')
|
||||
.addEdge('node2', END)
|
||||
.compile()
|
||||
```
|
||||
|
||||
```typescript
|
||||
const graph = new StateGraph(MyState)
|
||||
.addNode('chatbot', chatbot)
|
||||
.addEdge(START, 'chatbot')
|
||||
.addEdge('chatbot', END)
|
||||
.compile()
|
||||
```
|
||||
|
||||
```typescript
|
||||
const graph = new StateGraph(MyState)
|
||||
.addNode('chatbot', chatbot)
|
||||
.addEdge(START, 'chatbot')
|
||||
.addEdge('chatbot', END)
|
||||
.compile()
|
||||
```
|
||||
"""
|
||||
)
|
||||
+ "\n"
|
||||
+ "INCORRECT examples to avoid:\n"
|
||||
+ dedent(
|
||||
"""
|
||||
```typescript
|
||||
// WRONG: Creating separate builder variable
|
||||
const graphBuilder = new StateGraph(MyState)
|
||||
graphBuilder.addNode('node1', node1)
|
||||
graphBuilder.addEdge(START, 'node1')
|
||||
const graph = graphBuilder.compile()
|
||||
```
|
||||
|
||||
```typescript
|
||||
// WRONG: Using Python-style method names
|
||||
const workflow = new StateGraph(MyState)
|
||||
workflow.add_node('node1', node1)
|
||||
workflow.add_edge(START, 'node1')
|
||||
const graph = workflow.compile()
|
||||
```
|
||||
|
||||
```typescript
|
||||
// WRONG: Calling methods individually
|
||||
const graphBuilder = new StateGraph(MyState)
|
||||
graphBuilder.addNode('chatbot', chatbot)
|
||||
graphBuilder.addEdge(START, 'chatbot')
|
||||
graphBuilder.addEdge('chatbot', END)
|
||||
const graph = graphBuilder.compile()
|
||||
```
|
||||
"""
|
||||
)
|
||||
+ "\n"
|
||||
+ "Key rules:\n"
|
||||
+ "- Always chain methods directly on the StateGraph constructor\n"
|
||||
+ "- Use camelCase method names (addNode, addEdge, not add_node, add_edge)\n"
|
||||
+ "- Always end with .compile()\n"
|
||||
+ "- Never store the builder in a separate variable\n"
|
||||
)
|
||||
|
||||
|
||||
TRANSLATION_PROMPT = (
|
||||
"You are a helpful assistant that translates Python-based technical "
|
||||
"documentation written in Markdown to equivalent TypeScript-based documentation. "
|
||||
@@ -32,6 +108,12 @@ TRANSLATION_PROMPT = (
|
||||
"the translation. "
|
||||
"Use the reference TypeScript snippets as guidance whenever possible to "
|
||||
"maintain alignment with existing conventions.\n\n"
|
||||
"IMPORTANT REQUIREMENTS:\n"
|
||||
"- Use Zod for state definition for StateGraph. Avoid using Annotation since it will be deprecated in the future.\n"
|
||||
"- ALWAYS use fluent interface (method chaining) for StateGraph operations - this is CRITICAL\n"
|
||||
"- Never create separate variables for graph builders\n"
|
||||
"- Always chain methods directly on the StateGraph constructor and end with .compile()\n\n"
|
||||
f"{FLUENT_INTERFACE_PROMPT}\n\n"
|
||||
f"Here are the reference TypeScript snippets:\n\n{reference_snippets}\n\n"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
.prettierrc
|
||||
.eslint.config.mjs
|
||||
package.json
|
||||
README.md
|
||||
tsconfig.json
|
||||
yarn.lock
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"quoteProps": "as-needed",
|
||||
"jsxSingleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "always",
|
||||
"requirePragma": false,
|
||||
"insertPragma": false,
|
||||
"proseWrap": "preserve",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"vueIndentScriptAndStyle": false,
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
# \_codeblocks
|
||||
@@ -0,0 +1,14 @@
|
||||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
import tseslint from "typescript-eslint";
|
||||
import { defineConfig } from "eslint/config";
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
|
||||
plugins: { js },
|
||||
extends: ["js/recommended"],
|
||||
languageOptions: { globals: globals.browser },
|
||||
},
|
||||
tseslint.configs.recommended,
|
||||
]);
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "_codeblocks",
|
||||
"packageManager": "yarn@4.6.0",
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"format": "prettier --write .",
|
||||
"format:fix": "prettier --write . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@langchain/anthropic": "^0.3.24",
|
||||
"@langchain/core": "^0.3.66",
|
||||
"@langchain/langgraph": "^0.3.11",
|
||||
"@langchain/langgraph-api": "^0.0.52",
|
||||
"@langchain/langgraph-sdk": "^0.0.102",
|
||||
"@langchain/openai": "^0.6.3",
|
||||
"zod": "^4.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.32.0",
|
||||
"eslint": "^9.32.0",
|
||||
"globals": "^16.3.0",
|
||||
"jiti": "^2.5.1",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.38.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "esnext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "libReplacement": true, /* Enable lib replacement. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "nodenext", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
"moduleResolution": "nodenext", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||
// "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
// "noUncheckedSideEffectImports": true, /* Check side effect imports. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
|
||||
// "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": false, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
""
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+150
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env python
|
||||
"""Extracts typescript code blocks from a markdown file."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import os
|
||||
from typing import List, TypedDict, Literal
|
||||
|
||||
|
||||
class CodeBlock(TypedDict):
|
||||
"""A code block extracted from a markdown file."""
|
||||
starting_line: int
|
||||
"""The line number where the code block starts in the source file"""
|
||||
ending_line: int
|
||||
"""The line number where the code block ends in the source file"""
|
||||
indentation: int
|
||||
"""Number of spaces/tabs used for indentation of the code block"""
|
||||
source_file: str
|
||||
"""Path to the markdown file containing this code block"""
|
||||
frontmatter: str
|
||||
"""Any metadata or frontmatter specified after the opening code fence"""
|
||||
code: str
|
||||
"""The actual code content within the code block"""
|
||||
language: str
|
||||
"""The language of the code block (e.g. typescript, javascript)"""
|
||||
|
||||
|
||||
def extract_code_blocks(markdown_content: str, source_file: str) -> List[CodeBlock]:
|
||||
"""Extracts code blocks from a markdown file.
|
||||
|
||||
Args:
|
||||
markdown_content: The content of the markdown file.
|
||||
source_file: The path to the markdown file.
|
||||
|
||||
Returns:
|
||||
A list of TypedDicts, where each dict represents a code block.
|
||||
"""
|
||||
# Regex to find code blocks with specified languages, capturing indentation
|
||||
# and frontmatter.
|
||||
pattern = re.compile(
|
||||
r"^(?P<indentation>\s*)```(?P<language>typescript|javascript|ts|js)(?P<frontmatter>[^\n]*)\n(?P<code>.*?)\n^(?P=indentation)```\s*$",
|
||||
re.DOTALL | re.MULTILINE,
|
||||
)
|
||||
|
||||
code_blocks: List[CodeBlock] = []
|
||||
for match in pattern.finditer(markdown_content):
|
||||
start_pos = match.start()
|
||||
|
||||
# Calculate line numbers
|
||||
starting_line = markdown_content.count("\n", 0, start_pos) + 1
|
||||
ending_line = starting_line + match.group(0).count("\n")
|
||||
|
||||
indentation_str = match.group("indentation")
|
||||
|
||||
code_block: CodeBlock = {
|
||||
"starting_line": starting_line,
|
||||
"ending_line": ending_line,
|
||||
"indentation": len(indentation_str),
|
||||
"source_file": source_file,
|
||||
"frontmatter": match.group("frontmatter").strip(),
|
||||
"code": match.group("code"),
|
||||
"language": match.group("language"),
|
||||
}
|
||||
code_blocks.append(code_block)
|
||||
|
||||
return code_blocks
|
||||
|
||||
def dump_code_blocks(input_file: str, output_file: str, format: Literal["json", "inline"]) -> None:
|
||||
"""Function to extract and save code blocks from a markdown file.
|
||||
|
||||
Args:
|
||||
input_file: Path to the input markdown file.
|
||||
output_file: Path to the output JSON file for the extracted code blocks.
|
||||
format: Output format - either "json" or "inline"
|
||||
"""
|
||||
with open(input_file, "r", encoding="utf-8") as f:
|
||||
markdown_content = f.read()
|
||||
|
||||
extracted_code = extract_code_blocks(markdown_content, input_file)
|
||||
|
||||
if len(extracted_code) == 0:
|
||||
print(f"No code blocks found in {input_file}")
|
||||
return
|
||||
|
||||
if format == "json":
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
json.dump(extracted_code, f, indent=2)
|
||||
elif format == "inline":
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
for code_block in extracted_code:
|
||||
f.write(f"// {json.dumps({k:v for k,v in code_block.items() if k != 'code'})}\n")
|
||||
f.write("\n")
|
||||
f.write(code_block["code"])
|
||||
f.write("\n")
|
||||
print(f"Extracted {len(extracted_code)} code blocks from {input_file} to {output_file}")
|
||||
|
||||
def main(input_path: str, output_path: str, format: Literal["json", "inline"]) -> None:
|
||||
"""Main function to extract code blocks from a markdown file.
|
||||
|
||||
Args:
|
||||
input_file: Path to the input markdown file.
|
||||
output_file: Path to the output JSON file for the extracted code blocks.
|
||||
format: Output format - either "json" or "inline"
|
||||
"""
|
||||
# Check if input path is a directory
|
||||
if os.path.isdir(input_path):
|
||||
if os.path.isfile(output_path):
|
||||
raise ValueError("If input_path is a directory, output_path must also be a directory")
|
||||
if not os.path.isdir(output_path):
|
||||
os.makedirs(output_path, exist_ok=True)
|
||||
|
||||
# Process each markdown file in the directory recursively
|
||||
for root, _, files in os.walk(input_path):
|
||||
for filename in files:
|
||||
if filename.endswith(".md"):
|
||||
# Get relative path to maintain directory structure
|
||||
rel_path = os.path.relpath(root, input_path)
|
||||
input_file = os.path.join(root, filename)
|
||||
# Create output directory if it doesn't exist
|
||||
output_dir = os.path.join(output_path, rel_path)
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
output_file = os.path.join(output_dir, filename.replace(".md", ".ts"))
|
||||
dump_code_blocks(input_file, output_file, format)
|
||||
else:
|
||||
# Process single file
|
||||
dump_code_blocks(input_path, output_path, format)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Extract typescript code blocks from a markdown file."
|
||||
)
|
||||
parser.add_argument(
|
||||
"input_file",
|
||||
help="Path to the input markdown file.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"output_file",
|
||||
help="Path to the output JSON file for the extracted code blocks.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--format",
|
||||
choices=["json", "inline"],
|
||||
default="json",
|
||||
help="Output format - either 'json' or 'inline'",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
main(args.input_file, args.output_file, args.format)
|
||||
+126
-4
@@ -1,5 +1,127 @@
|
||||
JS_LINK_MAP = {
|
||||
"langgraph.types.interrupt": "https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph.interrupt-2.html",
|
||||
"create_react_agent": "https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph_prebuilt.createReactAgent.html",
|
||||
"langgraph.types.Command": "https://langchain-ai.github.io/langgraphjs/reference/classes/langgraph.Command.html",
|
||||
"""Link mapping for cross-reference resolution across different scopes.
|
||||
|
||||
This module provides link mappings for different language/framework scopes
|
||||
to resolve @[link_name] references to actual URLs.
|
||||
"""
|
||||
|
||||
# Python-specific link mappings
|
||||
PYTHON_LINK_MAP = {
|
||||
"StateGraph": "reference/graphs/#langgraph.graph.StateGraph",
|
||||
"add_conditional_edges": "reference/graphs/#langgraph.graph.state.StateGraph.add_conditional_edges",
|
||||
"add_edge": "reference/graphs/#langgraph.graph.state.StateGraph.add_edge",
|
||||
"add_node": "reference/graphs/#langgraph.graph.state.StateGraph.add_node",
|
||||
"add_messages": "reference/graphs/#langgraph.graph.message.add_messages",
|
||||
"ToolNode": "reference/agents/#langgraph.prebuilt.tool_node.ToolNode",
|
||||
"CompiledStateGraph.astream": "reference/graphs/#langgraph.graph.state.CompiledStateGraph.astream",
|
||||
"Pregel.astream": "reference/pregel/#langgraph.pregel.Pregel.astream",
|
||||
"AsyncPostgresSaver": "reference/checkpoints/#langgraph.checkpoint.postgres.aio.AsyncPostgresSaver",
|
||||
"AsyncSqliteSaver": "reference/checkpoints/#langgraph.checkpoint.sqlite.aio.AsyncSqliteSaver",
|
||||
"BaseCheckpointSaver": "reference/checkpoints/#langgraph.checkpoint.base.BaseCheckpointSaver",
|
||||
"BaseStore": "reference/store/#langgraph.store.base.BaseStore",
|
||||
"BaseStore.put": "reference/store/#langgraph.store.base.BaseStore.put",
|
||||
"BinaryOperatorAggregate": "reference/pregel/#langgraph.pregel.Pregel--advanced-channels-context-and-binaryoperatoraggregate",
|
||||
"CipherProtocol": "reference/checkpoints/#langgraph.checkpoint.serde.base.CipherProtocol",
|
||||
"client.runs.stream": "cloud/reference/sdk/python_sdk_ref/#langgraph_sdk.client.RunsClient.stream",
|
||||
"client.runs.wait": "cloud/reference/sdk/python_sdk_ref/#langgraph_sdk.client.RunsClient.wait",
|
||||
"client.threads.get_history": "cloud/reference/sdk/python_sdk_ref/#langgraph_sdk.client.ThreadsClient.get_history",
|
||||
"client.threads.update_state": "cloud/reference/sdk/python_sdk_ref/#langgraph_sdk.client.ThreadsClient.update_state",
|
||||
"Command": "reference/types/#langgraph.types.Command",
|
||||
"CompiledStateGraph": "reference/graphs/#langgraph.graph.state.CompiledStateGraph",
|
||||
"create_react_agent": "reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent",
|
||||
"create_supervisor": "reference/supervisor/#langgraph_supervisor.supervisor.create_supervisor",
|
||||
"EncryptedSerializer": "reference/checkpoints/#langgraph.checkpoint.serde.encrypted.EncryptedSerializer",
|
||||
"entrypoint.final": "reference/func/#langgraph.func.entrypoint.final",
|
||||
"entrypoint": "reference/func/#langgraph.func.entrypoint",
|
||||
"from_pycryptodome_aes": "reference/checkpoints/#langgraph.checkpoint.serde.encrypted.EncryptedSerializer.from_pycryptodome_aes",
|
||||
"get_state_history": "reference/graphs/#langgraph.graph.state.CompiledStateGraph.get_state_history",
|
||||
"get_stream_writer": "reference/config/#langgraph.config.get_stream_writer",
|
||||
"HumanInterrupt": "reference/prebuilt/#langgraph.prebuilt.interrupt.HumanInterrupt",
|
||||
"InjectedState": "reference/agents/#langgraph.prebuilt.tool_node.InjectedState",
|
||||
"InMemorySaver": "reference/checkpoints/#langgraph.checkpoint.memory.InMemorySaver",
|
||||
"interrupt": "reference/types/#langgraph.types.Interrupt",
|
||||
"CompiledStateGraph.invoke": "reference/graphs/#langgraph.graph.state.CompiledStateGraph.invoke",
|
||||
"JsonPlusSerializer": "reference/checkpoints/#langgraph.checkpoint.serde.jsonplus.JsonPlusSerializer",
|
||||
"langgraph.json": "cloud/reference/cli/#configuration-file",
|
||||
"LastValue": "reference/channels/#langgraph.channels.LastValue",
|
||||
"PostgresSaver": "reference/checkpoints/#langgraph.checkpoint.postgres.PostgresSaver",
|
||||
"Pregel": "reference/pregel/",
|
||||
"Pregel.stream": "reference/pregel/#langgraph.pregel.Pregel.stream",
|
||||
"pre_model_hook": "reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent",
|
||||
"protocol": "reference/checkpoints/#langgraph.checkpoint.serde.base.SerializerProtocol",
|
||||
"Send": "reference/types/#langgraph.types.Send",
|
||||
"SerializerProtocol": "reference/checkpoints/#langgraph.checkpoint.serde.base.SerializerProtocol",
|
||||
"SqliteSaver": "reference/checkpoints/#langgraph.checkpoint.sqlite.SqliteSaver",
|
||||
"START": "reference/constants/#langgraph.constants.START",
|
||||
"CompiledStateGraph.stream": "reference/graphs/#langgraph.graph.state.CompiledStateGraph.stream",
|
||||
"task": "reference/func/#langgraph.func.task",
|
||||
"Topic": "reference/channels/#langgraph.channels.Topic",
|
||||
"update_state": "reference/graphs/#langgraph.graph.state.CompiledStateGraph.update_state",
|
||||
}
|
||||
|
||||
# JavaScript-specific link mappings
|
||||
JS_LINK_MAP = {
|
||||
"Auth": "reference/classes/sdk_auth.Auth.html",
|
||||
"StateGraph": "reference/classes/langgraph.StateGraph.html",
|
||||
"add_conditional_edges": "/reference/classes/langgraph.StateGraph.html#addConditionalEdges",
|
||||
"add_edge": "reference/classes/langgraph.StateGraph.html#addEdge",
|
||||
"add_node": "reference/classes/langgraph.StateGraph.html#addNode",
|
||||
"add_messages": "reference/modules/langgraph.html#addMessages",
|
||||
"ToolNode": "reference/classes/langgraph_prebuilt.ToolNode.html",
|
||||
"BaseCheckpointSaver": "reference/classes/checkpoint.BaseCheckpointSaver.html",
|
||||
"BaseStore": "reference/classes/checkpoint.BaseStore.html",
|
||||
"BaseStore.put": "reference/classes/checkpoint.BaseStore.html#put",
|
||||
"BinaryOperatorAggregate": "reference/classes/langgraph.BinaryOperatorAggregate.html",
|
||||
"client.runs.stream": "reference/classes/sdk_client.RunsClient.html#stream",
|
||||
"client.runs.wait": "reference/classes/sdk_client.RunsClient.html#wait",
|
||||
"client.threads.get_history": "reference/classes/sdk_client.ThreadsClient.html#getHistory",
|
||||
"client.threads.update_state": "reference/classes/sdk_client.ThreadsClient.html#updateState",
|
||||
"Command": "reference/classes/langgraph.Command.html",
|
||||
"CompiledStateGraph": "reference/classes/langgraph.CompiledStateGraph.html",
|
||||
"create_react_agent": "reference/functions/langgraph_prebuilt.createReactAgent.html",
|
||||
"create_supervisor": "reference/functions/langgraph_supervisor.createSupervisor.html",
|
||||
"entrypoint.final": "reference/functions/langgraph.entrypoint.html#final",
|
||||
"entrypoint": "reference/functions/langgraph.entrypoint.html",
|
||||
"getContextVariable": "https://v03.api.js.langchain.com/functions/_langchain_core.context.getContextVariable.html",
|
||||
"get_state_history": "reference/classes/langgraph.CompiledStateGraph.html#getStateHistory",
|
||||
"HumanInterrupt": "reference/interfaces/langgraph_prebuilt.HumanInterrupt.html",
|
||||
"interrupt": "reference/functions/langgraph.interrupt-2.html",
|
||||
"CompiledStateGraph.invoke": "reference/classes/langgraph.CompiledStateGraph.html#invoke",
|
||||
"langgraph.json": "cloud/reference/cli/#configuration-file",
|
||||
"MemorySaver": "reference/classes/checkpoint.MemorySaver.html",
|
||||
"messagesStateReducer": "reference/functions/langgraph.messagesStateReducer.html",
|
||||
"PostgresSaver": "reference/classes/checkpoint_postgres.PostgresSaver.html",
|
||||
"Pregel": "reference/classes/langgraph.Pregel.html",
|
||||
"Pregel.stream": "reference/classes/langgraph.Pregel.html#stream",
|
||||
"pre_model_hook": "reference/functions/langgraph_prebuilt.createReactAgent.html",
|
||||
"protocol": "reference/interfaces/checkpoint.SerializerProtocol.html",
|
||||
"Send": "reference/classes/langgraph.Send.html",
|
||||
"SerializerProtocol": "reference/interfaces/checkpoint.SerializerProtocol.html",
|
||||
"SqliteSaver": "reference/classes/checkpoint_sqlite.SqliteSaver.html",
|
||||
"START": "reference/variables/langgraph.START.html",
|
||||
"CompiledStateGraph.stream": "reference/classes/langgraph.CompiledStateGraph.html#stream",
|
||||
"task": "reference/functions/langgraph.task.html",
|
||||
## TODO (hntrl): export Topic from langgraphjs
|
||||
# "Topic": "reference/classes/langgraph_channels.Topic.html",
|
||||
"update_state": "reference/classes/langgraph.CompiledStateGraph.html#updateState",
|
||||
}
|
||||
|
||||
# TODO: Allow updating these to localhost for local development
|
||||
PY_REFERENCE_HOST = "https://langchain-ai.github.io/langgraph/"
|
||||
JS_REFERENCE_HOST = "https://langchain-ai.github.io/langgraphjs/"
|
||||
|
||||
for key, value in PYTHON_LINK_MAP.items():
|
||||
# Ensure the link is absolute
|
||||
if not value.startswith("http"):
|
||||
PYTHON_LINK_MAP[key] = f"{PY_REFERENCE_HOST}{value}"
|
||||
|
||||
for key, value in JS_LINK_MAP.items():
|
||||
# Ensure the link is absolute
|
||||
if not value.startswith("http"):
|
||||
JS_LINK_MAP[key] = f"{JS_REFERENCE_HOST}{value}"
|
||||
|
||||
# Global scope is assembled from the Python and JS mappings
|
||||
# Combined mapping by scope
|
||||
SCOPE_LINK_MAPS = {
|
||||
"python": PYTHON_LINK_MAP,
|
||||
"js": JS_LINK_MAP,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"""Convert Jupyter notebooks to markdown with custom processing."""
|
||||
|
||||
import ast
|
||||
import os
|
||||
import re
|
||||
|
||||
+611
-141
@@ -3,6 +3,7 @@
|
||||
Lifecycle events: https://www.mkdocs.org/dev-guide/plugins/#events
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import posixpath
|
||||
@@ -15,8 +16,8 @@ from mkdocs.structure.files import Files, File
|
||||
from mkdocs.structure.pages import Page
|
||||
|
||||
from _scripts.generate_api_reference_links import update_markdown_with_imports
|
||||
from _scripts.handle_auto_links import _replace_autolinks
|
||||
from _scripts.notebook_convert import convert_notebook
|
||||
from _scripts.link_map import JS_LINK_MAP
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig()
|
||||
@@ -26,105 +27,430 @@ DISABLED = os.getenv("DISABLE_NOTEBOOK_CONVERT") in ("1", "true", "True")
|
||||
|
||||
REDIRECT_MAP = {
|
||||
# lib redirects
|
||||
"how-tos/stream-values.ipynb": "how-tos/streaming.md#stream-graph-state",
|
||||
"how-tos/stream-updates.ipynb": "how-tos/streaming.md#stream-graph-state",
|
||||
"how-tos/streaming-content.ipynb": "how-tos/streaming.md",
|
||||
"how-tos/stream-multiple.ipynb": "how-tos/streaming.md#stream-multiple-nodes",
|
||||
"how-tos/streaming-tokens-without-langchain.ipynb": "how-tos/streaming.md#use-with-any-llm",
|
||||
"how-tos/streaming-from-final-node.ipynb": "how-tos/streaming-specific-nodes.ipynb",
|
||||
"how-tos/streaming-events-from-within-tools-without-langchain.ipynb": "how-tos/streaming-events-from-within-tools.ipynb#example-without-langchain",
|
||||
"how-tos/stream-values.ipynb": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"how-tos/stream-updates.ipynb": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"how-tos/streaming-content.ipynb": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"how-tos/stream-multiple.ipynb": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"how-tos/streaming-tokens-without-langchain.ipynb": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"how-tos/streaming-from-final-node.ipynb": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"how-tos/streaming-events-from-within-tools-without-langchain.ipynb": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
# graph-api
|
||||
"how-tos/state-reducers.ipynb": "how-tos/graph-api.md#define-and-update-state",
|
||||
"how-tos/sequence.ipynb": "how-tos/graph-api.md#create-a-sequence-of-steps",
|
||||
"how-tos/branching.ipynb": "how-tos/graph-api.md#create-branches",
|
||||
"how-tos/recursion-limit.ipynb": "how-tos/graph-api.md#create-and-control-loops",
|
||||
"how-tos/visualization.ipynb": "how-tos/graph-api.md#visualize-your-graph",
|
||||
"how-tos/input_output_schema.ipynb": "how-tos/graph-api.md#define-input-and-output-schemas",
|
||||
"how-tos/pass_private_state.ipynb": "how-tos/graph-api.md#pass-private-state-between-nodes",
|
||||
"how-tos/state-model.ipynb": "how-tos/graph-api.md#use-pydantic-models-for-graph-state",
|
||||
"how-tos/map-reduce.ipynb": "how-tos/graph-api.md#map-reduce-and-the-send-api",
|
||||
"how-tos/command.ipynb": "how-tos/graph-api.md#combine-control-flow-and-state-updates-with-command",
|
||||
"how-tos/configuration.ipynb": "how-tos/graph-api.md#add-runtime-configuration",
|
||||
"how-tos/node-retries.ipynb": "how-tos/graph-api.md#add-retry-policies",
|
||||
"how-tos/return-when-recursion-limit-hits.ipynb": "how-tos/graph-api.md#impose-a-recursion-limit",
|
||||
"how-tos/async.ipynb": "how-tos/graph-api.md#async",
|
||||
"how-tos/state-reducers.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#define-and-update-state",
|
||||
"how-tos/sequence.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#create-a-sequence-of-steps",
|
||||
"how-tos/branching.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#create-branches",
|
||||
"how-tos/recursion-limit.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#create-and-control-loops",
|
||||
"how-tos/visualization.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#visualize-your-graph",
|
||||
"how-tos/input_output_schema.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#define-input-and-output-schemas",
|
||||
"how-tos/pass_private_state.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#pass-private-state-between-nodes",
|
||||
"how-tos/state-model.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#use-pydantic-models-for-graph-state",
|
||||
"how-tos/map-reduce.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#map-reduce-and-the-send-api",
|
||||
"how-tos/command.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#combine-control-flow-and-state-updates-with-command",
|
||||
"how-tos/configuration.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#add-runtime-configuration",
|
||||
"how-tos/node-retries.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#add-retry-policies",
|
||||
"how-tos/return-when-recursion-limit-hits.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#impose-a-recursion-limit",
|
||||
"how-tos/async.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#async",
|
||||
# memory how-tos
|
||||
"how-tos/memory/manage-conversation-history.ipynb": "how-tos/memory/add-memory.md",
|
||||
"how-tos/memory/delete-messages.ipynb": "how-tos/memory/add-memory.md#delete-messages",
|
||||
"how-tos/memory/add-summary-conversation-history.ipynb": "how-tos/memory/add-memory.md#summarize-messages",
|
||||
"how-tos/memory.ipynb": "how-tos/memory/add-memory.md",
|
||||
"agents/memory.ipynb": "how-tos/memory/add-memory.md",
|
||||
"how-tos/memory/manage-conversation-history.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
"how-tos/memory/delete-messages.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory#delete-messages",
|
||||
"how-tos/memory/add-summary-conversation-history.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory#summarize-messages",
|
||||
"how-tos/memory.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
"agents/memory.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
# subgraph how-tos
|
||||
"how-tos/subgraph-transform-state.ipynb": "how-tos/subgraph.md#different-state-schemas",
|
||||
"how-tos/subgraphs-manage-state.ipynb": "how-tos/subgraph.md#add-persistence",
|
||||
"how-tos/subgraph-transform-state.ipynb": "https://docs.langchain.com/oss/python/langgraph/use-subgraphs#different-state-schemas",
|
||||
"how-tos/subgraphs-manage-state.ipynb": "https://docs.langchain.com/oss/python/langgraph/use-subgraphs#add-persistence",
|
||||
# persistence how-tos
|
||||
"how-tos/persistence_postgres.ipynb": "how-tos/memory/add-memory.md#use-in-production",
|
||||
"how-tos/persistence_mongodb.ipynb": "how-tos/memory/add-memory.md#use-in-production",
|
||||
"how-tos/persistence_redis.ipynb": "how-tos/memory/add-memory.md#use-in-production",
|
||||
"how-tos/subgraph-persistence.ipynb": "how-tos/memory/add-memory.md#use-with-subgraphs",
|
||||
"how-tos/cross-thread-persistence.ipynb": "how-tos/memory/add-memory.md#add-long-term-memory",
|
||||
"cloud/how-tos/copy_threads": "cloud/how-tos/use_threads",
|
||||
"cloud/how-tos/check-thread-status": "cloud/how-tos/use_threads",
|
||||
"cloud/concepts/threads.md": "concepts/persistence.md#threads",
|
||||
"how-tos/persistence.ipynb": "how-tos/memory/add-memory.md",
|
||||
"how-tos/persistence_postgres.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory#use-in-production",
|
||||
"how-tos/persistence_mongodb.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory#use-in-production",
|
||||
"how-tos/persistence_redis.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory#use-in-production",
|
||||
"how-tos/subgraph-persistence.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory#use-with-subgraphs",
|
||||
"how-tos/cross-thread-persistence.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory#add-long-term-memory",
|
||||
"cloud/how-tos/copy_threads": "https://docs.langchain.com/langsmith/use-threads",
|
||||
"cloud/how-tos/check-thread-status": "https://docs.langchain.com/langsmith/use-threads",
|
||||
"cloud/concepts/threads.md": "https://docs.langchain.com/oss/python/langgraph/persistence#threads",
|
||||
"how-tos/persistence.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
# tool calling how-tos
|
||||
"how-tos/tool-calling-errors.ipynb": "how-tos/tool-calling.ipynb#handle-errors",
|
||||
"how-tos/pass-config-to-tools.ipynb": "how-tos/tool-calling.ipynb#access-config",
|
||||
"how-tos/pass-run-time-values-to-tools.ipynb": "how-tos/tool-calling.ipynb#read-state",
|
||||
"how-tos/update-state-from-tools.ipynb": "how-tos/tool-calling.ipynb#update-state",
|
||||
"agents/tools.md": "how-tos/tool-calling.md",
|
||||
"how-tos/tool-calling-errors.ipynb": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"how-tos/pass-config-to-tools.ipynb": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"how-tos/pass-run-time-values-to-tools.ipynb": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"how-tos/update-state-from-tools.ipynb": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"agents/tools.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
# multi-agent how-tos
|
||||
"how-tos/agent-handoffs.ipynb": "how-tos/multi_agent.md#handoffs",
|
||||
"how-tos/multi-agent-network.ipynb": "how-tos/multi_agent.md#use-in-a-multi-agent-system",
|
||||
"how-tos/multi-agent-multi-turn-convo.ipynb": "how-tos/multi_agent.md#multi-turn-conversation",
|
||||
"how-tos/agent-handoffs.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
"how-tos/multi-agent-network.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
"how-tos/multi-agent-multi-turn-convo.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
# cloud redirects
|
||||
"cloud/index.md": "index.md",
|
||||
"cloud/how-tos/index.md": "concepts/langgraph_platform",
|
||||
"cloud/concepts/api.md": "concepts/langgraph_server.md",
|
||||
"cloud/concepts/cloud.md": "concepts/langgraph_cloud.md",
|
||||
"cloud/faq/studio.md": "concepts/langgraph_studio.md#studio-faqs",
|
||||
"cloud/how-tos/human_in_the_loop_edit_state.md": "cloud/how-tos/add-human-in-the-loop.md",
|
||||
"cloud/how-tos/human_in_the_loop_user_input.md": "cloud/how-tos/add-human-in-the-loop.md",
|
||||
"concepts/platform_architecture.md": "concepts/langgraph_cloud#architecture",
|
||||
"cloud/index.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"cloud/how-tos/index.md": "https://docs.langchain.com/langsmith/home",
|
||||
"cloud/concepts/api.md": "https://docs.langchain.com/langsmith/agent-server",
|
||||
"cloud/concepts/cloud.md": "https://docs.langchain.com/langsmith/cloud",
|
||||
"cloud/faq/studio.md": "https://docs.langchain.com/langsmith/studio",
|
||||
"cloud/how-tos/human_in_the_loop_edit_state.md": "https://docs.langchain.com/langsmith/add-human-in-the-loop",
|
||||
"cloud/how-tos/human_in_the_loop_user_input.md": "https://docs.langchain.com/langsmith/add-human-in-the-loop",
|
||||
"concepts/platform_architecture.md": "https://docs.langchain.com/langsmith/cloud#architecture",
|
||||
# cloud streaming redirects
|
||||
"cloud/how-tos/stream_values.md": "cloud/how-tos/streaming.md#stream-graph-state",
|
||||
"cloud/how-tos/stream_updates.md": "cloud/how-tos/streaming.md#stream-graph-state",
|
||||
"cloud/how-tos/stream_messages.md": "cloud/how-tos/streaming.md#messages",
|
||||
"cloud/how-tos/stream_events.md": "cloud/how-tos/streaming.md#stream-events",
|
||||
"cloud/how-tos/stream_debug.md": "cloud/how-tos/streaming.md#debug",
|
||||
"cloud/how-tos/stream_multiple.md": "cloud/how-tos/streaming.md#stream-multiple-modes",
|
||||
"cloud/concepts/streaming.md": "concepts/streaming.md",
|
||||
"agents/streaming.md": "how-tos/streaming.md",
|
||||
"cloud/how-tos/stream_values.md": "https://docs.langchain.com/langsmith/streaming",
|
||||
"cloud/how-tos/stream_updates.md": "https://docs.langchain.com/langsmith/streaming",
|
||||
"cloud/how-tos/stream_messages.md": "https://docs.langchain.com/langsmith/streaming",
|
||||
"cloud/how-tos/stream_events.md": "https://docs.langchain.com/langsmith/streaming",
|
||||
"cloud/how-tos/stream_debug.md": "https://docs.langchain.com/langsmith/streaming",
|
||||
"cloud/how-tos/stream_multiple.md": "https://docs.langchain.com/langsmith/streaming",
|
||||
"cloud/concepts/streaming.md": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"agents/streaming.md": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
# prebuilt redirects
|
||||
"how-tos/create-react-agent.ipynb": "agents/agents.md#basic-configuration",
|
||||
"how-tos/create-react-agent-memory.ipynb": "agents/memory.md",
|
||||
"how-tos/create-react-agent-system-prompt.ipynb": "agents/context.md#prompts",
|
||||
"how-tos/create-react-agent-structured-output.ipynb": "agents/agents.md#structured-output",
|
||||
# Time-travel
|
||||
"how-tos/human_in_the_loop/edit-graph-state.ipynb": "how-tos/human_in_the_loop/time-travel.md",
|
||||
# breakpoints
|
||||
"how-tos/human_in_the_loop/dynamic_breakpoints.ipynb": "how-tos/human_in_the_loop/breakpoints.md",
|
||||
"how-tos/create-react-agent.ipynb": "https://docs.langchain.com/oss/python/langchain/agents#basic-configuration",
|
||||
"how-tos/create-react-agent-memory.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
"how-tos/create-react-agent-system-prompt.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
"how-tos/create-react-agent-structured-output.ipynb": "https://docs.langchain.com/oss/python/langchain/agents#structured-output",
|
||||
# misc
|
||||
"prebuilt.md": "agents/prebuilt.md",
|
||||
"reference/prebuilt.md": "reference/agents.md",
|
||||
"concepts/high_level.md": "index.md",
|
||||
"concepts/index.md": "index.md",
|
||||
"concepts/v0-human-in-the-loop.md": "concepts/human-in-the-loop.md",
|
||||
"how-tos/index.md": "index.md",
|
||||
"tutorials/introduction.ipynb": "concepts/why-langgraph.md",
|
||||
"agents/deployment.md": "tutorials/langgraph-platform/local-server.md",
|
||||
"prebuilt.md": "https://docs.langchain.com/oss/python/langchain/agents",
|
||||
"reference/prebuilt.md": "https://reference.langchain.com/python/langgraph/agents/",
|
||||
"concepts/high_level.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"concepts/index.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"concepts/v0-human-in-the-loop.md": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"how-tos/index.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"tutorials/introduction.ipynb": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"agents/deployment.md": "https://docs.langchain.com/oss/python/langgraph/local-server",
|
||||
# deployment redirects
|
||||
"how-tos/deploy-self-hosted.md": "cloud/deployment/self_hosted_data_plane.md",
|
||||
"concepts/self_hosted.md": "concepts/langgraph_self_hosted_data_plane.md",
|
||||
"tutorials/deployment.md": "concepts/deployment_options.md",
|
||||
"how-tos/deploy-self-hosted.md": "https://docs.langchain.com/langsmith/platform-setup",
|
||||
"concepts/self_hosted.md": "https://docs.langchain.com/langsmith/platform-setup",
|
||||
"tutorials/deployment.md": "https://docs.langchain.com/langsmith/deployments",
|
||||
# assistant redirects
|
||||
"cloud/how-tos/assistant_versioning.md": "cloud/how-tos/configuration_cloud.md",
|
||||
"cloud/concepts/runs.md": "concepts/assistants.md#execution",
|
||||
"cloud/how-tos/assistant_versioning.md": "https://docs.langchain.com/langsmith/configuration-cloud",
|
||||
"cloud/concepts/runs.md": "https://docs.langchain.com/langsmith/assistants#execution",
|
||||
# hitl redirects
|
||||
"how-tos/wait-user-input-functional.ipynb": "how-tos/use-functional-api.md",
|
||||
"how-tos/review-tool-calls-functional.ipynb": "how-tos/use-functional-api.md",
|
||||
"how-tos/create-react-agent-hitl.ipynb": "how-tos/human_in_the_loop/add-human-in-the-loop.md",
|
||||
"agents/human-in-the-loop.md": "how-tos/human_in_the_loop/add-human-in-the-loop.md",
|
||||
"how-tos/wait-user-input-functional.ipynb": "https://docs.langchain.com/oss/python/langgraph/functional-api",
|
||||
"how-tos/review-tool-calls-functional.ipynb": "https://docs.langchain.com/oss/python/langgraph/functional-api",
|
||||
"how-tos/create-react-agent-hitl.ipynb": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"agents/human-in-the-loop.md": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"how-tos/human_in_the_loop/dynamic_breakpoints.ipynb": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"concepts/breakpoints.md": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"how-tos/human_in_the_loop/breakpoints.md": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"cloud/how-tos/human_in_the_loop_breakpoint.md": "https://docs.langchain.com/langsmith/add-human-in-the-loop",
|
||||
"how-tos/human_in_the_loop/edit-graph-state.ipynb": "https://docs.langchain.com/oss/python/langgraph/use-time-travel",
|
||||
|
||||
# LGP mintlify migration redirects
|
||||
"examples/index.md": "https://docs.langchain.com/oss/python/learn",
|
||||
"guides/index.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"concepts/index.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"tutorials/index.md": "https://docs.langchain.com/oss/python/learn",
|
||||
"llms-txt-overview.md": "https://docs.langchain.com/llms.txt",
|
||||
"tutorials/rag/langgraph_adaptive_rag.md": "https://docs.langchain.com/oss/python/langgraph/agentic-rag",
|
||||
"tutorials/multi_agent/multi-agent-collaboration.ipynb": "https://docs.langchain.com/oss/python/langchain/multi-agent",
|
||||
"how-tos/create-react-agent-manage-message-history.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
"how-tos/many-tools.ipynb": "https://docs.langchain.com/oss/python/langchain/tools",
|
||||
"tutorials/customer-support/customer-support.ipynb": "https://docs.langchain.com/oss/python/langgraph/agentic-rag",
|
||||
"how-tos/react-agent-structured-output.ipynb": "https://docs.langchain.com/oss/python/langchain/agents#structured-output",
|
||||
"tutorials/code_assistant/langgraph_code_assistant.ipynb": "https://docs.langchain.com/oss/python/langgraph/agentic-rag",
|
||||
"tutorials/multi_agent/hierarchical_agent_teams.ipynb": "https://docs.langchain.com/oss/python/langchain/supervisor",
|
||||
"tutorials/auth/getting_started.md": "https://docs.langchain.com/langsmith/auth",
|
||||
"tutorials/auth/resource_auth.md": "https://docs.langchain.com/langsmith/resource-auth",
|
||||
"tutorials/auth/add_auth_server.md": "https://docs.langchain.com/langsmith/add-auth-server",
|
||||
"how-tos/use-remote-graph.md": "https://docs.langchain.com/langsmith/use-remote-graph",
|
||||
"how-tos/autogen-integration.md": "https://docs.langchain.com/langsmith/autogen-integration",
|
||||
"how-tos/human_in_the_loop/wait-user-input.ipynb": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"cloud/how-tos/use_stream_react.md": "https://docs.langchain.com/langsmith/use-stream-react",
|
||||
"cloud/how-tos/generative_ui_react.md": "https://docs.langchain.com/langsmith/generative-ui-react",
|
||||
"concepts/langgraph_platform.md": "https://docs.langchain.com/langsmith/deployments",
|
||||
"concepts/langgraph_components.md": "https://docs.langchain.com/langsmith/components",
|
||||
"concepts/langgraph_server.md": "https://docs.langchain.com/langsmith/agent-server",
|
||||
"concepts/langgraph_data_plane.md": "https://docs.langchain.com/langsmith/data-plane",
|
||||
"concepts/langgraph_control_plane.md": "https://docs.langchain.com/langsmith/control-plane",
|
||||
"concepts/langgraph_cli.md": "https://docs.langchain.com/langsmith/cli",
|
||||
"concepts/langgraph_studio.md": "https://docs.langchain.com/langsmith/studio",
|
||||
"cloud/how-tos/studio/quick_start.md": "https://docs.langchain.com/langsmith/quick-start-studio",
|
||||
"cloud/how-tos/invoke_studio.md": "https://docs.langchain.com/langsmith/use-studio#run-application",
|
||||
"cloud/how-tos/studio/manage_assistants.md": "https://docs.langchain.com/langsmith/use-studio#manage-assistants",
|
||||
"cloud/how-tos/threads_studio.md": "https://docs.langchain.com/langsmith/use-studio#manage-threads",
|
||||
"cloud/how-tos/iterate_graph_studio.md": "https://docs.langchain.com/langsmith/observability-studio#iterate-on-prompts",
|
||||
"cloud/how-tos/studio/run_evals.md": "https://docs.langchain.com/langsmith/observability-studio#run-experiments-over-a-dataset",
|
||||
"cloud/how-tos/clone_traces_studio.md": "https://docs.langchain.com/langsmith/observability-studio#debug-langsmith-traces",
|
||||
"cloud/how-tos/datasets_studio.md": "https://docs.langchain.com/langsmith/observability-studio#add-node-to-dataset",
|
||||
"concepts/sdk.md": "https://docs.langchain.com/langsmith/sdk",
|
||||
"concepts/plans.md": "https://langchain.com/pricing",
|
||||
"concepts/application_structure.md": "https://docs.langchain.com/langsmith/application-structure",
|
||||
"concepts/scalability_and_resilience.md": "https://docs.langchain.com/langsmith/scalability-and-resilience",
|
||||
"concepts/auth.md": "https://docs.langchain.com/langsmith/authentication-methods",
|
||||
"how-tos/auth/custom_auth.md": "https://docs.langchain.com/langsmith/custom-auth",
|
||||
"how-tos/auth/openapi_security.md": "https://docs.langchain.com/langsmith/openapi-security",
|
||||
"concepts/assistants.md": "https://docs.langchain.com/langsmith/assistants",
|
||||
"cloud/how-tos/configuration_cloud.md": "https://docs.langchain.com/langsmith/cloud",
|
||||
"cloud/how-tos/use_threads.md": "https://docs.langchain.com/langsmith/use-threads",
|
||||
"cloud/how-tos/background_run.md": "https://docs.langchain.com/langsmith/background-run",
|
||||
"cloud/how-tos/same-thread.md": "https://docs.langchain.com/langsmith/same-thread",
|
||||
"cloud/how-tos/stateless_runs.md": "https://docs.langchain.com/langsmith/stateless-runs",
|
||||
"cloud/how-tos/configurable_headers.md": "https://docs.langchain.com/langsmith/configurable-headers",
|
||||
"concepts/double_texting.md": "https://docs.langchain.com/langsmith/double-texting",
|
||||
"cloud/how-tos/interrupt_concurrent.md": "https://docs.langchain.com/langsmith/interrupt-concurrent",
|
||||
"cloud/how-tos/rollback_concurrent.md": "https://docs.langchain.com/langsmith/rollback-concurrent",
|
||||
"cloud/how-tos/reject_concurrent.md": "https://docs.langchain.com/langsmith/reject-concurrent",
|
||||
"cloud/how-tos/enqueue_concurrent.md": "https://docs.langchain.com/langsmith/enqueue-concurrent",
|
||||
"cloud/concepts/webhooks.md": "https://docs.langchain.com/langsmith/use-webhooks",
|
||||
"cloud/how-tos/webhooks.md": "https://docs.langchain.com/langsmith/use-webhooks",
|
||||
"cloud/concepts/cron_jobs.md": "https://docs.langchain.com/langsmith/cron-jobs",
|
||||
"cloud/how-tos/cron_jobs.md": "https://docs.langchain.com/langsmith/cron-jobs",
|
||||
"how-tos/http/custom_lifespan.md": "https://docs.langchain.com/langsmith/custom-lifespan",
|
||||
"how-tos/http/custom_middleware.md": "https://docs.langchain.com/langsmith/custom-middleware",
|
||||
"how-tos/http/custom_routes.md": "https://docs.langchain.com/langsmith/custom-routes",
|
||||
"cloud/concepts/data_storage_and_privacy.md": "https://docs.langchain.com/langsmith/data-storage-and-privacy",
|
||||
"cloud/deployment/semantic_search.md": "https://docs.langchain.com/langsmith/semantic-search",
|
||||
"how-tos/ttl/configure_ttl.md": "https://docs.langchain.com/langsmith/configure-ttl",
|
||||
"concepts/deployment_options.md": "https://docs.langchain.com/langsmith/platform-setup",
|
||||
"cloud/quick_start.md": "https://docs.langchain.com/langsmith/deployment-quickstart",
|
||||
"cloud/deployment/setup.md": "https://docs.langchain.com/langsmith/setup-app-requirements-txt",
|
||||
"cloud/deployment/setup_pyproject.md": "https://docs.langchain.com/langsmith/setup-pyproject",
|
||||
"cloud/deployment/setup_javascript.md": "https://docs.langchain.com/langsmith/setup-javascript",
|
||||
"cloud/deployment/custom_docker.md": "https://docs.langchain.com/langsmith/custom-docker",
|
||||
"cloud/deployment/graph_rebuild.md": "https://docs.langchain.com/langsmith/graph-rebuild",
|
||||
"concepts/langgraph_cloud.md": "https://docs.langchain.com/langsmith/cloud",
|
||||
"concepts/langgraph_self_hosted_data_plane.md": "https://docs.langchain.com/langsmith/hybrid",
|
||||
"concepts/langgraph_self_hosted_control_plane.md": "https://docs.langchain.com/langsmith/self-hosted",
|
||||
"concepts/langgraph_standalone_container.md": "https://docs.langchain.com/langsmith/self-hosted#standalone-server",
|
||||
"cloud/deployment/cloud.md": "https://docs.langchain.com/langsmith/cloud",
|
||||
"cloud/deployment/self_hosted_data_plane.md": "https://docs.langchain.com/langsmith/deploy-hybrid",
|
||||
"cloud/deployment/self_hosted_control_plane.md": "https://docs.langchain.com/langsmith/deploy-self-hosted-full-platform",
|
||||
"cloud/deployment/standalone_container.md": "https://docs.langchain.com/langsmith/deploy-standalone-server",
|
||||
"concepts/server-mcp.md": "https://docs.langchain.com/langsmith/server-mcp",
|
||||
"cloud/how-tos/human_in_the_loop_time_travel.md": "https://docs.langchain.com/langsmith/human-in-the-loop-time-travel",
|
||||
"cloud/how-tos/add-human-in-the-loop.md": "https://docs.langchain.com/langsmith/add-human-in-the-loop",
|
||||
"cloud/deployment/egress.md": "https://docs.langchain.com/langsmith/env-var",
|
||||
"cloud/how-tos/streaming.md": "https://docs.langchain.com/langsmith/streaming",
|
||||
"cloud/reference/api/api_ref.md": "https://docs.langchain.com/langsmith/server-api-ref",
|
||||
"cloud/reference/langgraph_server_changelog.md": "https://docs.langchain.com/langsmith/agent-server-changelog",
|
||||
"cloud/reference/api/api_ref_control_plane.md": "https://docs.langchain.com/langsmith/api-ref-control-plane",
|
||||
"cloud/reference/cli.md": "https://docs.langchain.com/langsmith/cli",
|
||||
"cloud/reference/env_var.md": "https://docs.langchain.com/langsmith/env-var",
|
||||
"troubleshooting/studio.md": "https://docs.langchain.com/langsmith/troubleshooting-studio",
|
||||
|
||||
# LangGraph mintlify migration redirects
|
||||
"index.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"agents/agents.md": "https://docs.langchain.com/oss/python/langchain/agents",
|
||||
"concepts/why-langgraph.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"tutorials/get-started/1-build-basic-chatbot.md": "https://docs.langchain.com/oss/python/langgraph/quickstart",
|
||||
"tutorials/get-started/2-add-tools.md": "https://docs.langchain.com/oss/python/langgraph/quickstart",
|
||||
"tutorials/get-started/3-add-memory.md": "https://docs.langchain.com/oss/python/langgraph/quickstart",
|
||||
"tutorials/get-started/4-human-in-the-loop.md": "https://docs.langchain.com/oss/python/langgraph/quickstart",
|
||||
"tutorials/get-started/5-customize-state.md": "https://docs.langchain.com/oss/python/langgraph/quickstart",
|
||||
"tutorials/get-started/6-time-travel.md": "https://docs.langchain.com/oss/python/langgraph/quickstart",
|
||||
"tutorials/langsmith/local-server.md": "https://docs.langchain.com/oss/python/langgraph/local-server",
|
||||
"tutorials/workflows.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"tutorials/plan-and-execute/plan-and-execute.ipynb": "https://docs.langchain.com/oss/python/langchain/middleware/built-in#to-do-list",
|
||||
"tutorials/langgraph-platform/local-server/local-server.md": "https://docs.langchain.com/langsmith/local-server",
|
||||
"concepts/agentic_concepts.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"guides/index.md": "https://docs.langchain.com/oss/python/langchain/overview",
|
||||
"agents/overview.md": "https://docs.langchain.com/oss/python/langchain/agents",
|
||||
"agents/run_agents.md": "https://docs.langchain.com/oss/python/langgraph/quickstart",
|
||||
"concepts/low_level.md": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
"how-tos/graph-api.md": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
"how-tos/react-agent-from-scratch.ipynb": "https://docs.langchain.com/oss/python/langchain/quickstart",
|
||||
"concepts/functional_api.md": "https://docs.langchain.com/oss/python/langgraph/functional-api",
|
||||
"how-tos/use-functional-api.md": "https://docs.langchain.com/oss/python/langgraph/functional-api",
|
||||
"concepts/pregel.md": "https://docs.langchain.com/oss/python/langgraph/pregel",
|
||||
"concepts/streaming.md": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"how-tos/streaming.md": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"concepts/persistence.md": "https://docs.langchain.com/oss/python/langgraph/persistence",
|
||||
"concepts/durable_execution.md": "https://docs.langchain.com/oss/python/langgraph/durable-execution",
|
||||
"concepts/memory.md": "https://docs.langchain.com/oss/python/langgraph/memory",
|
||||
"how-tos/memory/add-memory.md": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
"agents/context.md": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
"agents/models.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"concepts/tools.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"how-tos/tool-calling.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"concepts/human_in_the_loop.md": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"how-tos/human_in_the_loop/add-human-in-the-loop.md": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"concepts/time-travel.md": "https://docs.langchain.com/oss/python/langgraph/persistence",
|
||||
"how-tos/human_in_the_loop/time-travel.md": "https://docs.langchain.com/oss/python/langgraph/use-time-travel",
|
||||
"concepts/subgraphs.md": "https://docs.langchain.com/oss/python/langgraph/use-subgraphs",
|
||||
"how-tos/subgraph.md": "https://docs.langchain.com/oss/python/langgraph/use-subgraphs",
|
||||
"concepts/multi_agent.md": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
"agents/multi-agent.md": "https://docs.langchain.com/oss/python/langchain/multi-agent",
|
||||
"how-tos/multi_agent.md": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
"concepts/mcp.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"agents/mcp.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"concepts/tracing.md": "https://docs.langchain.com/oss/python/langgraph/observability",
|
||||
"how-tos/enable-tracing.md": "https://docs.langchain.com/oss/python/langgraph/observability",
|
||||
"agents/evals.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"examples/index.md": "https://docs.langchain.com/oss/python/langgraph/case-studies",
|
||||
"concepts/template_applications.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"tutorials/rag/langgraph_agentic_rag.md": "https://docs.langchain.com/oss/python/langgraph/agentic-rag",
|
||||
"tutorials/multi_agent/agent_supervisor.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"tutorials/sql/sql-agent.md": "https://docs.langchain.com/oss/python/langgraph/sql-agent",
|
||||
"agents/ui.md": "https://docs.langchain.com/oss/python/langgraph/ui",
|
||||
"how-tos/run-id-langsmith.md": "https://docs.langchain.com/oss/python/langgraph/observability",
|
||||
"troubleshooting/errors/index.md": "https://docs.langchain.com/oss/python/langgraph/common-errors",
|
||||
"troubleshooting/errors/INVALID_CHAT_HISTORY.md": "https://docs.langchain.com/oss/python/langgraph/INVALID_CHAT_HISTORY",
|
||||
"troubleshooting/errors/INVALID_LICENSE.md": "https://docs.langchain.com/oss/python/langgraph/common-errors",
|
||||
"adopters.md": "https://docs.langchain.com/oss/python/langgraph/case-studies",
|
||||
"concepts/faq.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"agents/prebuilt.md": "https://docs.langchain.com/oss/python/langchain/agents",
|
||||
"reference/index.md": "https://reference.langchain.com/python/langgraph/",
|
||||
"reference/graphs.md": "https://reference.langchain.com/python/langgraph/graphs/",
|
||||
"reference/func.md": "https://reference.langchain.com/python/langgraph/func/",
|
||||
"reference/pregel.md": "https://reference.langchain.com/python/langgraph/pregel/",
|
||||
"reference/checkpoints.md": "https://reference.langchain.com/python/langgraph/checkpoints/",
|
||||
"reference/store.md": "https://reference.langchain.com/python/langgraph/store/",
|
||||
"reference/cache.md": "https://reference.langchain.com/python/langgraph/cache/",
|
||||
"reference/types.md": "https://reference.langchain.com/python/langgraph/types/",
|
||||
"reference/runtime.md": "https://reference.langchain.com/python/langgraph/runtime/",
|
||||
"reference/config.md": "https://reference.langchain.com/python/langgraph/config/",
|
||||
"reference/errors.md": "https://reference.langchain.com/python/langgraph/errors/",
|
||||
"reference/constants.md": "https://reference.langchain.com/python/langgraph/constants/",
|
||||
"reference/channels.md": "https://reference.langchain.com/python/langgraph/channels/",
|
||||
"reference/agents.md": "https://reference.langchain.com/python/langgraph/agents/",
|
||||
"reference/supervisor.md": "https://reference.langchain.com/python/langgraph/supervisor/",
|
||||
"reference/swarm.md": "https://reference.langchain.com/python/langgraph/swarm/",
|
||||
"reference/mcp.md": "https://reference.langchain.com/python/langgraph/mcp/",
|
||||
"cloud/reference/sdk/python_sdk_ref.md": "https://reference.langchain.com/python/langsmith/deployment/sdk/",
|
||||
"reference/remote_graph.md": "https://reference.langchain.com/python/langsmith/deployment/remote_graph/",
|
||||
|
||||
# additional exclude-search entries from mkdocs.yml
|
||||
"additional-resources/index.md": "https://docs.langchain.com/oss/python/langchain/overview",
|
||||
"cloud/concepts/cron_jobs.md": "https://docs.langchain.com/langsmith/cron-jobs",
|
||||
"cloud/concepts/data_storage_and_privacy.md": "https://docs.langchain.com/langsmith/data-storage-and-privacy",
|
||||
"cloud/concepts/webhooks.md": "https://docs.langchain.com/langsmith/use-webhooks",
|
||||
"cloud/deployment/cloud.md": "https://docs.langchain.com/langsmith/cloud",
|
||||
"cloud/deployment/custom_docker.md": "https://docs.langchain.com/langsmith/custom-docker",
|
||||
"cloud/deployment/egress.md": "https://docs.langchain.com/langsmith/env-var",
|
||||
"cloud/deployment/graph_rebuild.md": "https://docs.langchain.com/langsmith/graph-rebuild",
|
||||
"cloud/deployment/self_hosted_control_plane.md": "https://docs.langchain.com/langsmith/platform-setup",
|
||||
"cloud/deployment/self_hosted_data_plane.md": "https://docs.langchain.com/langsmith/platform-setup",
|
||||
"cloud/deployment/semantic_search.md": "https://docs.langchain.com/langsmith/semantic-search",
|
||||
"cloud/deployment/setup_javascript.md": "https://docs.langchain.com/langsmith/setup-javascript",
|
||||
"cloud/deployment/setup_pyproject.md": "https://docs.langchain.com/langsmith/setup-pyproject",
|
||||
"cloud/deployment/setup.md": "https://docs.langchain.com/langsmith/setup-app-requirements-txt",
|
||||
"cloud/deployment/standalone_container.md": "https://docs.langchain.com/langsmith/docker",
|
||||
"cloud/how-tos/add-human-in-the-loop.md": "https://docs.langchain.com/langsmith/add-human-in-the-loop",
|
||||
"cloud/how-tos/background_run.md": "https://docs.langchain.com/langsmith/background-run",
|
||||
"cloud/how-tos/clone_traces_studio.md": "https://docs.langchain.com/langsmith/observability",
|
||||
"cloud/how-tos/configurable_headers.md": "https://docs.langchain.com/langsmith/configurable-headers",
|
||||
"cloud/how-tos/configuration_cloud.md": "https://docs.langchain.com/langsmith/configuration-cloud",
|
||||
"cloud/how-tos/cron_jobs.md": "https://docs.langchain.com/langsmith/cron-jobs",
|
||||
"cloud/how-tos/datasets_studio.md": "https://docs.langchain.com/langsmith/use-studio",
|
||||
"cloud/how-tos/enqueue_concurrent.md": "https://docs.langchain.com/langsmith/enqueue-concurrent",
|
||||
"cloud/how-tos/generative_ui_react.md": "https://docs.langchain.com/langsmith/generative-ui-react",
|
||||
"cloud/how-tos/human_in_the_loop_time_travel.md": "https://docs.langchain.com/langsmith/human-in-the-loop-time-travel",
|
||||
"cloud/how-tos/interrupt_concurrent.md": "https://docs.langchain.com/langsmith/interrupt-concurrent",
|
||||
"cloud/how-tos/invoke_studio.md": "https://docs.langchain.com/langsmith/use-studio",
|
||||
"cloud/how-tos/iterate_graph_studio.md": "https://docs.langchain.com/langsmith/use-studio",
|
||||
"cloud/how-tos/reject_concurrent.md": "https://docs.langchain.com/langsmith/reject-concurrent",
|
||||
"cloud/how-tos/rollback_concurrent.md": "https://docs.langchain.com/langsmith/rollback-concurrent",
|
||||
"cloud/how-tos/same-thread.md": "https://docs.langchain.com/langsmith/same-thread",
|
||||
"cloud/how-tos/stateless_runs.md": "https://docs.langchain.com/langsmith/stateless-runs",
|
||||
"cloud/how-tos/streaming.md": "https://docs.langchain.com/langsmith/streaming",
|
||||
"cloud/how-tos/studio/manage_assistants.md": "https://docs.langchain.com/langsmith/use-studio",
|
||||
"cloud/how-tos/studio/quick_start.md": "https://docs.langchain.com/langsmith/quick-start-studio",
|
||||
"cloud/how-tos/studio/run_evals.md": "https://docs.langchain.com/langsmith/observability",
|
||||
"cloud/how-tos/threads_studio.md": "https://docs.langchain.com/langsmith/use-threads",
|
||||
"cloud/how-tos/use_stream_react.md": "https://docs.langchain.com/langsmith/use-stream-react",
|
||||
"cloud/how-tos/use_threads.md": "https://docs.langchain.com/langsmith/use-threads",
|
||||
"cloud/how-tos/webhooks.md": "https://docs.langchain.com/langsmith/use-webhooks",
|
||||
"cloud/quick_start.md": "https://docs.langchain.com/langsmith/deployment-quickstart",
|
||||
"cloud/reference/api/api_ref_control_plane.md": "https://docs.langchain.com/langsmith/api-ref-control-plane",
|
||||
"cloud/reference/api/api_ref.md": "https://docs.langchain.com/langsmith/server-api-ref",
|
||||
"cloud/reference/cli.md": "https://docs.langchain.com/langsmith/cli",
|
||||
"cloud/reference/env_var.md": "https://docs.langchain.com/langsmith/env-var",
|
||||
"cloud/reference/langgraph_server_changelog.md": "https://docs.langchain.com/langsmith/agent-server-changelog",
|
||||
"cloud/reference/sdk/js_ts_sdk_ref.md": "https://reference.langchain.com/javascript/modules/langsmith.html",
|
||||
"concepts/application_structure.md": "https://docs.langchain.com/langsmith/application-structure",
|
||||
"concepts/assistants.md": "https://docs.langchain.com/langsmith/assistants",
|
||||
"concepts/auth.md": "https://docs.langchain.com/langsmith/auth",
|
||||
"concepts/deployment_options.md": "https://docs.langchain.com/langsmith/deployments",
|
||||
"concepts/double_texting.md": "https://docs.langchain.com/langsmith/double-texting",
|
||||
"concepts/faq.md": "https://docs.langchain.com/langsmith/faq",
|
||||
"concepts/langgraph_cli.md": "https://docs.langchain.com/langsmith/cli",
|
||||
"concepts/langgraph_cloud.md": "https://docs.langchain.com/langsmith/cloud",
|
||||
"concepts/langgraph_components.md": "https://docs.langchain.com/langsmith/components",
|
||||
"concepts/langgraph_control_plane.md": "https://docs.langchain.com/langsmith/control-plane",
|
||||
"concepts/langgraph_data_plane.md": "https://docs.langchain.com/langsmith/data-plane",
|
||||
"concepts/langgraph_platform.md": "https://docs.langchain.com/langsmith/home",
|
||||
"concepts/langgraph_self_hosted_control_plane.md": "https://docs.langchain.com/langsmith/platform-setup",
|
||||
"concepts/langgraph_self_hosted_data_plane.md": "https://docs.langchain.com/langsmith/platform-setup",
|
||||
"concepts/langgraph_server.md": "https://docs.langchain.com/langsmith/agent-server",
|
||||
"concepts/langgraph_standalone_container.md": "https://docs.langchain.com/langsmith/docker",
|
||||
"concepts/langgraph_studio.md": "https://docs.langchain.com/langsmith/studio",
|
||||
"concepts/plans.md": "https://docs.langchain.com/langsmith/home",
|
||||
"concepts/scalability_and_resilience.md": "https://docs.langchain.com/langsmith/scalability-and-resilience",
|
||||
"concepts/sdk.md": "https://docs.langchain.com/langsmith/sdk",
|
||||
"concepts/server-mcp.md": "https://docs.langchain.com/langsmith/server-mcp",
|
||||
"concepts/template_applications.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"concepts/why-langgraph.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"examples/index.md": "https://docs.langchain.com/oss/python/langgraph/case-studies",
|
||||
"guides/index.md": "https://docs.langchain.com/oss/python/langchain/overview",
|
||||
"how-tos/auth/custom_auth.md": "https://docs.langchain.com/langsmith/custom-auth",
|
||||
"how-tos/auth/openapi_security.md": "https://docs.langchain.com/langsmith/openapi-security",
|
||||
"how-tos/autogen-integration.md": "https://docs.langchain.com/langsmith/autogen-integration",
|
||||
"how-tos/http/custom_lifespan.md": "https://docs.langchain.com/langsmith/custom-lifespan",
|
||||
"how-tos/http/custom_middleware.md": "https://docs.langchain.com/langsmith/custom-middleware",
|
||||
"how-tos/http/custom_routes.md": "https://docs.langchain.com/langsmith/custom-routes",
|
||||
"how-tos/ttl/configure_ttl.md": "https://docs.langchain.com/langsmith/configure-ttl",
|
||||
"how-tos/use-remote-graph.md": "https://docs.langchain.com/langsmith/use-remote-graph",
|
||||
"index.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"snippets/chat_model_tabs.md": "https://docs.langchain.com/oss/python/langchain/overview",
|
||||
"troubleshooting/errors/GRAPH_RECURSION_LIMIT.md": "https://docs.langchain.com/oss/python/langgraph/GRAPH_RECURSION_LIMIT",
|
||||
"troubleshooting/errors/index.md": "https://docs.langchain.com/oss/python/langgraph/common-errors",
|
||||
"troubleshooting/errors/INVALID_CHAT_HISTORY.md": "https://docs.langchain.com/oss/python/langgraph/INVALID_CHAT_HISTORY",
|
||||
"troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE.md": "https://docs.langchain.com/oss/python/langgraph/INVALID_CONCURRENT_GRAPH_UPDATE",
|
||||
"troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE.md": "https://docs.langchain.com/oss/python/langgraph/INVALID_GRAPH_NODE_RETURN_VALUE",
|
||||
"troubleshooting/errors/INVALID_LICENSE.md": "https://docs.langchain.com/oss/python/langgraph/common-errors",
|
||||
"troubleshooting/errors/MULTIPLE_SUBGRAPHS.md": "https://docs.langchain.com/oss/python/langgraph/MULTIPLE_SUBGRAPHS",
|
||||
"troubleshooting/studio.md": "https://docs.langchain.com/langsmith/troubleshooting-studio",
|
||||
"tutorials/auth/add_auth_server.md": "https://docs.langchain.com/langsmith/add-auth-server",
|
||||
"tutorials/auth/getting_started.md": "https://docs.langchain.com/langsmith/auth",
|
||||
"tutorials/auth/resource_auth.md": "https://docs.langchain.com/langsmith/resource-auth",
|
||||
"agents/agents.md": "https://docs.langchain.com/oss/python/langchain/agents",
|
||||
"concepts/why-langgraph.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"tutorials/langsmith/local-server.md": "https://docs.langchain.com/oss/python/langgraph/local-server",
|
||||
"tutorials/workflows.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"concepts/agentic_concepts.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"guides/index.md": "https://docs.langchain.com/oss/python/langchain/overview",
|
||||
"agents/overview.md": "https://docs.langchain.com/oss/python/langchain/agents",
|
||||
"concepts/agentic_concepts.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"agents/run_agents.md": "https://docs.langchain.com/oss/python/langgraph/quickstart",
|
||||
"concepts/low_level.md": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
"how-tos/graph-api.md": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
"concepts/functional_api.md": "https://docs.langchain.com/oss/python/langgraph/functional-api",
|
||||
"how-tos/use-functional-api.md": "https://docs.langchain.com/oss/python/langgraph/functional-api",
|
||||
"concepts/pregel.md": "https://docs.langchain.com/oss/python/langgraph/pregel",
|
||||
"concepts/streaming.md": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"how-tos/streaming.md": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"concepts/persistence.md": "https://docs.langchain.com/oss/python/langgraph/persistence",
|
||||
"concepts/durable_execution.md": "https://docs.langchain.com/oss/python/langgraph/durable-execution",
|
||||
"concepts/memory.md": "https://docs.langchain.com/oss/python/langgraph/memory",
|
||||
"how-tos/memory/add-memory.md": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
"agents/context.md": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
"agents/models.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"concepts/tools.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"how-tos/tool-calling.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"concepts/human_in_the_loop.md": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"how-tos/human_in_the_loop/add-human-in-the-loop.md": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"concepts/time-travel.md": "https://docs.langchain.com/oss/python/langgraph/persistence",
|
||||
"how-tos/human_in_the_loop/time-travel.md": "https://docs.langchain.com/oss/python/langgraph/use-time-travel",
|
||||
"concepts/subgraphs.md": "https://docs.langchain.com/oss/python/langgraph/use-subgraphs",
|
||||
"how-tos/subgraph.md": "https://docs.langchain.com/oss/python/langgraph/use-subgraphs",
|
||||
"concepts/multi_agent.md": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
"agents/multi-agent.md": "https://docs.langchain.com/oss/python/langchain/multi-agent",
|
||||
"how-tos/multi_agent.md": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
"concepts/mcp.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"agents/mcp.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"concepts/tracing.md": "https://docs.langchain.com/oss/python/langgraph/observability",
|
||||
"how-tos/enable-tracing.md": "https://docs.langchain.com/oss/python/langgraph/observability",
|
||||
"agents/evals.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"examples/index.md": "https://docs.langchain.com/oss/python/langgraph/case-studies",
|
||||
"concepts/template_applications.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"tutorials/rag/langgraph_agentic_rag.md": "https://docs.langchain.com/oss/python/langgraph/agentic-rag",
|
||||
"tutorials/multi_agent/agent_supervisor.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"tutorials/sql/sql-agent.md": "https://docs.langchain.com/oss/python/langgraph/sql-agent",
|
||||
"agents/ui.md": "https://docs.langchain.com/oss/python/langgraph/ui",
|
||||
"how-tos/run-id-langsmith.md": "https://docs.langchain.com/oss/python/langgraph/observability",
|
||||
"troubleshooting/errors/index.md": "https://docs.langchain.com/oss/python/langgraph/common-errors",
|
||||
"troubleshooting/errors/GRAPH_RECURSION_LIMIT.md": "https://docs.langchain.com/oss/python/langgraph/GRAPH_RECURSION_LIMIT",
|
||||
"troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE.md": "https://docs.langchain.com/oss/python/langgraph/INVALID_CONCURRENT_GRAPH_UPDATE",
|
||||
"troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE.md": "https://docs.langchain.com/oss/python/langgraph/INVALID_GRAPH_NODE_RETURN_VALUE",
|
||||
"troubleshooting/errors/MULTIPLE_SUBGRAPHS.md": "https://docs.langchain.com/oss/python/langgraph/MULTIPLE_SUBGRAPHS",
|
||||
"troubleshooting/errors/INVALID_CHAT_HISTORY.md": "https://docs.langchain.com/oss/python/langgraph/INVALID_CHAT_HISTORY",
|
||||
"troubleshooting/errors/INVALID_LICENSE.md": "https://docs.langchain.com/oss/python/langgraph/common-errors",
|
||||
"adopters.md": "https://docs.langchain.com/oss/python/langgraph/case-studies",
|
||||
"concepts/faq.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"agents/prebuilt.md": "https://docs.langchain.com/oss/python/langchain/agents",
|
||||
}
|
||||
|
||||
|
||||
@@ -174,31 +500,7 @@ def _add_path_to_code_blocks(markdown: str, page: Page) -> str:
|
||||
return code_block_pattern.sub(replace_code_block_header, markdown)
|
||||
|
||||
|
||||
def _resolve_cross_references(md_text: str, link_map: dict[str, str]) -> str:
|
||||
"""Replace [title][identifier] with [title](url) using language-specific link_map.
|
||||
|
||||
Args:
|
||||
md_text: The markdown text to process.
|
||||
link_map: mapping of identifier to URL.
|
||||
|
||||
Returns:
|
||||
The processed markdown text with cross-references resolved.
|
||||
"""
|
||||
# Pattern to match [title][identifier]
|
||||
pattern = re.compile(r"\[([^\]]+)\]\[([^\]]+)\]")
|
||||
|
||||
def replace_reference(match: re.Match) -> str:
|
||||
"""Replace the matched reference with the corresponding URL."""
|
||||
title, identifier = match.group(1), match.group(2)
|
||||
url = link_map.get(identifier)
|
||||
|
||||
if url:
|
||||
return f"[{title}]({url})"
|
||||
else:
|
||||
# Leave it unchanged if not found
|
||||
return match.group(0)
|
||||
|
||||
return pattern.sub(replace_reference, md_text)
|
||||
# Compiled regex patterns for better performance and readability
|
||||
|
||||
|
||||
def _apply_conditional_rendering(md_text: str, target_language: str) -> str:
|
||||
@@ -208,7 +510,7 @@ def _apply_conditional_rendering(md_text: str, target_language: str) -> str:
|
||||
pattern = re.compile(
|
||||
r"(?P<indent>[ \t]*):::(?P<language>\w+)\s*\n"
|
||||
r"(?P<content>((?:.*\n)*?))" # Capture the content inside the block
|
||||
r"(?P=indent):::" # Match closing with the same indentation
|
||||
r"(?P=indent)[ \t]*:::" # Match closing with the same indentation + any additional whitespace
|
||||
)
|
||||
|
||||
def replace_conditional_blocks(match: re.Match) -> str:
|
||||
@@ -293,7 +595,7 @@ def _highlight_code_blocks(markdown: str) -> str:
|
||||
opening_fence += f" {attributes}"
|
||||
|
||||
if highlighted_lines:
|
||||
opening_fence += f" hl_lines=\"{' '.join(highlighted_lines)}\""
|
||||
opening_fence += f' hl_lines="{" ".join(highlighted_lines)}"'
|
||||
|
||||
return (
|
||||
# The indent and opening fence
|
||||
@@ -308,6 +610,21 @@ def _highlight_code_blocks(markdown: str) -> str:
|
||||
return markdown
|
||||
|
||||
|
||||
def _save_page_output(markdown: str, output_path: str):
|
||||
"""Save markdown content to a file, creating parent directories if needed.
|
||||
|
||||
Args:
|
||||
markdown: The markdown content to save
|
||||
output_path: The file path to save to
|
||||
"""
|
||||
# Create parent directories recursively if they don't exist
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
|
||||
# Write the markdown content to the file
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write(markdown)
|
||||
|
||||
|
||||
def _on_page_markdown_with_config(
|
||||
markdown: str,
|
||||
page: Page,
|
||||
@@ -323,6 +640,14 @@ def _on_page_markdown_with_config(
|
||||
# logger.info("Processing Jupyter notebook: %s", page.file.src_path)
|
||||
markdown = convert_notebook(page.file.abs_src_path)
|
||||
|
||||
target_language = kwargs.get(
|
||||
"target_language",
|
||||
os.environ.get("TARGET_LANGUAGE", "python")
|
||||
)
|
||||
|
||||
# Apply cross-reference preprocessing to all markdown content
|
||||
markdown = _replace_autolinks(markdown, page.file.src_path, default_scope=target_language)
|
||||
|
||||
# Append API reference links to code blocks
|
||||
if add_api_references:
|
||||
markdown = update_markdown_with_imports(markdown, page.file.abs_src_path)
|
||||
@@ -330,18 +655,7 @@ def _on_page_markdown_with_config(
|
||||
markdown = _highlight_code_blocks(markdown)
|
||||
|
||||
# Apply conditional rendering for code blocks
|
||||
target_language = kwargs.get("target_language", "python")
|
||||
markdown = _apply_conditional_rendering(markdown, target_language)
|
||||
if target_language == "js":
|
||||
markdown = _resolve_cross_references(markdown, JS_LINK_MAP)
|
||||
elif target_language == "python":
|
||||
# Via a dedicated plugin
|
||||
pass
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unsupported target language: {target_language}. "
|
||||
"Supported languages are 'python' and 'js'."
|
||||
)
|
||||
|
||||
# Add file path as an attribute to code blocks that are executable.
|
||||
# This file path is used to associate fixtures with the executable code
|
||||
@@ -356,12 +670,20 @@ def _on_page_markdown_with_config(
|
||||
|
||||
|
||||
def on_page_markdown(markdown: str, page: Page, **kwargs: Dict[str, Any]):
|
||||
return _on_page_markdown_with_config(
|
||||
finalized_markdown = _on_page_markdown_with_config(
|
||||
markdown,
|
||||
page,
|
||||
add_api_references=True,
|
||||
**kwargs,
|
||||
)
|
||||
page.meta["original_markdown"] = finalized_markdown
|
||||
|
||||
output_path = os.environ.get("MD_OUTPUT_PATH")
|
||||
if output_path:
|
||||
file_path = os.path.join(output_path, page.file.src_path)
|
||||
_save_page_output(finalized_markdown, file_path)
|
||||
|
||||
return finalized_markdown
|
||||
|
||||
|
||||
# redirects
|
||||
@@ -432,34 +754,182 @@ height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
|
||||
return html # fallback if no <body> found
|
||||
|
||||
|
||||
def on_post_page(output: str, page: Page, config: MkDocsConfig) -> str:
|
||||
def _inject_markdown_into_html(html: str, page: Page) -> str:
|
||||
"""Inject the original markdown content into the HTML page as JSON."""
|
||||
original_markdown = page.meta.get("original_markdown", "")
|
||||
if not original_markdown:
|
||||
return html
|
||||
markdown_data = {
|
||||
"markdown": original_markdown,
|
||||
"title": page.title or "Page Content",
|
||||
"url": page.url or "",
|
||||
}
|
||||
|
||||
# Properly escape the JSON for HTML
|
||||
json_content = json.dumps(markdown_data, ensure_ascii=False)
|
||||
|
||||
json_content = (
|
||||
json_content.replace("</", "\\u003c/")
|
||||
.replace("<script", "\\u003cscript")
|
||||
.replace("</script", "\\u003c/script")
|
||||
)
|
||||
|
||||
script_content = (
|
||||
f'<script id="page-markdown-content" '
|
||||
f'type="application/json">{json_content}</script>'
|
||||
)
|
||||
|
||||
# Insert before </head> if it exists, otherwise before </body>
|
||||
if "</head>" not in html:
|
||||
raise ValueError(
|
||||
"HTML does not contain </head> tag. Cannot inject markdown content."
|
||||
)
|
||||
return html.replace("</head>", f"{script_content}</head>")
|
||||
|
||||
|
||||
def on_post_page(html: str, page: Page, config: MkDocsConfig) -> str:
|
||||
"""Inject Google Tag Manager noscript tag immediately after <body>.
|
||||
|
||||
Args:
|
||||
output: The HTML output of the page.
|
||||
html: The HTML output of the page.
|
||||
page: The page instance.
|
||||
config: The MkDocs configuration object.
|
||||
|
||||
Returns:
|
||||
modified HTML output with GTM code injected.
|
||||
"""
|
||||
return _inject_gtm(output)
|
||||
html = _inject_markdown_into_html(html, page)
|
||||
return _inject_gtm(html)
|
||||
|
||||
|
||||
# Create HTML files for redirects after site dir has been built
|
||||
def on_post_build(config):
|
||||
use_directory_urls = config.get("use_directory_urls")
|
||||
site_dir = config["site_dir"]
|
||||
|
||||
# Track which paths have explicit redirects
|
||||
redirected_paths = set()
|
||||
|
||||
# Collect all existing HTML files in the site
|
||||
all_html_files = set()
|
||||
for root, dirs, files in os.walk(site_dir):
|
||||
for file in files:
|
||||
if file.endswith(".html"):
|
||||
# Get relative path from site_dir
|
||||
html_path = os.path.relpath(os.path.join(root, file), site_dir)
|
||||
# Normalize path separators to forward slashes
|
||||
html_path = html_path.replace(os.sep, "/")
|
||||
all_html_files.add(html_path)
|
||||
|
||||
# Process explicit redirects from REDIRECT_MAP
|
||||
for page_old, page_new in REDIRECT_MAP.items():
|
||||
# Convert .ipynb to .md for path calculation
|
||||
page_old = page_old.replace(".ipynb", ".md")
|
||||
page_new = page_new.replace(".ipynb", ".md")
|
||||
page_new_before_hash, hash, suffix = page_new.partition("#")
|
||||
old_html_path = File(page_old, "", "", use_directory_urls).dest_path.replace(
|
||||
os.sep, "/"
|
||||
)
|
||||
new_html_path = File(page_new_before_hash, "", "", True).url
|
||||
new_html_path = (
|
||||
posixpath.relpath(new_html_path, start=posixpath.dirname(old_html_path))
|
||||
+ hash
|
||||
+ suffix
|
||||
)
|
||||
_write_html(config["site_dir"], old_html_path, new_html_path)
|
||||
|
||||
# Calculate the HTML path for the old page (whether it exists or not)
|
||||
if use_directory_urls:
|
||||
# With directory URLs: /path/to/page/ becomes /path/to/page/index.html
|
||||
if page_old.endswith(".md"):
|
||||
old_html_path = page_old[:-3] + "/index.html"
|
||||
else:
|
||||
old_html_path = page_old + "/index.html"
|
||||
else:
|
||||
# Without directory URLs: /path/to/page.md becomes /path/to/page.html
|
||||
if page_old.endswith(".md"):
|
||||
old_html_path = page_old[:-3] + ".html"
|
||||
else:
|
||||
old_html_path = page_old + ".html"
|
||||
|
||||
# Track this path as redirected
|
||||
redirected_paths.add(old_html_path)
|
||||
|
||||
if isinstance(page_new, str) and page_new.startswith("http"):
|
||||
# Handle external redirects
|
||||
_write_html(site_dir, old_html_path, page_new)
|
||||
else:
|
||||
# Handle internal redirects
|
||||
page_new = page_new.replace(".ipynb", ".md")
|
||||
page_new_before_hash, hash, suffix = page_new.partition("#")
|
||||
|
||||
# Try to get the new path using File class, but fallback to manual calculation
|
||||
try:
|
||||
new_html_path = File(page_new_before_hash, "", "", True).url
|
||||
new_html_path = (
|
||||
posixpath.relpath(new_html_path, start=posixpath.dirname(old_html_path))
|
||||
+ hash
|
||||
+ suffix
|
||||
)
|
||||
except:
|
||||
# Fallback: calculate relative path manually
|
||||
if use_directory_urls:
|
||||
if page_new_before_hash.endswith(".md"):
|
||||
new_html_path = page_new_before_hash[:-3] + "/"
|
||||
else:
|
||||
new_html_path = page_new_before_hash + "/"
|
||||
else:
|
||||
if page_new_before_hash.endswith(".md"):
|
||||
new_html_path = page_new_before_hash[:-3] + ".html"
|
||||
else:
|
||||
new_html_path = page_new_before_hash + ".html"
|
||||
new_html_path += hash + suffix
|
||||
|
||||
_write_html(site_dir, old_html_path, new_html_path)
|
||||
|
||||
# Create catch-all redirects for any HTML files not explicitly redirected
|
||||
catchall_url = "https://docs.langchain.com/oss/python/langgraph/overview"
|
||||
for html_file in all_html_files:
|
||||
# Skip if this file is already explicitly redirected
|
||||
if html_file in redirected_paths:
|
||||
continue
|
||||
|
||||
# Skip the root index.html (we handle that separately)
|
||||
if html_file == "index.html":
|
||||
continue
|
||||
|
||||
# Skip reference documentation (keep those accessible)
|
||||
if html_file.startswith("reference/"):
|
||||
continue
|
||||
|
||||
# Create redirect for this unmapped file
|
||||
_write_html(site_dir, html_file, catchall_url)
|
||||
|
||||
# Create root index.html redirect
|
||||
root_redirect_html = """<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Redirecting to LangGraph Documentation</title>
|
||||
<link rel="canonical" href="https://docs.langchain.com/oss/python/langgraph/overview">
|
||||
<meta name="robots" content="noindex">
|
||||
<script>var anchor=window.location.hash.substr(1);location.href="https://docs.langchain.com/oss/python/langgraph/overview"+(anchor?"#"+anchor:"")</script>
|
||||
<meta http-equiv="refresh" content="0; url=https://docs.langchain.com/oss/python/langgraph/overview">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Documentation has moved</h1>
|
||||
<p>The LangGraph documentation has moved to <a href="https://docs.langchain.com/oss/python/langgraph/overview">docs.langchain.com</a>.</p>
|
||||
<p>Redirecting you now...</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
root_index_path = os.path.join(site_dir, "index.html")
|
||||
with open(root_index_path, "w", encoding="utf-8") as f:
|
||||
f.write(root_redirect_html)
|
||||
|
||||
# Create server-side catch-all redirect file for Netlify/Cloudflare Pages
|
||||
# This handles any pages not explicitly mapped in REDIRECT_MAP
|
||||
# Note: This won't work on GitHub Pages, but kept for potential future use
|
||||
redirects_content = """# Netlify/Cloudflare Pages redirect rules
|
||||
# Specific redirects are handled by individual HTML redirect pages
|
||||
# This is the catch-all for any unmapped pages
|
||||
|
||||
# Exclude reference docs from catch-all
|
||||
/reference/* 200
|
||||
|
||||
# Catch-all: redirect any page not explicitly mapped
|
||||
/* https://docs.langchain.com/oss/python/langgraph/overview 301
|
||||
"""
|
||||
|
||||
redirects_path = os.path.join(site_dir, "_redirects")
|
||||
with open(redirects_path, "w", encoding="utf-8") as f:
|
||||
f.write(redirects_content)
|
||||
|
||||
@@ -15,9 +15,10 @@ If you’re looking for other prebuilt libraries, explore the community-built op
|
||||
below. These libraries can extend LangGraph's functionality in various ways.
|
||||
|
||||
## 📚 Available Libraries
|
||||
|
||||
[//]: # (This file is automatically generated using a script in docs/_scripts. Do not edit this file directly!)
|
||||
{library_list}
|
||||
|
||||
:::python
|
||||
{python_library_list}
|
||||
|
||||
## ✨ Contributing Your Library
|
||||
|
||||
@@ -28,16 +29,39 @@ To share your project, simply open a Pull Request adding an entry for your packa
|
||||
|
||||
**Guidelines**
|
||||
|
||||
- Your repo must be distributed as an installable package (e.g., PyPI for Python, npm
|
||||
for JavaScript/TypeScript, etc.) 📦
|
||||
- Your repo must be distributed as an installable package on PyPI 📦
|
||||
- The repo should either use the Graph API (exposing a `StateGraph` instance) or
|
||||
the Functional API (exposing an `entrypoint`).
|
||||
- The package must include documentation (e.g., a `README.md` or docs site)
|
||||
explaining how to use it.
|
||||
|
||||
|
||||
We'll review your contribution and merge it in!
|
||||
|
||||
Thanks for contributing! 🚀
|
||||
:::
|
||||
|
||||
:::js
|
||||
{js_library_list}
|
||||
|
||||
## ✨ Contributing Your Library
|
||||
|
||||
Have you built an awesome open-source library using LangGraph? We'd love to feature
|
||||
your project on the official LangGraph documentation pages! 🏆
|
||||
|
||||
To share your project, simply open a Pull Request adding an entry for your package in our [packages.yml]({langgraph_url}) file.
|
||||
|
||||
**Guidelines**
|
||||
|
||||
- Your repo must be distributed as an installable package on npm 📦
|
||||
- The repo should either use the Graph API (exposing a `StateGraph` instance) or
|
||||
the Functional API (exposing an `entrypoint`).
|
||||
- The package must include documentation (e.g., a `README.md` or docs site)
|
||||
explaining how to use it.
|
||||
|
||||
We'll review your contribution and merge it in!
|
||||
|
||||
Thanks for contributing! 🚀
|
||||
:::
|
||||
"""
|
||||
|
||||
|
||||
@@ -46,36 +70,18 @@ class ResolvedPackage(TypedDict):
|
||||
"""The name of the package."""
|
||||
repo: str
|
||||
"""Repository ID within github. Format is: [orgname]/[repo_name]."""
|
||||
monorepo_path: str | None
|
||||
"""Optional: The path to the package in the monorepo. Must be relative to the root of the monorepo."""
|
||||
language: str
|
||||
"""The language of the package. (either 'python' or 'js')"""
|
||||
weekly_downloads: int | None
|
||||
"""The weekly download count of the package."""
|
||||
description: str
|
||||
"""A brief description of what the package does."""
|
||||
|
||||
|
||||
def generate_markdown(resolved_packages: List[ResolvedPackage], language: str) -> str:
|
||||
"""Generate the markdown content for the third party page.
|
||||
|
||||
Args:
|
||||
resolved_packages: A list of resolved package information.
|
||||
language: str
|
||||
|
||||
Returns:
|
||||
The markdown content as a string.
|
||||
def generate_package_table(resolved_packages: List[ResolvedPackage]) -> str:
|
||||
"""Generate the package table for the third party page.
|
||||
"""
|
||||
# Update the URL to the actual file once the initial version is merged
|
||||
if language == "python":
|
||||
langgraph_url = (
|
||||
"https://github.com/langchain-ai/langgraph/blob/main/docs"
|
||||
"/_scripts/third_party_page/packages.yml"
|
||||
)
|
||||
elif language == "js":
|
||||
langgraph_url = (
|
||||
"https://github.com/langchain-ai/langgraphjs/blob/main/docs"
|
||||
"/_scripts/third_party/packages.yml"
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Invalid language '{language}'. Expected 'python' or 'js'.")
|
||||
|
||||
sorted_packages = sorted(
|
||||
resolved_packages, key=lambda p: p["weekly_downloads"] or 0, reverse=True
|
||||
)
|
||||
@@ -85,7 +91,15 @@ def generate_markdown(resolved_packages: List[ResolvedPackage], language: str) -
|
||||
]
|
||||
for package in sorted_packages:
|
||||
name = f"**{package['name']}**"
|
||||
repo_url = f"[{package['repo']}](https://github.com/{package['repo']})"
|
||||
|
||||
monorepo_path = package.get("monorepo_path", "")
|
||||
if monorepo_path:
|
||||
monorepo_path = monorepo_path[1:] if monorepo_path.startswith('/') else monorepo_path
|
||||
repo_url_suffix = f"/tree/main/{monorepo_path}"
|
||||
else:
|
||||
repo_url_suffix = ""
|
||||
repo_url = f"https://github.com/{package['repo']}{repo_url_suffix}"
|
||||
|
||||
stars_badge = (
|
||||
f"https://img.shields.io/github/stars/{package['repo']}?style=social"
|
||||
)
|
||||
@@ -93,13 +107,39 @@ def generate_markdown(resolved_packages: List[ResolvedPackage], language: str) -
|
||||
downloads = package["weekly_downloads"] or "-"
|
||||
row = f"| {name} | {repo_url} | {package['description']} | {downloads} | {stars}"
|
||||
rows.append(row)
|
||||
return "\n".join(rows)
|
||||
|
||||
def generate_markdown(resolved_packages: List[ResolvedPackage]) -> str:
|
||||
"""Generate the markdown content for the third party page.
|
||||
|
||||
Args:
|
||||
resolved_packages: A list of resolved package information.
|
||||
|
||||
Returns:
|
||||
The markdown content as a string.
|
||||
"""
|
||||
# Update the URL to the actual file once the initial version is merged
|
||||
langgraph_url = (
|
||||
"https://github.com/langchain-ai/langgraph/blob/main/docs"
|
||||
"/_scripts/third_party_page/packages.yml"
|
||||
)
|
||||
|
||||
python_library_list = generate_package_table(
|
||||
[p for p in resolved_packages if p["language"] == "python"]
|
||||
)
|
||||
js_library_list = generate_package_table(
|
||||
[p for p in resolved_packages if p["language"] == "js"]
|
||||
)
|
||||
|
||||
markdown_content = MARKDOWN.format(
|
||||
library_list="\n".join(rows), langgraph_url=langgraph_url
|
||||
python_library_list=python_library_list,
|
||||
js_library_list=js_library_list,
|
||||
langgraph_url=langgraph_url,
|
||||
)
|
||||
return markdown_content
|
||||
|
||||
|
||||
def main(input_file: str, output_file: str, language: str) -> None:
|
||||
def main(input_file: str, output_file: str) -> None:
|
||||
"""Main function to create the third party page.
|
||||
|
||||
Args:
|
||||
@@ -111,7 +151,7 @@ def main(input_file: str, output_file: str, language: str) -> None:
|
||||
with open(input_file, "r") as f:
|
||||
resolved_packages: List[ResolvedPackage] = yaml.safe_load(f)
|
||||
|
||||
markdown_content = generate_markdown(resolved_packages, language)
|
||||
markdown_content = generate_markdown(resolved_packages)
|
||||
|
||||
# Write the markdown content to the output file
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
@@ -127,12 +167,6 @@ if __name__ == "__main__":
|
||||
parser.add_argument(
|
||||
"output_file", help="Path to the output file for the third party page."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--language",
|
||||
choices=["python", "js"],
|
||||
default="python",
|
||||
help="The language for which to generate the third party page. Defaults to 'python'.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
main(args.input_file, args.output_file, args.language)
|
||||
main(args.input_file, args.output_file)
|
||||
|
||||
@@ -11,101 +11,158 @@ import yaml
|
||||
|
||||
|
||||
class Package(TypedDict):
|
||||
"""A TypedDict representing a package"""
|
||||
|
||||
name: str
|
||||
"""The name of the package."""
|
||||
repo: str
|
||||
"""Repository ID within github. Format is: [orgname]/[repo_name]."""
|
||||
monorepo_path: str | None
|
||||
"""The path to the package in the monorepo. Only used for JS packages."""
|
||||
description: str
|
||||
"""A brief description of what the package does."""
|
||||
|
||||
|
||||
class ResolvedPackage(Package):
|
||||
weekly_downloads: int | None
|
||||
"""The weekly download count of the package."""
|
||||
language: str
|
||||
"""The language of the package. (either 'python' or 'js')"""
|
||||
|
||||
|
||||
HERE = pathlib.Path(__file__).parent
|
||||
PACKAGES_FILE = HERE / "packages.yml"
|
||||
PACKAGES = yaml.safe_load(PACKAGES_FILE.read_text())['packages']
|
||||
PACKAGES = yaml.safe_load(PACKAGES_FILE.read_text())["packages"]
|
||||
|
||||
|
||||
def _get_weekly_downloads(packages: list[Package], fake: bool) -> list[ResolvedPackage]:
|
||||
"""Retrieve the monthly download count for a list of packages from PyPIStats."""
|
||||
def _get_pypi_downloads(package: Package) -> int:
|
||||
"""Retrieve the weekly download count for a package from PyPIStats."""
|
||||
|
||||
# First check if package exists on PyPI
|
||||
pypi_url = f"https://pypi.org/pypi/{package['name']}/json"
|
||||
try:
|
||||
pypi_response = requests.get(pypi_url)
|
||||
pypi_response.raise_for_status()
|
||||
except requests.exceptions.HTTPError:
|
||||
raise AssertionError(f"Package {package['name']} does not exist on PyPI")
|
||||
|
||||
# Get first release date
|
||||
pypi_data = pypi_response.json()
|
||||
releases = pypi_data["releases"]
|
||||
first_release_date = None
|
||||
for version_releases in releases.values():
|
||||
if version_releases: # Some versions may be empty lists
|
||||
upload_time = datetime.fromisoformat(version_releases[0]["upload_time"])
|
||||
if first_release_date is None or upload_time < first_release_date:
|
||||
first_release_date = upload_time
|
||||
|
||||
if first_release_date is None:
|
||||
raise AssertionError(f"Package {package['name']} has no releases yet")
|
||||
|
||||
# If package was published in last 48 hours, skip download stats
|
||||
if (datetime.now() - first_release_date).total_seconds() >= 48 * 3600:
|
||||
url = f"https://pypistats.org/api/packages/{package['name']}/overall"
|
||||
|
||||
response = requests.get(url)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
sorted_data = sorted(
|
||||
data["data"],
|
||||
key=lambda x: datetime.strptime(x["date"], "%Y-%m-%d"),
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
# Sum the last 7 days of downloads
|
||||
return sum(entry["downloads"] for entry in sorted_data[:7])
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def _get_npm_downloads(package: Package) -> int:
|
||||
"""Retrieve the weekly download count for a package on the npm registry."""
|
||||
|
||||
# Check if package exists on the npm registry
|
||||
npm_url = f"https://registry.npmjs.org/{package['name']}"
|
||||
try:
|
||||
npm_response = requests.get(npm_url)
|
||||
npm_response.raise_for_status()
|
||||
except requests.exceptions.HTTPError:
|
||||
raise AssertionError(
|
||||
f"Package {package['name']} does not exist on npm registry"
|
||||
)
|
||||
|
||||
npm_data = npm_response.json()
|
||||
|
||||
# Retrieve the first publish date using the 'created' timestamp from the 'time' field.
|
||||
created_str = npm_data.get("time", {}).get("created")
|
||||
if created_str is None:
|
||||
raise AssertionError(
|
||||
f"Package {package['name']} has no creation time in registry data"
|
||||
)
|
||||
# Remove the trailing 'Z' if present and parse the ISO format timestamp
|
||||
first_publish_date = datetime.fromisoformat(created_str.rstrip("Z"))
|
||||
|
||||
# If package was published more than 48 hours ago, fetch download stats.
|
||||
if (datetime.now() - first_publish_date).total_seconds() >= 48 * 3600:
|
||||
stats_url = f"https://api.npmjs.org/downloads/point/last-week/{package['name']}"
|
||||
stats_response = requests.get(stats_url)
|
||||
stats_response.raise_for_status()
|
||||
stats_data = stats_response.json()
|
||||
return stats_data.get("downloads", None)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def _get_weekly_downloads(
|
||||
packages: dict[str, list[Package]], fake: bool
|
||||
) -> list[ResolvedPackage]:
|
||||
"""Retrieve the weekly download count for a dictionary of python or js packages."""
|
||||
resolved_packages: list[ResolvedPackage] = []
|
||||
|
||||
if fake:
|
||||
# To avoid making network requests during testing, return fake download counts
|
||||
for package in packages:
|
||||
for language, package_list in packages.items():
|
||||
for package in package_list:
|
||||
resolved_packages.append(
|
||||
{
|
||||
"name": package["name"],
|
||||
"repo": package["repo"],
|
||||
"monorepo_path": package.get("monorepo_path", None),
|
||||
"language": language,
|
||||
"description": package["description"],
|
||||
"weekly_downloads": -12345,
|
||||
}
|
||||
)
|
||||
return resolved_packages
|
||||
|
||||
for language, package_list in packages.items():
|
||||
for package in package_list:
|
||||
if language == "python":
|
||||
num_downloads = _get_pypi_downloads(package)
|
||||
elif language == "js":
|
||||
num_downloads = _get_npm_downloads(package)
|
||||
else:
|
||||
num_downloads = None
|
||||
|
||||
resolved_packages.append(
|
||||
{
|
||||
"name": package["name"],
|
||||
"repo": package["repo"],
|
||||
"weekly_downloads": -12345,
|
||||
"monorepo_path": package.get("monorepo_path", None),
|
||||
"language": language,
|
||||
"description": package["description"],
|
||||
"weekly_downloads": num_downloads,
|
||||
}
|
||||
)
|
||||
return resolved_packages
|
||||
|
||||
for package in packages:
|
||||
# First check if package exists on PyPI
|
||||
pypi_url = f"https://pypi.org/pypi/{package['name']}/json"
|
||||
try:
|
||||
pypi_response = requests.get(pypi_url)
|
||||
pypi_response.raise_for_status()
|
||||
except requests.exceptions.HTTPError:
|
||||
raise AssertionError(f"Package {package['name']} does not exist on PyPI")
|
||||
|
||||
# Get first release date
|
||||
pypi_data = pypi_response.json()
|
||||
releases = pypi_data["releases"]
|
||||
first_release_date = None
|
||||
for version_releases in releases.values():
|
||||
if version_releases: # Some versions may be empty lists
|
||||
upload_time = datetime.fromisoformat(version_releases[0]["upload_time"])
|
||||
if first_release_date is None or upload_time < first_release_date:
|
||||
first_release_date = upload_time
|
||||
|
||||
if first_release_date is None:
|
||||
raise AssertionError(f"Package {package['name']} has no releases yet")
|
||||
|
||||
# If package was published in last 48 hours, skip download stats
|
||||
if (datetime.now() - first_release_date).total_seconds() >= 48 * 3600:
|
||||
url = f"https://pypistats.org/api/packages/{package['name']}/overall"
|
||||
|
||||
response = requests.get(url)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
sorted_data = sorted(
|
||||
data["data"],
|
||||
key=lambda x: datetime.strptime(x["date"], "%Y-%m-%d"),
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
# Sum the last 7 days of downloads
|
||||
num_downloads = sum(entry["downloads"] for entry in sorted_data[:7])
|
||||
else:
|
||||
num_downloads = None
|
||||
|
||||
resolved_packages.append(
|
||||
{
|
||||
"name": package["name"],
|
||||
"repo": package["repo"],
|
||||
"weekly_downloads": num_downloads,
|
||||
"description": package["description"],
|
||||
}
|
||||
)
|
||||
|
||||
return resolved_packages
|
||||
|
||||
|
||||
|
||||
def main(output_file: str, fake: bool) -> None:
|
||||
"""Main function to generate package download information.
|
||||
|
||||
Args:
|
||||
output_file: Path to the output YAML file.
|
||||
fake: If `True`, use fake download counts for testing purposes.
|
||||
"""
|
||||
resolved_packages: list[ResolvedPackage] = _get_weekly_downloads(PACKAGES, fake)
|
||||
|
||||
|
||||
@@ -1,41 +1,58 @@
|
||||
#A list of third-party packages to surface on the third-party page.
|
||||
packages:
|
||||
- name: "trustcall"
|
||||
repo: "hinthornw/trustcall"
|
||||
description: "Tenacious tool calling built on LangGraph."
|
||||
- name: "breeze-agent"
|
||||
repo: "andrestorres123/breeze-agent"
|
||||
description: "A streamlined research system built inspired on STORM and built on LangGraph."
|
||||
- name: "langgraph-supervisor"
|
||||
repo: "langchain-ai/langgraph-supervisor-py"
|
||||
description: "Build supervisor multi-agent systems with LangGraph."
|
||||
- name: "langmem"
|
||||
repo: "langchain-ai/langmem"
|
||||
description: "Build agents that learn and adapt from interactions over time."
|
||||
- name: "langchain-mcp-adapters"
|
||||
repo: "langchain-ai/langchain-mcp-adapters"
|
||||
description: "Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents."
|
||||
- name: "open-deep-research"
|
||||
repo: "langchain-ai/open_deep_research"
|
||||
description: "Open source assistant for iterative web research and report writing."
|
||||
- name: "langgraph-swarm"
|
||||
repo: "langchain-ai/langgraph-swarm-py"
|
||||
description: "Build swarm-style multi-agent systems using LangGraph."
|
||||
- name: "delve-taxonomy-generator"
|
||||
repo: "andrestorres123/delve"
|
||||
description: "A taxonomy generator for unstructured data"
|
||||
- name: "nodeology"
|
||||
repo: "xyin-anl/Nodeology"
|
||||
description: "Enable researcher to build scientific workflows easily with simplified interface."
|
||||
- name: "langgraph-bigtool"
|
||||
repo: "langchain-ai/langgraph-bigtool"
|
||||
description: "Build LangGraph agents with large numbers of tools."
|
||||
- name: "ai-data-science-team"
|
||||
repo: "business-science/ai-data-science-team"
|
||||
description: "An AI-powered data science team of agents to help you perform common data science tasks 10X faster."
|
||||
- name: "langgraph-reflection"
|
||||
repo: "langchain-ai/langgraph-reflection"
|
||||
description: "LangGraph agent that runs a reflection step."
|
||||
- name: "langgraph-codeact"
|
||||
repo: "langchain-ai/langgraph-codeact"
|
||||
description: "LangGraph implementation of CodeAct agent that generates and executes code instead of tool calling."
|
||||
python:
|
||||
- name: "trustcall"
|
||||
repo: "hinthornw/trustcall"
|
||||
description: "Tenacious tool calling built on LangGraph."
|
||||
- name: "breeze-agent"
|
||||
repo: "andrestorres123/breeze-agent"
|
||||
description: "A streamlined research system built inspired on STORM and built on LangGraph."
|
||||
- name: "langgraph-supervisor"
|
||||
repo: "langchain-ai/langgraph-supervisor-py"
|
||||
description: "Build supervisor multi-agent systems with LangGraph."
|
||||
- name: "langmem"
|
||||
repo: "langchain-ai/langmem"
|
||||
description: "Build agents that learn and adapt from interactions over time."
|
||||
- name: "langchain-mcp-adapters"
|
||||
repo: "langchain-ai/langchain-mcp-adapters"
|
||||
description: "Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents."
|
||||
- name: "open-deep-research"
|
||||
repo: "langchain-ai/open_deep_research"
|
||||
description: "Open source assistant for iterative web research and report writing."
|
||||
- name: "langgraph-swarm"
|
||||
repo: "langchain-ai/langgraph-swarm-py"
|
||||
description: "Build swarm-style multi-agent systems using LangGraph."
|
||||
- name: "delve-taxonomy-generator"
|
||||
repo: "andrestorres123/delve"
|
||||
description: "A taxonomy generator for unstructured data"
|
||||
- name: "nodeology"
|
||||
repo: "xyin-anl/Nodeology"
|
||||
description: "Enable researcher to build scientific workflows easily with simplified interface."
|
||||
- name: "langgraph-bigtool"
|
||||
repo: "langchain-ai/langgraph-bigtool"
|
||||
description: "Build LangGraph agents with large numbers of tools."
|
||||
- name: "ai-data-science-team"
|
||||
repo: "business-science/ai-data-science-team"
|
||||
description: "An AI-powered data science team of agents to help you perform common data science tasks 10X faster."
|
||||
- name: "langgraph-reflection"
|
||||
repo: "langchain-ai/langgraph-reflection"
|
||||
description: "LangGraph agent that runs a reflection step."
|
||||
- name: "langgraph-codeact"
|
||||
repo: "langchain-ai/langgraph-codeact"
|
||||
description: "LangGraph implementation of CodeAct agent that generates and executes code instead of tool calling."
|
||||
js:
|
||||
- name: "@langchain/mcp-adapters"
|
||||
repo: "langchain-ai/langchainjs"
|
||||
description: "Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents."
|
||||
- name: "@langchain/langgraph-supervisor"
|
||||
repo: "langchain-ai/langgraphjs"
|
||||
monorepo_path: "libs/langgraph-supervisor"
|
||||
description: "Build supervisor multi-agent systems with LangGraph"
|
||||
- name: "@langchain/langgraph-swarm"
|
||||
repo: "langchain-ai/langgraphjs"
|
||||
monorepo_path: "libs/langgraph-swarm"
|
||||
description: "Build multi-agent swarms with LangGraph"
|
||||
- name: "@langchain/langgraph-cua"
|
||||
repo: "langchain-ai/langgraphjs"
|
||||
monorepo_path: "libs/langgraph-cua"
|
||||
description: "Build computer use agents with LangGraph"
|
||||
|
||||
@@ -8,4 +8,4 @@ This section contains additional resources for LangGraph.
|
||||
- [FAQ](../concepts/faq.md): A collection of frequently asked questions about LangGraph.
|
||||
- [llms.txt](../llms-txt-overview.md): A list of documentation files in the `llms.txt` format that allow LLMs and agents to access our documentation.
|
||||
- [LangChain Forum](https://forum.langchain.com/): A place to ask questions and get help from other LangGraph users.
|
||||
- [Troubleshooting](../troubleshooting/errors/index.md.md): A collection of troubleshooting guides for common issues.
|
||||
- [Troubleshooting](../troubleshooting/errors/index.md): A collection of troubleshooting guides for common issues.
|
||||
+233
-9
@@ -15,23 +15,40 @@ This guide shows you how to set up and use LangGraph's **prebuilt**, **reusable*
|
||||
|
||||
Before you start this tutorial, ensure you have the following:
|
||||
|
||||
- An [Anthropic](https://console.anthropic.com/settings/keys) API key
|
||||
- An [Anthropic](https://console.anthropic.com/settings/keys) API key
|
||||
|
||||
## 1. Install dependencies
|
||||
|
||||
If you haven't already, install LangGraph and LangChain:
|
||||
|
||||
:::python
|
||||
|
||||
```
|
||||
pip install -U langgraph "langchain[anthropic]"
|
||||
```
|
||||
|
||||
!!! info
|
||||
!!! info
|
||||
|
||||
LangChain is installed so the agent can call the [model](https://python.langchain.com/docs/integrations/chat/).
|
||||
`langchain[anthropic]` is installed so the agent can call the [model](https://python.langchain.com/docs/integrations/chat/).
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```bash
|
||||
npm install @langchain/langgraph @langchain/core @langchain/anthropic
|
||||
```
|
||||
|
||||
!!! info
|
||||
|
||||
`@langchain/core` `@langchain/anthropic` are installed so the agent can call the [model](https://js.langchain.com/docs/integrations/chat/).
|
||||
|
||||
:::
|
||||
|
||||
## 2. Create an agent
|
||||
|
||||
To create an agent, use [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent]:
|
||||
:::python
|
||||
To create an agent, use @[`create_react_agent`][create_react_agent]:
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
@@ -56,9 +73,52 @@ agent.invoke(
|
||||
2. Provide a language model for the agent to use. To learn more about configuring language models for the agents, check the [models](./models.md) page.
|
||||
3. Provide a list of tools for the model to use.
|
||||
4. Provide a system prompt (instructions) to the language model used by the agent.
|
||||
:::
|
||||
|
||||
:::js
|
||||
To create an agent, use [`createReactAgent`](https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph_prebuilt.createReactAgent.html):
|
||||
|
||||
```typescript
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { z } from "zod";
|
||||
|
||||
const getWeather = tool(
|
||||
// (1)!
|
||||
async ({ city }) => {
|
||||
return `It's always sunny in ${city}!`;
|
||||
},
|
||||
{
|
||||
name: "get_weather",
|
||||
description: "Get weather for a given city.",
|
||||
schema: z.object({
|
||||
city: z.string().describe("The city to get weather for"),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
const agent = createReactAgent({
|
||||
llm: new ChatAnthropic({ model: "anthropic:claude-3-5-sonnet-latest" }), // (2)!
|
||||
tools: [getWeather], // (3)!
|
||||
stateModifier: "You are a helpful assistant", // (4)!
|
||||
});
|
||||
|
||||
// Run the agent
|
||||
await agent.invoke({
|
||||
messages: [{ role: "user", content: "what is the weather in sf" }],
|
||||
});
|
||||
```
|
||||
|
||||
1. Define a tool for the agent to use. Tools can be defined using the `tool` function. For more advanced tool usage and customization, check the [tools](./tools.md) page.
|
||||
2. Provide a language model for the agent to use. To learn more about configuring language models for the agents, check the [models](./models.md) page.
|
||||
3. Provide a list of tools for the model to use.
|
||||
4. Provide a system prompt (instructions) to the language model used by the agent.
|
||||
:::
|
||||
|
||||
## 3. Configure an LLM
|
||||
|
||||
:::python
|
||||
To configure an LLM with specific parameters, such as temperature, use [init_chat_model](https://python.langchain.com/api_reference/langchain/chat_models/langchain.chat_models.base.init_chat_model.html):
|
||||
|
||||
```python
|
||||
@@ -79,19 +139,45 @@ agent = create_react_agent(
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
To configure an LLM with specific parameters, such as temperature, use a model instance:
|
||||
|
||||
```typescript
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
|
||||
// highlight-next-line
|
||||
const model = new ChatAnthropic({
|
||||
model: "claude-3-5-sonnet-latest",
|
||||
// highlight-next-line
|
||||
temperature: 0,
|
||||
});
|
||||
|
||||
const agent = createReactAgent({
|
||||
// highlight-next-line
|
||||
llm: model,
|
||||
tools: [getWeather],
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
For more information on how to configure LLMs, see [Models](./models.md).
|
||||
|
||||
## 4. Add a custom prompt
|
||||
|
||||
Prompts instruct the LLM how to behave. Add one of the following types of prompts:
|
||||
|
||||
* **Static**: A string is interpreted as a **system message**.
|
||||
* **Dynamic**: A list of messages generated at **runtime**, based on input or configuration.
|
||||
- **Static**: A string is interpreted as a **system message**.
|
||||
- **Dynamic**: A list of messages generated at **runtime**, based on input or configuration.
|
||||
|
||||
=== "Static prompt"
|
||||
|
||||
Define a fixed prompt string or list of messages:
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
@@ -107,9 +193,30 @@ Prompts instruct the LLM how to behave. Add one of the following types of prompt
|
||||
{"messages": [{"role": "user", "content": "what is the weather in sf"}]}
|
||||
)
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
|
||||
const agent = createReactAgent({
|
||||
llm: new ChatAnthropic({ model: "anthropic:claude-3-5-sonnet-latest" }),
|
||||
tools: [getWeather],
|
||||
// A static prompt that never changes
|
||||
// highlight-next-line
|
||||
stateModifier: "Never answer questions about the weather."
|
||||
});
|
||||
|
||||
await agent.invoke({
|
||||
messages: [{ role: "user", content: "what is the weather in sf" }]
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
=== "Dynamic prompt"
|
||||
|
||||
:::python
|
||||
Define a function that returns a message list based on the agent's state and configuration:
|
||||
|
||||
```python
|
||||
@@ -144,12 +251,52 @@ Prompts instruct the LLM how to behave. Add one of the following types of prompt
|
||||
- Internal agent state updated during a multi-step reasoning process (using `state`).
|
||||
|
||||
Dynamic prompts can be defined as functions that take `state` and `config` and return a list of messages to send to the LLM.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Define a function that returns messages based on the agent's state and configuration:
|
||||
|
||||
```typescript
|
||||
import { type BaseMessageLike } from "@langchain/core/messages";
|
||||
import { type RunnableConfig } from "@langchain/core/runnables";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
|
||||
// highlight-next-line
|
||||
const dynamicPrompt = (state: { messages: BaseMessageLike[] }, config: RunnableConfig): BaseMessageLike[] => { // (1)!
|
||||
const userName = config.configurable?.user_name;
|
||||
const systemMsg = `You are a helpful assistant. Address the user as ${userName}.`;
|
||||
return [{ role: "system", content: systemMsg }, ...state.messages];
|
||||
};
|
||||
|
||||
const agent = createReactAgent({
|
||||
llm: "anthropic:claude-3-5-sonnet-latest",
|
||||
tools: [getWeather],
|
||||
// highlight-next-line
|
||||
stateModifier: dynamicPrompt
|
||||
});
|
||||
|
||||
await agent.invoke(
|
||||
{ messages: [{ role: "user", content: "what is the weather in sf" }] },
|
||||
// highlight-next-line
|
||||
{ configurable: { user_name: "John Smith" } }
|
||||
);
|
||||
```
|
||||
|
||||
1. Dynamic prompts allow including non-message [context](./context.md) when constructing an input to the LLM, such as:
|
||||
|
||||
- Information passed at runtime, like a `user_id` or API credentials (using `config`).
|
||||
- Internal agent state updated during a multi-step reasoning process (using `state`).
|
||||
|
||||
Dynamic prompts can be defined as functions that take `state` and `config` and return a list of messages to send to the LLM.
|
||||
:::
|
||||
|
||||
For more information, see [Context](./context.md).
|
||||
|
||||
## 5. Add memory
|
||||
|
||||
To allow multi-turn conversations with an agent, you need to enable [persistence](../concepts/persistence.md) by providing a `checkpointer` when creating an agent. At runtime, you need to provide a config containing `thread_id` — a unique identifier for the conversation (session):
|
||||
To allow multi-turn conversations with an agent, you need to enable [persistence](../concepts/persistence.md) by providing a checkpointer when creating an agent. At runtime, you need to provide a config containing `thread_id` — a unique identifier for the conversation (session):
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
@@ -182,8 +329,50 @@ ny_response = agent.invoke(
|
||||
|
||||
1. `checkpointer` allows the agent to store its state at every step in the tool calling loop. This enables [short-term memory](../how-tos/memory/add-memory.md#add-short-term-memory) and [human-in-the-loop](../concepts/human_in_the_loop.md) capabilities.
|
||||
2. Pass configuration with `thread_id` to be able to resume the same conversation on future agent invocations.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { MemorySaver } from "@langchain/langgraph";
|
||||
|
||||
// highlight-next-line
|
||||
const checkpointer = new MemorySaver();
|
||||
|
||||
const agent = createReactAgent({
|
||||
llm: "anthropic:claude-3-5-sonnet-latest",
|
||||
tools: [getWeather],
|
||||
// highlight-next-line
|
||||
checkpointSaver: checkpointer, // (1)!
|
||||
});
|
||||
|
||||
// Run the agent
|
||||
// highlight-next-line
|
||||
const config = { configurable: { thread_id: "1" } };
|
||||
const sfResponse = await agent.invoke(
|
||||
{ messages: [{ role: "user", content: "what is the weather in sf" }] },
|
||||
// highlight-next-line
|
||||
config // (2)!
|
||||
);
|
||||
const nyResponse = await agent.invoke(
|
||||
{ messages: [{ role: "user", content: "what about new york?" }] },
|
||||
// highlight-next-line
|
||||
config
|
||||
);
|
||||
```
|
||||
|
||||
1. `checkpointSaver` allows the agent to store its state at every step in the tool calling loop. This enables [short-term memory](../how-tos/memory/add-memory.md#add-short-term-memory) and [human-in-the-loop](../concepts/human_in_the_loop.md) capabilities.
|
||||
2. Pass configuration with `thread_id` to be able to resume the same conversation on future agent invocations.
|
||||
:::
|
||||
|
||||
:::python
|
||||
When you enable the checkpointer, it stores agent state at every step in the provided checkpointer database (or in memory, if using `InMemorySaver`).
|
||||
:::
|
||||
|
||||
:::js
|
||||
When you enable the checkpointer, it stores agent state at every step in the provided checkpointer database (or in memory, if using `MemorySaver`).
|
||||
:::
|
||||
|
||||
Note that in the above example, when the agent is invoked the second time with the same `thread_id`, the original message history from the first conversation is automatically included, together with the new user input.
|
||||
|
||||
@@ -191,6 +380,7 @@ For more information, see [Memory](../how-tos/memory/add-memory.md).
|
||||
|
||||
## 6. Configure structured output
|
||||
|
||||
:::python
|
||||
To produce structured responses conforming to a schema, use the `response_format` parameter. The schema can be defined with a `Pydantic` model or `TypedDict`. The result will be accessible via the `structured_response` field.
|
||||
|
||||
```python
|
||||
@@ -215,9 +405,43 @@ response = agent.invoke(
|
||||
response["structured_response"]
|
||||
```
|
||||
|
||||
1. When `response_format` is provided, a separate step is added at the end of the agent loop: agent message history is passed to an LLM with structured output to generate a structured response.
|
||||
1. When `response_format` is provided, a separate step is added at the end of the agent loop: agent message history is passed to an LLM with structured output to generate a structured response.
|
||||
|
||||
To provide a system prompt to this LLM, use a tuple `(prompt, schema)`, e.g., `response_format=(prompt, WeatherResponse)`.
|
||||
To provide a system prompt to this LLM, use a tuple `(prompt, schema)`, e.g., `response_format=(prompt, WeatherResponse)`.
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
To produce structured responses conforming to a schema, use the `responseFormat` parameter. The schema can be defined with a `Zod` schema. The result will be accessible via the `structuredResponse` field.
|
||||
|
||||
```typescript
|
||||
import { z } from "zod";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
|
||||
const WeatherResponse = z.object({
|
||||
conditions: z.string(),
|
||||
});
|
||||
|
||||
const agent = createReactAgent({
|
||||
llm: "anthropic:claude-3-5-sonnet-latest",
|
||||
tools: [getWeather],
|
||||
// highlight-next-line
|
||||
responseFormat: WeatherResponse, // (1)!
|
||||
});
|
||||
|
||||
const response = await agent.invoke({
|
||||
messages: [{ role: "user", content: "what is the weather in sf" }],
|
||||
});
|
||||
|
||||
// highlight-next-line
|
||||
response.structuredResponse;
|
||||
```
|
||||
|
||||
1. When `responseFormat` is provided, a separate step is added at the end of the agent loop: agent message history is passed to an LLM with structured output to generate a structured response.
|
||||
|
||||
To provide a system prompt to this LLM, use an object `{ prompt, schema }`, e.g., `responseFormat: { prompt, schema: WeatherResponse }`.
|
||||
|
||||
:::
|
||||
|
||||
!!! Note "LLM post-processing"
|
||||
|
||||
|
||||
+162
-38
@@ -1,67 +1,84 @@
|
||||
# Context
|
||||
|
||||
**Context engineering** is the practice of building dynamic systems that provide the right information and tools, in the right format, so that a language model can plausibly accomplish a task.
|
||||
**Context engineering** is the practice of building dynamic systems that provide the right information and tools, in the right format, so that an AI application can accomplish a task. Context can be characterized along two key dimensions:
|
||||
|
||||
Context includes *any* data outside the message list that can shape behavior. This can be:
|
||||
1. By **mutability**:
|
||||
- **Static context**: Immutable data that doesn't change during execution (e.g., user metadata, database connections, tools)
|
||||
- **Dynamic context**: Mutable data that evolves as the application runs (e.g., conversation history, intermediate results, tool call observations)
|
||||
2. By **lifetime**:
|
||||
- **Runtime context**: Data scoped to a single run or invocation
|
||||
- **Cross-conversation context**: Data that persists across multiple conversations or sessions
|
||||
|
||||
- Information passed at runtime, like a `user_id` or API credentials.
|
||||
- Internal state updated during a multi-step reasoning process.
|
||||
- Persistent memory or facts from previous interactions.
|
||||
!!! tip "Runtime context vs LLM context"
|
||||
|
||||
LangGraph provides **three** primary ways to supply context:
|
||||
Runtime context refers to local context: data and dependencies your code needs to run. It does **not** refer to:
|
||||
|
||||
| Type | Description | Mutable? | Lifetime |
|
||||
|------------------------------------------------------------------------------|-----------------------------------------------|----------|-------------------------|
|
||||
| [**Config**](#config-static-context) | data passed at the start of a run | ❌ | per run |
|
||||
| [**Short-term memory (State)**](#short-term-memory-mutable-context) | dynamic data that can change during execution | ✅ | per run or conversation |
|
||||
| [**Long-term memory (Store)**](#long-term-memory-cross-conversation-context) | data that can be shared between conversations | ✅ | across conversations |
|
||||
* The LLM context, which is the data passed into the LLM's prompt.
|
||||
* The "context window", which is the maximum number of tokens that can be passed to the LLM.
|
||||
|
||||
## Provide runtime context
|
||||
Runtime context can be used to optimize the LLM context. For example, you can use user metadata
|
||||
in the runtime context to fetch user preferences and feed them into the context window.
|
||||
|
||||
### Config (static context)
|
||||
LangGraph provides three ways to manage context, which combines the mutability and lifetime dimensions:
|
||||
|
||||
Config is for immutable data like user metadata or API keys. Use
|
||||
when you have values that don't change mid-run.
|
||||
:::python
|
||||
|
||||
Specify configuration using a key called **"configurable"** which is reserved
|
||||
for this purpose:
|
||||
| Context type | Description | Mutability | Lifetime | Access method |
|
||||
| ------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ---------- | ------------------ | --------------------------------------- |
|
||||
| [**Static runtime context**](#static-runtime-context) | User metadata, tools, db connections passed at startup | Static | Single run | `context` argument to `invoke`/`stream` |
|
||||
| [**Dynamic runtime context (state)**](#dynamic-runtime-context-state) | Mutable data that evolves during a single run | Dynamic | Single run | LangGraph state object |
|
||||
| [**Dynamic cross-conversation context (store)**](#dynamic-cross-conversation-context-store) | Persistent data shared across conversations | Dynamic | Cross-conversation | LangGraph store |
|
||||
|
||||
## Static runtime context
|
||||
|
||||
**Static runtime context** represents immutable data like user metadata, tools, and database connections that are passed to an application at the start of a run via the `context` argument to `invoke`/`stream`. This data does not change during execution.
|
||||
|
||||
!!! version-added "Added in version 0.6.0: `context` replaces `config['configurable']`"
|
||||
|
||||
Runtime context is now passed to the `context` argument of `invoke`/`stream`,
|
||||
which replaces the previous pattern of passing application configuration to `config['configurable']`.
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class ContextSchema:
|
||||
user_name: str
|
||||
|
||||
graph.invoke( # (1)!
|
||||
{"messages": [{"role": "user", "content": "hi!"}]}, # (2)!
|
||||
# highlight-next-line
|
||||
config={"configurable": {"user_id": "user_123"}} # (3)!
|
||||
context={"user_name": "John Smith"} # (3)!
|
||||
)
|
||||
```
|
||||
|
||||
1. This is the invocation of the agent or graph. The `invoke` method runs the underlying graph with the provided input.
|
||||
2. This example uses messages as an input, which is common, but your application may use different input structures.
|
||||
3. This is where you pass the configuration data. The `config` parameter allows you to provide additional context that the agent can use during its execution.
|
||||
3. This is where you pass the runtime data. The `context` parameter allows you to provide additional dependencies that the agent can use during its execution.
|
||||
|
||||
=== "Agent prompt"
|
||||
|
||||
```python
|
||||
from langchain_core.messages import AnyMessage
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.runtime import get_runtime
|
||||
from langgraph.prebuilt.chat_agent_executor import AgentState
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
# highlight-next-line
|
||||
def prompt(state: AgentState, config: RunnableConfig) -> list[AnyMessage]:
|
||||
user_name = config["configurable"].get("user_name")
|
||||
system_msg = f"You are a helpful assistant. Address the user as {user_name}."
|
||||
def prompt(state: AgentState) -> list[AnyMessage]:
|
||||
runtime = get_runtime(ContextSchema)
|
||||
system_msg = f"You are a helpful assistant. Address the user as {runtime.context.user_name}."
|
||||
return [{"role": "system", "content": system_msg}] + state["messages"]
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_weather],
|
||||
prompt=prompt
|
||||
prompt=prompt,
|
||||
context_schema=ContextSchema
|
||||
)
|
||||
|
||||
agent.invoke(
|
||||
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
|
||||
# highlight-next-line
|
||||
config={"configurable": {"user_name": "John Smith"}}
|
||||
context={"user_name": "John Smith"}
|
||||
)
|
||||
```
|
||||
|
||||
@@ -70,11 +87,11 @@ graph.invoke( # (1)!
|
||||
=== "Workflow node"
|
||||
|
||||
```python
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.runtime import Runtime
|
||||
|
||||
# highlight-next-line
|
||||
def node(state: State, config: RunnableConfig):
|
||||
user_name = config["configurable"].get("user_name")
|
||||
def node(state: State, runtime: Runtime[ContextSchema]):
|
||||
user_name = runtime.context.user_name
|
||||
...
|
||||
```
|
||||
|
||||
@@ -83,21 +100,55 @@ graph.invoke( # (1)!
|
||||
=== "In a tool"
|
||||
|
||||
```python
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.runtime import get_runtime
|
||||
|
||||
@tool
|
||||
# highlight-next-line
|
||||
def get_user_info(config: RunnableConfig) -> str:
|
||||
def get_user_email() -> str:
|
||||
"""Retrieve user information based on user ID."""
|
||||
user_id = config["configurable"].get("user_id")
|
||||
return "User is John Smith" if user_id == "user_123" else "Unknown user"
|
||||
# simulate fetching user info from a database
|
||||
runtime = get_runtime(ContextSchema)
|
||||
email = get_user_email_from_db(runtime.context.user_name)
|
||||
return email
|
||||
```
|
||||
|
||||
See the [tool calling guide](../how-tos/tool-calling.md#configuration) for details.
|
||||
|
||||
### Short-term memory (mutable context)
|
||||
!!! tip
|
||||
|
||||
State acts as [short-term memory](../concepts/memory.md) during a run. It holds dynamic data that can evolve during execution, such as values derived from tools or LLM outputs.
|
||||
The `Runtime` object can be used to access static context and other utilities like the active store and stream writer.
|
||||
See the [Runtime][langgraph.runtime.Runtime] documentation for details.
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
| Context type | Description | Mutability | Lifetime |
|
||||
| ------------------------------------------------------------------------------------------- | --------------------------------------------- | ---------- | ------------------ |
|
||||
| [**Config**](#config-static-context) | data passed at the start of a run | Static | Single run |
|
||||
| [**Dynamic runtime context (state)**](#dynamic-runtime-context-state) | Mutable data that evolves during a single run | Dynamic | Single run |
|
||||
| [**Dynamic cross-conversation context (store)**](#dynamic-cross-conversation-context-store) | Persistent data shared across conversations | Dynamic | Cross-conversation |
|
||||
|
||||
## Config (static context)
|
||||
|
||||
Config is for immutable data like user metadata or API keys. Use this when you have values that don't change mid-run.
|
||||
|
||||
Specify configuration using a key called **"configurable"** which is reserved for this purpose.
|
||||
|
||||
```typescript
|
||||
await graph.invoke(
|
||||
// (1)!
|
||||
{ messages: [{ role: "user", content: "hi!" }] }, // (2)!
|
||||
// highlight-next-line
|
||||
{ configurable: { user_id: "user_123" } } // (3)!
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Dynamic runtime context (state)
|
||||
|
||||
**Dynamic runtime context** represents mutable data that can evolve during a single run and is managed through the LangGraph state object. This includes conversation history, intermediate results, and values derived from tools or LLM outputs. In LangGraph, the state object acts as [short-term memory](../concepts/memory.md) during a run.
|
||||
|
||||
=== "In an agent"
|
||||
|
||||
@@ -105,6 +156,7 @@ State acts as [short-term memory](../concepts/memory.md) during a run. It holds
|
||||
|
||||
State can also be accessed by the agent's **tools**, which can read or update the state as needed. See [tool calling guide](../how-tos/tool-calling.md#short-term-memory) for details.
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langchain_core.messages import AnyMessage
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
@@ -139,10 +191,51 @@ State acts as [short-term memory](../concepts/memory.md) during a run. It holds
|
||||
|
||||
1. Define a custom state schema that extends `AgentState` or `MessagesState`.
|
||||
2. Pass the custom state schema to the agent. This allows the agent to access and modify the state during execution.
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import type { BaseMessage } from "@langchain/core/messages";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { MessagesZodState } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
// highlight-next-line
|
||||
const CustomState = z.object({ // (1)!
|
||||
messages: MessagesZodState.shape.messages,
|
||||
userName: z.string(),
|
||||
});
|
||||
|
||||
const prompt = (
|
||||
// highlight-next-line
|
||||
state: z.infer<typeof CustomState>
|
||||
): BaseMessage[] => {
|
||||
const userName = state.userName;
|
||||
const systemMsg = `You are a helpful assistant. User's name is ${userName}`;
|
||||
return [{ role: "system", content: systemMsg }, ...state.messages];
|
||||
};
|
||||
|
||||
const agent = createReactAgent({
|
||||
llm: model,
|
||||
tools: [...],
|
||||
// highlight-next-line
|
||||
stateSchema: CustomState, // (2)!
|
||||
stateModifier: prompt,
|
||||
});
|
||||
|
||||
await agent.invoke({
|
||||
messages: [{ role: "user", content: "hi!" }],
|
||||
userName: "John Smith",
|
||||
});
|
||||
```
|
||||
|
||||
1. Define a custom state schema that extends `MessagesZodState` or creates a new schema.
|
||||
2. Pass the custom state schema to the agent. This allows the agent to access and modify the state during execution.
|
||||
:::
|
||||
|
||||
=== "In a workflow"
|
||||
|
||||
:::python
|
||||
```python
|
||||
from typing_extensions import TypedDict
|
||||
from langchain_core.messages import AnyMessage
|
||||
@@ -167,18 +260,49 @@ State acts as [short-term memory](../concepts/memory.md) during a run. It holds
|
||||
builder.set_entry_point("node")
|
||||
graph = builder.compile()
|
||||
```
|
||||
|
||||
|
||||
1. Define a custom state
|
||||
2. Access the state in any node or tool
|
||||
3. The Graph API is designed to work as easily as possible with state. The return value of a node represents a requested update to the state.
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import type { BaseMessage } from "@langchain/core/messages";
|
||||
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
// highlight-next-line
|
||||
const CustomState = z.object({ // (1)!
|
||||
messages: MessagesZodState.shape.messages,
|
||||
extraField: z.number(),
|
||||
});
|
||||
|
||||
const builder = new StateGraph(CustomState)
|
||||
.addNode("node", async (state) => { // (2)!
|
||||
const messages = state.messages;
|
||||
// ...
|
||||
return { // (3)!
|
||||
// highlight-next-line
|
||||
extraField: state.extraField + 1,
|
||||
};
|
||||
})
|
||||
.addEdge(START, "node");
|
||||
|
||||
const graph = builder.compile();
|
||||
```
|
||||
|
||||
1. Define a custom state
|
||||
2. Access the state in any node or tool
|
||||
3. The Graph API is designed to work as easily as possible with state. The return value of a node represents a requested update to the state.
|
||||
:::
|
||||
|
||||
!!! tip "Turning on memory"
|
||||
|
||||
Please see the [memory guide](../how-tos/memory/add-memory.md) for more details on how to enable memory. This is a powerful feature that allows you to persist the agent's state across multiple invocations. Otherwise, the state is scoped only to a single run.
|
||||
|
||||
### Long-term memory (cross-conversation context)
|
||||
## Dynamic cross-conversation context (store)
|
||||
|
||||
For context that spans *across* conversations or sessions, LangGraph allows access to **long-term memory** via a `store`. This can be used to read or update persistent facts (e.g., user profiles, preferences, prior interactions).
|
||||
**Dynamic cross-conversation context** represents persistent, mutable data that spans across multiple conversations or sessions and is managed through the LangGraph store. This includes user profiles, preferences, and historical interactions. The LangGraph store acts as [long-term memory](../concepts/memory.md#long-term-memory) across multiple runs. This can be used to read or update persistent facts (e.g., user profiles, preferences, prior interactions).
|
||||
|
||||
For more information, see the [Memory guide](../how-tos/memory/add-memory.md).
|
||||
For more information, see the [Memory guide](../how-tos/memory/add-memory.md).
|
||||
|
||||
+140
-3
@@ -11,25 +11,62 @@ hide:
|
||||
|
||||
To evaluate your agent's performance you can use `LangSmith` [evaluations](https://docs.smith.langchain.com/evaluation). You would need to first define an evaluator function to judge the results from an agent, such as final outputs or trajectory. Depending on your evaluation technique, this may or may not involve a reference output:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
def evaluator(*, outputs: dict, reference_outputs: dict):
|
||||
# compare agent outputs against reference outputs
|
||||
output_messages = outputs["messages"]
|
||||
reference_messages = reference["messages"]
|
||||
reference_messages = reference_outputs["messages"]
|
||||
score = compare_messages(output_messages, reference_messages)
|
||||
return {"key": "evaluator_score", "score": score}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
type EvaluatorParams = {
|
||||
outputs: Record<string, any>;
|
||||
referenceOutputs: Record<string, any>;
|
||||
};
|
||||
|
||||
function evaluator({ outputs, referenceOutputs }: EvaluatorParams) {
|
||||
// compare agent outputs against reference outputs
|
||||
const outputMessages = outputs.messages;
|
||||
const referenceMessages = referenceOutputs.messages;
|
||||
const score = compareMessages(outputMessages, referenceMessages);
|
||||
return { key: "evaluator_score", score: score };
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
To get started, you can use prebuilt evaluators from `AgentEvals` package:
|
||||
|
||||
:::python
|
||||
|
||||
```bash
|
||||
pip install -U agentevals
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```bash
|
||||
npm install agentevals
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Create evaluator
|
||||
|
||||
A common way to evaluate agent performance is by comparing its trajectory (the order in which it calls its tools) against a reference trajectory:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
import json
|
||||
# highlight-next-line
|
||||
@@ -80,8 +117,63 @@ result = evaluator(
|
||||
)
|
||||
```
|
||||
|
||||
1. Specify how the trajectories will be compared. `superset` will accept output trajectory as valid if it's a superset of the reference one. Other options include: [strict](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#strict-match), [unordered](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#unordered-match) and [subset](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#subset-and-superset-match)
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { createTrajectoryMatchEvaluator } from "agentevals/trajectory/match";
|
||||
|
||||
const outputs = [
|
||||
{
|
||||
role: "assistant",
|
||||
tool_calls: [
|
||||
{
|
||||
function: {
|
||||
name: "get_weather",
|
||||
arguments: JSON.stringify({ city: "san francisco" }),
|
||||
},
|
||||
},
|
||||
{
|
||||
function: {
|
||||
name: "get_directions",
|
||||
arguments: JSON.stringify({ destination: "presidio" }),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const referenceOutputs = [
|
||||
{
|
||||
role: "assistant",
|
||||
tool_calls: [
|
||||
{
|
||||
function: {
|
||||
name: "get_weather",
|
||||
arguments: JSON.stringify({ city: "san francisco" }),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Create the evaluator
|
||||
const evaluator = createTrajectoryMatchEvaluator({
|
||||
// Specify how the trajectories will be compared. `superset` will accept output trajectory as valid if it's a superset of the reference one. Other options include: strict, unordered and subset
|
||||
trajectoryMatchMode: "superset", // (1)!
|
||||
});
|
||||
|
||||
// Run the evaluator
|
||||
const result = evaluator({
|
||||
outputs: outputs,
|
||||
referenceOutputs: referenceOutputs,
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
1. Specify how the trajectories will be compared. `superset` will accept output trajectory as valid if it's a superset of the reference one. Other options include: [strict](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#strict-match), [unordered](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#unordered-match) and [subset](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#subset-and-superset-match)
|
||||
|
||||
As a next step, learn more about how to [customize trajectory match evaluator](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#agent-trajectory-match).
|
||||
|
||||
@@ -89,6 +181,8 @@ As a next step, learn more about how to [customize trajectory match evaluator](h
|
||||
|
||||
You can use LLM-as-a-judge evaluator that uses an LLM to compare the trajectory against the reference outputs and output a score:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
import json
|
||||
from agentevals.trajectory.llm import (
|
||||
@@ -103,6 +197,24 @@ evaluator = create_trajectory_llm_as_judge(
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import {
|
||||
createTrajectoryLlmAsJudge,
|
||||
TRAJECTORY_ACCURACY_PROMPT_WITH_REFERENCE,
|
||||
} from "agentevals/trajectory/llm";
|
||||
|
||||
const evaluator = createTrajectoryLlmAsJudge({
|
||||
prompt: TRAJECTORY_ACCURACY_PROMPT_WITH_REFERENCE,
|
||||
model: "openai:o3-mini",
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Run evaluator
|
||||
|
||||
To run an evaluator, you will first need to create a [LangSmith dataset](https://docs.smith.langchain.com/evaluation/concepts#datasets). To use the prebuilt AgentEvals evaluators, you will need a dataset with the following schema:
|
||||
@@ -110,6 +222,8 @@ To run an evaluator, you will first need to create a [LangSmith dataset](https:/
|
||||
- **input**: `{"messages": [...]}` input messages to call the agent with.
|
||||
- **output**: `{"messages": [...]}` expected message history in the agent output. For trajectory evaluation, you can choose to keep only assistant messages.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langsmith import Client
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
@@ -125,4 +239,27 @@ experiment_results = client.evaluate(
|
||||
data="<Name of your dataset>",
|
||||
evaluators=[evaluator]
|
||||
)
|
||||
```
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { Client } from "langsmith";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { createTrajectoryMatchEvaluator } from "agentevals/trajectory/match";
|
||||
|
||||
const client = new Client();
|
||||
const agent = createReactAgent({...});
|
||||
const evaluator = createTrajectoryMatchEvaluator({...});
|
||||
|
||||
const experimentResults = await client.evaluate(
|
||||
(inputs) => agent.invoke(inputs),
|
||||
// replace with your dataset name
|
||||
{ data: "<Name of your dataset>" },
|
||||
{ evaluators: [evaluator] }
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
+381
-15
@@ -9,10 +9,31 @@ hide:
|
||||
|
||||
# Use MCP
|
||||
|
||||
The Model Context Protocol (MCP) is an open protocol that standardizes how applications provide tools and context to language models. LangGraph agents can use tools defined on MCP servers through the `langchain-mcp-adapters` library.
|
||||
[Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol that standardizes how applications provide tools and context to language models. LangGraph agents can use tools defined on MCP servers through the `langchain-mcp-adapters` library.
|
||||
|
||||

|
||||
|
||||
:::python
|
||||
Install the `langchain-mcp-adapters` library to use MCP tools in LangGraph:
|
||||
|
||||
```bash
|
||||
pip install langchain-mcp-adapters
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Install the `@langchain/mcp-adapters` library to use MCP tools in LangGraph:
|
||||
|
||||
```bash
|
||||
npm install langchain-mcp-adapters
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Use MCP tools
|
||||
|
||||
:::python
|
||||
The `langchain-mcp-adapters` package enables agents to use tools defined across one or more MCP servers.
|
||||
|
||||
=== "In an agent"
|
||||
@@ -55,14 +76,16 @@ The `langchain-mcp-adapters` package enables agents to use tools defined across
|
||||
|
||||
=== "In a workflow"
|
||||
|
||||
```python
|
||||
```python title="Workflow using MCP tools with ToolNode"
|
||||
from langchain_mcp_adapters.client import MultiServerMCPClient
|
||||
from langgraph.graph import StateGraph, MessagesState, START
|
||||
from langgraph.prebuilt import ToolNode, tools_condition
|
||||
|
||||
from langchain.chat_models import init_chat_model
|
||||
model = init_chat_model("openai:gpt-4.1")
|
||||
from langgraph.graph import StateGraph, MessagesState, START, END
|
||||
from langgraph.prebuilt import ToolNode
|
||||
|
||||
# Initialize the model
|
||||
model = init_chat_model("anthropic:claude-3-5-sonnet-latest")
|
||||
|
||||
# Set up MCP client
|
||||
client = MultiServerMCPClient(
|
||||
{
|
||||
"math": {
|
||||
@@ -80,28 +103,154 @@ The `langchain-mcp-adapters` package enables agents to use tools defined across
|
||||
)
|
||||
tools = await client.get_tools()
|
||||
|
||||
def call_model(state: MessagesState):
|
||||
response = model.bind_tools(tools).invoke(state["messages"])
|
||||
return {"messages": response}
|
||||
# Bind tools to model
|
||||
model_with_tools = model.bind_tools(tools)
|
||||
|
||||
# Create ToolNode
|
||||
tool_node = ToolNode(tools)
|
||||
|
||||
def should_continue(state: MessagesState):
|
||||
messages = state["messages"]
|
||||
last_message = messages[-1]
|
||||
if last_message.tool_calls:
|
||||
return "tools"
|
||||
return END
|
||||
|
||||
# Define call_model function
|
||||
async def call_model(state: MessagesState):
|
||||
messages = state["messages"]
|
||||
response = await model_with_tools.ainvoke(messages)
|
||||
return {"messages": [response]}
|
||||
|
||||
# Build the graph
|
||||
builder = StateGraph(MessagesState)
|
||||
builder.add_node(call_model)
|
||||
builder.add_node(ToolNode(tools))
|
||||
builder.add_node("call_model", call_model)
|
||||
builder.add_node("tools", tool_node)
|
||||
|
||||
builder.add_edge(START, "call_model")
|
||||
builder.add_conditional_edges(
|
||||
"call_model",
|
||||
tools_condition,
|
||||
should_continue,
|
||||
)
|
||||
builder.add_edge("tools", "call_model")
|
||||
|
||||
# Compile the graph
|
||||
graph = builder.compile()
|
||||
math_response = await graph.ainvoke({"messages": "what's (3 + 5) x 12?"})
|
||||
weather_response = await graph.ainvoke({"messages": "what is the weather in nyc?"})
|
||||
|
||||
# Test the graph
|
||||
math_response = await graph.ainvoke(
|
||||
{"messages": [{"role": "user", "content": "what's (3 + 5) x 12?"}]}
|
||||
)
|
||||
weather_response = await graph.ainvoke(
|
||||
{"messages": [{"role": "user", "content": "what is the weather in nyc?"}]}
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
The `@langchain/mcp-adapters` package enables agents to use tools defined across one or more MCP servers.
|
||||
|
||||
=== "In an agent"
|
||||
|
||||
```typescript title="Agent using tools defined on MCP servers"
|
||||
// highlight-next-line
|
||||
import { MultiServerMCPClient } from "langchain-mcp-adapters/client";
|
||||
import { ChatAnthropic } from "@langchain/langgraph/prebuilt";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
|
||||
// highlight-next-line
|
||||
const client = new MultiServerMCPClient({
|
||||
math: {
|
||||
command: "node",
|
||||
// Replace with absolute path to your math_server.js file
|
||||
args: ["/path/to/math_server.js"],
|
||||
transport: "stdio",
|
||||
},
|
||||
weather: {
|
||||
// Ensure you start your weather server on port 8000
|
||||
url: "http://localhost:8000/mcp",
|
||||
transport: "streamable_http",
|
||||
},
|
||||
});
|
||||
|
||||
// highlight-next-line
|
||||
const tools = await client.getTools();
|
||||
const agent = createReactAgent({
|
||||
llm: new ChatAnthropic({ model: "claude-3-7-sonnet-latest" }),
|
||||
// highlight-next-line
|
||||
tools,
|
||||
});
|
||||
|
||||
const mathResponse = await agent.invoke({
|
||||
messages: [{ role: "user", content: "what's (3 + 5) x 12?" }],
|
||||
});
|
||||
|
||||
const weatherResponse = await agent.invoke({
|
||||
messages: [{ role: "user", content: "what is the weather in nyc?" }],
|
||||
});
|
||||
```
|
||||
|
||||
=== "In a workflow"
|
||||
|
||||
```typescript
|
||||
import { MultiServerMCPClient } from "langchain-mcp-adapters/client";
|
||||
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
|
||||
import { ToolNode } from "@langchain/langgraph/prebuilt";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { AIMessage } from "@langchain/core/messages";
|
||||
import { z } from "zod";
|
||||
|
||||
const model = new ChatOpenAI({ model: "gpt-4" });
|
||||
|
||||
const client = new MultiServerMCPClient({
|
||||
math: {
|
||||
command: "node",
|
||||
// Make sure to update to the full absolute path to your math_server.js file
|
||||
args: ["./examples/math_server.js"],
|
||||
transport: "stdio",
|
||||
},
|
||||
weather: {
|
||||
// make sure you start your weather server on port 8000
|
||||
url: "http://localhost:8000/mcp/",
|
||||
transport: "streamable_http",
|
||||
},
|
||||
});
|
||||
|
||||
const tools = await client.getTools();
|
||||
|
||||
const builder = new StateGraph(MessagesZodState)
|
||||
.addNode("callModel", async (state) => {
|
||||
const response = await model.bindTools(tools).invoke(state.messages);
|
||||
return { messages: [response] };
|
||||
})
|
||||
.addNode("tools", new ToolNode(tools))
|
||||
.addEdge(START, "callModel")
|
||||
.addConditionalEdges("callModel", (state) => {
|
||||
const lastMessage = state.messages.at(-1) as AIMessage | undefined;
|
||||
if (!lastMessage?.tool_calls?.length) {
|
||||
return "__end__";
|
||||
}
|
||||
return "tools";
|
||||
})
|
||||
.addEdge("tools", "callModel");
|
||||
|
||||
const graph = builder.compile();
|
||||
|
||||
const mathResponse = await graph.invoke({
|
||||
messages: [{ role: "user", content: "what's (3 + 5) x 12?" }],
|
||||
});
|
||||
|
||||
const weatherResponse = await graph.invoke({
|
||||
messages: [{ role: "user", content: "what is the weather in nyc?" }],
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Custom MCP servers
|
||||
|
||||
:::python
|
||||
To create your own MCP servers, you can use the `mcp` library. This library provides a simple way to define tools and run them as servers.
|
||||
|
||||
Install the MCP library:
|
||||
@@ -109,8 +258,24 @@ Install the MCP library:
|
||||
```bash
|
||||
pip install mcp
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
To create your own MCP servers, you can use the `@modelcontextprotocol/sdk` library. This library provides a simple way to define tools and run them as servers.
|
||||
|
||||
Install the MCP SDK:
|
||||
|
||||
```bash
|
||||
npm install @modelcontextprotocol/sdk
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Use the following reference implementations to test your agent with MCP tool servers.
|
||||
|
||||
:::python
|
||||
|
||||
```python title="Example Math Server (stdio transport)"
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
@@ -130,6 +295,115 @@ if __name__ == "__main__":
|
||||
mcp.run(transport="stdio")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript title="Example Math Server (stdio transport)"
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
|
||||
const server = new Server(
|
||||
{
|
||||
name: "math-server",
|
||||
version: "0.1.0",
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
tools: {},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
name: "add",
|
||||
description: "Add two numbers",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
a: {
|
||||
type: "number",
|
||||
description: "First number",
|
||||
},
|
||||
b: {
|
||||
type: "number",
|
||||
description: "Second number",
|
||||
},
|
||||
},
|
||||
required: ["a", "b"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiply",
|
||||
description: "Multiply two numbers",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
a: {
|
||||
type: "number",
|
||||
description: "First number",
|
||||
},
|
||||
b: {
|
||||
type: "number",
|
||||
description: "Second number",
|
||||
},
|
||||
},
|
||||
required: ["a", "b"],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
switch (request.params.name) {
|
||||
case "add": {
|
||||
const { a, b } = request.params.arguments as { a: number; b: number };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: String(a + b),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
case "multiply": {
|
||||
const { a, b } = request.params.arguments as { a: number; b: number };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: String(a * b),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${request.params.name}`);
|
||||
}
|
||||
});
|
||||
|
||||
async function main() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error("Math MCP server running on stdio");
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
```python title="Example Weather Server (Streamable HTTP transport)"
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
@@ -144,8 +418,100 @@ if __name__ == "__main__":
|
||||
mcp.run(transport="streamable-http")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript title="Example Weather Server (HTTP transport)"
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import express from "express";
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
const server = new Server(
|
||||
{
|
||||
name: "weather-server",
|
||||
version: "0.1.0",
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
tools: {},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
name: "get_weather",
|
||||
description: "Get weather for location",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
location: {
|
||||
type: "string",
|
||||
description: "Location to get weather for",
|
||||
},
|
||||
},
|
||||
required: ["location"],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
switch (request.params.name) {
|
||||
case "get_weather": {
|
||||
const { location } = request.params.arguments as { location: string };
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `It's always sunny in ${location}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${request.params.name}`);
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/mcp", async (req, res) => {
|
||||
const transport = new SSEServerTransport("/mcp", res);
|
||||
await server.connect(transport);
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 8000;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Weather MCP server running on port ${PORT}`);
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
## Additional resources
|
||||
|
||||
- [MCP documentation](https://modelcontextprotocol.io/introduction)
|
||||
- [MCP Transport documentation](https://modelcontextprotocol.io/docs/concepts/transports)
|
||||
- [langchain_mcp_adapters](https://github.com/langchain-ai/langchain-mcp-adapters)
|
||||
- [langchain_mcp_adapters](https://github.com/langchain-ai/langchain-mcp-adapters)
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
## Additional resources
|
||||
|
||||
- [MCP documentation](https://modelcontextprotocol.io/introduction)
|
||||
- [MCP Transport documentation](https://modelcontextprotocol.io/docs/concepts/transports)
|
||||
- [`@langchain/mcp-adapters`](https://npmjs.com/package/@langchain/mcp-adapters)
|
||||
:::
|
||||
|
||||
+216
-7
@@ -2,18 +2,70 @@
|
||||
|
||||
LangGraph provides built-in support for [LLMs (language models)](https://python.langchain.com/docs/concepts/chat_models/) via the LangChain library. This makes it easy to integrate various LLMs into your agents and workflows.
|
||||
|
||||
|
||||
## Initialize a model
|
||||
|
||||
:::python
|
||||
Use [`init_chat_model`](https://python.langchain.com/docs/how_to/chat_models_universal_init/) to initialize models:
|
||||
|
||||
{!snippets/chat_model_tabs.md!}
|
||||
{% include-markdown "../../snippets/chat_model_tabs.md" %}
|
||||
:::
|
||||
|
||||
:::js
|
||||
Use model provider classes to initialize models:
|
||||
|
||||
=== "OpenAI"
|
||||
|
||||
```typescript
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
|
||||
const model = new ChatOpenAI({
|
||||
model: "gpt-4o",
|
||||
temperature: 0,
|
||||
});
|
||||
```
|
||||
|
||||
=== "Anthropic"
|
||||
|
||||
```typescript
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
|
||||
const model = new ChatAnthropic({
|
||||
model: "claude-3-5-sonnet-20240620",
|
||||
temperature: 0,
|
||||
maxTokens: 2048,
|
||||
});
|
||||
```
|
||||
|
||||
=== "Google"
|
||||
|
||||
```typescript
|
||||
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
|
||||
|
||||
const model = new ChatGoogleGenerativeAI({
|
||||
model: "gemini-1.5-pro",
|
||||
temperature: 0,
|
||||
});
|
||||
```
|
||||
|
||||
=== "Groq"
|
||||
|
||||
```typescript
|
||||
import { ChatGroq } from "@langchain/groq";
|
||||
|
||||
const model = new ChatGroq({
|
||||
model: "llama-3.1-70b-versatile",
|
||||
temperature: 0,
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
### Instantiate a model directly
|
||||
|
||||
If a model provider is not available via `init_chat_model`, you can instantiate the provider's model class directly. The model must implement the [BaseChatModel interface](https://python.langchain.com/api_reference/core/language_models/langchain_core.language_models.chat_models.BaseChatModel.html) and support tool calling:
|
||||
|
||||
|
||||
```python
|
||||
# Anthropic is already supported by `init_chat_model`,
|
||||
# but you can also instantiate it directly.
|
||||
@@ -26,19 +78,20 @@ model = ChatAnthropic(
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! important "Tool calling support"
|
||||
|
||||
If you are building an agent or workflow that requires the model to call external tools, ensure that the underlying
|
||||
language model supports [tool calling](../concepts/tools.md). Compatible models can be found in the [LangChain integrations directory](https://python.langchain.com/docs/integrations/chat/).
|
||||
|
||||
|
||||
## Use in an agent
|
||||
|
||||
:::python
|
||||
When using `create_react_agent` you can specify the model by its name string, which is a shorthand for initializing the model using `init_chat_model`. This allows you to use the model without needing to import or instantiate it directly.
|
||||
|
||||
=== "model name"
|
||||
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
@@ -70,10 +123,103 @@ When using `create_react_agent` you can specify the model by its name string, wh
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
When using `createReactAgent` you can pass the model instance directly:
|
||||
|
||||
```typescript
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
|
||||
const model = new ChatOpenAI({
|
||||
model: "gpt-4o",
|
||||
temperature: 0,
|
||||
});
|
||||
|
||||
const agent = createReactAgent({
|
||||
llm: model,
|
||||
tools: tools,
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
### Dynamic model selection
|
||||
|
||||
Pass a callable function to `create_react_agent` to dynamically select the model at runtime. This is useful for scenarios where you want to choose a model based on user input, configuration settings, or other runtime conditions.
|
||||
|
||||
The selector function must return a chat model. If you're using tools, you must bind the tools to the model within the selector function.
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
from langchain.chat_models import init_chat_model
|
||||
from langchain_core.language_models import BaseChatModel
|
||||
from langchain_core.tools import tool
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from langgraph.prebuilt.chat_agent_executor import AgentState
|
||||
from langgraph.runtime import Runtime
|
||||
|
||||
@tool
|
||||
def weather() -> str:
|
||||
"""Returns the current weather conditions."""
|
||||
return "It's nice and sunny."
|
||||
|
||||
|
||||
# Define the runtime context
|
||||
@dataclass
|
||||
class CustomContext:
|
||||
provider: Literal["anthropic", "openai"]
|
||||
|
||||
# Initialize models
|
||||
openai_model = init_chat_model("openai:gpt-4o")
|
||||
anthropic_model = init_chat_model("anthropic:claude-sonnet-4-20250514")
|
||||
|
||||
|
||||
# Selector function for model choice
|
||||
def select_model(state: AgentState, runtime: Runtime[CustomContext]) -> BaseChatModel:
|
||||
if runtime.context.provider == "anthropic":
|
||||
model = anthropic_model
|
||||
elif runtime.context.provider == "openai":
|
||||
model = openai_model
|
||||
else:
|
||||
raise ValueError(f"Unsupported provider: {runtime.context.provider}")
|
||||
|
||||
# With dynamic model selection, you must bind tools explicitly
|
||||
return model.bind_tools([weather])
|
||||
|
||||
|
||||
# Create agent with dynamic model selection
|
||||
agent = create_react_agent(select_model, tools=[weather])
|
||||
|
||||
# Invoke with context to select model
|
||||
output = agent.invoke(
|
||||
{
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Which model is handling this?",
|
||||
}
|
||||
]
|
||||
},
|
||||
context=CustomContext(provider="openai"),
|
||||
)
|
||||
|
||||
print(output["messages"][-1].text())
|
||||
```
|
||||
|
||||
!!! version-added "Added in version 0.6.0"
|
||||
|
||||
:::
|
||||
|
||||
## Advanced model configuration
|
||||
|
||||
### Disable streaming
|
||||
|
||||
:::python
|
||||
To disable streaming of the individual LLM tokens, set `disable_streaming=True` when initializing the model:
|
||||
|
||||
=== "`init_chat_model`"
|
||||
@@ -101,9 +247,25 @@ To disable streaming of the individual LLM tokens, set `disable_streaming=True`
|
||||
```
|
||||
|
||||
Refer to the [API reference](https://python.langchain.com/api_reference/core/language_models/langchain_core.language_models.chat_models.BaseChatModel.html#langchain_core.language_models.chat_models.BaseChatModel.disable_streaming) for more information on `disable_streaming`
|
||||
:::
|
||||
|
||||
:::js
|
||||
To disable streaming of the individual LLM tokens, set `streaming: false` when initializing the model:
|
||||
|
||||
```typescript
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
|
||||
const model = new ChatOpenAI({
|
||||
model: "gpt-4o",
|
||||
streaming: false,
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Add model fallbacks
|
||||
|
||||
:::python
|
||||
You can add a fallback to a different model or a different LLM provider using `model.with_fallbacks([...])`:
|
||||
|
||||
=== "`init_chat_model`"
|
||||
@@ -136,6 +298,28 @@ You can add a fallback to a different model or a different LLM provider using `m
|
||||
```
|
||||
|
||||
See this [guide](https://python.langchain.com/docs/how_to/fallbacks/#fallback-to-better-model) for more information on model fallbacks.
|
||||
:::
|
||||
|
||||
:::js
|
||||
You can add a fallback to a different model or a different LLM provider using `model.withFallbacks([...])`:
|
||||
|
||||
```typescript
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
|
||||
const modelWithFallbacks = new ChatOpenAI({
|
||||
model: "gpt-4o",
|
||||
}).withFallbacks([
|
||||
new ChatAnthropic({
|
||||
model: "claude-3-5-sonnet-20240620",
|
||||
}),
|
||||
]);
|
||||
```
|
||||
|
||||
See this [guide](https://js.langchain.com/docs/how_to/fallbacks/#fallback-to-better-model) for more information on model fallbacks.
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
### Use the built-in rate limiter
|
||||
|
||||
@@ -152,28 +336,53 @@ rate_limiter = InMemoryRateLimiter(
|
||||
)
|
||||
|
||||
model = ChatAnthropic(
|
||||
model_name="claude-3-opus-20240229",
|
||||
model_name="claude-3-opus-20240229",
|
||||
rate_limiter=rate_limiter
|
||||
)
|
||||
```
|
||||
|
||||
See the LangChain docs for more information on how to [handle rate limiting](https://python.langchain.com/docs/how_to/chat_model_rate_limiting/).
|
||||
:::
|
||||
|
||||
## Bring your own model
|
||||
|
||||
If your desired LLM isn't officially supported by LangChain, consider these options:
|
||||
|
||||
:::python
|
||||
|
||||
1. **Implement a custom LangChain chat model**: Create a model conforming to the [LangChain chat model interface](https://python.langchain.com/docs/how_to/custom_chat_model/). This enables full compatibility with LangGraph's agents and workflows but requires understanding of the LangChain framework.
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
1. **Implement a custom LangChain chat model**: Create a model conforming to the [LangChain chat model interface](https://js.langchain.com/docs/how_to/custom_chat/). This enables full compatibility with LangGraph's agents and workflows but requires understanding of the LangChain framework.
|
||||
|
||||
:::
|
||||
|
||||
2. **Direct invocation with custom streaming**: Use your model directly by [adding custom streaming logic](../how-tos/streaming.md#use-with-any-llm) with `StreamWriter`.
|
||||
Refer to the [custom streaming documentation](../how-tos/streaming.md#use-with-any-llm) for guidance. This approach suits custom workflows where prebuilt agent integration is not necessary.
|
||||
|
||||
|
||||
## Additional resources
|
||||
|
||||
:::python
|
||||
|
||||
- [Multimodal inputs](https://python.langchain.com/docs/how_to/multimodal_inputs/)
|
||||
- [Structured outputs](https://python.langchain.com/docs/how_to/structured_output/)
|
||||
- [Model integration directory](https://python.langchain.com/docs/integrations/chat/)
|
||||
- [Force model to call a specific tool](https://python.langchain.com/docs/how_to/tool_choice/)
|
||||
- [All chat model how-to guides](https://python.langchain.com/docs/how_to/#chat-models)
|
||||
- [Chat model integrations](https://python.langchain.com/docs/integrations/chat/)
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- [Multimodal inputs](https://js.langchain.com/docs/how_to/multimodal_inputs/)
|
||||
- [Structured outputs](https://js.langchain.com/docs/how_to/structured_output/)
|
||||
- [Model integration directory](https://js.langchain.com/docs/integrations/chat/)
|
||||
- [Force model to call a specific tool](https://js.langchain.com/docs/how_to/tool_choice/)
|
||||
- [All chat model how-to guides](https://js.langchain.com/docs/how_to/#chat-models)
|
||||
- [Chat model integrations](https://js.langchain.com/docs/integrations/chat/)
|
||||
|
||||
:::
|
||||
|
||||
@@ -22,6 +22,7 @@ Two of the most popular multi-agent architectures are:
|
||||
|
||||

|
||||
|
||||
:::python
|
||||
Use [`langgraph-supervisor`](https://github.com/langchain-ai/langgraph-supervisor-py) library to create a supervisor multi-agent system:
|
||||
|
||||
```bash
|
||||
@@ -82,10 +83,76 @@ for chunk in supervisor.stream(
|
||||
print("\n")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Use [`@langchain/langgraph-supervisor`](https://github.com/langchain-ai/langgraphjs/tree/main/libs/langgraph-supervisor) library to create a supervisor multi-agent system:
|
||||
|
||||
```bash
|
||||
npm install @langchain/langgraph-supervisor
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
// highlight-next-line
|
||||
import { createSupervisor } from "langgraph-supervisor";
|
||||
|
||||
function bookHotel(hotelName: string) {
|
||||
/**Book a hotel*/
|
||||
return `Successfully booked a stay at ${hotelName}.`;
|
||||
}
|
||||
|
||||
function bookFlight(fromAirport: string, toAirport: string) {
|
||||
/**Book a flight*/
|
||||
return `Successfully booked a flight from ${fromAirport} to ${toAirport}.`;
|
||||
}
|
||||
|
||||
const flightAssistant = createReactAgent({
|
||||
llm: "openai:gpt-4o",
|
||||
tools: [bookFlight],
|
||||
stateModifier: "You are a flight booking assistant",
|
||||
// highlight-next-line
|
||||
name: "flight_assistant",
|
||||
});
|
||||
|
||||
const hotelAssistant = createReactAgent({
|
||||
llm: "openai:gpt-4o",
|
||||
tools: [bookHotel],
|
||||
stateModifier: "You are a hotel booking assistant",
|
||||
// highlight-next-line
|
||||
name: "hotel_assistant",
|
||||
});
|
||||
|
||||
// highlight-next-line
|
||||
const supervisor = createSupervisor({
|
||||
agents: [flightAssistant, hotelAssistant],
|
||||
llm: new ChatOpenAI({ model: "gpt-4o" }),
|
||||
systemPrompt:
|
||||
"You manage a hotel booking assistant and a " +
|
||||
"flight booking assistant. Assign work to them.",
|
||||
});
|
||||
|
||||
for await (const chunk of supervisor.stream({
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "book a flight from BOS to JFK and a stay at McKittrick Hotel",
|
||||
},
|
||||
],
|
||||
})) {
|
||||
console.log(chunk);
|
||||
console.log("\n");
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Swarm
|
||||
|
||||

|
||||
|
||||
:::python
|
||||
Use [`langgraph-swarm`](https://github.com/langchain-ai/langgraph-swarm-py) library to create a swarm multi-agent system:
|
||||
|
||||
```bash
|
||||
@@ -143,18 +210,82 @@ for chunk in swarm.stream(
|
||||
print("\n")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Use [`@langchain/langgraph-swarm`](https://github.com/langchain-ai/langgraphjs/tree/main/libs/langgraph-swarm) library to create a swarm multi-agent system:
|
||||
|
||||
```bash
|
||||
npm install @langchain/langgraph-swarm
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
// highlight-next-line
|
||||
import { createSwarm, createHandoffTool } from "@langchain/langgraph-swarm";
|
||||
|
||||
const transferToHotelAssistant = createHandoffTool({
|
||||
agentName: "hotel_assistant",
|
||||
description: "Transfer user to the hotel-booking assistant.",
|
||||
});
|
||||
|
||||
const transferToFlightAssistant = createHandoffTool({
|
||||
agentName: "flight_assistant",
|
||||
description: "Transfer user to the flight-booking assistant.",
|
||||
});
|
||||
|
||||
const flightAssistant = createReactAgent({
|
||||
llm: "anthropic:claude-3-5-sonnet-latest",
|
||||
// highlight-next-line
|
||||
tools: [bookFlight, transferToHotelAssistant],
|
||||
stateModifier: "You are a flight booking assistant",
|
||||
// highlight-next-line
|
||||
name: "flight_assistant",
|
||||
});
|
||||
|
||||
const hotelAssistant = createReactAgent({
|
||||
llm: "anthropic:claude-3-5-sonnet-latest",
|
||||
// highlight-next-line
|
||||
tools: [bookHotel, transferToFlightAssistant],
|
||||
stateModifier: "You are a hotel booking assistant",
|
||||
// highlight-next-line
|
||||
name: "hotel_assistant",
|
||||
});
|
||||
|
||||
// highlight-next-line
|
||||
const swarm = createSwarm({
|
||||
agents: [flightAssistant, hotelAssistant],
|
||||
defaultActiveAgent: "flight_assistant",
|
||||
});
|
||||
|
||||
for await (const chunk of swarm.stream({
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "book a flight from BOS to JFK and a stay at McKittrick Hotel",
|
||||
},
|
||||
],
|
||||
})) {
|
||||
console.log(chunk);
|
||||
console.log("\n");
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Handoffs
|
||||
|
||||
A common pattern in multi-agent interactions is **handoffs**, where one agent *hands off* control to another. Handoffs allow you to specify:
|
||||
A common pattern in multi-agent interactions is **handoffs**, where one agent _hands off_ control to another. Handoffs allow you to specify:
|
||||
|
||||
- **destination**: target agent to navigate to
|
||||
- **payload**: information to pass to that agent
|
||||
|
||||
:::python
|
||||
This is used both by `langgraph-supervisor` (supervisor hands off to individual agents) and `langgraph-swarm` (an individual agent can hand off to other agents).
|
||||
|
||||
To implement handoffs with `create_react_agent`, you need to:
|
||||
|
||||
1. Create a special tool that can transfer control to a different agent
|
||||
1. Create a special tool that can transfer control to a different agent
|
||||
|
||||
```python
|
||||
def transfer_to_bob():
|
||||
@@ -173,7 +304,7 @@ To implement handoffs with `create_react_agent`, you need to:
|
||||
)
|
||||
```
|
||||
|
||||
1. Create individual agents that have access to handoff tools:
|
||||
2. Create individual agents that have access to handoff tools:
|
||||
|
||||
```python
|
||||
flight_assistant = create_react_agent(
|
||||
@@ -184,7 +315,7 @@ To implement handoffs with `create_react_agent`, you need to:
|
||||
)
|
||||
```
|
||||
|
||||
1. Define a parent graph that contains individual agents as nodes:
|
||||
3. Define a parent graph that contains individual agents as nodes:
|
||||
|
||||
```python
|
||||
from langgraph.graph import StateGraph, MessagesState
|
||||
@@ -196,8 +327,60 @@ To implement handoffs with `create_react_agent`, you need to:
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
This is used both by `@langchain/langgraph-supervisor` (supervisor hands off to individual agents) and `@langchain/langgraph-swarm` (an individual agent can hand off to other agents).
|
||||
|
||||
To implement handoffs with `createReactAgent`, you need to:
|
||||
|
||||
1. Create a special tool that can transfer control to a different agent
|
||||
|
||||
```typescript
|
||||
function transferToBob() {
|
||||
/**Transfer to bob.*/
|
||||
return new Command({
|
||||
// name of the agent (node) to go to
|
||||
// highlight-next-line
|
||||
goto: "bob",
|
||||
// data to send to the agent
|
||||
// highlight-next-line
|
||||
update: { messages: [...] },
|
||||
// indicate to LangGraph that we need to navigate to
|
||||
// agent node in a parent graph
|
||||
// highlight-next-line
|
||||
graph: Command.PARENT,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
2. Create individual agents that have access to handoff tools:
|
||||
|
||||
```typescript
|
||||
const flightAssistant = createReactAgent({
|
||||
..., tools: [bookFlight, transferToHotelAssistant]
|
||||
});
|
||||
const hotelAssistant = createReactAgent({
|
||||
..., tools: [bookHotel, transferToFlightAssistant]
|
||||
});
|
||||
```
|
||||
|
||||
3. Define a parent graph that contains individual agents as nodes:
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState } from "@langchain/langgraph";
|
||||
const multiAgentGraph = new StateGraph(MessagesZodState)
|
||||
.addNode("flight_assistant", flightAssistant)
|
||||
.addNode("hotel_assistant", hotelAssistant)
|
||||
// ...
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Putting this together, here is how you can implement a simple multi-agent system with two agents — a flight booking assistant and a hotel booking assistant:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from langchain_core.tools import tool, InjectedToolCallId
|
||||
@@ -298,11 +481,158 @@ for chunk in multi_agent_graph.stream(
|
||||
3. Name of the agent or node to hand off to.
|
||||
4. Take the agent's messages and **add** them to the parent's **state** as part of the handoff. The next agent will see the parent state.
|
||||
5. Indicate to LangGraph that we need to navigate to agent node in a **parent** multi-agent graph.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import {
|
||||
StateGraph,
|
||||
START,
|
||||
MessagesZodState,
|
||||
Command,
|
||||
} from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
function createHandoffTool({
|
||||
agentName,
|
||||
description,
|
||||
}: {
|
||||
agentName: string;
|
||||
description?: string;
|
||||
}) {
|
||||
const name = `transfer_to_${agentName}`;
|
||||
const toolDescription = description || `Transfer to ${agentName}`;
|
||||
|
||||
return tool(
|
||||
async (_, config) => {
|
||||
const toolMessage = {
|
||||
role: "tool" as const,
|
||||
content: `Successfully transferred to ${agentName}`,
|
||||
name: name,
|
||||
tool_call_id: config.toolCall?.id!,
|
||||
};
|
||||
return new Command({
|
||||
// (2)!
|
||||
// highlight-next-line
|
||||
goto: agentName, // (3)!
|
||||
// highlight-next-line
|
||||
update: { messages: [toolMessage] }, // (4)!
|
||||
// highlight-next-line
|
||||
graph: Command.PARENT, // (5)!
|
||||
});
|
||||
},
|
||||
{
|
||||
name,
|
||||
description: toolDescription,
|
||||
schema: z.object({}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Handoffs
|
||||
const transferToHotelAssistant = createHandoffTool({
|
||||
agentName: "hotel_assistant",
|
||||
description: "Transfer user to the hotel-booking assistant.",
|
||||
});
|
||||
|
||||
const transferToFlightAssistant = createHandoffTool({
|
||||
agentName: "flight_assistant",
|
||||
description: "Transfer user to the flight-booking assistant.",
|
||||
});
|
||||
|
||||
// Simple agent tools
|
||||
const bookHotel = tool(
|
||||
async ({ hotelName }) => {
|
||||
/**Book a hotel*/
|
||||
return `Successfully booked a stay at ${hotelName}.`;
|
||||
},
|
||||
{
|
||||
name: "book_hotel",
|
||||
description: "Book a hotel",
|
||||
schema: z.object({
|
||||
hotelName: z.string().describe("Name of the hotel to book"),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
const bookFlight = tool(
|
||||
async ({ fromAirport, toAirport }) => {
|
||||
/**Book a flight*/
|
||||
return `Successfully booked a flight from ${fromAirport} to ${toAirport}.`;
|
||||
},
|
||||
{
|
||||
name: "book_flight",
|
||||
description: "Book a flight",
|
||||
schema: z.object({
|
||||
fromAirport: z.string().describe("Departure airport code"),
|
||||
toAirport: z.string().describe("Arrival airport code"),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
// Define agents
|
||||
const flightAssistant = createReactAgent({
|
||||
llm: new ChatAnthropic({ model: "anthropic:claude-3-5-sonnet-latest" }),
|
||||
// highlight-next-line
|
||||
tools: [bookFlight, transferToHotelAssistant],
|
||||
stateModifier: "You are a flight booking assistant",
|
||||
// highlight-next-line
|
||||
name: "flight_assistant",
|
||||
});
|
||||
|
||||
const hotelAssistant = createReactAgent({
|
||||
llm: new ChatAnthropic({ model: "anthropic:claude-3-5-sonnet-latest" }),
|
||||
// highlight-next-line
|
||||
tools: [bookHotel, transferToFlightAssistant],
|
||||
stateModifier: "You are a hotel booking assistant",
|
||||
// highlight-next-line
|
||||
name: "hotel_assistant",
|
||||
});
|
||||
|
||||
// Define multi-agent graph
|
||||
const multiAgentGraph = new StateGraph(MessagesZodState)
|
||||
.addNode("flight_assistant", flightAssistant)
|
||||
.addNode("hotel_assistant", hotelAssistant)
|
||||
.addEdge(START, "flight_assistant")
|
||||
.compile();
|
||||
|
||||
// Run the multi-agent graph
|
||||
for await (const chunk of multiAgentGraph.stream({
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "book a flight from BOS to JFK and a stay at McKittrick Hotel",
|
||||
},
|
||||
],
|
||||
})) {
|
||||
console.log(chunk);
|
||||
console.log("\n");
|
||||
}
|
||||
```
|
||||
|
||||
1. Access agent's state
|
||||
2. The `Command` primitive allows specifying a state update and a node transition as a single operation, making it useful for implementing handoffs.
|
||||
3. Name of the agent or node to hand off to.
|
||||
4. Take the agent's messages and **add** them to the parent's **state** as part of the handoff. The next agent will see the parent state.
|
||||
5. Indicate to LangGraph that we need to navigate to agent node in a **parent** multi-agent graph.
|
||||
|
||||
:::
|
||||
|
||||
!!! Note
|
||||
|
||||
This handoff implementation assumes that:
|
||||
|
||||
- each agent receives overall message history (across all agents) in the multi-agent system as its input
|
||||
- each agent outputs its internal messages history to the overall message history of the multi-agent system
|
||||
|
||||
Check out LangGraph [supervisor](https://github.com/langchain-ai/langgraph-supervisor-py#customizing-handoff-tools) and [swarm](https://github.com/langchain-ai/langgraph-swarm-py#customizing-handoff-tools) documentation to learn how to customize handoffs.
|
||||
:::python
|
||||
Check out LangGraph [supervisor](https://github.com/langchain-ai/langgraph-supervisor-py#customizing-handoff-tools) and [swarm](https://github.com/langchain-ai/langgraph-swarm-py#customizing-handoff-tools) documentation to learn how to customize handoffs.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Check out LangGraph [supervisor](https://github.com/langchain-ai/langgraphjs/tree/main/libs/langgraph-supervisor#customizing-handoff-tools) and [swarm](https://github.com/langchain-ai/langgraphjs/tree/main/libs/langgraph-swarm#customizing-handoff-tools) documentation to learn how to customize handoffs.
|
||||
:::
|
||||
|
||||
+178
-22
@@ -14,7 +14,7 @@ LangGraph provides both low-level primitives and high-level prebuilt components
|
||||
|
||||
## What is an agent?
|
||||
|
||||
An *agent* consists of three components: a **large language model (LLM)**, a set of **tools** it can use, and a **prompt** that provides instructions.
|
||||
An _agent_ consists of three components: a **large language model (LLM)**, a set of **tools** it can use, and a **prompt** that provides instructions.
|
||||
|
||||
The LLM operates in a loop. In each iteration, it selects a tool to invoke, provides input, receives the result (an observation), and uses that observation to inform the next action. The loop continues until a stopping condition is met — typically when the agent has gathered enough information to respond to the user.
|
||||
|
||||
@@ -27,12 +27,12 @@ The LLM operates in a loop. In each iteration, it selects a tool to invoke, prov
|
||||
|
||||
LangGraph includes several capabilities essential for building robust, production-ready agentic systems:
|
||||
|
||||
- [**Memory integration**](../how-tos/memory/add-memory.md): Native support for *short-term* (session-based) and *long-term* (persistent across sessions) memory, enabling stateful behaviors in chatbots and assistants.
|
||||
- [**Human-in-the-loop control**](../concepts/human_in_the_loop.md): Execution can pause *indefinitely* to await human feedback—unlike websocket-based solutions limited to real-time interaction. This enables asynchronous approval, correction, or intervention at any point in the workflow.
|
||||
- [**Memory integration**](../how-tos/memory/add-memory.md): Native support for _short-term_ (session-based) and _long-term_ (persistent across sessions) memory, enabling stateful behaviors in chatbots and assistants.
|
||||
- [**Human-in-the-loop control**](../concepts/human_in_the_loop.md): Execution can pause _indefinitely_ to await human feedback—unlike websocket-based solutions limited to real-time interaction. This enables asynchronous approval, correction, or intervention at any point in the workflow.
|
||||
- [**Streaming support**](../how-tos/streaming.md): Real-time streaming of agent state, model tokens, tool outputs, or combined streams.
|
||||
- [**Deployment tooling**](../tutorials/langgraph-platform/local-server.md): Includes infrastructure-free deployment tools. [**LangGraph Platform**](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) supports testing, debugging, and deployment.
|
||||
- **[Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/)**: A visual IDE for inspecting and debugging workflows.
|
||||
- Supports multiple [**deployment options**](https://langchain-ai.github.io/langgraph/concepts/deployment_options.md) for production.
|
||||
- **[Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/)**: A visual IDE for inspecting and debugging workflows.
|
||||
- Supports multiple [**deployment options**](https://langchain-ai.github.io/langgraph/concepts/deployment_options.md) for production.
|
||||
|
||||
## High-level building blocks
|
||||
|
||||
@@ -40,30 +40,32 @@ LangGraph comes with a set of prebuilt components that implement common agent be
|
||||
|
||||
Using LangGraph for agent development allows you to focus on your application's logic and behavior, instead of building and maintaining the supporting infrastructure for state, memory, and human feedback.
|
||||
|
||||
:::python
|
||||
|
||||
## Package ecosystem
|
||||
|
||||
The high-level components are organized into several packages, each with a specific focus.
|
||||
|
||||
| Package | Description | Installation |
|
||||
|--------------------------------------------|-----------------------------------------------------------------------------|-----------------------------------------|
|
||||
| `langgraph-prebuilt` (part of `langgraph`) | Prebuilt components to [**create agents**](./agents.md) | `pip install -U langgraph langchain` |
|
||||
| `langgraph-supervisor` | Tools for building [**supervisor**](./multi-agent.md#supervisor) agents | `pip install -U langgraph-supervisor` |
|
||||
| `langgraph-swarm` | Tools for building a [**swarm**](./multi-agent.md#swarm) multi-agent system | `pip install -U langgraph-swarm` |
|
||||
| `langchain-mcp-adapters` | Interfaces to [**MCP servers**](./mcp.md) for tool and resource integration | `pip install -U langchain-mcp-adapters` |
|
||||
| `langmem` | Agent memory management: [**short-term and long-term**](../how-tos/memory/add-memory.md) | `pip install -U langmem` |
|
||||
| `agentevals` | Utilities to [**evaluate agent performance**](./evals.md) | `pip install -U agentevals` |
|
||||
| Package | Description | Installation |
|
||||
| ------------------------------------------ | ---------------------------------------------------------------------------------------- | --------------------------------------- |
|
||||
| `langgraph-prebuilt` (part of `langgraph`) | Prebuilt components to [**create agents**](./agents.md) | `pip install -U langgraph langchain` |
|
||||
| `langgraph-supervisor` | Tools for building [**supervisor**](./multi-agent.md#supervisor) agents | `pip install -U langgraph-supervisor` |
|
||||
| `langgraph-swarm` | Tools for building a [**swarm**](./multi-agent.md#swarm) multi-agent system | `pip install -U langgraph-swarm` |
|
||||
| `langchain-mcp-adapters` | Interfaces to [**MCP servers**](./mcp.md) for tool and resource integration | `pip install -U langchain-mcp-adapters` |
|
||||
| `langmem` | Agent memory management: [**short-term and long-term**](../how-tos/memory/add-memory.md) | `pip install -U langmem` |
|
||||
| `agentevals` | Utilities to [**evaluate agent performance**](./evals.md) | `pip install -U agentevals` |
|
||||
|
||||
## Visualize an agent graph
|
||||
|
||||
Use the following tool to visualize the graph generated by
|
||||
[`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent]
|
||||
@[`create_react_agent`][create_react_agent]
|
||||
and to view an outline of the corresponding code.
|
||||
It allows you to explore the infrastructure of the agent as defined by the presence of:
|
||||
|
||||
* [`tools`](../how-tos/tool-calling.md): A list of tools (functions, APIs, or other callable objects) that the agent can use to perform tasks.
|
||||
* [`pre_model_hook`](../how-tos/create-react-agent-manage-message-history.ipynb): A function that is called before the model is invoked. It can be used to condense messages or perform other preprocessing tasks.
|
||||
* `post_model_hook`: A function that is called after the model is invoked. It can be used to implement guardrails, human-in-the-loop flows, or other postprocessing tasks.
|
||||
* [`response_format`](../agents/agents.md#6-configure-structured-output): A data structure used to constrain the type of the final output, e.g., a `pydantic` `BaseModel`.
|
||||
- [`tools`](../how-tos/tool-calling.md): A list of tools (functions, APIs, or other callable objects) that the agent can use to perform tasks.
|
||||
- [`pre_model_hook`](../how-tos/create-react-agent-manage-message-history.ipynb): A function that is called before the model is invoked. It can be used to condense messages or perform other preprocessing tasks.
|
||||
- `post_model_hook`: A function that is called after the model is invoked. It can be used to implement guardrails, human-in-the-loop flows, or other postprocessing tasks.
|
||||
- [`response_format`](../agents/agents.md#6-configure-structured-output): A data structure used to constrain the type of the final output, e.g., a `pydantic` `BaseModel`.
|
||||
|
||||
<div class="agent-layout">
|
||||
<div class="agent-graph-features-container">
|
||||
@@ -82,15 +84,13 @@ It allows you to explore the infrastructure of the agent as defined by the prese
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
The following code snippet shows how to create the above agent (and underlying graph) with
|
||||
[`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent]:
|
||||
@[`create_react_agent`][create_react_agent]:
|
||||
|
||||
<div class="language-python">
|
||||
<pre><code id="agent-code" class="language-python"></code></pre>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
function getCheckedValue(id) {
|
||||
return document.getElementById(id).checked ? "1" : "0";
|
||||
@@ -159,7 +159,7 @@ function generateCodeSnippet({ tools, pre, post, response }) {
|
||||
if (post) lines.push(" post_model_hook=post_model_hook,");
|
||||
if (response) lines.push(" response_format=ResponseFormat,");
|
||||
|
||||
lines.push(")", "", "agent.get_graph().draw_mermaid_png()");
|
||||
lines.push(")", "", "# Visualize the graph", "# For Jupyter or GUI environments:", "agent.get_graph().draw_mermaid_png()", "", "# To save PNG to file:", "png_data = agent.get_graph().draw_mermaid_png()", "with open(\"graph.png\", \"wb\") as f:", " f.write(png_data)", "", "# For terminal/ASCII output:", "agent.get_graph().draw_ascii()");
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
@@ -189,3 +189,159 @@ function initializeWidget() {
|
||||
window.addEventListener("DOMContentLoaded", initializeWidget);
|
||||
document$.subscribe(initializeWidget);
|
||||
</script>
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
## Package ecosystem
|
||||
|
||||
The high-level components are organized into several packages, each with a specific focus.
|
||||
|
||||
| Package | Description | Installation |
|
||||
| ------------------------ | --------------------------------------------------------------------------- | -------------------------------------------------- |
|
||||
| `langgraph` | Prebuilt components to [**create agents**](./agents.md) | `npm install @langchain/langgraph @langchain/core` |
|
||||
| `langgraph-supervisor` | Tools for building [**supervisor**](./multi-agent.md#supervisor) agents | `npm install @langchain/langgraph-supervisor` |
|
||||
| `langgraph-swarm` | Tools for building a [**swarm**](./multi-agent.md#swarm) multi-agent system | `npm install @langchain/langgraph-swarm` |
|
||||
| `langchain-mcp-adapters` | Interfaces to [**MCP servers**](./mcp.md) for tool and resource integration | `npm install @langchain/mcp-adapters` |
|
||||
| `agentevals` | Utilities to [**evaluate agent performance**](./evals.md) | `npm install agentevals` |
|
||||
|
||||
## Visualize an agent graph
|
||||
|
||||
Use the following tool to visualize the graph generated by @[`createReactAgent`][create_react_agent] and to view an outline of the corresponding code. It allows you to explore the infrastructure of the agent as defined by the presence of:
|
||||
|
||||
- [`tools`](./tools.md): A list of tools (functions, APIs, or other callable objects) that the agent can use to perform tasks.
|
||||
- `preModelHook`: A function that is called before the model is invoked. It can be used to condense messages or perform other preprocessing tasks.
|
||||
- `postModelHook`: A function that is called after the model is invoked. It can be used to implement guardrails, human-in-the-loop flows, or other postprocessing tasks.
|
||||
- [`responseFormat`](./agents.md#6-configure-structured-output): A data structure used to constrain the type of the final output (via Zod schemas).
|
||||
|
||||
<div class="agent-layout">
|
||||
<div class="agent-graph-features-container">
|
||||
<div class="agent-graph-features">
|
||||
<h3 class="agent-section-title">Features</h3>
|
||||
<label><input type="checkbox" id="tools" checked> <code>tools</code></label>
|
||||
<label><input type="checkbox" id="preModelHook"> <code>preModelHook</code></label>
|
||||
<label><input type="checkbox" id="postModelHook"> <code>postModelHook</code></label>
|
||||
<label><input type="checkbox" id="responseFormat"> <code>responseFormat</code></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent-graph-container">
|
||||
<h3 class="agent-section-title">Graph</h3>
|
||||
<img id="agent-graph-img" src="../assets/react_agent_graphs/0001.svg" alt="graph image" style="max-width: 100%;"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
The following code snippet shows how to create the above agent (and underlying graph) with @[`createReactAgent`][create_react_agent]:
|
||||
|
||||
<div class="language-typescript">
|
||||
<pre><code id="agent-code" class="language-typescript"></code></pre>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function getCheckedValue(id) {
|
||||
return document.getElementById(id).checked ? "1" : "0";
|
||||
}
|
||||
|
||||
function getKey() {
|
||||
return [
|
||||
getCheckedValue("responseFormat"),
|
||||
getCheckedValue("postModelHook"),
|
||||
getCheckedValue("preModelHook"),
|
||||
getCheckedValue("tools")
|
||||
].join("");
|
||||
}
|
||||
|
||||
function dedent(strings, ...values) {
|
||||
const str = String.raw({ raw: strings }, ...values)
|
||||
const [space] = str.split("\n").filter(Boolean).at(0).match(/^(\s*)/)
|
||||
const spaceLen = space.length
|
||||
return str.split("\n").map(line => line.slice(spaceLen)).join("\n").trim()
|
||||
}
|
||||
|
||||
Object.assign(dedent, {
|
||||
offset: (size) => (strings, ...values) => {
|
||||
return dedent(strings, ...values).split("\n").map(line => " ".repeat(size) + line).join("\n")
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
function generateCodeSnippet({ tools, pre, post, response }) {
|
||||
const lines = []
|
||||
|
||||
lines.push(dedent`
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
`)
|
||||
|
||||
if (tools) lines.push(`import { tool } from "@langchain/core/tools";`);
|
||||
if (response || tools) lines.push(`import { z } from "zod";`);
|
||||
|
||||
lines.push("", dedent`
|
||||
const agent = createReactAgent({
|
||||
llm: new ChatOpenAI({ model: "o4-mini" }),
|
||||
`)
|
||||
|
||||
if (tools) {
|
||||
lines.push(dedent.offset(2)`
|
||||
tools: [
|
||||
tool(() => "Sample tool output", {
|
||||
name: "sampleTool",
|
||||
schema: z.object({}),
|
||||
}),
|
||||
],
|
||||
`)
|
||||
}
|
||||
|
||||
if (pre) {
|
||||
lines.push(dedent.offset(2)`
|
||||
preModelHook: (state) => ({ llmInputMessages: state.messages }),
|
||||
`)
|
||||
}
|
||||
|
||||
if (post) {
|
||||
lines.push(dedent.offset(2)`
|
||||
postModelHook: (state) => state,
|
||||
`)
|
||||
}
|
||||
|
||||
if (response) {
|
||||
lines.push(dedent.offset(2)`
|
||||
responseFormat: z.object({ result: z.string() }),
|
||||
`)
|
||||
}
|
||||
|
||||
lines.push(`});`);
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function render() {
|
||||
const key = getKey();
|
||||
document.getElementById("agent-graph-img").src = `../assets/react_agent_graphs/${key}.svg`;
|
||||
|
||||
const state = {
|
||||
tools: document.getElementById("tools").checked,
|
||||
pre: document.getElementById("preModelHook").checked,
|
||||
post: document.getElementById("postModelHook").checked,
|
||||
response: document.getElementById("responseFormat").checked
|
||||
};
|
||||
|
||||
document.getElementById("agent-code").textContent = generateCodeSnippet(state);
|
||||
}
|
||||
|
||||
function initializeWidget() {
|
||||
render(); // no need for `await` here
|
||||
document.querySelectorAll(".agent-graph-features input").forEach((input) => {
|
||||
input.addEventListener("change", render);
|
||||
});
|
||||
}
|
||||
|
||||
// Init for both full reload and SPA nav (used by MkDocs Material)
|
||||
window.addEventListener("DOMContentLoaded", initializeWidget);
|
||||
document$.subscribe(initializeWidget);
|
||||
</script>
|
||||
|
||||
:::
|
||||
|
||||
@@ -5,23 +5,24 @@ If you’re looking for other prebuilt libraries, explore the community-built op
|
||||
below. These libraries can extend LangGraph's functionality in various ways.
|
||||
|
||||
## 📚 Available Libraries
|
||||
|
||||
[//]: # (This file is automatically generated using a script in docs/_scripts. Do not edit this file directly!)
|
||||
|
||||
:::python
|
||||
| Name | GitHub URL | Description | Weekly Downloads | Stars |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **trustcall** | [hinthornw/trustcall](https://github.com/hinthornw/trustcall) | Tenacious tool calling built on LangGraph. | -12345 | 
|
||||
| **breeze-agent** | [andrestorres123/breeze-agent](https://github.com/andrestorres123/breeze-agent) | A streamlined research system built inspired on STORM and built on LangGraph. | -12345 | 
|
||||
| **langgraph-supervisor** | [langchain-ai/langgraph-supervisor-py](https://github.com/langchain-ai/langgraph-supervisor-py) | Build supervisor multi-agent systems with LangGraph. | -12345 | 
|
||||
| **langmem** | [langchain-ai/langmem](https://github.com/langchain-ai/langmem) | Build agents that learn and adapt from interactions over time. | -12345 | 
|
||||
| **langchain-mcp-adapters** | [langchain-ai/langchain-mcp-adapters](https://github.com/langchain-ai/langchain-mcp-adapters) | Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents. | -12345 | 
|
||||
| **open-deep-research** | [langchain-ai/open_deep_research](https://github.com/langchain-ai/open_deep_research) | Open source assistant for iterative web research and report writing. | -12345 | 
|
||||
| **langgraph-swarm** | [langchain-ai/langgraph-swarm-py](https://github.com/langchain-ai/langgraph-swarm-py) | Build swarm-style multi-agent systems using LangGraph. | -12345 | 
|
||||
| **delve-taxonomy-generator** | [andrestorres123/delve](https://github.com/andrestorres123/delve) | A taxonomy generator for unstructured data | -12345 | 
|
||||
| **nodeology** | [xyin-anl/Nodeology](https://github.com/xyin-anl/Nodeology) | Enable researcher to build scientific workflows easily with simplified interface. | -12345 | 
|
||||
| **langgraph-bigtool** | [langchain-ai/langgraph-bigtool](https://github.com/langchain-ai/langgraph-bigtool) | Build LangGraph agents with large numbers of tools. | -12345 | 
|
||||
| **ai-data-science-team** | [business-science/ai-data-science-team](https://github.com/business-science/ai-data-science-team) | An AI-powered data science team of agents to help you perform common data science tasks 10X faster. | -12345 | 
|
||||
| **langgraph-reflection** | [langchain-ai/langgraph-reflection](https://github.com/langchain-ai/langgraph-reflection) | LangGraph agent that runs a reflection step. | -12345 | 
|
||||
| **langgraph-codeact** | [langchain-ai/langgraph-codeact](https://github.com/langchain-ai/langgraph-codeact) | LangGraph implementation of CodeAct agent that generates and executes code instead of tool calling. | -12345 | 
|
||||
| **trustcall** | https://github.com/hinthornw/trustcall | Tenacious tool calling built on LangGraph. | -12345 | 
|
||||
| **breeze-agent** | https://github.com/andrestorres123/breeze-agent | A streamlined research system built inspired on STORM and built on LangGraph. | -12345 | 
|
||||
| **langgraph-supervisor** | https://github.com/langchain-ai/langgraph-supervisor-py | Build supervisor multi-agent systems with LangGraph. | -12345 | 
|
||||
| **langmem** | https://github.com/langchain-ai/langmem | Build agents that learn and adapt from interactions over time. | -12345 | 
|
||||
| **langchain-mcp-adapters** | https://github.com/langchain-ai/langchain-mcp-adapters | Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents. | -12345 | 
|
||||
| **open-deep-research** | https://github.com/langchain-ai/open_deep_research | Open source assistant for iterative web research and report writing. | -12345 | 
|
||||
| **langgraph-swarm** | https://github.com/langchain-ai/langgraph-swarm-py | Build swarm-style multi-agent systems using LangGraph. | -12345 | 
|
||||
| **delve-taxonomy-generator** | https://github.com/andrestorres123/delve | A taxonomy generator for unstructured data | -12345 | 
|
||||
| **nodeology** | https://github.com/xyin-anl/Nodeology | Enable researcher to build scientific workflows easily with simplified interface. | -12345 | 
|
||||
| **langgraph-bigtool** | https://github.com/langchain-ai/langgraph-bigtool | Build LangGraph agents with large numbers of tools. | -12345 | 
|
||||
| **ai-data-science-team** | https://github.com/business-science/ai-data-science-team | An AI-powered data science team of agents to help you perform common data science tasks 10X faster. | -12345 | 
|
||||
| **langgraph-reflection** | https://github.com/langchain-ai/langgraph-reflection | LangGraph agent that runs a reflection step. | -12345 | 
|
||||
| **langgraph-codeact** | https://github.com/langchain-ai/langgraph-codeact | LangGraph implementation of CodeAct agent that generates and executes code instead of tool calling. | -12345 | 
|
||||
|
||||
## ✨ Contributing Your Library
|
||||
|
||||
@@ -32,13 +33,41 @@ To share your project, simply open a Pull Request adding an entry for your packa
|
||||
|
||||
**Guidelines**
|
||||
|
||||
- Your repo must be distributed as an installable package (e.g., PyPI for Python, npm
|
||||
for JavaScript/TypeScript, etc.) 📦
|
||||
- Your repo must be distributed as an installable package on PyPI 📦
|
||||
- The repo should either use the Graph API (exposing a `StateGraph` instance) or
|
||||
the Functional API (exposing an `entrypoint`).
|
||||
- The package must include documentation (e.g., a `README.md` or docs site)
|
||||
explaining how to use it.
|
||||
|
||||
|
||||
We'll review your contribution and merge it in!
|
||||
|
||||
Thanks for contributing! 🚀
|
||||
:::
|
||||
|
||||
:::js
|
||||
| Name | GitHub URL | Description | Weekly Downloads | Stars |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **@langchain/mcp-adapters** | https://github.com/langchain-ai/langchainjs | Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents. | -12345 | 
|
||||
| **@langchain/langgraph-supervisor** | https://github.com/langchain-ai/langgraphjs/tree/main/libs/langgraph-supervisor | Build supervisor multi-agent systems with LangGraph | -12345 | 
|
||||
| **@langchain/langgraph-swarm** | https://github.com/langchain-ai/langgraphjs/tree/main/libs/langgraph-swarm | Build multi-agent swarms with LangGraph | -12345 | 
|
||||
| **@langchain/langgraph-cua** | https://github.com/langchain-ai/langgraphjs/tree/main/libs/langgraph-cua | Build computer use agents with LangGraph | -12345 | 
|
||||
|
||||
## ✨ Contributing Your Library
|
||||
|
||||
Have you built an awesome open-source library using LangGraph? We'd love to feature
|
||||
your project on the official LangGraph documentation pages! 🏆
|
||||
|
||||
To share your project, simply open a Pull Request adding an entry for your package in our [packages.yml](https://github.com/langchain-ai/langgraph/blob/main/docs/_scripts/third_party_page/packages.yml) file.
|
||||
|
||||
**Guidelines**
|
||||
|
||||
- Your repo must be distributed as an installable package on npm 📦
|
||||
- The repo should either use the Graph API (exposing a `StateGraph` instance) or
|
||||
the Functional API (exposing an `entrypoint`).
|
||||
- The package must include documentation (e.g., a `README.md` or docs site)
|
||||
explaining how to use it.
|
||||
|
||||
We'll review your contribution and merge it in!
|
||||
|
||||
Thanks for contributing! 🚀
|
||||
:::
|
||||
|
||||
@@ -9,18 +9,27 @@ hide:
|
||||
|
||||
# Running agents
|
||||
|
||||
|
||||
Agents support both synchronous and asynchronous execution using either `.invoke()` / `await .ainvoke()` for full responses, or `.stream()` / `.astream()` for **incremental** [streaming](../how-tos/streaming.md) output. This section explains how to provide input, interpret output, enable streaming, and control execution limits.
|
||||
|
||||
|
||||
## Basic usage
|
||||
|
||||
Agents can be executed in two primary modes:
|
||||
|
||||
:::python
|
||||
|
||||
- **Synchronous** using `.invoke()` or `.stream()`
|
||||
- **Asynchronous** using `await .ainvoke()` or `async for` with `.astream()`
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- **Synchronous** using `.invoke()` or `.stream()`
|
||||
- **Asynchronous** using `await .invoke()` or `for await` with `.stream()`
|
||||
:::
|
||||
|
||||
:::python
|
||||
=== "Sync invocation"
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
@@ -31,6 +40,7 @@ Agents can be executed in two primary modes:
|
||||
```
|
||||
|
||||
=== "Async invocation"
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
@@ -39,6 +49,24 @@ Agents can be executed in two primary modes:
|
||||
response = await agent.ainvoke({"messages": [{"role": "user", "content": "what is the weather in sf"}]})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
|
||||
const agent = createReactAgent(...);
|
||||
// highlight-next-line
|
||||
const response = await agent.invoke({
|
||||
"messages": [
|
||||
{ "role": "user", "content": "what is the weather in sf" }
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
Agents use a language model that expects a list of `messages` as an input. Therefore, agent inputs and outputs are stored as a list of `messages` under the `messages` key in the agent [state](../concepts/low_level.md#working-with-messages-in-graph-state).
|
||||
@@ -47,33 +75,73 @@ Agents use a language model that expects a list of `messages` as an input. There
|
||||
|
||||
Agent input must be a dictionary with a `messages` key. Supported formats are:
|
||||
|
||||
| Format | Example |
|
||||
:::python
|
||||
| Format | Example |
|
||||
|--------------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| String | `{"messages": "Hello"}` — Interpreted as a [HumanMessage](https://python.langchain.com/docs/concepts/messages/#humanmessage) |
|
||||
| Message dictionary | `{"messages": {"role": "user", "content": "Hello"}}` |
|
||||
| List of messages | `{"messages": [{"role": "user", "content": "Hello"}]}` |
|
||||
| With custom state | `{"messages": [{"role": "user", "content": "Hello"}], "user_name": "Alice"}` — If using a custom `state_schema` |
|
||||
| String | `{"messages": "Hello"}` — Interpreted as a [HumanMessage](https://python.langchain.com/docs/concepts/messages/#humanmessage) |
|
||||
| Message dictionary | `{"messages": {"role": "user", "content": "Hello"}}` |
|
||||
| List of messages | `{"messages": [{"role": "user", "content": "Hello"}]}` |
|
||||
| With custom state | `{"messages": [{"role": "user", "content": "Hello"}], "user_name": "Alice"}` — If using a custom `state_schema` |
|
||||
:::
|
||||
|
||||
:::js
|
||||
| Format | Example |
|
||||
|--------------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| String | `{"messages": "Hello"}` — Interpreted as a [HumanMessage](https://js.langchain.com/docs/concepts/messages/#humanmessage) |
|
||||
| Message dictionary | `{"messages": {"role": "user", "content": "Hello"}}` |
|
||||
| List of messages | `{"messages": [{"role": "user", "content": "Hello"}]}` |
|
||||
| With custom state | `{"messages": [{"role": "user", "content": "Hello"}], "user_name": "Alice"}` — If using a custom state definition |
|
||||
:::
|
||||
|
||||
:::python
|
||||
Messages are automatically converted into LangChain's internal message format. You can read
|
||||
more about [LangChain messages](https://python.langchain.com/docs/concepts/messages/#langchain-messages) in the LangChain documentation.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Messages are automatically converted into LangChain's internal message format. You can read
|
||||
more about [LangChain messages](https://js.langchain.com/docs/concepts/messages/#langchain-messages) in the LangChain documentation.
|
||||
:::
|
||||
|
||||
!!! tip "Using custom agent state"
|
||||
|
||||
You can provide additional fields defined in your agent’s state schema directly in the input dictionary. This allows dynamic behavior based on runtime data or prior tool outputs.
|
||||
:::python
|
||||
You can provide additional fields defined in your agent's state schema directly in the input dictionary. This allows dynamic behavior based on runtime data or prior tool outputs.
|
||||
See the [context guide](./context.md) for full details.
|
||||
:::
|
||||
|
||||
:::js
|
||||
You can provide additional fields defined in your agent's state directly in the state definition. This allows dynamic behavior based on runtime data or prior tool outputs.
|
||||
See the [context guide](./context.md) for full details.
|
||||
:::
|
||||
|
||||
!!! note
|
||||
|
||||
:::python
|
||||
A string input for `messages` is converted to a [HumanMessage](https://python.langchain.com/docs/concepts/messages/#humanmessage). This behavior differs from the `prompt` parameter in `create_react_agent`, which is interpreted as a [SystemMessage](https://python.langchain.com/docs/concepts/messages/#systemmessage) when passed as a string.
|
||||
:::
|
||||
|
||||
:::js
|
||||
A string input for `messages` is converted to a [HumanMessage](https://js.langchain.com/docs/concepts/messages/#humanmessage). This behavior differs from the `prompt` parameter in `createReactAgent`, which is interpreted as a [SystemMessage](https://js.langchain.com/docs/concepts/messages/#systemmessage) when passed as a string.
|
||||
:::
|
||||
|
||||
## Output format
|
||||
|
||||
:::python
|
||||
Agent output is a dictionary containing:
|
||||
|
||||
- `messages`: A list of all messages exchanged during execution (user input, assistant replies, tool invocations).
|
||||
- Optionally, `structured_response` if [structured output](./agents.md#6-configure-structured-output) is configured.
|
||||
- If using a custom `state_schema`, additional keys corresponding to your defined fields may also be present in the output. These can hold updated state values from tool execution or prompt logic.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Agent output is a dictionary containing:
|
||||
|
||||
- `messages`: A list of all messages exchanged during execution (user input, assistant replies, tool invocations).
|
||||
- Optionally, `structuredResponse` if [structured output](./agents.md#6-configure-structured-output) is configured.
|
||||
- If using a custom state definition, additional keys corresponding to your defined fields may also be present in the output. These can hold updated state values from tool execution or prompt logic.
|
||||
:::
|
||||
|
||||
See the [context guide](./context.md) for more details on working with custom state schemas and accessing context.
|
||||
|
||||
@@ -87,6 +155,7 @@ Agents support streaming responses for more responsive applications. This includ
|
||||
|
||||
Streaming is available in both sync and async modes:
|
||||
|
||||
:::python
|
||||
=== "Sync streaming"
|
||||
|
||||
```python
|
||||
@@ -107,14 +176,36 @@ Streaming is available in both sync and async modes:
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
for await (const chunk of agent.stream(
|
||||
{ messages: [{ role: "user", content: "what is the weather in sf" }] },
|
||||
{ streamMode: "updates" }
|
||||
)) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! tip
|
||||
|
||||
For full details, see the [streaming guide](../how-tos/streaming.md).
|
||||
|
||||
## Max iterations
|
||||
|
||||
:::python
|
||||
To control agent execution and avoid infinite loops, set a recursion limit. This defines the maximum number of steps the agent can take before raising a `GraphRecursionError`. You can configure `recursion_limit` at runtime or when defining agent via `.with_config()`:
|
||||
:::
|
||||
|
||||
:::js
|
||||
To control agent execution and avoid infinite loops, set a recursion limit. This defines the maximum number of steps the agent can take before raising a `GraphRecursionError`. You can configure `recursionLimit` at runtime or when defining agent via `.withConfig()`:
|
||||
:::
|
||||
|
||||
:::python
|
||||
=== "Runtime"
|
||||
|
||||
```python
|
||||
@@ -163,6 +254,70 @@ To control agent execution and avoid infinite loops, set a recursion limit. This
|
||||
print("Agent stopped due to max iterations.")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
=== "Runtime"
|
||||
|
||||
```typescript
|
||||
import { GraphRecursionError } from "@langchain/langgraph";
|
||||
import { ChatAnthropic } from "@langchain/langgraph/prebuilt";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
|
||||
const maxIterations = 3;
|
||||
// highlight-next-line
|
||||
const recursionLimit = 2 * maxIterations + 1;
|
||||
const agent = createReactAgent({
|
||||
llm: new ChatAnthropic({ model: "claude-3-5-haiku-latest" }),
|
||||
tools: [getWeather]
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await agent.invoke(
|
||||
{"messages": [{"role": "user", "content": "what's the weather in sf"}]},
|
||||
// highlight-next-line
|
||||
{ recursionLimit }
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof GraphRecursionError) {
|
||||
console.log("Agent stopped due to max iterations.");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "`.withConfig()`"
|
||||
|
||||
```typescript
|
||||
import { GraphRecursionError } from "@langchain/langgraph";
|
||||
import { ChatAnthropic } from "@langchain/langgraph/prebuilt";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
|
||||
const maxIterations = 3;
|
||||
// highlight-next-line
|
||||
const recursionLimit = 2 * maxIterations + 1;
|
||||
const agent = createReactAgent({
|
||||
llm: new ChatAnthropic({ model: "claude-3-5-haiku-latest" }),
|
||||
tools: [getWeather]
|
||||
});
|
||||
// highlight-next-line
|
||||
const agentWithRecursionLimit = agent.withConfig({ recursionLimit });
|
||||
|
||||
try {
|
||||
const response = await agentWithRecursionLimit.invoke(
|
||||
{"messages": [{"role": "user", "content": "what's the weather in sf"}]},
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof GraphRecursionError) {
|
||||
console.log("Agent stopped due to max iterations.");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
## Additional Resources
|
||||
|
||||
* [Async programming in LangChain](https://python.langchain.com/docs/concepts/async)
|
||||
- [Async programming in LangChain](https://python.langchain.com/docs/concepts/async)
|
||||
:::
|
||||
|
||||
@@ -31,7 +31,7 @@ Agent Chat UI has full support for [human-in-the-loop](../concepts/human_in_the_
|
||||
|
||||
!!! Important
|
||||
|
||||
Agent Chat UI works best if your LangGraph agent interrupts using the [`HumanInterrupt` schema][langgraph.prebuilt.interrupt.HumanInterrupt]. If you do not use that schema, the Agent Chat UI will be able to render the input passed to the `interrupt` function, but it will not have full support for resuming your graph.
|
||||
Agent Chat UI works best if your LangGraph agent interrupts using the @[`HumanInterrupt` schema][HumanInterrupt]. If you do not use that schema, the Agent Chat UI will be able to render the input passed to the `interrupt` function, but it will not have full support for resuming your graph.
|
||||
|
||||
## Generative UI
|
||||
|
||||
|
||||
@@ -99,8 +99,8 @@ Starting from the `LangGraph Platform` view...
|
||||
1. In the top-right corner, select the gear icon (`Deployment Settings`).
|
||||
1. Update the `Git Branch` to the desired branch.
|
||||
1. Check/uncheck checkbox to `Automatically update deployment on push to branch`.
|
||||
1. Branch creation/deletion and tag creation/deletion events will not trigger an update. Only pushes to an existing branch will trigger an update.
|
||||
1. Pushes in quick succession to a branch will not trigger subsequent updates. In the future, this functionality may be changed/improved.
|
||||
1. Branch creation/deletion and tag creation/deletion events will not trigger an update. Only pushes to an existing branch will trigger an update.
|
||||
1. Pushes in quick succession to a branch will queue subsequent updates. Once a build completes, the most recent commit will begin building and the other queued builds will be skipped.
|
||||
|
||||
## Add or Remove GitHub Repositories
|
||||
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
# Egress for Subscription Metrics and Operational Metadata
|
||||
|
||||
> **Important: Self Hosted Only**
|
||||
> This section only applies to customers who are not running in offline mode and assumes you are using a self-hosted LangGraph Platform instance.
|
||||
> This does not apply to SaaS or Hybrid deployments.
|
||||
|
||||
Self-Hosted LangGraph Platform instances store all information locally and will never send sensitive information outside of your network. We currently only track platform usage for billing purposes according to the entitlements in your order. In order to better remotely support our customers, we do require egress to `https://beacon.langchain.com`.
|
||||
|
||||
In the future, we will be introducing support diagnostics to help us ensure that the LangGraph Platform is running at an optimal level within your environment.
|
||||
|
||||
> **Warning**
|
||||
> **This will require egress to `https://beacon.langchain.com` from your network.**
|
||||
> **If using an API key, you will also need to allow egress to `https://api.smith.langchain.com` or `https://eu.api.smith.langchain.com` for API key verification.**
|
||||
|
||||
Generally, data that we send to Beacon can be categorized as follows:
|
||||
|
||||
- **Subscription Metrics**
|
||||
- Subscription metrics are used to determine level of access and utilization of LangSmith. This includes, but are not limited to:
|
||||
- Nodes Executed
|
||||
- Runs Executed
|
||||
- License Key Verification
|
||||
- **Operational Metadata**
|
||||
- This metadata will contain and collect the above subscription metrics to assist with remote support, allowing the LangChain team to diagnose and troubleshoot performance issues more effectively and proactively.
|
||||
|
||||
## Example Payloads
|
||||
|
||||
In an effort to maximize transparency, we provide sample payloads here:
|
||||
|
||||
### License Verification (If using an Enterprise License)
|
||||
|
||||
**Endpoint:**
|
||||
|
||||
`POST beacon.langchain.com/v1/beacon/verify`
|
||||
|
||||
**Request:**
|
||||
|
||||
```json
|
||||
{
|
||||
"license": "<YOUR_LICENSE_KEY>"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "Valid JWT" // Short-lived JWT token to avoid repeated license checks
|
||||
}
|
||||
```
|
||||
|
||||
### Api Key Verification (If using a LangSmith API Key)
|
||||
|
||||
**Endpoint:**
|
||||
`POST api.smith.langchain.com/auth`
|
||||
|
||||
**Request:**
|
||||
|
||||
```json
|
||||
"Headers": {
|
||||
X-Api-Key: <YOUR_API_KEY>
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"org_config": {
|
||||
"org_id": "3a1c2b6f-4430-4b92-8a5b-79b8b567bbc1",
|
||||
... // Additional organization details
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Usage Reporting
|
||||
|
||||
**Endpoint:**
|
||||
|
||||
`POST beacon.langchain.com/v1/metadata/submit`
|
||||
|
||||
**Request:**
|
||||
|
||||
```json
|
||||
{
|
||||
"license": "<YOUR_LICENSE_KEY>",
|
||||
"from_timestamp": "2025-01-06T09:00:00Z",
|
||||
"to_timestamp": "2025-01-06T10:00:00Z",
|
||||
"tags": {
|
||||
"langgraph.python.version": "0.1.0",
|
||||
"langgraph_api.version": "0.2.0",
|
||||
"langgraph.platform.revision": "abc123",
|
||||
"langgraph.platform.variant": "standard",
|
||||
"langgraph.platform.host": "host-1",
|
||||
"langgraph.platform.tenant_id": "3a1c2b6f-4430-4b92-8a5b-79b8b567bbc1",
|
||||
"langgraph.platform.project_id": "c5b5f53a-4716-4326-8967-d4f7f7799735",
|
||||
"langgraph.platform.plan": "enterprise",
|
||||
"user_app.uses_indexing": "true",
|
||||
"user_app.uses_custom_app": "false",
|
||||
"user_app.uses_custom_auth": "true",
|
||||
"user_app.uses_thread_ttl": "true",
|
||||
"user_app.uses_store_ttl": "false"
|
||||
},
|
||||
"measures": {
|
||||
"langgraph.platform.runs": 150,
|
||||
"langgraph.platform.nodes": 450
|
||||
},
|
||||
"logs": []
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
"204 No Content"
|
||||
```
|
||||
|
||||
## Our Commitment
|
||||
|
||||
LangChain will not store any sensitive information in the Subscription Metrics or Operational Metadata. Any data collected will not be shared with a third party. If you have any concerns about the data being sent, please reach out to your account team.
|
||||
@@ -23,6 +23,8 @@ Before deploying, review the [conceptual guide for the Self-Hosted Control Plane
|
||||
|
||||
kubectl get storageclass
|
||||
|
||||
1. Egress to `https://beacon.langchain.com` from your network. This is required for license verification and usage reporting if not running in air-gapped mode. See the [Egress documentation](../../cloud/deployment/egress.md) for more details.
|
||||
|
||||
## Setup
|
||||
|
||||
1. As part of configuring your Self-Hosted LangSmith instance, you enable the `langgraphPlatform` option. This will provision a few key resources.
|
||||
|
||||
@@ -35,7 +35,6 @@ Before deploying, review the [conceptual guide for the Self-Hosted Data Plane](.
|
||||
1. Configure your `langgraph-dataplane-values.yaml` file.
|
||||
|
||||
config:
|
||||
langgraphPlatformLicenseKey: "" # Your LangGraph Platform license key
|
||||
langsmithApiKey: "" # API Key of your Workspace
|
||||
langsmithWorkspaceId: "" # Workspace ID
|
||||
hostBackendUrl: "https://api.host.langchain.com" # Only override this if on EU
|
||||
|
||||
@@ -95,7 +95,7 @@ my-app/
|
||||
|
||||
## Define Graphs
|
||||
|
||||
Implement your graphs! Graphs can be defined in a single file or multiple files. Make note of the variable names of each [CompiledStateGraph][langgraph.graph.state.CompiledStateGraph] to be included in the LangGraph application. The variable names will be used later when creating the [LangGraph configuration file](../reference/cli.md#configuration-file).
|
||||
Implement your graphs! Graphs can be defined in a single file or multiple files. Make note of the variable names of each @[CompiledStateGraph][CompiledStateGraph] to be included in the LangGraph application. The variable names will be used later when creating the [LangGraph configuration file](../reference/cli.md#configuration-file).
|
||||
|
||||
Example `agent.py` file, which shows how to import from other modules you define (code for the modules is not shown here, please see [this repository](https://github.com/langchain-ai/langgraph-example) to see their implementation):
|
||||
|
||||
@@ -108,11 +108,11 @@ from langgraph.graph import StateGraph, END, START
|
||||
from my_agent.utils.nodes import call_model, should_continue, tool_node # import nodes
|
||||
from my_agent.utils.state import AgentState # import state
|
||||
|
||||
# Define the config
|
||||
class GraphConfig(TypedDict):
|
||||
# Define the runtime context
|
||||
class GraphContext(TypedDict):
|
||||
model_name: Literal["anthropic", "openai"]
|
||||
|
||||
workflow = StateGraph(AgentState, config_schema=GraphConfig)
|
||||
workflow = StateGraph(AgentState, context_schema=GraphContext)
|
||||
workflow.add_node("agent", call_model)
|
||||
workflow.add_node("action", tool_node)
|
||||
workflow.add_edge(START, "agent")
|
||||
|
||||
@@ -108,7 +108,7 @@ my-app/
|
||||
|
||||
## Define Graphs
|
||||
|
||||
Implement your graphs! Graphs can be defined in a single file or multiple files. Make note of the variable names of each [CompiledStateGraph][langgraph.graph.state.CompiledStateGraph] to be included in the LangGraph application. The variable names will be used later when creating the [LangGraph configuration file](../reference/cli.md#configuration-file).
|
||||
Implement your graphs! Graphs can be defined in a single file or multiple files. Make note of the variable names of each @[CompiledStateGraph][CompiledStateGraph] to be included in the LangGraph application. The variable names will be used later when creating the [LangGraph configuration file](../reference/cli.md#configuration-file).
|
||||
|
||||
Example `agent.py` file, which shows how to import from other modules you define (code for the modules is not shown here, please see [this repository](https://github.com/langchain-ai/langgraph-example-pyproject) to see their implementation):
|
||||
|
||||
@@ -121,11 +121,11 @@ from langgraph.graph import StateGraph, END, START
|
||||
from my_agent.utils.nodes import call_model, should_continue, tool_node # import nodes
|
||||
from my_agent.utils.state import AgentState # import state
|
||||
|
||||
# Define the config
|
||||
class GraphConfig(TypedDict):
|
||||
# Define the runtime context
|
||||
class GraphContext(TypedDict):
|
||||
model_name: Literal["anthropic", "openai"]
|
||||
|
||||
workflow = StateGraph(AgentState, config_schema=GraphConfig)
|
||||
workflow = StateGraph(AgentState, context_schema=GraphContext)
|
||||
workflow.add_node("agent", call_model)
|
||||
workflow.add_node("action", tool_node)
|
||||
workflow.add_edge(START, "agent")
|
||||
|
||||
@@ -21,9 +21,9 @@ Before deploying, review the [conceptual guide for the Standalone Container](../
|
||||
|
||||
`<database_name_1>` and `database_name_2` are different databases within the same instance, but `<hostname_1>` is shared. **The same database cannot be used for separate deployments**.
|
||||
|
||||
1. `LANGSMITH_API_KEY`: (if using [Lite](../../concepts/langgraph_server.md#server-versions)) LangSmith API key. This will be used to authenticate ONCE at server start up.
|
||||
1. `LANGGRAPH_CLOUD_LICENSE_KEY`: (if using [Enterprise](../../concepts/langgraph_data_plane.md#licensing)) LangGraph Platform license key. This will be used to authenticate ONCE at server start up.
|
||||
1. `LANGSMITH_ENDPOINT`: To send traces to a [self-hosted LangSmith](https://docs.smith.langchain.com/self_hosting) instance, set `LANGSMITH_ENDPOINT` to the hostname of the self-hosted LangSmith instance.
|
||||
1. Egress to `https://beacon.langchain.com` from your network. This is required for license verification and usage reporting if not running in air-gapped mode. See the [Egress documentation](../../cloud/deployment/egress.md) for more details.
|
||||
|
||||
## Kubernetes (Helm)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
To review, edit, and approve tool calls in an agent or workflow, use LangGraph's [human-in-the-loop](../../concepts/human_in_the_loop.md) features.
|
||||
|
||||
## LangGraph API invoke & resume
|
||||
## Dynamic interrupts
|
||||
|
||||
=== "Python"
|
||||
|
||||
@@ -30,9 +30,7 @@ To review, edit, and approve tool calls in an agent or workflow, use LangGraph's
|
||||
# > [
|
||||
# > {
|
||||
# > 'value': {'text_to_revise': 'original text'},
|
||||
# > 'resumable': True,
|
||||
# > 'ns': ['human_node:fc722478-2f21-0578-c572-d9fc4dd07c3b'],
|
||||
# > 'when': 'during'
|
||||
# > 'id': '...',
|
||||
# > }
|
||||
# > ]
|
||||
|
||||
@@ -203,9 +201,7 @@ To review, edit, and approve tool calls in an agent or workflow, use LangGraph's
|
||||
# > [
|
||||
# > {
|
||||
# > 'value': {'text_to_revise': 'original text'},
|
||||
# > 'resumable': True,
|
||||
# > 'ns': ['human_node:fc722478-2f21-0578-c572-d9fc4dd07c3b'],
|
||||
# > 'when': 'during'
|
||||
# > 'id': '...',
|
||||
# > }
|
||||
# > ]
|
||||
|
||||
@@ -305,6 +301,185 @@ To review, edit, and approve tool calls in an agent or workflow, use LangGraph's
|
||||
}"
|
||||
```
|
||||
|
||||
## Static interrupts
|
||||
|
||||
Static interrupts (also known as static breakpoints) are triggered either before or after a node executes.
|
||||
|
||||
!!! warning
|
||||
|
||||
Static interrupts are **not** recommended for human-in-the-loop workflows. They are best used for debugging and testing.
|
||||
|
||||
You can set static interrupts by specifying `interrupt_before` and `interrupt_after` at compile time:
|
||||
|
||||
```python
|
||||
# highlight-next-line
|
||||
graph = graph_builder.compile( # (1)!
|
||||
# highlight-next-line
|
||||
interrupt_before=["node_a"], # (2)!
|
||||
# highlight-next-line
|
||||
interrupt_after=["node_b", "node_c"], # (3)!
|
||||
)
|
||||
```
|
||||
|
||||
1. The breakpoints are set during `compile` time.
|
||||
2. `interrupt_before` specifies the nodes where execution should pause before the node is executed.
|
||||
3. `interrupt_after` specifies the nodes where execution should pause after the node is executed.
|
||||
|
||||
Alternatively, you can set static interrupts at run time:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
# highlight-next-line
|
||||
await client.runs.wait( # (1)!
|
||||
thread_id,
|
||||
assistant_id,
|
||||
inputs=inputs,
|
||||
# highlight-next-line
|
||||
interrupt_before=["node_a"], # (2)!
|
||||
# highlight-next-line
|
||||
interrupt_after=["node_b", "node_c"] # (3)!
|
||||
)
|
||||
```
|
||||
|
||||
1. `client.runs.wait` is called with the `interrupt_before` and `interrupt_after` parameters. This is a run-time configuration and can be changed for every invocation.
|
||||
2. `interrupt_before` specifies the nodes where execution should pause before the node is executed.
|
||||
3. `interrupt_after` specifies the nodes where execution should pause after the node is executed.
|
||||
|
||||
=== "JavaScript"
|
||||
|
||||
```js
|
||||
// highlight-next-line
|
||||
await client.runs.wait( // (1)!
|
||||
threadID,
|
||||
assistantID,
|
||||
{
|
||||
input: input,
|
||||
// highlight-next-line
|
||||
interruptBefore: ["node_a"], // (2)!
|
||||
// highlight-next-line
|
||||
interruptAfter: ["node_b", "node_c"] // (3)!
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
1. `client.runs.wait` is called with the `interruptBefore` and `interruptAfter` parameters. This is a run-time configuration and can be changed for every invocation.
|
||||
2. `interruptBefore` specifies the nodes where execution should pause before the node is executed.
|
||||
3. `interruptAfter` specifies the nodes where execution should pause after the node is executed.
|
||||
|
||||
=== "cURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"interrupt_before\": [\"node_a\"],
|
||||
\"interrupt_after\": [\"node_b\", \"node_c\"],
|
||||
\"input\": <INPUT>
|
||||
}"
|
||||
```
|
||||
|
||||
The following example shows how to add static interrupts:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
|
||||
# Using the graph deployed with the name "agent"
|
||||
assistant_id = "agent"
|
||||
|
||||
# create a thread
|
||||
thread = await client.threads.create()
|
||||
thread_id = thread["thread_id"]
|
||||
|
||||
# Run the graph until the breakpoint
|
||||
result = await client.runs.wait(
|
||||
thread_id,
|
||||
assistant_id,
|
||||
input=inputs # (1)!
|
||||
)
|
||||
|
||||
# Resume the graph
|
||||
await client.runs.wait(
|
||||
thread_id,
|
||||
assistant_id,
|
||||
input=None # (2)!
|
||||
)
|
||||
```
|
||||
|
||||
1. The graph is run until the first breakpoint is hit.
|
||||
2. The graph is resumed by passing in `None` for the input. This will run the graph until the next breakpoint is hit.
|
||||
|
||||
=== "JavaScript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
|
||||
// Using the graph deployed with the name "agent"
|
||||
const assistantID = "agent";
|
||||
|
||||
// create a thread
|
||||
const thread = await client.threads.create();
|
||||
const threadID = thread["thread_id"];
|
||||
|
||||
// Run the graph until the breakpoint
|
||||
const result = await client.runs.wait(
|
||||
threadID,
|
||||
assistantID,
|
||||
{ input: input } // (1)!
|
||||
);
|
||||
|
||||
// Resume the graph
|
||||
await client.runs.wait(
|
||||
threadID,
|
||||
assistantID,
|
||||
{ input: null } // (2)!
|
||||
);
|
||||
```
|
||||
|
||||
1. The graph is run until the first breakpoint is hit.
|
||||
2. The graph is resumed by passing in `null` for the input. This will run the graph until the next breakpoint is hit.
|
||||
|
||||
=== "cURL"
|
||||
|
||||
Create a thread:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{}'
|
||||
```
|
||||
|
||||
Run the graph until the breakpoint:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": <INPUT>
|
||||
}"
|
||||
```
|
||||
|
||||
Resume the graph:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\"
|
||||
}"
|
||||
```
|
||||
|
||||
|
||||
## Learn more
|
||||
|
||||
- [Human-in-the-loop conceptual guide](../../concepts/human_in_the_loop.md): learn more about LangGraph human-in-the-loop features.
|
||||
|
||||
@@ -2,21 +2,20 @@
|
||||
|
||||
In this guide we will show how to create, configure, and manage an [assistant](../../concepts/assistants.md).
|
||||
|
||||
First, as a brief refresher on the concept of configurations, consider the following simple `call_model` node and configuration schema. Observe that this node tries to read and use the `model_name` as defined by the `config` object's `configurable`.
|
||||
First, as a brief refresher on the concept of runtime context, consider the following simple `call_model` node and context schema. Observe that this node tries to read and use the `model_provider` as defined by the `Runtime` object's `context` property.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class ContextSchema:
|
||||
llm_provider: str = "anthropic"
|
||||
|
||||
class ConfigSchema(TypedDict):
|
||||
model_name: str
|
||||
builder = StateGraph(AgentState, context_schema=ContextSchema)
|
||||
|
||||
builder = StateGraph(AgentState, config_schema=ConfigSchema)
|
||||
|
||||
def call_model(state, config):
|
||||
def call_model(state, runtime: Runtime[ContextSchema]):
|
||||
messages = state["messages"]
|
||||
model_name = config.get('configurable', {}).get("model_name", "anthropic")
|
||||
model = _get_model(model_name)
|
||||
model = _get_model(runtime.context.llm_provider)
|
||||
response = model.invoke(messages)
|
||||
# We return a list, because this will get added to the existing list
|
||||
return {"messages": [response]}
|
||||
@@ -44,7 +43,9 @@ First, as a brief refresher on the concept of configurations, consider the follo
|
||||
}
|
||||
```
|
||||
|
||||
For more information on configurations, [see here](../../concepts/low_level.md#configuration).
|
||||
:::python
|
||||
For more information on runtime context, [see here](../../concepts/low_level.md#runtime-context).
|
||||
:::
|
||||
|
||||
## Create an assistant
|
||||
|
||||
@@ -328,4 +329,4 @@ If you now run your graph and pass in this assistant id, it will use the first v
|
||||
If using LangGraph Studio, to set the active version of your assistant, click the "Manage Assistants" button and locate the assistant you would like to use. Select the assistant and the version, and then click the "Active" toggle. This will update the assistant to make the selected version active.
|
||||
|
||||
!!! warning "Deleting Assistants"
|
||||
Deleting as assistant will delete ALL of its versions. There is currently no way to delete a single version, but by pointing your assistant to the correct version you can skip any versions that you don't wish to use.
|
||||
Deleting as assistant will delete ALL of its versions. There is currently no way to delete a single version, but by pointing your assistant to the correct version you can skip any versions that you don't wish to use.
|
||||
|
||||
@@ -30,17 +30,33 @@ export default {
|
||||
|
||||
Next, define your UI components in your `langgraph.json` configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"node_version": "20",
|
||||
"graphs": {
|
||||
"agent": "./src/agent/index.ts:graph"
|
||||
},
|
||||
"ui": {
|
||||
"agent": "./src/agent/ui.tsx"
|
||||
}
|
||||
}
|
||||
```
|
||||
=== "Python agent"
|
||||
|
||||
```json title="langgraph.json"
|
||||
{
|
||||
"node_version": "20",
|
||||
"graphs": {
|
||||
"agent": "./src/agent.py:graph"
|
||||
},
|
||||
"ui": {
|
||||
"agent": "./src/agent/ui.tsx"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "JS agent"
|
||||
|
||||
```json title="langgraph.json"
|
||||
{
|
||||
"node_version": "20",
|
||||
"graphs": {
|
||||
"agent": "./src/agent/index.ts:graph"
|
||||
},
|
||||
"ui": {
|
||||
"agent": "./src/agent/ui.tsx"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `ui` section points to the UI components that will be used by graphs. By default, we recommend using the same key as the graph name, but you can split out the components however you like, see [Customise the namespace of UI components](#customise-the-namespace-of-ui-components) for more details.
|
||||
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
# Set breakpoints using Server API
|
||||
|
||||
[Breakpoints](../../concepts/breakpoints.md) pause graph execution at defined points and let you step through each stage. They use LangGraph's [**persistence layer**](../../concepts/persistence.md), which saves the graph state after each step.
|
||||
|
||||
With breakpoints, you can inspect the graph's state and node inputs at any point. Execution pauses indefinitely until you resume, as the checkpointer preserves the state.
|
||||
|
||||
!!! tip
|
||||
|
||||
For conceptual information on breakpoints, see [Breakpoints](../../concepts/breakpoints.md).
|
||||
|
||||
## Set static breakpoints
|
||||
|
||||
Static breakpoints are triggered either before or after a node executes. You can set static breakpoints by specifying `interrupt_before` and `interrupt_after` at compile time or run time.
|
||||
|
||||
=== "Compile time"
|
||||
|
||||
```python
|
||||
# highlight-next-line
|
||||
graph = graph_builder.compile( # (1)!
|
||||
# highlight-next-line
|
||||
interrupt_before=["node_a"], # (2)!
|
||||
# highlight-next-line
|
||||
interrupt_after=["node_b", "node_c"], # (3)!
|
||||
)
|
||||
```
|
||||
|
||||
1. The breakpoints are set during `compile` time.
|
||||
2. `interrupt_before` specifies the nodes where execution should pause before the node is executed.
|
||||
3. `interrupt_after` specifies the nodes where execution should pause after the node is executed.
|
||||
|
||||
=== "Run time"
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
# highlight-next-line
|
||||
await client.runs.wait( # (1)!
|
||||
thread_id,
|
||||
assistant_id,
|
||||
inputs=inputs,
|
||||
# highlight-next-line
|
||||
interrupt_before=["node_a"], # (2)!
|
||||
# highlight-next-line
|
||||
interrupt_after=["node_b", "node_c"] # (3)!
|
||||
)
|
||||
```
|
||||
|
||||
1. `client.runs.wait` is called with the `interrupt_before` and `interrupt_after` parameters. This is a run-time configuration and can be changed for every invocation.
|
||||
2. `interrupt_before` specifies the nodes where execution should pause before the node is executed.
|
||||
3. `interrupt_after` specifies the nodes where execution should pause after the node is executed.
|
||||
|
||||
=== "JavaScript"
|
||||
|
||||
```js
|
||||
// highlight-next-line
|
||||
await client.runs.wait( // (1)!
|
||||
threadID,
|
||||
assistantID,
|
||||
{
|
||||
input: input,
|
||||
// highlight-next-line
|
||||
interruptBefore: ["node_a"], // (2)!
|
||||
// highlight-next-line
|
||||
interruptAfter: ["node_b", "node_c"] // (3)!
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
1. `client.runs.wait` is called with the `interruptBefore` and `interruptAfter` parameters. This is a run-time configuration and can be changed for every invocation.
|
||||
2. `interruptBefore` specifies the nodes where execution should pause before the node is executed.
|
||||
3. `interruptAfter` specifies the nodes where execution should pause after the node is executed.
|
||||
|
||||
=== "cURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"interrupt_before\": [\"node_a\"],
|
||||
\"interrupt_after\": [\"node_b\", \"node_c\"],
|
||||
\"input\": <INPUT>
|
||||
}"
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
This example shows how to add **static** breakpoints. See [Use breakpoints](../../how-tos/human_in_the_loop/breakpoints.md) for more options on adding breakpoints.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
|
||||
# Using the graph deployed with the name "agent"
|
||||
assistant_id = "agent"
|
||||
|
||||
# create a thread
|
||||
thread = await client.threads.create()
|
||||
thread_id = thread["thread_id"]
|
||||
|
||||
# Run the graph until the breakpoint
|
||||
result = await client.runs.wait(
|
||||
thread_id,
|
||||
assistant_id,
|
||||
input=inputs # (1)!
|
||||
)
|
||||
|
||||
# Resume the graph
|
||||
await client.runs.wait(
|
||||
thread_id,
|
||||
assistant_id,
|
||||
input=None # (2)!
|
||||
)
|
||||
```
|
||||
|
||||
1. The graph is run until the first breakpoint is hit.
|
||||
2. The graph is resumed by passing in `None` for the input. This will run the graph until the next breakpoint is hit.
|
||||
|
||||
=== "JavaScript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
|
||||
// Using the graph deployed with the name "agent"
|
||||
const assistantID = "agent";
|
||||
|
||||
// create a thread
|
||||
const thread = await client.threads.create();
|
||||
const threadID = thread["thread_id"];
|
||||
|
||||
// Run the graph until the breakpoint
|
||||
const result = await client.runs.wait(
|
||||
threadID,
|
||||
assistantID,
|
||||
{ input: input } // (1)!
|
||||
);
|
||||
|
||||
// Resume the graph
|
||||
await client.runs.wait(
|
||||
threadID,
|
||||
assistantID,
|
||||
{ input: null } // (2)!
|
||||
);
|
||||
```
|
||||
|
||||
1. The graph is run until the first breakpoint is hit.
|
||||
2. The graph is resumed by passing in `null` for the input. This will run the graph until the next breakpoint is hit.
|
||||
|
||||
=== "cURL"
|
||||
|
||||
Create a thread:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{}'
|
||||
```
|
||||
|
||||
Run the graph until the breakpoint:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": <INPUT>
|
||||
}"
|
||||
```
|
||||
|
||||
Resume the graph:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\"
|
||||
}"
|
||||
```
|
||||
@@ -4,11 +4,11 @@ LangGraph provides the [**time travel**](../../concepts/time-travel.md) function
|
||||
|
||||
To time travel using the LangGraph Server API (via the LangGraph SDK):
|
||||
|
||||
1. **Run the graph** with initial inputs using [LangGraph SDK](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/)'s [`client.runs.wait`][langgraph_sdk.client.RunsClient.wait] or [`client.runs.stream`][langgraph_sdk.client.RunsClient.stream] APIs.
|
||||
2. **Identify a checkpoint in an existing thread**: Use [`client.threads.get_history`][langgraph_sdk.client.ThreadsClient.get_history] method to retrieve the execution history for a specific `thread_id` and locate the desired `checkpoint_id`.
|
||||
1. **Run the graph** with initial inputs using [LangGraph SDK](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/)'s @[`client.runs.wait`][client.runs.wait] or @[`client.runs.stream`][client.runs.stream] APIs.
|
||||
2. **Identify a checkpoint in an existing thread**: Use @[`client.threads.get_history`][client.threads.get_history] method to retrieve the execution history for a specific `thread_id` and locate the desired `checkpoint_id`.
|
||||
Alternatively, set a [breakpoint](./human_in_the_loop_breakpoint.md) before the node(s) where you want execution to pause. You can then find the most recent checkpoint recorded up to that breakpoint.
|
||||
3. **(Optional) modify the graph state**: Use the [`client.threads.update_state`][langgraph_sdk.client.ThreadsClient.update_state] method to modify the graph’s state at the checkpoint and resume execution from alternative state.
|
||||
4. **Resume execution from the checkpoint**: Use the [`client.runs.wait`][langgraph_sdk.client.RunsClient.wait] or [`client.runs.stream`][langgraph_sdk.client.RunsClient.stream] APIs with an input of `None` and the appropriate `thread_id` and `checkpoint_id`.
|
||||
3. **(Optional) modify the graph state**: Use the @[`client.threads.update_state`][client.threads.update_state] method to modify the graph’s state at the checkpoint and resume execution from alternative state.
|
||||
4. **Resume execution from the checkpoint**: Use the @[`client.runs.wait`][client.runs.wait] or @[`client.runs.stream`][client.runs.stream] APIs with an input of `None` and the appropriate `thread_id` and `checkpoint_id`.
|
||||
|
||||
## Use time travel in a workflow
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ Click the dropdown next to "Submit" and click the toggle to enable/disable strea
|
||||
To run your graph with breakpoints, click the "Interrupt" button. Select a node and whether to pause before and/or after that node has executed. Click "Continue" in the thread log to resume execution.
|
||||
|
||||
|
||||
For more information on breakpoints see [here](../../concepts/breakpoints.md).
|
||||
For more information on breakpoints see [here](../../concepts/human_in_the_loop.md).
|
||||
|
||||
### Submit run
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ const thread = useStream<{ messages: Message[] }>({
|
||||
|
||||
You can also manually manage the resuming process by using the run callbacks to persist the run metadata and the `joinStream` function to resume the stream. Make sure to pass `streamResumable: true` when creating the run; otherwise some events might be lost.
|
||||
|
||||
````tsx
|
||||
```tsx
|
||||
import type { Message } from "@langchain/langgraph-sdk";
|
||||
import { useStream } from "@langchain/langgraph-sdk/react";
|
||||
import { useCallback, useState, useEffect, useRef } from "react";
|
||||
@@ -236,7 +236,7 @@ const thread = useStream<{ messages: Message[] }>({
|
||||
threadId: threadId,
|
||||
onThreadId: setThreadId,
|
||||
});
|
||||
````
|
||||
```
|
||||
|
||||
We recommend storing the `threadId` in your URL's query parameters to let users resume conversations after page refreshes.
|
||||
|
||||
|
||||
@@ -140,6 +140,22 @@ https://my-server.app/my-webhook-endpoint?token=YOUR_SECRET_TOKEN
|
||||
|
||||
Your server should extract and validate this token before processing requests.
|
||||
|
||||
## Disable webhooks
|
||||
|
||||
As of `langgraph-api>=0.2.78`, developers can disable webhooks in the `langgraph.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"http": {
|
||||
"disable_webhooks": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This feature is primarily intended for self-hosted deployments, where platform administrators or developers may prefer to disable webhooks to simplify their security posture—especially if they are not configuring firewall rules or other network controls. Disabling webhooks helps prevent untrusted payloads from being sent to internal endpoints.
|
||||
|
||||
For full configuration details, refer to the [configuration file reference](https://langchain-ai.github.io/langgraph/cloud/reference/cli/?h=disable_webhooks#configuration-file).
|
||||
|
||||
## Test webhooks
|
||||
|
||||
You can test your webhook using online services like:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -409,6 +409,8 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
|
||||
| Option | Default | Description |
|
||||
| ---------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| `--wait` | | Wait for services to start before returning. Implies --detach |
|
||||
| `--base-image TEXT` | `langchain/langgraph-api` | Base image to use for the LangGraph API server. Pin to specific versions using version tags. |
|
||||
| `--image TEXT` | | Docker image to use for the langgraph-api service. If specified, skips building and uses this image directly. |
|
||||
| `--postgres-uri TEXT` | Local database | Postgres URI to use for the database. |
|
||||
| `--watch` | | Restart on file changes |
|
||||
| `--debugger-base-url TEXT` | `http://127.0.0.1:[PORT]` | URL used by the debugger to access LangGraph API. |
|
||||
@@ -436,6 +438,8 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
|
||||
| Option | Default | Description |
|
||||
| ---------------------------------------------------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| <span style="white-space: nowrap;">`--wait`</span> | | Wait for services to start before returning. Implies --detach |
|
||||
| <span style="white-space: nowrap;">`--base-image TEXT`</span> | <span style="white-space: nowrap;">`langchain/langgraph-api`</span> | Base image to use for the LangGraph API server. Pin to specific versions using version tags. |
|
||||
| <span style="white-space: nowrap;">`--image TEXT`</span> | | Docker image to use for the langgraph-api service. If specified, skips building and uses this image directly. |
|
||||
| <span style="white-space: nowrap;">`--postgres-uri TEXT`</span> | Local database | Postgres URI to use for the database. |
|
||||
| <span style="white-space: nowrap;">`--watch`</span> | | Restart on file changes |
|
||||
| <span style="white-space: nowrap;">`-c, --config FILE`</span> | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. |
|
||||
@@ -479,19 +483,19 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
|
||||
|
||||
RUN PIP_CONFIG_FILE=/pipconfig.txt PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt langchain_community langchain_anthropic langchain_openai wikipedia scikit-learn
|
||||
|
||||
ADD ./graphs /deps/__outer_graphs/src
|
||||
ADD ./graphs /deps/outer-graphs/src
|
||||
RUN set -ex && \
|
||||
for line in '[project]' \
|
||||
'name = "graphs"' \
|
||||
'version = "0.1"' \
|
||||
'[tool.setuptools.package-data]' \
|
||||
'"*" = ["**/*"]'; do \
|
||||
echo "$line" >> /deps/__outer_graphs/pyproject.toml; \
|
||||
echo "$line" >> /deps/outer-graphs/pyproject.toml; \
|
||||
done
|
||||
|
||||
RUN PIP_CONFIG_FILE=/pipconfig.txt PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
|
||||
|
||||
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_graphs/src/agent.py:graph", "storm": "/deps/__outer_graphs/src/storm.py:graph"}'
|
||||
ENV LANGSERVE_GRAPHS='{"agent": "/deps/outer-graphs/src/agent.py:graph", "storm": "/deps/outer-graphs/src/storm.py:graph"}'
|
||||
```
|
||||
|
||||
???+ note "Updating your langgraph.json file"
|
||||
|
||||
@@ -10,6 +10,10 @@ This environment variable should be set to `True` if the implementation of a gra
|
||||
|
||||
Defaults to `False`.
|
||||
|
||||
## `BG_JOB_SHUTDOWN_GRACE_PERIOD_SECS`
|
||||
|
||||
Specifies, in seconds, how long the server will wait for background jobs to finish after the queue receives a shutdown signal. After this period, the server will force termination. Defaults to `180` seconds. Set this to ensure jobs have enough time to complete cleanly during shutdown. Added in `langgraph-api==0.2.16`.
|
||||
|
||||
## `BG_JOB_TIMEOUT_SECS`
|
||||
|
||||
The timeout of a background run can be increased. However, the infrastructure for a Cloud SaaS deployment enforces a 1 hour timeout limit for API requests. This means the connection between client and server will timeout after 1 hour. This is not configurable.
|
||||
@@ -18,16 +22,15 @@ A background run can execute for longer than 1 hour, but a client must reconnect
|
||||
|
||||
Defaults to `3600`.
|
||||
|
||||
## `BG_JOB_SHUTDOWN_GRACE_PERIOD_SECS`
|
||||
|
||||
Specifies, in seconds, how long the server will wait for background jobs to finish after the queue receives a shutdown signal. After this period, the server will force termination. Defaults to `3600` seconds. Set this to ensure jobs have enough time to complete cleanly during shutdown. Added in `langgraph-api==0.2.16`.
|
||||
|
||||
## `DD_API_KEY`
|
||||
|
||||
Specify `DD_API_KEY` (your [Datadog API Key](https://docs.datadoghq.com/account_management/api-app-keys/)) to automatically enable Datadog tracing for the deployment. Specify other [`DD_*` environment variables](https://ddtrace.readthedocs.io/en/stable/configuration.html) to configure the tracing instrumentation.
|
||||
|
||||
If `DD_API_KEY` is specified, the application process is wrapped in the [`ddtrace-run` command](https://ddtrace.readthedocs.io/en/stable/installation_quickstart.html). Other `DD_*` environment variables (e.g. `DD_SITE`, `DD_ENV`, `DD_SERVICE`, `DD_TRACE_ENABLED`) are typically needed to properly configure the tracing instrumentation. See [`DD_*` environment variables](https://ddtrace.readthedocs.io/en/stable/configuration.html) for more details.
|
||||
|
||||
!!! note
|
||||
Enabling `DD_API_KEY` (and thus `ddtrace-run`) can override or interfere with other auto-instrumentation solutions (such as OpenTelemetry) that you may have instrumented into your application code.
|
||||
|
||||
## `LANGCHAIN_TRACING_SAMPLING_RATE`
|
||||
|
||||
Sampling rate for traces sent to LangSmith. Valid values: Any float between `0` and `1`.
|
||||
@@ -40,6 +43,14 @@ Type of authentication for the LangGraph Server deployment. Valid values: `langs
|
||||
|
||||
For deployments to LangGraph Platform, this environment variable is set automatically. For local development or deployments where authentication is handled externally (e.g. self-hosted), set this environment variable to `noop`.
|
||||
|
||||
## `LANGGRAPH_POSTGRES_POOL_MAX_SIZE`
|
||||
|
||||
Beginning with langgraph-api version `0.2.12`, the maximum size of the Postgres connection pool (per replica) can be controlled using the `LANGGRAPH_POSTGRES_POOL_MAX_SIZE` environment variable. By setting this variable, you can determine the upper bound on the number of simultaneous connections the server will establish with the Postgres database.
|
||||
|
||||
For example, if a deployment is scaled up to 10 replicas and `LANGGRAPH_POSTGRES_POOL_MAX_SIZE` is configured to `150`, then up to `1500` connections to Postgres can be established. This is particularly useful for deployments where database resources are limited (or more available) or where you need to tune connection behavior for performance or scaling reasons.
|
||||
|
||||
Defaults to `150` connections.
|
||||
|
||||
## `LANGSMITH_RUNS_ENDPOINTS`
|
||||
|
||||
For deployments with [self-hosted LangSmith](https://docs.smith.langchain.com/self_hosting) only.
|
||||
@@ -54,6 +65,10 @@ Set `LANGSMITH_TRACING` to `false` to disable tracing to LangSmith.
|
||||
|
||||
Defaults to `true`.
|
||||
|
||||
## `LOG_COLOR`
|
||||
|
||||
This is mainly relevant in the context of using the dev server via the `langgraph dev` command. Set `LOG_COLOR` to `true` to enable ANSI-colored console output when using the default console renderer. Disabling color output by setting this variable to `false` produces monochrome logs. Defaults to `true`.
|
||||
|
||||
## `LOG_LEVEL`
|
||||
|
||||
Configure [log level](https://docs.python.org/3/library/logging.html#logging-levels). Defaults to `INFO`.
|
||||
@@ -62,9 +77,14 @@ Configure [log level](https://docs.python.org/3/library/logging.html#logging-lev
|
||||
|
||||
Set `LOG_JSON` to `true` to render all log messages as JSON objects using the configured `JSONRenderer`. This produces structured logs that can be easily parsed or ingested by log management systems. Defaults to `false`.
|
||||
|
||||
## `LOG_COLOR`
|
||||
## `MOUNT_PREFIX`
|
||||
|
||||
This is mainly relevant in the context of using the dev server via the `langgraph dev` command. Set `LOG_COLOR` to `true` to enable ANSI-colored console output when using the default console renderer. Disabling color output by setting this variable to `false` produces monochrome logs. Defaults to `true`.
|
||||
!!! info "Only Allowed in Self-Hosted Deployments"
|
||||
The `MOUNT_PREFIX` environment variable is only allowed in Self-Hosted Deployment models, LangGraph Platform SaaS will not allow this environment variable.
|
||||
|
||||
Set `MOUNT_PREFIX` to serve the LangGraph Server under a specific path prefix. This is useful for deployments where the server is behind a reverse proxy or load balancer that requires a specific path prefix.
|
||||
|
||||
For example, if the server is to be served under `https://example.com/langgraph`, set `MOUNT_PREFIX` to `/langgraph`.
|
||||
|
||||
## `N_JOBS_PER_WORKER`
|
||||
|
||||
@@ -94,16 +114,14 @@ Database Connectivity:
|
||||
|
||||
- The custom Postgres instance must be accessible by the LangGraph Server. The user is responsible for ensuring connectivity.
|
||||
|
||||
## `LANGGRAPH_POSTGRES_POOL_MAX_SIZE`
|
||||
## `REDIS_CLUSTER`
|
||||
|
||||
Beginning with langgraph-api version `0.2.12`, the maximum size of the Postgres connection pool can be controlled using the `LANGGRAPH_POSTGRES_POOL_MAX_SIZE` environment variable. By setting this variable, you can determine the upper bound on the number of simultaneous connections the server will establish with the Postgres database. This is particularly useful for deployments where database resources are limited (or more available) or where you need to tune connection behavior for performance or scaling reasons. If not specified, the pool size defaults to 150 connections.
|
||||
!!! info "Only Allowed in Self-Hosted Deployments"
|
||||
Redis Cluster mode is only available in Self-Hosted Deployment models, LangGraph Platform SaaS will provision a redis instance for you by default.
|
||||
|
||||
## `REDIS_URI_CUSTOM`
|
||||
Set `REDIS_CLUSTER` to `True` to enable Redis Cluster mode. When enabled, the system will connect to Redis using cluster mode. This is useful when connecting to a Redis Cluster deployment.
|
||||
|
||||
!!! info "Only for Self-Hosted Data Plane and Self-Hosted Control Plane"
|
||||
Custom Redis instances are only available for [Self-Hosted Data Plane](../../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
|
||||
Specify `REDIS_URI_CUSTOM` to use a custom Redis instance. The value of `REDIS_URI_CUSTOM` must be a valid [Redis connection URI](https://redis-py.readthedocs.io/en/stable/connections.html#redis.Redis.from_url).
|
||||
Defaults to `False`.
|
||||
|
||||
## `REDIS_KEY_PREFIX`
|
||||
|
||||
@@ -114,20 +132,19 @@ Specify a prefix for Redis keys. This allows multiple LangGraph Server instances
|
||||
|
||||
Defaults to `''`.
|
||||
|
||||
## `REDIS_CLUSTER`
|
||||
## `REDIS_URI_CUSTOM`
|
||||
|
||||
!!! info "Only Allowed in Self-Hosted Deployments"
|
||||
Redis Cluster mode is only available in Self-Hosted Deployment models, LangGraph Platform SaaS will provision a redis instance for you by default.
|
||||
!!! info "Only for Self-Hosted Data Plane and Self-Hosted Control Plane"
|
||||
Custom Redis instances are only available for [Self-Hosted Data Plane](../../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
|
||||
Set `REDIS_CLUSTER` to `True` to enable Redis Cluster mode. When enabled, the system will connect to Redis using cluster mode. This is useful when connecting to a Redis Cluster deployment.
|
||||
Specify `REDIS_URI_CUSTOM` to use a custom Redis instance. The value of `REDIS_URI_CUSTOM` must be a valid [Redis connection URI](https://redis-py.readthedocs.io/en/stable/connections.html#redis.Redis.from_url).
|
||||
|
||||
Defaults to `False`.
|
||||
## `RESUMABLE_STREAM_TTL_SECONDS`
|
||||
|
||||
## `MOUNT_PREFIX`
|
||||
Time-to-live in seconds for resumable stream data in Redis.
|
||||
|
||||
!!! info "Only Allowed in Self-Hosted Deployments"
|
||||
The `MOUNT_PREFIX` environment variable is only allowed in Self-Hosted Deployment models, LangGraph Platform SaaS will not allow this environment variable.
|
||||
When a run is created and the output is streamed, the stream can be configured to be resumable (e.g. `stream_resumable=True`). If a stream is resumable, output from the stream is temporarily stored in Redis. The TTL for this data can be configured by setting `RESUMABLE_STREAM_TTL_SECONDS`.
|
||||
|
||||
Set `MOUNT_PREFIX` to serve the LangGraph Server under a specific path prefix. This is useful for deployments where the server is behind a reverse proxy or load balancer that requires a specific path prefix.
|
||||
See the [Python](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/#langgraph_sdk.client.RunsClient.stream) and [JS/TS](https://langchain-ai.github.io/langgraphjs/reference/classes/sdk_client.RunsClient.html#stream) SDKs for more details on how to implement resumable streams.
|
||||
|
||||
For example, if the server is to be served under `https://example.com/langgraph`, set `MOUNT_PREFIX` to `/langgraph`.
|
||||
Defaults to `120` seconds.
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
# LangGraph Server Changelog
|
||||
|
||||
> **Note:** This changelog is no longer actively maintained. For the most up-to-date LangGraph Server changelog, please visit our new documentation site: [LangGraph Server Changelog](https://docs.langchain.com/langgraph-platform/langgraph-server-changelog#langgraph-server-changelog)
|
||||
|
||||
[LangGraph Server](../../concepts/langgraph_server.md) is an API platform for creating and managing agent-based applications. It provides built-in persistence, a task queue, and supports deploying, configuring, and running assistants (agentic workflows) at scale. This changelog documents all notable updates, features, and fixes to LangGraph Server releases.
|
||||
|
||||
---
|
||||
|
||||
## v0.2.111 (2025-07-29)
|
||||
- Started the heartbeat immediately upon connection to prevent JS graph streaming errors during long startups.
|
||||
|
||||
## v0.2.110 (2025-07-29)
|
||||
- Added interrupts as default values for all operations except streams to maintain consistent behavior.
|
||||
|
||||
## v0.2.109 (2025-07-28)
|
||||
- Fixed an issue where missing config schema occurred when `config_type` was not set.
|
||||
|
||||
## v0.2.108 (2025-07-28)
|
||||
- Added compatibility for langgraph v0.6, including new context API support and a migration to enhance context handling in assistant operations.
|
||||
|
||||
## v0.2.107 (2025-07-27)
|
||||
- Implemented caching for authentication processes to improve performance.
|
||||
- Merged count and select queries to improve database query efficiency.
|
||||
|
||||
## v0.2.106 (2025-07-27)
|
||||
- Log whether run uses resumable streams.
|
||||
|
||||
## v0.2.105 (2025-07-27)
|
||||
- Added a `/heapdump` endpoint to capture and save JS process heap data.
|
||||
|
||||
## v0.2.103 (2025-07-25)
|
||||
- Corrected the metadata endpoint to ensure accurate data retrieval.
|
||||
|
||||
## v0.2.102 (2025-07-24)
|
||||
- Captured interrupt events in the wait method to preserve legacy behavior and stream updates by default.
|
||||
- Added support for SDK structlog in the JavaScript environment, enhancing logging capabilities.
|
||||
|
||||
## v0.2.101 (2025-07-24)
|
||||
- Used the correct metadata endpoint for self-hosted environments, resolving an access issue.
|
||||
|
||||
## v0.2.99 (2025-07-22)
|
||||
- Improved license validation by adding an in-memory cache and handling Redis connection errors more effectively.
|
||||
- Automatically remove agents from memory that are removed from `langgraph.json` to prevent persistence issues.
|
||||
- Ensured the UI namespace for generated UI is a valid JavaScript property name to prevent errors.
|
||||
- Raised a 422 error for improved request validation feedback.
|
||||
|
||||
## v0.2.98 (2025-07-19)
|
||||
- Added langgraph node context for improved log filtering and trace visibility.
|
||||
|
||||
## v0.2.97 (2025-07-19)
|
||||
- Fixed scheduling issue with ckpt ingestion worker that occurred on isolated background loops.
|
||||
- Ensured queue worker starts only after all migrations have completed.
|
||||
- Added more detailed error messages for thread state issues and improved response handling when state updates fail.
|
||||
- Exposed interrupt ID while retrieving thread state for enhanced API response details.
|
||||
|
||||
## v0.2.96 (2025-07-17)
|
||||
- Added a fallback mechanism for configurable header patterns to handle exclude/include settings more effectively.
|
||||
|
||||
## v0.2.95 (2025-07-17)
|
||||
- Avoided setting the future if it is already done to prevent redundant operations.
|
||||
- Resolved compatibility errors in CI by switching from `typing.TypedDict` to `typing_extensions.TypedDict` for Python versions below 3.12.
|
||||
|
||||
## v0.2.94 (2025-07-16)
|
||||
- Improved performance by omitting pending sends for langgraph versions 0.5 and above.
|
||||
- Improved server startup logs to provide clearer warnings when the DD_API_KEY environment variable is set.
|
||||
|
||||
## v0.2.93 (2025-07-16)
|
||||
- Removed the GIN index for run metadata to improve performance.
|
||||
|
||||
## v0.2.92 (2025-07-16)
|
||||
- Enabled copying functionality for blobs and checkpoints, improving data management flexibility.
|
||||
|
||||
## v0.2.91 (2025-07-16)
|
||||
- Reduced writes to the `checkpoint_blobs` table by inlining small values (null, numeric, str, etc.). This means we don't need to store extra values for channels that haven't been updated.
|
||||
|
||||
## v0.2.90 (2025-07-16)
|
||||
- Improve checkpoint writes via node-local background queueing.
|
||||
|
||||
|
||||
## v0.2.89 (2025-07-15)
|
||||
- Decoupled checkpoint writing from thread/run state by removing foreign keys and updated logger to prevent timeout-related failures.
|
||||
|
||||
## v0.2.88 (2025-07-14)
|
||||
- Removed the foreign key constraint for `thread` in the `run` table to simplify database schema.
|
||||
|
||||
## v0.2.87 (2025-07-14)
|
||||
- Added more detailed logs for Redis worker signaling to improve debugging.
|
||||
|
||||
## v0.2.86 (2025-07-11)
|
||||
- Honored tool descriptions in the `/mcp` endpoint to align with expected functionality.
|
||||
|
||||
## v0.2.85 (2025-07-10)
|
||||
- Added support for the `on_disconnect` field to `runs/wait` and included disconnect logs for better debugging.
|
||||
|
||||
## v0.2.84 (2025-07-09)
|
||||
- Removed unnecessary status updates to streamline thread handling and updated version to 0.2.84.
|
||||
|
||||
## v0.2.83 (2025-07-09)
|
||||
- Reduced the default time-to-live for resumable streams to 2 minutes.
|
||||
- Enhanced data submission logic to send data to both Beacon and LangSmith instance based on license configuration.
|
||||
- Enabled submission of self-hosted data to a Langsmith instance when the endpoint is configured.
|
||||
|
||||
## v0.2.82 (2025-07-03)
|
||||
- Addressed a race condition in background runs by implementing a lock using join, ensuring reliable execution across CTEs.
|
||||
|
||||
## v0.2.81 (2025-07-03)
|
||||
- Optimized run streams by reducing initial wait time to improve responsiveness for older or non-existent runs.
|
||||
|
||||
## v0.2.80 (2025-07-03)
|
||||
- Corrected parameter passing in the `logger.ainfo()` API call to resolve a TypeError.
|
||||
|
||||
## v0.2.79 (2025-07-02)
|
||||
- Fixed a JsonDecodeError in checkpointing with remote graph by correcting JSON serialization to handle trailing slashes properly.
|
||||
- Introduced a configuration flag to disable webhooks globally across all routes.
|
||||
|
||||
## v0.2.78 (2025-07-02)
|
||||
- Added timeout retries to webhook calls to improve reliability.
|
||||
- Added HTTP request metrics, including a request count and latency histogram, for enhanced monitoring capabilities.
|
||||
|
||||
## v0.2.77 (2025-07-02)
|
||||
- Added HTTP metrics to improve performance monitoring.
|
||||
- Changed the Redis cache delimiter to reduce conflicts with subgraph message names and updated caching behavior.
|
||||
|
||||
## v0.2.76 (2025-07-01)
|
||||
- Updated Redis cache delimiter to prevent conflicts with subgraph messages.
|
||||
|
||||
## v0.2.74 (2025-06-30)
|
||||
- Scheduled webhooks in an isolated loop to ensure thread-safe operations and prevent errors with PYTHONASYNCIODEBUG=1.
|
||||
|
||||
## v0.2.73 (2025-06-27)
|
||||
- Fixed an infinite frame loop issue and removed the dict_parser due to structlog's unexpected behavior.
|
||||
- Throw a 409 error on deadlock occurrence during run cancellations to handle lock conflicts gracefully.
|
||||
|
||||
## v0.2.72 (2025-06-27)
|
||||
- Ensured compatibility with future langgraph versions.
|
||||
- Implemented a 409 response status to handle deadlock issues during cancellation.
|
||||
|
||||
## v0.2.71 (2025-06-26)
|
||||
- Improved logging for better clarity and detail regarding log types.
|
||||
|
||||
## v0.2.70 (2025-06-26)
|
||||
- Improved error handling to better distinguish and log TimeoutErrors caused by users from internal run timeouts.
|
||||
|
||||
## v0.2.69 (2025-06-26)
|
||||
- Added sorting and pagination to the crons API and updated schema definitions for improved accuracy.
|
||||
|
||||
## v0.2.66 (2025-06-26)
|
||||
- Fixed a 404 error when creating multiple runs with the same thread_id using `on_not_exist="create"`.
|
||||
|
||||
## v0.2.65 (2025-06-25)
|
||||
- Ensured that only fields from `assistant_versions` are returned when necessary.
|
||||
- Ensured consistent data types for in-memory and PostgreSQL users, improving internal authentication handling.
|
||||
|
||||
## v0.2.64 (2025-06-24)
|
||||
- Added descriptions to version entries for better clarity.
|
||||
|
||||
## v0.2.62 (2025-06-23)
|
||||
- Improved user handling for custom authentication in the JS Studio.
|
||||
- Added Prometheus-format run statistics to the metrics endpoint for better monitoring.
|
||||
- Added run statistics in Prometheus format to the metrics endpoint.
|
||||
|
||||
## v0.2.61 (2025-06-20)
|
||||
- Set a maximum idle time for Redis connections to prevent unnecessary open connections.
|
||||
|
||||
## v0.2.60 (2025-06-20)
|
||||
- Enhanced error logging to include traceback details for dictionary operations.
|
||||
- Added a `/metrics` endpoint to expose queue worker metrics for monitoring.
|
||||
|
||||
## v0.2.57 (2025-06-18)
|
||||
- Removed CancelledError from retriable exceptions to allow local interrupts while maintaining retriability for workers.
|
||||
- Introduced middleware to gracefully shut down the server after completing in-flight requests upon receiving a SIGINT.
|
||||
- Reduced metadata stored in checkpoint to only include necessary information.
|
||||
- Improved error handling in join runs to return error details when present.
|
||||
|
||||
## v0.2.56 (2025-06-17)
|
||||
- Improved application stability by adding a handler for SIGTERM signals.
|
||||
|
||||
## v0.2.55 (2025-06-17)
|
||||
- Improved the handling of cancellations in the queue entrypoint.
|
||||
- Improved cancellation handling in the queue entry point.
|
||||
|
||||
## v0.2.54 (2025-06-16)
|
||||
- Enhanced error message for LuaLock timeout during license validation.
|
||||
- Fixed the $contains filter in custom auth by requiring an explicit ::text cast and updated tests accordingly.
|
||||
- Ensured project and tenant IDs are formatted as UUIDs for consistency.
|
||||
|
||||
## v0.2.53 (2025-06-13)
|
||||
- Resolved a timing issue to ensure the queue starts only after the graph is registered.
|
||||
- Improved performance by setting thread and run status in a single query and enhanced error handling during checkpoint writes.
|
||||
- Reduced the default background grace period to 3 minutes.
|
||||
|
||||
## v0.2.52 (2025-06-12)
|
||||
- Now logging expected graphs when one is omitted to improve traceability.
|
||||
- Implemented a time-to-live (TTL) feature for resumable streams.
|
||||
- Improved query efficiency and consistency by adding a unique index and optimizing row locking.
|
||||
|
||||
## v0.2.51 (2025-06-12)
|
||||
- Handled `CancelledError` by marking tasks as ready to retry, improving error management in worker processes.
|
||||
- Added LG API version and request ID to metadata and logs for better tracking.
|
||||
- Added LG API version and request ID to metadata and logs to improve traceability.
|
||||
- Improved database performance by creating indexes concurrently.
|
||||
- Ensured postgres write is committed only after the Redis running marker is set to prevent race conditions.
|
||||
- Enhanced query efficiency and reliability by adding a unique index on thread_id/running, optimizing row locks, and ensuring deterministic run selection.
|
||||
- Resolved a race condition by ensuring Postgres updates only occur after the Redis running marker is set.
|
||||
|
||||
## v0.2.46 (2025-06-07)
|
||||
- Introduced a new connection for each operation while preserving transaction characteristics in Threads state `update()` and `bulk()` commands.
|
||||
|
||||
## v0.2.45 (2025-06-05)
|
||||
- Enhanced streaming feature by incorporating tracing contexts.
|
||||
- Removed an unnecessary query from the Crons.search function.
|
||||
- Resolved connection reuse issue when scheduling next run for multiple cron jobs.
|
||||
- Removed an unnecessary query in the Crons.search function to improve efficiency.
|
||||
- Resolved an issue with scheduling the next cron run by improving connection reuse.
|
||||
|
||||
## v0.2.44 (2025-06-04)
|
||||
- Enhanced the worker logic to exit the pipeline before continuing when the Redis message limit is reached.
|
||||
- Introduced a ceiling for Redis message size with an option to skip messages larger than 128 MB for improved performance.
|
||||
- Ensured the pipeline always closes properly to prevent resource leaks.
|
||||
|
||||
## v0.2.43 (2025-06-04)
|
||||
- Improved performance by omitting logs in metadata calls and ensuring output schema compliance in value streaming.
|
||||
- Ensured the connection is properly closed after use.
|
||||
- Aligned output format to strictly adhere to the specified schema.
|
||||
- Stopped sending internal logs in metadata requests to improve privacy.
|
||||
|
||||
## v0.2.42 (2025-06-04)
|
||||
- Added timestamps to track the start and end of a request's run.
|
||||
- Added tracer information to the configuration settings.
|
||||
- Added support for streaming with tracing contexts.
|
||||
|
||||
## v0.2.41 (2025-06-03)
|
||||
- Added locking mechanism to prevent errors in pipelined executions.
|
||||
@@ -22,8 +22,9 @@ To deploy using the LangGraph Platform, the following information should be prov
|
||||
|
||||
## File Structure
|
||||
|
||||
Below are examples of directory structures for Python and JavaScript applications:
|
||||
Below are examples of directory structures for applications:
|
||||
|
||||
:::python
|
||||
=== "Python (requirements.txt)"
|
||||
|
||||
```plaintext
|
||||
@@ -40,6 +41,7 @@ Below are examples of directory structures for Python and JavaScript application
|
||||
├── requirements.txt # package dependencies
|
||||
└── langgraph.json # configuration file for LangGraph
|
||||
```
|
||||
|
||||
=== "Python (pyproject.toml)"
|
||||
|
||||
```plaintext
|
||||
@@ -57,20 +59,24 @@ Below are examples of directory structures for Python and JavaScript application
|
||||
└── pyproject.toml # dependencies for your project
|
||||
```
|
||||
|
||||
=== "JS (package.json)"
|
||||
:::
|
||||
|
||||
```plaintext
|
||||
my-app/
|
||||
├── src # all project code lies within here
|
||||
│ ├── utils # optional utilities for your graph
|
||||
│ │ ├── tools.ts # tools for your graph
|
||||
│ │ ├── nodes.ts # node functions for your graph
|
||||
│ │ └── state.ts # state definition of your graph
|
||||
│ └── agent.ts # code for constructing your graph
|
||||
├── package.json # package dependencies
|
||||
├── .env # environment variables
|
||||
└── langgraph.json # configuration file for LangGraph
|
||||
```
|
||||
:::js
|
||||
|
||||
```plaintext
|
||||
my-app/
|
||||
├── src # all project code lies within here
|
||||
│ ├── utils # optional utilities for your graph
|
||||
│ │ ├── tools.ts # tools for your graph
|
||||
│ │ ├── nodes.ts # node functions for your graph
|
||||
│ │ └── state.ts # state definition of your graph
|
||||
│ └── agent.ts # code for constructing your graph
|
||||
├── package.json # package dependencies
|
||||
├── .env # environment variables
|
||||
└── langgraph.json # configuration file for LangGraph
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! note
|
||||
|
||||
@@ -88,52 +94,66 @@ See the [LangGraph configuration file reference](../cloud/reference/cli.md#confi
|
||||
|
||||
### Examples
|
||||
|
||||
=== "Python"
|
||||
:::python
|
||||
|
||||
* The dependencies involve a custom local package and the `langchain_openai` package.
|
||||
* A single graph will be loaded from the file `./your_package/your_file.py` with the variable `variable`.
|
||||
* The environment variables are loaded from the `.env` file.
|
||||
- The dependencies involve a custom local package and the `langchain_openai` package.
|
||||
- A single graph will be loaded from the file `./your_package/your_file.py` with the variable `variable`.
|
||||
- The environment variables are loaded from the `.env` file.
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": [
|
||||
"langchain_openai",
|
||||
"./your_package"
|
||||
],
|
||||
"graphs": {
|
||||
"my_agent": "./your_package/your_file.py:agent"
|
||||
},
|
||||
"env": "./.env"
|
||||
}
|
||||
```
|
||||
```json
|
||||
{
|
||||
"dependencies": ["langchain_openai", "./your_package"],
|
||||
"graphs": {
|
||||
"my_agent": "./your_package/your_file.py:agent"
|
||||
},
|
||||
"env": "./.env"
|
||||
}
|
||||
```
|
||||
|
||||
=== "JavaScript"
|
||||
:::
|
||||
|
||||
* The dependencies will be loaded from a dependency file in the local directory (e.g., `package.json`).
|
||||
* A single graph will be loaded from the file `./your_package/your_file.js` with the function `agent`.
|
||||
* The environment variable `OPENAI_API_KEY` is set inline.
|
||||
:::js
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": [
|
||||
"."
|
||||
],
|
||||
"graphs": {
|
||||
"my_agent": "./your_package/your_file.js:agent"
|
||||
},
|
||||
"env": {
|
||||
"OPENAI_API_KEY": "secret-key"
|
||||
}
|
||||
}
|
||||
```
|
||||
- The dependencies will be loaded from a dependency file in the local directory (e.g., `package.json`).
|
||||
- A single graph will be loaded from the file `./your_package/your_file.js` with the function `agent`.
|
||||
- The environment variable `OPENAI_API_KEY` is set inline.
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"my_agent": "./your_package/your_file.js:agent"
|
||||
},
|
||||
"env": {
|
||||
"OPENAI_API_KEY": "secret-key"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Dependencies
|
||||
|
||||
A LangGraph application may depend on other Python packages or JavaScript libraries (depending on the programming language in which the application is written).
|
||||
:::python
|
||||
A LangGraph application may depend on other Python packages.
|
||||
:::
|
||||
|
||||
:::js
|
||||
A LangGraph application may depend on other TypeScript/JavaScript libraries.
|
||||
:::
|
||||
|
||||
You will generally need to specify the following information for dependencies to be set up correctly:
|
||||
|
||||
:::python
|
||||
|
||||
1. A file in the directory that specifies the dependencies (e.g. `requirements.txt`, `pyproject.toml`, or `package.json`).
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
1. A file in the directory that specifies the dependencies (e.g. `package.json`).
|
||||
:::
|
||||
|
||||
2. A `dependencies` key in the [LangGraph configuration file](#configuration-file-concepts) that specifies the dependencies required to run the LangGraph application.
|
||||
3. Any additional binaries or system libraries can be specified using `dockerfile_lines` key in the [LangGraph configuration file](#configuration-file-concepts).
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Assistants
|
||||
|
||||
**Assistants** allow you to manage configurations (like prompts, LLM selection, tools) separately from your graph's core logic, enabling rapid changes that don't alter the graph architecture. It is a way to create multiple specialized versions of the same graph architecture, each optimized for different use cases through configuration variations rather than structural changes.
|
||||
**Assistants** allow you to manage configurations (like prompts, LLM selection, tools) separately from your graph's core logic, enabling rapid changes that don't alter the graph architecture. It is a way to create multiple specialized versions of the same graph architecture, each optimized for different use cases through context/configuration variations rather than structural changes.
|
||||
|
||||
For example, imagine a general-purpose writing agent built on a common graph architecture. While the structure remains the same, different writing styles—such as blog posts and tweets—require tailored configurations to optimize performance. To support these variations, you can create multiple assistants (e.g., one for blogs and another for tweets) that share the underlying graph but differ in model selection and system prompt.
|
||||
|
||||
@@ -14,8 +14,11 @@ The LangGraph Cloud API provides several endpoints for creating and managing ass
|
||||
|
||||
## Configuration
|
||||
|
||||
Assistants build on the LangGraph open source concept of [configuration](low_level.md#configuration).
|
||||
While configuration is available in the open source LangGraph library, assistants are only present in [LangGraph Platform](langgraph_platform.md). This is due to the fact that assistants are tightly coupled to your deployed graph. Upon deployment, LangGraph Server will automatically create a default assistant for each graph using the graph's default configuration settings.
|
||||
:::python
|
||||
Assistants build on the LangGraph open source concepts of configuration and [runtime context](low_level.md#runtime-context).
|
||||
:::
|
||||
|
||||
While these features are available in the open source LangGraph library, assistants are only present in [LangGraph Platform](langgraph_platform.md). This is due to the fact that assistants are tightly coupled to your deployed graph. Upon deployment, LangGraph Server will automatically create a default assistant for each graph using the graph's default context and configuration settings.
|
||||
|
||||
In practice, an assistant is just an _instance_ of a graph with a specific configuration. Therefore, multiple assistants can reference the same graph but can contain different configurations (e.g. prompts, models, tools). The LangGraph Server API provides several endpoints for creating and managing assistants. See the [API reference](../cloud/reference/api/api_ref.html) and [this how-to](../cloud/how-tos/configuration_cloud.md) for more details on how to create assistants.
|
||||
|
||||
@@ -26,6 +29,6 @@ Once you've created an assistant, subsequent edits to that assistant will create
|
||||
|
||||
## Execution
|
||||
|
||||
A **run** is an invocation of an assistant. Each run may have its own input, configuration, and metadata, which may affect execution and output of the underlying graph. A run can optionally be executed on a [thread](./persistence.md#threads).
|
||||
A **run** is an invocation of an assistant. Each run may have its own input, configuration, context, and metadata, which may affect execution and output of the underlying graph. A run can optionally be executed on a [thread](./persistence.md#threads).
|
||||
|
||||
The LangGraph Platform API provides several endpoints for creating and managing runs. See the [API reference](../cloud/reference/api/api_ref.html#tag/thread-runs/) for more details.
|
||||
|
||||
+355
-31
@@ -16,7 +16,13 @@ While often used interchangeably, these terms represent distinct security concep
|
||||
- [**Authentication**](#authentication) ("AuthN") verifies _who_ you are. This runs as middleware for every request.
|
||||
- [**Authorization**](#authorization) ("AuthZ") determines _what you can do_. This validates the user's privileges and roles on a per-resource basis.
|
||||
|
||||
:::python
|
||||
In LangGraph Platform, authentication is handled by your [`@auth.authenticate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler, and authorization is handled by your [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handlers.
|
||||
:::
|
||||
|
||||
:::js
|
||||
In LangGraph Platform, authentication is handled by your [`@auth.authenticate`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.authenticate) handler, and authorization is handled by your [`@auth.on`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.on) handlers.
|
||||
:::
|
||||
|
||||
## Default Security Models
|
||||
|
||||
@@ -29,7 +35,8 @@ LangGraph Platform provides different security defaults:
|
||||
- Can be customized with your auth handler
|
||||
|
||||
!!! note "Custom auth"
|
||||
Custom auth **is supported** for all plans in LangGraph Platform.
|
||||
|
||||
Custom auth **is supported** for all plans in LangGraph Platform.
|
||||
|
||||
### Self-Hosted
|
||||
|
||||
@@ -37,34 +44,30 @@ LangGraph Platform provides different security defaults:
|
||||
- Complete flexibility to implement your security model
|
||||
- You control all aspects of authentication and authorization
|
||||
|
||||
!!! note "Custom auth"
|
||||
Custom auth is supported for **Enterprise** self-hosted deployments.
|
||||
Standalone Container (Lite) deployments do not support custom auth natively.
|
||||
|
||||
## System Architecture
|
||||
|
||||
A typical authentication setup involves three main components:
|
||||
|
||||
1. **Authentication Provider** (Identity Provider/IdP)
|
||||
|
||||
* A dedicated service that manages user identities and credentials
|
||||
* Handles user registration, login, password resets, etc.
|
||||
* Issues tokens (JWT, session tokens, etc.) after successful authentication
|
||||
* Examples: Auth0, Supabase Auth, Okta, or your own auth server
|
||||
- A dedicated service that manages user identities and credentials
|
||||
- Handles user registration, login, password resets, etc.
|
||||
- Issues tokens (JWT, session tokens, etc.) after successful authentication
|
||||
- Examples: Auth0, Supabase Auth, Okta, or your own auth server
|
||||
|
||||
2. **LangGraph Backend** (Resource Server)
|
||||
|
||||
* Your LangGraph application that contains business logic and protected resources
|
||||
* Validates tokens with the auth provider
|
||||
* Enforces access control based on user identity and permissions
|
||||
* Doesn't store user credentials directly
|
||||
- Your LangGraph application that contains business logic and protected resources
|
||||
- Validates tokens with the auth provider
|
||||
- Enforces access control based on user identity and permissions
|
||||
- Doesn't store user credentials directly
|
||||
|
||||
3. **Client Application** (Frontend)
|
||||
|
||||
* Web app, mobile app, or API client
|
||||
* Collects time-sensitive user credentials and sends to auth provider
|
||||
* Receives tokens from auth provider
|
||||
* Includes these tokens in requests to LangGraph backend
|
||||
- Web app, mobile app, or API client
|
||||
- Collects time-sensitive user credentials and sends to auth provider
|
||||
- Receives tokens from auth provider
|
||||
- Includes these tokens in requests to LangGraph backend
|
||||
|
||||
Here's how these components typically interact:
|
||||
|
||||
@@ -84,15 +87,22 @@ sequenceDiagram
|
||||
LG-->>Client: 8. Return resources
|
||||
```
|
||||
|
||||
:::python
|
||||
Your [`@auth.authenticate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler in LangGraph handles steps 4-6, while your [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handlers implement step 7.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Your [`auth.authenticate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#authenticate) handler in LangGraph handles steps 4-6, while your [`auth.on`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#on>) handlers implement step 7.
|
||||
:::
|
||||
|
||||
## Authentication
|
||||
|
||||
:::python
|
||||
Authentication in LangGraph runs as middleware on every request. Your [`@auth.authenticate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler receives request information and should:
|
||||
|
||||
1. Validate the credentials
|
||||
2. Return [user info](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.MinimalUserDict) containing the user's identity and user information if valid
|
||||
3. Raise an [HTTP exception](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.exceptions.HTTPException) or AssertionError if invalid
|
||||
3. Raise an [HTTPException](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.exceptions.HTTPException) or AssertionError if invalid
|
||||
|
||||
```python
|
||||
from langgraph_sdk import Auth
|
||||
@@ -126,9 +136,49 @@ The returned user information is available:
|
||||
|
||||
- To your authorization handlers via [`ctx.user`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.AuthContext)
|
||||
- In your application via `config["configuration"]["langgraph_auth_user"]`
|
||||
:::
|
||||
|
||||
:::js
|
||||
Authentication in LangGraph runs as middleware on every request. Your [`authenticate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#authenticate>) handler receives request information and should:
|
||||
|
||||
1. Validate the credentials
|
||||
2. Return user information containing the user's identity and user information if valid
|
||||
3. Raise an [HTTPException](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#class-httpexception>) if invalid
|
||||
|
||||
```typescript
|
||||
import { Auth, HTTPException } from "@langchain/langgraph-sdk";
|
||||
|
||||
export const auth = new Auth();
|
||||
|
||||
auth.authenticate(async (request) => {
|
||||
// Validate credentials (e.g., API key, JWT token)
|
||||
const apiKey = request.headers.get("x-api-key");
|
||||
if (!apiKey || !isValidKey(apiKey)) {
|
||||
throw new HTTPException(401, "Invalid API key");
|
||||
}
|
||||
|
||||
// Return user info - only identity and isAuthenticated are required
|
||||
// Add any additional fields you need for authorization
|
||||
return {
|
||||
identity: "user-123", // Required: unique user identifier
|
||||
isAuthenticated: true, // Optional: assumed true by default
|
||||
permissions: ["read", "write"], // Optional: for permission-based auth
|
||||
// You can add more custom fields if you want to implement other auth patterns
|
||||
role: "admin",
|
||||
orgId: "org-456",
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
The returned user information is available:
|
||||
|
||||
- To your authorization handlers via the `user` property in a [callback handler](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#on)
|
||||
- In your application via `config.configurable.langgraph_auth_user`
|
||||
:::
|
||||
|
||||
??? tip "Supported Parameters"
|
||||
|
||||
:::python
|
||||
The [`@auth.authenticate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler can accept any of the following parameters by name:
|
||||
|
||||
* request (Request): The raw ASGI request object
|
||||
@@ -139,13 +189,27 @@ The returned user information is available:
|
||||
* query_params (dict[str, str]): URL query parameters, e.g., {"stream": "true"}
|
||||
* headers (dict[bytes, bytes]): Request headers
|
||||
* authorization (str | None): The Authorization header value (e.g., "Bearer <token>")
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
The [`authenticate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#authenticate) handler can accept any of the following parameters:
|
||||
|
||||
* request (Request): The raw request object
|
||||
* body (object): The parsed request body
|
||||
* path (string): The request path, e.g., "/threads/abcd-1234-abcd-1234/runs/abcd-1234-abcd-1234/stream"
|
||||
* method (string): The HTTP method, e.g., "GET"
|
||||
* pathParams (Record<string, string>): URL path parameters, e.g., {"threadId": "abcd-1234-abcd-1234", "runId": "abcd-1234-abcd-1234"}
|
||||
* queryParams (Record<string, string>): URL query parameters, e.g., {"stream": "true"}
|
||||
* headers (Record<string, string>): Request headers
|
||||
* authorization (string | null): The Authorization header value (e.g., "Bearer <token>")
|
||||
:::
|
||||
|
||||
In many of our tutorials, we will just show the "authorization" parameter to be concise, but you can opt to accept more information as needed
|
||||
to implement your custom authentication scheme.
|
||||
|
||||
### Agent authentication
|
||||
|
||||
Custom authentication permits delegated access. The values you return in `@auth.authenticate` are added to the run context, giving agents user-scoped credentials lets them access resources on the user’s behalf.
|
||||
Custom authentication permits delegated access. The values you return in `@auth.authenticate` are added to the run context, giving agents user-scoped credentials lets them access resources on the user’s behalf.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
@@ -177,7 +241,7 @@ sequenceDiagram
|
||||
ExternalService -->> LangGraph: 10. Service response
|
||||
|
||||
%% Return to caller
|
||||
LangGraph -->> ClientApp: 11. Return resources
|
||||
LangGraph -->> ClientApp: 11. Return resources
|
||||
```
|
||||
|
||||
After authentication, the platform creates a special configuration object that is passed to your graph and all nodes via the configurable context.
|
||||
@@ -193,13 +257,16 @@ For information on how to authenticate an agent to an MCP server, see the [MCP c
|
||||
|
||||
## Authorization
|
||||
|
||||
After authentication, LangGraph calls your [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handlers to control access to specific resources (e.g., threads, assistants, crons). These handlers can:
|
||||
After authentication, LangGraph calls your authorization handlers to control access to specific resources (e.g., threads, assistants, crons). These handlers can:
|
||||
|
||||
1. Add metadata to be saved during resource creation by mutating the `value["metadata"]` dictionary directly. See the [supported actions table](#supported-actions) for the list of types the value can take for each action.
|
||||
2. Filter resources by metadata during search/list or read operations by returning a [filter dictionary](#filter-operations).
|
||||
1. Add metadata to be saved during resource creation by mutating the metadata. See the [supported actions table](#supported-actions) for the list of types the value can take for each action.
|
||||
2. Filter resources by metadata during search/list or read operations by returning a [filter](#filter-operations).
|
||||
3. Raise an HTTP exception if access is denied.
|
||||
|
||||
If you want to just implement simple user-scoped access control, you can use a single [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handler for all resources and actions. If you want to have different control depending on the resource and action, you can use [resource-specific handlers](#resource-specific-handlers). See the [Supported Resources](#supported-resources) section for a full list of the resources that support access control.
|
||||
If you want to just implement simple user-scoped access control, you can use a single authorization handler for all resources and actions. If you want to have different control depending on the resource and action, you can use [resource-specific handlers](#resource-specific-handlers). See the [Supported Resources](#supported-resources) section for a full list of the resources that support access control.
|
||||
|
||||
:::python
|
||||
Your [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handlers control access by mutating the `value["metadata"]` dictionary directly and returning a [filter dictionary](#filter-operations).
|
||||
|
||||
```python
|
||||
@auth.on
|
||||
@@ -241,9 +308,42 @@ async def add_owner(
|
||||
return filters
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
You can granularly control access by mutating the `value.metadata` object directly and returning a [filter object](#filter-operations) when registering an [`on()`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#on) handler.
|
||||
|
||||
```typescript
|
||||
import { Auth, HTTPException } from "@langchain/langgraph-sdk/auth";
|
||||
|
||||
export const auth = new Auth()
|
||||
.authenticate(async (request: Request) => ({
|
||||
identity: "user-123",
|
||||
permissions: [],
|
||||
}))
|
||||
.on("*", ({ value, user }) => {
|
||||
// Create filter to restrict access to just this user's resources
|
||||
const filters = { owner: user.identity };
|
||||
|
||||
// If the operation supports metadata, add the user identity
|
||||
// as metadata to the resource.
|
||||
if ("metadata" in value) {
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
}
|
||||
|
||||
// Return filters to restrict access
|
||||
// These filters are applied to ALL operations (create, read, update, search, etc.)
|
||||
// to ensure users can only access their own resources
|
||||
return filters;
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Resource-Specific Handlers {#resource-specific-handlers}
|
||||
|
||||
You can register handlers for specific resources and actions by chaining the resource and action names together with the [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) decorator.
|
||||
You can register handlers for specific resources and actions by chaining the resource and action names together with the authorization decorator.
|
||||
When a request is made, the most specific handler that matches that resource and action is called. Below is an example of how to register handlers for specific resources and actions. For the following setup:
|
||||
|
||||
1. Authenticated users are able to create threads, read threads, and create runs on threads
|
||||
@@ -254,6 +354,8 @@ When a request is made, the most specific handler that matches that resource and
|
||||
|
||||
For a full list of supported resources and actions, see the [Supported Resources](#supported-resources) section below.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
# Generic / global handler catches calls that aren't handled by more specific handlers
|
||||
@auth.on
|
||||
@@ -338,11 +440,104 @@ async def on_assistant_create(
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { Auth, HTTPException } from "@langchain/langgraph-sdk/auth";
|
||||
|
||||
export const auth = new Auth()
|
||||
.authenticate(async (request: Request) => ({
|
||||
identity: "user-123",
|
||||
permissions: ["threads:write", "threads:read"],
|
||||
}))
|
||||
.on("*", ({ event, user }) => {
|
||||
console.log(`Request for ${event} by ${user.identity}`);
|
||||
throw new HTTPException(403, { message: "Forbidden" });
|
||||
})
|
||||
|
||||
// Matches the "threads" resource and all actions - create, read, update, delete, search
|
||||
// Since this is **more specific** than the generic `on("*")` handler, it will take precedence over the generic handler for all actions on the "threads" resource
|
||||
.on("threads", ({ permissions, value, user }) => {
|
||||
if (!permissions.includes("write")) {
|
||||
throw new HTTPException(403, {
|
||||
message: "User lacks the required permissions.",
|
||||
});
|
||||
}
|
||||
|
||||
// Not all events do include `metadata` property in `value`.
|
||||
// So we need to add this type guard.
|
||||
if ("metadata" in value) {
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
}
|
||||
|
||||
return { owner: user.identity };
|
||||
})
|
||||
|
||||
// Thread creation. This will match only on thread create actions.
|
||||
// Since this is **more specific** than both the generic `on("*")` handler and the `on("threads")` handler, it will take precedence for any "create" actions on the "threads" resources
|
||||
.on("threads:create", ({ value, user, permissions }) => {
|
||||
if (!permissions.includes("write")) {
|
||||
throw new HTTPException(403, {
|
||||
message: "User lacks the required permissions.",
|
||||
});
|
||||
}
|
||||
|
||||
// Setting metadata on the thread being created will ensure that the resource contains an "owner" field
|
||||
// Then any time a user tries to access this thread or runs within the thread,
|
||||
// we can filter by owner
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
|
||||
return { owner: user.identity };
|
||||
})
|
||||
|
||||
// Reading a thread. Since this is also more specific than the generic `on("*")` handler, and the `on("threads")` handler,
|
||||
.on("threads:read", ({ user }) => {
|
||||
// Since we are reading (and not creating) a thread,
|
||||
// we don't need to set metadata. We just need to
|
||||
// return a filter to ensure users can only see their own threads.
|
||||
return { owner: user.identity };
|
||||
})
|
||||
|
||||
// Run creation, streaming, updates, etc.
|
||||
// This takes precedence over the generic `on("*")` handler and the `on("threads")` handler
|
||||
.on("threads:create_run", ({ value, user }) => {
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
|
||||
return { owner: user.identity };
|
||||
})
|
||||
|
||||
// Assistant creation. This will match only on assistant create actions.
|
||||
// Since this is **more specific** than both the generic `on("*")` handler and the `on("assistants")` handler, it will take precedence for any "create" actions on the "assistants" resources
|
||||
.on("assistants:create", ({ value, user, permissions }) => {
|
||||
if (!permissions.includes("assistants:create")) {
|
||||
throw new HTTPException(403, {
|
||||
message: "User lacks the required permissions.",
|
||||
});
|
||||
}
|
||||
|
||||
// Setting metadata on the assistant being created will ensure that the resource contains an "owner" field.
|
||||
// Then any time a user tries to access this assistant, we can filter by owner
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
|
||||
return { owner: user.identity };
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Notice that we are mixing global and resource-specific handlers in the above example. Since each request is handled by the most specific handler, a request to create a `thread` would match the `on_thread_create` handler but NOT the `reject_unhandled_requests` handler. A request to `update` a thread, however would be handled by the global handler, since we don't have a more specific handler for that resource and action.
|
||||
|
||||
### Filter Operations {#filter-operations}
|
||||
|
||||
Authorization handlers can return `None`, a boolean, or a filter dictionary.
|
||||
:::python
|
||||
Authorization handlers can return different types of values:
|
||||
|
||||
- `None` and `True` mean "authorize access to all underling resources"
|
||||
- `False` means "deny access to all underling resources (raises a 403 exception)"
|
||||
- A metadata filter dictionary will restrict access to resources
|
||||
@@ -355,6 +550,24 @@ A filter dictionary is a dictionary with keys that match the resource metadata.
|
||||
|
||||
A dictionary with multiple keys is treated using a logical `AND` filter. For example, `{"owner": org_id, "allowed_users": {"$contains": user_id}}` will only match resources with metadata whose "owner" is `org_id` and whose "allowed_users" list contains `user_id`.
|
||||
See the reference [here](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.FilterType) for more information.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Authorization handlers can return different types of values:
|
||||
|
||||
- `null` and `true` mean "authorize access to all underling resources"
|
||||
- `false` means "deny access to all underling resources (raises a 403 exception)"
|
||||
- A metadata filter object will restrict access to resources
|
||||
|
||||
A filter object is an object with keys that match the resource metadata. It supports three operators:
|
||||
|
||||
- The default value is a shorthand for exact match, or "$eq", below. For example, `{ owner: userId}` will include only resources with metadata containing `{ owner: userId }`
|
||||
- `$eq`: Exact match (e.g., `{ owner: { $eq: userId } }`) - this is equivalent to the shorthand above, `{ owner: userId }`
|
||||
- `$contains`: List membership (e.g., `{ allowedUsers: { $contains: userId} }`) The value here must be an element of the list. The metadata in the stored resource must be a list/container type.
|
||||
|
||||
An object with multiple keys is treated using a logical `AND` filter. For example, `{ owner: orgId, allowedUsers: { $contains: userId} }` will only match resources with metadata whose "owner" is `orgId` and whose "allowedUsers" list contains `userId`.
|
||||
See the reference [here](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.FilterType) for more information.
|
||||
:::
|
||||
|
||||
## Common Access Patterns
|
||||
|
||||
@@ -364,6 +577,8 @@ Here are some typical authorization patterns:
|
||||
|
||||
This common pattern lets you scope all threads, assistants, crons, and runs to a single user. It's useful for common single-user use cases like regular chatbot-style apps.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
@auth.on
|
||||
async def owner_only(ctx: Auth.types.AuthContext, value: dict):
|
||||
@@ -372,10 +587,33 @@ async def owner_only(ctx: Auth.types.AuthContext, value: dict):
|
||||
return {"owner": ctx.user.identity}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
export const auth = new Auth()
|
||||
.authenticate(async (request: Request) => ({
|
||||
identity: "user-123",
|
||||
permissions: ["threads:write", "threads:read"],
|
||||
}))
|
||||
.on("*", ({ value, user }) => {
|
||||
if ("metadata" in value) {
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
}
|
||||
return { owner: user.identity };
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Permission-based Access
|
||||
|
||||
This pattern lets you control access based on **permissions**. It's useful if you want certain roles to have broader or more restricted access to resources.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
# In your auth handler:
|
||||
@auth.authenticate
|
||||
@@ -412,19 +650,72 @@ async def rbac_create(ctx: Auth.types.AuthContext, value: dict):
|
||||
return _default(ctx, value)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { Auth, HTTPException } from "@langchain/langgraph-sdk/auth";
|
||||
|
||||
export const auth = new Auth()
|
||||
.authenticate(async (request: Request) => ({
|
||||
identity: "user-123",
|
||||
// Define permissions in auth
|
||||
permissions: ["threads:write", "threads:read"],
|
||||
}))
|
||||
.on("threads:create", ({ value, user, permissions }) => {
|
||||
if (!permissions.includes("threads:write")) {
|
||||
throw new HTTPException(403, { message: "Unauthorized" });
|
||||
}
|
||||
|
||||
if ("metadata" in value) {
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
}
|
||||
return { owner: user.identity };
|
||||
})
|
||||
.on("threads:read", ({ user, permissions }) => {
|
||||
if (
|
||||
!permissions.includes("threads:read") &&
|
||||
!permissions.includes("threads:write")
|
||||
) {
|
||||
throw new HTTPException(403, { message: "Unauthorized" });
|
||||
}
|
||||
|
||||
return { owner: user.identity };
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Supported Resources
|
||||
|
||||
LangGraph provides three levels of authorization handlers, from most general to most specific:
|
||||
|
||||
:::python
|
||||
|
||||
1. **Global Handler** (`@auth.on`): Matches all resources and actions
|
||||
2. **Resource Handler** (e.g., `@auth.on.threads`, `@auth.on.assistants`, `@auth.on.crons`): Matches all actions for a specific resource
|
||||
3. **Action Handler** (e.g., `@auth.on.threads.create`, `@auth.on.threads.read`): Matches a specific action on a specific resource
|
||||
|
||||
The most specific matching handler will be used. For example, `@auth.on.threads.create` takes precedence over `@auth.on.threads` for thread creation.
|
||||
If a more specific handler is registered, the more general handler will not be called for that resource and action.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
1. **Global Handler** (`on("*")`): Matches all resources and actions
|
||||
2. **Resource Handler** (e.g., `on("threads")`, `on("assistants")`, `on("crons")`): Matches all actions for a specific resource
|
||||
3. **Action Handler** (e.g., `on("threads:create")`, `on("threads:read")`): Matches a specific action on a specific resource
|
||||
|
||||
The most specific matching handler will be used. For example, `on("threads:create")` takes precedence over `on("threads")` for thread creation.
|
||||
If a more specific handler is registered, the more general handler will not be called for that resource and action.
|
||||
:::
|
||||
|
||||
:::python
|
||||
???+ tip "Type Safety"
|
||||
Each handler has type hints available for its `value` parameter at `Auth.types.on.<resource>.<action>.value`. For example:
|
||||
Each handler has type hints available for its `value` parameter. For example:
|
||||
|
||||
```python
|
||||
@auth.on.threads.create
|
||||
async def on_thread_create(
|
||||
@@ -432,14 +723,14 @@ If a more specific handler is registered, the more general handler will not be c
|
||||
value: Auth.types.on.threads.create.value # Specific type for thread creation
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
@auth.on.threads
|
||||
async def on_threads(
|
||||
ctx: Auth.types.AuthContext,
|
||||
value: Auth.types.on.threads.value # Union type of all thread actions
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
@auth.on
|
||||
async def on_all(
|
||||
ctx: Auth.types.AuthContext,
|
||||
@@ -447,11 +738,16 @@ If a more specific handler is registered, the more general handler will not be c
|
||||
):
|
||||
...
|
||||
```
|
||||
|
||||
More specific handlers provide better type hints since they handle fewer action types.
|
||||
|
||||
:::
|
||||
|
||||
#### Supported actions and types {#supported-actions}
|
||||
|
||||
Here are all the supported action handlers:
|
||||
|
||||
:::python
|
||||
| Resource | Handler | Description | Value Type |
|
||||
|----------|---------|-------------|------------|
|
||||
| **Threads** | `@auth.on.threads.create` | Thread creation | [`ThreadsCreate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.ThreadsCreate) |
|
||||
@@ -470,12 +766,40 @@ Here are all the supported action handlers:
|
||||
| | `@auth.on.crons.update` | Cron job updates | [`CronsUpdate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.CronsUpdate) |
|
||||
| | `@auth.on.crons.delete` | Cron job deletion | [`CronsDelete`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.CronsDelete) |
|
||||
| | `@auth.on.crons.search` | Listing cron jobs | [`CronsSearch`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.CronsSearch) |
|
||||
:::
|
||||
|
||||
:::js
|
||||
| Resource | Event | Description | Value Type |
|
||||
| -------------- | -------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| **Threads** | `threads:create` | Thread creation | [`ThreadsCreate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#threadscreate) |
|
||||
| | `threads:read` | Thread retrieval | [`ThreadsRead`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#threadsread) |
|
||||
| | `threads:update` | Thread updates | [`ThreadsUpdate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#threadsupdate) |
|
||||
| | `threads:delete` | Thread deletion | [`ThreadsDelete`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#threadsdelete) |
|
||||
| | `threads:search` | Listing threads | [`ThreadsSearch`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#threadssearch) |
|
||||
| | `threads:create_run` | Creating or updating a run | [`RunsCreate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#threadscreate_run) |
|
||||
| **Assistants** | `assistants:create` | Assistant creation | [`AssistantsCreate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#assistantscreate) |
|
||||
| | `assistants:read` | Assistant retrieval | [`AssistantsRead`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#assistantsread) |
|
||||
| | `assistants:update` | Assistant updates | [`AssistantsUpdate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#assistantsupdate) |
|
||||
| | `assistants:delete` | Assistant deletion | [`AssistantsDelete`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#assistantsdelete) |
|
||||
| | `assistants:search` | Listing assistants | [`AssistantsSearch`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#assistantssearch) |
|
||||
| **Crons** | `crons:create` | Cron job creation | [`CronsCreate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#cronscreate) |
|
||||
| | `crons:read` | Cron job retrieval | [`CronsRead`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#cronsread) |
|
||||
| | `crons:update` | Cron job updates | [`CronsUpdate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#cronsupdate) |
|
||||
| | `crons:delete` | Cron job deletion | [`CronsDelete`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#cronsdelete) |
|
||||
| | `crons:search` | Listing cron jobs | [`CronsSearch`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#cronssearch) |
|
||||
:::
|
||||
|
||||
???+ note "About Runs"
|
||||
|
||||
Runs are scoped to their parent thread for access control. This means permissions are typically inherited from the thread, reflecting the conversational nature of the data model. All run operations (reading, listing) except creation are controlled by the thread's handlers.
|
||||
There is a specific `create_run` handler for creating new runs because it had more arguments that you can view in the handler.
|
||||
|
||||
:::python
|
||||
There is a specific `create_run` handler for creating new runs because it had more arguments that you can view in the handler.
|
||||
:::
|
||||
|
||||
:::js
|
||||
There is a specific `threads:create_run` handler for creating new runs because it had more arguments that you can view in the handler.
|
||||
:::
|
||||
|
||||
## Next Steps
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Breakpoints
|
||||
|
||||
[Breakpoints](../how-tos/human_in_the_loop/breakpoints.md) pause graph execution at defined points and let you step through each stage. They use LangGraph's [**persistence layer**](./persistence.md), which saves the graph state after each step.
|
||||
|
||||
With breakpoints, you can inspect the graph's state and node inputs at any point. Execution pauses **indefinitely** until you resume, as the checkpointer preserves the state.
|
||||
|
||||
<figure markdown="1">
|
||||
{: style="max-height:400px"}
|
||||
<figcaption>An example graph consisting of 3 sequential steps with a breakpoint before step_3. </figcaption> </figure>
|
||||
|
||||
!!! tip
|
||||
|
||||
For information on how to use breakpoints, see [Set breakpoints](../how-tos/human_in_the_loop/breakpoints.md) and [Set breakpoints using Server API](../cloud/how-tos/human_in_the_loop_breakpoint.md).
|
||||
@@ -7,10 +7,7 @@ search:
|
||||
|
||||
## Free deployment
|
||||
|
||||
There are two free options for deploying LangGraph applications via the LangGraph Server:
|
||||
|
||||
1. [Local](../tutorials/langgraph-platform/local-server.md): Deploy for local testing and development.
|
||||
1. [Standalone Container (Lite)](../concepts/langgraph_standalone_container.md): A limited version of Standalone Container for deployments unlikely to see more that 1 million node executions per year and that do not need crons and other enterprise features. Standalone Container (Lite) deployment option is free with a LangSmith API key.
|
||||
[Local](../tutorials/langgraph-platform/local-server.md): Deploy for local testing and development.
|
||||
|
||||
## Production deployment
|
||||
|
||||
@@ -33,8 +30,7 @@ A quick comparison:
|
||||
| **CI/CD** | Managed internally by platform | Managed externally by you | Managed externally by you | Managed externally by you |
|
||||
| **Data/compute residency** | LangChain's cloud | Your cloud | Your cloud | Your cloud |
|
||||
| **LangSmith compatibility** | Trace to LangSmith SaaS | Trace to LangSmith SaaS | Trace to Self-Hosted LangSmith | Optional tracing |
|
||||
| **[Server version compatibility](../concepts/langgraph_server.md#server-versions)** | Enterprise | Enterprise | Enterprise | Lite, Enterprise |
|
||||
| **[Pricing](https://www.langchain.com/pricing-langgraph-platform)** | Plus | Enterprise | Enterprise | Developer |
|
||||
| **[Pricing](https://www.langchain.com/pricing-langgraph-platform)** | Plus | Enterprise | Enterprise | Enterprise |
|
||||
|
||||
## Cloud SaaS
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ search:
|
||||
|
||||
# Durable Execution
|
||||
|
||||
**Durable execution** is a technique in which a process or workflow saves its progress at key points, allowing it to pause and later resume exactly where it left off. This is particularly useful in scenarios that require [human-in-the-loop](./human_in_the_loop.md), where users can inspect, validate, or modify the process before continuing, and in long-running tasks that might encounter interruptions or errors (e.g., calls to an LLM timing out). By preserving completed work, durable execution enables a process to resume without reprocessing previous steps -- even after a significant delay (e.g., a week later).
|
||||
**Durable execution** is a technique in which a process or workflow saves its progress at key points, allowing it to pause and later resume exactly where it left off. This is particularly useful in scenarios that require [human-in-the-loop](./human_in_the_loop.md), where users can inspect, validate, or modify the process before continuing, and in long-running tasks that might encounter interruptions or errors (e.g., calls to an LLM timing out). By preserving completed work, durable execution enables a process to resume without reprocessing previous steps -- even after a significant delay (e.g., a week later).
|
||||
|
||||
LangGraph's built-in [persistence](./persistence.md) layer provides durable execution for workflows, ensuring that the state of each execution step is saved to a durable store. This capability guarantees that if a workflow is interrupted -- whether by a system failure or for [human-in-the-loop](./human_in_the_loop.md) interactions -- it can be resumed from its last recorded state.
|
||||
|
||||
@@ -20,7 +20,18 @@ To leverage durable execution in LangGraph, you need to:
|
||||
|
||||
1. Enable [persistence](./persistence.md) in your workflow by specifying a [checkpointer](./persistence.md#checkpointer-libraries) that will save workflow progress.
|
||||
2. Specify a [thread identifier](./persistence.md#threads) when executing a workflow. This will track the execution history for a particular instance of the workflow.
|
||||
3. Wrap any non-deterministic operations (e.g., random number generation) or operations with side effects (e.g., file writes, API calls) inside [tasks][langgraph.func.task] to ensure that when a workflow is resumed, these operations are not repeated for the particular run, and instead their results are retrieved from the persistence layer. For more information, see [Determinism and Consistent Replay](#determinism-and-consistent-replay).
|
||||
|
||||
:::python
|
||||
|
||||
3. Wrap any non-deterministic operations (e.g., random number generation) or operations with side effects (e.g., file writes, API calls) inside @[tasks][task] to ensure that when a workflow is resumed, these operations are not repeated for the particular run, and instead their results are retrieved from the persistence layer. For more information, see [Determinism and Consistent Replay](#determinism-and-consistent-replay).
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
3. Wrap any non-deterministic operations (e.g., random number generation) or operations with side effects (e.g., file writes, API calls) inside @[tasks][task] to ensure that when a workflow is resumed, these operations are not repeated for the particular run, and instead their results are retrieved from the persistence layer. For more information, see [Determinism and Consistent Replay](#determinism-and-consistent-replay).
|
||||
|
||||
:::
|
||||
|
||||
## Determinism and Consistent Replay
|
||||
|
||||
@@ -30,17 +41,72 @@ As a result, when you are writing a workflow for durable execution, you must wra
|
||||
|
||||
To ensure that your workflow is deterministic and can be consistently replayed, follow these guidelines:
|
||||
|
||||
- **Avoid Repeating Work**: If a [node](./low_level.md#nodes) contains multiple operations with side effects (e.g., logging, file writes, or network calls), wrap each operation in a separate **task**. This ensures that when the workflow is resumed, the operations are not repeated, and their results are retrieved from the persistence layer.
|
||||
- **Encapsulate Non-Deterministic Operations:** Wrap any code that might yield non-deterministic results (e.g., random number generation) inside **tasks** or **nodes**. This ensures that, upon resumption, the workflow follows the exact recorded sequence of steps with the same outcomes.
|
||||
- **Avoid Repeating Work**: If a [node](./low_level.md#nodes) contains multiple operations with side effects (e.g., logging, file writes, or network calls), wrap each operation in a separate **task**. This ensures that when the workflow is resumed, the operations are not repeated, and their results are retrieved from the persistence layer.
|
||||
- **Encapsulate Non-Deterministic Operations:** Wrap any code that might yield non-deterministic results (e.g., random number generation) inside **tasks** or **nodes**. This ensures that, upon resumption, the workflow follows the exact recorded sequence of steps with the same outcomes.
|
||||
- **Use Idempotent Operations**: When possible ensure that side effects (e.g., API calls, file writes) are idempotent. This means that if an operation is retried after a failure in the workflow, it will have the same effect as the first time it was executed. This is particularly important for operations that result in data writes. In the event that a **task** starts but fails to complete successfully, the workflow's resumption will re-run the **task**, relying on recorded outcomes to maintain consistency. Use idempotency keys or verify existing results to avoid unintended duplication, ensuring a smooth and predictable workflow execution.
|
||||
|
||||
:::python
|
||||
For some examples of pitfalls to avoid, see the [Common Pitfalls](./functional_api.md#common-pitfalls) section in the functional API, which shows
|
||||
how to structure your code using **tasks** to avoid these issues. The same principles apply to the [StateGraph (Graph API)][langgraph.graph.state.StateGraph].
|
||||
how to structure your code using **tasks** to avoid these issues. The same principles apply to the @[StateGraph (Graph API)][StateGraph].
|
||||
:::
|
||||
|
||||
:::js
|
||||
For some examples of pitfalls to avoid, see the [Common Pitfalls](./functional_api.md#common-pitfalls) section in the functional API, which shows
|
||||
how to structure your code using **tasks** to avoid these issues. The same principles apply to the @[StateGraph (Graph API)][StateGraph].
|
||||
:::
|
||||
|
||||
## Durability modes
|
||||
|
||||
LangGraph supports three durability modes that allow you to balance performance and data consistency based on your application's requirements. The durability modes, from least to most durable, are as follows:
|
||||
|
||||
- [`"exit"`](#exit)
|
||||
- [`"async"`](#async)
|
||||
- [`"sync"`](#sync)
|
||||
|
||||
A higher durability mode add more overhead to the workflow execution.
|
||||
|
||||
!!! version-added "Added in version 0.6.0"
|
||||
|
||||
Use the `durability` parameter instead of `checkpoint_during` (deprecated in v0.6.0) for persistence policy management:
|
||||
|
||||
* `durability="async"` replaces `checkpoint_during=True`
|
||||
* `durability="exit"` replaces `checkpoint_during=False`
|
||||
|
||||
for persistence policy management, with the following mapping:
|
||||
|
||||
* `checkpoint_during=True` -> `durability="async"`
|
||||
* `checkpoint_during=False` -> `durability="exit"`
|
||||
|
||||
### `"exit"`
|
||||
|
||||
Changes are persisted only when graph execution completes (either successfully or with an error). This provides the best performance for long-running graphs but means intermediate state is not saved, so you cannot recover from mid-execution failures or interrupt the graph execution.
|
||||
|
||||
### `"async"`
|
||||
|
||||
Changes are persisted asynchronously while the next step executes. This provides good performance and durability, but there's a small risk that checkpoints might not be written if the process crashes during execution.
|
||||
|
||||
### `"sync"`
|
||||
|
||||
Changes are persisted synchronously before the next step starts. This ensures that every checkpoint is written before continuing execution, providing high durability at the cost of some performance overhead.
|
||||
|
||||
You can specify the durability mode when calling any graph execution method:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
graph.stream(
|
||||
{"input": "test"},
|
||||
durability="sync"
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Using tasks in nodes
|
||||
|
||||
If a [node](./low_level.md#nodes) contains multiple operations, you may find it easier to convert each operation into a **task** rather than refactor the operations into individual nodes.
|
||||
|
||||
:::python
|
||||
=== "Original"
|
||||
|
||||
```python
|
||||
@@ -48,7 +114,7 @@ If a [node](./low_level.md#nodes) contains multiple operations, you may find it
|
||||
from typing_extensions import TypedDict
|
||||
import uuid
|
||||
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
import requests
|
||||
|
||||
@@ -74,7 +140,7 @@ If a [node](./low_level.md#nodes) contains multiple operations, you may find it
|
||||
builder.add_edge("call_api", END)
|
||||
|
||||
# Specify a checkpointer
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
# Compile the graph with the checkpointer
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
@@ -94,7 +160,7 @@ If a [node](./low_level.md#nodes) contains multiple operations, you may find it
|
||||
from typing_extensions import TypedDict
|
||||
import uuid
|
||||
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.func import task
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
import requests
|
||||
@@ -129,7 +195,7 @@ If a [node](./low_level.md#nodes) contains multiple operations, you may find it
|
||||
builder.add_edge("call_api", END)
|
||||
|
||||
# Specify a checkpointer
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
# Compile the graph with the checkpointer
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
@@ -142,16 +208,140 @@ If a [node](./low_level.md#nodes) contains multiple operations, you may find it
|
||||
graph.invoke({"urls": ["https://www.example.com"]}, config)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
=== "Original"
|
||||
|
||||
```typescript
|
||||
import { StateGraph, START, END } from "@langchain/langgraph";
|
||||
import { MemorySaver } from "@langchain/langgraph";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { z } from "zod";
|
||||
|
||||
// Define a Zod schema to represent the state
|
||||
const State = z.object({
|
||||
url: z.string(),
|
||||
result: z.string().optional(),
|
||||
});
|
||||
|
||||
const callApi = async (state: z.infer<typeof State>) => {
|
||||
// highlight-next-line
|
||||
const response = await fetch(state.url);
|
||||
const text = await response.text();
|
||||
const result = text.slice(0, 100); // Side-effect
|
||||
return {
|
||||
result,
|
||||
};
|
||||
};
|
||||
|
||||
// Create a StateGraph builder and add a node for the callApi function
|
||||
const builder = new StateGraph(State)
|
||||
.addNode("callApi", callApi)
|
||||
.addEdge(START, "callApi")
|
||||
.addEdge("callApi", END);
|
||||
|
||||
// Specify a checkpointer
|
||||
const checkpointer = new MemorySaver();
|
||||
|
||||
// Compile the graph with the checkpointer
|
||||
const graph = builder.compile({ checkpointer });
|
||||
|
||||
// Define a config with a thread ID.
|
||||
const threadId = uuidv4();
|
||||
const config = { configurable: { thread_id: threadId } };
|
||||
|
||||
// Invoke the graph
|
||||
await graph.invoke({ url: "https://www.example.com" }, config);
|
||||
```
|
||||
|
||||
=== "With task"
|
||||
|
||||
```typescript
|
||||
import { StateGraph, START, END } from "@langchain/langgraph";
|
||||
import { MemorySaver } from "@langchain/langgraph";
|
||||
import { task } from "@langchain/langgraph";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { z } from "zod";
|
||||
|
||||
// Define a Zod schema to represent the state
|
||||
const State = z.object({
|
||||
urls: z.array(z.string()),
|
||||
results: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
const makeRequest = task("makeRequest", async (url: string) => {
|
||||
// highlight-next-line
|
||||
const response = await fetch(url);
|
||||
const text = await response.text();
|
||||
return text.slice(0, 100);
|
||||
});
|
||||
|
||||
const callApi = async (state: z.infer<typeof State>) => {
|
||||
// highlight-next-line
|
||||
const requests = state.urls.map((url) => makeRequest(url));
|
||||
const results = await Promise.all(requests);
|
||||
return {
|
||||
results,
|
||||
};
|
||||
};
|
||||
|
||||
// Create a StateGraph builder and add a node for the callApi function
|
||||
const builder = new StateGraph(State)
|
||||
.addNode("callApi", callApi)
|
||||
.addEdge(START, "callApi")
|
||||
.addEdge("callApi", END);
|
||||
|
||||
// Specify a checkpointer
|
||||
const checkpointer = new MemorySaver();
|
||||
|
||||
// Compile the graph with the checkpointer
|
||||
const graph = builder.compile({ checkpointer });
|
||||
|
||||
// Define a config with a thread ID.
|
||||
const threadId = uuidv4();
|
||||
const config = { configurable: { thread_id: threadId } };
|
||||
|
||||
// Invoke the graph
|
||||
await graph.invoke({ urls: ["https://www.example.com"] }, config);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Resuming Workflows
|
||||
|
||||
Once you have enabled durable execution in your workflow, you can resume execution for the following scenarios:
|
||||
|
||||
- **Pausing and Resuming Workflows:** Use the [interrupt][langgraph.types.interrupt] function to pause a workflow at specific points and the [Command][langgraph.types.Command] primitive to resume it with updated state. See [**Human-in-the-Loop**](./human_in_the_loop.md) for more details.
|
||||
:::python
|
||||
|
||||
- **Pausing and Resuming Workflows:** Use the @[interrupt][interrupt] function to pause a workflow at specific points and the @[Command] primitive to resume it with updated state. See [**Human-in-the-Loop**](./human_in_the_loop.md) for more details.
|
||||
- **Recovering from Failures:** Automatically resume workflows from the last successful checkpoint after an exception (e.g., LLM provider outage). This involves executing the workflow with the same thread identifier by providing it with a `None` as the input value (see this [example](../how-tos/use-functional-api.md#resuming-after-an-error) with the functional API).
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- **Pausing and Resuming Workflows:** Use the @[interrupt][interrupt] function to pause a workflow at specific points and the @[Command] primitive to resume it with updated state. See [**Human-in-the-Loop**](./human_in_the_loop.md) for more details.
|
||||
- **Recovering from Failures:** Automatically resume workflows from the last successful checkpoint after an exception (e.g., LLM provider outage). This involves executing the workflow with the same thread identifier by providing it with a `null` as the input value (see this [example](../how-tos/use-functional-api.md#resuming-after-an-error) with the functional API).
|
||||
|
||||
:::
|
||||
|
||||
## Starting Points for Resuming Workflows
|
||||
|
||||
* If you're using a [StateGraph (Graph API)][langgraph.graph.state.StateGraph], the starting point is the beginning of the [**node**](./low_level.md#nodes) where execution stopped.
|
||||
* If you're making a subgraph call inside a node, the starting point will be the **parent** node that called the subgraph that was halted.
|
||||
Inside the subgraph, the starting point will be the specific [**node**](./low_level.md#nodes) where execution stopped.
|
||||
* If you're using the Functional API, the starting point is the beginning of the [**entrypoint**](./functional_api.md#entrypoint) where execution stopped.
|
||||
:::python
|
||||
|
||||
- If you're using a @[StateGraph (Graph API)][StateGraph], the starting point is the beginning of the [**node**](./low_level.md#nodes) where execution stopped.
|
||||
- If you're making a subgraph call inside a node, the starting point will be the **parent** node that called the subgraph that was halted.
|
||||
Inside the subgraph, the starting point will be the specific [**node**](./low_level.md#nodes) where execution stopped.
|
||||
- If you're using the Functional API, the starting point is the beginning of the [**entrypoint**](./functional_api.md#entrypoint) where execution stopped.
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- If you're using a [StateGraph (Graph API)](./low_level.md), the starting point is the beginning of the [**node**](./low_level.md#nodes) where execution stopped.
|
||||
- If you're making a subgraph call inside a node, the starting point will be the **parent** node that called the subgraph that was halted.
|
||||
Inside the subgraph, the starting point will be the specific [**node**](./low_level.md#nodes) where execution stopped.
|
||||
- If you're using the Functional API, the starting point is the beginning of the [**entrypoint**](./functional_api.md#entrypoint) where execution stopped.
|
||||
|
||||
:::
|
||||
|
||||
@@ -13,7 +13,7 @@ No. LangGraph is an orchestration framework for complex agentic systems and is m
|
||||
|
||||
## How is LangGraph different from other agent frameworks?
|
||||
|
||||
Other agentic frameworks can work for simple, generic tasks but fall short for complex tasks bespoke to a company’s needs. LangGraph provides a more expressive framework to handle companies’ unique tasks without restricting users to a single black-box cognitive architecture.
|
||||
Other agentic frameworks can work for simple, generic tasks but fall short for complex tasks. LangGraph provides a more expressive framework to handle your unique tasks without restricting you to a single black-box cognitive architecture.
|
||||
|
||||
## Does LangGraph impact the performance of my app?
|
||||
|
||||
@@ -28,14 +28,14 @@ Yes. LangGraph is an MIT-licensed open-source library and is free to use.
|
||||
LangGraph is a stateful, orchestration framework that brings added control to agent workflows. LangGraph Platform is a service for deploying and scaling LangGraph applications, with an opinionated API for building agent UXs, plus an integrated developer studio.
|
||||
|
||||
| Features | LangGraph (open source) | LangGraph Platform |
|
||||
|---------------------|-----------------------------------------------------------|--------------------------------------------------------------------------------------------------------|
|
||||
| ------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
||||
| Description | Stateful orchestration framework for agentic applications | Scalable infrastructure for deploying LangGraph applications |
|
||||
| SDKs | Python and JavaScript | Python and JavaScript |
|
||||
| HTTP APIs | None | Yes - useful for retrieving & updating state or long-term memory, or creating a configurable assistant |
|
||||
| Streaming | Basic | Dedicated mode for token-by-token messages |
|
||||
| Checkpointer | Community contributed | Supported out-of-the-box |
|
||||
| Persistence Layer | Self-managed | Managed Postgres with efficient storage |
|
||||
| Deployment | Self-managed | • Cloud SaaS <br> • Free self-hosted <br> • Enterprise (paid self-hosted) |
|
||||
| Deployment | Self-managed | • Cloud SaaS <br> • Free self-hosted <br> • Enterprise (paid self-hosted) |
|
||||
| Scalability | Self-managed | Auto-scaling of task queues and servers |
|
||||
| Fault-tolerance | Self-managed | Automated retries |
|
||||
| Concurrency Control | Simple threading | Supports double-texting |
|
||||
@@ -67,4 +67,4 @@ If you set an environment variable of `LANGSMITH_TRACING=false`, then no traces
|
||||
|
||||
## What does "nodes executed" mean for LangGraph Platform usage?
|
||||
|
||||
**Nodes Executed** is the aggregate number of nodes in a LangGraph application that are called and completed successfully during an invocation of the application. If a node in the graph is not called during execution or ends in an error state, these nodes will not be counted. If a node is called and completes successfully multiple times, each occurrence will be counted.
|
||||
**Nodes Executed** is the aggregate number of nodes in a LangGraph application that are called and completed successfully during an invocation of the application. If a node in the graph is not called during execution or ends in an error state, these nodes will not be counted. If a node is called and completes successfully multiple times, each occurrence will be counted.
|
||||
|
||||
@@ -9,12 +9,21 @@ search:
|
||||
|
||||
The **Functional API** allows you to add LangGraph's key features — [persistence](./persistence.md), [memory](../how-tos/memory/add-memory.md), [human-in-the-loop](./human_in_the_loop.md), and [streaming](./streaming.md) — to your applications with minimal changes to your existing code.
|
||||
|
||||
It is designed to integrate these features into existing code that may use standard language primitives for branching and control flow, such as `if` statements, `for` loops, and function calls. Unlike many data orchestration frameworks that require restructuring code into an explicit pipeline or DAG, the Functional API allows you to incorporate these capabilities without enforcing a rigid execution model.
|
||||
It is designed to integrate these features into existing code that may use standard language primitives for branching and control flow, such as `if` statements, `for` loops, and function calls. Unlike many data orchestration frameworks that require restructuring code into an explicit pipeline or DAG, the Functional API allows you to incorporate these capabilities without enforcing a rigid execution model.
|
||||
|
||||
The Functional API uses two key building blocks:
|
||||
The Functional API uses two key building blocks:
|
||||
|
||||
- **`@entrypoint`** – Marks a function as the starting point of a workflow, encapsulating logic and managing execution flow, including handling long-running tasks and interrupts.
|
||||
:::python
|
||||
|
||||
- **`@entrypoint`** – Marks a function as the starting point of a workflow, encapsulating logic and managing execution flow, including handling long-running tasks and interrupts.
|
||||
- **`@task`** – Represents a discrete unit of work, such as an API call or data processing step, that can be executed asynchronously within an entrypoint. Tasks return a future-like object that can be awaited or resolved synchronously.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- **`entrypoint`** – An entrypoint encapsulates workflow logic and manages execution flow, including handling long-running tasks and interrupts.
|
||||
- **`task`** – Represents a discrete unit of work, such as an API call or data processing step, that can be executed asynchronously within an entrypoint. Tasks return a future-like object that can be awaited or resolved synchronously.
|
||||
:::
|
||||
|
||||
This provides a minimal abstraction for building workflows with state management and streaming.
|
||||
|
||||
@@ -33,24 +42,24 @@ Here are some key differences:
|
||||
- **Checkpointing**: Both APIs generate and use checkpoints. In the **Graph API** a new checkpoint is generated after every [superstep](./low_level.md). In the **Functional API**, when tasks are executed, their results are saved to an existing checkpoint associated with the given entrypoint instead of creating a new checkpoint.
|
||||
- **Visualization**: The Graph API makes it easy to visualize the workflow as a graph which can be useful for debugging, understanding the workflow, and sharing with others. The Functional API does not support visualization as the graph is dynamically generated during runtime.
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
Below we demonstrate a simple application that writes an essay and [interrupts](human_in_the_loop.md) to request human review.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.func import entrypoint, task
|
||||
from langgraph.types import interrupt
|
||||
|
||||
|
||||
@task
|
||||
def write_essay(topic: str) -> str:
|
||||
"""Write an essay about the given topic."""
|
||||
time.sleep(1) # A placeholder for a long-running task.
|
||||
return f"An essay about topic: {topic}"
|
||||
|
||||
@entrypoint(checkpointer=MemorySaver())
|
||||
@entrypoint(checkpointer=InMemorySaver())
|
||||
def workflow(topic: str) -> dict:
|
||||
"""A simple workflow that writes an essay and asks for a review."""
|
||||
essay = write_essay("cat").result()
|
||||
@@ -70,60 +79,101 @@ def workflow(topic: str) -> dict:
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { MemorySaver, entrypoint, task, interrupt } from "@langchain/langgraph";
|
||||
|
||||
const writeEssay = task("writeEssay", async (topic: string) => {
|
||||
// A placeholder for a long-running task.
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
return `An essay about topic: ${topic}`;
|
||||
});
|
||||
|
||||
const workflow = entrypoint(
|
||||
{ checkpointer: new MemorySaver(), name: "workflow" },
|
||||
async (topic: string) => {
|
||||
const essay = await writeEssay(topic);
|
||||
const isApproved = interrupt({
|
||||
// Any json-serializable payload provided to interrupt as argument.
|
||||
// It will be surfaced on the client side as an Interrupt when streaming data
|
||||
// from the workflow.
|
||||
essay, // The essay we want reviewed.
|
||||
// We can add any additional information that we need.
|
||||
// For example, introduce a key called "action" with some instructions.
|
||||
action: "Please approve/reject the essay",
|
||||
});
|
||||
|
||||
return {
|
||||
essay, // The essay that was generated
|
||||
isApproved, // Response from HIL
|
||||
};
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
??? example "Detailed Explanation"
|
||||
|
||||
This workflow will write an essay about the topic "cat" and then pause to get a review from a human. The workflow can be interrupted for an indefinite amount of time until a review is provided.
|
||||
|
||||
When the workflow is resumed, it executes from the very start, but because the result of the `write_essay` task was already saved, the task result will be loaded from the checkpoint instead of being recomputed.
|
||||
When the workflow is resumed, it executes from the very start, but because the result of the `writeEssay` task was already saved, the task result will be loaded from the checkpoint instead of being recomputed.
|
||||
|
||||
:::python
|
||||
```python
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from langgraph.func import entrypoint, task
|
||||
from langgraph.types import interrupt
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
|
||||
@task
|
||||
def write_essay(topic: str) -> str:
|
||||
"""Write an essay about the given topic."""
|
||||
time.sleep(1) # This is a placeholder for a long-running task.
|
||||
time.sleep(1) # This is a placeholder for a long-running task.
|
||||
return f"An essay about topic: {topic}"
|
||||
|
||||
@entrypoint(checkpointer=MemorySaver())
|
||||
@entrypoint(checkpointer=InMemorySaver())
|
||||
def workflow(topic: str) -> dict:
|
||||
"""A simple workflow that writes an essay and asks for a review."""
|
||||
essay = write_essay("cat").result()
|
||||
is_approved = interrupt({
|
||||
# Any json-serializable payload provided to interrupt as argument.
|
||||
# It will be surfaced on the client side as an Interrupt when streaming data
|
||||
# from the workflow.
|
||||
"essay": essay, # The essay we want reviewed.
|
||||
# We can add any additional information that we need.
|
||||
# For example, introduce a key called "action" with some instructions.
|
||||
"action": "Please approve/reject the essay",
|
||||
})
|
||||
|
||||
is_approved = interrupt(
|
||||
{
|
||||
# Any json-serializable payload provided to interrupt as argument.
|
||||
# It will be surfaced on the client side as an Interrupt when streaming data
|
||||
# from the workflow.
|
||||
"essay": essay, # The essay we want reviewed.
|
||||
# We can add any additional information that we need.
|
||||
# For example, introduce a key called "action" with some instructions.
|
||||
"action": "Please approve/reject the essay",
|
||||
}
|
||||
)
|
||||
return {
|
||||
"essay": essay, # The essay that was generated
|
||||
"is_approved": is_approved, # Response from HIL
|
||||
"essay": essay, # The essay that was generated
|
||||
"is_approved": is_approved, # Response from HIL
|
||||
}
|
||||
|
||||
|
||||
thread_id = str(uuid.uuid4())
|
||||
|
||||
config = {
|
||||
"configurable": {
|
||||
"thread_id": thread_id
|
||||
}
|
||||
}
|
||||
|
||||
config = {"configurable": {"thread_id": thread_id}}
|
||||
for item in workflow.stream("cat", config):
|
||||
print(item)
|
||||
```
|
||||
|
||||
```pycon
|
||||
{'write_essay': 'An essay about topic: cat'}
|
||||
{'__interrupt__': (Interrupt(value={'essay': 'An essay about topic: cat', 'action': 'Please approve/reject the essay'}, resumable=True, ns=['workflow:f7b8508b-21c0-8b4c-5958-4e8de74d2684'], when='during'),)}
|
||||
# > {'write_essay': 'An essay about topic: cat'}
|
||||
# > {
|
||||
# > '__interrupt__': (
|
||||
# > Interrupt(
|
||||
# > value={
|
||||
# > 'essay': 'An essay about topic: cat',
|
||||
# > 'action': 'Please approve/reject the essay'
|
||||
# > },
|
||||
# > id='b9b2b9d788f482663ced6dc755c9e981'
|
||||
# > ),
|
||||
# > )
|
||||
# > }
|
||||
```
|
||||
|
||||
An essay has been written and is ready for review. Once the review is provided, we can resume the workflow:
|
||||
@@ -144,18 +194,104 @@ def workflow(topic: str) -> dict:
|
||||
```
|
||||
|
||||
The workflow has been completed and the review has been added to the essay.
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { MemorySaver, entrypoint, task, interrupt } from "@langchain/langgraph";
|
||||
|
||||
const writeEssay = task("writeEssay", async (topic: string) => {
|
||||
// This is a placeholder for a long-running task.
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
return `An essay about topic: ${topic}`;
|
||||
});
|
||||
|
||||
const workflow = entrypoint(
|
||||
{ checkpointer: new MemorySaver(), name: "workflow" },
|
||||
async (topic: string) => {
|
||||
const essay = await writeEssay(topic);
|
||||
const isApproved = interrupt({
|
||||
// Any json-serializable payload provided to interrupt as argument.
|
||||
// It will be surfaced on the client side as an Interrupt when streaming data
|
||||
// from the workflow.
|
||||
essay, // The essay we want reviewed.
|
||||
// We can add any additional information that we need.
|
||||
// For example, introduce a key called "action" with some instructions.
|
||||
action: "Please approve/reject the essay",
|
||||
});
|
||||
|
||||
return {
|
||||
essay, // The essay that was generated
|
||||
isApproved, // Response from HIL
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const threadId = uuidv4();
|
||||
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: threadId
|
||||
}
|
||||
};
|
||||
|
||||
for await (const item of workflow.stream("cat", config)) {
|
||||
console.log(item);
|
||||
}
|
||||
```
|
||||
|
||||
```console
|
||||
{ writeEssay: 'An essay about topic: cat' }
|
||||
{
|
||||
__interrupt__: [{
|
||||
value: { essay: 'An essay about topic: cat', action: 'Please approve/reject the essay' },
|
||||
resumable: true,
|
||||
ns: ['workflow:f7b8508b-21c0-8b4c-5958-4e8de74d2684'],
|
||||
when: 'during'
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
An essay has been written and is ready for review. Once the review is provided, we can resume the workflow:
|
||||
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
// Get review from a user (e.g., via a UI)
|
||||
// In this case, we're using a bool, but this can be any json-serializable value.
|
||||
const humanReview = true;
|
||||
|
||||
for await (const item of workflow.stream(new Command({ resume: humanReview }), config)) {
|
||||
console.log(item);
|
||||
}
|
||||
```
|
||||
|
||||
```console
|
||||
{ workflow: { essay: 'An essay about topic: cat', isApproved: true } }
|
||||
```
|
||||
|
||||
The workflow has been completed and the review has been added to the essay.
|
||||
:::
|
||||
|
||||
## Entrypoint
|
||||
|
||||
The [`@entrypoint`][langgraph.func.entrypoint] decorator can be used to create a workflow from a function. It encapsulates workflow logic and manages execution flow, including handling *long-running tasks* and [interrupts](./human_in_the_loop.md).
|
||||
:::python
|
||||
The @[`@entrypoint`][entrypoint] decorator can be used to create a workflow from a function. It encapsulates workflow logic and manages execution flow, including handling _long-running tasks_ and [interrupts](./human_in_the_loop.md).
|
||||
:::
|
||||
|
||||
:::js
|
||||
The @[`entrypoint`][entrypoint] function can be used to create a workflow from a function. It encapsulates workflow logic and manages execution flow, including handling _long-running tasks_ and [interrupts](./human_in_the_loop.md).
|
||||
:::
|
||||
|
||||
### Definition
|
||||
|
||||
An **entrypoint** is defined by decorating a function with the `@entrypoint` decorator.
|
||||
:::python
|
||||
An **entrypoint** is defined by decorating a function with the `@entrypoint` decorator.
|
||||
|
||||
The function **must accept a single positional argument**, which serves as the workflow input. If you need to pass multiple pieces of data, use a dictionary as the input type for the first argument.
|
||||
|
||||
Decorating a function with an `entrypoint` produces a [`Pregel`][langgraph.pregel.Pregel.stream] instance which helps to manage the execution of the workflow (e.g., handles streaming, resumption, and checkpointing).
|
||||
Decorating a function with an `entrypoint` produces a @[`Pregel`][Pregel.stream] instance which helps to manage the execution of the workflow (e.g., handles streaming, resumption, and checkpointing).
|
||||
|
||||
You will usually want to pass a **checkpointer** to the `@entrypoint` decorator to enable persistence and use features like **human-in-the-loop**.
|
||||
|
||||
@@ -182,22 +318,48 @@ You will usually want to pass a **checkpointer** to the `@entrypoint` decorator
|
||||
# some logic that may involve long-running tasks like API calls,
|
||||
# and may be interrupted for human-in-the-loop
|
||||
...
|
||||
return result
|
||||
return result
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
An **entrypoint** is defined by calling the `entrypoint` function with configuration and a function.
|
||||
|
||||
The function **must accept a single positional argument**, which serves as the workflow input. If you need to pass multiple pieces of data, use an object as the input type for the first argument.
|
||||
|
||||
Creating an entrypoint with a function produces a workflow instance which helps to manage the execution of the workflow (e.g., handles streaming, resumption, and checkpointing).
|
||||
|
||||
You will often want to pass a **checkpointer** to the `entrypoint` function to enable persistence and use features like **human-in-the-loop**.
|
||||
|
||||
```typescript
|
||||
import { entrypoint } from "@langchain/langgraph";
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (someInput: Record<string, any>): Promise<number> => {
|
||||
// some logic that may involve long-running tasks like API calls,
|
||||
// and may be interrupted for human-in-the-loop
|
||||
return result;
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! important "Serialization"
|
||||
|
||||
The **inputs** and **outputs** of entrypoints must be JSON-serializable to support checkpointing. Please see the [serialization](#serialization) section for more details.
|
||||
|
||||
:::python
|
||||
|
||||
### Injectable parameters
|
||||
|
||||
When declaring an `entrypoint`, you can request access to additional parameters that will be injected automatically at run time. These parameters include:
|
||||
|
||||
|
||||
| Parameter | Description |
|
||||
|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **previous** | Access the state associated with the previous `checkpoint` for the given thread. See [short-term-memory](#short-term-memory). |
|
||||
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **previous** | Access the state associated with the previous `checkpoint` for the given thread. See [short-term-memory](#short-term-memory). |
|
||||
| **store** | An instance of [BaseStore][langgraph.store.base.BaseStore]. Useful for [long-term memory](../how-tos/use-functional-api.md#long-term-memory). |
|
||||
| **writer** | Use to access the StreamWriter when working with Async Python < 3.11. See [streaming with functional API for details](../how-tos/use-functional-api.md#streaming). |
|
||||
| **config** | For accessing run time configuration. See [RunnableConfig](https://python.langchain.com/docs/concepts/runnables/#runnableconfig) for information. |
|
||||
@@ -219,7 +381,7 @@ When declaring an `entrypoint`, you can request access to additional parameters
|
||||
@entrypoint(
|
||||
checkpointer=checkpointer, # Specify the checkpointer
|
||||
store=in_memory_store # Specify the store
|
||||
)
|
||||
)
|
||||
def my_workflow(
|
||||
some_input: dict, # The input (e.g., passed via `invoke`)
|
||||
*,
|
||||
@@ -230,9 +392,12 @@ When declaring an `entrypoint`, you can request access to additional parameters
|
||||
) -> ...:
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Executing
|
||||
|
||||
Using the [`@entrypoint`](#entrypoint) yields a [`Pregel`][langgraph.pregel.Pregel.stream] object that can be executed using the `invoke`, `ainvoke`, `stream`, and `astream` methods.
|
||||
:::python
|
||||
Using the [`@entrypoint`](#entrypoint) yields a @[`Pregel`][Pregel.stream] object that can be executed using the `invoke`, `ainvoke`, `stream`, and `astream` methods.
|
||||
|
||||
=== "Invoke"
|
||||
|
||||
@@ -257,7 +422,7 @@ Using the [`@entrypoint`](#entrypoint) yields a [`Pregel`][langgraph.pregel.Preg
|
||||
```
|
||||
|
||||
=== "Stream"
|
||||
|
||||
|
||||
```python
|
||||
config = {
|
||||
"configurable": {
|
||||
@@ -282,9 +447,42 @@ Using the [`@entrypoint`](#entrypoint) yields a [`Pregel`][langgraph.pregel.Preg
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Using the [`entrypoint`](#entrypoint) function will return an object that can be executed using the `invoke` and `stream` methods.
|
||||
|
||||
=== "Invoke"
|
||||
|
||||
```typescript
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id"
|
||||
}
|
||||
};
|
||||
await myWorkflow.invoke(someInput, config); // Wait for the result
|
||||
```
|
||||
|
||||
=== "Stream"
|
||||
|
||||
```typescript
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id"
|
||||
}
|
||||
};
|
||||
|
||||
for await (const chunk of myWorkflow.stream(someInput, config)) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Resuming
|
||||
|
||||
Resuming an execution after an [interrupt][langgraph.types.interrupt] can be done by passing a **resume** value to the [Command][langgraph.types.Command] primitive.
|
||||
:::python
|
||||
Resuming an execution after an @[interrupt][interrupt] can be done by passing a **resume** value to the @[Command] primitive.
|
||||
|
||||
=== "Invoke"
|
||||
|
||||
@@ -296,7 +494,7 @@ Resuming an execution after an [interrupt][langgraph.types.interrupt] can be don
|
||||
"thread_id": "some_thread_id"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
my_workflow.invoke(Command(resume=some_resume_value), config)
|
||||
```
|
||||
|
||||
@@ -310,7 +508,7 @@ Resuming an execution after an [interrupt][langgraph.types.interrupt] can be don
|
||||
"thread_id": "some_thread_id"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await my_workflow.ainvoke(Command(resume=some_resume_value), config)
|
||||
```
|
||||
|
||||
@@ -324,7 +522,7 @@ Resuming an execution after an [interrupt][langgraph.types.interrupt] can be don
|
||||
"thread_id": "some_thread_id"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for chunk in my_workflow.stream(Command(resume=some_resume_value), config):
|
||||
print(chunk)
|
||||
```
|
||||
@@ -344,8 +542,51 @@ Resuming an execution after an [interrupt][langgraph.types.interrupt] can be don
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
**Resuming after an error**
|
||||
:::
|
||||
|
||||
:::js
|
||||
Resuming an execution after an @[interrupt][interrupt] can be done by passing a **resume** value to the @[`Command`][Command] primitive.
|
||||
|
||||
=== "Invoke"
|
||||
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id"
|
||||
}
|
||||
};
|
||||
|
||||
await myWorkflow.invoke(new Command({ resume: someResumeValue }), config);
|
||||
```
|
||||
|
||||
=== "Stream"
|
||||
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id"
|
||||
}
|
||||
};
|
||||
|
||||
const stream = await myWorkflow.stream(
|
||||
new Command({ resume: someResumableValue }),
|
||||
config,
|
||||
)
|
||||
|
||||
for await (const chunk of stream) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
**Resuming after an error**
|
||||
|
||||
To resume after an error, run the `entrypoint` with a `None` and the same **thread id** (config).
|
||||
|
||||
@@ -360,7 +601,7 @@ This assumes that the underlying **error** has been resolved and execution can p
|
||||
"thread_id": "some_thread_id"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
my_workflow.invoke(None, config)
|
||||
```
|
||||
|
||||
@@ -373,7 +614,7 @@ This assumes that the underlying **error** has been resolved and execution can p
|
||||
"thread_id": "some_thread_id"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await my_workflow.ainvoke(None, config)
|
||||
```
|
||||
|
||||
@@ -386,7 +627,7 @@ This assumes that the underlying **error** has been resolved and execution can p
|
||||
"thread_id": "some_thread_id"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for chunk in my_workflow.stream(None, config):
|
||||
print(chunk)
|
||||
```
|
||||
@@ -405,10 +646,49 @@ This assumes that the underlying **error** has been resolved and execution can p
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
**Resuming after an error**
|
||||
|
||||
To resume after an error, run the `entrypoint` with `null` and the same **thread id** (config).
|
||||
|
||||
This assumes that the underlying **error** has been resolved and execution can proceed successfully.
|
||||
|
||||
=== "Invoke"
|
||||
|
||||
```typescript
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id"
|
||||
}
|
||||
};
|
||||
|
||||
await myWorkflow.invoke(null, config);
|
||||
```
|
||||
|
||||
=== "Stream"
|
||||
|
||||
```typescript
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id"
|
||||
}
|
||||
};
|
||||
|
||||
for await (const chunk of myWorkflow.stream(null, config)) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Short-term memory
|
||||
|
||||
When an `entrypoint` is defined with a `checkpointer`, it stores information between successive invocations on the same **thread id** in [checkpoints](persistence.md#checkpoints).
|
||||
When an `entrypoint` is defined with a `checkpointer`, it stores information between successive invocations on the same **thread id** in [checkpoints](persistence.md#checkpoints).
|
||||
|
||||
:::python
|
||||
This allows accessing the state from the previous invocation using the `previous` parameter.
|
||||
|
||||
By default, the `previous` parameter is the return value of the previous invocation.
|
||||
@@ -429,9 +709,40 @@ my_workflow.invoke(1, config) # 1 (previous was None)
|
||||
my_workflow.invoke(2, config) # 3 (previous was 1 from the previous invocation)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
This allows accessing the state from the previous invocation using the `getPreviousState` function.
|
||||
|
||||
By default, the `getPreviousState` function returns the return value of the previous invocation.
|
||||
|
||||
```typescript
|
||||
import { entrypoint, getPreviousState } from "@langchain/langgraph";
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (number: number) => {
|
||||
const previous = getPreviousState<number>() ?? 0;
|
||||
return number + previous;
|
||||
}
|
||||
);
|
||||
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id",
|
||||
},
|
||||
};
|
||||
|
||||
await myWorkflow.invoke(1, config); // 1 (previous was undefined)
|
||||
await myWorkflow.invoke(2, config); // 3 (previous was 1 from the previous invocation)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#### `entrypoint.final`
|
||||
|
||||
[entrypoint.final][langgraph.func.entrypoint.final] is a special primitive that can be returned from an entrypoint and allows **decoupling** the value that is **saved in the checkpoint** from the **return value of the entrypoint**.
|
||||
:::python
|
||||
@[`entrypoint.final`][entrypoint.final] is a special primitive that can be returned from an entrypoint and allows **decoupling** the value that is **saved in the checkpoint** from the **return value of the entrypoint**.
|
||||
|
||||
The first value is the return value of the entrypoint, and the second value is the value that will be saved in the checkpoint. The type annotation is `entrypoint.final[return_type, save_type]`.
|
||||
|
||||
@@ -440,7 +751,7 @@ The first value is the return value of the entrypoint, and the second value is t
|
||||
def my_workflow(number: int, *, previous: Any = None) -> entrypoint.final[int, int]:
|
||||
previous = previous or 0
|
||||
# This will return the previous value to the caller, saving
|
||||
# 2 * number to the checkpoint, which will be used in the next invocation
|
||||
# 2 * number to the checkpoint, which will be used in the next invocation
|
||||
# for the `previous` parameter.
|
||||
return entrypoint.final(value=previous, save=2 * number)
|
||||
|
||||
@@ -454,15 +765,52 @@ my_workflow.invoke(3, config) # 0 (previous was None)
|
||||
my_workflow.invoke(1, config) # 6 (previous was 3 * 2 from the previous invocation)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
@[`entrypoint.final`][entrypoint.final] is a special primitive that can be returned from an entrypoint and allows **decoupling** the value that is **saved in the checkpoint** from the **return value of the entrypoint**.
|
||||
|
||||
The first value is the return value of the entrypoint, and the second value is the value that will be saved in the checkpoint.
|
||||
|
||||
```typescript
|
||||
import { entrypoint, getPreviousState } from "@langchain/langgraph";
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (number: number) => {
|
||||
const previous = getPreviousState<number>() ?? 0;
|
||||
// This will return the previous value to the caller, saving
|
||||
// 2 * number to the checkpoint, which will be used in the next invocation
|
||||
// for the `previous` parameter.
|
||||
return entrypoint.final({
|
||||
value: previous,
|
||||
save: 2 * number,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "1",
|
||||
},
|
||||
};
|
||||
|
||||
await myWorkflow.invoke(3, config); // 0 (previous was undefined)
|
||||
await myWorkflow.invoke(1, config); // 6 (previous was 3 * 2 from the previous invocation)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Task
|
||||
|
||||
A **task** represents a discrete unit of work, such as an API call or data processing step. It has two key characteristics:
|
||||
|
||||
* **Asynchronous Execution**: Tasks are designed to be executed asynchronously, allowing multiple operations to run concurrently without blocking.
|
||||
* **Checkpointing**: Task results are saved to a checkpoint, enabling resumption of the workflow from the last saved state. (See [persistence](persistence.md) for more details).
|
||||
- **Asynchronous Execution**: Tasks are designed to be executed asynchronously, allowing multiple operations to run concurrently without blocking.
|
||||
- **Checkpointing**: Task results are saved to a checkpoint, enabling resumption of the workflow from the last saved state. (See [persistence](persistence.md) for more details).
|
||||
|
||||
### Definition
|
||||
|
||||
:::python
|
||||
Tasks are defined using the `@task` decorator, which wraps a regular Python function.
|
||||
|
||||
```python
|
||||
@@ -475,21 +823,37 @@ def slow_computation(input_value):
|
||||
return result
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Tasks are defined using the `task` function, which wraps a regular function.
|
||||
|
||||
```typescript
|
||||
import { task } from "@langchain/langgraph";
|
||||
|
||||
const slowComputation = task("slowComputation", async (inputValue: any) => {
|
||||
// Simulate a long-running operation
|
||||
return result;
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! important "Serialization"
|
||||
|
||||
The **outputs** of tasks must be JSON-serializable to support checkpointing.
|
||||
|
||||
### Execution
|
||||
|
||||
**Tasks** can only be called from within an **entrypoint**, another **task**, or a [state graph node](./low_level.md#nodes).
|
||||
**Tasks** can only be called from within an **entrypoint**, another **task**, or a [state graph node](./low_level.md#nodes).
|
||||
|
||||
Tasks *cannot* be called directly from the main application code.
|
||||
Tasks _cannot_ be called directly from the main application code.
|
||||
|
||||
When you call a **task**, it returns *immediately* with a future object. A future is a placeholder for a result that will be available later.
|
||||
:::python
|
||||
When you call a **task**, it returns _immediately_ with a future object. A future is a placeholder for a result that will be available later.
|
||||
|
||||
To obtain the result of a **task**, you can either wait for it synchronously (using `result()`) or await it asynchronously (using `await`).
|
||||
|
||||
|
||||
=== "Synchronous Invocation"
|
||||
|
||||
```python
|
||||
@@ -507,6 +871,22 @@ To obtain the result of a **task**, you can either wait for it synchronously (us
|
||||
return await slow_computation(some_input) # Await result asynchronously
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
When you call a **task**, it returns a Promise that can be awaited.
|
||||
|
||||
```typescript
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (someInput: number): Promise<number> => {
|
||||
return await slowComputation(someInput);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## When to use a task
|
||||
|
||||
**Tasks** are useful in the following scenarios:
|
||||
@@ -516,16 +896,21 @@ To obtain the result of a **task**, you can either wait for it synchronously (us
|
||||
- **Parallel Execution**: For I/O-bound tasks, **tasks** enable parallel execution, allowing multiple operations to run concurrently without blocking (e.g., calling multiple APIs).
|
||||
- **Observability**: Wrapping operations in **tasks** provides a way to track the progress of the workflow and monitor the execution of individual operations using [LangSmith](https://docs.smith.langchain.com/).
|
||||
- **Retryable Work**: When work needs to be retried to handle failures or inconsistencies, **tasks** provide a way to encapsulate and manage the retry logic.
|
||||
|
||||
|
||||
## Serialization
|
||||
|
||||
There are two key aspects to serialization in LangGraph:
|
||||
|
||||
1. `@entrypoint` inputs and outputs must be JSON-serializable.
|
||||
2. `@task` outputs must be JSON-serializable.
|
||||
1. `entrypoint` inputs and outputs must be JSON-serializable.
|
||||
2. `task` outputs must be JSON-serializable.
|
||||
|
||||
These requirements are necessary for enabling checkpointing and workflow resumption. Use python primitives
|
||||
like dictionaries, lists, strings, numbers, and booleans to ensure that your inputs and outputs are serializable.
|
||||
:::python
|
||||
These requirements are necessary for enabling checkpointing and workflow resumption. Use python primitives like dictionaries, lists, strings, numbers, and booleans to ensure that your inputs and outputs are serializable.
|
||||
:::
|
||||
|
||||
:::js
|
||||
These requirements are necessary for enabling checkpointing and workflow resumption. Use primitives like objects, arrays, strings, numbers, and booleans to ensure that your inputs and outputs are serializable.
|
||||
:::
|
||||
|
||||
Serialization ensures that workflow state, such as task results and intermediate values, can be reliably saved and restored. This is critical for enabling human-in-the-loop interactions, fault tolerance, and parallel execution.
|
||||
|
||||
@@ -533,9 +918,9 @@ Providing non-serializable inputs or outputs will result in a runtime error when
|
||||
|
||||
## Determinism
|
||||
|
||||
To utilize features like **human-in-the-loop**, any randomness should be encapsulated inside of **tasks**. This guarantees that when execution is halted (e.g., for human in the loop) and then resumed, it will follow the same *sequence of steps*, even if **task** results are non-deterministic.
|
||||
To utilize features like **human-in-the-loop**, any randomness should be encapsulated inside of **tasks**. This guarantees that when execution is halted (e.g., for human in the loop) and then resumed, it will follow the same _sequence of steps_, even if **task** results are non-deterministic.
|
||||
|
||||
LangGraph achieves this behavior by persisting **task** and [**subgraph**](./subgraphs.md) results as they execute. A well-designed workflow ensures that resuming execution follows the *same sequence of steps*, allowing previously computed results to be retrieved correctly without having to re-execute them. This is particularly useful for long-running **tasks** or **tasks** with non-deterministic results, as it avoids repeating previously done work and allows resuming from essentially the same.
|
||||
LangGraph achieves this behavior by persisting **task** and [**subgraph**](./subgraphs.md) results as they execute. A well-designed workflow ensures that resuming execution follows the _same sequence of steps_, allowing previously computed results to be retrieved correctly without having to re-execute them. This is particularly useful for long-running **tasks** or **tasks** with non-deterministic results, as it avoids repeating previously done work and allows resuming from essentially the same.
|
||||
|
||||
While different runs of a workflow can produce different results, resuming a **specific** run should always follow the same sequence of recorded steps. This allows LangGraph to efficiently look up **task** and **subgraph** results that were executed prior to the graph being interrupted and avoid recomputing them.
|
||||
|
||||
@@ -553,6 +938,7 @@ Encapsulate side effects (e.g., writing to a file, sending an email) in tasks to
|
||||
|
||||
In this example, a side effect (writing to a file) is directly included in the workflow, so it will be executed a second time when resuming the workflow.
|
||||
|
||||
:::python
|
||||
```python
|
||||
@entrypoint(checkpointer=checkpointer)
|
||||
def my_workflow(inputs: dict) -> int:
|
||||
@@ -565,11 +951,31 @@ Encapsulate side effects (e.g., writing to a file, sending an email) in tasks to
|
||||
value = interrupt("question")
|
||||
return value
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { entrypoint, interrupt } from "@langchain/langgraph";
|
||||
import fs from "fs";
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow },
|
||||
async (inputs: Record<string, any>) => {
|
||||
// This code will be executed a second time when resuming the workflow.
|
||||
// Which is likely not what you want.
|
||||
fs.writeFileSync("output.txt", "Side effect executed");
|
||||
const value = interrupt("question");
|
||||
return value;
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
=== "Correct"
|
||||
|
||||
In this example, the side effect is encapsulated in a task, ensuring consistent execution upon resumption.
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.func import task
|
||||
|
||||
@@ -587,17 +993,43 @@ Encapsulate side effects (e.g., writing to a file, sending an email) in tasks to
|
||||
value = interrupt("question")
|
||||
return value
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { entrypoint, task, interrupt } from "@langchain/langgraph";
|
||||
import * as fs from "fs";
|
||||
|
||||
const writeToFile = task("writeToFile", async () => {
|
||||
fs.writeFileSync("output.txt", "Side effect executed");
|
||||
});
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (inputs: Record<string, any>) => {
|
||||
// The side effect is now encapsulated in a task.
|
||||
await writeToFile();
|
||||
const value = interrupt("question");
|
||||
return value;
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
### Non-deterministic control flow
|
||||
|
||||
Operations that might give different results each time (like getting current time or random numbers) should be encapsulated in tasks to ensure that on resume, the same result is returned.
|
||||
|
||||
* In a task: Get random number (5) → interrupt → resume → (returns 5 again) → ...
|
||||
* Not in a task: Get random number (5) → interrupt → resume → get new random number (7) → ...
|
||||
- In a task: Get random number (5) → interrupt → resume → (returns 5 again) → ...
|
||||
- Not in a task: Get random number (5) → interrupt → resume → get new random number (7) → ...
|
||||
|
||||
This is especially important when using **human-in-the-loop** workflows with multiple interrupts calls. LangGraph keeps a list
|
||||
of resume values for each task/entrypoint. When an interrupt is encountered, it's matched with the corresponding resume value.
|
||||
This matching is strictly **index-based**, so the order of the resume values should match the order of the interrupts.
|
||||
:::python
|
||||
This is especially important when using **human-in-the-loop** workflows with multiple interrupts calls. LangGraph keeps a list of resume values for each task/entrypoint. When an interrupt is encountered, it's matched with the corresponding resume value. This matching is strictly **index-based**, so the order of the resume values should match the order of the interrupts.
|
||||
:::
|
||||
|
||||
:::js
|
||||
This is especially important when using **human-in-the-loop** workflows with multiple interrupt calls. LangGraph keeps a list of resume values for each task/entrypoint. When an interrupt is encountered, it's matched with the corresponding resume value. This matching is strictly **index-based**, so the order of the resume values should match the order of the interrupts.
|
||||
:::
|
||||
|
||||
If order of execution is not maintained when resuming, one `interrupt` call may be matched with the wrong `resume` value, leading to incorrect results.
|
||||
|
||||
@@ -607,6 +1039,7 @@ Please read the section on [determinism](#determinism) for more details.
|
||||
|
||||
In this example, the workflow uses the current time to determine which task to execute. This is non-deterministic because the result of the workflow depends on the time at which it is executed.
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.func import entrypoint
|
||||
|
||||
@@ -615,24 +1048,51 @@ Please read the section on [determinism](#determinism) for more details.
|
||||
t0 = inputs["t0"]
|
||||
# highlight-next-line
|
||||
t1 = time.time()
|
||||
|
||||
|
||||
delta_t = t1 - t0
|
||||
|
||||
|
||||
if delta_t > 1:
|
||||
result = slow_task(1).result()
|
||||
value = interrupt("question")
|
||||
else:
|
||||
result = slow_task(2).result()
|
||||
value = interrupt("question")
|
||||
|
||||
|
||||
return {
|
||||
"result": result,
|
||||
"value": value
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { entrypoint, interrupt } from "@langchain/langgraph";
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (inputs: { t0: number }) => {
|
||||
const t1 = Date.now();
|
||||
|
||||
const deltaT = t1 - inputs.t0;
|
||||
|
||||
if (deltaT > 1000) {
|
||||
const result = await slowTask(1);
|
||||
const value = interrupt("question");
|
||||
return { result, value };
|
||||
} else {
|
||||
const result = await slowTask(2);
|
||||
const value = interrupt("question");
|
||||
return { result, value };
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
=== "Correct"
|
||||
|
||||
:::python
|
||||
In this example, the workflow uses the input `t0` to determine which task to execute. This is deterministic because the result of the workflow depends only on the input.
|
||||
|
||||
```python
|
||||
@@ -651,19 +1111,48 @@ Please read the section on [determinism](#determinism) for more details.
|
||||
t0 = inputs["t0"]
|
||||
# highlight-next-line
|
||||
t1 = get_time().result()
|
||||
|
||||
|
||||
delta_t = t1 - t0
|
||||
|
||||
|
||||
if delta_t > 1:
|
||||
result = slow_task(1).result()
|
||||
value = interrupt("question")
|
||||
else:
|
||||
result = slow_task(2).result()
|
||||
value = interrupt("question")
|
||||
|
||||
|
||||
return {
|
||||
"result": result,
|
||||
"value": value
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
In this example, the workflow uses the input `t0` to determine which task to execute. This is deterministic because the result of the workflow depends only on the input.
|
||||
|
||||
```typescript
|
||||
import { entrypoint, task, interrupt } from "@langchain/langgraph";
|
||||
|
||||
const getTime = task("getTime", () => Date.now());
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (inputs: { t0: number }): Promise<any> => {
|
||||
const t1 = await getTime();
|
||||
|
||||
const deltaT = t1 - inputs.t0;
|
||||
|
||||
if (deltaT > 1000) {
|
||||
const result = await slowTask(1);
|
||||
const value = interrupt("question");
|
||||
return { result, value };
|
||||
} else {
|
||||
const result = await slowTask(2);
|
||||
const value = interrupt("question");
|
||||
return { result, value };
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
@@ -23,9 +23,18 @@ To review, edit, and approve tool calls in an agent or workflow, [use LangGraph'
|
||||
|
||||
## Key capabilities
|
||||
|
||||
* **Persistent execution state**: LangGraph allows you to pause execution **indefinitely** — for minutes, hours, or even days—until human input is received. This is possible because LangGraph checkpoints the graph state after each step, which allows the system to persist execution context and later resume the workflow, continuing from where it left off. This supports asynchronous human review or input without time constraints.
|
||||
* **Persistent execution state**: Interrupts use LangGraph's [persistence](./persistence.md) layer, which saves the graph state, to indefinitely pause graph execution until you resume. This is possible because LangGraph checkpoints the graph state after each step, which allows the system to persist execution context and later resume the workflow, continuing from where it left off. This supports asynchronous human review or input without time constraints.
|
||||
|
||||
* **Flexible integration points**: HIL logic can be introduced at any point in the workflow. This allows targeted human involvement, such as approving API calls, correcting outputs, or guiding conversations.
|
||||
There are two ways to pause a graph:
|
||||
|
||||
- [Dynamic interrupts](../how-tos/human_in_the_loop/add-human-in-the-loop.md#pause-using-interrupt): Use `interrupt` to pause a graph from inside a specific node, based on the current state of the graph.
|
||||
- [Static interrupts](../how-tos/human_in_the_loop/add-human-in-the-loop.md#debug-with-interrupts): Use `interrupt_before` and `interrupt_after` to pause the graph at pre-defined points, either before or after a node executes.
|
||||
|
||||
<figure markdown="1">
|
||||
{: style="max-height:400px"}
|
||||
<figcaption>An example graph consisting of 3 sequential steps with a breakpoint before step_3. </figcaption> </figure>
|
||||
|
||||
* **Flexible integration points**: Human-in-the-loop logic can be introduced at any point in the workflow. This allows targeted human involvement, such as approving API calls, correcting outputs, or guiding conversations.
|
||||
|
||||
## Patterns
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 121 KiB |
@@ -7,29 +7,66 @@ search:
|
||||
|
||||
**LangGraph CLI** is a multi-platform command-line tool for building and running the [LangGraph API server](./langgraph_server.md) locally. The resulting server includes all API endpoints for your graph's runs, threads, assistants, etc. as well as the other services required to run your agent, including a managed database for checkpointing and storage.
|
||||
|
||||
:::python
|
||||
|
||||
## Installation
|
||||
|
||||
The LangGraph CLI can be installed via pip or [Homebrew](https://brew.sh/):
|
||||
|
||||
=== "pip"
|
||||
=== "pip"
|
||||
|
||||
```bash
|
||||
pip install langgraph-cli
|
||||
```
|
||||
|
||||
=== "Homebrew"
|
||||
|
||||
```bash
|
||||
brew install langgraph-cli
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
## Installation
|
||||
|
||||
The LangGraph.js CLI can be installed from the NPM registry:
|
||||
|
||||
=== "npx"
|
||||
```bash
|
||||
npx @langchain/langgraph-cli
|
||||
```
|
||||
|
||||
=== "npm"
|
||||
```bash
|
||||
npm install @langchain/langgraph-cli
|
||||
```
|
||||
|
||||
=== "yarn"
|
||||
```bash
|
||||
yarn add @langchain/langgraph-cli
|
||||
```
|
||||
|
||||
=== "pnpm"
|
||||
```bash
|
||||
pnpm add @langchain/langgraph-cli
|
||||
```
|
||||
|
||||
=== "bun"
|
||||
```bash
|
||||
bun add @langchain/langgraph-cli
|
||||
```
|
||||
:::
|
||||
|
||||
## Commands
|
||||
|
||||
LangGraph CLI provides the following core functionality:
|
||||
|
||||
| Command | Description |
|
||||
| -------- | -------|
|
||||
| [`langgraph build`](../cloud/reference/cli.md#build) | Builds a Docker image for the [LangGraph API server](./langgraph_server.md) that can be directly deployed. |
|
||||
| [`langgraph dev`](../cloud/reference/cli.md#dev) | Starts a lightweight development server that requires no Docker installation. This server is ideal for rapid development and testing. This is available in version 0.1.55 and up.
|
||||
| Command | Description |
|
||||
| -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [`langgraph build`](../cloud/reference/cli.md#build) | Builds a Docker image for the [LangGraph API server](./langgraph_server.md) that can be directly deployed. |
|
||||
| [`langgraph dev`](../cloud/reference/cli.md#dev) | Starts a lightweight development server that requires no Docker installation. This server is ideal for rapid development and testing. |
|
||||
| [`langgraph dockerfile`](../cloud/reference/cli.md#dockerfile) | Generates a [Dockerfile](https://docs.docker.com/reference/dockerfile/) that can be used to build images for and deploy instances of the [LangGraph API server](./langgraph_server.md). This is useful if you want to further customize the dockerfile or deploy in a more custom way. |
|
||||
| [`langgraph up`](../cloud/reference/cli.md#up) | Starts an instance of the [LangGraph API server](./langgraph_server.md) locally in a docker container. This requires the docker server to be running locally. It also requires a LangSmith API key for local development or a license key for production use. |
|
||||
| [`langgraph up`](../cloud/reference/cli.md#up) | Starts an instance of the [LangGraph API server](./langgraph_server.md) locally in a docker container. This requires the docker server to be running locally. It also requires a LangSmith API key for local development or a license key for production use. |
|
||||
|
||||
For more information, see the [LangGraph CLI Reference](../cloud/reference/cli.md).
|
||||
|
||||
@@ -11,11 +11,11 @@ To deploy a [LangGraph Server](../concepts/langgraph_server.md), follow the how-
|
||||
|
||||
The Cloud SaaS deployment option is a fully managed model for deployment where we manage the [control plane](./langgraph_control_plane.md) and [data plane](./langgraph_data_plane.md) in our cloud.
|
||||
|
||||
| | [Control plane](../concepts/langgraph_control_plane.md) | [Data plane](../concepts/langgraph_data_plane.md) |
|
||||
|-------------------|-------------------|------------|
|
||||
| **What is it?** | <ul><li>Control plane UI for creating deployments and revisions</li><li>Control plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
|
||||
| **Where is it hosted?** | LangChain's cloud | LangChain's cloud |
|
||||
| **Who provisions and manages it?** | LangChain | LangChain |
|
||||
| | [Control plane](../concepts/langgraph_control_plane.md) | [Data plane](../concepts/langgraph_data_plane.md) |
|
||||
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **What is it?** | <ul><li>Control plane UI for creating deployments and revisions</li><li>Control plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
|
||||
| **Where is it hosted?** | LangChain's cloud | LangChain's cloud |
|
||||
| **Who provisions and manages it?** | LangChain | LangChain |
|
||||
|
||||
## Architecture
|
||||
|
||||
|
||||
@@ -10,4 +10,4 @@ The LangGraph Platform consists of components that work together to support the
|
||||
- [LangGraph control plane](./langgraph_control_plane.md): The LangGraph Control Plane refers to the Control Plane UI where users create and update LangGraph Servers and the Control Plane APIs that support the UI experience.
|
||||
- [LangGraph data plane](./langgraph_data_plane.md): The LangGraph Data Plane refers to LangGraph Servers, the corresponding infrastructure for each server, and the "listener" application that continuously polls for updates from the LangGraph Control Plane.
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -44,8 +44,8 @@ This section describes various features of the control plane.
|
||||
|
||||
For simplicity, the control plane offers two deployment types with different resource allocations: `Development` and `Production`.
|
||||
|
||||
| **Deployment Type** | **CPU/Memory** | **Scaling** | **Database** |
|
||||
|---------------------|-----------------|---------------------|----------------------------------------------------------------------------------|
|
||||
| **Deployment Type** | **CPU/Memory** | **Scaling** | **Database** |
|
||||
| ------------------- | --------------- | ----------------- | -------------------------------------------------------------------------------- |
|
||||
| Development | 1 CPU, 1 GB RAM | Up to 1 replica | 10 GB disk, no backups |
|
||||
| Production | 2 CPU, 2 GB RAM | Up to 10 replicas | Autoscaling disk, automatic backups, highly available (multi-zone configuration) |
|
||||
|
||||
@@ -56,7 +56,7 @@ CPU and memory resources are per replica.
|
||||
Once a deployment is created, the deployment type cannot be changed.
|
||||
|
||||
!!! info "Self-Hosted Deployment"
|
||||
Resources for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments can be fully customized. Deployment types are only applicable for [Cloud SaaS](../concepts/langgraph_cloud.md) deployments.
|
||||
Resources for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments can be fully customized. Deployment types are only applicable for [Cloud SaaS](../concepts/langgraph_cloud.md) deployments.
|
||||
|
||||
#### Production
|
||||
|
||||
@@ -69,12 +69,12 @@ Resources for `Production` type deployments can be manually increased on a case-
|
||||
`Development` type deployments are suitable development and testing. For example, select `Development` for internal testing environments. `Development` type deployments are not suitable for "production" workloads.
|
||||
|
||||
!!! danger "Preemptible Compute Infrastructure"
|
||||
`Development` type deployments (API server, queue server, and database) are provisioned on preemptible compute infrastructure. This means the compute infrastructure **may be terminated at any time without notice**. This may result in intermittent...
|
||||
`Development` type deployments (API server, queue server, and database) are provisioned on preemptible compute infrastructure. This means the compute infrastructure **may be terminated at any time without notice**. This may result in intermittent...
|
||||
|
||||
- Redis connection timeouts/errors
|
||||
- Postgres connection timeouts/errors
|
||||
- Failed or retrying background runs
|
||||
|
||||
|
||||
This behavior is expected. Preemptible compute infrastructure **significantly reduces the cost to provision a `Development` type deployment**. By design, LangGraph Server is fault-tolerant. The implementation will automatically attempt to recover from Redis/Postgres connection errors and retry failed background runs.
|
||||
|
||||
`Production` type deployments are provisioned on durable compute infrastructure, not preemptible compute infrastructure.
|
||||
@@ -92,7 +92,7 @@ There is no direct access to the database. All access to the database occurs thr
|
||||
The database is never deleted until the deployment itself is deleted.
|
||||
|
||||
!!! info
|
||||
A custom Postgres instance can be configured for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
A custom Postgres instance can be configured for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
|
||||
### Asynchronous Deployment
|
||||
|
||||
@@ -119,6 +119,11 @@ These metrics are displayed as charts in the Control Plane UI.
|
||||
|
||||
### LangSmith Integration
|
||||
|
||||
A [LangSmith](https://docs.smith.langchain.com/) tracing project is automatically created for each deployment. The tracing project has the same name as the deployment. When creating a deployment, the `LANGCHAIN_TRACING` and `LANGSMITH_API_KEY`/`LANGCHAIN_API_KEY` environment variables do not need to be specified; they are set automatically by the control plane.
|
||||
A [LangSmith](https://docs.smith.langchain.com/) tracing project and LangSmith API key are automatically created for each deployment. The deployment uses the API key to automatically send traces to LangSmith.
|
||||
|
||||
When a deployment is deleted, the traces and the tracing project are not deleted.
|
||||
- The tracing project has the same name as the deployment.
|
||||
- The API key has the description `LangGraph Platform: <deployment_name>`.
|
||||
- The API key is never revealed and cannot be deleted manually.
|
||||
- When creating a deployment, the `LANGCHAIN_TRACING` and `LANGSMITH_API_KEY`/`LANGCHAIN_API_KEY` environment variables do not need to be specified; they are set automatically by the control plane.
|
||||
|
||||
When a deployment is deleted, the traces and the tracing project are not deleted. However, the API will be deleted when the deployment is deleted.
|
||||
|
||||
@@ -78,25 +78,25 @@ Scale down actions are delayed for 30 minutes before any action is taken. In oth
|
||||
### Static IP Addresses
|
||||
|
||||
!!! info "Only for Cloud SaaS"
|
||||
Static IP addresses are only available for [Cloud SaaS](../concepts/langgraph_cloud.md) deployments.
|
||||
Static IP addresses are only available for [Cloud SaaS](../concepts/langgraph_cloud.md) deployments.
|
||||
|
||||
All traffic from deployments created after January 6th 2025 will come through a NAT gateway. This NAT gateway will have several static IP addresses depending on the data region. Refer to the table below for the list of static IP addresses:
|
||||
|
||||
| US | EU |
|
||||
|----------------|----------------|
|
||||
| -------------- | -------------- |
|
||||
| 35.197.29.146 | 34.13.192.67 |
|
||||
| 34.145.102.123 | 34.147.105.64 |
|
||||
| 34.169.45.153 | 34.90.22.166 |
|
||||
| 34.82.222.17 | 34.147.36.213 |
|
||||
| 35.227.171.135 | 34.32.137.113 |
|
||||
| 35.227.171.135 | 34.32.137.113 |
|
||||
| 34.169.88.30 | 34.91.238.184 |
|
||||
| 34.19.93.202 | 35.204.101.241 |
|
||||
| 34.19.34.50 | 35.204.48.32 |
|
||||
|
||||
### Custom Postgres
|
||||
|
||||
!!! info
|
||||
Custom Postgres instances are only available for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
!!! info
|
||||
Custom Postgres instances are only available for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
|
||||
A custom Postgres instance can be used instead of the [one automatically created by the control plane](./langgraph_control_plane.md#database-provisioning). Specify the [`POSTGRES_URI_CUSTOM`](../cloud/reference/env_var.md#postgres_uri_custom) environment variable to use a custom Postgres instance.
|
||||
|
||||
@@ -105,33 +105,32 @@ Multiple deployments can share the same Postgres instance. For example, for `Dep
|
||||
### Custom Redis
|
||||
|
||||
!!! info
|
||||
Custom Redis instances are only available for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_control_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
Custom Redis instances are only available for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_control_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
|
||||
A custom Redis instance can be used instead of the one automatically created by the control plane. Specify the [REDIS_URI_CUSTOM](../cloud/reference/env_var.md#redis_uri_custom) environment variable to use a custom Redis instance.
|
||||
|
||||
|
||||
Multiple deployments can share the same Redis instance. For example, for `Deployment A`, `REDIS_URI_CUSTOM` can be set to `redis://<hostname_1>:<port>/1` and for `Deployment B`, `REDIS_URI_CUSTOM` can be set to `redis://<hostname_1>:<port>/2`. `1` and `2` are different database numbers within the same instance, but `<hostname_1>` is shared. **The same database number cannot be used for separate deployments**.
|
||||
|
||||
### LangSmith Tracing
|
||||
|
||||
LangGraph Server is automatically configured to send traces to LangSmith. See the table below for details with respect to each deployment option.
|
||||
|
||||
| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container |
|
||||
|------------|------------------------|---------------------------|----------------------|
|
||||
| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container |
|
||||
| ---------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
|
||||
| Required<br><br>Trace to LangSmith SaaS. | Optional<br><br>Disable tracing or trace to LangSmith SaaS. | Optional<br><br>Disable tracing or trace to Self-Hosted LangSmith. | Optional<br><br>Disable tracing, trace to LangSmith SaaS, or trace to Self-Hosted LangSmith. |
|
||||
|
||||
### Telemetry
|
||||
|
||||
LangGraph Server is automatically configured to report telemetry metadata for billing purposes. See the table below for details with respect to each deployment option.
|
||||
|
||||
| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container |
|
||||
|------------|------------------------|---------------------------|----------------------|
|
||||
| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container |
|
||||
| --------------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Telemetry sent to LangSmith SaaS. | Telemetry sent to LangSmith SaaS. | Self-reported usage (audit) for air-gapped license key.<br><br>Telemetry sent to LangSmith SaaS for LangGraph Platform License Key. | Self-reported usage (audit) for air-gapped license key.<br><br>Telemetry sent to LangSmith SaaS for LangGraph Platform License Key. |
|
||||
|
||||
### Licensing
|
||||
|
||||
LangGraph Server is automatically configured to perform license key validation. See the table below for details with respect to each deployment option.
|
||||
|
||||
| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container |
|
||||
|------------|------------------------|---------------------------|----------------------|
|
||||
| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container |
|
||||
| --------------------------------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
|
||||
| LangSmith API Key validated against LangSmith SaaS. | LangSmith API Key validated against LangSmith SaaS. | Air-gapped license key or LangGraph Platform License Key validated against LangSmith SaaS. | Air-gapped license key or LangGraph Platform License Key validated against LangSmith SaaS. |
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](./deployment_options.md#self-hosted-data-plane) and [Self-Hosted Control Plane](./deployment_options.md#self-hosted-control-plane).
|
||||
|
||||
!!! info "Important"
|
||||
|
||||
The Self-Hosted Control Plane deployment option requires an [Enterprise](plans.md) plan.
|
||||
|
||||
## Requirements
|
||||
|
||||
- You use `langgraph-cli` and/or [LangGraph Studio](./langgraph_studio.md) app to test graph locally.
|
||||
- You use the [LangGraph CLI](./langgraph_cli.md) and/or [LangGraph Studio](./langgraph_studio.md) app to test graph locally.
|
||||
- You use `langgraph build` command to build image.
|
||||
- You have a Self-Hosted LangSmith instance deployed.
|
||||
- You are using Ingress for your LangSmith instance. All agents will be deployed as Kubernetes services behind this ingress.
|
||||
@@ -16,11 +17,11 @@ There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](.
|
||||
|
||||
The [Self-Hosted Control Plane](./langgraph_self_hosted_control_plane.md) deployment option is a fully self-hosted model for deployment where you manage the [control plane](./langgraph_control_plane.md) and [data plane](./langgraph_data_plane.md) in your cloud. This option gives you full control and responsibility of the control plane and data plane infrastructure.
|
||||
|
||||
| | [Control plane](../concepts/langgraph_control_plane.md) | [Data plane](../concepts/langgraph_data_plane.md) |
|
||||
|-------------------|-------------------|------------|
|
||||
| **What is it?** | <ul><li>Control plane UI for creating deployments and revisions</li><li>Control plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
|
||||
| **Where is it hosted?** | Your cloud | Your cloud |
|
||||
| **Who provisions and manages it?** | You | You |
|
||||
| | [Control plane](../concepts/langgraph_control_plane.md) | [Data plane](../concepts/langgraph_data_plane.md) |
|
||||
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **What is it?** | <ul><li>Control plane UI for creating deployments and revisions</li><li>Control plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
|
||||
| **Where is it hosted?** | Your cloud | Your cloud |
|
||||
| **Who provisions and manages it?** | You | You |
|
||||
|
||||
### Architecture
|
||||
|
||||
@@ -28,7 +29,7 @@ The [Self-Hosted Control Plane](./langgraph_self_hosted_control_plane.md) deploy
|
||||
|
||||
### Compute Platforms
|
||||
|
||||
- **Kubernetes**: The Self-Hosted Control Plane deployment option supports deploying control plane and data plane infrastructure to any Kubernetes cluster.
|
||||
- **Kubernetes**: The Self-Hosted Control Plane deployment option supports deploying control plane and data plane infrastructure to any Kubernetes cluster.
|
||||
|
||||
!!! tip
|
||||
If you would like to enable this on your LangSmith instance, please follow the [Self-Hosted Control Plane deployment guide](../cloud/deployment/self_hosted_control_plane.md).
|
||||
If you would like to enable this on your LangSmith instance, please follow the [Self-Hosted Control Plane deployment guide](../cloud/deployment/self_hosted_control_plane.md).
|
||||
|
||||
@@ -8,6 +8,7 @@ search:
|
||||
There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](./deployment_options.md#self-hosted-data-plane) and [Self-Hosted Control Plane](./deployment_options.md#self-hosted-control-plane).
|
||||
|
||||
!!! info "Important"
|
||||
|
||||
The Self-Hosted Data Plane deployment option requires an [Enterprise](plans.md) plan.
|
||||
|
||||
## Requirements
|
||||
@@ -19,11 +20,11 @@ There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](.
|
||||
|
||||
The [Self-Hosted Data Plane](../cloud/deployment/self_hosted_data_plane.md) deployment option is a "hybrid" model for deployment where we manage the [control plane](./langgraph_control_plane.md) in our cloud and you manage the [data plane](./langgraph_data_plane.md) in your cloud. This option provides a way to securely manage your data plane infrastructure, while offloading control plane management to us. When using the Self-Hosted Data Plane version, you authenticate with a [LangSmith](https://smith.langchain.com/) API key.
|
||||
|
||||
| | [Control plane](../concepts/langgraph_control_plane.md) | [Data plane](../concepts/langgraph_data_plane.md) |
|
||||
|-------------------|-------------------|------------|
|
||||
| **What is it?** | <ul><li>Control plane UI for creating deployments and revisions</li><li>Control plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
|
||||
| **Where is it hosted?** | LangChain's cloud | Your cloud |
|
||||
| **Who provisions and manages it?** | LangChain | You |
|
||||
| | [Control plane](../concepts/langgraph_control_plane.md) | [Data plane](../concepts/langgraph_data_plane.md) |
|
||||
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **What is it?** | <ul><li>Control plane UI for creating deployments and revisions</li><li>Control plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
|
||||
| **Where is it hosted?** | LangChain's cloud | Your cloud |
|
||||
| **Who provisions and manages it?** | LangChain | You |
|
||||
|
||||
For information on how to deploy a [LangGraph Server](../concepts/langgraph_server.md) to Self-Hosted Data Plane, see [Deploy to Self-Hosted Data Plane](../cloud/deployment/self_hosted_data_plane.md)
|
||||
|
||||
@@ -37,4 +38,4 @@ For information on how to deploy a [LangGraph Server](../concepts/langgraph_serv
|
||||
- **Amazon ECS**: Coming soon!
|
||||
|
||||
!!! tip
|
||||
If you would like to deploy to Kubernetes, you can follow the [Self-Hosted Data Plane deployment guide](../cloud/deployment/self_hosted_data_plane.md).
|
||||
If you would like to deploy to Kubernetes, you can follow the [Self-Hosted Data Plane deployment guide](../cloud/deployment/self_hosted_data_plane.md).
|
||||
|
||||
@@ -13,21 +13,6 @@ Use LangGraph Server to create and manage [assistants](assistants.md), [threads]
|
||||
|
||||
For detailed information on the API endpoints and data models, see [LangGraph Platform API reference docs](../cloud/reference/api/api_ref.html).
|
||||
|
||||
## Server versions
|
||||
|
||||
There are two versions of LangGraph Server:
|
||||
|
||||
- `Lite` is a limited version of the LangGraph Server that you can run locally or in a self-hosted manner (up to 1 million [nodes executed](../concepts/faq.md#what-does-nodes-executed-mean-for-langgraph-platform-usage) per year).
|
||||
- `Enterprise` is the full version of the LangGraph Server. To use the `Enterprise` version, you must acquire a license key that you will need to specify when running the Docker image. To acquire a license key, please email sales@langchain.dev.
|
||||
|
||||
Feature Differences:
|
||||
|
||||
| | Lite | Enterprise |
|
||||
|-------|------------|------------|
|
||||
| [Cron Jobs](../cloud/concepts/cron_jobs.md) |❌|✅|
|
||||
| [Custom Authentication](../concepts/auth.md) |❌|✅|
|
||||
| [Deployment options](../concepts/deployment_options.md) | Standalone container | Cloud SaaS, Self-Hosted Data Plane, Self-Hosted Control Plane, Standalone container
|
||||
|
||||
## Application structure
|
||||
|
||||
To deploy a LangGraph Server application, you need to specify the graph(s) you want to deploy, as well as any relevant configuration settings, such as dependencies and environment variables.
|
||||
|
||||
@@ -34,12 +34,3 @@ The Standalone Container deployment option supports deploying data plane infrast
|
||||
### Docker
|
||||
|
||||
The Standalone Container deployment option supports deploying data plane infrastructure to any Docker-supported compute platform.
|
||||
|
||||
## Lite vs. Enterprise
|
||||
|
||||
The Standalone Container deployment option supports both of the [server versions](../concepts/langgraph_server.md#langgraph-server):
|
||||
|
||||
- The `Lite` version is free, but has limited features.
|
||||
- The `Enterprise` version has custom pricing and is fully featured.
|
||||
|
||||
For more details on feature difference, see [LangGraph Server](../concepts/langgraph_server.md#server-versions).
|
||||
|
||||
+628
-50
@@ -9,13 +9,13 @@ search:
|
||||
|
||||
At its core, LangGraph models agent workflows as graphs. You define the behavior of your agents using three key components:
|
||||
|
||||
1. [`State`](#state): A shared data structure that represents the current snapshot of your application. It can be any Python type, but is typically a `TypedDict` or Pydantic `BaseModel`.
|
||||
1. [`State`](#state): A shared data structure that represents the current snapshot of your application. It can be any data type, but is typically defined using a shared state schema.
|
||||
|
||||
2. [`Nodes`](#nodes): Python functions that encode the logic of your agents. They receive the current `State` as input, perform some computation or side-effect, and return an updated `State`.
|
||||
2. [`Nodes`](#nodes): Functions that encode the logic of your agents. They receive the current state as input, perform some computation or side-effect, and return an updated state.
|
||||
|
||||
3. [`Edges`](#edges): Python functions that determine which `Node` to execute next based on the current `State`. They can be conditional branches or fixed transitions.
|
||||
3. [`Edges`](#edges): Functions that determine which `Node` to execute next based on the current state. They can be conditional branches or fixed transitions.
|
||||
|
||||
By composing `Nodes` and `Edges`, you can create complex, looping workflows that evolve the `State` over time. The real power, though, comes from how LangGraph manages that `State`. To emphasize: `Nodes` and `Edges` are nothing more than Python functions - they can contain an LLM or just good ol' Python code.
|
||||
By composing `Nodes` and `Edges`, you can create complex, looping workflows that evolve the state over time. The real power, though, comes from how LangGraph manages that state. To emphasize: `Nodes` and `Edges` are nothing more than functions - they can contain an LLM or just good ol' code.
|
||||
|
||||
In short: _nodes do the work, edges tell what to do next_.
|
||||
|
||||
@@ -33,21 +33,51 @@ To build your graph, you first define the [state](#state), you then add [nodes](
|
||||
|
||||
Compiling is a pretty simple step. It provides a few basic checks on the structure of your graph (no orphaned nodes, etc). It is also where you can specify runtime args like [checkpointers](./persistence.md) and breakpoints. You compile your graph by just calling the `.compile` method:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
graph = graph_builder.compile(...)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
const graph = new StateGraph(StateAnnotation)
|
||||
.addNode("nodeA", nodeA)
|
||||
.addEdge(START, "nodeA")
|
||||
.addEdge("nodeA", END)
|
||||
.compile();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
You **MUST** compile your graph before you can use it.
|
||||
|
||||
## State
|
||||
|
||||
:::python
|
||||
The first thing you do when you define a graph is define the `State` of the graph. The `State` consists of the [schema of the graph](#schema) as well as [`reducer` functions](#reducers) which specify how to apply updates to the state. The schema of the `State` will be the input schema to all `Nodes` and `Edges` in the graph, and can be either a `TypedDict` or a `Pydantic` model. All `Nodes` will emit updates to the `State` which are then applied using the specified `reducer` function.
|
||||
:::
|
||||
|
||||
:::js
|
||||
The first thing you do when you define a graph is define the `State` of the graph. The `State` consists of the [schema of the graph](#schema) as well as [`reducer` functions](#reducers) which specify how to apply updates to the state. The schema of the `State` will be the input schema to all `Nodes` and `Edges` in the graph, and can be either a Zod schema or a schema built using `Annotation.Root`. All `Nodes` will emit updates to the `State` which are then applied using the specified `reducer` function.
|
||||
:::
|
||||
|
||||
### Schema
|
||||
|
||||
The main documented way to specify the schema of a graph is by using `TypedDict`. However, we also support [using a Pydantic BaseModel](../how-tos/graph-api.md#use-pydantic-models-for-graph-state) as your graph state to add **default values** and additional data validation.
|
||||
:::python
|
||||
The main documented way to specify the schema of a graph is by using a [`TypedDict`](https://docs.python.org/3/library/typing.html#typing.TypedDict). If you want to provide default values in your state, use a [`dataclass`](https://docs.python.org/3/library/dataclasses.html). We also support using a Pydantic [BaseModel](../how-tos/graph-api.md#use-pydantic-models-for-graph-state) as your graph state if you want recursive data validation (though note that pydantic is less performant than a `TypedDict` or `dataclass`).
|
||||
|
||||
By default, the graph will have the same input and output schemas. If you want to change this, you can also specify explicit input and output schemas directly. This is useful when you have a lot of keys, and some are explicitly for input and others for output. See the [guide here](../how-tos/graph-api.md#define-input-and-output-schemas) for how to use.
|
||||
:::
|
||||
|
||||
:::js
|
||||
The main documented way to specify the schema of a graph is by using Zod schemas. However, we also support using the `Annotation` API to define the schema of the graph.
|
||||
|
||||
By default, the graph will have the same input and output schemas. If you want to change this, you can also specify explicit input and output schemas directly. This is useful when you have a lot of keys, and some are explicitly for input and others for output.
|
||||
:::
|
||||
|
||||
#### Multiple schemas
|
||||
|
||||
@@ -56,12 +86,14 @@ Typically, all graph nodes communicate with a single schema. This means that the
|
||||
- Internal nodes can pass information that is not required in the graph's input / output.
|
||||
- We may also want to use different input / output schemas for the graph. The output might, for example, only contain a single relevant output key.
|
||||
|
||||
It is possible to have nodes write to private state channels inside the graph for internal node communication. We can simply define a private schema, `PrivateState`. See [this guide](../how-tos/graph-api.md#pass-private-state-between-nodes) for more detail.
|
||||
It is possible to have nodes write to private state channels inside the graph for internal node communication. We can simply define a private schema, `PrivateState`.
|
||||
|
||||
It is also possible to define explicit input and output schemas for a graph. In these cases, we define an "internal" schema that contains _all_ keys relevant to graph operations. But, we also define `input` and `output` schemas that are sub-sets of the "internal" schema to constrain the input and output of the graph. See [this guide](../how-tos/graph-api.md#define-input-and-output-schemas) for more detail.
|
||||
|
||||
Let's look at an example:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
class InputState(TypedDict):
|
||||
user_input: str
|
||||
@@ -100,14 +132,80 @@ builder.add_edge("node_3", END)
|
||||
|
||||
graph = builder.compile()
|
||||
graph.invoke({"user_input":"My"})
|
||||
{'graph_output': 'My name is Lance'}
|
||||
# {'graph_output': 'My name is Lance'}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
const InputState = z.object({
|
||||
userInput: z.string(),
|
||||
});
|
||||
|
||||
const OutputState = z.object({
|
||||
graphOutput: z.string(),
|
||||
});
|
||||
|
||||
const OverallState = z.object({
|
||||
foo: z.string(),
|
||||
userInput: z.string(),
|
||||
graphOutput: z.string(),
|
||||
});
|
||||
|
||||
const PrivateState = z.object({
|
||||
bar: z.string(),
|
||||
});
|
||||
|
||||
const graph = new StateGraph({
|
||||
state: OverallState,
|
||||
input: InputState,
|
||||
output: OutputState,
|
||||
})
|
||||
.addNode("node1", (state) => {
|
||||
// Write to OverallState
|
||||
return { foo: state.userInput + " name" };
|
||||
})
|
||||
.addNode("node2", (state) => {
|
||||
// Read from OverallState, write to PrivateState
|
||||
return { bar: state.foo + " is" };
|
||||
})
|
||||
.addNode(
|
||||
"node3",
|
||||
(state) => {
|
||||
// Read from PrivateState, write to OutputState
|
||||
return { graphOutput: state.bar + " Lance" };
|
||||
},
|
||||
{ input: PrivateState }
|
||||
)
|
||||
.addEdge(START, "node1")
|
||||
.addEdge("node1", "node2")
|
||||
.addEdge("node2", "node3")
|
||||
.addEdge("node3", END)
|
||||
.compile();
|
||||
|
||||
await graph.invoke({ userInput: "My" });
|
||||
// { graphOutput: 'My name is Lance' }
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
There are two subtle and important points to note here:
|
||||
|
||||
:::python
|
||||
|
||||
1. We pass `state: InputState` as the input schema to `node_1`. But, we write out to `foo`, a channel in `OverallState`. How can we write out to a state channel that is not included in the input schema? This is because a node _can write to any state channel in the graph state._ The graph state is the union of the state channels defined at initialization, which includes `OverallState` and the filters `InputState` and `OutputState`.
|
||||
|
||||
2. We initialize the graph with `StateGraph(OverallState,input_schema=InputState,output_schema=OutputState)`. So, how can we write to `PrivateState` in `node_2`? How does the graph gain access to this schema if it was not passed in the `StateGraph` initialization? We can do this because _nodes can also declare additional state channels_ as long as the state schema definition exists. In this case, the `PrivateState` schema is defined, so we can add `bar` as a new state channel in the graph and write to it.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
1. We pass `state` as the input schema to `node1`. But, we write out to `foo`, a channel in `OverallState`. How can we write out to a state channel that is not included in the input schema? This is because a node _can write to any state channel in the graph state._ The graph state is the union of the state channels defined at initialization, which includes `OverallState` and the filters `InputState` and `OutputState`.
|
||||
|
||||
2. We initialize the graph with `StateGraph({ state: OverallState, input: InputState, output: OutputState })`. So, how can we write to `PrivateState` in `node2`? How does the graph gain access to this schema if it was not passed in the `StateGraph` initialization? We can do this because _nodes can also declare additional state channels_ as long as the state schema definition exists. In this case, the `PrivateState` schema is defined, so we can add `bar` as a new state channel in the graph and write to it.
|
||||
:::
|
||||
|
||||
### Reducers
|
||||
|
||||
@@ -119,6 +217,8 @@ These two examples show how to use the default reducer:
|
||||
|
||||
**Example A:**
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
@@ -127,10 +227,33 @@ class State(TypedDict):
|
||||
bar: list[str]
|
||||
```
|
||||
|
||||
In this example, no reducer functions are specified for any key. Let's assume the input to the graph is `{"foo": 1, "bar": ["hi"]}`. Let's then assume the first `Node` returns `{"foo": 2}`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{"foo": 2, "bar": ["hi"]}`. If the second node returns `{"bar": ["bye"]}` then the `State` would then be `{"foo": 2, "bar": ["bye"]}`
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
const State = z.object({
|
||||
foo: z.number(),
|
||||
bar: z.array(z.string()),
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
In this example, no reducer functions are specified for any key. Let's assume the input to the graph is:
|
||||
|
||||
:::python
|
||||
`{"foo": 1, "bar": ["hi"]}`. Let's then assume the first `Node` returns `{"foo": 2}`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{"foo": 2, "bar": ["hi"]}`. If the second node returns `{"bar": ["bye"]}` then the `State` would then be `{"foo": 2, "bar": ["bye"]}`
|
||||
:::
|
||||
|
||||
:::js
|
||||
`{ foo: 1, bar: ["hi"] }`. Let's then assume the first `Node` returns `{ foo: 2 }`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{ foo: 2, bar: ["hi"] }`. If the second node returns `{ bar: ["bye"] }` then the `State` would then be `{ foo: 2, bar: ["bye"] }`
|
||||
:::
|
||||
|
||||
**Example B:**
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from typing_extensions import TypedDict
|
||||
@@ -142,21 +265,56 @@ class State(TypedDict):
|
||||
```
|
||||
|
||||
In this example, we've used the `Annotated` type to specify a reducer function (`operator.add`) for the second key (`bar`). Note that the first key remains unchanged. Let's assume the input to the graph is `{"foo": 1, "bar": ["hi"]}`. Let's then assume the first `Node` returns `{"foo": 2}`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{"foo": 2, "bar": ["hi"]}`. If the second node returns `{"bar": ["bye"]}` then the `State` would then be `{"foo": 2, "bar": ["hi", "bye"]}`. Notice here that the `bar` key is updated by adding the two lists together.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { z } from "zod";
|
||||
import { withLangGraph } from "@langchain/langgraph/zod";
|
||||
|
||||
const State = z.object({
|
||||
foo: z.number(),
|
||||
bar: withLangGraph(z.array(z.string()), {
|
||||
reducer: {
|
||||
fn: (x, y) => x.concat(y),
|
||||
},
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
In this example, we've used the `withLangGraph` function to specify a reducer function for the second key (`bar`). Note that the first key remains unchanged. Let's assume the input to the graph is `{ foo: 1, bar: ["hi"] }`. Let's then assume the first `Node` returns `{ foo: 2 }`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{ foo: 2, bar: ["hi"] }`. If the second node returns `{ bar: ["bye"] }` then the `State` would then be `{ foo: 2, bar: ["hi", "bye"] }`. Notice here that the `bar` key is updated by adding the two arrays together.
|
||||
:::
|
||||
|
||||
### Working with Messages in Graph State
|
||||
|
||||
#### Why use messages?
|
||||
|
||||
:::python
|
||||
Most modern LLM providers have a chat model interface that accepts a list of messages as input. LangChain's [`ChatModel`](https://python.langchain.com/docs/concepts/#chat-models) in particular accepts a list of `Message` objects as inputs. These messages come in a variety of forms such as `HumanMessage` (user input) or `AIMessage` (LLM response). To read more about what message objects are, please refer to [this](https://python.langchain.com/docs/concepts/#messages) conceptual guide.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Most modern LLM providers have a chat model interface that accepts a list of messages as input. LangChain's [`ChatModel`](https://js.langchain.com/docs/concepts/#chat-models) in particular accepts a list of `Message` objects as inputs. These messages come in a variety of forms such as `HumanMessage` (user input) or `AIMessage` (LLM response). To read more about what message objects are, please refer to [this](https://js.langchain.com/docs/concepts/#messages) conceptual guide.
|
||||
:::
|
||||
|
||||
#### Using Messages in your Graph
|
||||
|
||||
:::python
|
||||
In many cases, it is helpful to store prior conversation history as a list of messages in your graph state. To do so, we can add a key (channel) to the graph state that stores a list of `Message` objects and annotate it with a reducer function (see `messages` key in the example below). The reducer function is vital to telling the graph how to update the list of `Message` objects in the state with each state update (for example, when a node sends an update). If you don't specify a reducer, every state update will overwrite the list of messages with the most recently provided value. If you wanted to simply append messages to the existing list, you could use `operator.add` as a reducer.
|
||||
|
||||
However, you might also want to manually update messages in your graph state (e.g. human-in-the-loop). If you were to use `operator.add`, the manual state updates you send to the graph would be appended to the existing list of messages, instead of updating existing messages. To avoid that, you need a reducer that can keep track of message IDs and overwrite existing messages, if updated. To achieve this, you can use the prebuilt `add_messages` function. For brand new messages, it will simply append to existing list, but it will also handle the updates for existing messages correctly.
|
||||
:::
|
||||
|
||||
:::js
|
||||
In many cases, it is helpful to store prior conversation history as a list of messages in your graph state. To do so, we can add a key (channel) to the graph state that stores a list of `Message` objects and annotate it with a reducer function (see `messages` key in the example below). The reducer function is vital to telling the graph how to update the list of `Message` objects in the state with each state update (for example, when a node sends an update). If you don't specify a reducer, every state update will overwrite the list of messages with the most recently provided value. If you wanted to simply append messages to the existing list, you could use a function that concatenates arrays as a reducer.
|
||||
|
||||
However, you might also want to manually update messages in your graph state (e.g. human-in-the-loop). If you were to use a simple concatenation function, the manual state updates you send to the graph would be appended to the existing list of messages, instead of updating existing messages. To avoid that, you need a reducer that can keep track of message IDs and overwrite existing messages, if updated. To achieve this, you can use the prebuilt `MessagesZodState` schema. For brand new messages, it will simply append to existing list, but it will also handle the updates for existing messages correctly.
|
||||
:::
|
||||
|
||||
#### Serialization
|
||||
|
||||
:::python
|
||||
In addition to keeping track of message IDs, the `add_messages` function will also try to deserialize messages into LangChain `Message` objects whenever a state update is received on the `messages` channel. See more information on LangChain serialization/deserialization [here](https://python.langchain.com/docs/how_to/serialization/). This allows sending graph inputs / state updates in the following format:
|
||||
|
||||
```python
|
||||
@@ -179,6 +337,45 @@ class GraphState(TypedDict):
|
||||
messages: Annotated[list[AnyMessage], add_messages]
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
In addition to keeping track of message IDs, `MessagesZodState` will also try to deserialize messages into LangChain `Message` objects whenever a state update is received on the `messages` channel. This allows sending graph inputs / state updates in the following format:
|
||||
|
||||
```typescript
|
||||
// this is supported
|
||||
{
|
||||
messages: [new HumanMessage("message")];
|
||||
}
|
||||
|
||||
// and this is also supported
|
||||
{
|
||||
messages: [{ role: "human", content: "message" }];
|
||||
}
|
||||
```
|
||||
|
||||
Since the state updates are always deserialized into LangChain `Messages` when using `MessagesZodState`, you should use dot notation to access message attributes, like `state.messages[state.messages.length - 1].content`. Below is an example of a graph that uses `MessagesZodState`:
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState } from "@langchain/langgraph";
|
||||
|
||||
const graph = new StateGraph(MessagesZodState)
|
||||
...
|
||||
```
|
||||
|
||||
`MessagesZodState` is defined with a single `messages` key which is a list of `BaseMessage` objects and uses the appropriate reducer. Typically, there is more state to track than just messages, so we see people extend this state and add more fields, like:
|
||||
|
||||
```typescript
|
||||
const State = z.object({
|
||||
messages: MessagesZodState.shape.messages,
|
||||
documents: z.array(z.string()),
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
#### MessagesState
|
||||
|
||||
Since having a list of messages in your state is so common, there exists a prebuilt state called `MessagesState` which makes it easy to use messages. `MessagesState` is defined with a single `messages` key which is a list of `AnyMessage` objects and uses the `add_messages` reducer. Typically, there is more state to track than just messages, so we see people subclass this state and add more fields, like:
|
||||
@@ -190,81 +387,173 @@ class State(MessagesState):
|
||||
documents: list[str]
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Nodes
|
||||
|
||||
In LangGraph, nodes are typically python functions (sync or async) where the **first** positional argument is the [state](#state), and (optionally), the **second** positional argument is a "config", containing optional [configurable parameters](#configuration) (such as a `thread_id`).
|
||||
:::python
|
||||
|
||||
Similar to `NetworkX`, you add these nodes to a graph using the [add_node][langgraph.graph.StateGraph.add_node] method:
|
||||
In LangGraph, nodes are Python functions (either synchronous or asynchronous) that accept the following arguments:
|
||||
|
||||
1. `state`: The [state](#state) of the graph
|
||||
2. `config`: A `RunnableConfig` object that contains configuration information like `thread_id` and tracing information like `tags`
|
||||
3. `runtime`: A `Runtime` object that contains [runtime `context`](#runtime-context) and other information like `store` and `stream_writer`
|
||||
|
||||
Similar to `NetworkX`, you add these nodes to a graph using the @[add_node][add_node] method:
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.graph import StateGraph
|
||||
from langgraph.runtime import Runtime
|
||||
|
||||
class State(TypedDict):
|
||||
input: str
|
||||
results: str
|
||||
|
||||
@dataclass
|
||||
class Context:
|
||||
user_id: str
|
||||
|
||||
builder = StateGraph(State)
|
||||
|
||||
def plain_node(state: State):
|
||||
return state
|
||||
|
||||
def my_node(state: State, config: RunnableConfig):
|
||||
print("In node: ", config["configurable"]["user_id"])
|
||||
def node_with_runtime(state: State, runtime: Runtime[Context]):
|
||||
print("In node: ", runtime.context.user_id)
|
||||
return {"results": f"Hello, {state['input']}!"}
|
||||
|
||||
def node_with_config(state: State, config: RunnableConfig):
|
||||
print("In node with thread_id: ", config["configurable"]["thread_id"])
|
||||
return {"results": f"Hello, {state['input']}!"}
|
||||
|
||||
|
||||
# The second argument is optional
|
||||
def my_other_node(state: State):
|
||||
return state
|
||||
|
||||
|
||||
builder.add_node("my_node", my_node)
|
||||
builder.add_node("other_node", my_other_node)
|
||||
builder.add_node("plain_node", plain_node)
|
||||
builder.add_node("node_with_runtime", node_with_runtime)
|
||||
builder.add_node("node_with_config", node_with_config)
|
||||
...
|
||||
```
|
||||
|
||||
Behind the scenes, functions are converted to [RunnableLambda](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.RunnableLambda.html#langchain_core.runnables.base.RunnableLambda)s, which add batch and async support to your function, along with native tracing and debugging.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
In LangGraph, nodes are typically functions (sync or async) that accept the following arguments:
|
||||
|
||||
1. `state`: The [state](#state) of the graph
|
||||
2. `config`: A `RunnableConfig` object that contains configuration information like `thread_id` and tracing information like `tags`
|
||||
|
||||
You can add nodes to a graph using the `addNode` method.
|
||||
|
||||
```typescript
|
||||
import { StateGraph } from "@langchain/langgraph";
|
||||
import { RunnableConfig } from "@langchain/core/runnables";
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({
|
||||
input: z.string(),
|
||||
results: z.string(),
|
||||
});
|
||||
|
||||
const builder = new StateGraph(State);
|
||||
.addNode("myNode", (state, config) => {
|
||||
console.log("In node: ", config?.configurable?.user_id);
|
||||
return { results: `Hello, ${state.input}!` };
|
||||
})
|
||||
addNode("otherNode", (state) => {
|
||||
return state;
|
||||
})
|
||||
...
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Behind the scenes, functions are converted to [RunnableLambda](https://python.langchain.com/api_reference/core/runnables/langchain_core.runnables.base.RunnableLambda.html)s, which add batch and async support to your function, along with native tracing and debugging.
|
||||
|
||||
If you add a node to a graph without specifying a name, it will be given a default name equivalent to the function name.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
builder.add_node(my_node)
|
||||
# You can then create edges to/from this node by referencing it as `"my_node"`
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
builder.addNode(myNode);
|
||||
// You can then create edges to/from this node by referencing it as `"myNode"`
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### `START` Node
|
||||
|
||||
The `START` Node is a special node that represents the node that sends user input to the graph. The main purpose for referencing this node is to determine which nodes should be called first.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langgraph.graph import START
|
||||
|
||||
graph.add_edge(START, "node_a")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { START } from "@langchain/langgraph";
|
||||
|
||||
graph.addEdge(START, "nodeA");
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### `END` Node
|
||||
|
||||
The `END` Node is a special node that represents a terminal node. This node is referenced when you want to denote which edges have no actions after they are done.
|
||||
|
||||
```
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langgraph.graph import END
|
||||
|
||||
graph.add_edge("node_a", END)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { END } from "@langchain/langgraph";
|
||||
|
||||
graph.addEdge("nodeA", END);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Node Caching
|
||||
|
||||
:::python
|
||||
LangGraph supports caching of tasks/nodes based on the input to the node. To use caching:
|
||||
|
||||
* Specify a cache when compiling a graph (or specifying an entrypoint)
|
||||
* Specify a cache policy for nodes. Each cache policy supports:
|
||||
* `key_func` used to generate a cache key based on the input to a node, which defaults to a `hash` of the input with pickle.
|
||||
* `ttl`, the time to live for the cache in seconds. If not specified, the cache will never expire.
|
||||
- Specify a cache when compiling a graph (or specifying an entrypoint)
|
||||
- Specify a cache policy for nodes. Each cache policy supports:
|
||||
- `key_func` used to generate a cache key based on the input to a node, which defaults to a `hash` of the input with pickle.
|
||||
- `ttl`, the time to live for the cache in seconds. If not specified, the cache will never expire.
|
||||
|
||||
For example:
|
||||
|
||||
```py
|
||||
```python
|
||||
import time
|
||||
from typing_extensions import TypedDict
|
||||
from langgraph.graph import StateGraph
|
||||
@@ -298,8 +587,42 @@ print(graph.invoke({"x": 5}, stream_mode='updates')) # (2)!
|
||||
[{'expensive_node': {'result': 10}, '__metadata__': {'cached': True}}]
|
||||
```
|
||||
|
||||
1. First run takes the full second to run (due to mocked expensive computation).
|
||||
1. First run takes two seconds to run (due to mocked expensive computation).
|
||||
2. Second run utilizes cache and returns quickly.
|
||||
:::
|
||||
|
||||
:::js
|
||||
LangGraph supports caching of tasks/nodes based on the input to the node. To use caching:
|
||||
|
||||
- Specify a cache when compiling a graph (or specifying an entrypoint)
|
||||
- Specify a cache policy for nodes. Each cache policy supports:
|
||||
- `keyFunc`, which is used to generate a cache key based on the input to a node.
|
||||
- `ttl`, the time to live for the cache in seconds. If not specified, the cache will never expire.
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState } from "@langchain/langgraph";
|
||||
import { InMemoryCache } from "@langchain/langgraph-checkpoint";
|
||||
|
||||
const graph = new StateGraph(MessagesZodState)
|
||||
.addNode(
|
||||
"expensive_node",
|
||||
async () => {
|
||||
// Simulate an expensive operation
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
return { result: 10 };
|
||||
},
|
||||
{ cachePolicy: { ttl: 3 } }
|
||||
)
|
||||
.addEdge(START, "expensive_node")
|
||||
.compile({ cache: new InMemoryCache() });
|
||||
|
||||
await graph.invoke({ x: 5 }, { streamMode: "updates" }); // (1)!
|
||||
// [{"expensive_node": {"result": 10}}]
|
||||
await graph.invoke({ x: 5 }, { streamMode: "updates" }); // (2)!
|
||||
// [{"expensive_node": {"result": 10}, "__metadata__": {"cached": true}}]
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Edges
|
||||
|
||||
@@ -314,15 +637,28 @@ A node can have MULTIPLE outgoing edges. If a node has multiple out-going edges,
|
||||
|
||||
### Normal Edges
|
||||
|
||||
If you **always** want to go from node A to node B, you can use the [add_edge][langgraph.graph.StateGraph.add_edge] method directly.
|
||||
:::python
|
||||
If you **always** want to go from node A to node B, you can use the @[add_edge][add_edge] method directly.
|
||||
|
||||
```python
|
||||
graph.add_edge("node_a", "node_b")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
If you **always** want to go from node A to node B, you can use the @[`addEdge`][add_edge] method directly.
|
||||
|
||||
```typescript
|
||||
graph.addEdge("nodeA", "nodeB");
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Conditional Edges
|
||||
|
||||
If you want to **optionally** route to 1 or more edges (or optionally terminate), you can use the [add_conditional_edges][langgraph.graph.StateGraph.add_conditional_edges] method. This method accepts the name of a node and a "routing function" to call after that node is executed:
|
||||
:::python
|
||||
If you want to **optionally** route to 1 or more edges (or optionally terminate), you can use the @[add_conditional_edges][add_conditional_edges] method. This method accepts the name of a node and a "routing function" to call after that node is executed:
|
||||
|
||||
```python
|
||||
graph.add_conditional_edges("node_a", routing_function)
|
||||
@@ -338,12 +674,38 @@ You can optionally provide a dictionary that maps the `routing_function`'s outpu
|
||||
graph.add_conditional_edges("node_a", routing_function, {True: "node_b", False: "node_c"})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
If you want to **optionally** route to 1 or more edges (or optionally terminate), you can use the @[`addConditionalEdges`][add_conditional_edges] method. This method accepts the name of a node and a "routing function" to call after that node is executed:
|
||||
|
||||
```typescript
|
||||
graph.addConditionalEdges("nodeA", routingFunction);
|
||||
```
|
||||
|
||||
Similar to nodes, the `routingFunction` accepts the current `state` of the graph and returns a value.
|
||||
|
||||
By default, the return value `routingFunction` is used as the name of the node (or list of nodes) to send the state to next. All those nodes will be run in parallel as a part of the next superstep.
|
||||
|
||||
You can optionally provide an object that maps the `routingFunction`'s output to the name of the next node.
|
||||
|
||||
```typescript
|
||||
graph.addConditionalEdges("nodeA", routingFunction, {
|
||||
true: "nodeB",
|
||||
false: "nodeC",
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! tip
|
||||
|
||||
Use [`Command`](#command) instead of conditional edges if you want to combine state updates and routing in a single function.
|
||||
|
||||
### Entry Point
|
||||
|
||||
The entry point is the first node(s) that are run when the graph starts. You can use the [`add_edge`][langgraph.graph.StateGraph.add_edge] method from the virtual [`START`][langgraph.constants.START] node to the first node to execute to specify where to enter the graph.
|
||||
:::python
|
||||
The entry point is the first node(s) that are run when the graph starts. You can use the @[`add_edge`][add_edge] method from the virtual @[`START`][START] node to the first node to execute to specify where to enter the graph.
|
||||
|
||||
```python
|
||||
from langgraph.graph import START
|
||||
@@ -351,9 +713,23 @@ from langgraph.graph import START
|
||||
graph.add_edge(START, "node_a")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
The entry point is the first node(s) that are run when the graph starts. You can use the @[`addEdge`][add_edge] method from the virtual @[`START`][START] node to the first node to execute to specify where to enter the graph.
|
||||
|
||||
```typescript
|
||||
import { START } from "@langchain/langgraph";
|
||||
|
||||
graph.addEdge(START, "nodeA");
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Conditional Entry Point
|
||||
|
||||
A conditional entry point lets you start at different nodes depending on custom logic. You can use [`add_conditional_edges`][langgraph.graph.StateGraph.add_conditional_edges] from the virtual [`START`][langgraph.constants.START] node to accomplish this.
|
||||
:::python
|
||||
A conditional entry point lets you start at different nodes depending on custom logic. You can use @[`add_conditional_edges`][add_conditional_edges] from the virtual @[`START`][START] node to accomplish this.
|
||||
|
||||
```python
|
||||
from langgraph.graph import START
|
||||
@@ -367,11 +743,34 @@ You can optionally provide a dictionary that maps the `routing_function`'s outpu
|
||||
graph.add_conditional_edges(START, routing_function, {True: "node_b", False: "node_c"})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
A conditional entry point lets you start at different nodes depending on custom logic. You can use @[`addConditionalEdges`][add_conditional_edges] from the virtual @[`START`][START] node to accomplish this.
|
||||
|
||||
```typescript
|
||||
import { START } from "@langchain/langgraph";
|
||||
|
||||
graph.addConditionalEdges(START, routingFunction);
|
||||
```
|
||||
|
||||
You can optionally provide an object that maps the `routingFunction`'s output to the name of the next node.
|
||||
|
||||
```typescript
|
||||
graph.addConditionalEdges(START, routingFunction, {
|
||||
true: "nodeB",
|
||||
false: "nodeC",
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## `Send`
|
||||
|
||||
:::python
|
||||
By default, `Nodes` and `Edges` are defined ahead of time and operate on the same shared state. However, there can be cases where the exact edges are not known ahead of time and/or you may want different versions of `State` to exist at the same time. A common example of this is with [map-reduce](https://langchain-ai.github.io/langgraph/how-tos/map-reduce/) design patterns. In this design pattern, a first node may generate a list of objects, and you may want to apply some other node to all those objects. The number of objects may be unknown ahead of time (meaning the number of edges may not be known) and the input `State` to the downstream `Node` should be different (one for each generated object).
|
||||
|
||||
To support this design pattern, LangGraph supports returning [`Send`][langgraph.types.Send] objects from conditional edges. `Send` takes two arguments: first is the name of the node, and second is the state to pass to that node.
|
||||
To support this design pattern, LangGraph supports returning @[`Send`][Send] objects from conditional edges. `Send` takes two arguments: first is the name of the node, and second is the state to pass to that node.
|
||||
|
||||
```python
|
||||
def continue_to_jokes(state: OverallState):
|
||||
@@ -380,9 +779,27 @@ def continue_to_jokes(state: OverallState):
|
||||
graph.add_conditional_edges("node_a", continue_to_jokes)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
By default, `Nodes` and `Edges` are defined ahead of time and operate on the same shared state. However, there can be cases where the exact edges are not known ahead of time and/or you may want different versions of `State` to exist at the same time. A common example of this is with map-reduce design patterns. In this design pattern, a first node may generate a list of objects, and you may want to apply some other node to all those objects. The number of objects may be unknown ahead of time (meaning the number of edges may not be known) and the input `State` to the downstream `Node` should be different (one for each generated object).
|
||||
|
||||
To support this design pattern, LangGraph supports returning @[`Send`][Send] objects from conditional edges. `Send` takes two arguments: first is the name of the node, and second is the state to pass to that node.
|
||||
|
||||
```typescript
|
||||
import { Send } from "@langchain/langgraph";
|
||||
|
||||
graph.addConditionalEdges("nodeA", (state) => {
|
||||
return state.subjects.map((subject) => new Send("generateJoke", { subject }));
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## `Command`
|
||||
|
||||
It can be useful to combine control flow (edges) and state updates (nodes). For example, you might want to BOTH perform state updates AND decide which node to go to next in the SAME node. LangGraph provides a way to do so by returning a [`Command`][langgraph.types.Command] object from node functions:
|
||||
:::python
|
||||
It can be useful to combine control flow (edges) and state updates (nodes). For example, you might want to BOTH perform state updates AND decide which node to go to next in the SAME node. LangGraph provides a way to do so by returning a @[`Command`][Command] object from node functions:
|
||||
|
||||
```python
|
||||
def my_node(state: State) -> Command[Literal["my_other_node"]]:
|
||||
@@ -402,6 +819,47 @@ def my_node(state: State) -> Command[Literal["my_other_node"]]:
|
||||
return Command(update={"foo": "baz"}, goto="my_other_node")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
It can be useful to combine control flow (edges) and state updates (nodes). For example, you might want to BOTH perform state updates AND decide which node to go to next in the SAME node. LangGraph provides a way to do so by returning a `Command` object from node functions:
|
||||
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
graph.addNode("myNode", (state) => {
|
||||
return new Command({
|
||||
update: { foo: "bar" },
|
||||
goto: "myOtherNode",
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
With `Command` you can also achieve dynamic control flow behavior (identical to [conditional edges](#conditional-edges)):
|
||||
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
graph.addNode("myNode", (state) => {
|
||||
if (state.foo === "bar") {
|
||||
return new Command({
|
||||
update: { foo: "baz" },
|
||||
goto: "myOtherNode",
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
When using `Command` in your node functions, you must add the `ends` parameter when adding the node to specify which nodes it can route to:
|
||||
|
||||
```typescript
|
||||
builder.addNode("myNode", myNode, {
|
||||
ends: ["myOtherNode", END],
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! important
|
||||
|
||||
When returning `Command` in your node functions, you must add return type annotations with the list of node names the node is routing to, e.g. `Command[Literal["my_other_node"]]`. This is necessary for the graph rendering and tells LangGraph that `my_node` can navigate to `my_other_node`.
|
||||
@@ -410,12 +868,12 @@ Check out this [how-to guide](../how-tos/graph-api.md#combine-control-flow-and-s
|
||||
|
||||
### When should I use Command instead of conditional edges?
|
||||
|
||||
Use `Command` when you need to **both** update the graph state **and** route to a different node. For example, when implementing [multi-agent handoffs](./multi_agent.md#handoffs) where it's important to route to a different agent and pass some information to that agent.
|
||||
|
||||
Use [conditional edges](#conditional-edges) to route between nodes conditionally without updating the state.
|
||||
- Use `Command` when you need to **both** update the graph state **and** route to a different node. For example, when implementing [multi-agent handoffs](./multi_agent.md#handoffs) where it's important to route to a different agent and pass some information to that agent.
|
||||
- Use [conditional edges](#conditional-edges) to route between nodes conditionally without updating the state.
|
||||
|
||||
### Navigating to a node in a parent graph
|
||||
|
||||
:::python
|
||||
If you are using [subgraphs](./subgraphs.md), you might want to navigate from a node within a subgraph to a different subgraph (i.e. a different node in the parent graph). To do so, you can specify `graph=Command.PARENT` in `Command`:
|
||||
|
||||
```python
|
||||
@@ -435,6 +893,58 @@ def my_node(state: State) -> Command[Literal["other_subgraph"]]:
|
||||
|
||||
When you send updates from a subgraph node to a parent graph node for a key that's shared by both parent and subgraph [state schemas](#schema), you **must** define a [reducer](#reducers) for the key you're updating in the parent graph state. See this [example](../how-tos/graph-api.md#navigate-to-a-node-in-a-parent-graph).
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
If you are using [subgraphs](./subgraphs.md), you might want to navigate from a node within a subgraph to a different subgraph (i.e. a different node in the parent graph). To do so, you can specify `graph: Command.PARENT` in `Command`:
|
||||
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
graph.addNode("myNode", (state) => {
|
||||
return new Command({
|
||||
update: { foo: "bar" },
|
||||
goto: "otherSubgraph", // where `otherSubgraph` is a node in the parent graph
|
||||
graph: Command.PARENT,
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
Setting `graph` to `Command.PARENT` will navigate to the closest parent graph.
|
||||
|
||||
!!! important "State updates with `Command.PARENT`"
|
||||
|
||||
When you send updates from a subgraph node to a parent graph node for a key that's shared by both parent and subgraph [state schemas](#schema), you **must** define a [reducer](#reducers) for the key you're updating in the parent graph state.
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
If you are using [subgraphs](./subgraphs.md), you might want to navigate from a node within a subgraph to a different subgraph (i.e. a different node in the parent graph). To do so, you can specify `graph: Command.PARENT` in `Command`:
|
||||
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
graph.addNode("myNode", (state) => {
|
||||
return new Command({
|
||||
update: { foo: "bar" },
|
||||
goto: "otherSubgraph", // where `otherSubgraph` is a node in the parent graph
|
||||
graph: Command.PARENT,
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
Setting `graph` to `Command.PARENT` will navigate to the closest parent graph.
|
||||
|
||||
!!! important "State updates with `Command.PARENT`"
|
||||
|
||||
When you send updates from a subgraph node to a parent graph node for a key that's shared by both parent and subgraph [state schemas](#schema), you **must** define a [reducer](#reducers) for the key you're updating in the parent graph state.
|
||||
|
||||
:::
|
||||
|
||||
This is particularly useful when implementing [multi-agent handoffs](./multi_agent.md#handoffs).
|
||||
|
||||
Check out [this guide](../how-tos/graph-api.md#navigate-to-a-node-in-a-parent-graph) for detail.
|
||||
@@ -447,7 +957,13 @@ Refer to [this guide](../how-tos/graph-api.md#use-inside-tools) for detail.
|
||||
|
||||
### Human-in-the-loop
|
||||
|
||||
:::python
|
||||
`Command` is an important part of human-in-the-loop workflows: when using `interrupt()` to collect user input, `Command` is then used to supply the input and resume execution via `Command(resume="User input")`. Check out [this conceptual guide](./human_in_the_loop.md) for more information.
|
||||
:::
|
||||
|
||||
:::js
|
||||
`Command` is an important part of human-in-the-loop workflows: when using `interrupt()` to collect user input, `Command` is then used to supply the input and resume execution via `new Command({ resume: "User input" })`. Check out the [human-in-the-loop conceptual guide](./human_in_the_loop.md) for more information.
|
||||
:::
|
||||
|
||||
## Graph Migrations
|
||||
|
||||
@@ -459,47 +975,109 @@ LangGraph can easily handle migrations of graph definitions (nodes, edges, and s
|
||||
- State keys that are renamed lose their saved state in existing threads
|
||||
- State keys whose types change in incompatible ways could currently cause issues in threads with state from before the change -- if this is a blocker please reach out and we can prioritize a solution.
|
||||
|
||||
## Configuration
|
||||
:::python
|
||||
|
||||
## Runtime Context
|
||||
|
||||
When creating a graph, you can specify a `context_schema` for runtime context passed to nodes. This is useful for passing
|
||||
information to nodes that is not part of the graph state. For example, you might want to pass dependencies such as model name or a database connection.
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class ContextSchema:
|
||||
llm_provider: str = "openai"
|
||||
|
||||
graph = StateGraph(State, context_schema=ContextSchema)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
When creating a graph, you can also mark that certain parts of the graph are configurable. This is commonly done to enable easily switching between models or system prompts. This allows you to create a single "cognitive architecture" (the graph) but have multiple different instance of it.
|
||||
|
||||
You can optionally specify a `config_schema` when creating a graph.
|
||||
You can optionally specify a config schema when creating a graph.
|
||||
|
||||
```python
|
||||
class ConfigSchema(TypedDict):
|
||||
llm: str
|
||||
```typescript
|
||||
import { z } from "zod";
|
||||
|
||||
graph = StateGraph(State, config_schema=ConfigSchema)
|
||||
const ConfigSchema = z.object({
|
||||
llm: z.string(),
|
||||
});
|
||||
|
||||
const graph = new StateGraph(State, ConfigSchema);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
You can then pass this context into the graph using the `context` parameter of the `invoke` method.
|
||||
|
||||
```python
|
||||
graph.invoke(inputs, context={"llm_provider": "anthropic"})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
You can then pass this configuration into the graph using the `configurable` config field.
|
||||
|
||||
```python
|
||||
config = {"configurable": {"llm": "anthropic"}}
|
||||
```typescript
|
||||
const config = { configurable: { llm: "anthropic" } };
|
||||
|
||||
graph.invoke(inputs, config=config)
|
||||
await graph.invoke(inputs, config);
|
||||
```
|
||||
|
||||
You can then access and use this configuration inside a node or conditional edge:
|
||||
:::
|
||||
|
||||
You can then access and use this context inside a node or conditional edge:
|
||||
|
||||
```python
|
||||
def node_a(state, config):
|
||||
llm_type = config.get("configurable", {}).get("llm", "openai")
|
||||
llm = get_llm(llm_type)
|
||||
from langgraph.runtime import Runtime
|
||||
|
||||
def node_a(state: State, runtime: Runtime[ContextSchema]):
|
||||
llm = get_llm(runtime.context.llm_provider)
|
||||
...
|
||||
```
|
||||
|
||||
See [this guide](../how-tos/graph-api.md#add-runtime-configuration) for a full breakdown on configuration.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
graph.addNode("myNode", (state, config) => {
|
||||
const llmType = config?.configurable?.llm || "openai";
|
||||
const llm = getLlm(llmType);
|
||||
return { results: `Hello, ${state.input}!` };
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Recursion Limit
|
||||
|
||||
:::python
|
||||
The recursion limit sets the maximum number of [super-steps](#graphs) the graph can execute during a single execution. Once the limit is reached, LangGraph will raise `GraphRecursionError`. By default this value is set to 25 steps. The recursion limit can be set on any graph at runtime, and is passed to `.invoke`/`.stream` via the config dictionary. Importantly, `recursion_limit` is a standalone `config` key and should not be passed inside the `configurable` key as all other user-defined configuration. See the example below:
|
||||
|
||||
```python
|
||||
graph.invoke(inputs, config={"recursion_limit": 5, "configurable":{"llm": "anthropic"}})
|
||||
graph.invoke(inputs, config={"recursion_limit": 5}, context={"llm": "anthropic"})
|
||||
```
|
||||
|
||||
Read [this how-to](https://langchain-ai.github.io/langgraph/how-tos/recursion-limit/) to learn more about how the recursion limit works.
|
||||
:::
|
||||
|
||||
:::js
|
||||
The recursion limit sets the maximum number of [super-steps](#graphs) the graph can execute during a single execution. Once the limit is reached, LangGraph will raise `GraphRecursionError`. By default this value is set to 25 steps. The recursion limit can be set on any graph at runtime, and is passed to `.invoke`/`.stream` via the config object. Importantly, `recursionLimit` is a standalone `config` key and should not be passed inside the `configurable` key as all other user-defined configuration. See the example below:
|
||||
|
||||
```typescript
|
||||
await graph.invoke(inputs, {
|
||||
recursionLimit: 5,
|
||||
configurable: { llm: "anthropic" },
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Visualization
|
||||
|
||||
|
||||
@@ -6,52 +6,14 @@
|
||||
|
||||
Install the `langchain-mcp-adapters` library to use MCP tools in LangGraph:
|
||||
|
||||
:::python
|
||||
```bash
|
||||
pip install langchain-mcp-adapters
|
||||
```
|
||||
:::
|
||||
|
||||
## Authenticate to an MCP server
|
||||
|
||||
You can set up [custom authentication middleware](../how-tos/auth/custom_auth.md) to authenticate a user with an MCP server to get access to user-scoped tools within your LangGraph Platform deployment.
|
||||
|
||||
!!! note
|
||||
Custom authentication is a LangGraph Platform feature.
|
||||
|
||||
An example architecture for this flow:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
%% Actors
|
||||
participant ClientApp as Client
|
||||
participant AuthProv as Auth Provider
|
||||
participant LangGraph as LangGraph Backend
|
||||
participant SecretStore as Secret Store
|
||||
participant MCPServer as MCP Server
|
||||
|
||||
%% Platform login / AuthN
|
||||
ClientApp ->> AuthProv: 1. Login (username / password)
|
||||
AuthProv -->> ClientApp: 2. Return token
|
||||
ClientApp ->> LangGraph: 3. Request with token
|
||||
|
||||
Note over LangGraph: 4. Validate token (@auth.authenticate)
|
||||
LangGraph -->> AuthProv: 5. Fetch user info
|
||||
AuthProv -->> LangGraph: 6. Confirm validity
|
||||
|
||||
%% Fetch user tokens from secret store
|
||||
LangGraph ->> SecretStore: 6a. Fetch user tokens
|
||||
SecretStore -->> LangGraph: 6b. Return tokens
|
||||
|
||||
Note over LangGraph: 7. Apply access control (@auth.on.*)
|
||||
|
||||
%% MCP round-trip
|
||||
Note over LangGraph: 8. Build MCP client with user token
|
||||
LangGraph ->> MCPServer: 9. Call MCP tool (with header)
|
||||
Note over MCPServer: 10. MCP validates header and runs tool
|
||||
MCPServer -->> LangGraph: 11. Tool response
|
||||
|
||||
%% Return to caller
|
||||
LangGraph -->> ClientApp: 12. Return resources / tool output
|
||||
:::js
|
||||
```bash
|
||||
npm install @langchain/mcp-adapters
|
||||
```
|
||||
|
||||
For more information, see [MCP endpoint in LangGraph Server](../concepts/server-mcp.md#use-user-scoped-mcp-tools-in-your-deployment).
|
||||
|
||||
:::
|
||||
@@ -87,11 +87,25 @@ Regardless of memory management approach, the central point is that the agent wi
|
||||
|
||||
[Episodic memory](https://en.wikipedia.org/wiki/Episodic_memory), in both humans and AI agents, involves recalling past events or actions. The [CoALA paper](https://arxiv.org/pdf/2309.02427) frames this well: facts can be written to semantic memory, whereas *experiences* can be written to episodic memory. For AI agents, episodic memory is often used to help an agent remember how to accomplish a task.
|
||||
|
||||
:::python
|
||||
In practice, episodic memories are often implemented through [few-shot example prompting](https://python.langchain.com/docs/concepts/few_shot_prompting/), where agents learn from past sequences to perform tasks correctly. Sometimes it's easier to "show" than "tell" and LLMs learn well from examples. Few-shot learning lets you ["program"](https://x.com/karpathy/status/1627366413840322562) your LLM by updating the prompt with input-output examples to illustrate the intended behavior. While various [best-practices](https://python.langchain.com/docs/concepts/#1-generating-examples) can be used to generate few-shot examples, often the challenge lies in selecting the most relevant examples based on user input.
|
||||
:::
|
||||
|
||||
:::js
|
||||
In practice, episodic memories are often implemented through few-shot example prompting, where agents learn from past sequences to perform tasks correctly. Sometimes it's easier to "show" than "tell" and LLMs learn well from examples. Few-shot learning lets you ["program"](https://x.com/karpathy/status/1627366413840322562) your LLM by updating the prompt with input-output examples to illustrate the intended behavior. While various best-practices can be used to generate few-shot examples, often the challenge lies in selecting the most relevant examples based on user input.
|
||||
:::
|
||||
|
||||
:::python
|
||||
Note that the memory [store](persistence.md#memory-store) is just one way to store data as few-shot examples. If you want to have more developer involvement, or tie few-shots more closely to your evaluation harness, you can also use a [LangSmith Dataset](https://docs.smith.langchain.com/evaluation/how_to_guides/datasets/index_datasets_for_dynamic_few_shot_example_selection) to store your data. Then dynamic few-shot example selectors can be used out-of-the box to achieve this same goal. LangSmith will index the dataset for you and enable retrieval of few shot examples that are most relevant to the user input based upon keyword similarity ([using a BM25-like algorithm](https://docs.smith.langchain.com/how_to_guides/datasets/index_datasets_for_dynamic_few_shot_example_selection) for keyword based similarity).
|
||||
|
||||
See this how-to [video](https://www.youtube.com/watch?v=37VaU7e7t5o) for example usage of dynamic few-shot example selection in LangSmith. Also, see this [blog post](https://blog.langchain.dev/few-shot-prompting-to-improve-tool-calling-performance/) showcasing few-shot prompting to improve tool calling performance and this [blog post](https://blog.langchain.dev/aligning-llm-as-a-judge-with-human-preferences/) using few-shot example to align an LLMs to human preferences.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Note that the memory [store](persistence.md#memory-store) is just one way to store data as few-shot examples. If you want to have more developer involvement, or tie few-shots more closely to your evaluation harness, you can also use a LangSmith Dataset to store your data. Then dynamic few-shot example selectors can be used out-of-the box to achieve this same goal. LangSmith will index the dataset for you and enable retrieval of few shot examples that are most relevant to the user input based upon keyword similarity.
|
||||
|
||||
See this how-to [video](https://www.youtube.com/watch?v=37VaU7e7t5o) for example usage of dynamic few-shot example selection in LangSmith. Also, see this [blog post](https://blog.langchain.dev/few-shot-prompting-to-improve-tool-calling-performance/) showcasing few-shot prompting to improve tool calling performance and this [blog post](https://blog.langchain.dev/aligning-llm-as-a-judge-with-human-preferences/) using few-shot example to align an LLMs to human preferences.
|
||||
:::
|
||||
|
||||
#### Procedural memory
|
||||
|
||||
@@ -105,6 +119,7 @@ For example, we built a [Tweet generator](https://www.youtube.com/watch?v=Vn8A3B
|
||||
|
||||
The below pseudo-code shows how you might implement this with the LangGraph memory [store](persistence.md#memory-store), using the store to save a prompt, the `update_instructions` node to get the current prompt (as well as feedback from the conversation with the user captured in `state["messages"]`), update the prompt, and save the new prompt back to the store. Then, the `call_model` get the updated prompt from the store and uses it to generate a response.
|
||||
|
||||
:::python
|
||||
```python
|
||||
# Node that *uses* the instructions
|
||||
def call_model(state: State, store: BaseStore):
|
||||
@@ -119,12 +134,45 @@ def update_instructions(state: State, store: BaseStore):
|
||||
namespace = ("instructions",)
|
||||
current_instructions = store.search(namespace)[0]
|
||||
# Memory logic
|
||||
prompt = prompt_template.format(instructions=instructions.value["instructions"], conversation=state["messages"])
|
||||
prompt = prompt_template.format(instructions=current_instructions.value["instructions"], conversation=state["messages"])
|
||||
output = llm.invoke(prompt)
|
||||
new_instructions = output['new_instructions']
|
||||
store.put(("agent_instructions",), "agent_a", {"instructions": new_instructions})
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
// Node that *uses* the instructions
|
||||
const callModel = async (state: State, store: BaseStore) => {
|
||||
const namespace = ["agent_instructions"];
|
||||
const instructions = await store.get(namespace, "agent_a");
|
||||
// Application logic
|
||||
const prompt = promptTemplate.format({
|
||||
instructions: instructions[0].value.instructions
|
||||
});
|
||||
// ...
|
||||
};
|
||||
|
||||
// Node that updates instructions
|
||||
const updateInstructions = async (state: State, store: BaseStore) => {
|
||||
const namespace = ["instructions"];
|
||||
const currentInstructions = await store.search(namespace);
|
||||
// Memory logic
|
||||
const prompt = promptTemplate.format({
|
||||
instructions: currentInstructions[0].value.instructions,
|
||||
conversation: state.messages
|
||||
});
|
||||
const output = await llm.invoke(prompt);
|
||||
const newInstructions = output.new_instructions;
|
||||
await store.put(["agent_instructions"], "agent_a", {
|
||||
instructions: newInstructions
|
||||
});
|
||||
// ...
|
||||
};
|
||||
```
|
||||
:::
|
||||
|
||||

|
||||
|
||||
@@ -154,6 +202,7 @@ See our [memory-service](https://github.com/langchain-ai/memory-template) templa
|
||||
|
||||
LangGraph stores long-term memories as JSON documents in a [store](persistence.md#memory-store). Each memory is organized under a custom `namespace` (similar to a folder) and a distinct `key` (like a file name). Namespaces often include user or org IDs or other labels that makes it easier to organize information. This structure enables hierarchical organization of memories. Cross-namespace searching is then supported through content filters.
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.store.memory import InMemoryStore
|
||||
|
||||
@@ -186,5 +235,47 @@ items = store.search(
|
||||
namespace, filter={"my-key": "my-value"}, query="language preferences"
|
||||
)
|
||||
```
|
||||
:::
|
||||
|
||||
For more information about the memory store, see the [Persistence](persistence.md#memory-store) guide.
|
||||
:::js
|
||||
```typescript
|
||||
import { InMemoryStore } from "@langchain/langgraph";
|
||||
|
||||
const embed = (texts: string[]): number[][] => {
|
||||
// Replace with an actual embedding function or LangChain embeddings object
|
||||
return texts.map(() => [1.0, 2.0]);
|
||||
};
|
||||
|
||||
// InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production use.
|
||||
const store = new InMemoryStore({ index: { embed, dims: 2 } });
|
||||
const userId = "my-user";
|
||||
const applicationContext = "chitchat";
|
||||
const namespace = [userId, applicationContext];
|
||||
|
||||
await store.put(
|
||||
namespace,
|
||||
"a-memory",
|
||||
{
|
||||
rules: [
|
||||
"User likes short, direct language",
|
||||
"User only speaks English & TypeScript",
|
||||
],
|
||||
"my-key": "my-value",
|
||||
}
|
||||
);
|
||||
|
||||
// get the "memory" by ID
|
||||
const item = await store.get(namespace, "a-memory");
|
||||
|
||||
// search for "memories" within this namespace, filtering on content equivalence, sorted by vector similarity
|
||||
const items = await store.search(
|
||||
namespace,
|
||||
{
|
||||
filter: { "my-key": "my-value" },
|
||||
query: "language preferences"
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
For more information about the memory store, see the [Persistence](persistence.md#memory-store) guide.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user