Compare commits

...
Author SHA1 Message Date
Vadym BardaandGitHub 39d9cdbef0 checkpoint-postgres: release 2.0.2 (#2183) 2024-10-24 17:06:36 -04:00
Vadym BardaandGitHub 6dacd1aabe langgraph: always raise NodeInterrupt in ToolNode if raised from a tool (#2175) 2024-10-24 18:21:18 +00:00
Vadym BardaandGitHub 91ad8b803b checkpoint-duckdb: release 2.0.1 (#2180) 2024-10-24 13:58:48 -04:00
Vadym BardaandGitHub a0e99f704f checkpoint: release 2.0.2 (#2179) 2024-10-24 13:52:02 -04:00
Vadym BardaandGitHub def3e06b4a move py.typed to submodules for namespace packages (#2177) 2024-10-24 13:43:43 -04:00
David DuongandGitHub a4fab2a867 Merge pull request #2178 from langchain-ai/dqbd/sdk-bump-24oct
feat(sdk): bump SDK to js@0.0.18 and py@0.1.34
2024-10-24 18:58:07 +02:00
Tat Dat Duong cf1c7f3673 feat(sdk): bump SDK to js@0.0.18 and py@0.1.34 2024-10-24 18:51:56 +02:00
Nuno CamposandGitHub 6c05b66c7c Merge pull request #2176 from langchain-ai/dqbd/error-thread-state
fix(sdk): add error thread state
2024-10-24 09:23:20 -07:00
Tat Dat Duong 5c44dcef81 fix(sdk): add error thread state 2024-10-24 18:16:21 +02:00
Nuno Campos 7fd6b1b4be Fix 2024-10-24 08:48:46 -07:00
Vadym BardaandGitHub 6202e0f1d9 docs: update branching how-to notebook (#2174) 2024-10-24 14:56:19 +00:00
Nuno CamposandGitHub 83238f51d8 Merge pull request #2166 from langchain-ai/nc/23oct/remote-graph-interop
Interop of RemoteGraph w core lib
2024-10-23 20:36:38 -07:00
Nuno Campos 05f008cbfb Lint 2024-10-23 20:30:32 -07:00
Nuno Campos a8ae2a52a3 Lint 2024-10-23 20:27:18 -07:00
Nuno Campos aa245a8e71 Fix up 2024-10-23 20:22:04 -07:00
bdc75a22d5 langgraph: expand handle_tool_errors in ToolNode (#1667)
This change expands error-handling functionality of the `ToolNode` by
introducing more options for `handle_tool_errors`. Default behavior of
the `ToolNode` is unchanged -- all errors are handled and wrapped in a
`ToolMessage` to be sent back to LLM.

With this change, users have flexibility to only handle the exceptions
that they need to pass back to the LLM:

* they can specify exceptions to handle by passing a tuple of exceptions
in `handle_tool_errors`
* specify `handle_tool_errors=True/str/callable`
* when `handle_tool_errors` is a callable, the signature will be
inspected and exceptions from the signature will be handled

---------

Co-authored-by: vbarda <vadym@langchain.dev>
2024-10-24 00:58:05 +00:00
Nuno Campos 69227daff3 Lint 2024-10-23 17:01:58 -07:00
Nuno Campos dc8260bb72 Interop of RemoteGraph w core lib 2024-10-23 15:37:17 -07:00
Vadym BardaandGitHub 62a5ec509d checkpoint: add DuckDB store (#2154) 2024-10-23 22:20:10 +00:00
Vadym BardaandGitHub d32386f849 checkpoint: add DuckDB checkpointer (#2145) 2024-10-23 21:11:03 +00:00
Nuno CamposandGitHub 08a1ed38f1 Merge pull request #2092 from langchain-ai/an/11oct/remote-graph-interrupt
Update `stream()` and `astream()` methods in `RemoteGraph` to process `updates` event types
2024-10-23 13:48:43 -07:00
Nuno Campos 037a95ff60 Update tests 2024-10-23 13:43:48 -07:00
Nuno Campos e294720ec5 Lint 2024-10-23 13:29:59 -07:00
Nuno Campos f8a0b7a464 Use if_not_exists 2024-10-23 13:24:19 -07:00
Nuno Campos 1121806ba4 Add if_not_exists 2024-10-23 13:23:49 -07:00
Nuno Campos dca200d6c4 Finish 2024-10-23 13:23:42 -07:00
Vadym BardaandGitHub 6f236b5f2c docs: update multi-agent concept examples (#2151) 2024-10-23 13:40:24 -04:00
Vadym BardaandGitHub 916affa1b5 langgraph: add 'messages_key' param to ToolNode / tools_condition (#2049) 2024-10-22 17:32:05 -04:00
Nuno Campos 58cf0c6a6e chore: Switch s3 client utils from httpx client to curl client 2024-10-22 10:46:37 -07:00
gbaian10andGitHub 0042889c31 Support read type hints from the method in add_node (#2014)
Add function to read type hints from the `__call__` method to resolve issue #1950.
2024-10-22 17:11:59 +00:00
nikhildigdeandGitHub 2be012d8ed docs: fix typo in concept docs 2024-10-21 21:40:47 +00:00
Yuki OshimaandGitHub 780285ef91 Fix(docs): InMemoryStore example error (#2148) 2024-10-21 18:39:43 +00:00
Nuno CamposandGitHub 47c7b76aa1 Merge pull request #2152 from langchain-ai/nc/21oct/skip-docker-login-for-forks
ci: Skip docker login for PRs from forks
2024-10-21 10:09:54 -07:00
Nuno Campos 9931f61525 ci: Skip docker login for PRs from forks 2024-10-21 10:04:40 -07:00
Nuno CamposandGitHub 0d81ad92f0 Merge pull request #2143 from langchain-ai/nc/19oct/async-max-concurrency
lib: Add max_concurrency for async executions
2024-10-19 15:16:15 -07:00
Nuno Campos 7d3f2ca3ed Lint 2024-10-19 15:11:18 -07:00
Nuno Campos 42648c88dd lib: Add max_concurrency for async executions 2024-10-19 12:45:01 -07:00
Vadym BardaandGitHub 1aab758634 langgraph: release 0.2.39 (#2139) 2024-10-18 14:47:50 -04:00
Vadym BardaandGitHub b5fbc7a7b8 docs: temporarily disable some link checks (#2138) 2024-10-18 14:37:19 -04:00
b647dcb0f2 feat: Add LangGraph error pages (#2136)
Co-authored-by: Erick Friis <erick@langchain.dev>
Co-authored-by: vbarda <vadym@langchain.dev>
2024-10-18 18:20:22 +00:00
Nuno CamposandGitHub 4df5680732 Merge pull request #2132 from langchain-ai/nc/17oct/stream-messages-nostream-tag
For stream_mode=messages skip any nodes/llms with tag nostream
2024-10-17 15:57:09 -07:00
Nuno Campos 74a17a6d4c Update tests 2024-10-17 15:52:17 -07:00
Nuno Campos e2a3698250 For stream_mode=messages skip any nodes/llms with tag nostream 2024-10-17 15:28:14 -07:00
Vadym BardaandGitHub 4dfdb9a83e docs: update tags for store endpoints in API docs (#2127) 2024-10-16 16:34:54 +00:00
Vadym BardaandGitHub 583d8c9499 docs: update tutorial names/links (#2126) 2024-10-16 15:17:28 +00:00
vbarda 15bbede7bc update image in multi-agent concepts 2024-10-16 10:59:07 -04:00
Vadym BardaandGitHub 3ffdf4bb3f docs: update image in concepts (#2125) 2024-10-16 13:59:17 +00:00
Nuno CamposandGitHub 6578698414 Merge pull request #2124 from langchain-ai/dqbd/js-bump-0.0.17
feat(sdk-js): bump to 0.0.17
2024-10-16 06:21:22 -07:00
Tat Dat Duong 048ae6c17b feat(sdk-js): bump to 0.0.17 2024-10-16 15:19:39 +02:00
Nuno CamposandGitHub 0e2c2eb13a Merge pull request #2120 from langchain-ai/nc/15oct/executor-dict
fix: Avoid errors from executor modifying tasks dict during exit routine
2024-10-15 16:15:55 -07:00
Nuno Campos 515c4ffebe Fix 2024-10-15 16:11:08 -07:00
Nuno Campos eefe057a47 fix: Avoid errors from executor modifying tasks dict during exit routine
- This could happen if a task happened to finish while the exit routine is running
2024-10-15 15:13:46 -07:00
18f34c30d8 docs: update subgraph how-to (#2079)
Co-authored-by: vbarda <vadym@langchain.dev>
2024-10-15 17:30:34 -04:00
2670bcf330 docs: add concepts for subgraphs and multi-agent (#2069)
Co-authored-by: Harrison Chase <hw.chase.17@gmail.com>
Co-authored-by: Nuno Campos <nuno@langchain.dev>
2024-10-15 17:17:22 -04:00
Nuno Campos f6fb2ef5ca langgraph 0.2.38 2024-10-15 11:04:22 -07:00
Vadym BardaandGitHub 2fb7e92879 docs: update recursion notebook to use RemainingSteps (#2114) 2024-10-15 12:05:46 -04:00
Nuno CamposandGitHub 46b2d08a8a Merge pull request #2115 from langchain-ai/nc/15oct/update-is-last-step
Return IsLastStep to previous definition
2024-10-15 08:41:30 -07:00
Nuno Campos 649b742e0a Update types for RemainingSteps, return IsLastStep to previous definition 2024-10-15 08:35:31 -07:00
Nuno CamposandGitHub fd4629e778 Merge pull request #2112 from langchain-ai/vb/fix-type
langgraph: fix type for RemainingSteps
2024-10-15 08:34:50 -07:00
vbarda d14f98f01b langgraph: fix type for RemainingSteps 2024-10-15 09:07:00 -04:00
Nuno Campos c0b56bf60d langgraph 0.2.37 2024-10-14 17:29:11 -07:00
Nuno CamposandGitHub e8b875906f Merge pull request #2105 from langchain-ai/nc/14oct/is-last-step-fix
Fix IsLastStep counter for runs with checkpointers
2024-10-14 17:21:31 -07:00
Nuno Campos d48faecd42 Fix 2024-10-14 17:16:32 -07:00
Nuno Campos 5e175e098b Update kafka 2024-10-14 17:10:10 -07:00
Nuno Campos bcf335651e Fix is_last_step 2024-10-14 17:05:16 -07:00
Nuno Campos 965849823a Fix 2024-10-14 17:03:53 -07:00
Nuno Campos 45e7101457 Backwards compat 2024-10-14 16:57:25 -07:00
Nuno Campos ecd75a8c4d Fix IsLastStep counter for runs with checkpointers
- Share step/stop logic with PregelLoop
- Add RemainingSteps value which contains the number of remaining steps
- Switch create_react_agent to use RemainingSteps, so that it behave correctly for return_direct tools
2024-10-14 16:54:16 -07:00
Nuno CamposandGitHub edec5c055e Merge pull request #2065 from langchain-ai/dqbd/debug-stream-checkpoint-map
fix(debug): send checkpoint_map as well
2024-10-14 15:50:39 -07:00
Nuno Campos ff310cc8d6 One more 2024-10-14 15:45:12 -07:00
Nuno Campos 233bd78ee4 Add checkpoint_map to parent_config 2024-10-14 15:44:18 -07:00
Nuno Campos b818bf2fba Fix up 2024-10-14 15:32:41 -07:00
Tat Dat DuongandNuno Campos c5ec568cfb Patch config before entering map_debug_checkpoint 2024-10-14 15:16:24 -07:00
Tat Dat DuongandNuno Campos 29548b2e27 fix(debug): add failing tests 2024-10-14 15:16:03 -07:00
Nuno Campos f2dc537696 langgraph 0.2.36 2024-10-14 12:28:43 -07:00
Nuno Campos 31d21c8d24 sdk-py 0.1.33 2024-10-14 12:28:22 -07:00
Nuno CamposandGitHub 4a03ed5915 Merge pull request #2091 from langchain-ai/nc/11oct/checkpoint-task-result
lib: Add result for each task in a checkpoint
2024-10-14 11:37:28 -07:00
Nuno Campos dc083c6563 Fix 2024-10-14 11:31:03 -07:00
Nuno Campos bfe005fef0 Lint 2024-10-14 10:30:59 -07:00
vbarda 5388b7c74f install dev sdk 2024-10-14 13:23:18 -04:00
vbarda 0d4617817d Merge branch 'nc/11oct/checkpoint-task-result' of github.com:langchain-ai/langgraph into nc/11oct/checkpoint-task-result 2024-10-14 13:20:25 -04:00
Nuno Campos 5046ec4f43 Lint 2024-10-14 10:16:14 -07:00
Nuno Campos c26bb9e156 lib: Add result for each task in a checkpoint
- Note this requires disabling the optimization that avoids saving writes for the last task in a step
2024-10-14 10:16:14 -07:00
Nuno CamposandGitHub 3982090c6d Merge pull request #2088 from langchain-ai/nc/11oct/stream-interrupt
lib: Add interrupts to stream_mode=updates
2024-10-14 10:15:53 -07:00
Nuno CamposandGitHub 6d3a2c59da Merge pull request #2102 from langchain-ai/vb/ci-update
ci: run core 0.2.x for a single python version
2024-10-14 10:15:38 -07:00
vbardaandNuno Campos 79444dee9c ci: run core 0.2.x for a single python version 2024-10-14 10:10:42 -07:00
Nuno CamposandGitHub 07f8f87780 Merge pull request #2103 from langchain-ai/nc/14oct/docker-ro-token
Add read-only token for pulling public images from dockerhub
2024-10-14 10:07:45 -07:00
Nuno Campos 99bde8774e Add read-only token for pulling public images from dockerhub 2024-10-14 09:56:24 -07:00
Nuno CamposandGitHub 5946f4ff2b Merge pull request #2100 from langchain-ai/vb/copy
langgraph: support copy without update in Pregel
2024-10-14 09:12:36 -07:00
vbarda b99734d157 langgraph: support copy without update in Pregel 2024-10-14 10:14:41 -04:00
Vadym BardaandGitHub d1c29fc8be docs: remove example with missing link (#2099) 2024-10-14 14:05:48 +00:00
Andrew Nguonly 19ccb0c6af Update astream_events() to process interrupt. 2024-10-11 19:06:52 -07:00
Andrew Nguonly a277b86fcb Fix unit test. 2024-10-11 18:53:45 -07:00
Andrew Nguonly 2f819a6a9b Update stream() and astream() to process 'updates' event types. 2024-10-11 18:40:40 -07:00
Nuno Campos dc47c7b357 lib: Add result for each task in a checkpoint
- Note this requires disabling the optimization that avoids saving writes for the last task in a step
2024-10-11 16:08:00 -07:00
Nuno Campos 561aa3080e Lint 2024-10-11 14:28:16 -07:00
Nuno Campos c6a450b857 lib: Add interrupts to stream_mode=updates 2024-10-11 14:28:16 -07:00
Nuno Campos 0557fb03a4 format 2024-10-11 14:28:08 -07:00
Nuno CamposandGitHub c9adf995c2 Merge pull request #2087 from langchain-ai/nc/11oct/o-flag
fix: Work w python's O flag
2024-10-11 12:03:21 -07:00
Nuno Campos fc20de5bba Fix 2024-10-11 11:39:19 -07:00
Nuno Campos 0822a287e3 fix: Work w python's O flag
- assert statements are skipped in that case, so we need to move calls to apply_writes to outside assert statements
2024-10-11 11:24:56 -07:00
Andrew NguonlyandGitHub 66741ba071 Rename RemotePregel to RemoteGraph (#2085) 2024-10-11 10:55:04 -07:00
Andrew NguonlyandGitHub e72c25873f Implement PregelProtocol and RemotePregel class (attempt 2) (#2078)
### Summary
Redo of [this PR](https://github.com/langchain-ai/langgraph/pull/2034)
(branched from clean branch).
2024-10-11 09:23:12 -07:00
Vadym BardaandGitHub 739336516d docs: remove empty cells for admonitions (#2082) 2024-10-11 13:36:24 +00:00
Nuno CamposandGitHub ae6c793bdf Merge pull request #2077 from langchain-ai/nc/10oct/313
Test w Python 3.13 in CI
2024-10-10 16:35:53 -07:00
Nuno Campos d8954963b4 Update snapshots 2024-10-10 16:27:43 -07:00
Nuno Campos ea38ba9e29 Update uvloop 2024-10-10 16:22:34 -07:00
Nuno Campos 76229ade66 Update psycopg 2024-10-10 16:17:19 -07:00
Nuno Campos 4aad36947e Upgrade pydantic 2024-10-10 16:13:05 -07:00
Nuno Campos cb7b667e6f Update psycopg 2024-10-10 16:09:24 -07:00
Nuno Campos 822ddb5f48 Test aux libs 2024-10-10 16:06:05 -07:00
Nuno Campos de355ee2d2 Update rpds-py 2024-10-10 16:05:43 -07:00
Nuno Campos 9ca270d62d Test w Python 3.13 in CI 2024-10-10 15:58:59 -07:00
David DuongandGitHub 28b5105913 Merge pull request #2070 from langchain-ai/dqbd/debug-self-referencing-checkpoint
fix(debug): self-referencing checkpoints when resuming streaming mid-thread
2024-10-10 12:44:45 +02:00
Tat Dat Duong be47752f0e Initialise to None 2024-10-10 11:15:54 +02:00
Isaac FranciscoandGitHub aa83f4a33e adding support for more notebooks in CI (#2060) 2024-10-10 01:48:13 +00:00
Tat Dat Duong 4d69331a52 Add async tests 2024-10-10 02:29:28 +02:00
Tat Dat Duong fb8c386958 fix(debug): address self-referencing 2024-10-10 02:21:32 +02:00
Tat Dat Duong ac8b51f1f2 fix(debug): add failing test for self-referencing 2024-10-10 02:21:17 +02:00
Vadym BardaandGitHub db0f508269 docs: add custom hooks for rendering jupyter notebooks (#2067) 2024-10-09 18:49:54 -04:00
Vadym BardaandGitHub fe110ae145 docs: fix intro in supervisor tutorial (#2068) 2024-10-09 20:59:49 +00:00
David DuongandGitHub fc276c5ac0 Merge pull request #2066 from langchain-ai/dqbd/checkpoint-sdk-js
fix(sdk-js): pass checkpoint when creating run
2024-10-09 20:50:54 +02:00
Tat Dat Duong 9360545659 Remove deprecated message 2024-10-09 20:41:18 +02:00
Tat Dat Duong 26e30ad6af Remove error warning 2024-10-09 20:40:24 +02:00
Tat Dat Duong 8df533b489 fix(sdk-js): allow passing checkpoint when creating a run 2024-10-09 20:34:19 +02:00
Vadym BardaandGitHub f9df0f4700 docs: update many tools how-to chart (#2059) 2024-10-09 13:46:59 +00:00
David DuongandGitHub 5d0afa3888 Merge pull request #2048 from langchain-ai/dqbd/debug-tasks-state
feat(debug): send tasks info
2024-10-09 14:38:26 +02:00
Tat Dat Duong a53a566730 Bump to 0.2.35 2024-10-09 14:33:14 +02:00
Andrew NguonlyandTat Dat Duong d2c359f7c9 docs: Update LangGraph API docs (#2056)
### Summary
Adding endpoints for `/subgraphs` and `/store`.
2024-10-09 14:33:14 +02:00
Andrew NguonlyandTat Dat Duong c4d251b05c sdk-py: Add Sequence[dict] type to values param type for update_state() (#2054)
### Summary
The LangGraph API supports a list of `dict` for the `values` field for
the `POST /threads/<thread_id>/state` endpoint.

Reference:
https://github.com/langchain-ai/langgraph-api/blob/main/api/openapi.json#L3005-L3021
2024-10-09 14:33:14 +02:00
Andrew NguonlyandTat Dat Duong 9a752e1563 sdk-py: Add "*" literal to interrupt_before and interrupt_after types (#2053)
### Summary
The LangGraph API supports the literal string `"*"` for
`interrupt_before` and `interrupt_after`.

Reference:
https://github.com/langchain-ai/langgraph-api/blob/main/api/openapi.json#L2425
2024-10-09 14:33:14 +02:00
Andrew NguonlyandTat Dat Duong 6d4a426059 sdk-py: Add custom stream mode to StreamMode type (#2051)
### Summary
The LangGraph API supports `custom` stream mode type.

Reference:
https://github.com/langchain-ai/langgraph/blob/main/docs/docs/cloud/reference/api/openapi.json#L2403
2024-10-09 14:33:14 +02:00
Andrew NguonlyandTat Dat Duong 598bb5a641 sdk-py: Update return type annotation for Thread.update_state() methods. (#2050)
### Summary
The response body of the endpoint `POST /threads/{thread_id}/state`
looks like this:
```
{
    "checkpoint": {
        "thread_id": "e2496803-ecd5-4e0c-a779-3226296181c2",
        "checkpoint_ns": "",
        "checkpoint_id": "1ef4a9b8-e6fb-67b1-8001-abd5184439d1",
        "checkpoint_map": {}
    }
}
```
2024-10-09 14:33:14 +02:00
Andrew NguonlyandGitHub 28ff7fd7ba docs: Update LangGraph API docs (#2056)
### Summary
Adding endpoints for `/subgraphs` and `/store`.
2024-10-08 16:44:01 -07:00
Tat Dat Duong 0628c6402f Fix typo 2024-10-09 01:00:36 +02:00
Tat Dat Duong db61d294a6 Code review 2024-10-09 01:00:06 +02:00
Andrew NguonlyandGitHub 7883ceae64 sdk-py: Add Sequence[dict] type to values param type for update_state() (#2054)
### Summary
The LangGraph API supports a list of `dict` for the `values` field for
the `POST /threads/<thread_id>/state` endpoint.

Reference:
https://github.com/langchain-ai/langgraph-api/blob/main/api/openapi.json#L3005-L3021
2024-10-08 13:49:27 -07:00
Andrew NguonlyandGitHub 6698e25a04 sdk-py: Add "*" literal to interrupt_before and interrupt_after types (#2053)
### Summary
The LangGraph API supports the literal string `"*"` for
`interrupt_before` and `interrupt_after`.

Reference:
https://github.com/langchain-ai/langgraph-api/blob/main/api/openapi.json#L2425
2024-10-08 13:29:11 -07:00
Andrew NguonlyandGitHub fd1a9e4da3 sdk-py: Add custom stream mode to StreamMode type (#2051)
### Summary
The LangGraph API supports `custom` stream mode type.

Reference:
https://github.com/langchain-ai/langgraph/blob/main/docs/docs/cloud/reference/api/openapi.json#L2403
2024-10-08 12:44:37 -07:00
Andrew NguonlyandGitHub 82c316b3f7 sdk-py: Update return type annotation for Thread.update_state() methods. (#2050)
### Summary
The response body of the endpoint `POST /threads/{thread_id}/state`
looks like this:
```
{
    "checkpoint": {
        "thread_id": "e2496803-ecd5-4e0c-a779-3226296181c2",
        "checkpoint_ns": "",
        "checkpoint_id": "1ef4a9b8-e6fb-67b1-8001-abd5184439d1",
        "checkpoint_map": {}
    }
}
```
2024-10-08 12:04:26 -07:00
Tat Dat Duong 7a282f82dc Tests? 2024-10-08 20:55:49 +02:00
Tat Dat Duong 45a12c938b Add more nested tests for nested subgraphs 2024-10-08 20:49:16 +02:00
Tat Dat Duong b51f5d6345 Fix optional types 2024-10-08 20:09:51 +02:00
Tat Dat Duong f9e900f39a Fix 3.9 2024-10-08 19:58:42 +02:00
Tat Dat Duong e564902753 Add async tests 2024-10-08 19:57:39 +02:00
Tat Dat Duong e3bee7d843 Add tests 2024-10-08 19:52:05 +02:00
Tat Dat Duong 4f61dd1aa6 Fix tests 2024-10-08 19:18:52 +02:00
Tat Dat Duong 7e9cf02922 Use casting instead 2024-10-08 17:08:08 +02:00
Tat Dat Duong 50b1a1e230 Cleanup, move instanceof checks to an util 2024-10-08 17:01:40 +02:00
Tat Dat Duong c1081af6bc Fix lint 2024-10-08 16:41:48 +02:00
Tat Dat Duong dfb265f296 feat(debug): send tasks info 2024-10-08 16:21:38 +02:00
187 changed files with 12306 additions and 2124 deletions
+7
View File
@@ -21,6 +21,7 @@ jobs:
- "3.10"
- "3.11"
- "3.12"
- "3.13"
name: "test #${{ matrix.python-version }}"
steps:
@@ -32,6 +33,12 @@ jobs:
poetry-version: ${{ env.POETRY_VERSION }}
working-directory: ${{ inputs.working-directory }}
cache-key: test-${{ inputs.working-directory }}
- name: Login to Docker Hub
uses: docker/login-action@v3
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_RO_TOKEN }}
- name: Install dependencies
shell: bash
+10 -1
View File
@@ -16,9 +16,12 @@ jobs:
- "3.10"
- "3.11"
- "3.12"
- "3.13"
core-version:
- ">=0.2.39,<0.3.0"
- "latest"
include:
- python-version: "3.11"
core-version: ">=0.2.39,<0.3.0"
defaults:
run:
@@ -32,6 +35,12 @@ jobs:
python-version: ${{ matrix.python-version }}
poetry-version: ${{ env.POETRY_VERSION }}
cache-key: test-langgraph
- name: Login to Docker Hub
uses: docker/login-action@v3
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_RO_TOKEN }}
- name: Install dependencies
shell: bash
@@ -27,6 +27,12 @@ jobs:
python-version: ${{ matrix.python-version }}
poetry-version: ${{ env.POETRY_VERSION }}
cache-key: test-scheduler-kafka
- name: Login to Docker Hub
uses: docker/login-action@v3
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_RO_TOKEN }}
- name: Install dependencies
shell: bash
+2
View File
@@ -31,6 +31,7 @@ jobs:
"libs/cli",
"libs/checkpoint",
"libs/checkpoint-sqlite",
"libs/checkpoint-duckdb",
"libs/checkpoint-postgres",
"libs/scheduler-kafka",
]
@@ -47,6 +48,7 @@ jobs:
"libs/cli",
"libs/checkpoint",
"libs/checkpoint-sqlite",
"libs/checkpoint-duckdb",
"libs/checkpoint-postgres"
]
uses: ./.github/workflows/_test.yml
+2 -1
View File
@@ -58,7 +58,7 @@ jobs:
- name: Install dependencies
run: |
poetry install --with docs
poetry install --with test
poetry run pip install -U pytest pytest-check-links langsmith langchain GitPython
- name: Lint Docs
@@ -81,6 +81,7 @@ jobs:
--check-links-ignore "https://x.com/.*" \
--check-links-ignore "https://github\.com/.*" \
--check-links-ignore "/.*\.(ipynb|html)$" \
--check-links-ignore "https://python\.langchain\.com/.*" \
--check-links $(find docs/site -name "index.html" | grep -v 'storm/index.html')
else
echo "Fetching changes from origin/main..."
+1 -1
View File
@@ -24,7 +24,7 @@ export -f execute_notebook
# Check if custom notebook paths are provided
if [ $# -gt 0 ]; then
notebooks="$@"
notebooks=$(echo "$@" | tr ' ' '\n' | grep -vFf <(echo "$SKIP_NOTEBOOKS"))
else
# Find all notebooks and filter out those in the skip list
notebooks=$(find docs/docs/tutorials docs/docs/how-tos -name "*.ipynb" | grep -v ".ipynb_checkpoints" | grep -vFf <(echo "$SKIP_NOTEBOOKS"))
@@ -0,0 +1,246 @@
import importlib
import inspect
import logging
import os
import re
from typing import List, Literal, Optional
from typing_extensions import TypedDict
import nbformat
from nbconvert.preprocessors import Preprocessor
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Base URL for all class documentation
_LANGCHAIN_API_REFERENCE = "https://python.langchain.com/api_reference/"
_LANGGRAPH_API_REFERENCE = "https://langchain-ai.github.io/langgraph/reference/"
# (alias/re-exported modules, source module, class, docs namespace)
MANUAL_API_REFERENCES_LANGGRAPH = [
(
["langgraph.prebuilt"],
"langgraph.prebuilt.chat_agent_executor",
"create_react_agent",
"prebuilt",
),
(["langgraph.prebuilt"], "langgraph.prebuilt.tool_node", "ToolNode", "prebuilt"),
(
["langgraph.prebuilt"],
"langgraph.prebuilt.tool_node",
"tools_condition",
"prebuilt",
),
(
["langgraph.prebuilt"],
"langgraph.prebuilt.tool_node",
"InjectedState",
"prebuilt",
),
# Graph
(["langgraph.graph"], "langgraph.graph.message", "add_messages", "graphs"),
(["langgraph.graph"], "langgraph.graph.state", "StateGraph", "graphs"),
(["langgraph.graph"], "langgraph.graph.state", "CompiledStateGraph", "graphs"),
([], "langgraph.types", "StreamMode", "types"),
(["langgraph.graph"], "langgraph.constants", "START", "constants"),
(["langgraph.graph"], "langgraph.constants", "END", "constants"),
(["langgraph.constants"], "langgraph.types", "Send", "types"),
(["langgraph.constants"], "langgraph.types", "Interrupt", "types"),
([], "langgraph.types", "RetryPolicy", "types"),
([], "langgraph.checkpoint.base", "Checkpoint", "checkpoints"),
([], "langgraph.checkpoint.base", "CheckpointMetadata", "checkpoints"),
([], "langgraph.checkpoint.base", "BaseCheckpointSaver", "checkpoints"),
([], "langgraph.checkpoint.base", "SerializerProtocol", "checkpoints"),
([], "langgraph.checkpoint.serde.jsonplus", "JsonPlusSerializer", "checkpoints"),
([], "langgraph.checkpoint.memory", "MemorySaver", "checkpoints"),
([], "langgraph.checkpoint.sqlite.aio", "AsyncSqliteSaver", "checkpoints"),
([], "langgraph.checkpoint.sqlite", "SqliteSaver", "checkpoints"),
([], "langgraph.checkpoint.postgres.aio", "AsyncPostgresSaver", "checkpoints"),
([], "langgraph.checkpoint.postgres", "PostgresSaver", "checkpoints"),
]
WELL_KNOWN_LANGGRAPH_OBJECTS = {
(module_, class_): (source_module, namespace)
for (modules, source_module, class_, namespace) in MANUAL_API_REFERENCES_LANGGRAPH
for module_ in modules + [source_module]
}
def _make_regular_expression(pkg_prefix: str) -> re.Pattern:
if not pkg_prefix.isidentifier():
raise ValueError(f"Invalid package prefix: {pkg_prefix}")
return re.compile(
r"from\s+(" + pkg_prefix + "(?:_\w+)?(?:\.\w+)*?)\s+import\s+"
r"((?:\w+(?:,\s*)?)*" # Match zero or more words separated by a comma+optional ws
r"(?:\s*\(.*?\))?)", # Match optional parentheses block
re.DOTALL, # Match newlines as well
)
# Regular expression to match langchain import lines
_IMPORT_LANGCHAIN_RE = _make_regular_expression("langchain")
_IMPORT_LANGGRAPH_RE = _make_regular_expression("langgraph")
def _get_full_module_name(module_path, class_name) -> Optional[str]:
"""Get full module name using inspect"""
try:
module = importlib.import_module(module_path)
class_ = getattr(module, class_name)
module = inspect.getmodule(class_)
if module is None:
# For constants, inspect.getmodule() might return None
# In this case, we'll return the original module_path
return module_path
return module.__name__
except AttributeError as e:
logger.warning(f"Could not find module for {class_name}, {e}")
return None
except ImportError as e:
logger.warning(f"Failed to load for class {class_name}, {e}")
return None
def _get_doc_title(data: str, file_name: str) -> str:
try:
return re.findall(r"^#\s*(.*)", data, re.MULTILINE)[0]
except IndexError:
pass
# Parse the rst-style titles
try:
return re.findall(r"^(.*)\n=+\n", data, re.MULTILINE)[0]
except IndexError:
return file_name
class ImportInformation(TypedDict):
imported: str # imported class name
source: str # module path
docs: str # URL to the documentation
title: str # Title of the document
def _get_imports(
code: str, doc_title: str, package_ecosystem: Literal["langchain", "langgraph"]
) -> List[ImportInformation]:
"""Get imports from the given code block.
Args:
code: Python code block from which to extract imports
doc_title: Title of the document
package_ecosystem: "langchain" or "langgraph". The two live in different
repositories and have separate documentation sites.
Returns:
List of import information for the given code block
"""
imports = []
if package_ecosystem == "langchain":
pattern = _IMPORT_LANGCHAIN_RE
elif package_ecosystem == "langgraph":
pattern = _IMPORT_LANGGRAPH_RE
else:
raise ValueError(f"Invalid package ecosystem: {package_ecosystem}")
for import_match in pattern.finditer(code):
module = import_match.group(1)
if "pydantic_v1" in module:
continue
imports_str = (
import_match.group(2).replace("(\n", "").replace("\n)", "")
) # Handle newlines within parentheses
# remove any newline and spaces, then split by comma
imported_classes = [
imp.strip()
for imp in re.split(r",\s*", imports_str.replace("\n", ""))
if imp.strip()
]
for class_name in imported_classes:
module_path = _get_full_module_name(module, class_name)
if not module_path:
continue
if len(module_path.split(".")) < 2:
continue
if package_ecosystem == "langchain":
pkg = module_path.split(".")[0].replace("langchain_", "")
top_level_mod = module_path.split(".")[1]
url = (
_LANGCHAIN_API_REFERENCE
+ pkg
+ "/"
+ top_level_mod
+ "/"
+ module_path
+ "."
+ class_name
+ ".html"
)
elif package_ecosystem == "langgraph":
if (module, class_name) not in WELL_KNOWN_LANGGRAPH_OBJECTS:
# Likely not documented yet
continue
source_module, namespace = WELL_KNOWN_LANGGRAPH_OBJECTS[
(module, class_name)
]
url = (
_LANGGRAPH_API_REFERENCE
+ namespace
+ "/#"
+ source_module
+ "."
+ class_name
)
else:
raise ValueError(f"Invalid package ecosystem: {package_ecosystem}")
# Add the import information to our list
imports.append(
{
"imported": class_name,
"source": module,
"docs": url,
"title": doc_title,
}
)
return imports
class ImportPreprocessor(Preprocessor):
"""A preprocessor to replace imports in each Python code cell with links to their
documentation and append the import info in a comment."""
def preprocess(self, nb, resources):
self.all_imports = []
file_name = os.path.basename(resources.get("metadata", {}).get("name", ""))
_DOC_TITLE = _get_doc_title(nb.cells[0].source, file_name)
cells = []
for cell in nb.cells:
if cell.cell_type == "code":
cells.append(cell)
imports = _get_imports(
cell.source, _DOC_TITLE, "langchain"
) + _get_imports(cell.source, _DOC_TITLE, "langgraph")
if not imports:
continue
cells.append(
nbformat.v4.new_markdown_cell(
source=f"""
<div>
<b>API Reference:</b>
{' | '.join(f'<a href="{imp["docs"]}">{imp["imported"]}</a>' for imp in imports)}
</div>
"""
)
)
else:
cells.append(cell)
nb.cells = cells
return nb, resources
+126
View File
@@ -0,0 +1,126 @@
import os
import re
from pathlib import Path
import nbformat
from nbconvert.exporters import MarkdownExporter
from nbconvert.preprocessors import Preprocessor
from generate_api_reference_links import ImportPreprocessor
class EscapePreprocessor(Preprocessor):
def preprocess_cell(self, cell, resources, cell_index):
if cell.cell_type == "markdown":
# rewrite markdown links to html links (excluding image links)
cell.source = re.sub(
r"(?<!!)\[([^\]]*)\]\((?![^\)]*//)([^)]*)(?:\.ipynb)?\)",
r'<a href="\2">\1</a>',
cell.source,
)
# Fix image paths in <img> tags
cell.source = re.sub(
r'<img\s+src="\.?/img/([^"]+)"', r'<img src="../img/\1"', cell.source
)
elif cell.cell_type == "code":
# escape ``` in code
cell.source = cell.source.replace("```", r"\`\`\`")
# escape ``` in output
if "outputs" in cell:
filter_out = set()
for i, output in enumerate(cell["outputs"]):
if "text" in output:
if not output["text"].strip():
filter_out.add(i)
continue
value = output["text"].replace("```", r"\`\`\`")
# handle a funky case w/ references in text
value = re.sub(r"\[(\d+)\](?=\[(\d+)\])", r"[\1]\\", value)
output["text"] = value
elif "data" in output:
for key, value in output["data"].items():
if isinstance(value, str):
value = value.replace("```", r"\`\`\`")
# handle a funky case w/ references in text
output["data"][key] = re.sub(
r"\[(\d+)\](?=\[(\d+)\])", r"[\1]\\", value
)
cell["outputs"] = [
output
for i, output in enumerate(cell["outputs"])
if i not in filter_out
]
return cell, resources
class ExtractAttachmentsPreprocessor(Preprocessor):
"""
Extracts all of the outputs from the notebook file. The extracted
outputs are returned in the 'resources' dictionary.
"""
def preprocess_cell(self, cell, resources, cell_index):
"""
Apply a transformation on each cell,
Parameters
----------
cell : NotebookNode cell
Notebook cell being processed
resources : dictionary
Additional resources used in the conversion process. Allows
preprocessors to pass variables into the Jinja engine.
cell_index : int
Index of the cell being processed (see base.py)
"""
# Get files directory if it has been specified
# Make sure outputs key exists
if not isinstance(resources["outputs"], dict):
resources["outputs"] = {}
# Loop through all of the attachments in the cell
for name, attach in cell.get("attachments", {}).items():
for mime, data in attach.items():
if mime not in {
"image/png",
"image/jpeg",
"image/svg+xml",
"application/pdf",
}:
continue
# attachments are pre-rendered. Only replace markdown-formatted
# images with the following logic
attach_str = f"({name})"
if attach_str in cell.source:
data = f"(data:{mime};base64,{data})"
cell.source = cell.source.replace(attach_str, data)
return cell, resources
exporter = MarkdownExporter(
preprocessors=[
EscapePreprocessor,
ExtractAttachmentsPreprocessor,
ImportPreprocessor,
],
template_name="mdoutput",
extra_template_basedirs=[
os.path.join(os.path.dirname(__file__), "notebook_convert_templates")
],
)
def convert_notebook(
notebook_path: Path,
) -> Path:
with open(notebook_path) as f:
nb = nbformat.read(f, as_version=4)
body, _ = exporter.from_notebook_node(nb)
return body
@@ -0,0 +1,5 @@
{
"mimetypes": {
"text/markdown": true
}
}
@@ -0,0 +1,33 @@
{% extends 'markdown/index.md.j2' %}
{%- block traceback_line -%}
```output
{{ line.rstrip() | strip_ansi }}
```
{%- endblock traceback_line -%}
{%- block stream -%}
```output
{{ output.text.rstrip() }}
```
{%- endblock stream -%}
{%- block data_text scoped -%}
```output
{{ output.data['text/plain'].rstrip() }}
```
{%- endblock data_text -%}
{%- block data_html scoped -%}
```html
{{ output.data['text/html'] | safe }}
```
{%- endblock data_html -%}
{%- block data_jpg scoped -%}
![](data:image/jpg;base64,{{ output.data['image/jpeg'] }})
{%- endblock data_jpg -%}
{%- block data_png scoped -%}
![](data:image/png;base64,{{ output.data['image/png'] }})
{%- endblock data_png -%}
+40
View File
@@ -0,0 +1,40 @@
import logging
from typing import Any, Dict
from mkdocs.structure.pages import Page
from mkdocs.structure.files import Files, File
from notebook_convert import convert_notebook
logger = logging.getLogger(__name__)
logging.basicConfig()
logger.setLevel(logging.INFO)
class NotebookFile(File):
def is_documentation_page(self):
return True
def on_files(files: Files, **kwargs: Dict[str, Any]):
new_files = Files([])
for file in files:
if file.src_path.endswith(".ipynb"):
new_file = NotebookFile(
path=file.src_path,
src_dir=file.src_dir,
dest_dir=file.dest_dir,
use_directory_urls=file.use_directory_urls,
)
new_files.append(new_file)
else:
new_files.append(file)
return new_files
def on_page_markdown(markdown: str, page: Page, **kwargs: Dict[str, Any]):
if page.file.src_path.endswith(".ipynb"):
logger.info("Processing Jupyter notebook: %s", page.file.src_path)
body = convert_notebook(page.file.abs_src_path)
return body
return markdown
+9 -5
View File
@@ -40,7 +40,6 @@ NOTEBOOKS_NO_EXECUTION = [
"docs/docs/tutorials/storm/storm.ipynb", # issues only when running with VCR
"docs/docs/tutorials/lats/lats.ipynb", # issues only when running with VCR
"docs/docs/tutorials/multi_agent/hierarchical_agent_teams.ipynb", # taking a very long time to run
"docs/docs/tutorials/customer-support/customer-support.ipynb", # user input - update
"docs/docs/tutorials/rag/langgraph_crag.ipynb", # flakiness from tavily
"docs/docs/tutorials/rag/langgraph_adaptive_rag.ipynb", # Cannot create a consistent method resolution error from VCR
"docs/docs/how-tos/map-reduce.ipynb" # flakiness from structured output, only when running with VCR
@@ -70,10 +69,13 @@ def is_comment(code: str) -> bool:
return code.strip().startswith("#")
def has_blocklisted_command(code: str) -> bool:
def has_blocklisted_command(code: str, metadata: dict) -> bool:
if 'hide_from_vcr' in metadata:
return True
code = code.strip()
for blocklisted_command in BLOCKLIST_COMMANDS:
if blocklisted_command in code:
for blocklisted_pattern in BLOCKLIST_COMMANDS:
if blocklisted_pattern in code:
return True
return False
@@ -108,7 +110,7 @@ def add_vcr_to_notebook(
if all(is_comment(line) or not line.strip() for line in lines):
continue
if has_blocklisted_command(cell.source):
if has_blocklisted_command(cell.source, cell.metadata):
continue
cell_id = cell.get("id", idx)
@@ -125,6 +127,8 @@ def add_vcr_to_notebook(
"import msgpack",
"import base64",
"import zlib",
"import os",
"os.environ.pop(\"LANGCHAIN_TRACING_V2\", None)",
"custom_vcr = vcr.VCR()",
"",
"def compress_data(data, compression_level=9):",
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -6,7 +6,7 @@ This can be in several ways, but the primary supported way is to add an "interru
## 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.
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#agent) if you want to. Once this graph is hosted, we are ready to invoke it and wait for user input.
### SDK initialization
@@ -14,7 +14,7 @@ Luckily, LangGraph makes it possible to do similar things in a production way. T
## 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.
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#agent) if you want to. Once this graph is hosted, we are ready to invoke it and wait for user input.
### SDK initialization
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -103,15 +103,15 @@ Parallel processing is vital for efficient multi-agent systems and complex tasks
For practical implementation, see our [map-reduce tutorial](../how-tos/map-reduce.ipynb).
### Sub-graphs
### Subgraphs
Sub-graphs are essential for managing complex agent architectures, particularly in multi-agent systems. They allow:
[Subgraphs](./low_level.md#subgraphs) are essential for managing complex agent architectures, particularly in [multi-agent systems](./multi_agent.md). They allow:
- Isolated state management for individual agents
- Hierarchical organization of agent teams
- Controlled communication between agents and the main system
Sub-graphs communicate with the parent graph through overlapping keys in the state schema. This enables flexible, modular agent design. For implementation details, refer to our [sub-graph tutorial](../how-tos/subgraph.ipynb).
Subgraphs communicate with the parent graph through overlapping keys in the state schema. This enables flexible, modular agent design. For implementation details, refer to our [subgraph how-to guide](../how-tos/subgraph.ipynb).
### Reflection
Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

+112 -9
View File
@@ -20,7 +20,7 @@ A super-step can be considered a single iteration over the graph nodes. Nodes th
### StateGraph
The `StateGraph` class is the main graph class to uses. This is parameterized by a user defined `State` object.
The `StateGraph` class is the main graph class to use. This is parameterized by a user defined `State` object.
### MessageGraph
@@ -52,12 +52,12 @@ By default, the graph will have the same input and output schemas. If you want t
Typically, all graph nodes communicate with a single schema. This means that they will read and write to the same state channels. But, there are cases where we want more control over this:
* Internal nodes can pass information that is not required in the graph's input / output.
* We may also want to use different input / output schemas for the graph. The output might, for example, only contain a single relevant output key.
- Internal nodes can pass information that is not required in the graph's input / output.
- We may also want to use different input / output schemas for the graph. The output might, for example, only contain a single relevant output key.
It is possible to have nodes write to private state channels inside the graph for internal node communication. We can simply define a private schema, `PrivateState`. See [this notebook](../how-tos/pass_private_state.ipynb) for more detail.
It is possible to have nodes write to private state channels inside the graph for internal node communication. We can simply define a private schema, `PrivateState`. See [this notebook](../how-tos/pass_private_state.ipynb) for more detail.
It is also possible to define explicit input and output schemas for a graph. In these cases, we define an "internal" schema that contains *all* keys relevant to graph operations. But, we also define `input` and `output` schemas that are sub-sets of the "internal" schema to constrain the input and output of the graph. See [this notebook](../how-tos/input_output_schema.ipynb) for more detail.
It is also possible to define explicit input and output schemas for a graph. In these cases, we define an "internal" schema that contains _all_ keys relevant to graph operations. But, we also define `input` and `output` schemas that are sub-sets of the "internal" schema to constrain the input and output of the graph. See [this notebook](../how-tos/input_output_schema.ipynb) for more detail.
Let's look at an example:
@@ -101,11 +101,12 @@ graph = builder.compile()
graph.invoke({"user_input":"My"})
{'graph_output': 'My name is Lance'}
```
There are two subtle and important points to note here:
1. We pass `state: InputState` as the input schema to `node_1`. But, we write out to `foo`, a channel in `OverallState`. How can we write out to a state channel that is not included in the input schema? This is because a node *can write to any state channel in the graph state.* The graph state is the union of of the state channels defined at initialization, which includes `OverallState` and the filters `InputState` and `OutputState`.
1. We pass `state: InputState` as the input schema to `node_1`. But, we write out to `foo`, a channel in `OverallState`. How can we write out to a state channel that is not included in the input schema? This is because a node _can write to any state channel in the graph state._ The graph state is the union of of the state channels defined at initialization, which includes `OverallState` and the filters `InputState` and `OutputState`.
2. We initialize the graph with `StateGraph(OverallState,input=InputState,output=OutputState)`. So, how can we write to `PrivateState` in `node_2`? How does the graph gain access to this schema if it was not passed in the `StateGraph` initialization? We can do this because *nodes can also declare additional state channels* as long as the state schema definition exists. In this case, the `PrivateState` schema is defined, so we can add `bar` as a new state channel in the graph and write to it.
2. We initialize the graph with `StateGraph(OverallState,input=InputState,output=OutputState)`. So, how can we write to `PrivateState` in `node_2`? How does the graph gain access to this schema if it was not passed in the `StateGraph` initialization? We can do this because _nodes can also declare additional state channels_ as long as the state schema definition exists. In this case, the `PrivateState` schema is defined, so we can add `bar` as a new state channel in the graph and write to it.
### Reducers
@@ -323,7 +324,7 @@ graph.add_conditional_edges("node_a", continue_to_jokes)
## Persistence
LangGraph provides built-in persistence for your agent's state using [checkpointers][langgraph.checkpoint.base.BaseCheckpointSaver]. Checkpointers save snapshots of the graph state at every superstep, allowing resumption at any time. This enables features like human-in-the-loop interactions, memory management, and fault-tolerance. You can even directly manipulate a graph's state after its execution using the
LangGraph provides built-in persistence for your agent's state using [checkpointers][langgraph.checkpoint.base.BaseCheckpointSaver]. Checkpointers save snapshots of the graph state at every superstep, allowing resumption at any time. This enables features like human-in-the-loop interactions, memory management, and fault-tolerance. You can even directly manipulate a graph's state after its execution using the
appropriate `get` and `update` methods. For more details, see the [persistence conceptual guide](./persistence.md).
## Threads
@@ -416,10 +417,112 @@ def my_node(state: State) -> State:
return state
```
## Subgraphs
A subgraph is a [graph](#graphs) that is used as a [node](#nodes) in another graph. This is nothing more than the age-old concept of encapsulation, applied to LangGraph. Some reasons for using subgraphs are:
- building [multi-agent systems](./multi_agent.md)
- when you want to reuse a set of nodes in multiple graphs, which maybe share some state, you can define them once in a subgraph and then use them in multiple parent graphs
- when you want different teams to work on different parts of the graph independently, you can define each part as a subgraph, and as long as the subgraph interface (the input and output schemas) is respected, the parent graph can be built without knowing any details of the subgraph
There are two ways to add subgraphs to a parent graph:
- add a node with the compiled subgraph: this is useful when the parent graph and the subgraph share state keys and you don't need to transform state on the way in or out
```python
builder.add_node("subgraph", subgraph_builder.compile())
```
- add a node with a function that invokes the subgraph: this is useful when the parent graph and the subgraph have different state schemas and you need to transform state before or after calling the subgraph
```python
subgraph = subgraph_builder.compile()
def call_subgraph(state: State):
return subgraph.invoke({"subgraph_key": state["parent_key"]})
builder.add_node("subgraph", call_subgraph)
```
Let's take a look at examples for each.
### As a compiled graph
The simplest way to create subgraph nodes is by using a [compiled subgraph](#compiling-your-graph) directly. When doing so, it is **important** that the parent graph and the subgraph [state schemas](#state) share at least one key which they can use to communicate. If your graph and subgraph do not share any keys, you should use write a function [invoking the subgraph](#as-a-function) instead.
!!! Note
If you pass extra keys to the subgraph node (i.e., in addition to the shared keys), they will be ignored by the subgraph node. Similarly, if you return extra keys from the subgraph, they will be ignored by the parent graph.
```python
from langgraph.graph import START, StateGraph
from typing import TypedDict
class State(TypedDict):
foo: str
class SubgraphState(TypedDict):
foo: str # note that this key is shared with the parent graph state
bar: str
# Define subgraph
def subgraph_node(state: SubgraphState):
# note that this subgraph node can communicate with the parent graph via the shared "foo" key
return {"foo": state["foo"] + "bar"}
subgraph_builder = StateGraph(SubgraphState)
subgraph_builder.add_node(subgraph_node)
...
subgraph = subgraph_builder.compile()
# Define parent graph
builder = StateGraph(State)
builder.add_node("subgraph", subgraph)
...
graph = builder.compile()
```
### As a function
You might want to define a subgraph with a completely different schema. In this case, you can create a node function that invokes the subgraph. This function will need to [transform](../how-tos/subgraph-transform-state.ipynb) the input (parent) state to the subgraph state before invoking the subgraph, and transform the results back to the parent state before returning the state update from the node.
```python
class State(TypedDict):
foo: str
class SubgraphState(TypedDict):
# note that none of these keys are shared with the parent graph state
bar: str
baz: str
# Define subgraph
def subgraph_node(state: SubgraphState):
return {"bar": state["bar"] + "baz"}
subgraph_builder = StateGraph(SubgraphState)
subgraph_builder.add_node(subgraph_node)
...
subgraph = subgraph_builder.compile()
# Define parent graph
def node(state: State):
# transform the state to the subgraph state
response = subgraph.invoke({"bar": state["foo"]})
# transform response back to the parent state
return {"foo": response["bar"]}
builder = StateGraph(State)
# note that we are using `node` function instead of a compiled subgraph
builder.add_node(node)
...
graph = builder.compile()
```
## Visualization
It's often nice to be able to visualize graphs, especially as they get more complex. LangGraph comes with several built-in ways to visualize graphs. See [this how-to guide](../how-tos/visualization.ipynb) for more info.
## Streaming
LangGraph is built with first class support for streaming, including streaming updates from graph nodes during the execution, streaming tokens from LLM calls and more. See this [conceptual guide](./streaming.md) for more information.
LangGraph is built with first class support for streaming, including streaming updates from graph nodes during the execution, streaming tokens from LLM calls and more. See this [conceptual guide](./streaming.md) for more information.
+2 -2
View File
@@ -183,9 +183,9 @@ store = InMemoryStore()
user_id = "my-user"
application_context = "chitchat"
namespace = (user_id, application_context)
store.put(namespace, key="a-memory", {"rules": ["User likes short, direct language", "User only speaks English & python"], "my-key": "my-value"})
store.put(namespace, "a-memory", {"rules": ["User likes short, direct language", "User only speaks English & python"], "my-key": "my-value"})
# get the "memory" by ID
item = store.get(namespace)
item = store.get(namespace, "a-memory")
# list "memories" within this namespace, filtering on content equivalence
items = store.search(namespace, filter={"my-key": "my-value"})
```
+225 -82
View File
@@ -1,138 +1,281 @@
# Multi-agent Systems
A multi-agent system is a system with multiple independent actors powered by LLMs that are connected in a specific way. These actors can be as simple as a prompt and an LLM call, or as complex as a [ReAct](./agentic_concepts.md#react-implementation) agent.
An [agent](./agentic_concepts.md#agent-architectures) is _a system that uses an LLM to decide the control flow of an application_. As you develop these systems, they might grow more complex over time, making them harder to manage and scale. For example, you might run into the following problems:
The primary benefits of this architecture are:
- agent has too many tools at its disposal and makes poor decisions about which tool to call next
- context grows too complex for a single agent to keep track of
- there is a need for multiple specialization areas in the system (e.g. planner, researcher, math expert, etc.)
* **Modularity**: Separate agents facilitate easier development, testing, and maintenance of agentic systems.
* **Specialization**: You can create expert agents focused on specific domains, and compose them into more complex applications
* **Control**: You can explicitly control how agents communicate (as opposed to relying on function calling)
To tackle these, you might consider breaking your application into multiple smaller, independent agents and composing them into a **multi-agent system**. These independent agents can be as simple as a prompt and an LLM call, or as complex as a [ReAct](./agentic_concepts.md#react-implementation) agent (and more!).
## Multi-agent systems in LangGraph
The primary benefits of using multi-agent systems are:
### Agents as nodes
- **Modularity**: Separate agents make it easier to develop, test, and maintain agentic systems.
- **Specialization**: You can create expert agents focused on specific domains, which helps with the overall system performance.
- **Control**: You can explicitly control how agents communicate (as opposed to relying on function calling).
Agents can be defined as nodes in LangGraph. As any other node in the LangGraph, these agent nodes receive the graph state as an input and return an update to the state as their output.
## Multi-agent architectures
* Simple **LLM nodes**: single LLMs with custom prompts
* **Subgraph nodes**: complex graphs called inside the orchestrator graph node
![](./img/multi_agent/architectures.png)
![](./img/multi_agent/subgraph.png)
There are several ways to connect agents in a multi-agent system:
### Agents as tools
- **Network**: each agent can communicate with [every other agent](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/multi-agent-collaboration/). Any agent can decide which other agent to call next.
- **Supervisor**: each agent communicates with a single [supervisor](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/agent_supervisor/) agent. Supervisor agent makes decisions on which agent should be called next.
- **Supervisor (tool-calling)**: this is a special case of supervisor architecture. Individual agents can be represented as tools. In this case, a supervisor agent uses a tool-calling LLM to decide which of the agent tools to call, as well as the arguments to pass to those agents.
- **Hierarchical**: you can define a multi-agent system with [a supervisor of supervisors](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/hierarchical_agent_teams/). This is a generalization of the supervisor architecture and allows for more complex control flows.
- **Custom multi-agent workflow**: each agent communicates with only a subset of agents. Parts of the flow are deterministic, and only some agents can decide which other agents to call next.
Agents can also be defined as tools. In this case, the orchestrator agent (e.g. ReAct agent) would use a tool-calling LLM to decide which of the agent tools to call, as well as the arguments to pass to those agents.
### Network
You could also take a "mega-graph" approach incorporating subordinate agents' nodes directly into the parent, orchestrator graph. However, this is not recommended for complex subordinate agents, as it would make the overall system harder to scale, maintain and debug you should use subgraphs or tools in those cases.
In this architecture, agents are defined as graph nodes. Each agent can communicate with every other agent (many-to-many connections) and can decide which agent to call next. While very flexible, this architecture doesn't scale well as the number of agents grows:
## Communication in multi-agent systems
- hard to enforce which agent should be called next
- hard to determine how much [information](#shared-message-list) should be passed between the agents
A big question in multi-agent systems is how the agents communicate amongst themselves and with the orchestrator agent. This involves both the schema of how they communicate, as well as the sequence in which they communicate. LangGraph is perfect for orchestrating these types of systems and allows you to define both.
We recommend avoiding this architecture in production and using one of the below architectures instead.
### Schema
### Supervisor
LangGraph provides a lot of flexibility for how to communicate within multi-agent architectures.
* A node in LangGraph can have a [private input state schema](https://langchain-ai.github.io/langgraph/how-tos/pass_private_state/) that is distinct from the graph state schema. This allows passing additional information during the graph execution that is only needed for executing a particular node.
* Subgraph node agents can have independent [input / output state schemas](https://langchain-ai.github.io/langgraph/how-tos/input_output_schema/). In this case its important to [add input / output transformations](https://langchain-ai.github.io/langgraph/how-tos/subgraph-transform-state/) so that the parent graph knows how to communicate with the subgraphs.
* For tool-based subordinate agents, the orchestrator determines the inputs based on the tool schema. Additionally, LangGraph allows passing state to individual tools at runtime, so subordinate agents can access parent state, if needed.
### Sequence
LangGraph provides multiple methods to control agent communication sequence:
* **Explicit control flow (graph edges)**: LangGraph allows you to define the control flow of your application (i.e. the sequence of how agents communicate) explicitly, via [graph edges](./low_level.md#edges).
In this architecture, we define agents as nodes and add a supervisor node (LLM) that decides which agent nodes should be called next. We use [conditional edges](./low_level.md#conditional-edges) to route execution to the appropriate agent node based on supervisor's decision. This architecture also lends itself well to running multiple agents in parallel or using [map-reduce](../how-tos/map-reduce.ipynb) pattern.
```python
from typing import Literal
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.graph import StateGraph, MessagesState, START
model = ChatOpenAI(model="gpt-4o-mini")
model = ChatOpenAI()
def research_agent(state: MessagesState):
"""Call research agent"""
messages = [SystemMessage(content="You are a research assistant. Given a topic, provide key facts and information.")] + state["messages"]
response = model.invoke(messages)
class AgentState(MessagesState):
next: Literal["agent_1", "agent_2", "__end__"]
def supervisor(state: AgentState):
# you can pass relevant parts of the state to the LLM (e.g., state["messages"])
# to determine which agent to call next. a common pattern is to call the model
# with a structured output (e.g. force it to return an output with a "next_agent" field)
response = model.invoke(...)
# the "next" key will be used by the conditional edges to route execution
# to the appropriate agent
return {"next": response["next_agent"]}
def agent_1(state: AgentState):
# you can pass relevant parts of the state to the LLM (e.g., state["messages"])
# and add any additional logic (different models, custom prompts, structured output, etc.)
response = model.invoke(...)
return {"messages": [response]}
def summarize_agent(state: MessagesState):
"""Call summarization agent"""
messages = [SystemMessage(content="You are a summarization expert. Condense the given information into a brief summary.")] + state["messages"]
response = model.invoke(messages)
def agent_2(state: AgentState):
response = model.invoke(...)
return {"messages": [response]}
graph = StateGraph(MessagesState)
graph.add_node("research", research_agent)
graph.add_node("summarize", summarize_agent)
builder = StateGraph(AgentState)
builder.add_node(supervisor)
builder.add_node(agent_1)
builder.add_node(agent_2)
# define the flow explicitly
graph.add_edge(START, "research")
graph.add_edge("research", "summarize")
graph.add_edge("summarize", END)
builder.add_edge(START, "supervisor")
# route to one of the agents or exit based on the supervisor's decisiion
# if the supervisor returns "__end__", the graph will finish execution
builder.add_conditional_edges("supervisor", lambda state: state["next"])
builder.add_edge("agent_1", "supervisor")
builder.add_edge("agent_2", "supervisor")
supervisor = builder.compile()
```
* **Dynamic control flow (conditional edges)**: LangGraph also allows you to define [conditional edges](./low_level.md#conditional-edges), where the control flow is dependent on satisfying a given condition. In such cases, you can use an LLM to decide which subordinate agent to call next.
Check out this [tutorial](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/agent_supervisor/) for an example of supervisor multi-agent architecture.
### Supervisor (tool-calling)
* **Implicit control flow (tool calling)**: if the orchestrator agent treats subordinate agents as tools, the tool-calling LLM powering the orchestrator will make decisions about the order in which the tools (agents) are being called.
In this variant of the [supervisor](#supervisor) architecture, we define individual agents as **tools** and use a tool-calling LLM in the supervisor node. This can be implemented as a [ReAct](./agentic_concepts.md#react-implementation)-style agent with two nodes — an LLM node (supervisor) and a tool-calling node that executes tools (agents in this case).
```python
from typing import Annotated
from langchain_core.messages import SystemMessage, ToolMessage
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import ToolNode, InjectedState, create_react_agent
from langgraph.prebuilt import InjectedState, create_react_agent
model = ChatOpenAI(model="gpt-4o-mini")
model = ChatOpenAI()
def research_agent(state: Annotated[dict, InjectedState]):
"""Call research agent"""
messages = [SystemMessage(content="You are a research assistant. Given a topic, provide key facts and information.")] + state["messages"][:-1]
response = model.invoke(messages)
tool_call = state["messages"][-1].tool_calls[0]
return {"messages": [ToolMessage(response.content, tool_call_id=tool_call["id"])]}
# this is the agent function that will be called as tool
# notice that you can pass the state to the tool via InjectedState annotation
def agent_1(state: Annotated[dict, InjectedState]):
# you can pass relevant parts of the state to the LLM (e.g., state["messages"])
# and add any additional logic (different models, custom prompts, structured output, etc.)
response = model.invoke(...)
# return the LLM response as a string (expected tool response format)
# this will be automatically turned to ToolMessage
# by the prebuilt create_react_agent (supervisor)
return response.content
def summarize_agent(state: Annotated[dict, InjectedState]):
"""Call summarization agent"""
messages = [SystemMessage(content="You are a summarization expert. Condense the given information into a brief summary.")] + state["messages"][:-1]
response = model.invoke(messages)
tool_call = state["messages"][-1].tool_calls[0]
return {"messages": [ToolMessage(response.content, tool_call_id=tool_call["id"])]}
def agent_2(state: Annotated[dict, InjectedState]):
response = model.invoke(...)
return response.content
tool_node = ToolNode([research_agent, summarize_agent])
graph = create_react_agent(model, [research_agent, summarize_agent], state_modifier="First research and then summarize information on a given topic.")
tools = [agent_1, agent_2]
# the simplest way to build a supervisor w/ tool-calling is to use prebuilt ReAct agent graph
# that consists of a tool-calling LLM node (i.e. supervisor) and a tool-executing node
supervisor = create_react_agent(model, tools)
```
## Example architectures
### Hierarchical
Below are several examples of complex multi-agent architectures that can be implemented in LangGraph.
As you add more agents to your system, it might become too hard for the supervisor to manage all of them. The supervisor might start making poor decisions about which agent to call next, the context might become too complex for a single supervisor to keep track of. In other words, you end up with the same problems that motivated the multi-agent architecture in the first place.
### Multi-Agent Collaboration
To address this, you can design your system _hierarchically_. For example, you can create separate, specialized teams of agents managed by individual supervisors, and a top-level supervisor to manage the teams.
In this example, different agents collaborate on a **shared** scratchpad of messages (i.e. shared graph state). This means that all the work any of them do is visible to the other ones. The benefit is that the other agents can see all the individual steps done. The downside is that sometimes is it overly verbose and unnecessary to pass ALL this information along, and sometimes only the final answer from an agent is needed. We call this **collaboration** because of the shared nature the scratchpad.
```python
from typing import Literal
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, MessagesState, START
In this case, the independent agents are actually just a single LLM call with a custom system message.
model = ChatOpenAI()
Here is a visualization of how these agents are connected:
# define team 1 (same as the single supervisor example above)
class Team1State(MessagesState):
next: Literal["team_1_agent_1", "team_1_agent_2", "__end__"]
![](./img/multi_agent/collaboration.png)
def team_1_supervisor(state: Team1State):
response = model.invoke(...)
return {"next": response["next_agent"]}
See full code example in this [tutorial](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/multi-agent-collaboration/).
def team_1_agent_1(state: Team1State):
response = model.invoke(...)
return {"messages": [response]}
### Agent Supervisor
def team_1_agent_2(state: Team1State):
response = model.invoke(...)
return {"messages": [response]}
In this example, multiple agents are connected, but compared to above they do NOT share a shared scratchpad. Rather, they have their own independent scratchpads (i.e. their own state), and then their final responses are appended to a global scratchpad.
team_1_builder = StateGraph(Team1State)
team_1_builder.add_node(team_1_supervisor)
team_1_builder.add_node(team_1_agent_1)
team_1_builder.add_node(team_1_agent_2)
team_1_builder.add_edge(START, "team_1_supervisor")
# route to one of the agents or exit based on the supervisor's decisiion
# if the supervisor returns "__end__", the graph will finish execution
team_1_builder.add_conditional_edges("team_1_supervisor", lambda state: state["next"])
team_1_builder.add_edge("team_1_agent_1", "team_1_supervisor")
team_1_builder.add_edge("team_1_agent_2", "team_1_supervisor")
In this case, the independent agents are a LangGraph ReAct agent (graph). This means they have their own individual prompt, LLM, and tools. When called, it's not just a single LLM call, but rather an invocation of the graph powering the ReAct agent.
team_1_graph = team_1_builder.compile()
![](./img/multi_agent/supervisor.png)
# define team 2 (same as the single supervisor example above)
class Team2State(MessagesState):
next: Literal["team_2_agent_1", "team_2_agent_2", "__end__"]
See full code example in this [tutorial](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/agent_supervisor/).
def team_2_supervisor(state: Team2State):
...
### Hierarchical Agent Teams
def team_2_agent_1(state: Team2State):
...
What if the job for a single worker in agent supervisor example becomes too complex? What if the number of workers becomes too large? For some applications, the system may be more effective if work is distributed hierarchically. You can do this by creating additional level of subgraphs and creating a top-level supervisor, along with mid-level supervisors:
def team_2_agent_2(state: Team2State):
...
![](./img/multi_agent/hierarchical.png)
team_2_builder = StateGraph(Team2State)
...
team_2_graph = team_2_builder.compile()
See full code example in this [tutorial](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/hierarchical_agent_teams/).
# define top-level supervisor
class TopLevelState(MessagesState):
next: Literal["team_1", "team_2", "__end__"]
builder = StateGraph(TopLevelState)
def top_level_supervisor(state: TopLevelState):
# you can pass relevant parts of the state to the LLM (e.g., state["messages"])
# to determine which team to call next. a common pattern is to call the model
# with a structured output (e.g. force it to return an output with a "next_team" field)
response = model.invoke(...)
# the "next" key will be used by the conditional edges to route execution
# to the appropriate team
return {"next": response["next_team"]}
builder = StateGraph(TopLevelState)
builder.add_node(top_level_supervisor)
builder.add_node(team_1_graph)
builder.add_node(team_2_graph)
builder.add_edge(START, "top_level_supervisor")
# route to one of the teams or exit based on the supervisor's decision
# if the top-level supervisor returns "__end__", the graph will finish execution
builder.add_conditional_edges("top_level_supervisor", lambda state: state["next"])
builder.add_edge("team_1_graph", "top_level_supervisor")
builder.add_edge("team_2_graph", "top_level_supervisor")
graph = builder.compile()
```
### Custom multi-agent workflow
In this architecture we add individual agents as graph nodes and define the order in which agents are called ahead of time, in a custom workflow. In LangGraph the workflow can be defined in two ways:
- **Explicit control flow (normal edges)**: LangGraph allows you to explicitly define the control flow of your application (i.e. the sequence of how agents communicate) explicitly, via [normal graph edges](./low_level.md#normal-edges). This is the most deterministic variant of this architecture above — we always know which agent will be called next ahead of time.
- **Dynamic control flow (conditional edges)**: in LangGraph you can allow LLMs to decide parts of your application control flow. This can be achieved by using [conditional edges](./low_level.md#conditional-edges). A special case of this is a [supervisor tool-calling](#supervisor-tool-calling) architecture. In that case, the tool-calling LLM powering the supervisor agent will make decisions about the order in which the tools (agents) are being called.
```python
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, MessagesState, START
model = ChatOpenAI()
def agent_1(state: MessagesState):
response = model.invoke(...)
return {"messages": [response]}
def agent_2(state: MessagesState):
response = model.invoke(...)
return {"messages": [response]}
builder = StateGraph(MessagesState)
builder.add_node(agent_1)
builder.add_node(agent_2)
# define the flow explicitly
builder.add_edge(START, "agent_1")
builder.add_edge("agent_1", "agent_2")
```
## Communication between agents
The most important thing when building multi-agent systems is figuring out how the agents communicate. There are few different considerations:
- Do agents communicate via [**via graph state or via tool calls**](#graph-state-vs-tool-calls)?
- What if two agents have [**different state schemas**](#different-state-schemas)?
- How to communicate over a [**shared message list**](#shared-message-list)?
### Graph state vs tool calls
What is the "payload" that is being passed around between agents? In most of the architectures discussed above the agents communicate via the [graph state](./low_level.md#state). In the case of the [supervisor with tool-calling](#supervisor-tool-calling), the payloads are tool call arguments.
![](./img/multi_agent/request.png)
#### Graph state
To communicate via graph state, individual agents need to be defined as [graph nodes](./low_level.md#nodes). These can be added as functions or as entire [subgraphs](./low_level.md#subgraphs). At each step of the graph execution, agent node receives the current state of the graph, executes the agent code and then passes the updated state to the next nodes.
Typically agent nodes share a single [state schema](./low_level.md#schema). However, you might want to design agent nodes with [different state schemas](#different-state-schemas).
### Different state schemas
An agent might need to have a different state schema from the rest of the agents. For example, a search agent might only need to keep track of queries and retrieved documents. There are two ways to achieve this in LangGraph:
- Define [subgraph](./low_level.md#subgraphs) agents with a separate state schema. If there are no shared state keys (channels) between the subgraph and the parent graph, its important to [add input / output transformations](https://langchain-ai.github.io/langgraph/how-tos/subgraph-transform-state/) so that the parent graph knows how to communicate with the subgraphs.
- Define agent node functions with a [private input state schema](https://langchain-ai.github.io/langgraph/how-tos/pass_private_state/) that is distinct from the overall graph state schema. This allows passing information that is only needed for executing that particular agent.
### Shared message list
The most common way for the agents to communicate is via a shared state channel, typically a list of messages. This assumes that there is always at least a single channel (key) in the state that is shared by the agents. When communicating via a shared message list there is an additional consideration: should the agents [share the full history](#share-full-history) of their thought process or only [the final result](#share-final-result)?
![](./img/multi_agent/response.png)
#### Share full history
Agents can **share the full history** of their thought process (i.e. "scratchpad") with all other agents. This "scratchpad" would typically look like a [list of messages](./low_level.md#why-use-messages). The benefit of sharing full thought process is that it might help other agents make better decisions and improve reasoning ability for the system as a whole. The downside is that as the number of agents and their complexity grows, the "scratchpad" will grow quickly and might require additional strategies for [memory management](./memory.md/#managing-long-conversation-history).
#### Share final result
Agents can have their own private "scratchpad" and only **share the final result** with the rest of the agents. This approach might work better for systems with many agents or agents that are more complex. In this case, you would need to define agents with [different state schemas](#different-state-schemas)
For agents called as tools, the supervisor determines the inputs based on the tool schema. Additionally, LangGraph allows [passing state](https://langchain-ai.github.io/langgraph/how-tos/pass-run-time-values-to-tools/#pass-graph-state-to-tools) to individual tools at runtime, so subordinate agents can access parent state, if needed.
+2 -2
View File
@@ -46,7 +46,7 @@
" <p>\n",
" In this how-to, we will create our agent from scratch to be transparent (but verbose). You can accomplish similar functionality using the <code>create_react_agent(model, tools=tool)</code> (<a href=\"https://langchain-ai.github.io/langgraph/reference/prebuilt/#create_react_agent\">API doc</a>) constructor. This may be more appropriate if you are used to LangChains <a href=\"https://python.langchain.com/v0.1/docs/modules/agents/concepts/#agentexecutor\">AgentExecutor</a> class.\n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -107,7 +107,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+40 -22
View File
@@ -72,7 +72,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -89,7 +89,7 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": 2,
"id": "09372b8b-edea-4b9d-9ec3-3d93ce1ba819",
"metadata": {},
"outputs": [],
@@ -132,7 +132,7 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": 3,
"id": "66f52a20",
"metadata": {},
"outputs": [
@@ -163,7 +163,7 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 4,
"id": "38846b01",
"metadata": {},
"outputs": [
@@ -183,7 +183,7 @@
"{'aggregate': [\"I'm A\", \"I'm B\", \"I'm C\", \"I'm D\"]}"
]
},
"execution_count": 5,
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
@@ -220,7 +220,7 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 5,
"id": "259a7704-5aa0-4e4c-aeef-cca04e8be0ff",
"metadata": {},
"outputs": [],
@@ -238,6 +238,15 @@
" aggregate: Annotated[list, operator.add]\n",
"\n",
"\n",
"class ReturnNodeValue:\n",
" def __init__(self, node_secret: str):\n",
" self._value = node_secret\n",
"\n",
" def __call__(self, state: State) -> Any:\n",
" print(f\"Adding {self._value} to {state['aggregate']}\")\n",
" return {\"aggregate\": [self._value]}\n",
"\n",
"\n",
"builder = StateGraph(State)\n",
"builder.add_node(\"a\", ReturnNodeValue(\"I'm A\"))\n",
"builder.add_edge(START, \"a\")\n",
@@ -255,7 +264,7 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 6,
"id": "83320227-8ab3-44c0-b6cf-064a7a425b9f",
"metadata": {},
"outputs": [
@@ -278,7 +287,7 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": 7,
"id": "3f971fa3-29e4-466f-a85e-2863bfecf7fe",
"metadata": {},
"outputs": [
@@ -299,7 +308,7 @@
"{'aggregate': [\"I'm A\", \"I'm B\", \"I'm C\", \"I'm B2\", \"I'm D\"]}"
]
},
"execution_count": 8,
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
@@ -322,7 +331,7 @@
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": 8,
"id": "95f5e026",
"metadata": {},
"outputs": [],
@@ -341,6 +350,15 @@
" which: str\n",
"\n",
"\n",
"class ReturnNodeValue:\n",
" def __init__(self, node_secret: str):\n",
" self._value = node_secret\n",
"\n",
" def __call__(self, state: State) -> Any:\n",
" print(f\"Adding {self._value} to {state['aggregate']}\")\n",
" return {\"aggregate\": [self._value]}\n",
"\n",
"\n",
"builder = StateGraph(State)\n",
"builder.add_node(\"a\", ReturnNodeValue(\"I'm A\"))\n",
"builder.add_edge(START, \"a\")\n",
@@ -372,7 +390,7 @@
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": 9,
"id": "1d0e6c56",
"metadata": {},
"outputs": [
@@ -395,7 +413,7 @@
},
{
"cell_type": "code",
"execution_count": 11,
"execution_count": 10,
"id": "7134f652",
"metadata": {},
"outputs": [
@@ -415,7 +433,7 @@
"{'aggregate': [\"I'm A\", \"I'm B\", \"I'm C\", \"I'm E\"], 'which': 'bc'}"
]
},
"execution_count": 11,
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
@@ -426,7 +444,7 @@
},
{
"cell_type": "code",
"execution_count": 12,
"execution_count": 11,
"id": "b130e694",
"metadata": {},
"outputs": [
@@ -446,7 +464,7 @@
"{'aggregate': [\"I'm A\", \"I'm C\", \"I'm D\", \"I'm E\"], 'which': 'cd'}"
]
},
"execution_count": 12,
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
@@ -471,7 +489,7 @@
},
{
"cell_type": "code",
"execution_count": 13,
"execution_count": 12,
"id": "836bc12d",
"metadata": {},
"outputs": [],
@@ -564,7 +582,7 @@
},
{
"cell_type": "code",
"execution_count": 14,
"execution_count": 13,
"id": "932c497e",
"metadata": {},
"outputs": [
@@ -587,7 +605,7 @@
},
{
"cell_type": "code",
"execution_count": 15,
"execution_count": 14,
"id": "933b3afd",
"metadata": {},
"outputs": [
@@ -608,7 +626,7 @@
" 'which': 'bc'}"
]
},
"execution_count": 15,
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
@@ -619,7 +637,7 @@
},
{
"cell_type": "code",
"execution_count": 16,
"execution_count": 15,
"id": "e30531bf",
"metadata": {},
"outputs": [
@@ -640,7 +658,7 @@
" 'which': 'cd'}"
]
},
"execution_count": 16,
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
@@ -666,7 +684,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.4"
"version": "3.12.3"
}
},
"nbformat": 4,
+1 -1
View File
@@ -76,7 +76,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -91,7 +91,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -96,7 +96,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -92,7 +92,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+1 -1
View File
@@ -100,7 +100,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -66,7 +66,7 @@
"metadata": {},
"outputs": [
{
"name": "stdin",
"name": "stdout",
"output_type": "stream",
"text": [
"ANTHROPIC_API_KEY: ········\n"
@@ -91,12 +91,9 @@
"id": "51b6817d",
"metadata": {},
"source": [
"<div class=\"admonition tip\">\n",
" <p class=\"admonition-title\">Set up <a href=\"https://smith.langchain.com\">LangSmith</a> for LangGraph development</p>\n",
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"!!! tip \"Set up [LangSmith](https://smith.langchain.com) for LangGraph development\"\n",
"\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started [here](https://docs.smith.langchain.com)"
]
},
{
@@ -85,7 +85,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -45,7 +45,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -85,7 +85,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -82,7 +82,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -89,7 +89,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -78,7 +78,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+10 -3
View File
@@ -21,6 +21,7 @@ These how-to guides show how to achieve that controllability.
LangGraph makes it easy to persist state across graph runs (thread-level persistence) and across threads (cross-thread persistence). These how-to guides show how to add persistence to your graph.
- [How to add thread-level persistence to your graph](persistence.ipynb)
- [How to add thread-level persistence to subgraphs](subgraph-persistence.ipynb)
- [How to add cross-thread persistence to your graph](cross-thread-persistence.ipynb)
- [How to use Postgres checkpointer for persistence](persistence_postgres.ipynb)
- [How to create a custom checkpointer using MongoDB](persistence_mongodb.ipynb)
@@ -73,8 +74,8 @@ These guides show how to use different streaming modes.
## Subgraphs
- [How to create subgraphs](subgraph.ipynb)
- [How to manage state in subgraphs](subgraphs-manage-state.ipynb)
- [How to add and use subgraphs](subgraph.ipynb)
- [How to view and update state in subgraphs](subgraphs-manage-state.ipynb)
- [How to transform inputs and outputs of a subgraph](subgraph-transform-state.ipynb)
## State Management
@@ -103,4 +104,10 @@ Please note that here will we use a **prebuilt agent**. One of the big benefits
- [How to add memory to a ReAct agent](create-react-agent-memory.ipynb)
- [How to add a custom system prompt to a ReAct agent](create-react-agent-system-prompt.ipynb)
- [How to add human-in-the-loop processes to a ReAct agent](create-react-agent-hitl.ipynb)
- [How to create prebuilt ReAct agent from scratch](react-agent-from-scratch.ipynb)
- [How to create prebuilt ReAct agent from scratch](react-agent-from-scratch.ipynb)
## Troubleshooting
### Errors
- [Error reference](../troubleshooting/errors/index.md)
+1 -1
View File
@@ -58,7 +58,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -103,7 +103,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -75,7 +75,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -82,7 +82,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -73,7 +73,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+1 -1
View File
@@ -66,7 +66,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+1 -1
View File
@@ -100,7 +100,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -158,7 +158,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+1 -1
View File
@@ -51,7 +51,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+3 -3
View File
@@ -47,7 +47,7 @@
"<div class=\"admonition tip\">\n",
" <p class=\"admonition-title\">Note</p>\n",
" <p>\n",
" If you need memory that is <b>shared</b> across multiple conversations or users (cross-thread persistence), check out this <a href=\"https://langchain-ai.github.io/langgraph/how-tos/cross-thread-persistence/\">how-to guide</a>).\n",
" If you need memory that is <b>shared</b> across multiple conversations or users (cross-thread persistence), check out this <a href=\"https://langchain-ai.github.io/langgraph/how-tos/cross-thread-persistence/\">how-to guide</a>.\n",
" </p>\n",
"</div>"
]
@@ -88,7 +88,7 @@
"metadata": {},
"outputs": [
{
"name": "stdin",
"name": "stdout",
"output_type": "stream",
"text": [
"ANTHROPIC_API_KEY: ········\n"
@@ -118,7 +118,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+1 -1
View File
@@ -102,7 +102,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+1 -1
View File
@@ -98,7 +98,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+1 -1
View File
@@ -102,7 +102,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -123,7 +123,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+1 -1
View File
@@ -59,7 +59,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -44,7 +44,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
@@ -61,7 +61,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -70,12 +70,12 @@
"source": [
"## Without returning state\n",
"\n",
"We are going to define a dummy graph in this example that will always hit the recursion limit. First, we will implement it without returning the state and show that it hits the recursion limit. This graph is based on the ReACT architecture, but instead of actually making decisions and taking actions it just loops forever."
"We are going to define a dummy graph in this example that will always hit the recursion limit. First, we will implement it without returning the state and show that it hits the recursion limit. This graph is based on the ReAct architecture, but instead of actually making decisions and taking actions it just loops forever."
]
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
@@ -116,7 +116,7 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": 3,
"metadata": {},
"outputs": [
{
@@ -145,7 +145,7 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": 4,
"metadata": {},
"outputs": [
{
@@ -171,18 +171,18 @@
"source": [
"## With returning state\n",
"\n",
"If we wanted to actually return the state, what we are going to do is introduce a new key to our state called `is_last_step` which keeps track of if we are on the last step of our recursion limit. If so, we will bypass all other graph decisions and simply terminate the graph, returning the state to the user without causing an error.\n",
"To avoid hitting the recursion limit, we can introduce a new key to our state called `remaining_steps`. It will keep track of number of steps until reaching the recursion limit. We can then check the value of `remaining_steps` to determine whether we should terminate the graph execution and return the state to the user without causing the `RecursionError`.\n",
"\n",
"We are going to use a `ManagedValue` channel to do this. A `ManagedValue` channel is a state channel that will exist for the duration of our graph run and no longer. Since our `action` node is going to always induce at least 2 extra steps to our graph (since the `action` node ALWAYS calls the `decision` node afterwards), we will use this channel to check if we are within 2 steps of the limit. See the implementation of `IsLastOrSecondToLastStepManager` below.\n",
"To do so, we will use a special `RemainingSteps` annotation. Under the hood, it creates a special `ManagedValue` channel -- a state channel that will exist for the duration of our graph run and no longer.\n",
"\n",
"This implementation very closely mirrors the implementation of `isLastStep` (which you can use by calling `from langgraph.managed import IsLastStep` and then decorating state keys with the `isLastStep` type), but in this case we check if we are on the last OR second-to-last step, instead of just the last step.\n",
"Since our `action` node is going to always induce at least 2 extra steps to our graph (since the `action` node ALWAYS calls the `decision` node afterwards), we will use this channel to check if we are within 2 steps of the limit.\n",
"\n",
"Now, when we run our graph we should receive no errors and instead get the last value of the state before the recursion limit was hit."
]
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
@@ -190,24 +190,18 @@
"from langgraph.graph import StateGraph\n",
"from typing import Annotated\n",
"\n",
"from langgraph.managed.base import ManagedValue\n",
"\n",
"\n",
"class IsLastOrSecondToLastStepManager(ManagedValue[bool]):\n",
" def __call__(self, step: int) -> bool:\n",
" limit = self.config.get(\"recursion_limit\", 0)\n",
" return step >= limit - 2\n",
"from langgraph.managed.is_last_step import RemainingSteps\n",
"\n",
"\n",
"class State(TypedDict):\n",
" value: str\n",
" action_result: str\n",
" is_last_step: Annotated[bool, IsLastOrSecondToLastStepManager]\n",
" remaining_steps: RemainingSteps\n",
"\n",
"\n",
"def router(state: State):\n",
" # Force the agent to end if it is on the last step\n",
" if state[\"is_last_step\"]:\n",
" # Force the agent to end\n",
" if state[\"remaining_steps\"] <= 2:\n",
" return END\n",
" if state[\"value\"] == \"end\":\n",
" return END\n",
@@ -235,7 +229,7 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 6,
"metadata": {},
"outputs": [
{
@@ -244,7 +238,7 @@
"{'value': 'keep going!', 'action_result': 'what a great result!'}"
]
},
"execution_count": 5,
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
@@ -277,7 +271,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.4"
"version": "3.11.9"
}
},
"nbformat": 4,
+1 -1
View File
@@ -112,7 +112,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+1 -1
View File
@@ -109,7 +109,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+1 -1
View File
@@ -66,7 +66,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+1 -1
View File
@@ -71,7 +71,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+1 -1
View File
@@ -71,7 +71,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+1 -1
View File
@@ -72,7 +72,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -89,7 +89,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -93,7 +93,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+1 -1
View File
@@ -50,7 +50,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -66,7 +66,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+4 -4
View File
@@ -16,7 +16,7 @@
" <p>\n",
" In this how-to, we will create our agent from scratch to be transparent (but verbose). You can accomplish similar functionality using the <code>create_react_agent(model, tools=tool)</code> (<a href=\"https://langchain-ai.github.io/langgraph/reference/prebuilt/#create_react_agent\">API doc</a>) constructor. This may be more appropriate if you are used to LangChains <a href=\"https://python.langchain.com/docs/how_to/agent_executor/#concepts\">AgentExecutor</a> class.\n",
" </p>\n",
"</div> \n",
"</div>\n",
"\n",
"<div class=\"admonition warning\">\n",
" <p class=\"admonition-title\">Note on Python < 3.11</p>\n",
@@ -24,7 +24,7 @@
" When using python 3.8, 3.9, or 3.10, please ensure you manually pass the RunnableConfig through to the llm when invoking it like so: <code>llm.ainvoke(..., config)</code>.\n",
" The stream method collects all events from your nested code using a streaming tracer passed as a callback. In 3.11 and above, this is automatically handled via <a href=\"https://docs.python.org/3/library/contextvars.html\">contextvar</a>'s; prior to 3.11, <a href=\"https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task\">asyncio's tasks</a> lacked proper contextvar support, meaning that the callbacks will only propagate if you manually pass the config through. We do this in the <code>call_model</code> method below.\n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -85,7 +85,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -264,7 +264,7 @@
" <p class=\"admonition-title\">Manual Callback Propagation</p>\n",
" <p>\n",
" Note that in <code>call_model(state: State, config: RunnableConfig):</code> below, we a) accept the <a href=\"https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.config.RunnableConfig.html#langchain_core.runnables.config.RunnableConfig\">RunnableConfig</a> in the node and b) pass this in as the second arg for <code>llm.ainvoke(..., config)</code>. This is optional for python 3.11 and later.</p>\n",
"</div> "
"</div>"
]
},
{
@@ -0,0 +1,379 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "176e8dbb-1a0a-49ce-a10e-2417e8ea17a0",
"metadata": {},
"source": [
"# How to add thread-level persistence to subgraphs"
]
},
{
"cell_type": "markdown",
"id": "8c67581a-49fb-4597-a7fc-6774581c2160",
"metadata": {},
"source": [
"<div class=\"admonition tip\">\n",
" <p class=\"admonition-title\">Prerequisites</p>\n",
" <p>\n",
" This guide assumes familiarity with the following:\n",
" <ul>\n",
" <li> \n",
" <a href=\"https://langchain-ai.github.io/langgraph/concepts/low_level/#subgraphs\">\n",
" Subgraphs\n",
" </a>\n",
" </li>\n",
" <li>\n",
" <a href=\"https://langchain-ai.github.io/langgraph/concepts/persistence/\">\n",
" Persistence\n",
" </a>\n",
" </li>\n",
" </ul>\n",
" </p>\n",
"</div>\n",
"\n",
"This guide shows how you can add [thread-level](https://langchain-ai.github.io/langgraph/how-tos/persistence/) persistence to graphs that use [subgraphs](https://langchain-ai.github.io/langgraph/how-tos/subgraph/)."
]
},
{
"cell_type": "markdown",
"id": "8f83b855-ab23-4de7-9559-702cad9a29c6",
"metadata": {},
"source": [
"## Setup\n",
"\n",
"First, let's install the required packages"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "77d1eafa-3252-45f6-9af0-d94e1f9c5c9e",
"metadata": {},
"outputs": [],
"source": [
"%%capture --no-stderr\n",
"%pip install -U langgraph"
]
},
{
"cell_type": "markdown",
"id": "2e60c6cd-bf4e-46af-9761-b872d0fbe3b6",
"metadata": {},
"source": [
"<div class=\"admonition tip\">\n",
" <p class=\"admonition-title\">Set up <a href=\"https://smith.langchain.com\">LangSmith</a> for LangGraph development</p>\n",
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div>"
]
},
{
"cell_type": "markdown",
"id": "871b9056-fec7-4683-8c22-f56c91f5b13b",
"metadata": {},
"source": [
"## Define the graph with persistence"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "9f1303ef-df37-48e0-8a59-8ff169c52c5b",
"metadata": {},
"source": [
"To add persistence to a graph with subgraphs, all you need to do is pass a [checkpointer](https://langchain-ai.github.io/langgraph/reference/checkpoints/#langgraph.checkpoint.base.BaseCheckpointSaver) when **compiling the parent graph**. LangGraph will automatically propagate the checkpointer to the child subgraphs."
]
},
{
"cell_type": "markdown",
"id": "c74cde2e-c127-4326-8d36-b6acef987f0a",
"metadata": {},
"source": [
"!!! note\n",
" You **shouldn't provide** a checkpointer when compiling a subgraph. Instead, you must define a **single** checkpointer that you pass to `parent_graph.compile()`, and LangGraph will automatically propagate the checkpointer to the child subgraphs. If you pass the checkpointer to the `subgraph.compile()`, it will simply be ignored. This also applies when you [add a node function that invokes the subgraph](../subgraph#add-a-node-function-that-invokes-the-subgraph)."
]
},
{
"cell_type": "markdown",
"id": "c3a1fe22-1ca9-45eb-a35b-71b9c905e8c5",
"metadata": {},
"source": [
"Let's define a simple graph with a single subgraph node to show how to do this."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "0d76f0c0-bd77-4eca-9527-27bcdf85dd42",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<langgraph.graph.state.StateGraph at 0x106d2fa10>"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langgraph.graph import START, StateGraph\n",
"from langgraph.checkpoint.memory import MemorySaver\n",
"from typing import TypedDict\n",
"\n",
"\n",
"# subgraph\n",
"\n",
"\n",
"class SubgraphState(TypedDict):\n",
" foo: str # note that this key is shared with the parent graph state\n",
" bar: str\n",
"\n",
"\n",
"def subgraph_node_1(state: SubgraphState):\n",
" return {\"bar\": \"bar\"}\n",
"\n",
"\n",
"def subgraph_node_2(state: SubgraphState):\n",
" # note that this node is using a state key ('bar') that is only available in the subgraph\n",
" # and is sending update on the shared state key ('foo')\n",
" return {\"foo\": state[\"foo\"] + state[\"bar\"]}\n",
"\n",
"\n",
"subgraph_builder = StateGraph(SubgraphState)\n",
"subgraph_builder.add_node(subgraph_node_1)\n",
"subgraph_builder.add_node(subgraph_node_2)\n",
"subgraph_builder.add_edge(START, \"subgraph_node_1\")\n",
"subgraph_builder.add_edge(\"subgraph_node_1\", \"subgraph_node_2\")\n",
"subgraph = subgraph_builder.compile()\n",
"\n",
"\n",
"# parent graph\n",
"\n",
"\n",
"class State(TypedDict):\n",
" foo: str\n",
"\n",
"\n",
"def node_1(state: State):\n",
" return {\"foo\": \"hi! \" + state[\"foo\"]}\n",
"\n",
"\n",
"builder = StateGraph(State)\n",
"builder.add_node(\"node_1\", node_1)\n",
"# note that we're adding the compiled subgraph as a node to the parent graph\n",
"builder.add_node(\"node_2\", subgraph)\n",
"builder.add_edge(START, \"node_1\")\n",
"builder.add_edge(\"node_1\", \"node_2\")"
]
},
{
"cell_type": "markdown",
"id": "47084b1f-9fd5-40a9-9d75-89eb5f853d02",
"metadata": {},
"source": [
"We can now compile the graph with an in-memory checkpointer (`MemorySaver`)."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "7657d285-c896-40c9-a569-b4a3b9c230c7",
"metadata": {},
"outputs": [],
"source": [
"checkpointer = MemorySaver()\n",
"# You must only pass checkpointer when compiling the parent graph.\n",
"# LangGraph will automatically propagate the checkpointer to the child subgraphs.\n",
"graph = builder.compile(checkpointer=checkpointer)"
]
},
{
"cell_type": "markdown",
"id": "0d193e3c-4ec3-4034-beed-8e5550c6542c",
"metadata": {},
"source": [
"## Verify persistence works"
]
},
{
"cell_type": "markdown",
"id": "eb69a5f0-b92e-4d4e-9aa9-c4c4ec7de91a",
"metadata": {},
"source": [
"Let's now run the graph and inspect the persisted state for both the parent graph and the subgraph to verify that persistence works. We should expect to see the final execution results for both the parent and subgraph in `state.values`."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "13da686e-6ed6-4b83-93e8-1631fcc8c2a9",
"metadata": {},
"outputs": [],
"source": [
"config = {\"configurable\": {\"thread_id\": \"1\"}}"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "8721f045-2e82-4bf0-9d85-5ba6ecf899d6",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'node_1': {'foo': 'hi! foo'}}\n",
"{'subgraph_node_1': {'bar': 'bar'}}\n",
"{'subgraph_node_2': {'foo': 'hi! foobar'}}\n",
"{'node_2': {'foo': 'hi! foobar'}}\n"
]
}
],
"source": [
"for _, chunk in graph.stream({\"foo\": \"foo\"}, config, subgraphs=True):\n",
" print(chunk)"
]
},
{
"cell_type": "markdown",
"id": "ec6b5ce4-becc-4910-8a6d-d6b60d9d6f60",
"metadata": {},
"source": [
"We can now view the parent graph state by calling `graph.get_state()` with the same config that we used to invoke the graph."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "3e817283-142d-4fda-8cb1-8de34717f833",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'foo': 'hi! foobar'}"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"graph.get_state(config).values"
]
},
{
"cell_type": "markdown",
"id": "fbc4f30b-941e-4140-8bfa-3b8cc670489c",
"metadata": {},
"source": [
"To view the subgraph state, we need to do two things:\n",
"\n",
"1. Find the most recent config value for the subgraph\n",
"2. Use `graph.get_state()` to retrieve that value for the most recent subgraph config.\n",
"\n",
"To find the correct config, we can examine the state history from the parent graph and find the state snapshot before we return results from `node_2` (the node with subgraph):"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "e896628f-36b2-45eb-b7c5-c64c1098f328",
"metadata": {},
"outputs": [],
"source": [
"state_with_subgraph = [\n",
" s for s in graph.get_state_history(config) if s.next == (\"node_2\",)\n",
"][0]"
]
},
{
"cell_type": "markdown",
"id": "7af49977-42b1-40a1-88f1-f07437f8b7f9",
"metadata": {},
"source": [
"The state snapshot will include the list of `tasks` to be executed next. When using subgraphs, the `tasks` will contain the config that we can use to retrieve the subgraph state:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "21e96df3-946d-40f8-8d6d-055ae4177452",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'configurable': {'thread_id': '1',\n",
" 'checkpoint_ns': 'node_2:6ef111a6-f290-7376-0dfc-a4152307bc5b'}}"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"subgraph_config = state_with_subgraph.tasks[0].state\n",
"subgraph_config"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "1d2401b3-d52b-4895-a5d1-dccf015ba216",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'foo': 'hi! foobar', 'bar': 'bar'}"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"graph.get_state(subgraph_config).values"
]
},
{
"cell_type": "markdown",
"id": "40aded92-99dd-427b-932d-aa78f474c271",
"metadata": {},
"source": [
"If you want to learn more about how to modify the subgraph state for human-in-the-loop workflows, check out this [how-to guide](https://langchain-ai.github.io/langgraph/how-tos/subgraphs-manage-state/)."
]
}
],
"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.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -35,7 +35,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -171,7 +171,7 @@
" <p>\n",
" We're wrapping the <code>grandchild_graph</code> invocation in a separate function (<code>call_grandchild_graph</code>) that transforms the input state before calling the grandchild graph and then transforms the output of grandchild graph back to child graph state. If you just pass <code>grandchild_graph</code> directly to <code>.add_node</code> without the transformations, LangGraph will raise an error as there are no shared state channels (keys) between child and grandchild states.\n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -240,7 +240,7 @@
" <p>\n",
" We're wrapping the <code>child_graph</code> invocation in a separate function (<code>call_child_graph</code>) that transforms the input state before calling the child graph and then transforms the output of the child graph back to parent graph state. If you just pass <code>child_graph</code> directly to <code>.add_node</code> without the transformations, LangGraph will raise an error as there are no shared state channels (keys) between parent and child states.\n",
" </p>\n",
"</div> \n",
"</div>\n",
"\n",
"Let's run the parent graph and make sure it correctly calls both the child and grandchild subgraphs:"
]
File diff suppressed because one or more lines are too long
+48 -9
View File
@@ -5,15 +5,48 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to manage state in subgraphs\n",
"# How to view and update state in subgraphs\n",
"\n",
"For more complex systems, sub-graphs are a useful design principle. Sub-graphs allow you to create and manage different states in different parts of your graph. This allows you build things like [multi-agent teams](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/hierarchical_agent_teams/), where each team can track its own separate state.\n",
"<div class=\"admonition tip\">\n",
" <p class=\"admonition-title\">Prerequisites</p>\n",
" <p>\n",
" This guide assumes familiarity with the following:\n",
" <ul>\n",
" <li> \n",
" <a href=\"https://langchain-ai.github.io/langgraph/concepts/low_level/#subgraphs\">\n",
" Subgraphs\n",
" </a>\n",
" </li>\n",
" <li> \n",
" <a href=\"https://langchain-ai.github.io/langgraph/concepts/human_in_the_loop/\">\n",
" Human-in-the-loop\n",
" </a>\n",
" </li>\n",
" <li> \n",
" <a href=\"https://langchain-ai.github.io/langgraph/concepts/low_level/#state\">\n",
" State\n",
" </a>\n",
" </li>\n",
" </ul>\n",
" </p>\n",
"</div> \n",
"\n",
"In this how-to guide we will cover how to manage the persisted state in subgraphs. This will enable a lot of the human-in-the-loop interaction patterns.\n",
"Once you add [persistence](../subgraph-persistence), you can easily view and update the state of the subgraph at any point in time. This enables a lot of the human-in-the-loop interaction patterns:\n",
"\n",
"* You can surface a state during an interrupt to a user to let them accept an action.\n",
"* You can rewind the subgraph to reproduce or avoid issues.\n",
"* You can modify the state to let the user better control its actions.\n",
"\n",
"This guide shows how you can do this."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Setup\n",
"\n",
"First we need to install the packages required"
"First, let's install the required packages"
]
},
{
@@ -60,7 +93,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -68,7 +101,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Define SubGraph\n",
"## Define subgraph\n",
"\n",
"First, let's set up our subgraph. For this, we will create a simple graph that can get the weather for a specific city. We will compile this graph with a [breakpoint](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/breakpoints/) before the `weather_node`:"
]
@@ -121,7 +154,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Define Parent Graph\n",
"## Define parent graph\n",
"\n",
"We can now setup the overall graph. This graph will first route to the subgraph if it needs to get the weather, otherwise it will route to a normal LLM."
]
@@ -444,7 +477,7 @@
" if h.next == (\"model_node\",)\n",
")\n",
"\n",
"# This pattern can be extended no matter how many levels deep - image model node was another subgraph in this case\n",
"# This pattern can be extended no matter how many levels deep\n",
"# subsubgraph_stat_history = next(h for h in graph.get_state_history(subgraph_state_before_model_node.tasks[0].state) if h.next == ('my_subsubgraph_node',))"
]
},
@@ -660,7 +693,9 @@
" print(update)\n",
"# Graph execution should stop before the weather node\n",
"print(\"interrupted!\")\n",
"\n",
"state = graph.get_state(config, subgraphs=True)\n",
"\n",
"# We update the state by passing in the message we want returned from the weather node, and make sure to use as_node\n",
"graph.update_state(\n",
" state.tasks[0].state.config,\n",
@@ -669,6 +704,7 @@
")\n",
"for update in graph.stream(None, config=config, stream_mode=\"updates\", subgraphs=True):\n",
" print(update)\n",
"\n",
"print(graph.get_state(config).values[\"messages\"])"
]
},
@@ -708,6 +744,7 @@
" print(update)\n",
"# Graph execution should stop before the weather node\n",
"print(\"interrupted!\")\n",
"\n",
"# We update the state by passing in the message we want returned from the weather graph, making sure to use as_node\n",
"# Note that we don't need to pass in the subgraph config, since we aren't updating the state inside the subgraph\n",
"graph.update_state(\n",
@@ -717,6 +754,7 @@
")\n",
"for update in graph.stream(None, config=config, stream_mode=\"updates\"):\n",
" print(update)\n",
"\n",
"print(graph.get_state(config).values[\"messages\"])"
]
},
@@ -947,6 +985,7 @@
" None, config=config, stream_mode=\"updates\", subgraphs=True\n",
"):\n",
" print(update)\n",
"\n",
"print(grandparent_graph.get_state(config).values[\"messages\"])"
]
},
@@ -1002,7 +1041,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
"version": "3.12.3"
}
},
"nbformat": 4,
+1 -1
View File
@@ -76,7 +76,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+1 -1
View File
@@ -57,7 +57,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -0,0 +1,29 @@
# GRAPH_RECURSION_LIMIT
Your LangGraph [`StateGraph`](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.state.StateGraph) reached the maximum number of steps before hitting a stop condition.
This is often due to an infinite loop caused by code like the example below:
```python
class State(TypedDict):
some_key: str
builder = StateGraph(State)
builder.add_node("a", ...)
builder.add_node("b", ...)
builder.add_edge("a", "b")
builder.add_edge("b", "a")
...
graph = builder.compile()
```
However, complex graphs may hit the default limit naturally.
## Troubleshooting
- If you are not expecting your graph to go through many iterations, you likely have a cycle. Check your logic for infinite loops.
- If you have a complex graph, you can pass in a higher `recursion_limit` value into your `config` object when invoking your graph like this:
```python
graph.invoke({...}, {"recursion_limit": 100})
```
@@ -0,0 +1,49 @@
# INVALID_CONCURRENT_GRAPH_UPDATE
A LangGraph [`StateGraph`](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.state.StateGraph) received concurrent updates to its state from multiple nodes to a state property that doesn't
support it.
One way this can occur is if you are using a [fanout](https://langchain-ai.github.io/langgraph/how-tos/map-reduce/)
or other parallel execution in your graph and you have defined a graph like this:
```python
class State(TypedDict):
some_key: str
def node(state: State):
return {"some_key": "some_string_value"}
def other_node(state: State):
return {"some_key": "some_string_value"}
builder = StateGraph(State)
builder.add_node(node)
builder.add_node(other_node)
builder.add_edge(START, "node")
builder.add_edge(START, "other_node")
graph = builder.compile()
```
If a node in the above graph returns `{ "some_key": "some_string_value" }`, this will overwrite the state value for `"some_key"` with `"some_string_value"`.
However, if multiple nodes in e.g. a fanout within a single step return values for `"some_key"`, the graph will throw this error because
there is uncertainty around how to update the internal state.
To get around this, you can define a reducer that combines multiple values:
```python
import operator
from typing import Annotated
class State(TypedDict):
# The operator.add reducer fn makes this append-only
some_key: Annotated[list, operator.add]
```
This will allow you to define logic that handles the same key returned from multiple nodes executed in parallel.
## Troubleshooting
The following may help resolve this error:
- If your graph executes nodes in parallel, make sure you have defined relevant state keys with a reducer.
@@ -0,0 +1,38 @@
# INVALID_GRAPH_NODE_RETURN_VALUE
A LangGraph [`StateGraph`](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.state.StateGraph)
received a non-dict return type from a node. Here's an example:
```python
class State(TypedDict):
some_key: str
def bad_node(state: State):
# Should return an dict with a value for "some_key", not a list
return ["whoops"]
builder = StateGraph(State)
builder.add_node(bad_node)
...
graph = builder.compile()
```
Invoking the above graph will result in an error like this:
```python
graph.invoke({ "some_key": "someval" });
```
```
InvalidUpdateError: Expected dict, got ['whoops']
For troubleshooting, visit: https://python.langchain.com/docs/troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE
```
Nodes in your graph must return an dict containing one or more keys defined in your state.
## Troubleshooting
The following may help resolve this error:
- If you have complex logic in your node, make sure all code paths return an appropriate dict for your defined state.
@@ -0,0 +1,12 @@
# MULTIPLE_SUBGRAPHS
You are calling the same subgraph multiple times within a single LangGraph node with checkpointing enabled for each subgraph.
This is currently not allowed due to internal restrictions on how checkpoint namespacing for subgraphs works.
## Troubleshooting
The following may help resolve this error:
- If you don't need to interrupt/resume from a subgraph, pass `checkpointer=False` when compiling it like this: `.compile(checkpointer=False)`
- Don't imperatively call graphs multiple times in the same node, and instead use the [`Send`](https://langchain-ai.github.io/langgraph/concepts/low_level/#send) API.
@@ -0,0 +1,9 @@
# Error reference
This page contains guides around resolving common errors you may find while building with LangChain.
Errors referenced below will have an `lc_error_code` property corresponding to one of the below codes when they are thrown in code.
- [GRAPH_RECURSION_LIMIT](./GRAPH_RECURSION_LIMIT.md)
- [INVALID_CONCURRENT_GRAPH_UPDATE](./INVALID_CONCURRENT_GRAPH_UPDATE.md)
- [INVALID_GRAPH_NODE_RETURN_VALUE](./INVALID_GRAPH_NODE_RETURN_VALUE.md)
- [MULTIPLE_SUBGRAPHS](./MULTIPLE_SUBGRAPHS.md)
@@ -71,7 +71,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -78,7 +78,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -68,7 +68,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -85,7 +85,7 @@
"cell_type": "code",
"execution_count": 21,
"id": "71638c2a-5038-439e-907a-de2bb548db34",
"metadata": {},
"metadata": {"hide_from_vcr": true},
"outputs": [],
"source": [
"import os\n",
@@ -176,7 +176,7 @@
"cell_type": "code",
"execution_count": 22,
"id": "654e2f81",
"metadata": {},
"metadata": {"hide_from_vcr": true},
"outputs": [],
"source": [
"import re\n",
@@ -2340,10 +2340,13 @@
" # We have an interrupt! The agent is trying to use a tool, and the user can approve or deny it\n",
" # Note: This code is all outside of your graph. Typically, you would stream the output to a UI.\n",
" # Then, you would have the frontend trigger a new run via an API call when the user has provided input.\n",
" user_input = input(\n",
" \"Do you approve of the above actions? Type 'y' to continue;\"\n",
" \" otherwise, explain your requested changed.\\n\\n\"\n",
" )\n",
" try:\n",
" user_input = input(\n",
" \"Do you approve of the above actions? Type 'y' to continue;\"\n",
" \" otherwise, explain your requested changed.\\n\\n\"\n",
" )\n",
" except:\n",
" user_input = \"y\"\n",
" if user_input.strip() == \"y\":\n",
" # Just continue\n",
" result = part_2_graph.invoke(\n",
@@ -2958,10 +2961,13 @@
" # We have an interrupt! The agent is trying to use a tool, and the user can approve or deny it\n",
" # Note: This code is all outside of your graph. Typically, you would stream the output to a UI.\n",
" # Then, you would have the frontend trigger a new run via an API call when the user has provided input.\n",
" user_input = input(\n",
" \"Do you approve of the above actions? Type 'y' to continue;\"\n",
" \" otherwise, explain your requested changed.\\n\\n\"\n",
" )\n",
" try:\n",
" user_input = input(\n",
" \"Do you approve of the above actions? Type 'y' to continue;\"\n",
" \" otherwise, explain your requested changed.\\n\\n\"\n",
" )\n",
" except:\n",
" user_input = \"y\"\n",
" if user_input.strip() == \"y\":\n",
" # Just continue\n",
" result = part_3_graph.invoke(\n",
@@ -4373,10 +4379,13 @@
" # We have an interrupt! The agent is trying to use a tool, and the user can approve or deny it\n",
" # Note: This code is all outside of your graph. Typically, you would stream the output to a UI.\n",
" # Then, you would have the frontend trigger a new run via an API call when the user has provided input.\n",
" user_input = input(\n",
" \"Do you approve of the above actions? Type 'y' to continue;\"\n",
" \" otherwise, explain your requested changed.\\n\\n\"\n",
" )\n",
" try:\n",
" user_input = input(\n",
" \"Do you approve of the above actions? Type 'y' to continue;\"\n",
" \" otherwise, explain your requested changed.\\n\\n\"\n",
" )\n",
" except:\n",
" user_input = \"y\"\n",
" if user_input.strip() == \"y\":\n",
" # Just continue\n",
" result = part_4_graph.invoke(\n",
+1 -1
View File
@@ -60,7 +60,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+2 -2
View File
@@ -25,8 +25,8 @@ Learn from example implementations of graphs designed for specific scenarios and
#### Multi-Agent Systems
- [Collaboration](multi_agent/multi-agent-collaboration.ipynb): Enable two agents to collaborate on a task
- [Supervision](multi_agent/agent_supervisor.ipynb): Use an LLM to orchestrate and delegate to individual agents
- [Network](multi_agent/multi-agent-collaboration.ipynb): Enable two or more agents to collaborate on a task
- [Supervisor](multi_agent/agent_supervisor.ipynb): Use an LLM to orchestrate and delegate to individual agents
- [Hierarchical Teams](multi_agent/hierarchical_agent_teams.ipynb): Orchestrate nested teams of agents to solve problems
#### RAG
+1 -1
View File
@@ -78,7 +78,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -73,7 +73,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -10,17 +10,17 @@
"id": "a3e3ebc4-57af-4fe4-bdd3-36aff67bf276",
"metadata": {},
"source": [
"# Agent Supervisor\n",
"# Multi-agent supervisor\n",
"\n",
"The [previous example](../multi-agent-collaboration) routed messages automatically based on the output of the initial researcher agent.\n",
"\n",
"We can also choose to use an LLM to orchestrate the different agents.\n",
"We can also choose to use an [LLM to orchestrate](https://langchain-ai.github.io/langgraph/concepts/multi_agent/#supervisor) the different agents.\n",
"\n",
"Below, we will create an agent group, with an agent supervisor to help delegate tasks.\n",
"\n",
"![diagram](attachment:8ee0a8ce-f0a8-4019-b5bf-b20933e40956.png)\n",
"\n",
"To simplify the code in each agent node, we will use the AgentExecutor class from LangChain. This and other \"advanced agent\" notebooks are designed to show how you can implement certain design patterns in LangGraph. If the pattern suits your needs, we recommend combining it with some of the other fundamental patterns described elsewhere in the docs for best performance.\n",
"To simplify the code in each agent node, we will use LangGraph's prebuilt [create_react_agent](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent). This and other \"advanced agent\" notebooks are designed to show how you can implement certain design patterns in LangGraph. If the pattern suits your needs, we recommend combining it with some of the other fundamental patterns described elsewhere in the docs for best performance.\n",
"\n",
"## Setup\n",
"\n",
@@ -68,7 +68,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -376,7 +376,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
"version": "3.12.3"
}
},
"nbformat": 4,
@@ -12,7 +12,7 @@
"source": [
"# Hierarchical Agent Teams\n",
"\n",
"In our previous example ([Agent Supervisor](../agent_supervisor)), we introduced the concept of a single supervisor node to route work between different worker nodes.\n",
"In our previous example ([Agent Supervisor](../agent_supervisor)), we introduced the concept of a single [supervisor node](https://langchain-ai.github.io/langgraph/concepts/multi_agent/#supervisor) to route work between different worker nodes.\n",
"\n",
"But what if the job for a single worker becomes too complex? What if the number of workers becomes too large?\n",
"\n",
@@ -87,7 +87,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -1117,7 +1117,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
"version": "3.12.3"
}
},
"nbformat": 4,
@@ -10,11 +10,11 @@
"id": "39fd1948-b5c3-48c4-b10e-2ae7e8c83334",
"metadata": {},
"source": [
"# Basic Multi-agent Collaboration\n",
"# Multi-agent network\n",
"\n",
"A single agent can usually operate effectively using a handful of tools within a single domain, but even using powerful models like `gpt-4`, it can be less effective at using many tools. \n",
"\n",
"One way to approach complicated tasks is through a \"divide-and-conquer\" approach: create an specialized agent for each task or domain and route tasks to the correct \"expert\".\n",
"One way to approach complicated tasks is through a \"divide-and-conquer\" approach: create an specialized agent for each task or domain and route tasks to the correct \"expert\". This is an example of a [multi-agent network](https://langchain-ai.github.io/langgraph/concepts/multi_agent/#network) architecture.\n",
"\n",
"This notebook (inspired by the paper [AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation](https://arxiv.org/abs/2308.08155), by Wu, et. al.) shows one way to do this using LangGraph.\n",
"\n",
@@ -70,7 +70,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -535,7 +535,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
"version": "3.12.3"
}
},
"nbformat": 4,
@@ -92,7 +92,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -82,7 +82,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -58,7 +58,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
+1 -1
View File
@@ -83,7 +83,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -92,7 +92,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{
@@ -94,7 +94,7 @@
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
"</div>"
]
},
{

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