Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
770132dd79 | ||
|
|
3943637202 | ||
|
|
13cf9b7ec5 | ||
|
|
e744db169a | ||
|
|
770c092b57 | ||
|
|
c5c10a41e5 | ||
|
|
35807068ef | ||
|
|
6379cdc36d | ||
|
|
8154ab784e | ||
|
|
c761b80970 | ||
|
|
36d1570c0f | ||
|
|
99a845128e | ||
|
|
6036b4a7d4 | ||
|
|
33edc8e808 | ||
|
|
15a204b2be | ||
|
|
24a7e6403f | ||
|
|
9fd64cd37b | ||
|
|
fcd0f094f9 | ||
|
|
30598cd913 | ||
|
|
44153e4a49 | ||
|
|
4133910670 | ||
|
|
4fc4080597 | ||
|
|
8865831104 | ||
|
|
a707eef6f7 | ||
|
|
090c759da0 | ||
|
|
4ebaaba282 | ||
|
|
5343350a5c | ||
|
|
5dcb5976d9 | ||
|
|
791ab654cb | ||
|
|
c183f4f16a | ||
|
|
11e8728685 | ||
|
|
b1f22d3d81 | ||
|
|
ddf67d9233 | ||
|
|
b479b88c7e | ||
|
|
dd52248ef2 | ||
|
|
80e442e13d | ||
|
|
a627a30f4c | ||
|
|
01f14d3166 | ||
|
|
9d68aac76f | ||
|
|
e526674172 | ||
|
|
ca8614f873 | ||
|
|
ae8b1e164b | ||
|
|
cd28799215 | ||
|
|
883b39ba9f | ||
|
|
337ccc058f | ||
|
|
6111d37b99 | ||
|
|
6962aa5acc | ||
|
|
4769122828 | ||
|
|
c4010c03a6 | ||
|
|
03f6cf779d | ||
|
|
112700810c | ||
|
|
2645eb5a48 | ||
|
|
ab71d29286 | ||
|
|
da38905db7 | ||
|
|
7d3527db38 | ||
|
|
36ed1a5d4b | ||
|
|
7637a769dd | ||
|
|
3bbe09dac4 | ||
|
|
947f8c8c1d | ||
|
|
84cb3ea151 | ||
|
|
f4dea9c5f7 | ||
|
|
a8a2a89602 | ||
|
|
e338a4ec98 | ||
|
|
499fe10ef2 | ||
|
|
a8758661bc | ||
|
|
8912d50312 | ||
|
|
8d23929288 | ||
|
|
9b6100bc4f | ||
|
|
238b562e78 | ||
|
|
7fcc0da4e3 | ||
|
|
d7eff6ab85 | ||
|
|
330c91f622 | ||
|
|
3dbf6f144e | ||
|
|
8c2fefcb28 | ||
|
|
c88d289a73 | ||
|
|
d12f5c6d8b | ||
|
|
9ff54e029e | ||
|
|
2a89c7a5b4 | ||
|
|
f2ec99b9a8 | ||
|
|
eb387cc946 | ||
|
|
54b53f384d | ||
|
|
65784d8174 | ||
|
|
507930e5b7 | ||
|
|
85e698e20b | ||
|
|
bf4dc5d5f6 | ||
|
|
ce0fad8ddb | ||
|
|
1315c0d743 | ||
|
|
ada751d19b | ||
|
|
f310716921 | ||
|
|
568044171b | ||
|
|
1d0f3577a7 | ||
|
|
3979bdb792 | ||
|
|
d8d663ccd5 | ||
|
|
25a72e77ef | ||
|
|
da806c466d | ||
|
|
49c316578b | ||
|
|
7039a54871 | ||
|
|
ef17e0351a | ||
|
|
0d9c0732d4 | ||
|
|
a2cfe694f1 | ||
|
|
858c166cae | ||
|
|
03785c7d83 | ||
|
|
93cb2a7730 | ||
|
|
4b48e71d2c | ||
|
|
b7744aff9b | ||
|
|
4a4dd16535 | ||
|
|
ef790a57c6 | ||
|
|
c49692d794 | ||
|
|
3cda14b069 | ||
|
|
66a13b8865 | ||
|
|
58e139e7fd | ||
|
|
a8d860273b | ||
|
|
3ac4cdf3d4 | ||
|
|
3a524e0e56 | ||
|
|
134f8faf8c | ||
|
|
904a1a3471 | ||
|
|
cb30f68642 | ||
|
|
15692acef9 | ||
|
|
610257665f | ||
|
|
bc482431c3 | ||
|
|
155e0c66d5 | ||
|
|
ca63a06549 | ||
|
|
e8c553c41e | ||
|
|
beafddf7c8 | ||
|
|
1e6da19257 | ||
|
|
e0898409b9 | ||
|
|
bc86757e73 | ||
|
|
71442916e5 | ||
|
|
1f29925034 | ||
|
|
4c4d7054ab | ||
|
|
1333d8b478 | ||
|
|
2bac0d0f41 | ||
|
|
ed7b2c9e8a | ||
|
|
0597aedaff | ||
|
|
82db383199 | ||
|
|
dec7eb6f58 | ||
|
|
065055e587 | ||
|
|
4162be8119 | ||
|
|
6ece7124ed | ||
|
|
ffa9b8672a | ||
|
|
19b382335f | ||
|
|
c72acc9145 | ||
|
|
c30aa1ca13 | ||
|
|
15c3105748 | ||
|
|
0b7f451b40 | ||
|
|
75dec9b924 | ||
|
|
8090ca67c5 | ||
|
|
9f6e57d2a7 | ||
|
|
72893d9abb | ||
|
|
7074604204 | ||
|
|
0720b931e8 | ||
|
|
4935cf52bf | ||
|
|
45e3d1a3f1 | ||
|
|
acd8acf237 | ||
|
|
3ec419a2f6 | ||
|
|
8a00a0026e | ||
|
|
7e32de9405 | ||
|
|
96af4c72ce | ||
|
|
6c7d9c35bc | ||
|
|
5654d8fc86 | ||
|
|
f93512e3b3 | ||
|
|
a261e1a497 | ||
|
|
38daba5259 | ||
|
|
078f9f7275 | ||
|
|
22f5367af7 | ||
|
|
4e2b508ebb | ||
|
|
7ec3c0e827 | ||
|
|
82408bacf1 | ||
|
|
9a306ce931 | ||
|
|
a9a59dd4e4 | ||
|
|
a545a70afb | ||
|
|
4521f9312d | ||
|
|
d67419522d | ||
|
|
d85e267a83 | ||
|
|
426125cf96 | ||
|
|
4827377191 | ||
|
|
0b6088f913 | ||
|
|
578ec48031 | ||
|
|
e7bc74e918 | ||
|
|
0a87b9fa1c | ||
|
|
37848a5361 | ||
|
|
9147d05cc4 | ||
|
|
46171dd759 | ||
|
|
47ed3d97e9 | ||
|
|
06c2481783 | ||
|
|
1907646bd4 | ||
|
|
3a65f83ae1 | ||
|
|
57811a6bfd | ||
|
|
02697b5712 | ||
|
|
e76f4cc434 | ||
|
|
f51e7ea9a4 | ||
|
|
14ec51601c | ||
|
|
14976d4c56 | ||
|
|
a94168af7f | ||
|
|
2f41b2891b | ||
|
|
c857a77dd1 | ||
|
|
5fb2c2c6f8 | ||
|
|
4250ff92b8 | ||
|
|
7fd4a9ed30 | ||
|
|
2106b5e4a6 | ||
|
|
1af0367b34 | ||
|
|
f037a2e9cb | ||
|
|
aa1a6be160 | ||
|
|
c9e6ee6da7 | ||
|
|
bd7b9cca21 | ||
|
|
b228fc1a9b | ||
|
|
c3794f1fd3 | ||
|
|
4e1db854f6 | ||
|
|
630d9c79ed | ||
|
|
8f8f3849fc | ||
|
|
656f89e16a | ||
|
|
9b90a24d94 | ||
|
|
77d7deb033 | ||
|
|
7ca37afc74 | ||
|
|
2bc0e2df42 | ||
|
|
1dc09dc45f | ||
|
|
1cc02825ea | ||
|
|
d1b7a787ee | ||
|
|
f8b053a286 | ||
|
|
ebf060675d | ||
|
|
3d33b575d6 | ||
|
|
314488b5c5 | ||
|
|
6e4dc26890 | ||
|
|
9cfb4b01c8 | ||
|
|
e7d2621a05 | ||
|
|
c0c534cbe3 | ||
|
|
7aaeedd7ba | ||
|
|
e15d56c2f2 | ||
|
|
8a7c6b4fa7 | ||
|
|
2e073473dc | ||
|
|
31dcd15927 | ||
|
|
1bd40b2ebf | ||
|
|
b5429b6342 | ||
|
|
fc95028738 | ||
|
|
e916cab08f | ||
|
|
e4f1f90127 | ||
|
|
a16a33806c | ||
|
|
007f419bdc | ||
|
|
5bee9e92b5 | ||
|
|
6a68581a9f | ||
|
|
78781b0a90 | ||
|
|
d15c5a46de | ||
|
|
ce37fd3a04 | ||
|
|
64636ee978 | ||
|
|
388a9643a5 | ||
|
|
c9f4b5aede | ||
|
|
855139d869 | ||
|
|
d07cbd34ce | ||
|
|
c701fef634 | ||
|
|
2c8fc3c9c6 | ||
|
|
45054df71a | ||
|
|
8ad797e261 | ||
|
|
2dbfc8aca3 | ||
|
|
7fa97898aa | ||
|
|
3b2d5f02e1 | ||
|
|
34fd833b04 | ||
|
|
6531ec7669 | ||
|
|
728b283063 | ||
|
|
a3dd43b39b | ||
|
|
41a6eb7cf4 | ||
|
|
409b915a3f | ||
|
|
c04133b1df | ||
|
|
d1450a463e | ||
|
|
8daeb56194 | ||
|
|
3b56cdf524 | ||
|
|
d9618880a3 | ||
|
|
aa1b94166c | ||
|
|
307ae4cf66 | ||
|
|
392891f5fc | ||
|
|
58887a5a3b | ||
|
|
0a25e865b9 | ||
|
|
a01c1c2bfa | ||
|
|
3295274711 | ||
|
|
0135c6f743 | ||
|
|
e2a72bf511 | ||
|
|
519b3b9ce1 | ||
|
|
0456f52195 | ||
|
|
b5caf1ad3f | ||
|
|
f65d9b2b7d | ||
|
|
408676913c | ||
|
|
21c842baf6 | ||
|
|
a03886b9e4 | ||
|
|
f7bda4c940 | ||
|
|
abe9b7c08e | ||
|
|
6d4cdc9456 | ||
|
|
5033044587 | ||
|
|
246dd0f8c0 | ||
|
|
fb05bdc2bf | ||
|
|
9948125745 | ||
|
|
b8233ded23 | ||
|
|
6a22b7c26f | ||
|
|
322cfc46d3 | ||
|
|
c9d6a41d75 | ||
|
|
a64c0a2679 | ||
|
|
fdc5aece52 | ||
|
|
685db62029 | ||
|
|
4e643baaa6 | ||
|
|
9634468260 | ||
|
|
00c1b6a2b0 | ||
|
|
b45273d112 | ||
|
|
350adae672 | ||
|
|
bdb3832985 | ||
|
|
1fc8718352 | ||
|
|
d98eec6e91 | ||
|
|
659586947d | ||
|
|
c303ef2a2b | ||
|
|
ff92beb88a | ||
|
|
dce73fde66 | ||
|
|
da6462e608 | ||
|
|
49bf8f6816 | ||
|
|
71ce07d971 | ||
|
|
a16f86b5bd | ||
|
|
4dc27b98f1 | ||
|
|
be8476d981 | ||
|
|
cd92f19858 | ||
|
|
a2f4d57bf2 | ||
|
|
f9fa35ed82 | ||
|
|
3b87a81c70 | ||
|
|
002b260e7a | ||
|
|
659c9881cf | ||
|
|
b535d38393 | ||
|
|
d33807b5ea | ||
|
|
9650f0d41d | ||
|
|
127b6dedee | ||
|
|
db5a6c569a | ||
|
|
b47ab6c30a | ||
|
|
281c97b9e5 | ||
|
|
cb21a3547e | ||
|
|
6028eb02c9 | ||
|
|
712d52ad3b | ||
|
|
6078c408c3 | ||
|
|
9caeeaeeec | ||
|
|
2bd9eb4298 | ||
|
|
4c2542aac6 | ||
|
|
54da68cfe7 | ||
|
|
51b62ca0bd | ||
|
|
ca4b65e1a6 | ||
|
|
1e237bf33a | ||
|
|
fb8390e138 | ||
|
|
5cab47f751 | ||
|
|
172b4af6ed | ||
|
|
186dfd3976 | ||
|
|
d9adebed84 | ||
|
|
2b860f04ce | ||
|
|
b37f78942d | ||
|
|
81e60893b6 | ||
|
|
657cd30045 | ||
|
|
7cf2132df1 | ||
|
|
b0b6c0ae7b | ||
|
|
5f9b0bc0ae | ||
|
|
f7c09f5e9e | ||
|
|
9c1aeb31dd | ||
|
|
43fb6012be | ||
|
|
997a2afc79 | ||
|
|
f050f71584 | ||
|
|
3b63010673 | ||
|
|
bb47a0d9b7 | ||
|
|
475019731a | ||
|
|
1e3a0aa88c | ||
|
|
3ba8500e90 | ||
|
|
8850e8a373 | ||
|
|
566e6c9b60 | ||
|
|
9aaa73cc55 | ||
|
|
55717c9bde | ||
|
|
64c30508d9 | ||
|
|
a00ace21cb | ||
|
|
e590a027db | ||
|
|
2976d4a3ba | ||
|
|
14d448b74d | ||
|
|
ad31da06d1 | ||
|
|
f124ce2f72 | ||
|
|
95f1032215 | ||
|
|
c2c5b31f4b | ||
|
|
e8c2fefb29 | ||
|
|
4f2f5f7cbc | ||
|
|
2ae1121994 | ||
|
|
3b5669a90b | ||
|
|
85454b6371 | ||
|
|
25a98fa888 | ||
|
|
2370db0f8c | ||
|
|
1b353aed73 | ||
|
|
7b441e64e7 | ||
|
|
351a29fcfb | ||
|
|
c149a99b44 | ||
|
|
16a6450534 | ||
|
|
045f07c396 | ||
|
|
373bcfe5cf | ||
|
|
2742b2f884 | ||
|
|
c30e80df67 | ||
|
|
55044ba231 | ||
|
|
b1d0dbac77 | ||
|
|
742f17689e | ||
|
|
bbd5e692e1 | ||
|
|
c40df063d6 | ||
|
|
a0cd3ff7ad | ||
|
|
b2b31a323b | ||
|
|
50eea98fc6 | ||
|
|
a83718bec8 | ||
|
|
4d7a42a65e | ||
|
|
487157eafa | ||
|
|
4b2187c9a3 | ||
|
|
a6e32e57e8 | ||
|
|
51dbb9493c | ||
|
|
862afa27de | ||
|
|
aa8cd8259d | ||
|
|
000066d1a1 | ||
|
|
cd2b6642ee | ||
|
|
15ded2c17b | ||
|
|
e4905f438a | ||
|
|
eb762c4a77 | ||
|
|
ea5eb73b9f | ||
|
|
ae74825ea7 | ||
|
|
913a2d975b | ||
|
|
5043aaf4fa | ||
|
|
c3f6c58e13 | ||
|
|
298c93ca4a | ||
|
|
dd52472312 | ||
|
|
6475d81f29 | ||
|
|
51b4475fcc | ||
|
|
3238fa0870 | ||
|
|
fdaa5a3037 | ||
|
|
12238c7b7e | ||
|
|
3006084326 | ||
|
|
f448df4638 | ||
|
|
466cb8acb5 | ||
|
|
1a0ad5fdd0 | ||
|
|
ea071935fe | ||
|
|
794a0fff03 | ||
|
|
06ed6d7cab | ||
|
|
6eacc6b7c8 | ||
|
|
f6ac881591 | ||
|
|
f431b415fc | ||
|
|
8d4b95afa8 | ||
|
|
4b51c27461 | ||
|
|
585c5c41ce | ||
|
|
c64588a673 | ||
|
|
75fa7395bd | ||
|
|
66ad48e771 | ||
|
|
41fd8020ee | ||
|
|
09a28ccef6 | ||
|
|
c0431227d8 | ||
|
|
b5f861722d | ||
|
|
770e1601e5 | ||
|
|
045c2af663 | ||
|
|
23d3a7ac07 | ||
|
|
67d00aca90 | ||
|
|
e54989ca74 | ||
|
|
14372a4515 | ||
|
|
6e33bda433 | ||
|
|
77eb88eef2 | ||
|
|
7584f058c2 | ||
|
|
fba6e0504c | ||
|
|
c17fe2d189 | ||
|
|
4a58dcccf2 | ||
|
|
ed2e1a736f | ||
|
|
a168615f2d | ||
|
|
190372e137 | ||
|
|
eba8303c98 | ||
|
|
2845d7ace5 | ||
|
|
2ceac211e7 | ||
|
|
8f6b3b636d | ||
|
|
56bf9c9047 | ||
|
|
ebe01c2639 | ||
|
|
272219e410 | ||
|
|
a7d48465da | ||
|
|
bb1324cdc3 | ||
|
|
85af0603fa | ||
|
|
590f810b53 | ||
|
|
e6e5911aae | ||
|
|
9fdbd0dd49 | ||
|
|
8a20c6f7e4 | ||
|
|
03f9b27e3b | ||
|
|
19f6f7d5ca | ||
|
|
745b96eb63 | ||
|
|
7ea5da73c7 | ||
|
|
6ae2c6c7b9 | ||
|
|
b90d44d97d | ||
|
|
d99d3e05c7 | ||
|
|
ca6aef4746 | ||
|
|
f5b9e463e3 | ||
|
|
82cbe25be8 | ||
|
|
36505d9656 | ||
|
|
ae696d4f30 | ||
|
|
b43ef6440f | ||
|
|
e615aabf14 | ||
|
|
b72ea0ea1b | ||
|
|
92ae8f4817 | ||
|
|
2268802908 | ||
|
|
261cdf88a5 | ||
|
|
603a137d33 | ||
|
|
4cdb38d337 | ||
|
|
03bf1e5414 | ||
|
|
4fa5c8bab0 | ||
|
|
9711f39995 | ||
|
|
091bc003ae | ||
|
|
57c40026f8 | ||
|
|
bf61f6f869 | ||
|
|
6c299a1b5a | ||
|
|
aeae796e60 | ||
|
|
7ec37575de | ||
|
|
4fdd05532d | ||
|
|
43c793f7fe | ||
|
|
1765000263 | ||
|
|
3fdd4715f5 | ||
|
|
97cb44058a | ||
|
|
73e0871345 | ||
|
|
c8f046869b | ||
|
|
bd4dccd9cb | ||
|
|
c268d695f0 | ||
|
|
0891cc1864 | ||
|
|
ee7cab664a | ||
|
|
fd8f9e87dc | ||
|
|
ed9c015d24 | ||
|
|
cd4fae601c | ||
|
|
aed313ebb5 | ||
|
|
2008df03fa | ||
|
|
3f860617a5 | ||
|
|
98a962661b | ||
|
|
9483da1bdb | ||
|
|
8d3da565c4 | ||
|
|
42174ad9fb | ||
|
|
0c1ec8d5d2 | ||
|
|
fdbb33ddad | ||
|
|
3f43e0d945 | ||
|
|
d00503ecc4 | ||
|
|
ab80c113ac | ||
|
|
fbced023f3 | ||
|
|
91d6b964c4 | ||
|
|
6e57fa5f9a | ||
|
|
77f6ec5a2b | ||
|
|
f2a95ad67f | ||
|
|
d635a22302 | ||
|
|
b1ffa52da6 | ||
|
|
e2b959ee7e | ||
|
|
84e404eb71 | ||
|
|
a1a5fc01a9 | ||
|
|
2616614470 | ||
|
|
64fd304ea0 | ||
|
|
f820ca8f7f | ||
|
|
4bacbdd2bd | ||
|
|
34407d9de1 | ||
|
|
b81612c292 | ||
|
|
b3e44bed22 | ||
|
|
e8b05faff4 | ||
|
|
610b6cc78c | ||
|
|
75f8a33c9e | ||
|
|
afe3905958 | ||
|
|
375d9a45d9 | ||
|
|
f5390a4ee5 | ||
|
|
29d7a812ae | ||
|
|
fb9d5b9304 | ||
|
|
b77ef7d162 | ||
|
|
b0e0d269c5 | ||
|
|
1dbf7a3392 | ||
|
|
27cd4e2221 | ||
|
|
dc703857dd | ||
|
|
5df5eb6b09 | ||
|
|
eaa23ef4e5 | ||
|
|
4dc1195bcd | ||
|
|
55c78bbf90 | ||
|
|
2a7ae83bf8 | ||
|
|
d7dc16310f | ||
|
|
afd940d9bd | ||
|
|
ed5d114087 | ||
|
|
9d14f4f2f1 | ||
|
|
29e860acca | ||
|
|
b26be0c113 | ||
|
|
ec2892098b | ||
|
|
c7133fd34b | ||
|
|
7ed5f9e4bc | ||
|
|
8bd2d88be4 | ||
|
|
3a53843185 | ||
|
|
f974471f7d | ||
|
|
909bf4433f | ||
|
|
73b4b6ec5b | ||
|
|
06f83710c8 | ||
|
|
b3ee728839 | ||
|
|
60f7a7d593 | ||
|
|
56238036d7 | ||
|
|
3f35a0a44e | ||
|
|
58af2c7d9f | ||
|
|
95a4a55849 | ||
|
|
97281d708b | ||
|
|
c39f4ce47a | ||
|
|
833b1faba2 | ||
|
|
2ca816ca25 | ||
|
|
50861cdf1b | ||
|
|
b5b0f8d7e3 | ||
|
|
87e80fe45a | ||
|
|
ffe9a6d5b8 | ||
|
|
97ae473002 | ||
|
|
246b7dafcf | ||
|
|
254926c4a8 | ||
|
|
5fbc9555c3 | ||
|
|
83089c3a7f | ||
|
|
c2e25e2ac2 | ||
|
|
e3ef9adac7 | ||
|
|
e4bfcffed4 | ||
|
|
a184c7f23a | ||
|
|
a96f5f8f27 | ||
|
|
79dc1a64b3 | ||
|
|
7bc489f1f4 | ||
|
|
924cd1f2d8 | ||
|
|
6fd1dc5697 | ||
|
|
09c5ebc94d | ||
|
|
ebeebcd095 | ||
|
|
3713ba66a8 | ||
|
|
449b42717c | ||
|
|
2a1cdd31c5 | ||
|
|
5444563fdc | ||
|
|
fad4de789f | ||
|
|
bf10f39b6b | ||
|
|
ebf6316fa7 | ||
|
|
647c5737f0 | ||
|
|
9e7695085e | ||
|
|
8cf10fa460 | ||
|
|
3ef65c37b3 | ||
|
|
4b5152fde5 | ||
|
|
07c3c5ff6f | ||
|
|
ada17d2ccb | ||
|
|
738f725aea | ||
|
|
558a513a1a | ||
|
|
5a55974f15 | ||
|
|
f84f9374d3 | ||
|
|
f8c87afee1 | ||
|
|
dedbdefd93 | ||
|
|
f13cf5dc2c | ||
|
|
1e2682616f | ||
|
|
71d9d02d26 | ||
|
|
bd100fdfef | ||
|
|
3f4a8e557e | ||
|
|
e9f7cd3e52 | ||
|
|
39321ba042 | ||
|
|
85ec1934de | ||
|
|
45dbf8c656 | ||
|
|
60763d4ddf | ||
|
|
8c4da2c41c | ||
|
|
dfb2ac321f | ||
|
|
4d2456be40 | ||
|
|
166aaa1bac | ||
|
|
7cad1658f6 | ||
|
|
a3a9c6ceed | ||
|
|
4849501ccf | ||
|
|
a6dddd322a | ||
|
|
17785375b8 | ||
|
|
b632fd8c54 | ||
|
|
f32daee7d5 | ||
|
|
df52115de0 | ||
|
|
2eb7d14acd | ||
|
|
2842dae598 | ||
|
|
2354066c6d | ||
|
|
4b564f69a8 | ||
|
|
c283d7e2ba | ||
|
|
3675c5b89d | ||
|
|
a8644baf50 | ||
|
|
cf75003cbe | ||
|
|
fab950acfb | ||
|
|
7ed26e1396 | ||
|
|
293bc2c11f | ||
|
|
bb6785bbef | ||
|
|
878567f6b0 | ||
|
|
165e82eb33 | ||
|
|
64a17599c1 | ||
|
|
23f422e9d9 | ||
|
|
d16b8b4e59 | ||
|
|
9b4bb69001 | ||
|
|
5232ea2606 | ||
|
|
56086512ca | ||
|
|
cd3a1cbf8f | ||
|
|
6e8e532995 | ||
|
|
2093347f8a | ||
|
|
f380e2301d | ||
|
|
8b45146f13 | ||
|
|
5c42d26475 | ||
|
|
6e7265a659 | ||
|
|
5e8aa5d9f2 | ||
|
|
d180c80c34 | ||
|
|
ab8f3174e1 | ||
|
|
320a87e1b9 | ||
|
|
727e63c01e | ||
|
|
267f5e5234 | ||
|
|
99e592b474 | ||
|
|
2cbfc1cbeb | ||
|
|
00514955fa | ||
|
|
d7a04663da | ||
|
|
94300877ee | ||
|
|
dfdb4d00ea | ||
|
|
3506be18a4 | ||
|
|
7a68cc63c4 | ||
|
|
281e66ed2e | ||
|
|
1fe13cb009 | ||
|
|
5b6c021d6a | ||
|
|
ca4ad62762 | ||
|
|
d9bfe22f00 | ||
|
|
b8e6483770 | ||
|
|
7e54aba3e7 | ||
|
|
6929afda8a | ||
|
|
c326ad019d | ||
|
|
2bf4a72d9d | ||
|
|
b066b6ffa1 | ||
|
|
36f5e49693 | ||
|
|
19087ed6d8 | ||
|
|
0a2a64a830 | ||
|
|
bccea30c2a | ||
|
|
5fa2172066 |
@@ -1,5 +1,5 @@
|
||||
name: "\U0001F41B Bug Report"
|
||||
description: Report a bug in LangChain. To report a security issue, please instead use the security option below. For questions, please use the GitHub Discussions.
|
||||
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: ["02 Bug Report"]
|
||||
body:
|
||||
- type: markdown
|
||||
@@ -15,11 +15,11 @@ body:
|
||||
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 documentation](https://langchain-ai.github.io/langgraph/).
|
||||
[LangChain documentation with the integrated search](https://python.langchain.com/docs/get_started/introduction),
|
||||
[API Reference](https://api.python.langchain.com/en/stable/),
|
||||
[GitHub search](https://github.com/langchain-ai/langchain),
|
||||
[LangChain Github Discussions](https://github.com/langchain-ai/langchain/discussions),
|
||||
[LangChain Github Issues](https://github.com/langchain-ai/langchain/issues?q=is%3Aissue),
|
||||
[GitHub search](https://github.com/langchain-ai/langgraph),
|
||||
[LangChain Github Discussions](https://github.com/langchain-ai/langgraph/discussions),
|
||||
[LangChain Github Issues](https://github.com/langchain-ai/langgraph/issues),
|
||||
[LangChain ChatBot](https://chat.langchain.com/)
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
@@ -54,13 +54,13 @@ body:
|
||||
* Avoid screenshots when possible, as they are hard to read and (more importantly) don't allow others to copy-and-paste your code.
|
||||
|
||||
placeholder: |
|
||||
from langchain_core.runnables import RunnableLambda
|
||||
from langgraph.graph import StateGraph
|
||||
|
||||
def bad_code(inputs) -> int:
|
||||
raise NotImplementedError('For demo purpose')
|
||||
|
||||
chain = RunnableLambda(bad_code)
|
||||
chain.invoke('Hello!')
|
||||
chain = StateGraph(list)
|
||||
chain.invoke('Hello!')
|
||||
render: python
|
||||
- type: textarea
|
||||
id: error
|
||||
@@ -82,7 +82,7 @@ body:
|
||||
|
||||
Write a short description telling what you are doing, what you expect to happen, and what is currently happening.
|
||||
placeholder: |
|
||||
* I'm trying to use the `langchain` library to do X.
|
||||
* I'm trying to use the `langgraph` library to do X.
|
||||
* I expect to see Y.
|
||||
* Instead, it does Z.
|
||||
validations:
|
||||
@@ -102,7 +102,7 @@ body:
|
||||
|
||||
python -m langchain_core.sys_info
|
||||
placeholder: |
|
||||
"pip freeze | grep langchain"
|
||||
"pip freeze | grep langgraph"
|
||||
platform
|
||||
python version
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@ version: 2.1
|
||||
contact_links:
|
||||
- name: 🤔 Question or Problem
|
||||
about: Ask a question or ask about a problem in GitHub Discussions.
|
||||
url: https://www.github.com/langchain-ai/langchain/discussions/categories/q-a
|
||||
- name: Discord
|
||||
url: https://discord.gg/6adMQxSpJS
|
||||
about: General community discussions
|
||||
url: https://github.com/langchain-ai/langgraph/discussions/categories/q-a
|
||||
- name: Feature Request
|
||||
url: https://www.github.com/langchain-ai/langchain/discussions/categories/ideas
|
||||
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://www.github.com/langchain-ai/langchain/discussions/categories/show-and-tell
|
||||
url: https://github.com/langchain-ai/langgraph/discussions/categories/show-and-tell
|
||||
- name: Slack
|
||||
url: https://www.langchain.com/join-community
|
||||
about: General community discussions
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: Documentation
|
||||
description: Report an issue related to the LangChain documentation.
|
||||
description: Report an issue related to the LangGraph documentation.
|
||||
title: "DOC: <Please write a comprehensive title after the 'DOC: ' prefix>"
|
||||
labels: [03 - Documentation]
|
||||
|
||||
|
||||
@@ -24,13 +24,7 @@ jobs:
|
||||
name: "test #${{ matrix.python-version }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: Ana06/get-changed-files@v2.2.0
|
||||
with:
|
||||
filter: "${{ inputs.working-directory }}/**"
|
||||
- name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }}
|
||||
if: steps.changed-files.outputs.all
|
||||
uses: "./.github/actions/poetry_setup"
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
@@ -39,20 +33,17 @@ jobs:
|
||||
cache-key: core
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.changed-files.outputs.all
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: poetry install --with dev
|
||||
|
||||
- name: Run core tests
|
||||
if: steps.changed-files.outputs.all
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: |
|
||||
make test
|
||||
|
||||
- name: Ensure the tests did not create any additional files
|
||||
if: steps.changed-files.outputs.all
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: |
|
||||
|
||||
@@ -36,7 +36,10 @@
|
||||
working-directory: [
|
||||
"libs/langgraph",
|
||||
"libs/sdk-py",
|
||||
"libs/cli"
|
||||
"libs/cli",
|
||||
"libs/checkpoint",
|
||||
"libs/checkpoint-sqlite",
|
||||
"libs/checkpoint-postgres"
|
||||
]
|
||||
uses: ./.github/workflows/_lint.yml
|
||||
with:
|
||||
@@ -50,7 +53,10 @@
|
||||
matrix:
|
||||
working-directory: [
|
||||
"libs/langgraph",
|
||||
"libs/cli"
|
||||
"libs/cli",
|
||||
"libs/checkpoint",
|
||||
"libs/checkpoint-sqlite",
|
||||
"libs/checkpoint-postgres"
|
||||
]
|
||||
uses: ./.github/workflows/_test.yml
|
||||
with:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Check Links
|
||||
name: Check Docs & Links
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Check links in Markdown files
|
||||
uses: gaurav-nelson/github-action-markdown-link-check@v1
|
||||
with:
|
||||
folder-path: "examples/"
|
||||
folder-path: "examples/,docs/"
|
||||
check-modified-files-only: ${{ github.event_name != 'schedule' }}
|
||||
file-path: "./README.md"
|
||||
config-file: "./.markdown-link-check.config.json"
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
echo "Fetching changes from origin/main..."
|
||||
git fetch origin main
|
||||
echo "Checking for changed notebook files..."
|
||||
CHANGED_FILES=$(git diff --name-only origin/main | grep '\.ipynb$' || true)
|
||||
CHANGED_FILES=$(git diff --name-only --diff-filter=d origin/main | grep '\.ipynb$' || true)
|
||||
echo "Changed files: ${CHANGED_FILES}"
|
||||
if [ -n "${CHANGED_FILES}" ]; then
|
||||
echo "Running link check on changed notebook files..."
|
||||
@@ -69,3 +69,19 @@ jobs:
|
||||
echo "No notebook files changed."
|
||||
fi
|
||||
fi
|
||||
check-readmes-synced:
|
||||
# This checks that the repo README.md is identical to the libs/langgraph/README.md
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Check README.md is in sync
|
||||
run: |
|
||||
if ! diff -q README.md libs/langgraph/README.md >/dev/null; then
|
||||
echo "README.md is out of sync with libs/langgraph/README.md"
|
||||
diff -C 3 README.md libs/langgraph/README.md
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
working-directory:
|
||||
required: true
|
||||
type: string
|
||||
default: 'libs/langgraph'
|
||||
default: "libs/langgraph"
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.11"
|
||||
@@ -99,12 +99,13 @@ jobs:
|
||||
TAG: ${{ needs.build.outputs.tag }}
|
||||
run: |
|
||||
if [ -z $SHORT_PKG_NAME ]; then
|
||||
REGEX="^\\d+\\.\\d+\\.\\d+((a|b|rc)\\d+)?$"
|
||||
REGEX="^\\d+\\.\\d+\\.\\d+((a|b|rc)\\d+)?\$"
|
||||
else
|
||||
REGEX="^$SHORT_PKG_NAME==\\d+\\.\\d+\\.\\d+((a|b|rc)\\d+)?$"
|
||||
REGEX="^$SHORT_PKG_NAME==\\d+\\.\\d+\\.\\d+((a|b|rc)\\d+)?\$"
|
||||
fi
|
||||
echo $REGEX
|
||||
PREV_TAG=$(git tag --sort=-creatordate | grep -P $REGEX || true | head -1)
|
||||
PREV_TAG=$(git tag --sort=-creatordate | grep -P $REGEX | head -1 || echo "")
|
||||
echo $PREV_TAG
|
||||
if [ "$TAG" == "$PREV_TAG" ]; then
|
||||
echo "No new version to release"
|
||||
exit 1
|
||||
@@ -135,8 +136,8 @@ jobs:
|
||||
needs:
|
||||
- build
|
||||
- release-notes
|
||||
uses:
|
||||
./.github/workflows/_test_release.yml
|
||||
permissions: write-all
|
||||
uses: ./.github/workflows/_test_release.yml
|
||||
with:
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
secrets: inherit
|
||||
@@ -196,9 +197,15 @@ jobs:
|
||||
"$PKG_NAME==$VERSION" \
|
||||
)
|
||||
|
||||
# Replace all dashes in the package name with underscores,
|
||||
# since that's how Python imports packages with dashes in the name.
|
||||
IMPORT_NAME="$(echo "$PKG_NAME" | sed s/-/_/g)"
|
||||
if [[ "$PKG_NAME" == *checkpoint* ]]; then
|
||||
# since checkpoint packages are namespace packages, import them with . convention
|
||||
# i.e. import langgraph.checkpoint or langgraph.checkpoint.sqlite
|
||||
IMPORT_NAME="$(echo "$PKG_NAME" | sed s/-/./g)"
|
||||
else
|
||||
# Replace all dashes in the package name with underscores,
|
||||
# since that's how Python imports packages with dashes in the name.
|
||||
IMPORT_NAME="$(echo "$PKG_NAME" | sed s/-/_/g)"
|
||||
fi
|
||||
|
||||
poetry run python -c "import $IMPORT_NAME; print(dir($IMPORT_NAME))"
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
name: Check File Size
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
file-size-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v44
|
||||
- name: Filter by size
|
||||
run: |
|
||||
large_added_files=$(find ${{ steps.changed-files.outputs.added_files }} -maxdepth 0 -size +1M)
|
||||
if [ -n "$large_added_files" ]; then
|
||||
echo "Large files added: $large_added_files"
|
||||
echo "# Large files added:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "$large_added_files" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,6 +1,14 @@
|
||||
.PHONY: build-docs serve-docs serve-clean-docs clean-docs codespell
|
||||
.PHONY: build-docs serve-docs serve-clean-docs clean-docs codespell build-typedoc
|
||||
|
||||
build-docs:
|
||||
build-typedoc:
|
||||
cd libs/sdk-js && yarn install --include-dev && yarn typedoc
|
||||
cd libs/sdk-js && yarn --silent concat-md --decrease-title-levels --ignore=js_ts_sdk_ref.md --start-title-level-at 2 docs > ../../docs/docs/cloud/reference/sdk/js_ts_sdk_ref.md 2>/dev/null
|
||||
# Add links to the monorepo
|
||||
sed -e '1,10s|@langchain/langgraph-sdk|[@langchain/langgraph-sdk](https://github.com/langchain-ai/langgraph/tree/main/libs/sdk-js)|g' docs/docs/cloud/reference/sdk/js_ts_sdk_ref.md > temp_file && mv temp_file docs/docs/cloud/reference/sdk/js_ts_sdk_ref.md
|
||||
|
||||
|
||||
|
||||
build-docs: build-typedoc
|
||||
poetry run python docs/_scripts/copy_notebooks.py
|
||||
poetry run python -m mkdocs build --clean -f docs/mkdocs.yml --strict
|
||||
|
||||
@@ -8,7 +16,7 @@ serve-clean-docs: clean-docs
|
||||
poetry run python docs/_scripts/copy_notebooks.py
|
||||
poetry run python -m mkdocs serve -c -f docs/mkdocs.yml --strict -w ./libs/langgraph
|
||||
|
||||
serve-docs:
|
||||
serve-docs: build-typedoc
|
||||
poetry run python docs/_scripts/copy_notebooks.py
|
||||
poetry run python -m mkdocs serve -f docs/mkdocs.yml -w ./libs/langgraph --dirty
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||

|
||||
[](https://pepy.tech/project/langgraph)
|
||||
[](https://github.com/langchain-ai/langgraph/issues)
|
||||
[](https://discord.com/channels/1038097195422978059/1170024642245832774)
|
||||
[](https://langchain-ai.github.io/langgraph/)
|
||||
|
||||
⚡ Building language agents as graphs ⚡
|
||||
@@ -36,40 +35,49 @@ pip install -U langgraph
|
||||
|
||||
One of the central concepts of LangGraph is state. Each graph execution creates a state that is passed between nodes in the graph as they execute, and each node updates this internal state with its return value after it executes. The way that the graph updates its internal state is defined by either the type of graph chosen or a custom function.
|
||||
|
||||
Let's take a look at a simple example of an agent that can search the web using [Tavily Search API](https://tavily.com/).
|
||||
Let's take a look at a simple example of an agent that can use a search tool.
|
||||
|
||||
```shell
|
||||
pip install langchain_openai langchain_community
|
||||
pip install langchain-anthropic
|
||||
```
|
||||
|
||||
```shell
|
||||
export OPENAI_API_KEY=sk-...
|
||||
export TAVILY_API_KEY=tvly-...
|
||||
export ANTHROPIC_API_KEY=sk-...
|
||||
```
|
||||
|
||||
Optionally, we can set up [LangSmith](https://docs.smith.langchain.com/) for best-in-class observability.
|
||||
|
||||
```shell
|
||||
export LANGCHAIN_TRACING_V2="true"
|
||||
export LANGCHAIN_API_KEY=ls__...
|
||||
export LANGSMITH_TRACING=true
|
||||
export LANGSMITH_API_KEY=lsv2_sk_...
|
||||
```
|
||||
|
||||
```python
|
||||
from typing import Annotated, Literal, TypedDict
|
||||
|
||||
from langchain_core.messages import HumanMessage
|
||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.checkpoint import MemorySaver
|
||||
from langgraph.graph import END, StateGraph, MessagesState
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langchain_core.tools import tool
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.graph import END, START, StateGraph, MessagesState
|
||||
from langgraph.prebuilt import ToolNode
|
||||
|
||||
|
||||
# Define the tools for the agent to use
|
||||
tools = [TavilySearchResults(max_results=1)]
|
||||
@tool
|
||||
def search(query: str):
|
||||
"""Call to surf the web."""
|
||||
# This is a placeholder, but don't tell the LLM that...
|
||||
if "sf" in query.lower() or "san francisco" in query.lower():
|
||||
return "It's 60 degrees and foggy."
|
||||
return "It's 90 degrees and sunny."
|
||||
|
||||
|
||||
tools = [search]
|
||||
|
||||
tool_node = ToolNode(tools)
|
||||
|
||||
model = ChatOpenAI(temperature=0).bind_tools(tools)
|
||||
model = ChatAnthropic(model="claude-3-5-sonnet-20240620", temperature=0).bind_tools(tools)
|
||||
|
||||
# Define the function that determines whether to continue or not
|
||||
def should_continue(state: MessagesState) -> Literal["tools", END]:
|
||||
@@ -99,7 +107,7 @@ workflow.add_node("tools", tool_node)
|
||||
|
||||
# Set the entrypoint as `agent`
|
||||
# This means that this node is the first one called
|
||||
workflow.set_entry_point("agent")
|
||||
workflow.add_edge(START, "agent")
|
||||
|
||||
# We now add a conditional edge
|
||||
workflow.add_conditional_edges(
|
||||
@@ -132,7 +140,7 @@ final_state["messages"][-1].content
|
||||
```
|
||||
|
||||
```
|
||||
'The current weather in San Francisco is as follows:\n- Temperature: 60.1°F (15.6°C)\n- Condition: Partly cloudy\n- Wind: 5.6 mph (9.0 kph) from SSW\n- Humidity: 83%\n- Visibility: 9.0 miles (16.0 km)\n- UV Index: 4.0\n\nFor more details, you can visit [Weather API](https://www.weatherapi.com/).'
|
||||
"Based on the search results, I can tell you that the current weather in San Francisco is:\n\nTemperature: 60 degrees Fahrenheit\nConditions: Foggy\n\nSan Francisco is known for its microclimates and frequent fog, especially during the summer months. The temperature of 60°F (about 15.5°C) is quite typical for the city, which tends to have mild temperatures year-round. The fog, often referred to as "Karl the Fog" by locals, is a characteristic feature of San Francisco\'s weather, particularly in the mornings and evenings.\n\nIs there anything else you\'d like to know about the weather in San Francisco or any other location?"
|
||||
```
|
||||
|
||||
Now when we pass the same `"thread_id"`, the conversation context is retained via the saved state (i.e. stored list of messages)
|
||||
@@ -146,7 +154,7 @@ final_state["messages"][-1].content
|
||||
```
|
||||
|
||||
```
|
||||
'The current weather in New York is as follows:\n- Temperature: 20.3°C (68.5°F)\n- Condition: Overcast\n- Wind: 2.2 mph from the north\n- Humidity: 65%\n- Cloud Cover: 100%\n- UV Index: 5.0\n\nFor more details, you can visit [Weather API](https://www.weatherapi.com/).'
|
||||
"Based on the search results, I can tell you that the current weather in New York City is:\n\nTemperature: 90 degrees Fahrenheit (approximately 32.2 degrees Celsius)\nConditions: Sunny\n\nThis weather is quite different from what we just saw in San Francisco. New York is experiencing much warmer temperatures right now. Here are a few points to note:\n\n1. The temperature of 90°F is quite hot, typical of summer weather in New York City.\n2. The sunny conditions suggest clear skies, which is great for outdoor activities but also means it might feel even hotter due to direct sunlight.\n3. This kind of weather in New York often comes with high humidity, which can make it feel even warmer than the actual temperature suggests.\n\nIt's interesting to see the stark contrast between San Francisco's mild, foggy weather and New York's hot, sunny conditions. This difference illustrates how varied weather can be across different parts of the United States, even on the same day.\n\nIs there anything else you'd like to know about the weather in New York or any other location?"
|
||||
```
|
||||
|
||||
### Step-by-step Breakdown
|
||||
@@ -154,8 +162,8 @@ final_state["messages"][-1].content
|
||||
1. <details>
|
||||
<summary>Initialize the model and tools.</summary>
|
||||
|
||||
- we use `ChatOpenAI` as our LLM. **NOTE:** we need make sure the model knows that it has these tools available to call. We can do this by converting the LangChain tools into the format for OpenAI tool calling using the `.bind_tools()` method.
|
||||
- we define the tools we want to use - a web search tool in our case. It is really easy to create your own tools - see documentation here on how to do that [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools).
|
||||
- we use `ChatAnthropic` as our LLM. **NOTE:** we need make sure the model knows that it has these tools available to call. We can do this by converting the LangChain tools into the format for OpenAI tool calling using the `.bind_tools()` method.
|
||||
- we define the tools we want to use - a search tool in our case. It is really easy to create your own tools - see documentation here on how to do that [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools).
|
||||
</details>
|
||||
|
||||
2. <details>
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
*.ipynb
|
||||
site/
|
||||
docs/tutorials/**/*.png
|
||||
docs/cloud/reference/sdk/js_ts_sdk_ref.md
|
||||
|
||||
@@ -15,22 +15,36 @@ cloud_sdk_dir = docs_dir / "cloud"
|
||||
|
||||
_MANUAL = {
|
||||
"how-tos": [
|
||||
"state-context-key.ipynb",
|
||||
"async.ipynb",
|
||||
"stream-values.ipynb",
|
||||
"stream-updates.ipynb",
|
||||
"stream-multiple.ipynb",
|
||||
"streaming-tokens.ipynb",
|
||||
"streaming-tokens-without-langchain.ipynb",
|
||||
"streaming-content.ipynb",
|
||||
"streaming-events-from-within-tools.ipynb",
|
||||
"streaming-events-from-within-tools-without-langchain.ipynb",
|
||||
"streaming-from-final-node.ipynb",
|
||||
"persistence.ipynb",
|
||||
"managing-conversation-history.ipynb",
|
||||
"input_output_schema.ipynb",
|
||||
"pass_private_state.ipynb",
|
||||
"memory/manage-conversation-history.ipynb",
|
||||
"memory/delete-messages.ipynb",
|
||||
"memory/add-summary-conversation-history.ipynb",
|
||||
"persistence_postgres.ipynb",
|
||||
"persistence_mongodb.ipynb",
|
||||
"persistence_redis.ipynb",
|
||||
"visualization.ipynb",
|
||||
"state-model.ipynb",
|
||||
"subgraph.ipynb",
|
||||
"recursion-limit.ipynb",
|
||||
"force-calling-a-tool-first.ipynb",
|
||||
"pass-run-time-values-to-tools.ipynb",
|
||||
"tool-calling.ipynb",
|
||||
"tool-calling-errors.ipynb",
|
||||
"pass-config-to-tools.ipynb",
|
||||
"many-tools.ipynb",
|
||||
"dynamic-returning-direct.ipynb",
|
||||
"managing-agent-steps.ipynb",
|
||||
"respond-in-format.ipynb",
|
||||
@@ -43,9 +57,14 @@ _MANUAL = {
|
||||
"create-react-agent-memory.ipynb",
|
||||
"create-react-agent-hitl.ipynb",
|
||||
"human_in_the_loop/breakpoints.ipynb",
|
||||
"human_in_the_loop/dynamic_breakpoints.ipynb",
|
||||
"human_in_the_loop/time-travel.ipynb",
|
||||
"human_in_the_loop/edit-graph-state.ipynb",
|
||||
"human_in_the_loop/wait-user-input.ipynb",
|
||||
"human_in_the_loop/review-tool-calls.ipynb",
|
||||
"node-retries.ipynb",
|
||||
"react_diagrams.png",
|
||||
"react-agent-structured-output.ipynb",
|
||||
],
|
||||
"tutorials": [
|
||||
"introduction.ipynb",
|
||||
@@ -81,14 +100,14 @@ _HIDE = set(
|
||||
"dynamically-returning-directly.ipynb",
|
||||
"force-calling-a-tool-first.ipynb",
|
||||
"managing-agent-steps.ipynb",
|
||||
"pass-run-time-values-to-tools.ipynb",
|
||||
"respond-in-format.ipynb",
|
||||
"quickstart.ipynb",
|
||||
"human-in-the-loop.ipynb",
|
||||
"learning.ipynb",
|
||||
"docs/quickstart.ipynb",
|
||||
"tutorials/rag-agent-testing.ipynb",
|
||||
"state-context-key.ipynb",
|
||||
"tutorials/rag-agent-testing-local.ipynb",
|
||||
"tutorials/tool-calling-agent-local.ipynb",
|
||||
"time-travel.ipynb",
|
||||
"code_assistant/langgraph_code_assistant_mistral.ipynb",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
# API Concepts
|
||||
|
||||
This page describes the high-level concepts of the LangGraph Cloud API. The conceptual guide of LangGraph (Python library) is [here](../../concepts/index.md).
|
||||
|
||||
## Data Models
|
||||
|
||||
The LangGraph Cloud API consists of a few core data models: [Assistants](#assistants), [Threads](#threads), [Runs](#runs), and [Cron Jobs](#cron-jobs).
|
||||
|
||||
### Assistants
|
||||
|
||||
An assistant is a configured instance of a [`CompiledGraph`][compiledgraph]. It abstracts the cognitive architecture of the graph and contains instance specific configuration and metadata. Multiple assistants can reference the same graph but can contain different configuration and metadata, which may differentiate the behavior of the assistants. An assistant (i.e. the graph) is invoked as part of a run.
|
||||
|
||||
The LangGraph Cloud API provides several endpoints for creating and managing assistants. See the [API reference](../reference/api/api_ref.html#tag/assistantscreate) for more details.
|
||||
|
||||
#### Configuring Assistants
|
||||
|
||||
You can save custom assistants from the same graph to set different default prompts, models, and other configurations without changing a line of code in your graph. This allows you the ability to quickly test out different configurations without having to rewrite your graph every time, and also give users the flexibility to select different configurations when using your LangGraph application. See <a href="https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/configuration_cloud/">this</a> how-to for information on how to configure a deployed graph.
|
||||
|
||||
### Threads
|
||||
|
||||
A thread contains the accumulated state of a group of runs. If a run is executed on a thread, then the [state][state] of the underlying graph of the assistant will be persisted to the thread. A thread's current and historical state can be retrieved. To persist state, a thread must be created prior to executing a run.
|
||||
|
||||
The state of a thread at a particular point in time is called a checkpoint.
|
||||
|
||||
For more on threads and checkpoints, see this section of the [LangGraph conceptual guide](../../concepts/low_level.md#checkpointer).
|
||||
|
||||
The LangGraph Cloud API provides several endpoints for creating and managing threads and thread state. See the [API reference](../reference/api/api_ref.html#tag/threadscreate) for more details.
|
||||
|
||||
### Runs
|
||||
|
||||
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.
|
||||
|
||||
The LangGraph Cloud API provides several endpoints for creating and managing runs. See the [API reference](../reference/api/api_ref.html#tag/runscreate) for more details.
|
||||
|
||||
### Cron Jobs
|
||||
|
||||
It's often useful to run graphs on some schedule. LangGraph Cloud supports cron jobs, which run on a user defined schedule. The user specifies a schedule, an assistant, and some input. After than, on the specified schedule LangGraph cloud will:
|
||||
|
||||
- Create a new thread with the specified assistant
|
||||
- Send the specified input to that thread
|
||||
|
||||
Note that this sends the same input to the thread every time. See the [how-to guide](../how-tos/cloud_examples/cron_jobs.ipynb) for creating cron jobs.
|
||||
|
||||
The LangGraph Cloud API provides several endpoints for creating and managing cron jobs. See the [API reference](../reference/api/api_ref.html#tag/runscreate/POST/threads/{thread_id}/runs/crons) for more details.
|
||||
|
||||
## Features
|
||||
|
||||
The LangGraph Cloud API offers several features to support complex agent architectures.
|
||||
|
||||
### Streaming
|
||||
|
||||
Streaming is critical for making LLM applications feel responsive to end users. When creating a streaming run, the streaming mode determines what data is streamed back to the API client. The LangGraph Cloud API supports five streaming modes.
|
||||
|
||||
- `values`: Stream the full state of the graph after each [super-step](https://langchain-ai.github.io/langgraph/concepts/low_level/#graphs) is executed. See the [how-to guide](../how-tos/stream_values.md) for streaming values.
|
||||
- `messages`: Stream complete messages (at the end of node execution) as well as tokens for any messages generated inside a node. This mode is primarily meant for powering chat applications. This is only an option if your graph contains a `messages` key. See the [how-to guide](../how-tos/stream_messages.md) for streaming messages.
|
||||
- `updates`: Streams updates to the state of the graph after each node is executed. See the [how-to guide](../how-tos/stream_updates.md) for streaming updates.
|
||||
- `events`: Stream all events (including the state of the graph) that occur during graph execution. See the [how-to guide](../how-tos/stream_events.md) for streaming events. This can be used to do token-by-token streaming for LLMs.
|
||||
- `debug`: Stream debug events throughout graph execution. See the [how-to guide](../how-tos/stream_debug.md) for streaming debug events.
|
||||
|
||||
You can also specify multiple streaming modes at the same time. See the [how-to guide](../how-tos/stream_multiple.md) for configuring multiple streaming modes at the same time.
|
||||
|
||||
See the [API reference](../reference/api/api_ref.html#tag/runscreate/POST/threads/{thread_id}/runs/stream) for how to create streaming runs.
|
||||
|
||||
Streaming modes `values`, `updates`, and `debug` are very similar to modes available in the LangGraph library - for a deeper conceptual explanation of those, you can see the LangGraph library documentation [here](../../concepts/low_level.md#streaming).
|
||||
|
||||
Streaming mode `events` is the same as using `.astream_events` in the LangGraph library - for a deeper conceptual explanation of this, you can see the LangGraph library documentation [here](../../concepts/low_level.md#streaming).
|
||||
|
||||
#### `mode="messages"`
|
||||
Streaming mode `messages` is a new streaming mode, currently only available in the API. What does this mode enable?
|
||||
|
||||
This mode is focused on streaming back messages. It currently assumes that you have a `messages` key in your graph that is a list of messages. Assuming we have a simple react agent deployed, what does this stream look like?
|
||||
|
||||
All events emitted have two attributes:
|
||||
|
||||
- `event`: This is the name of the event
|
||||
- `data`: This is data associated with the event
|
||||
|
||||
Let's run it on a question that should trigger a tool call:
|
||||
|
||||
```python
|
||||
thread = await client.threads.create()
|
||||
input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]}
|
||||
|
||||
events = []
|
||||
async for event in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistant_id="agent", # This may need to change depending on the graph you deployed
|
||||
input=input,
|
||||
stream_mode="messages",
|
||||
):
|
||||
print(event.event)
|
||||
```
|
||||
```shell
|
||||
metadata
|
||||
messages/complete
|
||||
messages/metadata
|
||||
messages/partial
|
||||
...
|
||||
messages/partial
|
||||
messages/complete
|
||||
messages/complete
|
||||
messages/metadata
|
||||
messages/partial
|
||||
...
|
||||
messages/partial
|
||||
messages/complete
|
||||
end
|
||||
```
|
||||
|
||||
We first get some `metadata` - this is metadata about the run.
|
||||
|
||||
```python
|
||||
StreamPart(event='metadata', data={'run_id': '1ef657cf-ae55-6f65-97d4-f4ed1dbdabc6'})
|
||||
```
|
||||
|
||||
We then get a `messages/complete` event - this a fully formed message getting emitted. In this case,
|
||||
this was the just the input message we sent in.
|
||||
|
||||
```python
|
||||
StreamPart(event='messages/complete', data=[{'content': 'hi!', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '833c09a3-bb19-46c9-81d9-1e5954ec5f92', 'example': False}])
|
||||
```
|
||||
|
||||
We then get a `messages/metadata` - this is just letting us know that a new message is starting.
|
||||
|
||||
```python
|
||||
StreamPart(event='messages/metadata', data={'run-985c0f14-9f43-40d4-a505-4637fc58e333': {'metadata': {'created_by': 'system', 'run_id': '1ef657de-7594-66df-8eb2-31518e4a1ee2', 'graph_id': 'agent', 'thread_id': 'c178eab5-e293-423c-8e7d-1d113ffe7cd9', 'model_name': 'openai', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.0}}})
|
||||
```
|
||||
|
||||
We then get a BUNCH of `messages/partial` events - these are the individual tokens from the LLM! In the case below, we can see the START of a tool call.
|
||||
|
||||
```python
|
||||
StreamPart(event='messages/partial', data=[{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs', 'function': {'arguments': '', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-985c0f14-9f43-40d4-a505-4637fc58e333', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [{'name': 'tavily_search_results_json', 'args': '', 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs', 'error': None}], 'usage_metadata': None}])
|
||||
```
|
||||
|
||||
After that, we get a `messages/complete` event - this is the AIMessage finishing. It's now a complete tool call:
|
||||
|
||||
```python
|
||||
StreamPart(event='messages/complete', data=[{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs', 'function': {'arguments': '{"query":"current weather in San Francisco"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-985c0f14-9f43-40d4-a505-4637fc58e333', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'current weather in San Francisco'}, 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs'}], 'invalid_tool_calls': [], 'usage_metadata': None}])
|
||||
```
|
||||
|
||||
After that, we get ANOTHER `messages/complete` event. This is a tool message - our agent has called a tool, gotten a response, and now inserting it into the state in the form of a tool message.
|
||||
|
||||
```python
|
||||
StreamPart(event='messages/complete', data=[{'content': '[{"url": "https://www.weatherapi.com/", "content": "{\'location\': {\'name\': \'San Francisco\', \'region\': \'California\', \'country\': \'United States of America\', \'lat\': 37.78, \'lon\': -122.42, \'tz_id\': \'America/Los_Angeles\', \'localtime_epoch\': 1724877689, \'localtime\': \'2024-08-28 13:41\'}, \'current\': {\'last_updated_epoch\': 1724877000, \'last_updated\': \'2024-08-28 13:30\', \'temp_c\': 23.3, \'temp_f\': 73.9, \'is_day\': 1, \'condition\': {\'text\': \'Partly cloudy\', \'icon\': \'//cdn.weatherapi.com/weather/64x64/day/116.png\', \'code\': 1003}, \'wind_mph\': 15.0, \'wind_kph\': 24.1, \'wind_degree\': 310, \'wind_dir\': \'NW\', \'pressure_mb\': 1014.0, \'pressure_in\': 29.93, \'precip_mm\': 0.0, \'precip_in\': 0.0, \'humidity\': 57, \'cloud\': 25, \'feelslike_c\': 25.0, \'feelslike_f\': 77.1, \'windchill_c\': 20.9, \'windchill_f\': 69.6, \'heatindex_c\': 23.3, \'heatindex_f\': 74.0, \'dewpoint_c\': 12.9, \'dewpoint_f\': 55.2, \'vis_km\': 16.0, \'vis_miles\': 9.0, \'uv\': 6.0, \'gust_mph\': 19.5, \'gust_kph\': 31.3}}"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': '0112eba5-7660-4375-9f24-c7a1d6777b97', 'tool_call_id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs'}])
|
||||
```
|
||||
|
||||
After that, we see the agent doing another LLM call and streaming back a response. We then get an `end` event:
|
||||
|
||||
```python
|
||||
StreamPart(event='end', data=None)
|
||||
```
|
||||
|
||||
And that's it! This is more focused streaming mode specifically focused on streaming back messages. See this [how-to guide](../how-tos/stream_messages.md) for more information.
|
||||
|
||||
|
||||
### Human-in-the-Loop
|
||||
|
||||
There are many occasions where the graph cannot run completely autonomously. For instance, the user might need to input some additional arguments to a function call, or select the next edge for the graph to continue on. In these instances, we need to insert some human in the loop interaction, which you can learn about in the [human in the loop how-tos](../how-tos/index.md#human-in-the-loop).
|
||||
|
||||
### Double Texting
|
||||
|
||||
Many times users might interact with your graph in unintended ways. For instance, a user may send one message and before the graph has finished running send a second message. To solve this issue of "double-texting" (i.e. prompting the graph a second time before the first run has finished), LangGraph has provided four different solutions, all of which are covered in the [Double Texting how-tos](../how-tos/index.md#double-texting). These options are:
|
||||
|
||||
- `reject`: This is the simplest option, this just rejects any follow up runs and does not allow double texting. See the [how-to guide](../how-tos/reject_concurrent.md) for configuring the reject double text option.
|
||||
- `enqueue`: This is a relatively simple option which continues the first run until it completes the whole run, then sends the new input as a separate run. See the [how-to guide](../how-tos/enqueue_concurrent.md) for configuring the enqueue double text option.
|
||||
- `interrupt`: This option interrupts the current execution but saves all the work done up until that point. It then inserts the user input and continues from there. If you enable this option, your graph should be able to handle weird edge cases that may arise. See the [how-to guide](../how-tos/interrupt_concurrent.md) for configuring the interrupt double text option.
|
||||
- `rollback`: This option rolls back all work done up until that point. It then sends the user input in, basically as if it just followed the original run input. See the [how-to guide](../how-tos/rollback_concurrent.md) for configuring the rollback double text option.
|
||||
|
||||
### Stateless Runs
|
||||
|
||||
All runs use the built-in checkpointer to store checkpoints for runs. However, it can often be useful to just kick off a run without worrying about explicitly creating a thread and without wanting to keep those checkpointers around. Stateless runs allow you to do this by exposing an endpoint that:
|
||||
|
||||
- Takes in user input
|
||||
- Under the hood, creates a thread
|
||||
- Runs the agent but skips all checkpointing steps
|
||||
- Cleans up the thread afterwards
|
||||
|
||||
Stateless runs are still retried as regular retries are per node, while everything still in memory, so doesn't use checkpoints.
|
||||
|
||||
The only difference is in stateless background runs, if the task worker dies halfway (not because the run itself failed, for some external reason) then the whole run will be retried like any background run, but
|
||||
|
||||
- whereas a stateful background run would retry from the last successful checkpoint
|
||||
- a stateless background run would retry from the beginning
|
||||
|
||||
See the [how-to guide](../how-tos/cloud_examples/stateless_runs.ipynb) for creating stateless runs.
|
||||
|
||||
### Webhooks
|
||||
|
||||
For all types of runs, langgraph cloud supports completion webhooks. When you create the run you can pass a webhook URL to be called when the completes (successfully or not). This is especially useful for background runs and cron jobs, as the webhook can give you an indication the run has completed and you can perform further actions for your appilcation.
|
||||
|
||||
See this [how-to guide](../how-tos/cloud_examples/webhooks.ipynb) to learn about how to use webhooks with LangGraph Cloud.
|
||||
|
||||
## Deployment
|
||||
|
||||
The LangGraph Cloud offers several features to support secure and robost deployments.
|
||||
|
||||
### Authentication
|
||||
|
||||
LangGraph applications deployed to LangGraph Cloud are automatically configured with LangSmith authentication. In order to call the API, a valid <a href="https://docs.smith.langchain.com/how_to_guides/setup/create_account_api_key#api-keys" target="_blank">LangSmith API key</a> is required.
|
||||
|
||||
### Local Testing
|
||||
|
||||
Before deploying your app in production to LangGraph Cloud, you may wish to test out your graph locally in order to ensure that everything is running as expected. Luckily, LangGraph makes this easy for you through use of the LangGraph CLI. Read more in this [how-to guide](../deployment/test_locally.md) or look at the [CLI reference](../reference/cli.md) to learn more.
|
||||
@@ -0,0 +1,28 @@
|
||||
# Cloud Concepts
|
||||
|
||||
This page describes the high-level concepts of the LangGraph Cloud deployment.
|
||||
|
||||
## Deployment
|
||||
|
||||
A deployment is an instance of a LangGraph API. A single deployment can have many [revisions](#revision). When a deployment is created, all of the necessary infrastructure (e.g. database, containers, secrets store) are automatically provisioned. See the [architecture diagram](#architecture) below for more details.
|
||||
|
||||
See the [how-to guide](../deployment/cloud.md#create-new-deployment) for creating a new deployment.
|
||||
|
||||
## Revision
|
||||
|
||||
A revision is an iteration of a [deployment](#deployment). When a new deployment is created, an initial revision is automatically created. To deploy new code changes or update environment variable configurations for a deployment, a new revision must be created. When a revision is created, a new container image is built automatically.
|
||||
|
||||
See the [how-to guide](../deployment/cloud.md#create-new-revision) for creating a new revision.
|
||||
|
||||
## Asynchronous Deployment
|
||||
|
||||
Infrastructure for [deployments](#deployment) and [revisions](#revision) are provisioned and deployed asynchronously. They are not deployed immediately after submission. Currently, deployment can take up to several minutes.
|
||||
|
||||
## Architecture
|
||||
|
||||
!!! warning "Subject to Change"
|
||||
The LangGraph Cloud deployment architecture may change in the future.
|
||||
|
||||
A high-level diagram of a LangGraph Cloud deployment.
|
||||
|
||||

|
||||
@@ -1,103 +0,0 @@
|
||||
# API Concepts
|
||||
|
||||
This page describes the high-level concepts of the LangGraph Cloud API. The conceptual guide of LangGraph (Python library) is [here](../../concepts/index.md).
|
||||
|
||||
## Data Models
|
||||
|
||||
The LangGraph Cloud API consists of a few core data models: [Assistants](#assistants), [Threads](#threads), [Runs](#runs), and [Cron Jobs](#cron-jobs).
|
||||
|
||||
### Assistants
|
||||
|
||||
An assistant is a configured instance of a [`CompiledGraph`][compiledgraph]. It abstracts the cognitive architecture of the graph and contains instance specific configuration and metadata. Multiple assistants can reference the same graph but can contain different configuration and metadata, which may differentiate the behavior of the assistants. An assistant (i.e. the graph) is invoked as part of a run.
|
||||
|
||||
The LangGraph Cloud API provides several endpoints for creating and managing assistants. See the <a href="../reference/api/api_ref.html#tag/assistantscreate" target="_blank">API reference</a> for more details.
|
||||
|
||||
### Threads
|
||||
|
||||
A thread contains the accumulated state of a group of runs. If a run is executed on a thread, then the [state][state] of the underlying graph of the assistant will be persisted to the thread. A thread's current and historical state can be retrieved. To persist state, a thread must be created prior to executing a run.
|
||||
|
||||
The state of a thread at a particular point in time is called a checkpoint.
|
||||
|
||||
For more on threads and checkpoints, see this section of the [LangGraph conceptual guide](https://langchain-ai.github.io/langgraph/concepts/low_level/#checkpointer).
|
||||
|
||||
The LangGraph Cloud API provides several endpoints for creating and managing threads and thread state. See the <a href="../reference/api/api_ref.html#tag/threadscreate" target="_blank">API reference</a> for more details.
|
||||
|
||||
### Runs
|
||||
|
||||
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.
|
||||
|
||||
The LangGraph Cloud API provides several endpoints for creating and managing runs. See the <a href="../reference/api/api_ref.html#tag/runscreate" target="_blank">API reference</a> for more details.
|
||||
|
||||
### Cron Jobs
|
||||
|
||||
It's often useful to run graphs on some schedule. LangGraph Cloud supports cron jobs, which run on a user defined schedule. The user specifies a schedule, an assistant, and some input. After than, on the specified schedule LangGraph cloud will:
|
||||
|
||||
- Create a new thread with the specified assistant
|
||||
- Send the specified input to that thread
|
||||
|
||||
Note that this sends the same input to the thread every time. See the [how-to guide](../how-tos/cloud_examples/cron_jobs.ipynb) for creating cron jobs.
|
||||
|
||||
The LangGraph Cloud API provides several endpoints for creating and managing cron jobs. See the <a href="../reference/api/api_ref.html#tag/runscreate/POST/threads/{thread_id}/runs/crons" target="_blank">API reference</a> for more details.
|
||||
|
||||
## Features
|
||||
|
||||
The LangGraph Cloud API offers several features to support complex agent architectures.
|
||||
|
||||
### Streaming
|
||||
|
||||
Streaming is critical for making LLM applications feel responsive to end users. When creating a streaming run, the streaming mode determines what data is streamed back to the API client. The LangGraph Cloud API supports five streaming modes.
|
||||
|
||||
- `values`: Stream the full state of the graph after each node is executed. See the [how-to guide](../how-tos/cloud_examples/stream_values.ipynb) for streaming values.
|
||||
- `messages`: Stream complete messages (at the end of node execution) as well as tokens for any messages generated inside a node. This mode is primarily meant for powering chat applications. This is only an option if your graph contains a `messages` key. See the [how-to guide](../how-tos/cloud_examples/stream_messages.ipynb) for streaming messages.
|
||||
- `updates`: Streams updates to the state of the graph after each node is executed. See the [how-to guide](../how-tos/cloud_examples/stream_updates.ipynb) for streaming updates.
|
||||
- `events`: Stream all events (including the state of the graph) after each node is executed. See the [how-to guide](../how-tos/cloud_examples/stream_events.ipynb) for streaming events. This can be used to do token-by-token streaming for LLMs.
|
||||
- `debug`: Stream debug events after each node is executed. See the [how-to guide](../how-tos/cloud_examples/stream_debug.ipynb) for streaming debug events.
|
||||
|
||||
You can also specify multiple streaming modes at the same time. See the [how-to guide](../how-tos/cloud_examples/stream_multiple.ipynb) for configuring multiple streaming modes at the same time.
|
||||
|
||||
See the <a href="../reference/api/api_ref.html#tag/runscreate/POST/threads/{thread_id}/runs/stream" target="_blank">API reference</a> for how to create streaming runs.
|
||||
|
||||
### Human-in-the-Loop
|
||||
|
||||
There are many occasions where the graph cannot run completely autonomously. For instance, the user might need to input some additional arguments to a function call, or select the next edge for the graph to continue on. In these instances, we need to insert some human in the loop interaction, which you can learn about in the [human in the loop how-tos](../how-tos/index.md#human-in-the-loop).
|
||||
|
||||
### Double Texting
|
||||
|
||||
Many times users might interact with your graph in unintended ways. For instance, a user may send one message and before the graph has finished running send a second message. To solve this issue of "double-texting" (i.e. prompting the graph a second time before the first run has finished), Langgraph has provided four different solutions, all of which are covered in the [Double Texting how-tos](../how-tos/index.md#double-texting). These options are:
|
||||
|
||||
- `reject`: This is the simplest option, this just rejects any follow up runs and does not allow double texting. See the [how-to guide](../how-tos/cloud_examples/reject_concurrent.ipynb) for configuring the reject double text option.
|
||||
- `enqueue`: This is a relatively simple option which continues the first run until it completes the whole run, then sends the new input as a separate run. See the [how-to guide](../how-tos/cloud_examples/enqueue_concurrent.ipynb) for configuring the enqueue double text option.
|
||||
- `interrupt`: This option interrupts the current execution but saves all the work done up until that point. It then inserts the user input and continues from there. If you enable this option, your graph should be able to handle weird edge cases that may arise. See the [how-to guide](../how-tos/cloud_examples/interrupt_concurrent.ipynb) for configuring the interrupt double text option.
|
||||
- `rollback`: This option rolls back all work done up until that point. It then sends the user input in, basically as if it just followed the original run input. See the [how-to guide](../how-tos/cloud_examples/rollback_concurrent.ipynb) for configuring the rollback double text option.
|
||||
|
||||
### Stateless Runs
|
||||
|
||||
All runs use the built-in checkpointer to store checkpoints for runs. However, it can often be useful to just kick off a run without worrying about explicitly creating a thread and without wanting to keep those checkpointers around. Stateless runs allow you to do this by exposing an endpoint that:
|
||||
|
||||
- Takes in user input
|
||||
- Under the hood, creates a thread
|
||||
- Runs the agent but skips all checkpointing steps
|
||||
- Cleans up the thread afterwards
|
||||
|
||||
Stateless runs are still retried as regular retries are per node, while everything still in memory, so doesn't use checkpoints.
|
||||
|
||||
The only difference is in stateless background runs, if the task worker dies halfway (not because the run itself failed, for some external reason) then the whole run will be retried like any background run, but
|
||||
|
||||
- whereas a stateful background run would retry from the last successful checkpoint
|
||||
- a stateless background run would retry from the beginning
|
||||
|
||||
See the [how-to guide](../how-tos/cloud_examples/stateless_runs.ipynb) for creating stateless runs.
|
||||
|
||||
### Webhooks
|
||||
|
||||
For all types of runs, langgraph cloud supports completion webhooks. When you create the run you can pass a webhook URL to be called when the completes (successfully or not). This is especially useful for background runs and cron jobs, as the webhook can give you an indication the run has completed and you can perform further actions for your appilcation.
|
||||
|
||||
See this [how-to guide](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/webhooks/) to learn about how to use webhooks with LangGraph Cloud.
|
||||
|
||||
## Deployment
|
||||
|
||||
The LangGraph Cloud offers several features to support secure and robost deployments.
|
||||
|
||||
### Authentication
|
||||
|
||||
LangGraph applications deployed to LangGraph Cloud are automatically configured with LangSmith authentication. In order to call the API, a valid <a href="https://docs.smith.langchain.com/how_to_guides/setup/create_account_api_key#api-keys" target="_blank">LangSmith API key</a> is required.
|
||||
|
After Width: | Height: | Size: 157 KiB |
@@ -2,9 +2,10 @@
|
||||
|
||||
LangGraph Cloud is available within <a href="https://www.langchain.com/langsmith" target="_blank">LangSmith</a>. To deploy a LangGraph Cloud API, navigate to the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>.
|
||||
|
||||
## Setup GitHub Repository
|
||||
## Prerequisites
|
||||
|
||||
LangGraph Cloud applications are deployed from GitHub repositories. Configure and upload a LangGraph Cloud application to a GitHub repository in order to deploy it to LangGraph Cloud.
|
||||
1. LangGraph Cloud applications are deployed from GitHub repositories. Configure and upload a LangGraph Cloud application to a GitHub repository in order to deploy it to LangGraph Cloud.
|
||||
1. [Verify that the LangGraph API runs locally](test_locally.md). If the API does not build and run successfully (i.e. `langgraph up`), deploying to LangGraph Cloud will fail as well.
|
||||
|
||||
## Create New Deployment
|
||||
|
||||
@@ -45,8 +46,33 @@ Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmi
|
||||
1. Update the value of existing secrets or environment variables.
|
||||
1. Select `Submit`. After a few seconds, the `New Revision` modal will close and the new revision will be queued for deployment.
|
||||
|
||||
## Asynchronous Deployment
|
||||
## View Build and Deployment Logs
|
||||
|
||||
New [deployments](#create-new-deployment) and [revisions](#create-new-revision) are provisioned and deployed asynchronously. They are not deployed immediately after submission. Currently, deployment can take up to several minutes.
|
||||
Build and deployment logs are available for each revision.
|
||||
|
||||
The `Deployment` view continually updates the status of pending revisions.
|
||||
Starting from the `Deployment` view...
|
||||
|
||||
1. Select the desired revision from the `Revisions` table. A panel slides open from the right-hand side and the `Build` tab is selected by default, which displays build logs for the revision.
|
||||
1. In the panel, select the `Deploy` tab to view deployment logs for the revision.
|
||||
1. Within the `Deploy` tab, adjust the date/time range picker as needed. By default, the date/time range picker is set to the `Last 15 minutes`.
|
||||
|
||||
## Interrupt Revision
|
||||
|
||||
Interrupting a revision will stop deployment of the revision.
|
||||
|
||||
!!! warning "Undefined Behavior"
|
||||
Interrupted revisions have undefined behavior. This is only useful if you need to deploy a new revision and you already have a revision "stuck" in progress. In the future, this feature may be removed.
|
||||
|
||||
Starting from the `Deployment` view...
|
||||
|
||||
1. Select the menu icon (three dots) on the right-hand side of the row for the desired revision from the `Revisions` table.
|
||||
1. Select `Interrupt` from the menu.
|
||||
1. A modal will appear. Review the confirmation message. Select `Interrupt revision`.
|
||||
|
||||
## Delete Deployment
|
||||
|
||||
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
|
||||
|
||||
1. In the left-hand navigation panel, select `Deployments`. The `Deployments` view contains a list of existing LangGraph Cloud deployments.
|
||||
1. Select the menu icon (three dots) on the right-hand side of the row for the desired deployment and select `Delete`.
|
||||
1. A `Confirmation` modal will appear. Select `Delete`.
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
# Rebuild Graph at Runtime
|
||||
|
||||
You might need to rebuild your graph with a different configuration for a new run. For example, you might need to use a different graph state or graph structure depending on the config. This guide shows how you can do this.
|
||||
|
||||
!!! note "Note"
|
||||
In most cases, customizing behavior based on the config should be handled by a single graph where each node can read a config and change its behavior based on it
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Make sure to check out [this how-to guide](./setup.md) on setting up your app for deployment first.
|
||||
|
||||
## Define graphs
|
||||
|
||||
Let's say you have an app with a simple graph that calls an LLM and returns the response to the user. The app file directory looks like the following:
|
||||
|
||||
```
|
||||
my-app/
|
||||
|-- requirements.txt
|
||||
|-- .env
|
||||
|-- openai_agent.py # code for your graph
|
||||
```
|
||||
|
||||
where the graph is defined in `openai_agent.py`.
|
||||
|
||||
### No rebuild
|
||||
|
||||
In the standard LangGraph API configuration, the server uses the compiled graph instance that's defined at the top level of `openai_agent.py`, which looks like the following:
|
||||
|
||||
```python
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import END, START, MessageGraph
|
||||
|
||||
model = ChatOpenAI(temperature=0)
|
||||
|
||||
graph_workflow = MessageGraph()
|
||||
|
||||
graph_workflow.add_node("agent", model)
|
||||
graph_workflow.add_edge("agent", END)
|
||||
graph_workflow.add_edge(START, "agent")
|
||||
|
||||
agent = graph_workflow.compile()
|
||||
```
|
||||
|
||||
To make the server aware of your graph, you need to specify a path to the variable that contains the `CompiledStateGraph` instance in your LangGraph API configuration (`langgraph.json`), e.g.:
|
||||
|
||||
```
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"openai_agent": "./openai_agent.py:agent",
|
||||
},
|
||||
"env": "./.env"
|
||||
}
|
||||
```
|
||||
|
||||
### Rebuild
|
||||
|
||||
To make your graph rebuild on each new run with custom configuration, you need to rewrite `openai_agent.py` to instead provide a _function_ that takes a config and returns a graph (or compiled graph) instance. Let's say we want to return our existing graph for user ID '1', and a tool-calling agent for other users. We can modify `openai_agent.py` as follows:
|
||||
|
||||
```python
|
||||
from typing import Annotated, TypedDict
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import END, START, MessageGraph
|
||||
from langgraph.graph.state import StateGraph
|
||||
from langgraph.graph.message import add_messages
|
||||
from langgraph.prebuilt import ToolNode
|
||||
from langchain_core.tools import tool
|
||||
from langchain_core.messages import BaseMessage
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
|
||||
|
||||
class State(TypedDict):
|
||||
messages: Annotated[list[BaseMessage], add_messages]
|
||||
|
||||
|
||||
model = ChatOpenAI(temperature=0)
|
||||
|
||||
def make_default_graph():
|
||||
"""Make a simple LLM agent"""
|
||||
graph_workflow = StateGraph(State)
|
||||
def call_model(state):
|
||||
return {"messages": [model.invoke(state["messages"])]}
|
||||
|
||||
graph_workflow.add_node("agent", call_model)
|
||||
graph_workflow.add_edge("agent", END)
|
||||
graph_workflow.add_edge(START, "agent")
|
||||
|
||||
agent = graph_workflow.compile()
|
||||
return agent
|
||||
|
||||
|
||||
def make_alternative_graph():
|
||||
"""Make a tool-calling agent"""
|
||||
|
||||
@tool
|
||||
def add(a: float, b: float):
|
||||
"""Adds two numbers."""
|
||||
return a + b
|
||||
|
||||
tool_node = ToolNode([add])
|
||||
model_with_tools = model.bind_tools([add])
|
||||
def call_model(state):
|
||||
return {"messages": [model_with_tools.invoke(state["messages"])]}
|
||||
|
||||
def should_continue(state: State):
|
||||
if state["messages"][-1].tool_calls:
|
||||
return "tools"
|
||||
else:
|
||||
return END
|
||||
|
||||
graph_workflow = StateGraph(State)
|
||||
|
||||
graph_workflow.add_node("agent", call_model)
|
||||
graph_workflow.add_node("tools", tool_node)
|
||||
graph_workflow.add_edge("tools", "agent")
|
||||
graph_workflow.add_edge(START, "agent")
|
||||
graph_workflow.add_conditional_edges("agent", should_continue)
|
||||
|
||||
agent = graph_workflow.compile()
|
||||
return agent
|
||||
|
||||
|
||||
# this is the graph making function that will decide which graph to
|
||||
# build based on the provided config
|
||||
def make_graph(config: RunnableConfig):
|
||||
user_id = config.get("configurable", {}).get("user_id")
|
||||
# route to different graph state / structure based on the user ID
|
||||
if user_id == "1":
|
||||
return make_default_graph()
|
||||
else:
|
||||
return make_alternative_graph()
|
||||
```
|
||||
|
||||
Finally, you need to specify the path to your graph-making function (`make_graph`) in `langgraph.json`:
|
||||
|
||||
```
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"openai_agent": "./openai_agent.py:make_graph",
|
||||
},
|
||||
"env": "./.env"
|
||||
}
|
||||
```
|
||||
|
||||
See more info on LangGraph API configuration file [here](../reference/cli.md#configuration-file)
|
||||
|
Before Width: | Height: | Size: 56 KiB |
@@ -5,6 +5,8 @@
|
||||
|
||||
LangGraph Cloud APIs can be self-hosted with a valid LangGraph Cloud license key. Self-hosted deployments are built with Docker and deployed with Helm (on Kubernetes) or with Docker Compose. Ensure that the [Docker CLI](https://docs.docker.com/engine/reference/commandline/cli/) is installed.
|
||||
|
||||
LangGraph Cloud license key should be passed to the service as an environment variable named LANGGRAPH_CLOUD_LICENSE_KEY.
|
||||
|
||||
## Build Docker Image
|
||||
|
||||
1. Follow the [How-to Guide](setup.md) for setting up a LangGraph application for deployment. Your LangGraph application will vary from the example in the How-to Guide. However, ensure that the [LangGraph API configuration file](../reference/cli.md#configuration-file) is created.
|
||||
|
||||
@@ -1,16 +1,30 @@
|
||||
# How to Set Up a LangGraph Application for Deployment
|
||||
|
||||
A LangGraph application must be configured with a [LangGraph API configuration file](../reference/cli.md#configuration-file) in order to be deployed to LangGraph Cloud (or to be self-hosted). This how-to guide discusses the basic steps to setup a LangGraph application for deployment using `requirements.txt` to specify project dependencies. If you prefer using poetry for dependency management, check out [this how-to guide](./setup_pyproject.md) on using `pyproject.toml` for LangGraph Cloud.
|
||||
A LangGraph application must be configured with a [LangGraph API configuration file](../reference/cli.md#configuration-file) in order to be deployed to LangGraph Cloud (or to be self-hosted). This how-to guide discusses the basic steps to setup a LangGraph application for deployment using `requirements.txt` to specify project dependencies.
|
||||
|
||||
This walkthrough is based on [this repository](https://github.com/langchain-ai/langgraph-example), which you can play around with to learn more about how to setup your LangGraph application for deployment.
|
||||
|
||||
!!! tip "Setup with pyproject.toml"
|
||||
If you prefer using poetry for dependency management, check out [this how-to guide](./setup_pyproject.md) on using `pyproject.toml` for LangGraph Cloud.
|
||||
|
||||
!!! tip "Setup with a Monorepo"
|
||||
If you are interested in deploying a graph located inside a monorepo, take a look at [this](https://github.com/langchain-ai/langgraph-example-monorepo) repository for an example of how to do so.
|
||||
|
||||
The final repo structure will look something like this:
|
||||
|
||||
```bash
|
||||
my-app/
|
||||
|-- requirements.txt # package dependencies
|
||||
|-- .env # environment variables
|
||||
|-- openai_agent.py # code for an agent
|
||||
|-- anthropic_agent.py # code for another agent
|
||||
|-- langgraph.json # configuration file for LangGraph
|
||||
├── my_agent # all project code lies within here
|
||||
│ ├── utils # utilities for your graph
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── tools.py # tools for your graph
|
||||
│ │ ├── nodes.py # node functions for you graph
|
||||
│ │ └── state.py # state definition of your graph
|
||||
│ ├── requirements.txt # package dependencies
|
||||
│ ├── __init__.py
|
||||
│ └── agent.py # code for constructing your graph
|
||||
├── .env # environment variables
|
||||
└── langgraph.json # configuration file for LangGraph
|
||||
```
|
||||
|
||||
After each step, an example file directory is provided to demonstrate how code can be organized.
|
||||
@@ -19,16 +33,43 @@ After each step, an example file directory is provided to demonstrate how code c
|
||||
|
||||
Dependencies can optionally be specified in one of the following files: `pyproject.toml`, `setup.py`, or `requirements.txt`. If none of these files is created, then dependencies can be specified later in the [LangGraph API configuration file](#create-langgraph-api-config).
|
||||
|
||||
The dependencies below will be included in the image, you can also use them in your code, as long as with a compatible version range:
|
||||
|
||||
```
|
||||
langgraph>=0.2.7,<0.3.0
|
||||
langgraph-checkpoint>=1.0.4
|
||||
langchain-core>=0.2.27,<0.3.0
|
||||
langsmith>=0.1.63
|
||||
orjson>=3.9.7
|
||||
httpx>=0.25.0
|
||||
tenacity>=8.0.0
|
||||
uvicorn>=0.26.0
|
||||
sse-starlette>=2.1.0
|
||||
uvloop>=0.18.0
|
||||
httptools>=0.5.0
|
||||
jsonschema-rs>=0.16.3
|
||||
croniter>=1.0.1
|
||||
structlog>=23.1.0
|
||||
redis>=5.0.0,<6.0.0
|
||||
```
|
||||
|
||||
Example `requirements.txt` file:
|
||||
|
||||
```
|
||||
langgraph
|
||||
langchain_anthropic
|
||||
tavily-python
|
||||
langchain_community
|
||||
langchain_openai
|
||||
|
||||
```
|
||||
|
||||
Example file directory:
|
||||
```
|
||||
|
||||
```bash
|
||||
my-app/
|
||||
|-- requirements.txt # Python packages required for your graph
|
||||
├── my_agent # all project code lies within here
|
||||
│ └── requirements.txt # package dependencies
|
||||
```
|
||||
|
||||
## Specify Environment Variables
|
||||
@@ -36,6 +77,7 @@ my-app/
|
||||
Environment variables can optionally be specified in a file (e.g. `.env`). See the [Environment Variables reference](../reference/env_var.md) to configure additional variables for a deployment.
|
||||
|
||||
Example `.env` file:
|
||||
|
||||
```
|
||||
MY_ENV_VAR_1=foo
|
||||
MY_ENV_VAR_2=bar
|
||||
@@ -43,42 +85,66 @@ OPENAI_API_KEY=key
|
||||
```
|
||||
|
||||
Example file directory:
|
||||
```
|
||||
|
||||
```bash
|
||||
my-app/
|
||||
|-- requirements.txt
|
||||
|-- .env # file with environment variables
|
||||
├── my_agent # all project code lies within here
|
||||
│ └── requirements.txt # package dependencies
|
||||
└── .env # environment variables
|
||||
```
|
||||
|
||||
## Define Graphs
|
||||
|
||||
Implement your graphs! Graphs can be defined in a single file or multiple files. Make note of the variable names of each [CompiledGraph][compiledgraph] to be included in the LangGraph application. The variable names will be used later when creating the [LangGraph API configuration file](../reference/cli.md#configuration-file).
|
||||
|
||||
Example `openai_agent.py` 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 repo](https://github.com/langchain-ai/langgraph-example) to see their implementation):
|
||||
|
||||
```python
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import END, MessageGraph
|
||||
# my_agent/agent.py
|
||||
from typing import TypedDict, Literal
|
||||
|
||||
model = ChatOpenAI(temperature=0)
|
||||
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
|
||||
|
||||
graph_workflow = MessageGraph()
|
||||
# Define the config
|
||||
class GraphConfig(TypedDict):
|
||||
model_name: Literal["anthropic", "openai"]
|
||||
|
||||
graph_workflow.add_node("agent", model)
|
||||
graph_workflow.add_edge("agent", END)
|
||||
graph_workflow.set_entry_point("agent")
|
||||
workflow = StateGraph(AgentState, config_schema=GraphConfig)
|
||||
workflow.add_node("agent", call_model)
|
||||
workflow.add_node("action", tool_node)
|
||||
workflow.add_edge(START, "agent")
|
||||
workflow.add_conditional_edges(
|
||||
"agent",
|
||||
should_continue,
|
||||
{
|
||||
"continue": "action",
|
||||
"end": END,
|
||||
},
|
||||
)
|
||||
workflow.add_edge("action", "agent")
|
||||
|
||||
agent = graph_workflow.compile()
|
||||
graph = workflow.compile()
|
||||
```
|
||||
|
||||
!!! warning "Assign `CompiledGraph` to Variable"
|
||||
The build process for LangGraph Cloud requires that the `CompiledGraph` object be assigned to a variable at the top-level of a Python module.
|
||||
The build process for LangGraph Cloud requires that the `CompiledGraph` object be assigned to a variable at the top-level of a Python module (alternatively, you can provide [a function that creates a graph](./graph_rebuild.md)).
|
||||
|
||||
Example file directory:
|
||||
```
|
||||
|
||||
```bash
|
||||
my-app/
|
||||
|-- requirements.txt
|
||||
|-- .env
|
||||
|-- openai_agent.py # code for your graph
|
||||
|-- anthropic_agent.py # code for your graph
|
||||
├── my_agent # all project code lies within here
|
||||
│ ├── utils # utilities for your graph
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── tools.py # tools for your graph
|
||||
│ │ ├── nodes.py # node functions for you graph
|
||||
│ │ └── state.py # state definition of your graph
|
||||
│ ├── requirements.txt # package dependencies
|
||||
│ ├── __init__.py
|
||||
│ └── agent.py # code for constructing your graph
|
||||
└── .env # environment variables
|
||||
```
|
||||
|
||||
## Create LangGraph API Config
|
||||
@@ -86,36 +152,39 @@ my-app/
|
||||
Create a [LangGraph API configuration file](../reference/cli.md#configuration-file) called `langgraph.json`. See the [LangGraph CLI reference](../reference/cli.md#configuration-file) for detailed explanations of each key in the JSON object of the configuration file.
|
||||
|
||||
Example `langgraph.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": [
|
||||
"."
|
||||
],
|
||||
"graphs": {
|
||||
"openai_agent": "./openai_agent.py:agent",
|
||||
"anthropic_agent": "./anthropic_agent.py:agent"
|
||||
},
|
||||
"env": "./.env"
|
||||
"dependencies": ["./my_agent"],
|
||||
"graphs": {
|
||||
"agent": "./my_agent/agent.py:graph"
|
||||
},
|
||||
"env": ".env"
|
||||
}
|
||||
```
|
||||
|
||||
Note that the variable name of the `CompiledGraph` appears at the end of the value of each subkey in the top-level `graphs` key (i.e. `:<variable_name>`).
|
||||
|
||||
!!! warning "Configuration Location"
|
||||
The LangGraph API configuration file must be placed in a directory that is at the same level or higher than the Python files that contain compiled graphs and associated dependencies.
|
||||
|
||||
Example file directory:
|
||||
|
||||
```bash
|
||||
my-app/
|
||||
|-- requirements.txt
|
||||
|-- .env
|
||||
|-- openai_agent.py
|
||||
|-- anthropic_agent.py
|
||||
|-- langgraph.json # configuration file for LangGraph
|
||||
├── my_agent # all project code lies within here
|
||||
│ ├── utils # utilities for your graph
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── tools.py # tools for your graph
|
||||
│ │ ├── nodes.py # node functions for you graph
|
||||
│ │ └── state.py # state definition of your graph
|
||||
│ ├── requirements.txt # package dependencies
|
||||
│ ├── __init__.py
|
||||
│ └── agent.py # code for constructing your graph
|
||||
├── .env # environment variables
|
||||
└── langgraph.json # configuration file for LangGraph
|
||||
```
|
||||
|
||||
## Upload to GitHub
|
||||
|
||||
To deploy the LangGraph application to LangGraph Cloud, the code must be uploaded to a GitHub repository.
|
||||
|
||||
## Next
|
||||
|
||||
After you setup your repo, it's time to [deploy your app](./cloud.md).
|
||||
After you setup your project and place it in a github repo, it's time to [deploy your app](./cloud.md).
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
# How to Set Up a LangGraph Application for Deployment
|
||||
|
||||
A LangGraph application must be configured with a [LangGraph API configuration file](../reference/cli.md#configuration-file) in order to be deployed to LangGraph Cloud (or to be self-hosted). This how-to guide discusses the basic steps to setup a LangGraph application for deployment using `pyproject.toml` to define your package's dependencies. If you prefer using `requirements.txt` for dependency management, check out [this how-to guide](./setup.md).
|
||||
A LangGraph application must be configured with a [LangGraph API configuration file](../reference/cli.md#configuration-file) in order to be deployed to LangGraph Cloud (or to be self-hosted). This how-to guide discusses the basic steps to setup a LangGraph application for deployment using `pyproject.toml` to define your package's dependencies.
|
||||
|
||||
This walkthrough is based on [this repository](https://github.com/langchain-ai/langgraph-example), which you can play around with to learn more about how to setup your LangGraph application for deployment.
|
||||
|
||||
!!! tip "Setup with requirements.txt"
|
||||
If you prefer using `requirements.txt` for dependency management, check out [this how-to guide](./setup.md).
|
||||
|
||||
!!! tip "Setup with a Monorepo"
|
||||
If you are interested in deploying a graph located inside a monorepo, take a look at [this](https://github.com/langchain-ai/langgraph-example-monorepo) repository for an example of how to do so.
|
||||
|
||||
The final repo structure will look something like this:
|
||||
|
||||
```bash
|
||||
my-app/
|
||||
├── my_agent # all project code lies within here
|
||||
│ ├── utils # utilities for your graph
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── tools.py # tools for your graph
|
||||
│ │ ├── nodes.py # node functions for you graph
|
||||
│ │ └── state.py # state definition of your graph
|
||||
│ ├── __init__.py
|
||||
│ └── agent.py # code for your graph
|
||||
│-- .env # environment variables
|
||||
│-- langgraph.json # configuration file for LangGraph
|
||||
│ └── agent.py # code for constructing your graph
|
||||
├── .env # environment variables
|
||||
├── langgraph.json # configuration file for LangGraph
|
||||
└── pyproject.toml # dependencies for your project
|
||||
```
|
||||
|
||||
@@ -20,6 +33,25 @@ After each step, an example file directory is provided to demonstrate how code c
|
||||
|
||||
Dependencies can optionally be specified in one of the following files: `pyproject.toml`, `setup.py`, or `requirements.txt`. If none of these files is created, then dependencies can be specified later in the [LangGraph API configuration file](#create-langgraph-api-config).
|
||||
|
||||
The dependencies below will be included in the image, you can also use them in your code, as long as with a compatible version range:
|
||||
```
|
||||
langgraph>=0.2.7,<0.3.0
|
||||
langgraph-checkpoint>=1.0.4
|
||||
langchain-core>=0.2.27,<0.3.0
|
||||
langsmith>=0.1.63
|
||||
orjson>=3.9.7
|
||||
httpx>=0.25.0
|
||||
tenacity>=8.0.0
|
||||
uvicorn>=0.26.0
|
||||
sse-starlette>=2.1.0
|
||||
uvloop>=0.18.0
|
||||
httptools>=0.5.0
|
||||
jsonschema-rs>=0.16.3
|
||||
croniter>=1.0.1
|
||||
structlog>=24.4.0
|
||||
redis>=5.0.8,<6.0.0
|
||||
```
|
||||
|
||||
Example `pyproject.toml` file:
|
||||
|
||||
```toml
|
||||
@@ -33,7 +65,7 @@ readme = "README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.9.0,<3.13"
|
||||
langgraph = "^0.1.0"
|
||||
langgraph = "^0.2.0"
|
||||
langchain-fireworks = "^0.1.3"
|
||||
|
||||
|
||||
@@ -46,9 +78,6 @@ Example file directory:
|
||||
|
||||
```bash
|
||||
my-app/
|
||||
├── my_agent
|
||||
│ ├── __init__.py
|
||||
│ └── agent.py
|
||||
└── pyproject.toml # Python packages required for your graph
|
||||
```
|
||||
|
||||
@@ -68,10 +97,7 @@ Example file directory:
|
||||
|
||||
```bash
|
||||
my-app/
|
||||
├── my_agent
|
||||
│ ├── __init__.py
|
||||
│ └── agent.py
|
||||
|-- .env # file with environment variables
|
||||
├── .env # file with environment variables
|
||||
└── pyproject.toml
|
||||
```
|
||||
|
||||
@@ -79,39 +105,53 @@ my-app/
|
||||
|
||||
Implement your graphs! Graphs can be defined in a single file or multiple files. Make note of the variable names of each [CompiledGraph][compiledgraph] to be included in the LangGraph application. The variable names will be used later when creating the [LangGraph API configuration file](../reference/cli.md#configuration-file).
|
||||
|
||||
Example `agent.py` 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 repo](https://github.com/langchain-ai/langgraph-example-pyproject) to see their implementation):
|
||||
|
||||
```python
|
||||
# my_agent/agent.py
|
||||
from langchain_fireworks import ChatFireworks
|
||||
from langgraph.graph import END, StateGraph, add_messages
|
||||
from typing_extensions import TypedDict, Annotated
|
||||
from typing import TypedDict, Literal
|
||||
|
||||
model = ChatFireworks(model="accounts/fireworks/models/firefunction-v2", temperature=0)
|
||||
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
|
||||
|
||||
class State(TypedDict):
|
||||
messages: Annotated[list, add_messages]
|
||||
# Define the config
|
||||
class GraphConfig(TypedDict):
|
||||
model_name: Literal["anthropic", "openai"]
|
||||
|
||||
graph_workflow = StateGraph(State)
|
||||
workflow = StateGraph(AgentState, config_schema=GraphConfig)
|
||||
workflow.add_node("agent", call_model)
|
||||
workflow.add_node("action", tool_node)
|
||||
workflow.add_edge(START, "agent")
|
||||
workflow.add_conditional_edges(
|
||||
"agent",
|
||||
should_continue,
|
||||
{
|
||||
"continue": "action",
|
||||
"end": END,
|
||||
},
|
||||
)
|
||||
workflow.add_edge("action", "agent")
|
||||
|
||||
graph_workflow.add_node("agent", model)
|
||||
graph_workflow.add_edge("agent", END)
|
||||
graph_workflow.set_entry_point("agent")
|
||||
|
||||
agent = graph_workflow.compile()
|
||||
graph = workflow.compile()
|
||||
```
|
||||
|
||||
!!! warning "Assign `CompiledGraph` to Variable"
|
||||
The build process for LangGraph Cloud requires that the `CompiledGraph` object be assigned to a variable at the top-level of a Python module.
|
||||
The build process for LangGraph Cloud requires that the `CompiledGraph` object be assigned to a variable at the top-level of a Python module.
|
||||
|
||||
Example file directory:
|
||||
|
||||
```bash
|
||||
my-app/
|
||||
├── my_agent
|
||||
├── my_agent # all project code lies within here
|
||||
│ ├── utils # utilities for your graph
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── tools.py # tools for your graph
|
||||
│ │ ├── nodes.py # node functions for you graph
|
||||
│ │ └── state.py # state definition of your graph
|
||||
│ ├── __init__.py
|
||||
│ └── agent.py # code for your graph
|
||||
|-- .env
|
||||
│ └── agent.py # code for constructing your graph
|
||||
├── .env
|
||||
└── pyproject.toml
|
||||
```
|
||||
|
||||
@@ -125,30 +165,34 @@ Example `langgraph.json` file:
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"my_fantastic_agent": "./my_agent/agent.py:agent"
|
||||
"agent": "./my_agent/agent.py:graph"
|
||||
},
|
||||
"env": "./.env"
|
||||
"env": ".env"
|
||||
}
|
||||
```
|
||||
|
||||
Note that the variable name of the `CompiledGraph` appears at the end of the value of each subkey in the top-level `graphs` key (i.e. `:<variable_name>`).
|
||||
|
||||
!!! warning "Configuration Location"
|
||||
The LangGraph API configuration file must be placed in a directory that is at the same level or higher than the Python files that contain compiled graphs and associated dependencies.
|
||||
|
||||
Example file directory:
|
||||
|
||||
```bash
|
||||
my-app/
|
||||
├── my_agent
|
||||
├── my_agent # all project code lies within here
|
||||
│ ├── utils # utilities for your graph
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── tools.py # tools for your graph
|
||||
│ │ ├── nodes.py # node functions for you graph
|
||||
│ │ └── state.py # state definition of your graph
|
||||
│ ├── __init__.py
|
||||
│ └── agent.py # code for your graph
|
||||
│-- .env
|
||||
│-- langgraph.json # configuration file for LangGraph
|
||||
└── pyproject.toml
|
||||
│ └── agent.py # code for constructing your graph
|
||||
├── .env # environment variables
|
||||
├── langgraph.json # configuration file for LangGraph
|
||||
└── pyproject.toml # dependencies for your project
|
||||
```
|
||||
|
||||
## Upload to GitHub
|
||||
|
||||
To deploy the LangGraph application to LangGraph Cloud, the code must be uploaded to a GitHub repository.
|
||||
|
||||
## Next
|
||||
|
||||
After you setup your repo, it's time to [deploy your app](./cloud.md).
|
||||
After you setup your project and place it in a github repo, it's time to [deploy your app](./cloud.md).
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
# How to test a LangGraph app locally
|
||||
|
||||
This guide assumes you have a LangGraph app correctly set up with a proper configuration file and a corresponding compiled graph, and that you have a proper LangChain API key.
|
||||
|
||||
Testing locally ensures that there are no errors or conflicts with Python dependencies and confirms that the configuration file is specified correctly.
|
||||
|
||||
## Setup
|
||||
|
||||
Install the proper packages:
|
||||
|
||||
```shell
|
||||
pip install langgraph-cli
|
||||
```
|
||||
|
||||
Ensure you have an API key, which you can create from the LangSmith UI (Settings > API Keys). This is required to authenticate that you have LangGraph Cloud access. After you have saved the key to a safe place, place the following line in your `.env` file:
|
||||
|
||||
```python
|
||||
LANGCHAIN_API_KEY = *********
|
||||
```
|
||||
|
||||
## Start the API server
|
||||
|
||||
Once you have downloaded the CLI, you can run the following command to start the API server for local testing:
|
||||
|
||||
```shell
|
||||
langgraph up
|
||||
```
|
||||
|
||||
This will start up the LangGraph API server locally. If this runs successfully, you should see something like:
|
||||
|
||||
```shell
|
||||
Ready!
|
||||
- API: http://localhost:8123
|
||||
2024-06-26 19:20:41,056:INFO:uvicorn.access 127.0.0.1:44138 - "GET /ok HTTP/1.1" 200
|
||||
```
|
||||
|
||||
### Interact with the server
|
||||
|
||||
We can now interact with the API server using the LangGraph SDK. First, we need to start our client, select our assistant (in this case a graph we called "agent", make sure to select the proper assistant you wish to test).
|
||||
|
||||
You can either initialize by passing authentication or by setting an environment variable.
|
||||
|
||||
#### Initialize with authentication
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
# only pass the url argument to get_client() if you changed the default port when calling langgraph up
|
||||
client = get_client(url=<DEPLOYMENT_URL>,api_key=<LANGCHAIN_API_KEY>)
|
||||
assistant_id = "agent"
|
||||
thread = await client.threads.create()
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
// only set the apiUrl if you changed the default port when calling langgraph up
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL>, apiKey: <LANGCHAIN_API_KEY> });
|
||||
const assistantId = "agent"
|
||||
const thread = await client.threads.create();
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json'
|
||||
--header 'x-api-key: <LANGCHAIN_API_KEY>'
|
||||
```
|
||||
|
||||
|
||||
#### Initialize with environment variables
|
||||
|
||||
If you have a `LANGCHAIN_API_KEY` set in your environment, you do not need to explicitly pass authentication to the client
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
# only pass the url argument to get_client() if you changed the default port when calling langgraph up
|
||||
client = get_client()
|
||||
assistant_id = "agent"
|
||||
thread = await client.threads.create()
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
// only set the apiUrl if you changed the default port when calling langgraph up
|
||||
const client = new Client();
|
||||
const assistantId = "agent"
|
||||
const thread = await client.threads.create();
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
Now we can invoke our graph to ensure it is working. Make sure to change the input to match the proper schema for your graph.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
input = {"messages": [{"role": "human", "content": "what's the weather in sf"}]}
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistant_id,
|
||||
input=input,
|
||||
stream_mode="updates",
|
||||
):
|
||||
print(f"Receiving new event of type: {chunk.event}...")
|
||||
print(chunk.data)
|
||||
print("\n\n")
|
||||
```
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const input = { "messages": [{ "role": "human", "content": "what's the weather in sf"}] }
|
||||
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
{
|
||||
input: input,
|
||||
streamMode: "updates",
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
console.log(`Receiving new event of type: ${chunk.event}...`);
|
||||
console.log(chunk.data);
|
||||
console.log("\n\n");
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf\"}]},
|
||||
\"stream_mode\": [
|
||||
\"events\"
|
||||
]
|
||||
}" | \
|
||||
sed 's/\r$//' | \
|
||||
awk '
|
||||
/^event:/ {
|
||||
if (data_content != "") {
|
||||
print data_content "\n"
|
||||
}
|
||||
sub(/^event: /, "Receiving event of type: ", $0)
|
||||
printf "%s...\n", $0
|
||||
data_content = ""
|
||||
}
|
||||
/^data:/ {
|
||||
sub(/^data: /, "", $0)
|
||||
data_content = $0
|
||||
}
|
||||
END {
|
||||
if (data_content != "") {
|
||||
print data_content "\n"
|
||||
}
|
||||
}
|
||||
'
|
||||
```
|
||||
|
||||
If your graph works correctly, you should see your graph output displayed in the console. Of course, there are many more ways you might need to test your graph, for a full list of commands you can send with the SDK, see the [Python](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/) and [JS/TS](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/) references.
|
||||
@@ -0,0 +1,65 @@
|
||||
# Studio FAQs
|
||||
|
||||
## Why is my project failing to start?
|
||||
|
||||
There are a few reasons that your project might fail to start, here are some of the most common ones.
|
||||
|
||||
### Docker issues
|
||||
|
||||
LangGraph Studio requires Docker Desktop version 4.24 or higher. Please make sure you have a version of Docker installed that satisfies that requirement and also make sure you have the Docker Desktop app up and running before trying to use LangGraph Studio. In addition, make sure you have docker-compose updated to version 2.22.0 or higher.
|
||||
|
||||
### Configuration or environment issues
|
||||
|
||||
Another reason your project might fail to start is because your configuration file is defined incorrectly, or you are missing required environment variables.
|
||||
|
||||
## How does interrupt work?
|
||||
|
||||
When you select the `Interrupts` dropdown and select a node to interrupt the graph will pause execution before and after (unless the node goes straight to `END`) that node has run. This means that you will be able to both edit the state before the node is ran and the state after the node has ran. This is intended to allow developers more fine-grained control over the behavior of a node and make it easier to observe how the node is behaving. You will not be able to edit the state after the node has ran if the node is the final node in the graph.
|
||||
|
||||
## How do I reload the app?
|
||||
|
||||
If you would like to reload the app, don't use Command+R as you might normally do. Instead, close and reopen the app for a full refresh.
|
||||
|
||||
## How does automatic rebuilding work?
|
||||
|
||||
One of the key features of LangGraph Studio is that it automatically rebuilds your image when you change the source code. This allows for a super fast development and testing cycle which makes it easy to iterate on your graph. There are two different ways that LangGraph rebuilds your image: either by editing the image or completely rebuilding it.
|
||||
|
||||
### Rebuilds from source code changes
|
||||
|
||||
If you modified the source code only (no configuration or dependency changes!) then the image does not require a full rebuild, and LangGraph Studio will only update the relevant parts. The UI status in the bottom left will switch from `Online` to `Stopping` temporarily while the image gets edited. The logs will be shown as this process is happening, and after the image has been edited the status will change back to `Online` and you will be able to run your graph with the modified code!
|
||||
|
||||
|
||||
### Rebuilds from configuration or dependency changes
|
||||
|
||||
If you edit your graph configuration file (`langgraph.json`) or the dependencies (either `pyproject.toml` or `requirements.txt`) then the entire image will be rebuilt. This will cause the UI to switch away from the graph view and start showing the logs of the new image building process. This can take a minute or two, and once it is done your updated image will be ready to use!
|
||||
|
||||
## Why is my graph taking so long to startup?
|
||||
|
||||
The LangGraph Studio interacts with a local LangGraph API server. To stay aligned with ongoing updates, the LangGraph API requires regular rebuilding. As a result, you may occasionally experience slight delays when starting up your project.
|
||||
|
||||
## Why are extra edges showing up in my graph?
|
||||
|
||||
If you don't define your conditional edges carefully, you might notice extra edges appearing in your graph. This is because without proper definition, LangGraph Studio assumes the conditional edge could access all other nodes. In order for this to not be the case, you need to be explicit about how you define the nodes the conditional edge routes to. There are two ways you can do this:
|
||||
|
||||
### Solution 1: Include a path map
|
||||
|
||||
The first way to solve this is to add path maps to your conditional edges. A path map is just a dictionary that maps the possible outputs of your router function with the names of the nodes that each output corresponds to. The path map is passed as the third argument to the `add_conditional_edges` function like so:
|
||||
|
||||
```python
|
||||
graph.add_conditional_edges("node_a", routing_function, {True: "node_b", False: "node_c"})
|
||||
```
|
||||
|
||||
In this case, the routing function returns either True or False, which map to `node_b` and `node_c` respectively.
|
||||
|
||||
### Solution 2: Update the typing of the router
|
||||
|
||||
Instead of passing a path map, you can also be explicit about the typing of your routing function by specifying the nodes it can map to using the `Literal` python definition. Here is an example of how to define a routing function in that way:
|
||||
|
||||
```python
|
||||
def routing_function(state: GraphState) -> Literal["node_b","node_c"]:
|
||||
if state['some_condition'] == True:
|
||||
return "node_a"
|
||||
else:
|
||||
return "node_b"
|
||||
```
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
# Check the Status of your Threads
|
||||
|
||||
## Setup
|
||||
|
||||
To start, we can setup our client with whatever URL you are hosting your graph from:
|
||||
|
||||
### SDK initialization
|
||||
|
||||
First, we need to setup our client so that we can communicate with our hosted graph:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
assistant_id = "agent"
|
||||
thread = await client.threads.create()
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
const assistantId = agent;
|
||||
const thread = await client.threads.create();
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
## Find idle threads
|
||||
|
||||
We can use the following commands to find threads that are idle, which means that all runs executed on the thread have finished running:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
print(await client.threads.search(status="idle",limit=1))
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
console.log(await client.threads.search({status: "idle",limit:1}));
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/search \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{"status": "idle", "limit": 1}'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
[{'thread_id': 'cacf79bb-4248-4d01-aabc-938dbd60ed2c',
|
||||
'created_at': '2024-08-14T17:36:38.921660+00:00',
|
||||
'updated_at': '2024-08-14T17:36:38.921660+00:00',
|
||||
'metadata': {'graph_id': 'agent'},
|
||||
'status': 'idle',
|
||||
'config': {'configurable': {}}}]
|
||||
|
||||
|
||||
## Find interrupted threads
|
||||
|
||||
We can use the following commands to find threads that have been interrupted in the middle of a run, which could either mean an error occurred before the run finished or a human-in-the-loop breakpoint was reached and the run is waiting to continue:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
print(await client.threads.search(status="interrupted",limit=1))
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
console.log(await client.threads.search({status: "interrupted",limit:1}));
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/search \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{"status": "interrupted", "limit": 1}'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
[{'thread_id': '0d282b22-bbd5-4d95-9c61-04dcc2e302a5',
|
||||
'created_at': '2024-08-14T17:41:50.235455+00:00',
|
||||
'updated_at': '2024-08-14T17:41:50.235455+00:00',
|
||||
'metadata': {'graph_id': 'agent'},
|
||||
'status': 'interrupted',
|
||||
'config': {'configurable': {}}}]
|
||||
|
||||
## Find busy threads
|
||||
|
||||
We can use the following commands to find threads that are busy, meaning they are currently handling the execution of a run:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
print(await client.threads.search(status="busy",limit=1))
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
console.log(await client.threads.search({status: "busy",limit: 1}));
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/search \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{"status": "busy", "limit": 1}'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
[{'thread_id': '0d282b22-bbd5-4d95-9c61-04dcc2e302a5',
|
||||
'created_at': '2024-08-14T17:41:50.235455+00:00',
|
||||
'updated_at': '2024-08-14T17:41:50.235455+00:00',
|
||||
'metadata': {'graph_id': 'agent'},
|
||||
'status': 'busy',
|
||||
'config': {'configurable': {}}}]
|
||||
|
||||
## Find specific threads
|
||||
|
||||
You may also want to check the status of specific threads, which you can do in a few ways:
|
||||
|
||||
### Find by ID
|
||||
|
||||
You can use the `get` function to find the status of a specific thread, as long as you have the ID saved
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
print((await client.threads.get(<THREAD_ID>))['status'])
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
console.log((await client.threads.get(<THREAD_ID>)).status);
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request GET \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID> \
|
||||
--header 'Content-Type: application/json' | jq -r '.status'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
'idle'
|
||||
|
||||
### Find by metadata
|
||||
|
||||
The search endpoint for threads also allows you to filter on metadata, which can be helpful if you use metadata to tag threads in order to keep them organized:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
print((await client.threads.search(metadata={"foo":"bar"},limit=1))[0]['status'])
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
console.log((await client.threads.search({metadata: {"foo":"bar"},limit: 1}))[0].status);
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/search \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{"metadata": {"foo":"bar"}, "limit": 1}' | jq -r '.[0].status'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
'idle'
|
||||
|
After Width: | Height: | Size: 322 KiB |
@@ -0,0 +1,132 @@
|
||||
# Copying Threads
|
||||
|
||||
You may wish to copy (i.e. "fork") an existing thread in order to keep the existing thread's history and create independent runs that do not affect the original thread. This guide shows how you can do that.
|
||||
|
||||
## Setup
|
||||
|
||||
This code assumes you already have a thread to copy. You can read about what a thread is [here](https://langchain-ai.github.io/langgraph/cloud/concepts/api/#threads) and learn how to stream a run on a thread in [these how-to guides](https://langchain-ai.github.io/langgraph/cloud/how-tos/#streaming).
|
||||
|
||||
### SDK initialization
|
||||
|
||||
First, we need to setup our client so that we can communicate with our hosted graph:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
client = get_client(url="<DEPLOYMENT_URL>")
|
||||
assistant_id = "agent"
|
||||
thread = await client.threads.create()
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
const client = new Client({ apiUrl:"<DEPLOYMENT_URL>" });
|
||||
const assistantId = agent;
|
||||
const thread = await client.threads.create();
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"metadata": {}
|
||||
}'
|
||||
```
|
||||
|
||||
## Copying a thread
|
||||
|
||||
The code below assumes that a thread you'd like to copy already exists.
|
||||
|
||||
Copying a thread will create a new thread with the same history as the existing thread, and then allow you to continue executing runs.
|
||||
|
||||
### Create copy
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
copied_thread = await client.threads.copy(<THREAD_ID>)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
let copiedThread = await client.threads.copy(<THREAD_ID>);
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/copy \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
### Verify copy
|
||||
|
||||
We can verify that the history from the prior thread did indeed copy over correctly:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
def remove_thread_id(d):
|
||||
if 'metadata' in d and 'thread_id' in d['metadata']:
|
||||
del d['metadata']['thread_id']
|
||||
return d
|
||||
|
||||
original_thread_history = list(map(remove_thread_id,await client.threads.get_history(<THREAD_ID>)))
|
||||
copied_thread_history = list(map(remove_thread_id,await client.threads.get_history(copied_thread['thread_id'])))
|
||||
|
||||
# Compare the two histories
|
||||
assert original_thread_history == copied_thread_history
|
||||
# if we made it here the assertion passed!
|
||||
print("The histories are the same.")
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
function removeThreadId(d) {
|
||||
if (d.metadata && d.metadata.thread_id) {
|
||||
delete d.metadata.thread_id;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
// Assuming `client.threads.getHistory(threadId)` is an async function that returns a list of dicts
|
||||
async function compareThreadHistories(threadId, copiedThreadId) {
|
||||
const originalThreadHistory = (await client.threads.getHistory(threadId)).map(removeThreadId);
|
||||
const copiedThreadHistory = (await client.threads.getHistory(copiedThreadId)).map(removeThreadId);
|
||||
|
||||
// Compare the two histories
|
||||
console.assert(JSON.stringify(originalThreadHistory) === JSON.stringify(copiedThreadHistory))
|
||||
// if we made it here the assertion passed!
|
||||
console.log("The histories are the same.");
|
||||
}
|
||||
|
||||
// Example usage
|
||||
compareThreadHistories(<THREAD_ID>, copiedThread.thread_id);
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
if diff <(
|
||||
curl --request GET --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/history | jq -S 'map(del(.metadata.thread_id))'
|
||||
) <(
|
||||
curl --request GET --url <DEPLOYMENT_URL>/threads/<COPIED_THREAD_ID>/history | jq -S 'map(del(.metadata.thread_id))'
|
||||
) >/dev/null; then
|
||||
echo "The histories are the same."
|
||||
else
|
||||
echo "The histories are different."
|
||||
fi
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
The histories are the same.
|
||||
@@ -0,0 +1,183 @@
|
||||
## Enqueue
|
||||
|
||||
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../concepts/api.md#double-texting).
|
||||
|
||||
The guide covers the `enqueue` option for double texting, which adds the interruptions to a queue and executes them in the order they are received by the client. Below is a quick example of using the `enqueue` option.
|
||||
|
||||
|
||||
First, we will define a quick helper function for printing out JS model outputs (you can skip this if using Python):
|
||||
|
||||
```js
|
||||
function prettyPrint(m) {
|
||||
const padded = " " + m['type'] + " ";
|
||||
const sepLen = Math.floor((80 - padded.length) / 2);
|
||||
const sep = "=".repeat(sepLen);
|
||||
const secondSep = sep + (padded.length % 2 ? "=" : "");
|
||||
|
||||
console.log(`${sep}${padded}${secondSep}`);
|
||||
console.log("\n\n");
|
||||
console.log(m.content);
|
||||
}
|
||||
```
|
||||
|
||||
Then, let's import our required packages and instantiate our client, assistant, and thread.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
import httpx
|
||||
from langchain_core.messages import convert_to_messages
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
assistant_id = "agent"
|
||||
thread = await client.threads.create()
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
const assistantId = "agent";
|
||||
const thread = await client.threads.create();
|
||||
```
|
||||
|
||||
Now let's start two runs, with the second interrupting the first one with a multitask strategy of "enqueue":
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
first_run = await client.runs.create(
|
||||
thread["thread_id"],
|
||||
assistant_id,
|
||||
input={"messages": [{"role": "human", "content": "what's the weather in sf?"}]},
|
||||
)
|
||||
second_run = await client.runs.create(
|
||||
thread["thread_id"],
|
||||
assistant_id,
|
||||
input={"messages": [{"role": "human", "content": "what's the weather in nyc?"}]},
|
||||
multitask_strategy="enqueue",
|
||||
)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const firstRun = await client.runs.create(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
input={"messages": [{"role": "human", "content": "what's the weather in sf?"}]},
|
||||
)
|
||||
|
||||
const secondRun = await client.runs.create(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
input={"messages": [{"role": "human", "content": "what's the weather in nyc?"}]},
|
||||
multitask_strategy="enqueue",
|
||||
)
|
||||
```
|
||||
|
||||
Verify that the thread has data from both runs:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
# wait until the second run completes
|
||||
await client.runs.join(thread["thread_id"], second_run["run_id"])
|
||||
|
||||
state = await client.threads.get_state(thread["thread_id"])
|
||||
|
||||
for m in convert_to_messages(state["values"]["messages"]):
|
||||
m.pretty_print()
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
await client.runs.join(thread["thread_id"], secondRun["run_id"]);
|
||||
|
||||
const state = await client.threads.getState(thread["thread_id"]);
|
||||
|
||||
for (const m of state["values"]["messages"]) {
|
||||
prettyPrint(m);
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
================================[1m Human Message [0m=================================
|
||||
|
||||
what's the weather in sf?
|
||||
==================================[1m Ai Message [0m==================================
|
||||
|
||||
[{'id': 'toolu_01Dez1sJre4oA2Y7NsKJV6VT', 'input': {'query': 'weather in san francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
|
||||
Tool Calls:
|
||||
tavily_search_results_json (toolu_01Dez1sJre4oA2Y7NsKJV6VT)
|
||||
Call ID: toolu_01Dez1sJre4oA2Y7NsKJV6VT
|
||||
Args:
|
||||
query: weather in san francisco
|
||||
=================================[1m Tool Message [0m=================================
|
||||
Name: tavily_search_results_json
|
||||
|
||||
[{"url": "https://www.accuweather.com/en/us/san-francisco/94103/weather-forecast/347629", "content": "Get the current and future weather conditions for San Francisco, CA, including temperature, precipitation, wind, air quality and more. See the hourly and 10-day outlook, radar maps, alerts and allergy information."}]
|
||||
==================================[1m Ai Message [0m==================================
|
||||
|
||||
According to AccuWeather, the current weather conditions in San Francisco are:
|
||||
|
||||
Temperature: 57°F (14°C)
|
||||
Conditions: Mostly Sunny
|
||||
Wind: WSW 10 mph
|
||||
Humidity: 72%
|
||||
|
||||
The forecast for the next few days shows partly sunny skies with highs in the upper 50s to mid 60s F (14-18°C) and lows in the upper 40s to low 50s F (9-11°C). Typical mild, dry weather for San Francisco this time of year.
|
||||
|
||||
Some key details from the AccuWeather forecast:
|
||||
|
||||
Today: Mostly sunny, high of 62°F (17°C)
|
||||
Tonight: Partly cloudy, low of 49°F (9°C)
|
||||
Tomorrow: Partly sunny, high of 59°F (15°C)
|
||||
Saturday: Mostly sunny, high of 64°F (18°C)
|
||||
Sunday: Partly sunny, high of 61°F (16°C)
|
||||
|
||||
So in summary, expect seasonable spring weather in San Francisco over the next several days, with a mix of sun and clouds and temperatures ranging from the upper 40s at night to the low 60s during the days. Typical dry conditions with no rain in the forecast.
|
||||
================================[1m Human Message [0m=================================
|
||||
|
||||
what's the weather in nyc?
|
||||
==================================[1m Ai Message [0m==================================
|
||||
|
||||
[{'text': 'Here are the current weather conditions and forecast for New York City:', 'type': 'text'}, {'id': 'toolu_01FFft5Sx9oS6AdVJuRWWcGp', 'input': {'query': 'weather in new york city'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
|
||||
Tool Calls:
|
||||
tavily_search_results_json (toolu_01FFft5Sx9oS6AdVJuRWWcGp)
|
||||
Call ID: toolu_01FFft5Sx9oS6AdVJuRWWcGp
|
||||
Args:
|
||||
query: weather in new york city
|
||||
=================================[1m Tool Message [0m=================================
|
||||
Name: tavily_search_results_json
|
||||
|
||||
[{"url": "https://www.weatherapi.com/", "content": "{'location': {'name': 'New York', 'region': 'New York', 'country': 'United States of America', 'lat': 40.71, 'lon': -74.01, 'tz_id': 'America/New_York', 'localtime_epoch': 1718734479, 'localtime': '2024-06-18 14:14'}, 'current': {'last_updated_epoch': 1718733600, 'last_updated': '2024-06-18 14:00', 'temp_c': 29.4, 'temp_f': 84.9, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 2.2, 'wind_kph': 3.6, 'wind_degree': 158, 'wind_dir': 'SSE', 'pressure_mb': 1025.0, 'pressure_in': 30.26, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 63, 'cloud': 0, 'feelslike_c': 31.3, 'feelslike_f': 88.3, 'windchill_c': 28.3, 'windchill_f': 82.9, 'heatindex_c': 29.6, 'heatindex_f': 85.3, 'dewpoint_c': 18.4, 'dewpoint_f': 65.2, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 7.0, 'gust_mph': 16.5, 'gust_kph': 26.5}}"}]
|
||||
==================================[1m Ai Message [0m==================================
|
||||
|
||||
According to the weather data from WeatherAPI:
|
||||
|
||||
Current Conditions in New York City (as of 2:00 PM local time):
|
||||
- Temperature: 85°F (29°C)
|
||||
- Conditions: Sunny
|
||||
- Wind: 2 mph (4 km/h) from the SSE
|
||||
- Humidity: 63%
|
||||
- Heat Index: 85°F (30°C)
|
||||
|
||||
The forecast shows sunny and warm conditions persisting over the next few days:
|
||||
|
||||
Today: Sunny, high of 85°F (29°C)
|
||||
Tonight: Clear, low of 68°F (20°C)
|
||||
Tomorrow: Sunny, high of 88°F (31°C)
|
||||
Thursday: Mostly sunny, high of 90°F (32°C)
|
||||
Friday: Partly cloudy, high of 87°F (31°C)
|
||||
|
||||
So New York City is experiencing beautiful sunny weather with seasonably warm temperatures in the mid-to-upper 80s Fahrenheit (around 30°C). Humidity is moderate in the 60% range. Overall, ideal late spring/early summer conditions for being outdoors in the city over the next several days.
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
# How to Add Breakpoints
|
||||
|
||||
When creating LangGraph agents, it is often nice to add a human-in-the-loop component.
|
||||
This can be helpful when giving them access to tools.
|
||||
Often in these situations you may want to manually approve an action before taking.
|
||||
|
||||
This can be in several ways, but the primary supported way is to add an "interrupt" before a node is executed.
|
||||
This interrupts execution at that node.
|
||||
You can then resume from that spot to continue.
|
||||
|
||||
## Setup
|
||||
|
||||
### Code for your graph
|
||||
|
||||
In this how-to we use a simple ReAct style hosted graph (you can see the full code for defining it [here](../../how-tos/human_in_the_loop/breakpoints.ipynb)). The important thing is that there are two nodes (one named `agent` that calls the LLM, and one named `action` that calls the tool), and a routing function from `agent` that determines whether to call `action` next or just end the graph run (the `action` node always calls the `agent` node after execution).
|
||||
|
||||
### SDK Initialization
|
||||
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
assistant_id = "agent"
|
||||
thread = await client.threads.create()
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
const assistantId = "agent"
|
||||
const thread = await client.threads.create();
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
## Adding a breakpoint
|
||||
|
||||
We now want to add a breakpoint in our graph run, which we will do before a tool is called.
|
||||
We can do this by adding `interrupt_before=["action"]`, which tells us to interrupt before calling the action node.
|
||||
We can do this either when compiling the graph or when kicking off a run.
|
||||
Here we will do it when kicking of a run, if you would like to to do it at compile time you need to edit the python file where your graph is defined and add the `interrupt_before` parameter when you call `.compile`.
|
||||
|
||||
First let's access our hosted LangGraph instance through the SDK:
|
||||
|
||||
And, now let's compile it with a breakpoint before the tool node:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
input = {"messages": [{"role": "human", "content": "what's the weather in sf"}]}
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistant_id,
|
||||
input=input,
|
||||
stream_mode="updates",
|
||||
interrupt_before=["action"],
|
||||
):
|
||||
print(f"Receiving new event of type: {chunk.event}...")
|
||||
print(chunk.data)
|
||||
print("\n\n")
|
||||
```
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const input = { "messages": [{ "role": "human", "content": "what's the weather in sf"}] }
|
||||
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
{
|
||||
input: input,
|
||||
streamMode: "updates",
|
||||
interruptBefore: ["action"],
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
console.log(`Receiving new event of type: ${chunk.event}...`);
|
||||
console.log(chunk.data);
|
||||
console.log("\n\n");
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf\"}]},
|
||||
\"interrupt_before\": [\"action\"],
|
||||
\"stream_mode\": [
|
||||
\"messages\"
|
||||
]
|
||||
}" | \
|
||||
sed 's/\r$//' | \
|
||||
awk '
|
||||
/^event:/ {
|
||||
if (data_content != "") {
|
||||
print data_content "\n"
|
||||
}
|
||||
sub(/^event: /, "Receiving event of type: ", $0)
|
||||
printf "%s...\n", $0
|
||||
data_content = ""
|
||||
}
|
||||
/^data:/ {
|
||||
sub(/^data: /, "", $0)
|
||||
data_content = $0
|
||||
}
|
||||
END {
|
||||
if (data_content != "") {
|
||||
print data_content "\n"
|
||||
}
|
||||
}
|
||||
'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
Receiving new event of type: metadata...
|
||||
{'run_id': '3b77ef83-687a-4840-8858-0371f91a92c3'}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: data...
|
||||
{'agent': {'messages': [{'content': [{'id': 'toolu_01HwZqM1ptX6E15A5LAmyZTB', 'input': {'query': 'weather in san francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-e5d17791-4d37-4ad2-815f-a0c4cba62585', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in san francisco'}, 'id': 'toolu_01HwZqM1ptX6E15A5LAmyZTB'}], 'invalid_tool_calls': []}]}}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: end...
|
||||
None
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
# How to Edit State of a Deployed Graph
|
||||
|
||||
When creating LangGraph agents, it is often nice to add a human-in-the-loop component. This can be helpful when giving them access to tools. Often in these situations you may want to edit the graph state before continuing (for example, to edit what tool is being called, or how it is being called).
|
||||
|
||||
This can be in several ways, but the primary supported way is to add an "interrupt" before a node is executed. This interrupts execution at that node. You can then use update_state to update the state, and then resume from that spot to continue.
|
||||
|
||||
## Setup
|
||||
|
||||
We are not going to show the full code for the graph we are hosting, but you can see it [here](../../how-tos/human_in_the_loop/edit-graph-state.ipynb#build-the-agent) if you want to. Once this graph is hosted, we are ready to invoke it and wait for user input.
|
||||
|
||||
### SDK initialization
|
||||
|
||||
First, we need to setup our client so that we can communicate with our hosted graph:
|
||||
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
assistant_id = "agent"
|
||||
thread = await client.threads.create()
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
const assistantId = "agent";
|
||||
const thread = await client.threads.create();
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
## Editing state
|
||||
|
||||
### Initial invocation
|
||||
|
||||
Now let's invoke our graph, making sure to interrupt before the `action` node.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
input = { 'messages':[{ "role":"user", "content":"search for weather in SF" }] }
|
||||
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistant_id,
|
||||
input=input,
|
||||
stream_mode="updates",
|
||||
interrupt_before=["action"],
|
||||
):
|
||||
if chunk.data and chunk.event != "metadata":
|
||||
print(chunk.data)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const input = {"messages": [{ "role": "human", "content": "search for weather in SF"}] }
|
||||
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
{
|
||||
input: input,
|
||||
streamMode: "updates",
|
||||
interruptBefore: ["action"],
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
if (chunk.data && chunk.event !== "metadata") {
|
||||
console.log(chunk.data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"search for weather in SF\"}]},
|
||||
\"interrupt_before\": [\"action\"],
|
||||
\"stream_mode\": [
|
||||
\"updates\"
|
||||
]
|
||||
}" | \
|
||||
sed 's/\r$//' | \
|
||||
awk '
|
||||
/^event:/ {
|
||||
if (data_content != "" && event_type != "metadata") {
|
||||
print data_content "\n"
|
||||
}
|
||||
sub(/^event: /, "", $0)
|
||||
event_type = $0
|
||||
data_content = ""
|
||||
}
|
||||
/^data:/ {
|
||||
sub(/^data: /, "", $0)
|
||||
data_content = $0
|
||||
}
|
||||
END {
|
||||
if (data_content != "" && event_type != "metadata") {
|
||||
print data_content "\n"
|
||||
}
|
||||
}
|
||||
'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
{'agent': {'messages': [{'content': [{'text': "Certainly! I'll search for the current weather in San Francisco for you using the search function. Here's how I'll do that:", 'type': 'text'}, {'id': 'toolu_01KEJMBFozSiZoS4mAcPZeqQ', 'input': {'query': 'current weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-6dbb0167-f8f6-4e2a-ab68-229b2d1fbb64', 'example': False, 'tool_calls': [{'name': 'search', 'args': {'query': 'current weather in San Francisco'}, 'id': 'toolu_01KEJMBFozSiZoS4mAcPZeqQ'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
|
||||
|
||||
|
||||
### Edit the state
|
||||
|
||||
Now, let's assume we actually meant to search for the weather in Sidi Frej (another city with the initials SF). We can edit the state to properly reflect that:
|
||||
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
# First, lets get the current state
|
||||
current_state = await client.threads.get_state(thread['thread_id'])
|
||||
|
||||
# Let's now get the last message in the state
|
||||
# This is the one with the tool calls that we want to update
|
||||
last_message = current_state['values']['messages'][-1]
|
||||
|
||||
# Let's now update the args for that tool call
|
||||
last_message['tool_calls'][0]['args'] = {'query': 'current weather in Sidi Frej'}
|
||||
|
||||
# Let's now call `update_state` to pass in this message in the `messages` key
|
||||
# This will get treated as any other update to the state
|
||||
# It will get passed to the reducer function for the `messages` key
|
||||
# That reducer function will use the ID of the message to update it
|
||||
# It's important that it has the right ID! Otherwise it would get appended
|
||||
# as a new message
|
||||
await client.threads.update_state(thread['thread_id'], {"messages": last_message})
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
// First, lets get the current state
|
||||
const currentState = await client.threads.getState(thread['thread_id']);
|
||||
|
||||
// Let's now get the last message in the state
|
||||
// This is the one with the tool calls that we want to update
|
||||
let lastMessage = currentState['values']['messages'][-1];
|
||||
|
||||
// Let's now update the args for that tool call
|
||||
lastMessage['tool_calls'][0]['args'] = {'query': 'current weather in Sidi Frej'};
|
||||
|
||||
// Let's now call `update_state` to pass in this message in the `messages` key
|
||||
// This will get treated as any other update to the state
|
||||
// It will get passed to the reducer function for the `messages` key
|
||||
// That reducer function will use the ID of the message to update it
|
||||
// It's important that it has the right ID! Otherwise it would get appended
|
||||
// as a new message
|
||||
await client.threads.updateState(thread['thread_id'], {values:{"messages": lastMessage}});
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request GET --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/state | \
|
||||
jq '.values.messages[-1] | (.tool_calls[0].args = {"query": "current weather in Sidi Frej"})' | \
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/state \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data @-
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
{'configurable': {'thread_id': '9c8f1a43-9dd8-4017-9271-2c53e57cf66a',
|
||||
'checkpoint_ns': '',
|
||||
'checkpoint_id': '1ef58e7e-3641-649f-8002-8b4305a64858'}}
|
||||
|
||||
|
||||
|
||||
### Resume invocation
|
||||
|
||||
Now we can resume our graph run but with the updated state:
|
||||
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistant_id,
|
||||
input=None,
|
||||
stream_mode="updates",
|
||||
):
|
||||
if chunk.data and chunk.event != "metadata":
|
||||
print(chunk.data)
|
||||
```
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
{
|
||||
input: null,
|
||||
streamMode: "updates",
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
if (chunk.data && chunk.event !== "metadata") {
|
||||
console.log(chunk.data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"stream_mode\": [
|
||||
\"updates\"
|
||||
]
|
||||
}"| \
|
||||
sed 's/\r$//' | \
|
||||
awk '
|
||||
/^event:/ {
|
||||
if (data_content != "" && event_type != "metadata") {
|
||||
print data_content "\n"
|
||||
}
|
||||
sub(/^event: /, "", $0)
|
||||
event_type = $0
|
||||
data_content = ""
|
||||
}
|
||||
/^data:/ {
|
||||
sub(/^data: /, "", $0)
|
||||
data_content = $0
|
||||
}
|
||||
END {
|
||||
if (data_content != "" && event_type != "metadata") {
|
||||
print data_content "\n"
|
||||
}
|
||||
}
|
||||
'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
{'action': {'messages': [{'content': '["I looked up: current weather in Sidi Frej. Result: It\'s sunny in San Francisco, but you better look out if you\'re a Gemini 😈."]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': '1161b8d1-bee4-4188-9be8-698aecb69f10', 'tool_call_id': 'toolu_01KEJMBFozSiZoS4mAcPZeqQ'}]}}
|
||||
{'agent': {'messages': [{'content': [{'text': 'I apologize for the confusion in my search query. It seems the search function interpreted "SF" as "Sidi Frej" instead of "San Francisco" as we intended. Let me search again with the full city name to get the correct information:', 'type': 'text'}, {'id': 'toolu_0111rrwgfAcmurHZn55qjqTR', 'input': {'query': 'current weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-b8c25779-cfb4-46fc-a421-48553551242f', 'example': False, 'tool_calls': [{'name': 'search', 'args': {'query': 'current weather in San Francisco'}, 'id': 'toolu_0111rrwgfAcmurHZn55qjqTR'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
|
||||
{'action': {'messages': [{'content': '["I looked up: current weather in San Francisco. Result: It\'s sunny in San Francisco, but you better look out if you\'re a Gemini 😈."]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': '6bc632ae-5ee6-4d01-9532-79c524a2d443', 'tool_call_id': 'toolu_0111rrwgfAcmurHZn55qjqTR'}]}}
|
||||
{'agent': {'messages': [{'content': "Now, based on the search results, I can provide you with information about the current weather in San Francisco:\n\nThe weather in San Francisco is currently sunny. \n\nIt's worth noting that the search result included an unusual comment about Gemini, which doesn't seem directly related to the weather. This might be due to the search engine including some astrological information or a joke in its results. However, for the purpose of weather information, we can focus on the fact that it's sunny in San Francisco right now.\n\nIs there anything else you'd like to know about the weather in San Francisco or any other location?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-227a042b-dd97-476e-af32-76a3703af5d8', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
|
||||
|
||||
|
||||
As you can see it now looks up the current weather in Sidi Frej (although our dummy search node still returns results for SF because we don't actually do a search in this example, we just return the same "It's sunny in San Francisco ..." result every time).
|
||||
@@ -0,0 +1,575 @@
|
||||
# Review Tool Calls
|
||||
|
||||
Human-in-the-loop (HIL) interactions are crucial for [agentic systems](https://langchain-ai.github.io/langgraph/concepts/agentic_concepts/#human-in-the-loop). A common pattern is to add some human in the loop step after certain tool calls. These tool calls often lead to either a function call or saving of some information. Examples include:
|
||||
|
||||
- A tool call to execute SQL, which will then be run by the tool
|
||||
- A tool call to generate a summary, which will then be saved to the State of the graph
|
||||
|
||||
Note that using tool calls is common **whether actually calling tools or not**.
|
||||
|
||||
There are typically a few different interactions you may want to do here:
|
||||
|
||||
1. Approve the tool call and continue
|
||||
2. Modify the tool call manually and then continue
|
||||
3. Give natural language feedback, and then pass that back to the agent instead of continuing
|
||||
|
||||
We can implement this in LangGraph using a [breakpoint](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/breakpoints/): breakpoints allow us to interrupt graph execution before a specific step. At this breakpoint, we can manually update the graph state taking one of the three options above
|
||||
|
||||
## Setup
|
||||
|
||||
We are not going to show the full code for the graph we are hosting, but you can see it [here](../../how-tos/human_in_the_loop/review-tool-calls.ipynb#simple-usage) if you want to. Once this graph is hosted, we are ready to invoke it and wait for user input.
|
||||
|
||||
### SDK initialization
|
||||
|
||||
First, we need to setup our client so that we can communicate with our hosted graph:
|
||||
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
assistant_id = "agent"
|
||||
thread = await client.threads.create()
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
const assistantId = "agent";
|
||||
const thread = await client.threads.create();
|
||||
```
|
||||
|
||||
## Example with no review
|
||||
|
||||
Let's look at an example when no review is required (because no tools are called)
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
input = { 'messages':[{ "role":"user", "content":"hi!" }] }
|
||||
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistant_id,
|
||||
input=input,
|
||||
stream_mode="updates",
|
||||
interrupt_before=["action"],
|
||||
):
|
||||
if chunk.data and chunk.event != "metadata":
|
||||
print(chunk.data)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const input = {"messages": [{ "role": "human", "content": "hi!"}] }
|
||||
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
{
|
||||
input: input,
|
||||
streamMode: "updates",
|
||||
interruptBefore: ["action"],
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
if (chunk.data && chunk.event !== "metadata") {
|
||||
console.log(chunk.data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
{'messages': [{'content': 'hi!', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '39c51f14-2d5c-4690-883a-d940854b1845', 'example': False}]}
|
||||
{'messages': [{'content': 'hi!', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '39c51f14-2d5c-4690-883a-d940854b1845', 'example': False}, {'content': [{'text': "Hello! Welcome. How can I assist you today? Is there anything specific you'd like to know or any information you're looking for?", 'type': 'text', 'index': 0}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'end_turn', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-d65e07fb-43ff-4d98-ab6b-6316191b9c8b', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 355, 'output_tokens': 31, 'total_tokens': 386}}]}
|
||||
|
||||
|
||||
If we check the state, we can see that it is finished
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
state = await client.threads.get_state(thread["thread_id"])
|
||||
|
||||
print(state['next'])
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const state = await client.threads.getState(thread["thread_id"]);
|
||||
|
||||
console.log(state.next);
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
[]
|
||||
|
||||
## Example of approving tool
|
||||
|
||||
Let's now look at what it looks like to approve a tool call. Note that we don't need to pass an interrupt to our streaming calls because the graph (defined [here](../../how-tos/human_in_the_loop/review-tool-calls.ipynb#simple-usage)) was already compiled with an interrupt before the `human_review_node`.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]}
|
||||
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
"agent",
|
||||
input=input,
|
||||
stream_mode="values",
|
||||
):
|
||||
if chunk.data and chunk.event != "metadata":
|
||||
print(chunk.data)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]}
|
||||
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
{
|
||||
input: input,
|
||||
streamMode: "values",
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
if (chunk.data && chunk.event !== "metadata") {
|
||||
console.log(chunk.data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
{'messages': [{'content': "what's the weather in sf?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '54e19d6e-89fa-44fb-b92c-12e7dd4ddf08', 'example': False}]}
|
||||
{'messages': [{'content': "what's the weather in sf?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '54e19d6e-89fa-44fb-b92c-12e7dd4ddf08', 'example': False}, {'content': [{'text': "Certainly! I can help you check the weather in San Francisco. To get this information, I'll use the weather search function. Let me do that for you right away.", 'type': 'text', 'index': 0}, {'id': 'toolu_015yrR3GMDXe6X8m2p9CsEDN', 'input': {}, 'name': 'weather_search', 'type': 'tool_use', 'index': 1, 'partial_json': '{"city": "San Francisco"}'}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'tool_use', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-45a6b6c3-ac69-42a4-8957-d982203d6392', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco'}, 'id': 'toolu_015yrR3GMDXe6X8m2p9CsEDN', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 360, 'output_tokens': 90, 'total_tokens': 450}}]}
|
||||
|
||||
|
||||
If we now check, we can see that it is waiting on human review:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
state = await client.threads.get_state(thread["thread_id"])
|
||||
|
||||
print(state['next'])
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const state = await client.threads.getState(thread["thread_id"]);
|
||||
|
||||
console.log(state.next);
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
['human_review_node']
|
||||
|
||||
To approve the tool call, we can just continue the thread with no edits. To do this, we just create a new run with no inputs.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
"agent",
|
||||
input=None,
|
||||
stream_mode="values",
|
||||
):
|
||||
if chunk.data and chunk.event != "metadata":
|
||||
print(chunk.data)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
{
|
||||
input: undefined,
|
||||
streamMode: "values",
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
if (chunk.data && chunk.event !== "metadata") {
|
||||
console.log(chunk.data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
{'messages': [{'content': "what's the weather in sf?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '54e19d6e-89fa-44fb-b92c-12e7dd4ddf08', 'example': False}, {'content': [{'text': "Certainly! I can help you check the weather in San Francisco. To get this information, I'll use the weather search function. Let me do that for you right away.", 'type': 'text', 'index': 0}, {'id': 'toolu_015yrR3GMDXe6X8m2p9CsEDN', 'input': {}, 'name': 'weather_search', 'type': 'tool_use', 'index': 1, 'partial_json': '{"city": "San Francisco"}'}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'tool_use', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-45a6b6c3-ac69-42a4-8957-d982203d6392', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco'}, 'id': 'toolu_015yrR3GMDXe6X8m2p9CsEDN', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 360, 'output_tokens': 90, 'total_tokens': 450}}, {'content': 'Sunny!', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'weather_search', 'id': '826cd0f2-9cc6-46f0-b7df-daa6a05d13d2', 'tool_call_id': 'toolu_015yrR3GMDXe6X8m2p9CsEDN', 'artifact': None, 'status': 'success'}]}
|
||||
{'messages': [{'content': "what's the weather in sf?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '54e19d6e-89fa-44fb-b92c-12e7dd4ddf08', 'example': False}, {'content': [{'text': "Certainly! I can help you check the weather in San Francisco. To get this information, I'll use the weather search function. Let me do that for you right away.", 'type': 'text', 'index': 0}, {'id': 'toolu_015yrR3GMDXe6X8m2p9CsEDN', 'input': {}, 'name': 'weather_search', 'type': 'tool_use', 'index': 1, 'partial_json': '{"city": "San Francisco"}'}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'tool_use', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-45a6b6c3-ac69-42a4-8957-d982203d6392', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco'}, 'id': 'toolu_015yrR3GMDXe6X8m2p9CsEDN', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 360, 'output_tokens': 90, 'total_tokens': 450}}, {'content': 'Sunny!', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'weather_search', 'id': '826cd0f2-9cc6-46f0-b7df-daa6a05d13d2', 'tool_call_id': 'toolu_015yrR3GMDXe6X8m2p9CsEDN', 'artifact': None, 'status': 'success'}, {'content': [{'text': "\n\nGreat news! The weather in San Francisco is sunny today. It's a beautiful day in the city by the bay. Is there anything else you'd like to know about the weather or any other information I can help you with?", 'type': 'text', 'index': 0}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'end_turn', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-5d5fd0f1-a939-447e-801a-9aaa812322d3', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 464, 'output_tokens': 50, 'total_tokens': 514}}]}
|
||||
|
||||
## Edit Tool Call
|
||||
|
||||
Let's now say we want to edit the tool call. E.g. change some of the parameters (or even the tool called!) but then execute that tool.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]}
|
||||
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
"agent",
|
||||
input=input,
|
||||
stream_mode="values",
|
||||
):
|
||||
if chunk.data and chunk.event != "metadata":
|
||||
print(chunk.data)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]}
|
||||
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
{
|
||||
input: input,
|
||||
streamMode: "values",
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
if (chunk.data && chunk.event !== "metadata") {
|
||||
console.log(chunk.data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
{'messages': [{'content': "what's the weather in sf?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'cec11391-84da-464b-bd2a-bd4f0d93b9ee', 'example': False}]}
|
||||
{'messages': [{'content': "what's the weather in sf?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'cec11391-84da-464b-bd2a-bd4f0d93b9ee', 'example': False}, {'content': [{'text': 'To get the weather information for San Francisco, I can use the weather_search function. Let me do that for you.', 'type': 'text', 'index': 0}, {'id': 'toolu_01SunSpDurNfcnXppWLPrtjC', 'input': {}, 'name': 'weather_search', 'type': 'tool_use', 'index': 1, 'partial_json': '{"city": "San Francisco"}'}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'tool_use', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-6326da9f-6061-4e12-8586-482e32ab4cab', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco'}, 'id': 'toolu_01SunSpDurNfcnXppWLPrtjC', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 360, 'output_tokens': 80, 'total_tokens': 440}}]}
|
||||
|
||||
|
||||
To do this, we first need to update the state. We can do this by passing a message in with the **same** id of the message we want to overwrite. This will have the effect of **replacing** that old message. Note that this is only possible because of the **reducer** we are using that replaces messages with the same ID - read more about that [here](https://langchain-ai.github.io/langgraph/concepts/low_level/#working-with-messages-in-graph-state).
|
||||
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
# To get the ID of the message we want to replace, we need to fetch the current state and find it there.
|
||||
state = await client.threads.get_state(thread['thread_id'])
|
||||
print("Current State:")
|
||||
print(state['values'])
|
||||
print("\nCurrent Tool Call ID:")
|
||||
current_content = state['values']['messages'][-1]['content']
|
||||
current_id = state['values']['messages'][-1]['id']
|
||||
tool_call_id = state['values']['messages'][-1]['tool_calls'][0]['id']
|
||||
print(tool_call_id)
|
||||
|
||||
# We now need to construct a replacement tool call.
|
||||
# We will change the argument to be `San Francisco, USA`
|
||||
# Note that we could change any number of arguments or tool names - it just has to be a valid one
|
||||
new_message = {
|
||||
"role": "assistant",
|
||||
"content": current_content,
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": tool_call_id,
|
||||
"name": "weather_search",
|
||||
"args": {"city": "San Francisco, USA"}
|
||||
}
|
||||
],
|
||||
# This is important - this needs to be the same as the message you replacing!
|
||||
# Otherwise, it will show up as a separate message
|
||||
"id": current_id
|
||||
}
|
||||
await client.threads.update_state(
|
||||
# This is the config which represents this thread
|
||||
thread['thread_id'],
|
||||
# This is the updated value we want to push
|
||||
{"messages": [new_message]},
|
||||
# We push this update acting as our human_review_node
|
||||
as_node="human_review_node"
|
||||
)
|
||||
|
||||
print("\nResuming Execution")
|
||||
# Let's now continue executing from here
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
"agent",
|
||||
input=None,
|
||||
stream_mode="values",
|
||||
):
|
||||
if chunk.data and chunk.event != "metadata":
|
||||
print(chunk.data)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const state = await client.threads.getState(thread.thread_id);
|
||||
console.log("Current State:");
|
||||
console.log(state.values);
|
||||
|
||||
console.log("\nCurrent Tool Call ID:");
|
||||
const lastMessage = state.values.messages[state.values.messages.length - 1];
|
||||
const currentContent = lastMessage.content;
|
||||
const currentId = lastMessage.id;
|
||||
const toolCallId = lastMessage.tool_calls[0].id;
|
||||
console.log(toolCallId);
|
||||
|
||||
// Construct a replacement tool call
|
||||
const newMessage = {
|
||||
role: "assistant",
|
||||
content: currentContent,
|
||||
tool_calls: [
|
||||
{
|
||||
id: toolCallId,
|
||||
name: "weather_search",
|
||||
args: { city: "San Francisco, USA" }
|
||||
}
|
||||
],
|
||||
// Ensure the ID is the same as the message you're replacing
|
||||
id: currentId
|
||||
};
|
||||
|
||||
await client.threads.updateState(
|
||||
thread.thread_id, // Thread ID
|
||||
{
|
||||
values: { "messages": [newMessage] }, // Updated message
|
||||
asNode: "human_review_node"
|
||||
} // Acting as human_review_node
|
||||
);
|
||||
|
||||
console.log("\nResuming Execution");
|
||||
// Continue executing from here
|
||||
const streamResponseResumed = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
{
|
||||
input: undefined,
|
||||
streamMode: "values",
|
||||
interruptBefore: ["action"],
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponseResumed) {
|
||||
if (chunk.data && chunk.event !== "metadata") {
|
||||
console.log(chunk.data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
Current State:
|
||||
{'messages': [{'content': "what's the weather in sf?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '8713d1fa-9b26-4eab-b768-dafdaac70590', 'example': False}, {'content': [{'text': 'To get the weather information for San Francisco, I can use the weather_search function. Let me do that for you.', 'type': 'text', 'index': 0}, {'id': 'toolu_01VzagzsUGZsNMwW1wHkcw7h', 'input': {}, 'name': 'weather_search', 'type': 'tool_use', 'index': 1, 'partial_json': '{"city": "San Francisco"}'}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'tool_use', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-ede13f26-daf5-4d8f-817a-7611075bbcf1', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco'}, 'id': 'toolu_01VzagzsUGZsNMwW1wHkcw7h', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 360, 'output_tokens': 80, 'total_tokens': 440}}]}
|
||||
|
||||
Current Tool Call ID:
|
||||
toolu_01VzagzsUGZsNMwW1wHkcw7h
|
||||
|
||||
Resuming Execution
|
||||
{'messages': [{'content': "what's the weather in sf?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '8713d1fa-9b26-4eab-b768-dafdaac70590', 'example': False}, {'content': [{'text': 'To get the weather information for San Francisco, I can use the weather_search function. Let me do that for you.', 'type': 'text', 'index': 0}, {'id': 'toolu_01VzagzsUGZsNMwW1wHkcw7h', 'input': {}, 'name': 'weather_search', 'type': 'tool_use', 'index': 1, 'partial_json': '{"city": "San Francisco"}'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ede13f26-daf5-4d8f-817a-7611075bbcf1', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco, USA'}, 'id': 'toolu_01VzagzsUGZsNMwW1wHkcw7h', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Sunny!', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'weather_search', 'id': '7fc7d463-66bf-4555-9929-6af483de169b', 'tool_call_id': 'toolu_01VzagzsUGZsNMwW1wHkcw7h', 'artifact': None, 'status': 'success'}]}
|
||||
{'messages': [{'content': "what's the weather in sf?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '8713d1fa-9b26-4eab-b768-dafdaac70590', 'example': False}, {'content': [{'text': 'To get the weather information for San Francisco, I can use the weather_search function. Let me do that for you.', 'type': 'text', 'index': 0}, {'id': 'toolu_01VzagzsUGZsNMwW1wHkcw7h', 'input': {}, 'name': 'weather_search', 'type': 'tool_use', 'index': 1, 'partial_json': '{"city": "San Francisco"}'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ede13f26-daf5-4d8f-817a-7611075bbcf1', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco, USA'}, 'id': 'toolu_01VzagzsUGZsNMwW1wHkcw7h', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Sunny!', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'weather_search', 'id': '7fc7d463-66bf-4555-9929-6af483de169b', 'tool_call_id': 'toolu_01VzagzsUGZsNMwW1wHkcw7h', 'artifact': None, 'status': 'success'}, {'content': [{'text': "\n\nBased on the search result, the weather in San Francisco is sunny! It's a beautiful day in the city by the bay. Is there anything else you'd like to know about the weather or any other information I can help you with?", 'type': 'text', 'index': 0}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'end_turn', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-d90ce97a-39f9-4330-985e-67c5f351a0c5', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 455, 'output_tokens': 52, 'total_tokens': 507}}]}
|
||||
|
||||
## Give feedback to a tool call
|
||||
|
||||
Sometimes, you may not want to execute a tool call, but you also may not want to ask the user to manually modify the tool call. In that case it may be better to get natural language feedback from the user. You can then insert these feedback as a mock **RESULT** of the tool call.
|
||||
|
||||
There are multiple ways to do this:
|
||||
|
||||
You could add a new message to the state (representing the "result" of a tool call)
|
||||
You could add TWO new messages to the state - one representing an "error" from the tool call, other HumanMessage representing the feedback
|
||||
Both are similar in that they involve adding messages to the state. The main difference lies in the logic AFTER the `human_node` and how it handles different types of messages.
|
||||
|
||||
For this example we will just add a single tool call representing the feedback. Let's see this in action!
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]}
|
||||
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
"agent",
|
||||
input=input,
|
||||
stream_mode="values",
|
||||
):
|
||||
if chunk.data and chunk.event != "metadata":
|
||||
print(chunk.data)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]}
|
||||
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
{
|
||||
input: input,
|
||||
streamMode: "values",
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
if (chunk.data && chunk.event !== "metadata") {
|
||||
console.log(chunk.data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
{'messages': [{'content': "what's the weather in sf?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c80f13d0-674d-4233-b6a0-3940509d3cf3', 'example': False}]}
|
||||
{'messages': [{'content': "what's the weather in sf?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c80f13d0-674d-4233-b6a0-3940509d3cf3', 'example': False}, {'content': [{'text': 'To get the weather information for San Francisco, I can use the weather_search function. Let me do that for you.', 'type': 'text', 'index': 0}, {'id': 'toolu_016XyTdFA8NuPWeLyZPSzoM3', 'input': {}, 'name': 'weather_search', 'type': 'tool_use', 'index': 1, 'partial_json': '{"city": "San Francisco"}'}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'tool_use', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-4911ac27-3d7c-4edf-a3ca-c2908e3922eb', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco'}, 'id': 'toolu_016XyTdFA8NuPWeLyZPSzoM3', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 360, 'output_tokens': 80, 'total_tokens': 440}}]}
|
||||
|
||||
To do this, we first need to update the state. We can do this by passing a message in with the same **tool call id** of the tool call we want to respond to. Note that this is a **different*** ID from above
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
# To get the ID of the message we want to replace, we need to fetch the current state and find it there.
|
||||
state = await client.threads.get_state(thread['thread_id'])
|
||||
print("Current State:")
|
||||
print(state['values'])
|
||||
print("\nCurrent Tool Call ID:")
|
||||
tool_call_id = state['values']['messages'][-1]['tool_calls'][0]['id']
|
||||
print(tool_call_id)
|
||||
|
||||
# We now need to construct a replacement tool call.
|
||||
# We will change the argument to be `San Francisco, USA`
|
||||
# Note that we could change any number of arguments or tool names - it just has to be a valid one
|
||||
new_message = {
|
||||
"role": "tool",
|
||||
# This is our natural language feedback
|
||||
"content": "User requested changes: pass in the country as well",
|
||||
"name": "weather_search",
|
||||
"tool_call_id": tool_call_id
|
||||
}
|
||||
await client.threads.update_state(
|
||||
# This is the config which represents this thread
|
||||
thread['thread_id'],
|
||||
# This is the updated value we want to push
|
||||
{"messages": [new_message]},
|
||||
# We push this update acting as our human_review_node
|
||||
as_node="human_review_node"
|
||||
)
|
||||
|
||||
print("\nResuming execution")
|
||||
# Let's now continue executing from here
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
"agent",
|
||||
input=None,
|
||||
stream_mode="values",
|
||||
):
|
||||
if chunk.data and chunk.event != "metadata":
|
||||
print(chunk.data)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const state = await client.threads.getState(thread.thread_id);
|
||||
console.log("Current State:");
|
||||
console.log(state.values);
|
||||
|
||||
console.log("\nCurrent Tool Call ID:");
|
||||
const lastMessage = state.values.messages[state.values.messages.length - 1];
|
||||
const toolCallId = lastMessage.tool_calls[0].id;
|
||||
console.log(toolCallId);
|
||||
|
||||
// Construct a replacement tool call
|
||||
const newMessage = {
|
||||
role: "tool",
|
||||
content: "User requested changes: pass in the country as well",
|
||||
name: "weather_search",
|
||||
tool_call_id: toolCallId,
|
||||
};
|
||||
|
||||
await client.threads.updateState(
|
||||
thread.thread_id, // Thread ID
|
||||
{
|
||||
values: { "messages": [newMessage] }, // Updated message
|
||||
asNode: "human_review_node"
|
||||
} // Acting as human_review_node
|
||||
);
|
||||
|
||||
console.log("\nResuming Execution");
|
||||
// Continue executing from here
|
||||
const streamResponseEdited = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
{
|
||||
input: undefined,
|
||||
streamMode: "values",
|
||||
interruptBefore: ["action"],
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponseEdited) {
|
||||
if (chunk.data && chunk.event !== "metadata") {
|
||||
console.log(chunk.data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
Current State:
|
||||
{'messages': [{'content': "what's the weather in sf?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '3b2bbc38-d11b-49eb-80c0-c24a40dab5a8', 'example': False}, {'content': [{'text': 'To get the weather information for San Francisco, I can use the weather_search function. Let me do that for you.', 'type': 'text', 'index': 0}, {'id': 'toolu_01NNw18j57GEGPZvsa9f1wvX', 'input': {}, 'name': 'weather_search', 'type': 'tool_use', 'index': 1, 'partial_json': '{"city": "San Francisco"}'}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'tool_use', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-c5a50900-abf5-4885-9cdb-da2bf0d892ac', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco'}, 'id': 'toolu_01NNw18j57GEGPZvsa9f1wvX', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 360, 'output_tokens': 80, 'total_tokens': 440}}]}
|
||||
|
||||
Current Tool Call ID:
|
||||
toolu_01NNw18j57GEGPZvsa9f1wvX
|
||||
|
||||
Resuming execution
|
||||
{'messages': [{'content': "what's the weather in sf?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '3b2bbc38-d11b-49eb-80c0-c24a40dab5a8', 'example': False}, {'content': [{'text': 'To get the weather information for San Francisco, I can use the weather_search function. Let me do that for you.', 'type': 'text', 'index': 0}, {'id': 'toolu_01NNw18j57GEGPZvsa9f1wvX', 'input': {}, 'name': 'weather_search', 'type': 'tool_use', 'index': 1, 'partial_json': '{"city": "San Francisco"}'}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'tool_use', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-c5a50900-abf5-4885-9cdb-da2bf0d892ac', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco'}, 'id': 'toolu_01NNw18j57GEGPZvsa9f1wvX', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 360, 'output_tokens': 80, 'total_tokens': 440}}, {'content': 'User requested changes: pass in the country as well', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'weather_search', 'id': '787288be-213c-4fd3-8503-4a009bdb1b00', 'tool_call_id': 'toolu_01NNw18j57GEGPZvsa9f1wvX', 'artifact': None, 'status': 'success'}, {'content': [{'text': '\n\nI apologize for the oversight. It seems the function requires additional information. Let me try again with a more specific request.', 'type': 'text', 'index': 0}, {'id': 'toolu_01YAbLBoKozJyRQnB8LUMpXC', 'input': {}, 'name': 'weather_search', 'type': 'tool_use', 'index': 1, 'partial_json': '{"city": "San Francisco, USA"}'}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'tool_use', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-5c355a56-cfe3-4046-b49f-f5b09fc397ef', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco, USA'}, 'id': 'toolu_01YAbLBoKozJyRQnB8LUMpXC', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 461, 'output_tokens': 83, 'total_tokens': 544}}]}
|
||||
|
||||
We can see that we now get to another breakpoint - because it went back to the model and got an entirely new prediction of what to call. Let's now approve this one and continue
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
"agent",
|
||||
input=None,
|
||||
stream_mode="values",
|
||||
):
|
||||
if chunk.data and chunk.event != "metadata":
|
||||
print(chunk.data)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const streamResponseResumed = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
{
|
||||
input: undefined,
|
||||
streamMode: "values",
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponseResumed) {
|
||||
if (chunk.data && chunk.event !== "metadata") {
|
||||
console.log(chunk.data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
{'messages': [{'content': "what's the weather in sf?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '3b2bbc38-d11b-49eb-80c0-c24a40dab5a8', 'example': False}, {'content': [{'text': 'To get the weather information for San Francisco, I can use the weather_search function. Let me do that for you.', 'type': 'text', 'index': 0}, {'id': 'toolu_01NNw18j57GEGPZvsa9f1wvX', 'input': {}, 'name': 'weather_search', 'type': 'tool_use', 'index': 1, 'partial_json': '{"city": "San Francisco"}'}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'tool_use', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-c5a50900-abf5-4885-9cdb-da2bf0d892ac', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco'}, 'id': 'toolu_01NNw18j57GEGPZvsa9f1wvX', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 360, 'output_tokens': 80, 'total_tokens': 440}}, {'content': 'User requested changes: pass in the country as well', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'weather_search', 'id': '787288be-213c-4fd3-8503-4a009bdb1b00', 'tool_call_id': 'toolu_01NNw18j57GEGPZvsa9f1wvX', 'artifact': None, 'status': 'success'}, {'content': [{'text': '\n\nI apologize for the oversight. It seems the function requires additional information. Let me try again with a more specific request.', 'type': 'text', 'index': 0}, {'id': 'toolu_01YAbLBoKozJyRQnB8LUMpXC', 'input': {}, 'name': 'weather_search', 'type': 'tool_use', 'index': 1, 'partial_json': '{"city": "San Francisco, USA"}'}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'tool_use', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-5c355a56-cfe3-4046-b49f-f5b09fc397ef', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco, USA'}, 'id': 'toolu_01YAbLBoKozJyRQnB8LUMpXC', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 461, 'output_tokens': 83, 'total_tokens': 544}}, {'content': 'Sunny!', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'weather_search', 'id': '3b857482-bca2-4a73-a9ab-1f35a3e43e5f', 'tool_call_id': 'toolu_01YAbLBoKozJyRQnB8LUMpXC', 'artifact': None, 'status': 'success'}]}
|
||||
{'messages': [{'content': "what's the weather in sf?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '3b2bbc38-d11b-49eb-80c0-c24a40dab5a8', 'example': False}, {'content': [{'text': 'To get the weather information for San Francisco, I can use the weather_search function. Let me do that for you.', 'type': 'text', 'index': 0}, {'id': 'toolu_01NNw18j57GEGPZvsa9f1wvX', 'input': {}, 'name': 'weather_search', 'type': 'tool_use', 'index': 1, 'partial_json': '{"city": "San Francisco"}'}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'tool_use', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-c5a50900-abf5-4885-9cdb-da2bf0d892ac', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco'}, 'id': 'toolu_01NNw18j57GEGPZvsa9f1wvX', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 360, 'output_tokens': 80, 'total_tokens': 440}}, {'content': 'User requested changes: pass in the country as well', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'weather_search', 'id': '787288be-213c-4fd3-8503-4a009bdb1b00', 'tool_call_id': 'toolu_01NNw18j57GEGPZvsa9f1wvX', 'artifact': None, 'status': 'success'}, {'content': [{'text': '\n\nI apologize for the oversight. It seems the function requires additional information. Let me try again with a more specific request.', 'type': 'text', 'index': 0}, {'id': 'toolu_01YAbLBoKozJyRQnB8LUMpXC', 'input': {}, 'name': 'weather_search', 'type': 'tool_use', 'index': 1, 'partial_json': '{"city": "San Francisco, USA"}'}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'tool_use', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-5c355a56-cfe3-4046-b49f-f5b09fc397ef', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco, USA'}, 'id': 'toolu_01YAbLBoKozJyRQnB8LUMpXC', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 461, 'output_tokens': 83, 'total_tokens': 544}}, {'content': 'Sunny!', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'weather_search', 'id': '3b857482-bca2-4a73-a9ab-1f35a3e43e5f', 'tool_call_id': 'toolu_01YAbLBoKozJyRQnB8LUMpXC', 'artifact': None, 'status': 'success'}, {'content': [{'text': "\n\nGreat news! The weather in San Francisco is sunny today. Is there anything else you'd like to know about the weather or any other information I can help you with?", 'type': 'text', 'index': 0}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'end_turn', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-6a857bb1-f65b-4b86-93d6-c025e003c777', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 557, 'output_tokens': 38, 'total_tokens': 595}}]}
|
||||
@@ -0,0 +1,383 @@
|
||||
# How to Replay and Branch from Prior States
|
||||
|
||||
With LangGraph Cloud you have the ability to return to any of your prior states and either re-run the graph to reproduce issues noticed during testing, or branch out in a different way from what was originally done in the prior states. In this guide we will show a quick example of how to rerun past states and how to branch off from previous states as well.
|
||||
|
||||
## Setup
|
||||
|
||||
We are not going to show the full code for the graph we are hosting, but you can see it [here](../../how-tos/human_in_the_loop/time-travel.ipynb#build-the-agent) if you want to. Once this graph is hosted, we are ready to invoke it and wait for user input.
|
||||
|
||||
### SDK initialization
|
||||
|
||||
First, we need to setup our client so that we can communicate with our hosted graph:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
assistant_id = "agent"
|
||||
thread = await client.threads.create()
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
const assistantId = agent;
|
||||
const thread = await client.threads.create();
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data {}
|
||||
```
|
||||
|
||||
## Replay a state
|
||||
|
||||
### Initial invocation
|
||||
|
||||
Before replaying a state - we need to create states to replay from! In order to do this, let's invoke our graph with a simple message:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
input = {"messages": [{"role": "user", "content": "Please search the weather in SF"}]}
|
||||
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistant_id, # graph_id
|
||||
input=input,
|
||||
stream_mode="updates",
|
||||
):
|
||||
if chunk.data and chunk.event != "metadata":
|
||||
print(chunk.data)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const input = { "messages": [{ "role": "human", "content": "Please search the weather in SF" }] }
|
||||
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
{
|
||||
input: input,
|
||||
streamMode: "updates",
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
if (chunk.data && chunk.event !== "metadata") {
|
||||
console.log(chunk.data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"Please search the weather in SF\"}]},
|
||||
\"stream_mode\": [
|
||||
\"updates\"
|
||||
]
|
||||
}" | \
|
||||
sed 's/\r$//' | \
|
||||
awk '
|
||||
/^event:/ {
|
||||
if (data_content != "" && event_type != "metadata") {
|
||||
print data_content "\n"
|
||||
}
|
||||
sub(/^event: /, "", $0)
|
||||
event_type = $0
|
||||
data_content = ""
|
||||
}
|
||||
/^data:/ {
|
||||
sub(/^data: /, "", $0)
|
||||
data_content = $0
|
||||
}
|
||||
END {
|
||||
if (data_content != "" && event_type != "metadata") {
|
||||
print data_content "\n"
|
||||
}
|
||||
}
|
||||
'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
{'agent': {'messages': [{'content': [{'text': "Certainly! I'll use the search function to look up the current weather in San Francisco for you. Let me do that now.", 'type': 'text'}, {'id': 'toolu_011vroKUtWU7SBdrngpgpFMn', 'input': {'query': 'current weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ee639877-d97d-40f8-96dc-d0d1ae22d203', 'example': False, 'tool_calls': [{'name': 'search', 'args': {'query': 'current weather in San Francisco'}, 'id': 'toolu_011vroKUtWU7SBdrngpgpFMn'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
|
||||
{'action': {'messages': [{'content': '["I looked up: current weather in San Francisco. Result: It\'s sunny in San Francisco, but you better look out if you\'re a Gemini 😈."]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': '7bad0e72-5ebe-4b08-9b8a-b99b0fe22fb7', 'tool_call_id': 'toolu_011vroKUtWU7SBdrngpgpFMn'}]}}
|
||||
{'agent': {'messages': [{'content': "Based on the search results, I can provide you with information about the current weather in San Francisco:\n\nThe weather in San Francisco is currently sunny. This is great news for outdoor activities and enjoying the city's beautiful sights.\n\nIt's worth noting that the search result included an unusual comment about Geminis, which isn't typically part of a weather report. This might be due to the search engine including some astrological information or a joke in its results. However, for the purpose of answering your question about the weather, we can focus on the fact that it's sunny in San Francisco.\n\nIf you need any more specific information about the weather in San Francisco, such as temperature, wind speed, or forecast for the coming days, please let me know, and I'd be happy to search for that information for you.", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-dbac539a-33c8-4f0c-9e20-91f318371e7c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
|
||||
|
||||
|
||||
Now let's get our list of states, and invoke from the third state (right before the tool get called):
|
||||
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
states = await client.threads.get_history(thread['thread_id'])
|
||||
|
||||
# We can confirm that this state is correct by checking the 'next' attribute and seeing that it is the tool call node
|
||||
state_to_replay = states[2]
|
||||
print(state_to_replay['next'])
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const states = await client.threads.getHistory(thread['thread_id']);
|
||||
|
||||
// We can confirm that this state is correct by checking the 'next' attribute and seeing that it is the tool call node
|
||||
const stateToReplay = states[2];
|
||||
console.log(stateToReplay['next']);
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request GET --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/history | jq -r '.[2].next'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
['action']
|
||||
|
||||
|
||||
|
||||
To rerun from a state, we need first issue an empty update to the thread state. Then we need to pass in the resulting `checkpoint_id` as follows:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
state_to_replay = states[2]
|
||||
updated_config = await client.threads.update_state(
|
||||
thread["thread_id"],
|
||||
{"messages": []},
|
||||
checkpoint_id=state_to_replay["checkpoint_id"]
|
||||
)
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistant_id, # graph_id
|
||||
input=None,
|
||||
stream_mode="updates",
|
||||
checkpoint_id=updated_config["checkpoint_id"]
|
||||
):
|
||||
if chunk.data and chunk.event != "metadata":
|
||||
print(chunk.data)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const stateToReplay = states[2];
|
||||
const config = await client.threads.updateState(thread["thread_id"], { values: {"messages": [] }, checkpointId: stateToReplay["checkpoint_id"] });
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
{
|
||||
input: null,
|
||||
streamMode: "updates",
|
||||
checkpointId: config["checkpoint_id"]
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
if (chunk.data && chunk.event !== "metadata") {
|
||||
console.log(chunk.data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request GET --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/history | jq -c '
|
||||
.[2] as $state_to_replay |
|
||||
{
|
||||
values: { messages: .[2].values.messages[-1] },
|
||||
checkpoint_id: $state_to_replay.checkpoint_id
|
||||
}' | \
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/state \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data @- | jq .checkpoint_id | \
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"checkpoint_id\": \"$1\",
|
||||
\"stream_mode\": [
|
||||
\"updates\"
|
||||
]
|
||||
}" | \
|
||||
sed 's/\r$//' | \
|
||||
awk '
|
||||
/^event:/ {
|
||||
if (data_content != "" && event_type != "metadata") {
|
||||
print data_content "\n"
|
||||
}
|
||||
sub(/^event: /, "", $0)
|
||||
event_type = $0
|
||||
data_content = ""
|
||||
}
|
||||
/^data:/ {
|
||||
sub(/^data: /, "", $0)
|
||||
data_content = $0
|
||||
}
|
||||
END {
|
||||
if (data_content != "" && event_type != "metadata") {
|
||||
print data_content "\n"
|
||||
}
|
||||
}
|
||||
'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
{'action': {'messages': [{'content': '["I looked up: current weather in San Francisco. Result: It\'s sunny in San Francisco, but you better look out if you\'re a Gemini 😈."]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': 'eba650e5-400e-4938-8508-f878dcbcc532', 'tool_call_id': 'toolu_011vroKUtWU7SBdrngpgpFMn'}]}}
|
||||
{'agent': {'messages': [{'content': "Based on the search results, I can provide you with information about the current weather in San Francisco:\n\nThe weather in San Francisco is currently sunny. This is great news if you're planning any outdoor activities or simply want to enjoy a pleasant day in the city.\n\nIt's worth noting that the search result included an unusual comment about Geminis, which doesn't seem directly related to the weather. This appears to be a playful or humorous addition to the weather report, possibly from the source where this information was obtained.\n\nIs there anything else you'd like to know about the weather in San Francisco or any other information you need?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-bc6dca3f-a1e2-4f59-a69b-fe0515a348bb', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
|
||||
|
||||
|
||||
As we can see, the graph restarted from the tool node with the same input as our original graph run.
|
||||
|
||||
## Branch off from previous state
|
||||
|
||||
Using LangGraph's checkpointing, you can do more than just replay past states. You can branch off previous locations to let the agent explore alternate trajectories or to let a user "version control" changes in a workflow.
|
||||
|
||||
Let's show how to do this to edit the state at a particular point in time. Let's update the state to change the input to the tool
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
# Let's now get the last message in the state
|
||||
# This is the one with the tool calls that we want to update
|
||||
last_message = state_to_replay['values']['messages'][-1]
|
||||
|
||||
# Let's now update the args for that tool call
|
||||
last_message['tool_calls'][0]['args'] = {'query': 'current weather in SF'}
|
||||
|
||||
config = await client.threads.update_state(thread['thread_id'],{"messages":[last_message]},checkpoint_id=state_to_replay['checkpoint_id'])
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
// Let's now get the last message in the state
|
||||
// This is the one with the tool calls that we want to update
|
||||
let lastMessage = stateToReplay['values']['messages'][-1];
|
||||
|
||||
// Let's now update the args for that tool call
|
||||
lastMessage['tool_calls'][0]['args'] = { 'query': 'current weather in SF' };
|
||||
|
||||
const config = await client.threads.updateState(thread['thread_id'], { values: { "messages": [lastMessage] }, checkpointId: stateToReplay['checkpoint_id'] });
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl -s --request GET --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/history | \
|
||||
jq -c '
|
||||
.[2] as $state_to_replay |
|
||||
.[2].values.messages[-1].tool_calls[0].args.query = "current weather in SF" |
|
||||
{
|
||||
values: { messages: .[2].values.messages[-1] },
|
||||
checkpoint_id: $state_to_replay.checkpoint_id
|
||||
}' | \
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/state \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data @-
|
||||
```
|
||||
|
||||
Now we can rerun our graph with this new config, starting from the `new_state`, which is a branch of our `state_to_replay`:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistant["assistant_id"], # graph_id
|
||||
input=None,
|
||||
stream_mode="updates",
|
||||
checkpoint_id=config['checkpoint_id']
|
||||
):
|
||||
if chunk.data and chunk.event != "metadata":
|
||||
print(chunk.data)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistant["assistant_id"],
|
||||
{
|
||||
input: null,
|
||||
streamMode: "updates",
|
||||
checkpointId: config['checkpoint_id'],
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
if (chunk.data && chunk.event !== "metadata") {
|
||||
console.log(chunk.data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl -s --request GET --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/state | \
|
||||
jq -c '.checkpoint_id' | \
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"checkpoint_id\": \"$1\",
|
||||
\"stream_mode\": [
|
||||
\"updates\"
|
||||
]
|
||||
}" | \
|
||||
sed 's/\r$//' | \
|
||||
awk '
|
||||
/^event:/ {
|
||||
if (data_content != "" && event_type != "metadata") {
|
||||
print data_content "\n"
|
||||
}
|
||||
sub(/^event: /, "", $0)
|
||||
event_type = $0
|
||||
data_content = ""
|
||||
}
|
||||
/^data:/ {
|
||||
sub(/^data: /, "", $0)
|
||||
data_content = $0
|
||||
}
|
||||
END {
|
||||
if (data_content != "" && event_type != "metadata") {
|
||||
print data_content "\n"
|
||||
}
|
||||
}
|
||||
'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
|
||||
{'action': {'messages': [{'content': '["I looked up: current weather in SF. Result: It\'s sunny in San Francisco, but you better look out if you\'re a Gemini 😈."]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': '2baf9941-4fda-4081-9f87-d76795d289f1', 'tool_call_id': 'toolu_011vroKUtWU7SBdrngpgpFMn'}]}}
|
||||
{'agent': {'messages': [{'content': "Based on the search results, I can provide you with information about the current weather in San Francisco (SF):\n\nThe weather in San Francisco is currently sunny. This means it's a clear day with plenty of sunshine. \n\nIt's worth noting that the specific temperature wasn't provided in the search result, but sunny weather in San Francisco typically means comfortable temperatures. San Francisco is known for its mild climate, so even on sunny days, it's often not too hot.\n\nThe search result also included a playful reference to astrological signs, mentioning Gemini. However, this is likely just a joke or part of the search engine's presentation and not related to the actual weather conditions.\n\nIs there any specific information about the weather in San Francisco you'd like to know more about? I'd be happy to perform another search if you need details on temperature, wind conditions, or the forecast for the coming days.", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-a83de52d-ed18-4402-9384-75c462485743', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
|
||||
|
||||
|
||||
As we can see, the search query changed from San Francisco to SF, just as we had hoped!
|
||||
@@ -0,0 +1,264 @@
|
||||
# How to Wait for User Input
|
||||
|
||||
One of the main human-in-the-loop interaction patterns is waiting for human input. A key use case involves asking the user clarifying questions. One way to accomplish this is simply go to the `END` node and exit the graph. Then, any user response comes back in as fresh invocation of the graph. This is basically just creating a chatbot architecture.
|
||||
|
||||
The issue with this is it is tough to resume back in a particular point in the graph. Often times the agent is halfway through some process, and just needs a bit of a user input. Although it is possible to design your graph in such a way where you have a `conditional_entry_point` to route user messages back to the right place, that is not super scalable (as it essentially involves having a routing function that can end up almost anywhere).
|
||||
|
||||
A separate way to do this is to have a node explicitly for getting user input. This is easy to implement in a notebook setting - you just put an `input()` call in the node. But that isn't exactly production ready.
|
||||
|
||||
Luckily, LangGraph makes it possible to do similar things in a production way. The basic idea is:
|
||||
|
||||
- Set up a node that represents human input. This can have specific incoming/outgoing edges (as you desire). There shouldn't actually be any logic inside this node.
|
||||
- Add a breakpoint before the node. This will stop the graph before this node executes (which is good, because there's no real logic in it anyways)
|
||||
- Use `.update_state` to update the state of the graph. Pass in whatever human response you get. The key here is to use the `as_node` parameter to apply this update **as if you were that node**. This will have the effect of making it so that when you resume execution next it resumes as if that node just acted, and not from the beginning.
|
||||
|
||||
## Setup
|
||||
|
||||
We are not going to show the full code for the graph we are hosting, but you can see it [here](../../how-tos/human_in_the_loop/wait-user-input.ipynb#build-the-agent) if you want to. Once this graph is hosted, we are ready to invoke it and wait for user input.
|
||||
|
||||
### SDK initialization
|
||||
|
||||
First, we need to setup our client so that we can communicate with our hosted graph:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
assistant_id = "agent"
|
||||
thread = await client.threads.create()
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
const assistantId = "agent";
|
||||
const thread = await client.threads.create();
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
## Waiting for user input
|
||||
|
||||
### Initial invocation
|
||||
|
||||
Now, let's invoke our graph by interrupting before `ask_human` node:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
input = { 'messages':[{ "role":"user", "content":"Use the search tool to ask the user where they are, then look up the weather there" }] }
|
||||
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistant_id,
|
||||
input=input,
|
||||
stream_mode="updates",
|
||||
interrupt_before=["ask_human"],
|
||||
):
|
||||
if chunk.data and chunk.event != "metadata":
|
||||
print(chunk.data)
|
||||
```
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const input = { "messages":[{ "role":"human", "content": "Use the search tool to ask the user where they are, then look up the weather there"}] }
|
||||
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
{
|
||||
input: input,
|
||||
streamMode: "updates",
|
||||
interruptBefore: ["ask_human"],
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
if (chunk.data && chunk.event !== "metadata") {
|
||||
console.log(chunk.data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"Use the search tool to ask the user where they are, then look up the weather there\"}]},
|
||||
\"interrupt_before\": [\"ask_human\"],
|
||||
\"stream_mode\": [
|
||||
\"updates\"
|
||||
]
|
||||
}" | \
|
||||
sed 's/\r$//' | \
|
||||
awk '
|
||||
/^event:/ {
|
||||
if (data_content != "" && event_type != "metadata") {
|
||||
print data_content "\n"
|
||||
}
|
||||
sub(/^event: /, "", $0)
|
||||
event_type = $0
|
||||
data_content = ""
|
||||
}
|
||||
/^data:/ {
|
||||
sub(/^data: /, "", $0)
|
||||
data_content = $0
|
||||
}
|
||||
END {
|
||||
if (data_content != "" && event_type != "metadata") {
|
||||
print data_content "\n"
|
||||
}
|
||||
}
|
||||
'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
{'agent': {'messages': [{'content': [{'text': "Certainly! I'll use the AskHuman function to ask the user about their location, and then I'll use the search function to look up the weather for that location. Let's start by asking the user where they are.", 'type': 'text'}, {'id': 'toolu_01RFahzYPvnPWTb2USk2RdKR', 'input': {'question': 'Where are you currently located?'}, 'name': 'AskHuman', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-a8422215-71d3-4093-afb4-9db141c94ddb', 'example': False, 'tool_calls': [{'name': 'AskHuman', 'args': {'question': 'Where are you currently located?'}, 'id': 'toolu_01RFahzYPvnPWTb2USk2RdKR'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
|
||||
|
||||
|
||||
### Adding user input to state
|
||||
|
||||
We now want to update this thread with a response from the user. We then can kick off another run.
|
||||
|
||||
Because we are treating this as a tool call, we will need to update the state as if it is a response from a tool call. In order to do this, we will need to check the state to get the ID of the tool call.
|
||||
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
state = await client.threads.get_state(thread['thread_id'])
|
||||
tool_call_id = state['values']['messages'][-1]['tool_calls'][0]['id']
|
||||
|
||||
# We now create the tool call with the id and the response we want
|
||||
tool_message = [{"tool_call_id": tool_call_id, "type": "tool", "content": "san francisco"}]
|
||||
|
||||
await client.threads.update_state(thread['thread_id'], {"messages": tool_message}, as_node="ask_human")
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const state = await client.threads.getState(thread['thread_id']);
|
||||
const toolCallId = state['values']['messages'][-1]['tool_calls'][0]['id'];
|
||||
|
||||
# We now create the tool call with the id and the response we want
|
||||
const toolMessage = [{"tool_call_id": toolCallId, "type": "tool", "content": "san francisco"}];
|
||||
|
||||
await client.threads.updateState(thread['thread_id'], {values: {"messages": toolMessage}, asNode:"ask_human"})
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request GET \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/state \
|
||||
| jq -r '.values.messages[-1].tool_calls[0].id' \
|
||||
| sh -c '
|
||||
TOOL_CALL_ID="$1"
|
||||
|
||||
# Construct the JSON payload
|
||||
JSON_PAYLOAD=$(printf "{\"messages\": [{\"tool_call_id\": \"%s\", \"type\": \"tool\", \"content\": \"san francisco\"}], \"as_node\": \"ask_human\"}" "$TOOL_CALL_ID")
|
||||
|
||||
# Send the updated state
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/state \
|
||||
--header "Content-Type: application/json" \
|
||||
--data "${JSON_PAYLOAD}"
|
||||
' _
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
{'configurable': {'thread_id': 'a9f322ae-4ed1-41ec-942b-38cb3d342c3a',
|
||||
'checkpoint_ns': '',
|
||||
'checkpoint_id': '1ef58e97-a623-63dd-8002-39a9a9b20be3'}}
|
||||
|
||||
|
||||
### Invoking after receiving human input
|
||||
|
||||
We can now tell the agent to continue. We can just pass in None as the input to the graph, since no additional input is needed:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistant_id,
|
||||
input=None,
|
||||
stream_mode="updates",
|
||||
):
|
||||
if chunk.data and chunk.event != "metadata":
|
||||
print(chunk.data)
|
||||
```
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
{
|
||||
input: null,
|
||||
streamMode: "updates",
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
if (chunk.data && chunk.event !== "metadata") {
|
||||
console.log(chunk.data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"stream_mode\": [
|
||||
\"updates\"
|
||||
]
|
||||
}"| \
|
||||
sed 's/\r$//' | \
|
||||
awk '
|
||||
/^event:/ {
|
||||
if (data_content != "" && event_type != "metadata") {
|
||||
print data_content "\n"
|
||||
}
|
||||
sub(/^event: /, "", $0)
|
||||
event_type = $0
|
||||
data_content = ""
|
||||
}
|
||||
/^data:/ {
|
||||
sub(/^data: /, "", $0)
|
||||
data_content = $0
|
||||
}
|
||||
END {
|
||||
if (data_content != "" && event_type != "metadata") {
|
||||
print data_content "\n"
|
||||
}
|
||||
}
|
||||
'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
{'agent': {'messages': [{'content': [{'text': "Thank you for letting me know that you're in San Francisco. Now, I'll use the search function to look up the weather in San Francisco.", 'type': 'text'}, {'id': 'toolu_01K57ofmgG2wyJ8tYJjbq5k7', 'input': {'query': 'current weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-241baed7-db5e-44ce-ac3c-56431705c22b', 'example': False, 'tool_calls': [{'name': 'search', 'args': {'query': 'current weather in San Francisco'}, 'id': 'toolu_01K57ofmgG2wyJ8tYJjbq5k7'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
|
||||
{'action': {'messages': [{'content': '["I looked up: current weather in San Francisco. Result: It\'s sunny in San Francisco, but you better look out if you\'re a Gemini 😈."]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': '8b699b95-8546-4557-8e66-14ea71a15ed8', 'tool_call_id': 'toolu_01K57ofmgG2wyJ8tYJjbq5k7'}]}}
|
||||
{'agent': {'messages': [{'content': "Based on the search results, I can provide you with information about the current weather in San Francisco:\n\nThe weather in San Francisco is currently sunny. It's a beautiful day in the city! \n\nHowever, I should note that the search result included an unusual comment about Gemini zodiac signs. This appears to be either a joke or potentially irrelevant information added by the search engine. For accurate and detailed weather information, you might want to check a reliable weather service or app for San Francisco.\n\nIs there anything else you'd like to know about the weather or San Francisco?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-b4d7309f-f849-46aa-b6ef-475bcabd2be9', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
|
||||
|
||||
|
Before Width: | Height: | Size: 62 MiB |
|
After Width: | Height: | Size: 721 KiB |
|
Before Width: | Height: | Size: 39 MiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 79 MiB |
|
After Width: | Height: | Size: 267 KiB |
|
Before Width: | Height: | Size: 10 MiB |
|
After Width: | Height: | Size: 355 KiB |
@@ -11,46 +11,51 @@ Welcome to the LangGraph Cloud how-to guides! These guides provide practical, st
|
||||
|
||||
LangGraph Cloud gives you best in class observability, testing, and hosting services. Read more about them in these how to guides:
|
||||
|
||||
- [How to set up app for deployment](https://langchain-ai.github.io/langgraph/cloud/deployment/setup/)
|
||||
- [How to deploy to LangGraph cloud](https://langchain-ai.github.io/langgraph/cloud/deployment/cloud/)
|
||||
- [How to self-host](https://langchain-ai.github.io/langgraph/cloud/deployment/self_hosted/)
|
||||
- [How to set up app for deployment (requirements.txt)](../deployment/setup.md)
|
||||
- [How to set up app for deployment (pyproject.toml)](../deployment/setup_pyproject.md)
|
||||
- [How to test locally](../deployment/test_locally.md)
|
||||
- [How to deploy to LangGraph cloud](../deployment/cloud.md)
|
||||
- [How to self-host](../deployment/self_hosted.md)
|
||||
|
||||
|
||||
## Streaming
|
||||
|
||||
Streaming the results of your LLM application is vital for ensuring a good user experience, especially when your graph may call multiple models and take a long time to fully complete a run. Read about how to stream values from your graph in these how to guides:
|
||||
|
||||
- [How to stream values](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/stream_values/)
|
||||
- [How to stream updates](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/stream_updates/)
|
||||
- [How to stream messages](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/stream_messages/)
|
||||
- [How to stream events](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/stream_events/)
|
||||
- [How to stream in debug mode](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/stream_debug/)
|
||||
- [How to stream multiple modes](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/stream_multiple/)
|
||||
- [How to stream values](./stream_values.md)
|
||||
- [How to stream updates](./stream_updates.md)
|
||||
- [How to stream messages](./stream_messages.md)
|
||||
- [How to stream events](./stream_events.md)
|
||||
- [How to stream in debug mode](./stream_debug.md)
|
||||
- [How to stream multiple modes](./stream_multiple.md)
|
||||
|
||||
## Double-texting
|
||||
|
||||
Graph execution can take a while, and sometimes users may change their mind about the input they wanted to send before their original input has finished running. For example, a user might notice a typo in their original request and will edit the prompt and resend it. Deciding what to do in these cases is important for ensuring a smooth user experience and preventing your graphs from behaving in unexpected ways. The following how-to guides provide information on the various options LangGraph Cloud gives you for dealing with double-texting:
|
||||
|
||||
- [How to use the interrupt option](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/interrupt_concurrent/)
|
||||
- [How to use the rollback option](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/rollback_concurrent/)
|
||||
- [How to use the reject option](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/reject_concurrent/)
|
||||
- [How to use the rnqueue option](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/enqueue_concurrent/)
|
||||
- [How to use the interrupt option](./interrupt_concurrent.md)
|
||||
- [How to use the rollback option](./rollback_concurrent.md)
|
||||
- [How to use the reject option](./reject_concurrent.md)
|
||||
- [How to use the enqueue option](./enqueue_concurrent.md)
|
||||
|
||||
## Human-in-the-loop
|
||||
|
||||
When creating complex graphs, leaving every decision up to the LLM can be dangerous, especially when the decisions involve invoking certain tools or accessing specific documents. To remedy this, LangGraph allows you to insert human-in-the-loop behavior to ensure your graph does not have undesired outcomes. Read more about the different ways you can add human-in-the-loop capabilities to your LangGraph Cloud projects in these how-to guides:
|
||||
|
||||
- [How to add a breakpoint](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/human_in_the_loop_breakpoint/)
|
||||
- [How to wait for user input](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/human_in_the_loop_user_input/)
|
||||
- [How to edit graph state](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/human_in_the_loop_edit_state/)
|
||||
- [How to replay and branch from prior states](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/human_in_the_loop_time_travel/)
|
||||
- [How to add a breakpoint](./human_in_the_loop_breakpoint.md)
|
||||
- [How to wait for user input](./human_in_the_loop_user_input.md)
|
||||
- [How to edit graph state](./human_in_the_loop_edit_state.md)
|
||||
- [How to replay and branch from prior states](./human_in_the_loop_time_travel.md)
|
||||
- [How to review tool calls](./human_in_the_loop_review_tool_calls.md)
|
||||
|
||||
## LangGraph Studio
|
||||
|
||||
LangGraph Studio is a built-in UI for visualizing, testing, and debugging your agents.
|
||||
|
||||
- [How to enter LangGraph Studio](https://langchain-ai.github.io/langgraph/cloud/how-tos/test_deployment/)
|
||||
- [How to test your graph in LangGraph Studio](https://langchain-ai.github.io/langgraph/cloud/how-tos/invoke_studio/)
|
||||
- [Interact with threads in LangGraph Studio](https://langchain-ai.github.io/langgraph/cloud/how-tos/threads_studio/)
|
||||
- [How to enter LangGraph Studio](./test_deployment.md)
|
||||
- [How to enter LangGraph Studio for local deployment](./test_local_deployment.md)
|
||||
- [How to test your graph in LangGraph Studio](./invoke_studio.md)
|
||||
- [Interact with threads in LangGraph Studio](./threads_studio.md)
|
||||
|
||||
## Different Types of Runs:
|
||||
|
||||
@@ -68,3 +73,5 @@ Other guides that may prove helpful!
|
||||
- [How to configure agents](cloud_examples/configuration_cloud.ipynb)
|
||||
- [How to convert LangGraph calls to LangGraph cloud calls](cloud_examples/langgraph_to_langgraph_cloud.ipynb)
|
||||
- [How to integrate webhooks](cloud_examples/webhooks.ipynb)
|
||||
- [How to copy threads](./copy_threads.md)
|
||||
- [How to check status of your threads](./check_thread_status.md)
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
## Interrupt
|
||||
|
||||
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../concepts/api.md#double-texting).
|
||||
|
||||
The guide covers the `interrupt` option for double texting, which interrupts the prior run of the graph and starts a new one with the double-text. This option does not delete the first run, but rather keeps it in the database but sets its status to `interrupted`. Below is a quick example of using the `interrupt` option.
|
||||
|
||||
First, we will define a quick helper function for printing out JS model outputs (you can skip this if using Python):
|
||||
|
||||
```js
|
||||
function prettyPrint(m) {
|
||||
const padded = " " + m['type'] + " ";
|
||||
const sepLen = Math.floor((80 - padded.length) / 2);
|
||||
const sep = "=".repeat(sepLen);
|
||||
const secondSep = sep + (padded.length % 2 ? "=" : "");
|
||||
|
||||
console.log(`${sep}${padded}${secondSep}`);
|
||||
console.log("\n\n");
|
||||
console.log(m.content);
|
||||
}
|
||||
```
|
||||
|
||||
Now, let's import our required packages and instantiate our client, assistant, and thread.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from langchain_core.messages import convert_to_messages
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
assistant_id = "agent"
|
||||
thread = await client.threads.create()
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
const assistantId = "agent";
|
||||
const thread = await client.threads.create();
|
||||
```
|
||||
|
||||
Now we can start our two runs and join the second on euntil it has completed:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
# the first run will be interrupted
|
||||
interrupted_run = await client.runs.create(
|
||||
thread["thread_id"],
|
||||
assistant_id,
|
||||
input={"messages": [{"role": "human", "content": "what's the weather in sf?"}]},
|
||||
)
|
||||
await asyncio.sleep(2)
|
||||
run = await client.runs.create(
|
||||
thread["thread_id"],
|
||||
assistant_id,
|
||||
input={"messages": [{"role": "human", "content": "what's the weather in nyc?"}]},
|
||||
multitask_strategy="interrupt",
|
||||
)
|
||||
# wait until the second run completes
|
||||
await client.runs.join(thread["thread_id"], run["run_id"])
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
// the first run will be interrupted
|
||||
let interruptedRun = await client.runs.create(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
{ input: { messages: [{ role: "human", content: "what's the weather in sf?" }] } }
|
||||
);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
let run = await client.runs.create(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
{
|
||||
input: { messages: [{ role: "human", content: "what's the weather in nyc?" }] },
|
||||
multitaskStrategy: "interrupt"
|
||||
}
|
||||
);
|
||||
|
||||
// wait until the second run completes
|
||||
await client.runs.join(thread["thread_id"], run["run_id"]);
|
||||
```
|
||||
|
||||
We can see that the thread has partial data from the first run + data from the second run
|
||||
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
state = await client.threads.get_state(thread["thread_id"])
|
||||
|
||||
for m in convert_to_messages(state["values"]["messages"]):
|
||||
m.pretty_print()
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const state = await client.threads.getState(thread["thread_id"]);
|
||||
|
||||
for (const m of state['values']['messages']) {
|
||||
prettyPrint(m);
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
================================[1m Human Message [0m=================================
|
||||
|
||||
what's the weather in sf?
|
||||
==================================[1m Ai Message [0m==================================
|
||||
|
||||
[{'id': 'toolu_01MjNtVJwEcpujRGrf3x6Pih', 'input': {'query': 'weather in san francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
|
||||
Tool Calls:
|
||||
tavily_search_results_json (toolu_01MjNtVJwEcpujRGrf3x6Pih)
|
||||
Call ID: toolu_01MjNtVJwEcpujRGrf3x6Pih
|
||||
Args:
|
||||
query: weather in san francisco
|
||||
=================================[1m Tool Message [0m=================================
|
||||
Name: tavily_search_results_json
|
||||
|
||||
[{"url": "https://www.wunderground.com/hourly/us/ca/san-francisco/KCASANFR2002/date/2024-6-18", "content": "High 64F. Winds W at 10 to 20 mph. A few clouds from time to time. Low 49F. Winds W at 10 to 20 mph. Temp. San Francisco Weather Forecasts. Weather Underground provides local & long-range weather ..."}]
|
||||
================================[1m Human Message [0m=================================
|
||||
|
||||
what's the weather in nyc?
|
||||
==================================[1m Ai Message [0m==================================
|
||||
|
||||
[{'id': 'toolu_01KtE1m1ifPLQAx4fQLyZL9Q', 'input': {'query': 'weather in new york city'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
|
||||
Tool Calls:
|
||||
tavily_search_results_json (toolu_01KtE1m1ifPLQAx4fQLyZL9Q)
|
||||
Call ID: toolu_01KtE1m1ifPLQAx4fQLyZL9Q
|
||||
Args:
|
||||
query: weather in new york city
|
||||
=================================[1m Tool Message [0m=================================
|
||||
Name: tavily_search_results_json
|
||||
|
||||
[{"url": "https://www.accuweather.com/en/us/new-york/10021/june-weather/349727", "content": "Get the monthly weather forecast for New York, NY, including daily high/low, historical averages, to help you plan ahead."}]
|
||||
==================================[1m Ai Message [0m==================================
|
||||
|
||||
The search results provide weather forecasts and information for New York City. Based on the top result from AccuWeather, here are some key details about the weather in NYC:
|
||||
|
||||
- This is a monthly weather forecast for New York City for the month of June.
|
||||
- It includes daily high and low temperatures to help plan ahead.
|
||||
- Historical averages for June in NYC are also provided as a reference point.
|
||||
- More detailed daily or hourly forecasts with precipitation chances, humidity, wind, etc. can be found by visiting the AccuWeather page.
|
||||
|
||||
So in summary, the search provides a convenient overview of the expected weather conditions in New York City over the next month to give you an idea of what to prepare for if traveling or making plans there. Let me know if you need any other details!
|
||||
|
||||
|
||||
Verify that the original, interrupted run was interrupted
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
print((await client.runs.get(thread["thread_id"], interrupted_run["run_id"]))["status"])
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
console.log((await client.runs.get(thread['thread_id'], interruptedRun["run_id"]))["status"])
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
'interrupted'
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
# Invoke Assistant
|
||||
|
||||
The LangGraph Studio lets you test different configurations and inputs to your graph. The UI allows you to see exactly how your
|
||||
The LangGraph Studio lets you test different configurations and inputs to your graph. It also provides a nice visualization of your graph during execution so it is easy to see which nodes are being run and what the outputs of each individual node are.
|
||||
|
||||
1. The LangGraph Studio UI displays a visualization of the selected assistant.
|
||||
1. In the top-right dropdown menu of the left-hand pane, select an assistant.
|
||||
1. In the top-left dropdown menu of the left-hand pane, select an assistant.
|
||||
1. In the bottom of the left-hand pane, edit the `Input` and `Configure` the assistant.
|
||||
1. Select `Submit` to invoke the selected assistant.
|
||||
1. View output of the invocation in the right-hand pane.
|
||||
|
||||
The following GIF shows these exact steps being carried out:
|
||||
The following video shows these exact steps being carried out:
|
||||
|
||||

|
||||
<video controls allowfullscreen="true" poster="../img/studio_input_poster.png">
|
||||
<source src="../img/studio_input.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
## Reject
|
||||
|
||||
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide][double-texting].
|
||||
|
||||
The guide covers the `reject` option for double texting, which rejects the new run of the graph by throwing an error and continues with the original run until completion. Below is a quick example of using the `reject` option.
|
||||
|
||||
First, we will define a quick helper function for printing out JS model outputs (you can skip this if using Python):
|
||||
|
||||
```js
|
||||
function prettyPrint(m) {
|
||||
const padded = " " + m['type'] + " ";
|
||||
const sepLen = Math.floor((80 - padded.length) / 2);
|
||||
const sep = "=".repeat(sepLen);
|
||||
const secondSep = sep + (padded.length % 2 ? "=" : "");
|
||||
|
||||
console.log(`${sep}${padded}${secondSep}`);
|
||||
console.log("\n\n");
|
||||
console.log(m.content);
|
||||
}
|
||||
```
|
||||
|
||||
Now, let's import our required packages and instantiate our client, assistant, and thread.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
import httpx
|
||||
from langchain_core.messages import convert_to_messages
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
assistant_id = "agent"
|
||||
thread = await client.threads.create()
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
const assistantId = "agent";
|
||||
const thread = await client.threads.create();
|
||||
```
|
||||
|
||||
Now we can run a thread and try to run a second one with the "reject" option, which should fail since we have already started a run:
|
||||
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
run = await client.runs.create(
|
||||
thread["thread_id"],
|
||||
assistant_id,
|
||||
input={"messages": [{"role": "human", "content": "what's the weather in sf?"}]},
|
||||
)
|
||||
try:
|
||||
await client.runs.create(
|
||||
thread["thread_id"],
|
||||
assistant_id,
|
||||
input={
|
||||
"messages": [{"role": "human", "content": "what's the weather in nyc?"}]
|
||||
},
|
||||
multitask_strategy="reject",
|
||||
)
|
||||
except httpx.HTTPStatusError as e:
|
||||
print("Failed to start concurrent run", e)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const run = await client.runs.create(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
input={"messages": [{"role": "human", "content": "what's the weather in sf?"}]},
|
||||
);
|
||||
|
||||
try {
|
||||
await client.runs.create(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
{
|
||||
input: {"messages": [{"role": "human", "content": "what's the weather in nyc?"}]},
|
||||
multitask_strategy:"reject"
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Failed to start concurrent run", e);
|
||||
}
|
||||
```
|
||||
|
||||
Failed to start concurrent run Client error '409 Conflict' for url 'http://localhost:8123/threads/f9e7088b-8028-4e5c-88d2-9cc9a2870e50/runs'
|
||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409
|
||||
|
||||
|
||||
We can verify that the original thread finished executing:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
# wait until the original run completes
|
||||
await client.runs.join(thread["thread_id"], run["run_id"])
|
||||
|
||||
state = await client.threads.get_state(thread["thread_id"])
|
||||
|
||||
for m in convert_to_messages(state["values"]["messages"]):
|
||||
m.pretty_print()
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
await client.runs.join(thread["thread_id"], run["run_id"]);
|
||||
|
||||
const state = await client.threads.getState(thread["thread_id"]);
|
||||
|
||||
for (const m of state["values"]["messages"]) {
|
||||
prettyPrint(m);
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
================================[1m Human Message [0m=================================
|
||||
|
||||
what's the weather in sf?
|
||||
==================================[1m Ai Message [0m==================================
|
||||
|
||||
[{'id': 'toolu_01CyewEifV2Kmi7EFKHbMDr1', 'input': {'query': 'weather in san francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
|
||||
Tool Calls:
|
||||
tavily_search_results_json (toolu_01CyewEifV2Kmi7EFKHbMDr1)
|
||||
Call ID: toolu_01CyewEifV2Kmi7EFKHbMDr1
|
||||
Args:
|
||||
query: weather in san francisco
|
||||
=================================[1m Tool Message [0m=================================
|
||||
Name: tavily_search_results_json
|
||||
|
||||
[{"url": "https://www.accuweather.com/en/us/san-francisco/94103/june-weather/347629", "content": "Get the monthly weather forecast for San Francisco, CA, including daily high/low, historical averages, to help you plan ahead."}]
|
||||
==================================[1m Ai Message [0m==================================
|
||||
|
||||
According to the search results from Tavily, the current weather in San Francisco is:
|
||||
|
||||
The average high temperature in San Francisco in June is around 65°F (18°C), with average lows around 54°F (12°C). June tends to be one of the cooler and foggier months in San Francisco due to the marine layer of fog that often blankets the city during the summer months.
|
||||
|
||||
Some key points about the typical June weather in San Francisco:
|
||||
|
||||
- Mild temperatures with highs in the 60s F and lows in the 50s F
|
||||
- Foggy mornings that often burn off to sunny afternoons
|
||||
- Little to no rainfall, as June falls in the dry season
|
||||
- Breezy conditions, with winds off the Pacific Ocean
|
||||
- Layers are recommended for changing weather conditions
|
||||
|
||||
So in summary, you can expect mild, foggy mornings giving way to sunny but cool afternoons in San Francisco this time of year. The marine layer keeps temperatures moderate compared to other parts of California in June.
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
## Rollback
|
||||
|
||||
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide][double-texting].
|
||||
|
||||
The guide covers the `rollback` option for double texting, which interrupts the prior run of the graph and starts a new one with the double-text. This option is very similar to the `interrupt` option, but in this case the first run is completely deleted from the database and cannot be restarted. Below is a quick example of using the `rollback` option.
|
||||
|
||||
First, we will define a quick helper function for printing out JS model outputs (you can skip this if using Python):
|
||||
|
||||
```js
|
||||
function prettyPrint(m) {
|
||||
const padded = " " + m['type'] + " ";
|
||||
const sepLen = Math.floor((80 - padded.length) / 2);
|
||||
const sep = "=".repeat(sepLen);
|
||||
const secondSep = sep + (padded.length % 2 ? "=" : "");
|
||||
|
||||
console.log(`${sep}${padded}${secondSep}`);
|
||||
console.log("\n\n");
|
||||
console.log(m.content);
|
||||
}
|
||||
```
|
||||
|
||||
Now, let's import our required packages and instantiate our client, assistant, and thread.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
import httpx
|
||||
from langchain_core.messages import convert_to_messages
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
assistant_id = "agent"
|
||||
thread = await client.threads.create()
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
const assistantId = "agent";
|
||||
const thread = await client.threads.create();
|
||||
```
|
||||
|
||||
Now let's run a thread with the multitask parameter set to "rollback":
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
# the first run will be rolled back
|
||||
rolled_back_run = await client.runs.create(
|
||||
thread["thread_id"],
|
||||
assistant_id,
|
||||
input={"messages": [{"role": "human", "content": "what's the weather in sf?"}]},
|
||||
)
|
||||
await asyncio.sleep(2)
|
||||
run = await client.runs.create(
|
||||
thread["thread_id"],
|
||||
assistant_id,
|
||||
input={"messages": [{"role": "human", "content": "what's the weather in nyc?"}]},
|
||||
multitask_strategy="rollback",
|
||||
)
|
||||
# wait until the second run completes
|
||||
await client.runs.join(thread["thread_id"], run["run_id"])
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
// the first run will be interrupted
|
||||
let rolledBackRun = await client.runs.create(
|
||||
thread["thread_id"],
|
||||
assistantId,
|
||||
{ input: { messages: [{ role: "human", content: "what's the weather in sf?" }] } }
|
||||
);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
let run = await client.runs.create(
|
||||
thread["thread_id"],
|
||||
assistant_id,
|
||||
{
|
||||
input: { messages: [{ role: "human", content: "what's the weather in nyc?" }] },
|
||||
multitaskStrategy: "rollback"
|
||||
}
|
||||
);
|
||||
|
||||
// wait until the second run completes
|
||||
await client.runs.join(thread["thread_id"], run["run_id"]);
|
||||
```
|
||||
|
||||
We can see that the thread has data only from the second run
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
state = await client.threads.get_state(thread["thread_id"])
|
||||
|
||||
for m in convert_to_messages(state["values"]["messages"]):
|
||||
m.pretty_print()
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const state = await client.threads.getState(thread["thread_id"]);
|
||||
|
||||
for (const m of state['values']['messages']) {
|
||||
prettyPrint(m);
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
================================[1m Human Message [0m=================================
|
||||
|
||||
what's the weather in nyc?
|
||||
==================================[1m Ai Message [0m==================================
|
||||
|
||||
[{'id': 'toolu_01JzPqefao1gxwajHQ3Yh3JD', 'input': {'query': 'weather in nyc'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
|
||||
Tool Calls:
|
||||
tavily_search_results_json (toolu_01JzPqefao1gxwajHQ3Yh3JD)
|
||||
Call ID: toolu_01JzPqefao1gxwajHQ3Yh3JD
|
||||
Args:
|
||||
query: weather in nyc
|
||||
=================================[1m Tool Message [0m=================================
|
||||
Name: tavily_search_results_json
|
||||
|
||||
[{"url": "https://www.weatherapi.com/", "content": "{'location': {'name': 'New York', 'region': 'New York', 'country': 'United States of America', 'lat': 40.71, 'lon': -74.01, 'tz_id': 'America/New_York', 'localtime_epoch': 1718734479, 'localtime': '2024-06-18 14:14'}, 'current': {'last_updated_epoch': 1718733600, 'last_updated': '2024-06-18 14:00', 'temp_c': 29.4, 'temp_f': 84.9, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 2.2, 'wind_kph': 3.6, 'wind_degree': 158, 'wind_dir': 'SSE', 'pressure_mb': 1025.0, 'pressure_in': 30.26, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 63, 'cloud': 0, 'feelslike_c': 31.3, 'feelslike_f': 88.3, 'windchill_c': 28.3, 'windchill_f': 82.9, 'heatindex_c': 29.6, 'heatindex_f': 85.3, 'dewpoint_c': 18.4, 'dewpoint_f': 65.2, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 7.0, 'gust_mph': 16.5, 'gust_kph': 26.5}}"}]
|
||||
==================================[1m Ai Message [0m==================================
|
||||
|
||||
The weather API results show that the current weather in New York City is sunny with a temperature of around 85°F (29°C). The wind is light at around 2-3 mph from the south-southeast. Overall it looks like a nice sunny summer day in NYC.
|
||||
|
||||
|
||||
Verify that the original, rolled back run was deleted
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
try:
|
||||
await client.runs.get(thread["thread_id"], rolled_back_run["run_id"])
|
||||
except httpx.HTTPStatusError as _:
|
||||
print("Original run was correctly deleted")
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
try {
|
||||
await client.runs.get(thread["thread_id"], rolledBackRun["run_id"]);
|
||||
} catch (e) {
|
||||
console.log("Original run was correctly deleted");
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
Original run was correctly deleted
|
||||
|
||||
@@ -0,0 +1,412 @@
|
||||
# How to stream events
|
||||
|
||||
This guide covers how to stream events from your graph (`stream_mode="events"`). Depending on the use case and user experience of your LangGraph application, your application may process event types differently. Read more about events in this [conceptual guide](https://langchain-ai.github.io/langgraph/concepts/low_level/#astream_events-for-streaming-tokens-of-llm-calls).
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
# create thread
|
||||
thread = await client.threads.create()
|
||||
print(thread)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
// create thread
|
||||
const thread = await client.threads.create();
|
||||
console.log(thread)
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
|
||||
{'thread_id': '3f4c64e0-f792-4a5e-aa07-a4404e06e0bd',
|
||||
'created_at': '2024-06-24T22:16:29.301522+00:00',
|
||||
'updated_at': '2024-06-24T22:16:29.301522+00:00',
|
||||
'metadata': {},
|
||||
'status': 'idle',
|
||||
'config': {}}
|
||||
|
||||
|
||||
|
||||
Streaming events produces responses containing an `event` key (in addition to other keys such as `data`). See the LangChain [`Runnable.astream_events()` reference](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.Runnable.html#langchain_core.runnables.base.Runnable.astream_events) for all event types.
|
||||
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
# create input
|
||||
input = {
|
||||
"messages": [
|
||||
{
|
||||
"role": "human",
|
||||
"content": "What's the weather in SF?",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# stream events
|
||||
async for chunk in client.runs.stream(
|
||||
thread_id=thread["thread_id"],
|
||||
assistant_id="agent",
|
||||
input=input,
|
||||
stream_mode="events",
|
||||
):
|
||||
print(f"Receiving new event of type: {chunk.event}...")
|
||||
print(chunk.data)
|
||||
print("\n\n")
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
// create input
|
||||
const input = {
|
||||
"messages": [
|
||||
{
|
||||
"role": "human",
|
||||
"content": "What's the weather in SF?",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// stream events
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
"agent",
|
||||
{
|
||||
input,
|
||||
streamMode: "events"
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
console.log(f"Receiving new event of type: {chunk.event}...")
|
||||
console.log(chunk.data)
|
||||
console.log("\n\n")
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"What's the weather in sf\"}]},
|
||||
\"stream_mode\": [
|
||||
\"events\"
|
||||
]
|
||||
}" | \
|
||||
sed 's/\r$//' | \
|
||||
awk '
|
||||
/^event:/ {
|
||||
if (data_content != "") {
|
||||
print data_content "\n"
|
||||
}
|
||||
sub(/^event: /, "Receiving event of type: ", $0)
|
||||
printf "%s...\n", $0
|
||||
data_content = ""
|
||||
}
|
||||
/^data:/ {
|
||||
sub(/^data: /, "", $0)
|
||||
data_content = $0
|
||||
}
|
||||
END {
|
||||
if (data_content != "") {
|
||||
print data_content "\n"
|
||||
}
|
||||
}
|
||||
'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
Receiving new event of type: metadata...
|
||||
{'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8'}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_start', 'data': {'input': {'messages': [{'role': 'human', 'content': "What's the weather in SF?"}]}}, 'name': 'LangGraph', 'tags': [], 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'parent_ids': []}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_start', 'data': {}, 'name': 'agent', 'tags': ['graph:step:6'], 'run_id': '7bb08493-d507-4e28-b9e6-4a5eda9d04f0', 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_start', 'data': {'input': {'messages': [[{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}]]}}, 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'run_id': 'cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '7bb08493-d507-4e28-b9e6-4a5eda9d04f0']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'b', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': 'cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '7bb08493-d507-4e28-b9e6-4a5eda9d04f0']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'e', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': 'cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '7bb08493-d507-4e28-b9e6-4a5eda9d04f0']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'g', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': 'cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '7bb08493-d507-4e28-b9e6-4a5eda9d04f0']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'i', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': 'cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '7bb08493-d507-4e28-b9e6-4a5eda9d04f0']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'n', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': 'cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '7bb08493-d507-4e28-b9e6-4a5eda9d04f0']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_end', 'data': {'output': {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, 'input': {'messages': [[{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}]]}}, 'run_id': 'cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '7bb08493-d507-4e28-b9e6-4a5eda9d04f0']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_start', 'data': {'input': {'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'name': 'should_continue', 'tags': ['seq:step:3'], 'run_id': 'c7fe4d2d-3fb8-4e53-946d-03de13527853', 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '7bb08493-d507-4e28-b9e6-4a5eda9d04f0']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_end', 'data': {'output': 'tool', 'input': {'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'run_id': 'c7fe4d2d-3fb8-4e53-946d-03de13527853', 'name': 'should_continue', 'tags': ['seq:step:3'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '7bb08493-d507-4e28-b9e6-4a5eda9d04f0']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': '7bb08493-d507-4e28-b9e6-4a5eda9d04f0', 'name': 'agent', 'tags': ['graph:step:6'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0}, 'data': {'chunk': {'messages': [{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_end', 'data': {'output': {'messages': [{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}, 'input': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}], 'sleep': None}}, 'run_id': '7bb08493-d507-4e28-b9e6-4a5eda9d04f0', 'name': 'agent', 'tags': ['graph:step:6'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_start', 'data': {}, 'name': 'tool', 'tags': ['graph:step:7'], 'run_id': 'f044fd3d-7271-488f-b8aa-e01572ff9112', 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 7, 'langgraph_node': 'tool', 'langgraph_triggers': ['branch:agent:should_continue:tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': 'f044fd3d-7271-488f-b8aa-e01572ff9112', 'name': 'tool', 'tags': ['graph:step:7'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 7, 'langgraph_node': 'tool', 'langgraph_triggers': ['branch:agent:should_continue:tool'], 'langgraph_task_idx': 0}, 'data': {'chunk': {'messages': [{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': None, 'tool_call_id': 'tool_call_id'}]}}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_end', 'data': {'output': {'messages': [{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1c9a16d2-5f0a-4eba-a0d2-240484a4ce7e', 'tool_call_id': 'tool_call_id'}]}, 'input': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'sleep': None}}, 'run_id': 'f044fd3d-7271-488f-b8aa-e01572ff9112', 'name': 'tool', 'tags': ['graph:step:7'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 7, 'langgraph_node': 'tool', 'langgraph_triggers': ['branch:agent:should_continue:tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_start', 'data': {}, 'name': 'agent', 'tags': ['graph:step:8'], 'run_id': '1f4f95d0-0ce1-4061-85d4-946446bbd3e5', 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 8, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_start', 'data': {'input': {'messages': [[{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1c9a16d2-5f0a-4eba-a0d2-240484a4ce7e', 'tool_call_id': 'tool_call_id'}]]}}, 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'run_id': '028a68fb-6435-4b46-a156-c3326f73985c', 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 8, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '1f4f95d0-0ce1-4061-85d4-946446bbd3e5']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'e', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-028a68fb-6435-4b46-a156-c3326f73985c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '028a68fb-6435-4b46-a156-c3326f73985c', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 8, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '1f4f95d0-0ce1-4061-85d4-946446bbd3e5']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'n', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-028a68fb-6435-4b46-a156-c3326f73985c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '028a68fb-6435-4b46-a156-c3326f73985c', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 8, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '1f4f95d0-0ce1-4061-85d4-946446bbd3e5']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'd', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-028a68fb-6435-4b46-a156-c3326f73985c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '028a68fb-6435-4b46-a156-c3326f73985c', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 8, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '1f4f95d0-0ce1-4061-85d4-946446bbd3e5']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_end', 'data': {'output': {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-028a68fb-6435-4b46-a156-c3326f73985c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, 'input': {'messages': [[{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1c9a16d2-5f0a-4eba-a0d2-240484a4ce7e', 'tool_call_id': 'tool_call_id'}]]}}, 'run_id': '028a68fb-6435-4b46-a156-c3326f73985c', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 8, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '1f4f95d0-0ce1-4061-85d4-946446bbd3e5']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_start', 'data': {'input': {'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1c9a16d2-5f0a-4eba-a0d2-240484a4ce7e', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-028a68fb-6435-4b46-a156-c3326f73985c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'name': 'should_continue', 'tags': ['seq:step:3'], 'run_id': 'f2b2dfaf-475d-422b-8bf5-02a31bcc7d1a', 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 8, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '1f4f95d0-0ce1-4061-85d4-946446bbd3e5']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_end', 'data': {'output': '__end__', 'input': {'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1c9a16d2-5f0a-4eba-a0d2-240484a4ce7e', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-028a68fb-6435-4b46-a156-c3326f73985c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'run_id': 'f2b2dfaf-475d-422b-8bf5-02a31bcc7d1a', 'name': 'should_continue', 'tags': ['seq:step:3'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 8, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '1f4f95d0-0ce1-4061-85d4-946446bbd3e5']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': '1f4f95d0-0ce1-4061-85d4-946446bbd3e5', 'name': 'agent', 'tags': ['graph:step:8'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 8, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0}, 'data': {'chunk': {'messages': [{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-028a68fb-6435-4b46-a156-c3326f73985c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_end', 'data': {'output': {'messages': [{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-028a68fb-6435-4b46-a156-c3326f73985c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}, 'input': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1c9a16d2-5f0a-4eba-a0d2-240484a4ce7e', 'tool_call_id': 'tool_call_id'}], 'sleep': None}}, 'run_id': '1f4f95d0-0ce1-4061-85d4-946446bbd3e5', 'name': 'agent', 'tags': ['graph:step:8'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 8, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_end', 'data': {'output': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1c9a16d2-5f0a-4eba-a0d2-240484a4ce7e', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-028a68fb-6435-4b46-a156-c3326f73985c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}, 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'name': 'LangGraph', 'tags': [], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'parent_ids': []}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: end...
|
||||
None
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Token-by-Token Streaming
|
||||
|
||||
Token-by-token streaming can be implemented with the `events` streaming mode. The `on_chat_model_stream` event type should be processed to stream LLM responses token-by-token.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
llm_response = ""
|
||||
|
||||
# stream token-by-token
|
||||
async for chunk in client.runs.stream(
|
||||
thread_id=thread["thread_id"],
|
||||
assistant_id="agent",
|
||||
input=input,
|
||||
stream_mode="events",
|
||||
):
|
||||
if (
|
||||
chunk.event == "events" and
|
||||
chunk.data["event"] == "on_chat_model_stream" and
|
||||
len(chunk.data["data"]["chunk"]["content"]) > 0 and
|
||||
'text' in chunk.data["data"]["chunk"]["content"][0]
|
||||
):
|
||||
llm_response += chunk.data["data"]["chunk"]["content"][0]['text']
|
||||
print(llm_response)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const llmResponse = "";
|
||||
// stream events
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
"agent",
|
||||
{
|
||||
input,
|
||||
streamMode: "events"
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
if (chunk.event === "events" && chunk.data.event === "on_chat_model_stream" && chunk.data.chunk.content.length > 0 && 'text' in chunk.data.chunk.content[0]) {
|
||||
llmResponse += chunk.data.data.chunk.content[0].text;
|
||||
console.log(llmResponse);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"What's the weather in sf\"}]},
|
||||
\"stream_mode\": [
|
||||
\"events\"
|
||||
]
|
||||
}" | sed 's/\r$//' | awk '
|
||||
/^event:/ { event = $2 }
|
||||
/^data:/ {
|
||||
json_data = substr($0, index($0, $2))
|
||||
|
||||
if (event == "events") {
|
||||
print json_data
|
||||
}
|
||||
}' | jq -r '
|
||||
select(.event == "on_chat_model_stream") |
|
||||
.data.chunk.content[] | .text // empty
|
||||
' | awk '
|
||||
BEGIN { llm_response="" }
|
||||
$0 != "" && $0 != "null" {
|
||||
llm_response = llm_response $0
|
||||
print llm_response
|
||||
}'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
The
|
||||
The search
|
||||
The search results provide
|
||||
The search results provide the current weather conditions
|
||||
The search results provide the current weather conditions in San Francisco.
|
||||
The search results provide the current weather conditions in San Francisco. According
|
||||
The search results provide the current weather conditions in San Francisco. According to the data,
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12,
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024,
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C).
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The win
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is bl
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 k
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph).
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70%
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km).
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). Overall
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). Overall, it appears
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). Overall, it appears to be a nice
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). Overall, it appears to be a nice sunny day in San
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). Overall, it appears to be a nice sunny day in San Francisco.
|
||||
|
||||
|
||||
@@ -0,0 +1,484 @@
|
||||
# How to stream messages from your graph
|
||||
|
||||
This guide covers how to stream messages from your graph. In order to use this mode, the state of the graph you are interacting with MUST have a `messages` key that is a list of messages.
|
||||
|
||||
E.g., the state should look something like:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from typing import TypedDict, Annotated
|
||||
from langgraph.graph import add_messages
|
||||
from langchain_core.messages import AnyMessage
|
||||
|
||||
class State(TypedDict):
|
||||
messages: Annotated[list[AnyMessage], add_messages]
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { type BaseMessage } from "@langchain/core/messages";
|
||||
import { Annotation, messagesStateReducer } from "@langchain/langgraph";
|
||||
|
||||
export const StateAnnotation = Annotation.Root({
|
||||
messages: Annotation<BaseMessage[]>({
|
||||
reducer: messagesStateReducer,
|
||||
default: () => [],
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
Alternatively, you can use an instance or subclass of `from langgraph.graph import MessagesState` (`MessagesState` is equivalent to the implementation above). Or in Javascript: `import { MessagesAnnotation } from "@langchain/langgraph";`.
|
||||
|
||||
With `stream_mode="messages"` two things will be streamed back:
|
||||
|
||||
- It outputs messages produced by any chat model called inside (unless tagged in a special way)
|
||||
- It outputs messages returned from nodes (to allow for nodes to return `ToolMessages` and the like)
|
||||
|
||||
Read more about how the `messages` streaming mode works [here](https://langchain-ai.github.io/langgraph/cloud/concepts/api/#modemessages)
|
||||
|
||||
First let's set up our client and thread:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
# create thread
|
||||
thread = await client.threads.create()
|
||||
print(thread)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
// create thread
|
||||
const thread = await client.threads.create();
|
||||
console.log(thread)
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
{'thread_id': 'e1431c95-e241-4d1d-a252-27eceb1e5c86',
|
||||
'created_at': '2024-06-21T15:48:59.808924+00:00',
|
||||
'updated_at': '2024-06-21T15:48:59.808924+00:00',
|
||||
'metadata': {},
|
||||
'status': 'idle',
|
||||
'config': {}}
|
||||
|
||||
Let's also define a helper function for better formatting of the tool calls in messages (for CURL we will define a helper script called `process_stream.sh`)
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
def format_tool_calls(tool_calls):
|
||||
if tool_calls:
|
||||
formatted_calls = []
|
||||
for call in tool_calls:
|
||||
formatted_calls.append(
|
||||
f"Tool Call ID: {call['id']}, Function: {call['name']}, Arguments: {call['args']}"
|
||||
)
|
||||
return "\n".join(formatted_calls)
|
||||
return "No tool calls"
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
function formatToolCalls(toolCalls) {
|
||||
if (toolCalls && toolCalls.length > 0) {
|
||||
const formattedCalls = toolCalls.map(call => {
|
||||
return `Tool Call ID: ${call.id}, Function: ${call.name}, Arguments: ${call.args}`;
|
||||
});
|
||||
return formattedCalls.join("\n");
|
||||
}
|
||||
return "No tool calls";
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
# process_stream.sh
|
||||
|
||||
format_tool_calls() {
|
||||
echo "$1" | jq -r 'map("Tool Call ID: \(.id), Function: \(.name), Arguments: \(.args)") | join("\n")'
|
||||
}
|
||||
|
||||
process_data_item() {
|
||||
local data_item="$1"
|
||||
|
||||
if echo "$data_item" | jq -e '.role == "user"' > /dev/null; then
|
||||
echo "Human: $(echo "$data_item" | jq -r '.content')"
|
||||
else
|
||||
local tool_calls=$(echo "$data_item" | jq -r '.tool_calls // []')
|
||||
local invalid_tool_calls=$(echo "$data_item" | jq -r '.invalid_tool_calls // []')
|
||||
local content=$(echo "$data_item" | jq -r '.content // ""')
|
||||
local response_metadata=$(echo "$data_item" | jq -r '.response_metadata // {}')
|
||||
|
||||
if [ -n "$content" ] && [ "$content" != "null" ]; then
|
||||
echo "AI: $content"
|
||||
fi
|
||||
|
||||
if [ "$tool_calls" != "[]" ]; then
|
||||
echo "Tool Calls:"
|
||||
format_tool_calls "$tool_calls"
|
||||
fi
|
||||
|
||||
if [ "$invalid_tool_calls" != "[]" ]; then
|
||||
echo "Invalid Tool Calls:"
|
||||
format_tool_calls "$invalid_tool_calls"
|
||||
fi
|
||||
|
||||
if [ "$response_metadata" != "{}" ]; then
|
||||
local finish_reason=$(echo "$response_metadata" | jq -r '.finish_reason // "N/A"')
|
||||
echo "Response Metadata: Finish Reason - $finish_reason"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
while IFS=': ' read -r key value; do
|
||||
case "$key" in
|
||||
event)
|
||||
event="$value"
|
||||
;;
|
||||
data)
|
||||
if [ "$event" = "metadata" ]; then
|
||||
run_id=$(echo "$value" | jq -r '.run_id')
|
||||
echo "Metadata: Run ID - $run_id"
|
||||
echo "------------------------------------------------"
|
||||
elif [ "$event" = "messages/partial" ]; then
|
||||
echo "$value" | jq -c '.[]' | while read -r data_item; do
|
||||
process_data_item "$data_item"
|
||||
done
|
||||
echo "------------------------------------------------"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
```
|
||||
|
||||
|
||||
Now we can stream by messages, which will return complete messages (at the end of node execution) as well as tokens for any messages generated inside a node:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
input = {"messages": [{"role": "user", "content": "what's the weather in sf"}]}
|
||||
config = {"configurable": {"model_name": "openai"}}
|
||||
|
||||
async for event in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistant_id="agent",
|
||||
input=input,
|
||||
config=config,
|
||||
stream_mode="messages",
|
||||
):
|
||||
if event.event == "metadata":
|
||||
print(f"Metadata: Run ID - {event.data['run_id']}")
|
||||
print("-" * 50)
|
||||
elif event.event == "messages/partial":
|
||||
for data_item in event.data:
|
||||
if "role" in data_item and data_item["role"] == "user":
|
||||
print(f"Human: {data_item['content']}")
|
||||
else:
|
||||
tool_calls = data_item.get("tool_calls", [])
|
||||
invalid_tool_calls = data_item.get("invalid_tool_calls", [])
|
||||
content = data_item.get("content", "")
|
||||
response_metadata = data_item.get("response_metadata", {})
|
||||
|
||||
if content:
|
||||
print(f"AI: {content}")
|
||||
|
||||
if tool_calls:
|
||||
print("Tool Calls:")
|
||||
print(format_tool_calls(tool_calls))
|
||||
|
||||
if invalid_tool_calls:
|
||||
print("Invalid Tool Calls:")
|
||||
print(format_tool_calls(invalid_tool_calls))
|
||||
|
||||
if response_metadata:
|
||||
finish_reason = response_metadata.get("finish_reason", "N/A")
|
||||
print(f"Response Metadata: Finish Reason - {finish_reason}")
|
||||
print("-" * 50)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const input = {
|
||||
"messages": [
|
||||
{
|
||||
"role": "human",
|
||||
"content": "What's the weather in sf",
|
||||
}
|
||||
]
|
||||
}
|
||||
const config = {"configurable": {"model_name": "openai"}}
|
||||
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
"agent",
|
||||
{
|
||||
input,
|
||||
config,
|
||||
streamMode: "messages"
|
||||
}
|
||||
);
|
||||
for await (const event of streamResponse) {
|
||||
if (event.event === "metadata") {
|
||||
console.log(`Metadata: Run ID - ${event.data.run_id}`);
|
||||
console.log("-".repeat(50));
|
||||
} else if (event.event === "messages/partial") {
|
||||
event.data.forEach(dataItem => {
|
||||
if (dataItem.role && dataItem.role === "user") {
|
||||
console.log(`Human: ${dataItem.content}`);
|
||||
} else {
|
||||
const toolCalls = dataItem.tool_calls || [];
|
||||
const invalidToolCalls = dataItem.invalid_tool_calls || [];
|
||||
const content = dataItem.content || "";
|
||||
const responseMetadata = dataItem.response_metadata || {};
|
||||
|
||||
if (content) {
|
||||
console.log(`AI: ${content}`);
|
||||
}
|
||||
|
||||
if (toolCalls.length > 0) {
|
||||
console.log("Tool Calls:");
|
||||
console.log(formatToolCalls(toolCalls));
|
||||
}
|
||||
|
||||
if (invalidToolCalls.length > 0) {
|
||||
console.log("Invalid Tool Calls:");
|
||||
console.log(formatToolCalls(invalidToolCalls));
|
||||
}
|
||||
|
||||
if (responseMetadata) {
|
||||
const finishReason = responseMetadata.finish_reason || "N/A";
|
||||
console.log(`Response Metadata: Finish Reason - ${finishReason}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log("-".repeat(50));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"config\":{\"configurable\":{\"model_name\":\"openai\"}},
|
||||
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"What's the weather in sf\"}]},
|
||||
\"stream_mode\": [
|
||||
\"messages\"
|
||||
]
|
||||
}" | sed 's/\r$//' | ./process_stream.sh
|
||||
```
|
||||
|
||||
|
||||
Output:
|
||||
|
||||
Metadata: Run ID - 1ef2fe5c-6a1d-6575-bc09-d7832711c17e
|
||||
--------------------------------------------------
|
||||
Invalid Tool Calls:
|
||||
Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments:
|
||||
--------------------------------------------------
|
||||
Tool Calls:
|
||||
Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: {}
|
||||
--------------------------------------------------
|
||||
Tool Calls:
|
||||
Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: {}
|
||||
--------------------------------------------------
|
||||
Tool Calls:
|
||||
Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: {'query': ''}
|
||||
--------------------------------------------------
|
||||
Tool Calls:
|
||||
Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: {'query': 'current'}
|
||||
--------------------------------------------------
|
||||
Tool Calls:
|
||||
Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: {'query': 'current weather'}
|
||||
--------------------------------------------------
|
||||
Tool Calls:
|
||||
Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: {'query': 'current weather in'}
|
||||
--------------------------------------------------
|
||||
Tool Calls:
|
||||
Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: {'query': 'current weather in San'}
|
||||
--------------------------------------------------
|
||||
Tool Calls:
|
||||
Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: {'query': 'current weather in San Francisco'}
|
||||
--------------------------------------------------
|
||||
Tool Calls:
|
||||
Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: {'query': 'current weather in San Francisco'}
|
||||
--------------------------------------------------
|
||||
Tool Calls:
|
||||
Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: {'query': 'current weather in San Francisco'}
|
||||
Response Metadata: Finish Reason - tool_calls
|
||||
--------------------------------------------------
|
||||
--------------------------------------------------
|
||||
AI: The
|
||||
--------------------------------------------------
|
||||
AI: The current
|
||||
--------------------------------------------------
|
||||
AI: The current weather
|
||||
--------------------------------------------------
|
||||
AI: The current weather in
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is over
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F).
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-s
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-south
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 k
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph).
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%,
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 miles
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 miles).
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 miles). The
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 miles). The UV
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 miles). The UV index
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 miles). The UV index is
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 miles). The UV index is
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 miles). The UV index is 3
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 miles). The UV index is 3.
|
||||
--------------------------------------------------
|
||||
AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 miles). The UV index is 3.
|
||||
Response Metadata: Finish Reason - stop
|
||||
--------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,486 @@
|
||||
# How to configure multiple streaming modes at the same time
|
||||
|
||||
This guide covers how to configure multiple streaming modes at the same time.
|
||||
|
||||
First let's set up our client and thread:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
# create thread
|
||||
thread = await client.threads.create()
|
||||
print(thread)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
// create thread
|
||||
const thread = await client.threads.create();
|
||||
console.log(thread)
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
{'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4',
|
||||
'created_at': '2024-06-24T21:30:07.980789+00:00',
|
||||
'updated_at': '2024-06-24T21:30:07.980789+00:00',
|
||||
'metadata': {},
|
||||
'status': 'idle',
|
||||
'config': {}}
|
||||
|
||||
When configuring multiple streaming modes for a run, responses for each respective mode will be produced. In the following example, note that a `list` of modes (`messages`, `events`, `debug`) is passed to the `stream_mode` parameter and the response contains `events`, `debug`, `messages/complete`, `messages/metadata`, and `messages/partial` event types.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
# create input
|
||||
input = {
|
||||
"messages": [
|
||||
{
|
||||
"role": "human",
|
||||
"content": "What's the weather in SF?",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# stream events with multiple streaming modes
|
||||
async for chunk in client.runs.stream(
|
||||
thread_id=thread["thread_id"],
|
||||
assistant_id="agent",
|
||||
input=input,
|
||||
stream_mode=["messages", "events", "debug"],
|
||||
):
|
||||
print(f"Receiving new event of type: {chunk.event}...")
|
||||
print(chunk.data)
|
||||
print("\n\n")
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
// create input
|
||||
const input = {
|
||||
"messages": [
|
||||
{
|
||||
"role": "human",
|
||||
"content": "What's the weather in SF?",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// stream events with multiple streaming modes
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
"agent",
|
||||
{
|
||||
input,
|
||||
streamMode: ["messages", "events", "debug"]
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
console.log(f"Receiving new event of type: {chunk.event}...")
|
||||
console.log(chunk.data)
|
||||
console.log("\n\n")
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"What's the weather in SF?\"}]},
|
||||
\"stream_mode\": [
|
||||
\"messages\",
|
||||
\"events\",
|
||||
\"debug\"
|
||||
]
|
||||
}" | \
|
||||
sed 's/\r$//' | \
|
||||
awk '
|
||||
/^event:/ {
|
||||
if (data_content != "") {
|
||||
print data_content "\n"
|
||||
}
|
||||
sub(/^event: /, "Receiving event of type: ", $0)
|
||||
printf "%s...\n", $0
|
||||
data_content = ""
|
||||
}
|
||||
/^data:/ {
|
||||
sub(/^data: /, "", $0)
|
||||
data_content = $0
|
||||
}
|
||||
END {
|
||||
if (data_content != "") {
|
||||
print data_content "\n"
|
||||
}
|
||||
}
|
||||
'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
Receiving new event of type: metadata...
|
||||
{'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_start', 'data': {'input': {'messages': [{'role': 'human', 'content': "What's the weather in SF?"}]}}, 'name': 'LangGraph', 'tags': [], 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'parent_ids': []}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: debug...
|
||||
{'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.116009+00:00', 'step': -1, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bc7c-6daa-bfff-6b9027c1a50e', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'messages': []}, 'metadata': {'source': 'input', 'step': -1, 'writes': {'messages': [{'role': 'human', 'content': "What's the weather in SF?"}]}}}}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.116009+00:00', 'step': -1, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bc7c-6daa-bfff-6b9027c1a50e', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'messages': []}, 'metadata': {'source': 'input', 'step': -1, 'writes': {'messages': [{'role': 'human', 'content': "What's the weather in SF?"}]}}}}]}, 'parent_ids': []}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['values', {'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}]}]}, 'parent_ids': []}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages/complete...
|
||||
[{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}]
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: debug...
|
||||
{'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.117924+00:00', 'step': 0, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bc81-68c8-8000-4e18ae7d67a5', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}]}, 'metadata': {'source': 'loop', 'step': 0, 'writes': None}}}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.117924+00:00', 'step': 0, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bc81-68c8-8000-4e18ae7d67a5', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}]}, 'metadata': {'source': 'loop', 'step': 0, 'writes': None}}}]}, 'parent_ids': []}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: debug...
|
||||
{'type': 'task', 'timestamp': '2024-06-24T21:34:06.118042+00:00', 'step': 1, 'payload': {'id': '212ed9c2-a454-50c5-a202-12066bbbe7b8', 'name': 'agent', 'input': {'some_bytes': None, 'some_byte_array': None, 'dict_with_bytes': None, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}], 'sleep': None}, 'triggers': ['start:agent']}}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'task', 'timestamp': '2024-06-24T21:34:06.118042+00:00', 'step': 1, 'payload': {'id': '212ed9c2-a454-50c5-a202-12066bbbe7b8', 'name': 'agent', 'input': {'some_bytes': None, 'some_byte_array': None, 'dict_with_bytes': None, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}], 'sleep': None}, 'triggers': ['start:agent']}}]}, 'parent_ids': []}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_start', 'data': {}, 'name': 'agent', 'tags': ['graph:step:1'], 'run_id': '72b74d24-5792-48da-a887-102100d6e2c0', 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_start', 'data': {'input': {'messages': [[{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}]]}}, 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'run_id': '2424dd6d-5cf5-4244-8d98-357640ce6e12', 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', '72b74d24-5792-48da-a887-102100d6e2c0']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'b', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '2424dd6d-5cf5-4244-8d98-357640ce6e12', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', '72b74d24-5792-48da-a887-102100d6e2c0']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages/metadata...
|
||||
{'run-2424dd6d-5cf5-4244-8d98-357640ce6e12': {'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}}}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages/partial...
|
||||
[{'content': 'b', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'e', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '2424dd6d-5cf5-4244-8d98-357640ce6e12', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', '72b74d24-5792-48da-a887-102100d6e2c0']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages/partial...
|
||||
[{'content': 'be', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'g', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '2424dd6d-5cf5-4244-8d98-357640ce6e12', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', '72b74d24-5792-48da-a887-102100d6e2c0']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages/partial...
|
||||
[{'content': 'beg', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'i', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '2424dd6d-5cf5-4244-8d98-357640ce6e12', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', '72b74d24-5792-48da-a887-102100d6e2c0']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages/partial...
|
||||
[{'content': 'begi', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'n', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '2424dd6d-5cf5-4244-8d98-357640ce6e12', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', '72b74d24-5792-48da-a887-102100d6e2c0']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages/partial...
|
||||
[{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_end', 'data': {'output': {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, 'input': {'messages': [[{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}]]}}, 'run_id': '2424dd6d-5cf5-4244-8d98-357640ce6e12', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', '72b74d24-5792-48da-a887-102100d6e2c0']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_start', 'data': {'input': {'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'name': 'should_continue', 'tags': ['seq:step:3'], 'run_id': '227afb0f-f909-4d54-a042-556ca6d98a69', 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', '72b74d24-5792-48da-a887-102100d6e2c0']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_end', 'data': {'output': 'tool', 'input': {'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'run_id': '227afb0f-f909-4d54-a042-556ca6d98a69', 'name': 'should_continue', 'tags': ['seq:step:3'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', '72b74d24-5792-48da-a887-102100d6e2c0']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': '72b74d24-5792-48da-a887-102100d6e2c0', 'name': 'agent', 'tags': ['graph:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0}, 'data': {'chunk': {'messages': [{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_end', 'data': {'output': {'messages': [{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}, 'input': {'some_bytes': None, 'some_byte_array': None, 'dict_with_bytes': None, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}], 'sleep': None}}, 'run_id': '72b74d24-5792-48da-a887-102100d6e2c0', 'name': 'agent', 'tags': ['graph:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: debug...
|
||||
{'type': 'task_result', 'timestamp': '2024-06-24T21:34:06.124350+00:00', 'step': 1, 'payload': {'id': '212ed9c2-a454-50c5-a202-12066bbbe7b8', 'name': 'agent', 'result': [['some_bytes', 'c29tZV9ieXRlcw=='], ['some_byte_array', 'c29tZV9ieXRlX2FycmF5'], ['dict_with_bytes', {'more_bytes': 'bW9yZV9ieXRlcw=='}], ['messages', [{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]]]}}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'task_result', 'timestamp': '2024-06-24T21:34:06.124350+00:00', 'step': 1, 'payload': {'id': '212ed9c2-a454-50c5-a202-12066bbbe7b8', 'name': 'agent', 'result': [['some_bytes', 'c29tZV9ieXRlcw=='], ['some_byte_array', 'c29tZV9ieXRlX2FycmF5'], ['dict_with_bytes', {'more_bytes': 'bW9yZV9ieXRlcw=='}], ['messages', [{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]]]}}]}, 'parent_ids': []}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['values', {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}]}, 'parent_ids': []}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages/complete...
|
||||
[{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: debug...
|
||||
{'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.124510+00:00', 'step': 1, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bc91-6a34-8001-26353c117c25', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}, 'metadata': {'source': 'loop', 'step': 1, 'writes': {'agent': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}}}}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.124510+00:00', 'step': 1, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bc91-6a34-8001-26353c117c25', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}, 'metadata': {'source': 'loop', 'step': 1, 'writes': {'agent': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}}}}]}, 'parent_ids': []}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: debug...
|
||||
{'type': 'task', 'timestamp': '2024-06-24T21:34:06.124572+00:00', 'step': 2, 'payload': {'id': '44139125-a1be-57c2-9cb2-19eb62bbaf2f', 'name': 'tool', 'input': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'sleep': None}, 'triggers': ['branch:agent:should_continue:tool']}}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'task', 'timestamp': '2024-06-24T21:34:06.124572+00:00', 'step': 2, 'payload': {'id': '44139125-a1be-57c2-9cb2-19eb62bbaf2f', 'name': 'tool', 'input': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'sleep': None}, 'triggers': ['branch:agent:should_continue:tool']}}]}, 'parent_ids': []}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_start', 'data': {}, 'name': 'tool', 'tags': ['graph:step:2'], 'run_id': '91575720-886e-485e-ae2d-d6817e5346bf', 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 2, 'langgraph_node': 'tool', 'langgraph_triggers': ['branch:agent:should_continue:tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': '91575720-886e-485e-ae2d-d6817e5346bf', 'name': 'tool', 'tags': ['graph:step:2'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 2, 'langgraph_node': 'tool', 'langgraph_triggers': ['branch:agent:should_continue:tool'], 'langgraph_task_idx': 0}, 'data': {'chunk': {'messages': [{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': None, 'tool_call_id': 'tool_call_id'}]}}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_end', 'data': {'output': {'messages': [{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]}, 'input': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'sleep': None}}, 'run_id': '91575720-886e-485e-ae2d-d6817e5346bf', 'name': 'tool', 'tags': ['graph:step:2'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 2, 'langgraph_node': 'tool', 'langgraph_triggers': ['branch:agent:should_continue:tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: debug...
|
||||
{'type': 'task_result', 'timestamp': '2024-06-24T21:34:06.126828+00:00', 'step': 2, 'payload': {'id': '44139125-a1be-57c2-9cb2-19eb62bbaf2f', 'name': 'tool', 'result': [['messages', [{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]]]}}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'task_result', 'timestamp': '2024-06-24T21:34:06.126828+00:00', 'step': 2, 'payload': {'id': '44139125-a1be-57c2-9cb2-19eb62bbaf2f', 'name': 'tool', 'result': [['messages', [{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]]]}}]}, 'parent_ids': []}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['values', {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]}]}, 'parent_ids': []}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages/complete...
|
||||
[{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: debug...
|
||||
{'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.126966+00:00', 'step': 2, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bc97-6a06-8002-8e9ffc1ea75a', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]}, 'metadata': {'source': 'loop', 'step': 2, 'writes': {'tool': {'messages': [{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]}}}}}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.126966+00:00', 'step': 2, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bc97-6a06-8002-8e9ffc1ea75a', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]}, 'metadata': {'source': 'loop', 'step': 2, 'writes': {'tool': {'messages': [{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]}}}}}]}, 'parent_ids': []}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: debug...
|
||||
{'type': 'task', 'timestamp': '2024-06-24T21:34:06.127034+00:00', 'step': 3, 'payload': {'id': 'f1ccf371-63b3-5268-a837-7f360a93c4ec', 'name': 'agent', 'input': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}], 'sleep': None}, 'triggers': ['tool']}}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'task', 'timestamp': '2024-06-24T21:34:06.127034+00:00', 'step': 3, 'payload': {'id': 'f1ccf371-63b3-5268-a837-7f360a93c4ec', 'name': 'agent', 'input': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}], 'sleep': None}, 'triggers': ['tool']}}]}, 'parent_ids': []}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_start', 'data': {}, 'name': 'agent', 'tags': ['graph:step:3'], 'run_id': 'b7d0900c-bfc2-43e4-b760-99bbc5bad84e', 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_start', 'data': {'input': {'messages': [[{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]]}}, 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'run_id': '0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', 'b7d0900c-bfc2-43e4-b760-99bbc5bad84e']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'e', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', 'b7d0900c-bfc2-43e4-b760-99bbc5bad84e']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages/metadata...
|
||||
{'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575': {'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}}}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages/partial...
|
||||
[{'content': 'e', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'n', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', 'b7d0900c-bfc2-43e4-b760-99bbc5bad84e']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages/partial...
|
||||
[{'content': 'en', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'd', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', 'b7d0900c-bfc2-43e4-b760-99bbc5bad84e']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages/partial...
|
||||
[{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chat_model_end', 'data': {'output': {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, 'input': {'messages': [[{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]]}}, 'run_id': '0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', 'b7d0900c-bfc2-43e4-b760-99bbc5bad84e']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_start', 'data': {'input': {'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'name': 'should_continue', 'tags': ['seq:step:3'], 'run_id': '8af814e9-8136-4aab-acbc-dffc5bcafdfd', 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', 'b7d0900c-bfc2-43e4-b760-99bbc5bad84e']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_end', 'data': {'output': '__end__', 'input': {'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'run_id': '8af814e9-8136-4aab-acbc-dffc5bcafdfd', 'name': 'should_continue', 'tags': ['seq:step:3'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', 'b7d0900c-bfc2-43e4-b760-99bbc5bad84e']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': 'b7d0900c-bfc2-43e4-b760-99bbc5bad84e', 'name': 'agent', 'tags': ['graph:step:3'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0}, 'data': {'chunk': {'messages': [{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_end', 'data': {'output': {'messages': [{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}, 'input': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}], 'sleep': None}}, 'run_id': 'b7d0900c-bfc2-43e4-b760-99bbc5bad84e', 'name': 'agent', 'tags': ['graph:step:3'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25']}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: debug...
|
||||
{'type': 'task_result', 'timestamp': '2024-06-24T21:34:06.133991+00:00', 'step': 3, 'payload': {'id': 'f1ccf371-63b3-5268-a837-7f360a93c4ec', 'name': 'agent', 'result': [['some_bytes', 'c29tZV9ieXRlcw=='], ['some_byte_array', 'c29tZV9ieXRlX2FycmF5'], ['dict_with_bytes', {'more_bytes': 'bW9yZV9ieXRlcw=='}], ['messages', [{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]]]}}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'task_result', 'timestamp': '2024-06-24T21:34:06.133991+00:00', 'step': 3, 'payload': {'id': 'f1ccf371-63b3-5268-a837-7f360a93c4ec', 'name': 'agent', 'result': [['some_bytes', 'c29tZV9ieXRlcw=='], ['some_byte_array', 'c29tZV9ieXRlX2FycmF5'], ['dict_with_bytes', {'more_bytes': 'bW9yZV9ieXRlcw=='}], ['messages', [{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]]]}}]}, 'parent_ids': []}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['values', {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}]}, 'parent_ids': []}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages/complete...
|
||||
[{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: debug...
|
||||
{'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.134190+00:00', 'step': 3, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bca9-6418-8003-8d0d0b06845c', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}, 'metadata': {'source': 'loop', 'step': 3, 'writes': {'agent': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}}}}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.134190+00:00', 'step': 3, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bca9-6418-8003-8d0d0b06845c', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}, 'metadata': {'source': 'loop', 'step': 3, 'writes': {'agent': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}}}}]}, 'parent_ids': []}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: events...
|
||||
{'event': 'on_chain_end', 'data': {'output': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'parent_ids': []}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: end...
|
||||
None
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
# How to stream state updates of your graph
|
||||
|
||||
This guide covers how to use `stream_mode="updates"` for your graph, which will stream the updates to the graph state that are made after each node is executed. This differs from using `stream_mode="values"`: instead of streaming the entire value of the state at each superstep, it only streams the updates from each of the nodes that made an update to the state at that superstep. Read [this conceptual guide](https://langchain-ai.github.io/langgraph/concepts/low_level/#stream-and-astream) to learn more.```
|
||||
|
||||
First let's set up our client and thread:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
# create thread
|
||||
thread = await client.threads.create()
|
||||
print(thread)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
// create thread
|
||||
const thread = await client.threads.create();
|
||||
console.log(thread)
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
{'thread_id': '979e3c89-a702-4882-87c2-7a59a250ce16',
|
||||
'created_at': '2024-06-21T15:22:07.453100+00:00',
|
||||
'updated_at': '2024-06-21T15:22:07.453100+00:00',
|
||||
'metadata': {},
|
||||
'status': 'idle',
|
||||
'config': {}}
|
||||
|
||||
Now we can stream by updates, which outputs updates made to the state by each node after it has executed:
|
||||
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
input = {
|
||||
"messages": [
|
||||
{
|
||||
"role": "human",
|
||||
"content": "what's the weather in la"
|
||||
}
|
||||
]
|
||||
}
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
"agent",
|
||||
input=input,
|
||||
stream_mode="updates",
|
||||
):
|
||||
print(f"Receiving new event of type: {chunk.event}...")
|
||||
print(chunk.data)
|
||||
print("\n\n")
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const input = {
|
||||
"messages": [
|
||||
{
|
||||
"role": "human",
|
||||
"content": "What's the weather in la",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
"agent",
|
||||
{
|
||||
input,
|
||||
streamMode: "updates"
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
console.log(f"Receiving new event of type: {chunk.event}...")
|
||||
console.log(chunk.data)
|
||||
console.log("\n\n")
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"What's the weather in la\"}]},
|
||||
\"stream_mode\": [
|
||||
\"updates\"
|
||||
]
|
||||
}" | \
|
||||
sed 's/\r$//' | \
|
||||
awk '
|
||||
/^event:/ {
|
||||
if (data_content != "") {
|
||||
print data_content "\n"
|
||||
}
|
||||
sub(/^event: /, "Receiving event of type: ", $0)
|
||||
printf "%s...\n", $0
|
||||
data_content = ""
|
||||
}
|
||||
/^data:/ {
|
||||
sub(/^data: /, "", $0)
|
||||
data_content = $0
|
||||
}
|
||||
END {
|
||||
if (data_content != "") {
|
||||
print data_content "\n"
|
||||
}
|
||||
}
|
||||
'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
Receiving new event of type: metadata...
|
||||
{'run_id': 'cfc96c16-ed9a-44bd-b5bb-c30e3c0725f0'}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: data...
|
||||
{'agent': {'messages': [{'content': [{'id': 'toolu_0148tMmDK51iLQfG1yaNwRHM', 'input': {'query': 'weather in los angeles'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-1a9d32b0-7007-4a36-abde-8df812a0ed94', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in los angeles'}, 'id': 'toolu_0148tMmDK51iLQfG1yaNwRHM'}], 'invalid_tool_calls': []}]}}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: data...
|
||||
{'action': {'messages': [{'content': '[{"url": "https://www.weatherapi.com/", "content": "{\'location\': {\'name\': \'Los Angeles\', \'region\': \'California\', \'country\': \'United States of America\', \'lat\': 34.05, \'lon\': -118.24, \'tz_id\': \'America/Los_Angeles\', \'localtime_epoch\': 1716062239, \'localtime\': \'2024-05-18 12:57\'}, \'current\': {\'last_updated_epoch\': 1716061500, \'last_updated\': \'2024-05-18 12:45\', \'temp_c\': 18.9, \'temp_f\': 66.0, \'is_day\': 1, \'condition\': {\'text\': \'Overcast\', \'icon\': \'//cdn.weatherapi.com/weather/64x64/day/122.png\', \'code\': 1009}, \'wind_mph\': 2.2, \'wind_kph\': 3.6, \'wind_degree\': 10, \'wind_dir\': \'N\', \'pressure_mb\': 1017.0, \'pressure_in\': 30.02, \'precip_mm\': 0.0, \'precip_in\': 0.0, \'humidity\': 65, \'cloud\': 100, \'feelslike_c\': 18.9, \'feelslike_f\': 66.0, \'vis_km\': 16.0, \'vis_miles\': 9.0, \'uv\': 6.0, \'gust_mph\': 7.5, \'gust_kph\': 12.0}}"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': 'a36e8cd1-0e96-4417-9c15-f10a945d2b42', 'tool_call_id': 'toolu_0148tMmDK51iLQfG1yaNwRHM'}]}}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: data...
|
||||
{'agent': {'messages': [{'content': 'The weather in Los Angeles is currently overcast with a temperature of around 66°F (18.9°C). There are light winds from the north at around 2-3 mph. The humidity is 65% and visibility is good at 9 miles. Overall, mild spring weather conditions in LA.', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-d5c1c2f0-b12d-41ce-990b-f36570e7483d', 'example': False, 'tool_calls': [], 'invalid_tool_calls': []}]}}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: end...
|
||||
None
|
||||
@@ -0,0 +1,255 @@
|
||||
# How to stream full state of your graph
|
||||
|
||||
This guide covers how to use `stream_mode="values"`, which streams the value of the state at each superstep. This differs from using `stream_mode="updates"`: instead of streaming just the updates to the state from each node, it streams the entire graph state at that superstep. Read [this conceptual guide](https://langchain-ai.github.io/langgraph/concepts/low_level/#stream-and-astream) to learn more.```
|
||||
|
||||
First let's set up our client and thread:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
# create thread
|
||||
thread = await client.threads.create()
|
||||
print(thread)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
// create thread
|
||||
const thread = await client.threads.create();
|
||||
console.log(thread)
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
{'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4',
|
||||
'created_at': '2024-06-24T21:30:07.980789+00:00',
|
||||
'updated_at': '2024-06-24T21:30:07.980789+00:00',
|
||||
'metadata': {},
|
||||
'status': 'idle',
|
||||
'config': {}}
|
||||
|
||||
Now we can stream by values, which streams the full state of the graph after each node has finished executing:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
input = {"messages": [{"role": "human", "content": "what's the weather in la"}]}
|
||||
|
||||
# stream values
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
"agent",
|
||||
input=input,
|
||||
stream_mode="values"
|
||||
):
|
||||
print(f"Receiving new event of type: {chunk.event}...")
|
||||
print(chunk.data)
|
||||
print("\n\n")
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const input = {"messages": [{"role": "human", "content": "what's the weather in la"}]}
|
||||
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
"agent",
|
||||
{
|
||||
input,
|
||||
streamMode: "values"
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
console.log(f"Receiving new event of type: {chunk.event}...")
|
||||
console.log(chunk.data)
|
||||
console.log("\n\n")
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in la\"}]},
|
||||
\"stream_mode\": [
|
||||
\"values\"
|
||||
]
|
||||
}" | \
|
||||
sed 's/\r$//' | \
|
||||
awk '
|
||||
/^event:/ {
|
||||
if (data_content != "") {
|
||||
print data_content "\n"
|
||||
}
|
||||
sub(/^event: /, "Receiving event of type: ", $0)
|
||||
printf "%s...\n", $0
|
||||
data_content = ""
|
||||
}
|
||||
/^data:/ {
|
||||
sub(/^data: /, "", $0)
|
||||
data_content = $0
|
||||
}
|
||||
END {
|
||||
if (data_content != "") {
|
||||
print data_content "\n"
|
||||
}
|
||||
}
|
||||
'
|
||||
```
|
||||
|
||||
|
||||
Output:
|
||||
|
||||
Receiving new event of type: metadata...
|
||||
{'run_id': 'f08791ce-0a3d-44e0-836c-ff62cd2e2786'}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: values...
|
||||
{'messages': [{'role': 'human', 'content': 'what's the weather in la'}]}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: values...
|
||||
{'messages': [{'content': 'what's the weather in la', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'faa15565-8823-4aa1-87af-e21b40526fae', 'example': False}, {'content': [{'id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g', 'input': {'query': 'weather in los angeles'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-3fe1db7a-6b8d-4d83-ba07-8657190ad811', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in los angeles'}, 'id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g'}], 'invalid_tool_calls': []}]}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: values...
|
||||
{'messages': [{'content': 'what's the weather in la', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'faa15565-8823-4aa1-87af-e21b40526fae', 'example': False}, {'content': [{'id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g', 'input': {'query': 'weather in los angeles'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-3fe1db7a-6b8d-4d83-ba07-8657190ad811', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in los angeles'}, 'id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g'}], 'invalid_tool_calls': []}, {'content': '[{"url": "https://www.weatherapi.com/", "content": "{\'location\': {\'name\': \'Los Angeles\', \'region\': \'California\', \'country\': \'United States of America\', \'lat\': 34.05, \'lon\': -118.24, \'tz_id\': \'America/Los_Angeles\', \'localtime_epoch\': 1716310320, \'localtime\': \'2024-05-21 9:52\'}, \'current\': {\'last_updated_epoch\': 1716309900, \'last_updated\': \'2024-05-21 09:45\', \'temp_c\': 16.7, \'temp_f\': 62.1, \'is_day\': 1, \'condition\': {\'text\': \'Overcast\', \'icon\': \'//cdn.weatherapi.com/weather/64x64/day/122.png\', \'code\': 1009}, \'wind_mph\': 8.1, \'wind_kph\': 13.0, \'wind_degree\': 250, \'wind_dir\': \'WSW\', \'pressure_mb\': 1015.0, \'pressure_in\': 29.97, \'precip_mm\': 0.0, \'precip_in\': 0.0, \'humidity\': 65, \'cloud\': 100, \'feelslike_c\': 16.7, \'feelslike_f\': 62.1, \'vis_km\': 16.0, \'vis_miles\': 9.0, \'uv\': 5.0, \'gust_mph\': 12.5, \'gust_kph\': 20.2}}"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': '0d5dab31-5ff8-4ae2-a560-bc4bcba7c9d7', 'tool_call_id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g'}]}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: values...
|
||||
{'messages': [{'content': 'what's the weather in la', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'faa15565-8823-4aa1-87af-e21b40526fae', 'example': False}, {'content': [{'id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g', 'input': {'query': 'weather in los angeles'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-3fe1db7a-6b8d-4d83-ba07-8657190ad811', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in los angeles'}, 'id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g'}], 'invalid_tool_calls': []}, {'content': '[{"url": "https://www.weatherapi.com/", "content": "{\'location\': {\'name\': \'Los Angeles\', \'region\': \'California\', \'country\': \'United States of America\', \'lat\': 34.05, \'lon\': -118.24, \'tz_id\': \'America/Los_Angeles\', \'localtime_epoch\': 1716310320, \'localtime\': \'2024-05-21 9:52\'}, \'current\': {\'last_updated_epoch\': 1716309900, \'last_updated\': \'2024-05-21 09:45\', \'temp_c\': 16.7, \'temp_f\': 62.1, \'is_day\': 1, \'condition\': {\'text\': \'Overcast\', \'icon\': \'//cdn.weatherapi.com/weather/64x64/day/122.png\', \'code\': 1009}, \'wind_mph\': 8.1, \'wind_kph\': 13.0, \'wind_degree\': 250, \'wind_dir\': \'WSW\', \'pressure_mb\': 1015.0, \'pressure_in\': 29.97, \'precip_mm\': 0.0, \'precip_in\': 0.0, \'humidity\': 65, \'cloud\': 100, \'feelslike_c\': 16.7, \'feelslike_f\': 62.1, \'vis_km\': 16.0, \'vis_miles\': 9.0, \'uv\': 5.0, \'gust_mph\': 12.5, \'gust_kph\': 20.2}}"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': '0d5dab31-5ff8-4ae2-a560-bc4bcba7c9d7', 'tool_call_id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g'}, {'content': 'Based on the weather API results, the current weather in Los Angeles is overcast with a temperature of around 62°F (17°C). There are light winds from the west-southwest around 8-13 mph. The humidity is 65% and visibility is good at 9 miles. Overall, mild spring weather conditions in LA.', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-4d6d4c23-5aad-4042-b0d9-19407a9e08e3', 'example': False, 'tool_calls': [], 'invalid_tool_calls': []}]}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: end...
|
||||
None
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
If we want to just get the final result, we can use this endpoint and just keep track of the last value we received
|
||||
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
final_answer = None
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
"agent",
|
||||
input=input,
|
||||
stream_mode="values"
|
||||
):
|
||||
if chunk.event == "values":
|
||||
final_answer = chunk.data
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
let finalAnswer;
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
"agent",
|
||||
{
|
||||
input,
|
||||
streamMode: "values"
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
finalAnswer = chunk.data;
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in la\"}]},
|
||||
\"stream_mode\": [
|
||||
\"values\"
|
||||
]
|
||||
}" | \
|
||||
sed 's/\r$//' | \
|
||||
awk '
|
||||
/^data:/ {
|
||||
sub(/^data: /, "", $0)
|
||||
data_content = $0
|
||||
}
|
||||
END {
|
||||
if (data_content != "") {
|
||||
print data_content
|
||||
}
|
||||
}
|
||||
'
|
||||
```
|
||||
|
||||
|
||||
Output:
|
||||
|
||||
{'messages': [{'content': 'what's the weather in la',
|
||||
'additional_kwargs': {},
|
||||
'response_metadata': {},
|
||||
'type': 'human',
|
||||
'name': None,
|
||||
'id': 'e78c2f94-d810-42fc-a399-11f6bb1b1092',
|
||||
'example': False},
|
||||
{'content': [{'id': 'toolu_01SBMoAGr4U9x3ibztm2UUom',
|
||||
'input': {'query': 'weather in los angeles'},
|
||||
'name': 'tavily_search_results_json',
|
||||
'type': 'tool_use'}],
|
||||
'additional_kwargs': {},
|
||||
'response_metadata': {},
|
||||
'type': 'ai',
|
||||
'name': None,
|
||||
'id': 'run-80767ab8-09fc-40ec-9e45-657ddef5e0b1',
|
||||
'example': False,
|
||||
'tool_calls': [{'name': 'tavily_search_results_json',
|
||||
'args': {'query': 'weather in los angeles'},
|
||||
'id': 'toolu_01SBMoAGr4U9x3ibztm2UUom'}],
|
||||
'invalid_tool_calls': []},
|
||||
{'content': '[{"url": "https://www.weatherapi.com/", "content": "{\'location\': {\'name\': \'Los Angeles\', \'region\': \'California\', \'country\': \'United States of America\', \'lat\': 34.05, \'lon\': -118.24, \'tz_id\': \'America/Los_Angeles\', \'localtime_epoch\': 1716310320, \'localtime\': \'2024-05-21 9:52\'}, \'current\': {\'last_updated_epoch\': 1716309900, \'last_updated\': \'2024-05-21 09:45\', \'temp_c\': 16.7, \'temp_f\': 62.1, \'is_day\': 1, \'condition\': {\'text\': \'Overcast\', \'icon\': \'//cdn.weatherapi.com/weather/64x64/day/122.png\', \'code\': 1009}, \'wind_mph\': 8.1, \'wind_kph\': 13.0, \'wind_degree\': 250, \'wind_dir\': \'WSW\', \'pressure_mb\': 1015.0, \'pressure_in\': 29.97, \'precip_mm\': 0.0, \'precip_in\': 0.0, \'humidity\': 65, \'cloud\': 100, \'feelslike_c\': 16.7, \'feelslike_f\': 62.1, \'vis_km\': 16.0, \'vis_miles\': 9.0, \'uv\': 5.0, \'gust_mph\': 12.5, \'gust_kph\': 20.2}}"}]',
|
||||
'additional_kwargs': {},
|
||||
'response_metadata': {},
|
||||
'type': 'tool',
|
||||
'name': 'tavily_search_results_json',
|
||||
'id': 'af25e94a-c119-48c3-bbd3-096e42f472ac',
|
||||
'tool_call_id': 'toolu_01SBMoAGr4U9x3ibztm2UUom'},
|
||||
{'content': 'Based on the weather API results, the current weather in Los Angeles is overcast with a temperature of around 62°F (17°C). There are light winds from the west-southwest around 8-13 mph. The humidity is 65% and visibility is good at 9 miles. Overall, mild spring weather conditions in LA.',
|
||||
'additional_kwargs': {},
|
||||
'response_metadata': {},
|
||||
'type': 'ai',
|
||||
'name': None,
|
||||
'id': 'run-b90f0037-e56a-4f3b-ad92-00d10d079a9e',
|
||||
'example': False,
|
||||
'tool_calls': [],
|
||||
'invalid_tool_calls': []}]}
|
||||
@@ -9,6 +9,8 @@ Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmi
|
||||
1. In the top-right corner, select `Open LangGraph Studio`.
|
||||
1. [Invoke an assistant](./invoke_studio.md) or [view an existing thread](./threads_studio.md).
|
||||
|
||||
The following GIF shows these exact steps being carried out:
|
||||
The following video shows these exact steps being carried out:
|
||||
|
||||

|
||||
<video controls allowfullscreen="true" poster="../img/studio_usage_poster.png">
|
||||
<source src="../img/studio_usage.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# LangGraph Studio With Local Deployment
|
||||
|
||||
!!! warning "Browser Compatibility"
|
||||
Viewing the studio page of a local LangGraph deployment does not work in Safari. Use Chrome instead.
|
||||
|
||||
## Setup
|
||||
|
||||
Make sure you have setup your app correctly, by creating a compiled graph, a `.env` file with any environment variables, and a `langgraph.json` config file that points to your environment file and compiled graph. See [here](https://langchain-ai.github.io/langgraph/cloud/deployment/setup/) for more detailed instructions.
|
||||
|
||||
After you have your app setup, head into the directory with your `langgraph.json` file and call `langgraph up -c langgraph.json --watch` to start the API server in watch mode which means it will restart on code changes, which is ideal for local testing. If the API server start correctly you should see logs that look something like this:
|
||||
|
||||
Ready!
|
||||
- API: http://localhost:8123
|
||||
2024-06-26 19:20:41,056:INFO:uvicorn.access 127.0.0.1:44138 - "GET /ok HTTP/1.1" 200
|
||||
|
||||
Read this [reference](https://langchain-ai.github.io/langgraph/cloud/reference/cli/#up) to learn about all the options for starting the API server.
|
||||
|
||||
## Access Studio
|
||||
|
||||
Once you have successfully started the API server, you can access the studio by going to the following URL: `https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:8123` (see warning above if using Safari).
|
||||
|
||||
If everything is working correctly you should see the studio show up looking something like this (with your graph diagram on the left hand side):
|
||||
|
||||

|
||||
|
||||
## Use the Studio for Testing
|
||||
|
||||
To learn about how to use the studio for testing, read the [LangGraph Studio how-tos](https://langchain-ai.github.io/langgraph/cloud/how-tos/#langgraph-studio).
|
||||
@@ -6,14 +6,18 @@
|
||||
1. View the state of the thread (i.e. the output) in the right-hand pane.
|
||||
1. To create a new thread, select `+ New Thread`.
|
||||
|
||||
The following GIF shows these exact steps being carried out:
|
||||
The following video shows these exact steps being carried out:
|
||||
|
||||

|
||||
<video controls="true" allowfullscreen="true" poster="../img/studio_threads_poster.png">
|
||||
<source src="../img/studio_threads.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
## Edit Thread State
|
||||
|
||||
The LangGraph Studio UI contains features for editing thread state. Explore these features in the right-hand pane. Select the `Edit` icon, modify the desired state, and then select `Fork` to invoke the assistant with the updated state.
|
||||
|
||||
The following GIF shows how to edit a thread in the studio:
|
||||
The following video shows how to edit a thread in the studio:
|
||||
|
||||

|
||||
<video controls allowfullscreen="true" poster="../img/studio_forks_poster.png">
|
||||
<source src="../img/studio_forks.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
|
After Width: | Height: | Size: 884 KiB |
@@ -1,21 +1,30 @@
|
||||
# LangGraph Cloud (beta)
|
||||
|
||||
!!! danger "Important"
|
||||
LangGraph Cloud is a closed source, paid product in an invite-only stage. We are currently focused on providing high bandwidth support to make our select early customers successful. If you are interested in applying for access, please fill out [this form](https://www.langchain.com/langgraph-cloud-beta).
|
||||
!!! tip
|
||||
- LangGraph is an MIT-licensed open-source library, which we are committed to maintaining and growing for the community.
|
||||
- LangGraph Cloud is an optional managed hosting service for LangGraph, which provides additional features geared towards production deployments.
|
||||
- We are actively contributing improvements back to LangGraph informed by our work on LangGraph Cloud.
|
||||
- You can always deploy LangGraph applications on your own infrastructure using the open-source LangGraph project.
|
||||
|
||||
!!! warning "Under Construction"
|
||||
LangGraph Cloud documentation is under construction. Contents may change until general availability.
|
||||
|
||||

|
||||
|
||||
<video controls preload="auto" allowfullscreen="true" poster="how-tos/img/studio_forks_poster.png">
|
||||
<source src="how-tos/img/studio_forks.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
LangGraph Cloud is a managed service for deploying and hosting LangGraph applications. Deploying applications with LangGraph Cloud shortens the time-to-market for developers. With one click, deploy a production-ready API with built-in persistence for your LangGraph application. LangGraph Cloud APIs are horizontally scalable and deployed with durable storage.
|
||||
|
||||
The LangGraph Cloud API exposes functionality of your LangGraph application through [Assistants](./concepts/index.md#assistants). An assistant abstracts the cognitive architecture of your graph. Invoke an assistant by calling the pre-built [API endpoints](./reference/api/api_ref.md).
|
||||
The LangGraph Cloud API exposes functionality of your LangGraph application through [Assistants](./concepts/api.md#assistants). An assistant abstracts the cognitive architecture of your graph. Invoke an assistant by calling the pre-built [API endpoints](./reference/api/api_ref.md).
|
||||
|
||||
LangGraph Cloud is seamlessly integrated with [LangSmith](https://www.langchain.com/langsmith) and is accessible from within the LangSmith UI.
|
||||
|
||||
LangGraph Cloud applications can be tested and debugged using the [LangGraph Studio Desktop](https://github.com/langchain-ai/langgraph-studio).
|
||||
|
||||
## Key Features
|
||||
|
||||
The LangGraph Cloud API supports key LangGraph features in addition to new functionality for enabling complex, agentic workflows.
|
||||
@@ -31,5 +40,5 @@ The LangGraph Cloud API supports key LangGraph features in addition to new funct
|
||||
|
||||
- [Tutorials](./quick_start.md): Learn to build and deploy applications for LangGraph Cloud.
|
||||
- [How-to Guides](./how-tos/index.md): Learn how to set up a LangGraph application for deployment and implement features of the LangGraph Cloud API such as streaming tokens, configuring double texting, and creating cron jobs. Go here if you want to copy and run a specific code snippet.
|
||||
- [Conceptual Guides](./concepts/index.md): In-depth explanations of the core data models (e.g. assistants) and key features (e.g. double texting) of the LangGraph Cloud API.
|
||||
- [Conceptual Guides](./concepts/api.md): In-depth explanations of the core data models (e.g. assistants), key features of the LangGraph Cloud API (e.g. double texting), and the architecture of a LangGraph Cloud deployment.
|
||||
- [Reference](./reference/api/api_ref.md): References for the LangGraph Cloud API, the corresponding Python and JS/TS SDKs, the LangGraph CLI, and deployment environment variables.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# Quick Start
|
||||
|
||||
This quick start guide will cover how to build a simple agent that can look up things on the internet. We will then deploy it to LangGraph Cloud, use the LangGraph Studio to visualize and test it out, and use the LangGraph SDK to interact with it.
|
||||
|
||||
## Set up requirements
|
||||
@@ -9,10 +10,9 @@ This tutorial will use:
|
||||
- Tavily for the search engine - sign up and get an API key [here](https://app.tavily.com/)
|
||||
- LangSmith for hosting - sign up and get an API key [here](https://smith.langchain.com/)
|
||||
|
||||
|
||||
## Set up local files
|
||||
|
||||
1. Create a new application with the following directory and files:
|
||||
1. Create a new application with the following directory and files:
|
||||
|
||||
<my-app>/
|
||||
|-- agent.py # code for your LangGraph agent
|
||||
@@ -20,52 +20,63 @@ This tutorial will use:
|
||||
|-- langgraph.json # configuration file for LangGraph
|
||||
|-- .env # environment files with API keys
|
||||
|
||||
2. The `agent.py` file should contain Python code for defining your graph. The following code is a simple example, the important thing is that at some point in your file you compile your graph and assign the compiled graph to a variable (in this case the `graph` variable). This example code uses `create_react_agent`, a prebuilt agent, read more about it [here](..//concepts/agentic_concepts.md#react-agent).
|
||||
2. The `agent.py` file should contain Python code for defining your graph. The following code is a simple example, the important thing is that at some point in your file you compile your graph and assign the compiled graph to a variable (in this case the `graph` variable). This example code uses `create_react_agent`, a prebuilt agent, read more about it [here](..//concepts/agentic_concepts.md#react-agent).
|
||||
|
||||
```python
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
|
||||
model = ChatAnthropic(model="claude-3-5-sonnet-20240620")
|
||||
|
||||
|
||||
tools = [TavilySearchResults(max_results=2)]
|
||||
|
||||
|
||||
graph = create_react_agent(model, tools)
|
||||
```
|
||||
|
||||
3. The `requirements.txt` file should contain any dependencies for your graph(s). In this case we only require four packages for our graph to run:
|
||||
3. The `requirements.txt` file should contain any dependencies for your graph(s). In this case we only require four packages for our graph to run:
|
||||
|
||||
langgraph
|
||||
langchain_anthropic
|
||||
tavily-python
|
||||
langchain_community
|
||||
|
||||
4. The `langgraph.json` file is a configuration file that describes what graph(s) you are going to host. In this case we only have one graph to host: the compiled `graph` object from `agent.py`.
|
||||
4. The [`langgraph.json`][langgraph.json] file is a configuration file that describes what graph(s) you are going to host. In this case we only have one graph to host: the compiled `graph` object from `agent.py`.
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"agent": "./agent.py:graph"
|
||||
},
|
||||
"env": ".env"
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"agent": "./agent.py:graph"
|
||||
},
|
||||
"env": ".env"
|
||||
}
|
||||
```
|
||||
|
||||
Learn more about the LangGraph CLI configuration file [here](./reference/cli.md#configuration-file).
|
||||
5. The `.env` file should have any environment variables needed to run your graph. This will only be used for local testing, so if you are not testing locally you can skip this step. NOTE: if you do add this, you should NOT check this into git. For this graph, we need two environment variables:
|
||||
|
||||
5. The `.env` file should have any environment variables needed to run your graph. This will only be used for local testing, so if you are not testing locally you can skip this step. NOTE: if you do add this, you should NOT check this into git. For this graph, we need two environment variables:
|
||||
|
||||
```shell
|
||||
ANTHROPIC_API_KEY=...
|
||||
TAVILY_API_KEY=...
|
||||
```
|
||||
|
||||
Now that we have set everything up on our local file system, we are ready to host our graph.
|
||||
Now that we have set everything up on our local file system, we are ready to host our graph.
|
||||
|
||||
## Test the graph build locally
|
||||
|
||||
Before deploying to the cloud, we probably want to test the building of our graph locally. This is useful to make sure we have configured our CLI configuration file correctly and our graph runs.
|
||||
### Using LangGraph Studio Desktop (recommended)
|
||||
|
||||

|
||||
|
||||
Testing your graph locally is easy with LangGraph Studio Desktop. LangGraph Studio offers a new way to develop LLM applications by providing a specialized agent IDE that enables visualization, interaction, and debugging of complex agentic applications
|
||||
|
||||
With visual graphs and the ability to edit state, you can better understand agent workflows and iterate faster. LangGraph Studio integrates with [LangSmith](https://smith.langchain.com) so you can collaborate with teammates to debug failure modes.
|
||||
|
||||
### Using the LangGraph CLI
|
||||
|
||||
Before deploying to the cloud, we probably want to test the building of our graph locally. This is useful to make sure we have configured our [CLI configuration file][langgraph.json] correctly and our graph runs.
|
||||
|
||||
In order to do this we can first install the LangGraph CLI
|
||||
|
||||
@@ -73,13 +84,13 @@ In order to do this we can first install the LangGraph CLI
|
||||
pip install langgraph-cli
|
||||
```
|
||||
|
||||
We can then stand up a simple test server. The server this stands up is INCREDIBLY simple - it is just a single endpoint and has no persistence. **This should not be used for hosting your application, only for testing the build and basic functionality.**
|
||||
We can then test our API server locally. This requires access to LangGraph closed beta. In order to run the server locally, you will need to add your `LANGSMITH_API_KEY` to the .env file so we can validate you have access to LangGraph closed beta.
|
||||
|
||||
```shell
|
||||
langgraph test
|
||||
langgraph up
|
||||
```
|
||||
|
||||
This will test building of the agent server. If this runs successfully, you should see something like:
|
||||
This will start up the LangGraph API server locally. If this runs successfully, you should see something like:
|
||||
|
||||
```shell
|
||||
Ready!
|
||||
@@ -87,7 +98,7 @@ Ready!
|
||||
2024-06-26 19:20:41,056:INFO:uvicorn.access 127.0.0.1:44138 - "GET /ok HTTP/1.1" 200
|
||||
```
|
||||
|
||||
You can now test this out! Again, we only expose a single simple endpoint (for streaming stateless runs). This is intended to allow you to test that the agent is properly set up, but should **NOT** but used for production purposes. To test it out, you can go to another terminal window and run:
|
||||
You can now test this out! **Note: this local server is intended SOLELY for local testing purposes and is not performant enough for production applications, so please do not use it as such.** To test it out, you can go to another terminal window and run:
|
||||
|
||||
```shell
|
||||
curl --request POST \
|
||||
@@ -124,13 +135,13 @@ Turn the `<my-app>` directory into a GitHub repo. You can use the GitHub CLI if
|
||||
|
||||
### Deploy from GitHub with LangGraph Cloud
|
||||
|
||||
Once you have created your github repository with a Python file containing your compiled graph as well as a `langgraph.json` file containing the configuration for hosting your graph, you can head over to LangSmith and click on the 🚀 icon on the left navbar to create a new deployment. Then click the `+ New Deployment` button.
|
||||
Once you have created your github repository with a Python file containing your compiled graph as well as a `langgraph.json` file containing the configuration for hosting your graph, you can head over to LangSmith and click on the 🚀 icon on the left navbar to create a new deployment. Then click the `+ New Deployment` button.
|
||||
|
||||

|
||||
|
||||
***If you have not deployed to LangGraph Cloud before:*** there will be a button that shows up saying Import from GitHub. You’ll need to follow that flow to connect LangGraph Cloud to GitHub.
|
||||
**_If you have not deployed to LangGraph Cloud before:_** there will be a button that shows up saying Import from GitHub. You’ll need to follow that flow to connect LangGraph Cloud to GitHub.
|
||||
|
||||
***Once you have set up your GitHub connection:*** the new deployment page will look as follows:
|
||||
**_Once you have set up your GitHub connection:_** the new deployment page will look as follows:
|
||||
|
||||

|
||||
|
||||
@@ -140,7 +151,7 @@ To deploy your application, you should do the following:
|
||||
2. Search for your repo to deploy in the search bar and select it
|
||||
3. Choose any name
|
||||
4. In the `LangGraph API config file` field, enter the path to your `langgraph.json` file (which in this case is just `langgraph.json`)
|
||||
5. For Git Reference, you can select either the git branch for the code you want to deploy, or the exact commit SHA.
|
||||
5. For Git Reference, you can select either the git branch for the code you want to deploy, or the exact commit SHA.
|
||||
6. If your chain relies on environment variables, add those in. They will be propagated to the underlying server so your code can access them. In this case, we need `ANTHROPIC_API_KEY` and `TAVILY_API_KEY`.
|
||||
|
||||
Putting this all together, you should have something as follows for your deployment details:
|
||||
@@ -157,7 +168,7 @@ After your deployment is complete, your deployments page should look as follows:
|
||||
|
||||

|
||||
|
||||
You can see that by default, you get access to the `Trace Count` monitoring chart and `Recent Traces` run view. These are powered by LangSmith.
|
||||
You can see that by default, you get access to the `Trace Count` monitoring chart and `Recent Traces` run view. These are powered by LangSmith.
|
||||
|
||||
You can click on `All Charts` to view all monitoring info for your server, or click on `See tracing project` to get more information on an individual trace.
|
||||
|
||||
@@ -181,18 +192,16 @@ On this page you can test out your graph by passing in starting states and click
|
||||
|
||||
## Use with the SDK
|
||||
|
||||
Once you have tested that your hosted graph works as expected using LangGraph Studio, you can start using your hosted graph all over your organization by using the LangGraph SDK. Let's see how we can access our hosted graph and execute our run from a python file.
|
||||
Once you have tested that your hosted graph works as expected using LangGraph Studio, you can start using your hosted graph all over your organization by using the LangGraph SDK. Let's see how we can access our hosted graph and execute our run from a python file.
|
||||
|
||||
First, make sure you have the SDK installed by calling `pip install langgraph_sdk`.
|
||||
|
||||
Before using, you need to get the URL of your LangGraph deployment. You can find this on the auto generated documentation page here:
|
||||
|
||||

|
||||
Before using, you need to get the URL of your LangGraph deployment. You can find this in the `Deployment` view. Click the URL to copy it to the clipboard.
|
||||
|
||||
You also need to make sure you have set up your API key properly so you can authenticate with LangGraph Cloud.
|
||||
|
||||
```shell
|
||||
export LANGCHAIN_API_KEY=...
|
||||
export LANGSMITH_API_KEY=...
|
||||
```
|
||||
|
||||
The first thing to do when using the SDK is to setup our client, access our assistant, and create a thread to execute a run on:
|
||||
@@ -224,31 +233,30 @@ async for chunk in client.runs.stream(
|
||||
input=input,
|
||||
stream_mode="updates",
|
||||
):
|
||||
if chunk.data and "run_id" not in chunk.data:
|
||||
print(chunk.data)
|
||||
if chunk.data and chunk.event != "metadata":
|
||||
print(chunk.data)
|
||||
```
|
||||
|
||||
{'agent': {'messages': [{'content': "Hi Bagatur! It's nice to meet you. How can I assist you today?", 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_9cb5d38cf7'}, 'type': 'ai', 'name': None, 'id': 'run-c89118b7-1b1e-42b9-a85d-c43fe99881cd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
|
||||
|
||||
|
||||
## What's Next
|
||||
|
||||
Congratulations! If you've worked your way through this tutorial you are well on your way to becoming a LangGraph Cloud expert. Here are some other resources to check out to help you out on the path to expertise:
|
||||
|
||||
### LangGraph Cloud How-tos
|
||||
|
||||
If you want to learn more about streaming from hosted graphs, check out the Streaming [how-to guides](https://langchain-ai.github.io/langgraph/cloud/how-tos/#streaming).
|
||||
If you want to learn more about streaming from hosted graphs, check out the Streaming [how-to guides](how-tos/index.md#streaming).
|
||||
|
||||
To learn more about double-texting and all the ways you can handle it in your application, read up on these [how-to guides](https://langchain-ai.github.io/langgraph/cloud/how-tos/#double-texting).
|
||||
To learn more about double-texting and all the ways you can handle it in your application, read up on these [how-to guides](how-tos/index.md#double-texting).
|
||||
|
||||
To learn about how to include different human-in-the-loop behavior in your graph, take a look at [these how-tos](https://langchain-ai.github.io/langgraph/cloud/how-tos/#human-in-the-loop).
|
||||
To learn about how to include different human-in-the-loop behavior in your graph, take a look at [these how-tos](how-tos/index.md#human-in-the-loop).
|
||||
|
||||
### LangGraph Tutorials
|
||||
|
||||
Before hosting, you have to write a graph to host. Here are some tutorials to get you more comfortable with writing LangGraph graphs and give you inspiration for the types of graphs you want to host.
|
||||
|
||||
[This tutorial](https://langchain-ai.github.io/langgraph/tutorials/customer-support/customer-support/) walks you through how to write a customer support bot using LangGraph.
|
||||
[This tutorial](../tutorials/customer-support/customer-support.ipynb) walks you through how to write a customer support bot using LangGraph.
|
||||
|
||||
If you are interested in writing a SQL agent, check out [this tutorial](https://langchain-ai.github.io/langgraph/tutorials/sql-agent/).
|
||||
If you are interested in writing a SQL agent, check out [this tutorial](../tutorials/sql-agent.ipynb).
|
||||
|
||||
Check out the [LangGraph tutorials](https://langchain-ai.github.io/langgraph/tutorials/) page to read about more exciting use cases.
|
||||
Check out the [LangGraph tutorials](../tutorials/index.md) page to read about more exciting use cases.
|
||||
|
||||
@@ -3,3 +3,20 @@
|
||||
The LangGraph Cloud API reference is available with each deployment at the `/docs` URL path (e.g. `http://localhost:8124/docs`).
|
||||
|
||||
Click <a href="/langgraph/cloud/reference/api/api_ref.html" target="_blank">here</a> to view the API reference.
|
||||
|
||||
## Authentication
|
||||
|
||||
For deployments to LangGraph Cloud, authentication is required. Pass the `X-Api-Key` header with each request to the LangGraph Cloud API. The value of the header should be set to a valid LangSmith API key for the organization where the API is deployed.
|
||||
|
||||
Example `curl` command:
|
||||
```shell
|
||||
curl --request POST \
|
||||
--url http://localhost:8124/assistants/search \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'X-Api-Key: LANGSMITH_API_KEY' \
|
||||
--data '{
|
||||
"metadata": {},
|
||||
"limit": 10,
|
||||
"offset": 0
|
||||
}'
|
||||
```
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SearchRequest"
|
||||
"$ref": "#/components/schemas/AssistantSearchRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -388,7 +388,7 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SearchRequest"
|
||||
"$ref": "#/components/schemas/ThreadSearchRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1463,6 +1463,52 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/runs/crons/search": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"crons/search"
|
||||
],
|
||||
"summary": "Search Crons",
|
||||
"description": "Search all active crons",
|
||||
"operationId": "search_crons_runs_crons_post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CronSearch"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Cron"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Response Search Crons Search Post"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/runs/stream": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@@ -1920,6 +1966,39 @@
|
||||
"title": "CronCreate",
|
||||
"description": "Payload for creating a cron."
|
||||
},
|
||||
"CronSearch" : {
|
||||
"properties": {
|
||||
"assistant_id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Assistant Id"
|
||||
},
|
||||
"thread_id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Thread Id"
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"title": "Limit",
|
||||
"description": "Maximum number to return.",
|
||||
"default": 10,
|
||||
"minimum": 1,
|
||||
"maximum": 1000
|
||||
},
|
||||
"offset": {
|
||||
"type": "integer",
|
||||
"title": "Offset",
|
||||
"description": "Offset to start from.",
|
||||
"default": 0,
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required" : [],
|
||||
"title": "CronSearch",
|
||||
"description": "Payload for listing crons"
|
||||
},
|
||||
"GraphSchema": {
|
||||
"properties": {
|
||||
"graph_id": {
|
||||
@@ -2335,7 +2414,76 @@
|
||||
},
|
||||
"type": "object",
|
||||
"title": "SearchRequest",
|
||||
"description": "Payload for listing assistants/threads/runs."
|
||||
"description": "Payload for listing runs."
|
||||
},
|
||||
"AssistantSearchRequest": {
|
||||
"properties": {
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"title": "Metadata",
|
||||
"description": "Metadata to search for."
|
||||
},
|
||||
"graph_id": {
|
||||
"type": "string",
|
||||
"title": "Graph Id",
|
||||
"description": "Filter by graph ID."
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"title": "Limit",
|
||||
"description": "Maximum number to return.",
|
||||
"default": 10,
|
||||
"minimum": 1,
|
||||
"maximum": 1000
|
||||
},
|
||||
"offset": {
|
||||
"type": "integer",
|
||||
"title": "Offset",
|
||||
"description": "Offset to start from.",
|
||||
"default": 0,
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"title": "SearchRequest",
|
||||
"description": "Payload for listing assistants."
|
||||
},
|
||||
"ThreadSearchRequest": {
|
||||
"properties": {
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"title": "Metadata",
|
||||
"description": "Metadata to search for."
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"idle",
|
||||
"busy",
|
||||
"interrupted"
|
||||
],
|
||||
"title": "Status",
|
||||
"description": "Filter by thread status."
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"title": "Limit",
|
||||
"description": "Maximum number to return.",
|
||||
"default": 10,
|
||||
"minimum": 1,
|
||||
"maximum": 1000
|
||||
},
|
||||
"offset": {
|
||||
"type": "integer",
|
||||
"title": "Offset",
|
||||
"description": "Offset to start from.",
|
||||
"default": 0,
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"title": "SearchRequest",
|
||||
"description": "Payload for listing threads."
|
||||
},
|
||||
"Thread": {
|
||||
"properties": {
|
||||
@@ -2357,6 +2505,15 @@
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"title": "Metadata"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"idle",
|
||||
"busy",
|
||||
"interrupted"
|
||||
],
|
||||
"title": "Status"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
|
||||
@@ -6,13 +6,14 @@ The LangGraph CLI includes commands to build and run a LangGraph Cloud API serve
|
||||
2. Install the `langgraph-cli` Python package (e.g. `pip install langgraph-cli`).
|
||||
3. Run the command `langgraph --help` to confirm that the CLI is installed.
|
||||
|
||||
[](){#langgraph.json}
|
||||
## Configuration File
|
||||
The LangGraph CLI requires a JSON configuration file with the following keys:
|
||||
|
||||
| Key | Description |
|
||||
| --- | ----------- |
|
||||
| `dependencies` | **Required**. Array of dependencies for LangGraph Cloud API server. Dependencies can be one of the following: (1) `"."`, which will look for local Python packages, (2) `pyproject.toml`, `setup.py` or `requirements.txt` in the app directory `"./local_package"`, or (3) a package name. |
|
||||
| `graphs` | **Required**. Mapping from graph ID to path where the compiled graph is defined. Example: `./your_package/your_file.py:variable`, where `variable` is an instance of `langgraph.graph.graph.CompiledGraph`. |
|
||||
| `graphs` | **Required**. Mapping from graph ID to path where the compiled graph or a function that makes a graph is defined. Example: <ul><li>`./your_package/your_file.py:variable`, where `variable` is an instance of `langgraph.graph.state.CompiledStateGraph`</li><li>`./your_package/your_file.py:make_graph`, where `make_graph` is a function that takes a config dictionary (`langchain_core.runnables.RunnableConfig`) and creates an instance of `langgraph.graph.state.StateGraph` / `langgraph.graph.state.CompiledStateGraph`.</li></ul> |
|
||||
| `env` | Path to `.env` file or a mapping from environment variable to its value. |
|
||||
| `python_version` | `3.11` or `3.12`. Defaults to `3.11`. |
|
||||
| `pip_config_file`| Path to `pip` config file. |
|
||||
@@ -48,7 +49,7 @@ Example:
|
||||
"."
|
||||
],
|
||||
"graphs": {
|
||||
"my_graph_id": "./your_package/your_file.py:variable"
|
||||
"my_graph_id": "./your_package/your_file.py:make_graph"
|
||||
},
|
||||
"env": {
|
||||
"OPENAI_API_KEY": "secret-key"
|
||||
@@ -82,6 +83,31 @@ langgraph build [OPTIONS]
|
||||
| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. |
|
||||
| `--help` | | Display command documentation. |
|
||||
|
||||
### `up`
|
||||
Start langgraph API server. For local testing, requires a LangSmith API key with access to LangGraph Cloud closed beta. Requires a license key for production use.
|
||||
|
||||
**Usage**
|
||||
```
|
||||
langgraph up [OPTIONS]
|
||||
```
|
||||
|
||||
**Options**
|
||||
|
||||
| Option | Default | Description |
|
||||
| ------ | ------- | ----------- |
|
||||
| `--wait` | | Wait for services to start before returning. Implies --detach |
|
||||
| `--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. |
|
||||
| `--debugger-port INTEGER` | | Pull the debugger image locally and serve the UI on specified port |
|
||||
| `--verbose` | | Show more output from the server logs. |
|
||||
| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. |
|
||||
| `-d, --docker-compose FILE` | | Path to docker-compose.yml file with additional services to launch. |
|
||||
| `-p, --port INTEGER` | `8123` | Port to expose. Example: `langgraph test --port 8000` |
|
||||
| `--pull / --no-pull` | `pull` | Pull latest images. Use --no-pull for running the server with locally-built images. Example: `langgraph up --no-pull` |
|
||||
| `--recreate / --no-recreate` | `no-recreate` | Recreate containers even if their configuration and image haven't changed |
|
||||
| `--help` | | Display command documentation. |
|
||||
|
||||
### `test`
|
||||
Test your LangGraph in the cloud. The only function you can call from the SDK after testing your graph is `client.runs.stream(thread_id=None, ...)`
|
||||
|
||||
|
||||
@@ -56,6 +56,25 @@ This is a pretty advanced interaction pattern. In this interaction pattern, the
|
||||
|
||||
See [this guide](../how-tos/human_in_the_loop/time-travel.ipynb) for how to do this in LangGraph.
|
||||
|
||||
## Review Tool Calls
|
||||
|
||||
This is a specific type of human-in-the-loop interaction but it's worth calling out because it is so common. A lot of agent decisions are made via tool calling, so having a clear UX for reviewing tool calls is handy.
|
||||
|
||||
A tool call consists of:
|
||||
- The name of the tool to call
|
||||
- Arguments to pass to the tool
|
||||
|
||||
Note that these tool calls can obviously be used for actually calling functions, but they can also be used for other purposes, like to route the agent in a specific direction.
|
||||
You will want to review the tool call for both of these use cases.
|
||||
|
||||
When reviewing tool calls, there are few actions you may want to take.
|
||||
|
||||
1. Approve the tool call (and let the agent continue on its way)
|
||||
2. Manually change the tool call, either the tool name or the tool arguments (and let the agent continue on its way after that)
|
||||
3. Leave feedback on the tool call. This differs from (2) in that you are not changing the tool call directly, but rather leaving natural language feedback suggesting the LLM call it differently (or call a different tool). You could do this by either adding a `ToolMessage` and having the feedback be the result of the tool call, or by adding a `ToolMessage` (that simulates an error) and then a `HumanMessage` (with the feedback).
|
||||
|
||||
See [this guide](../how-tos/human_in_the_loop/review-tool-calls.ipynb) for how to do this in LangGraph.
|
||||
|
||||
## Map-Reduce
|
||||
|
||||
A common pattern in agents is to generate a list of objects, do some work on each of those objects, and then combine the results. This is very similar to the common [map-reduce](https://en.wikipedia.org/wiki/MapReduce) operation. This can be tricky for a few reasons. First, it can be tough to define a structured graph ahead of time because the length of the list of objects may be unknown. Second, in order to do this map-reduce you need multiple versions of the state to exist... but the graph shares a common shared state, so how can this be?
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Conceptual Guides
|
||||
|
||||
In this guide we will explore the concepts behind build agentic and multi-agent systems with LangGraph. We assume you have already learned the basic covered in the [introduction tutorial](https://langchain-ai.github.io/langgraph/tutorials/introduction) and want to deepen your understanding of LangGraph's underlying design and inner workings.
|
||||
In this guide we will explore the concepts behind build agentic and multi-agent systems with LangGraph. We assume you have already learned the basic covered in the [introduction tutorial](../tutorials/introduction.ipynb) and want to deepen your understanding of LangGraph's underlying design and inner workings.
|
||||
|
||||
There are three main parts to this concept guide. First, we'll discuss at a very high level what it means to be agentic. Next, we'll look at lower-level concepts in LangGraph that are core for understanding how to build your own agentic systems. Finally, we'll discuss common agentic patterns and how you can achieve those with LangGraph. These will be mostly conceptual guides - for more technical, hands-on guides see our [how-to guides](https://langchain-ai.github.io/langgraph/how-tos/)
|
||||
There are three main parts to this concept guide. First, we'll discuss at a very high level what it means to be agentic. Next, we'll look at lower-level concepts in LangGraph that are core for understanding how to build your own agentic systems. Finally, we'll discuss common agentic patterns and how you can achieve those with LangGraph. These will be mostly conceptual guides - for more technical, hands-on guides see our [how-to guides](../how-tos/index.md)
|
||||
|
||||
|
||||
LangGraph for Agentic Applications
|
||||
@@ -20,7 +20,7 @@ Low Level Concepts
|
||||
- [State](low_level.md#state)
|
||||
- [Schema](low_level.md#schema)
|
||||
- [Reducers](low_level.md#reducers)
|
||||
- [MessageState](low_level.md#messagestate)
|
||||
- [MessageState](low_level.md#working-with-messages-in-graph-state)
|
||||
- [Nodes](low_level.md#nodes)
|
||||
- [`START` node](low_level.md#start-node)
|
||||
- [`END` node](low_level.md#end-node)
|
||||
|
||||
@@ -14,7 +14,9 @@ By composing `Nodes` and `Edges`, you can create complex, looping workflows that
|
||||
|
||||
In short: _nodes do the work. edges tell what to do next_.
|
||||
|
||||
LangGraph's underlying graph algorithm uses [message passing](https://en.wikipedia.org/wiki/Message_passing) to define a general program. When a `Node` completes, it sends a message along one or more edges to other node(s). These nodes run their functions, pass the resulting messages to the next set of nodes, and on and on it goes. Inspired by [Pregel](https://research.google/pubs/pregel-a-system-for-large-scale-graph-processing/), the program proceeds in discrete "super-steps" that are all executed conceptually in parallel. Whenever the graph is run, all the nodes start in an `inactive` state. Whenever an incoming edge (or "channel") receives a new message (state), the node becomes `active`, runs the function, and responds with updates. At the end of each superstep, each node votes to `halt` by marking itself as `inactive` if it has no more incoming messages. The graph terminates when all nodes are `inactive` and when no messages are in transit.
|
||||
LangGraph's underlying graph algorithm uses [message passing](https://en.wikipedia.org/wiki/Message_passing) to define a general program. When a Node completes its operation, it sends messages along one or more edges to other node(s). These recipient nodes then execute their functions, pass the resulting messages to the next set of nodes, and the process continues. Inspired by Google's [Pregel](https://research.google/pubs/pregel-a-system-for-large-scale-graph-processing/) system, the program proceeds in discrete "super-steps."
|
||||
|
||||
A super-step can be considered a single iteration over the graph nodes. Nodes that run in parallel are part of the same super-step, while nodes that run sequentially belong to separate super-steps. At the start of graph execution, all nodes begin in an `inactive` state. A node becomes `active` when it receives a new message (state) on any of its incoming edges (or "channels"). The active node then runs its function and responds with updates. At the end of each super-step, nodes with no incoming messages vote to `halt` by marking themselves as `inactive`. The graph execution terminates when all nodes are `inactive` and no messages are in transit.
|
||||
|
||||
### StateGraph
|
||||
|
||||
@@ -44,9 +46,17 @@ The first thing you do when you define a graph is define the `State` of the grap
|
||||
|
||||
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/state-model.ipynb) as your graph state to add **default values** and additional data validation.
|
||||
|
||||
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 [notebook here](../how-tos/input_output_schema.ipynb) for how to use.
|
||||
|
||||
By default, all nodes in the graph will share the same state. This means that they will read and write to the same state channels. It is possible to have nodes write to private state channels inside the graph for internal node communication - see [this notebook](../how-tos/pass_private_state.ipynb) for how to do that.
|
||||
|
||||
### Reducers
|
||||
|
||||
Reducers are key to understanding how updates from nodes are applied to the `State`. Each key in the `State` has its own independent reducer function. If no reducer function is explicitly specified then it is assumed that all updates to that key should override it. Let's take a look at a few examples to understand them better.
|
||||
Reducers are key to understanding how updates from nodes are applied to the `State`. Each key in the `State` has its own independent reducer function. If no reducer function is explicitly specified then it is assumed that all updates to that key should override it. There are a few different types of reducers, starting with the default type of reducer:
|
||||
|
||||
#### Default Reducer
|
||||
|
||||
These two examples show how to use the default reducer:
|
||||
|
||||
**Example A:**
|
||||
|
||||
@@ -73,22 +83,48 @@ 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.
|
||||
|
||||
### MessageState
|
||||
#### Context Reducer
|
||||
|
||||
`MessageState` is one of the few opinionated components in LangGraph. `MessageState` is a special state designed to make it easy to use a list of messages as a key in your state. Specifically, `MessageState` is defined as:
|
||||
You can use `Context` channels to define shared resources (such as database connections) that are managed outside of your graph's nodes and excluded from checkpointing. The context manager provided to the Context channel is entered before the first step of the graph execution and exited after the last step, allowing you to set up and clean up resources for the duration of the graph invocation. Read this [how to](https://langchain-ai.github.io/langgraph/how-tos/state-context-key) to see an example of using the `Context` channel in your graph.
|
||||
|
||||
### Working with Messages in Graph State
|
||||
|
||||
#### Why use messages?
|
||||
|
||||
Most modern LLM providers have a chat model interface that accepts a list of messages as input. LangChain's [`ChatModel`](https://python.langchain.com/v0.2/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/v0.2/docs/concepts/#messages) conceptual guide.
|
||||
|
||||
#### Using Messages in your Graph
|
||||
|
||||
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.
|
||||
|
||||
#### Serialization
|
||||
|
||||
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/v0.2/docs/how_to/serialization/). This allows sending graph inputs / state updates in the following format:
|
||||
|
||||
```python
|
||||
# this is supported
|
||||
{"messages": [HumanMessage(content="message")]}
|
||||
|
||||
# and this is also supported
|
||||
{"messages": [{"type": "human", "content": "message"}]}
|
||||
```
|
||||
|
||||
Since the state updates are always deserialized into LangChain `Messages` when using `add_messages`, you should use dot notation to access message attributes, like `state["messages"][-1].content`. Below is an example of a graph that uses `add_messages` as it's reducer function.
|
||||
|
||||
```python
|
||||
from langchain_core.messages import AnyMessage
|
||||
from langgraph.graph.message import add_messages
|
||||
from typing import Annotated, TypedDict
|
||||
|
||||
class MessagesState(TypedDict):
|
||||
class GraphState(TypedDict):
|
||||
messages: Annotated[list[AnyMessage], add_messages]
|
||||
```
|
||||
|
||||
What this is doing is creating a `TypedDict` with a single key: `messages`. This is a list of `Message` objects, with `add_messages` as a reducer. `add_messages` basically adds messages to the existing list (it also does some nice extra things, like convert from OpenAI message format to the standard LangChain message format, handle updates based on message IDs, etc).
|
||||
#### MessagesState
|
||||
|
||||
We often see a list of messages being a key component of state, so this prebuilt state is intended to make it easy to use messages. Typically, there is more state to track than just messages, so we see people subclass this state and add more fields, like:
|
||||
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:
|
||||
|
||||
```python
|
||||
from langgraph.graph import MessagesState
|
||||
@@ -178,7 +214,7 @@ graph.add_edge("node_a", "node_b")
|
||||
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
|
||||
graph.add_edge("node_a", routing_function)
|
||||
graph.add_conditional_edges("node_a", routing_function)
|
||||
```
|
||||
|
||||
Similar to nodes, the `routing_function` accept the current `state` of the graph and return a value.
|
||||
@@ -188,18 +224,12 @@ By default, the return value `routing_function` is used as the name of the node
|
||||
You can optionally provide a dictionary that maps the `routing_function`'s output to the name of the next node.
|
||||
|
||||
```python
|
||||
graph.add_edge("node_a", routing_function, {True: "node_b", False: "node_c"})
|
||||
graph.add_conditional_edges("node_a", routing_function, {True: "node_b", False: "node_c"})
|
||||
```
|
||||
|
||||
### Entry Point
|
||||
|
||||
The entry point is first node to call when the graph starts. You can use [`set_entry_point`][langgraph.graph.StateGraph.set_entry_point] to specify this.
|
||||
|
||||
```python
|
||||
graph.set_entry_point("node_a")
|
||||
```
|
||||
|
||||
This is equivalent to adding an edge between the `START` node and this node. You may want to use `START` directly when you want to have **multiple** nodes be called first.
|
||||
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`][start] node to the first node to execute to specify where to enter the graph.
|
||||
|
||||
```python
|
||||
from langgraph.graph import START
|
||||
@@ -209,17 +239,18 @@ graph.add_edge(START, "node_a")
|
||||
|
||||
### Conditional Entry Point
|
||||
|
||||
The conditional entry point is used when you want to specify a function to call to determine which node(s) should be called first.
|
||||
You can use [`set_conditional_entry_point`][langgraph.graph.StateGraph.set_conditional_entry_point] to specify this.
|
||||
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`][start] node to accomplish this.
|
||||
|
||||
```python
|
||||
graph.set_conditional_entry_point(routing_function)
|
||||
from langgraph.graph import START
|
||||
|
||||
graph.add_conditional_edges(START, routing_function)
|
||||
```
|
||||
|
||||
You can optionally provide a dictionary that maps the `routing_function`'s output to the name of the next node.
|
||||
|
||||
```python
|
||||
graph.set_conditional_entry_point(routing_function, {True: "node_b", False: "node_c"})
|
||||
graph.add_conditional_edges(START, routing_function, {True: "node_b", False: "node_c"})
|
||||
```
|
||||
|
||||
## `Send`
|
||||
@@ -237,11 +268,9 @@ graph.add_conditional_edges("node_a", continue_to_jokes)
|
||||
|
||||
## Checkpointer
|
||||
|
||||
One of the main benefits of LangGraph is that it comes backed by a persistence layer. This is accomplished via [checkpointers][basecheckpointsaver].
|
||||
LangGraph has a built-in persistence layer, implemented through [checkpointers][basecheckpointsaver]. When you use a checkpointer with a graph, you can interact with the state of that graph. When you use a checkpointer with a graph, you can interact with and manage the graph's state. The checkpointer saves a _checkpoint_ of the graph state at every super-step, enabling several powerful capabilities:
|
||||
|
||||
Checkpointers can be used to save a _checkpoint_ of the state of a graph after all steps of the graph. This allows for several things.
|
||||
|
||||
First, it allows for [human-in-the-loop workflows](agentic_concepts.md#human-in-the-loop), as it allows humans to inspect, interrupt, and approve steps. Checkpointers are needed for these workflows as the human has to be able to view the state of a graph at any point in time, and the graph has to be to resume execution after the human has made any updates to the state.
|
||||
First, checkpointers facilitate [human-in-the-loop workflows](agentic_concepts.md#human-in-the-loop) workflows by allowing humans to inspect, interrupt, and approve steps.Checkpointers are needed for these workflows as the human has to be able to view the state of a graph at any point in time, and the graph has to be to resume execution after the human has made any updates to the state.
|
||||
|
||||
Second, it allows for ["memory"](agentic_concepts.md#memory) between interactions. You can use checkpointers to create threads and save the state of a thread after a graph executes. In the case of repeated human interactions (like conversations) any follow up messages can be sent to that checkpoint, which will retain its memory of previous ones.
|
||||
|
||||
@@ -249,8 +278,7 @@ See [this guide](../how-tos/persistence.ipynb) for how to add a checkpointer to
|
||||
|
||||
## Threads
|
||||
|
||||
When using a checkpointer, you must specify a `thread_id` or `thread_ts` when running the graph.
|
||||
Threads are used to checkpoint multiple different runs. This can be used to enable a multi-tenant chat applications.
|
||||
Threads enable the checkpointing of multiple different runs, making them essential for multi-tenant chat applications and other scenarios where maintaining separate states is necessary. A thread is a unique ID assigned to a series of checkpoints saved by a checkpointer. When using a checkpointer, you must specify a `thread_id` or `thread_ts` when running the graph.
|
||||
|
||||
`thread_id` is simply the ID of a thread. This is always required
|
||||
|
||||
@@ -267,14 +295,7 @@ See [this guide](../how-tos/persistence.ipynb) for how to use threads.
|
||||
|
||||
## Checkpointer state
|
||||
|
||||
When you use a checkpointer with a graph, you can interact with the state of that graph.
|
||||
This usually done when enabling different human-in-the-loop interaction patterns.
|
||||
Each time you run the graph, the checkpointer creates several checkpoints every time a
|
||||
node or set of nodes finishes running.
|
||||
The most recent checkpoint is the current state of the thread.
|
||||
When interacting with the checkpointer state, you must specify a [thread identifier](#threads).
|
||||
|
||||
Each checkpoint has two properties:
|
||||
When interacting with the checkpointer state, you must specify a [thread identifier](#threads).Each checkpoint saved by the checkpointer has two properties:
|
||||
|
||||
- **values**: This is the value of the state at this point in time.
|
||||
- **next**: This is a tuple of the nodes to execute next in the graph.
|
||||
@@ -340,6 +361,16 @@ The final thing you specify when calling `update_state` is `as_node`. This updat
|
||||
|
||||
The reason this matters is that the next steps in the graph to execute depend on the last node to have given an update, so this can be used to control which node executes next.
|
||||
|
||||
## Graph Migrations
|
||||
|
||||
LangGraph can easily handle migrations of graph definitions (nodes, edges, and state) even when using a checkpointer to track state.
|
||||
|
||||
- For threads at the end of the graph (i.e. not interrupted) you can change the entire topology of the graph (i.e. all nodes and edges, remove, add, rename, etc)
|
||||
- For threads currently interrupted, we support all topology changes other than renaming / removing nodes (as that thread could now be about to enter a node that no longer exists) -- if this is a blocker please reach out and we can prioritize a solution.
|
||||
- For modifying state, we have full backwards and forwards compatibility for adding and removing keys
|
||||
- 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
|
||||
|
||||
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.
|
||||
@@ -370,7 +401,17 @@ def node_a(state, config):
|
||||
...
|
||||
```
|
||||
|
||||
See [this guide](../how-tos/configuration.ipynb) for a full breakdown on configuration
|
||||
See [this guide](../how-tos/configuration.ipynb) for a full breakdown on configuration.
|
||||
|
||||
### Recursion Limit
|
||||
|
||||
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"}})
|
||||
```
|
||||
|
||||
Read [this how-to](https://langchain-ai.github.io/langgraph/how-tos/recursion-limit/) to learn more about how the recursion limit works.
|
||||
|
||||
## Breakpoints
|
||||
|
||||
@@ -396,10 +437,202 @@ It's often nice to be able to visualize graphs, especially as they get more comp
|
||||
|
||||
## Streaming
|
||||
|
||||
LangGraph is built with first class support for streaming. There are several different streaming modes that LangGraph supports:
|
||||
LangGraph is built with first class support for streaming. There are several different ways to stream back results
|
||||
|
||||
### `.stream` and `.astream`
|
||||
|
||||
`.stream` and `.astream` are sync and async methods for streaming back results.
|
||||
There are several different modes you can specify when calling these methods (e.g. `graph.stream(..., mode="...")):
|
||||
|
||||
- [`"values"`](../how-tos/stream-values.ipynb): This streams the full value of the state after each step of the graph.
|
||||
- [`"updates`](../how-tos/stream-updates.ipynb): This streams the updates to the state after each step of the graph. If multiple updates are made in the same step (e.g. multiple nodes are run) then those updates are streamed separately.
|
||||
- [`"updates"`](../how-tos/stream-updates.ipynb): This streams the updates to the state after each step of the graph. If multiple updates are made in the same step (e.g. multiple nodes are run) then those updates are streamed separately.
|
||||
- `"debug"`: This streams as much information as possible throughout the execution of the graph.
|
||||
|
||||
The below visualization shows the difference between the `values` and `updates` modes:
|
||||
|
||||

|
||||
|
||||
|
||||
### `.astream_events` (for streaming tokens of LLM calls)
|
||||
|
||||
In addition, you can use the [`astream_events`](../how-tos/streaming-events-from-within-tools.ipynb) method to stream back events that happen _inside_ nodes. This is useful for [streaming tokens of LLM calls](../how-tos/streaming-tokens.ipynb).
|
||||
|
||||
This is a standard method on all [LangChain objects](https://python.langchain.com/v0.2/docs/concepts/#runnable-interface). This means that as the graph is executed, certain events are emitted along the way and can be seen if you run the graph using `.astream_events`.
|
||||
|
||||
All events have (among other things) `event`, `name`, and `data` fields. What do these mean?
|
||||
|
||||
- `event`: This is the type of event that is being emitted. You can find a detailed table of all callback events and triggers [here](https://python.langchain.com/v0.2/docs/concepts/#callback-events).
|
||||
- `name`: This is the name of event.
|
||||
- `data`: This is the data associated with the event.
|
||||
|
||||
What types of things cause events to be emitted?
|
||||
|
||||
* each node (runnable) emits `on_chain_start` when it starts execution, `on_chain_stream` during the node execution and `on_chain_end` when the node finishes. Node events will have the node name in the event's `name` field
|
||||
* the graph will emit `on_chain_start` in the beginning of the graph execution, `on_chain_stream` after each node execution and `on_chain_end` when the graph finishes. Graph events will have the `LangGraph` in the event's `name` field
|
||||
* Any writes to state channels (i.e. anytime you update the value of one of your state keys) will emit `on_chain_start` and `on_chain_end` events
|
||||
|
||||
Additionally, any events that are created inside your nodes (LLM events, tool events, manually emitted events, etc.) will also be visible in the output of `.astream_events`.
|
||||
|
||||
To make this more concrete and to see what this looks like, let's see what events are returned when we run a simple graph:
|
||||
|
||||
```python
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import StateGraph, MessagesState, START, END
|
||||
|
||||
model = ChatOpenAI(model="gpt-3.5-turbo")
|
||||
|
||||
|
||||
def call_model(state: MessagesState):
|
||||
response = model.invoke(state['messages'])
|
||||
return {"messages": response}
|
||||
|
||||
workflow = StateGraph(MessagesState)
|
||||
workflow.add_node(call_model)
|
||||
workflow.add_edge(START, "call_model")
|
||||
workflow.add_edge("call_model", END)
|
||||
app = workflow.compile()
|
||||
|
||||
inputs = [{"role": "user", "content": "hi!"}]
|
||||
async for event in app.astream_events({"messages": inputs}, version="v2"):
|
||||
kind = event["event"]
|
||||
print(f"{kind}: {event['name']}")
|
||||
```
|
||||
```shell
|
||||
on_chain_start: LangGraph
|
||||
on_chain_start: __start__
|
||||
on_chain_end: __start__
|
||||
on_chain_start: call_model
|
||||
on_chat_model_start: ChatOpenAI
|
||||
on_chat_model_stream: ChatOpenAI
|
||||
on_chat_model_stream: ChatOpenAI
|
||||
on_chat_model_stream: ChatOpenAI
|
||||
on_chat_model_stream: ChatOpenAI
|
||||
on_chat_model_stream: ChatOpenAI
|
||||
on_chat_model_stream: ChatOpenAI
|
||||
on_chat_model_stream: ChatOpenAI
|
||||
on_chat_model_stream: ChatOpenAI
|
||||
on_chat_model_stream: ChatOpenAI
|
||||
on_chat_model_stream: ChatOpenAI
|
||||
on_chat_model_stream: ChatOpenAI
|
||||
on_chat_model_end: ChatOpenAI
|
||||
on_chain_start: ChannelWrite<call_model,messages>
|
||||
on_chain_end: ChannelWrite<call_model,messages>
|
||||
on_chain_stream: call_model
|
||||
on_chain_end: call_model
|
||||
on_chain_stream: LangGraph
|
||||
on_chain_end: LangGraph
|
||||
```
|
||||
|
||||
We start with the overall graph start (`on_chain_start: LangGraph`). We then write to the `__start__` node (this is special node to handle input).
|
||||
We then start the `call_model` node (`on_chain_start: call_model`). We then start the chat model invocation (`on_chat_model_start: ChatOpenAI`),
|
||||
stream back token by token (`on_chat_model_stream: ChatOpenAI`) and then finish the chat model (`on_chat_model_end: ChatOpenAI`). From there,
|
||||
we write the results back to the channel (`ChannelWrite<call_model,messages>`) and then finish the `call_model` node and then the graph as a whole.
|
||||
|
||||
This should hopefully give you a good sense of what events are emitted in a simple graph. But what data do these events contain?
|
||||
Each type of event contains data in a different format. Let's look at what `on_chat_model_stream` events look like. This is an important type of event
|
||||
since it is needed for streaming tokens from an LLM response.
|
||||
|
||||
These events look like:
|
||||
|
||||
```shell
|
||||
{'event': 'on_chat_model_stream',
|
||||
'name': 'ChatOpenAI',
|
||||
'run_id': '3fdbf494-acce-402e-9b50-4eab46403859',
|
||||
'tags': ['seq:step:1'],
|
||||
'metadata': {'langgraph_step': 1,
|
||||
'langgraph_node': 'call_model',
|
||||
'langgraph_triggers': ['start:call_model'],
|
||||
'langgraph_task_idx': 0,
|
||||
'checkpoint_id': '1ef657a0-0f9d-61b8-bffe-0c39e4f9ad6c',
|
||||
'checkpoint_ns': 'call_model',
|
||||
'ls_provider': 'openai',
|
||||
'ls_model_name': 'gpt-3.5-turbo',
|
||||
'ls_model_type': 'chat',
|
||||
'ls_temperature': 0.7},
|
||||
'data': {'chunk': AIMessageChunk(content='Hello', id='run-3fdbf494-acce-402e-9b50-4eab46403859')},
|
||||
'parent_ids': []}
|
||||
```
|
||||
We can see that we have the event type and name (which we knew from before).
|
||||
|
||||
We also have a bunch of stuff in metadata. Noticeably, `'langgraph_node': 'call_model',` is some really helpful information
|
||||
which tells us which node this model was invoked inside of.
|
||||
|
||||
Finally, `data` is a really important field. This contains the actual data for this event! Which in this case
|
||||
is an AIMessageChunk. This contains the `content` for the message, as well as an `id`.
|
||||
This is the ID of the overall AIMessage (not just this chunk) and is super helpful - it helps
|
||||
us track which chunks are part of the same message (so we can show them together in the UI).
|
||||
|
||||
This information contains all that is needed for creating a UI for streaming LLM tokens. You can see a
|
||||
guide for that [here](../how-tos/streaming-tokens.ipynb).
|
||||
|
||||
|
||||
!!! warning "ASYNC IN PYTHON<=3.10"
|
||||
You may fail to see events being emitted from inside a node when using `.astream_events` in Python <= 3.10. If you're using a Langchain RunnableLambda, a RunnableGenerator, or Tool asynchronously inside your node, you will have to propagate callbacks to these objects manually. This is because LangChain cannot automatically propagate callbacks to child objects in this case. Please see examples [here](../how-tos/streaming-content.ipynb) and [here](../how-tos/streaming-events-from-within-tools.ipynb).
|
||||
|
||||
#### Only stream tokens from specific nodes/LLMs
|
||||
|
||||
|
||||
There are certain cases where you have multiple nodes in your graph that make LLM calls, and you do not wish to stream the tokens from every single LLM call. For example, you may use one LLM as a planner for the next steps to take, and another LLM somewhere else in the graph that actually responds to the user. In that case, you most likely WON'T want to stream tokens from the planner LLM but WILL want to stream them from the respond to user LLM. Below we show two different ways of doing this, one by streaming from specific nodes only and the second by streaming from specific LLMs only.
|
||||
|
||||
First, let's define our graph:
|
||||
|
||||
```python
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import StateGraph, MessagesState, START, END
|
||||
|
||||
model_1 = ChatOpenAI(model="gpt-3.5-turbo", name="model_1")
|
||||
model_2 = ChatOpenAI(model="gpt-3.5-turbo", name="model_2")
|
||||
|
||||
def call_first_model(state: MessagesState):
|
||||
response = model_1.invoke(state['messages'])
|
||||
return {"messages": response}
|
||||
|
||||
def call_second_model(state: MessagesState):
|
||||
response = model_2.invoke(state['messages'])
|
||||
return {"messages": response}
|
||||
|
||||
workflow = StateGraph(MessagesState)
|
||||
workflow.add_node(call_first_model)
|
||||
workflow.add_node(call_second_model)
|
||||
workflow.add_edge(START, "call_first_model")
|
||||
workflow.add_edge("call_first_model", "call_second_model")
|
||||
workflow.add_edge("call_second_model", END)
|
||||
app = workflow.compile()
|
||||
```
|
||||
|
||||
**Streaming from specific node**
|
||||
|
||||
In the case that we only want the output from a single node, we can use the event metadata to filter node names:
|
||||
|
||||
```python
|
||||
inputs = [{"role": "user", "content": "hi!"}]
|
||||
|
||||
async for event in app.astream_events({"messages": inputs}, version="v2"):
|
||||
# Get chat model tokens from a particular node
|
||||
if event["event"] == "on_chat_model_stream" and event['metadata'].get('langgraph_node','') == "call_second_model":
|
||||
print(event["data"]["chunk"].content, end="|", flush=True)
|
||||
```
|
||||
|
||||
```shell
|
||||
|Hello|!| How| can| I| help| you| today|?||
|
||||
```
|
||||
|
||||
As we can see only the response from the second LLM was streamed (you can tell because we only received a single response, if we had streamed both we would have received two "Hello! How can I help you today?" messages).
|
||||
|
||||
**Streaming from specific LLM**
|
||||
|
||||
Sometimes you might want to stream from specific LLMs instead of specific nodes. This could be the case if you have multiple LLM calls inside a single node, and only want to stream the output of a specific one or if you use the same LLM in different nodes and want to stream it's output anytime it is called. We can do this by using the `name` parameter for LLMs and events:
|
||||
|
||||
```python
|
||||
inputs = [{"role": "user", "content": "hi!"}]
|
||||
async for event in app.astream_events({"messages": inputs}, version="v2"):
|
||||
# Get chat model tokens from a particular LLM inside a particular node
|
||||
if event["event"] == "on_chat_model_stream" and event['name'] == "model_2":
|
||||
print(event["data"]["chunk"].content, end="|", flush=True)
|
||||
```
|
||||
|
||||
```shell
|
||||
|Hello|!| How| can| I| assist| you| today|?||
|
||||
```
|
||||
|
||||
As expected, we only see a single LLM response since the response from `model_1` was not streamed.
|
||||
@@ -15,15 +15,19 @@ These how-to guides show how to achieve that controllability.
|
||||
- [How to create subgraphs](subgraph.ipynb)
|
||||
- [How to create branches for parallel execution](branching.ipynb)
|
||||
- [How to create map-reduce branches for parallel execution](map-reduce.ipynb)
|
||||
|
||||
- [How to control graph recursion limit](recursion-limit.ipynb)
|
||||
|
||||
## Persistence
|
||||
|
||||
LangGraph makes it easy to persist state across graph runs. The guide below shows how to add persistence to your graph.
|
||||
|
||||
- [How to add persistence ("memory") to your graph](persistence.ipynb)
|
||||
- [How to manage conversation history](managing-conversation-history.ipynb)
|
||||
- [How to create a custom checkpointer using Postgres](persistence_postgres.ipynb)
|
||||
- [How to manage conversation history](memory/manage-conversation-history.ipynb)
|
||||
- [How to delete messages](memory/delete-messages.ipynb)
|
||||
- [How to add summary conversation memory](memory/add-summary-conversation-history.ipynb)
|
||||
- [How to use Postgres checkpointer for persistence](persistence_postgres.ipynb)
|
||||
- [How to create a custom checkpointer using MongoDB](persistence_mongodb.ipynb)
|
||||
- [How to create a custom checkpointer using Redis](persistence_redis.ipynb)
|
||||
|
||||
## Human in the Loop
|
||||
|
||||
@@ -31,9 +35,11 @@ One of LangGraph's main benefits is that it makes human-in-the-loop workflows ea
|
||||
These guides cover common examples of that.
|
||||
|
||||
- [How to add breakpoints](human_in_the_loop/breakpoints.ipynb)
|
||||
- [How to add dynamic breakpoints](human_in_the_loop/dynamic_breakpoints.ipynb)
|
||||
- [How to edit graph state](human_in_the_loop/edit-graph-state.ipynb)
|
||||
- [How to wait for user input](human_in_the_loop/wait-user-input.ipynb)
|
||||
- [How to view and update past graph state](human_in_the_loop/time-travel.ipynb)
|
||||
- [Review tool calls](human_in_the_loop/review-tool-calls.ipynb)
|
||||
|
||||
## Streaming
|
||||
|
||||
@@ -43,16 +49,37 @@ These guides show how to use different streaming modes.
|
||||
- [How to stream full state of your graph](stream-values.ipynb)
|
||||
- [How to stream state updates of your graph](stream-updates.ipynb)
|
||||
- [How to stream LLM tokens](streaming-tokens.ipynb)
|
||||
- [How to stream LLM tokens without LangChain models](streaming-tokens-without-langchain.ipynb)
|
||||
- [How to stream arbitrarily nested content](streaming-content.ipynb)
|
||||
- [How to configure multiple streaming modes at the same time](stream-multiple.ipynb)
|
||||
- [How to stream events from within a tool](streaming-events-from-within-tools.ipynb)
|
||||
- [How to stream events from within a tool without LangChain models](streaming-events-from-within-tools-without-langchain.ipynb)
|
||||
- [How to stream events from the final node](streaming-from-final-node.ipynb)
|
||||
|
||||
## Tool calling
|
||||
|
||||
- [How to call tools using ToolNode](tool-calling.ipynb)
|
||||
- [How to handle tool calling errors](tool-calling-errors.ipynb)
|
||||
- [How to pass graph state to tools](pass-run-time-values-to-tools.ipynb)
|
||||
- [How to pass config to tools](pass-config-to-tools.ipynb)
|
||||
- [How to handle large numbers of tools](many-tools.ipynb)
|
||||
|
||||
## State Management
|
||||
|
||||
- [Use Pydantic model as state](state-model.ipynb)
|
||||
- [Use a context object in state](state-context-key.ipynb)
|
||||
- [Have a separate input and output schema](input_output_schema.ipynb)
|
||||
- [Pass private state between nodes inside the graph](pass_private_state.ipynb)
|
||||
|
||||
## Other
|
||||
|
||||
- [How to run graph asynchronously](async.ipynb)
|
||||
- [How to visualize your graph](visualization.ipynb)
|
||||
- [How to add runtime configuration to your graph](configuration.ipynb)
|
||||
- [How to use a Pydantic model as your state](state-model.ipynb)
|
||||
- [How to use a context object in state](state-context-key.ipynb)
|
||||
- [How to add node retries](node-retries.ipynb)
|
||||
- [How to force function calling agent to structure output](react-agent-structured-output.ipynb)
|
||||
|
||||
## Prebuilt ReAct Agent
|
||||
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
# Checkpoints
|
||||
|
||||
You can [compile](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.MessageGraph.compile) any LangGraph workflow with a [CheckPointer](https://langchain-ai.github.io/langgraph/reference/checkpoints/#basecheckpointsaver) to give your agent "memory" by persisting its state. This permits things like:
|
||||
You can [compile][langgraph.graph.MessageGraph.compile] any LangGraph workflow with a [CheckPointer][basecheckpointsaver] to give your agent "memory" by persisting its state. This permits things like:
|
||||
|
||||
- Remembering things across multiple interactions
|
||||
- Interrupting to wait for user input
|
||||
- Resilience for long-running, error-prone agents
|
||||
- Time travel retry and branch from a previous checkpoint
|
||||
|
||||
Key checkpointer interfaces and primitives are defined in [`langgraph_checkpoint`](https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint) library.
|
||||
|
||||
### Checkpoint
|
||||
|
||||
::: langgraph.checkpoint.Checkpoint
|
||||
::: langgraph.checkpoint.base.Checkpoint
|
||||
|
||||
### CheckpointMetadata
|
||||
|
||||
::: langgraph.checkpoint.base.CheckpointMetadata
|
||||
|
||||
### BaseCheckpointSaver
|
||||
|
||||
::: langgraph.checkpoint.base.BaseCheckpointSaver
|
||||
handler: python
|
||||
|
||||
### SerializerProtocol
|
||||
|
||||
::: langgraph.checkpoint.SerializerProtocol
|
||||
handler: python
|
||||
::: langgraph.checkpoint.base.SerializerProtocol
|
||||
|
||||
## Implementations
|
||||
|
||||
@@ -28,14 +32,23 @@ LangGraph also natively provides the following checkpoint implementations.
|
||||
### MemorySaver
|
||||
|
||||
::: langgraph.checkpoint.memory.MemorySaver
|
||||
handler: python
|
||||
|
||||
### AsyncSqliteSaver
|
||||
|
||||
::: langgraph.checkpoint.aiosqlite.AsyncSqliteSaver
|
||||
handler: python
|
||||
::: langgraph.checkpoint.sqlite.aio.AsyncSqliteSaver
|
||||
|
||||
### SqliteSaver
|
||||
|
||||
::: langgraph.checkpoint.sqlite.SqliteSaver
|
||||
|
||||
### AsyncPostgresSaver
|
||||
|
||||
::: langgraph.checkpoint.postgres.aio.AsyncPostgresSaver
|
||||
|
||||
### PostgresSaver
|
||||
|
||||
::: langgraph.checkpoint.postgres.PostgresSaver
|
||||
handler: python
|
||||
|
||||
|
||||
handler: python
|
||||
|
||||
@@ -65,4 +65,8 @@ builder.add_conditional_edges("my_node", my_condition)
|
||||
|
||||
## Send
|
||||
|
||||
::: langgraph.constants.Send
|
||||
::: langgraph.constants.Send
|
||||
|
||||
## RetryPolicy
|
||||
|
||||
::: langgraph.pregel.types.RetryPolicy
|
||||
@@ -55,4 +55,13 @@ from langgraph.prebuilt import tools_condition
|
||||
from langgraph.prebuilt import ValidationNode
|
||||
```
|
||||
|
||||
::: langgraph.prebuilt.ValidationNode
|
||||
::: langgraph.prebuilt.ValidationNode
|
||||
|
||||
## InjectedState
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import InjectedState
|
||||
```
|
||||
|
||||
::: langgraph.prebuilt.InjectedState
|
||||
handler: python
|
||||
|
||||
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
@@ -65,4 +65,3 @@ Learn from example implementations of graphs designed for specific scenarios and
|
||||
- [Web Navigation](web-navigation/web_voyager.ipynb): Build an agent that can navigate and interact with websites
|
||||
- [Competitive Programming](usaco/usaco.ipynb): Build an agent with few-shot "episodic memory" and human-in-the-loop collaboration to solve problems from the USA Computing Olympiad; adapted from the ["Can Language Models Solve Olympiad Programming?"](https://arxiv.org/abs/2404.10952v1) paper by Shi, Tang, Narasimhan, and Yao.
|
||||
- [Complex data extraction](extraction/retries.ipynb): Build an agent that can use function calling to do complex extraction tasks
|
||||
-
|
||||
@@ -1,18 +1,17 @@
|
||||
site_name: LangGraph
|
||||
site_name: ""
|
||||
site_description: Build language agents as graphs
|
||||
site_url: https://langchain-ai.github.io/langgraph/
|
||||
repo_url: https://github.com/langchain-ai/langgraph
|
||||
theme:
|
||||
name: material
|
||||
custom_dir: overrides
|
||||
logo: static/wordmark.png
|
||||
logo_dark_mode: static/wordmark_light.svg
|
||||
logo_light_mode: static/wordmark_dark.svg
|
||||
favicon: static/favicon.png
|
||||
icon:
|
||||
repo: fontawesome/brands/git-alt
|
||||
features:
|
||||
- announce.dismiss
|
||||
- content.action.edit
|
||||
- content.action.view
|
||||
- content.code.annotate
|
||||
- content.code.copy
|
||||
- content.code.select
|
||||
@@ -130,33 +129,54 @@ nav:
|
||||
- Create subgraphs: how-tos/subgraph.ipynb
|
||||
- Create branches for parallel execution: how-tos/branching.ipynb
|
||||
- Create map-reduce branches for parallel execution: how-tos/map-reduce.ipynb
|
||||
- Control graph recursion limit: how-tos/recursion-limit.ipynb
|
||||
- Persistence:
|
||||
- Add persistence ("memory"): how-tos/persistence.ipynb
|
||||
- Manage conversation history: how-tos/managing-conversation-history.ipynb
|
||||
- Create custom checkpointer using Postgres: how-tos/persistence_postgres.ipynb
|
||||
- Manage conversation history: how-tos/memory/manage-conversation-history.ipynb
|
||||
- Delete messages: how-tos/memory/delete-messages.ipynb
|
||||
- Add summary of the conversation history: how-tos/memory/add-summary-conversation-history.ipynb
|
||||
- Use Postgres checkpointer for persistence: how-tos/persistence_postgres.ipynb
|
||||
- Create custom checkpointer using MongoDB: how-tos/persistence_mongodb.ipynb
|
||||
- Create custom checkpointer using Redis: how-tos/persistence_redis.ipynb
|
||||
- Human-in-the-loop:
|
||||
- Add breakpoints: how-tos/human_in_the_loop/breakpoints.ipynb
|
||||
- Add dynamic breakpoints: how-tos/human_in_the_loop/dynamic_breakpoints.ipynb
|
||||
- Wait for user input: how-tos/human_in_the_loop/wait-user-input.ipynb
|
||||
- View and update past graph state: how-tos/human_in_the_loop/time-travel.ipynb
|
||||
- Edit graph state: how-tos/human_in_the_loop/edit-graph-state.ipynb
|
||||
- Review tool calls: how-tos/human_in_the_loop/review-tool-calls.ipynb
|
||||
- Streaming:
|
||||
- Stream full state: how-tos/stream-values.ipynb
|
||||
- Stream state updates: how-tos/stream-updates.ipynb
|
||||
- Stream LLM tokens: how-tos/streaming-tokens.ipynb
|
||||
- Stream LLM tokens without LangChain models: how-tos/streaming-tokens-without-langchain.ipynb
|
||||
- Stream arbitrarily nested content: how-tos/streaming-content.ipynb
|
||||
- Configure multiple streaming modes: how-tos/stream-multiple.ipynb
|
||||
- Stream events from within tools: how-tos/streaming-events-from-within-tools.ipynb
|
||||
- Stream events from within tools without LangChain models: how-tos/streaming-events-from-within-tools-without-langchain.ipynb
|
||||
- Stream events from the final node: how-tos/streaming-from-final-node.ipynb
|
||||
- Tool calling:
|
||||
- Call tools using ToolNode: how-tos/tool-calling.ipynb
|
||||
- Handle tool calling errors: how-tos/tool-calling-errors.ipynb
|
||||
- Pass graph state to tools: how-tos/pass-run-time-values-to-tools.ipynb
|
||||
- Pass config to tools: how-tos/pass-config-to-tools.ipynb
|
||||
- Handle many tools: how-tos/many-tools.ipynb
|
||||
- State Management:
|
||||
- Use Pydantic model as state: how-tos/state-model.ipynb
|
||||
- Use a context object in state: how-tos/state-context-key.ipynb
|
||||
- Have a separate input and output schema: how-tos/input_output_schema.ipynb
|
||||
- Pass private state between nodes inside the graph: how-tos/pass_private_state.ipynb
|
||||
- Other:
|
||||
- Run graph asynchronously: how-tos/async.ipynb
|
||||
- Visualize your graph: how-tos/visualization.ipynb
|
||||
- Add runtime configuration: how-tos/configuration.ipynb
|
||||
- Use Pydantic model as state: how-tos/state-model.ipynb
|
||||
- Add node retries: how-tos/node-retries.ipynb
|
||||
- How to return structured output from a ReAct agent: how-tos/react-agent-structured-output.ipynb
|
||||
- Prebuilt ReAct Agent:
|
||||
- Create a ReAct agent: how-tos/create-react-agent.ipynb
|
||||
- Add memory to a ReAct agent: how-tos/create-react-agent-memory.ipynb
|
||||
- Add a system prompt to a ReAct agent: how-tos/create-react-agent-system-prompt.ipynb
|
||||
- Add human-in-the-Loop to a ReAct agent: how-tos/create-react-agent-hitl.ipynb
|
||||
- Add Human-in-the-loop to a ReAct agent: how-tos/create-react-agent-hitl.ipynb
|
||||
- "Conceptual Guides":
|
||||
- "concepts/index.md"
|
||||
- LangGraph for Agentic Applications: concepts/high_level.md
|
||||
@@ -174,30 +194,35 @@ nav:
|
||||
- Quick Start: "cloud/quick_start.md"
|
||||
- How-to Guides:
|
||||
- "cloud/how-tos/index.md"
|
||||
- Deployment:
|
||||
- Setup:
|
||||
- Setup App: "cloud/deployment/setup.md"
|
||||
- Setup App (pyproject.toml): "cloud/deployment/setup_pyproject.md"
|
||||
- Rebuild Graph at Runtime: "cloud/deployment/graph_rebuild.md"
|
||||
- Test App Locally: "cloud/deployment/test_locally.md"
|
||||
- Deployment:
|
||||
- Deploy to Cloud: "cloud/deployment/cloud.md"
|
||||
- Self-Host: "cloud/deployment/self_hosted.md"
|
||||
- Streaming:
|
||||
- Stream Values: "cloud/how-tos/cloud_examples/stream_values.ipynb"
|
||||
- Stream Updates: "cloud/how-tos/cloud_examples/stream_updates.ipynb"
|
||||
- Stream Messages: "cloud/how-tos/cloud_examples/stream_messages.ipynb"
|
||||
- Stream Events: "cloud/how-tos/cloud_examples/stream_events.ipynb"
|
||||
- Stream Debug: "cloud/how-tos/cloud_examples/stream_debug.ipynb"
|
||||
- Multiple Modes: "cloud/how-tos/cloud_examples/stream_multiple.ipynb"
|
||||
- Stream Values: "cloud/how-tos/stream_values.md"
|
||||
- Stream Updates: "cloud/how-tos/stream_updates.md"
|
||||
- Stream Messages: "cloud/how-tos/stream_messages.md"
|
||||
- Stream Events: "cloud/how-tos/stream_events.md"
|
||||
- Stream Debug: "cloud/how-tos/stream_debug.md"
|
||||
- Multiple Modes: "cloud/how-tos/stream_multiple.md"
|
||||
- Double Texting:
|
||||
- Interrupt: "cloud/how-tos/cloud_examples/interrupt_concurrent.ipynb"
|
||||
- Rollback: "cloud/how-tos/cloud_examples/rollback_concurrent.ipynb"
|
||||
- Reject: "cloud/how-tos/cloud_examples/reject_concurrent.ipynb"
|
||||
- Enqueue: "cloud/how-tos/cloud_examples/enqueue_concurrent.ipynb"
|
||||
- Interrupt: "cloud/how-tos/interrupt_concurrent.md"
|
||||
- Rollback: "cloud/how-tos/rollback_concurrent.md"
|
||||
- Reject: "cloud/how-tos/reject_concurrent.md"
|
||||
- Enqueue: "cloud/how-tos/enqueue_concurrent.md"
|
||||
- Human-in-the-Loop:
|
||||
- Add Breakpoint: "cloud/how-tos/cloud_examples/human_in_the_loop_breakpoint.ipynb"
|
||||
- Wait for User Input: "cloud/how-tos/cloud_examples/human_in_the_loop_user_input.ipynb"
|
||||
- Edit Graph State: "cloud/how-tos/cloud_examples/human_in_the_loop_edit_state.ipynb"
|
||||
- Replay and Branch from Prior States: "cloud/how-tos/cloud_examples/human_in_the_loop_time_travel.ipynb"
|
||||
- Add Breakpoint: "cloud/how-tos/human_in_the_loop_breakpoint.md"
|
||||
- Wait for User Input: "cloud/how-tos/human_in_the_loop_user_input.md"
|
||||
- Edit Graph State: "cloud/how-tos/human_in_the_loop_edit_state.md"
|
||||
- Replay and Branch from Prior States: "cloud/how-tos/human_in_the_loop_time_travel.md"
|
||||
- Review Tool Calls: "cloud/how-tos/human_in_the_loop_review_tool_calls.md"
|
||||
- LangGraph Studio:
|
||||
- Test Cloud Deployment: "cloud/how-tos/test_deployment.md"
|
||||
- Test Local Deployment: "cloud/how-tos/test_local_deployment.md"
|
||||
- Invoke graph in LangGraph Studio: "cloud/how-tos/invoke_studio.md"
|
||||
- Interact with threads in LangGraph Studio: "cloud/how-tos/threads_studio.md"
|
||||
- Different Types of Runs:
|
||||
@@ -209,7 +234,11 @@ nav:
|
||||
- Configure Agents: "cloud/how-tos/cloud_examples/configuration_cloud.ipynb"
|
||||
- Convert LangGraph calls to LangGraph Cloud calls: "cloud/how-tos/cloud_examples/langgraph_to_langgraph_cloud.ipynb"
|
||||
- Integrate Webhooks: 'cloud/how-tos/cloud_examples/webhooks.ipynb'
|
||||
- Conceptual Guides: "cloud/concepts/index.md"
|
||||
- Copy Threads: 'cloud/how-tos/copy_threads.md'
|
||||
- Check Status of Threads: "cloud/how-tos/check_thread_status.md"
|
||||
- Conceptual Guides:
|
||||
- API Concepts: "cloud/concepts/api.md"
|
||||
- Cloud Concepts: "cloud/concepts/cloud.md"
|
||||
- Reference:
|
||||
- API: "cloud/reference/api/api_ref.md"
|
||||
- SDK:
|
||||
@@ -217,6 +246,8 @@ nav:
|
||||
- JS/TS: "cloud/reference/sdk/js_ts_sdk_ref.md"
|
||||
- CLI: "cloud/reference/cli.md"
|
||||
- Environment Variables: "cloud/reference/env_var.md"
|
||||
- FAQ:
|
||||
- Studio: "cloud/faq/studio.md"
|
||||
|
||||
markdown_extensions:
|
||||
- abbr
|
||||
@@ -226,6 +257,9 @@ markdown_extensions:
|
||||
- def_list
|
||||
- footnotes
|
||||
- md_in_html
|
||||
- pymdownx.superfences
|
||||
- pymdownx.tabbed:
|
||||
alternate_style: true
|
||||
- toc:
|
||||
permalink: true
|
||||
- pymdownx.arithmatex:
|
||||
|
||||
@@ -21,12 +21,6 @@
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background-color: #FFFFFF;
|
||||
color: #333333;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.md-footer {
|
||||
background-color: #F5F5F5;
|
||||
color: #666666;
|
||||
@@ -100,6 +94,29 @@
|
||||
color: #BDBDBD;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .md-header {
|
||||
background-color: #1E1E1E;
|
||||
color: #BDBDBD;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .md-tabs {
|
||||
background-color: #1E1E1E;
|
||||
color: #BDBDBD;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .md-search__input {
|
||||
background-color: #F5F5F5;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .md-search__icon {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .md-search__input::placeholder {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .md-footer-meta {
|
||||
background-color: #1E1E1E;
|
||||
}
|
||||
@@ -111,6 +128,7 @@
|
||||
[data-md-color-scheme="slate"] .md-typeset a:hover {
|
||||
color: #90CAF9;
|
||||
}
|
||||
|
||||
.notebook-links {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
@@ -119,7 +137,22 @@
|
||||
.notebook-links .md-content__button {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
[data-md-color-scheme=default] .logo-dark {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
[data-md-color-scheme=slate] .logo-light {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.jupyter-wrapper .jp-CodeCell .jp-Cell-inputWrapper .jp-InputPrompt.jp-InputArea-prompt {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.jupyter-wrapper .jp-Notebook .jp-Cell .jp-OutputPrompt {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -134,4 +167,15 @@
|
||||
</div>
|
||||
|
||||
{{ super() }}
|
||||
{% endblock content %}
|
||||
{% endblock content %}
|
||||
|
||||
|
||||
{% block htmltitle %}
|
||||
{% if page.meta and page.meta.title %}
|
||||
<title>{{ page.meta.title }}</title>
|
||||
{% elif page.title and not page.is_homepage %}
|
||||
<title>{{ page.title | striptags }}</title>
|
||||
{% else %}
|
||||
<title>{{ config.site_name }}</title>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,4 @@
|
||||
{% if config.theme.logo_light_mode %}
|
||||
<img src="{{ config.theme.logo_light_mode | url }}" alt="logo" class="logo-light" />
|
||||
<img src="{{ config.theme.logo_dark_mode | url }}" alt="logo" class="logo-dark" />
|
||||
{% endif %}
|
||||
@@ -25,10 +25,7 @@
|
||||
"id": "fdd4ce41-4152-423b-b3f7-be3b4d568cf4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langgraph langchain langchain_openai langchainhub tavily-python"
|
||||
]
|
||||
"source": ["%%capture --no-stderr\n%pip install --quiet -U langgraph langchain langchain_openai langchainhub tavily-python"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -44,13 +41,7 @@
|
||||
"id": "6398c4c1-da78-4595-8a5a-051ed2d1de72",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
]
|
||||
"source": ["import getpass\nimport os\n\nos.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\nos.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -66,10 +57,7 @@
|
||||
"id": "dcbf79ad-4de5-43b0-a3a1-25b33711e46c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"
|
||||
]
|
||||
"source": ["os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\nos.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -87,23 +75,7 @@
|
||||
"id": "4499eb16-bca8-4a60-9a3a-2f34ae3f7078",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain import hub\n",
|
||||
"from langchain.agents import create_openai_functions_agent\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"from langchain_openai.chat_models import ChatOpenAI\n",
|
||||
"\n",
|
||||
"tools = [TavilySearchResults(max_results=1)]\n",
|
||||
"\n",
|
||||
"# Get the prompt to use - you can modify this!\n",
|
||||
"prompt = hub.pull(\"hwchase17/openai-functions-agent\")\n",
|
||||
"\n",
|
||||
"# Choose the LLM that will drive the agent\n",
|
||||
"llm = ChatOpenAI(model=\"gpt-3.5-turbo-1106\", streaming=True)\n",
|
||||
"\n",
|
||||
"# Construct the OpenAI Functions agent\n",
|
||||
"agent_runnable = create_openai_functions_agent(llm, tools, prompt)"
|
||||
]
|
||||
"source": ["from langchain import hub\nfrom langchain.agents import create_openai_functions_agent\nfrom langchain_community.tools.tavily_search import TavilySearchResults\nfrom langchain_openai.chat_models import ChatOpenAI\n\ntools = [TavilySearchResults(max_results=1)]\n\n# Get the prompt to use - you can modify this!\nprompt = hub.pull(\"hwchase17/openai-functions-agent\")\n\n# Choose the LLM that will drive the agent\nllm = ChatOpenAI(model=\"gpt-3.5-turbo-1106\", streaming=True)\n\n# Construct the OpenAI Functions agent\nagent_runnable = create_openai_functions_agent(llm, tools, prompt)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -126,27 +98,7 @@
|
||||
"id": "c941fb10-dbe5-4d6a-ab7d-133d01c33cc4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import operator\n",
|
||||
"from typing import Annotated, TypedDict, Union\n",
|
||||
"\n",
|
||||
"from langchain_core.agents import AgentAction, AgentFinish\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
" # The input string\n",
|
||||
" input: str\n",
|
||||
" # The list of previous messages in the conversation\n",
|
||||
" chat_history: list[BaseMessage]\n",
|
||||
" # The outcome of a given call to the agent\n",
|
||||
" # Needs `None` as a valid type, since this is what this will start as\n",
|
||||
" agent_outcome: Union[AgentAction, AgentFinish, None]\n",
|
||||
" # List of actions and corresponding observations\n",
|
||||
" # Here we annotate this with `operator.add` to indicate that operations to\n",
|
||||
" # this state should be ADDED to the existing values (not overwrite it)\n",
|
||||
" intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add]"
|
||||
]
|
||||
"source": ["import operator\nfrom typing import Annotated, TypedDict, Union\n\nfrom langchain_core.agents import AgentAction, AgentFinish\nfrom langchain_core.messages import BaseMessage\n\n\nclass AgentState(TypedDict):\n # The input string\n input: str\n # The list of previous messages in the conversation\n chat_history: list[BaseMessage]\n # The outcome of a given call to the agent\n # Needs `None` as a valid type, since this is what this will start as\n agent_outcome: Union[AgentAction, AgentFinish, None]\n # List of actions and corresponding observations\n # Here we annotate this with `operator.add` to indicate that operations to\n # this state should be ADDED to the existing values (not overwrite it)\n intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -181,42 +133,7 @@
|
||||
"id": "d61a970d-edf4-4eef-9678-28bab7c72331",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.agents import AgentFinish\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt.tool_executor import ToolExecutor\n",
|
||||
"\n",
|
||||
"# This a helper class we have that is useful for running tools\n",
|
||||
"# It takes in an agent action and calls that tool and returns the result\n",
|
||||
"tool_executor = ToolExecutor(tools)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the agent\n",
|
||||
"def run_agent(data):\n",
|
||||
" agent_outcome = agent_runnable.invoke(data)\n",
|
||||
" return {\"agent_outcome\": agent_outcome}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function to execute tools\n",
|
||||
"def execute_tools(data):\n",
|
||||
" # Get the most recent agent_outcome - this is the key added in the `agent` above\n",
|
||||
" agent_action = data[\"agent_outcome\"]\n",
|
||||
" output = tool_executor.invoke(agent_action)\n",
|
||||
" return {\"intermediate_steps\": [(agent_action, str(output))]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define logic that will be used to determine which conditional edge to go down\n",
|
||||
"def should_continue(data):\n",
|
||||
" # If the agent outcome is an AgentFinish, then we return `exit` string\n",
|
||||
" # This will be used when setting up the graph to define the flow\n",
|
||||
" if isinstance(data[\"agent_outcome\"], AgentFinish):\n",
|
||||
" return \"end\"\n",
|
||||
" # Otherwise, an AgentAction is returned\n",
|
||||
" # Here we return `continue` string\n",
|
||||
" # This will be used when setting up the graph to define the flow\n",
|
||||
" else:\n",
|
||||
" return \"continue\""
|
||||
]
|
||||
"source": ["from langchain_core.agents import AgentFinish\n\nfrom langgraph.prebuilt.tool_executor import ToolExecutor\n\n# This a helper class we have that is useful for running tools\n# It takes in an agent action and calls that tool and returns the result\ntool_executor = ToolExecutor(tools)\n\n\n# Define the agent\ndef run_agent(data):\n agent_outcome = agent_runnable.invoke(data)\n return {\"agent_outcome\": agent_outcome}\n\n\n# Define the function to execute tools\ndef execute_tools(data):\n # Get the most recent agent_outcome - this is the key added in the `agent` above\n agent_action = data[\"agent_outcome\"]\n output = tool_executor.invoke(agent_action)\n return {\"intermediate_steps\": [(agent_action, str(output))]}\n\n\n# Define logic that will be used to determine which conditional edge to go down\ndef should_continue(data):\n # If the agent outcome is an AgentFinish, then we return `exit` string\n # This will be used when setting up the graph to define the flow\n if isinstance(data[\"agent_outcome\"], AgentFinish):\n return \"end\"\n # Otherwise, an AgentAction is returned\n # Here we return `continue` string\n # This will be used when setting up the graph to define the flow\n else:\n return \"continue\""]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -234,50 +151,7 @@
|
||||
"id": "c4054dde-4618-49b7-998a-daa0c1d6d6c0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
"\n",
|
||||
"# Define the two nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", run_agent)\n",
|
||||
"workflow.add_node(\"action\", execute_tools)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.set_entry_point(\"agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"app = workflow.compile()"
|
||||
]
|
||||
"source": ["from langgraph.graph import END, StateGraph, START\n\n# Define a new graph\nworkflow = StateGraph(AgentState)\n\n# Define the two nodes we will cycle between\nworkflow.add_node(\"agent\", run_agent)\nworkflow.add_node(\"action\", execute_tools)\n\n# Set the entrypoint as `agent`\n# This means that this node is the first one called\nworkflow.add_edge(START, \"agent\")\n\n# We now add a conditional edge\nworkflow.add_conditional_edges(\n # First, we define the start node. We use `agent`.\n # This means these are the edges taken after the `agent` node is called.\n \"agent\",\n # Next, we pass in the function that will determine which node is called next.\n should_continue,\n # Finally we pass in a mapping.\n # The keys are strings, and the values are other nodes.\n # END is a special node marking that the graph should finish.\n # What will happen is we will call `should_continue`, and then the output of that\n # will be matched against the keys in this mapping.\n # Based on which one it matches, that node will then be called.\n {\n # If `tools`, then we call the tool node.\n \"continue\": \"action\",\n # Otherwise we finish.\n \"end\": END,\n },\n)\n\n# We now add a normal edge from `tools` to `agent`.\n# This means that after `tools` is called, `agent` node is called next.\nworkflow.add_edge(\"action\", \"agent\")\n\n# Finally, we compile it!\n# This compiles it into a LangChain Runnable,\n# meaning you can use it as you would any other runnable\napp = workflow.compile()"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -300,12 +174,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"input\": \"what is the weather in sf\", \"chat_history\": []}\n",
|
||||
"for s in app.stream(inputs):\n",
|
||||
" print(list(s.values())[0])\n",
|
||||
" print(\"----\")"
|
||||
]
|
||||
"source": ["inputs = {\"input\": \"what is the weather in sf\", \"chat_history\": []}\nfor s in app.stream(inputs):\n print(list(s.values())[0])\n print(\"----\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -313,7 +182,7 @@
|
||||
"id": "2eb662bc-de7d-4a57-a3e8-2f00dcf4ff8b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
"source": [""]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -30,10 +30,7 @@
|
||||
"id": "694cfc4c-22a7-495d-930d-56b21d850ff9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langgraph langchain langchain_openai tavily-python"
|
||||
]
|
||||
"source": ["%%capture --no-stderr\n%pip install --quiet -U langgraph langchain langchain_openai tavily-python"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -49,13 +46,7 @@
|
||||
"id": "30c06a84-291a-4f58-9d31-53d3b56a3def",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
]
|
||||
"source": ["import getpass\nimport os\n\nos.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\nos.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -71,10 +62,7 @@
|
||||
"id": "a8fb285a-7e6e-46fc-a273-43ab1a676189",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"
|
||||
]
|
||||
"source": ["os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\nos.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -92,23 +80,7 @@
|
||||
"id": "4499eb16-bca8-4a60-9a3a-2f34ae3f7078",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain import hub\n",
|
||||
"from langchain.agents import create_openai_functions_agent\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"from langchain_openai.chat_models import ChatOpenAI\n",
|
||||
"\n",
|
||||
"tools = [TavilySearchResults(max_results=1)]\n",
|
||||
"\n",
|
||||
"# Get the prompt to use - you can modify this!\n",
|
||||
"prompt = hub.pull(\"hwchase17/openai-functions-agent\")\n",
|
||||
"\n",
|
||||
"# Choose the LLM that will drive the agent\n",
|
||||
"llm = ChatOpenAI(model=\"gpt-3.5-turbo-1106\", streaming=True)\n",
|
||||
"\n",
|
||||
"# Construct the OpenAI Functions agent\n",
|
||||
"agent_runnable = create_openai_functions_agent(llm, tools, prompt)"
|
||||
]
|
||||
"source": ["from langchain import hub\nfrom langchain.agents import create_openai_functions_agent\nfrom langchain_community.tools.tavily_search import TavilySearchResults\nfrom langchain_openai.chat_models import ChatOpenAI\n\ntools = [TavilySearchResults(max_results=1)]\n\n# Get the prompt to use - you can modify this!\nprompt = hub.pull(\"hwchase17/openai-functions-agent\")\n\n# Choose the LLM that will drive the agent\nllm = ChatOpenAI(model=\"gpt-3.5-turbo-1106\", streaming=True)\n\n# Construct the OpenAI Functions agent\nagent_runnable = create_openai_functions_agent(llm, tools, prompt)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -131,27 +103,7 @@
|
||||
"id": "c941fb10-dbe5-4d6a-ab7d-133d01c33cc4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import operator\n",
|
||||
"from typing import Annotated, TypedDict, Union\n",
|
||||
"\n",
|
||||
"from langchain_core.agents import AgentAction, AgentFinish\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
" # The input string\n",
|
||||
" input: str\n",
|
||||
" # The list of previous messages in the conversation\n",
|
||||
" chat_history: list[BaseMessage]\n",
|
||||
" # The outcome of a given call to the agent\n",
|
||||
" # Needs `None` as a valid type, since this is what this will start as\n",
|
||||
" agent_outcome: Union[AgentAction, AgentFinish, None]\n",
|
||||
" # List of actions and corresponding observations\n",
|
||||
" # Here we annotate this with `operator.add` to indicate that operations to\n",
|
||||
" # this state should be ADDED to the existing values (not overwrite it)\n",
|
||||
" intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add]"
|
||||
]
|
||||
"source": ["import operator\nfrom typing import Annotated, TypedDict, Union\n\nfrom langchain_core.agents import AgentAction, AgentFinish\nfrom langchain_core.messages import BaseMessage\n\n\nclass AgentState(TypedDict):\n # The input string\n input: str\n # The list of previous messages in the conversation\n chat_history: list[BaseMessage]\n # The outcome of a given call to the agent\n # Needs `None` as a valid type, since this is what this will start as\n agent_outcome: Union[AgentAction, AgentFinish, None]\n # List of actions and corresponding observations\n # Here we annotate this with `operator.add` to indicate that operations to\n # this state should be ADDED to the existing values (not overwrite it)\n intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -186,42 +138,7 @@
|
||||
"id": "d61a970d-edf4-4eef-9678-28bab7c72331",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.agents import AgentFinish\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt.tool_executor import ToolExecutor\n",
|
||||
"\n",
|
||||
"# This a helper class we have that is useful for running tools\n",
|
||||
"# It takes in an agent action and calls that tool and returns the result\n",
|
||||
"tool_executor = ToolExecutor(tools)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the agent\n",
|
||||
"def run_agent(data):\n",
|
||||
" agent_outcome = agent_runnable.invoke(data)\n",
|
||||
" return {\"agent_outcome\": agent_outcome}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function to execute tools\n",
|
||||
"def execute_tools(data):\n",
|
||||
" # Get the most recent agent_outcome - this is the key added in the `agent` above\n",
|
||||
" agent_action = data[\"agent_outcome\"]\n",
|
||||
" output = tool_executor.invoke(agent_action)\n",
|
||||
" return {\"intermediate_steps\": [(agent_action, str(output))]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define logic that will be used to determine which conditional edge to go down\n",
|
||||
"def should_continue(data):\n",
|
||||
" # If the agent outcome is an AgentFinish, then we return `exit` string\n",
|
||||
" # This will be used when setting up the graph to define the flow\n",
|
||||
" if isinstance(data[\"agent_outcome\"], AgentFinish):\n",
|
||||
" return \"end\"\n",
|
||||
" # Otherwise, an AgentAction is returned\n",
|
||||
" # Here we return `continue` string\n",
|
||||
" # This will be used when setting up the graph to define the flow\n",
|
||||
" else:\n",
|
||||
" return \"continue\""
|
||||
]
|
||||
"source": ["from langchain_core.agents import AgentFinish\n\nfrom langgraph.prebuilt.tool_executor import ToolExecutor\n\n# This a helper class we have that is useful for running tools\n# It takes in an agent action and calls that tool and returns the result\ntool_executor = ToolExecutor(tools)\n\n\n# Define the agent\ndef run_agent(data):\n agent_outcome = agent_runnable.invoke(data)\n return {\"agent_outcome\": agent_outcome}\n\n\n# Define the function to execute tools\ndef execute_tools(data):\n # Get the most recent agent_outcome - this is the key added in the `agent` above\n agent_action = data[\"agent_outcome\"]\n output = tool_executor.invoke(agent_action)\n return {\"intermediate_steps\": [(agent_action, str(output))]}\n\n\n# Define logic that will be used to determine which conditional edge to go down\ndef should_continue(data):\n # If the agent outcome is an AgentFinish, then we return `exit` string\n # This will be used when setting up the graph to define the flow\n if isinstance(data[\"agent_outcome\"], AgentFinish):\n return \"end\"\n # Otherwise, an AgentAction is returned\n # Here we return `continue` string\n # This will be used when setting up the graph to define the flow\n else:\n return \"continue\""]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -250,9 +167,7 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"tools[0].name"
|
||||
]
|
||||
"source": ["tools[0].name"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -260,21 +175,7 @@
|
||||
"id": "df25d899-2338-4f31-a8bf-0582a2eec325",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.agents import AgentActionMessageLog\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def first_agent(inputs):\n",
|
||||
" action = AgentActionMessageLog(\n",
|
||||
" # We force call this tool\n",
|
||||
" tool=\"tavily_search_results_json\",\n",
|
||||
" # We just pass in the `input` key to this tool\n",
|
||||
" tool_input=inputs[\"input\"],\n",
|
||||
" log=\"\",\n",
|
||||
" message_log=[],\n",
|
||||
" )\n",
|
||||
" return {\"agent_outcome\": action}"
|
||||
]
|
||||
"source": ["from langchain_core.agents import AgentActionMessageLog\n\n\ndef first_agent(inputs):\n action = AgentActionMessageLog(\n # We force call this tool\n tool=\"tavily_search_results_json\",\n # We just pass in the `input` key to this tool\n tool_input=inputs[\"input\"],\n log=\"\",\n message_log=[],\n )\n return {\"agent_outcome\": action}"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -296,54 +197,7 @@
|
||||
"id": "c4054dde-4618-49b7-998a-daa0c1d6d6c0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
"\n",
|
||||
"# Define the two nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", run_agent)\n",
|
||||
"workflow.add_node(\"action\", execute_tools)\n",
|
||||
"workflow.add_node(\"first_agent\", first_agent)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.set_entry_point(\"first_agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"\n",
|
||||
"# After the first agent, we want to take an action\n",
|
||||
"workflow.add_edge(\"first_agent\", \"action\")\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"app = workflow.compile()"
|
||||
]
|
||||
"source": ["from langgraph.graph import END, StateGraph, START\n\n# Define a new graph\nworkflow = StateGraph(AgentState)\n\n# Define the two nodes we will cycle between\nworkflow.add_node(\"agent\", run_agent)\nworkflow.add_node(\"action\", execute_tools)\nworkflow.add_node(\"first_agent\", first_agent)\n\n# Set the entrypoint as `agent`\n# This means that this node is the first one called\nworkflow.add_edge(START, \"first_agent\")\n\n# We now add a conditional edge\nworkflow.add_conditional_edges(\n # First, we define the start node. We use `agent`.\n # This means these are the edges taken after the `agent` node is called.\n \"agent\",\n # Next, we pass in the function that will determine which node is called next.\n should_continue,\n # Finally we pass in a mapping.\n # The keys are strings, and the values are other nodes.\n # END is a special node marking that the graph should finish.\n # What will happen is we will call `should_continue`, and then the output of that\n # will be matched against the keys in this mapping.\n # Based on which one it matches, that node will then be called.\n {\n # If `tools`, then we call the tool node.\n \"continue\": \"action\",\n # Otherwise we finish.\n \"end\": END,\n },\n)\n\n# We now add a normal edge from `tools` to `agent`.\n# This means that after `tools` is called, `agent` node is called next.\nworkflow.add_edge(\"action\", \"agent\")\n\n# After the first agent, we want to take an action\nworkflow.add_edge(\"first_agent\", \"action\")\n\n# Finally, we compile it!\n# This compiles it into a LangChain Runnable,\n# meaning you can use it as you would any other runnable\napp = workflow.compile()"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -366,12 +220,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"input\": \"what is the weather in sf\", \"chat_history\": []}\n",
|
||||
"for s in app.stream(inputs):\n",
|
||||
" print(list(s.values())[0])\n",
|
||||
" print(\"----\")"
|
||||
]
|
||||
"source": ["inputs = {\"input\": \"what is the weather in sf\", \"chat_history\": []}\nfor s in app.stream(inputs):\n print(list(s.values())[0])\n print(\"----\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -379,7 +228,7 @@
|
||||
"id": "2eb662bc-de7d-4a57-a3e8-2f00dcf4ff8b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
"source": [""]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -30,10 +30,7 @@
|
||||
"id": "3fa9e224-2f00-49e2-bca3-e9cb8d9f3d41",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langgraph langchain langchain_openai tavily-python"
|
||||
]
|
||||
"source": ["%%capture --no-stderr\n%pip install --quiet -U langgraph langchain langchain_openai tavily-python"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -49,13 +46,7 @@
|
||||
"id": "d180f0d0-385f-4ce3-994c-11e1d64595b5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
]
|
||||
"source": ["import getpass\nimport os\n\nos.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\nos.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -71,10 +62,7 @@
|
||||
"id": "72ad0539-ecd8-4eb1-b2c1-2242e5fc556f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"
|
||||
]
|
||||
"source": ["os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\nos.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -92,23 +80,7 @@
|
||||
"id": "4499eb16-bca8-4a60-9a3a-2f34ae3f7078",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain import hub\n",
|
||||
"from langchain.agents import create_openai_functions_agent\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"from langchain_openai.chat_models import ChatOpenAI\n",
|
||||
"\n",
|
||||
"tools = [TavilySearchResults(max_results=1)]\n",
|
||||
"\n",
|
||||
"# Get the prompt to use - you can modify this!\n",
|
||||
"prompt = hub.pull(\"hwchase17/openai-functions-agent\")\n",
|
||||
"\n",
|
||||
"# Choose the LLM that will drive the agent\n",
|
||||
"llm = ChatOpenAI(model=\"gpt-3.5-turbo-1106\", streaming=True)\n",
|
||||
"\n",
|
||||
"# Construct the OpenAI Functions agent\n",
|
||||
"agent_runnable = create_openai_functions_agent(llm, tools, prompt)"
|
||||
]
|
||||
"source": ["from langchain import hub\nfrom langchain.agents import create_openai_functions_agent\nfrom langchain_community.tools.tavily_search import TavilySearchResults\nfrom langchain_openai.chat_models import ChatOpenAI\n\ntools = [TavilySearchResults(max_results=1)]\n\n# Get the prompt to use - you can modify this!\nprompt = hub.pull(\"hwchase17/openai-functions-agent\")\n\n# Choose the LLM that will drive the agent\nllm = ChatOpenAI(model=\"gpt-3.5-turbo-1106\", streaming=True)\n\n# Construct the OpenAI Functions agent\nagent_runnable = create_openai_functions_agent(llm, tools, prompt)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -131,27 +103,7 @@
|
||||
"id": "c941fb10-dbe5-4d6a-ab7d-133d01c33cc4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import operator\n",
|
||||
"from typing import Annotated, TypedDict, Union\n",
|
||||
"\n",
|
||||
"from langchain_core.agents import AgentAction, AgentFinish\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
" # The input string\n",
|
||||
" input: str\n",
|
||||
" # The list of previous messages in the conversation\n",
|
||||
" chat_history: list[BaseMessage]\n",
|
||||
" # The outcome of a given call to the agent\n",
|
||||
" # Needs `None` as a valid type, since this is what this will start as\n",
|
||||
" agent_outcome: Union[AgentAction, AgentFinish, None]\n",
|
||||
" # List of actions and corresponding observations\n",
|
||||
" # Here we annotate this with `operator.add` to indicate that operations to\n",
|
||||
" # this state should be ADDED to the existing values (not overwrite it)\n",
|
||||
" intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add]"
|
||||
]
|
||||
"source": ["import operator\nfrom typing import Annotated, TypedDict, Union\n\nfrom langchain_core.agents import AgentAction, AgentFinish\nfrom langchain_core.messages import BaseMessage\n\n\nclass AgentState(TypedDict):\n # The input string\n input: str\n # The list of previous messages in the conversation\n chat_history: list[BaseMessage]\n # The outcome of a given call to the agent\n # Needs `None` as a valid type, since this is what this will start as\n agent_outcome: Union[AgentAction, AgentFinish, None]\n # List of actions and corresponding observations\n # Here we annotate this with `operator.add` to indicate that operations to\n # this state should be ADDED to the existing values (not overwrite it)\n intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -186,21 +138,7 @@
|
||||
"id": "2b757f84-1175-445e-8f8c-e5aeb765a03d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.agents import AgentFinish\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt.tool_executor import ToolExecutor\n",
|
||||
"\n",
|
||||
"# This a helper class we have that is useful for running tools\n",
|
||||
"# It takes in an agent action and calls that tool and returns the result\n",
|
||||
"tool_executor = ToolExecutor(tools)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the agent\n",
|
||||
"def run_agent(data):\n",
|
||||
" agent_outcome = agent_runnable.invoke(data)\n",
|
||||
" return {\"agent_outcome\": agent_outcome}"
|
||||
]
|
||||
"source": ["from langchain_core.agents import AgentFinish\n\nfrom langgraph.prebuilt.tool_executor import ToolExecutor\n\n# This a helper class we have that is useful for running tools\n# It takes in an agent action and calls that tool and returns the result\ntool_executor = ToolExecutor(tools)\n\n\n# Define the agent\ndef run_agent(data):\n agent_outcome = agent_runnable.invoke(data)\n return {\"agent_outcome\": agent_outcome}"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -218,30 +156,7 @@
|
||||
"id": "2fecf5e0-9604-4992-9c82-b9627466cd32",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Define the function to execute tools\n",
|
||||
"def execute_tools(data):\n",
|
||||
" # Get the most recent agent_outcome - this is the key added in the `agent` above\n",
|
||||
" agent_action = data[\"agent_outcome\"]\n",
|
||||
" response = input(prompt=f\"[y/n] continue with: {agent_action}?\")\n",
|
||||
" if response == \"n\":\n",
|
||||
" raise ValueError\n",
|
||||
" output = tool_executor.invoke(agent_action)\n",
|
||||
" return {\"intermediate_steps\": [(agent_action, str(output))]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define logic that will be used to determine which conditional edge to go down\n",
|
||||
"def should_continue(data):\n",
|
||||
" # If the agent outcome is an AgentFinish, then we return `exit` string\n",
|
||||
" # This will be used when setting up the graph to define the flow\n",
|
||||
" if isinstance(data[\"agent_outcome\"], AgentFinish):\n",
|
||||
" return \"end\"\n",
|
||||
" # Otherwise, an AgentAction is returned\n",
|
||||
" # Here we return `continue` string\n",
|
||||
" # This will be used when setting up the graph to define the flow\n",
|
||||
" else:\n",
|
||||
" return \"continue\""
|
||||
]
|
||||
"source": ["# Define the function to execute tools\ndef execute_tools(data):\n # Get the most recent agent_outcome - this is the key added in the `agent` above\n agent_action = data[\"agent_outcome\"]\n response = input(prompt=f\"[y/n] continue with: {agent_action}?\")\n if response == \"n\":\n raise ValueError\n output = tool_executor.invoke(agent_action)\n return {\"intermediate_steps\": [(agent_action, str(output))]}\n\n\n# Define logic that will be used to determine which conditional edge to go down\ndef should_continue(data):\n # If the agent outcome is an AgentFinish, then we return `exit` string\n # This will be used when setting up the graph to define the flow\n if isinstance(data[\"agent_outcome\"], AgentFinish):\n return \"end\"\n # Otherwise, an AgentAction is returned\n # Here we return `continue` string\n # This will be used when setting up the graph to define the flow\n else:\n return \"continue\""]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -259,50 +174,7 @@
|
||||
"id": "c4054dde-4618-49b7-998a-daa0c1d6d6c0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
"\n",
|
||||
"# Define the two nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", run_agent)\n",
|
||||
"workflow.add_node(\"action\", execute_tools)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.set_entry_point(\"agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"app = workflow.compile()"
|
||||
]
|
||||
"source": ["from langgraph.graph import END, StateGraph, START\n\n# Define a new graph\nworkflow = StateGraph(AgentState)\n\n# Define the two nodes we will cycle between\nworkflow.add_node(\"agent\", run_agent)\nworkflow.add_node(\"action\", execute_tools)\n\n# Set the entrypoint as `agent`\n# This means that this node is the first one called\nworkflow.add_edge(START, \"agent\")\n\n# We now add a conditional edge\nworkflow.add_conditional_edges(\n # First, we define the start node. We use `agent`.\n # This means these are the edges taken after the `agent` node is called.\n \"agent\",\n # Next, we pass in the function that will determine which node is called next.\n should_continue,\n # Finally we pass in a mapping.\n # The keys are strings, and the values are other nodes.\n # END is a special node marking that the graph should finish.\n # What will happen is we will call `should_continue`, and then the output of that\n # will be matched against the keys in this mapping.\n # Based on which one it matches, that node will then be called.\n {\n # If `tools`, then we call the tool node.\n \"continue\": \"action\",\n # Otherwise we finish.\n \"end\": END,\n },\n)\n\n# We now add a normal edge from `tools` to `agent`.\n# This means that after `tools` is called, `agent` node is called next.\nworkflow.add_edge(\"action\", \"agent\")\n\n# Finally, we compile it!\n# This compiles it into a LangChain Runnable,\n# meaning you can use it as you would any other runnable\napp = workflow.compile()"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -338,12 +210,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"input\": \"what is the weather in sf\", \"chat_history\": []}\n",
|
||||
"for s in app.stream(inputs):\n",
|
||||
" print(list(s.values())[0])\n",
|
||||
" print(\"----\")"
|
||||
]
|
||||
"source": ["inputs = {\"input\": \"what is the weather in sf\", \"chat_history\": []}\nfor s in app.stream(inputs):\n print(list(s.values())[0])\n print(\"----\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -351,7 +218,7 @@
|
||||
"id": "2eb662bc-de7d-4a57-a3e8-2f00dcf4ff8b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
"source": [""]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -30,10 +30,7 @@
|
||||
"id": "aa752131-27e3-4bd8-9f21-d6749a7e74f4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langgraph langchain langchain_openai tavily-python"
|
||||
]
|
||||
"source": ["%%capture --no-stderr\n%pip install --quiet -U langgraph langchain langchain_openai tavily-python"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -49,13 +46,7 @@
|
||||
"id": "5732e68f-4ae2-4db9-bf9c-454b4cc9ec01",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
]
|
||||
"source": ["import getpass\nimport os\n\nos.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\nos.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -71,10 +62,7 @@
|
||||
"id": "652d4600-8f95-493f-b9b9-d4095aed9218",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"
|
||||
]
|
||||
"source": ["os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\nos.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -92,23 +80,7 @@
|
||||
"id": "4499eb16-bca8-4a60-9a3a-2f34ae3f7078",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain import hub\n",
|
||||
"from langchain.agents import create_openai_functions_agent\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"from langchain_openai.chat_models import ChatOpenAI\n",
|
||||
"\n",
|
||||
"tools = [TavilySearchResults(max_results=1)]\n",
|
||||
"\n",
|
||||
"# Get the prompt to use - you can modify this!\n",
|
||||
"prompt = hub.pull(\"hwchase17/openai-functions-agent\")\n",
|
||||
"\n",
|
||||
"# Choose the LLM that will drive the agent\n",
|
||||
"llm = ChatOpenAI(model=\"gpt-3.5-turbo-1106\", streaming=True)\n",
|
||||
"\n",
|
||||
"# Construct the OpenAI Functions agent\n",
|
||||
"agent_runnable = create_openai_functions_agent(llm, tools, prompt)"
|
||||
]
|
||||
"source": ["from langchain import hub\nfrom langchain.agents import create_openai_functions_agent\nfrom langchain_community.tools.tavily_search import TavilySearchResults\nfrom langchain_openai.chat_models import ChatOpenAI\n\ntools = [TavilySearchResults(max_results=1)]\n\n# Get the prompt to use - you can modify this!\nprompt = hub.pull(\"hwchase17/openai-functions-agent\")\n\n# Choose the LLM that will drive the agent\nllm = ChatOpenAI(model=\"gpt-3.5-turbo-1106\", streaming=True)\n\n# Construct the OpenAI Functions agent\nagent_runnable = create_openai_functions_agent(llm, tools, prompt)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -131,27 +103,7 @@
|
||||
"id": "c941fb10-dbe5-4d6a-ab7d-133d01c33cc4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import operator\n",
|
||||
"from typing import Annotated, TypedDict, Union\n",
|
||||
"\n",
|
||||
"from langchain_core.agents import AgentAction, AgentFinish\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
" # The input string\n",
|
||||
" input: str\n",
|
||||
" # The list of previous messages in the conversation\n",
|
||||
" chat_history: list[BaseMessage]\n",
|
||||
" # The outcome of a given call to the agent\n",
|
||||
" # Needs `None` as a valid type, since this is what this will start as\n",
|
||||
" agent_outcome: Union[AgentAction, AgentFinish, None]\n",
|
||||
" # List of actions and corresponding observations\n",
|
||||
" # Here we annotate this with `operator.add` to indicate that operations to\n",
|
||||
" # this state should be ADDED to the existing values (not overwrite it)\n",
|
||||
" intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add]"
|
||||
]
|
||||
"source": ["import operator\nfrom typing import Annotated, TypedDict, Union\n\nfrom langchain_core.agents import AgentAction, AgentFinish\nfrom langchain_core.messages import BaseMessage\n\n\nclass AgentState(TypedDict):\n # The input string\n input: str\n # The list of previous messages in the conversation\n chat_history: list[BaseMessage]\n # The outcome of a given call to the agent\n # Needs `None` as a valid type, since this is what this will start as\n agent_outcome: Union[AgentAction, AgentFinish, None]\n # List of actions and corresponding observations\n # Here we annotate this with `operator.add` to indicate that operations to\n # this state should be ADDED to the existing values (not overwrite it)\n intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -186,15 +138,7 @@
|
||||
"id": "77e3c059-e31f-4c8f-81bf-edb58688e12b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.agents import AgentFinish\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt.tool_executor import ToolExecutor\n",
|
||||
"\n",
|
||||
"# This a helper class we have that is useful for running tools\n",
|
||||
"# It takes in an agent action and calls that tool and returns the result\n",
|
||||
"tool_executor = ToolExecutor(tools)"
|
||||
]
|
||||
"source": ["from langchain_core.agents import AgentFinish\n\nfrom langgraph.prebuilt.tool_executor import ToolExecutor\n\n# This a helper class we have that is useful for running tools\n# It takes in an agent action and calls that tool and returns the result\ntool_executor = ToolExecutor(tools)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -212,36 +156,7 @@
|
||||
"id": "a9f66a3e-aba1-4893-95b1-a433c7091d5e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Define the agent\n",
|
||||
"def run_agent(data):\n",
|
||||
" inputs = data.copy()\n",
|
||||
" if len(inputs[\"intermediate_steps\"]) > 5:\n",
|
||||
" inputs[\"intermediate_steps\"] = inputs[\"intermediate_steps\"][-5:]\n",
|
||||
" agent_outcome = agent_runnable.invoke(inputs)\n",
|
||||
" return {\"agent_outcome\": agent_outcome}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function to execute tools\n",
|
||||
"def execute_tools(data):\n",
|
||||
" # Get the most recent agent_outcome - this is the key added in the `agent` above\n",
|
||||
" agent_action = data[\"agent_outcome\"]\n",
|
||||
" output = tool_executor.invoke(agent_action)\n",
|
||||
" return {\"intermediate_steps\": [(agent_action, str(output))]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define logic that will be used to determine which conditional edge to go down\n",
|
||||
"def should_continue(data):\n",
|
||||
" # If the agent outcome is an AgentFinish, then we return `exit` string\n",
|
||||
" # This will be used when setting up the graph to define the flow\n",
|
||||
" if isinstance(data[\"agent_outcome\"], AgentFinish):\n",
|
||||
" return \"end\"\n",
|
||||
" # Otherwise, an AgentAction is returned\n",
|
||||
" # Here we return `continue` string\n",
|
||||
" # This will be used when setting up the graph to define the flow\n",
|
||||
" else:\n",
|
||||
" return \"continue\""
|
||||
]
|
||||
"source": ["# Define the agent\ndef run_agent(data):\n inputs = data.copy()\n if len(inputs[\"intermediate_steps\"]) > 5:\n inputs[\"intermediate_steps\"] = inputs[\"intermediate_steps\"][-5:]\n agent_outcome = agent_runnable.invoke(inputs)\n return {\"agent_outcome\": agent_outcome}\n\n\n# Define the function to execute tools\ndef execute_tools(data):\n # Get the most recent agent_outcome - this is the key added in the `agent` above\n agent_action = data[\"agent_outcome\"]\n output = tool_executor.invoke(agent_action)\n return {\"intermediate_steps\": [(agent_action, str(output))]}\n\n\n# Define logic that will be used to determine which conditional edge to go down\ndef should_continue(data):\n # If the agent outcome is an AgentFinish, then we return `exit` string\n # This will be used when setting up the graph to define the flow\n if isinstance(data[\"agent_outcome\"], AgentFinish):\n return \"end\"\n # Otherwise, an AgentAction is returned\n # Here we return `continue` string\n # This will be used when setting up the graph to define the flow\n else:\n return \"continue\""]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -259,50 +174,7 @@
|
||||
"id": "c4054dde-4618-49b7-998a-daa0c1d6d6c0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
"\n",
|
||||
"# Define the two nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", run_agent)\n",
|
||||
"workflow.add_node(\"action\", execute_tools)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.set_entry_point(\"agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"app = workflow.compile()"
|
||||
]
|
||||
"source": ["from langgraph.graph import END, StateGraph, START\n\n# Define a new graph\nworkflow = StateGraph(AgentState)\n\n# Define the two nodes we will cycle between\nworkflow.add_node(\"agent\", run_agent)\nworkflow.add_node(\"action\", execute_tools)\n\n# Set the entrypoint as `agent`\n# This means that this node is the first one called\nworkflow.add_edge(START, \"agent\")\n\n# We now add a conditional edge\nworkflow.add_conditional_edges(\n # First, we define the start node. We use `agent`.\n # This means these are the edges taken after the `agent` node is called.\n \"agent\",\n # Next, we pass in the function that will determine which node is called next.\n should_continue,\n # Finally we pass in a mapping.\n # The keys are strings, and the values are other nodes.\n # END is a special node marking that the graph should finish.\n # What will happen is we will call `should_continue`, and then the output of that\n # will be matched against the keys in this mapping.\n # Based on which one it matches, that node will then be called.\n {\n # If `tools`, then we call the tool node.\n \"continue\": \"action\",\n # Otherwise we finish.\n \"end\": END,\n },\n)\n\n# We now add a normal edge from `tools` to `agent`.\n# This means that after `tools` is called, `agent` node is called next.\nworkflow.add_edge(\"action\", \"agent\")\n\n# Finally, we compile it!\n# This compiles it into a LangChain Runnable,\n# meaning you can use it as you would any other runnable\napp = workflow.compile()"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -325,12 +197,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"input\": \"what is the weather in sf\", \"chat_history\": []}\n",
|
||||
"for s in app.stream(inputs):\n",
|
||||
" print(list(s.values())[0])\n",
|
||||
" print(\"----\")"
|
||||
]
|
||||
"source": ["inputs = {\"input\": \"what is the weather in sf\", \"chat_history\": []}\nfor s in app.stream(inputs):\n print(list(s.values())[0])\n print(\"----\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -338,7 +205,7 @@
|
||||
"id": "2eb662bc-de7d-4a57-a3e8-2f00dcf4ff8b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
"source": [""]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"source": [
|
||||
"# How to run graph asynchronously\n",
|
||||
"\n",
|
||||
"In this example we will build a ReAct agent with native [async](https://docs.python.org/3/library/asyncio.html) implementations of the core logic. When Chat Models have async clients, this can give us some nice performance improvements if you\n",
|
||||
"In this example we will build a ReAct agent with native [async](https://docs.python.org/3/library/asyncio.html) implementations of the core logic. When chat models have async clients, this can give us some nice performance improvements if you\n",
|
||||
"are running concurrent branches in your graph or if your graph is running within a larger web server process.\n",
|
||||
"\n",
|
||||
"In general, you don't need to change anything about your graph to add `async` support. That's one of the beauties of [Runnables](https://python.langchain.com/docs/expression_language/interface/). \n",
|
||||
@@ -37,10 +37,7 @@
|
||||
"id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langgraph langchain_anthropic"
|
||||
]
|
||||
"source": ["%%capture --no-stderr\n%pip install --quiet -U langgraph langchain_anthropic"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -56,18 +53,7 @@
|
||||
"id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
" if not os.environ.get(var):\n",
|
||||
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"_set_env(\"ANTHROPIC_API_KEY\")"
|
||||
]
|
||||
"source": ["import getpass\nimport os\n\n\ndef _set_env(var: str):\n if not os.environ.get(var):\n os.environ[var] = getpass.getpass(f\"{var}: \")\n\n\n_set_env(\"ANTHROPIC_API_KEY\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -83,10 +69,7 @@
|
||||
"id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"_set_env(\"LANGCHAIN_API_KEY\")"
|
||||
]
|
||||
"source": ["os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n_set_env(\"LANGCHAIN_API_KEY\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -112,22 +95,7 @@
|
||||
"id": "6768a3ab",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph.message import add_messages\n",
|
||||
"\n",
|
||||
"# Add messages essentially does this with more\n",
|
||||
"# robust handling\n",
|
||||
"# def add_messages(left: list, right: list):\n",
|
||||
"# return left + right\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class State(TypedDict):\n",
|
||||
" messages: Annotated[list, add_messages]"
|
||||
]
|
||||
"source": ["from typing import Annotated\n\nfrom typing_extensions import TypedDict\n\nfrom langgraph.graph.message import add_messages\n\n# Add messages essentially does this with more\n# robust handling\n# def add_messages(left: list, right: list):\n# return left + right\n\n\nclass State(TypedDict):\n messages: Annotated[list, add_messages]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -147,19 +115,7 @@
|
||||
"id": "d7ef57dd-5d6e-4ad3-9377-a92201c1310e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.tools import tool\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def search(query: str):\n",
|
||||
" \"\"\"Call to surf the web.\"\"\"\n",
|
||||
" # This is a placeholder, but don't tell the LLM that...\n",
|
||||
" return [\"The answer to your question lies within.\"]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [search]"
|
||||
]
|
||||
"source": ["from langchain_core.tools import tool\n\n\n@tool\ndef search(query: str):\n \"\"\"Call to surf the web.\"\"\"\n # This is a placeholder, but don't tell the LLM that...\n return [\"The answer to your question lies within.\"]\n\n\ntools = [search]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -176,11 +132,7 @@
|
||||
"id": "5cf3331e-ccb3-41c8-aeb9-a840a94d41e7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"\n",
|
||||
"tool_node = ToolNode(tools)"
|
||||
]
|
||||
"source": ["from langgraph.prebuilt import ToolNode\n\ntool_node = ToolNode(tools)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -204,11 +156,7 @@
|
||||
"id": "892b54b9-75f0-4804-9ed0-88b5e5532989",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"\n",
|
||||
"model = ChatAnthropic(model=\"claude-3-haiku-20240307\")"
|
||||
]
|
||||
"source": ["from langchain_anthropic import ChatAnthropic\n\nmodel = ChatAnthropic(model=\"claude-3-haiku-20240307\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -226,9 +174,7 @@
|
||||
"id": "cd3cbae5-d92c-4559-a4aa-44721b80d107",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model = model.bind_tools(tools)"
|
||||
]
|
||||
"source": ["model = model.bind_tools(tools)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -267,29 +213,7 @@
|
||||
"id": "3b541bb9-900c-40d0-964d-7b5dfee30667",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Literal\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state: State) -> Literal[\"end\", \"continue\"]:\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # If there is no tool call, then we finish\n",
|
||||
" if not last_message.tool_calls:\n",
|
||||
" return \"end\"\n",
|
||||
" # Otherwise if there is, we continue\n",
|
||||
" else:\n",
|
||||
" return \"continue\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that calls the model\n",
|
||||
"async def call_model(state: State):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" response = await model.ainvoke(messages)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [response]}"
|
||||
]
|
||||
"source": ["from typing import Literal\n\n\n# Define the function that determines whether to continue or not\ndef should_continue(state: State) -> Literal[\"end\", \"continue\"]:\n messages = state[\"messages\"]\n last_message = messages[-1]\n # If there is no tool call, then we finish\n if not last_message.tool_calls:\n return \"end\"\n # Otherwise if there is, we continue\n else:\n return \"continue\"\n\n\n# Define the function that calls the model\nasync def call_model(state: State):\n messages = state[\"messages\"]\n response = await model.ainvoke(messages)\n # We return a list, because this will get added to the existing list\n return {\"messages\": [response]}"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -307,50 +231,7 @@
|
||||
"id": "813ae66c-3b58-4283-a02a-36da72a2ab90",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(State)\n",
|
||||
"\n",
|
||||
"# Define the two nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", call_model)\n",
|
||||
"workflow.add_node(\"action\", tool_node)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.set_entry_point(\"agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"app = workflow.compile()"
|
||||
]
|
||||
"source": ["from langgraph.graph import END, StateGraph, START\n\n# Define a new graph\nworkflow = StateGraph(State)\n\n# Define the two nodes we will cycle between\nworkflow.add_node(\"agent\", call_model)\nworkflow.add_node(\"action\", tool_node)\n\n# Set the entrypoint as `agent`\n# This means that this node is the first one called\nworkflow.add_edge(START, \"agent\")\n\n# We now add a conditional edge\nworkflow.add_conditional_edges(\n # First, we define the start node. We use `agent`.\n # This means these are the edges taken after the `agent` node is called.\n \"agent\",\n # Next, we pass in the function that will determine which node is called next.\n should_continue,\n # Finally we pass in a mapping.\n # The keys are strings, and the values are other nodes.\n # END is a special node marking that the graph should finish.\n # What will happen is we will call `should_continue`, and then the output of that\n # will be matched against the keys in this mapping.\n # Based on which one it matches, that node will then be called.\n {\n # If `tools`, then we call the tool node.\n \"continue\": \"action\",\n # Otherwise we finish.\n \"end\": END,\n },\n)\n\n# We now add a normal edge from `tools` to `agent`.\n# This means that after `tools` is called, `agent` node is called next.\nworkflow.add_edge(\"action\", \"agent\")\n\n# Finally, we compile it!\n# This compiles it into a LangChain Runnable,\n# meaning you can use it as you would any other runnable\napp = workflow.compile()"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -369,11 +250,7 @@
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from IPython.display import Image, display\n",
|
||||
"\n",
|
||||
"display(Image(app.get_graph().draw_mermaid_png()))"
|
||||
]
|
||||
"source": ["from IPython.display import Image, display\n\ndisplay(Image(app.get_graph().draw_mermaid_png()))"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -406,12 +283,7 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\n",
|
||||
"await app.ainvoke(inputs)"
|
||||
]
|
||||
"source": ["from langchain_core.messages import HumanMessage\n\ninputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\nawait app.ainvoke(inputs)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -480,16 +352,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\n",
|
||||
"async for output in app.astream(inputs, stream_mode=\"updates\"):\n",
|
||||
" # stream_mode=\"updates\" yields dictionaries with output keyed by node name\n",
|
||||
" for key, value in output.items():\n",
|
||||
" print(f\"Output from node '{key}':\")\n",
|
||||
" print(\"---\")\n",
|
||||
" print(value[\"messages\"][-1].pretty_print())\n",
|
||||
" print(\"\\n---\\n\")"
|
||||
]
|
||||
"source": ["inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\nasync for output in app.astream(inputs, stream_mode=\"updates\"):\n # stream_mode=\"updates\" yields dictionaries with output keyed by node name\n for key, value in output.items():\n print(f\"Output from node '{key}':\")\n print(\"---\")\n print(value[\"messages\"][-1].pretty_print())\n print(\"\\n---\\n\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -546,20 +409,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\n",
|
||||
"async for output in app.astream_log(inputs, include_types=[\"llm\"]):\n",
|
||||
" # astream_log() yields the requested logs (here LLMs) in JSONPatch format\n",
|
||||
" for op in output.ops:\n",
|
||||
" if op[\"path\"] == \"/streamed_output/-\":\n",
|
||||
" # this is the output from .stream()\n",
|
||||
" ...\n",
|
||||
" elif op[\"path\"].startswith(\"/logs/\") and op[\"path\"].endswith(\n",
|
||||
" \"/streamed_output/-\"\n",
|
||||
" ):\n",
|
||||
" # because we chose to only include LLMs, these are LLM tokens\n",
|
||||
" print(op[\"value\"].content, end=\"|\")"
|
||||
]
|
||||
"source": ["inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\nasync for output in app.astream_log(inputs, include_types=[\"llm\"]):\n # astream_log() yields the requested logs (here LLMs) in JSONPatch format\n for op in output.ops:\n if op[\"path\"] == \"/streamed_output/-\":\n # this is the output from .stream()\n ...\n elif op[\"path\"].startswith(\"/logs/\") and op[\"path\"].endswith(\n \"/streamed_output/-\"\n ):\n # because we chose to only include LLMs, these are LLM tokens\n print(op[\"value\"].content, end=\"|\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -567,7 +417,7 @@
|
||||
"id": "08ae8246-11d5-40e1-8567-361e5bef8917",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
"source": [""]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -27,10 +27,7 @@
|
||||
"id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langchain langchain_anthropic tavily-python"
|
||||
]
|
||||
"source": ["%%capture --no-stderr\n%pip install --quiet -U langchain langchain_anthropic tavily-python"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -46,13 +43,7 @@
|
||||
"id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
]
|
||||
"source": ["import getpass\nimport os\n\nos.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\nos.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -68,10 +59,7 @@
|
||||
"id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"
|
||||
]
|
||||
"source": ["os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\nos.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -95,11 +83,7 @@
|
||||
"id": "d7ef57dd-5d6e-4ad3-9377-a92201c1310e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"\n",
|
||||
"tools = [TavilySearchResults(max_results=1)]"
|
||||
]
|
||||
"source": ["from langchain_community.tools.tavily_search import TavilySearchResults\n\ntools = [TavilySearchResults(max_results=1)]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -123,11 +107,7 @@
|
||||
"id": "892b54b9-75f0-4804-9ed0-88b5e5532989",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"\n",
|
||||
"model = ChatAnthropic(temperature=0, model_name=\"claude-3-opus-20240229\")"
|
||||
]
|
||||
"source": ["from langchain_anthropic import ChatAnthropic\n\nmodel = ChatAnthropic(temperature=0, model_name=\"claude-3-opus-20240229\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -154,9 +134,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = model.bind_tools(tools)"
|
||||
]
|
||||
"source": ["model = model.bind_tools(tools)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -164,16 +142,7 @@
|
||||
"id": "ea793afa-2eab-4901-910d-6eed90cd6564",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
" messages: Annotated[Sequence[BaseMessage], operator.add]"
|
||||
]
|
||||
"source": ["import operator\nfrom typing import Annotated, Sequence, TypedDict\n\nfrom langchain_core.messages import BaseMessage\n\n\nclass AgentState(TypedDict):\n messages: Annotated[Sequence[BaseMessage], operator.add]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -208,33 +177,7 @@
|
||||
"id": "3b541bb9-900c-40d0-964d-7b5dfee30667",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # If there are no tool calls, then we finish\n",
|
||||
" if not last_message.tool_calls:\n",
|
||||
" return \"end\"\n",
|
||||
" # Otherwise if there is, we continue\n",
|
||||
" else:\n",
|
||||
" return \"continue\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that calls the model\n",
|
||||
"def call_model(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" response = model.invoke(messages)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [response]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function to execute tools\n",
|
||||
"tool_node = ToolNode(tools)"
|
||||
]
|
||||
"source": ["from langgraph.prebuilt import ToolNode\n\n\n# Define the function that determines whether to continue or not\ndef should_continue(state):\n messages = state[\"messages\"]\n last_message = messages[-1]\n # If there are no tool calls, then we finish\n if not last_message.tool_calls:\n return \"end\"\n # Otherwise if there is, we continue\n else:\n return \"continue\"\n\n\n# Define the function that calls the model\ndef call_model(state):\n messages = state[\"messages\"]\n response = model.invoke(messages)\n # We return a list, because this will get added to the existing list\n return {\"messages\": [response]}\n\n\n# Define the function to execute tools\ntool_node = ToolNode(tools)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -252,50 +195,7 @@
|
||||
"id": "813ae66c-3b58-4283-a02a-36da72a2ab90",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
"\n",
|
||||
"# Define the two nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", call_model)\n",
|
||||
"workflow.add_node(\"action\", tool_node)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.set_entry_point(\"agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"app = workflow.compile()"
|
||||
]
|
||||
"source": ["from langgraph.graph import END, StateGraph, START\n\n# Define a new graph\nworkflow = StateGraph(AgentState)\n\n# Define the two nodes we will cycle between\nworkflow.add_node(\"agent\", call_model)\nworkflow.add_node(\"action\", tool_node)\n\n# Set the entrypoint as `agent`\n# This means that this node is the first one called\nworkflow.add_edge(START, \"agent\")\n\n# We now add a conditional edge\nworkflow.add_conditional_edges(\n # First, we define the start node. We use `agent`.\n # This means these are the edges taken after the `agent` node is called.\n \"agent\",\n # Next, we pass in the function that will determine which node is called next.\n should_continue,\n # Finally we pass in a mapping.\n # The keys are strings, and the values are other nodes.\n # END is a special node marking that the graph should finish.\n # What will happen is we will call `should_continue`, and then the output of that\n # will be matched against the keys in this mapping.\n # Based on which one it matches, that node will then be called.\n {\n # If `tools`, then we call the tool node.\n \"continue\": \"action\",\n # Otherwise we finish.\n \"end\": END,\n },\n)\n\n# We now add a normal edge from `tools` to `agent`.\n# This means that after `tools` is called, `agent` node is called next.\nworkflow.add_edge(\"action\", \"agent\")\n\n# Finally, we compile it!\n# This compiles it into a LangChain Runnable,\n# meaning you can use it as you would any other runnable\napp = workflow.compile()"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -328,12 +228,7 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\n",
|
||||
"app.invoke(inputs)"
|
||||
]
|
||||
"source": ["from langchain_core.messages import HumanMessage\n\ninputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\napp.invoke(inputs)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -383,16 +278,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\n",
|
||||
"for output in app.stream(inputs):\n",
|
||||
" # stream() yields dictionaries with output keyed by node name\n",
|
||||
" for key, value in output.items():\n",
|
||||
" print(f\"Output from node '{key}':\")\n",
|
||||
" print(\"---\")\n",
|
||||
" print(value)\n",
|
||||
" print(\"\\n---\\n\")"
|
||||
]
|
||||
"source": ["inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\nfor output in app.stream(inputs):\n # stream() yields dictionaries with output keyed by node name\n for key, value in output.items():\n print(f\"Output from node '{key}':\")\n print(\"---\")\n print(value)\n print(\"\\n---\\n\")"]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -26,10 +26,7 @@
|
||||
"id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langgraph langchain langchain_openai tavily-python"
|
||||
]
|
||||
"source": ["%%capture --no-stderr\n%pip install --quiet -U langgraph langchain langchain_openai tavily-python"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -45,13 +42,7 @@
|
||||
"id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
]
|
||||
"source": ["import getpass\nimport os\n\nos.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\nos.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -67,10 +58,7 @@
|
||||
"id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"
|
||||
]
|
||||
"source": ["os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\nos.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -90,11 +78,7 @@
|
||||
"id": "d7ef57dd-5d6e-4ad3-9377-a92201c1310e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"\n",
|
||||
"tools = [TavilySearchResults(max_results=1)]"
|
||||
]
|
||||
"source": ["from langchain_community.tools.tavily_search import TavilySearchResults\n\ntools = [TavilySearchResults(max_results=1)]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -112,11 +96,7 @@
|
||||
"id": "5cf3331e-ccb3-41c8-aeb9-a840a94d41e7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolExecutor\n",
|
||||
"\n",
|
||||
"tool_executor = ToolExecutor(tools)"
|
||||
]
|
||||
"source": ["from langgraph.prebuilt import ToolExecutor\n\ntool_executor = ToolExecutor(tools)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -140,13 +120,7 @@
|
||||
"id": "892b54b9-75f0-4804-9ed0-88b5e5532989",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"# We will set streaming=True so that we can stream tokens\n",
|
||||
"# See the streaming section for more information on this.\n",
|
||||
"model = ChatOpenAI(temperature=0, streaming=True)"
|
||||
]
|
||||
"source": ["from langchain_openai import ChatOpenAI\n\n# We will set streaming=True so that we can stream tokens\n# See the streaming section for more information on this.\nmodel = ChatOpenAI(temperature=0, streaming=True)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -164,9 +138,7 @@
|
||||
"id": "cd3cbae5-d92c-4559-a4aa-44721b80d107",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model = model.bind_tools(tools)"
|
||||
]
|
||||
"source": ["model = model.bind_tools(tools)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -192,16 +164,7 @@
|
||||
"id": "ea793afa-2eab-4901-910d-6eed90cd6564",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
" messages: Annotated[Sequence[BaseMessage], operator.add]"
|
||||
]
|
||||
"source": ["import operator\nfrom typing import Annotated, Sequence, TypedDict\n\nfrom langchain_core.messages import BaseMessage\n\n\nclass AgentState(TypedDict):\n messages: Annotated[Sequence[BaseMessage], operator.add]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -236,53 +199,7 @@
|
||||
"id": "3b541bb9-900c-40d0-964d-7b5dfee30667",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # If there is no function call, then we finish\n",
|
||||
" if not last_message.tool_calls:\n",
|
||||
" return \"end\"\n",
|
||||
" # Otherwise if there is, we continue\n",
|
||||
" else:\n",
|
||||
" return \"continue\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that calls the model\n",
|
||||
"def call_model(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" response = model.invoke(messages)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [response]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function to execute tools\n",
|
||||
"def call_tool(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" # Based on the continue condition\n",
|
||||
" # we know the last message involves a function call\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # We construct an ToolInvocation from the function_call\n",
|
||||
" tool_call = last_message.tool_calls[0]\n",
|
||||
" action = ToolInvocation(\n",
|
||||
" tool=tool_call[\"name\"],\n",
|
||||
" tool_input=tool_call[\"args\"],\n",
|
||||
" )\n",
|
||||
" # We call the tool_executor and get back a response\n",
|
||||
" response = tool_executor.invoke(action)\n",
|
||||
" # We use the response to create a FunctionMessage\n",
|
||||
" function_message = ToolMessage(\n",
|
||||
" content=str(response), name=action.tool, tool_call_id=tool_call[\"id\"]\n",
|
||||
" )\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [function_message]}"
|
||||
]
|
||||
"source": ["from langchain_core.messages import ToolMessage\n\nfrom langgraph.prebuilt import ToolInvocation\n\n\n# Define the function that determines whether to continue or not\ndef should_continue(state):\n messages = state[\"messages\"]\n last_message = messages[-1]\n # If there is no function call, then we finish\n if not last_message.tool_calls:\n return \"end\"\n # Otherwise if there is, we continue\n else:\n return \"continue\"\n\n\n# Define the function that calls the model\ndef call_model(state):\n messages = state[\"messages\"]\n response = model.invoke(messages)\n # We return a list, because this will get added to the existing list\n return {\"messages\": [response]}\n\n\n# Define the function to execute tools\ndef call_tool(state):\n messages = state[\"messages\"]\n # Based on the continue condition\n # we know the last message involves a function call\n last_message = messages[-1]\n # We construct an ToolInvocation from the function_call\n tool_call = last_message.tool_calls[0]\n action = ToolInvocation(\n tool=tool_call[\"name\"],\n tool_input=tool_call[\"args\"],\n )\n # We call the tool_executor and get back a response\n response = tool_executor.invoke(action)\n # We use the response to create a FunctionMessage\n function_message = ToolMessage(\n content=str(response), name=action.tool, tool_call_id=tool_call[\"id\"]\n )\n # We return a list, because this will get added to the existing list\n return {\"messages\": [function_message]}"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -300,50 +217,7 @@
|
||||
"id": "813ae66c-3b58-4283-a02a-36da72a2ab90",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
"\n",
|
||||
"# Define the two nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", call_model)\n",
|
||||
"workflow.add_node(\"action\", call_tool)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.set_entry_point(\"agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"app = workflow.compile()"
|
||||
]
|
||||
"source": ["from langgraph.graph import END, StateGraph, START\n\n# Define a new graph\nworkflow = StateGraph(AgentState)\n\n# Define the two nodes we will cycle between\nworkflow.add_node(\"agent\", call_model)\nworkflow.add_node(\"action\", call_tool)\n\n# Set the entrypoint as `agent`\n# This means that this node is the first one called\nworkflow.add_edge(START, \"agent\")\n\n# We now add a conditional edge\nworkflow.add_conditional_edges(\n # First, we define the start node. We use `agent`.\n # This means these are the edges taken after the `agent` node is called.\n \"agent\",\n # Next, we pass in the function that will determine which node is called next.\n should_continue,\n # Finally we pass in a mapping.\n # The keys are strings, and the values are other nodes.\n # END is a special node marking that the graph should finish.\n # What will happen is we will call `should_continue`, and then the output of that\n # will be matched against the keys in this mapping.\n # Based on which one it matches, that node will then be called.\n {\n # If `tools`, then we call the tool node.\n \"continue\": \"action\",\n # Otherwise we finish.\n \"end\": END,\n },\n)\n\n# We now add a normal edge from `tools` to `agent`.\n# This means that after `tools` is called, `agent` node is called next.\nworkflow.add_edge(\"action\", \"agent\")\n\n# Finally, we compile it!\n# This compiles it into a LangChain Runnable,\n# meaning you can use it as you would any other runnable\napp = workflow.compile()"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -362,15 +236,7 @@
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from IPython.display import Image, display\n",
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(app.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
"source": ["from IPython.display import Image, display\n\ntry:\n display(Image(app.get_graph(xray=True).draw_mermaid_png()))\nexcept Exception:\n # This requires some extra dependencies and is optional\n pass"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -403,12 +269,7 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\n",
|
||||
"app.invoke(inputs)"
|
||||
]
|
||||
"source": ["from langchain_core.messages import HumanMessage\n\ninputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\napp.invoke(inputs)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -458,16 +319,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\n",
|
||||
"for output in app.stream(inputs):\n",
|
||||
" # stream() yields dictionaries with output keyed by node name\n",
|
||||
" for key, value in output.items():\n",
|
||||
" print(f\"Output from node '{key}':\")\n",
|
||||
" print(\"---\")\n",
|
||||
" print(value)\n",
|
||||
" print(\"\\n---\\n\")"
|
||||
]
|
||||
"source": ["inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\nfor output in app.stream(inputs):\n # stream() yields dictionaries with output keyed by node name\n for key, value in output.items():\n print(f\"Output from node '{key}':\")\n print(\"---\")\n print(value)\n print(\"\\n---\\n\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -604,21 +456,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf?\")]}\n",
|
||||
"\n",
|
||||
"async for output in app.astream_log(inputs, include_types=[\"llm\"]):\n",
|
||||
" # astream_log() yields the requested logs (here LLMs) in JSONPatch format\n",
|
||||
" for op in output.ops:\n",
|
||||
" if op[\"path\"] == \"/streamed_output/-\":\n",
|
||||
" # this is the output from .stream()\n",
|
||||
" ...\n",
|
||||
" elif op[\"path\"].startswith(\"/logs/\") and op[\"path\"].endswith(\n",
|
||||
" \"/streamed_output/-\"\n",
|
||||
" ):\n",
|
||||
" # because we chose to only include LLMs, these are LLM tokens\n",
|
||||
" print(op[\"value\"])"
|
||||
]
|
||||
"source": ["inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf?\")]}\n\nasync for output in app.astream_log(inputs, include_types=[\"llm\"]):\n # astream_log() yields the requested logs (here LLMs) in JSONPatch format\n for op in output.ops:\n if op[\"path\"] == \"/streamed_output/-\":\n # this is the output from .stream()\n ...\n elif op[\"path\"].startswith(\"/logs/\") and op[\"path\"].endswith(\n \"/streamed_output/-\"\n ):\n # because we chose to only include LLMs, these are LLM tokens\n print(op[\"value\"])"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -626,7 +464,7 @@
|
||||
"id": "08ae8246-11d5-40e1-8567-361e5bef8917",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
"source": [""]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -38,10 +38,7 @@
|
||||
"id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langgraph langchain langchain_openai tavily-python"
|
||||
]
|
||||
"source": ["%%capture --no-stderr\n%pip install --quiet -U langgraph langchain langchain_openai tavily-python"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -57,13 +54,7 @@
|
||||
"id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
]
|
||||
"source": ["import getpass\nimport os\n\nos.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\nos.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -79,10 +70,7 @@
|
||||
"id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"
|
||||
]
|
||||
"source": ["os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\nos.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -106,19 +94,7 @@
|
||||
"id": "4a1b9990-3b11-4a51-bd51-76117afd38b9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class SearchTool(BaseModel):\n",
|
||||
" \"\"\"Look up things online, optionally returning directly\"\"\"\n",
|
||||
"\n",
|
||||
" query: str = Field(description=\"query to look up online\")\n",
|
||||
" return_direct: bool = Field(\n",
|
||||
" description=\"Whether or the result of this should be returned directly to the user without you seeing what it is\",\n",
|
||||
" default=False,\n",
|
||||
" )"
|
||||
]
|
||||
"source": ["from langchain_core.pydantic_v1 import BaseModel, Field\n\n\nclass SearchTool(BaseModel):\n \"\"\"Look up things online, optionally returning directly\"\"\"\n\n query: str = Field(description=\"query to look up online\")\n return_direct: bool = Field(\n description=\"Whether or the result of this should be returned directly to the user without you seeing what it is\",\n default=False,\n )"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -126,12 +102,7 @@
|
||||
"id": "d7ef57dd-5d6e-4ad3-9377-a92201c1310e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"\n",
|
||||
"search_tool = TavilySearchResults(max_results=1, args_schema=SearchTool)\n",
|
||||
"tools = [search_tool]"
|
||||
]
|
||||
"source": ["from langchain_community.tools.tavily_search import TavilySearchResults\n\nsearch_tool = TavilySearchResults(max_results=1, args_schema=SearchTool)\ntools = [search_tool]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -149,11 +120,7 @@
|
||||
"id": "5cf3331e-ccb3-41c8-aeb9-a840a94d41e7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolExecutor\n",
|
||||
"\n",
|
||||
"tool_executor = ToolExecutor(tools)"
|
||||
]
|
||||
"source": ["from langgraph.prebuilt import ToolExecutor\n\ntool_executor = ToolExecutor(tools)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -177,13 +144,7 @@
|
||||
"id": "892b54b9-75f0-4804-9ed0-88b5e5532989",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"# We will set streaming=True so that we can stream tokens\n",
|
||||
"# See the streaming section for more information on this.\n",
|
||||
"model = ChatOpenAI(temperature=0, streaming=True)"
|
||||
]
|
||||
"source": ["from langchain_openai import ChatOpenAI\n\n# We will set streaming=True so that we can stream tokens\n# See the streaming section for more information on this.\nmodel = ChatOpenAI(temperature=0, streaming=True)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -201,9 +162,7 @@
|
||||
"id": "cd3cbae5-d92c-4559-a4aa-44721b80d107",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model = model.bind_tools(tools)"
|
||||
]
|
||||
"source": ["model = model.bind_tools(tools)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -229,16 +188,7 @@
|
||||
"id": "ea793afa-2eab-4901-910d-6eed90cd6564",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
" messages: Annotated[Sequence[BaseMessage], operator.add]"
|
||||
]
|
||||
"source": ["import operator\nfrom typing import Annotated, Sequence, TypedDict\n\nfrom langchain_core.messages import BaseMessage\n\n\nclass AgentState(TypedDict):\n messages: Annotated[Sequence[BaseMessage], operator.add]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -273,11 +223,7 @@
|
||||
"id": "03308b6b-de72-4cdc-b6c6-47e654df340e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolInvocation"
|
||||
]
|
||||
"source": ["from langchain_core.messages import ToolMessage\n\nfrom langgraph.prebuilt import ToolInvocation"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -295,22 +241,7 @@
|
||||
"id": "55e088b1-f3c8-4798-9ca8-5b0be961b49a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # If there is no function call, then we finish\n",
|
||||
" if not last_message.tool_calls:\n",
|
||||
" return \"end\"\n",
|
||||
" # Otherwise if there is, we check if it's suppose to return direct\n",
|
||||
" else:\n",
|
||||
" arguments = last_message.tool_calls[0][\"args\"]\n",
|
||||
" if arguments.get(\"return_direct\", False):\n",
|
||||
" return \"final\"\n",
|
||||
" else:\n",
|
||||
" return \"continue\""
|
||||
]
|
||||
"source": ["# Define the function that determines whether to continue or not\ndef should_continue(state):\n messages = state[\"messages\"]\n last_message = messages[-1]\n # If there is no function call, then we finish\n if not last_message.tool_calls:\n return \"end\"\n # Otherwise if there is, we check if it's suppose to return direct\n else:\n arguments = last_message.tool_calls[0][\"args\"]\n if arguments.get(\"return_direct\", False):\n return \"final\"\n else:\n return \"continue\""]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -318,14 +249,7 @@
|
||||
"id": "2b45da72-1afa-4cd7-9b7f-49a7c99cdb8a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Define the function that calls the model\n",
|
||||
"def call_model(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" response = model.invoke(messages)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [response]}"
|
||||
]
|
||||
"source": ["# Define the function that calls the model\ndef call_model(state):\n messages = state[\"messages\"]\n response = model.invoke(messages)\n # We return a list, because this will get added to the existing list\n return {\"messages\": [response]}"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -343,33 +267,7 @@
|
||||
"id": "dd876f5d-88d6-4f93-b1d0-f2f0b6f4d991",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Define the function to execute tools\n",
|
||||
"def call_tool(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" # Based on the continue condition\n",
|
||||
" # we know the last message involves a function call\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # We construct an ToolInvocation from the function_call\n",
|
||||
" tool_call = last_message.tool_calls[0]\n",
|
||||
" tool_name = tool_call[\"name\"]\n",
|
||||
" arguments = tool_call[\"args\"]\n",
|
||||
" if tool_name == \"tavily_search_results_json\":\n",
|
||||
" if \"return_direct\" in arguments:\n",
|
||||
" del arguments[\"return_direct\"]\n",
|
||||
" action = ToolInvocation(\n",
|
||||
" tool=tool_name,\n",
|
||||
" tool_input=arguments,\n",
|
||||
" )\n",
|
||||
" # We call the tool_executor and get back a response\n",
|
||||
" response = tool_executor.invoke(action)\n",
|
||||
" # We use the response to create a ToolMessage\n",
|
||||
" tool_message = ToolMessage(\n",
|
||||
" content=str(response), name=action.tool, tool_call_id=tool_call[\"id\"]\n",
|
||||
" )\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [tool_message]}"
|
||||
]
|
||||
"source": ["# Define the function to execute tools\ndef call_tool(state):\n messages = state[\"messages\"]\n # Based on the continue condition\n # we know the last message involves a function call\n last_message = messages[-1]\n # We construct an ToolInvocation from the function_call\n tool_call = last_message.tool_calls[0]\n tool_name = tool_call[\"name\"]\n arguments = tool_call[\"args\"]\n if tool_name == \"tavily_search_results_json\":\n if \"return_direct\" in arguments:\n del arguments[\"return_direct\"]\n action = ToolInvocation(\n tool=tool_name,\n tool_input=arguments,\n )\n # We call the tool_executor and get back a response\n response = tool_executor.invoke(action)\n # We use the response to create a ToolMessage\n tool_message = ToolMessage(\n content=str(response), name=action.tool, tool_call_id=tool_call[\"id\"]\n )\n # We return a list, because this will get added to the existing list\n return {\"messages\": [tool_message]}"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -391,54 +289,7 @@
|
||||
"id": "813ae66c-3b58-4283-a02a-36da72a2ab90",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
"\n",
|
||||
"# Define the two nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", call_model)\n",
|
||||
"workflow.add_node(\"action\", call_tool)\n",
|
||||
"workflow.add_node(\"final\", call_tool)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.set_entry_point(\"agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # Final call\n",
|
||||
" \"final\": \"final\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"workflow.add_edge(\"final\", END)\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"app = workflow.compile()"
|
||||
]
|
||||
"source": ["from langgraph.graph import END, StateGraph, START\n\n# Define a new graph\nworkflow = StateGraph(AgentState)\n\n# Define the two nodes we will cycle between\nworkflow.add_node(\"agent\", call_model)\nworkflow.add_node(\"action\", call_tool)\nworkflow.add_node(\"final\", call_tool)\n\n# Set the entrypoint as `agent`\n# This means that this node is the first one called\nworkflow.add_edge(START, \"agent\")\n\n# We now add a conditional edge\nworkflow.add_conditional_edges(\n # First, we define the start node. We use `agent`.\n # This means these are the edges taken after the `agent` node is called.\n \"agent\",\n # Next, we pass in the function that will determine which node is called next.\n should_continue,\n # Finally we pass in a mapping.\n # The keys are strings, and the values are other nodes.\n # END is a special node marking that the graph should finish.\n # What will happen is we will call `should_continue`, and then the output of that\n # will be matched against the keys in this mapping.\n # Based on which one it matches, that node will then be called.\n {\n # If `tools`, then we call the tool node.\n \"continue\": \"action\",\n # Final call\n \"final\": \"final\",\n # Otherwise we finish.\n \"end\": END,\n },\n)\n\n# We now add a normal edge from `tools` to `agent`.\n# This means that after `tools` is called, `agent` node is called next.\nworkflow.add_edge(\"action\", \"agent\")\nworkflow.add_edge(\"final\", END)\n\n# Finally, we compile it!\n# This compiles it into a LangChain Runnable,\n# meaning you can use it as you would any other runnable\napp = workflow.compile()"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -457,15 +308,7 @@
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from IPython.display import Image, display\n",
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(app.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
"source": ["from IPython.display import Image, display\n\ntry:\n display(Image(app.get_graph(xray=True).draw_mermaid_png()))\nexcept Exception:\n # This requires some extra dependencies and is optional\n pass"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -509,18 +352,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\n",
|
||||
"for output in app.stream(inputs):\n",
|
||||
" # stream() yields dictionaries with output keyed by node name\n",
|
||||
" for key, value in output.items():\n",
|
||||
" print(f\"Output from node '{key}':\")\n",
|
||||
" print(\"---\")\n",
|
||||
" print(value)\n",
|
||||
" print(\"\\n---\\n\")"
|
||||
]
|
||||
"source": ["from langchain_core.messages import HumanMessage\n\ninputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\nfor output in app.stream(inputs):\n # stream() yields dictionaries with output keyed by node name\n for key, value in output.items():\n print(f\"Output from node '{key}':\")\n print(\"---\")\n print(value)\n print(\"\\n---\\n\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -547,24 +379,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"inputs = {\n",
|
||||
" \"messages\": [\n",
|
||||
" HumanMessage(\n",
|
||||
" content=\"what is the weather in sf? return this result directly by setting return_direct = True\"\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
"}\n",
|
||||
"for output in app.stream(inputs):\n",
|
||||
" # stream() yields dictionaries with output keyed by node name\n",
|
||||
" for key, value in output.items():\n",
|
||||
" print(f\"Output from node '{key}':\")\n",
|
||||
" print(\"---\")\n",
|
||||
" print(value)\n",
|
||||
" print(\"\\n---\\n\")"
|
||||
]
|
||||
"source": ["from langchain_core.messages import HumanMessage\n\ninputs = {\n \"messages\": [\n HumanMessage(\n content=\"what is the weather in sf? return this result directly by setting return_direct = True\"\n )\n ]\n}\nfor output in app.stream(inputs):\n # stream() yields dictionaries with output keyed by node name\n for key, value in output.items():\n print(f\"Output from node '{key}':\")\n print(\"---\")\n print(value)\n print(\"\\n---\\n\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -572,7 +387,7 @@
|
||||
"id": "49ccc134-4abe-4982-8ecd-d70fc56a4d2d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
"source": [""]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -30,10 +30,7 @@
|
||||
"id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langgraph langchain_openai tavily-python"
|
||||
]
|
||||
"source": ["%%capture --no-stderr\n%pip install --quiet -U langgraph langchain_openai tavily-python"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -49,18 +46,7 @@
|
||||
"id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
" if not os.environ.get(var):\n",
|
||||
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"_set_env(\"OPENAI_API_KEY\")"
|
||||
]
|
||||
"source": ["import getpass\nimport os\n\n\ndef _set_env(var: str):\n if not os.environ.get(var):\n os.environ[var] = getpass.getpass(f\"{var}: \")\n\n\n_set_env(\"OPENAI_API_KEY\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -76,10 +62,7 @@
|
||||
"id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"_set_env(\"LANGCHAIN_API_KEY\")"
|
||||
]
|
||||
"source": ["os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n_set_env(\"LANGCHAIN_API_KEY\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -99,19 +82,7 @@
|
||||
"id": "d7ef57dd-5d6e-4ad3-9377-a92201c1310e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.tools import tool\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def search(query: str):\n",
|
||||
" \"\"\"Call to surf the web.\"\"\"\n",
|
||||
" # This is a placeholder, but don't tell the LLM that...\n",
|
||||
" return [\"The answer to your question lies within.\"]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [search]"
|
||||
]
|
||||
"source": ["from langchain_core.tools import tool\n\n\n@tool\ndef search(query: str):\n \"\"\"Call to surf the web.\"\"\"\n # This is a placeholder, but don't tell the LLM that...\n return [\"The answer to your question lies within.\"]\n\n\ntools = [search]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -129,11 +100,7 @@
|
||||
"id": "5cf3331e-ccb3-41c8-aeb9-a840a94d41e7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolExecutor\n",
|
||||
"\n",
|
||||
"tool_executor = ToolExecutor(tools)"
|
||||
]
|
||||
"source": ["from langgraph.prebuilt import ToolExecutor\n\ntool_executor = ToolExecutor(tools)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -157,11 +124,7 @@
|
||||
"id": "892b54b9-75f0-4804-9ed0-88b5e5532989",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"model = ChatOpenAI(temperature=0)"
|
||||
]
|
||||
"source": ["from langchain_openai import ChatOpenAI\n\nmodel = ChatOpenAI(temperature=0)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -179,9 +142,7 @@
|
||||
"id": "cd3cbae5-d92c-4559-a4aa-44721b80d107",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model = model.bind_tools(tools)"
|
||||
]
|
||||
"source": ["model = model.bind_tools(tools)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -207,16 +168,7 @@
|
||||
"id": "ea793afa-2eab-4901-910d-6eed90cd6564",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
" messages: Annotated[Sequence[BaseMessage], operator.add]"
|
||||
]
|
||||
"source": ["import operator\nfrom typing import Annotated, Sequence, TypedDict\n\nfrom langchain_core.messages import BaseMessage\n\n\nclass AgentState(TypedDict):\n messages: Annotated[Sequence[BaseMessage], operator.add]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -251,69 +203,7 @@
|
||||
"id": "3b541bb9-900c-40d0-964d-7b5dfee30667",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state: AgentState):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # If there is no function call, then we finish\n",
|
||||
" if not last_message.tool_calls:\n",
|
||||
" return \"end\"\n",
|
||||
" # Otherwise if there is, we continue\n",
|
||||
" else:\n",
|
||||
" return \"continue\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that calls the model\n",
|
||||
"def call_model(state: AgentState):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" response = model.invoke(messages)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [response]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function to execute tools\n",
|
||||
"# We recommend you use ToolNode\n",
|
||||
"# for this, but we are showing the\n",
|
||||
"# manual way here for clarity\n",
|
||||
"def call_tool(state: AgentState):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" # Based on the continue condition\n",
|
||||
" # we know the last message involves a function call\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # We construct an ToolInvocation for each tool call\n",
|
||||
" tool_invocations = []\n",
|
||||
" for tool_call in last_message.tool_calls:\n",
|
||||
" action = ToolInvocation(\n",
|
||||
" tool=tool_call[\"name\"],\n",
|
||||
" tool_input=tool_call[\"args\"],\n",
|
||||
" )\n",
|
||||
" tool_invocations.append(action)\n",
|
||||
"\n",
|
||||
" action = ToolInvocation(\n",
|
||||
" tool=tool_call[\"name\"],\n",
|
||||
" tool_input=tool_call[\"args\"],\n",
|
||||
" )\n",
|
||||
" # We call the tool_executor and get back a response\n",
|
||||
" responses = tool_executor.batch(tool_invocations, return_exceptions=True)\n",
|
||||
" # We use the response to create tool messages\n",
|
||||
" tool_messages = [\n",
|
||||
" ToolMessage(\n",
|
||||
" content=str(response),\n",
|
||||
" name=tc[\"name\"],\n",
|
||||
" tool_call_id=tc[\"id\"],\n",
|
||||
" )\n",
|
||||
" for tc, response in zip(last_message.tool_calls, responses)\n",
|
||||
" ]\n",
|
||||
"\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": tool_messages}"
|
||||
]
|
||||
"source": ["from langchain_core.messages import ToolMessage\n\nfrom langgraph.prebuilt import ToolInvocation\n\n\n# Define the function that determines whether to continue or not\ndef should_continue(state: AgentState):\n messages = state[\"messages\"]\n last_message = messages[-1]\n # If there is no function call, then we finish\n if not last_message.tool_calls:\n return \"end\"\n # Otherwise if there is, we continue\n else:\n return \"continue\"\n\n\n# Define the function that calls the model\ndef call_model(state: AgentState):\n messages = state[\"messages\"]\n response = model.invoke(messages)\n # We return a list, because this will get added to the existing list\n return {\"messages\": [response]}\n\n\n# Define the function to execute tools\n# We recommend you use ToolNode\n# for this, but we are showing the\n# manual way here for clarity\ndef call_tool(state: AgentState):\n messages = state[\"messages\"]\n # Based on the continue condition\n # we know the last message involves a function call\n last_message = messages[-1]\n # We construct an ToolInvocation for each tool call\n tool_invocations = []\n for tool_call in last_message.tool_calls:\n action = ToolInvocation(\n tool=tool_call[\"name\"],\n tool_input=tool_call[\"args\"],\n )\n tool_invocations.append(action)\n\n action = ToolInvocation(\n tool=tool_call[\"name\"],\n tool_input=tool_call[\"args\"],\n )\n # We call the tool_executor and get back a response\n responses = tool_executor.batch(tool_invocations, return_exceptions=True)\n # We use the response to create tool messages\n tool_messages = [\n ToolMessage(\n content=str(response),\n name=tc[\"name\"],\n tool_call_id=tc[\"id\"],\n )\n for tc, response in zip(last_message.tool_calls, responses)\n ]\n\n # We return a list, because this will get added to the existing list\n return {\"messages\": tool_messages}"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -331,30 +221,7 @@
|
||||
"id": "1bfd2b22-292a-4f4d-91a0-46bb704f5e38",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# This is the new first - the first call of the model we want to explicitly hard-code some action\n",
|
||||
"from langchain_core.messages import AIMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def first_model(state: AgentState):\n",
|
||||
" human_input = state[\"messages\"][-1].content\n",
|
||||
" return {\n",
|
||||
" \"messages\": [\n",
|
||||
" AIMessage(\n",
|
||||
" content=\"\",\n",
|
||||
" tool_calls=[\n",
|
||||
" {\n",
|
||||
" \"name\": \"tavily_search_results_json\",\n",
|
||||
" \"args\": {\n",
|
||||
" \"query\": human_input,\n",
|
||||
" },\n",
|
||||
" \"id\": \"tool_abcd123\",\n",
|
||||
" }\n",
|
||||
" ],\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
" }"
|
||||
]
|
||||
"source": ["# This is the new first - the first call of the model we want to explicitly hard-code some action\nfrom langchain_core.messages import AIMessage\n\n\ndef first_model(state: AgentState):\n human_input = state[\"messages\"][-1].content\n return {\n \"messages\": [\n AIMessage(\n content=\"\",\n tool_calls=[\n {\n \"name\": \"tavily_search_results_json\",\n \"args\": {\n \"query\": human_input,\n },\n \"id\": \"tool_abcd123\",\n }\n ],\n )\n ]\n }"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -376,56 +243,7 @@
|
||||
"id": "813ae66c-3b58-4283-a02a-36da72a2ab90",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
"\n",
|
||||
"# Define the new entrypoint\n",
|
||||
"workflow.add_node(\"first_agent\", first_model)\n",
|
||||
"\n",
|
||||
"# Define the two nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", call_model)\n",
|
||||
"workflow.add_node(\"action\", call_tool)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.set_entry_point(\"first_agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"\n",
|
||||
"# After we call the first agent, we know we want to go to action\n",
|
||||
"workflow.add_edge(\"first_agent\", \"action\")\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"app = workflow.compile()"
|
||||
]
|
||||
"source": ["from langgraph.graph import END, StateGraph, START\n\n# Define a new graph\nworkflow = StateGraph(AgentState)\n\n# Define the new entrypoint\nworkflow.add_node(\"first_agent\", first_model)\n\n# Define the two nodes we will cycle between\nworkflow.add_node(\"agent\", call_model)\nworkflow.add_node(\"action\", call_tool)\n\n# Set the entrypoint as `agent`\n# This means that this node is the first one called\nworkflow.add_edge(START, \"first_agent\")\n\n# We now add a conditional edge\nworkflow.add_conditional_edges(\n # First, we define the start node. We use `agent`.\n # This means these are the edges taken after the `agent` node is called.\n \"agent\",\n # Next, we pass in the function that will determine which node is called next.\n should_continue,\n # Finally we pass in a mapping.\n # The keys are strings, and the values are other nodes.\n # END is a special node marking that the graph should finish.\n # What will happen is we will call `should_continue`, and then the output of that\n # will be matched against the keys in this mapping.\n # Based on which one it matches, that node will then be called.\n {\n # If `tools`, then we call the tool node.\n \"continue\": \"action\",\n # Otherwise we finish.\n \"end\": END,\n },\n)\n\n# We now add a normal edge from `tools` to `agent`.\n# This means that after `tools` is called, `agent` node is called next.\nworkflow.add_edge(\"action\", \"agent\")\n\n# After we call the first agent, we know we want to go to action\nworkflow.add_edge(\"first_agent\", \"action\")\n\n# Finally, we compile it!\n# This compiles it into a LangChain Runnable,\n# meaning you can use it as you would any other runnable\napp = workflow.compile()"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -444,11 +262,7 @@
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from IPython.display import Image, display\n",
|
||||
"\n",
|
||||
"display(Image(app.get_graph(xray=True).draw_mermaid_png()))"
|
||||
]
|
||||
"source": ["from IPython.display import Image, display\n\ndisplay(Image(app.get_graph(xray=True).draw_mermaid_png()))"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -670,17 +484,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\n",
|
||||
"for output in app.stream(inputs, stream_mode=\"values\"):\n",
|
||||
" # stream() yields dictionaries with output keyed by node name\n",
|
||||
" messages = output[\"messages\"]\n",
|
||||
" for message in messages:\n",
|
||||
" message.pretty_print()\n",
|
||||
" print(\"\\n---\\n\")"
|
||||
]
|
||||
"source": ["from langchain_core.messages import HumanMessage\n\ninputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\nfor output in app.stream(inputs, stream_mode=\"values\"):\n # stream() yields dictionaries with output keyed by node name\n messages = output[\"messages\"]\n for message in messages:\n message.pretty_print()\n print(\"\\n---\\n\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -688,7 +492,7 @@
|
||||
"id": "08ae8246-11d5-40e1-8567-361e5bef8917",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
"source": [""]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -30,10 +30,7 @@
|
||||
"id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langgraph langchain langchain_openai tavily-python"
|
||||
]
|
||||
"source": ["%%capture --no-stderr\n%pip install --quiet -U langgraph langchain langchain_openai tavily-python"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -49,13 +46,7 @@
|
||||
"id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
]
|
||||
"source": ["import getpass\nimport os\n\nos.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\nos.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -71,10 +62,7 @@
|
||||
"id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"
|
||||
]
|
||||
"source": ["os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\nos.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -94,11 +82,7 @@
|
||||
"id": "d7ef57dd-5d6e-4ad3-9377-a92201c1310e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"\n",
|
||||
"tools = [TavilySearchResults(max_results=1)]"
|
||||
]
|
||||
"source": ["from langchain_community.tools.tavily_search import TavilySearchResults\n\ntools = [TavilySearchResults(max_results=1)]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -116,11 +100,7 @@
|
||||
"id": "5cf3331e-ccb3-41c8-aeb9-a840a94d41e7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolExecutor\n",
|
||||
"\n",
|
||||
"tool_executor = ToolExecutor(tools)"
|
||||
]
|
||||
"source": ["from langgraph.prebuilt import ToolExecutor\n\ntool_executor = ToolExecutor(tools)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -144,13 +124,7 @@
|
||||
"id": "892b54b9-75f0-4804-9ed0-88b5e5532989",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"# We will set streaming=True so that we can stream tokens\n",
|
||||
"# See the streaming section for more information on this.\n",
|
||||
"model = ChatOpenAI(temperature=0, streaming=True)"
|
||||
]
|
||||
"source": ["from langchain_openai import ChatOpenAI\n\n# We will set streaming=True so that we can stream tokens\n# See the streaming section for more information on this.\nmodel = ChatOpenAI(temperature=0, streaming=True)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -168,9 +142,7 @@
|
||||
"id": "cd3cbae5-d92c-4559-a4aa-44721b80d107",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model = model.bind_tools(tools)"
|
||||
]
|
||||
"source": ["model = model.bind_tools(tools)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -196,16 +168,7 @@
|
||||
"id": "ea793afa-2eab-4901-910d-6eed90cd6564",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
" messages: Annotated[Sequence[BaseMessage], operator.add]"
|
||||
]
|
||||
"source": ["import operator\nfrom typing import Annotated, Sequence, TypedDict\n\nfrom langchain_core.messages import BaseMessage\n\n\nclass AgentState(TypedDict):\n messages: Annotated[Sequence[BaseMessage], operator.add]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -240,23 +203,7 @@
|
||||
"id": "e718a9c5-6596-457f-ac25-a25d8cb8c259",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # If there is no function call, then we finish\n",
|
||||
" if not last_message.tool_calls:\n",
|
||||
" return \"end\"\n",
|
||||
" # Otherwise if there is, we continue\n",
|
||||
" else:\n",
|
||||
" return \"continue\""
|
||||
]
|
||||
"source": ["from langchain_core.messages import ToolMessage\n\nfrom langgraph.prebuilt import ToolInvocation\n\n\n# Define the function that determines whether to continue or not\ndef should_continue(state):\n messages = state[\"messages\"]\n last_message = messages[-1]\n # If there is no function call, then we finish\n if not last_message.tool_calls:\n return \"end\"\n # Otherwise if there is, we continue\n else:\n return \"continue\""]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -274,14 +221,7 @@
|
||||
"id": "714e4135-7cb5-4f17-b2ae-46f7e98bde61",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Define the function that calls the model\n",
|
||||
"def call_model(state):\n",
|
||||
" messages = state[\"messages\"][-5:]\n",
|
||||
" response = model.invoke(messages)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [response]}"
|
||||
]
|
||||
"source": ["# Define the function that calls the model\ndef call_model(state):\n messages = state[\"messages\"][-5:]\n response = model.invoke(messages)\n # We return a list, because this will get added to the existing list\n return {\"messages\": [response]}"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -289,41 +229,7 @@
|
||||
"id": "b3ca9564-63cc-4309-b158-5e8d3e907164",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Define the function to execute tools\n",
|
||||
"def call_tool(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" # Based on the continue condition\n",
|
||||
" # we know the last message involves a function call\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # We construct an ToolInvocation for each tool call\n",
|
||||
" tool_invocations = []\n",
|
||||
" for tool_call in last_message.tool_calls:\n",
|
||||
" action = ToolInvocation(\n",
|
||||
" tool=tool_call[\"name\"],\n",
|
||||
" tool_input=tool_call[\"args\"],\n",
|
||||
" )\n",
|
||||
" tool_invocations.append(action)\n",
|
||||
"\n",
|
||||
" action = ToolInvocation(\n",
|
||||
" tool=tool_call[\"name\"],\n",
|
||||
" tool_input=tool_call[\"args\"],\n",
|
||||
" )\n",
|
||||
" # We call the tool_executor and get back a response\n",
|
||||
" responses = tool_executor.batch(tool_invocations, return_exceptions=True)\n",
|
||||
" # We use the response to create tool messages\n",
|
||||
" tool_messages = [\n",
|
||||
" ToolMessage(\n",
|
||||
" content=str(response),\n",
|
||||
" name=tc[\"name\"],\n",
|
||||
" tool_call_id=tc[\"id\"],\n",
|
||||
" )\n",
|
||||
" for tc, response in zip(last_message.tool_calls, responses)\n",
|
||||
" ]\n",
|
||||
"\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": tool_messages}"
|
||||
]
|
||||
"source": ["# Define the function to execute tools\ndef call_tool(state):\n messages = state[\"messages\"]\n # Based on the continue condition\n # we know the last message involves a function call\n last_message = messages[-1]\n # We construct an ToolInvocation for each tool call\n tool_invocations = []\n for tool_call in last_message.tool_calls:\n action = ToolInvocation(\n tool=tool_call[\"name\"],\n tool_input=tool_call[\"args\"],\n )\n tool_invocations.append(action)\n\n action = ToolInvocation(\n tool=tool_call[\"name\"],\n tool_input=tool_call[\"args\"],\n )\n # We call the tool_executor and get back a response\n responses = tool_executor.batch(tool_invocations, return_exceptions=True)\n # We use the response to create tool messages\n tool_messages = [\n ToolMessage(\n content=str(response),\n name=tc[\"name\"],\n tool_call_id=tc[\"id\"],\n )\n for tc, response in zip(last_message.tool_calls, responses)\n ]\n\n # We return a list, because this will get added to the existing list\n return {\"messages\": tool_messages}"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -341,50 +247,7 @@
|
||||
"id": "813ae66c-3b58-4283-a02a-36da72a2ab90",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
"\n",
|
||||
"# Define the two nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", call_model)\n",
|
||||
"workflow.add_node(\"action\", call_tool)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.set_entry_point(\"agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"app = workflow.compile()"
|
||||
]
|
||||
"source": ["from langgraph.graph import END, StateGraph, START\n\n# Define a new graph\nworkflow = StateGraph(AgentState)\n\n# Define the two nodes we will cycle between\nworkflow.add_node(\"agent\", call_model)\nworkflow.add_node(\"action\", call_tool)\n\n# Set the entrypoint as `agent`\n# This means that this node is the first one called\nworkflow.add_edge(START, \"agent\")\n\n# We now add a conditional edge\nworkflow.add_conditional_edges(\n # First, we define the start node. We use `agent`.\n # This means these are the edges taken after the `agent` node is called.\n \"agent\",\n # Next, we pass in the function that will determine which node is called next.\n should_continue,\n # Finally we pass in a mapping.\n # The keys are strings, and the values are other nodes.\n # END is a special node marking that the graph should finish.\n # What will happen is we will call `should_continue`, and then the output of that\n # will be matched against the keys in this mapping.\n # Based on which one it matches, that node will then be called.\n {\n # If `tools`, then we call the tool node.\n \"continue\": \"action\",\n # Otherwise we finish.\n \"end\": END,\n },\n)\n\n# We now add a normal edge from `tools` to `agent`.\n# This means that after `tools` is called, `agent` node is called next.\nworkflow.add_edge(\"action\", \"agent\")\n\n# Finally, we compile it!\n# This compiles it into a LangChain Runnable,\n# meaning you can use it as you would any other runnable\napp = workflow.compile()"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -403,15 +266,7 @@
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from IPython.display import Image, display\n",
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(app.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
"source": ["from IPython.display import Image, display\n\ntry:\n display(Image(app.get_graph(xray=True).draw_mermaid_png()))\nexcept Exception:\n # This requires some extra dependencies and is optional\n pass"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -455,18 +310,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\n",
|
||||
"for output in app.stream(inputs):\n",
|
||||
" # stream() yields dictionaries with output keyed by node name\n",
|
||||
" for key, value in output.items():\n",
|
||||
" print(f\"Output from node '{key}':\")\n",
|
||||
" print(\"---\")\n",
|
||||
" print(value)\n",
|
||||
" print(\"\\n---\\n\")"
|
||||
]
|
||||
"source": ["from langchain_core.messages import HumanMessage\n\ninputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\nfor output in app.stream(inputs):\n # stream() yields dictionaries with output keyed by node name\n for key, value in output.items():\n print(f\"Output from node '{key}':\")\n print(\"---\")\n print(value)\n print(\"\\n---\\n\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -474,7 +318,7 @@
|
||||
"id": "08ae8246-11d5-40e1-8567-361e5bef8917",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
"source": [""]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -27,10 +27,7 @@
|
||||
"id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langgraph langchain langchain_openai tavily-python"
|
||||
]
|
||||
"source": ["%%capture --no-stderr\n%pip install --quiet -U langgraph langchain langchain_openai tavily-python"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -46,13 +43,7 @@
|
||||
"id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
]
|
||||
"source": ["import getpass\nimport os\n\nos.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\nos.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -68,10 +59,7 @@
|
||||
"id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"
|
||||
]
|
||||
"source": ["os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\nos.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -95,11 +83,7 @@
|
||||
"id": "d7ef57dd-5d6e-4ad3-9377-a92201c1310e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"\n",
|
||||
"tools = [TavilySearchResults(max_results=1)]"
|
||||
]
|
||||
"source": ["from langchain_community.tools.tavily_search import TavilySearchResults\n\ntools = [TavilySearchResults(max_results=1)]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -123,11 +107,7 @@
|
||||
"id": "892b54b9-75f0-4804-9ed0-88b5e5532989",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"model = ChatOpenAI(temperature=0)"
|
||||
]
|
||||
"source": ["from langchain_openai import ChatOpenAI\n\nmodel = ChatOpenAI(temperature=0)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -145,9 +125,7 @@
|
||||
"id": "cd3cbae5-d92c-4559-a4aa-44721b80d107",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model = model.bind_tools(tools)"
|
||||
]
|
||||
"source": ["model = model.bind_tools(tools)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -173,16 +151,7 @@
|
||||
"id": "ea793afa-2eab-4901-910d-6eed90cd6564",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
" messages: Annotated[Sequence[BaseMessage], operator.add]"
|
||||
]
|
||||
"source": ["import operator\nfrom typing import Annotated, Sequence, TypedDict\n\nfrom langchain_core.messages import BaseMessage\n\n\nclass AgentState(TypedDict):\n messages: Annotated[Sequence[BaseMessage], operator.add]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -217,33 +186,7 @@
|
||||
"id": "3b541bb9-900c-40d0-964d-7b5dfee30667",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # If there are no tool calls, then we finish\n",
|
||||
" if not last_message.tool_calls:\n",
|
||||
" return \"end\"\n",
|
||||
" # Otherwise if there is, we continue\n",
|
||||
" else:\n",
|
||||
" return \"continue\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that calls the model\n",
|
||||
"def call_model(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" response = model.invoke(messages)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [response]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function to execute tools\n",
|
||||
"tool_node = ToolNode(tools)"
|
||||
]
|
||||
"source": ["from langgraph.prebuilt import ToolNode\n\n\n# Define the function that determines whether to continue or not\ndef should_continue(state):\n messages = state[\"messages\"]\n last_message = messages[-1]\n # If there are no tool calls, then we finish\n if not last_message.tool_calls:\n return \"end\"\n # Otherwise if there is, we continue\n else:\n return \"continue\"\n\n\n# Define the function that calls the model\ndef call_model(state):\n messages = state[\"messages\"]\n response = model.invoke(messages)\n # We return a list, because this will get added to the existing list\n return {\"messages\": [response]}\n\n\n# Define the function to execute tools\ntool_node = ToolNode(tools)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -261,50 +204,7 @@
|
||||
"id": "813ae66c-3b58-4283-a02a-36da72a2ab90",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
"\n",
|
||||
"# Define the two nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", call_model)\n",
|
||||
"workflow.add_node(\"action\", tool_node)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.set_entry_point(\"agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"app = workflow.compile()"
|
||||
]
|
||||
"source": ["from langgraph.graph import END, StateGraph, START\n\n# Define a new graph\nworkflow = StateGraph(AgentState)\n\n# Define the two nodes we will cycle between\nworkflow.add_node(\"agent\", call_model)\nworkflow.add_node(\"action\", tool_node)\n\n# Set the entrypoint as `agent`\n# This means that this node is the first one called\nworkflow.add_edge(START, \"agent\")\n\n# We now add a conditional edge\nworkflow.add_conditional_edges(\n # First, we define the start node. We use `agent`.\n # This means these are the edges taken after the `agent` node is called.\n \"agent\",\n # Next, we pass in the function that will determine which node is called next.\n should_continue,\n # Finally we pass in a mapping.\n # The keys are strings, and the values are other nodes.\n # END is a special node marking that the graph should finish.\n # What will happen is we will call `should_continue`, and then the output of that\n # will be matched against the keys in this mapping.\n # Based on which one it matches, that node will then be called.\n {\n # If `tools`, then we call the tool node.\n \"continue\": \"action\",\n # Otherwise we finish.\n \"end\": END,\n },\n)\n\n# We now add a normal edge from `tools` to `agent`.\n# This means that after `tools` is called, `agent` node is called next.\nworkflow.add_edge(\"action\", \"agent\")\n\n# Finally, we compile it!\n# This compiles it into a LangChain Runnable,\n# meaning you can use it as you would any other runnable\napp = workflow.compile()"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -337,12 +237,7 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\n",
|
||||
"app.invoke(inputs)"
|
||||
]
|
||||
"source": ["from langchain_core.messages import HumanMessage\n\ninputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\napp.invoke(inputs)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -392,16 +287,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\n",
|
||||
"for output in app.stream(inputs):\n",
|
||||
" # stream() yields dictionaries with output keyed by node name\n",
|
||||
" for key, value in output.items():\n",
|
||||
" print(f\"Output from node '{key}':\")\n",
|
||||
" print(\"---\")\n",
|
||||
" print(value)\n",
|
||||
" print(\"\\n---\\n\")"
|
||||
]
|
||||
"source": ["inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\nfor output in app.stream(inputs):\n # stream() yields dictionaries with output keyed by node name\n for key, value in output.items():\n print(f\"Output from node '{key}':\")\n print(\"---\")\n print(value)\n print(\"\\n---\\n\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -496,21 +382,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf?\")]}\n",
|
||||
"\n",
|
||||
"async for output in app.astream_log(inputs, include_types=[\"llm\"]):\n",
|
||||
" # astream_log() yields the requested logs (here LLMs) in JSONPatch format\n",
|
||||
" for op in output.ops:\n",
|
||||
" if op[\"path\"] == \"/streamed_output/-\":\n",
|
||||
" # this is the output from .stream()\n",
|
||||
" ...\n",
|
||||
" elif op[\"path\"].startswith(\"/logs/\") and op[\"path\"].endswith(\n",
|
||||
" \"/streamed_output/-\"\n",
|
||||
" ):\n",
|
||||
" # because we chose to only include LLMs, these are LLM tokens\n",
|
||||
" print(op[\"value\"])"
|
||||
]
|
||||
"source": ["inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf?\")]}\n\nasync for output in app.astream_log(inputs, include_types=[\"llm\"]):\n # astream_log() yields the requested logs (here LLMs) in JSONPatch format\n for op in output.ops:\n if op[\"path\"] == \"/streamed_output/-\":\n # this is the output from .stream()\n ...\n elif op[\"path\"].startswith(\"/logs/\") and op[\"path\"].endswith(\n \"/streamed_output/-\"\n ):\n # because we chose to only include LLMs, these are LLM tokens\n print(op[\"value\"])"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -518,7 +390,7 @@
|
||||
"id": "08ae8246-11d5-40e1-8567-361e5bef8917",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
"source": [""]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -38,22 +38,7 @@
|
||||
"id": "de1db3c1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langgraph.graph.message import add_messages\n",
|
||||
"\n",
|
||||
"# Add messages essentially does this with more\n",
|
||||
"# robust handling\n",
|
||||
"# def add_messages(left: list, right: list):\n",
|
||||
"# return left + right\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class State(TypedDict):\n",
|
||||
" messages: Annotated[list, add_messages]"
|
||||
]
|
||||
"source": ["from typing import Annotated\n\nfrom typing_extensions import TypedDict\n\nfrom langgraph.graph.message import add_messages\n\n# Add messages essentially does this with more\n# robust handling\n# def add_messages(left: list, right: list):\n# return left + right\n\n\nclass State(TypedDict):\n messages: Annotated[list, add_messages]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -73,19 +58,7 @@
|
||||
"id": "23a2ca43",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.tools import tool\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def search(query: str):\n",
|
||||
" \"\"\"Call to surf the web.\"\"\"\n",
|
||||
" # This is a placeholder, but don't tell the LLM that...\n",
|
||||
" return [\"The answer to your question lies within.\"]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [search]"
|
||||
]
|
||||
"source": ["from langchain_core.tools import tool\n\n\n@tool\ndef search(query: str):\n \"\"\"Call to surf the web.\"\"\"\n # This is a placeholder, but don't tell the LLM that...\n return [\"The answer to your question lies within.\"]\n\n\ntools = [search]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -102,11 +75,7 @@
|
||||
"id": "979512e4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"\n",
|
||||
"tool_node = ToolNode(tools)"
|
||||
]
|
||||
"source": ["from langgraph.prebuilt import ToolNode\n\ntool_node = ToolNode(tools)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -130,11 +99,7 @@
|
||||
"id": "1c8132c5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"\n",
|
||||
"model = ChatAnthropic(model=\"claude-3-haiku-20240307\")"
|
||||
]
|
||||
"source": ["from langchain_anthropic import ChatAnthropic\n\nmodel = ChatAnthropic(model=\"claude-3-haiku-20240307\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -152,9 +117,7 @@
|
||||
"id": "055d84bf",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model = model.bind_tools(tools)"
|
||||
]
|
||||
"source": ["model = model.bind_tools(tools)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -172,10 +135,7 @@
|
||||
"id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langgraph langchain langchain_openai tavily-python"
|
||||
]
|
||||
"source": ["%%capture --no-stderr\n%pip install --quiet -U langgraph langchain langchain_openai tavily-python"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -191,13 +151,7 @@
|
||||
"id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
|
||||
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
|
||||
]
|
||||
"source": ["import getpass\nimport os\n\nos.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\nos.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -213,10 +167,7 @@
|
||||
"id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"
|
||||
]
|
||||
"source": ["os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\nos.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -236,11 +187,7 @@
|
||||
"id": "d7ef57dd-5d6e-4ad3-9377-a92201c1310e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"\n",
|
||||
"tools = [TavilySearchResults(max_results=1)]"
|
||||
]
|
||||
"source": ["from langchain_community.tools.tavily_search import TavilySearchResults\n\ntools = [TavilySearchResults(max_results=1)]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -258,11 +205,7 @@
|
||||
"id": "5cf3331e-ccb3-41c8-aeb9-a840a94d41e7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolExecutor\n",
|
||||
"\n",
|
||||
"tool_executor = ToolExecutor(tools)"
|
||||
]
|
||||
"source": ["from langgraph.prebuilt import ToolExecutor\n\ntool_executor = ToolExecutor(tools)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -286,13 +229,7 @@
|
||||
"id": "892b54b9-75f0-4804-9ed0-88b5e5532989",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"# We will set streaming=True so that we can stream tokens\n",
|
||||
"# See the streaming section for more information on this.\n",
|
||||
"model = ChatOpenAI(temperature=0, streaming=True)"
|
||||
]
|
||||
"source": ["from langchain_openai import ChatOpenAI\n\n# We will set streaming=True so that we can stream tokens\n# See the streaming section for more information on this.\nmodel = ChatOpenAI(temperature=0, streaming=True)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -315,19 +252,7 @@
|
||||
"id": "cd3cbae5-d92c-4559-a4aa-44721b80d107",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class Response(BaseModel):\n",
|
||||
" \"\"\"Final response to the user\"\"\"\n",
|
||||
"\n",
|
||||
" temperature: float = Field(description=\"the temperature\")\n",
|
||||
" other_notes: str = Field(description=\"any other notes about the weather\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"model = model.bind_tools(tools + [Response])"
|
||||
]
|
||||
"source": ["from langchain_core.pydantic_v1 import BaseModel, Field\n\n\nclass Response(BaseModel):\n \"\"\"Final response to the user\"\"\"\n\n temperature: float = Field(description=\"the temperature\")\n other_notes: str = Field(description=\"any other notes about the weather\")\n\n\nmodel = model.bind_tools(tools + [Response])"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -353,16 +278,7 @@
|
||||
"id": "ea793afa-2eab-4901-910d-6eed90cd6564",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
" messages: Annotated[Sequence[BaseMessage], operator.add]"
|
||||
]
|
||||
"source": ["import operator\nfrom typing import Annotated, Sequence, TypedDict\n\nfrom langchain_core.messages import BaseMessage\n\n\nclass AgentState(TypedDict):\n messages: Annotated[Sequence[BaseMessage], operator.add]"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -401,70 +317,7 @@
|
||||
"id": "3b541bb9-900c-40d0-964d-7b5dfee30667",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Literal\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state) -> Literal[\"continue\", \"end\"]:\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # If there is no function call, then we finish\n",
|
||||
" if not last_message.tool_calls:\n",
|
||||
" return \"end\"\n",
|
||||
" # Otherwise if there is, we need to check what type of function call it is\n",
|
||||
" if last_message.tool_calls[0][\"name\"] == \"Response\":\n",
|
||||
" return \"end\"\n",
|
||||
" # Otherwise we continue\n",
|
||||
" return \"continue\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that calls the model\n",
|
||||
"def call_model(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" response = model.invoke(messages)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [response]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function to execute tools\n",
|
||||
"def call_tool(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" # Based on the continue condition\n",
|
||||
" # we know the last message involves a function call\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # We construct an ToolInvocation for each tool call\n",
|
||||
" tool_invocations = []\n",
|
||||
" for tool_call in last_message.tool_calls:\n",
|
||||
" action = ToolInvocation(\n",
|
||||
" tool=tool_call[\"name\"],\n",
|
||||
" tool_input=tool_call[\"args\"],\n",
|
||||
" )\n",
|
||||
" tool_invocations.append(action)\n",
|
||||
"\n",
|
||||
" action = ToolInvocation(\n",
|
||||
" tool=tool_call[\"name\"],\n",
|
||||
" tool_input=tool_call[\"args\"],\n",
|
||||
" )\n",
|
||||
" # We call the tool_executor and get back a response\n",
|
||||
" responses = tool_executor.batch(tool_invocations, return_exceptions=True)\n",
|
||||
" # We use the response to create tool messages\n",
|
||||
" tool_messages = [\n",
|
||||
" ToolMessage(\n",
|
||||
" content=str(response),\n",
|
||||
" name=tc[\"name\"],\n",
|
||||
" tool_call_id=tc[\"id\"],\n",
|
||||
" )\n",
|
||||
" for tc, response in zip(last_message.tool_calls, responses)\n",
|
||||
" ]\n",
|
||||
"\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": tool_messages}"
|
||||
]
|
||||
"source": ["from typing import Literal\n\nfrom langchain_core.messages import ToolMessage\n\nfrom langgraph.prebuilt import ToolInvocation\n\n\n# Define the function that determines whether to continue or not\ndef should_continue(state) -> Literal[\"continue\", \"end\"]:\n messages = state[\"messages\"]\n last_message = messages[-1]\n # If there is no function call, then we finish\n if not last_message.tool_calls:\n return \"end\"\n # Otherwise if there is, we need to check what type of function call it is\n if last_message.tool_calls[0][\"name\"] == \"Response\":\n return \"end\"\n # Otherwise we continue\n return \"continue\"\n\n\n# Define the function that calls the model\ndef call_model(state):\n messages = state[\"messages\"]\n response = model.invoke(messages)\n # We return a list, because this will get added to the existing list\n return {\"messages\": [response]}\n\n\n# Define the function to execute tools\ndef call_tool(state):\n messages = state[\"messages\"]\n # Based on the continue condition\n # we know the last message involves a function call\n last_message = messages[-1]\n # We construct an ToolInvocation for each tool call\n tool_invocations = []\n for tool_call in last_message.tool_calls:\n action = ToolInvocation(\n tool=tool_call[\"name\"],\n tool_input=tool_call[\"args\"],\n )\n tool_invocations.append(action)\n\n action = ToolInvocation(\n tool=tool_call[\"name\"],\n tool_input=tool_call[\"args\"],\n )\n # We call the tool_executor and get back a response\n responses = tool_executor.batch(tool_invocations, return_exceptions=True)\n # We use the response to create tool messages\n tool_messages = [\n ToolMessage(\n content=str(response),\n name=tc[\"name\"],\n tool_call_id=tc[\"id\"],\n )\n for tc, response in zip(last_message.tool_calls, responses)\n ]\n\n # We return a list, because this will get added to the existing list\n return {\"messages\": tool_messages}"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -482,50 +335,7 @@
|
||||
"id": "813ae66c-3b58-4283-a02a-36da72a2ab90",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
"\n",
|
||||
"# Define the two nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", call_model)\n",
|
||||
"workflow.add_node(\"action\", call_tool)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.set_entry_point(\"agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"app = workflow.compile()"
|
||||
]
|
||||
"source": ["from langgraph.graph import END, StateGraph, START\n\n# Define a new graph\nworkflow = StateGraph(AgentState)\n\n# Define the two nodes we will cycle between\nworkflow.add_node(\"agent\", call_model)\nworkflow.add_node(\"action\", call_tool)\n\n# Set the entrypoint as `agent`\n# This means that this node is the first one called\nworkflow.add_edge(START, \"agent\")\n\n# We now add a conditional edge\nworkflow.add_conditional_edges(\n # First, we define the start node. We use `agent`.\n # This means these are the edges taken after the `agent` node is called.\n \"agent\",\n # Next, we pass in the function that will determine which node is called next.\n should_continue,\n # Finally we pass in a mapping.\n # The keys are strings, and the values are other nodes.\n # END is a special node marking that the graph should finish.\n # What will happen is we will call `should_continue`, and then the output of that\n # will be matched against the keys in this mapping.\n # Based on which one it matches, that node will then be called.\n {\n # If `tools`, then we call the tool node.\n \"continue\": \"action\",\n # Otherwise we finish.\n \"end\": END,\n },\n)\n\n# We now add a normal edge from `tools` to `agent`.\n# This means that after `tools` is called, `agent` node is called next.\nworkflow.add_edge(\"action\", \"agent\")\n\n# Finally, we compile it!\n# This compiles it into a LangChain Runnable,\n# meaning you can use it as you would any other runnable\napp = workflow.compile()"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -544,15 +354,7 @@
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from IPython.display import Image, display\n",
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(app.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
]
|
||||
"source": ["from IPython.display import Image, display\n\ntry:\n display(Image(app.get_graph(xray=True).draw_mermaid_png()))\nexcept Exception:\n # This requires some extra dependencies and is optional\n pass"]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -596,18 +398,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\n",
|
||||
"for output in app.stream(inputs):\n",
|
||||
" # stream() yields dictionaries with output keyed by node name\n",
|
||||
" for key, value in output.items():\n",
|
||||
" print(f\"Output from node '{key}':\")\n",
|
||||
" print(\"---\")\n",
|
||||
" print(value[\"messages\"][-1])\n",
|
||||
" print(\"\\n---\\n\")"
|
||||
]
|
||||
"source": ["from langchain_core.messages import HumanMessage\n\ninputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\nfor output in app.stream(inputs):\n # stream() yields dictionaries with output keyed by node name\n for key, value in output.items():\n print(f\"Output from node '{key}':\")\n print(\"---\")\n print(value[\"messages\"][-1])\n print(\"\\n---\\n\")"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
@@ -615,7 +406,7 @@
|
||||
"id": "eed4360d-2cdf-497b-b03f-8bc51062f780",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
"source": [""]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -228,13 +228,14 @@
|
||||
"from langchain_core.messages import AIMessage\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def chat_bot_node(messages):\n",
|
||||
"def chat_bot_node(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" # Convert from LangChain format to the OpenAI format, which our chatbot function expects.\n",
|
||||
" messages = [convert_message_to_dict(m) for m in messages]\n",
|
||||
" # Call the chat bot\n",
|
||||
" chat_bot_response = my_chat_bot(messages)\n",
|
||||
" # Respond with an AI Message\n",
|
||||
" return AIMessage(content=chat_bot_response[\"content\"])"
|
||||
" return {\"messages\": [AIMessage(content=chat_bot_response[\"content\"])]}"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -262,13 +263,14 @@
|
||||
" return new_messages\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def simulated_user_node(messages):\n",
|
||||
"def simulated_user_node(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" # Swap roles of messages\n",
|
||||
" new_messages = _swap_roles(messages)\n",
|
||||
" # Call the simulated user\n",
|
||||
" response = simulated_user.invoke({\"messages\": new_messages})\n",
|
||||
" # This response is an AI message - we need to flip this to be a human message\n",
|
||||
" return HumanMessage(content=response.content)"
|
||||
" return {\"messages\": [HumanMessage(content=response.content)]}"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -293,7 +295,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def should_continue(messages):\n",
|
||||
"def should_continue(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" if len(messages) > 6:\n",
|
||||
" return \"end\"\n",
|
||||
" elif messages[-1].content == \"FINISHED\":\n",
|
||||
@@ -319,9 +322,17 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import END, MessageGraph\n",
|
||||
"from langgraph.graph import END, StateGraph, START\n",
|
||||
"from langgraph.graph.message import add_messages\n",
|
||||
"from typing import Annotated\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"graph_builder = MessageGraph()\n",
|
||||
"\n",
|
||||
"class State(TypedDict):\n",
|
||||
" messages: Annotated[list, add_messages]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"graph_builder = StateGraph(State)\n",
|
||||
"graph_builder.add_node(\"user\", simulated_user_node)\n",
|
||||
"graph_builder.add_node(\"chat_bot\", chat_bot_node)\n",
|
||||
"# Every response from your chat bot will automatically go to the\n",
|
||||
@@ -338,7 +349,7 @@
|
||||
" },\n",
|
||||
")\n",
|
||||
"# The input will first go to your chat bot\n",
|
||||
"graph_builder.set_entry_point(\"chat_bot\")\n",
|
||||
"graph_builder.add_edge(START, \"chat_bot\")\n",
|
||||
"simulation = graph_builder.compile()"
|
||||
]
|
||||
},
|
||||
@@ -382,20 +393,12 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for chunk in simulation.stream([]):\n",
|
||||
"for chunk in simulation.stream({}):\n",
|
||||
" # Print out all events aside from the final end chunk\n",
|
||||
" if END not in chunk:\n",
|
||||
" print(chunk)\n",
|
||||
" print(\"----\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "dde4f2b5-cfe8-4ff0-99ea-fe2c5fed70c0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 56 KiB |
@@ -359,14 +359,6 @@
|
||||
" evaluation=evaluation,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "26735ed2-766d-4e0a-a185-b2295a0615b8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||