Compare commits

...
Author SHA1 Message Date
Vadym BardaandGitHub da6462e608 langgraph: release 0.2.3 (#1279) 2024-08-08 12:43:24 -04:00
Vadym BardaandGitHub 49bf8f6816 checkpoint-postgres: release 1.0.2 (#1278) 2024-08-08 12:42:06 -04:00
Vadym BardaandGitHub 71ce07d971 checkpoint: update docstrings for checkpoint libraries (#1277) 2024-08-08 16:26:16 +00:00
Vadym BardaandGitHub a16f86b5bd docs: update postgres persistence example (#1276) 2024-08-08 12:01:19 -04:00
Vadym BardaandGitHub 4dc27b98f1 langgraph, checkpoint-postgres: propagate new versions in update_state (#1270)
* langgraph, checkpoint-postgres: propagate new versions in update_state
2024-08-08 11:55:55 -04:00
Isaac FranciscoandGitHub be8476d981 added context (#1242) 2024-08-08 11:24:52 -04:00
Vadym BardaandGitHub cd92f19858 docs (examples): replace SqliteSaver with MemorySaver (#1271) 2024-08-08 10:30:43 -04:00
a2f4d57bf2 langgraph: more checkpointer tests (#1263)
* langgraph: more checkpointer tests

* more tests

* lint

* update tests

---------

Co-authored-by: Nuno Campos <nuno@langchain.dev>
2024-08-08 00:45:46 +00:00
Nuno CamposandGitHub f9fa35ed82 Merge pull request #1267 from langchain-ai/vb/update-empty-channels
checkpoint-postgres: set unset channel values to empty in blobs
2024-08-07 17:40:58 -07:00
Vadym BardaandGitHub 3b87a81c70 Merge branch 'main' into vb/update-empty-channels 2024-08-07 20:18:10 -04:00
Nuno CamposandGitHub 002b260e7a Various small changes (#1268) 2024-08-07 20:17:47 -04:00
vbarda 659c9881cf cleanup 2024-08-07 19:54:45 -04:00
vbarda b535d38393 test 2024-08-07 19:53:41 -04:00
vbarda d33807b5ea checkpoint-postgres: remove unset channel values from blobs 2024-08-07 17:31:07 -04:00
Nuno Campos 9650f0d41d postgres1.0.1 2024-08-07 12:15:40 -07:00
Nuno Campos 127b6dedee lib0.2.2 2024-08-07 12:14:34 -07:00
Nuno CamposandGitHub db5a6c569a Merge pull request #1265 from langchain-ai/nc/7aug/stream-nodes-wout-writes
Include in stream output all nodes ran, even if they returned no writes to state keys
2024-08-07 12:11:58 -07:00
Nuno CamposandGitHub b47ab6c30a Merge pull request #1264 from langchain-ai/nc/7aug/fix-tests
fix test setup for checkpointers
2024-08-07 12:08:22 -07:00
Nuno Campos 281c97b9e5 Include in stream output all nodes ran, even if they returned no writes to state keys
- This also includes these nodes in checkpoint.metadata.writes
2024-08-07 12:05:45 -07:00
Nuno Campos cb21a3547e Skip async postgres tests in 3.9 2024-08-07 12:04:11 -07:00
Nuno Campos 6028eb02c9 Lint 2024-08-07 11:49:46 -07:00
Nuno Campos 712d52ad3b fix test setup for checkpointers 2024-08-07 11:39:14 -07:00
Nuno CamposandGitHub 6078c408c3 Raise exception for reducers w wrong signature (#1261) 2024-08-07 17:48:07 +00:00
Nuno CamposandGitHub 9caeeaeeec Merge pull request #1258 from langchain-ai/nc/7aug/postgres-dont-sync-too-much
postgres: Avoid syncing pipeline unless actually needed
2024-08-07 09:43:52 -07:00
Nuno Campos 2bd9eb4298 postgres: Avoid syncing pipeline unless actually needed 2024-08-07 09:40:18 -07:00
Nuno CamposandGitHub 4c2542aac6 Merge pull request #1257 from langchain-ai/nc/7aug/postgres-migrations
postgres: Add migration tracking
2024-08-07 09:33:10 -07:00
vbarda 54da68cfe7 remove flag 2024-08-07 12:25:34 -04:00
vbarda 51b62ca0bd remove setup 2024-08-07 12:23:00 -04:00
Vadym BardaandGitHub ca4b65e1a6 checkpoint-postgres: use correct metadata serialization (#1255) 2024-08-07 12:13:44 -04:00
Nuno Campos 1e237bf33a postgres: Add migration tracking 2024-08-07 08:54:08 -07:00
Vadym BardaandGitHub fb8390e138 langgraph: release 0.2.1 (#1250) 2024-08-07 09:55:50 -04:00
Vadym BardaandGitHub 5cab47f751 checkpoint: move memory into a directory to fix namespace import issues (#1249) 2024-08-07 09:41:06 -04:00
Vadym BardaandGitHub 172b4af6ed langgraph: release 0.2.0 (#1246) 2024-08-06 23:25:18 -04:00
Vadym BardaandGitHub 186dfd3976 checkpoint-postgres: release 1.0.0 (#1245) 2024-08-06 22:53:29 -04:00
Vadym BardaandGitHub d9adebed84 checkpoint-sqlite: release 1.0.0 (#1244) 2024-08-06 22:43:35 -04:00
Vadym BardaandGitHub 2b860f04ce checkpoint: release 1.0.1 (#1243) 2024-08-06 22:38:13 -04:00
b37f78942d checkpoint-postgres: new library for postgres checkpointer implementation (#1236)
* checkpoint-postgres: new library for postgres checkpointer implementation

---------

Co-authored-by: Nuno Campos <nuno@langchain.dev>
2024-08-06 22:37:06 -04:00
Nuno CamposandGitHub 81e60893b6 Merge pull request #1240 from langchain-ai/nc/6aug/untracked-value
Add UntrackedValue to mark a state key as not checkpointable
2024-08-06 17:15:19 -07:00
Emil WåreusandGitHub 657cd30045 Update pass_private_state.ipynb (#1213)
Fix typo
2024-08-06 23:59:58 +00:00
Vadym BardaandGitHub 7cf2132df1 Merge branch 'main' into nc/6aug/untracked-value 2024-08-06 19:46:56 -04:00
vbarda b0b6c0ae7b lint + comment 2024-08-06 19:46:39 -04:00
Nuno Campos 5f9b0bc0ae Add UntrackedValue to mark a state key as not checkpointable 2024-08-06 15:41:52 -07:00
Nuno CamposandGitHub f7c09f5e9e Fix output schema affecting stream output (#1239)
* Fix output schema affecting stream output

* Remove file

* Remove file
2024-08-06 22:26:43 +00:00
Andrew NguonlyandGitHub 9c1aeb31dd Remove LangGraph Cloud waitlist from README (#1238)
* Remove waitlist from README.

* Remove waitlist from Cloud index page.

* Remove waitlist from README.
2024-08-06 13:32:55 -07:00
gbaian10andGitHub 43fb6012be fix: the execution error passed from ToolExecutor to create_react_agent (#1234) 2024-08-06 17:51:55 +00:00
Nuno CamposandGitHub 997a2afc79 Merge pull request #1233 from langchain-ai/nc/6aug/add-test-inherited-state-keys
Add test for inherited state keys
2024-08-06 09:30:59 -07:00
Nuno Campos f050f71584 Add test for inherited state keys 2024-08-06 09:20:33 -07:00
Nuno CamposandGitHub 3b63010673 Merge pull request #1232 from langchain-ai/dqbd/js-output-schema
feat(sdk-js): add output schema to match API, expose copy method for thread
2024-08-06 08:18:00 -07:00
Tat Dat Duong bb47a0d9b7 Reformat 2024-08-06 16:24:12 +02:00
Tat Dat Duong 475019731a Bump to 0.0.4 2024-08-06 15:34:28 +02:00
Tat Dat Duong 1e3a0aa88c Expose copy method for thread 2024-08-06 14:15:04 +02:00
Tat Dat Duong 3ba8500e90 feat(sdk-js): Add output schema typedef 2024-08-06 14:06:47 +02:00
Nuno CamposandGitHub 8850e8a373 Merge pull request #1228 from langchain-ai/vb/update-sqlite
checkpoint: stop using sqlite checkpointers as context managers, make memorysaver a context manager
2024-08-05 14:09:42 -07:00
vbarda 566e6c9b60 fix 2024-08-05 17:03:50 -04:00
vbarda 9aaa73cc55 undo comment 2024-08-05 16:55:19 -04:00
vbarda 55717c9bde update memory saver + tests 2024-08-05 16:53:33 -04:00
vbarda 64c30508d9 checkpoint-sqlite: stop using checkpointers as context managers, use contextmanager only in from_conn_string 2024-08-05 16:20:16 -04:00
ccurmeandGitHub a00ace21cb Merge pull request #1227 from langchain-ai/cc/fix_toolbar_title
docs: update title of doc in sidebar
2024-08-05 14:00:29 -04:00
Chester Curme e590a027db update title of doc in sidebar 2024-08-05 13:57:22 -04:00
ccurmeandGitHub 2976d4a3ba Merge pull request #1117 from langchain-ai/cc/many_tools_guide
docs: add how-to guide for handling many tools
2024-08-05 13:55:58 -04:00
Chester Curme 14d448b74d remove usage of upsert 2024-08-05 13:45:47 -04:00
Chester Curme ad31da06d1 update script + index + mkdocs 2024-08-05 13:34:30 -04:00
Chester Curme f124ce2f72 move file 2024-08-05 13:28:54 -04:00
Chester Curme 95f1032215 Merge branch 'main' into cc/many_tools_guide 2024-08-05 13:25:32 -04:00
Nuno CamposandGitHub c2c5b31f4b Merge pull request #1223 from langchain-ai/nc/5aug/test-update-state-custom-class
Add test for update_state when using custom state class
2024-08-05 09:00:34 -07:00
Nuno Campos e8c2fefb29 Add test for update_state when using custom state class 2024-08-05 08:54:16 -07:00
Nuno Campos 4f2f5f7cbc sdk0.1.27 2024-08-03 11:47:21 -07:00
Nuno CamposandGitHub 2ae1121994 sdk: Use an identifiable root path (#1216) 2024-08-03 18:46:36 +00:00
Nuno CamposandGitHub 3b5669a90b Merge pull request #1215 from langchain-ai/nc/3aug/sdk-asgi
sdk: Use ASGI transport when called inside langgraph-api
2024-08-03 11:20:26 -07:00
Nuno Campos 85454b6371 sdk: Use ASGI transport when called inside langgraph-api 2024-08-03 11:17:34 -07:00
Nuno CamposandGitHub 25a98fa888 Merge pull request #1210 from langchain-ai/vb/update-deps
checkpoint-sqlite: add checkpoint dependency
2024-08-02 17:14:54 -07:00
vbarda 2370db0f8c order 2024-08-02 19:08:16 -04:00
vbarda 1b353aed73 checkpoint-sqlite: add checkpoint dependency 2024-08-02 19:06:38 -04:00
Vadym BardaandGitHub 7b441e64e7 ci: update import pre-release check (#1209)
* ci: update import pre-release check

* fix
2024-08-02 18:58:04 -04:00
Vadym BardaandGitHub 351a29fcfb ci: handle initial library version in tags (#1208) 2024-08-02 18:44:25 -04:00
Vadym BardaandGitHub c149a99b44 checkpoint-sqlite: new library for sqlite checkpointer implementation (#1203)
* checkpoint-sqlite: new library for sqlite checkpointer implementation
2024-08-02 22:14:12 +00:00
Nuno CamposandGitHub 16a6450534 Merge pull request #1191 from langchain-ai/dqbd/js-sdk-types
feat(sdk-js): bump to 0.0.3, update types of updateState
2024-08-02 13:05:20 -07:00
Nuno CamposandGitHub 045f07c396 Merge pull request #1204 from langchain-ai/nc/2aug/graph-metadata-interrupt
Add interrupt info to graph repr
2024-08-02 13:05:11 -07:00
Nuno CamposandGitHub 373bcfe5cf Merge pull request #1206 from langchain-ai/nc/2aug/test-watch-all
Add make test_watch_all command
2024-08-02 13:04:39 -07:00
2742b2f884 aupdate_state now accepts null values (#1181)
* aupdate_state now accepts null values

---------

Co-authored-by: vbarda <vadym@langchain.dev>
2024-08-02 15:59:15 -04:00
Nuno Campos c30e80df67 Add missing 2024-08-02 12:59:11 -07:00
Nuno Campos 55044ba231 Add make test_watch_all command 2024-08-02 12:56:53 -07:00
Nuno Campos b1d0dbac77 Add interrupt info to graph repr 2024-08-02 12:20:56 -07:00
Nuno CamposandGitHub 742f17689e Merge pull request #1199 from langchain-ai/vb/bump-core
langgraph: bump core to 0.2.27
2024-08-02 12:07:23 -07:00
Vadym BardaandGitHub bbd5e692e1 checkpoint: release 1.0.0 (#1201) 2024-08-02 14:10:00 -04:00
Isaac FranciscoandGitHub c40df063d6 draft (#1200) 2024-08-02 11:06:52 -07:00
vbarda a0cd3ff7ad langgraph: bump core to 0.2.27 2024-08-02 13:56:26 -04:00
Nuno CamposandGitHub b2b31a323b Merge pull request #1197 from langchain-ai/nc/2aug/managed-rm-graph-arg
Remove graph arg from ManagedValue
2024-08-02 09:28:07 -07:00
Vadym BardaandGitHub 50eea98fc6 langgraph: remove deprecations and add new warnings (#1196)
* langgraph: remove deprecations and add new warnings
2024-08-02 12:18:34 -04:00
Nuno Campos a83718bec8 Remove graph arg from ManagedValue 2024-08-02 08:49:41 -07:00
Vadym BardaandGitHub 4d7a42a65e langgraph: remove FewShotExamples managed value (#1195) 2024-08-02 11:05:17 -04:00
Isaac FranciscoandGitHub 487157eafa typo fix (#1169) 2024-08-01 21:48:37 -04:00
4b2187c9a3 checkpoint: switch thread_ts -> checkpoint_id, add checkpoint_ns, change serializer protocol (#1185)
---------

Co-authored-by: Nuno Campos <nuno@langchain.dev>
2024-08-02 01:08:19 +00:00
Tat Dat Duong a6e32e57e8 Bump to 0.0.3 2024-08-01 14:14:40 -07:00
Tat Dat Duong 51dbb9493c Improve types for updateState 2024-08-01 14:14:12 -07:00
Nuno CamposandGitHub 862afa27de Merge pull request #1189 from langchain-ai/nfcampos-patch-2
Update constraints
2024-08-01 10:13:18 -07:00
Nuno CamposandGitHub aa8cd8259d Update setup_pyproject.md 2024-08-01 10:08:42 -07:00
Nuno CamposandGitHub 000066d1a1 Update setup.md 2024-08-01 10:08:07 -07:00
Nuno CamposandGitHub cd2b6642ee Merge pull request #1188 from langchain-ai/nc/1aug/update-sdks
Nc/1aug/update sdks
2024-08-01 09:54:47 -07:00
Nuno Campos 15ded2c17b Mark all schemas as optional in js and py sdk typings 2024-08-01 09:43:46 -07:00
Nuno Campos e4905f438a Fix create entrypoints script 2024-08-01 09:43:30 -07:00
Nuno Campos eb762c4a77 Undo 2024-07-31 15:02:25 -07:00
Nuno Campos ea5eb73b9f Enable builds outside of master 2024-07-31 15:00:44 -07:00
ae74825ea7 langgraph checkpoint: new library for checkpoint interfaces (#1163)
---------

Co-authored-by: Nuno Campos <nuno@langchain.dev>
2024-07-31 16:45:52 -04:00
Nuno CamposandGitHub 913a2d975b Merge pull request #1180 from langchain-ai/eugene/add_any_id_handling
langgraph[patch]: update unit tests to handle AnyStr() for pydantic 2 models
2024-07-31 12:30:54 -07:00
Eugene Yurtsev 5043aaf4fa UPdate 2024-07-31 14:33:20 -04:00
Vadym BardaandGitHub c3f6c58e13 docs: fix typo in retries (#1177) 2024-07-31 14:39:43 +00:00
Nuno Campos 298c93ca4a lib0.1.17 2024-07-30 18:27:48 -07:00
Nuno CamposandGitHub dd52472312 Merge pull request #1172 from langchain-ai/nc/30jul/update-no-values
Allow call to update_state without values
2024-07-30 18:27:00 -07:00
Nuno Campos 6475d81f29 Oops 2024-07-30 18:26:39 -07:00
Nuno Campos 51b4475fcc Allow call to update_state without values
- this means "fork without update" (eg to rerun a node)
2024-07-30 18:21:15 -07:00
Nuno Campos 3238fa0870 Add test for drawing lance example 2024-07-30 15:50:18 -07:00
Nuno CamposandGitHub fdaa5a3037 Merge pull request #1159 from akshseh/fix_visualization_example
fix: update the function for node colors
2024-07-30 10:04:05 -07:00
Akarsha SehwagandGitHub 12238c7b7e Merge branch 'main' into fix_visualization_example 2024-07-30 14:13:35 +02:00
Nuno Campos 3006084326 lib0.1.16 2024-07-29 12:46:33 -07:00
Nuno CamposandGitHub f448df4638 Merge pull request #1160 from langchain-ai/nc/29jul/fix-cond-after-multi-send
Fix issue when cond edge visited after multiple executions of Send
2024-07-29 12:46:00 -07:00
Nuno Campos 466cb8acb5 Fix issue when cond edge visited after multiple executions of Send
- cond edge will run for each execution of Send, so target channels need to support multiple publishes
2024-07-29 12:38:48 -07:00
Akarsha SehwagandGitHub 1a0ad5fdd0 fix: update the function for node colors
NodeColors does not exist anymore in Langchain_core -> updated to NodeStyles and changed the param names.
2024-07-29 17:23:48 +02:00
ea071935fe adding message info (#1150)
---------

Co-authored-by: vbarda <vadym@langchain.dev>
2024-07-26 20:24:19 +00:00
Nuno Campos 794a0fff03 lib0.1.15 2024-07-26 10:58:56 -07:00
Nuno CamposandGitHub 06ed6d7cab Merge pull request #1152 from langchain-ai/nc/26jul/pydantic-2-compat
lib: Improve compat with pydantic 2 models
2024-07-26 10:58:24 -07:00
Nuno Campos 6eacc6b7c8 lib: Improve compat with pydantic 2 models 2024-07-26 10:51:59 -07:00
Nuno CamposandGitHub f6ac881591 Merge pull request #1151 from langchain-ai/wfh/shrink
Shrink images
2024-07-26 10:50:56 -07:00
William Fu-Hinthorn f431b415fc Shrink images 2024-07-26 10:06:20 -07:00
Chester Curme 8d4b95afa8 add section 2024-07-26 11:22:01 -04:00
Lance MartinandGitHub 4b51c27461 Add llama3.1 tool calling (#1148) 2024-07-26 08:08:22 -07:00
Vadym BardaandGitHub 585c5c41ce docs: sync readmes (#1147) 2024-07-26 14:14:42 +00:00
BagaturandGitHub c64588a673 docs: rm discord from readme (#1145) 2024-07-26 08:19:54 -04:00
Vadym BardaandGitHub 75fa7395bd docs: add better state/reducers description in intro tutorial (#1140) 2024-07-25 22:01:18 +00:00
Isaac FranciscoandGitHub 66ad48e771 typo (#1139) 2024-07-25 21:36:28 +00:00
Isaac FranciscoandGitHub 41fd8020ee sdk-py: add docstrings (#1130) 2024-07-25 16:51:25 -04:00
Nuno CamposandGitHub 09a28ccef6 Merge pull request #1136 from langchain-ai/isaac/reducegifsizes
reduce video sizes
2024-07-25 11:57:21 -07:00
isaac hershenson c0431227d8 reduce videos 2024-07-25 11:53:29 -07:00
Nuno CamposandGitHub b5f861722d Merge branch 'main' into cc/many_tools_guide 2024-07-25 11:15:05 -07:00
Nuno CamposandGitHub 770e1601e5 Merge pull request #1135 from langchain-ai/nc/25jul/ci-large-size
Add CI check for large files added
2024-07-25 10:26:11 -07:00
Nuno Campos 045c2af663 Remove test image 2024-07-25 10:20:03 -07:00
Nuno Campos 23d3a7ac07 Improve output 2024-07-25 10:18:55 -07:00
Nuno Campos 67d00aca90 Fix 2024-07-25 10:16:56 -07:00
Nuno Campos e54989ca74 Add quotes 2024-07-25 10:05:25 -07:00
Nuno Campos 14372a4515 Try again 2024-07-25 10:04:05 -07:00
Nuno Campos 6e33bda433 Different flag? 2024-07-25 10:00:21 -07:00
Nuno Campos 77eb88eef2 Try again 2024-07-25 09:59:23 -07:00
Nuno Campos 7584f058c2 Add prints 2024-07-25 09:47:06 -07:00
Nuno Campos fba6e0504c Fix 2024-07-25 09:45:27 -07:00
Nuno Campos c17fe2d189 Support paths with spaces 2024-07-25 09:44:31 -07:00
Nuno Campos 4a58dcccf2 Add test large image 2024-07-25 09:36:02 -07:00
Nuno Campos ed2e1a736f Add CI check for large files added 2024-07-25 09:34:33 -07:00
Nuno CamposandGitHub a168615f2d Merge pull request #1126 from langchain-ai/vb/add-graph-factory-example 2024-07-24 18:35:36 -07:00
vbarda 190372e137 update 2024-07-24 21:22:19 -04:00
Isaac FranciscoandGitHub eba8303c98 display state management (#1127) 2024-07-24 16:57:17 -07:00
William FHandGitHub 2845d7ace5 Shrink Images (#1125) 2024-07-24 16:50:08 -07:00
vbarda 2ceac211e7 docs: add how to for graph factory + update cli 2024-07-24 18:14:34 -04:00
Vadym BardaandGitHub 8f6b3b636d docs: fix link in subgraphs how-to (#1124) 2024-07-24 16:59:46 -04:00
Nuno Campos ebe01c2639 lib0.1.14 2024-07-24 10:53:23 -07:00
Nuno CamposandGitHub 272219e410 Merge pull request #1121 from langchain-ai/nc/24jul/disable-nested-checkpoints-unless-interrupt 2024-07-24 10:52:29 -07:00
Nuno Campos bb1324cdc3 Disable nested checkpoints unless interrupts set on subgraph 2024-07-24 10:45:39 -07:00
Nuno Campos 85af0603fa lib0.1.13 2024-07-24 09:49:23 -07:00
Chester Curme 590f810b53 add concluding text 2024-07-24 12:45:37 -04:00
Nuno CamposandGitHub e6e5911aae Merge pull request #1119 from langchain-ai/nc/24jul/avoid-crash-missing-node
Avoid crash when a node in pending sends is removed
2024-07-24 09:41:20 -07:00
Nuno CamposandGitHub 9fdbd0dd49 Merge pull request #1118 from langchain-ai/nc/24jul/fix-recursion-limit-thread
Fix recursion limit considering steps taken in previous runs on same thread
2024-07-24 09:35:12 -07:00
Nuno Campos 8a20c6f7e4 Avoid crash when a node in pending sends is removed 2024-07-24 09:29:38 -07:00
Nuno Campos 03f9b27e3b Fix 2024-07-24 09:26:49 -07:00
Nuno CamposandGitHub 19f6f7d5ca Merge pull request #1110 from langchain-ai/wfh/test_pending_writes_null
Test Null Pending Writes
2024-07-24 08:51:33 -07:00
Nuno Campos 745b96eb63 Fix recursion limit considering steps taken in previous runs on same thread 2024-07-24 08:50:32 -07:00
Chester Curme 7ea5da73c7 add guide 2024-07-24 11:49:05 -04:00
b90d44d97d Add local tool calling agent example (#1109)
* Add local tool calling agent example

* Update copy_notebooks.py

---------

Co-authored-by: Nuno Campos <nuno@langchain.dev>
2024-07-24 07:39:53 -07:00
Vadym BardaandGitHub d99d3e05c7 langgraph: release 0.1.12 (#1116) 2024-07-24 10:38:07 -04:00
Vadym BardaandGitHub ca6aef4746 langgraph: bring back tool content stringify (#1114) 2024-07-24 10:36:32 -04:00
William Fu-Hinthorn f5b9e463e3 Merge branch 'main' into wfh/test_pending_writes_null 2024-07-23 21:28:51 -07:00
William FHandGitHub 82cbe25be8 Update typedoc build (#1087) 2024-07-23 21:27:40 -07:00
William Fu-Hinthorn 36505d9656 Add test 2024-07-23 21:15:53 -07:00
Lance MartinandGitHub b72ea0ea1b Update MR docs (#1098) 2024-07-23 17:13:20 -07:00
Vadym BardaandGitHub 603a137d33 langgraph: release 0.1.11 (#1107) 2024-07-23 18:06:07 -04:00
Nuno CamposandGitHub 4cdb38d337 Fix null pending writes (#1106) 2024-07-23 17:59:16 -04:00
Nuno Campos 03bf1e5414 Fix null pending writes 2024-07-23 14:55:07 -07:00
Nuno CamposandGitHub 4fa5c8bab0 Merge pull request #1089 from mihaic/patch-1
Fix StateGraph.add_edge end_key check
2024-07-23 13:52:33 -07:00
Nuno CamposandGitHub 9711f39995 Merge pull request #1090 from langchain-ai/harrison/private-state
Harrison/private state
2024-07-23 13:50:02 -07:00
Nuno CamposandGitHub 091bc003ae Merge pull request #1088 from langchain-ai/vb/update-return-type
langgraph: add proper type annotation for StateGraph.compile
2024-07-23 13:49:12 -07:00
Nuno CamposandGitHub 57c40026f8 Update pass_private_state.ipynb 2024-07-23 13:46:56 -07:00
Nuno CamposandGitHub bf61f6f869 Merge pull request #1102 from langchain-ai/an/23jul/docs-langgraph-cloud-constraints
docs: Add constraints info to LangGraph Cloud setup docs
2024-07-23 13:45:30 -07:00
Nuno Campos 6c299a1b5a Fix docs build 2024-07-23 13:42:57 -07:00
Nuno CamposandGitHub aeae796e60 Update setup_pyproject.md 2024-07-23 13:29:56 -07:00
Nuno CamposandGitHub 7ec37575de Update setup.md 2024-07-23 13:29:43 -07:00
Nuno CamposandGitHub 4fdd05532d Merge pull request #1100 from langchain-ai/harrison/migrations
migrations of graph
2024-07-23 13:26:18 -07:00
Nuno CamposandGitHub 43c793f7fe Update low_level.md 2024-07-23 13:24:46 -07:00
Lance MartinandGitHub 1765000263 Local RAG agent testing with Llama3.1 (#1104) 2024-07-23 13:05:51 -07:00
Vadym BardaandGitHub 3fdd4715f5 langgraph: release 0.1.10 (#1103) 2024-07-23 14:13:35 -04:00
Isaac FranciscoandGitHub 97cb44058a [docs]:open studio from local deployment (#1037)
* open studio from local deployment

* spelling

* harrison comments
2024-07-23 12:50:57 -04:00
Andrew Nguonly 73e0871345 Add constraints info to LangGraph Cloud setup docs. 2024-07-23 08:56:56 -07:00
Harrison Chase c8f046869b migrations of graph 2024-07-23 08:20:03 -07:00
Nuno CamposandGitHub bd4dccd9cb Merge pull request #1075 from langchain-ai/nc/19jul/nested-checkpoints 2024-07-22 18:41:56 -07:00
vbarda c268d695f0 spellcheck 2024-07-22 21:28:11 -04:00
vbarda 0891cc1864 comments / bring back sleep 2024-07-22 21:18:42 -04:00
Nuno Campos ee7cab664a Fix checkpoint put ordering issue 2024-07-22 17:52:09 -07:00
vbarda fd8f9e87dc update types 2024-07-22 20:48:21 -04:00
vbarda ed9c015d24 remove utils 2024-07-22 20:43:28 -04:00
vbarda cd4fae601c remove sleep 2024-07-22 20:26:12 -04:00
Nuno Campos aed313ebb5 Fix stack not being unwound when suppressing interrupt 2024-07-22 17:09:47 -07:00
Nuno Campos 2008df03fa Merge branch 'main' into nc/19jul/nested-checkpoints 2024-07-22 16:33:33 -07:00
Nuno Campos 3f860617a5 Update tests 2024-07-22 16:32:18 -07:00
Nuno Campos 98a962661b Instead of saving an additional interrupt checkpoint, make child graphs keep a single checkpoint for each parent checkpoint
- while the inner graph makes progress it overwrites the partial progress checkpoints, eventually keeping only one for each outer step
- implement parent_config in MemorySaver
- fix edge cases in PregelLoop
2024-07-22 16:32:15 -07:00
Harrison Chase 9483da1bdb cr 2024-07-22 16:31:14 -07:00
Harrison Chase 8d3da565c4 cr 2024-07-22 16:30:34 -07:00
Nuno Campos 42174ad9fb Add optional id arg to create_checkpoint 2024-07-22 16:30:19 -07:00
Nuno Campos 0c1ec8d5d2 Update sync bg executor to wait on all tasks on exit 2024-07-22 16:30:08 -07:00
Harrison Chase fdbb33ddad add private state 2024-07-22 16:26:31 -07:00
Mihai CapotăandGitHub 3f43e0d945 Fix StateGraph.add_edge end_key check
It looks like a typo to me: `END` should be `START`.

`END` should be accepted as `end_key` and it is accepted with the `super` call path.
2024-07-22 15:55:19 -07:00
vbarda d00503ecc4 langgraph: add proper type annotation for StateGraph.compile 2024-07-22 18:03:16 -04:00
trevor-cyiandGitHub ab80c113ac docs: Use bound model in convo history (#1082)
This fixes a small mistake in the manage-conversation-history notebook
where the model bound with tools was not used, instead, the original model
was used when invocations occur.
2024-07-22 16:27:42 -04:00
vbarda fbced023f3 add more tests 2024-07-22 16:23:54 -04:00
William FHandGitHub 91d6b964c4 Update js sdk links (#1086) 2024-07-22 13:08:48 -07:00
vbarda 6e57fa5f9a return future on self._put_checkpoint 2024-07-22 14:32:57 -04:00
vbarda 77f6ec5a2b update memory 2024-07-22 13:03:30 -04:00
vbarda f2a95ad67f fix broken interrupt tests 2024-07-22 12:18:12 -04:00
vbarda d635a22302 sort memory checkpoints monotonically decreasing 2024-07-22 12:14:33 -04:00
Nuno CamposandGitHub b1ffa52da6 Merge pull request #1064 from KYUUUW/patch-1
[docs]Fix runtime error README.md example
2024-07-22 08:42:34 -07:00
Nuno CamposandGitHub e2b959ee7e Merge pull request #1073 from langchain-ai/nc/19jul/binop-lambda-eq
Don't include lambdas in __eq__ for BinOp
2024-07-22 08:41:30 -07:00
Nuno Campos 84e404eb71 Lint 2024-07-22 08:24:53 -07:00
Nuno Campos a1a5fc01a9 cli0.1.50 2024-07-22 08:22:05 -07:00
Nuno CamposandGitHub 2616614470 Merge pull request #1080 from langchain-ai/nc/21jul/cli-stdout-overrun
cli: Fix crash when subprocess has a very long stdout/stderr line
2024-07-22 08:21:25 -07:00
Nuno CamposandGitHub 64fd304ea0 Merge pull request #1083 from langchain-ai/vb/update-sdk 2024-07-22 08:05:50 -07:00
vbarda f820ca8f7f sdk-py: add threads copy 2024-07-21 20:21:27 -04:00
Vadym BardaandGitHub 4bacbdd2bd docs: update create_react_agent to use state_modifier (#1081) 2024-07-21 17:07:50 -04:00
Nuno CamposandGitHub 34407d9de1 Update exec.py 2024-07-21 12:19:19 -07:00
Nuno Campos b81612c292 cli: Fix crash when subprocess has a very long stdout/stderr line 2024-07-21 12:16:37 -07:00
LEE KYU WONandGitHub b3e44bed22 Fix example error 2024-07-21 18:14:01 +09:00
LEE KYU WONandGitHub e8b05faff4 Merge branch 'main' into patch-1 2024-07-21 18:12:38 +09:00
BagaturandGitHub 610b6cc78c langgraph[patch]: InjectedState annotation (#1067)
Add annotated for injecting state vars into a Tool
2024-07-19 20:07:21 -07:00
William FHandGitHub 75f8a33c9e [Docs] Format notebooks (#1076) 2024-07-19 18:29:24 -07:00
Nuno CamposandGitHub afe3905958 Update loop.py 2024-07-19 18:15:54 -07:00
Nuno CamposandGitHub 375d9a45d9 Update loop.py 2024-07-19 18:15:47 -07:00
Nuno Campos f5390a4ee5 Port to new architecture 2024-07-19 18:04:55 -07:00
William FHandGitHub 29d7a812ae [Docs] Update checkpointer docstrings (#1074) 2024-07-19 17:52:47 -07:00
Nuno CamposandGitHub fb9d5b9304 Merge pull request #1059 from langchain-ai/nc/18jul/loop
Make Pregel loop runnable step-by-step
2024-07-19 17:16:48 -07:00
Nuno Campos b77ef7d162 Lint 2024-07-19 17:13:51 -07:00
Nuno Campos b0e0d269c5 Lint 2024-07-19 16:40:50 -07:00
Nuno Campos 1dbf7a3392 Move all checkpoint edits to apply_writes 2024-07-19 16:37:27 -07:00
Nuno Campos 27cd4e2221 Update core 2024-07-19 15:44:21 -07:00
Nuno Campos dc703857dd Run tests in parallel 2024-07-19 15:43:40 -07:00
Nuno CamposandGitHub 5df5eb6b09 Merge pull request #1072 from langchain-ai/isaac/retrynodeshowto
[docs]: add node retry docs
2024-07-19 15:00:03 -07:00
isaac hershenson eaa23ef4e5 added retrypolicy reference 2024-07-19 14:56:14 -07:00
Nuno Campos 4dc1195bcd Don't include lambdas in __eq__ for BinOp
When using forward refs, inline lambdas in Annotated are re-evaluated for every subclass, thus making the comparison fail
2024-07-19 14:46:01 -07:00
isaac hershenson 55c78bbf90 remove unneccesary imports 2024-07-19 13:13:02 -07:00
isaac hershenson 2a7ae83bf8 spelling 2024-07-19 13:07:26 -07:00
isaac hershenson d7dc16310f nuno comments 2024-07-19 13:05:28 -07:00
isaac hershenson afd940d9bd first draft 2024-07-19 12:38:55 -07:00
Nuno Campos ed5d114087 Fix 2024-07-19 12:19:43 -07:00
Nuno CamposandGitHub 9d14f4f2f1 docs: Add note on langgraph v langgraph cloud (#1071) 2024-07-19 10:40:02 -07:00
Nuno CamposandGitHub 29e860acca Update manager.py 2024-07-19 08:52:08 -07:00
Nuno CamposandGitHub b26be0c113 Merge pull request #1070 from langchain-ai/vb/update-assistants
sdk-py: add if_exists for assistants
2024-07-19 08:39:11 -07:00
Nuno CamposandGitHub ec2892098b Update client.py 2024-07-19 08:21:58 -07:00
vbarda c7133fd34b Merge branch 'main' into vb/update-assistants 2024-07-19 10:12:26 -04:00
Nuno Campos 7ed5f9e4bc Implement AsyncLoop 2024-07-18 17:28:44 -07:00
vbarda 8bd2d88be4 sdk-py: add if_exists for assistants 2024-07-18 19:59:12 -04:00
BagaturandGitHub 3a53843185 langgraph[patch]: refactor ToolNode (#1066) 2024-07-18 16:13:06 -07:00
Nuno Campos f974471f7d Lint 2024-07-18 15:52:20 -07:00
Nuno Campos 909bf4433f Lint 2024-07-18 15:51:37 -07:00
Nuno Campos 73b4b6ec5b Lint 2024-07-18 15:50:45 -07:00
Nuno Campos 06f83710c8 Lint 2024-07-18 15:49:58 -07:00
Nuno Campos b3ee728839 Pregel.stream passing all tests 2024-07-18 15:49:58 -07:00
Nuno Campos 60f7a7d593 Lint 2024-07-18 15:49:58 -07:00
Nuno Campos 56238036d7 WIP: Split out loop logic from Pregel class 2024-07-18 15:49:58 -07:00
LEE KYU WONandGitHub 3f35a0a44e Update wait-user-input.ipynb (#1065) 2024-07-18 18:48:22 -04:00
LEE KYU WONandGitHub 58af2c7d9f Fix error in example 2024-07-19 04:29:39 +09:00
Vadym BardaandGitHub 95a4a55849 docs: add how to for passing config to tools (#1063) 2024-07-18 13:33:56 -04:00
Nuno Campos 97281d708b lib 0.1.9 2024-07-18 10:28:24 -07:00
Nuno CamposandGitHub c39f4ce47a Merge pull request #1062 from langchain-ai/nc/18jul/node-retry-policy
Enable configuring retry policy for each node of StateGraph
2024-07-18 10:24:31 -07:00
Nuno Campos 833b1faba2 Enable configuring retry policy for each node of StateGraph 2024-07-18 10:18:29 -07:00
Nuno Campos 2ca816ca25 cli 0.1.49 2024-07-18 10:01:43 -07:00
Nuno CamposandGitHub 50861cdf1b Merge pull request #1036 from langchain-ai/nc/16jul/node-state
Add node state, customizable graph input and output schemas
2024-07-18 09:56:20 -07:00
Nuno Campos b5b0f8d7e3 Remove node output schemas 2024-07-18 09:46:06 -07:00
Nuno CamposandGitHub 87e80fe45a Merge pull request #1058 from langchain-ai/nc/18jul/cli-dockerfile
cli: Add dockerfile command to write dockerfile
2024-07-18 09:28:44 -07:00
Nuno Campos ffe9a6d5b8 cli: Add dockerfile command to write dockerfile 2024-07-18 09:04:14 -07:00
BagaturandGitHub 97ae473002 docs: pass in graph state to tool (#1056) 2024-07-18 07:34:19 -07:00
William FHandGitHub 246b7dafcf Add Docstrings (#1057) 2024-07-18 07:12:12 -07:00
William FHandGitHub 254926c4a8 Update template links (#1045) 2024-07-18 07:10:10 -07:00
Vadym BardaandGitHub 5fbc9555c3 docs: add how-to section on tool calling (#1051) 2024-07-18 09:36:15 -04:00
Vadym BardaandGitHub 83089c3a7f langgraph: add incorrect tool name handling to ToolNode (#1052) 2024-07-17 21:33:46 -04:00
Nuno CamposandGitHub c2e25e2ac2 Merge pull request #1049 from langchain-ai/nc/17jul/pregel-node-retry-policy
pregel: Support retry policy per node
2024-07-17 14:54:23 -07:00
Andrew NguonlyandGitHub e3ef9adac7 Add instructions to test locally before deploying to Cloud. (#1050) 2024-07-17 14:04:15 -07:00
Nuno Campos e4bfcffed4 pregel: Support retry policy per node
- to expose from StateGraph in future PR
2024-07-17 13:17:15 -07:00
Nuno CamposandGitHub a184c7f23a lib: Separate out executor logic from stream methods (#1048)
* lib: Separate out executor logic from stream methods

- first step towards splitting out tick and stream

* Lint

* Lint

* Lint

* Lint
2024-07-17 13:16:35 -07:00
clement.landGitHub a96f5f8f27 docs: fix typo in low_level.md (#1044) 2024-07-17 09:57:26 -04:00
Vadym BardaandGitHub 79dc1a64b3 langgraph: allow passing custom state & let message modifiers access state in create_react_agent (#1023) 2024-07-16 20:41:17 -04:00
Nuno Campos 7bc489f1f4 Add node state, customizable graph input and output schemas 2024-07-16 13:50:20 -07:00
924cd1f2d8 [docs]: minor changes for testing locally (#1029)
* small changes

* harrison comments

* Update docs/docs/cloud/deployment/test_locally.md

---------

Co-authored-by: Harrison Chase <hw.chase.17@gmail.com>
2024-07-16 10:43:03 -07:00
William FHandGitHub 6fd1dc5697 Add note to docstring (#1035) 2024-07-16 08:43:49 -07:00
Arthur BermanandGitHub 09c5ebc94d Update low_level.md (#1016)
replace add_edge by add_conditional_edges
fix a mistake
2024-07-16 08:43:40 -07:00
Vadym BardaandGitHub ebeebcd095 sdk-js: release 0.0.2 (#1033) 2024-07-16 09:20:25 -04:00
Vadym BardaandGitHub 3713ba66a8 sdk-js: add thread_id and if_exists to ThreadsClient.create (#1032) 2024-07-16 09:18:22 -04:00
Jacob LeeandGitHub 449b42717c Add guide on handling tool calling errors (#1012)
* Fix example

CC @vbarda

* Progress on tool calling errors

* Update

* Rename

* Format

* Clean up outputs

* Revert

* Use stream instead of invoke for final example
2024-07-15 23:22:50 -07:00
BagaturandGitHub 2a1cdd31c5 langgraph[patch]: ToolNode support for tools outputting msg (#977)
ToolNode passes ToolCall to tools directly and outputs the generated ToolMessages directly
2024-07-15 16:40:37 -07:00
Nuno CamposandGitHub 5444563fdc Only render end node if there is an edge leading to it (#1022) 2024-07-15 11:26:05 -07:00
Vadym BardaandGitHub fad4de789f sdk-py: release 0.1.26 (#1026) 2024-07-15 13:44:41 -04:00
Vadym BardaandGitHub bf10f39b6b docs: update delete messages how-to for clarity (#1025) 2024-07-15 13:36:19 -04:00
Vadym BardaandGitHub ebf6316fa7 docs: fix collapsible code block affecting TOC in tutorial (#1024) 2024-07-15 13:16:28 -04:00
Vadym BardaandGitHub 647c5737f0 sdk-py: add if_exists to threads.create (#1009) 2024-07-15 12:15:43 -04:00
Vedanta SPandGitHub 9e7695085e Update langgraph_self_rag_local.ipynb (#1014)
replaced pprint with print
2024-07-14 16:32:07 -04:00
Nuno CamposandGitHub 8cf10fa460 Specific error when updating state as an unknown node (#1015) 2024-07-14 06:54:01 -07:00
Vadym BardaandGitHub 3ef65c37b3 docs: add note for langgraph cloud checkpointers (#1011) 2024-07-12 16:48:03 -04:00
Vadym BardaandGitHub 4b5152fde5 docs: small fix in postgres docs (#1010) 2024-07-12 16:27:58 -04:00
Jacob LeeandGitHub 07c3c5ff6f Hide output spacer column in docs (#1008) 2024-07-12 13:00:01 -07:00
Lance MartinandGitHub ada17d2ccb Improve visuals (#1005) 2024-07-12 12:55:32 -07:00
738f725aea Support multiple interruptions after resuming execution (#1004)
* Support multiple interruptions

* Add async test, Use a different fix

* Update comment

---------

Co-authored-by: Nuno Campos <nuno@langchain.dev>
2024-07-12 12:53:17 -07:00
Jacob LeeandGitHub 558a513a1a Remove Jupyter cell 'In[<number>]' display column from docs (#1007) 2024-07-12 12:47:07 -07:00
Vadym BardaandGitHub 5a55974f15 docs: update postgres checkpointer (#1001) 2024-07-12 13:40:55 -04:00
Vadym BardaandGitHub f84f9374d3 docs: bring back how-to on passing runtime values to tools (#1003) 2024-07-12 11:52:34 -04:00
Vadym BardaandGitHub f8c87afee1 docs: update streaming from within the tool to use dispatch_custom_event (#1000) 2024-07-11 21:31:55 -04:00
dedbdefd93 [Docs] Added Asynchronous implementation of MongoDB persistence (#983)
---------

Co-authored-by: Vadym Barda <vadim.barda@gmail.com>
Co-authored-by: Vadym Barda <vadym@langchain.dev>
2024-07-11 21:20:19 -04:00
Nuno Campos f13cf5dc2c Update snapshot tests after core release 2024-07-11 17:06:12 -07:00
Nuno Campos 1e2682616f lib0.1.8 2024-07-11 17:03:56 -07:00
Nuno CamposandGitHub 71d9d02d26 lib: Add metadata arg to add_node() (#990)
* lib: Add metadata arg to add_node()

- use metadata when drawing graph
- use metadata for tracing

* Update core

* Lock
2024-07-11 17:03:22 -07:00
Lance MartinandGitHub bd100fdfef Update controllability docs (#995)
* Update controllability docs

* Update controllability docs

* Update docs
2024-07-11 16:10:12 -07:00
Jacob LeeandGitHub 3f4a8e557e Remove broken edit button, view source button (#998)
* Fix edit button, remove view source button

* Update mkdocs.yml

* Update mkdocs.yml
2024-07-11 15:17:23 -07:00
Andrew NguonlyandGitHub e9f7cd3e52 docs: Add Cloud Concepts docs, update cloud deployment how-to guide (#996)
* Update Cloud deployment docs. Add Cloud Concepts docs.

* Update quick start. Update how-to for setting up repo.

* Delete unused image base_url.png.

* Fix spelling error. Fix broken anchor link.

* Fix broken links.
2024-07-11 14:33:25 -07:00
Vadym BardaandGitHub 39321ba042 docs: reduce number of cells in streaming how-tos (#997) 2024-07-11 17:19:58 -04:00
Vadym BardaandGitHub 85ec1934de langgraph: update stream_mode docstring for .stream() (#994)
* langgraph: update stream_mode docstring for .stream()

* add astream docstring

* fix import
2024-07-11 09:08:19 -07:00
Vadym BardaandGitHub 45dbf8c656 sdk-py: remove defaults from typedef (#989) 2024-07-10 18:08:05 -04:00
Nuno Campos 60763d4ddf lib 0.1.7 2024-07-10 14:44:07 -07:00
Vadym BardaandGitHub 8c4da2c41c sdk-py: add create_batch method (#988) 2024-07-10 17:37:56 -04:00
Nuno CamposandGitHub dfb2ac321f lib: Checkpoint pending writes whenever a node finishes (#976)
* lib: Checkpoint pending writes whenever a node finishes

- Whenever a node finishes, checkpoint pending writes

* Rename arg

* Add tests, resume from pending writes

* Add comment

* Add descriptive error

* Fix bug found by will

* Fix comments

* Lint

* Don't save pending write if executing only one node in step
2024-07-10 14:28:06 -07:00
Lance MartinandGitHub 4d2456be40 Update HIL update / edit state documentation (#966)
* Update user input example

* Update editing graph state

* Minor clarifications
2024-07-10 13:24:16 -07:00
Vadym BardaandGitHub 166aaa1bac docs: update another langsmith key ref (#986) 2024-07-10 12:33:34 -04:00
Vadym BardaandGitHub 7cad1658f6 docs: update key in local cloud quickstart (#985) 2024-07-10 12:18:10 -04:00
Vadym BardaandGitHub a3a9c6ceed docs: add how-to on how to stream tokens from within a tool (#984) 2024-07-10 12:16:18 -04:00
William FHandGitHub 4849501ccf Copy core context in RunnableCallable (#973) 2024-07-09 20:26:45 -07:00
Vadym BardaandGitHub a6dddd322a docs: add how-to for streaming tokens w/o langchain chat models (#972) 2024-07-09 21:56:18 -04:00
Isaac FranciscoandGitHub 17785375b8 [docs]: adding LangGraph up documentation (#975)
* first draft

* harrison comments
2024-07-09 18:22:11 -07:00
William FHandGitHub b632fd8c54 Add cleaned up csbot chatbot example (#971) 2024-07-09 14:06:05 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f32daee7d5 chore(deps): bump certifi from 2024.6.2 to 2024.7.4 in /libs/langgraph (#969)
Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.6.2 to 2024.7.4.
- [Commits](https://github.com/certifi/python-certifi/compare/2024.06.02...2024.07.04)

---
updated-dependencies:
- dependency-name: certifi
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 13:52:41 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
df52115de0 chore(deps-dev): bump certifi in /libs/cli/examples (#967)
Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.6.2 to 2024.7.4.
- [Commits](https://github.com/certifi/python-certifi/compare/2024.06.02...2024.07.04)

---
updated-dependencies:
- dependency-name: certifi
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 12:54:47 -07:00
2eb7d14acd docs: add Redis Checkpointer example (#961)
---------

Co-authored-by: Vadym Barda <vadim.barda@gmail.com>
Co-authored-by: Vadym Barda <vadym@langchain.dev>
2024-07-09 14:32:01 -04:00
Lance MartinandGitHub 2842dae598 Minor edit to low-level docs to clarify Super Steps (#951) 2024-07-09 10:02:08 -07:00
Nuno Campos 2354066c6d Format 2024-07-09 09:40:29 -07:00
Nuno Campos 4b564f69a8 Fix tests 2024-07-09 09:35:50 -07:00
Vadym BardaandGitHub c283d7e2ba langgraph: release 0.1.6 (#965) 2024-07-09 12:34:11 -04:00
3675c5b89d [Docs] Added MongoDB persistence in how-tos (#960)
---------

Co-authored-by: Vadym Barda <vadim.barda@gmail.com>
Co-authored-by: Vadym Barda <vadym@langchain.dev>
2024-07-09 12:27:56 -04:00
a8644baf50 docs: add memory how-tos + update add_messages (#958)
---------

Co-authored-by: vbarda <vadym@langchain.dev>
2024-07-09 12:26:31 -04:00
Nuno CamposandGitHub cf75003cbe cli: Update pip install command 2024-07-09 12:31:08 +01:00
Andrew NguonlyandGitHub fab950acfb Update version of CLI to 0.1.48. (#957) 2024-07-08 18:20:23 -07:00
Andrew NguonlyandGitHub 7ed26e1396 Update LangGraph API documentation. (#956) 2024-07-08 17:52:21 -07:00
David DuongandGitHub 293bc2c11f feat(cli): add debugger host CLI flag to specify default base URL (#900) 2024-07-08 20:47:50 -04:00
Andrew NguonlyandGitHub bb6785bbef cli: set verbose flag for pull in langgraph build (#955)
* Set verbose flag for langgraph build.

* Update CLI README.
2024-07-08 17:46:21 -07:00
Henry KobinandGitHub 878567f6b0 spellcheck (#936) 2024-07-08 17:10:21 -04:00
William FHandGitHub 165e82eb33 [Docs] Replace Absolute External Links (#949) 2024-07-08 13:02:53 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
64a17599c1 chore(deps-dev): bump certifi from 2024.6.2 to 2024.7.4 (#950)
Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.6.2 to 2024.7.4.
- [Commits](https://github.com/certifi/python-certifi/compare/2024.06.02...2024.07.04)

---
updated-dependencies:
- dependency-name: certifi
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-08 13:02:14 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
23f422e9d9 chore(deps): bump certifi from 2024.2.2 to 2024.7.4 in /libs/sdk-py (#934)
Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.2.2 to 2024.7.4.
- [Commits](https://github.com/certifi/python-certifi/compare/2024.02.02...2024.07.04)

---
updated-dependencies:
- dependency-name: certifi
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-08 11:51:16 -07:00
Vadym BardaandGitHub d16b8b4e59 docs: fix more links (#948) 2024-07-08 12:32:05 -04:00
Vadym BardaandGitHub 9b4bb69001 docs: fix links in the cloud how-tos (#947) 2024-07-08 12:13:05 -04:00
Lance MartinandGitHub 5232ea2606 Improve and clean Breakpoints documentation (#923) 2024-07-03 19:38:42 -07:00
William FHandGitHub 56086512ca [Docs] Add State Context Key How-to (#920) 2024-07-03 12:28:40 -07:00
Vadym BardaandGitHub cd3a1cbf8f langgraph: add tool error handling (#910) 2024-07-03 15:12:50 -04:00
Vadym BardaandGitHub 6e8e532995 docs: update readme (#921) 2024-07-03 14:18:32 -04:00
Vadym BardaandGitHub 2093347f8a sdk-js: release 0.0.1 (#918) 2024-07-03 14:07:39 -04:00
William FHandGitHub f380e2301d [Docs] Update README example
Plus add CI check
2024-07-03 10:35:29 -07:00
Vadym BardaandGitHub 8b45146f13 docs: add langgraph logo (#916) 2024-07-03 13:15:15 -04:00
Andrew NguonlyandGitHub 5c42d26475 docs: Add CTA for LangGraph Cloud to README (#914)
* Update README.md

* Update README.
2024-07-03 08:20:17 -07:00
Vadym BardaandGitHub 6e7265a659 langgraph: add support for deleting messages (#651)
* (RFC) add support for deleting messages

* alternative design

* lint

* Revert "lint"

This reverts commit 88b59d2aef.

* Revert "alternative design"

This reverts commit 2ad6009160.

* switch to lc core removemessage

* lint

* add unittests
2024-07-03 06:43:53 +01:00
Vadym BardaandGitHub 5e8aa5d9f2 langgraph: update langchain-core & snapshot tests (#909) 2024-07-02 20:37:03 -04:00
Isaac FranciscoandGitHub d180c80c34 [docs] add JS examples for double texting and human in the loop for cloud (#885) 2024-07-02 11:42:46 -04:00
William FHandGitHub ab8f3174e1 [Docs] Img in map reduce (#904) 2024-07-02 06:34:09 -07:00
William FHandGitHub 320a87e1b9 [Docs] use END instead of set_finish_point (#903) 2024-07-01 21:56:10 -07:00
William FHandGitHub 727e63c01e [Docs] Update notebooks to use START (#902) 2024-07-01 21:36:34 -07:00
267f5e5234 docs: add js examples for streaming (#871)
---------

Co-authored-by: vbarda <vadym@langchain.dev>
2024-07-01 21:22:47 -04:00
William FHandGitHub 99e592b474 [Docs] More links to langgraph.json (#901) 2024-07-01 14:07:30 -07:00
2cbfc1cbeb docs: fixed variable error (#896)
Co-authored-by: William FH <13333726+hinthornw@users.noreply.github.com>
2024-07-01 14:53:39 -04:00
00514955fa fix: change 'action' to 'tools' in graph visualization (#895)
Co-authored-by: afsu <saf@zjuici.com>
2024-07-01 10:20:48 -07:00
William FHandGitHub d7a04663da [Docs] Improve create_react_agent docstring (#899)
- Cleanup parameter formatting
- Add diagrams
- Walk through example execution
2024-07-01 10:20:20 -07:00
Nuno Campos 94300877ee 0.1.5 2024-07-01 18:15:44 +01:00
Nuno Campos dfdb4d00ea Mark cancelled as seen 2024-07-01 18:12:37 +01:00
yoogleandGitHub 3506be18a4 fix: typo (#898) 2024-07-01 11:36:53 -04:00
Vadym BardaandGitHub 7a68cc63c4 ci: update permissions (#886) 2024-06-29 08:47:58 -04:00
Isaac FranciscoandGitHub 281e66ed2e sdk-js: added cron functionality to match langgraph-api (#863)
* testing

* first draft

* working

* first draft

* fmt

* vadym comments
2024-06-28 16:26:22 -07:00
Isaac FranciscoandGitHub 1fe13cb009 sdk-py: added Cron search functionality (#804)
* first draft

* minor fix

* second draft

* reformat

* rename

* remove f-string

* fmt

* fmt

* fmt

* fmt

* fmt

* typing

* fmt

* fmt

* fmt

* pagination

* fmt

* fmt

* schema

* fmt

* fmt
2024-06-28 16:23:15 -07:00
Nuno Campos 5b6c021d6a cli 0.1.47 2024-06-28 14:05:06 -07:00
Nuno CamposandGitHub ca4ad62762 cli: Update messaging for langsmith api key (#884)
* cli: Update messaging for langsmith api key

* Adjust
2024-06-28 14:04:43 -07:00
Vadym BardaandGitHub d9bfe22f00 ci: remove grep check for previous tag (#881) 2024-06-28 14:00:08 -04:00
Vadym BardaandGitHub b8e6483770 ci: display previous tag during release job (#880) 2024-06-28 13:33:17 -04:00
Vadym BardaandGitHub 7e54aba3e7 ci: attempt regex fix (#878) 2024-06-28 12:22:24 -04:00
Vadym BardaandGitHub 6929afda8a langgraph: release 0.1.4 (#877) 2024-06-28 12:04:15 -04:00
Nuno Campos c326ad019d Skip bad pending sends 2024-06-28 08:59:26 -07:00
Vadym BardaandGitHub 2bf4a72d9d langgraph: release 0.1.3 (#876) 2024-06-28 11:51:58 -04:00
Isaac FranciscoandGitHub b066b6ffa1 speed up gifs (#870) 2024-06-28 11:32:50 -04:00
Vadym BardaandGitHub 36f5e49693 docs: update HITL how-tos (#875) 2024-06-28 10:44:17 -04:00
William FHandGitHub 19087ed6d8 [Docs] spelling (#869) 2024-06-27 19:39:18 -07:00
Isaac FranciscoandGitHub 0a2a64a830 sdk-js: adding assistant_id param for assistant_search functionality (#865)
* first draft

* wrong text
2024-06-27 13:18:51 -07:00
William FHandGitHub bccea30c2a [Docstrings] Add reminder to close connection (#864)
In AsyncSqliteSaver, otherwise your program will "hang".

Recommend using async with AsyncSqliteSaver.from_conn_str(...).
2024-06-27 12:59:26 -07:00
Nuno CamposandGitHub 5fa2172066 Update self_hosted.md (#862) 2024-06-27 12:32:35 -07:00
332 changed files with 37368 additions and 30946 deletions
+10 -10
View File
@@ -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
+6 -6
View File
@@ -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 -1
View File
@@ -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]
+8 -2
View File
@@ -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:
+19 -3
View File
@@ -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
+16 -9
View File
@@ -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))"
+28
View File
@@ -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
+11 -3
View File
@@ -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
+24 -16
View File
@@ -3,7 +3,6 @@
![Version](https://img.shields.io/pypi/v/langgraph)
[![Downloads](https://static.pepy.tech/badge/langgraph/month)](https://pepy.tech/project/langgraph)
[![Open Issues](https://img.shields.io/github/issues-raw/langchain-ai/langgraph)](https://github.com/langchain-ai/langgraph/issues)
[![](https://dcbadge.vercel.app/api/server/6adMQxSpJS?compact=true&style=flat)](https://discord.com/channels/1038097195422978059/1170024642245832774)
[![Docs](https://img.shields.io/badge/docs-latest-blue)](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 langchain_anthropic import ChatAnthropic
from langchain_core.tools import tool
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import END, 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]:
@@ -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>
+2
View File
@@ -1,2 +1,4 @@
*.ipynb
site/
docs/tutorials/**/*.png
docs/cloud/reference/sdk/js_ts_sdk_ref.md
+17 -3
View File
@@ -15,22 +15,35 @@ 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",
"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",
@@ -46,6 +59,7 @@ _MANUAL = {
"human_in_the_loop/time-travel.ipynb",
"human_in_the_loop/edit-graph-state.ipynb",
"human_in_the_loop/wait-user-input.ipynb",
"node-retries.ipynb",
],
"tutorials": [
"introduction.ipynb",
@@ -81,14 +95,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",
]
@@ -12,13 +12,17 @@ An assistant is a configured instance of a [`CompiledGraph`][compiledgraph]. It
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.
#### 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](https://langchain-ai.github.io/langgraph/concepts/low_level/#checkpointer).
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 <a href="../reference/api/api_ref.html#tag/threadscreate" target="_blank">API reference</a> for more details.
@@ -47,13 +51,13 @@ The LangGraph Cloud API offers several features to support complex agent archite
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.
- `values`: Stream the full state of the graph after each node 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) after each node is executed. 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 after each node is executed. 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/cloud_examples/stream_multiple.ipynb) for configuring multiple streaming modes at the same time.
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 <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.
@@ -63,12 +67,12 @@ There are many occasions where the graph cannot run completely autonomously. For
### 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:
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.
- `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
@@ -92,7 +96,7 @@ See the [how-to guide](../how-tos/cloud_examples/stateless_runs.ipynb) for creat
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.
See this [how-to guide](../how-tos/cloud_examples/webhooks.ipynb) to learn about how to use webhooks with LangGraph Cloud.
## Deployment
@@ -101,3 +105,7 @@ The LangGraph Cloud offers several features to support secure and robost deploym
### 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.
+28
View File
@@ -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.
![diagram](langgraph_cloud_architecture.png)
Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

+31 -5
View File
@@ -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`.
+146
View File
@@ -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, MessageGraph
model = ChatOpenAI(temperature=0)
graph_workflow = MessageGraph()
graph_workflow.add_node("agent", model)
graph_workflow.add_edge("agent", END)
graph_workflow.set_entry_point("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, 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.set_entry_point("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.set_entry_point("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)
Binary file not shown.

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.
+25 -7
View File
@@ -1,6 +1,9 @@
# 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.
!!! 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.
The final repo structure will look something like this:
@@ -19,6 +22,22 @@ 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.1.19,<0.2.0
langchain-core>=0.2.8,<0.3.0
langsmith>=0.1.63
orjson>=3.10.1
httpx>=0.27.0
tenacity>=8.3.0
uvicorn>=0.29.0
sse-starlette>=2.1.0
uvloop>=0.19.0
httptools>=0.6.1
jsonschema-rs>=0.18.0
croniter>=1.0.1
```
Example `requirements.txt` file:
```
langgraph
@@ -70,7 +89,7 @@ agent = 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:
```
@@ -101,6 +120,9 @@ Example `langgraph.json` file:
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
@@ -112,10 +134,6 @@ my-app/
|-- 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).
+22 -7
View File
@@ -20,6 +20,22 @@ 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.1.19,<0.2.0
langchain-core>=0.2.8,<0.3.0
langsmith>=0.1.63
orjson>=3.10.1
httpx>=0.27.0
tenacity>=8.3.0
uvicorn>=0.29.0
sse-starlette>=2.1.0
uvloop>=0.19.0
httptools>=0.6.1
jsonschema-rs>=0.18.0
croniter>=1.0.1
```
Example `pyproject.toml` file:
```toml
@@ -33,7 +49,7 @@ readme = "README.md"
[tool.poetry.dependencies]
python = ">=3.9.0,<3.13"
langgraph = "^0.1.0"
langgraph = "^0.1.7"
langchain-fireworks = "^0.1.3"
@@ -102,7 +118,7 @@ agent = 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:
@@ -133,6 +149,9 @@ Example `langgraph.json` file:
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
@@ -145,10 +164,6 @@ my-app/
└── pyproject.toml
```
## 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,99 @@
# 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).
=== "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();
```
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");
}
```
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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

@@ -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="whatever-your-deployment-url-is")
assistant_id = "agent"
thread = await client.threads.create()
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl:"whatever-your-deployment-url-is" });
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:
================================ Human Message =================================
what's the weather in sf?
================================== Ai Message ==================================
[{'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
================================= Tool Message =================================
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."}]
================================== Ai Message ==================================
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.
================================ Human Message =================================
what's the weather in nyc?
================================== Ai Message ==================================
[{'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
================================= Tool Message =================================
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}}"}]
================================== Ai Message ==================================
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,103 @@
# 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="whatever-your-deployment-url-is")
assistant_id = "agent"
thread = await client.threads.create()
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl:"whatever-your-deployment-url-is" });
const assistantId = "agent"
const thread = await client.threads.create();
```
## 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");
}
```
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,182 @@
# 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="whatever-your-deployment-url-is")
assistant_id = "agent"
thread = await client.threads.create()
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl:"whatever-your-deployment-url-is" });
const assistantId = "agent";
const thread = await client.threads.create();
```
## 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);
}
}
```
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}});
```
Output:
{'configurable': {'thread_id': '88d58d3f-4151-47a9-a8e0-e42fdd3527b8',
'thread_ts': '1ef3274b-a809-6913-8002-91536ce6554d'}}
### 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);
}
}
```
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,226 @@
# 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="whatever-your-deployment-url-is")
assistant_id = "agent"
thread = await client.threads.create()
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl:"whatever-your-deployment-url-is" });
const assistantId = agent;
const thread = await client.threads.create();
```
## 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);
}
}
```
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']);
```
Output:
['action']
To rerun from a state, we need to pass in the `checkpoint_id` into the config of the run like follows:
=== "Python"
```python
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id, # graph_id
input=None,
stream_mode="updates",
config={"configurable": {"thread_ts": state_to_replay['checkpoint_id']}}
):
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",
config: {"configurable": {"thread_ts": stateToReplay['checkpoint_id']}},
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
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'}
new_state = 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 newState = await client.threads.updateState(thread['thread_id'],{values:{"messages":[lastMessage]},checkpointId:stateToReplay['checkpoint_id']});
```
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",
config={"configurable": {"thread_ts": new_state['configurable']['thread_ts']}}
):
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",
config: {"configurable": {"thread_ts": newState['configurable']['thread_ts']}},
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
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,166 @@
# 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="whatever-your-deployment-url-is")
assistant_id = "agent"
thread = await client.threads.create()
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl:"whatever-your-deployment-url-is" });
const assistantId = "agent";
const thread = await client.threads.create();
```
## 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);
}
}
```
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"})
```
Output:
{'configurable': {'thread_id': '10d0ee61-db47-48fc-a58c-109a1e68cd73',
'thread_ts': '1ef32729-3cc3-6647-8002-14dcb621b46e'}}
### 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, # graph_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);
}
}
```
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}]}}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 MiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 721 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 MiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 MiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 MiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB

+24 -20
View File
@@ -11,46 +11,50 @@ 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)
## 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:
@@ -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="whatever-your-deployment-url-is")
assistant_id = "agent"
thread = await client.threads.create()
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl:"whatever-your-deployment-url-is" });
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_strategychrom="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:
================================ Human Message =================================
what's the weather in sf?
================================== Ai Message ==================================
[{'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
================================= Tool Message =================================
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 ..."}]
================================ Human Message =================================
what's the weather in nyc?
================================== Ai Message ==================================
[{'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
================================= Tool Message =================================
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."}]
================================== Ai Message ==================================
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'
+6 -4
View File
@@ -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:
![Using LangGraph Studio](./img/studio_input.gif)
<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="whatever-your-deployment-url-is")
assistant_id = "agent"
thread = await client.threads.create()
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl:"whatever-your-deployment-url-is" });
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:
================================ Human Message =================================
what's the weather in sf?
================================== Ai Message ==================================
[{'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
================================= Tool Message =================================
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."}]
================================== Ai Message ==================================
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="whatever-your-deployment-url-is")
assistant_id = "agent"
thread = await client.threads.create()
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl:"whatever-your-deployment-url-is" });
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:
================================ Human Message =================================
what's the weather in nyc?
================================== Ai Message ==================================
[{'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
================================= Tool Message =================================
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}}"}]
================================== Ai Message ==================================
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
+161
View File
@@ -0,0 +1,161 @@
# How to stream debug events
This guide covers how to stream debug events from your graph (`stream_mode="debug"`).
First let's set up our client and thread:
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url="whatever-your-deployment-url-is")
# create thread
thread = await client.threads.create()
print(thread)
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl:"whatever-your-deployment-url-is" });
// create thread
const thread = await client.threads.create();
console.log(thread)
```
Output:
{'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3',
'created_at': '2024-06-21T22:10:27.696862+00:00',
'updated_at': '2024-06-21T22:10:27.696862+00:00',
'metadata': {}}
Streaming debug events produces responses containing `type` and `timestamp` keys. Debug events correspond to different steps in the graph's execution (e.g. `task`, `task_result`, `checkpoint`).
=== "Python"
```python
# create input
input = {
"messages": [
{
"role": "human",
"content": "What's the weather in SF?",
}
]
}
# stream debug
async for chunk in client.runs.stream(
thread_id=thread["thread_id"],
assistant_id="agent",
input=input,
stream_mode="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 debug
const streamResponse = client.runs.stream(
thread["thread_id"],
"agent",
{
input,
streamMode: "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")
}
```
Output:
Receiving new event of type: metadata...
{'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a'}
Receiving new event of type: debug...
{'type': 'checkpoint', 'timestamp': '2024-06-21T22:11:09.256850+00:00', 'step': -1, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', 'thread_ts': '1ef301b2-9a2e-6bb6-bfff-8423bcf47561', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a'}, 'values': {'messages': []}, 'metadata': {'source': 'input', 'step': -1, 'writes': {'messages': [{'role': 'human', 'content': "What's the weather in SF?"}]}}}}
Receiving new event of type: debug...
{'type': 'checkpoint', 'timestamp': '2024-06-21T22:11:09.259723+00:00', 'step': 0, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', 'thread_ts': '1ef301b2-9a35-6c86-8000-f4a85315dbeb', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a'}, 'values': {'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '906529f7-fbf2-41c9-a28c-b1fe8f891e4e', 'example': False}]}, 'metadata': {'source': 'loop', 'step': 0, 'writes': None}}}
Receiving new event of type: debug...
{'type': 'task', 'timestamp': '2024-06-21T22:11:09.260021+00:00', 'step': 1, 'payload': {'id': '12ab1026-a551-5f96-9ad3-43424f094774', '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': '906529f7-fbf2-41c9-a28c-b1fe8f891e4e', 'example': False}], 'sleep': None}, 'triggers': ['start:agent']}}
Receiving new event of type: debug...
{'type': 'task_result', 'timestamp': '2024-06-21T22:11:09.267632+00:00', 'step': 1, 'payload': {'id': '12ab1026-a551-5f96-9ad3-43424f094774', '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-54bd965b-734a-4a0a-8d4d-840865054810', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]]]}}
Receiving new event of type: debug...
{'type': 'checkpoint', 'timestamp': '2024-06-21T22:11:09.268469+00:00', 'step': 1, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', 'thread_ts': '1ef301b2-9a4b-60ae-8001-dd378f965bf7', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a'}, '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': '906529f7-fbf2-41c9-a28c-b1fe8f891e4e', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-54bd965b-734a-4a0a-8d4d-840865054810', '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-54bd965b-734a-4a0a-8d4d-840865054810', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}}}}
Receiving new event of type: debug...
{'type': 'task', 'timestamp': '2024-06-21T22:11:09.268659+00:00', 'step': 2, 'payload': {'id': '494ad427-fe8d-5654-91e6-50495a2699f5', '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': '906529f7-fbf2-41c9-a28c-b1fe8f891e4e', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-54bd965b-734a-4a0a-8d4d-840865054810', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'sleep': None}, 'triggers': ['branch:agent:should_continue:tool']}}
Receiving new event of type: debug...
{'type': 'task_result', 'timestamp': '2024-06-21T22:11:09.272916+00:00', 'step': 2, 'payload': {'id': '494ad427-fe8d-5654-91e6-50495a2699f5', 'name': 'tool', 'result': [['messages', [{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '222ed3b8-450f-41cb-ac40-905def3c700a', 'tool_call_id': 'tool_call_id'}]]]}}
Receiving new event of type: debug...
{'type': 'checkpoint', 'timestamp': '2024-06-21T22:11:09.273113+00:00', 'step': 2, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', 'thread_ts': '1ef301b2-9a56-6832-8002-8ab17e662980', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a'}, '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': '906529f7-fbf2-41c9-a28c-b1fe8f891e4e', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-54bd965b-734a-4a0a-8d4d-840865054810', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '222ed3b8-450f-41cb-ac40-905def3c700a', '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': '222ed3b8-450f-41cb-ac40-905def3c700a', 'tool_call_id': 'tool_call_id'}]}}}}}
Receiving new event of type: debug...
{'type': 'task', 'timestamp': '2024-06-21T22:11:09.273192+00:00', 'step': 3, 'payload': {'id': '677de327-99b7-5d97-9bbd-0092abb62d46', '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': '906529f7-fbf2-41c9-a28c-b1fe8f891e4e', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-54bd965b-734a-4a0a-8d4d-840865054810', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '222ed3b8-450f-41cb-ac40-905def3c700a', 'tool_call_id': 'tool_call_id'}], 'sleep': None}, 'triggers': ['tool']}}
Receiving new event of type: debug...
{'type': 'task_result', 'timestamp': '2024-06-21T22:11:09.277262+00:00', 'step': 3, 'payload': {'id': '677de327-99b7-5d97-9bbd-0092abb62d46', '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-006e1758-b1ca-4c90-9ff3-d2e75b9ca9a7', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]]]}}
Receiving new event of type: debug...
{'type': 'checkpoint', 'timestamp': '2024-06-21T22:11:09.277519+00:00', 'step': 3, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', 'thread_ts': '1ef301b2-9a61-6462-8003-1316d9875b7f', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a'}, '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': '906529f7-fbf2-41c9-a28c-b1fe8f891e4e', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-54bd965b-734a-4a0a-8d4d-840865054810', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '222ed3b8-450f-41cb-ac40-905def3c700a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-006e1758-b1ca-4c90-9ff3-d2e75b9ca9a7', '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-006e1758-b1ca-4c90-9ff3-d2e75b9ca9a7', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}}}}
Receiving new event of type: end...
None
+298
View File
@@ -0,0 +1,298 @@
# 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.
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url="whatever-your-deployment-url-is")
# create thread
thread = await client.threads.create()
print(thread)
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl:"whatever-your-deployment-url-is" });
// create thread
const thread = await client.threads.create();
console.log(thread)
```
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': {}}
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")
}
```
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"
):
llm_response += chunk.data["data"]["chunk"]["content"]
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") {
llmResponse += chunk.data.data.chunk.content;
console.log(llmResponse);
}
}
```
Output:
b
be
beg
begi
begin
begine
beginen
beginend
+390
View File
@@ -0,0 +1,390 @@
# How to stream messages from your graph
LangGraph Cloud supports multiple streaming modes. The main ones are:
- `values`: This streaming mode streams back values of the graph. This is the **full state of the graph** after each node is called.
- `updates`: This streaming mode streams back updates to the graph. This is the **update to the state of the graph** after each node is called.
- `messages`: This streaming mode streams back messages - both complete messages (at the end of a node) as well as **tokens** for any messages generated inside a node. This mode is primarily meant for powering chat applications.
This guide covers `stream_mode="messages"`.
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]
```
Alternatively, you can use an instance or subclass of `from langgraph.graph import MessagesState` (`MessagesState` is equivalent to the implementation above).
> [!NOTE]
> LangGraph Cloud only supports hosting graphs written in Python at the moment.
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
First let's set up our client and thread:
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url="whatever-your-deployment-url-is")
# create thread
thread = await client.threads.create()
print(thread)
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl:"whatever-your-deployment-url-is" });
// create thread
const thread = await client.threads.create();
console.log(thread)
```
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': {}}
Let's also define a helper function for better formatting of the tool calls in messages
=== "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";
}
```
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));
}
}
```
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
--------------------------------------------------
+440
View File
@@ -0,0 +1,440 @@
# 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="whatever-your-deployment-url-is")
# create thread
thread = await client.threads.create()
print(thread)
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl:"whatever-your-deployment-url-is" });
// create thread
const thread = await client.threads.create();
console.log(thread)
```
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': {}}
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")
}
```
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
+119
View File
@@ -0,0 +1,119 @@
# How to stream state updates of your graph
LangGraph Cloud supports multiple streaming modes. The main ones are:
- `values`: This streaming mode streams back values of the graph. This is the **full state of the graph** after each node is called.
- `updates`: This streaming mode streams back updates to the graph. This is the **update to the state of the graph** after each node is called.
- `messages`: This streaming mode streams back messages - both complete messages (at the end of a node) as well as **tokens** for any messages generated inside a node. This mode is primarily meant for powering chat applications.
This guide covers `stream_mode="updates"`.
First let's set up our client and thread:
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url="whatever-your-deployment-url-is")
# create thread
thread = await client.threads.create()
print(thread)
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl:"whatever-your-deployment-url-is" });
// create thread
const thread = await client.threads.create();
console.log(thread)
```
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': {}}
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")
}
```
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
+190
View File
@@ -0,0 +1,190 @@
# How to stream full state of your graph
LangGraph Cloud supports multiple streaming modes. The main ones are:
- `values`: This streaming mode streams back values of the graph. This is the **full state of the graph** after each node is called.
- `updates`: This streaming mode streams back updates to the graph. This is the **update to the state of the graph** after each node is called.
- `messages`: This streaming mode streams back messages - both complete messages (at the end of a node) as well as **tokens** for any messages generated inside a node. This mode is primarily meant for powering chat applications.
This guide covers `stream_mode="values"`.
First let's set up our client and thread:
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url="whatever-your-deployment-url-is")
# create thread
thread = await client.threads.create()
print(thread)
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: "whatever-your-deployment-url-is" });
// create thread
const thread = await client.threads.create();
console.log(thread)
```
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': {}}
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")
}
```
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;
}
```
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': []}]}
+4 -2
View File
@@ -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:
![Using LangGraph Studio](./img/studio_usage.gif)
<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):
![LangGraph Studio](./img/studio_screenshot.png)
## 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).
+8 -4
View File
@@ -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:
![Using LangGraph Studio](./img/studio_threads.gif)
<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:
![Using LangGraph Studio](./img/studio_forks.gif)
<video controls allowfullscreen="true" poster="../img/studio_forks_poster.png">
<source src="../img/studio_forks.mp4" type="video/mp4">
</video>
+12 -5
View File
@@ -1,18 +1,25 @@
# 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.
![GIF](./how-tos/img/studio_input.gif)
<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.
@@ -31,5 +38,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.
+37 -39
View File
@@ -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,53 @@ 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.
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 +74,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 +88,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 +125,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.
![Langsmith Workflow](./img/cloud_deployment.png)
***If you have not deployed to LangGraph Cloud before:*** there will be a button that shows up saying Import from GitHub. Youll 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. Youll 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:
![Deployment before being filled out](./deployment/img/deployment_page.png)
@@ -140,7 +141,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 +158,7 @@ After your deployment is complete, your deployments page should look as follows:
![Deployed page](./deployment/img/deployed_page.png)
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 +182,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:
![Base URL of LangGraph deployment](./deployment/img/base_url.png)
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 +223,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.
+160 -3
View File
@@ -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",
+28 -2
View File
@@ -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, ...)`
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -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)
+65 -34
View File
@@ -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.
+26 -2
View File
@@ -22,8 +22,12 @@ These how-to guides show how to achieve that controllability.
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
@@ -43,16 +47,36 @@ 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)
## Prebuilt ReAct Agent
+21 -8
View File
@@ -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
+5 -1
View File
@@ -65,4 +65,8 @@ builder.add_conditional_edges("my_node", my_condition)
## Send
::: langgraph.constants.Send
::: langgraph.constants.Send
## RetryPolicy
::: langgraph.pregel.types.RetryPolicy
+10 -1
View File
@@ -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
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

+48 -23
View File
@@ -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
@@ -132,8 +131,12 @@ nav:
- Create map-reduce branches for parallel execution: how-tos/map-reduce.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
- Wait for user input: how-tos/human_in_the_loop/wait-user-input.ipynb
@@ -143,15 +146,28 @@ nav:
- 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
- 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
@@ -174,30 +190,34 @@ 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"
- 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 +229,9 @@ 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"
- Conceptual Guides:
- API Concepts: "cloud/concepts/api.md"
- Cloud Concepts: "cloud/concepts/cloud.md"
- Reference:
- API: "cloud/reference/api/api_ref.md"
- SDK:
@@ -226,6 +248,9 @@ markdown_extensions:
- def_list
- footnotes
- md_in_html
- pymdownx.superfences
- pymdownx.tabbed:
alternate_style: true
- toc:
permalink: true
- pymdownx.arithmatex:
+52 -8
View File
@@ -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 %}
+4
View File
@@ -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 %}
+9 -140
View File
@@ -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": {
+10 -143
View File
@@ -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": {
+16 -166
View File
@@ -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": {
File diff suppressed because one or more lines are too long
@@ -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": {
File diff suppressed because one or more lines are too long
@@ -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": {
@@ -26,10 +26,7 @@
"id": "0d30b6f7-3bec-4d9f-af50-43dfdc81ae6c",
"metadata": {},
"outputs": [],
"source": [
"# %%capture --no-stderr\n",
"# %pip install -U langgraph langchain langchain_openai"
]
"source": ["# %%capture --no-stderr\n# %pip install -U langgraph langchain langchain_openai"]
},
{
"cell_type": "code",
@@ -37,24 +34,7 @@
"id": "30c2f3de-c730-4aec-85a6-af2c2f058803",
"metadata": {},
"outputs": [],
"source": [
"import getpass\n",
"import os\n",
"\n",
"\n",
"def _set_if_undefined(var: str):\n",
" if not os.environ.get(var):\n",
" os.environ[var] = getpass.getpass(f\"Please provide your {var}\")\n",
"\n",
"\n",
"_set_if_undefined(\"OPENAI_API_KEY\")\n",
"_set_if_undefined(\"LANGCHAIN_API_KEY\")\n",
"\n",
"# Optional, add tracing in LangSmith.\n",
"# This will help you visualize and debug the control flow\n",
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
"os.environ[\"LANGCHAIN_PROJECT\"] = \"Agent Simulation Evaluation\""
]
"source": ["import getpass\nimport os\n\n\ndef _set_if_undefined(var: str):\n if not os.environ.get(var):\n os.environ[var] = getpass.getpass(f\"Please provide your {var}\")\n\n\n_set_if_undefined(\"OPENAI_API_KEY\")\n_set_if_undefined(\"LANGCHAIN_API_KEY\")\n\n# Optional, add tracing in LangSmith.\n# This will help you visualize and debug the control flow\nos.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\nos.environ[\"LANGCHAIN_PROJECT\"] = \"Agent Simulation Evaluation\""]
},
{
"cell_type": "markdown",
@@ -75,24 +55,7 @@
"id": "828479af-cf9c-4888-a365-599643a96b55",
"metadata": {},
"outputs": [],
"source": [
"from typing import List\n",
"\n",
"import openai\n",
"\n",
"\n",
"# This is flexible, but you can define your agent here, or call your agent API here.\n",
"def my_chat_bot(messages: List[dict]) -> dict:\n",
" system_message = {\n",
" \"role\": \"system\",\n",
" \"content\": \"You are a customer support agent for an airline.\",\n",
" }\n",
" messages = [system_message] + messages\n",
" completion = openai.chat.completions.create(\n",
" messages=messages, model=\"gpt-3.5-turbo\"\n",
" )\n",
" return completion.choices[0].message.model_dump()"
]
"source": ["from typing import List\n\nimport openai\n\n\n# This is flexible, but you can define your agent here, or call your agent API here.\ndef my_chat_bot(messages: List[dict]) -> dict:\n system_message = {\n \"role\": \"system\",\n \"content\": \"You are a customer support agent for an airline.\",\n }\n messages = [system_message] + messages\n completion = openai.chat.completions.create(\n messages=messages, model=\"gpt-3.5-turbo\"\n )\n return completion.choices[0].message.model_dump()"]
},
{
"cell_type": "code",
@@ -114,9 +77,7 @@
"output_type": "execute_result"
}
],
"source": [
"my_chat_bot([{\"role\": \"user\", \"content\": \"hi!\"}])"
]
"source": ["my_chat_bot([{\"role\": \"user\", \"content\": \"hi!\"}])"]
},
{
"cell_type": "markdown",
@@ -135,33 +96,7 @@
"id": "32c147df-7f90-4b0d-9a6b-671677020353",
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"system_prompt_template = \"\"\"You are a customer of an airline company. \\\n",
"You are interacting with a user who is a customer support person. \\\n",
"\n",
"{instructions}\n",
"\n",
"When you are finished with the conversation, respond with a single word 'FINISHED'\"\"\"\n",
"\n",
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\"system\", system_prompt_template),\n",
" MessagesPlaceholder(variable_name=\"messages\"),\n",
" ]\n",
")\n",
"instructions = \"\"\"Your name is Harrison. You are trying to get a refund for the trip you took to Alaska. \\\n",
"You want them to give you ALL the money back. \\\n",
"This trip happened 5 years ago.\"\"\"\n",
"\n",
"prompt = prompt.partial(name=\"Harrison\", instructions=instructions)\n",
"\n",
"model = ChatOpenAI()\n",
"\n",
"simulated_user = prompt | model"
]
"source": ["from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\nfrom langchain_openai import ChatOpenAI\n\nsystem_prompt_template = \"\"\"You are a customer of an airline company. \\\nYou are interacting with a user who is a customer support person. \\\n\n{instructions}\n\nWhen you are finished with the conversation, respond with a single word 'FINISHED'\"\"\"\n\nprompt = ChatPromptTemplate.from_messages(\n [\n (\"system\", system_prompt_template),\n MessagesPlaceholder(variable_name=\"messages\"),\n ]\n)\ninstructions = \"\"\"Your name is Harrison. You are trying to get a refund for the trip you took to Alaska. \\\nYou want them to give you ALL the money back. \\\nThis trip happened 5 years ago.\"\"\"\n\nprompt = prompt.partial(name=\"Harrison\", instructions=instructions)\n\nmodel = ChatOpenAI()\n\nsimulated_user = prompt | model"]
},
{
"cell_type": "code",
@@ -180,12 +115,7 @@
"output_type": "execute_result"
}
],
"source": [
"from langchain_core.messages import HumanMessage\n",
"\n",
"messages = [HumanMessage(content=\"Hi! How can I help you?\")]\n",
"simulated_user.invoke({\"messages\": messages})"
]
"source": ["from langchain_core.messages import HumanMessage\n\nmessages = [HumanMessage(content=\"Hi! How can I help you?\")]\nsimulated_user.invoke({\"messages\": messages})"]
},
{
"cell_type": "markdown",
@@ -223,19 +153,7 @@
"id": "69e2a3a3-40f3-4223-9136-113738440be9",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.adapters.openai import convert_message_to_dict\n",
"from langchain_core.messages import AIMessage\n",
"\n",
"\n",
"def chat_bot_node(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\"])"
]
"source": ["from langchain_community.adapters.openai import convert_message_to_dict\nfrom langchain_core.messages import AIMessage\n\n\ndef chat_bot_node(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\"])"]
},
{
"cell_type": "markdown",
@@ -251,25 +169,7 @@
"id": "7cad7527-ffa5-4c30-8585-b54a7a18bd98",
"metadata": {},
"outputs": [],
"source": [
"def _swap_roles(messages):\n",
" new_messages = []\n",
" for m in messages:\n",
" if isinstance(m, AIMessage):\n",
" new_messages.append(HumanMessage(content=m.content))\n",
" else:\n",
" new_messages.append(AIMessage(content=m.content))\n",
" return new_messages\n",
"\n",
"\n",
"def simulated_user_node(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)"
]
"source": ["def _swap_roles(messages):\n new_messages = []\n for m in messages:\n if isinstance(m, AIMessage):\n new_messages.append(HumanMessage(content=m.content))\n else:\n new_messages.append(AIMessage(content=m.content))\n return new_messages\n\n\ndef simulated_user_node(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)"]
},
{
"cell_type": "markdown",
@@ -292,15 +192,7 @@
"id": "28004fbf-a2f3-46b7-bde7-46c7adaf97fb",
"metadata": {},
"outputs": [],
"source": [
"def should_continue(messages):\n",
" if len(messages) > 6:\n",
" return \"end\"\n",
" elif messages[-1].content == \"FINISHED\":\n",
" return \"end\"\n",
" else:\n",
" return \"continue\""
]
"source": ["def should_continue(messages):\n if len(messages) > 6:\n return \"end\"\n elif messages[-1].content == \"FINISHED\":\n return \"end\"\n else:\n return \"continue\""]
},
{
"cell_type": "markdown",
@@ -318,29 +210,7 @@
"id": "0b597e4b-4cbb-4bbc-82e5-f7e31275964c",
"metadata": {},
"outputs": [],
"source": [
"from langgraph.graph import END, MessageGraph\n",
"\n",
"graph_builder = MessageGraph()\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",
"# simulated user\n",
"graph_builder.add_edge(\"chat_bot\", \"user\")\n",
"graph_builder.add_conditional_edges(\n",
" \"user\",\n",
" should_continue,\n",
" # If the finish criteria are met, we will stop the simulation,\n",
" # otherwise, the virtual user's message will be sent to your chat bot\n",
" {\n",
" \"end\": END,\n",
" \"continue\": \"chat_bot\",\n",
" },\n",
")\n",
"# The input will first go to your chat bot\n",
"graph_builder.set_entry_point(\"chat_bot\")\n",
"simulation = graph_builder.compile()"
]
"source": ["from langgraph.graph import END, MessageGraph, START\n\ngraph_builder = MessageGraph()\ngraph_builder.add_node(\"user\", simulated_user_node)\ngraph_builder.add_node(\"chat_bot\", chat_bot_node)\n# Every response from your chat bot will automatically go to the\n# simulated user\ngraph_builder.add_edge(\"chat_bot\", \"user\")\ngraph_builder.add_conditional_edges(\n \"user\",\n should_continue,\n # If the finish criteria are met, we will stop the simulation,\n # otherwise, the virtual user's message will be sent to your chat bot\n {\n \"end\": END,\n \"continue\": \"chat_bot\",\n },\n)\n# The input will first go to your chat bot\ngraph_builder.add_edge(START, \"chat_bot\")\nsimulation = graph_builder.compile()"]
},
{
"cell_type": "markdown",
@@ -381,13 +251,7 @@
]
}
],
"source": [
"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(\"----\")"
]
"source": ["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",
@@ -395,7 +259,7 @@
"id": "dde4f2b5-cfe8-4ff0-99ea-fe2c5fed70c0",
"metadata": {},
"outputs": [],
"source": []
"source": [""]
}
],
"metadata": {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 56 KiB

@@ -9,7 +9,7 @@ from langchain_core.runnables import chain as as_runnable
from langchain_openai import ChatOpenAI
from typing_extensions import TypedDict
from langgraph.graph import END, StateGraph
from langgraph.graph import END, StateGraph, START
def langchain_to_openai_messages(messages: List[BaseMessage]):
@@ -116,7 +116,7 @@ def create_chat_simulator(
should_continue or functools.partial(_should_continue, max_turns=max_turns),
)
# If your dataset has a 'leading question/input', then we route first to the assistant, otherwise, we let the user take the lead.
graph_builder.set_entry_point("assistant" if input_key is not None else "user")
graph_builder.add_edge(START, "assistant" if input_key is not None else "user")
return (
RunnableLambda(_prepare_example).bind(input_key=input_key)
+731 -2
View File
@@ -2,10 +2,739 @@
"cells": [
{
"cell_type": "markdown",
"id": "4c37bb65-6e2c-42e4-bfa7-9df10e2652a0",
"id": "d9d1a28b-c2a1-4246-b1c2-c58d6938f798",
"metadata": {},
"source": [
"This example has moved! Check out the [Customer Support Tutorial](../customer-support/customer-support.ipynb) for more information."
"# Customer Support\n",
"\n",
"Here, we show an example of building a customer support chatbot.\n",
"\n",
"This customer support chatbot interacts with SQL database to answer questions.\n",
"We will use a mock SQL database to get started: the [Chinook](https://www.sqlitetutorial.net/sqlite-sample-database/) database.\n",
"This database is about sales from a music store: what songs and album exists, customer orders, things like that.\n",
"\n",
"This chatbot has two different states: \n",
"1. Music: the user can inquire about different songs and albums present in the store\n",
"2. Account: the user can ask questions about their account\n",
"\n",
"Under the hood, this is handled by two separate agents. \n",
"Each has a specific prompt and tools related to their objective. \n",
"There is also a generic agent who is responsible for routing between these two agents as needed.\n",
"\n",
"Note: This is a very simple example! For a more complete tutorial on building a customer support bot, check out the [Customer Support Tutorial](../customer-support/customer-support.ipynb) for more information."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "35abc013-2613-4a49-a806-939dcf13ccf3",
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"%%capture --no-stderr\n",
"%pip install -U langgraph langchain-community langchain-openai scikit-learn"
]
},
{
"cell_type": "markdown",
"id": "9431e7f1-07fa-49d9-ac45-29613703dcc1",
"metadata": {},
"source": [
"## Load the data"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "3d1ef253-6b0c-4481-868c-e1fe84f2c8ff",
"metadata": {},
"outputs": [],
"source": [
"import requests\n",
"\n",
"url = \"https://storage.googleapis.com/benchmarks-artifacts/chinook/Chinook.db\"\n",
"response = requests.get(url)\n",
"\n",
"with open(\"Chinook.db\", \"wb\") as file:\n",
" file.write(response.content)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "61f7ef9c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['Album',\n",
" 'Artist',\n",
" 'Customer',\n",
" 'Employee',\n",
" 'Genre',\n",
" 'Invoice',\n",
" 'InvoiceLine',\n",
" 'MediaType',\n",
" 'Playlist',\n",
" 'PlaylistTrack',\n",
" 'Track']"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain_community.utilities import SQLDatabase\n",
"\n",
"db = SQLDatabase.from_uri(\"sqlite:///Chinook.db\")\n",
"db.get_usable_table_names()"
]
},
{
"cell_type": "markdown",
"id": "1cf668e4-8cb4-4de1-bc5e-c90284bf74bc",
"metadata": {},
"source": [
"## Load an LLM\n",
"\n",
"We will load a language model to use.\n",
"For this demo we will use OpenAI."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "d9ea4e80-30e6-4d46-b480-35f0be2fb055",
"metadata": {},
"outputs": [],
"source": [
"from langchain_openai import ChatOpenAI\n",
"\n",
"model = ChatOpenAI(temperature=0, streaming=True, model=\"gpt-4o\")"
]
},
{
"cell_type": "markdown",
"id": "73907422-7e05-431e-b06d-256c9ec1f6f6",
"metadata": {},
"source": [
"## Load Other Modules\n",
"\n",
"Load other modules we will use.\n",
"\n",
"All of the tools our agents will use will be custom tools. As such, we will use the `@tool` decorator to create custom tools.\n",
"\n",
"We will pass in messages to the agent, so we load `HumanMessage` and `SystemMessage`"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "ea958e9f-ab1f-49b5-bd85-16332055297c",
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.messages import HumanMessage, SystemMessage"
]
},
{
"cell_type": "markdown",
"id": "35271d4d-2a1c-41be-9359-a3a7c3fed3d9",
"metadata": {},
"source": [
"## Define the Customer Agent\n",
"\n",
"This agent is responsible for looking up customer information.\n",
"It will have a specific prompt as well a specific tool to look up information about that customer (after asking for their user id)."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "975b039a",
"metadata": {},
"outputs": [],
"source": [
"# This tool is given to the agent to look up information about a customer\n",
"def get_customer_info(customer_id: int):\n",
" \"\"\"Look up customer info given their ID. ALWAYS make sure you have the customer ID before invoking this.\"\"\"\n",
" return db.run(f\"SELECT * FROM Customer WHERE CustomerID = {customer_id};\")"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "1d5fa446",
"metadata": {},
"outputs": [],
"source": [
"customer_prompt = \"\"\"Your job is to help a user update their profile.\n",
"\n",
"You only have certain tools you can use. These tools require specific input. If you don't know the required input, then ask the user for it.\n",
"\n",
"If you are unable to help the user, you can \"\"\"\n",
"\n",
"\n",
"def get_customer_messages(messages):\n",
" return [SystemMessage(content=customer_prompt)] + messages\n",
"\n",
"\n",
"customer_chain = get_customer_messages | model.bind_tools([get_customer_info])"
]
},
{
"cell_type": "markdown",
"id": "904a9485-3857-458e-8b9d-33bc33842bc9",
"metadata": {},
"source": [
"## Define the Music Agent\n",
"\n",
"This agent is responsible for figuring out information about music. To do that, we will create a prompt and various tools for looking up information about music\n",
"\n",
"First, we will create indexes for looking up artists and track names.\n",
"This will allow us to look up artists and tracks without having to spell their names exactly right."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "a8604a3b-b484-4b2b-a914-4236cb98c524",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.vectorstores import SKLearnVectorStore\n",
"from langchain_openai import OpenAIEmbeddings\n",
"\n",
"artists = db._execute(\"select * from Artist\")\n",
"songs = db._execute(\"select * from Track\")\n",
"artist_retriever = SKLearnVectorStore.from_texts(\n",
" [a[\"Name\"] for a in artists], OpenAIEmbeddings(), metadatas=artists\n",
").as_retriever()\n",
"song_retriever = SKLearnVectorStore.from_texts(\n",
" [a[\"Name\"] for a in songs], OpenAIEmbeddings(), metadatas=songs\n",
").as_retriever()"
]
},
{
"cell_type": "markdown",
"id": "ac7eb264-c572-4925-ad55-a1d52a18b1c0",
"metadata": {},
"source": [
"First, let's create a tool for getting albums by artist."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "0a2a2b74",
"metadata": {},
"outputs": [],
"source": [
"def get_albums_by_artist(artist):\n",
" \"\"\"Get albums by an artist (or similar artists).\"\"\"\n",
" docs = artist_retriever.get_relevant_documents(artist)\n",
" artist_ids = \", \".join([str(d.metadata[\"ArtistId\"]) for d in docs])\n",
" return db.run(\n",
" f\"SELECT Title, Name FROM Album LEFT JOIN Artist ON Album.ArtistId = Artist.ArtistId WHERE Album.ArtistId in ({artist_ids});\",\n",
" include_columns=True,\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "45e85066-f2fc-490e-992d-cd66c9cd6486",
"metadata": {},
"source": [
"Next, lets create a tool for getting tracks by an artist"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "da533f50",
"metadata": {},
"outputs": [],
"source": [
"def get_tracks_by_artist(artist):\n",
" \"\"\"Get songs by an artist (or similar artists).\"\"\"\n",
" docs = artist_retriever.invoke(artist)\n",
" artist_ids = \", \".join([str(d.metadata[\"ArtistId\"]) for d in docs])\n",
" return db.run(\n",
" f\"SELECT Track.Name as SongName, Artist.Name as ArtistName FROM Album LEFT JOIN Artist ON Album.ArtistId = Artist.ArtistId LEFT JOIN Track ON Track.AlbumId = Album.AlbumId WHERE Album.ArtistId in ({artist_ids});\",\n",
" include_columns=True,\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "bb0e50ab-b059-427c-924b-f8072d8db23c",
"metadata": {},
"source": [
"Finally, let's create a tool for looking up songs by their name."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "b3c07010",
"metadata": {},
"outputs": [],
"source": [
"def check_for_songs(song_title):\n",
" \"\"\"Check if a song exists by its name.\"\"\"\n",
" return song_retriever.invoke(song_title)"
]
},
{
"cell_type": "markdown",
"id": "88388ff8-38b5-4e4e-a24d-de8c3670bd2b",
"metadata": {},
"source": [
"Create the chain to call the relevant tools"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "72a14d5c",
"metadata": {},
"outputs": [],
"source": [
"song_system_message = \"\"\"Your job is to help a customer find any songs they are looking for. \n",
"\n",
"You only have certain tools you can use. If a customer asks you to look something up that you don't know how, politely tell them what you can help with.\n",
"\n",
"When looking up artists and songs, sometimes the artist/song will not be found. In that case, the tools will return information \\\n",
"on similar songs and artists. This is intentional, it is not the tool messing up.\"\"\"\n",
"\n",
"\n",
"def get_song_messages(messages):\n",
" return [SystemMessage(content=song_system_message)] + messages\n",
"\n",
"\n",
"song_recc_chain = get_song_messages | model.bind_tools(\n",
" [get_albums_by_artist, get_tracks_by_artist, check_for_songs]\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "cff15eb0-62c7-451d-a5f9-4576b24c879e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_aXa9rSRXvTCJabrMY6AqkSV8', 'function': {'arguments': '{\"artist\":\"Amy Winehouse\"}', 'name': 'get_tracks_by_artist'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_ce0793330f'}, id='run-60633269-fd02-43e8-b434-28e3b8b69fb1-0', tool_calls=[{'name': 'get_tracks_by_artist', 'args': {'artist': 'Amy Winehouse'}, 'id': 'call_aXa9rSRXvTCJabrMY6AqkSV8'}])"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"msgs = [HumanMessage(content=\"hi! can you help me find songs by amy whinehouse?\")]\n",
"song_recc_chain.invoke(msgs)"
]
},
{
"cell_type": "markdown",
"id": "0a42c293-0816-4f3c-b4a3-5b9f3a0665d1",
"metadata": {},
"source": [
"## Define the Generic Agent\n",
"\n",
"We now define a generic agent that is responsible for handling initial inquiries and routing to the right sub agent."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "73e74268",
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.messages import AIMessage, HumanMessage, SystemMessage\n",
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
"\n",
"\n",
"class Router(BaseModel):\n",
" \"\"\"Call this if you are able to route the user to the appropriate representative.\"\"\"\n",
"\n",
" choice: str = Field(description=\"should be one of: music, customer\")\n",
"\n",
"\n",
"system_message = \"\"\"Your job is to help as a customer service representative for a music store.\n",
"\n",
"You should interact politely with customers to try to figure out how you can help. You can help in a few ways:\n",
"\n",
"- Updating user information: if a customer wants to update the information in the user database. Call the router with `customer`\n",
"- Recommending music: if a customer wants to find some music or information about music. Call the router with `music`\n",
"\n",
"If the user is asking or wants to ask about updating or accessing their information, send them to that route.\n",
"If the user is asking or wants to ask about music, send them to that route.\n",
"Otherwise, respond.\"\"\"\n",
"\n",
"\n",
"def get_messages(messages):\n",
" return [SystemMessage(content=system_message)] + messages"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "ddf27314",
"metadata": {},
"outputs": [],
"source": [
"chain = get_messages | model.bind_tools([Router])"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "3c896f34",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_0aaFPPCWDiAoPyXQX2PS8TcJ', 'function': {'arguments': '{\"choice\":\"music\"}', 'name': 'Router'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_ce0793330f'}, id='run-73d51d75-b7c5-49fe-b558-105ede7c75d1-0', tool_calls=[{'name': 'Router', 'args': {'choice': 'music'}, 'id': 'call_0aaFPPCWDiAoPyXQX2PS8TcJ'}])"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"msgs = [HumanMessage(content=\"hi! can you help me find a good song?\")]\n",
"chain.invoke(msgs)"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "40d86f59",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_Okla9DfMHIPs5TslS6KPaoBA', 'function': {'arguments': '{\"choice\":\"customer\"}', 'name': 'Router'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_4008e3b719'}, id='run-0d82d4f8-f4eb-4b16-add8-3e7fdffd6332-0', tool_calls=[{'name': 'Router', 'args': {'choice': 'customer'}, 'id': 'call_Okla9DfMHIPs5TslS6KPaoBA'}])"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"msgs = [HumanMessage(content=\"hi! what's the email you have for me?\")]\n",
"chain.invoke(msgs)"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "bd6ddd8b-7500-46a7-811d-3bcb937bda51",
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.messages import AIMessage\n",
"\n",
"\n",
"def add_name(message, name):\n",
" _dict = message.dict()\n",
" _dict[\"name\"] = name\n",
" return AIMessage(**_dict)"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "27494de5-8345-4c23-bc0e-81e0dd5d47d8",
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"\n",
"from langgraph.graph import END, START\n",
"\n",
"\n",
"def _get_last_ai_message(messages):\n",
" for m in messages[::-1]:\n",
" if isinstance(m, AIMessage):\n",
" return m\n",
" return None\n",
"\n",
"\n",
"def _is_tool_call(msg):\n",
" return hasattr(msg, \"additional_kwargs\") and \"tool_calls\" in msg.additional_kwargs\n",
"\n",
"\n",
"def _route(messages):\n",
" last_message = messages[-1]\n",
" if isinstance(last_message, AIMessage):\n",
" if not last_message.tool_calls:\n",
" return END\n",
" else:\n",
" if last_message.name == \"general\":\n",
" if len(last_message.tool_calls) > 1:\n",
" raise ValueError(\"Too many tools\")\n",
" return last_message.tool_calls[0][\"args\"][\"choice\"]\n",
" else:\n",
" return \"tools\"\n",
" last_m = _get_last_ai_message(messages)\n",
" if last_m is None:\n",
" return \"general\"\n",
" if last_m.name == \"music\":\n",
" return \"music\"\n",
" elif last_m.name == \"customer\":\n",
" return \"customer\"\n",
" else:\n",
" return \"general\""
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "8aec704a-46fe-4fb3-bdee-11c3bbffc370",
"metadata": {},
"outputs": [],
"source": [
"from langgraph.prebuilt import ToolNode\n",
"\n",
"tools = [get_albums_by_artist, get_tracks_by_artist, check_for_songs, get_customer_info]\n",
"tool_node = ToolNode(tools)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "4d5b75c6-73e0-4922-a765-a15be63f869e",
"metadata": {},
"outputs": [],
"source": [
"def _filter_out_routes(messages):\n",
" ms = []\n",
" for m in messages:\n",
" if _is_tool_call(m):\n",
" if m.name == \"general\":\n",
" continue\n",
" ms.append(m)\n",
" return ms"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "fd4dbf98-dbb3-411a-bad6-2bb334072aaf",
"metadata": {},
"outputs": [],
"source": [
"from functools import partial\n",
"\n",
"general_node = _filter_out_routes | chain | partial(add_name, name=\"general\")\n",
"music_node = _filter_out_routes | song_recc_chain | partial(add_name, name=\"music\")\n",
"customer_node = _filter_out_routes | customer_chain | partial(add_name, name=\"customer\")"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "dcade924",
"metadata": {},
"outputs": [],
"source": [
"from langgraph.checkpoint.memory import MemorySaver\n",
"\n",
"from langgraph.graph import MessageGraph\n",
"\n",
"memory = MemorySaver()\n",
"graph = MessageGraph()\n",
"nodes = {\n",
" \"general\": \"general\",\n",
" \"music\": \"music\",\n",
" END: END,\n",
" \"tools\": \"tools\",\n",
" \"customer\": \"customer\",\n",
"}\n",
"# Define a new graph\n",
"workflow = MessageGraph()\n",
"workflow.add_node(\"general\", general_node)\n",
"workflow.add_node(\"music\", music_node)\n",
"workflow.add_node(\"customer\", customer_node)\n",
"workflow.add_node(\"tools\", tool_node)\n",
"workflow.add_conditional_edges(\"general\", _route, nodes)\n",
"workflow.add_conditional_edges(\"tools\", _route, nodes)\n",
"workflow.add_conditional_edges(\"music\", _route, nodes)\n",
"workflow.add_conditional_edges(\"customer\", _route, nodes)\n",
"workflow.add_conditional_edges(START, _route, nodes)\n",
"graph = workflow.compile()"
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "ac65d6d2",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"User (q/Q to quit): what music do you have?\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Output from node 'general':\n",
"---\n",
"content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_iste6NuKvZou8O9QudOectOU', 'function': {'arguments': '{\"choice\":\"music\"}', 'name': 'Router'}, 'type': 'function'}]} response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_4008e3b719'} name='general' id='run-9eb940ff-6592-43ae-aa34-22c3d630ac65-0' tool_calls=[{'name': 'Router', 'args': {'choice': 'music'}, 'id': 'call_iste6NuKvZou8O9QudOectOU'}]\n",
"\n",
"---\n",
"\n",
"Output from node 'music':\n",
"---\n",
"content=\"I can help you find songs and albums by specific artists, or check if a particular song exists. Just let me know the name of the artist or song you're interested in!\" response_metadata={'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_4008e3b719'} name='music' id='run-91f560e7-ffa5-437f-afda-27490cbd1efe-0'\n",
"\n",
"---\n",
"\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"User (q/Q to quit): how about shakira?\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Output from node 'general':\n",
"---\n",
"content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_fH4oKyA3U9aQy3p31MYXv2VP', 'function': {'arguments': '{\"choice\":\"music\"}', 'name': 'Router'}, 'type': 'function'}]} response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_d576307f90'} name='general' id='run-6f2eee09-9e3e-4011-a045-196cc5baa1ee-0' tool_calls=[{'name': 'Router', 'args': {'choice': 'music'}, 'id': 'call_fH4oKyA3U9aQy3p31MYXv2VP'}]\n",
"\n",
"---\n",
"\n",
"Output from node 'music':\n",
"---\n",
"content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_qivZqsI8zQAqSDP2jsvHyR7T', 'function': {'arguments': '{\"artist\": \"Shakira\"}', 'name': 'get_albums_by_artist'}, 'type': 'function'}, {'index': 1, 'id': 'call_GER0B3vlAjxcvYOYq1NGlV4r', 'function': {'arguments': '{\"artist\": \"Shakira\"}', 'name': 'get_tracks_by_artist'}, 'type': 'function'}]} response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_ce0793330f'} name='music' id='run-38e2eca8-771c-48a2-83be-023fd17fc6f6-0' tool_calls=[{'name': 'get_albums_by_artist', 'args': {'artist': 'Shakira'}, 'id': 'call_qivZqsI8zQAqSDP2jsvHyR7T'}, {'name': 'get_tracks_by_artist', 'args': {'artist': 'Shakira'}, 'id': 'call_GER0B3vlAjxcvYOYq1NGlV4r'}]\n",
"\n",
"---\n",
"\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/wfh/code/lc/langchain/libs/core/langchain_core/_api/deprecation.py:139: LangChainDeprecationWarning: The method `BaseRetriever.get_relevant_documents` was deprecated in langchain-core 0.1.46 and will be removed in 0.3.0. Use invoke instead.\n",
" warn_deprecated(\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Output from node 'tools':\n",
"---\n",
"[ToolMessage(content=\"[{'Title': 'Supernatural', 'Name': 'Santana'}, {'Title': 'Santana - As Years Go By', 'Name': 'Santana'}, {'Title': 'Santana Live', 'Name': 'Santana'}, {'Title': 'Lulu Santos - RCA 100 Anos De Música - Álbum 01', 'Name': 'Lulu Santos'}, {'Title': 'Lulu Santos - RCA 100 Anos De Música - Álbum 02', 'Name': 'Lulu Santos'}]\", name='get_albums_by_artist', id='14ad12f3-afa1-4375-a89d-e878babf2d95', tool_call_id='call_qivZqsI8zQAqSDP2jsvHyR7T'), ToolMessage(content='[{\\'SongName\\': \\'(Da Le) Yaleo\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Love Of My Life\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Put Your Lights On\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Africa Bamba\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Smooth\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Do You Like The Way\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Maria Maria\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Migra\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Corazon Espinado\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Wishing It Was\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'El Farol\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Primavera\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'The Calling\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Jingo\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'El Corazon Manda\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'La Puesta Del Sol\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Persuasion\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'As The Years Go by\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Soul Sacrifice\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Fried Neckbones And Home Fries\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Santana Jam\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Evil Ways\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \"We\\'ve Got To Get Together/Jingo\", \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Rock Me\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \"Just Ain\\'t Good Enough\", \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Funky Piano\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'The Way You Do To Mer\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Assim Caminha A Humanidade\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Um Pro Outro\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Casa\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Condição\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Satisfação\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Brumário\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Sábado À Noite\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'A Cura\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Atrás Do Trio Elétrico\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Tudo Bem\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Toda Forma De Amor\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Sereia\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Se Você Pensa\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Lá Vem O Sol (Here Comes The Sun)\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Honolulu\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Dancin´Days\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Aviso Aos Navegantes\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Hyperconectividade\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'O Descobridor Dos Sete Mares\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Um Certo Alguém\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Fullgás\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Aquilo\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Senta A Pua\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Ro-Que-Se-Da-Ne\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Tudo Igual\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Fogo De Palha\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Assaltaram A Gramática\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'O Último Romântico (Ao Vivo)\\', \\'ArtistName\\': \\'Lulu Santos\\'}]', name='get_tracks_by_artist', id='4ddcebc3-8e4e-42d4-8ae4-3ce3a62b548c', tool_call_id='call_GER0B3vlAjxcvYOYq1NGlV4r')]\n",
"\n",
"---\n",
"\n",
"Output from node 'music':\n",
"---\n",
"content=\"It seems I couldn't find specific albums or songs by Shakira, but I did find some related artists and their works. Here are some albums and songs by Santana and Lulu Santos:\\n\\n### Albums:\\n- **Santana:**\\n - Supernatural\\n - Santana - As Years Go By\\n - Santana Live\\n\\n- **Lulu Santos:**\\n - Lulu Santos - RCA 100 Anos De Música - Álbum 01\\n - Lulu Santos - RCA 100 Anos De Música - Álbum 02\\n\\n### Songs:\\n- **Santana:**\\n - (Da Le) Yaleo\\n - Love Of My Life\\n - Put Your Lights On\\n - Africa Bamba\\n - Smooth\\n - Maria Maria\\n - Corazon Espinado\\n - Jingo\\n - Evil Ways\\n\\n- **Lulu Santos:**\\n - Assim Caminha A Humanidade\\n - Um Pro Outro\\n - Casa\\n - Condição\\n - Satisfação\\n - A Cura\\n - Atrás Do Trio Elétrico\\n - Toda Forma De Amor\\n - Sereia\\n - Se Você Pensa\\n\\nIf you have any other specific artists or songs in mind, feel free to let me know!\" response_metadata={'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_d576307f90'} name='music' id='run-3d90d7cc-e7f7-48dc-bffd-00765c3f5d13-0'\n",
"\n",
"---\n",
"\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"User (q/Q to quit): hm cool\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Output from node 'general':\n",
"---\n",
"content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_Nc5d0TWNbpnVJFeYJdQFuFGd', 'function': {'arguments': '{\"choice\":\"music\"}', 'name': 'Router'}, 'type': 'function'}]} response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_4008e3b719'} name='general' id='run-19a3588e-f04c-48db-a54f-95d6b930ee3e-0' tool_calls=[{'name': 'Router', 'args': {'choice': 'music'}, 'id': 'call_Nc5d0TWNbpnVJFeYJdQFuFGd'}]\n",
"\n",
"---\n",
"\n",
"Output from node 'music':\n",
"---\n",
"content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_weMHkgu3GYaMwXZM6hnCZd0z', 'function': {'arguments': '{\"artist\": \"Shakira\"}', 'name': 'get_albums_by_artist'}, 'type': 'function'}, {'index': 1, 'id': 'call_rXlVbPiEHbt10CNDUJ5GA2ZQ', 'function': {'arguments': '{\"artist\": \"Shakira\"}', 'name': 'get_tracks_by_artist'}, 'type': 'function'}]} response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_ce0793330f'} name='music' id='run-71bf8550-b5a8-423f-b463-83d9ff51391b-0' tool_calls=[{'name': 'get_albums_by_artist', 'args': {'artist': 'Shakira'}, 'id': 'call_weMHkgu3GYaMwXZM6hnCZd0z'}, {'name': 'get_tracks_by_artist', 'args': {'artist': 'Shakira'}, 'id': 'call_rXlVbPiEHbt10CNDUJ5GA2ZQ'}]\n",
"\n",
"---\n",
"\n",
"Output from node 'tools':\n",
"---\n",
"[ToolMessage(content=\"[{'Title': 'Supernatural', 'Name': 'Santana'}, {'Title': 'Santana - As Years Go By', 'Name': 'Santana'}, {'Title': 'Santana Live', 'Name': 'Santana'}, {'Title': 'Lulu Santos - RCA 100 Anos De Música - Álbum 01', 'Name': 'Lulu Santos'}, {'Title': 'Lulu Santos - RCA 100 Anos De Música - Álbum 02', 'Name': 'Lulu Santos'}]\", name='get_albums_by_artist', id='52ada997-83f6-4500-b0a7-1104d4d38eb9', tool_call_id='call_weMHkgu3GYaMwXZM6hnCZd0z'), ToolMessage(content='[{\\'SongName\\': \\'(Da Le) Yaleo\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Love Of My Life\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Put Your Lights On\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Africa Bamba\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Smooth\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Do You Like The Way\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Maria Maria\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Migra\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Corazon Espinado\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Wishing It Was\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'El Farol\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Primavera\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'The Calling\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Jingo\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'El Corazon Manda\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'La Puesta Del Sol\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Persuasion\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'As The Years Go by\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Soul Sacrifice\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Fried Neckbones And Home Fries\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Santana Jam\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Evil Ways\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \"We\\'ve Got To Get Together/Jingo\", \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Rock Me\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \"Just Ain\\'t Good Enough\", \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Funky Piano\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'The Way You Do To Mer\\', \\'ArtistName\\': \\'Santana\\'}, {\\'SongName\\': \\'Assim Caminha A Humanidade\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Um Pro Outro\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Casa\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Condição\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Satisfação\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Brumário\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Sábado À Noite\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'A Cura\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Atrás Do Trio Elétrico\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Tudo Bem\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Toda Forma De Amor\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Sereia\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Se Você Pensa\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Lá Vem O Sol (Here Comes The Sun)\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Honolulu\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Dancin´Days\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Aviso Aos Navegantes\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Hyperconectividade\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'O Descobridor Dos Sete Mares\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Um Certo Alguém\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Fullgás\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Aquilo\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Senta A Pua\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Ro-Que-Se-Da-Ne\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Tudo Igual\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Fogo De Palha\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'Assaltaram A Gramática\\', \\'ArtistName\\': \\'Lulu Santos\\'}, {\\'SongName\\': \\'O Último Romântico (Ao Vivo)\\', \\'ArtistName\\': \\'Lulu Santos\\'}]', name='get_tracks_by_artist', id='7583e077-2fb4-44bf-bb00-b89d599ae84d', tool_call_id='call_rXlVbPiEHbt10CNDUJ5GA2ZQ')]\n",
"\n",
"---\n",
"\n",
"Output from node 'music':\n",
"---\n",
"content=\"It seems like I couldn't find specific albums or songs by Shakira. However, I did find some related artists and their works. Here are some albums and songs by Santana and Lulu Santos:\\n\\n### Albums:\\n1. **Santana**\\n - Supernatural\\n - Santana - As Years Go By\\n - Santana Live\\n\\n2. **Lulu Santos**\\n - RCA 100 Anos De Música - Álbum 01\\n - RCA 100 Anos De Música - Álbum 02\\n\\n### Songs:\\n1. **Santana**\\n - (Da Le) Yaleo\\n - Love Of My Life\\n - Put Your Lights On\\n - Africa Bamba\\n - Smooth\\n - Maria Maria\\n - Corazon Espinado\\n - Jingo\\n - Evil Ways\\n\\n2. **Lulu Santos**\\n - Assim Caminha A Humanidade\\n - Um Pro Outro\\n - Casa\\n - Condição\\n - Satisfação\\n - A Cura\\n - Atrás Do Trio Elétrico\\n - Toda Forma De Amor\\n - Sereia\\n\\nIf you have any other artists or songs in mind, feel free to let me know!\" response_metadata={'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_d576307f90'} name='music' id='run-2418b45b-2762-49ca-b166-a0fee309ed9e-0'\n",
"\n",
"---\n",
"\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"User (q/Q to quit): q\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"AI: Byebye\n"
]
}
],
"source": [
"import uuid\n",
"\n",
"from langchain_core.messages import HumanMessage\n",
"\n",
"from langgraph.graph.graph import START\n",
"\n",
"history = []\n",
"while True:\n",
" user = input(\"User (q/Q to quit): \")\n",
" if user in {\"q\", \"Q\"}:\n",
" print(\"AI: Byebye\")\n",
" break\n",
" history.append(HumanMessage(content=user))\n",
" async for output in graph.astream(history):\n",
" for key, value in output.items():\n",
" print(f\"Output from node '{key}':\")\n",
" print(\"---\")\n",
" print(value)\n",
" print(\"\\n---\\n\")"
]
}
],
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 10 KiB

@@ -176,10 +176,10 @@
"metadata": {},
"outputs": [],
"source": [
"from langgraph.checkpoint.sqlite import SqliteSaver\n",
"from langgraph.checkpoint.memory import MemorySaver\n",
"from langgraph.graph import START, MessageGraph\n",
"\n",
"memory = SqliteSaver.from_conn_string(\":memory:\")\n",
"memory = MemorySaver()\n",
"workflow = MessageGraph()\n",
"workflow.add_node(\"info\", chain)\n",
"workflow.add_node(\"prompt\", prompt_gen_chain)\n",
+15 -5
View File
@@ -20,9 +20,10 @@
"outputs": [],
"source": [
"from langgraph_sdk import get_client\n",
"\n",
"client = get_client()\n",
"assistants = await client.assistants.search()\n",
"assistants = [a for a in assistants if not a['config']]\n",
"assistants = [a for a in assistants if not a[\"config\"]]\n",
"assistant = assistants[0]\n",
"thread = await client.threads.create()"
]
@@ -43,7 +44,12 @@
"outputs": [],
"source": [
"# This schedules a job to run at 15:27 (3:27PM) every day\n",
"cron_1 = await client.crons.create_for_thread(thread['thread_id'],assistant['assistant_id'],schedule=\"27 15 * * *\",input={'messages':[{\"role\":\"user\",\"content\":\"What time is it?\"}]})"
"cron_1 = await client.crons.create_for_thread(\n",
" thread[\"thread_id\"],\n",
" assistant[\"assistant_id\"],\n",
" schedule=\"27 15 * * *\",\n",
" input={\"messages\": [{\"role\": \"user\", \"content\": \"What time is it?\"}]},\n",
")"
]
},
{
@@ -59,7 +65,7 @@
"metadata": {},
"outputs": [],
"source": [
"await client.crons.delete(cron_1['cron_id'])"
"await client.crons.delete(cron_1[\"cron_id\"])"
]
},
{
@@ -78,7 +84,11 @@
"outputs": [],
"source": [
"# This schedules a job to run at 15:27 (3:27PM) every day\n",
"cron_2 = await client.crons.create(assistant['assistant_id'],schedule=\"27 15 * * *\",input={'messages':[{\"role\":\"user\",\"content\":\"What time is it?\"}]})"
"cron_2 = await client.crons.create(\n",
" assistant[\"assistant_id\"],\n",
" schedule=\"27 15 * * *\",\n",
" input={\"messages\": [{\"role\": \"user\", \"content\": \"What time is it?\"}]},\n",
")"
]
},
{
@@ -94,7 +104,7 @@
"metadata": {},
"outputs": [],
"source": [
"await client.crons.delete(cron_2['cron_id'])"
"await client.crons.delete(cron_2[\"cron_id\"])"
]
}
],
@@ -1,190 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Enqueue\n",
"\n",
"This notebook assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](https://langchain-ai.github.io/langgraph/cloud/concepts/#double-texting).\n",
"\n",
"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.\n",
"\n",
"First, let's import our required packages and instantiate our client, assistant, and thread."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.messages import convert_to_messages\n",
"from langgraph_sdk import get_client"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"client = get_client()\n",
"assistant_id = \"agent\"\n",
"thread = await client.threads.create()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# this run will be interrupted\n",
"first_run = await client.runs.create(\n",
" thread[\"thread_id\"],\n",
" assistant_id,\n",
" input={\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf?\"}]},\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"second_run = await client.runs.create(\n",
" thread[\"thread_id\"],\n",
" assistant_id,\n",
" input={\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in nyc?\"}]},\n",
" multitask_strategy=\"enqueue\",\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Verify that the thread has data from both runs"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# wait until the second run completes\n",
"await client.runs.join(thread[\"thread_id\"], second_run[\"run_id\"])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"state = await client.threads.get_state(thread[\"thread_id\"])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"================================\u001b[1m Human Message \u001b[0m=================================\n",
"\n",
"what's the weather in sf?\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"[{'id': 'toolu_01Dez1sJre4oA2Y7NsKJV6VT', 'input': {'query': 'weather in san francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n",
"Tool Calls:\n",
" tavily_search_results_json (toolu_01Dez1sJre4oA2Y7NsKJV6VT)\n",
" Call ID: toolu_01Dez1sJre4oA2Y7NsKJV6VT\n",
" Args:\n",
" query: weather in san francisco\n",
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
"Name: tavily_search_results_json\n",
"\n",
"[{\"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.\"}]\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"According to AccuWeather, the current weather conditions in San Francisco are:\n",
"\n",
"Temperature: 57°F (14°C)\n",
"Conditions: Mostly Sunny\n",
"Wind: WSW 10 mph\n",
"Humidity: 72%\n",
"\n",
"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.\n",
"\n",
"Some key details from the AccuWeather forecast:\n",
"\n",
"Today: Mostly sunny, high of 62°F (17°C)\n",
"Tonight: Partly cloudy, low of 49°F (9°C) \n",
"Tomorrow: Partly sunny, high of 59°F (15°C)\n",
"Saturday: Mostly sunny, high of 64°F (18°C)\n",
"Sunday: Partly sunny, high of 61°F (16°C)\n",
"\n",
"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.\n",
"================================\u001b[1m Human Message \u001b[0m=================================\n",
"\n",
"what's the weather in nyc?\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"[{'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'}]\n",
"Tool Calls:\n",
" tavily_search_results_json (toolu_01FFft5Sx9oS6AdVJuRWWcGp)\n",
" Call ID: toolu_01FFft5Sx9oS6AdVJuRWWcGp\n",
" Args:\n",
" query: weather in new york city\n",
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
"Name: tavily_search_results_json\n",
"\n",
"[{\"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}}\"}]\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"According to the weather data from WeatherAPI:\n",
"\n",
"Current Conditions in New York City (as of 2:00 PM local time):\n",
"- Temperature: 85°F (29°C)\n",
"- Conditions: Sunny\n",
"- Wind: 2 mph (4 km/h) from the SSE\n",
"- Humidity: 63%\n",
"- Heat Index: 85°F (30°C)\n",
"\n",
"The forecast shows sunny and warm conditions persisting over the next few days:\n",
"\n",
"Today: Sunny, high of 85°F (29°C)\n",
"Tonight: Clear, low of 68°F (20°C)\n",
"Tomorrow: Sunny, high of 88°F (31°C) \n",
"Thursday: Mostly sunny, high of 90°F (32°C)\n",
"Friday: Partly cloudy, high of 87°F (31°C)\n",
"\n",
"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.\n"
]
}
],
"source": [
"for m in convert_to_messages(state[\"values\"][\"messages\"]):\n",
" m.pretty_print()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "",
"name": ""
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
@@ -1,116 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to Add Breakpoints\n",
"\n",
"When creating LangGraph agents, it is often nice to add a human-in-the-loop component.\n",
"This can be helpful when giving them access to tools.\n",
"Often in these situations you may want to manually approve an action before taking.\n",
"\n",
"This can be in several ways, but the primary supported way is to add an \"interrupt\" before a node is executed.\n",
"This interrupts execution at that node.\n",
"You can then resume from that spot to continue. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Setup\n",
"\n",
"### Code for your graph\n",
"\n",
"In this how-to we use a simple ReAct style hosted graph (you can see the full code for defining it [here](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/breakpoints/)). 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).\n",
"\n",
"### SDK Initialization"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langgraph_sdk import get_client\n",
"client = get_client()\n",
"assistants = await client.assistants.search()\n",
"assistants = [a for a in assistants if not a['config']]\n",
"assistant = assistants[0]\n",
"assistant_id = assistant['assistant_id']\n",
"thread = await client.threads.create()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Adding a breakpoint\n",
"\n",
"We now want to add a breakpoint in our graph run, which we will do before a tool is called.\n",
"We can do this by adding `interrupt_before=[\"action\"]`, which tells us to interrupt before calling the action node.\n",
"We can do this either when compiling the graph or when kicking off a run.\n",
"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`.\n",
"\n",
"First let's access our hosted Langgraph instance through the SDK:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And, now let's compile it with a breakpoint before the tool node:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Receiving new event of type: metadata...\n",
"{'run_id': '3b77ef83-687a-4840-8858-0371f91a92c3'}\n",
"\n",
"\n",
"\n",
"Receiving new event of type: data...\n",
"{'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': []}]}}\n",
"\n",
"\n",
"\n",
"Receiving new event of type: end...\n",
"None\n",
"\n",
"\n",
"\n"
]
}
],
"source": [
"input = {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf\"}]}\n",
"async for chunk in client.runs.stream(\n",
" thread[\"thread_id\"],\n",
" assistant_id,\n",
" input=input,\n",
" stream_mode=\"updates\",\n",
" interrupt_before=[\"action\"],\n",
"):\n",
" print(f\"Receiving new event of type: {chunk.event}...\")\n",
" print(chunk.data)\n",
" print(\"\\n\\n\")"
]
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -1,173 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to Edit State of a Deployed Graph\n",
"\n",
"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).\n",
"\n",
"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.\n",
"\n",
"## Setup\n",
"\n",
"We are not going to show the full code for the graph we are hosting, but you can see it [here](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/edit-graph-state/#build-the-agent) if you want to. Once this graph is hosted, we are ready to invoke it and wait for user input. \n",
"\n",
"### SDK initialization\n",
"\n",
"First, we need to setup our client so that we can communicate with our hosted graph:"
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {},
"outputs": [],
"source": [
"from langgraph_sdk import get_client\n",
"client = get_client()\n",
"assistants = await client.assistants.search()\n",
"assistants = [a for a in assistants if not a['config']]\n",
"assistant = assistants[0]\n",
"thread = await client.threads.create()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Editing state\n",
"\n",
"### Initial invocation\n",
"\n",
"Now let's invoke our graph, making sure to interrupt before the `action` node."
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'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}]}}\n"
]
}
],
"source": [
"input = {'messages':[{\"role\":\"user\",\"content\":\"search for weather in SF\"}]}\n",
"\n",
"async for chunk in client.runs.stream(\n",
" thread[\"thread_id\"],\n",
" assistant[\"assistant_id\"], # graph_id\n",
" input=input,\n",
" stream_mode=\"updates\",\n",
" interrupt_before=[\"action\"],\n",
"):\n",
" if chunk.data and \"run_id\" not in chunk.data:\n",
" print(chunk.data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Edit the state\n",
"\n",
"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:"
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'configurable': {'thread_id': '88d58d3f-4151-47a9-a8e0-e42fdd3527b8',\n",
" 'thread_ts': '1ef3274b-a809-6913-8002-91536ce6554d'}}"
]
},
"execution_count": 39,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# First, lets get the current state\n",
"current_state = await client.threads.get_state(thread['thread_id'])\n",
"\n",
"# Let's now get the last message in the state\n",
"# This is the one with the tool calls that we want to update\n",
"last_message = current_state['values']['messages'][-1]\n",
"\n",
"# Let's now update the args for that tool call\n",
"last_message['tool_calls'][0]['args'] = {'query': 'current weather in Sidi Frej'}\n",
"\n",
"# Let's now call `update_state` to pass in this message in the `messages` key\n",
"# This will get treated as any other update to the state\n",
"# It will get passed to the reducer function for the `messages` key\n",
"# That reducer function will use the ID of the message to update it\n",
"# It's important that it has the right ID! Otherwise it would get appended\n",
"# as a new message\n",
"await client.threads.update_state(thread['thread_id'], {\"messages\": last_message})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Resume invocation\n",
"\n",
"Now we can resume our graph run but with the updated state:"
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'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'}]}}\n",
"{'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}]}}\n",
"{'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'}]}}\n",
"{'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}]}}\n"
]
}
],
"source": [
"async for chunk in client.runs.stream(\n",
" thread[\"thread_id\"],\n",
" assistant[\"assistant_id\"], # graph_id\n",
" input=None,\n",
" stream_mode=\"updates\",\n",
"):\n",
" if chunk.data and \"run_id\" not in chunk.data:\n",
" print(chunk.data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -1,235 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to Replay and Branch from Prior States\n",
"\n",
"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.\n",
"\n",
"## Setup\n",
"\n",
"We are not going to show the full code for the graph we are hosting, but you can see it [here](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/time-travel/#build-the-agent) if you want to. Once this graph is hosted, we are ready to invoke it and wait for user input. \n",
"\n",
"### SDK initialization\n",
"\n",
"First, we need to setup our client so that we can communicate with our hosted graph:"
]
},
{
"cell_type": "code",
"execution_count": 99,
"metadata": {},
"outputs": [],
"source": [
"from langgraph_sdk import get_client\n",
"client = get_client()\n",
"assistants = await client.assistants.search()\n",
"assistants = [a for a in assistants if not a['config']]\n",
"assistant = assistants[0]\n",
"thread = await client.threads.create()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Replay a state\n",
"\n",
"### Initial invocation\n",
"\n",
"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:"
]
},
{
"cell_type": "code",
"execution_count": 100,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'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}]}}\n",
"{'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'}]}}\n",
"{'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}]}}\n"
]
}
],
"source": [
"input = {'messages':[{\"role\":\"user\",\"content\":\"Please search the weather in SF\"}]}\n",
"\n",
"async for chunk in client.runs.stream(\n",
" thread[\"thread_id\"],\n",
" assistant[\"assistant_id\"], # graph_id\n",
" input=input,\n",
" stream_mode=\"updates\",\n",
"):\n",
" if chunk.data and \"run_id\" not in chunk.data:\n",
" print(chunk.data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now let's get our list of states, and invoke from the third state (right before the tool get called):"
]
},
{
"cell_type": "code",
"execution_count": 101,
"metadata": {},
"outputs": [],
"source": [
"states = await client.threads.get_history(thread['thread_id'])"
]
},
{
"cell_type": "code",
"execution_count": 102,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['action']"
]
},
"execution_count": 102,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# We can confirm that this state is correct by checking the 'next' attribute and seeing that it is the tool call node\n",
"state_to_replay = states[2]\n",
"state_to_replay['next']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To rerun from a state, we need to pass in the `checkpoint_id` into the config of the run like follows:"
]
},
{
"cell_type": "code",
"execution_count": 103,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'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'}]}}\n",
"{'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}]}}\n"
]
}
],
"source": [
"async for chunk in client.runs.stream(\n",
" thread[\"thread_id\"],\n",
" assistant[\"assistant_id\"], # graph_id\n",
" input=None,\n",
" stream_mode=\"updates\",\n",
" config={\"configurable\":{\"thread_ts\":state_to_replay['checkpoint_id']}}\n",
"):\n",
" if chunk.data and \"run_id\" not in chunk.data:\n",
" print(chunk.data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As we can see, the graph restarted from the tool node with the same input as our original graph run.\n",
"\n",
"## Branch off from previous state\n",
"\n",
"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.\n",
"\n",
"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"
]
},
{
"cell_type": "code",
"execution_count": 104,
"metadata": {},
"outputs": [],
"source": [
"# Let's now get the last message in the state\n",
"# This is the one with the tool calls that we want to update\n",
"last_message = state_to_replay['values']['messages'][-1]\n",
"\n",
"# Let's now update the args for that tool call\n",
"last_message['tool_calls'][0]['args'] = {'query': 'current weather in SF'}\n",
"\n",
"new_state = await client.threads.update_state(thread['thread_id'],{\"messages\":[last_message]},checkpoint_id=state_to_replay['checkpoint_id'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we can rerun our graph with this new config, starting from the `new_state`, which is a branch of our `state_to_replay`:"
]
},
{
"cell_type": "code",
"execution_count": 105,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'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'}]}}\n",
"{'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}]}}\n"
]
}
],
"source": [
"async for chunk in client.runs.stream(\n",
" thread[\"thread_id\"],\n",
" assistant[\"assistant_id\"], # graph_id\n",
" input=None,\n",
" stream_mode=\"updates\",\n",
" config={\"configurable\":{\"thread_ts\":new_state['configurable']['thread_ts']}}\n",
"):\n",
" if chunk.data and \"run_id\" not in chunk.data:\n",
" print(chunk.data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As we can see, the search query changed from San Francisco to SF, just as we had hoped!"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -1,175 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to Wait for User Input\n",
"\n",
"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.\n",
"\n",
"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).\n",
"\n",
"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.\n",
"\n",
"Luckily, LangGraph makes it possible to do similar things in a production way. The basic idea is:\n",
"\n",
"- 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.\n",
"- 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)\n",
"- 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."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Setup\n",
"\n",
"We are not going to show the full code for the graph we are hosting, but you can see it [here](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/wait-user-input/#build-the-agent) if you want to. Once this graph is hosted, we are ready to invoke it and wait for user input. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### SDK initialization\n",
"\n",
"First, we need to setup our client so that we can communicate with our hosted graph:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from langgraph_sdk import get_client\n",
"client = get_client()\n",
"assistants = await client.assistants.search()\n",
"assistants = [a for a in assistants if not a['config']]\n",
"assistant = assistants[0]\n",
"thread = await client.threads.create()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Waiting for user input\n",
"\n",
"### Initial invocation\n",
"\n",
"Now, let's invoke our graph by interrupting before `ask_human` node:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'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}]}}\n"
]
}
],
"source": [
"input = {'messages':[{\"role\":\"user\",\"content\":\"Use the search tool to ask the user where they are, then look up the weather there\"}]}\n",
"\n",
"async for chunk in client.runs.stream(\n",
" thread[\"thread_id\"],\n",
" assistant[\"assistant_id\"], # graph_id\n",
" input=input,\n",
" stream_mode=\"updates\",\n",
" interrupt_before=[\"ask_human\"],\n",
"):\n",
" if chunk.data and \"run_id\" not in chunk.data:\n",
" print(chunk.data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Adding user input to state\n",
"\n",
"We now want to update this thread with a response from the user. We then can kick off another run.\n",
"\n",
"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."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'configurable': {'thread_id': '10d0ee61-db47-48fc-a58c-109a1e68cd73',\n",
" 'thread_ts': '1ef32729-3cc3-6647-8002-14dcb621b46e'}}"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"state = await client.threads.get_state(thread['thread_id'])\n",
"tool_call_id = state['values']['messages'][-1]['tool_calls'][0]['id']\n",
"\n",
"# We now create the tool call with the id and the response we want\n",
"tool_message = [{\"tool_call_id\": tool_call_id, \"type\": \"tool\", \"content\": \"san francisco\"}]\n",
"\n",
"await client.threads.update_state(thread['thread_id'], {\"messages\": tool_message}, as_node=\"ask_human\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Invoking after receiving human input\n",
"\n",
"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:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'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}]}}\n",
"{'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'}]}}\n",
"{'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}]}}\n"
]
}
],
"source": [
"async for chunk in client.runs.stream(\n",
" thread[\"thread_id\"],\n",
" assistant[\"assistant_id\"], # graph_id\n",
" input=None,\n",
" stream_mode=\"updates\",\n",
"):\n",
" if chunk.data and \"run_id\" not in chunk.data:\n",
" print(chunk.data)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 KiB

After

Width:  |  Height:  |  Size: 432 KiB

@@ -1,181 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Interrupt\n",
"\n",
"This notebook assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](https://langchain-ai.github.io/langgraph/cloud/concepts/#double-texting).\n",
"\n",
"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.\n",
"\n",
"First, let's import our required packages and instantiate our client, assistant, and thread."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import asyncio\n",
"\n",
"from langchain_core.messages import convert_to_messages\n",
"from langgraph_sdk import get_client"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"client = get_client()\n",
"assistant_id = \"agent\"\n",
"thread = await client.threads.create()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# the first run will be interrupted\n",
"interrupted_run = await client.runs.create(\n",
" thread[\"thread_id\"],\n",
" assistant_id,\n",
" input={\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf?\"}]},\n",
")\n",
"await asyncio.sleep(2)\n",
"run = await client.runs.create(\n",
" thread[\"thread_id\"],\n",
" assistant_id,\n",
" input={\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in nyc?\"}]},\n",
" multitask_strategy=\"interrupt\",\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# wait until the second run completes\n",
"await client.runs.join(thread[\"thread_id\"], run[\"run_id\"])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can see that the thread has partial data from the first run + data from the second run"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"state = await client.threads.get_state(thread[\"thread_id\"])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"================================\u001b[1m Human Message \u001b[0m=================================\n",
"\n",
"what's the weather in sf?\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"[{'id': 'toolu_01MjNtVJwEcpujRGrf3x6Pih', 'input': {'query': 'weather in san francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n",
"Tool Calls:\n",
" tavily_search_results_json (toolu_01MjNtVJwEcpujRGrf3x6Pih)\n",
" Call ID: toolu_01MjNtVJwEcpujRGrf3x6Pih\n",
" Args:\n",
" query: weather in san francisco\n",
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
"Name: tavily_search_results_json\n",
"\n",
"[{\"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 ...\"}]\n",
"================================\u001b[1m Human Message \u001b[0m=================================\n",
"\n",
"what's the weather in nyc?\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"[{'id': 'toolu_01KtE1m1ifPLQAx4fQLyZL9Q', 'input': {'query': 'weather in new york city'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n",
"Tool Calls:\n",
" tavily_search_results_json (toolu_01KtE1m1ifPLQAx4fQLyZL9Q)\n",
" Call ID: toolu_01KtE1m1ifPLQAx4fQLyZL9Q\n",
" Args:\n",
" query: weather in new york city\n",
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
"Name: tavily_search_results_json\n",
"\n",
"[{\"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.\"}]\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"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:\n",
"\n",
"- This is a monthly weather forecast for New York City for the month of June.\n",
"- It includes daily high and low temperatures to help plan ahead.\n",
"- Historical averages for June in NYC are also provided as a reference point.\n",
"- More detailed daily or hourly forecasts with precipitation chances, humidity, wind, etc. can be found by visiting the AccuWeather page.\n",
"\n",
"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!\n"
]
}
],
"source": [
"for m in convert_to_messages(state[\"values\"][\"messages\"]):\n",
" m.pretty_print()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Verify that the original, interrupted run was interrupted"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'interrupted'"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"(await client.runs.get(thread[\"thread_id\"], interrupted_run[\"run_id\"]))[\"status\"]"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "",
"name": ""
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

Some files were not shown because too many files have changed in this diff Show More