Compare commits

...
Author SHA1 Message Date
Tat Dat Duong b225b603b8 Add combining and extract scripts 2025-07-15 17:09:10 +02:00
Tat Dat Duong 1fbc623d57 Update prompt 2025-07-15 15:16:53 +02:00
Tat Dat Duong bc6bb1027e Remove ref, update with more of Zod being ported 2025-07-15 14:34:31 +02:00
Tat Dat Duong a1e35d8a3b Update script 2025-07-14 14:29:29 +01:00
Tat Dat Duong 5112f55904 Format 2025-07-12 12:42:15 +02:00
Tat Dat Duong c438dd2a03 Make sure we have undefined set 2025-07-12 12:40:27 +02:00
Tat Dat Duong 2c520982bb Remove tslab mentions 2025-07-12 12:36:57 +02:00
Tat Dat Duong 8414ae01de Replace lst[lst.length - 1] with .at(-1), _getType() with getType() 2025-07-12 12:31:52 +02:00
Tat Dat Duong 1801a193bc Initial commit 2025-07-12 12:24:11 +02:00
Tat Dat Duong 05299c8dba Fix broken code 2025-07-09 22:30:35 +02:00
Tat Dat Duong 283dbe5015 Update lockfile 2025-07-09 22:22:31 +02:00
Tat Dat Duong e224fdc84a Update examples 2025-07-09 22:03:18 +02:00
Tat Dat Duong 01c9f6e722 Update last message 2025-07-09 17:45:21 +02:00
Tat Dat Duong 288337b217 Another pass at four pages of get started guide
- Make sure that we're calling `getType` instead of deprecated `_getType()`
- Make sure that we're referencing the proper camelCase symbols instead of snake_case symbols
- Make sure that we're using proper links to the API reference
- Make sure to chain calls of StateGraph methods
- Make sure to use Zod state in all snippets
2025-07-09 16:38:14 +02:00
Tat Dat Duong 01c5302c13 Update core snippet 2025-07-04 03:21:45 +02:00
Tat Dat Duong 3e64219370 Update tools 2025-07-04 03:15:43 +02:00
Tat Dat Duong 9257282252 Update first page 2025-07-04 03:11:51 +02:00
Eugene Yurtsev 73d0c804ae Add part 6 raw translation 2025-06-30 11:24:02 -04:00
Eugene Yurtsev 4f13a66924 Set build target to js add translations for 4th and 5th parts of the tutorial 2025-06-30 11:20:43 -04:00
Eugene Yurtsev 6859bc312d Raw output unreviewed 2025-06-30 10:57:28 -04:00
Eugene Yurtsev 38218c55e5 x 2025-06-30 10:26:05 -04:00
0fd6306623 docs: Time travel and breakpoints (#5215)
* docs: Time travel and breakpoints

* fix links

* fix

* edits

* Fix link

* Remove circular redirects

* revert llms.txt

---------

Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
2025-06-27 14:49:53 +00:00
Yassin NouhandGitHub f01ab13cab DOC: Add missing docs_txt = format_docs() lines in adaptive RAG tutorial (#5222) 2025-06-27 10:45:12 -04:00
Eugene YurtsevandGitHub 1beaf48cf9 docs: quick nav fixes (#5229)
Quick nav fixes
2025-06-27 10:37:42 -04:00
Sam CrowderandGitHub 847b0e8243 add warning about serverless (#5218) 2025-06-27 07:31:52 -07:00
Eugene YurtsevandGitHub 02a2467610 docs: context update (#5207)
Update context
2025-06-27 14:25:54 +00:00
Sydney RunkleandGitHub fa5e7a97c2 release[prebuilt]: bump version to v0.5.1 (#5228)
version bump
2025-06-27 14:02:14 +00:00
Sydney RunkleandGitHub 233a92ccb9 fix[prebuilt]: bind tools even if it's only provider tools (#5227)
bind tools even if just provider tools
2025-06-27 09:49:26 -04:00
lc-arjunandGitHub e277a6bd9b chore: sdk bumps (#5220)
* chore: sdk bumps

* uv lock
2025-06-26 17:57:05 -07:00
lc-arjunandGitHub 16250fe038 chore: update api ref docs and fix schemas (#5219) 2025-06-26 17:41:03 -07:00
Nuno Campos 216d1be0a5 Revert "Reapply "docs: deploy from v0 branch for now (#4960)" (#5184) (#5185)"
This reverts commit cb63c1ab72.
2025-06-26 15:52:45 -07:00
Nuno Campos 0a4cd5fcaa 0.5.0 2025-06-26 15:47:46 -07:00
Nuno CamposandGitHub f8503670af Add test for reducer exceptions (#5217) 2025-06-26 12:48:07 -07:00
Nuno Campos 0f22841f78 Add test for reducer exceptions 2025-06-26 11:40:55 -07:00
lc-arjunandGitHub 4a26ca5bc2 feat: crons sorting sdk (#5197)
* feat: crons sorting sdk

* update sync clients

* lock file

* lock file

* lock file
2025-06-26 11:27:44 -07:00
William FHandGitHub a622218746 chore: (CLI) bump api minbound (#5206) 2025-06-25 19:28:13 -07:00
William FHandGitHub 7fda427601 fix: [prebuilt] Checks for pre-bound model with builtin tools (#5203) 2025-06-26 01:34:21 +00:00
Nuno CamposandGitHub 1c9bfba23a feat(langgraph): task masquerading with update state (#5189) 2025-06-25 17:13:54 -07:00
Nuno Campos cee9ac0b7a One more 2025-06-25 17:07:26 -07:00
Nuno Campos 3ddb6b1477 One more 2025-06-25 17:05:41 -07:00
Nuno Campos 8a5519da29 Fix update_state bugs 2025-06-25 17:02:08 -07:00
Nuno CamposandTat Dat Duong abc5a5ff44 Update 2025-06-26 01:30:52 +02:00
Nuno CamposandTat Dat Duong 38ab90217f Fix assertion 2025-06-26 01:30:52 +02:00
Tat Dat Duong 005edb2979 Async test 2025-06-26 01:30:51 +02:00
Tat Dat Duong c5a851cd91 Use as_node=END instead 2025-06-26 01:30:51 +02:00
Tat Dat Duong 06a42aee53 Remove if/else 2025-06-26 01:30:51 +02:00
Tat Dat Duong 04b9947a41 Fix mypy 2025-06-26 01:30:51 +02:00
Tat Dat Duong 239df52e74 Avoid creating new root checkpoint to preserve behaviour 2025-06-26 01:30:48 +02:00
Tat Dat Duong 8a67a5ac25 Fix format 2025-06-26 01:30:11 +02:00
Tat Dat Duong adb2eee54b Remove dict 2025-06-26 01:30:10 +02:00
Tat Dat Duong 263a583f01 Fix lint 2025-06-26 01:30:10 +02:00
Tat Dat Duong 565d52975c Fix test 2025-06-26 01:30:10 +02:00
Tat Dat Duong 865fba9d50 feat(langgraph): task masquerading with update state 2025-06-26 01:30:10 +02:00
Nuno CamposandGitHub 929bba8337 Add print_mode= arg to invoke/stream (#5201) 2025-06-25 16:21:19 -07:00
Nuno CamposandGitHub 634511350f Remove unused branch in update_state (#5195) 2025-06-25 16:08:48 -07:00
Nuno Campos c9dcd3fbb5 Map debug=True to print_mode=['values', 'updates'] 2025-06-25 16:08:20 -07:00
Eugene YurtsevandGitHub 10a0a8633a docs: consolidate models (#5200)
* Expand chat model documentation a bit. 
* Adds links to langchain docs to make relevant information easier to find.
* This is a stop-gap until we merge langchain and langgraph docs
2025-06-25 15:55:17 -04:00
Eugene YurtsevandGitHub 23327f5647 docs: update mcp docs (#5191)
Update MCP docs
2025-06-25 15:06:35 -04:00
Nuno Campos e23da72ccd Add print_mode= arg to invoke/stream
- This is more flexible version of the debug= flag, which we'll deprecate
2025-06-25 11:52:42 -07:00
0aefe68a5f docs: HITL consolidation (#5192)
* HITL consolidation, minus server

* Fix links

* Fix server page

* remove extra page

* nits

* updates based on feedback

* Update docs/docs/concepts/human_in_the_loop.md

Co-authored-by: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com>

* edits based on feedback

---------

Co-authored-by: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com>
2025-06-25 14:16:46 -04:00
Eugene YurtsevandGitHub 5b8edf3c72 docs: consolidate tool documentation (#5190)
Consolidate tool documentation

- [x] Cross links between conceptual doc and tools guide
- [x] Tools guide includes both workflow and agent usage example
2025-06-25 14:13:11 -04:00
Lauren Hirata SinghandGitHub b64e1bcae4 docs: Add FAQ for nodes executed (#5198)
Add FAQ for nodes executed
2025-06-25 12:28:21 -04:00
Nuno Campos d877ea479b Remove unused branch in update_state
- update_state(None, as_node=None) was not tested/used, and was too similar in behavior to __copy__ / __end__, so removing it
2025-06-25 08:38:20 -07:00
Sydney RunkleandGitHub cb63c1ab72 Reapply "docs: deploy from v0 branch for now (#4960)" (#5184) (#5185)
This reverts commit a7ea5e44ce.
2025-06-24 16:01:03 +00:00
Sydney RunkleandGitHub a7ea5e44ce Revert "docs: deploy from v0 branch for now (#4960)" (#5184)
This reverts commit aedf974dfd.
2025-06-24 14:59:33 +00:00
Lauren Hirata SinghandGitHub c3ae5e6b71 docs: Reorganize existing content (#5122)
* Organize existing content differently

* Fix broken links

* Remove cookie consent popup

* edits

* Consolidate streaming

* Remove agents/streaming

* Fix broken links

* Edit stream modes

* Change titles

* Fix titles based on feedback

* Move LGP to platform section

* Update navigation

* Consolidate assistant conceptual guides

* docs: Memory consolidation (#5149)

* Memory consolidation

* Fix broken links

* fix links

* Fix links

* Fix links

* General content clean up for memory

* fix links

* Fix link

* fix links

* Fix title

* Edits based on feedback

* Link to memory store
2025-06-24 09:29:59 -04:00
Vedant PanchalandGitHub ac328c3fd8 [fix] snippet for Agents as a Tool (#5181)
snippet had a missing decorator that might lead the user to confuse whether it is really a tool
2025-06-24 11:44:32 +00:00
Nuno CamposandGitHub ebd7977936 Revert change to default value of checkpoint_during arg (#5177) 2025-06-23 18:12:19 -07:00
Nuno Campos a6381c32b0 Revert change to default value of checkpoint_during arg 2025-06-23 18:05:46 -07:00
Nuno CamposandGitHub 294d346650 Fix bug where Command(update=) could be ignored if there was a 2nd interrupt after it (#5175) 2025-06-23 17:35:36 -07:00
Nuno Campos 866c8009dc Fix bug where Command(update=) could be ignored if there was a 2nd interrupt after it
- writes from the null task (ie. from outside tasks) should be accummulated across invocations
2025-06-23 17:23:53 -07:00
Rauf ParchievandGitHub 73bed2cf7c Update workflows.md (#5116) 2025-06-23 23:54:45 +00:00
Eugene YurtsevandGitHub 4cffe58065 docs: fix tab syntax errors and admonition syntax errors (#5091) 2025-06-23 19:53:54 -04:00
nikhildigdeandGitHub e73964a971 Update custom_routes.md (#5110)
Updated the statement about app.py
2025-06-23 23:53:25 +00:00
William Fu-Hinthorn de91f21f6b Update cli config doc on pip_installer 2025-06-23 16:40:07 -07:00
David DuongandGitHub 41eed326b8 Disable values update on use stream in interrupt events (#5041) 2025-06-24 00:08:14 +02:00
Mason DaughertyandGitHub 946d23213d docs: add Homebrew install option to CLI docs (#5160)
Add Homebrew install option to CLI docs
2025-06-23 11:33:12 -04:00
hari-dhanushkodiandGitHub c78197a583 add more docs for lgp deployment metrics (#5151) 2025-06-23 07:12:06 -07:00
Lauren Hirata SinghandGitHub 50756207ee docs: fix a typo 'prebuit' to 'prebuilt' in notebook_hooks.py (#5154) 2025-06-23 09:44:45 -04:00
OfirTeneJunoandGitHub 903cec0cfa Merge branch 'main' into interrupt-use-stream-values 2025-06-23 13:25:35 +03:00
foie0222 f63952595d fix: a typo prebuit to prebuilt 2025-06-21 17:49:40 +09:00
Andrew NguonlyandGitHub 4a252bd03a docs: Remove note for LANGSMITH_TRACING environment variable (#5147)
Remove note for LANGSMITH_TRACING.
2025-06-20 13:36:29 -07:00
Eugene YurtsevandGitHub f0f329d9e1 docs: Add conditional js/python rendering (#5128)
# Overview

Adding conditional rendering logic to co-locate js and python documentation.

* `:::` conditional syntax can be used to switch between python only or js only content.
* Contains simple unit tests for `:::`
* PR adds set up for a way to implement a context switch between languages, but it will not be enabled until JS content is merged in.
* Contains a script that can add javascript documentation 

Implementation of: https://github.com/langchain-ai/langgraph/pull/5118

## Example

Example of conditional rendering / compilation.


```markdown

### Config (static context)

Config is for immutable data like user metadata or API keys. Use
when you have values that don't change mid-run.

Specify configuration using a key called **"configurable"** which is reserved
for this purpose:


:::python

This content will only be rendered for the python site.
:::

:::js
this content will only be rendered for the js / ts site.
:::

```
2025-06-20 16:20:16 -04:00
OfirTeneJunoandGitHub 77306c5142 Merge branch 'main' into interrupt-use-stream-values 2025-06-19 14:06:24 +03:00
Nuno CamposandGitHub eba18c3213 Reduce extraneous keys in checkpoint.metadata (#5133) 2025-06-17 17:46:45 -07:00
Nuno Campos a1c856c088 Reduce extraneous keys in checkpoint.metadata
- Leave it up to each checkpointer implementation to decide whether to merge in configurable/metadata (previously PregelLoop would do some of this always)
- Never copy over internal langgraph keys into checkpoint.metadata (these are redundant/misleading to include)
2025-06-17 17:40:18 -07:00
Josh RogersandGitHub 92010f84ec Adding disable_mcp field to cli docs (#5132) 2025-06-17 16:22:50 -07:00
Nuno CamposandGitHub 3b8f3f9de3 If FuturesDict callback has been GCed, don't call it (#5131) 2025-06-17 14:03:16 -07:00
Nuno Campos 6dcff8a839 If FuturesDict callback has been GCed, don't call it 2025-06-17 13:47:47 -07:00
1309243b29 docs: studio evals (#5129)
* docs: studio evals

* docs: added studio evals images (#5076)

* docs: added studio evals images

* Update docs/docs/cloud/how-tos/studio/run_evals.md

Co-authored-by: lc-arjun <arjun@langchain.dev>

* Update docs/docs/cloud/how-tos/studio/run_evals.md

Co-authored-by: lc-arjun <arjun@langchain.dev>

* Update docs/docs/cloud/how-tos/studio/run_evals.md

Co-authored-by: lc-arjun <arjun@langchain.dev>

* docs: updated studio evals

* Update docs/docs/cloud/how-tos/studio/run_evals.md

Co-authored-by: lc-arjun <arjun@langchain.dev>

* docs: removed images

---------

Co-authored-by: lc-arjun <arjun@langchain.dev>

* final changes

* i think its this

---------

Co-authored-by: Marco Perini <perinim.98@gmail.com>
2025-06-17 12:32:04 -07:00
Nuno Campos 771c6150a4 langgraph 0.5.0rc1 2025-06-16 17:52:13 -07:00
Nuno Campos edfb65fd3a langgraph-prebuilt 0.5.0rc0 2025-06-16 17:47:21 -07:00
Lauren Hirata SinghandGitHub 0f92470e49 docs: Remove cookie consent (#5123) 2025-06-16 18:41:55 -04:00
Nuno Campos dfcaf97c73 langgraph 0.5.0rc0 2025-06-16 15:17:56 -07:00
Nuno Campos 63a0028372 langgraph-checkpoint 2.1.0 2025-06-16 14:58:50 -07:00
Nuno CamposandGitHub 1134017d07 Preparation for 0.5 release: langgraph-checkpoint (#5124)
Prepare langgraph-checkpoint for 0.5

- Given we have no upper bound on langgraph-checkpoint dep need to undo all changes in langgraph-checkpoint that might break previous versions of langgraph
2025-06-16 21:57:11 +00:00
Lauren Hirata Singh 33feba4877 Remove cookie consent 2025-06-16 16:33:32 -04:00
Nuno CamposandGitHub 4fec8e9dec Preparation for 0.5 release (#5121) 2025-06-16 13:14:25 -07:00
Nuno Campos c137169325 Preparation for 0.5 release
- Update deprecation warnings to mention 0.5, no 1.0
- Add back type hint support for Runnable arg to add_node
2025-06-16 13:07:52 -07:00
Nuno CamposandGitHub 1e2672e63d Restore shallow checkpointer (#5105) 2025-06-16 11:23:27 -07:00
Nuno CamposandGitHub 06803ab683 Add migration for pending_sends (#5106) 2025-06-16 11:23:17 -07:00
hari-dhanushkodiandGitHub 3488ee47e0 chore: add docs for lgp deployment monitoring (#5104) 2025-06-16 10:21:42 -07:00
Nuno CamposandGitHub 289bdd0cea Introduce "tasks" and "checkpoints" stream modes (#5117) 2025-06-16 10:14:18 -07:00
Nuno Campos 417103066b Lint 2025-06-16 09:29:03 -07:00
Nuno Campos 25a59447c1 Introduce "tasks" and "checkpoints" stream modes
- These are split out of "debug" stream mode, which is now an alias for ["tasks", "checkpoints"]
2025-06-16 08:47:45 -07:00
Nuno Campos 21906d2b7b Add migration for pending_sends
- Checkpoints saved on older versions of langgraph will be compatible with langgraph 0.5 and 1.0
2025-06-13 17:42:14 -07:00
Nuno Campos 0cad7019cb Restore shallow checkpointer
- This should definitely be removed soon, but let's give people more time to update
2025-06-13 17:37:40 -07:00
Nuno CamposandGitHub 7e735672bf Restore compatibility with custom checkpointer classes created in prior versions (#5103) 2025-06-13 16:36:36 -07:00
Nuno Campos 5498893780 Restore compatibility with custom checkpointer classes created in prior versions
- Ensure existing custom checkpointer classes are compatible with new langgraph-checkpoint release
2025-06-13 16:29:55 -07:00
Nuno CamposandGitHub e80f47aa01 Revert removals of APIs that were slated for removal in 1.0 (#5101) 2025-06-13 16:09:20 -07:00
Nuno Campos a0b2f742a3 Revert "Remove UntrackedValue channel"
This reverts commit 05f3904d09.
2025-06-13 15:36:47 -07:00
William FHandGitHub b7973d65db fix: Update lockfile (#5102) 2025-06-13 14:53:40 -07:00
Nuno Campos 3fa3a586b5 Revert "Remove MessageGraph (#4875)"
This reverts commit a5e6223569.
2025-06-13 14:21:05 -07:00
OfirTeneJunoandGitHub 666279a241 Merge branch 'main' into interrupt-use-stream-values 2025-06-11 09:53:25 +03:00
OfirTeneJuno 2172bc89ed remove yarn 2025-06-10 17:37:27 +03:00
OfirTeneJuno 4138ef9c43 Change to continue 2025-06-10 17:33:52 +03:00
OfirTeneJuno 0ff181b7ce Disable values update on use stream in interrupt events 2025-06-10 17:21:17 +03:00
169 changed files with 13384 additions and 11069 deletions
+4 -6
View File
@@ -4,11 +4,9 @@ on:
push:
branches:
- main
- v0
pull_request:
branches:
- main
- v0
workflow_dispatch:
permissions:
@@ -84,9 +82,9 @@ jobs:
run: make llms-text
- name: Build site
run: |
# If this is v0 branch, then we want to download stats. we do this
# If this is main branch, then we want to download stats. we do this
# with the env variable DOWNLOAD_STATS=true
if [ "${{ github.ref }}" == "refs/heads/v0" ]; then
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
DOWNLOAD_STATS=true make build-docs
else
make build-docs
@@ -146,7 +144,7 @@ jobs:
fi
- name: Configure GitHub Pages
if: github.ref == 'refs/heads/v0'
if: github.ref == 'refs/heads/main'
uses: actions/configure-pages@v5
- name: Upload Pages Artifact
@@ -156,6 +154,6 @@ jobs:
path: ./docs/site/
- name: Deploy to GitHub Pages
if: github.ref == 'refs/heads/v0'
if: github.ref == 'refs/heads/main'
id: deployment
uses: actions/deploy-pages@v4
+1 -1
View File
@@ -22,7 +22,7 @@ build-prebuilt:
uv run python -m _scripts.third_party_page.create_third_party_page stats.yml docs/agents/prebuilt.md --language python
build-docs: build-typedoc build-prebuilt
uv run python -m mkdocs build --clean -f mkdocs.yml --strict
TARGET_LANGUAGE=js uv run python -m mkdocs build --clean -f mkdocs.yml --strict
llms-text:
uv run python -m _scripts.generate_llms_text docs/llms-full.txt
+197 -118
View File
@@ -1,157 +1,236 @@
"""Add typescript translation to a given markdown file."""
"""Translate Python markdown to TypeScript and/or consolidate Python-JS markdown into a single document."""
import argparse
import re
import requests
from langchain_anthropic import ChatAnthropic
from textwrap import dedent
URL = "https://gist.githubusercontent.com/eyurtsev/e7486731415463a9bc5b4682358859c8/raw/b5a5fda9c7e3387cfcb781f25082814d43675d50/gistfile1.txt"
# Load reference TypeScript snippets
URL = "https://gist.githubusercontent.com/dqbd/b35d49e2ceec80e654fe1c5ab61ec477/raw/f4768aeedb67628190a4e06d063a938afc8e7672/snippets.md"
response = requests.get(URL)
response.raise_for_status()
reference_snippets = response.text
model = ChatAnthropic(model="claude-3-5-sonnet-latest")
# Initialize model
model = ChatAnthropic(model="claude-sonnet-4-0", max_tokens=64_000)
def _get_tqdm():
try:
from tqdm import tqdm
except ImportError:
# If not available return a simple identity function
def tqdm(iterable, *args, **kwargs):
return iterable
return tqdm
FLUENT_INTERFACE_PROMPT = (
"CRITICAL: Always use method chaining (fluent interface) for StateGraph operations in TypeScript. "
"Never create separate variables for the graph builder or call methods individually. "
"The fluent interface provides better type safety and is the preferred pattern.\n\n"
"CORRECT examples with fluent interface:\n"
+ dedent(
"""
```typescript
const graph = new StateGraph(MyState)
.addNode('node1', node1)
.addNode('node2', node2)
.addEdge(START, 'node1')
.addEdge('node1', 'node2')
.addEdge('node2', END)
.compile()
```
```typescript
const graph = new StateGraph(MyState)
.addNode('chatbot', chatbot)
.addEdge(START, 'chatbot')
.addEdge('chatbot', END)
.compile()
```
```typescript
const graph = new StateGraph(MyState)
.addNode('chatbot', chatbot)
.addEdge(START, 'chatbot')
.addEdge('chatbot', END)
.compile()
```
"""
)
+ "\n"
+ "INCORRECT examples to avoid:\n"
+ dedent(
"""
```typescript
// WRONG: Creating separate builder variable
const graphBuilder = new StateGraph(MyState)
graphBuilder.addNode('node1', node1)
graphBuilder.addEdge(START, 'node1')
const graph = graphBuilder.compile()
```
```typescript
// WRONG: Using Python-style method names
const workflow = new StateGraph(MyState)
workflow.add_node('node1', node1)
workflow.add_edge(START, 'node1')
const graph = workflow.compile()
```
```typescript
// WRONG: Calling methods individually
const graphBuilder = new StateGraph(MyState)
graphBuilder.addNode('chatbot', chatbot)
graphBuilder.addEdge(START, 'chatbot')
graphBuilder.addEdge('chatbot', END)
const graph = graphBuilder.compile()
```
"""
)
+ "\n"
+ "Key rules:\n"
+ "- Always chain methods directly on the StateGraph constructor\n"
+ "- Use camelCase method names (addNode, addEdge, not add_node, add_edge)\n"
+ "- Always end with .compile()\n"
+ "- Never store the builder in a separate variable\n"
)
_tqdm = _get_tqdm()
TRANSLATION_PROMPT = (
"You are a helpful assistant that translates Python-based technical "
"documentation written in Markdown to equivalent TypeScript-based documentation. "
"The input is a Markdown file written in mkdocs format. It contains "
"Python code snippets embedded in prose. "
"Your task is to rewrite the content by translating the Python code to "
"idiomatic TypeScript, using the provided TypeScript reference snippets "
"to ensure accurate and consistent usage (e.g., correct imports, function "
"names, and patterns). "
"Remove the original Python code and replace it with the corresponding "
"TypeScript version. "
"Do not alter the surrounding prose unless a change is necessary to "
"reflect differences between Python and TypeScript. "
"Preserve the structure and formatting of the original Markdown document. "
"Do not make stylistic or structural changes unless they directly support "
"the translation. "
"Use the reference TypeScript snippets as guidance whenever possible to "
"maintain alignment with existing conventions.\n\n"
"IMPORTANT REQUIREMENTS:\n"
"- Use Zod for state definition for StateGraph. Avoid using Annotation since it will be deprecated in the future.\n"
"- ALWAYS use fluent interface (method chaining) for StateGraph operations - this is CRITICAL\n"
"- Never create separate variables for graph builders\n"
"- Always chain methods directly on the StateGraph constructor and end with .compile()\n\n"
f"{FLUENT_INTERFACE_PROMPT}\n\n"
f"Here are the reference TypeScript snippets:\n\n{reference_snippets}\n\n"
)
opening_pattern = re.compile(r"^\s*```python(?:\s+.*)?\s*$")
closing_pattern = re.compile(r"^\s*```\s*$")
CONSOLIDATION_PROMPT = (
"You are a helpful assistant that consolidates parallel Python and JavaScript (TypeScript) technical documentation "
"written in Markdown into a single unified Markdown document. "
"The input consists of two documents: the first is for Python users, and the second is for JavaScript/TypeScript users. "
"Your task is to merge these into one Markdown file using language-specific fenced blocks to separate the content where needed. "
"Use the following syntax to distinguish content for each language:\n\n"
":::python\n"
"# Python-specific content\n"
":::\n\n"
":::js\n"
"# JavaScript/TypeScript-specific content\n"
":::\n\n"
"Follow these consolidation rules:\n"
"- When content (prose or code) is the same or nearly identical in both versions, include it only once—outside of any fenced block.\n"
"- When content differs between the Python and JS versions, wrap each version in its corresponding fenced block.\n"
"- Prefer **paragraph-level separation** of language-specific content. Do not combine Python and JS snippets or terminology in the same sentence or paragraph using conditional phrases.\n"
" For example, avoid inline constructs like:\n"
" `The :::python add_messages ::: :::js reducer ::: function...`\n"
" Instead, write two distinct paragraphs:\n\n"
" :::python\n"
" The `add_messages` function in our `State` will append the LLM's response messages to whatever messages are already in the state.\n"
" ::: \n\n"
" :::js\n"
" The `reducer` function in our `StateAnnotation` will append the LLM's response messages to whatever messages are already in the state.\n"
" :::\n\n"
"- Preserve the overall structure, ordering, and formatting of the original Markdown documents.\n"
"- Do not rephrase or unify content unless it is logically and semantically identical.\n"
"- Use the fenced blocks for both prose and code as needed, and ensure output is clean, readable Markdown suitable for tools that parse these directives.\n"
"Your goal is to produce a cleanly merged documentation file that serves both Python and JavaScript users without redundancy, while maximizing clarity and separation of language-specific details."
)
def extract_python_snippets(markdown: str) -> list[str]:
"""
Extract all python code blocks (including their fence lines) from the markdown content.
A python block is defined as any block that starts with a line containing an opening fence
with '```python' (optionally with extra parameters) and ends with a closing fence '```'.
"""
snippets = []
inside_block = False
current_snippet = []
for line in markdown.splitlines(keepends=True):
if not inside_block:
if opening_pattern.match(line):
inside_block = True
current_snippet = [line]
else:
current_snippet.append(line)
if closing_pattern.match(line):
inside_block = False
snippets.append("".join(current_snippet))
current_snippet = []
return snippets
def translate_snippet(python_snippet: str) -> str:
"""Translate a python code block into a TypeScript code block using Langchain.
The response is expected to be a properly fenced TypeScript code block (i.e.
starting with ```typescript and ending with ```).
"""
ai_message = model.invoke(
def translate_python_to_ts(markdown_content: str) -> str:
response = model.invoke(
[
{
"role": "system",
"content": (
f"You have access to the following up-to-date example TypeScript code "
f"snippets that show examples of building with langgraph "
f"and langchain:\n\n{reference_snippets}\n\n"
"Use this context to translate the following Python code to equivalent "
"TypeScript. Ensure that your output is a valid fenced TypeScript "
"code block (i.e. starts with ```typescript and ends with ```)."
),
},
{
"role": "user",
"content": f"Translate this Python snippet to TypeScript:\n\n{python_snippet}",
"content": TRANSLATION_PROMPT,
"cache_control": {"type": "ephemeral"},
},
{"role": "user", "content": markdown_content},
]
)
# Use a regular expression to search for a TypeScript code block in the response.
pattern = r"```typescript\s*(.*?)\s*```"
match = re.search(pattern, ai_message.content, re.DOTALL)
if match:
# Reconstruct the code block with proper fences.
typescript_code = match.group(1).strip()
return f"```typescript\n{typescript_code}\n```"
else:
raise ValueError("No TypeScript code block found in the model's response.")
return response.content
def insert_translations_into_markdown(
markdown: str, typescript_snippets: list[str]
) -> str:
"""Walks through the original markdown content and, after each
Python snippet block, inserts the corresponding translated TypeScript snippet.
It assumes that the ordering of the Python snippets
(from extract_python_snippets) matches the order they appear in the markdown.
"""
output_lines = []
lines = markdown.splitlines(keepends=True)
inside_block = False
snippet_index = 0
for line in lines:
output_lines.append(line)
if not inside_block and opening_pattern.match(line):
# We've encountered the start of a python code block.
inside_block = True
elif inside_block:
if closing_pattern.match(line):
# End of a python snippet block.
inside_block = False
if snippet_index < len(typescript_snippets):
# Insert an extra newline for clarity, then the translated TypeScript snippet.
output_lines.append("\n")
output_lines.append(typescript_snippets[snippet_index])
output_lines.append("\n")
snippet_index += 1
return "".join(output_lines)
def consolidate_python_and_ts(combined_content: str) -> str:
response = model.invoke(
[
{
"role": "system",
"content": CONSOLIDATION_PROMPT,
"cache_control": {"type": "ephemeral"},
},
{"role": "user", "content": combined_content},
]
)
return response.content
def main(file_path: str) -> None:
# Read the markdown file.
with open(file_path, "r") as f:
def main(file_path: str, translate_only: bool, consolidate_only: bool) -> None:
with open(file_path, "r", encoding="utf-8") as f:
markdown_content = f.read()
# 1. Extract all Python snippets.
python_snippets = extract_python_snippets(markdown_content)[:1]
if translate_only:
translated = translate_python_to_ts(markdown_content)
output_path = file_path.replace(".md", ".translated.md")
with open(output_path, "w", encoding="utf-8") as f:
f.write(translated)
print(f"Translated JS/TS version written to: {output_path}")
# 2. Translate each Python snippet to TypeScript.
typescript_snippets = []
# Replace with .batch() for faster translation
for python_snippet in _tqdm(python_snippets):
ts_snippet = translate_snippet(python_snippet)
typescript_snippets.append(ts_snippet)
elif consolidate_only:
consolidated = consolidate_python_and_ts(markdown_content)
with open(file_path, "w", encoding="utf-8") as f:
f.write(consolidated)
print(f"Consolidated content written to: {file_path}")
# 3. Insert the TypeScript translations after their respective Python snippets.
updated_markdown = insert_translations_into_markdown(
markdown_content, typescript_snippets
)
# Overwrite the original markdown file with the updated content.
with open(file_path, "w") as f:
f.write(updated_markdown)
else:
# Default behavior: translate first, then consolidate both
translated = translate_python_to_ts(markdown_content)
combined = f"{markdown_content.strip()}\n\n\n{translated.strip()}"
consolidated = consolidate_python_and_ts(combined)
with open(file_path, "w", encoding="utf-8") as f:
f.write(consolidated)
print(f"Translated and consolidated content written to: {file_path}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Translate Python snippets in a markdown file to TypeScript and insert them after each Python snippet."
description=(
"Translate Python markdown to TypeScript and/or consolidate "
"Python-JS markdown into one file."
)
)
parser.add_argument("file_path", type=str, help="Path to the markdown file.")
parser.add_argument(
"--translate-only",
action="store_true",
help="Only generate the JS translation.",
)
parser.add_argument(
"--consolidate-only",
action="store_true",
help="Only consolidate pre-paired Python and JS content.",
)
args = parser.parse_args()
main(args.file_path)
if args.translate_only and args.consolidate_only:
raise ValueError(
"Cannot use both --translate-only and --consolidate-only at the same time."
)
main(
args.file_path,
translate_only=args.translate_only,
consolidate_only=args.consolidate_only,
)
+51
View File
@@ -0,0 +1,51 @@
import * as path from "node:path";
import * as fs from "node:fs/promises";
import * as url from "node:url";
const mdPath = url.fileURLToPath(
new URL("./add_translation_js_ref_updated.md", import.meta.url)
);
const extractedDir = url.fileURLToPath(
new URL(
"../../../oap-langgraphjs-tools-agent/src/add_transaction_js",
import.meta.url
)
);
const files = (await fs.readdir(extractedDir, { withFileTypes: true })).sort(
(a, b) => {
const aInt = Number.parseInt(a.name.split(".")[0], 10);
const bInt = Number.parseInt(b.name.split(".")[0], 10);
return aInt - bInt;
}
);
let count = 0;
let lines = [];
for (let file of files) {
if (file.isDirectory() || !file.name.endsWith(".mts")) continue;
count += 1;
const content = await fs.readFile(path.resolve(extractedDir, file.name), {
encoding: "utf-8",
});
lines = lines.concat(
content
.split("\n")
.reduce((acc, line) => {
if (line.trimStart().startsWith("// ```")) acc.push([]);
acc.at(-1)?.push(line);
return acc;
}, [])
.map((i) => {
const tag = i[0].trimStart().slice("// ```".length);
return ["```" + tag, ...i.slice(1), "```"].join("\n");
})
);
}
await fs.writeFile(mdPath, lines.join("\n\n"));
+46
View File
@@ -0,0 +1,46 @@
import * as fs from "node:fs/promises";
import * as path from "node:path";
import * as url from "node:url";
const mdPath = url.fileURLToPath(
new URL("./add_translation_js_ref.md", import.meta.url)
);
const extractedDir = url.fileURLToPath(
new URL(
"../../../oap-langgraphjs-tools-agent/src/add_transaction_js",
import.meta.url
)
);
await fs.mkdir(extractedDir, { recursive: true });
const md = (await fs.readFile(mdPath, { encoding: "utf-8" })).split("\n");
const chunks = [];
let current = [];
for (let line of md) {
if (line.trimStart().startsWith("```")) {
if (current.length > 0) {
chunks.push(current.join("\n"));
current = [];
} else {
current.push("// " + line.trimStart());
}
} else if (current.length > 0) {
current.push(line);
}
}
if (current.length > 0) {
chunks.push(current.join("\n"));
}
for (let i = 0; i < chunks.length; i += 1) {
await fs.writeFile(path.resolve(extractedDir, `${i}.mts`), chunks[i], {
encoding: "utf-8",
});
}
console.log("finished");
+10 -6
View File
@@ -3,19 +3,21 @@
import asyncio
import glob
import os
from typing import TypedDict, List, Optional
import pydantic
import re
from pydantic import BaseModel, Field
from langchain_core.rate_limiters import InMemoryRateLimiter
from typing import TypedDict, List, Optional
import yaml
from langchain.chat_models import init_chat_model
from langchain_core.rate_limiters import InMemoryRateLimiter
from mkdocs.structure.files import File
from mkdocs.structure.pages import Page
from pydantic import BaseModel, Field
from yaml import SafeLoader
from _scripts.notebook_hooks import _on_page_markdown_with_config
from _scripts.notebook_hooks import (
_on_page_markdown_with_config,
_apply_conditional_rendering,
)
HERE = os.path.dirname(os.path.abspath(__file__))
# Get source directory (parent of HERE / docs)
@@ -211,7 +213,9 @@ async def process_nav_items(nav_items: list[NavItem]) -> list[NavItem]:
# Remove any items that start with http:// or https:// looking only for
# local file at this stages.
nav_items = [
item for item in nav_items if not item["url"].startswith(("http://", "https://"))
item
for item in nav_items
if not item["url"].startswith(("http://", "https://"))
]
# Process items in parallel
tasks = [process_single_item(item) for item in nav_items]
+5
View File
@@ -0,0 +1,5 @@
JS_LINK_MAP = {
"langgraph.types.interrupt": "https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph.interrupt-2.html",
"create_react_agent": "https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph_prebuilt.createReactAgent.html",
"langgraph.types.Command": "https://langchain-ai.github.io/langgraphjs/reference/classes/langgraph.Command.html",
}
+101 -12
View File
@@ -15,6 +15,7 @@ from mkdocs.structure.files import Files, File
from mkdocs.structure.pages import Page
from _scripts.generate_api_reference_links import update_markdown_with_imports
from _scripts.link_map import JS_LINK_MAP
from _scripts.notebook_convert import convert_notebook
logger = logging.getLogger(__name__)
@@ -48,19 +49,24 @@ REDIRECT_MAP = {
"how-tos/return-when-recursion-limit-hits.ipynb": "how-tos/graph-api/#impose-a-recursion-limit",
"how-tos/async.ipynb": "how-tos/graph-api/#async",
# memory how-tos
"how-tos/memory/manage-conversation-history.ipynb": "how-tos/memory.ipynb",
"how-tos/memory/delete-messages.ipynb": "how-tos/memory.ipynb#delete-messages",
"how-tos/memory/add-summary-conversation-history.ipynb": "how-tos/memory.ipynb#summarize-messages",
"how-tos/memory/manage-conversation-history.ipynb": "how-tos/memory/add-memory.md",
"how-tos/memory/delete-messages.ipynb": "how-tos/memory/add-memory.md#delete-messages",
"how-tos/memory/add-summary-conversation-history.ipynb": "how-tos/memory/add-memory.md#summarize-messages",
"how-tos/memory.ipynb": "how-tos/memory/add-memory.md",
"agents/memory.ipynb": "how-tos/memory/add-memory.md",
# subgraph how-tos
"how-tos/subgraph-transform-state.ipynb": "how-tos/subgraph.ipynb#different-state-schemas",
"how-tos/subgraphs-manage-state.ipynb": "how-tos/subgraph.ipynb#add-persistence",
# persistence how-tos
"how-tos/persistence_postgres.ipynb": "how-tos/persistence.ipynb#use-in-production",
"how-tos/persistence_mongodb.ipynb": "how-tos/persistence.ipynb#use-in-production",
"how-tos/persistence_redis.ipynb": "how-tos/persistence.ipynb#use-in-production",
"how-tos/subgraph-persistence.ipynb": "how-tos/persistence.ipynb#use-with-subgraphs",
"how-tos/cross-thread-persistence.ipynb": "how-tos/persistence.ipynb#add-long-term-memory",
"how-tos/persistence_postgres.ipynb": "how-tos/memory/add-memory.md#use-in-production",
"how-tos/persistence_mongodb.ipynb": "how-tos/memory/add-memory.md#use-in-production",
"how-tos/persistence_redis.ipynb": "how-tos/memory/add-memory.md#use-in-production",
"how-tos/subgraph-persistence.ipynb": "how-tos/memory/add-memory.md#use-with-subgraphs",
"how-tos/cross-thread-persistence.ipynb": "how-tos/memory/add-memory.md#add-long-term-memory",
"cloud/how-tos/copy_threads": "cloud/how-tos/use_threads",
"cloud/how-tos/check-thread-status": "cloud/how-tos/use_threads",
"cloud/concepts/threads.md": "concepts/persistence.md#threads",
"how-tos/persistence.ipynb": "how-tos/memory/add-memory.md",
# tool calling how-tos
"how-tos/tool-calling-errors.ipynb": "how-tos/tool-calling.ipynb#handle-errors",
"how-tos/pass-config-to-tools.ipynb": "how-tos/tool-calling.ipynb#access-config",
@@ -86,16 +92,17 @@ REDIRECT_MAP = {
"cloud/how-tos/stream_events.md": "cloud/how-tos/streaming.md#stream-events",
"cloud/how-tos/stream_debug.md": "cloud/how-tos/streaming.md#debug",
"cloud/how-tos/stream_multiple.md": "cloud/how-tos/streaming.md#stream-multiple-modes",
# prebuit redirects
"cloud/concepts/streaming.md": "concepts/streaming.md",
"agents/streaming.md": "how-tos/streaming.md",
# prebuilt redirects
"how-tos/create-react-agent.ipynb": "agents/agents.md#basic-configuration",
"how-tos/create-react-agent-memory.ipynb": "agents/memory.md",
"how-tos/create-react-agent-system-prompt.ipynb": "agents/context.md#prompts",
"how-tos/create-react-agent-hitl.ipynb": "agents/human-in-the-loop.md",
"how-tos/create-react-agent-structured-output.ipynb": "agents/agents.md#structured-output",
# Time-travel
"how-tos/human_in_the_loop/edit-graph-state.ipynb": "how-tos/human_in_the_loop/time-travel.ipynb",
"how-tos/human_in_the_loop/edit-graph-state.ipynb": "how-tos/human_in_the_loop/time-travel.md",
# breakpoints
"how-tos/human_in_the_loop/dynamic_breakpoints.ipynb": "how-tos/human_in_the_loop/breakpoints.ipynb",
"how-tos/human_in_the_loop/dynamic_breakpoints.ipynb": "how-tos/human_in_the_loop/breakpoints.md",
# misc
"prebuilt.md": "agents/prebuilt.md",
"reference/prebuilt.md": "reference/agents.md",
@@ -107,8 +114,15 @@ REDIRECT_MAP = {
# deployment redirects
"how-tos/deploy-self-hosted.md": "cloud/deployment/self_hosted_data_plane.md",
"concepts/self_hosted.md": "concepts/langgraph_self_hosted_data_plane.md",
"tutorials/deployment.md": "concepts/deployment_options.md",
# assistant redirects
"cloud/how-tos/assistant_versioning.md": "cloud/how-tos/configuration_cloud.md",
"cloud/concepts/runs.md": "concepts/assistants.md#execution",
# hitl redirects
"how-tos/wait-user-input-functional.ipynb": "how-tos/use-functional-api.md",
"how-tos/review-tool-calls-functional.ipynb": "how-tos/use-functional-api.md",
"how-tos/create-react-agent-hitl.ipynb": "how-tos/human_in_the_loop/add-human-in-the-loop.md",
"agents/human-in-the-loop.md": "how-tos/human_in_the_loop/add-human-in-the-loop.md",
}
@@ -158,6 +172,62 @@ def _add_path_to_code_blocks(markdown: str, page: Page) -> str:
return code_block_pattern.sub(replace_code_block_header, markdown)
def _resolve_cross_references(md_text: str, link_map: dict[str, str]) -> str:
"""Replace [title][identifier] with [title](url) using language-specific link_map.
Args:
md_text: The markdown text to process.
link_map: mapping of identifier to URL.
Returns:
The processed markdown text with cross-references resolved.
"""
# Pattern to match [title][identifier]
pattern = re.compile(r"\[([^\]]+)\]\[([^\]]+)\]")
def replace_reference(match: re.Match) -> str:
"""Replace the matched reference with the corresponding URL."""
title, identifier = match.group(1), match.group(2)
url = link_map.get(identifier)
if url:
return f"[{title}]({url})"
else:
# Leave it unchanged if not found
return match.group(0)
return pattern.sub(replace_reference, md_text)
def _apply_conditional_rendering(md_text: str, target_language: str) -> str:
if target_language not in {"python", "js"}:
raise ValueError("target_language must be 'python' or 'js'")
pattern = re.compile(
r"(?P<indent>[ \t]*):::(?P<language>\w+)\s*\n"
r"(?P<content>((?:.*\n)*?))" # Capture the content inside the block
r"(?P=indent):::" # Match closing with the same indentation
)
def replace_conditional_blocks(match: re.Match) -> str:
"""Keep active conditionals."""
language = match.group("language")
content = match.group("content")
if language not in {"python", "js"}:
# If the language is not supported, return the original block
return match.group(0)
if language == target_language:
return content
# If the language does not match, return an empty string
return ""
processed = pattern.sub(replace_conditional_blocks, md_text)
return processed
def _highlight_code_blocks(markdown: str) -> str:
"""Find code blocks with highlight comments and add hl_lines attribute.
@@ -236,6 +306,12 @@ def _highlight_code_blocks(markdown: str) -> str:
return markdown
TARGET_LANGUAGE = os.environ.get("TARGET_LANGUAGE", "python")
if TARGET_LANGUAGE not in {"python", "js"}:
raise ValueError(f"TARGET_LANGUAGE must be 'python' or 'js', got {TARGET_LANGUAGE}")
def _on_page_markdown_with_config(
markdown: str,
page: Page,
@@ -257,6 +333,19 @@ def _on_page_markdown_with_config(
# Apply highlight comments to code blocks
markdown = _highlight_code_blocks(markdown)
# Apply conditional rendering for code blocks
markdown = _apply_conditional_rendering(markdown, TARGET_LANGUAGE)
if TARGET_LANGUAGE == "js":
markdown = _resolve_cross_references(markdown, JS_LINK_MAP)
elif TARGET_LANGUAGE == "python":
# Via a dedicated plugin
pass
else:
raise ValueError(
f"Unsupported target language: {TARGET_LANGUAGE}. "
"Supported languages are 'python' and 'js'."
)
# Add file path as an attribute to code blocks that are executable.
# This file path is used to associate fixtures with the executable code
# which can be used in CI to test the docs without making network requests.
+2 -2
View File
@@ -180,14 +180,14 @@ ny_response = agent.invoke(
)
```
1. `checkpointer` allows the agent to store its state at every step in the tool calling loop. This enables [short-term memory](./memory.md#short-term-memory) and [human-in-the-loop](./human-in-the-loop.md) capabilities.
1. `checkpointer` allows the agent to store its state at every step in the tool calling loop. This enables [short-term memory](../how-tos/memory/add-memory.md#add-short-term-memory) and [human-in-the-loop](../concepts/human_in_the_loop.md) capabilities.
2. Pass configuration with `thread_id` to be able to resume the same conversation on future agent invocations.
When you enable the checkpointer, it stores agent state at every step in the provided checkpointer database (or in memory, if using `InMemorySaver`).
Note that in the above example, when the agent is invoked the second time with the same `thread_id`, the original message history from the first conversation is automatically included, together with the new user input.
For more information, see [Memory](./memory.md).
For more information, see [Memory](../how-tos/memory/add-memory.md).
## 6. Configure structured output
+91 -143
View File
@@ -1,17 +1,8 @@
---
search:
boost: 2
tags:
- agent
hide:
- tags
---
# Context
Agents often require more than a list of messages to function effectively. They need **context**.
**Context engineering** is the practice of building dynamic systems that provide the right information and tools, in the right format, so that a language model can plausibly accomplish a task.
Context includes *any* data outside the message list that can shape agent behavior or tool execution. This can be:
Context includes *any* data outside the message list that can shape behavior. This can be:
- Information passed at runtime, like a `user_id` or API credentials.
- Internal state updated during a multi-step reasoning process.
@@ -22,18 +13,10 @@ LangGraph provides **three** primary ways to supply context:
| Type | Description | Mutable? | Lifetime |
|------------------------------------------------------------------------------|-----------------------------------------------|----------|-------------------------|
| [**Config**](#config-static-context) | data passed at the start of a run | ❌ | per run |
| [**State**](#state-mutable-context) | dynamic data that can change during execution | ✅ | per run or conversation |
| [**Long-term Memory (Store)**](#long-term-memory-cross-conversation-context) | data that can be shared between conversations | ✅ | across conversations |
| [**Short-term memory (State)**](#short-term-memory-mutable-context) | dynamic data that can change during execution | ✅ | per run or conversation |
| [**Long-term memory (Store)**](#long-term-memory-cross-conversation-context) | data that can be shared between conversations | ✅ | across conversations |
You can use context to:
- Adjust the system prompt the model sees
- Feed tools with necessary inputs
- Track facts during an ongoing conversation
## Providing Runtime Context
Use this when you need to inject data into an agent at runtime.
## Provide runtime context
### Config (static context)
@@ -44,88 +27,83 @@ Specify configuration using a key called **"configurable"** which is reserved
for this purpose:
```python
agent.invoke(
{"messages": [{"role": "user", "content": "hi!"}]},
graph.invoke( # (1)!
{"messages": [{"role": "user", "content": "hi!"}]}, # (2)!
# highlight-next-line
config={"configurable": {"user_id": "user_123"}}
config={"configurable": {"user_id": "user_123"}} # (3)!
)
```
### State (mutable context)
1. This is the invocation of the agent or graph. The `invoke` method runs the underlying graph with the provided input.
2. This example uses messages as an input, which is common, but your application may use different input structures.
3. This is where you pass the configuration data. The `config` parameter allows you to provide additional context that the agent can use during its execution.
State acts as short-term memory during a run. It holds dynamic data that can evolve during execution, such as values derived from tools or LLM outputs.
```python
class CustomState(AgentState):
# highlight-next-line
user_name: str
agent = create_react_agent(
# Other agent parameters...
# highlight-next-line
state_schema=CustomState,
)
agent.invoke({
"messages": "hi!",
"user_name": "Jane"
})
```
!!! tip "Turning on memory"
Please see the [memory guide](./memory.md) for more details on how to enable memory. This is a powerful feature that allows you to persist the agent's state across multiple invocations.
Otherwise, the state is scoped only to a single agent run.
### Long-Term Memory (cross-conversation context)
For context that spans *across* conversations or sessions, LangGraph allows access to **long-term memory** via a `store`. This can be used to read or update persistent facts (e.g., user profiles, preferences, prior interactions). For more, see the [Memory guide](./memory.md).
## Customizing Prompts with Context { #prompts }
Prompts define how the agent behaves. To incorporate runtime context, you can dynamically generate prompts based on the agent's state or config.
Common use cases:
- Personalization
- Role or goal customization
- Conditional behavior (e.g., user is admin)
=== "Using config"
=== "Agent prompt"
```python
from langchain_core.messages import AnyMessage
from langchain_core.runnables import RunnableConfig
from langgraph.prebuilt import create_react_agent
from langgraph.prebuilt.chat_agent_executor import AgentState
from langgraph.prebuilt import create_react_agent
def prompt(
state: AgentState,
# highlight-next-line
config: RunnableConfig,
) -> list[AnyMessage]:
# highlight-next-line
# highlight-next-line
def prompt(state: AgentState, config: RunnableConfig) -> list[AnyMessage]:
user_name = config["configurable"].get("user_name")
system_msg = f"You are a helpful assistant. User's name is {user_name}"
system_msg = f"You are a helpful assistant. Address the user as {user_name}."
return [{"role": "system", "content": system_msg}] + state["messages"]
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
# highlight-next-line
prompt=prompt
)
agent.invoke(
...,
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
config={"configurable": {"user_name": "John Smith"}}
)
```
=== "Using state"
* See [Agents](../agents/agents.md) for details.
=== "Workflow node"
```python
from langchain_core.runnables import RunnableConfig
# highlight-next-line
def node(state: State, config: RunnableConfig):
user_name = config["configurable"].get("user_name")
...
```
* See [the Graph API](https://langchain-ai.github.io/langgraph/how-tos/graph-api/#add-runtime-configuration) for details.
=== "In a tool"
```python
from langchain_core.runnables import RunnableConfig
@tool
# highlight-next-line
def get_user_info(config: RunnableConfig) -> str:
"""Retrieve user information based on user ID."""
user_id = config["configurable"].get("user_id")
return "User is John Smith" if user_id == "user_123" else "Unknown user"
```
See the [tool calling guide](../how-tos/tool-calling.md#configuration) for details.
### Short-term memory (mutable context)
State acts as [short-term memory](../concepts/memory.md) during a run. It holds dynamic data that can evolve during execution, such as values derived from tools or LLM outputs.
=== "In an agent"
Example shows how to incorporate state into an agent **prompt**.
State can also be accessed by the agent's **tools**, which can read or update the state as needed. See [tool calling guide](../how-tos/tool-calling.md#short-term-memory) for details.
```python
from langchain_core.messages import AnyMessage
@@ -133,15 +111,14 @@ Common use cases:
from langgraph.prebuilt import create_react_agent
from langgraph.prebuilt.chat_agent_executor import AgentState
class CustomState(AgentState):
# highlight-next-line
# highlight-next-line
class CustomState(AgentState): # (1)!
user_name: str
def prompt(
# highlight-next-line
state: CustomState
) -> list[AnyMessage]:
# highlight-next-line
user_name = state["user_name"]
system_msg = f"You are a helpful assistant. User's name is {user_name}"
return [{"role": "system", "content": system_msg}] + state["messages"]
@@ -150,87 +127,58 @@ Common use cases:
model="anthropic:claude-3-7-sonnet-latest",
tools=[...],
# highlight-next-line
state_schema=CustomState,
# highlight-next-line
state_schema=CustomState, # (2)!
prompt=prompt
)
agent.invoke({
"messages": "hi!",
# highlight-next-line
"user_name": "John Smith"
})
```
## Accessing Context in Tools { #tools }
Tools can access context through special parameter **annotations**.
* Use `RunnableConfig` for config access
* Use `Annotated[StateSchema, InjectedState]` for agent state
1. Define a custom state schema that extends `AgentState` or `MessagesState`.
2. Pass the custom state schema to the agent. This allows the agent to access and modify the state during execution.
!!! tip
These annotations prevent LLMs from attempting to fill in the values. These parameters will be **hidden** from the LLM.
=== "Using config"
=== "In a workflow"
```python
def get_user_info(
# highlight-next-line
config: RunnableConfig,
) -> str:
"""Look up user info."""
# highlight-next-line
user_id = config["configurable"].get("user_id")
return "User is John Smith" if user_id == "user_123" else "Unknown user"
from typing_extensions import TypedDict
from langchain_core.messages import AnyMessage
from langgraph.graph import StateGraph
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_user_info],
)
# highlight-next-line
class CustomState(TypedDict): # (1)!
messages: list[AnyMessage]
extra_field: int
agent.invoke(
{"messages": [{"role": "user", "content": "look up user information"}]},
# highlight-next-line
config={"configurable": {"user_id": "user_123"}}
)
# highlight-next-line
def node(state: CustomState): # (2)!
messages = state["messages"]
...
return { # (3)!
# highlight-next-line
"extra_field": state["extra_field"] + 1
}
builder = StateGraph(State)
builder.add_node(node)
builder.set_entry_point("node")
graph = builder.compile()
```
1. Define a custom state
2. Access the state in any node or tool
3. The Graph API is designed to work as easily as possible with state. The return value of a node represents a requested update to the state.
=== "Using State"
```python
from typing import Annotated
from langgraph.prebuilt import InjectedState
!!! tip "Turning on memory"
class CustomState(AgentState):
# highlight-next-line
user_id: str
Please see the [memory guide](../how-tos/memory/add-memory.md) for more details on how to enable memory. This is a powerful feature that allows you to persist the agent's state across multiple invocations. Otherwise, the state is scoped only to a single run.
def get_user_info(
# highlight-next-line
state: Annotated[CustomState, InjectedState]
) -> str:
"""Look up user info."""
# highlight-next-line
user_id = state["user_id"]
return "User is John Smith" if user_id == "user_123" else "Unknown user"
### Long-term memory (cross-conversation context)
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_user_info],
# highlight-next-line
state_schema=CustomState,
)
For context that spans *across* conversations or sessions, LangGraph allows access to **long-term memory** via a `store`. This can be used to read or update persistent facts (e.g., user profiles, preferences, prior interactions).
agent.invoke({
"messages": "look up user information",
# highlight-next-line
"user_id": "user_123"
})
```
### Update Context from Tools
Tools can update agent's context (state and long-term memory) during execution. This is useful for persisting intermediate results or making information accessible to subsequent tools or prompts. See [Memory](./memory.md#read-short-term) guide for more information.
For more information, see the [Memory guide](../how-tos/memory/add-memory.md).
+1 -1
View File
@@ -89,4 +89,4 @@ LangGraph Studio Web is a specialized UI that you can connect to LangGraph API s
## Deployment
Once your LangGraph app is running locally, you can deploy it using LangGraph Platform. Refer to the [deployment options guide](../tutorials/deployment.md) for detailed instructions on all supported deployment models.
Once your LangGraph app is running locally, you can deploy it using LangGraph Platform. Refer to the [deployment options guide](../concepts/deployment_options.md) for detailed instructions on all supported deployment models.
-238
View File
@@ -1,238 +0,0 @@
---
search:
boost: 2
tags:
- human-in-the-loop
- hil
- agent
hide:
- tags
---
# Human-in-the-loop
To review, edit and approve tool calls in an agent you can use LangGraph's built-in [Human-In-the-Loop (HIL)](../concepts/human_in_the_loop.md) features, specifically the [`interrupt()`][langgraph.types.interrupt] primitive.
LangGraph allows you to pause execution **indefinitely** — for minutes, hours, or even days—until human input is received.
This is possible because the agent state is **checkpointed into a database**, which allows the system to persist execution context and later resume the workflow, continuing from where it left off.
For a deeper dive into the **human-in-the-loop** concept, see the [concept guide](../concepts/human_in_the_loop.md).
<figure markdown="1">
![image](../concepts/img/human_in_the_loop/tool-call-review.png){: style="max-height:400px"}
<figcaption>
A human can review and edit the output from the agent before proceeding. This is particularly critical in applications where the tool calls requested may be sensitive or require human oversight.
</figcaption>
</figure>
## Review tool calls
To add a human approval step to a tool:
1. Use `interrupt()` in the tool to pause execution.
2. Resume with a `Command(resume=...)` to continue based on human input.
```python
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import interrupt
from langgraph.prebuilt import create_react_agent
# An example of a sensitive tool that requires human review / approval
def book_hotel(hotel_name: str):
"""Book a hotel"""
# highlight-next-line
response = interrupt( # (1)!
f"Trying to call `book_hotel` with args {{'hotel_name': {hotel_name}}}. "
"Please approve or suggest edits."
)
if response["type"] == "accept":
pass
elif response["type"] == "edit":
hotel_name = response["args"]["hotel_name"]
else:
raise ValueError(f"Unknown response type: {response['type']}")
return f"Successfully booked a stay at {hotel_name}."
# highlight-next-line
checkpointer = InMemorySaver() # (2)!
agent = create_react_agent(
model="anthropic:claude-3-5-sonnet-latest",
tools=[book_hotel],
# highlight-next-line
checkpointer=checkpointer, # (3)!
)
```
1. The [`interrupt` function][langgraph.types.interrupt] pauses the agent graph at a specific node. In this case, we call `interrupt()` at the beginning of the tool function, which pauses the graph at the node that executes the tool. The information inside `interrupt()` (e.g., tool calls) can be presented to a human, and the graph can be resumed with the user input (tool call approval, edit or feedback).
2. The `InMemorySaver` is used to store the agent state at every step in the tool calling loop. This enables [short-term memory](./memory.md#short-term-memory) and [human-in-the-loop](./human-in-the-loop.md) capabilities. In this example, we use `InMemorySaver` to store the agent state in memory. In a production application, the agent state will be stored in a database.
3. Initialize the agent with the `checkpointer`.
Run the agent with the `stream()` method, passing the `config` object to specify the thread ID. This allows the agent to resume the same conversation on future invocations.
```python
config = {
"configurable": {
# highlight-next-line
"thread_id": "1"
}
}
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "book a stay at McKittrick hotel"}]},
# highlight-next-line
config
):
print(chunk)
print("\n")
```
> You should see that the agent runs until it reaches the `interrupt()` call, at which point it pauses and waits for human input.
Resume the agent with a `Command(resume=...)` to continue based on human input.
```python
from langgraph.types import Command
for chunk in agent.stream(
# highlight-next-line
Command(resume={"type": "accept"}), # (1)!
# Command(resume={"type": "edit", "args": {"hotel_name": "McKittrick Hotel"}}),
config
):
print(chunk)
print("\n")
```
1. The [`interrupt` function][langgraph.types.interrupt] is used in conjunction with the [`Command`][langgraph.types.Command] object to resume the graph with a value provided by the human.
## Using with Agent Inbox
You can create a wrapper to add interrupts to *any* tool.
The example below provides a reference implementation compatible with [Agent Inbox UI](https://github.com/langchain-ai/agent-inbox) and [Agent Chat UI](https://github.com/langchain-ai/agent-chat-ui).
```python title="Wrapper that adds human-in-the-loop to any tool"
from typing import Callable
from langchain_core.tools import BaseTool, tool as create_tool
from langchain_core.runnables import RunnableConfig
from langgraph.types import interrupt
from langgraph.prebuilt.interrupt import HumanInterruptConfig, HumanInterrupt
def add_human_in_the_loop(
tool: Callable | BaseTool,
*,
interrupt_config: HumanInterruptConfig = None,
) -> BaseTool:
"""Wrap a tool to support human-in-the-loop review."""
if not isinstance(tool, BaseTool):
tool = create_tool(tool)
if interrupt_config is None:
interrupt_config = {
"allow_accept": True,
"allow_edit": True,
"allow_respond": True,
}
@create_tool( # (1)!
tool.name,
description=tool.description,
args_schema=tool.args_schema
)
def call_tool_with_interrupt(config: RunnableConfig, **tool_input):
request: HumanInterrupt = {
"action_request": {
"action": tool.name,
"args": tool_input
},
"config": interrupt_config,
"description": "Please review the tool call"
}
# highlight-next-line
response = interrupt([request])[0] # (2)!
# approve the tool call
if response["type"] == "accept":
tool_response = tool.invoke(tool_input, config)
# update tool call args
elif response["type"] == "edit":
tool_input = response["args"]["args"]
tool_response = tool.invoke(tool_input, config)
# respond to the LLM with user feedback
elif response["type"] == "response":
user_feedback = response["args"]
tool_response = user_feedback
else:
raise ValueError(f"Unsupported interrupt response type: {response['type']}")
return tool_response
return call_tool_with_interrupt
```
1. This wrapper creates a new tool that calls `interrupt()` **before** executing the wrapped tool.
2. `interrupt()` is using special input and output format that's expected by [Agent Inbox UI](https://github.com/langchain-ai/agent-inbox):
- a list of [`HumanInterrupt`][langgraph.prebuilt.interrupt.HumanInterrupt] objects is sent to `AgentInbox` render interrupt information to the end user
- resume value is provided by `AgentInbox` as a list (i.e., `Command(resume=[...])`)
You can use the `add_human_in_the_loop` wrapper to add `interrupt()` to any tool without having to add it *inside* the tool:
```python
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.prebuilt import create_react_agent
# highlight-next-line
checkpointer = InMemorySaver()
def book_hotel(hotel_name: str):
"""Book a hotel"""
return f"Successfully booked a stay at {hotel_name}."
agent = create_react_agent(
model="anthropic:claude-3-5-sonnet-latest",
tools=[
# highlight-next-line
add_human_in_the_loop(book_hotel), # (1)!
],
# highlight-next-line
checkpointer=checkpointer,
)
config = {"configurable": {"thread_id": "1"}}
# Run the agent
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "book a stay at McKittrick hotel"}]},
# highlight-next-line
config
):
print(chunk)
print("\n")
```
1. The `add_human_in_the_loop` wrapper is used to add `interrupt()` to the tool. This allows the agent to pause execution and wait for human input before proceeding with the tool call.
> You should see that the agent runs until it reaches the `interrupt()` call,
> at which point it pauses and waits for human input.
Resume the agent with a `Command(resume=...)` to continue based on human input.
```python
from langgraph.types import Command
for chunk in agent.stream(
# highlight-next-line
Command(resume=[{"type": "accept"}]),
# Command(resume=[{"type": "edit", "args": {"args": {"hotel_name": "McKittrick Hotel"}}}]),
config
):
print(chunk)
print("\n")
```
## Additional resources
* [Human-in-the-loop in LangGraph](../concepts/human_in_the_loop.md)
+86 -35
View File
@@ -7,7 +7,7 @@ hide:
- tags
---
# MCP Integration
# Use MCP
[Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol that standardizes how applications provide tools and context to language models. LangGraph agents can use tools defined on MCP servers through the `langchain-mcp-adapters` library.
@@ -23,41 +23,91 @@ pip install langchain-mcp-adapters
The `langchain-mcp-adapters` package enables agents to use tools defined across one or more MCP servers.
```python title="Agent using tools defined on MCP servers"
# highlight-next-line
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
# highlight-next-line
client = MultiServerMCPClient(
{
"math": {
"command": "python",
# Replace with absolute path to your math_server.py file
"args": ["/path/to/math_server.py"],
"transport": "stdio",
},
"weather": {
# Ensure you start your weather server on port 8000
"url": "http://localhost:8000/mcp",
"transport": "streamable_http",
}
}
)
# highlight-next-line
tools = await client.get_tools()
agent = create_react_agent(
"anthropic:claude-3-7-sonnet-latest",
=== "In an agent"
```python title="Agent using tools defined on MCP servers"
# highlight-next-line
tools
)
math_response = await agent.ainvoke(
{"messages": [{"role": "user", "content": "what's (3 + 5) x 12?"}]}
)
weather_response = await agent.ainvoke(
{"messages": [{"role": "user", "content": "what is the weather in nyc?"}]}
)
```
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
# highlight-next-line
client = MultiServerMCPClient(
{
"math": {
"command": "python",
# Replace with absolute path to your math_server.py file
"args": ["/path/to/math_server.py"],
"transport": "stdio",
},
"weather": {
# Ensure you start your weather server on port 8000
"url": "http://localhost:8000/mcp",
"transport": "streamable_http",
}
}
)
# highlight-next-line
tools = await client.get_tools()
agent = create_react_agent(
"anthropic:claude-3-7-sonnet-latest",
# highlight-next-line
tools
)
math_response = await agent.ainvoke(
{"messages": [{"role": "user", "content": "what's (3 + 5) x 12?"}]}
)
weather_response = await agent.ainvoke(
{"messages": [{"role": "user", "content": "what is the weather in nyc?"}]}
)
```
=== "In a workflow"
```python
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.graph import StateGraph, MessagesState, START
from langgraph.prebuilt import ToolNode, tools_condition
from langchain.chat_models import init_chat_model
model = init_chat_model("openai:gpt-4.1")
client = MultiServerMCPClient(
{
"math": {
"command": "python",
# Make sure to update to the full absolute path to your math_server.py file
"args": ["./examples/math_server.py"],
"transport": "stdio",
},
"weather": {
# make sure you start your weather server on port 8000
"url": "http://localhost:8000/mcp/",
"transport": "streamable_http",
}
}
)
tools = await client.get_tools()
def call_model(state: MessagesState):
response = model.bind_tools(tools).invoke(state["messages"])
return {"messages": response}
builder = StateGraph(MessagesState)
builder.add_node(call_model)
builder.add_node(ToolNode(tools))
builder.add_edge(START, "call_model")
builder.add_conditional_edges(
"call_model",
tools_condition,
)
builder.add_edge("tools", "call_model")
graph = builder.compile()
math_response = await graph.ainvoke({"messages": "what's (3 + 5) x 12?"})
weather_response = await graph.ainvoke({"messages": "what is the weather in nyc?"})
```
## Custom MCP servers
@@ -106,4 +156,5 @@ if __name__ == "__main__":
## Additional resources
- [MCP documentation](https://modelcontextprotocol.io/introduction)
- [MCP Transport documentation](https://modelcontextprotocol.io/docs/concepts/transports)
- [MCP Transport documentation](https://modelcontextprotocol.io/docs/concepts/transports)
- [langchain_mcp_adapters](https://github.com/langchain-ai/langchain-mcp-adapters)
-423
View File
@@ -1,423 +0,0 @@
---
search:
boost: 2
tags:
- agent
hide:
- tags
---
# Memory
LangGraph supports two types of memory essential for building conversational agents:
- **[Short-term memory](#short-term-memory)**: Tracks the ongoing conversation by maintaining message history within a session.
- **[Long-term memory](#long-term-memory)**: Stores user-specific or application-level data across sessions.
This guide demonstrates how to use both memory types with agents in LangGraph. For a deeper
understanding of memory concepts, refer to the [LangGraph memory documentation](../concepts/memory.md).
<figure markdown="1">
![image](./assets/memory.png){: style="max-height:400px"}
<figcaption>Both <strong>short-term</strong> and <strong>long-term</strong> memory require persistent storage to maintain continuity across LLM interactions. In production environments, this data is typically stored in a database.</figcaption>
</figure>
!!! note "Terminology"
In LangGraph:
- *Short-term memory* is also referred to as **thread-level memory**.
- *Long-term memory* is also called **cross-thread memory**.
A [thread](../concepts/persistence.md#threads) represents a sequence of related runs
grouped by the same `thread_id`.
## Short-term memory
Short-term memory enables agents to track multi-turn conversations. To use it, you must:
1. Provide a `checkpointer` when creating the agent. The `checkpointer` enables [persistence](../concepts/persistence.md) of the agent's state.
2. Supply a `thread_id` in the config when running the agent. The `thread_id` is a unique identifier for the conversation session.
```python
from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import InMemorySaver
# highlight-next-line
checkpointer = InMemorySaver() # (1)!
def get_weather(city: str) -> str:
"""Get weather for a given city."""
return f"It's always sunny in {city}!"
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
# highlight-next-line
checkpointer=checkpointer # (2)!
)
# Run the agent
config = {
"configurable": {
# highlight-next-line
"thread_id": "1" # (3)!
}
}
sf_response = agent.invoke(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
config
)
# Continue the conversation using the same thread_id
ny_response = agent.invoke(
{"messages": [{"role": "user", "content": "what about new york?"}]},
# highlight-next-line
config # (4)!
)
```
1. The `InMemorySaver` is a checkpointer that stores the agent's state in memory. In a production setting, you would typically use a database or other persistent storage. Please review the [checkpointer documentation](../reference/checkpoints.md) for more options. If you're deploying with **LangGraph Platform**, the platform will provide a production-ready checkpointer for you.
2. The `checkpointer` is passed to the agent. This enables the agent to persist its state across invocations.
3. A unique `thread_id` is provided in the config. This ID is used to identify the conversation session. The value is controlled by the user and can be any string.
4. The agent will continue the conversation using the same `thread_id`. This will allow the agent to infer that the user is asking specifically about the **weather** in New York.
When the agent is invoked the second time with the same `thread_id`, the original message history from the first conversation is automatically included, allowing the agent to infer that the user is asking specifically about the **weather** in New York.
!!! Note "LangGraph Platform provides a production-ready checkpointer"
If you're using [LangGraph Platform](./deployment.md), during deployment your checkpointer will be automatically configured to use a production-ready database.
### Manage message history
Long conversations can exceed the LLM's context window. Common solutions are:
* [Summarization](#summarize-message-history): Maintain a running summary of the conversation
* [Trimming](#trim-message-history): Remove first or last N messages in the history
This allows the agent to keep track of the conversation without exceeding the LLM's context window.
To manage message history, specify `pre_model_hook` — a function ([node](../concepts/low_level.md#nodes)) that will always run before calling the language model.
#### Summarize message history
<figure markdown="1">
![image](./assets/summary.png){: style="max-height:400px"}
<figcaption>Long conversations can exceed the LLM's context window. A common solution is to maintain a running summary of the conversation. This allows the agent to keep track of the conversation without exceeding the LLM's context window.
</figcaption>
</figure>
To summarize message history, you can use [`pre_model_hook`][langgraph.prebuilt.chat_agent_executor.create_react_agent] with a prebuilt [`SummarizationNode`](https://langchain-ai.github.io/langmem/reference/short_term/#langmem.short_term.SummarizationNode):
```python
from langchain_anthropic import ChatAnthropic
from langmem.short_term import SummarizationNode
from langchain_core.messages.utils import count_tokens_approximately
from langgraph.prebuilt import create_react_agent
from langgraph.prebuilt.chat_agent_executor import AgentState
from langgraph.checkpoint.memory import InMemorySaver
from typing import Any
model = ChatAnthropic(model="claude-3-7-sonnet-latest")
summarization_node = SummarizationNode( # (1)!
token_counter=count_tokens_approximately,
model=model,
max_tokens=384,
max_summary_tokens=128,
output_messages_key="llm_input_messages",
)
class State(AgentState):
# NOTE: we're adding this key to keep track of previous summary information
# to make sure we're not summarizing on every LLM call
# highlight-next-line
context: dict[str, Any] # (2)!
checkpointer = InMemorySaver() # (3)!
agent = create_react_agent(
model=model,
tools=tools,
# highlight-next-line
pre_model_hook=summarization_node, # (4)!
# highlight-next-line
state_schema=State, # (5)!
checkpointer=checkpointer,
)
```
1. The `InMemorySaver` is a checkpointer that stores the agent's state in memory. In a production setting, you would typically use a database or other persistent storage. Please review the [checkpointer documentation](../reference/checkpoints.md) for more options. If you're deploying with **LangGraph Platform**, the platform will provide a production-ready checkpointer for you.
2. The `context` key is added to the agent's state. The key contains book-keeping information for the summarization node. It is used to keep track of the last summary information and ensure that the agent doesn't summarize on every LLM call, which can be inefficient.
3. The `checkpointer` is passed to the agent. This enables the agent to persist its state across invocations.
4. The `pre_model_hook` is set to the `SummarizationNode`. This node will summarize the message history before sending it to the LLM. The summarization node will automatically handle the summarization process and update the agent's state with the new summary. You can replace this with a custom implementation if you prefer. Please see the [create_react_agent][langgraph.prebuilt.chat_agent_executor.create_react_agent] API reference for more details.
5. The `state_schema` is set to the `State` class, which is the custom state that contains an extra `context` key.
#### Trim message history
To trim message history, you can use [`pre_model_hook`][langgraph.prebuilt.chat_agent_executor.create_react_agent] with [`trim_messages`](https://python.langchain.com/api_reference/core/messages/langchain_core.messages.utils.trim_messages.html) function:
```python
# highlight-next-line
from langchain_core.messages.utils import (
# highlight-next-line
trim_messages,
# highlight-next-line
count_tokens_approximately
# highlight-next-line
)
from langgraph.prebuilt import create_react_agent
# This function will be called every time before the node that calls LLM
def pre_model_hook(state):
trimmed_messages = trim_messages(
state["messages"],
strategy="last",
token_counter=count_tokens_approximately,
max_tokens=384,
start_on="human",
end_on=("human", "tool"),
)
# highlight-next-line
return {"llm_input_messages": trimmed_messages}
checkpointer = InMemorySaver()
agent = create_react_agent(
model,
tools,
# highlight-next-line
pre_model_hook=pre_model_hook,
checkpointer=checkpointer,
)
```
To learn more about using `pre_model_hook` for managing message history, see this [how-to guide](../how-tos/create-react-agent-manage-message-history.ipynb)
### Read in tools { #read-short-term }
LangGraph allows agent to access its short-term memory (state) inside the tools.
```python
from typing import Annotated
from langgraph.prebuilt import InjectedState, create_react_agent
class CustomState(AgentState):
# highlight-next-line
user_id: str
def get_user_info(
# highlight-next-line
state: Annotated[CustomState, InjectedState]
) -> str:
"""Look up user info."""
# highlight-next-line
user_id = state["user_id"]
return "User is John Smith" if user_id == "user_123" else "Unknown user"
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_user_info],
# highlight-next-line
state_schema=CustomState,
)
agent.invoke({
"messages": "look up user information",
# highlight-next-line
"user_id": "user_123"
})
```
See the [Context](./context.md#__tabbed_2_2) guide for more information.
### Write from tools { #write-short-term }
To modify the agent's short-term memory (state) during execution, you can return state updates directly from the tools. This is useful for persisting intermediate results or making information accessible to subsequent tools or prompts.
```python
from typing import Annotated
from langchain_core.tools import InjectedToolCallId
from langchain_core.runnables import RunnableConfig
from langchain_core.messages import ToolMessage
from langgraph.prebuilt import InjectedState, create_react_agent
from langgraph.prebuilt.chat_agent_executor import AgentState
from langgraph.types import Command
class CustomState(AgentState):
# highlight-next-line
user_name: str
def update_user_info(
tool_call_id: Annotated[str, InjectedToolCallId],
config: RunnableConfig
) -> Command:
"""Look up and update user info."""
user_id = config["configurable"].get("user_id")
name = "John Smith" if user_id == "user_123" else "Unknown user"
# highlight-next-line
return Command(update={
# highlight-next-line
"user_name": name,
# update the message history
"messages": [
ToolMessage(
"Successfully looked up user information",
tool_call_id=tool_call_id
)
]
})
def greet(
# highlight-next-line
state: Annotated[CustomState, InjectedState]
) -> str:
"""Use this to greet the user once you found their info."""
user_name = state["user_name"]
return f"Hello {user_name}!"
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[update_user_info, greet],
# highlight-next-line
state_schema=CustomState
)
agent.invoke(
{"messages": [{"role": "user", "content": "greet the user"}]},
# highlight-next-line
config={"configurable": {"user_id": "user_123"}}
)
```
For more details, see [how to update state from tools](../how-tos/tool-calling.ipynb#update).
## Long-term memory
Use long-term memory to store user-specific or application-specific data across conversations. This is useful for applications like chatbots, where you want to remember user preferences or other information.
To use long-term memory, you need to:
1. [Configure a store](../how-tos/persistence.ipynb#add-long-term-memory) to persist data across invocations.
2. Use the [`get_store`][langgraph.config.get_store] function to access the store from within tools or prompts.
### Read { #read-long-term }
```python title="A tool the agent can use to look up user information"
from langchain_core.runnables import RunnableConfig
from langgraph.config import get_store
from langgraph.prebuilt import create_react_agent
from langgraph.store.memory import InMemoryStore
# highlight-next-line
store = InMemoryStore() # (1)!
# highlight-next-line
store.put( # (2)!
("users",), # (3)!
"user_123", # (4)!
{
"name": "John Smith",
"language": "English",
} # (5)!
)
def get_user_info(config: RunnableConfig) -> str:
"""Look up user info."""
# Same as that provided to `create_react_agent`
# highlight-next-line
store = get_store() # (6)!
user_id = config["configurable"].get("user_id")
# highlight-next-line
user_info = store.get(("users",), user_id) # (7)!
return str(user_info.value) if user_info else "Unknown user"
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_user_info],
# highlight-next-line
store=store # (8)!
)
# Run the agent
agent.invoke(
{"messages": [{"role": "user", "content": "look up user information"}]},
# highlight-next-line
config={"configurable": {"user_id": "user_123"}}
)
```
1. The `InMemoryStore` is a store that stores data in memory. In a production setting, you would typically use a database or other persistent storage. Please review the [store documentation](../reference/store.md) for more options. If you're deploying with **LangGraph Platform**, the platform will provide a production-ready store for you.
2. For this example, we write some sample data to the store using the `put` method. Please see the [BaseStore.put][langgraph.store.base.BaseStore.put] API reference for more details.
3. The first argument is the namespace. This is used to group related data together. In this case, we are using the `users` namespace to group user data.
4. A key within the namespace. This example uses a user ID for the key.
5. The data that we want to store for the given user.
6. The `get_store` function is used to access the store. You can call it from anywhere in your code, including tools and prompts. This function returns the store that was passed to the agent when it was created.
7. The `get` method is used to retrieve data from the store. The first argument is the namespace, and the second argument is the key. This will return a `StoreValue` object, which contains the value and metadata about the value.
8. The `store` is passed to the agent. This enables the agent to access the store when running tools. You can also use the `get_store` function to access the store from anywhere in your code.
### Write { #write-long-term }
```python title="Example of a tool that updates user information"
from typing_extensions import TypedDict
from langgraph.config import get_store
from langgraph.prebuilt import create_react_agent
from langgraph.store.memory import InMemoryStore
store = InMemoryStore() # (1)!
class UserInfo(TypedDict): # (2)!
name: str
def save_user_info(user_info: UserInfo, config: RunnableConfig) -> str: # (3)!
"""Save user info."""
# Same as that provided to `create_react_agent`
# highlight-next-line
store = get_store() # (4)!
user_id = config["configurable"].get("user_id")
# highlight-next-line
store.put(("users",), user_id, user_info) # (5)!
return "Successfully saved user info."
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[save_user_info],
# highlight-next-line
store=store
)
# Run the agent
agent.invoke(
{"messages": [{"role": "user", "content": "My name is John Smith"}]},
# highlight-next-line
config={"configurable": {"user_id": "user_123"}} # (6)!
)
# You can access the store directly to get the value
store.get(("users",), "user_123").value
```
1. The `InMemoryStore` is a store that stores data in memory. In a production setting, you would typically use a database or other persistent storage. Please review the [store documentation](../reference/store.md) for more options. If you're deploying with **LangGraph Platform**, the platform will provide a production-ready store for you.
2. The `UserInfo` class is a `TypedDict` that defines the structure of the user information. The LLM will use this to format the response according to the schema.
3. The `save_user_info` function is a tool that allows an agent to update user information. This could be useful for a chat application where the user wants to update their profile information.
4. The `get_store` function is used to access the store. You can call it from anywhere in your code, including tools and prompts. This function returns the store that was passed to the agent when it was created.
5. The `put` method is used to store data in the store. The first argument is the namespace, and the second argument is the key. This will store the user information in the store.
6. The `user_id` is passed in the config. This is used to identify the user whose information is being updated.
### Semantic search
LangGraph also allows you to [search](https://langchain-ai.github.io/langgraph/how-tos/memory/semantic-search/#using-in-create-react-agent) for items in long-term memory by semantic similarity.
### Prebuilt memory tools
**LangMem** is a LangChain-maintained library that offers tools for managing long-term memories in your agent. See the [LangMem documentation](https://langchain-ai.github.io/langmem/) for usage examples.
## Additional resources
* [Memory in LangGraph](../concepts/memory.md)
+94 -213
View File
@@ -1,233 +1,78 @@
---
search:
boost: 2
tags:
- anthropic
- openai
- agent
hide:
- tags
---
# Models
This page describes how to configure the chat model used by an agent.
## Tool calling support
To enable tool-calling agents, the underlying LLM must support [tool calling](https://python.langchain.com/docs/concepts/tool_calling/).
Compatible models can be found in the [LangChain integrations directory](https://python.langchain.com/docs/integrations/chat/).
## Specifying a model by name
You can configure an agent with a model name string:
=== "OpenAI"
```python
import os
from langgraph.prebuilt import create_react_agent
os.environ["OPENAI_API_KEY"] = "sk-..."
agent = create_react_agent(
# highlight-next-line
model="openai:gpt-4.1",
# other parameters
)
```
=== "Anthropic"
```python
import os
from langgraph.prebuilt import create_react_agent
os.environ["ANTHROPIC_API_KEY"] = "sk-..."
agent = create_react_agent(
# highlight-next-line
model="anthropic:claude-3-7-sonnet-latest",
# other parameters
)
```
=== "Azure"
```python
import os
from langgraph.prebuilt import create_react_agent
os.environ["AZURE_OPENAI_API_KEY"] = "..."
os.environ["AZURE_OPENAI_ENDPOINT"] = "..."
os.environ["OPENAI_API_VERSION"] = "2025-03-01-preview"
agent = create_react_agent(
# highlight-next-line
model="azure_openai:gpt-4.1",
# other parameters
)
```
=== "Google Gemini"
```python
import os
from langgraph.prebuilt import create_react_agent
os.environ["GOOGLE_API_KEY"] = "..."
agent = create_react_agent(
# highlight-next-line
model="google_genai:gemini-2.0-flash",
# other parameters
)
```
=== "AWS Bedrock"
```python
from langgraph.prebuilt import create_react_agent
# Follow the steps here to configure your credentials:
# https://docs.aws.amazon.com/bedrock/latest/userguide/getting-started.html
agent = create_react_agent(
# highlight-next-line
model="bedrock_converse:anthropic.claude-3-5-sonnet-20240620-v1:0",
# other parameters
)
```
LangGraph provides built-in support for [LLMs (language models)](https://python.langchain.com/docs/concepts/chat_models/) via the LangChain library. This makes it easy to integrate various LLMs into your agents and workflows.
## Using `init_chat_model`
## Initialize a model
The [`init_chat_model`](https://python.langchain.com/docs/how_to/chat_models_universal_init/) utility simplifies model initialization with configurable parameters:
Use [`init_chat_model`](https://python.langchain.com/docs/how_to/chat_models_universal_init/) to initialize models:
=== "OpenAI"
{!snippets/chat_model_tabs.md!}
```
pip install -U "langchain[openai]"
```
```python
import os
from langchain.chat_models import init_chat_model
os.environ["OPENAI_API_KEY"] = "sk-..."
model = init_chat_model(
"openai:gpt-4.1",
temperature=0,
# other parameters
)
```
=== "Anthropic"
```
pip install -U "langchain[anthropic]"
```
```python
import os
from langchain.chat_models import init_chat_model
os.environ["ANTHROPIC_API_KEY"] = "sk-..."
model = init_chat_model(
"anthropic:claude-3-5-sonnet-latest",
temperature=0,
# other parameters
)
```
=== "Azure"
```
pip install -U "langchain[openai]"
```
```python
import os
from langchain.chat_models import init_chat_model
os.environ["AZURE_OPENAI_API_KEY"] = "..."
os.environ["AZURE_OPENAI_ENDPOINT"] = "..."
os.environ["OPENAI_API_VERSION"] = "2025-03-01-preview"
model = init_chat_model(
"azure_openai:gpt-4.1",
azure_deployment=os.environ["AZURE_OPENAI_DEPLOYMENT_NAME"],
temperature=0,
# other parameters
)
```
=== "Google Gemini"
```
pip install -U "langchain[google-genai]"
```
```python
import os
from langchain.chat_models import init_chat_model
os.environ["GOOGLE_API_KEY"] = "..."
model = init_chat_model(
"google_genai:gemini-2.0-flash",
temperature=0,
# other parameters
)
```
=== "AWS Bedrock"
```
pip install -U "langchain[aws]"
```
```python
from langchain.chat_models import init_chat_model
# Follow the steps here to configure your credentials:
# https://docs.aws.amazon.com/bedrock/latest/userguide/getting-started.html
model = init_chat_model(
"anthropic.claude-3-5-sonnet-20240620-v1:0",
model_provider="bedrock_converse",
temperature=0,
# other parameters
)
```
Refer to the [API reference](https://python.langchain.com/api_reference/langchain/chat_models/langchain.chat_models.base.init_chat_model.html) for advanced options.
## Using provider-specific LLMs
### Instantiate a model directly
If a model provider is not available via `init_chat_model`, you can instantiate the provider's model class directly. The model must implement the [BaseChatModel interface](https://python.langchain.com/api_reference/core/language_models/langchain_core.language_models.chat_models.BaseChatModel.html) and support tool calling:
```python
# Anthropic is already supported by `init_chat_model`,
# but you can also instantiate it directly.
from langchain_anthropic import ChatAnthropic
from langgraph.prebuilt import create_react_agent
model = ChatAnthropic(
model="claude-3-7-sonnet-latest",
temperature=0,
max_tokens=2048
)
agent = create_react_agent(
# highlight-next-line
model=model,
# other parameters
model="claude-3-7-sonnet-latest",
temperature=0,
max_tokens=2048
)
```
!!! note "Illustrative example"
!!! important "Tool calling support"
The example above uses `ChatAnthropic`, which is already supported by `init_chat_model`. This pattern is shown to illustrate how to manually instantiate a model not available through init_chat_model.
If you are building an agent or workflow that requires the model to call external tools, ensure that the underlying
language model supports [tool calling](../concepts/tools.md). Compatible models can be found in the [LangChain integrations directory](https://python.langchain.com/docs/integrations/chat/).
## Disable streaming
## Use in an agent
When using `create_react_agent` you can specify the model by its name string, which is a shorthand for initializing the model using `init_chat_model`. This allows you to use the model without needing to import or instantiate it directly.
=== "model name"
```python
from langgraph.prebuilt import create_react_agent
create_react_agent(
# highlight-next-line
model="anthropic:claude-3-7-sonnet-latest",
# other parameters
)
```
=== "model instance"
```python
from langchain_anthropic import ChatAnthropic
from langgraph.prebuilt import create_react_agent
model = ChatAnthropic(
model="claude-3-7-sonnet-latest",
temperature=0,
max_tokens=2048
)
# Alternatively
# model = init_chat_model("anthropic:claude-3-7-sonnet-latest")
agent = create_react_agent(
# highlight-next-line
model=model,
# other parameters
)
```
## Advanced model configuration
### Disable streaming
To disable streaming of the individual LLM tokens, set `disable_streaming=True` when initializing the model:
@@ -257,7 +102,7 @@ To disable streaming of the individual LLM tokens, set `disable_streaming=True`
Refer to the [API reference](https://python.langchain.com/api_reference/core/language_models/langchain_core.language_models.chat_models.BaseChatModel.html#langchain_core.language_models.chat_models.BaseChatModel.disable_streaming) for more information on `disable_streaming`
## Adding model fallbacks
### Add model fallbacks
You can add a fallback to a different model or a different LLM provider using `model.with_fallbacks([...])`:
@@ -292,7 +137,43 @@ You can add a fallback to a different model or a different LLM provider using `m
See this [guide](https://python.langchain.com/docs/how_to/fallbacks/#fallback-to-better-model) for more information on model fallbacks.
### Use the built-in rate limiter
Langchain includes a built-in in-memory rate limiter. This rate limiter is thread safe and can be shared by multiple threads in the same process.
```python
from langchain_core.rate_limiters import InMemoryRateLimiter
from langchain_anthropic import ChatAnthropic
rate_limiter = InMemoryRateLimiter(
requests_per_second=0.1, # <-- Super slow! We can only make a request once every 10 seconds!!
check_every_n_seconds=0.1, # Wake up every 100 ms to check whether allowed to make a request,
max_bucket_size=10, # Controls the maximum burst size.
)
model = ChatAnthropic(
model_name="claude-3-opus-20240229",
rate_limiter=rate_limiter
)
```
See the LangChain docs for more information on how to [handle rate limiting](https://python.langchain.com/docs/how_to/chat_model_rate_limiting/).
## Bring your own model
If your desired LLM isn't officially supported by LangChain, consider these options:
1. **Implement a custom LangChain chat model**: Create a model conforming to the [LangChain chat model interface](https://python.langchain.com/docs/how_to/custom_chat_model/). This enables full compatibility with LangGraph's agents and workflows but requires understanding of the LangChain framework.
2. **Direct invocation with custom streaming**: Use your model directly by [adding custom streaming logic](../how-tos/streaming.md#use-with-any-llm) with `StreamWriter`.
Refer to the [custom streaming documentation](../how-tos/streaming.md#use-with-any-llm) for guidance. This approach suits custom workflows where prebuilt agent integration is not necessary.
## Additional resources
- [Multimodal inputs](https://python.langchain.com/docs/how_to/multimodal_inputs/)
- [Structured outputs](https://python.langchain.com/docs/how_to/structured_output/)
- [Model integration directory](https://python.langchain.com/docs/integrations/chat/)
- [Universal initialization with `init_chat_model`](https://python.langchain.com/docs/how_to/chat_models_universal_init/)
- [Force model to call a specific tool](https://python.langchain.com/docs/how_to/tool_choice/)
- [All chat model how-to guides](https://python.langchain.com/docs/how_to/#chat-models)
- [Chat model integrations](https://python.langchain.com/docs/integrations/chat/)
+5 -5
View File
@@ -27,12 +27,12 @@ The LLM operates in a loop. In each iteration, it selects a tool to invoke, prov
LangGraph includes several capabilities essential for building robust, production-ready agentic systems:
- [**Memory integration**](./memory.md): Native support for *short-term* (session-based) and *long-term* (persistent across sessions) memory, enabling stateful behaviors in chatbots and assistants.
- [**Human-in-the-loop control**](./human-in-the-loop.md): Execution can pause *indefinitely* to await human feedback—unlike websocket-based solutions limited to real-time interaction. This enables asynchronous approval, correction, or intervention at any point in the workflow.
- [**Streaming support**](./streaming.md): Real-time streaming of agent state, model tokens, tool outputs, or combined streams.
- [**Memory integration**](../how-tos/memory/add-memory.md): Native support for *short-term* (session-based) and *long-term* (persistent across sessions) memory, enabling stateful behaviors in chatbots and assistants.
- [**Human-in-the-loop control**](../concepts/human_in_the_loop.md): Execution can pause *indefinitely* to await human feedback—unlike websocket-based solutions limited to real-time interaction. This enables asynchronous approval, correction, or intervention at any point in the workflow.
- [**Streaming support**](../how-tos/streaming.md): Real-time streaming of agent state, model tokens, tool outputs, or combined streams.
- [**Deployment tooling**](./deployment.md): Includes infrastructure-free deployment tools. [**LangGraph Platform**](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) supports testing, debugging, and deployment.
- **[Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/)**: A visual IDE for inspecting and debugging workflows.
- Supports multiple [**deployment options**](https://langchain-ai.github.io/langgraph/tutorials/deployment/) for production.
- Supports multiple [**deployment options**](https://langchain-ai.github.io/langgraph/concepts/deployment_options.md) for production.
## High-level building blocks
@@ -50,7 +50,7 @@ The high-level components are organized into several packages, each with a speci
| `langgraph-supervisor` | Tools for building [**supervisor**](./multi-agent.md#supervisor) agents | `pip install -U langgraph-supervisor` |
| `langgraph-swarm` | Tools for building a [**swarm**](./multi-agent.md#swarm) multi-agent system | `pip install -U langgraph-swarm` |
| `langchain-mcp-adapters` | Interfaces to [**MCP servers**](./mcp.md) for tool and resource integration | `pip install -U langchain-mcp-adapters` |
| `langmem` | Agent memory management: [**short-term and long-term**](./memory.md) | `pip install -U langmem` |
| `langmem` | Agent memory management: [**short-term and long-term**](../how-tos/memory/add-memory.md) | `pip install -U langmem` |
| `agentevals` | Utilities to [**evaluate agent performance**](./evals.md) | `pip install -U agentevals` |
## Visualize an agent graph
+3 -3
View File
@@ -1,10 +1,10 @@
[//]: # (This file is automatically generated using a script in docs/_scripts. Do not edit this file directly!)
# Community agents
# Community Agents
If youre looking for other prebuilt libraries, explore the community-built options
below. These libraries can extend LangGraph's functionality in various ways.
## 📚 Available libraries
## 📚 Available Libraries
[//]: # (This file is automatically generated using a script in docs/_scripts. Do not edit this file directly!)
| Name | GitHub URL | Description | Weekly Downloads | Stars |
@@ -23,7 +23,7 @@ below. These libraries can extend LangGraph's functionality in various ways.
| **langgraph-reflection** | [langchain-ai/langgraph-reflection](https://github.com/langchain-ai/langgraph-reflection) | LangGraph agent that runs a reflection step. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langgraph-reflection?style=social)
| **langgraph-codeact** | [langchain-ai/langgraph-codeact](https://github.com/langchain-ai/langgraph-codeact) | LangGraph implementation of CodeAct agent that generates and executes code instead of tool calling. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langgraph-codeact?style=social)
## ✨ Contributing your library
## ✨ Contributing Your Library
Have you built an awesome open-source library using LangGraph? We'd love to feature
your project on the official LangGraph documentation pages! 🏆
+2 -2
View File
@@ -10,7 +10,7 @@ hide:
# Running agents
Agents support both synchronous and asynchronous execution using either `.invoke()` / `await .ainvoke()` for full responses, or `.stream()` / `.astream()` for **incremental** [streaming](streaming.md) output. This section explains how to provide input, interpret output, enable streaming, and control execution limits.
Agents support both synchronous and asynchronous execution using either `.invoke()` / `await .ainvoke()` for full responses, or `.stream()` / `.astream()` for **incremental** [streaming](../how-tos/streaming.md) output. This section explains how to provide input, interpret output, enable streaming, and control execution limits.
## Basic usage
@@ -109,7 +109,7 @@ Streaming is available in both sync and async modes:
!!! tip
For full details, see the [streaming guide](./streaming.md).
For full details, see the [streaming guide](../how-tos/streaming.md).
## Max iterations
-223
View File
@@ -1,223 +0,0 @@
---
search:
boost: 2
tags:
- agent
hide:
- tags
---
# Streaming
Streaming is key to building responsive applications. There are a few types of data youll want to stream:
1. [**Agent progress**](#agent-progress) — get updates after each node in the agent graph is executed.
2. [**LLM tokens**](#llm-tokens) — stream tokens as they are generated by the language model.
3. [**Custom updates**](#tool-updates) — emit custom data from tools during execution (e.g., "Fetched 10/100 records")
You can stream [more than one type of data](#stream-multiple-modes) at a time.
<figure markdown="1">
![image](./assets/fast_parrot.png){: style="max-height:300px"}
<figcaption>
Waiting is for pigeons.
</figcaption>
</figure>
## Agent progress
To stream agent progress, use the [`stream()`][langgraph.graph.state.CompiledStateGraph.stream] or [`astream()`][langgraph.graph.state.CompiledStateGraph.astream] methods with [`stream_mode="updates"`](https://langchain-ai.github.io/langgraph/how-tos/streaming/#updates). This emits an event after every agent step.
For example, if you have an agent that calls a tool once, you should see the following updates:
* **LLM node**: AI message with tool call requests
* **Tool node**: Tool message with execution result
* **LLM node**: Final AI response
=== "Sync"
```python
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
# highlight-next-line
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode="updates"
):
print(chunk)
print("\n")
```
=== "Async"
```python
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
# highlight-next-line
async for chunk in agent.astream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode="updates"
):
print(chunk)
print("\n")
```
## LLM tokens
To stream tokens as they are produced by the LLM, use `stream_mode="messages"`:
=== "Sync"
```python
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
# highlight-next-line
for token, metadata in agent.stream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode="messages"
):
print("Token", token)
print("Metadata", metadata)
print("\n")
```
=== "Async"
```python
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
# highlight-next-line
async for token, metadata in agent.astream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode="messages"
):
print("Token", token)
print("Metadata", metadata)
print("\n")
```
## Tool updates
To stream updates from tools as they are executed, you can use [get_stream_writer][langgraph.config.get_stream_writer].
=== "Sync"
```python
# highlight-next-line
from langgraph.config import get_stream_writer
def get_weather(city: str) -> str:
"""Get weather for a given city."""
# highlight-next-line
writer = get_stream_writer()
# stream any arbitrary data
# highlight-next-line
writer(f"Looking up data for city: {city}")
return f"It's always sunny in {city}!"
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode="custom"
):
print(chunk)
print("\n")
```
=== "Async"
```python
# highlight-next-line
from langgraph.config import get_stream_writer
def get_weather(city: str) -> str:
"""Get weather for a given city."""
# highlight-next-line
writer = get_stream_writer()
# stream any arbitrary data
# highlight-next-line
writer(f"Looking up data for city: {city}")
return f"It's always sunny in {city}!"
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
async for chunk in agent.astream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode="custom"
):
print(chunk)
print("\n")
```
!!! Note
If you add `get_stream_writer` inside your tool, you won't be able to invoke the tool outside of a LangGraph execution context.
## Stream multiple modes
You can specify multiple streaming modes by passing stream mode as a list: `stream_mode=["updates", "messages", "custom"]`:
=== "Sync"
```python
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
for stream_mode, chunk in agent.stream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode=["updates", "messages", "custom"]
):
print(chunk)
print("\n")
```
=== "Async"
```python
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
async for stream_mode, chunk in agent.astream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode=["updates", "messages", "custom"]
):
print(chunk)
print("\n")
```
## Disable streaming
In some applications you might need to disable streaming of individual tokens for a given model. This is useful in [multi-agent](./multi-agent.md) systems to control which agents stream their output.
See the [Models](./models.md#disable-streaming) guide to learn how to disable streaming.
## Additional resources
* [Streaming in LangGraph](https://langchain-ai.github.io/langgraph/how-tos/streaming)
+3 -3
View File
@@ -273,10 +273,10 @@ See [API reference][langgraph.prebuilt.tool_node.ToolNode] for more information
## Working with memory
LangGraph allows access to short-term and long-term memory from tools. See [Memory](./memory.md) guide for more information on:
LangGraph allows access to short-term and long-term memory from tools. See [Memory](../how-tos/memory/add-memory.md) guide for more information on:
* how to [read](./memory.md#read-short-term) from and [write](./memory.md#write-short-term) to **short-term** memory
* how to [read](./memory.md#read-long-term) from and [write](./memory.md#write-long-term) to **long-term** memory
* how to [read](../how-tos/memory/add-memory.md#read-short-term) from and [write](../how-tos/memory/add-memory.md#write-short-term) to **short-term** memory
* how to [read](../how-tos/memory/add-memory.md#read-long-term) from and [write](../how-tos/memory/add-memory.md#write-long-term) to **long-term** memory
## Prebuilt tools
+1 -1
View File
@@ -25,7 +25,7 @@ Then, navigate to [Agent Chat UI](https://agentchat.vercel.app), or clone the re
## Add human-in-the-loop
Agent Chat UI has full support for [human-in-the-loop](../concepts/human_in_the_loop.md) workflows. To try it out, replace the agent code in `src/agent/graph.py` (from the [deployment](./deployment.md) guide) with this [agent implementation](./human-in-the-loop.md#using-with-agent-inbox):
Agent Chat UI has full support for [human-in-the-loop](../concepts/human_in_the_loop.md) workflows. To try it out, replace the agent code in `src/agent/graph.py` (from the [deployment](./deployment.md) guide) with this [agent implementation](../how-tos/human_in_the_loop/add-human-in-the-loop.md#add-interrupts-to-any-tool):
<video controls src="../assets/interrupt-chat-ui.mp4" type="video/mp4"></video>
-5
View File
@@ -1,5 +0,0 @@
# Runs
A run is an invocation of an [assistant](../../concepts/assistants.md). Each run may have its own input, configuration, and metadata, which may affect execution and output of the underlying graph. A run can optionally be executed on a [thread](./threads.md).
The LangGraph Platform API provides several endpoints for creating and managing runs. See the [API reference](../../cloud/reference/api/api_ref.html#tag/thread-runs/) for more details.
-138
View File
@@ -1,138 +0,0 @@
# Streaming
Streaming is critical for making LLM applications feel responsive to end users.
When creating a streaming run, the **streaming mode** determines what kinds of data are streamed back to the API client.
## Supported streaming modes
LangGraph Platform supports the following streaming modes:
| Mode | Description | LangGraph Library Method |
|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------|
| **`values`** | Stream the full graph state after each [super-step](https://langchain-ai.github.io/langgraph/concepts/low_level/#graphs). [Guide](../how-tos/streaming.md#stream-graph-state) | `.stream()` / `.astream()` with `stream_mode="values"` |
| **`updates`** | Stream only the updates to the graph state after each node. [Guide](../how-tos/streaming.md#stream-graph-state) | `.stream()` / `.astream()` with `stream_mode="updates"` |
| **`messages-tuple`** | Stream LLM tokens for any messages generated inside the graph (useful for chat apps). [Guide](../how-tos/streaming.md#messages) | `.stream()` / `.astream()` with `stream_mode="messages"` |
| **`debug`** | Stream debug information throughout graph execution. [Guide](../how-tos/streaming.md#debug) | `.stream()` / `.astream()` with `stream_mode="debug"` |
| **`custom`** | Stream custom data. [Guide](../../how-tos/streaming.md#stream-custom-data) | `.stream()` / `.astream()` with `stream_mode="custom"` |
| **`events`** | Stream all events (including the state of the graph); mainly useful when migrating large LCEL apps. [Guide](../how-tos/streaming.md#stream-events) | `.astream_events()` |
✅ You can also **combine multiple modes** at the same time. See the [how-to guide](../how-tos/streaming.md#stream-multiple-modes) for configuration details.
## Stateless runs
If you don't want to **persist the outputs** of a streaming run in the [checkpointer](../../concepts/persistence.md) DB, you can create a stateless run without creating a thread:
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>, api_key=<API_KEY>)
async for chunk in client.runs.stream(
# highlight-next-line
None, # (1)!
assistant_id,
input=inputs,
stream_mode="updates"
):
print(chunk.data)
```
1. We are passing `None` instead of a `thread_id` UUID.
=== "JavaScript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL>, apiKey: <API_KEY> });
// create a streaming run
// highlight-next-line
const streamResponse = client.runs.stream(
// highlight-next-line
null, // (1)!
assistantID,
{
input,
streamMode: "updates"
}
);
for await (const chunk of streamResponse) {
console.log(chunk.data);
}
```
1. We are passing `None` instead of a `thread_id` UUID.
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/runs/stream \
--header 'Content-Type: application/json' \
--header 'x-api-key: <API_KEY>'
--data "{
\"assistant_id\": \"agent\",
\"input\": <inputs>,
\"stream_mode\": \"updates\"
}"
```
## Join and stream
LangGraph Platform allows you to join an active [background run](../how-tos/background_run.md) and stream outputs from it. To do so, you can use [LangGraph SDK's](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/) `client.runs.join_stream` method:
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>, api_key=<API_KEY>)
# highlight-next-line
async for chunk in client.runs.join_stream(
thread_id,
# highlight-next-line
run_id, # (1)!
):
print(chunk)
```
1. This is the `run_id` of an existing run you want to join.
=== "JavaScript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL>, apiKey: <API_KEY> });
// highlight-next-line
const streamResponse = client.runs.joinStream(
threadID,
// highlight-next-line
runId // (1)!
);
for await (const chunk of streamResponse) {
console.log(chunk);
}
```
1. This is the `run_id` of an existing run you want to join.
=== "cURL"
```bash
curl --request GET \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/<RUN_ID>/stream \
--header 'Content-Type: application/json' \
--header 'x-api-key: <API_KEY>'
```
!!! warning "Outputs not buffered"
When you use `.join_stream`, output is not buffered, so any output produced before joining will not be received.
## API Reference
For API usage and implementation, refer to the [API reference](../reference/api/api_ref.html#tag/thread-runs/POST/threads/{thread_id}/runs/stream).
+1 -1
View File
@@ -1,6 +1,6 @@
# Threads
A thread contains the accumulated state of a sequence of [runs](./runs.md). When a run is executed, the [state](../../concepts/low_level.md#state) of the underlying graph of the assistant will be persisted to the thread.
A thread contains the accumulated state of a sequence of [runs](../../concepts/assistants.md#execution). When a run is executed, the [state](../../concepts/low_level.md#state) of the underlying graph of the assistant will be persisted to the thread.
A thread's current and historical state can be retrieved. To persist state, a thread must be created prior to executing a run.
+9
View File
@@ -62,6 +62,15 @@ Starting from the `LangGraph Platform` view...
1. In the panel, select the `Server` tab to view server logs for the revision. Server logs are only available after a revision has been deployed.
1. Within the `Server` tab, adjust the date/time range picker as needed. By default, the date/time range picker is set to the `Last 7 days`.
## View Deployment Metrics
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
1. In the left-hand navigation panel, select `LangGraph Platform`. The `LangGraph Platform` view contains a list of existing LangGraph Platform deployments.
1. Select an existing deployment to monitor.
1. Select the `Monitoring` tab to view the deployment metrics. See a list of [all available metrics](../../concepts/langgraph_control_plane.md#monitoring).
1. Within the `Monitoring` tab, use the date/time range picker as needed. By default, the date/time range picker is set to the `Last 15 minutes`.
## Interrupt Revision
Interrupting a revision will stop deployment of the revision.
+5 -5
View File
@@ -20,7 +20,7 @@ my-app/
|-- openai_agent.py # code for your graph
```
where the graph is defined in `openai_agent.py`.
where the graph is defined in `openai_agent.py`.
### No rebuild
@@ -28,11 +28,11 @@ In the standard LangGraph API configuration, the server uses the compiled graph
```python
from langchain_openai import ChatOpenAI
from langgraph.graph import END, START, StateGraph, MessagesState
from langgraph.graph import END, START, MessageGraph
model = ChatOpenAI(temperature=0)
graph_workflow = StateGraph(MessagesState)
graph_workflow = MessageGraph()
graph_workflow.add_node("agent", model)
graph_workflow.add_edge("agent", END)
@@ -61,7 +61,7 @@ To make your graph rebuild on each new run with custom configuration, you need t
from typing import Annotated
from typing_extensions import TypedDict
from langchain_openai import ChatOpenAI
from langgraph.graph import END, START
from langgraph.graph import END, START, MessageGraph
from langgraph.graph.state import StateGraph
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
@@ -144,4 +144,4 @@ Finally, you need to specify the path to your graph-making function (`make_graph
}
```
See more info on LangGraph API configuration file [here](../reference/cli.md#configuration-file)
See more info on LangGraph API configuration file [here](../reference/cli.md#configuration-file)
@@ -1,38 +1,8 @@
# Human-in-the-loop
# Human-in-the-loop using Server API
LangGraph supports robust **human-in-the-loop (HIL)** workflows, enabling human intervention at any point in an automated process. This is especially useful in large language model (LLM)-driven applications where model output may require validation, correction, or additional context.
To review, edit, and approve tool calls in an agent or workflow, use LangGraph's [human-in-the-loop](../../concepts/human_in_the_loop.md) features.
Please see [the overview of LangGraph human-in-the-loop](../../concepts/human_in_the_loop.md) features for more information.
## `interrupt`
The [`interrupt` function][langgraph.types.interrupt] in LangGraph enables human-in-the-loop workflows by pausing the graph at a specific node, presenting information to a human, and resuming the graph with their input. It's useful for tasks like approvals, edits, or gathering additional context.
The graph is resumed using a [`Command`][langgraph.types.Command] object that provides the human's response.
**Graph node with `interrupt`:**
```python
# highlight-next-line
from langgraph.types import interrupt, Command
def human_node(state: State):
# highlight-next-line
value = interrupt( # (1)!
{
"text_to_revise": state["some_text"] # (2)!
}
)
return {
"some_text": value # (3)!
}
```
1. `interrupt(...)` pauses execution at `human_node`, surfacing the given payload to a human.
2. Any JSON serializable value can be passed to the `interrupt` function. Here, a dict containing the text to revise.
3. Once resumed, the return value of `interrupt(...)` is the human-provided input, which is used to update the state.
**LangGraph API invoke & resume:**
## LangGraph API invoke & resume
=== "Python"
@@ -337,6 +307,5 @@ def human_node(state: State):
## Learn more
- [**LangGraph human-in-the-loop overview**](../../concepts/human_in_the_loop.md): learn more about LangGraph human-in-the-loop features.
- [**Design patterns**](../../how-tos/human_in_the_loop/add-human-in-the-loop.md#design-patterns): learn how to implement patterns like approving/rejecting actions, requesting user input, and more.
- [**How to review tool calls**](./human_in_the_loop_review_tool_calls.md): detailed examples of how to review and approve/edit tool calls or provide feedback to the tool-calling LLM.
- [Human-in-the-loop conceptual guide](../../concepts/human_in_the_loop.md): learn more about LangGraph human-in-the-loop features.
- [Common patterns](../../how-tos/human_in_the_loop/add-human-in-the-loop.md#common-patterns): learn how to implement patterns like approving/rejecting actions, requesting user input, tool call review, and validating human input.
@@ -212,6 +212,7 @@ We have now created an assistant called "Open AI Assistant" that has `model_name
Output:
```
Receiving event of type: metadata
{'run_id': '1ef6746e-5893-67b1-978a-0f1cd4060e16'}
@@ -219,6 +220,7 @@ Output:
Receiving event of type: updates
{'agent': {'messages': [{'content': 'I was created by OpenAI, a research organization focused on developing and advancing artificial intelligence technology.', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-e1a6b25c-8416-41f2-9981-f9cfe043f414', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
```
### LangGraph Platform UI
@@ -231,9 +233,11 @@ Inside your deployment, select the "Assistants" tab. For the assistant you would
To edit the assistant, use the `update` method. This will create a new version of the assistant with the provided edits. See the [Python](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/#langgraph_sdk.client.AssistantsClient.update) and [JS](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#update) SDK reference docs for more information.
!!! note "Note"
You must pass in the ENTIRE config (and metadata if you are using it). The update endpoint creates new versions completely from scratch and does not rely on previous versions.
You must pass in the ENTIRE config (and metadata if you are using it). The update endpoint creates new versions completely from scratch and does not rely on previous versions.
For example, to update your assistant's system prompt:
=== "Python"
```python
@@ -1,10 +1,16 @@
# Breakpoints
# Set breakpoints using Server API
[Breakpoints](../../concepts/breakpoints.md) pause graph execution at defined points and let you step through each stage. They use LangGraph's [**persistence layer**](../../concepts/persistence.md), which saves the graph state after each step.
With breakpoints, you can inspect the graph's state and node inputs at any point. Execution pauses **indefinitely** until you resume, as the checkpointer preserves the state.
With breakpoints, you can inspect the graph's state and node inputs at any point. Execution pauses indefinitely until you resume, as the checkpointer preserves the state.
## Set breakpoints
!!! tip
For conceptual information on breakpoints, see [Breakpoints](../../concepts/breakpoints.md).
## Set static breakpoints
Static breakpoints are triggered either before or after a node executes. You can set static breakpoints by specifying `interrupt_before` and `interrupt_after` at compile time or run time.
=== "Compile time"
@@ -78,10 +84,9 @@ With breakpoints, you can inspect the graph's state and node inputs at any point
}"
```
!!! tip
This example shows how to add **static** breakpoints. See [this guide](../../how-tos/human_in_the_loop/breakpoints.ipynb) for more options for how to add breakpoints.
## Example
This example shows how to add **static** breakpoints. See [Use breakpoints](../../how-tos/human_in_the_loop/breakpoints.md) for more options on adding breakpoints.
=== "Python"
@@ -177,8 +182,4 @@ With breakpoints, you can inspect the graph's state and node inputs at any point
--data "{
\"assistant_id\": \"agent\"
}"
```
## Learn more
- [**LangGraph breakpoints guide**](../../how-tos/human_in_the_loop/breakpoints.ipynb): learn more about adding breakpoints in LangGraph.
```
@@ -1,549 +0,0 @@
# How to review tool calls
!!! tip "Prerequisites"
This guide assumes familiarity with the following concepts:
* [Tool calling](https://python.langchain.com/docs/concepts/tool_calling/)
* [Human-in-the-loop](../../concepts/human_in_the_loop.md)
* [LangGraph Glossary](../../concepts/low_level.md)
Human-in-the-loop (HIL) interactions are crucial for [agentic systems](../../concepts/agentic_concepts.md). A common pattern is to add some human in the loop step after certain tool calls. These tool calls often lead to either a function call or saving of some information. Examples include:
- A tool call to execute SQL, which will then be run by the tool
- A tool call to generate a summary, which will then be saved to the State of the graph
Note that using tool calls is common **whether actually calling tools or not**.
There are typically a few different interactions you may want to do here:
1. Approve the tool call and continue
2. Modify the tool call manually and then continue
3. Give natural language feedback, and then pass that back to the agent
We can implement these in LangGraph using the [`interrupt()`][langgraph.types.interrupt] function. `interrupt` allows us to stop graph execution to collect input from a user and continue execution with collected input:
```python
def human_review_node(state) -> Command[Literal["call_llm", "run_tool"]]:
# this is the value we'll be providing via Command(resume=<human_review>)
human_review = interrupt(
{
"question": "Is this correct?",
# Surface tool calls for review
"tool_call": tool_call
}
)
review_action, review_data = human_review
# Approve the tool call and continue
if review_action == "continue":
return Command(goto="run_tool")
# Modify the tool call manually and then continue
elif review_action == "update":
...
updated_msg = get_updated_msg(review_data)
return Command(goto="run_tool", update={"messages": [updated_message]})
# Give natural language feedback, and then pass that back to the agent
elif review_action == "feedback":
...
feedback_msg = get_feedback_msg(review_data)
return Command(goto="call_llm", update={"messages": [feedback_msg]})
```
## Setup
We are not going to show the full code for the graph we are hosting, but you can see it [here](../../how-tos/human_in_the_loop/review-tool-calls.ipynb). Once this graph is hosted, we are ready to invoke it and wait for user input.
### SDK initialization
First, we need to setup our client so that we can communicate with our hosted graph:
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
thread = await client.threads.create()
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
// Using the graph deployed with the name "agent"
const assistantId = "agent";
const thread = await client.threads.create();
```
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
## Example of approving tool
First, let's run the agent with an input that requires tool calls with approval:
=== "Python"
```python
input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]}
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
input=input,
stream_mode="updates",
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "Javascript"
```js
const input = { "messages": [{ "role": "user", "content": "what's the weather in sf?" }] };
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
input: input,
streamMode: "updates"
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf?\"}]},
\"stream_mode\": [
\"updates\"
]
}"
```
Output:
{'call_llm': {'messages': [{'content': [{'text': "I'll help you check the weather in San Francisco.", 'type': 'text'}, {'id': 'toolu_01142G3woscA8JjFTLdqymtn', 'input': {'city': 'San Francisco'}, 'name': 'weather_search', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {'id': 'msg_01Tdfufy4nZYXMbVZvgyNbhc', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 379, 'output_tokens': 66}, 'model_name': 'claude-3-5-sonnet-20241022'}, 'type': 'ai', 'name': None, 'id': 'run-a33434b2-f5ca-40c6-98e2-6288d349d4ce-0', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco'}, 'id': 'toolu_01142G3woscA8JjFTLdqymtn', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 379, 'output_tokens': 66, 'total_tokens': 445, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}}}]}}
{'__interrupt__': [{'value': {'question': 'Is this correct?', 'tool_call': {'name': 'weather_search', 'args': {'city': 'San Francisco'}, 'id': 'toolu_01142G3woscA8JjFTLdqymtn', 'type': 'tool_call'}}, 'resumable': True, 'ns': ['human_review_node:9caf42cf-1371-7213-a331-e6fe5d026be8'], 'when': 'during'}]}
To approve the tool call, we need to let `human_review_node` know what value to use for the `human_review` variable we defined inside the node. We can provide this value by invoking the graph with a `Command(resume=<human_review>)` input. Since we're approving the tool call, we'll provide `resume` value of `{"action": "continue"}` to navigate to `run_tool` node:
=== "Python"
```python
# highlight-next-line
from langgraph_sdk.schema import Command
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
# highlight-next-line
command=Command(resume={"action": "continue"}),
stream_mode="updates",
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "Javascript"
```js
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
// highlight-next-line
command: { resume: { "action": "continue" } },
streamMode: "updates"
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"command\": {
\"resume\": { \"action\": \"continue\"}
},
\"stream_mode\": [
\"updates\"
]
}"
```
Output:
{'human_review_node': None}
{'run_tool': {'messages': [{'role': 'tool', 'name': 'weather_search', 'content': 'Sunny!', 'tool_call_id': 'toolu_01142G3woscA8JjFTLdqymtn'}]}}
{'call_llm': {'messages': [{'content': "According to the search, it's sunny in San Francisco right now!", 'additional_kwargs': {}, 'response_metadata': {'id': 'msg_01JJE9AtT4a9Lob91RRiW9rU', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 458, 'output_tokens': 18}, 'model_name': 'claude-3-5-sonnet-20241022'}, 'type': 'ai', 'name': None, 'id': 'run-5e8d80b5-c46a-4aad-af37-b01f8bb15963-0', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 458, 'output_tokens': 18, 'total_tokens': 476, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}}}]}}
## Edit Tool Call
Let's now say we want to edit the tool call. E.g. change some of the parameters (or even the tool called!) but then execute that tool.
=== "Python"
```python
input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]}
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
input=input,
stream_mode="updates",
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "Javascript"
```js
const input = { "messages": [{ "role": "user", "content": "what's the weather in sf?" }] };
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
input: input,
streamMode: "updates",
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf?\"}]},
\"stream_mode\": [
\"updates\"
]
}"
```
To do this, we will use `Command` with a different resume value of `{"action": "update", "data": <tool call args>}`. This will do the following:
* combine existing tool call with user-provided tool call arguments and update the existing AI message with the new tool call
* navigate to `run_tool` node with the updated AI message and continue execution
=== "Python"
```python
# highlight-next-line
from langgraph_sdk.schema import Command
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
# highlight-next-line
command=Command(
# highlight-next-line
resume={"action": "update", "data": {"city": "San Francisco, USA"}}
# highlight-next-line
),
stream_mode="updates",
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "Javascript"
```js
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
// highlight-next-line
command: {
// highlight-next-line
resume: { "action": "update", "data": { "city": "San Francisco, USA" } }
// highlight-next-line
},
streamMode: "updates"
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"command\": {
\"resume\": { \"action\": \"update\", \"data\": { \"city\": \"San Francisco, USA\" } }
},
\"stream_mode\": [
\"updates\"
]
}"
```
Output:
{'human_review_node': {'messages': [{'role': 'ai', 'content': [{'text': "I'll help you check the weather in San Francisco.", 'type': 'text'}, {'id': 'toolu_016L4EDPcaQRzzZxiB4Wq2wa', 'input': {'city': 'San Francisco'}, 'name': 'weather_search', 'type': 'tool_use'}], 'tool_calls': [{'id': 'toolu_016L4EDPcaQRzzZxiB4Wq2wa', 'name': 'weather_search', 'args': {'city': 'San Francisco, USA'}}], 'id': 'run-b07f0c35-4e93-43a5-9b48-363767ada3ca-0'}]}}
{'run_tool': {'messages': [{'role': 'tool', 'name': 'weather_search', 'content': 'Sunny!', 'tool_call_id': 'toolu_016L4EDPcaQRzzZxiB4Wq2wa'}]}}
{'call_llm': {'messages': [{'content': "According to the search, it's sunny in San Francisco right now!", 'additional_kwargs': {}, 'response_metadata': {'id': 'msg_01De5HurjNUMwMUpfRtMLbX1', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 460, 'output_tokens': 18}, 'model_name': 'claude-3-5-sonnet-20241022'}, 'type': 'ai', 'name': None, 'id': 'run-85e2aaaa-6f61-4fa0-b594-b6e57129d7e7-0', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 460, 'output_tokens': 18, 'total_tokens': 478, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}}}]}}
## Give feedback to a tool call
Sometimes, you may not want to execute a tool call, but you also may not want to ask the user to manually modify the tool call. In that case it may be better to get natural language feedback from the user. You can then insert this feedback as a mock **RESULT** of the tool call.
There are multiple ways to do this:
1. You could add a new message to the state (representing the "result" of a tool call)
2. You could add TWO new messages to the state - one representing an "error" from the tool call, other HumanMessage representing the feedback
Both are similar in that they involve adding messages to the state. The main difference lies in the logic AFTER the `human_review_node` and how it handles different types of messages.
For this example we will just add a single tool call representing the feedback (see `human_review_node` implementation). Let's see this in action!
=== "Python"
```python
input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]}
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
input=input,
stream_mode="updates",
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "Javascript"
```js
const input = { "messages": [{ "role": "user", "content": "what's the weather in sf?" }] };
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
input: input,
streamMode: "updates"
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf?\"}]},
\"stream_mode\": [
\"updates\"
]
}"
```
To do this, we will use `Command` with a different resume value of `{"action": "feedback", "data": <feedback string>}`. This will do the following:
* create a new tool message that combines existing tool call from LLM with the with user-provided feedback as content
* navigate to `call_llm` node with the updated tool message and continue execution
=== "Python"
```python
# highlight-next-line
from langgraph_sdk.schema import Command
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
# highlight-next-line
command=Command(
resume={
"action": "feedback",
"data": "User requested changes: use <city, country> format for location"
}
),
stream_mode="updates",
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "Javascript"
```js
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
// highlight-next-line
command: {
resume: {
"action": "feedback",
"data": "User requested changes: use <city, country> format for location"
}
},
streamMode: "updates"
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"command\": {
\"resume\": { \"action\": \"feedback\", \"data\": \"User requested changes: use <city, country> format for location\" }
},
\"stream_mode\": [
\"updates\"
]
}"
```
Output:
{'human_review_node': {'messages': [{'role': 'tool', 'content': 'User requested changes: use <city, country> format for location', 'name': 'weather_search', 'tool_call_id': 'toolu_01RkPHCjpfoUvPAktaq4Cqhm'}]}}
{'call_llm': {'messages': [{'content': [{'text': 'Let me try that again with the correct format:', 'type': 'text'}, {'id': 'toolu_01Rdrag6cVufHZG26BwVaiE7', 'input': {'city': 'San Francisco, USA'}, 'name': 'weather_search', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {'id': 'msg_01EBan969yY5f6iGk6sPgKcj', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 469, 'output_tokens': 68}, 'model_name': 'claude-3-5-sonnet-20241022'}, 'type': 'ai', 'name': None, 'id': 'run-64bbc255-d126-4db0-8ae5-3197cf29bed1-0', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco, USA'}, 'id': 'toolu_01Rdrag6cVufHZG26BwVaiE7', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 469, 'output_tokens': 68, 'total_tokens': 537, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}}}]}}
{'__interrupt__': [{'value': {'question': 'Is this correct?', 'tool_call': {'name': 'weather_search', 'args': {'city': 'San Francisco, USA'}, 'id': 'toolu_01Rdrag6cVufHZG26BwVaiE7', 'type': 'tool_call'}}, 'resumable': True, 'ns': ['human_review_node:e9856878-e28c-5dd1-d353-4d83aa1a3a2b'], 'when': 'during'}]}
We can see that we now get to another interrupt - because it went back to the model and got an entirely new prediction of what to call. Let's now approve this one and continue.
=== "Python"
```python
# highlight-next-line
from langgraph_sdk.schema import Command
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
# highlight-next-line
command=Command(resume={"action": "continue"}),
stream_mode="updates",
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "Javascript"
```js
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
// highlight-next-line
command: { resume: { "action": "continue" } },
streamMode: "updates"
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"command\": {
\"resume\": { \"action\": \"continue\"}
},
\"stream_mode\": [
\"updates\"
]
}"
```
Output:
{'human_review_node': None}
{'run_tool': {'messages': [{'role': 'tool', 'name': 'weather_search', 'content': 'Sunny!', 'tool_call_id': 'toolu_01Rdrag6cVufHZG26BwVaiE7'}]}}
{'call_llm': {'messages': [{'content': 'The weather in San Francisco is sunny!', 'additional_kwargs': {}, 'response_metadata': {'id': 'msg_013WTDHhbg8WiYLiQ9n2CaTk', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 550, 'output_tokens': 12}, 'model_name': 'claude-3-5-sonnet-20241022'}, 'type': 'ai', 'name': None, 'id': 'run-b6c815f0-989a-47cf-b150-33e3bbc4eab7-0', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 550, 'output_tokens': 12, 'total_tokens': 562, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}}}]}}
@@ -1,10 +1,8 @@
# Time travel
# Time travel using Server API
LangGraph provides [**time travel**](../../concepts/time-travel.md) functionality to **resume execution from a prior checkpoint** either replaying the same state or modifying it to explore alternatives. In all cases, resuming past execution produces a **new fork** in the history.
LangGraph provides the [**time travel**](../../concepts/time-travel.md) functionality to resume execution from a prior checkpoint, either replaying the same state or modifying it to explore alternatives. In all cases, resuming past execution produces a new fork in the history.
## Use time travel
To use time-travel in LangGraph:
To time travel using the LangGraph Server API (via the LangGraph SDK):
1. **Run the graph** with initial inputs using [LangGraph SDK](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/)'s [`client.runs.wait`][langgraph_sdk.client.RunsClient.wait] or [`client.runs.stream`][langgraph_sdk.client.RunsClient.stream] APIs.
2. **Identify a checkpoint in an existing thread**: Use [`client.threads.get_history`][langgraph_sdk.client.ThreadsClient.get_history] method to retrieve the execution history for a specific `thread_id` and locate the desired `checkpoint_id`.
@@ -12,7 +10,7 @@ To use time-travel in LangGraph:
3. **(Optional) modify the graph state**: Use the [`client.threads.update_state`][langgraph_sdk.client.ThreadsClient.update_state] method to modify the graphs state at the checkpoint and resume execution from alternative state.
4. **Resume execution from the checkpoint**: Use the [`client.runs.wait`][langgraph_sdk.client.RunsClient.wait] or [`client.runs.stream`][langgraph_sdk.client.RunsClient.stream] APIs with an input of `None` and the appropriate `thread_id` and `checkpoint_id`.
## Example
## Use time travel in a workflow
??? example "Example graph"
@@ -237,4 +235,4 @@ To use time-travel in LangGraph:
## Learn more
- [**LangGraph time travel guide**](../../how-tos/human_in_the_loop/time-travel.ipynb): learn more about using time travel in LangGraph.
- [**LangGraph time travel guide**](../../how-tos/human_in_the_loop/time-travel.md): learn more about using time travel in LangGraph.
@@ -247,5 +247,7 @@ Verify that the original, interrupted run was interrupted
Output:
```
'interrupted'
```
+2 -2
View File
@@ -3,7 +3,7 @@
!!!info "Prerequisites"
- [Running agents](../../agents/run_agents.md#running-agents)
This guide shows how to submit a [run](../concepts/runs.md) to your application.
This guide shows how to submit a [run](../../concepts/assistants.md#execution) to your application.
## Graph mode
@@ -33,7 +33,7 @@ For more information on breakpoints see [here](../../concepts/breakpoints.md).
### Submit run
To submit the run with the specified input and run settings, click the "Submit" button. This will add a [run](../concepts/runs.md) to the existing selected [thread](../concepts/threads.md). If no thread is currently selected, a new one will be created.
To submit the run with the specified input and run settings, click the "Submit" button. This will add a [run](../../concepts/assistants.md#execution) to the existing selected [thread](../../concepts/persistence.md#threads). If no thread is currently selected, a new one will be created.
To cancel the ongoing run, click the "Cancel" button.
+125 -3
View File
@@ -1,8 +1,12 @@
# Stream outputs
# Streaming API
## Streaming API
[LangGraph SDK](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/) allows you to [stream outputs](../../concepts/streaming.md) from the LangGraph API server.
[LangGraph SDK](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/) allows you to stream outputs from the LangGraph API server.
!!! note
LangGraph SDK and LangGraph Server are a part of [LangGraph Platform](../../concepts/langgraph_platform.md).
## Basic usage
Basic usage example:
@@ -833,3 +837,121 @@ To stream all events, including the state of the graph:
\"stream_mode\": \"events\"
}"
```
## Stateless runs
If you don't want to **persist the outputs** of a streaming run in the [checkpointer](../../concepts/persistence.md) DB, you can create a stateless run without creating a thread:
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>, api_key=<API_KEY>)
async for chunk in client.runs.stream(
# highlight-next-line
None, # (1)!
assistant_id,
input=inputs,
stream_mode="updates"
):
print(chunk.data)
```
1. We are passing `None` instead of a `thread_id` UUID.
=== "JavaScript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL>, apiKey: <API_KEY> });
// create a streaming run
// highlight-next-line
const streamResponse = client.runs.stream(
// highlight-next-line
null, // (1)!
assistantID,
{
input,
streamMode: "updates"
}
);
for await (const chunk of streamResponse) {
console.log(chunk.data);
}
```
1. We are passing `None` instead of a `thread_id` UUID.
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/runs/stream \
--header 'Content-Type: application/json' \
--header 'x-api-key: <API_KEY>'
--data "{
\"assistant_id\": \"agent\",
\"input\": <inputs>,
\"stream_mode\": \"updates\"
}"
```
## Join and stream
LangGraph Platform allows you to join an active [background run](../how-tos/background_run.md) and stream outputs from it. To do so, you can use [LangGraph SDK's](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/) `client.runs.join_stream` method:
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>, api_key=<API_KEY>)
# highlight-next-line
async for chunk in client.runs.join_stream(
thread_id,
# highlight-next-line
run_id, # (1)!
):
print(chunk)
```
1. This is the `run_id` of an existing run you want to join.
=== "JavaScript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL>, apiKey: <API_KEY> });
// highlight-next-line
const streamResponse = client.runs.joinStream(
threadID,
// highlight-next-line
runId // (1)!
);
for await (const chunk of streamResponse) {
console.log(chunk);
}
```
1. This is the `run_id` of an existing run you want to join.
=== "cURL"
```bash
curl --request GET \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/<RUN_ID>/stream \
--header 'Content-Type: application/json' \
--header 'x-api-key: <API_KEY>'
```
!!! warning "Outputs not buffered"
When you use `.join_stream`, output is not buffered, so any output produced before joining will not be received.
## API Reference
For API usage and implementation, refer to the [API reference](../reference/api/api_ref.html#tag/thread-runs/POST/threads/{thread_id}/runs/stream).
+15 -8
View File
@@ -13,7 +13,7 @@ LangGraph Studio is accessed from the LangSmith UI, within the LangGraph Platfor
For applications that are [deployed](../../quick_start.md) on LangGraph Platform, you can access Studio as part of that deployment. To do so, navigate to the deployment in LangGraph Platform within the LangSmith UI and click the "LangGraph Studio" button.
This will load the Studio UI connected to your live deployment, allowing you to create, read, and update the [threads](../../concepts/threads.md), [assistants](../../../concepts/assistants.md), and [memory](../../../concepts//memory.md) in that deployment.
This will load the Studio UI connected to your live deployment, allowing you to create, read, and update the [threads](../../../concepts/persistence.md#threads), [assistants](../../../concepts/assistants.md), and [memory](../../../concepts//memory.md) in that deployment.
## Local development server
@@ -73,9 +73,11 @@ langgraph dev --debug-port 5678
Then attach your preferred debugger:
=== "VS Code"
Add this configuration to `launch.json`:
`json
{
Add this configuration to `launch.json`:
```json
{
"name": "Attach to LangGraph",
"type": "debugpy",
"request": "attach",
@@ -83,11 +85,16 @@ Add this configuration to `launch.json`:
"host": "0.0.0.0",
"port": 5678
}
}
`
Specify the port number you chose in the previous step.
}
```
=== "PyCharm" 1. Go to Run → Edit Configurations 2. Click + and select "Python Debug Server" 3. Set IDE host name: `localhost` 4. Set port: `5678` (or the port number you chose in the previous step) 5. Click "OK" and start debugging
=== "PyCharm"
1. Go to Run → Edit Configurations
2. Click + and select "Python Debug Server"
3. Set IDE host name: `localhost`
4. Set port: `5678` (or the port number you chose in the previous step)
5. Click "OK" and start debugging
## Troubleshooting
@@ -0,0 +1,57 @@
# Run experiments over a dataset
LangGraph Studio supports evaluations by allowing you to run your assistant over a pre-defined LangSmith dataset. This enables you to understand how your application performs over a variety of inputs, compare the results to reference outputs, and score the results using [evaluators](../../../agents/evals.md).
This guide shows you how to run an experiment end-to-end from Studio.
---
## Prerequisites
Before running an experiment, ensure you have the following:
1. **A LangSmith dataset**: Your dataset should contain the inputs you want to test and optionally, reference outputs for comparison.
- The schema for the inputs must match the required input schema for the assistant. For more information on schemas, see [here](../../../concepts/low_level.md#schema).
- For more on creating datasets, see [How to Manage Datasets](https://docs.smith.langchain.com/evaluation/how_to_guides/manage_datasets_in_application#set-up-your-dataset).
2. **(Optional) Evaluators**: You can attach evaluators (e.g., LLM-as-a-Judge, heuristics, or custom functions) to your dataset in LangSmith. These will run automatically after the graph has processed all inputs.
- To learn more, read about [Evaluation Concepts](https://docs.smith.langchain.com/evaluation/concepts#evaluators).
3. **A running application**: The experiment can be run against:
- An application deployed on [LangGraph Platform](../../quick_start.md).
- A locally running application started via the [langgraph-cli](../../../tutorials/langgraph-platform/local-server.md).
---
## Step-by-step guide
### 1. Launch the experiment
Click the **Run experiment** button in the top right corner of the Studio page.
### 2. Select your dataset
In the modal that appears, select the dataset (or a specific dataset split) to use for the experiment and click **Start**.
### 3. Monitor the progress
All of the inputs in the dataset will now be run against the active assistant. Monitor the experiment's progress via the badge in the top right corner.
You can continue to work in Studio while the experiment runs in the background. Click the arrow icon button at any time to navigate to LangSmith and view the detailed experiment results.
---
## Troubleshooting
### "Run experiment" button is disabled
If the "Run experiment" button is disabled, check the following:
- **Deployed application**: If your application is deployed on LangGraph Platform, you may need to create a new revision to enable this feature.
- **Local development server**: If you are running your application locally, make sure you have upgraded to the latest version of the `langgraph-cli` (`pip install -U langgraph-cli`). Additionally, ensure you have tracing enabled by setting the `LANGSMITH_API_KEY` in your project's `.env` file.
### Evaluator results are missing
When you run an experiment, any attached evaluators are scheduled for execution in a queue. If you don't see results immediately, it likely means they are still pending.
+1 -5
View File
@@ -1,10 +1,6 @@
# Manage threads
!!! info "Prerequisites"
- [Threads Overview](../concepts/threads.md)
Studio allows you to view threads from the server and edit their state.
Studio allows you to view [threads](../../concepts/persistence.md#threads) from the server and edit their state.
## View threads
+1 -5
View File
@@ -1,10 +1,6 @@
# Use threads
!!! info "Prerequisites"
- [Threads Overview](../concepts/threads.md)
In this guide, we will show how to create, view, and inspect threads.
In this guide, we will show how to create, view, and inspect [threads](../../concepts/persistence.md#threads).
## Create a thread
+75 -69
View File
@@ -8,15 +8,15 @@ Currently, the SDK does not provide built-in support for defining webhook endpoi
The following API endpoints accept a `webhook` parameter:
| Operation | HTTP Method | Endpoint |
|-----------|------------|----------|
| Create Run | `POST` | `/thread/{thread_id}/runs` |
| Create Thread Cron | `POST` | `/thread/{thread_id}/runs/crons` |
| Stream Run | `POST` | `/thread/{thread_id}/runs/stream` |
| Wait Run | `POST` | `/thread/{thread_id}/runs/wait` |
| Create Cron | `POST` | `/runs/crons` |
| Stream Run Stateless | `POST` | `/runs/stream` |
| Wait Run Stateless | `POST` | `/runs/wait` |
| Operation | HTTP Method | Endpoint |
|----------------------|-------------|-----------------------------------|
| Create Run | `POST` | `/thread/{thread_id}/runs` |
| Create Thread Cron | `POST` | `/thread/{thread_id}/runs/crons` |
| Stream Run | `POST` | `/thread/{thread_id}/runs/stream` |
| Wait Run | `POST` | `/thread/{thread_id}/runs/wait` |
| Create Cron | `POST` | `/runs/crons` |
| Stream Run Stateless | `POST` | `/runs/stream` |
| Wait Run Stateless | `POST` | `/runs/wait` |
In this guide, well show how to trigger a webhook after streaming a run.
@@ -25,36 +25,39 @@ In this guide, well show how to trigger a webhook after streaming a run.
Before making API calls, set up your assistant and thread.
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>)
assistant_id = "agent"
thread = await client.threads.create()
print(thread)
```
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>)
assistant_id = "agent"
thread = await client.threads.create()
print(thread)
```
=== "JavaScript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
const assistantID = "agent";
const thread = await client.threads.create();
console.log(thread);
```
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
const assistantID = "agent";
const thread = await client.threads.create();
console.log(thread);
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/assistants/search \
--header 'Content-Type: application/json' \
--data '{ "limit": 10, "offset": 0 }' | jq -c 'map(select(.config == null or .config == {})) | .[0]' && \
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/assistants/search \
--header 'Content-Type: application/json' \
--data '{ "limit": 10, "offset": 0 }' | jq -c 'map(select(.config == null or .config == {})) | .[0]' && \
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
Example response:
@@ -77,52 +80,55 @@ To use a webhook, specify the `webhook` parameter in your API request. When the
For example, if your server listens for webhook events at `https://my-server.app/my-webhook-endpoint`, include this in your request:
=== "Python"
```python
input = { "messages": [{ "role": "user", "content": "Hello!" }] }
async for chunk in client.runs.stream(
thread_id=thread["thread_id"],
assistant_id=assistant_id,
input=input,
stream_mode="events",
webhook="https://my-server.app/my-webhook-endpoint"
):
pass
```
```python
input = { "messages": [{ "role": "user", "content": "Hello!" }] }
async for chunk in client.runs.stream(
thread_id=thread["thread_id"],
assistant_id=assistant_id,
input=input,
stream_mode="events",
webhook="https://my-server.app/my-webhook-endpoint"
):
pass
```
=== "JavaScript"
```js
const input = { messages: [{ role: "human", content: "Hello!" }] };
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantID,
{
input: input,
webhook: "https://my-server.app/my-webhook-endpoint"
}
);
```js
const input = { messages: [{ role: "human", content: "Hello!" }] };
for await (const chunk of streamResponse) {
// Handle stream output
}
```
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantID,
{
input: input,
webhook: "https://my-server.app/my-webhook-endpoint"
}
);
for await (const chunk of streamResponse) {
// Handle stream output
}
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data '{
"assistant_id": <ASSISTANT_ID>,
"input": {"messages": [{"role": "user", "content": "Hello!"}]},
"webhook": "https://my-server.app/my-webhook-endpoint"
}'
```
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data '{
"assistant_id": <ASSISTANT_ID>,
"input": {"messages": [{"role": "user", "content": "Hello!"}]},
"webhook": "https://my-server.app/my-webhook-endpoint"
}'
```
## Webhook payload
LangGraph Platform sends webhook notifications in the format of a [Run](../../cloud/concepts/runs.md). See the [API Reference](https://langchain-ai.github.io/langgraph/cloud/reference/api/api_ref.html#model/run) for details. The request payload includes run input, configuration, and other metadata in the `kwargs` field.
LangGraph Platform sends webhook notifications in the format of a [Run](../../concepts/assistants.md#execution). See the [API Reference](https://langchain-ai.github.io/langgraph/cloud/reference/api/api_ref.html#model/run) for details. The request payload includes run input, configuration, and other metadata in the `kwargs` field.
## Secure webhooks
+20 -2
View File
@@ -2601,8 +2601,7 @@
"description": "Configuration to use for the graph. Useful when graph is configurable and you want to update the assistant's configuration."
},
"metadata": {
"type": "object",
"title": "Metadata",
"type": "object", "title": "Metadata",
"description": "Metadata to merge with existing assistant metadata."
},
"name": {
@@ -2708,6 +2707,13 @@
"title": "Schedule",
"description": "The cron schedule to execute this job on."
},
"end_time": {
"type": "string",
"format": "date-time",
"title": "End Time",
"description": "The end date to stop running the cron."
},
"assistant_id": {
"anyOf": [
{
@@ -2814,6 +2820,18 @@
"description": "The number of results to skip.",
"default": 0,
"minimum": 0
},
"sort_by": {
"type": "string",
"enum": ["cron_id", "assistant_id", "thread_id", "next_run_date", "end_time", "created_at", "updated_at"],
"title": "Sort By",
"description": "The field to sort by."
},
"sort_order": {
"type": "string",
"enum": ["asc", "desc"],
"title": "Sort Order",
"description": "The order to sort by."
}
},
"type": "object",
+7 -6
View File
@@ -50,9 +50,10 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
| <span style="white-space: nowrap;">`python_version`</span> | `3.11`, `3.12`, or `3.13`. Defaults to `3.11`. |
| <span style="white-space: nowrap;">`node_version`</span> | Specify `node_version: 20` to use LangGraph.js. |
| <span style="white-space: nowrap;">`pip_config_file`</span> | Path to `pip` config file. |
| <span style="white-space: nowrap;">`pip_installer`</span> | _(Added in v0.3)_ Optional. Python package installer selector. It can be set to `"auto"`, `"pip"`, or `"uv"`. From version&nbsp;0.3 onward the default strategy is to run `uv pip`, which typically delivers faster builds while remaining a drop-in replacement. In the uncommon situation where `uv` cannot handle your dependency graph or the structure of your `pyproject.toml`, specify `"pip"` here to revert to the earlier behaviour. |
| <span style="white-space: nowrap;">`dockerfile_lines`</span> | Array of additional lines to add to Dockerfile following the import from parent image. |
| <span style="white-space: nowrap;">`checkpointer`</span> | Configuration for the checkpointer. Contains a `ttl` field which is an object with the following keys: <ul><li>`strategy`: How to handle expired checkpoints (e.g., `"delete"`).</li><li>`sweep_interval_minutes`: How often to check for expired checkpoints (integer).</li><li>`default_ttl`: Default time-to-live for checkpoints in **minutes** (integer). Defines how long checkpoints are kept before the specified strategy is applied.</li></ul> |
| <span style="white-space: nowrap;">`http`</span> | HTTP server configuration with the following fields: <ul><li>`app`: Path to custom Starlette/FastAPI app (e.g., `"./src/agent/webapp.py:app"`). See [custom routes guide](../../how-tos/http/custom_routes.md).</li><li>`disable_assistants`: Disable `/assistants` routes</li><li>`disable_threads`: Disable `/threads` routes</li><li>`disable_runs`: Disable `/runs` routes</li><li>`disable_store`: Disable `/store` routes</li><li>`disable_meta`: Disable `/ok`, `/info`, `/metrics`, and `/docs` routes</li><li>`cors`: CORS configuration with fields for `allow_origins`, `allow_methods`, `allow_headers`, etc.</li><li>`configurable_headers`: Define which request headers to exclude or include as a run's configurable values.</li></ul> |
| <span style="white-space: nowrap;">`http`</span> | HTTP server configuration with the following fields: <ul><li>`app`: Path to custom Starlette/FastAPI app (e.g., `"./src/agent/webapp.py:app"`). See [custom routes guide](../../how-tos/http/custom_routes.md).</li><li>`disable_assistants`: Disable `/assistants` routes</li><li>`disable_threads`: Disable `/threads` routes</li><li>`disable_runs`: Disable `/runs` routes</li><li>`disable_store`: Disable `/store` routes</li><li>`disable_meta`: Disable `/ok`, `/info`, `/metrics`, and `/docs` routes</li><li>`disable_mcp`: Disable `/mcp` routes</li><li>`cors`: CORS configuration with fields for `allow_origins`, `allow_methods`, `allow_headers`, etc.</li><li>`configurable_headers`: Define which request headers to exclude or include as a run's configurable values.</li></ul> |
=== "JS"
@@ -128,7 +129,7 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
- `cohere:embed-english-v3.0`: 1024
- `cohere:embed-english-light-v3.0`: 384
- `cohere:embed-multilingual-v3.0`: 1024
- `cohere:embed-multilingual-light-v3.0`: 384
- `cohere:embed-multilingual-light-v3.0`: 384
#### Semantic search with a custom embedding function
@@ -361,8 +362,8 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
**Options**
| Option | Default | Description |
| -------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Option | Default | Description |
| -------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------- |
| `--platform TEXT` | | Target platform(s) to build the Docker image for. Example: `langgraph build --platform linux/amd64,linux/arm64` |
| `-t, --tag TEXT` | | **Required**. Tag for the Docker image. Example: `langgraph build -t my-image` |
| `--pull / --no-pull` | `--pull` | Build with latest remote Docker image. Use `--no-pull` for running the LangGraph Platform API server with locally built images. |
@@ -381,8 +382,8 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
**Options**
| Option | Default | Description |
| -------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Option | Default | Description |
| -------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------- |
| `--platform TEXT` | | Target platform(s) to build the Docker image for. Example: `langgraph build --platform linux/amd64,linux/arm64` |
| `-t, --tag TEXT` | | **Required**. Tag for the Docker image. Example: `langgraph build -t my-image` |
| `--no-pull` | | Use locally built images. Defaults to `false` to build with latest remote Docker image. |
+2 -3
View File
@@ -50,11 +50,10 @@ Set this environment variable to have a deployment send traces to a self-hosted
## `LANGSMITH_TRACING`
!!! info "Only for Self-Hosted Data Plane, Self-Hosted Control Plane, and Standalone Container"
Disabling LangSmith tracing is only available for [Self-Hosted Data Plane](../../concepts/langgraph_self_hosted_data_plane.md), [Self-Hosted Control Plane](../../concepts/langgraph_self_hosted_control_plane.md), and [Standalone Container](../../concepts/langgraph_standalone_container.md) deployments.
Set `LANGSMITH_TRACING` to `false` to disable tracing to LangSmith.
Defaults to `true`.
## `LOG_LEVEL`
Configure [log level](https://docs.python.org/3/library/logging.html#logging-levels). Defaults to `INFO`.
+4 -6
View File
@@ -58,10 +58,10 @@ Tools are useful whenever you want an agent to interact with external systems. E
### Memory
[Memory](./memory.md) is crucial for agents, enabling them to retain and utilize information across multiple steps of problem-solving. It operates on different scales:
[Memory](../how-tos/memory/add-memory.md) is crucial for agents, enabling them to retain and utilize information across multiple steps of problem-solving. It operates on different scales:
1. [Short-term memory](./memory.md#short-term-memory): Allows the agent to access information acquired during earlier steps in a sequence.
2. [Long-term memory](./memory.md#long-term-memory): Enables the agent to recall information from previous interactions, such as past messages in a conversation.
1. [Short-term memory](../how-tos/memory/add-memory.md#add-short-term-memory): Allows the agent to access information acquired during earlier steps in a sequence.
2. [Long-term memory](../how-tos/memory/add-memory.md#add-long-term-memory): Enables the agent to recall information from previous interactions, such as past messages in a conversation.
LangGraph provides full control over memory implementation:
@@ -69,9 +69,7 @@ LangGraph provides full control over memory implementation:
- [`Checkpointer`](./persistence.md#checkpoints): Mechanism to store state at every step across different interactions within a session.
- [`Store`](./persistence.md#memory-store): Mechanism to store user-specific or application-level data across sessions.
This flexible approach allows you to tailor the memory system to your specific agent architecture needs. For a practical guide on adding memory to your graph, see [this tutorial](../how-tos/persistence.ipynb).
Effective [memory management](../how-tos/memory.ipynb) enhances an agent's ability to maintain context, learn from past experiences, and make more informed decisions over time.
This flexible approach allows you to tailor the memory system to your specific agent architecture needs. Effective memory management enhances an agent's ability to maintain context, learn from past experiences, and make more informed decisions over time. For a practical guide on adding and managing memory, see [Memory](../how-tos/memory/add-memory.md).
### Planning
+15 -13
View File
@@ -1,29 +1,31 @@
# Assistants
!!! info "Prerequisites"
**Assistants** allow you to manage configurations (like prompts, LLM selection, tools) separately from your graph's core logic, enabling rapid changes that don't alter the graph architecture. It is a way to create multiple specialized versions of the same graph architecture, each optimized for different use cases through configuration variations rather than structural changes.
- [LangGraph Server](./langgraph_server.md)
- [Configuration](./low_level.md#configuration)
When building agents, it is common to make rapid changes that _do not_ alter the graph logic. For example, simply changing prompts or the LLM selection can have significant impacts on the behavior of the agent but does not require updating your graph's architecture. Assistants offer a straightforward way to manage these configurations separately from your graph's core logic.
Imagine a general-purpose writing agent built on a common graph architecture. While the structure remains the same, different writing styles—such as blog posts and tweets—require tailored configurations to optimize performance. To support these variations, you can create multiple assistants (e.g., one for blogs and another for tweets) that share the underlying graph but differ in model selection and system prompt.
For example, imagine a general-purpose writing agent built on a common graph architecture. While the structure remains the same, different writing styles—such as blog posts and tweets—require tailored configurations to optimize performance. To support these variations, you can create multiple assistants (e.g., one for blogs and another for tweets) that share the underlying graph but differ in model selection and system prompt.
![assistant versions](img/assistants.png)
## Configuring assistants
The LangGraph Cloud API provides several endpoints for creating and managing assistants and their versions. See the [API reference](../cloud/reference/api/api_ref.html#tag/assistants) for more details.
!!! info
Assistants are a [LangGraph Platform](langgraph_platform.md) concept. They are not available in the open source LangGraph library.
## Configuration
Assistants build on the LangGraph open source concept of [configuration](low_level.md#configuration).
While configuration is available in the open source LangGraph library, assistants are only present in [LangGraph Platform](langgraph_platform.md).
This is due to the fact that assistants are tightly coupled to your deployed graph. Upon deployment, LangGraph Server will automatically create a default assistant for each graph using the graph's default configuration settings.
While configuration is available in the open source LangGraph library, assistants are only present in [LangGraph Platform](langgraph_platform.md). This is due to the fact that assistants are tightly coupled to your deployed graph. Upon deployment, LangGraph Server will automatically create a default assistant for each graph using the graph's default configuration settings.
In practice, an assistant is just an _instance_ of a graph with a specific configuration. Therefore, multiple assistants can reference the same graph but can contain different configurations (e.g. prompts, models, tools). The LangGraph Server API provides several endpoints for creating and managing assistants. See the [API reference](../cloud/reference/api/api_ref.html) and [this how-to](../cloud/how-tos/configuration_cloud.md) for more details on how to create assistants.
## Versioning assistants
## Versioning
Assistants support versioning to track changes over time.
Once you've created an assistant, subsequent edits to that assistant will create new versions. See [this how-to](../cloud/how-tos/configuration_cloud.md#create-a-new-version-for-your-assistant) for more details on how to manage assistant versions.
## Learn more
## Execution
* The LangGraph Cloud API provides several endpoints for creating and managing assistants and their versions. See the [API reference](../cloud/reference/api/api_ref.html#tag/assistants) for more details.
A **run** is an invocation of an assistant. Each run may have its own input, configuration, and metadata, which may affect execution and output of the underlying graph. A run can optionally be executed on a [thread](./persistence.md#threads).
The LangGraph Platform API provides several endpoints for creating and managing runs. See the [API reference](../cloud/reference/api/api_ref.html#tag/thread-runs/) for more details.
+5 -1
View File
@@ -5,10 +5,14 @@ search:
# Breakpoints
Breakpoints pause graph execution at defined points and let you step through each stage. They use LangGraph's [**persistence layer**](./persistence.md), which saves the graph state after each step.
[Breakpoints](../how-tos/human_in_the_loop/breakpoints.md) pause graph execution at defined points and let you step through each stage. They use LangGraph's [**persistence layer**](./persistence.md), which saves the graph state after each step.
With breakpoints, you can inspect the graph's state and node inputs at any point. Execution pauses **indefinitely** until you resume, as the checkpointer preserves the state.
<figure markdown="1">
![image](img/breakpoints.png){: style="max-height:400px"}
<figcaption>An example graph consisting of 3 sequential steps with a breakpoint before step_3. </figcaption> </figure>
!!! tip
For information on how to use breakpoints, see [Set breakpoints](../how-tos/human_in_the_loop/breakpoints.md) and [Set breakpoints using Server API](../cloud/how-tos/human_in_the_loop_breakpoint.md).
+11 -2
View File
@@ -5,7 +5,16 @@ search:
# Deployment Options
There are 4 main options for deploying with the LangGraph Platform:
## Free deployment
There are two free options for deploying LangGraph applications via the LangGraph Server:
1. [Local](../tutorials/langgraph-platform/local-server.md): Deploy for local testing and development.
1. [Standalone Container (Lite)](../concepts/langgraph_standalone_container.md): A limited version of Standalone Container for deployments unlikely to see more that 1 million node executions per year and that do not need crons and other enterprise features. Standalone Container (Lite) deployment option is free with a LangSmith API key.
## Production deployment
There are 4 main options for deploying with the [LangGraph Platform](langgraph_platform.md):
1. [Cloud SaaS](#cloud-saas)
@@ -22,7 +31,7 @@ A quick comparison:
|----------------------|----------------|----------------------------|-------------------------------|--------------------------|
| **[Control plane UI/API](../concepts/langgraph_control_plane.md)** | Yes | Yes | Yes | No |
| **CI/CD** | Managed internally by platform | Managed externally by you | Managed externally by you | Managed externally by you |
| **Data/compute residency** | LangChains cloud | Your cloud | Your cloud | Your cloud |
| **Data/compute residency** | LangChain's cloud | Your cloud | Your cloud | Your cloud |
| **LangSmith compatibility** | Trace to LangSmith SaaS | Trace to LangSmith SaaS | Trace to Self-Hosted LangSmith | Optional tracing |
| **[Server version compatibility](../concepts/langgraph_server.md#server-versions)** | Enterprise | Enterprise | Enterprise | Lite, Enterprise |
| **[Pricing](https://www.langchain.com/pricing-langgraph-platform)** | Plus | Enterprise | Enterprise | Developer |
+5 -1
View File
@@ -63,4 +63,8 @@ Yes! LangGraph is totally ambivalent to what LLMs are used under the hood. The m
Yes! You can use the [development version of LangGraph Server](../tutorials/langgraph-platform/local-server.md) to run the backend locally.
This will connect to the studio frontend hosted as part of LangSmith.
If you set an environment variable of `LANGSMITH_TRACING=false`, then no traces will be sent to LangSmith.
If you set an environment variable of `LANGSMITH_TRACING=false`, then no traces will be sent to LangSmith.
## What does "nodes executed" mean for LangGraph Platform usage?
**Nodes Executed** is the aggregate number of nodes in a LangGraph application that are called and completed successfully during an invocation of the application. If a node in the graph is not called during execution or ends in an error state, these nodes will not be counted. If a node is called and completes successfully multiple times, each occurrence will be counted.
+1 -1
View File
@@ -7,7 +7,7 @@ search:
## Overview
The **Functional API** allows you to add LangGraph's key features — [persistence](./persistence.md), [memory](./memory.md), [human-in-the-loop](./human_in_the_loop.md), and [streaming](./streaming.md) — to your applications with minimal changes to your existing code.
The **Functional API** allows you to add LangGraph's key features — [persistence](./persistence.md), [memory](../how-tos/memory/add-memory.md), [human-in-the-loop](./human_in_the_loop.md), and [streaming](./streaming.md) — to your applications with minimal changes to your existing code.
It is designed to integrate these features into existing code that may use standard language primitives for branching and control flow, such as `if` statements, `for` loops, and function calls. Unlike many data orchestration frameworks that require restructuring code into an explicit pipeline or DAG, the Functional API allows you to incorporate these capabilities without enforcing a rigid execution model.
+16 -10
View File
@@ -11,21 +11,27 @@ hide:
# Human-in-the-loop
LangGraph supports robust **human-in-the-loop (HIL)** workflows, enabling human intervention at any point in an automated process. This is especially useful in large language model (LLM)-driven applications where model output may require validation, correction, or additional context.
To review, edit, and approve tool calls in an agent or workflow, [use LangGraph's human-in-the-loop features](../how-tos/human_in_the_loop/add-human-in-the-loop.md) to enable human intervention at any point in a workflow. This is especially useful in large language model (LLM)-driven applications where model output may require validation, correction, or additional context.
<figure markdown="1">
![image](../concepts/img/human_in_the_loop/tool-call-review.png){: style="max-height:400px"}
</figure>
!!! tip
For information on how to use human-in-the-loop, see [Enable human intervention](../how-tos/human_in_the_loop/add-human-in-the-loop.md) and [Human-in-the-loop using Server API](../cloud/how-tos/add-human-in-the-loop.md).
## Key capabilities
* **Persistent execution state**: LangGraph checkpoints the graph state after each step, allowing execution to pause indefinitely at defined nodes. This supports asynchronous human review or input without time constraints.
* **Persistent execution state**: LangGraph allows you to pause execution **indefinitely** — for minutes, hours, or even days—until human input is received. This is possible because LangGraph checkpoints the graph state after each step, which allows the system to persist execution context and later resume the workflow, continuing from where it left off. This supports asynchronous human review or input without time constraints.
* **Flexible integration points**: HIL logic can be introduced at any point in the workflow. This allows targeted human involvement, such as approving API calls, correcting outputs, or guiding conversations.
## Typical use cases
## Patterns
1. [**🛠️ Reviewing tool calls**](../how-tos/human_in_the_loop/add-human-in-the-loop.md#review-tool-calls): Humans can review, edit, or approve tool calls requested by the LLM before tool execution.
2. **✅ Validating LLM outputs**: Humans can review, edit, or approve content generated by the LLM.
3. **💡 Providing context**: Enable the LLM to explicitly request human input for clarification or additional details or to support multi-turn conversations.
There are four typical design patterns that you can implement using `interrupt` and `Command`:
## Implementation
* `interrupt` function: Pauses execution at a specific point, presents information for human review.
* `Command` primitive: Used to resume execution with a value provided by the human.
- [Approve or reject](../how-tos/human_in_the_loop/add-human-in-the-loop.md#approve-or-reject): Pause the graph before a critical step, such as an API call, to review and approve the action. If the action is rejected, you can prevent the graph from executing the step, and potentially take an alternative action. This pattern often involves routing the graph based on the human's input.
- [Edit graph state](../how-tos/human_in_the_loop/add-human-in-the-loop.md#review-and-edit-state): Pause the graph to review and edit the graph state. This is useful for correcting mistakes or updating the state with additional information. This pattern often involves updating the state with the human's input.
- [Review tool calls](../how-tos/human_in_the_loop/add-human-in-the-loop.md#review-tool-calls): Pause the graph to review and edit tool calls requested by the LLM before tool execution.
- [Validate human input](../how-tos/human_in_the_loop/add-human-in-the-loop.md#validate-human-input): Pause the graph to validate human input before proceeding with the next step.
+6 -1
View File
@@ -9,13 +9,18 @@ search:
## Installation
The LangGraph CLI can be installed via pip:
The LangGraph CLI can be installed via pip or [Homebrew](https://brew.sh/):
=== "pip"
```bash
pip install langgraph-cli
```
=== "Homebrew"
```bash
brew install langgraph-cli
```
## Commands
LangGraph CLI provides the following core functionality:
@@ -19,6 +19,7 @@ From the control plane UI, you can:
- Update a deployment.
- Update environment variables for a deployment.
- View build and server logs of a deployment.
- View deployment metrics such as CPU and memory usage.
- Delete a deployment.
The Control Plane UI is embedded in [LangSmith](https://docs.smith.langchain.com/langgraph_cloud).
@@ -88,6 +89,17 @@ Infrastructure for deployments and revisions are provisioned and deployed asynch
The control plane and [LangGraph Data Plane](./langgraph_data_plane.md) "listener" application coordinate to achieve asynchronous deployments.
### Monitoring
After a deployment is ready, the control plane monitors the deployment and records various metrics, such as:
- CPU and memory usage of the deployment.
- Number of container restarts.
- Number of replicas (this will increase with [autoscaling](../concepts/langgraph_data_plane.md#autoscaling)).
- [Postgres](../concepts/langgraph_data_plane.md#postgres) CPU, memory usage, and disk usage.
These metrics are displayed as charts in the Control Plane UI.
### LangSmith Integration
A [LangSmith](https://docs.smith.langchain.com/) tracing project is automatically created for each deployment. The tracing project has the same name as the deployment. When creating a deployment, the `LANGCHAIN_TRACING` and `LANGSMITH_API_KEY`/`LANGCHAIN_API_KEY` environment variables do not need to be specified; they are set automatically by the control plane.
+1 -1
View File
@@ -56,7 +56,7 @@ This section describes various features of the data plane.
1. CPU utilization
1. Memory utilization
1. Number of pending (in progress) [runs](../cloud/concepts/runs.md)
1. Number of pending (in progress) [runs](./assistants.md#execution)
For CPU utilization, the autoscaler targets 75% utilization. This means the autoscaler will scale the number of containers up or down to ensure that CPU utilization is at or near 75%. For memory utilization, the autoscaler targets 75% utilization as well.
+1 -1
View File
@@ -17,7 +17,7 @@ Develop, deploy, scale, and manage agents with **LangGraph Platform** — the pu
LangGraph Platform makes it easy to get your agent running in production — whether its built with LangGraph or another framework — so you can focus on your app logic, not infrastructure. Deploy with one click to get a live endpoint, and use our robust APIs and built-in task queues to handle production scale.
- **[Streaming Support](../cloud/concepts/streaming.md)**: As agents grow more sophisticated, they often benefit from streaming both token outputs and intermediate states back to the user. Without this, users are left waiting for potentially long operations with no feedback. LangGraph Server provides multiple streaming modes optimized for various application needs.
- **[Streaming Support](../cloud/how-tos/streaming.md)**: As agents grow more sophisticated, they often benefit from streaming both token outputs and intermediate states back to the user. Without this, users are left waiting for potentially long operations with no feedback. LangGraph Server provides multiple streaming modes optimized for various application needs.
- **[Background Runs](../cloud/how-tos/background_run.md)**: For agents that take longer to process (e.g., hours), maintaining an open connection can be impractical. The LangGraph Server supports launching agent runs in the background and provides both polling endpoints and webhooks to monitor run status effectively.
@@ -3,7 +3,7 @@
There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](./deployment_options.md#self-hosted-data-plane) and [Self-Hosted Control Plane](./deployment_options.md#self-hosted-control-plane).
!!! info "Important"
The Self-Hosted Control Plane deployment option is currently in beta stage and requires an [Enterprise](../../concepts/plans.md) plan.
The Self-Hosted Control Plane deployment option is currently in beta stage and requires an [Enterprise](plans.md) plan.
## Requirements
@@ -8,7 +8,7 @@ search:
There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](./deployment_options.md#self-hosted-data-plane) and [Self-Hosted Control Plane](./deployment_options.md#self-hosted-control-plane).
!!! info "Important"
The Self-Hosted Data Plane deployment option is currently in beta stage and requires an [Enterprise](../../concepts/plans.md) plan.
The Self-Hosted Data Plane deployment option is currently in beta stage and requires an [Enterprise](plans.md) plan.
## Requirements
+2 -2
View File
@@ -7,7 +7,7 @@ search:
**LangGraph Server** offers an API for creating and managing agent-based applications. It is built on the concept of [assistants](assistants.md), which are agents configured for specific tasks, and includes built-in [persistence](persistence.md#memory-store) and a **task queue**. This versatile API supports a wide range of agentic application use cases, from background processing to real-time interactions.
Use LangGraph Server to create and manage [assistants](assistants.md), [threads](../cloud/concepts/threads.md), [runs](../cloud/concepts/runs.md), [cron jobs](../cloud/concepts/cron_jobs.md), [webhooks](../cloud/concepts/webhooks.md), and more.
Use LangGraph Server to create and manage [assistants](assistants.md), [threads](./persistence.md#threads), [runs](./assistants.md#execution), [cron jobs](../cloud/concepts/cron_jobs.md), [webhooks](../cloud/concepts/webhooks.md), and more.
!!! tip "API reference"
@@ -17,7 +17,7 @@ Use LangGraph Server to create and manage [assistants](assistants.md), [threads]
There are two versions of LangGraph Server:
- `Lite` is a limited version of the LangGraph Server that you can run locally or in a self-hosted manner (up to 1 million nodes executed per year).
- `Lite` is a limited version of the LangGraph Server that you can run locally or in a self-hosted manner (up to 1 million [nodes executed](../concepts/faq.md#what-does-nodes-executed-mean-for-langgraph-platform-usage) per year).
- `Enterprise` is the full version of the LangGraph Server. To use the `Enterprise` version, you must acquire a license key that you will need to specify when running the Docker image. To acquire a license key, please email sales@langchain.dev.
Feature Differences:
@@ -17,6 +17,10 @@ The Standalone Container deployment option is the least restrictive model for de
| **Where is it hosted?** | n/a | Your cloud |
| **Who provisions and manages it?** | n/a | You |
!!! warning
LangGraph Platform should not be deployed in serverless environments.
## Architecture
![Standalone Container](./img/langgraph_platform_deployment_architecture.png)
+2 -1
View File
@@ -24,6 +24,7 @@ Key features of LangGraph Studio:
- [Manage assistants](../cloud/how-tos/studio/manage_assistants.md)
- [Manage threads](../cloud/how-tos/threads_studio.md)
- [Iterate on prompts](../cloud/how-tos/iterate_graph_studio.md)
- [Run experiments over a dataset](../cloud/how-tos/studio/run_evals.md)
- Manage [long term memory](memory.md)
- Debug agent state via [time travel](time-travel.md)
@@ -41,4 +42,4 @@ Chat mode is a simpler UI for iterating on and testing chat-specific agents. It
## Learn more
- See this guide on how to [get started](../cloud/how-tos/studio/quick_start.md) with LangGraph Studio.
- See this guide on how to [get started](../cloud/how-tos/studio/quick_start.md) with LangGraph Studio.
+69 -213
View File
@@ -5,246 +5,63 @@ search:
# Memory
## What is Memory?
[Memory](../how-tos/memory/add-memory.md) is a system that remembers information about previous interactions. For AI agents, memory is crucial because it lets them remember previous interactions, learn from feedback, and adapt to user preferences. As agents tackle more complex tasks with numerous user interactions, this capability becomes essential for both efficiency and user satisfaction.
[Memory](https://pmc.ncbi.nlm.nih.gov/articles/PMC10410470/) is a cognitive function that allows people to store, retrieve, and use information to understand their present and future. Consider the frustration of working with a colleague who forgets everything you tell them, requiring constant repetition! As AI agents undertake more complex tasks involving numerous user interactions, equipping them with memory becomes equally crucial for efficiency and user satisfaction. With memory, agents can learn from feedback and adapt to users' preferences. This guide covers two types of memory based on recall scope:
This conceptual guide covers two types of memory, based on their recall scope:
**Short-term memory**, or [thread](persistence.md#threads)-scoped memory, can be recalled at any time **from within** a single conversational thread with a user. LangGraph manages short-term memory as a part of your agent's [state](low_level.md#state). State is persisted to a database using a [checkpointer](persistence.md#checkpoints) so the thread can be resumed at any time. Short-term memory updates when the graph is invoked or a step is completed, and the State is read at the start of each step.
- [Short-term memory](#short-term-memory), or [thread](persistence.md#threads)-scoped memory, tracks the ongoing conversation by maintaining message history within a session. LangGraph manages short-term memory as a part of your agent's [state](low_level.md#state). State is persisted to a database using a [checkpointer](persistence.md#checkpoints) so the thread can be resumed at any time. Short-term memory updates when the graph is invoked or a step is completed, and the State is read at the start of each step.
**Long-term memory** is shared **across** conversational threads. It can be recalled _at any time_ and **in any thread**. Memories are scoped to any custom namespace, not just within a single thread ID. LangGraph provides [stores](persistence.md#memory-store) ([reference doc](https://langchain-ai.github.io/langgraph/reference/store/#langgraph.store.base.BaseStore)) to let you save and recall long-term memories.
Both are important to understand and implement for your application.
- [Long-term memory](#long-term-memory) stores user-specific or application-level data across sessions and is shared _across_ conversational threads. It can be recalled _at any time_ and _in any thread_. Memories are scoped to any custom namespace, not just within a single thread ID. LangGraph provides [stores](persistence.md#memory-store) ([reference doc](https://langchain-ai.github.io/langgraph/reference/store/#langgraph.store.base.BaseStore)) to let you save and recall long-term memories.
![](img/memory/short-vs-long.png)
## Short-term memory
Short-term memory lets your application remember previous interactions within a single [thread](persistence.md#threads) or conversation. A [thread](persistence.md#threads) organizes multiple interactions in a session, similar to the way email groups messages in a single conversation.
[Short-term memory](../how-tos/memory/add-memory.md#add-short-term-memory) lets your application remember previous interactions within a single [thread](persistence.md#threads) or conversation. A [thread](persistence.md#threads) organizes multiple interactions in a session, similar to the way email groups messages in a single conversation.
LangGraph manages short-term memory as part of the agent's state, persisted via thread-scoped checkpoints. This state can normally include the conversation history along with other stateful data, such as uploaded files, retrieved documents, or generated artifacts. By storing these in the graph's state, the bot can access the full context for a given conversation while maintaining separation between different threads.
Since conversation history is the most common form of representing short-term memory, in the next section, we will cover techniques for managing conversation history when the list of messages becomes **long**. If you want to stick to the high-level concepts, continue on to the [long-term memory](#long-term-memory) section.
### Manage short-term memory
### Managing long conversation history
Conversation history is the most common form of short-term memory, and long conversations pose a challenge to today's LLMs. A full history may not fit inside an LLM's context window, resulting in an irrecoverable error. Even if your LLM supports the full context length, most LLMs still perform poorly over long contexts. They get "distracted" by stale or off-topic content, all while suffering from slower response times and higher costs.
Long conversations pose a challenge to today's LLMs. The full history may not even fit inside an LLM's context window, resulting in an irrecoverable error. Even _if_ your LLM technically supports the full context length, most LLMs still perform poorly over long contexts. They get "distracted" by stale or off-topic content, all while suffering from slower response times and higher costs.
Managing short-term memory is an exercise of balancing [precision & recall](https://en.wikipedia.org/wiki/Precision_and_recall#:~:text=Precision%20can%20be%20seen%20as,irrelevant%20ones%20are%20also%20returned) with your application's other performance requirements (latency & cost). As always, it's important to think critically about how you represent information for your LLM and to look at your data. We cover a few common techniques for managing message lists below and hope to provide sufficient context for you to pick the best tradeoffs for your application:
- [Editing message lists](#editing-message-lists): How to think about trimming and filtering a list of messages before passing to language model.
- [Summarizing past conversations](#summarizing-past-conversations): A common technique to use when you don't just want to filter the list of messages.
### Editing message lists
Chat models accept context using [messages](https://python.langchain.com/docs/concepts/#messages), which include developer provided instructions (a system message) and user inputs (human messages). In chat applications, messages alternate between human inputs and model responses, resulting in a list of messages that grows longer over time. Because context windows are limited and token-rich message lists can be costly, many applications can benefit from using techniques to manually remove or forget stale information.
Chat models accept context using messages, which include developer provided instructions (a system message) and user inputs (human messages). In chat applications, messages alternate between human inputs and model responses, resulting in a list of messages that grows longer over time. Because context windows are limited and token-rich message lists can be costly, many applications can benefit from using techniques to manually remove or forget stale information.
![](img/memory/filter.png)
The most direct approach is to remove old messages from a list (similar to a [least-recently used cache](https://en.wikipedia.org/wiki/Page_replacement_algorithm#Least_recently_used)).
The typical technique for deleting content from a list in LangGraph is to return an update from a node telling the system to delete some portion of the list. You get to define what this update looks like, but a common approach would be to let you return an object or dictionary specifying which values to retain.
```python
def manage_list(existing: list, updates: Union[list, dict]):
if isinstance(updates, list):
# Normal case, add to the history
return existing + updates
elif isinstance(updates, dict) and updates["type"] == "keep":
# You get to decide what this looks like.
# For example, you could simplify and just accept a string "DELETE"
# and clear the entire list.
return existing[updates["from"]:updates["to"]]
# etc. We define how to interpret updates
class State(TypedDict):
my_list: Annotated[list, manage_list]
def my_node(state: State):
return {
# We return an update for the field "my_list" saying to
# keep only values from index -5 to the end (deleting the rest)
"my_list": {"type": "keep", "from": -5, "to": None}
}
```
LangGraph will call the `manage_list` "[reducer](low_level.md#reducers)" function any time an update is returned under the key "my_list". Within that function, we define what types of updates to accept. Typically, messages will be added to the existing list (the conversation will grow); however, we've also added support to accept a dictionary that lets you "keep" certain parts of the state. This lets you programmatically drop old message context.
Another common approach is to let you return a list of "remove" objects that specify the IDs of all messages to delete. If you're using the LangChain messages and the [`add_messages`](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.message.add_messages) reducer (or `MessagesState`, which uses the same underlying functionality) in LangGraph, you can do this using a `RemoveMessage`.
```python
from langchain_core.messages import RemoveMessage, AIMessage
from langgraph.graph import add_messages
# ... other imports
class State(TypedDict):
# add_messages will default to upserting messages by ID to the existing list
# if a RemoveMessage is returned, it will delete the message in the list by ID
messages: Annotated[list, add_messages]
def my_node_1(state: State):
# Add an AI message to the `messages` list in the state
return {"messages": [AIMessage(content="Hi")]}
def my_node_2(state: State):
# Delete all but the last 2 messages from the `messages` list in the state
delete_messages = [RemoveMessage(id=m.id) for m in state['messages'][:-2]]
return {"messages": delete_messages}
```
In the example above, the `add_messages` reducer allows us to [append](https://langchain-ai.github.io/langgraph/concepts/low_level/#serialization) new messages to the `messages` state key as shown in `my_node_1`. When it sees a `RemoveMessage`, it will delete the message with that ID from the list (and the RemoveMessage will then be discarded). For more information on LangChain-specific message handling, check out [this how-to on using `RemoveMessage` ](https://langchain-ai.github.io/langgraph/how-tos/memory/delete-messages/).
See this how-to [guide](https://langchain-ai.github.io/langgraph/how-tos/memory/manage-conversation-history/) and module 2 from our [LangChain Academy](https://github.com/langchain-ai/langchain-academy/tree/main/module-2) course for example usage.
### Summarizing past conversations
The problem with trimming or removing messages, as shown above, is that we may lose information from culling of the message queue. Because of this, some applications benefit from a more sophisticated approach of summarizing the message history using a chat model.
![](img/memory/summary.png)
Simple prompting and orchestration logic can be used to achieve this. As an example, in LangGraph we can extend the [MessagesState](https://langchain-ai.github.io/langgraph/concepts/low_level/#working-with-messages-in-graph-state) to include a `summary` key.
```python
from langgraph.graph import MessagesState
class State(MessagesState):
summary: str
```
Then, we can generate a summary of the chat history, using any existing summary as context for the next summary. This `summarize_conversation` node can be called after some number of messages have accumulated in the `messages` state key.
```python
def summarize_conversation(state: State):
# First, we get any existing summary
summary = state.get("summary", "")
# Create our summarization prompt
if summary:
# A summary already exists
summary_message = (
f"This is a summary of the conversation to date: {summary}\n\n"
"Extend the summary by taking into account the new messages above:"
)
else:
summary_message = "Create a summary of the conversation above:"
# Add prompt to our history
messages = state["messages"] + [HumanMessage(content=summary_message)]
response = model.invoke(messages)
# Delete all but the 2 most recent messages
delete_messages = [RemoveMessage(id=m.id) for m in state["messages"][:-2]]
return {"summary": response.content, "messages": delete_messages}
```
See this how-to [here](https://langchain-ai.github.io/langgraph/how-tos/memory/add-summary-conversation-history/) and module 2 from our [LangChain Academy](https://github.com/langchain-ai/langchain-academy/tree/main/module-2) course for example usage.
### Knowing **when** to remove messages
Most LLMs have a maximum supported context window (denominated in tokens). A simple way to decide when to truncate messages is to count the tokens in the message history and truncate whenever it approaches that limit. Naive truncation is straightforward to implement on your own, though there are a few "gotchas". Some model APIs further restrict the sequence of message types (must start with human message, cannot have consecutive messages of the same type, etc.). If you're using LangChain, you can use the [`trim_messages`](https://python.langchain.com/docs/how_to/trim_messages/#trimming-based-on-token-count) utility and specify the number of tokens to keep from the list, as well as the `strategy` (e.g., keep the last `max_tokens`) to use for handling the boundary.
Below is an example.
```python
from langchain_core.messages import trim_messages
trim_messages(
messages,
# Keep the last <= n_count tokens of the messages.
strategy="last",
# Remember to adjust based on your model
# or else pass a custom token_encoder
token_counter=ChatOpenAI(model="gpt-4"),
# Remember to adjust based on the desired conversation
# length
max_tokens=45,
# Most chat models expect that chat history starts with either:
# (1) a HumanMessage or
# (2) a SystemMessage followed by a HumanMessage
start_on="human",
# Most chat models expect that chat history ends with either:
# (1) a HumanMessage or
# (2) a ToolMessage
end_on=("human", "tool"),
# Usually, we want to keep the SystemMessage
# if it's present in the original history.
# The SystemMessage has special instructions for the model.
include_system=True,
)
```
For more information on common techniques for managing messages, see the [Add and manage memory](../how-tos/memory/add-memory.md#manage-short-term-memory) guide.
## Long-term memory
Long-term memory in LangGraph allows systems to retain information across different conversations or sessions. Unlike short-term memory, which is **thread-scoped**, long-term memory is saved within custom "namespaces."
[Long-term memory](../how-tos/memory/add-memory.md#add-long-term-memory) in LangGraph allows systems to retain information across different conversations or sessions. Unlike short-term memory, which is **thread-scoped**, long-term memory is saved within custom "namespaces."
### Storing memories
Long-term memory is a complex challenge without a one-size-fits-all solution. However, the following questions provide a framework to help you navigate the different techniques:
LangGraph stores long-term memories as JSON documents in a [store](persistence.md#memory-store) ([reference doc](https://langchain-ai.github.io/langgraph/reference/store/#langgraph.store.base.BaseStore)). Each memory is organized under a custom `namespace` (similar to a folder) and a distinct `key` (like a filename). Namespaces often include user or org IDs or other labels that makes it easier to organize information. This structure enables hierarchical organization of memories. Cross-namespace searching is then supported through content filters. See the example below for an example.
- [What is the type of memory?](#memory-types) Humans use memories to remember facts ([semantic memory](#semantic-memory)), experiences ([episodic memory](#episodic-memory)), and rules ([procedural memory](#procedural-memory)). AI agents can use memory in the same ways. For example, AI agents can use memory to remember specific facts about a user to accomplish a task.
```python
from langgraph.store.memory import InMemoryStore
- [When do you want to update memories?](#writing-memories) Memory can be updated as part of an agent's application logic (e.g., "on the hot path"). In this case, the agent typically decides to remember facts before responding to a user. Alternatively, memory can be updated as a background task (logic that runs in the background / asynchronously and generates memories). We explain the tradeoffs between these approaches in the [section below](#writing-memories).
def embed(texts: list[str]) -> list[list[float]]:
# Replace with an actual embedding function or LangChain embeddings object
return [[1.0, 2.0] * len(texts)]
# InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production use.
store = InMemoryStore(index={"embed": embed, "dims": 2})
user_id = "my-user"
application_context = "chitchat"
namespace = (user_id, application_context)
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, "a-memory")
# search for "memories" within this namespace, filtering on content equivalence, sorted by vector similarity
items = store.search(
namespace, filter={"my-key": "my-value"}, query="language preferences"
)
```
### Framework for thinking about long-term memory
Long-term memory is a complex challenge without a one-size-fits-all solution. However, the following questions provide a structure framework to help you navigate the different techniques:
**What is the type of memory?**
Humans use memories to remember [facts](https://en.wikipedia.org/wiki/Semantic_memory), [experiences](https://en.wikipedia.org/wiki/Episodic_memory), and [rules](https://en.wikipedia.org/wiki/Procedural_memory). AI agents can use memory in the same ways. For example, AI agents can use memory to remember specific facts about a user to accomplish a task. We expand on several types of memories in the [section below](#memory-types).
**When do you want to update memories?**
Memory can be updated as part of an agent's application logic (e.g. "on the hot path"). In this case, the agent typically decides to remember facts before responding to a user. Alternatively, memory can be updated as a background task (logic that runs in the background / asynchronously and generates memories). We explain the tradeoffs between these approaches in the [section below](#writing-memories).
## Memory types
### Memory types
Different applications require various types of memory. Although the analogy isn't perfect, examining [human memory types](https://www.psychologytoday.com/us/basics/memory/types-of-memory?ref=blog.langchain.dev) can be insightful. Some research (e.g., the [CoALA paper](https://arxiv.org/pdf/2309.02427)) have even mapped these human memory types to those used in AI agents.
| Memory Type | What is Stored | Human Example | Agent Example |
|-------------|----------------|---------------|---------------|
| Semantic | Facts | Things I learned in school | Facts about a user |
| Episodic | Experiences | Things I did | Past agent actions |
| Procedural | Instructions | Instincts or motor skills | Agent system prompt |
| [Semantic](#semantic-memory) | Facts | Things I learned in school | Facts about a user |
| [Episodic](#episodic-memory) | Experiences | Things I did | Past agent actions |
| [Procedural](#procedural-memory) | Instructions | Instincts or motor skills | Agent system prompt |
### Semantic Memory
#### Semantic memory
[Semantic memory](https://en.wikipedia.org/wiki/Semantic_memory), both in humans and AI agents, involves the retention of specific facts and concepts. In humans, it can include information learned in school and the understanding of concepts and their relationships. For AI agents, semantic memory is often used to personalize applications by remembering facts or concepts from past interactions.
> Note: Not to be confused with "semantic search" which is a technique for finding similar content using "meaning" (usually as embeddings). Semantic memory is a term from psychology, referring to storing facts and knowledge, while semantic search is a method for retrieving information based on meaning rather than exact matches.
!!! note
Semantic memory is different from "semantic search," which is a technique for finding similar content using "meaning" (usually as embeddings). Semantic memory is a term from psychology, referring to storing facts and knowledge, while semantic search is a method for retrieving information based on meaning rather than exact matches.
#### Profile
##### Profile
Semantic memories can be managed in different ways. For example, memories can be a single, continuously updated "profile" of well-scoped and specific information about a user, organization, or other entity (including the agent itself). A profile is generally just a JSON document with various key-value pairs you've selected to represent your domain.
@@ -252,7 +69,7 @@ When remembering a profile, you will want to make sure that you are **updating**
![](img/memory/update-profile.png)
#### Collection
##### Collection
Alternatively, memories can be a collection of documents that are continuously updated and extended over time. Each individual memory can be more narrowly scoped and easier to generate, which means that you're less likely to **lose** information over time. It's easier for an LLM to generate _new_ objects for new information than reconcile new information with an existing profile. As a result, a document collection tends to lead to [higher recall downstream](https://en.wikipedia.org/wiki/Precision_and_recall).
@@ -266,7 +83,7 @@ Finally, using a collection of memories can make it challenging to provide compr
Regardless of memory management approach, the central point is that the agent will use the semantic memories to [ground its responses](https://python.langchain.com/docs/concepts/rag/), which often leads to more personalized and relevant interactions.
### Episodic Memory
#### Episodic memory
[Episodic memory](https://en.wikipedia.org/wiki/Episodic_memory), in both humans and AI agents, involves recalling past events or actions. The [CoALA paper](https://arxiv.org/pdf/2309.02427) frames this well: facts can be written to semantic memory, whereas *experiences* can be written to episodic memory. For AI agents, episodic memory is often used to help an agent remember how to accomplish a task.
@@ -276,7 +93,7 @@ Note that the memory [store](persistence.md#memory-store) is just one way to sto
See this how-to [video](https://www.youtube.com/watch?v=37VaU7e7t5o) for example usage of dynamic few-shot example selection in LangSmith. Also, see this [blog post](https://blog.langchain.dev/few-shot-prompting-to-improve-tool-calling-performance/) showcasing few-shot prompting to improve tool calling performance and this [blog post](https://blog.langchain.dev/aligning-llm-as-a-judge-with-human-preferences/) using few-shot example to align an LLMs to human preferences.
### Procedural Memory
#### Procedural memory
[Procedural memory](https://en.wikipedia.org/wiki/Procedural_memory), in both humans and AI agents, involves remembering the rules used to perform tasks. In humans, procedural memory is like the internalized knowledge of how to perform tasks, such as riding a bike via basic motor skills and balance. Episodic memory, on the other hand, involves recalling specific experiences, such as the first time you successfully rode a bike without training wheels or a memorable bike ride through a scenic route. For AI agents, procedural memory is a combination of model weights, agent code, and agent's prompt that collectively determine the agent's functionality.
@@ -311,13 +128,13 @@ def update_instructions(state: State, store: BaseStore):
![](img/memory/update-instructions.png)
## Writing memories
### Writing memories
While [humans often form long-term memories during sleep](https://medicine.yale.edu/news-article/sleeps-crucial-role-in-preserving-memory/), AI agents need a different approach. When and how should agents create new memories? There are at least two primary methods for agents to write memories: "on the hot path" and "in the background".
There are two primary methods for agents to write memories: ["in the hot path"](#in-the-hot-path) and ["in the background"](#in-the-background).
![](img/memory/hot_path_vs_background.png)
### Writing memories in the hot path
#### In the hot path
Creating memories during runtime offers both advantages and challenges. On the positive side, this approach allows for real-time updates, making new memories immediately available for use in subsequent interactions. It also enables transparency, as users can be notified when memories are created and stored.
@@ -325,10 +142,49 @@ However, this method also presents challenges. It may increase complexity if the
As an example, ChatGPT uses a [save_memories](https://openai.com/index/memory-and-new-controls-for-chatgpt/) tool to upsert memories as content strings, deciding whether and how to use this tool with each user message. See our [memory-agent](https://github.com/langchain-ai/memory-agent) template as an reference implementation.
### Writing memories in the background
#### In the background
Creating memories as a separate background task offers several advantages. It eliminates latency in the primary application, separates application logic from memory management, and allows for more focused task completion by the agent. This approach also provides flexibility in timing memory creation to avoid redundant work.
However, this method has its own challenges. Determining the frequency of memory writing becomes crucial, as infrequent updates may leave other threads without new context. Deciding when to trigger memory formation is also important. Common strategies include scheduling after a set time period (with rescheduling if new events occur), using a cron schedule, or allowing manual triggers by users or the application logic.
See our [memory-service](https://github.com/langchain-ai/memory-template) template as an reference implementation.
### Memory storage
LangGraph stores long-term memories as JSON documents in a [store](persistence.md#memory-store). Each memory is organized under a custom `namespace` (similar to a folder) and a distinct `key` (like a file name). Namespaces often include user or org IDs or other labels that makes it easier to organize information. This structure enables hierarchical organization of memories. Cross-namespace searching is then supported through content filters.
```python
from langgraph.store.memory import InMemoryStore
def embed(texts: list[str]) -> list[list[float]]:
# Replace with an actual embedding function or LangChain embeddings object
return [[1.0, 2.0] * len(texts)]
# InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production use.
store = InMemoryStore(index={"embed": embed, "dims": 2})
user_id = "my-user"
application_context = "chitchat"
namespace = (user_id, application_context)
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, "a-memory")
# search for "memories" within this namespace, filtering on content equivalence, sorted by vector similarity
items = store.search(
namespace, filter={"my-key": "my-value"}, query="language preferences"
)
```
For more information about the memory store, see the [Persistence](persistence.md#memory-store) guide.
+4 -3
View File
@@ -87,6 +87,7 @@ One of the most common agent types is a [tool-calling agent](../agents/overview.
```python
from langchain_core.tools import tool
@tool
def transfer_to_bob():
"""Transfer to bob."""
return Command(
@@ -375,13 +376,13 @@ The most common way for agents to communicate is via a shared state channel, typ
#### Sharing full thought process
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 the 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).
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 the 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](../how-tos/memory/add-memory.md).
#### Sharing only final results
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](#using-different-state-schemas).
For agents called as tools, the supervisor determines the inputs based on the tool schema. Additionally, LangGraph allows [passing state](../how-tos/tool-calling.ipynb#read-state) to individual tools at runtime, so subordinate agents can access parent state, if needed.
For agents called as tools, the supervisor determines the inputs based on the tool schema. Additionally, LangGraph allows [passing state](../how-tos/tool-calling.md#short-term-memory) to individual tools at runtime, so subordinate agents can access parent state, if needed.
#### Indicating agent name in messages
@@ -414,4 +415,4 @@ There are two high-level approaches to achieve that:
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](./subgraphs.md) 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](../how-tos/subgraph.ipynb#different-state-schemas) so that the parent graph knows how to communicate with the subgraphs.
- Define agent node functions with a [private input state schema](../how-tos/graph-api.ipynb/#pass-private-state-between-nodes) that is distinct from the overall graph state schema. This allows passing information that is only needed for executing that particular agent.
- Define agent node functions with a [private input state schema](../how-tos/graph-api.ipynb/#pass-private-state-between-nodes) that is distinct from the overall graph state schema. This allows passing information that is only needed for executing that particular agent.
+14 -8
View File
@@ -5,7 +5,7 @@ search:
# Persistence
LangGraph has a built-in persistence layer, implemented through checkpointers. When you compile graph with a checkpointer, the checkpointer saves a `checkpoint` of the graph state at every super-step. Those checkpoints are saved to a `thread`, which can be accessed after graph execution. Because `threads` allow access to graph's state after execution, several powerful capabilities including human-in-the-loop, memory, time travel, and fault-tolerance are all possible. See [this how-to guide](../how-tos/persistence.ipynb) for an end-to-end example on how to add and use checkpointers with your graph. Below, we'll discuss each of these concepts in more detail.
LangGraph has a built-in persistence layer, implemented through checkpointers. When you compile graph with a checkpointer, the checkpointer saves a `checkpoint` of the graph state at every super-step. Those checkpoints are saved to a `thread`, which can be accessed after graph execution. Because `threads` allow access to graph's state after execution, several powerful capabilities including human-in-the-loop, memory, time travel, and fault-tolerance are all possible. Below, we'll discuss each of these concepts in more detail.
![Checkpoints](img/persistence/checkpoints.jpg)
@@ -15,21 +15,27 @@ LangGraph has a built-in persistence layer, implemented through checkpointers. W
## Threads
A thread is a unique ID or [thread identifier](#threads) assigned to each checkpoint saved by a checkpointer. When invoking graph with a checkpointer, you **must** specify a `thread_id` as part of the `configurable` portion of the config:
A thread is a unique ID or thread identifier assigned to each checkpoint saved by a checkpointer. It contains the accumulated state of a sequence of [runs](./assistants.md#execution). When a run is executed, the [state](../concepts/low_level.md#state) of the underlying graph of the assistant will be persisted to the thread.
When invoking graph with a checkpointer, you **must** specify a `thread_id` as part of the `configurable` portion of the config:
```python
{"configurable": {"thread_id": "1"}}
```
A thread's current and historical state can be retrieved. To persist state, a thread must be created prior to executing a run. The LangGraph Platform API provides several endpoints for creating and managing threads and thread state. See the [API reference](../cloud/reference/api/api_ref.html#tag/threads) for more details.
## Checkpoints
Checkpoint is a snapshot of the graph state saved at each super-step and is represented by `StateSnapshot` object with the following key properties:
The state of a thread at a particular point in time is called a checkpoint. Checkpoint is a snapshot of the graph state saved at each super-step and is represented by `StateSnapshot` object with the following key properties:
- `config`: Config associated with this checkpoint.
- `metadata`: Metadata associated with this checkpoint.
- `values`: Values of the state channels at this point in time.
- `next` A tuple of the node names to execute next in the graph.
- `tasks`: A tuple of `PregelTask` objects that contain information about next tasks to be executed. If the step was previously attempted, it will include error information. If a graph was interrupted [dynamically](../how-tos/human_in_the_loop/breakpoints.ipynb#dynamic-breakpoints) from within a node, tasks will contain additional data associated with interrupts.
- `tasks`: A tuple of `PregelTask` objects that contain information about next tasks to be executed. If the step was previously attempted, it will include error information. If a graph was interrupted [dynamically](../how-tos/human_in_the_loop/breakpoints.md#dynamic-breakpoints) from within a node, tasks will contain additional data associated with interrupts.
Checkpoints are persisted and can be used to restore the state of a thread at a later time.
Let's see what checkpoints are saved when a simple graph is invoked as follows:
@@ -168,7 +174,7 @@ config = {"configurable": {"thread_id": "1", "checkpoint_id": "0c62ca34-ac19-445
graph.invoke(None, config=config)
```
Importantly, LangGraph knows whether a particular step has been executed previously. If it has, LangGraph simply *re-plays* that particular step in the graph and does not re-execute the step, but only for the steps _before_ the provided `checkpoint_id`. All of the steps _after_ `checkpoint_id` will be executed (i.e., a new fork), even if they have been executed previously. See this [how to guide on time-travel to learn more about replaying](../how-tos/human_in_the_loop/time-travel.ipynb).
Importantly, LangGraph knows whether a particular step has been executed previously. If it has, LangGraph simply *re-plays* that particular step in the graph and does not re-execute the step, but only for the steps _before_ the provided `checkpoint_id`. All of the steps _after_ `checkpoint_id` will be executed (i.e., a new fork), even if they have been executed previously. See this [how to guide on time-travel to learn more about replaying](../how-tos/human_in_the_loop/time-travel.md).
![Replay](img/persistence/re_play.png)
@@ -218,7 +224,7 @@ The `foo` key (channel) is completely changed (because there is no reducer speci
#### `as_node`
The final thing you can optionally specify when calling `update_state` is `as_node`. If you provided it, the update will be applied as if it came from node `as_node`. If `as_node` is not provided, it will be set to the last node that updated the state, if not ambiguous. The reason this matters is that the next steps to execute depend on the last node to have given an update, so this can be used to control which node executes next. See this [how to guide on time-travel to learn more about forking state](../how-tos/human_in_the_loop/time-travel.ipynb).
The final thing you can optionally specify when calling `update_state` is `as_node`. If you provided it, the update will be applied as if it came from node `as_node`. If `as_node` is not provided, it will be set to the last node that updated the state, if not ambiguous. The reason this matters is that the next steps to execute depend on the last node to have given an update, so this can be used to control which node executes next. See this [how to guide on time-travel to learn more about forking state](../how-tos/human_in_the_loop/time-travel.md).
![Update](img/persistence/checkpoints_full_story.jpg)
@@ -519,11 +525,11 @@ When running on LangGraph Platform, encryption is automatically enabled whenever
### Human-in-the-loop
First, checkpointers facilitate [human-in-the-loop workflows](agentic_concepts.md#human-in-the-loop) workflows by allowing humans to inspect, interrupt, and approve graph steps. Checkpointers are needed for these workflows as the human has to be able to view the state of a graph at any point in time, and the graph has to be to resume execution after the human has made any updates to the state. See [these how-to guides](../how-tos/human_in_the_loop/breakpoints.ipynb) for concrete examples.
First, checkpointers facilitate [human-in-the-loop workflows](agentic_concepts.md#human-in-the-loop) workflows by allowing humans to inspect, interrupt, and approve graph steps. Checkpointers are needed for these workflows as the human has to be able to view the state of a graph at any point in time, and the graph has to be to resume execution after the human has made any updates to the state. See [these how-to guides](../how-tos/human_in_the_loop/breakpoints.md) for concrete examples.
### Memory
Second, checkpointers allow for ["memory"](agentic_concepts.md#memory) between interactions. In the case of repeated human interactions (like conversations) any follow up messages can be sent to that thread, which will retain its memory of previous ones. See [this how-to guide](../how-tos/memory.ipynb) for an end-to-end example on how to add and manage conversation memory using checkpointers.
Second, checkpointers allow for ["memory"](../concepts/memory.md) between interactions. In the case of repeated human interactions (like conversations) any follow up messages can be sent to that thread, which will retain its memory of previous ones. See [Add memory](../how-tos/memory/add-memory.md) for information on how to add and manage conversation memory using checkpointers.
### Time Travel
+1 -1
View File
@@ -20,7 +20,7 @@ There are three different plans for using it.
| | Developer | Plus | Enterprise |
|------------------------------------------------------------------|---------------------------------------------|-------------------------------------------------------|-----------------------------------------------------|
| Deployment Options | Standalone Container (Lite) | Cloud SaaS | <ul><li>Cloud SaaS</li><li>Self-Hosted Data Plane</li><li>Self-Hosted Control Plane</li><li>Standalone Container (Enterprise)</li></ul> |
| Usage | Free, limited to 1M nodes executed per year | Free while in Beta, will be charged per node executed | Custom |
| Usage | Free, limited to 1M [nodes executed](../concepts/faq.md#what-does-nodes-executed-mean-for-langgraph-platform-usage) per year | See [Pricing](https://www.langchain.com/langgraph-platform-pricing) | Custom |
| APIs for retrieving and updating state and conversational history | ✅ | ✅ | ✅ |
| APIs for retrieving and updating long-term memory | ✅ | ✅ | ✅ |
| Horizontally scalable task queues and servers | ✅ | ✅ | ✅ |
+2 -8
View File
@@ -6,20 +6,14 @@ hide:
- tags
---
# MCP Endpoint
# MCP endpoint in LangGraph Server
The **Model Context Protocol (MCP)** is an open protocol for describing tools and data sources in a model-agnostic format, enabling LLMs to discover
and use them via a structured API.
[LangGraph Server](./langgraph_server.md) implements MCP using the [Streamable HTTP transport](https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/transports/#streamable-http). This allows LangGraph **agents** to be exposed as **MCP tools**, making them usable with any MCP-compliant client supporting Streamable HTTP.
The MCP endpoint is available at:
```
/mcp
```
on [LangGraph Server](./langgraph_server.md).
The MCP endpoint is available at `/mcp` on [LangGraph Server](./langgraph_server.md).
## Requirements
+1 -1
View File
@@ -18,6 +18,6 @@ There are three main categories of data you can stream:
- [**Stream LLM tokens**](../how-tos/streaming.md#messages) — capture token streams from anywhere: inside nodes, subgraphs, or tools.
- [**Emit progress notifications from tools**](../how-tos/streaming.md#stream-custom-data) — send custom updates or progress signals directly from tool functions.
- [**Stream from subgraphs**](../how-tos/streaming.md#subgraphs) — include outputs from both the parent graph and any nested subgraphs.
- [**Stream from subgraphs**](../how-tos/streaming.md#stream-subgraph-outputs) — include outputs from both the parent graph and any nested subgraphs.
- [**Use any LLM**](../how-tos/streaming.md#use-with-any-llm) — stream tokens from any LLM, even if it's not a LangChain model using the `custom` streaming mode.
- [**Use multiple streaming modes**](../how-tos/streaming.md#stream-multiple-modes) — choose from `values` (full state), `updates` (state deltas), `messages` (LLM tokens + metadata), `custom` (arbitrary user data), or `debug` (detailed traces).
+7 -4
View File
@@ -7,9 +7,12 @@ search:
When working with non-deterministic systems that make model-based decisions (e.g., agents powered by LLMs), it can be useful to examine their decision-making process in detail:
1. 🤔 **Understand Reasoning**: Analyze the steps that led to a successful result.
2. 🐞 **Debug Mistakes**: Identify where and why errors occurred.
3. 🔍 **Explore Alternatives**: Test different paths to uncover better solutions.
1. 🤔 **Understand reasoning**: Analyze the steps that led to a successful result.
2. 🐞 **Debug mistakes**: Identify where and why errors occurred.
3. 🔍 **Explore alternatives**: Test different paths to uncover better solutions.
LangGraph provides [time travel functionality](../how-tos/human_in_the_loop/time-travel.md) to support these use cases. Specifically, you can resume execution from a prior checkpoint — either replaying the same state or modifying it to explore alternatives. In all cases, resuming past execution produces a new fork in the history.
LangGraph provides **time travel** functionality to support these use cases. Specifically, you can **resume execution from a prior checkpoint** — either replaying the same state or modifying it to explore alternatives. In all cases, resuming past execution produces a **new fork** in the history.
!!! tip
For information on how to use time travel, see [Use time travel](../how-tos/human_in_the_loop/time-travel.md) and [Time travel using Server API](../cloud/how-tos/human_in_the_loop_time_travel.md).
+40 -38
View File
@@ -1,62 +1,64 @@
# Tools
Many AI applications interact directly with humans. In these cases, it is appropriate for models to respond in natural language.
But what about cases where we want a model to also interact *directly* with systems, such as databases or an API?
These systems often have a particular input schema; for example, APIs frequently have a required payload structure. You can use [tool calling](https://platform.openai.com/docs/guides/function-calling/example-use-cases) to request model responses that match a particular schema.
Many AI applications interact with users via natural language. However, some use cases require models to interface directly with external systems—such as APIs, databases, or file systems—using structured input. In these scenarios, [tool calling](../how-tos/tool-calling.md) enables models to generate requests that conform to a specified input schema.
[Tools](https://python.langchain.com/docs/concepts/tools/) are a way to encapsulate a function and its input schema in a way that can be passed to a chat model that supports tool calling. This allows the model to request the execution of this function with specific inputs.
**Tools** can be passed to [chat models](https://python.langchain.com/docs/concepts/chat_models) that support [tool calling](https://python.langchain.com/docs/concepts/tool_calling) allowing the model to request the execution of a specific function with specific inputs.
You can [create custom tools](https://python.langchain.com/docs/how_to/custom_tools/) or use [prebuilt](#prebuilt-tools) tools.
**Tools** encapsulate a callable function and its input schema. These can be passed to compatible [chat models](https://python.langchain.com/docs/concepts/chat_models), allowing the model to decide whether to invoke a tool and with what arguments.
## Tool calling
![Diagram of a tool call by a model](./img/tool_call.png)
A key principle of tool calling is that the model decides when to use a tool based on the input's relevance. The model doesn't always need to call a tool.
For example, given an input that is *irrelevant to the tool*, the model would not call the tool:
Tool calling is typically **conditional**. Based on the user input and available tools, the model may choose to issue a tool call request. This request is returned in an `AIMessage` object, which includes a `tool_calls` field that specifies the tool name and input arguments:
```python
result = llm_with_tools.invoke("Hello world!")
llm_with_tools.invoke("What is 2 multiplied by 3?")
# -> AIMessage(tool_calls=[{'name': 'multiply', 'args': {'a': 2, 'b': 3}, ...}])
```
The result would be an `AIMessage` containing the model's response in natural language (e.g., "Hello!").
However, if we pass an input *relevant to the tool*, the model should choose to call it:
If the input is unrelated to any tool, the model returns only a natural language message:
```python
result = llm_with_tools.invoke("What is 2 multiplied by 3?")
llm_with_tools.invoke("Hello world!") # -> AIMessage(content="Hello!")
```
As before, the output `result` will be an `AIMessage`.
But, if the tool was called, `result` will have a `tool_calls` attribute.
This attribute includes everything needed to execute the tool, including the tool name and input arguments:
Importantly, the model does not execute the tool—it only generates a request. A separate executor (such as a runtime or agent) is responsible for handling the tool call and returning the result.
```
result.tool_calls
{'name': 'multiply', 'args': {'a': 2, 'b': 3}, 'id': 'xxx', 'type': 'tool_call'}
```
For more details on usage, see the [how-to guide](../how-tos/tool-calling.ipynb).
## Execute tools
LangGraph offers pre-built components — [`ToolNode`][langgraph.prebuilt.tool_node.ToolNode] and [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent] — that invoke the tools on behalf of the user.
See this [how-to guide](../how-tos/tool-calling.ipynb#use-prebuilt-toolnode) on tool calling.
See the [tool calling guide](../how-tos/tool-calling.md) for more details.
## Prebuilt tools
LangChain supports a wide range of prebuilt tool integrations for interacting with APIs, databases, file systems, web data, and more. These tools extend the functionality of agents and enable rapid development.
LangChain provides prebuilt tool integrations for common external systems including APIs, databases, file systems, and web data.
You can browse the full list of available integrations in the [LangChain integrations directory](https://python.langchain.com/docs/integrations/tools/).
Browse the [integrations directory](https://python.langchain.com/docs/integrations/tools/) for available tools.
Some commonly used tool categories include:
Common categories:
- **Search**: Bing, SerpAPI, Tavily
- **Code interpreters**: Python REPL, Node.js REPL
- **Databases**: SQL, MongoDB, Redis
- **Web data**: Web scraping and browsing
- **APIs**: OpenWeatherMap, NewsAPI, and others
* **Search**: Bing, SerpAPI, Tavily
* **Code execution**: Python REPL, Node.js REPL
* **Databases**: SQL, MongoDB, Redis
* **Web data**: Scraping and browsing
* **APIs**: OpenWeatherMap, NewsAPI, etc.
These integrations can be configured and added to your agents using the same `tools` parameter shown in the examples above.
## Custom tools
You can define custom tools using the `@tool` decorator or plain Python functions. For example:
```python
from langchain_core.tools import tool
@tool
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
```
See the [tool calling guide](../how-tos/tool-calling.md) for more details.
## Tool execution
While the model determines when to call a tool, execution of the tool call must be handled by a runtime component.
LangGraph provides prebuilt components for this:
* [`ToolNode`][langgraph.prebuilt.tool_node.ToolNode]: A prebuilt node that executes tools.
* [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent]: Constructs a full agent that manages tool calling automatically.
+2 -2
View File
@@ -34,7 +34,7 @@ def read_root():
## Configure `langgraph.json`
Add the following to your `langgraph.json` configuration file. Make sure the path points to the `app.py` file you created above.
Add the following to your `langgraph.json` configuration file. Make sure the path points to the FastAPI application instance `app` in the `webapp.py` file you created above.
```json
{
@@ -71,4 +71,4 @@ You can deploy this app as-is to LangGraph Platform or to your self-hosted platf
## Next steps
Now that you've added a custom route to your deployment, you can use this same technique to further customize how your server behaves, such as defining custom [custom middleware](./custom_middleware.md) and [custom lifespan events](./custom_lifespan.md).
Now that you've added a custom route to your deployment, you can use this same technique to further customize how your server behaves, such as defining custom [custom middleware](./custom_middleware.md) and [custom lifespan events](./custom_lifespan.md).
@@ -9,13 +9,20 @@ hide:
- tags
---
# Add human-in-the-loop
# Enable human intervention
## `interrupt`
To review, edit, and approve tool calls in an agent or workflow, use LangGraph's [human-in-the-loop](../../concepts/human_in_the_loop.md) features.
## Pause using `interrupt`
The [`interrupt` function][langgraph.types.interrupt] in LangGraph enables human-in-the-loop workflows by pausing the graph at a specific node, presenting information to a human, and resuming the graph with their input. It's useful for tasks like approvals, edits, or gathering additional context.
The graph is resumed using a [`Command`][langgraph.types.Command] object that provides the human's response.
To use `interrupt` in your graph, you need to:
1. [**Specify a checkpointer**](../../concepts/persistence.md#checkpoints) to save the graph state after each step.
2. **Call `interrupt()`** in the appropriate place. See the [Common Patterns](#common-patterns) section for examples.
3. **Run the graph** with a [**thread ID**](../../concepts/persistence.md#threads) until the `interrupt` is hit.
4. **Resume execution** using `invoke`/`ainvoke`/`stream`/`astream` (see [**The `Command` primitive**](#resume-using-the-command-primitive)).
```python
# highlight-next-line
@@ -125,34 +132,48 @@ print(graph.invoke(Command(resume="Edited text"), config=config)) # (7)!
7. The graph is resumed with a `Command(resume=...)`, injecting the human's input and continuing execution.
!!! tip "New in 0.4.0"
`__interrupt__` is a special key that will be returned when running the graph if the graph is interrupted. Support for `__interrupt__` in `invoke` and `ainvoke` has been added in version 0.4.0. If you're on an older version, you will only see `__interrupt__` in the result if you use `stream` or `astream`. You can also use `graph.get_state(thread_id)` to get the interrupt value.
!!! warning
Interrupts are both powerful and ergonomic. However, while they may resemble Python's input() function in terms of developer experience, it's important to note that they do not automatically resume execution from the interruption point. Instead, they rerun the entire node where the interrupt was used.
For this reason, interrupts are typically best placed at the start of a node or in a dedicated node. Please read the [resuming from an interrupt](#how-does-resuming-from-an-interrupt-work) section for more details.
Interrupts are both powerful and ergonomic. However, while they may resemble Python's input() function in terms of developer experience, it's important to note that they do not automatically resume execution from the interruption point. Instead, they rerun the entire node where the interrupt was used. For this reason, interrupts are typically best placed at the start of a node or in a dedicated node.
## Requirements
To use `interrupt` in your graph, you need to:
## Resume using the `Command` primitive
1. [**Specify a checkpointer**](../../concepts/persistence.md#checkpoints) to save the graph state after each step.
2. **Call `interrupt()`** in the appropriate place. See the [Design Patterns](#design-patterns) section for examples.
3. **Run the graph** with a [**thread ID**](../../concepts/persistence.md#threads) until the `interrupt` is hit.
4. **Resume execution** using `invoke`/`ainvoke`/`stream`/`astream` (see [**The `Command` primitive**](#resume-using-the-command-primitive)).
!!! warning
## Design patterns
Resuming from an `interrupt` is different from Python's `input()` function, where execution resumes from the exact point where the `input()` function was called.
There are typically three different **actions** that you can do with a human-in-the-loop workflow:
When the `interrupt` function is used within a graph, execution pauses at that point and awaits user input.
1. **Approve or Reject**: Pause the graph before a critical step, such as an API call, to review and approve the action. If the action is rejected, you can prevent the graph from executing the step, and potentially take an alternative action. This pattern often involve **routing** the graph based on the human's input.
2. **Edit Graph State**: Pause the graph to review and edit the graph state. This is useful for correcting mistakes or updating the state with additional information. This pattern often involves **updating** the state with the human's input.
3. **Get Input**: Explicitly request human input at a particular step in the graph. This is useful for collecting additional information or context to inform the agent's decision-making process.
To resume execution, use the [`Command`][langgraph.types.Command] primitive, which can be supplied via the `invoke`, `ainvoke`, `stream`, or `astream` methods. The graph resumes execution from the beginning of the node where `interrupt(...)` was initially called. This time, the `interrupt` function will return the value provided in `Command(resume=value)` rather than pausing again. All code from the beginning of the node to the `interrupt` will be re-executed.
Below we show different design patterns that can be implemented using these **actions**.
```python
# Resume graph execution by providing the user's input.
graph.invoke(Command(resume={"age": "25"}), thread_config)
```
### Resume multiple interrupts with one invocation
If you have multiple interrupts in the task queue, you can use `Command.resume` with a dictionary mapping of interrupt ids to resume with a single `invoke` / `stream` call.
For example, once your graph has been interrupted (multiple times, theoretically) and is stalled:
```python
resume_map = {
i.interrupt_id: f"human input for prompt {i.value}"
for i in parent.get_state(thread_config).interrupts
}
parent_graph.invoke(Command(resume=resume_map), config=thread_config)
```
## Common patterns
Below we show different design patterns that can be implemented using `interrupt` and `Command`.
### Approve or reject
@@ -263,9 +284,7 @@ graph.invoke(Command(resume=True), config=thread_config)
print(final_result)
```
See [how to review tool calls](./review-tool-calls.ipynb) for a more detailed example.
### Review & edit state
### Review and edit state
<figure markdown="1">
![image](../../concepts/img/human_in_the_loop/edit-graph-state-simple.png){: style="max-height:400px"}
@@ -393,41 +412,209 @@ critical in applications where the tool calls requested by the LLM may be sensit
</figcaption>
</figure>
To add a human approval step to a tool:
1. Use `interrupt()` in the tool to pause execution.
2. Resume with a `Command(resume=...)` to continue based on human input.
```python
def human_review_node(state) -> Command[Literal["call_llm", "run_tool"]]:
# This is the value we'll be providing via Command(resume=<human_review>)
human_review = interrupt(
{
"question": "Is this correct?",
# Surface tool calls for review
"tool_call": tool_call
}
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import interrupt
from langgraph.prebuilt import create_react_agent
# An example of a sensitive tool that requires human review / approval
def book_hotel(hotel_name: str):
"""Book a hotel"""
# highlight-next-line
response = interrupt( # (1)!
f"Trying to call `book_hotel` with args {{'hotel_name': {hotel_name}}}. "
"Please approve or suggest edits."
)
if response["type"] == "accept":
pass
elif response["type"] == "edit":
hotel_name = response["args"]["hotel_name"]
else:
raise ValueError(f"Unknown response type: {response['type']}")
return f"Successfully booked a stay at {hotel_name}."
review_action, review_data = human_review
# highlight-next-line
checkpointer = InMemorySaver() # (2)!
# Approve the tool call and continue
if review_action == "continue":
return Command(goto="run_tool")
# Modify the tool call manually and then continue
elif review_action == "update":
...
updated_msg = get_updated_msg(review_data)
# Remember that to modify an existing message you will need
# to pass the message with a matching ID.
return Command(goto="run_tool", update={"messages": [updated_message]})
# Give natural language feedback, and then pass that back to the agent
elif review_action == "feedback":
...
feedback_msg = get_feedback_msg(review_data)
return Command(goto="call_llm", update={"messages": [feedback_msg]})
agent = create_react_agent(
model="anthropic:claude-3-5-sonnet-latest",
tools=[book_hotel],
# highlight-next-line
checkpointer=checkpointer, # (3)!
)
```
See [how to review tool calls](./review-tool-calls.ipynb) for a more detailed example.
1. The [`interrupt` function][langgraph.types.interrupt] pauses the agent graph at a specific node. In this case, we call `interrupt()` at the beginning of the tool function, which pauses the graph at the node that executes the tool. The information inside `interrupt()` (e.g., tool calls) can be presented to a human, and the graph can be resumed with the user input (tool call approval, edit or feedback).
2. The `InMemorySaver` is used to store the agent state at every step in the tool calling loop. This enables [short-term memory](../memory/add-memory.md#add-short-term-memory) and [human-in-the-loop](../../concepts/human_in_the_loop.md) capabilities. In this example, we use `InMemorySaver` to store the agent state in memory. In a production application, the agent state will be stored in a database.
3. Initialize the agent with the `checkpointer`.
### Validating human input
Run the agent with the `stream()` method, passing the `config` object to specify the thread ID. This allows the agent to resume the same conversation on future invocations.
```python
config = {
"configurable": {
# highlight-next-line
"thread_id": "1"
}
}
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "book a stay at McKittrick hotel"}]},
# highlight-next-line
config
):
print(chunk)
print("\n")
```
> You should see that the agent runs until it reaches the `interrupt()` call, at which point it pauses and waits for human input.
Resume the agent with a `Command(resume=...)` to continue based on human input.
```python
from langgraph.types import Command
for chunk in agent.stream(
# highlight-next-line
Command(resume={"type": "accept"}), # (1)!
# Command(resume={"type": "edit", "args": {"hotel_name": "McKittrick Hotel"}}),
config
):
print(chunk)
print("\n")
```
1. The [`interrupt` function][langgraph.types.interrupt] is used in conjunction with the [`Command`][langgraph.types.Command] object to resume the graph with a value provided by the human.
### Add interrupts to any tool
You can create a wrapper to add interrupts to *any* tool. The example below provides a reference implementation compatible with [Agent Inbox UI](https://github.com/langchain-ai/agent-inbox) and [Agent Chat UI](https://github.com/langchain-ai/agent-chat-ui).
```python title="Wrapper that adds human-in-the-loop to any tool"
from typing import Callable
from langchain_core.tools import BaseTool, tool as create_tool
from langchain_core.runnables import RunnableConfig
from langgraph.types import interrupt
from langgraph.prebuilt.interrupt import HumanInterruptConfig, HumanInterrupt
def add_human_in_the_loop(
tool: Callable | BaseTool,
*,
interrupt_config: HumanInterruptConfig = None,
) -> BaseTool:
"""Wrap a tool to support human-in-the-loop review."""
if not isinstance(tool, BaseTool):
tool = create_tool(tool)
if interrupt_config is None:
interrupt_config = {
"allow_accept": True,
"allow_edit": True,
"allow_respond": True,
}
@create_tool( # (1)!
tool.name,
description=tool.description,
args_schema=tool.args_schema
)
def call_tool_with_interrupt(config: RunnableConfig, **tool_input):
request: HumanInterrupt = {
"action_request": {
"action": tool.name,
"args": tool_input
},
"config": interrupt_config,
"description": "Please review the tool call"
}
# highlight-next-line
response = interrupt([request])[0] # (2)!
# approve the tool call
if response["type"] == "accept":
tool_response = tool.invoke(tool_input, config)
# update tool call args
elif response["type"] == "edit":
tool_input = response["args"]["args"]
tool_response = tool.invoke(tool_input, config)
# respond to the LLM with user feedback
elif response["type"] == "response":
user_feedback = response["args"]
tool_response = user_feedback
else:
raise ValueError(f"Unsupported interrupt response type: {response['type']}")
return tool_response
return call_tool_with_interrupt
```
1. This wrapper creates a new tool that calls `interrupt()` **before** executing the wrapped tool.
2. `interrupt()` is using special input and output format that's expected by [Agent Inbox UI](https://github.com/langchain-ai/agent-inbox):
- a list of [`HumanInterrupt`][langgraph.prebuilt.interrupt.HumanInterrupt] objects is sent to `AgentInbox` render interrupt information to the end user
- resume value is provided by `AgentInbox` as a list (i.e., `Command(resume=[...])`)
You can use the `add_human_in_the_loop` wrapper to add `interrupt()` to any tool without having to add it *inside* the tool:
```python
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.prebuilt import create_react_agent
# highlight-next-line
checkpointer = InMemorySaver()
def book_hotel(hotel_name: str):
"""Book a hotel"""
return f"Successfully booked a stay at {hotel_name}."
agent = create_react_agent(
model="anthropic:claude-3-5-sonnet-latest",
tools=[
# highlight-next-line
add_human_in_the_loop(book_hotel), # (1)!
],
# highlight-next-line
checkpointer=checkpointer,
)
config = {"configurable": {"thread_id": "1"}}
# Run the agent
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "book a stay at McKittrick hotel"}]},
# highlight-next-line
config
):
print(chunk)
print("\n")
```
1. The `add_human_in_the_loop` wrapper is used to add `interrupt()` to the tool. This allows the agent to pause execution and wait for human input before proceeding with the tool call.
> You should see that the agent runs until it reaches the `interrupt()` call,
> at which point it pauses and waits for human input.
Resume the agent with a `Command(resume=...)` to continue based on human input.
```python
from langgraph.types import Command
for chunk in agent.stream(
# highlight-next-line
Command(resume=[{"type": "accept"}]),
# Command(resume=[{"type": "edit", "args": {"args": {"hotel_name": "McKittrick Hotel"}}}]),
config
):
print(chunk)
print("\n")
```
### Validate human input
If you need to validate the input provided by the human within the graph itself (rather than on the client side), you can achieve this by using multiple interrupt calls within a single node.
@@ -525,91 +712,15 @@ def human_node(state: State):
print(final_result) # Should include the valid age
```
## Considerations
## Resume using the `Command` primitive
When using human-in-the-loop, there are some considerations to keep in mind.
When the `interrupt` function is used within a graph, execution pauses at that point and awaits user input.
### Using with code with side-effects
To resume execution, use the [`Command`][langgraph.types.Command] primitive, which can be supplied via the `invoke`, `ainvoke`, `stream`, or `astream` methods.
Place code with side effects, such as API calls, after the `interrupt` or in a separate node to avoid duplication, as these are re-triggered every time the node is resumed.
**Providing a response to the `interrupt`:**
To continue execution, pass the user's input using `Command(resume=value)`. The graph resumes execution from the beginning of the node where `interrupt(...)` was initially called. This time, the `interrupt` function will return the value provided in `Command(resume=value)` rather than pausing again.
```python
# Resume graph execution by providing the user's input.
graph.invoke(Command(resume={"age": "25"}), thread_config)
```
## How does resuming from an interrupt work?
!!! warning
Resuming from an `interrupt` is **different** from Python's `input()` function, where execution resumes from the exact point where the `input()` function was called.
A critical aspect of using `interrupt` is understanding how resuming works. When you resume execution after an `interrupt`, graph execution starts from the **beginning** of the **graph node** where the last `interrupt` was triggered.
**All** code from the beginning of the node to the `interrupt` will be re-executed.
```python
counter = 0
def node(state: State):
# All the code from the beginning of the node to the interrupt will be re-executed
# when the graph resumes.
global counter
counter += 1
print(f"> Entered the node: {counter} # of times")
# Pause the graph and wait for user input.
answer = interrupt()
print("The value of counter is:", counter)
...
```
Upon **resuming** the graph, the counter will be incremented a second time, resulting in the following output:
```pycon
> Entered the node: 2 # of times
The value of counter is: 2
```
### Resuming multiple interrupts with one invocation
If you have multiple interrupts in the task queue, you can use `Command.resume` with a dictionary mapping
of interrupt ids to resume values to resume multiple interrupts with a single `invoke` / `stream` call.
For example, once your graph has been interrupted (multiple times, theoretically) and is stalled:
```python
resume_map = {
i.interrupt_id: f"human input for prompt {i.value}"
for i in parent.get_state(thread_config).interrupts
}
parent_graph.invoke(Command(resume=resume_map), config=thread_config)
```
## Common pitfalls
### Side-effects
Place code with side effects, such as API calls, **after** the `interrupt` to avoid duplication, as these are re-triggered every time the node is resumed.
=== "Side effects before interrupt (BAD)"
This code will re-execute the API call another time when the node is resumed from
the `interrupt`.
This can be problematic if the API call is not idempotent or is just expensive.
```python
from langgraph.types import interrupt
def human_node(state: State):
"""Human node with validation."""
api_call(...) # This code will be re-executed when the node is resumed.
answer = interrupt(question)
```
=== "Side effects after interrupt (OK)"
=== "Side effects after interrupt"
```python
from langgraph.types import interrupt
@@ -622,7 +733,7 @@ Place code with side effects, such as API calls, **after** the `interrupt` to av
api_call(answer) # OK as it's after the interrupt
```
=== "Side effects in a separate node (OK)"
=== "Side effects in a separate node"
```python
from langgraph.types import interrupt
@@ -640,11 +751,9 @@ Place code with side effects, such as API calls, **after** the `interrupt` to av
api_call(...) # OK as it's in a separate node
```
### Subgraphs called as functions
### Using with subgraphs called as functions
When invoking a subgraph [as a function](../../how-tos/subgraph.ipynb#different-state-schemas), the **parent graph** will resume execution from the **beginning of the node** where the subgraph was invoked (and where an `interrupt` was triggered). Similarly, the **subgraph**, will resume from the **beginning of the node** where the `interrupt()` function was called.
For example,
When invoking a subgraph as a function, the parent graph will resume execution from the **beginning of the node** where the subgraph was invoked where the `interrupt` was triggered. Similarly, the **subgraph** will resume from the **beginning of the node** where the `interrupt()` function was called.
```python
def node_in_parent_graph(state: State):
@@ -772,11 +881,9 @@ def node_in_parent_graph(state: State):
{'parent_node': {'state_counter': 1}}
```
### Using multiple interrupts
Using multiple interrupts within a **single** node can be helpful for patterns like [validating human input](#validating-human-input). However, using multiple interrupts in the same node can lead to unexpected behavior if not handled carefully.
Using multiple interrupts within a **single** node can be helpful for patterns like [validating human input](#validate-human-input). However, using multiple interrupts in the same node can lead to unexpected behavior if not handled carefully.
When a node contains multiple interrupt calls, LangGraph keeps a list of resume values specific to the task executing the node. Whenever execution resumes, it starts at the beginning of the node. For each interrupt encountered, LangGraph checks if a matching value exists in the task's resume list. Matching is **strictly index-based**, so the order of interrupt calls within the node is critical.
@@ -845,4 +952,5 @@ To avoid issues, refrain from dynamically changing the node's structure between
{'__interrupt__': (Interrupt(value='what is your name?', resumable=True, ns=['human_node:3a007ef9-c30d-c357-1ec1-86a1a70d8fba'], when='during'),)}
Name: N/A. Age: John
{'human_node': {'age': 'John', 'name': 'N/A'}}
```
```
File diff suppressed because one or more lines are too long
@@ -0,0 +1,342 @@
# Set breakpoints
There are two places where you can set breakpoints:
1. **Before** or **after** a node executes by setting breakpoints at **compile time** or **run time**. We call these [**static breakpoints**](#static-breakpoints).
2. **Inside** a node using the `NodeInterrupt` exception. We call these [**dynamic breakpoints**](#dynamic-breakpoints).
To use breakpoints, you will need to:
1. [**Specify a checkpointer**](../../concepts/persistence.md#checkpoints) to save the graph state after each step.
2. **Set breakpoints** to specify where execution should pause.
3. **Run the graph** with a [**thread ID**](../../concepts/persistence.md#threads) to pause execution at the breakpoint.
4. **Resume execution** using `invoke`/`ainvoke`/`stream`/`astream` passing a `None` as the argument for the inputs.
!!! tip
For a conceptual overview of breakpoints, see [Breakpoints](../../concepts/breakpoints.md).
## Static breakpoints
Static breakpoints are triggered either before or after a node executes. You can set static breakpoints by specifying `interrupt_before` and `interrupt_after` at compile time or run time.
Static breakpoints can be especially useful for debugging if you want to step through the graph execution one
node at a time or if you want to pause the graph execution at specific nodes.
=== "Compile time"
```python
# highlight-next-line
graph = graph_builder.compile( # (1)!
# highlight-next-line
interrupt_before=["node_a"], # (2)!
# highlight-next-line
interrupt_after=["node_b", "node_c"], # (3)!
checkpointer=checkpointer, # (4)!
)
config = {
"configurable": {
"thread_id": "some_thread"
}
}
# Run the graph until the breakpoint
graph.invoke(inputs, config=thread_config) # (5)!
# Resume the graph
graph.invoke(None, config=thread_config) # (6)!
```
1. The breakpoints are set during `compile` time.
2. `interrupt_before` specifies the nodes where execution should pause before the node is executed.
3. `interrupt_after` specifies the nodes where execution should pause after the node is executed.
4. A checkpointer is required to enable breakpoints.
5. The graph is run until the first breakpoint is hit.
6. The graph is resumed by passing in `None` for the input. This will run the graph until the next breakpoint is hit.
=== "Run time"
```python
# highlight-next-line
graph.invoke( # (1)!
inputs,
# highlight-next-line
interrupt_before=["node_a"], # (2)!
# highlight-next-line
interrupt_after=["node_b", "node_c"] # (3)!
config={
"configurable": {"thread_id": "some_thread"}
},
)
config = {
"configurable": {
"thread_id": "some_thread"
}
}
# Run the graph until the breakpoint
graph.invoke(inputs, config=config) # (4)!
# Resume the graph
graph.invoke(None, config=config) # (5)!
```
1. `graph.invoke` is called with the `interrupt_before` and `interrupt_after` parameters. This is a run-time configuration and can be changed for every invocation.
2. `interrupt_before` specifies the nodes where execution should pause before the node is executed.
3. `interrupt_after` specifies the nodes where execution should pause after the node is executed.
4. The graph is run until the first breakpoint is hit.
5. The graph is resumed by passing in `None` for the input. This will run the graph until the next breakpoint is hit.
!!! note
You cannot set static breakpoints at runtime for **sub-graphs**.
If you have a sub-graph, you must set the breakpoints at compilation time.
??? example "Setting static breakpoints"
```python
from IPython.display import Image, display
from typing_extensions import TypedDict
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
input: str
def step_1(state):
print("---Step 1---")
pass
def step_2(state):
print("---Step 2---")
pass
def step_3(state):
print("---Step 3---")
pass
builder = StateGraph(State)
builder.add_node("step_1", step_1)
builder.add_node("step_2", step_2)
builder.add_node("step_3", step_3)
builder.add_edge(START, "step_1")
builder.add_edge("step_1", "step_2")
builder.add_edge("step_2", "step_3")
builder.add_edge("step_3", END)
# Set up a checkpointer
checkpointer = InMemorySaver() # (1)!
graph = builder.compile(
checkpointer=checkpointer, # (2)!
interrupt_before=["step_3"] # (3)!
)
# View
display(Image(graph.get_graph().draw_mermaid_png()))
# Input
initial_input = {"input": "hello world"}
# Thread
thread = {"configurable": {"thread_id": "1"}}
# Run the graph until the first interruption
for event in graph.stream(initial_input, thread, stream_mode="values"):
print(event)
# This will run until the breakpoint
# You can get the state of the graph at this point
print(graph.get_state(config))
# You can continue the graph execution by passing in `None` for the input
for event in graph.stream(None, thread, stream_mode="values"):
print(event)
```
## Dynamic breakpoints
Use dynamic breakpoints if you need to interrupt the graph from inside a given node based on a condition.
```python
from langgraph.errors import NodeInterrupt
def step_2(state: State) -> State:
# highlight-next-line
if len(state["input"]) > 5:
# highlight-next-line
raise NodeInterrupt( # (1)!
f"Received input that is longer than 5 characters: {state['foo']}"
)
return state
```
1. raise NodeInterrupt exception based on a some condition. In this example, we create a dynamic breakpoint if the length of the attribute `input` is longer than 5 characters.
<details class="example"><summary>Using dynamic breakpoints</summary>
```python
from typing_extensions import TypedDict
from IPython.display import Image, display
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver
from langgraph.errors import NodeInterrupt
class State(TypedDict):
input: str
def step_1(state: State) -> State:
print("---Step 1---")
return state
def step_2(state: State) -> State:
# Let's optionally raise a NodeInterrupt
# if the length of the input is longer than 5 characters
if len(state["input"]) > 5:
raise NodeInterrupt(
f"Received input that is longer than 5 characters: {state['input']}"
)
print("---Step 2---")
return state
def step_3(state: State) -> State:
print("---Step 3---")
return state
builder = StateGraph(State)
builder.add_node("step_1", step_1)
builder.add_node("step_2", step_2)
builder.add_node("step_3", step_3)
builder.add_edge(START, "step_1")
builder.add_edge("step_1", "step_2")
builder.add_edge("step_2", "step_3")
builder.add_edge("step_3", END)
# Set up memory
memory = MemorySaver()
# Compile the graph with memory
graph = builder.compile(checkpointer=memory)
# View
display(Image(graph.get_graph().draw_mermaid_png()))
```
First, let's run the graph with an input that <= 5 characters long. This should safely ignore the interrupt condition we defined and return the original input at the end of the graph execution.
```python
initial_input = {"input": "hello"}
thread_config = {"configurable": {"thread_id": "1"}}
for event in graph.stream(initial_input, thread_config, stream_mode="values"):
print(event)
```
If we inspect the graph at this point, we can see that there are no more tasks left to run and that the graph indeed finished execution.
```python
state = graph.get_state(thread_config)
print(state.next)
print(state.tasks)
```
Now, let's run the graph with an input that's longer than 5 characters. This should trigger the dynamic interrupt we defined via raising a `NodeInterrupt` error inside the `step_2` node.
```python
initial_input = {"input": "hello world"}
thread_config = {"configurable": {"thread_id": "2"}}
# Run the graph until the first interruption
for event in graph.stream(initial_input, thread_config, stream_mode="values"):
print(event)
```
We can see that the graph now stopped while executing `step_2`. If we inspect the graph state at this point, we can see the information on what node is set to execute next (`step_2`), as well as what node raised the interrupt (also `step_2`), and additional information about the interrupt.
```python
state = graph.get_state(thread_config)
print(state.next)
print(state.tasks)
```
If we try to resume the graph from the breakpoint, we will simply interrupt again as our inputs & graph state haven't changed.
```python
# NOTE: to resume the graph from a dynamic interrupt we use the same syntax as with regular interrupts -- we pass None as the input
for event in graph.stream(None, thread_config, stream_mode="values"):
print(event)
```
```python
state = graph.get_state(thread_config)
print(state.next)
print(state.tasks)
```
</details>
## Use with subgraphs
To add breakpoints to subgraph either:
* Define [static breakpoints](#static-breakpoints) by specifying them when **compiling** the subgraph.
* Define [dynamic breakpoints](#dynamic-breakpoints).
<details class="example"><summary>Add breakpoints to subgraphs</summary>
```python
from typing_extensions import TypedDict
from langgraph.graph import START, StateGraph
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import interrupt
class State(TypedDict):
foo: str
def subgraph_node_1(state: State):
return {"foo": state["foo"]}
subgraph_builder = StateGraph(State)
subgraph_builder.add_node(subgraph_node_1)
subgraph_builder.add_edge(START, "subgraph_node_1")
subgraph = subgraph_builder.compile(interrupt_before=["subgraph_node_1"])
builder = StateGraph(State)
builder.add_node("node_1", subgraph) # directly include subgraph as a node
builder.add_edge(START, "node_1")
checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "1"}}
graph.invoke({"foo": ""}, config)
# Fetch state including subgraph state.
print(graph.get_state(config, subgraphs=True).tasks[0].state)
# resume the subgraph
graph.invoke(None, config)
```
</details>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,190 @@
# Use time-travel
To use [time-travel](../../concepts/time-travel.md) in LangGraph:
1. [Run the graph](#1-run-the-graph) with initial inputs using [`invoke`][langgraph.graph.state.CompiledStateGraph.invoke] or [`stream`][langgraph.graph.state.CompiledStateGraph.stream] methods.
2. [Identify a checkpoint in an existing thread](#2-identify-a-checkpoint): Use the [`get_state_history()`][langgraph.graph.state.CompiledStateGraph.get_state_history] method to retrieve the execution history for a specific `thread_id` and locate the desired `checkpoint_id`.
Alternatively, set a [breakpoint](../../concepts/breakpoints.md) before the node(s) where you want execution to pause. You can then find the most recent checkpoint recorded up to that breakpoint.
3. [Update the graph state (optional)](#3-update-the-state-optional): Use the [`update_state`][langgraph.graph.state.CompiledStateGraph.update_state] method to modify the graph's state at the checkpoint and resume execution from alternative state.
4. [Resume execution from the checkpoint](#4-resume-execution-from-the-checkpoint): Use the `invoke` or `stream` methods with an input of `None` and a configuration containing the appropriate `thread_id` and `checkpoint_id`.
!!! tip
For a conceptual overview of time-travel, see [Time travel](../../concepts/time-travel.md).
## In a workflow
This example builds a simple LangGraph workflow that generates a joke topic and writes a joke using an LLM. It demonstrates how to run the graph, retrieve past execution checkpoints, optionally modify the state, and resume execution from a chosen checkpoint to explore alternate outcomes.
### Setup
First we need to install the packages required
```python
%%capture --no-stderr
%pip install --quiet -U langgraph langchain_anthropic
```
Next, we need to set API keys for Anthropic (the LLM we will use)
```python
import getpass
import os
def _set_env(var: str):
if not os.environ.get(var):
os.environ[var] = getpass.getpass(f"{var}: ")
_set_env("ANTHROPIC_API_KEY")
```
<div class="admonition tip">
<p class="admonition-title">Set up <a href="https://smith.langchain.com">LangSmith</a> for LangGraph development</p>
<p style="padding-top: 5px;">
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>.
</p>
</div>
```python
import uuid
from typing_extensions import TypedDict, NotRequired
from langgraph.graph import StateGraph, START, END
from langchain.chat_models import init_chat_model
from langgraph.checkpoint.memory import InMemorySaver
class State(TypedDict):
topic: NotRequired[str]
joke: NotRequired[str]
llm = init_chat_model(
"anthropic:claude-3-7-sonnet-latest",
temperature=0,
)
def generate_topic(state: State):
"""LLM call to generate a topic for the joke"""
msg = llm.invoke("Give me a funny topic for a joke")
return {"topic": msg.content}
def write_joke(state: State):
"""LLM call to write a joke based on the topic"""
msg = llm.invoke(f"Write a short joke about {state['topic']}")
return {"joke": msg.content}
# Build workflow
workflow = StateGraph(State)
# Add nodes
workflow.add_node("generate_topic", generate_topic)
workflow.add_node("write_joke", write_joke)
# Add edges to connect nodes
workflow.add_edge(START, "generate_topic")
workflow.add_edge("generate_topic", "write_joke")
workflow.add_edge("write_joke", END)
# Compile
checkpointer = InMemorySaver()
graph = workflow.compile(checkpointer=checkpointer)
graph
```
### 1. Run the graph
```python
config = {
"configurable": {
"thread_id": uuid.uuid4(),
}
}
state = graph.invoke({}, config)
print(state["topic"])
print()
print(state["joke"])
```
**Output:**
```
How about "The Secret Life of Socks in the Dryer"? You know, exploring the mysterious phenomenon of how socks go into the laundry as pairs but come out as singles. Where do they go? Are they starting new lives elsewhere? Is there a sock paradise we don't know about? There's a lot of comedic potential in the everyday mystery that unites us all!
# The Secret Life of Socks in the Dryer
I finally discovered where all my missing socks go after the dryer. Turns out they're not missing at all—they've just eloped with someone else's socks from the laundromat to start new lives together.
My blue argyle is now living in Bermuda with a red polka dot, posting vacation photos on Sockstagram and sending me lint as alimony.
```
### 2. Identify a checkpoint
```python
# The states are returned in reverse chronological order.
states = list(graph.get_state_history(config))
for state in states:
print(state.next)
print(state.config["configurable"]["checkpoint_id"])
print()
```
**Output:**
```
()
1f02ac4a-ec9f-6524-8002-8f7b0bbeed0e
('write_joke',)
1f02ac4a-ce2a-6494-8001-cb2e2d651227
('generate_topic',)
1f02ac4a-a4e0-630d-8000-b73c254ba748
('__start__',)
1f02ac4a-a4dd-665e-bfff-e6c8c44315d9
```
```python
# This is the state before last (states are listed in chronological order)
selected_state = states[1]
print(selected_state.next)
print(selected_state.values)
```
**Output:**
```
('write_joke',)
{'topic': 'How about "The Secret Life of Socks in the Dryer"? You know, exploring the mysterious phenomenon of how socks go into the laundry as pairs but come out as singles. Where do they go? Are they starting new lives elsewhere? Is there a sock paradise we don\\'t know about? There\\'s a lot of comedic potential in the everyday mystery that unites us all!'}
```
### 3. Update the state (optional)
`update_state` will create a new checkpoint. The new checkpoint will be associated with the same thread, but a new checkpoint ID.
```python
new_config = graph.update_state(selected_state.config, values={"topic": "chickens"})
print(new_config)
```
**Output:**
```
{'configurable': {'thread_id': 'c62e2e03-c27b-4cb6-8cea-ea9bfedae006', 'checkpoint_ns': '', 'checkpoint_id': '1f02ac4a-ecee-600b-8002-a1d21df32e4c'}}
```
### 4. Resume execution from the checkpoint
```python
graph.invoke(None, new_config)
```
**Output:**
```python
{'topic': 'chickens',
'joke': 'Why did the chicken join a band?\n\nBecause it had excellent drumsticks!'}
```
-415
View File
@@ -1,415 +0,0 @@
{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"id": "15ed7413-c876-4d38-b080-79c71133549b",
"metadata": {},
"source": [
"# Manage memory\n",
"\n",
"Many AI applications need memory to share context across multiple interactions. LangGraph supports two types of memory essential for building conversational agents:\n",
"\n",
"- [Short-term memory](#add-short-term-memory): Tracks the ongoing conversation by maintaining message history within a session.\n",
"- [Long-term memory](#add-long-term-memory): Stores user-specific or application-level data across sessions.\n",
"\n",
"With [short-term memory](#add-short-term-memory) enabled, long conversations can exceed the LLM's context window. Common solutions are:\n",
"\n",
"* [Trimming](#trim-messages): Remove first or last N messages (before calling LLM)\n",
"* [Summarization](#summarize-messages): Summarize earlier messages in the history and replace them with a summary\n",
"* [Delete messages](#delete-messages) from LangGraph state permanently\n",
"* custom strategies (e.g., message filtering, etc.)\n",
"\n",
"This allows the agent to keep track of the conversation without exceeding the LLM's context window."
]
},
{
"cell_type": "markdown",
"id": "db38b03c-5609-49e3-9b93-bad0aab47ffb",
"metadata": {},
"source": [
"## Add short-term memory\n",
"\n",
"Short-term memory enables agents to track multi-turn conversations:\n",
"\n",
"```python\n",
"# highlight-next-line\n",
"from langgraph.checkpoint.memory import InMemorySaver\n",
"from langgraph.graph import StateGraph\n",
"\n",
"# highlight-next-line\n",
"checkpointer = InMemorySaver()\n",
"\n",
"builder = StateGraph(...)\n",
"# highlight-next-line\n",
"graph = builder.compile(checkpointer=checkpointer)\n",
"\n",
"graph.invoke(\n",
" {\"messages\": [{\"role\": \"user\", \"content\": \"hi! i am Bob\"}]},\n",
" # highlight-next-line\n",
" {\"configurable\": {\"thread_id\": \"1\"}},\n",
")\n",
"```\n",
"\n",
"See the [persistence](../persistence#add-short-term-memory) guide to learn more about working with short-term memory."
]
},
{
"cell_type": "markdown",
"id": "05bf55fd-b0b0-4fbf-9f15-aefac7f300bb",
"metadata": {},
"source": [
"## Add long-term memory\n",
"\n",
"Use long-term memory to store user-specific or application-specific data across conversations. This is useful for applications like chatbots, where you want to remember user preferences or other information.\n",
"\n",
"```python\n",
"# highlight-next-line\n",
"from langgraph.store.memory import InMemoryStore\n",
"from langgraph.graph import StateGraph\n",
"\n",
"# highlight-next-line\n",
"store = InMemoryStore()\n",
"\n",
"builder = StateGraph(...)\n",
"# highlight-next-line\n",
"graph = builder.compile(store=store)\n",
"```\n",
"\n",
"See the [persistence](../persistence#add-long-term-memory) guide to learn more about working with long-term memory."
]
},
{
"cell_type": "markdown",
"id": "e109c1b2-a44e-4ec0-8a11-1377e59315c6",
"metadata": {},
"source": [
"## Trim messages\n",
"\n",
"To trim message history, you can use [`trim_messages`](https://python.langchain.com/api_reference/core/messages/langchain_core.messages.utils.trim_messages.html) function:\n",
"\n",
"```python\n",
"# highlight-next-line\n",
"from langchain_core.messages.utils import (\n",
" # highlight-next-line\n",
" trim_messages,\n",
" # highlight-next-line\n",
" count_tokens_approximately\n",
"# highlight-next-line\n",
")\n",
"\n",
"def call_model(state: MessagesState):\n",
" # highlight-next-line\n",
" messages = trim_messages(\n",
" state[\"messages\"],\n",
" strategy=\"last\",\n",
" token_counter=count_tokens_approximately,\n",
" max_tokens=128,\n",
" start_on=\"human\",\n",
" end_on=(\"human\", \"tool\"),\n",
" )\n",
" response = model.invoke(messages)\n",
" return {\"messages\": [response]}\n",
"\n",
"builder = StateGraph(MessagesState)\n",
"builder.add_node(call_model)\n",
"...\n",
"```\n",
"\n",
"??? example \"Full example: trim messages\"\n",
"\n",
" ```python\n",
" # highlight-next-line\n",
" from langchain_core.messages.utils import (\n",
" # highlight-next-line\n",
" trim_messages,\n",
" # highlight-next-line\n",
" count_tokens_approximately\n",
" # highlight-next-line\n",
" )\n",
" from langchain.chat_models import init_chat_model\n",
" from langgraph.graph import StateGraph, START, MessagesState\n",
" \n",
" model = init_chat_model(\"anthropic:claude-3-7-sonnet-latest\")\n",
" summarization_model = model.bind(max_tokens=128)\n",
" \n",
" def call_model(state: MessagesState):\n",
" # highlight-next-line\n",
" messages = trim_messages(\n",
" state[\"messages\"],\n",
" strategy=\"last\",\n",
" token_counter=count_tokens_approximately,\n",
" max_tokens=128,\n",
" start_on=\"human\",\n",
" end_on=(\"human\", \"tool\"),\n",
" )\n",
" response = model.invoke(messages)\n",
" return {\"messages\": [response]}\n",
" \n",
" checkpointer = InMemorySaver()\n",
" builder = StateGraph(MessagesState)\n",
" builder.add_node(call_model)\n",
" builder.add_edge(START, \"call_model\")\n",
" graph = builder.compile(checkpointer=checkpointer)\n",
" \n",
" config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
" graph.invoke({\"messages\": \"hi, my name is bob\"}, config)\n",
" graph.invoke({\"messages\": \"write a short poem about cats\"}, config)\n",
" graph.invoke({\"messages\": \"now do the same but for dogs\"}, config)\n",
" final_response = graph.invoke({\"messages\": \"what's my name?\"}, config)\n",
"\n",
" final_response[\"messages\"][-1].pretty_print()\n",
" ```\n",
"\n",
" ```\n",
" ================================== Ai Message ==================================\n",
" \n",
" Your name is Bob, as you mentioned when you first introduced yourself.\n",
" ```"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "6676cd03-3e97-4550-ad4a-72d04f2cee7c",
"metadata": {},
"source": [
"## Summarize messages\n",
"\n",
"An effective strategy for handling long conversation history is to summarize earlier messages once they reach a certain threshold:\n",
"\n",
"```python\n",
"from typing import Any, TypedDict\n",
"\n",
"from langchain_core.messages import AnyMessage\n",
"from langchain_core.messages.utils import count_tokens_approximately\n",
"# highlight-next-line\n",
"from langmem.short_term import SummarizationNode\n",
"from langgraph.graph import StateGraph, START, MessagesState\n",
"\n",
"class State(MessagesState):\n",
" # highlight-next-line\n",
" context: dict[str, Any] # (1)!\n",
"\n",
"class LLMInputState(TypedDict): # (2)!\n",
" summarized_messages: list[AnyMessage]\n",
" context: dict[str, Any]\n",
"\n",
"# highlight-next-line\n",
"summarization_node = SummarizationNode(\n",
" token_counter=count_tokens_approximately,\n",
" model=summarization_model,\n",
" max_tokens=512,\n",
" max_tokens_before_summary=256,\n",
" max_summary_tokens=256,\n",
")\n",
"\n",
"# highlight-next-line\n",
"def call_model(state: LLMInputState): # (3)!\n",
" response = model.invoke(state[\"summarized_messages\"])\n",
" return {\"messages\": [response]}\n",
"\n",
"builder = StateGraph(State)\n",
"builder.add_node(call_model)\n",
"# highlight-next-line\n",
"builder.add_node(\"summarize\", summarization_node)\n",
"builder.add_edge(START, \"summarize\")\n",
"builder.add_edge(\"summarize\", \"call_model\")\n",
"...\n",
"```\n",
"\n",
"1. We will keep track of our running summary in the `context` field\n",
"(expected by the `SummarizationNode`).\n",
"2. Define private state that will be used only for filtering\n",
"the inputs to `call_model` node.\n",
"3. We're passing a private input state here to isolate the messages returned by the summarization node\n",
"\n",
"??? example \"Full example: summarize messages\"\n",
"\n",
" ```python\n",
" from typing import Any, TypedDict\n",
" \n",
" from langchain.chat_models import init_chat_model\n",
" from langchain_core.messages import AnyMessage\n",
" from langchain_core.messages.utils import count_tokens_approximately\n",
" from langgraph.graph import StateGraph, START, MessagesState\n",
" from langgraph.checkpoint.memory import InMemorySaver\n",
" # highlight-next-line\n",
" from langmem.short_term import SummarizationNode\n",
" \n",
" model = init_chat_model(\"anthropic:claude-3-7-sonnet-latest\")\n",
" summarization_model = model.bind(max_tokens=128)\n",
" \n",
" class State(MessagesState):\n",
" # highlight-next-line\n",
" context: dict[str, Any] # (1)!\n",
" \n",
" class LLMInputState(TypedDict): # (2)!\n",
" summarized_messages: list[AnyMessage]\n",
" context: dict[str, Any]\n",
" \n",
" # highlight-next-line\n",
" summarization_node = SummarizationNode(\n",
" token_counter=count_tokens_approximately,\n",
" model=summarization_model,\n",
" max_tokens=256,\n",
" max_tokens_before_summary=256,\n",
" max_summary_tokens=128,\n",
" )\n",
"\n",
" # highlight-next-line\n",
" def call_model(state: LLMInputState): # (3)!\n",
" response = model.invoke(state[\"summarized_messages\"])\n",
" return {\"messages\": [response]}\n",
" \n",
" checkpointer = InMemorySaver()\n",
" builder = StateGraph(State)\n",
" builder.add_node(call_model)\n",
" # highlight-next-line\n",
" builder.add_node(\"summarize\", summarization_node)\n",
" builder.add_edge(START, \"summarize\")\n",
" builder.add_edge(\"summarize\", \"call_model\")\n",
" graph = builder.compile(checkpointer=checkpointer)\n",
" \n",
" # Invoke the graph\n",
" config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
" graph.invoke({\"messages\": \"hi, my name is bob\"}, config)\n",
" graph.invoke({\"messages\": \"write a short poem about cats\"}, config)\n",
" graph.invoke({\"messages\": \"now do the same but for dogs\"}, config)\n",
" final_response = graph.invoke({\"messages\": \"what's my name?\"}, config)\n",
"\n",
" final_response[\"messages\"][-1].pretty_print()\n",
" print(\"\\nSummary:\", final_response[\"context\"][\"running_summary\"].summary)\n",
" ```\n",
"\n",
" 1. We will keep track of our running summary in the `context` field\n",
" (expected by the `SummarizationNode`).\n",
" 2. Define private state that will be used only for filtering\n",
" the inputs to `call_model` node.\n",
" 3. We're passing a private input state here to isolate the messages returned by the summarization node\n",
"\n",
" ```\n",
" ================================== Ai Message ==================================\n",
"\n",
" From our conversation, I can see that you introduced yourself as Bob. That's the name you shared with me when we began talking.\n",
" \n",
" Summary: In this conversation, I was introduced to Bob, who then asked me to write a poem about cats. I composed a poem titled \"The Mystery of Cats\" that captured cats' graceful movements, independent nature, and their special relationship with humans. Bob then requested a similar poem about dogs, so I wrote \"The Joy of Dogs,\" which highlighted dogs' loyalty, enthusiasm, and loving companionship. Both poems were written in a similar style but emphasized the distinct characteristics that make each pet special.\n",
" ```"
]
},
{
"cell_type": "markdown",
"id": "361d880b-1258-4708-8f0e-5efc95031e78",
"metadata": {},
"source": [
"## Delete messages\n",
"\n",
"To delete messages from the graph state, you can use the `RemoveMessage`.\n",
"\n",
"* Remove specific messages:\n",
"\n",
" ```python\n",
" # highlight-next-line\n",
" from langchain_core.messages import RemoveMessage\n",
" \n",
" def delete_messages(state):\n",
" messages = state[\"messages\"]\n",
" if len(messages) > 2:\n",
" # remove the earliest two messages\n",
" # highlight-next-line\n",
" return {\"messages\": [RemoveMessage(id=m.id) for m in messages[:2]]}\n",
" ```\n",
"\n",
"* Remove **all** messages:\n",
" \n",
" ```python\n",
" # highlight-next-line\n",
" from langgraph.graph.message import REMOVE_ALL_MESSAGES\n",
" \n",
" def delete_messages(state):\n",
" # highlight-next-line\n",
" return {\"messages\": [RemoveMessage(id=REMOVE_ALL_MESSAGES)]}\n",
" ```\n",
"\n",
"!!! important \"`add_messages` reducer\"\n",
"\n",
" For `RemoveMessage` to work, you need to use a state key with [`add_messages`][langgraph.graph.message.add_messages] [reducer](../../../concepts/low_level#reducers), like [`MessagesState`](../../../concepts/low_level#messagesstate)\n",
"\n",
"!!! warning \"Valid message history\"\n",
"\n",
" When deleting messages, **make sure** that the resulting message history is valid. Check the limitations of the LLM provider you're using. For example:\n",
" \n",
" * some providers expect message history to start with a `user` message\n",
" * most providers require `assistant` messages with tool calls to be followed by corresponding `tool` result messages.\n",
"\n",
"??? example \"Full example: delete messages\"\n",
"\n",
" ```python\n",
" # highlight-next-line\n",
" from langchain_core.messages import RemoveMessage\n",
" \n",
" def delete_messages(state):\n",
" messages = state[\"messages\"]\n",
" if len(messages) > 2:\n",
" # remove the earliest two messages\n",
" # highlight-next-line\n",
" return {\"messages\": [RemoveMessage(id=m.id) for m in messages[:2]]}\n",
" \n",
" def call_model(state: MessagesState):\n",
" response = model.invoke(state[\"messages\"])\n",
" return {\"messages\": response}\n",
" \n",
" builder = StateGraph(MessagesState)\n",
" builder.add_sequence([call_model, delete_messages])\n",
" builder.add_edge(START, \"call_model\")\n",
" \n",
" checkpointer = InMemorySaver()\n",
" app = builder.compile(checkpointer=checkpointer)\n",
" \n",
" for event in app.stream(\n",
" {\"messages\": [{\"role\": \"user\", \"content\": \"hi! I'm bob\"}]},\n",
" config,\n",
" stream_mode=\"values\"\n",
" ):\n",
" print([(message.type, message.content) for message in event[\"messages\"]])\n",
" \n",
" for event in app.stream(\n",
" {\"messages\": [{\"role\": \"user\", \"content\": \"what's my name?\"}]},\n",
" config,\n",
" stream_mode=\"values\"\n",
" ):\n",
" print([(message.type, message.content) for message in event[\"messages\"]])\n",
" ```\n",
"\n",
" ```\n",
" [('human', \"hi! I'm bob\")]\n",
" [('human', \"hi! I'm bob\"), ('ai', 'Hi Bob! How are you doing today? Is there anything I can help you with?')]\n",
" [('human', \"hi! I'm bob\"), ('ai', 'Hi Bob! How are you doing today? Is there anything I can help you with?'), ('human', \"what's my name?\")]\n",
" [('human', \"hi! I'm bob\"), ('ai', 'Hi Bob! How are you doing today? Is there anything I can help you with?'), ('human', \"what's my name?\"), ('ai', 'Your name is Bob.')]\n",
" [('human', \"what's my name?\"), ('ai', 'Your name is Bob.')]\n",
" ```"
]
}
],
"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
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,627 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to review tool calls (Functional API)\n",
"\n",
"!!! info \"Prerequisites\"\n",
" This guide assumes familiarity with the following:\n",
"\n",
" - Implementing [human-in-the-loop](../../concepts/human_in_the_loop) workflows with [interrupt](../../concepts/human_in_the_loop/#interrupt)\n",
" - [How to create a ReAct agent using the Functional API](../../how-tos/react-agent-from-scratch-functional)\n",
"\n",
"This guide demonstrates how to implement human-in-the-loop workflows in a ReAct agent using the LangGraph [Functional API](../../concepts/functional_api).\n",
"\n",
"We will build off of the agent created in the [How to create a ReAct agent using the Functional API](../../how-tos/react-agent-from-scratch-functional) guide.\n",
"\n",
"Specifically, we will demonstrate how to review [tool calls](https://python.langchain.com/docs/concepts/tool_calling/) generated by a [chat model](https://python.langchain.com/docs/concepts/chat_models/) prior to their execution. This can be accomplished through use of the [interrupt](../../concepts/human_in_the_loop/#interrupt) function at key points in our application.\n",
"\n",
"**Preview**:\n",
"\n",
"We will implement a simple function that reviews tool calls generated from our chat model and call it from inside our application's [entrypoint](../../concepts/functional_api/#entrypoint):\n",
"\n",
"```python\n",
"def review_tool_call(tool_call: ToolCall) -> Union[ToolCall, ToolMessage]:\n",
" \"\"\"Review a tool call, returning a validated version.\"\"\"\n",
" human_review = interrupt(\n",
" {\n",
" \"question\": \"Is this correct?\",\n",
" \"tool_call\": tool_call,\n",
" }\n",
" )\n",
" review_action = human_review[\"action\"]\n",
" review_data = human_review.get(\"data\")\n",
" if review_action == \"continue\":\n",
" return tool_call\n",
" elif review_action == \"update\":\n",
" updated_tool_call = {**tool_call, **{\"args\": review_data}}\n",
" return updated_tool_call\n",
" elif review_action == \"feedback\":\n",
" return ToolMessage(\n",
" content=review_data, name=tool_call[\"name\"], tool_call_id=tool_call[\"id\"]\n",
" )\n",
"```\n",
"\n",
"## Setup\n",
"\n",
"First, let's install the required packages and set our API keys:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"%%capture --no-stderr\n",
"%pip install -U langgraph langchain-openai"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"import getpass\n",
"import os\n",
"\n",
"\n",
"def _set_env(var: str):\n",
" if not os.environ.get(var):\n",
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
"\n",
"\n",
"_set_env(\"OPENAI_API_KEY\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<div class=\"admonition tip\">\n",
" <p class=\"admonition-title\">Set up <a href=\"https://smith.langchain.com\">LangSmith</a> for better debugging</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 aps built with LangGraph — read more about how to get started in the <a href=\"https://docs.smith.langchain.com\">docs</a>. \n",
" </p>\n",
" </div>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Define model and tools\n",
"\n",
"Let's first define the tools and model we will use for our example. As in the [ReAct agent guide](../../how-tos/react-agent-from-scratch-functional), we will use a single place-holder tool that gets a description of the weather for a location.\n",
"\n",
"We will use an [OpenAI](https://python.langchain.com/docs/integrations/providers/openai/) chat model for this example, but any model [supporting tool-calling](https://python.langchain.com/docs/integrations/chat/) will suffice."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from langchain_openai import ChatOpenAI\n",
"from langchain_core.tools import tool\n",
"\n",
"model = ChatOpenAI(model=\"gpt-4o-mini\")\n",
"\n",
"\n",
"@tool\n",
"def get_weather(location: str):\n",
" \"\"\"Call to get the weather from a specific location.\"\"\"\n",
" # This is a placeholder for the actual implementation\n",
" if any([city in location.lower() for city in [\"sf\", \"san francisco\"]]):\n",
" return \"It's sunny!\"\n",
" elif \"boston\" in location.lower():\n",
" return \"It's rainy!\"\n",
" else:\n",
" return f\"I am not sure what the weather is in {location}\"\n",
"\n",
"\n",
"tools = [get_weather]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Define tasks\n",
"\n",
"Our [tasks](../../concepts/functional_api/#task) are unchanged from the [ReAct agent guide](../../how-tos/react-agent-from-scratch-functional):\n",
"\n",
"1. **Call model**: We want to query our chat model with a list of messages.\n",
"2. **Call tool**: If our model generates tool calls, we want to execute them."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.messages import ToolCall, ToolMessage\n",
"from langgraph.func import entrypoint, task\n",
"\n",
"\n",
"tools_by_name = {tool.name: tool for tool in tools}\n",
"\n",
"\n",
"@task\n",
"def call_model(messages):\n",
" \"\"\"Call model with a sequence of messages.\"\"\"\n",
" response = model.bind_tools(tools).invoke(messages)\n",
" return response\n",
"\n",
"\n",
"@task\n",
"def call_tool(tool_call):\n",
" tool = tools_by_name[tool_call[\"name\"]]\n",
" observation = tool.invoke(tool_call[\"args\"])\n",
" return ToolMessage(content=observation, tool_call_id=tool_call[\"id\"])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Define entrypoint\n",
"\n",
"To review tool calls before execution, we add a `review_tool_call` function that calls [interrupt](../../concepts/human_in_the_loop/#interrupt). When this function is called, execution will be paused until we issue a command to resume it.\n",
"\n",
"Given a tool call, our function will `interrupt` for human review. At that point we can either:\n",
"\n",
"- Accept the tool call;\n",
"- Revise the tool call and continue;\n",
"- Generate a custom tool message (e.g., instructing the model to re-format its tool call).\n",
"\n",
"We will demonstrate these three cases in the [usage examples](#usage) below."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"from typing import Union\n",
"\n",
"\n",
"def review_tool_call(tool_call: ToolCall) -> Union[ToolCall, ToolMessage]:\n",
" \"\"\"Review a tool call, returning a validated version.\"\"\"\n",
" human_review = interrupt(\n",
" {\n",
" \"question\": \"Is this correct?\",\n",
" \"tool_call\": tool_call,\n",
" }\n",
" )\n",
" review_action = human_review[\"action\"]\n",
" review_data = human_review.get(\"data\")\n",
" if review_action == \"continue\":\n",
" return tool_call\n",
" elif review_action == \"update\":\n",
" updated_tool_call = {**tool_call, **{\"args\": review_data}}\n",
" return updated_tool_call\n",
" elif review_action == \"feedback\":\n",
" return ToolMessage(\n",
" content=review_data, name=tool_call[\"name\"], tool_call_id=tool_call[\"id\"]\n",
" )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can now update our [entrypoint](../../concepts/functional_api/#entrypoint) to review the generated tool calls. If a tool call is accepted or revised, we execute in the same way as before. Otherwise, we just append the `ToolMessage` supplied by the human.\n",
"\n",
"!!! tip\n",
"\n",
" The results of prior tasks — in this case the initial model call — are persisted, so that they are not run again following the `interrupt`."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"from langgraph.checkpoint.memory import MemorySaver\n",
"from langgraph.graph.message import add_messages\n",
"from langgraph.types import Command, interrupt\n",
"\n",
"\n",
"checkpointer = MemorySaver()\n",
"\n",
"\n",
"@entrypoint(checkpointer=checkpointer)\n",
"def agent(messages, previous):\n",
" if previous is not None:\n",
" messages = add_messages(previous, messages)\n",
"\n",
" llm_response = call_model(messages).result()\n",
" while True:\n",
" if not llm_response.tool_calls:\n",
" break\n",
"\n",
" # Review tool calls\n",
" tool_results = []\n",
" tool_calls = []\n",
" for i, tool_call in enumerate(llm_response.tool_calls):\n",
" review = review_tool_call(tool_call)\n",
" if isinstance(review, ToolMessage):\n",
" tool_results.append(review)\n",
" else: # is a validated tool call\n",
" tool_calls.append(review)\n",
" if review != tool_call:\n",
" llm_response.tool_calls[i] = review # update message\n",
"\n",
" # Execute remaining tool calls\n",
" tool_result_futures = [call_tool(tool_call) for tool_call in tool_calls]\n",
" remaining_tool_results = [fut.result() for fut in tool_result_futures]\n",
"\n",
" # Append to message list\n",
" messages = add_messages(\n",
" messages,\n",
" [llm_response, *tool_results, *remaining_tool_results],\n",
" )\n",
"\n",
" # Call model again\n",
" llm_response = call_model(messages).result()\n",
"\n",
" # Generate final response\n",
" messages = add_messages(messages, llm_response)\n",
" return entrypoint.final(value=llm_response, save=messages)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Usage\n",
"\n",
"Let's demonstrate some scenarios."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"def _print_step(step: dict) -> None:\n",
" for task_name, result in step.items():\n",
" if task_name == \"agent\":\n",
" continue # just stream from tasks\n",
" print(f\"\\n{task_name}:\")\n",
" if task_name in (\"__interrupt__\", \"review_tool_call\"):\n",
" print(result)\n",
" else:\n",
" result.pretty_print()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Accept a tool call\n",
"\n",
"To accept a tool call, we just indicate in the data we provide in the `Command` that the tool call should pass through."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"config = {\"configurable\": {\"thread_id\": \"1\"}}"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'role': 'user', 'content': \"What's the weather in san francisco?\"}\n",
"\n",
"call_model:\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"Tool Calls:\n",
" get_weather (call_Bh5cSwMqCpCxTjx7AjdrQTPd)\n",
" Call ID: call_Bh5cSwMqCpCxTjx7AjdrQTPd\n",
" Args:\n",
" location: San Francisco\n",
"\n",
"__interrupt__:\n",
"(Interrupt(value={'question': 'Is this correct?', 'tool_call': {'name': 'get_weather', 'args': {'location': 'San Francisco'}, 'id': 'call_Bh5cSwMqCpCxTjx7AjdrQTPd', 'type': 'tool_call'}}, resumable=True, ns=['agent:22fcc9cd-3573-b39b-eea7-272a025903e2'], when='during'),)\n"
]
}
],
"source": [
"user_message = {\"role\": \"user\", \"content\": \"What's the weather in san francisco?\"}\n",
"print(user_message)\n",
"\n",
"for step in agent.stream([user_message], config):\n",
" _print_step(step)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"call_tool:\n",
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
"\n",
"It's sunny!\n",
"\n",
"call_model:\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"The weather in San Francisco is sunny!\n"
]
}
],
"source": [
"# highlight-next-line\n",
"human_input = Command(resume={\"action\": \"continue\"})\n",
"\n",
"for step in agent.stream(human_input, config):\n",
" _print_step(step)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Revise a tool call\n",
"\n",
"To revise a tool call, we can supply updated arguments."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"config = {\"configurable\": {\"thread_id\": \"2\"}}"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'role': 'user', 'content': \"What's the weather in san francisco?\"}\n",
"\n",
"call_model:\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"Tool Calls:\n",
" get_weather (call_b9h8e18FqH0IQm3NMoeYKz6N)\n",
" Call ID: call_b9h8e18FqH0IQm3NMoeYKz6N\n",
" Args:\n",
" location: san francisco\n",
"\n",
"__interrupt__:\n",
"(Interrupt(value={'question': 'Is this correct?', 'tool_call': {'name': 'get_weather', 'args': {'location': 'san francisco'}, 'id': 'call_b9h8e18FqH0IQm3NMoeYKz6N', 'type': 'tool_call'}}, resumable=True, ns=['agent:9559a81d-5720-dc19-a457-457bac7bdd83'], when='during'),)\n"
]
}
],
"source": [
"user_message = {\"role\": \"user\", \"content\": \"What's the weather in san francisco?\"}\n",
"print(user_message)\n",
"\n",
"for step in agent.stream([user_message], config):\n",
" _print_step(step)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"call_tool:\n",
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
"\n",
"It's sunny!\n",
"\n",
"call_model:\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"The weather in San Francisco is sunny!\n"
]
}
],
"source": [
"# highlight-next-line\n",
"human_input = Command(resume={\"action\": \"update\", \"data\": {\"location\": \"SF, CA\"}})\n",
"\n",
"for step in agent.stream(human_input, config):\n",
" _print_step(step)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The LangSmith traces for this run are particularly informative:\n",
"\n",
"- In the trace [before the interrupt](https://smith.langchain.com/public/c8b07579-5cf4-4adb-a849-282163bc9d99/r/b5b128d6-e715-480b-b58d-59e64f724275), we generate a tool call for location `\"San Francisco\"`.\n",
"- In the trace [after resuming](https://smith.langchain.com/public/b28b92e5-a555-482d-aa4d-c675a19f0eb5/r), we see that the tool call in the message has been updated to `\"SF, CA\"`."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Generate a custom ToolMessage\n",
"\n",
"To Generate a custom `ToolMessage`, we supply the content of the message. In this case we will ask the model to reformat its tool call."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"config = {\"configurable\": {\"thread_id\": \"3\"}}"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'role': 'user', 'content': \"What's the weather in san francisco?\"}\n",
"\n",
"call_model:\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"Tool Calls:\n",
" get_weather (call_VqGjKE7uu8HdWs9XuY1kMV18)\n",
" Call ID: call_VqGjKE7uu8HdWs9XuY1kMV18\n",
" Args:\n",
" location: San Francisco\n",
"\n",
"__interrupt__:\n",
"(Interrupt(value={'question': 'Is this correct?', 'tool_call': {'name': 'get_weather', 'args': {'location': 'San Francisco'}, 'id': 'call_VqGjKE7uu8HdWs9XuY1kMV18', 'type': 'tool_call'}}, resumable=True, ns=['agent:4b3b372b-9da3-70be-5c68-3d9317346070'], when='during'),)\n"
]
}
],
"source": [
"user_message = {\"role\": \"user\", \"content\": \"What's the weather in san francisco?\"}\n",
"print(user_message)\n",
"\n",
"for step in agent.stream([user_message], config):\n",
" _print_step(step)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"call_model:\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"Tool Calls:\n",
" get_weather (call_xoXkK8Cz0zIpvWs78qnXpvYp)\n",
" Call ID: call_xoXkK8Cz0zIpvWs78qnXpvYp\n",
" Args:\n",
" location: San Francisco, CA\n",
"\n",
"__interrupt__:\n",
"(Interrupt(value={'question': 'Is this correct?', 'tool_call': {'name': 'get_weather', 'args': {'location': 'San Francisco, CA'}, 'id': 'call_xoXkK8Cz0zIpvWs78qnXpvYp', 'type': 'tool_call'}}, resumable=True, ns=['agent:4b3b372b-9da3-70be-5c68-3d9317346070'], when='during'),)\n"
]
}
],
"source": [
"# highlight-next-line\n",
"human_input = Command(\n",
" # highlight-next-line\n",
" resume={\n",
" # highlight-next-line\n",
" \"action\": \"feedback\",\n",
" # highlight-next-line\n",
" \"data\": \"Please format as <City>, <State>.\",\n",
" # highlight-next-line\n",
" },\n",
" # highlight-next-line\n",
")\n",
"\n",
"for step in agent.stream(human_input, config):\n",
" _print_step(step)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Once it is re-formatted, we can accept it:"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"call_tool:\n",
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
"\n",
"It's sunny!\n",
"\n",
"call_model:\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"The weather in San Francisco, CA is sunny!\n"
]
}
],
"source": [
"# highlight-next-line\n",
"human_input = Command(resume={\"action\": \"continue\"})\n",
"\n",
"for step in agent.stream(human_input, config):\n",
" _print_step(step)"
]
}
],
"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": 4
}
+223 -25
View File
@@ -1,11 +1,220 @@
# Stream outputs
## Streaming API
You can [stream outputs](../concepts/streaming.md) from a LangGraph agent or workflow.
## Supported stream modes
Pass one or more of the following stream modes as a list to the [`stream()`][langgraph.graph.state.CompiledStateGraph.stream] or [`astream()`][langgraph.graph.state.CompiledStateGraph.astream] methods:
| Mode | Description |
|------|-------------|
| `values` | Streams the full value of the state after each step of the graph. |
| `updates` | Streams the updates to the state after each step of the graph. If multiple updates are made in the same step (e.g., multiple nodes are run), those updates are streamed separately. |
| `custom` | Streams custom data from inside your graph nodes. |
| `messages` | Streams 2-tuples (LLM token, metadata) from any graph nodes where an LLM is invoked. |
| `debug` | Streams as much information as possible throughout the execution of the graph.
## Stream from an agent
### Agent progress
To stream agent progress, use the [`stream()`][langgraph.graph.state.CompiledStateGraph.stream] or [`astream()`][langgraph.graph.state.CompiledStateGraph.astream] methods with `stream_mode="updates"`. This emits an event after every agent step.
For example, if you have an agent that calls a tool once, you should see the following updates:
* **LLM node**: AI message with tool call requests
* **Tool node**: Tool message with execution result
* **LLM node**: Final AI response
=== "Sync"
```python
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
# highlight-next-line
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode="updates"
):
print(chunk)
print("\n")
```
=== "Async"
```python
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
# highlight-next-line
async for chunk in agent.astream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode="updates"
):
print(chunk)
print("\n")
```
### LLM tokens
To stream tokens as they are produced by the LLM, use `stream_mode="messages"`:
=== "Sync"
```python
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
# highlight-next-line
for token, metadata in agent.stream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode="messages"
):
print("Token", token)
print("Metadata", metadata)
print("\n")
```
=== "Async"
```python
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
# highlight-next-line
async for token, metadata in agent.astream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode="messages"
):
print("Token", token)
print("Metadata", metadata)
print("\n")
```
### Tool updates
To stream updates from tools as they are executed, you can use [get_stream_writer][langgraph.config.get_stream_writer].
=== "Sync"
```python
# highlight-next-line
from langgraph.config import get_stream_writer
def get_weather(city: str) -> str:
"""Get weather for a given city."""
# highlight-next-line
writer = get_stream_writer()
# stream any arbitrary data
# highlight-next-line
writer(f"Looking up data for city: {city}")
return f"It's always sunny in {city}!"
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode="custom"
):
print(chunk)
print("\n")
```
=== "Async"
```python
# highlight-next-line
from langgraph.config import get_stream_writer
def get_weather(city: str) -> str:
"""Get weather for a given city."""
# highlight-next-line
writer = get_stream_writer()
# stream any arbitrary data
# highlight-next-line
writer(f"Looking up data for city: {city}")
return f"It's always sunny in {city}!"
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
async for chunk in agent.astream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode="custom"
):
print(chunk)
print("\n")
```
!!! Note
If you add `get_stream_writer` inside your tool, you won't be able to invoke the tool outside of a LangGraph execution context.
### Stream multiple modes
You can specify multiple streaming modes by passing stream mode as a list: `stream_mode=["updates", "messages", "custom"]`:
=== "Sync"
```python
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
for stream_mode, chunk in agent.stream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode=["updates", "messages", "custom"]
):
print(chunk)
print("\n")
```
=== "Async"
```python
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
async for stream_mode, chunk in agent.astream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode=["updates", "messages", "custom"]
):
print(chunk)
print("\n")
```
### Disable streaming
In some applications you might need to disable streaming of individual tokens for a given model. This is useful in [multi-agent](../agents/multi-agent.md) systems to control which agents stream their output.
See the [Models](../agents/models.md#disable-streaming) guide to learn how to disable streaming.
## Stream from a workflow
### Basic usage example
LangGraph graphs expose the [`.stream()`][langgraph.pregel.Pregel.stream] (sync) and [`.astream()`][langgraph.pregel.Pregel.astream] (async) methods to yield streamed outputs as iterators.
Basic usage example:
=== "Sync"
```python
@@ -61,18 +270,7 @@ Basic usage example:
```output
{'refine_topic': {'topic': 'ice cream and cats'}}
{'generate_joke': {'joke': 'This is a joke about ice cream and cats'}}
```
### Supported stream modes
| Mode | Description |
|----------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [`values`](#stream-graph-state) | Streams the full value of the state after each step of the graph. |
| [`updates`](#stream-graph-state) | Streams the updates to the state after each step of the graph. If multiple updates are made in the same step (e.g., multiple nodes are run), those updates are streamed separately. |
| [`custom`](#stream-custom-data) | Streams custom data from inside your graph nodes. |
| [`messages`](#messages) | Streams 2-tuples (LLM token, metadata) from any graph nodes where an LLM is invoked. |
| [`debug`](#debug) | Streams as much information as possible throughout the execution of the graph. |
``` |
### Stream multiple modes
@@ -94,7 +292,7 @@ The streamed outputs will be tuples of `(mode, chunk)` where `mode` is the name
print(chunk)
```
## Stream graph state
### Stream graph state
Use the stream modes `updates` and `values` to stream the state of the graph as it executes.
@@ -157,7 +355,7 @@ graph = (
```
## Subgraphs
### Stream subgraph outputs
To include outputs from [subgraphs](../concepts/subgraphs.md) in the streamed outputs, you can set `subgraphs=True` in the `.stream()` method of the parent graph. This will stream outputs from both the parent graph and any subgraphs.
@@ -233,7 +431,7 @@ for chunk in graph.stream(
**Note** that we are receiving not just the node updates, but we also the namespaces which tell us what graph (or subgraph) we are streaming from.
## Debugging {#debug}
### Debugging {#debug}
Use the `debug` streaming mode to stream as much information as possible throughout the execution of the graph. The streamed outputs include the name of the node as well as the full state.
@@ -247,7 +445,7 @@ for chunk in graph.stream(
```
## LLM tokens {#messages}
### LLM tokens {#messages}
Use the `messages` streaming mode to stream Large Language Model (LLM) outputs **token by token** from any part of your graph, including nodes, tools, subgraphs, or tasks.
@@ -307,7 +505,7 @@ for message_chunk, metadata in graph.stream( # (2)!
2. The "messages" stream mode returns an iterator of tuples `(message_chunk, metadata)` where `message_chunk` is the token streamed by the LLM and `metadata` is a dictionary with information about the graph node where the LLM was called and other information.
### Filter by LLM invocation
#### Filter by LLM invocation
You can associate `tags` with LLM invocations to filter the streamed tokens by LLM invocation.
@@ -391,7 +589,7 @@ async for msg, metadata in graph.astream( # (3)!
4. The `stream_mode` is set to "messages" to stream LLM tokens. The `metadata` contains information about the LLM invocation, including the tags.
### Filter by node
#### Filter by node
To stream tokens only from specific nodes, use `stream_mode="messages"` and filter the outputs by the `langgraph_node` field in the streamed metadata:
@@ -464,7 +662,7 @@ for msg, metadata in graph.stream( # (1)!
1. The "messages" stream mode returns a tuple of `(message_chunk, metadata)` where `message_chunk` is the token streamed by the LLM and `metadata` is a dictionary with information about the graph node where the LLM was called and other information.
2. Filter the streamed tokens by the `langgraph_node` field in the metadata to only include the tokens from the `write_poem` node.
## Stream custom data
### Stream custom data
To send **custom user-defined data** from inside a LangGraph node or tool, follow these steps:
@@ -541,7 +739,7 @@ To send **custom user-defined data** from inside a LangGraph node or tool, follo
3. Emit another custom key-value pair.
4. Set `stream_mode="custom"` to receive the custom data in the stream.
## Use with any LLM
### Use with any LLM
You can use `stream_mode="custom"` to stream data from **any LLM API** — even if that API does **not** implement the LangChain chat model interface.
@@ -701,7 +899,7 @@ for chunk in graph.stream(
```
## Disable streaming for specific chat models
### Disable streaming for specific chat models
If your application mixes models that support streaming with those that do not, you may need to explicitly disable streaming for
models that do not support it.
@@ -733,7 +931,7 @@ Set `disable_streaming=True` when initializing the model.
1. Set `disable_streaming=True` to disable streaming for the chat model.
## Async with Python < 3.11 { #async }
### Async with Python < 3.11 { #async }
In Python versions < 3.11, [asyncio tasks](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task) do not support the `context` parameter.
This limits LangGraph ability to automatically propagate context, and affects LangGraphs streaming mechanisms in two key ways:
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+272 -3
View File
@@ -462,15 +462,284 @@ main.invoke(None, config=config)
The functional API supports [human-in-the-loop](../concepts/human_in_the_loop.md) workflows using the `interrupt` function and the `Command` primitive.
Please see the following examples for more details:
### Basic human-in-the-loop workflow
* [How to wait for user input (Functional API)](./wait-user-input-functional.ipynb): Shows how to implement a simple human-in-the-loop workflow using the functional API.
* [How to review tool calls (Functional API)](./review-tool-calls-functional.ipynb): Guide demonstrates how to implement human-in-the-loop workflows in a ReAct agent using the LangGraph Functional API.
We will create three [tasks](../concepts/functional_api.md#task):
1. Append `"bar"`.
2. Pause for human input. When resuming, append human input.
3. Append `"qux"`.
```python
from langgraph.func import entrypoint, task
from langgraph.types import Command, interrupt
@task
def step_1(input_query):
"""Append bar."""
return f"{input_query} bar"
@task
def human_feedback(input_query):
"""Append user input."""
feedback = interrupt(f"Please provide feedback: {input_query}")
return f"{input_query} {feedback}"
@task
def step_3(input_query):
"""Append qux."""
return f"{input_query} qux"
```
We can now compose these tasks in an [entrypoint](../concepts/functional_api.md#entrypoint):
```python
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
@entrypoint(checkpointer=checkpointer)
def graph(input_query):
result_1 = step_1(input_query).result()
result_2 = human_feedback(result_1).result()
result_3 = step_3(result_2).result()
return result_3
```
[interrupt()](../how-tos/human_in_the_loop/add-human-in-the-loop.md#pause-using-interrupt) is called inside a task, enabling a human to review and edit the output of the previous task. The results of prior tasks-- in this case `step_1`-- are persisted, so that they are not run again following the `interrupt`.
Let's send in a query string:
```python
config = {"configurable": {"thread_id": "1"}}
for event in graph.stream("foo", config):
print(event)
print("\n")
```
Note that we've paused with an `interrupt` after `step_1`. The interrupt provides instructions to resume the run. To resume, we issue a [Command](../how-tos/human_in_the_loop/add-human-in-the-loop.md#resume-using-the-command-primitive) containing the data expected by the `human_feedback` task.
```python
# Continue execution
for event in graph.stream(Command(resume="baz"), config):
print(event)
print("\n")
```
After resuming, the run proceeds through the remaining step and terminates as expected.
### Review tool calls
To review tool calls before execution, we add a `review_tool_call` function that calls [`interrupt`](../how-tos/human_in_the_loop/add-human-in-the-loop.md#pause-using-interrupt). When this function is called, execution will be paused until we issue a command to resume it.
Given a tool call, our function will `interrupt` for human review. At that point we can either:
- Accept the tool call
- Revise the tool call and continue
- Generate a custom tool message (e.g., instructing the model to re-format its tool call)
```python
from typing import Union
def review_tool_call(tool_call: ToolCall) -> Union[ToolCall, ToolMessage]:
"""Review a tool call, returning a validated version."""
human_review = interrupt(
{
"question": "Is this correct?",
"tool_call": tool_call,
}
)
review_action = human_review["action"]
review_data = human_review.get("data")
if review_action == "continue":
return tool_call
elif review_action == "update":
updated_tool_call = {**tool_call, **{"args": review_data}}
return updated_tool_call
elif review_action == "feedback":
return ToolMessage(
content=review_data, name=tool_call["name"], tool_call_id=tool_call["id"]
)
```
We can now update our [entrypoint](../concepts/functional_api.md#entrypoint) to review the generated tool calls. If a tool call is accepted or revised, we execute in the same way as before. Otherwise, we just append the `ToolMessage` supplied by the human. The results of prior tasks — in this case the initial model call — are persisted, so that they are not run again following the `interrupt`.
```python
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph.message import add_messages
from langgraph.types import Command, interrupt
checkpointer = MemorySaver()
@entrypoint(checkpointer=checkpointer)
def agent(messages, previous):
if previous is not None:
messages = add_messages(previous, messages)
llm_response = call_model(messages).result()
while True:
if not llm_response.tool_calls:
break
# Review tool calls
tool_results = []
tool_calls = []
for i, tool_call in enumerate(llm_response.tool_calls):
review = review_tool_call(tool_call)
if isinstance(review, ToolMessage):
tool_results.append(review)
else: # is a validated tool call
tool_calls.append(review)
if review != tool_call:
llm_response.tool_calls[i] = review # update message
# Execute remaining tool calls
tool_result_futures = [call_tool(tool_call) for tool_call in tool_calls]
remaining_tool_results = [fut.result() for fut in tool_result_futures]
# Append to message list
messages = add_messages(
messages,
[llm_response, *tool_results, *remaining_tool_results],
)
# Call model again
llm_response = call_model(messages).result()
# Generate final response
messages = add_messages(messages, llm_response)
return entrypoint.final(value=llm_response, save=messages)
```
## Short-term memory
Short-term memory allows storing information across different **invocations** of the same **thread id**. See [short-term memory](../concepts/functional_api.md#short-term-memory) for more details.
### Manage checkpoints
You can view and delete the information stored by the checkpointer.
#### View thread state (checkpoint)
```python
config = {
"configurable": {
# highlight-next-line
"thread_id": "1",
# optionally provide an ID for a specific checkpoint,
# otherwise the latest checkpoint is shown
# highlight-next-line
# "checkpoint_id": "1f029ca3-1f5b-6704-8004-820c16b69a5a"
}
}
# highlight-next-line
graph.get_state(config)
```
```
StateSnapshot(
values={'messages': [HumanMessage(content="hi! I'm bob"), AIMessage(content='Hi Bob! How are you doing today?), HumanMessage(content="what's my name?"), AIMessage(content='Your name is Bob.')]}, next=(),
config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1f5b-6704-8004-820c16b69a5a'}},
metadata={
'source': 'loop',
'writes': {'call_model': {'messages': AIMessage(content='Your name is Bob.')}},
'step': 4,
'parents': {},
'thread_id': '1'
},
created_at='2025-05-05T16:01:24.680462+00:00',
parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1790-6b0a-8003-baf965b6a38f'}},
tasks=(),
interrupts=()
)
```
#### View the history of the thread (checkpoints)
```python
config = {
"configurable": {
# highlight-next-line
"thread_id": "1"
}
}
# highlight-next-line
list(graph.get_state_history(config))
```
```
[
StateSnapshot(
values={'messages': [HumanMessage(content="hi! I'm bob"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?'), HumanMessage(content="what's my name?"), AIMessage(content='Your name is Bob.')]},
next=(),
config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1f5b-6704-8004-820c16b69a5a'}},
metadata={'source': 'loop', 'writes': {'call_model': {'messages': AIMessage(content='Your name is Bob.')}}, 'step': 4, 'parents': {}, 'thread_id': '1'},
created_at='2025-05-05T16:01:24.680462+00:00',
parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1790-6b0a-8003-baf965b6a38f'}},
tasks=(),
interrupts=()
),
StateSnapshot(
values={'messages': [HumanMessage(content="hi! I'm bob"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?'), HumanMessage(content="what's my name?")]},
next=('call_model',),
config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1790-6b0a-8003-baf965b6a38f'}},
metadata={'source': 'loop', 'writes': None, 'step': 3, 'parents': {}, 'thread_id': '1'},
created_at='2025-05-05T16:01:23.863421+00:00',
parent_config={...}
tasks=(PregelTask(id='8ab4155e-6b15-b885-9ce5-bed69a2c305c', name='call_model', path=('__pregel_pull', 'call_model'), error=None, interrupts=(), state=None, result={'messages': AIMessage(content='Your name is Bob.')}),),
interrupts=()
),
StateSnapshot(
values={'messages': [HumanMessage(content="hi! I'm bob"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')]},
next=('__start__',),
config={...},
metadata={'source': 'input', 'writes': {'__start__': {'messages': [{'role': 'user', 'content': "what's my name?"}]}}, 'step': 2, 'parents': {}, 'thread_id': '1'},
created_at='2025-05-05T16:01:23.863173+00:00',
parent_config={...}
tasks=(PregelTask(id='24ba39d6-6db1-4c9b-f4c5-682aeaf38dcd', name='__start__', path=('__pregel_pull', '__start__'), error=None, interrupts=(), state=None, result={'messages': [{'role': 'user', 'content': "what's my name?"}]}),),
interrupts=()
),
StateSnapshot(
values={'messages': [HumanMessage(content="hi! I'm bob"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')]},
next=(),
config={...},
metadata={'source': 'loop', 'writes': {'call_model': {'messages': AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')}}, 'step': 1, 'parents': {}, 'thread_id': '1'},
created_at='2025-05-05T16:01:23.862295+00:00',
parent_config={...}
tasks=(),
interrupts=()
),
StateSnapshot(
values={'messages': [HumanMessage(content="hi! I'm bob")]},
next=('call_model',),
config={...},
metadata={'source': 'loop', 'writes': None, 'step': 0, 'parents': {}, 'thread_id': '1'},
created_at='2025-05-05T16:01:22.278960+00:00',
parent_config={...}
tasks=(PregelTask(id='8cbd75e0-3720-b056-04f7-71ac805140a0', name='call_model', path=('__pregel_pull', 'call_model'), error=None, interrupts=(), state=None, result={'messages': AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')}),),
interrupts=()
),
StateSnapshot(
values={'messages': []},
next=('__start__',),
config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-0870-6ce2-bfff-1f3f14c3e565'}},
metadata={'source': 'input', 'writes': {'__start__': {'messages': [{'role': 'user', 'content': "hi! I'm bob"}]}}, 'step': -1, 'parents': {}, 'thread_id': '1'},
created_at='2025-05-05T16:01:22.277497+00:00',
parent_config=None,
tasks=(PregelTask(id='d458367b-8265-812c-18e2-33001d199ce6', name='__start__', path=('__pregel_pull', '__start__'), error=None, interrupts=(), state=None, result={'messages': [{'role': 'user', 'content': "hi! I'm bob"}]}),),
interrupts=()
)
]
```
### Decouple return value from saved value
Use `entrypoint.final` to decouple what is returned to the caller from what is persisted in the checkpoint. This is useful when:
@@ -1,561 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to wait for user input (Functional API)\n",
"\n",
"!!! info \"Prerequisites\"\n",
" This guide assumes familiarity with the following:\n",
"\n",
" - Implementing [human-in-the-loop](../../concepts/human_in_the_loop) workflows with [interrupt](../../concepts/human_in_the_loop/#interrupt)\n",
" - [How to create a ReAct agent using the Functional API](../../how-tos/react-agent-from-scratch-functional)\n",
"\n",
"**Human-in-the-loop (HIL)** interactions are crucial for [agentic systems](../../concepts/agentic_concepts/#human-in-the-loop). Waiting for human input is a common HIL interaction pattern, allowing the agent to ask the user clarifying questions and await input before proceeding. \n",
"\n",
"We can implement this in LangGraph using the [interrupt()][langgraph.types.interrupt] function. `interrupt` allows us to stop graph execution to collect input from a user and continue execution with collected input.\n",
"\n",
"This guide demonstrates how to implement human-in-the-loop workflows using LangGraph's [Functional API](../../concepts/functional_api). Specifically, we will demonstrate:\n",
"\n",
"1. [A simple usage example](#simple-usage)\n",
"2. [How to use with a ReAct agent](#agent)\n",
"\n",
"## Setup\n",
"\n",
"First, let's install the required packages and set our API keys:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"%%capture --no-stderr\n",
"%pip install -U langgraph langchain-openai"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"import getpass\n",
"import os\n",
"\n",
"\n",
"def _set_env(var: str):\n",
" if not os.environ.get(var):\n",
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
"\n",
"\n",
"_set_env(\"OPENAI_API_KEY\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<div class=\"admonition tip\">\n",
" <p class=\"admonition-title\">Set up <a href=\"https://smith.langchain.com\">LangSmith</a> for better debugging</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 aps built with LangGraph — read more about how to get started in the <a href=\"https://docs.smith.langchain.com\">docs</a>. \n",
" </p>\n",
" </div>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Simple usage\n",
"\n",
"Let's demonstrate a simple usage example. We will create three [tasks](../../concepts/functional_api/#task):\n",
"\n",
"1. Append `\"bar\"`.\n",
"2. Pause for human input. When resuming, append human input.\n",
"3. Append `\"qux\"`."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"from langgraph.func import entrypoint, task\n",
"from langgraph.types import Command, interrupt\n",
"\n",
"\n",
"@task\n",
"def step_1(input_query):\n",
" \"\"\"Append bar.\"\"\"\n",
" return f\"{input_query} bar\"\n",
"\n",
"\n",
"@task\n",
"def human_feedback(input_query):\n",
" \"\"\"Append user input.\"\"\"\n",
" feedback = interrupt(f\"Please provide feedback: {input_query}\")\n",
" return f\"{input_query} {feedback}\"\n",
"\n",
"\n",
"@task\n",
"def step_3(input_query):\n",
" \"\"\"Append qux.\"\"\"\n",
" return f\"{input_query} qux\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can now compose these tasks in a simple [entrypoint](../../concepts/functional_api/#entrypoint):"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"from langgraph.checkpoint.memory import MemorySaver\n",
"\n",
"checkpointer = MemorySaver()\n",
"\n",
"\n",
"@entrypoint(checkpointer=checkpointer)\n",
"def graph(input_query):\n",
" result_1 = step_1(input_query).result()\n",
" result_2 = human_feedback(result_1).result()\n",
" result_3 = step_3(result_2).result()\n",
"\n",
" return result_3"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"All we have done to enable human-in-the-loop workflows is called [interrupt()](../../concepts/human_in_the_loop/#interrupt) inside a task.\n",
"\n",
"!!! tip\n",
"\n",
" The results of prior tasks-- in this case `step_1`-- are persisted, so that they are not run again following the `interrupt`.\n",
"\n",
"\n",
"Let's send in a query string:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"config = {\"configurable\": {\"thread_id\": \"1\"}}"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'step_1': 'foo bar'}\n",
"\n",
"\n",
"{'__interrupt__': (Interrupt(value='Please provide feedback: foo bar', resumable=True, ns=['graph:d66b2e35-0ee3-d8d6-1a22-aec9d58f13b9', 'human_feedback:e0cd4ee2-b874-e1d2-8bc4-3f7ddc06bcc2'], when='during'),)}\n",
"\n",
"\n"
]
}
],
"source": [
"for event in graph.stream(\"foo\", config):\n",
" print(event)\n",
" print(\"\\n\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that we've paused with an `interrupt` after `step_1`. The interrupt provides instructions to resume the run. To resume, we issue a [Command](../../concepts/human_in_the_loop/#the-command-primitive) containing the data expected by the `human_feedback` task."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'human_feedback': 'foo bar baz'}\n",
"\n",
"\n",
"{'step_3': 'foo bar baz qux'}\n",
"\n",
"\n",
"{'graph': 'foo bar baz qux'}\n",
"\n",
"\n"
]
}
],
"source": [
"# Continue execution\n",
"for event in graph.stream(Command(resume=\"baz\"), config):\n",
" print(event)\n",
" print(\"\\n\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"After resuming, the run proceeds through the remaining step and terminates as expected."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Agent\n",
"\n",
"We will build off of the agent created in the [How to create a ReAct agent using the Functional API](../../how-tos/react-agent-from-scratch-functional) guide.\n",
"\n",
"Here we will extend the agent by allowing it to reach out to a human for assistance when needed.\n",
"\n",
"### Define model and tools\n",
"\n",
"Let's first define the tools and model we will use for our example. As in the [ReAct agent guide](../../how-tos/react-agent-from-scratch-functional), we will use a single place-holder tool that gets a description of the weather for a location.\n",
"\n",
"We will use an [OpenAI](https://python.langchain.com/docs/integrations/providers/openai/) chat model for this example, but any model [supporting tool-calling](https://python.langchain.com/docs/integrations/chat/) will suffice."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"from langchain_openai import ChatOpenAI\n",
"from langchain_core.tools import tool\n",
"\n",
"model = ChatOpenAI(model=\"gpt-4o-mini\")\n",
"\n",
"\n",
"@tool\n",
"def get_weather(location: str):\n",
" \"\"\"Call to get the weather from a specific location.\"\"\"\n",
" # This is a placeholder for the actual implementation\n",
" if any([city in location.lower() for city in [\"sf\", \"san francisco\"]]):\n",
" return \"It's sunny!\"\n",
" elif \"boston\" in location.lower():\n",
" return \"It's rainy!\"\n",
" else:\n",
" return f\"I am not sure what the weather is in {location}\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To reach out to a human for assistance, we can simply add a tool that calls [interrupt](../../concepts/human_in_the_loop/#interrupt):"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"from langgraph.types import Command, interrupt\n",
"\n",
"\n",
"@tool\n",
"def human_assistance(query: str) -> str:\n",
" \"\"\"Request assistance from a human.\"\"\"\n",
" human_response = interrupt({\"query\": query})\n",
" return human_response[\"data\"]\n",
"\n",
"\n",
"tools = [get_weather, human_assistance]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Define tasks\n",
"\n",
"Our tasks are otherwise unchanged from the [ReAct agent guide](../../how-tos/react-agent-from-scratch-functional):\n",
"\n",
"1. **Call model**: We want to query our chat model with a list of messages.\n",
"2. **Call tool**: If our model generates tool calls, we want to execute them.\n",
"\n",
"We just have one more tool accessible to the model."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.messages import ToolMessage\n",
"from langgraph.func import entrypoint, task\n",
"\n",
"tools_by_name = {tool.name: tool for tool in tools}\n",
"\n",
"\n",
"@task\n",
"def call_model(messages):\n",
" \"\"\"Call model with a sequence of messages.\"\"\"\n",
" response = model.bind_tools(tools).invoke(messages)\n",
" return response\n",
"\n",
"\n",
"@task\n",
"def call_tool(tool_call):\n",
" tool = tools_by_name[tool_call[\"name\"]]\n",
" observation = tool.invoke(tool_call)\n",
" return ToolMessage(content=observation, tool_call_id=tool_call[\"id\"])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Define entrypoint\n",
"\n",
"Our [entrypoint](../../concepts/functional_api/#entrypoint) is also unchanged from the [ReAct agent guide](../../how-tos/react-agent-from-scratch-functional):"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"from langgraph.checkpoint.memory import MemorySaver\n",
"from langgraph.graph.message import add_messages\n",
"\n",
"checkpointer = MemorySaver()\n",
"\n",
"\n",
"@entrypoint(checkpointer=checkpointer)\n",
"def agent(messages, previous):\n",
" if previous is not None:\n",
" messages = add_messages(previous, messages)\n",
"\n",
" llm_response = call_model(messages).result()\n",
" while True:\n",
" if not llm_response.tool_calls:\n",
" break\n",
"\n",
" # Execute tools\n",
" tool_result_futures = [\n",
" call_tool(tool_call) for tool_call in llm_response.tool_calls\n",
" ]\n",
" tool_results = [fut.result() for fut in tool_result_futures]\n",
"\n",
" # Append to message list\n",
" messages = add_messages(messages, [llm_response, *tool_results])\n",
"\n",
" # Call model again\n",
" llm_response = call_model(messages).result()\n",
"\n",
" # Generate final response\n",
" messages = add_messages(messages, llm_response)\n",
" return entrypoint.final(value=llm_response, save=messages)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Usage\n",
"\n",
"Let's invoke our model with a question that requires human assistance. Our question will also require an invocation of the `get_weather` tool:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"def _print_step(step: dict) -> None:\n",
" for task_name, result in step.items():\n",
" if task_name == \"agent\":\n",
" continue # just stream from tasks\n",
" print(f\"\\n{task_name}:\")\n",
" if task_name == \"__interrupt__\":\n",
" print(result)\n",
" else:\n",
" result.pretty_print()"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"config = {\"configurable\": {\"thread_id\": \"1\"}}"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'role': 'user', 'content': 'Can you reach out for human assistance: what should I feed my cat? Separately, can you check the weather in San Francisco?'}\n",
"\n",
"call_model:\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"Tool Calls:\n",
" human_assistance (call_joAEBVX7Abfm7TsZ0k95ZkVx)\n",
" Call ID: call_joAEBVX7Abfm7TsZ0k95ZkVx\n",
" Args:\n",
" query: What should I feed my cat?\n",
" get_weather (call_ut7zfHFCcms63BOZLrRHszGH)\n",
" Call ID: call_ut7zfHFCcms63BOZLrRHszGH\n",
" Args:\n",
" location: San Francisco\n",
"\n",
"call_tool:\n",
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
"\n",
"content=\"It's sunny!\" name='get_weather' tool_call_id='call_ut7zfHFCcms63BOZLrRHszGH'\n",
"\n",
"__interrupt__:\n",
"(Interrupt(value={'query': 'What should I feed my cat?'}, resumable=True, ns=['agent:aa676ccc-b038-25e3-9c8a-18e81d4e1372', 'call_tool:059d53d2-3344-13bc-e170-48b632c2dd97'], when='during'),)\n"
]
}
],
"source": [
"user_message = {\n",
" \"role\": \"user\",\n",
" \"content\": (\n",
" \"Can you reach out for human assistance: what should I feed my cat? \"\n",
" \"Separately, can you check the weather in San Francisco?\"\n",
" ),\n",
"}\n",
"print(user_message)\n",
"\n",
"for step in agent.stream([user_message], config):\n",
" _print_step(step)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that we generate two tool calls, and although our run is interrupted, we did not block the execution of the `get_weather` tool.\n",
"\n",
"Let's inspect where we're interrupted:"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'__interrupt__': (Interrupt(value={'query': 'What should I feed my cat?'}, resumable=True, ns=['agent:aa676ccc-b038-25e3-9c8a-18e81d4e1372', 'call_tool:059d53d2-3344-13bc-e170-48b632c2dd97'], when='during'),)}\n"
]
}
],
"source": [
"print(step)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can resume execution by issuing a [Command](../../concepts/human_in_the_loop/#the-command-primitive). Note that the data we supply in the `Command` can be customized to your needs based on the implementation of `human_assistance`."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"call_tool:\n",
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
"\n",
"content='You should feed your cat a fish.' name='human_assistance' tool_call_id='call_joAEBVX7Abfm7TsZ0k95ZkVx'\n",
"\n",
"call_model:\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"For human assistance, you should feed your cat fish. \n",
"\n",
"Regarding the weather in San Francisco, it's sunny!\n"
]
}
],
"source": [
"human_response = \"You should feed your cat a fish.\"\n",
"human_command = Command(resume={\"data\": human_response})\n",
"\n",
"for step in agent.stream(human_command, config):\n",
" _print_step(step)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Above, when we resume we provide the final tool message, allowing the model to generate its response. Check out the LangSmith traces to see a full breakdown of the runs:\n",
"\n",
"1. [Trace from initial query](https://smith.langchain.com/public/c3d8879d-4d01-41be-807e-6d9eed15df99/r)\n",
"2. [Trace after resuming](https://smith.langchain.com/public/97c05ef9-8b4c-428e-8826-3fd417c8c75f/r)"
]
}
],
"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": 4
}
-22
View File
@@ -1,22 +0,0 @@
---
search:
boost: 2
---
# Deployment 🚀
There are two free options for deploying LangGraph applications via the LangGraph Server:
- [Local](./langgraph-platform/local-server.md): Deploy for local testing and development.
- [Standalone Container (Lite)](../concepts/langgraph_standalone_container.md): A limited version of Standalone Container for deployments unlikely to see more that 1 million node executions per year and that do not need crons and other enterprise features. Standalone Container (Lite) deployment option is free with a LangSmith API key.
## Other deployment options
Additionally, you can deploy to production with [LangGraph Platform](../concepts/langgraph_platform.md):
- [Cloud SaaS](../concepts/langgraph_cloud.md): Connect your GitHub repositories and deploy LangGraph Servers within LangChain's cloud. *We manage everything.*
- [Self-Hosted Data Plane<sup>(Beta)</sup>](../concepts/langgraph_self_hosted_data_plane.md): Create deployments from the [Control Plane UI](../concepts/langgraph_control_plane.md#control-plane-ui) and deploy LangGraph Servers to **your** cloud. *We manage the [control plane](../concepts/langgraph_control_plane.md). You manage the deployments.*
- [Self-Hosted Control Plane<sup>(Beta)</sup>](../concepts/langgraph_self_hosted_control_plane.md): Create deployments from a self-hosted [Control Plane UI](../concepts/langgraph_control_plane.md#control-plane-ui) and deploy LangGraph Servers to **your** cloud. *You manage everything.*
- [Standalone Container](../concepts/langgraph_standalone_container.md): Deploy LangGraph Server Docker images however you like.
For more information, see [Deployment options](../concepts/deployment_options.md).
+3 -3
View File
@@ -89,7 +89,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 2,
"id": "baf669a0-04ee-492d-80d8-8fcb658ed128",
"metadata": {},
"outputs": [],
@@ -313,8 +313,8 @@
"\n",
" builder.add_edge(\"finalizer\", END)\n",
"\n",
" # These functions let the step be used in a\n",
" # StateGraph with 'messages' as the key.\n",
" # These functions let the step be used in a MessageGraph\n",
" # or a StateGraph with 'messages' as the key.\n",
" def encode(x: Union[Sequence[AnyMessage], PromptValue]) -> dict:\n",
" \"\"\"Ensure the input is the correct format.\"\"\"\n",
" if isinstance(x, PromptValue):\n",
@@ -1,6 +1,6 @@
# Build a basic chatbot
In this tutorial, you will build a basic chatbot. This chatbot is the basis for the following series of tutorials where you will progressively add more sophisticated capabilities, and be introduced to key LangGraph concepts along the way. Lets dive in! 🌟
In this tutorial, you will build a basic chatbot. This chatbot is the basis for the following series of tutorials where you will progressively add more sophisticated capabilities, and be introduced to key LangGraph concepts along the way. Let's dive in! 🌟
## Prerequisites
@@ -13,13 +13,44 @@ tool-calling features, such as [OpenAI](https://platform.openai.com/api-keys),
Install the required packages:
:::python
```bash
pip install -U langgraph langsmith
```
:::
:::js
=== "npm"
```bash
npm install @langchain/langgraph @langchain/core zod
```
=== "yarn"
```bash
yarn add @langchain/langgraph @langchain/core zod
```
=== "pnpm"
```bash
pnpm add @langchain/langgraph @langchain/core zod
```
=== "bun"
```bash
bun add @langchain/langgraph @langchain/core zod
```
:::
!!! tip
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. For more information on how to get started, see [LangSmith docs](https://docs.smith.langchain.com).
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. For more information on how to get started, see [LangSmith docs](https://docs.smith.langchain.com).
## 2. Create a `StateGraph`
@@ -27,6 +58,8 @@ Now you can create a basic chatbot using LangGraph. This chatbot will respond di
Start by creating a `StateGraph`. A `StateGraph` object defines the structure of our chatbot as a "state machine". We'll add `nodes` to represent the llm and functions our chatbot can call and `edges` to specify how the bot should transition between these functions.
:::python
```python
from typing import Annotated
@@ -46,23 +79,41 @@ class State(TypedDict):
graph_builder = StateGraph(State)
```
:::
:::js
```typescript
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
import { z } from "zod";
const State = z.object({ messages: MessagesZodState.shape.messages });
const graph = new StateGraph(State).compile();
```
:::
Our graph can now handle two key tasks:
1. Each `node` can receive the current `State` as input and output an update to the state.
2. Updates to `messages` will be appended to the existing list rather than overwriting it, thanks to the prebuilt [`add_messages`](https://langchain-ai.github.io/langgraph/reference/graphs/?h=add+messages#add_messages) function used with the `Annotated` syntax.
2. Updates to `messages` will be appended to the existing list rather than overwriting it, thanks to the prebuilt reducer function.
------
---
!!! tip "Concept"
When defining a graph, the first step is to define its `State`. The `State` includes the graph's schema and [reducer functions](https://langchain-ai.github.io/langgraph/concepts/low_level/#reducers) that handle state updates. In our example, `State` is a `TypedDict` with one key: `messages`. The [`add_messages`](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.message.add_messages) reducer function is used to append new messages to the list instead of overwriting it. Keys without a reducer annotation will overwrite previous values. To learn more about state, reducers, and related concepts, see [LangGraph reference docs](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.message.add_messages).
When defining a graph, the first step is to define its `State`. The `State` includes the graph's schema and [reducer functions](https://langchain-ai.github.io/langgraph/concepts/low_level/#reducers) that handle state updates. In our example, `State` is a schema with one key: `messages`. The reducer function is used to append new messages to the list instead of overwriting it. Keys without a reducer annotation will overwrite previous values.
To learn more about state, reducers, and related concepts, see [LangGraph reference docs](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.message.add_messages).
## 3. Add a node
Next, add a "`chatbot`" node. **Nodes** represent units of work and are typically regular Python functions.
Next, add a "`chatbot`" node. **Nodes** represent units of work and are typically regular functions.
Let's first select a chat model:
:::python
{!snippets/chat_model_tabs.md!}
<!---
@@ -73,9 +124,26 @@ llm = init_chat_model("anthropic:claude-3-5-sonnet-latest")
```
-->
:::
:::js
```typescript
import { ChatOpenAI } from "@langchain/openai";
// or import { ChatAnthropic } from "@langchain/anthropic";
const llm = new ChatOpenAI({
model: "gpt-4o",
temperature: 0,
});
```
:::
We can now incorporate the chat model into a simple node:
:::python
```python
def chatbot(state: State):
@@ -88,38 +156,132 @@ def chatbot(state: State):
graph_builder.add_node("chatbot", chatbot)
```
:::
:::js
```typescript hl_lines="7-9"
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
import { z } from "zod";
const State = z.object({ messages: MessagesZodState.shape.messages });
const graph = new StateGraph(State)
.addNode("chatbot", async (state: z.infer<typeof State>) => {
return { messages: [await llm.invoke(state.messages)] };
})
.compile();
```
:::
**Notice** how the `chatbot` node function takes the current `State` as input and returns a dictionary containing an updated `messages` list under the key "messages". This is the basic pattern for all LangGraph node functions.
:::python
The `add_messages` function in our `State` will append the LLM's response messages to whatever messages are already in the state.
:::
:::js
The `addMessages` function used within `MessagesZodState` will append the LLM's response messages to whatever messages are already in the state.
:::
## 4. Add an `entry` point
Add an `entry` point to tell the graph **where to start its work** each time it is run:
:::python
```python
graph_builder.add_edge(START, "chatbot")
```
:::
:::js
```typescript hl_lines="10"
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
import { z } from "zod";
const State = z.object({ messages: MessagesZodState.shape.messages });
const graph = new StateGraph(State)
.addNode("chatbot", async (state: z.infer<typeof State>) => {
return { messages: [await llm.invoke(state.messages)] };
})
.addEdge(START, "chatbot")
.compile();
```
:::
## 5. Add an `exit` point
Add an `exit` point to indicate **where the graph should finish execution**. This is helpful for more complex flows, but even in a simple graph like this, adding an end node improves clarity.
:::python
```python
graph_builder.add_edge("chatbot", END)
```
:::
:::js
```typescript hl_lines="11"
import { StateGraph, MessagesZodState, START, END } from "@langchain/langgraph";
import { z } from "zod";
const State = z.object({ messages: MessagesZodState.shape.messages });
const graph = new StateGraph(State)
.addNode("chatbot", async (state: z.infer<typeof State>) => {
return { messages: [await llm.invoke(state.messages)] };
})
.addEdge(START, "chatbot")
.addEdge("chatbot", END)
.compile();
```
:::
This tells the graph to terminate after running the chatbot node.
## 6. Compile the graph
Before running the graph, we'll need to compile it. We can do so by calling `compile()`
on the graph builder. This creates a `CompiledGraph` we can invoke on our state.
Before running the graph, we'll need to compile it. We can do so by calling `compile()` on the graph builder. This creates a `CompiledGraph` we can invoke on our state.
:::python
```python
graph = graph_builder.compile()
```
:::
:::js
```typescript hl_lines="12"
import { StateGraph, MessagesZodState, START, END } from "@langchain/langgraph";
import { z } from "zod";
const State = z.object({ messages: MessagesZodState.shape.messages });
const graph = new StateGraph(State)
.addNode("chatbot", async (state: z.infer<typeof State>) => {
return { messages: [await llm.invoke(state.messages)] };
})
.addEdge(START, "chatbot")
.addEdge("chatbot", END)
.compile();
```
:::
## 7. Visualize the graph (optional)
:::python
You can visualize the graph using the `get_graph` method and one of the "draw" methods, like `draw_ascii` or `draw_png`. The `draw` methods each require additional dependencies.
```python
@@ -132,17 +294,35 @@ except Exception:
pass
```
![basic chatbot diagram](basic-chatbot.png)
:::
:::js
You can visualize the graph using the `getGraph` method and render the graph with the `drawMermaidPng` method.
```typescript
import * as fs from "node:fs/promises";
const drawableGraph = await graph.getGraphAsync();
const image = await drawableGraph.drawMermaidPng();
const imageBuffer = new Uint8Array(await image.arrayBuffer());
await fs.writeFile("basic-chatbot.png", imageBuffer);
```
:::
![basic chatbot diagram](basic-chatbot.png)
## 8. Run the chatbot
Now run the chatbot!
Now run the chatbot!
!!! tip
You can exit the chat loop at any time by typing `quit`, `exit`, or `q`.
:::python
```python
def stream_graph_updates(user_input: str):
for event in graph.stream({"messages": [{"role": "user", "content": user_input}]}):
@@ -165,13 +345,66 @@ while True:
break
```
:::
:::js
```typescript
import * as readline from "node:readline/promises";
import { StateGraph, MessagesZodState, START, END } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
const llm = new ChatOpenAI({ model: "gpt-4o-mini" });
const State = z.object({ messages: MessagesZodState.shape.messages });
const graph = new StateGraph(State)
.addNode("chatbot", async (state: z.infer<typeof State>) => {
return { messages: [await llm.invoke(state.messages)] };
})
.addEdge(START, "chatbot")
.addEdge("chatbot", END)
.compile();
async function generateText(content: string) {
const stream = await graph.stream(
{ messages: [{ type: "human", content }] },
{ streamMode: "values" }
);
for await (const event of stream) {
const lastMessage = event.messages.at(-1);
if (lastMessage?.getType() === "ai") {
console.log(`Assistant: ${lastMessage.text}`);
}
}
}
const prompt = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
while (true) {
const human = await prompt.question("User: ");
if (["quit", "exit", "q"].includes(human.trim())) break;
await generateText(human || "What do you know about LangGraph?");
}
prompt.close();
```
:::
```
Assistant: LangGraph is a library designed to help build stateful multi-agent applications using language models. It provides tools for creating workflows and state machines to coordinate multiple AI agents or language model interactions. LangGraph is built on top of LangChain, leveraging its components while adding graph-based coordination capabilities. It's particularly useful for developing more complex, stateful AI applications that go beyond simple query-response interactions.
Goodbye!
```
**Congratulations!** You've built your first chatbot using LangGraph. This bot can engage in basic conversation by taking user input and generating responses using an LLM. You can inspect a [LangSmith Trace](https://smith.langchain.com/public/7527e308-9502-4894-b347-f34385740d5a/r) for the call above.
:::python
Below is the full code for this tutorial:
```python
@@ -207,8 +440,8 @@ graph_builder.add_edge("chatbot", END)
graph = graph_builder.compile()
```
:::
## Next steps
You may have noticed that the bot's knowledge is limited to what's in its training data. In the next part, we'll [add a web search tool](./2-add-tools.md) to expand the bot's knowledge and make it more capable.
+410 -7
View File
@@ -10,19 +10,64 @@ To handle queries that your chatbot can't answer "from memory", integrate a web
Before you start this tutorial, ensure you have the following:
:::python
- An API key for the [Tavily Search Engine](https://python.langchain.com/docs/integrations/tools/tavily_search/).
:::
:::js
- An API key for the [Tavily Search Engine](https://js.langchain.com/docs/integrations/tools/tavily_search/).
:::
## 1. Install the search engine
:::python
Install the requirements to use the [Tavily Search Engine](https://python.langchain.com/docs/integrations/tools/tavily_search/):
```bash
pip install -U langchain-tavily
```
:::
:::js
Install the requirements to use the [Tavily Search Engine](https://docs.tavily.com/):
=== "npm"
```bash
npm install @langchain/tavily
```
=== "yarn"
```bash
yarn add @langchain/tavily
```
=== "pnpm"
```bash
pnpm add @langchain/tavily
```
=== "bun"
```bash
bun add @langchain/tavily
```
:::
## 2. Configure your environment
Configure your environment with your search engine API key:
:::python
```bash
_set_env("TAVILY_API_KEY")
```
@@ -31,10 +76,22 @@ _set_env("TAVILY_API_KEY")
TAVILY_API_KEY: ········
```
:::
:::js
```typescript
process.env.TAVILY_API_KEY = "tvly-...";
```
:::
## 3. Define the tool
Define the web search tool:
:::python
```python
from langchain_tavily import TavilySearch
@@ -43,8 +100,25 @@ tools = [tool]
tool.invoke("What's a 'node' in LangGraph?")
```
:::
:::js
```typescript
import { TavilySearch } from "@langchain/tavily";
const tool = new TavilySearch({ maxResults: 2 });
const tools = [tool];
await tool.invoke({ query: "What's a 'node' in LangGraph?" });
```
:::
The results are page summaries our chat bot can use to answer questions:
:::python
```
{'query': "What's a 'node' in LangGraph?",
'follow_up_questions': None,
@@ -63,12 +137,51 @@ The results are page summaries our chat bot can use to answer questions:
'response_time': 1.38}
```
:::
:::js
```json
{
"query": "What's a 'node' in LangGraph?",
"follow_up_questions": null,
"answer": null,
"images": [],
"results": [
{
"url": "https://blog.langchain.dev/langgraph/",
"title": "LangGraph - LangChain Blog",
"content": "TL;DR: LangGraph is module built on top of LangChain to better enable creation of cyclical graphs, often needed for agent runtimes. This state is updated by nodes in the graph, which return operations to attributes of this state (in the form of a key-value store). After adding nodes, you can then add edges to create the graph. An example of this may be in the basic agent runtime, where we always want the model to be called after we call a tool. The state of this graph by default contains concepts that should be familiar to you if you've used LangChain agents: `input`, `chat_history`, `intermediate_steps` (and `agent_outcome` to represent the most recent agent outcome)",
"score": 0.7407191,
"raw_content": null
},
{
"url": "https://medium.com/@cplog/introduction-to-langgraph-a-beginners-guide-14f9be027141",
"title": "Introduction to LangGraph: A Beginner's Guide - Medium",
"content": "* **Stateful Graph:** LangGraph revolves around the concept of a stateful graph, where each node in the graph represents a step in your computation, and the graph maintains a state that is passed around and updated as the computation progresses. LangGraph supports conditional edges, allowing you to dynamically determine the next node to execute based on the current state of the graph. Image 10: Introduction to AI Agent with LangChain and LangGraph: A Beginners Guide Image 18: How to build LLM Agent with LangGraph — StateGraph and Reducer Image 20: Simplest Graphs using LangGraph Framework Image 24: Building a ReAct Agent with Langgraph: A Step-by-Step Guide Image 28: Building an Agentic RAG with LangGraph: A Step-by-Step Guide",
"score": 0.65279555,
"raw_content": null
}
],
"response_time": 1.34
}
```
:::
## 4. Define the graph
:::python
For the `StateGraph` you created in the [first tutorial](./1-build-basic-chatbot.md), add `bind_tools` on the LLM. This lets the LLM know the correct JSON format to use if it wants to use the search engine.
:::
:::js
For the `StateGraph` you created in the [first tutorial](./1-build-basic-chatbot.md), add `bindTools` on the LLM. This lets the LLM know the correct JSON format to use if it wants to use the search engine.
:::
Let's first select our LLM:
:::python
{!snippets/chat_model_tabs.md!}
<!---
@@ -79,8 +192,22 @@ llm = init_chat_model("anthropic:claude-3-5-sonnet-latest")
```
-->
:::
:::js
```typescript
import { ChatAnthropic } from "@langchain/anthropic";
const llm = new ChatAnthropic({ model: "claude-3-5-sonnet-latest" });
```
:::
We can now incorporate it into a `StateGraph`:
:::python
```python hl_lines="15"
from typing import Annotated
@@ -104,9 +231,31 @@ def chatbot(state: State):
graph_builder.add_node("chatbot", chatbot)
```
:::
:::js
```typescript hl_lines="7-8"
import { StateGraph, MessagesZodState } from "@langchain/langgraph";
import { z } from "zod";
const State = z.object({ messages: MessagesZodState.shape.messages });
const chatbot = async (state: z.infer<typeof State>) => {
// Modification: tell the LLM which tools it can call
const llmWithTools = llm.bindTools(tools);
return { messages: [await llmWithTools.invoke(state.messages)] };
};
```
:::
## 5. Create a function to run the tools
Now, create a function to run the tools if they are called. Do this by adding the tools to a new node called`BasicToolNode` that checks the most recent message in the state and calls tools if the message contains `tool_calls`. It relies on the LLM's `tool_calling` support, which is available in Anthropic, OpenAI, Google Gemini, and a number of other LLM providers.
:::python
Now, create a function to run the tools if they are called. Do this by adding the tools to a new node called `BasicToolNode` that checks the most recent message in the state and calls tools if the message contains `tool_calls`. It relies on the LLM's `tool_calling` support, which is available in Anthropic, OpenAI, Google Gemini, and a number of other LLM providers.
```python
import json
@@ -148,16 +297,80 @@ graph_builder.add_node("tools", tool_node)
If you do not want to build this yourself in the future, you can use LangGraph's prebuilt [ToolNode](https://langchain-ai.github.io/langgraph/reference/agents/#langgraph.prebuilt.tool_node.ToolNode).
:::
:::js
Now, create a function to run the tools if they are called. Do this by adding the tools to a new node called `"tools"` that checks the most recent message in the state and calls tools if the message contains `tool_calls`. It relies on the LLM's tool calling support, which is available in Anthropic, OpenAI, Google Gemini, and a number of other LLM providers.
```typescript
import type { StructuredToolInterface } from "@langchain/core/tools";
import { isAIMessage, ToolMessage } from "@langchain/core/messages";
function createToolNode(tools: StructuredToolInterface[]) {
const toolByName: Record<string, StructuredToolInterface> = {};
for (const tool of tools) {
toolByName[tool.name] = tool;
}
return async (inputs: z.infer<typeof State>) => {
const { messages } = inputs;
if (!messages || messages.length === 0) {
throw new Error("No message found in input");
}
const message = messages.at(-1);
if (!message || !isAIMessage(message) || !message.tool_calls) {
throw new Error("Last message is not an AI message with tool calls");
}
const outputs: ToolMessage[] = [];
for (const toolCall of message.tool_calls) {
if (!toolCall.id) throw new Error("Tool call ID is required");
const tool = toolByName[toolCall.name];
if (!tool) throw new Error(`Tool ${toolCall.name} not found`);
const result = await tool.invoke(toolCall.args);
outputs.push(
new ToolMessage({
content: JSON.stringify(result),
name: toolCall.name,
tool_call_id: toolCall.id,
})
);
}
return { messages: outputs };
};
}
```
!!! note
If you do not want to build this yourself in the future, you can use LangGraph's prebuilt [ToolNode](https://langchain-ai.github.io/langgraphjs/reference/classes/langgraph_prebuilt.ToolNode.html).
:::
## 6. Define the `conditional_edges`
With the tool node added, now you can define the `conditional_edges`.
With the tool node added, now you can define the `conditional_edges`.
**Edges** route the control flow from one node to the next. **Conditional edges** start from a single node and usually contain "if" statements to route to different nodes depending on the current graph state. These functions receive the current graph `state` and return a string or list of strings indicating which node(s) to call next.
Next, define a router function called `route_tools` that checks for `tool_calls` in the chatbot's output. Provide this function to the graph by calling `add_conditional_edges`, which tells the graph that whenever the `chatbot` node completes to check this function to see where to go next.
:::python
Next, define a router function called `route_tools` that checks for `tool_calls` in the chatbot's output. Provide this function to the graph by calling `add_conditional_edges`, which tells the graph that whenever the `chatbot` node completes to check this function to see where to go next.
:::
:::js
Next, define a router function called `routeTools` that checks for `tool_calls` in the chatbot's output. Provide this function to the graph by calling `addConditionalEdges`, which tells the graph that whenever the `chatbot` node completes to check this function to see where to go next.
:::
The condition will route to `tools` if tool calls are present and `END` if not. Because the condition can return `END`, you do not need to explicitly set a `finish_point` this time.
:::python
```python
def route_tools(
state: State,
@@ -197,10 +410,61 @@ graph = graph_builder.compile()
!!! note
You can replace this with the prebuilt [tools_condition](https://langchain-ai.github.io/langgraph/reference/prebuilt/#tools_condition) to be more concise.
You can replace this with the prebuilt [tools_condition](https://langchain-ai.github.io/langgraph/reference/prebuilt/#tools_condition) to be more concise.
:::
:::js
```typescript
import { END, START } from "@langchain/langgraph";
const routeTools = (state: z.infer<typeof State>) => {
/**
* Use as conditional edge to route to the ToolNode if the last message
* has tool calls.
*/
const lastMessage = state.messages.at(-1);
if (
lastMessage &&
isAIMessage(lastMessage) &&
lastMessage.tool_calls?.length
) {
return "tools";
}
/** Otherwise, route to the end. */
return END;
};
const graph = new StateGraph(State)
.addNode("chatbot", chatbot)
// The `routeTools` function returns "tools" if the chatbot asks to use a tool, and "END" if
// it is fine directly responding. This conditional routing defines the main agent loop.
.addNode("tools", createToolNode(tools))
// Start the graph with the chatbot
.addEdge(START, "chatbot")
// The `routeTools` function returns "tools" if the chatbot asks to use a tool, and "END" if
// it is fine directly responding.
.addConditionalEdges("chatbot", routeTools, ["tools", END])
// Any time a tool is called, we need to return to the chatbot
.addEdge("tools", "chatbot")
.compile();
```
!!! note
You can replace this with the prebuilt [toolsCondition](https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph_prebuilt.toolsCondition.html) to be more concise.
:::
## 7. Visualize the graph (optional)
:::python
You can visualize the graph using the `get_graph` method and one of the "draw" methods, like `draw_ascii` or `draw_png`. The `draw` methods each require additional dependencies.
```python
@@ -213,12 +477,31 @@ except Exception:
pass
```
:::
:::js
You can visualize the graph using the `getGraph` method and render the graph with the `drawMermaidPng` method.
```typescript
import * as fs from "node:fs/promises";
const drawableGraph = await graph.getGraphAsync();
const image = await drawableGraph.drawMermaidPng();
const imageBuffer = new Uint8Array(await image.arrayBuffer());
await fs.writeFile("chatbot-with-tools.png", imageBuffer);
```
:::
![chatbot-with-tools-diagram](chatbot-with-tools.png)
## 8. Ask the bot questions
Now you can ask the chatbot questions outside its training data:
:::python
```python
def stream_graph_updates(user_input: str):
for event in graph.stream({"messages": [{"role": "user", "content": user_input}]}):
@@ -241,7 +524,7 @@ while True:
break
```
```
```
Assistant: [{'text': "To provide you with accurate and up-to-date information about LangGraph, I'll need to search for the latest details. Let me do that for you.", 'type': 'text'}, {'id': 'toolu_01Q588CszHaSvvP2MxRq9zRD', 'input': {'query': 'LangGraph AI tool information'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
Assistant: [{"url": "https://www.langchain.com/langgraph", "content": "LangGraph sets the foundation for how we can build and scale AI workloads \u2014 from conversational agents, complex task automation, to custom LLM-backed experiences that 'just work'. The next chapter in building complex production-ready features with LLMs is agentic, and with LangGraph and LangSmith, LangChain delivers an out-of-the-box solution ..."}, {"url": "https://github.com/langchain-ai/langgraph", "content": "Overview. LangGraph is a library for building stateful, multi-actor applications with LLMs, used to create agent and multi-agent workflows. Compared to other LLM frameworks, it offers these core benefits: cycles, controllability, and persistence. LangGraph allows you to define flows that involve cycles, essential for most agentic architectures ..."}]
Assistant: Based on the search results, I can provide you with information about LangGraph:
@@ -272,18 +555,99 @@ Assistant: Based on the search results, I can provide you with information about
LangGraph appears to be a significant tool in the evolving landscape of LLM-based application development, offering developers new ways to create more complex, stateful, and interactive AI systems.
Goodbye!
Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings...
```
:::
:::js
```typescript
import readline from "node:readline/promises";
const prompt = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
async function generateText(content: string) {
const stream = await graph.stream(
{ messages: [{ type: "human", content }] },
{ streamMode: "values" }
);
for await (const event of stream) {
const lastMessage = event.messages.at(-1);
if (lastMessage?.getType() === "ai" || lastMessage?.getType() === "tool") {
console.log(`Assistant: ${lastMessage?.text}`);
}
}
}
while (true) {
const human = await prompt.question("User: ");
if (["quit", "exit", "q"].includes(human.trim())) break;
await generateText(human || "What do you know about LangGraph?");
}
prompt.close();
```
```
User: What do you know about LangGraph?
Assistant: I'll search for the latest information about LangGraph for you.
Assistant: [{"title":"Introduction to LangGraph: A Beginner's Guide - Medium","url":"https://medium.com/@cplog/introduction-to-langgraph-a-beginners-guide-14f9be027141","content":"..."}]
Assistant: Based on the search results, I can provide you with information about LangGraph:
LangGraph is a library within the LangChain ecosystem designed for building stateful, multi-actor applications with Large Language Models (LLMs). Here are the key aspects:
**Core Purpose:**
- LangGraph is specifically designed for creating agent and multi-agent workflows
- It provides a framework for defining, coordinating, and executing multiple LLM agents in a structured manner
**Key Features:**
1. **Stateful Graph Architecture**: LangGraph revolves around a stateful graph where each node represents a step in computation, and the graph maintains state that is passed around and updated as the computation progresses
2. **Conditional Edges**: It supports conditional edges, allowing you to dynamically determine the next node to execute based on the current state of the graph
3. **Cycles**: Unlike other LLM frameworks, LangGraph allows you to define flows that involve cycles, which is essential for most agentic architectures
4. **Controllability**: It offers enhanced control over the application flow
5. **Persistence**: The library provides ways to maintain state and persistence in LLM-based applications
**Use Cases:**
- Conversational agents
- Complex task automation
- Custom LLM-backed experiences
- Multi-agent systems that perform complex tasks
**Benefits:**
LangGraph allows developers to focus on the high-level logic of their applications rather than the intricacies of agent coordination, making it easier to build complex, production-ready features with LLMs.
This makes LangGraph a significant tool in the evolving landscape of LLM-based application development.
```
:::
## 9. Use prebuilts
For ease of use, adjust your code to replace the following with LangGraph prebuilt components. These have built in functionality like parallel API execution.
:::python
- `BasicToolNode` is replaced with the prebuilt [ToolNode](https://langchain-ai.github.io/langgraph/reference/prebuilt/#toolnode)
- `route_tools` is replaced with the prebuilt [tools_condition](https://langchain-ai.github.io/langgraph/reference/prebuilt/#tools_condition)
{!snippets/chat_model_tabs.md!}
<!---
```python
from langchain.chat_models import init_chat_model
llm = init_chat_model("anthropic:claude-3-5-sonnet-latest")
```
-->
```python hl_lines="25 30"
from typing import Annotated
@@ -323,7 +687,46 @@ graph_builder.add_edge(START, "chatbot")
graph = graph_builder.compile()
```
**Congratulations!** You've created a conversational agent in LangGraph that can use a search engine to retrieve updated information when needed. Now it can handle a wider range of user queries. To inspect all the steps your agent just took, check out this [LangSmith trace](https://smith.langchain.com/public/4fbd7636-25af-4638-9587-5a02fdbb0172/r).
:::
:::js
- `createToolNode` is replaced with the prebuilt [ToolNode](https://langchain-ai.github.io/langgraphjs/reference/classes/langgraph_prebuilt.ToolNode.html)
- `routeTools` is replaced with the prebuilt [toolsCondition](https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph_prebuilt.toolsCondition.html)
```typescript
import { TavilySearch } from "@langchain/tavily";
import { ChatOpenAI } from "@langchain/openai";
import { StateGraph, START, MessagesZodState, END } from "@langchain/langgraph";
import { ToolNode, toolsCondition } from "@langchain/langgraph/prebuilt";
import { z } from "zod";
const State = z.object({ messages: MessagesZodState.shape.messages });
const tools = [new TavilySearch({ maxResults: 2 })];
const llm = new ChatOpenAI({ model: "gpt-4o-mini" }).bindTools(tools);
const graph = new StateGraph(State)
.addNode("chatbot", async (state) => ({
messages: [await llm.invoke(state.messages)],
}))
.addNode("tools", new ToolNode(tools))
.addConditionalEdges("chatbot", toolsCondition, ["tools", END])
.addEdge("tools", "chatbot")
.addEdge(START, "chatbot")
.compile();
```
:::
**Congratulations!** You've created a conversational agent in LangGraph that can use a search engine to retrieve updated information when needed. Now it can handle a wider range of user queries.
:::python
To inspect all the steps your agent just took, check out this [LangSmith trace](https://smith.langchain.com/public/4fbd7636-25af-4638-9587-5a02fdbb0172/r).
:::
## Next steps
+253 -14
View File
@@ -2,7 +2,7 @@
The chatbot can now [use tools](./2-add-tools.md) to answer user questions, but it does not remember the context of previous interactions. This limits its ability to have coherent, multi-turn conversations.
LangGraph solves this problem through **persistent checkpointing**. If you provide a `checkpointer` when compiling the graph and a `thread_id` when calling your graph, LangGraph automatically saves the state after each step. When you invoke the graph again using the same `thread_id`, the graph loads its saved state, allowing the chatbot to pick up where it left off.
LangGraph solves this problem through **persistent checkpointing**. If you provide a `checkpointer` when compiling the graph and a `thread_id` when calling your graph, LangGraph automatically saves the state after each step. When you invoke the graph again using the same `thread_id`, the graph loads its saved state, allowing the chatbot to pick up where it left off.
We will see later that **checkpointing** is _much_ more powerful than simple chat memory - it lets you save and resume complex state at any time for error recovery, human-in-the-loop workflows, time travel interactions, and more. But first, let's add checkpointing to enable multi-turn conversations.
@@ -14,43 +14,79 @@ We will see later that **checkpointing** is _much_ more powerful than simple cha
Create a `MemorySaver` checkpointer:
``` python
:::python
```python
from langgraph.checkpoint.memory import MemorySaver
memory = MemorySaver()
```
:::
:::js
```typescript
import { MemorySaver } from "@langchain/langgraph";
const memory = new MemorySaver();
```
:::
This is in-memory checkpointer, which is convenient for the tutorial. However, in a production application, you would likely change this to use `SqliteSaver` or `PostgresSaver` and connect a database.
## 2. Compile the graph
Compile the graph with the provided checkpointer, which will checkpoint the `State` as the graph works through each node:
``` python
:::python
```python
graph = graph_builder.compile(checkpointer=memory)
```
``` python
from IPython.display import Image, display
:::
try:
display(Image(graph.get_graph().draw_mermaid_png()))
except Exception:
# This requires some extra dependencies and is optional
pass
:::js
```typescript hl_lines="7"
const graph = new StateGraph(State)
.addNode("chatbot", chatbot)
.addNode("tools", new ToolNode(tools))
.addConditionalEdges("chatbot", toolsCondition, ["tools", END])
.addEdge("tools", "chatbot")
.addEdge(START, "chatbot")
.compile({ checkpointer: memory });
```
:::
## 3. Interact with your chatbot
Now you can interact with your bot!
1. Pick a thread to use as the key for this conversation.
1. Pick a thread to use as the key for this conversation.
:::python
```python
config = {"configurable": {"thread_id": "1"}}
```
2. Call your chatbot:
:::
:::js
```typescript
const config = { configurable: { thread_id: "1" } };
```
:::
2. Call your chatbot:
:::python
```python
user_input = "Hi there! My name is Will."
@@ -74,14 +110,45 @@ Now you can interact with your bot!
Hello Will! It's nice to meet you. How can I assist you today? Is there anything specific you'd like to know or discuss?
```
!!! note
!!! note
The config was provided as the **second positional argument** when calling our graph. It importantly is _not_ nested within the graph inputs (`{'messages': []}`).
:::
:::js
```typescript
const userInput = "Hi there! My name is Will.";
const events = await graph.stream(
{ messages: [{ type: "human", content: userInput }] },
{ configurable: { thread_id: "1" }, streamMode: "values" }
);
for await (const event of events) {
const lastMessage = event.messages.at(-1);
console.log(`${lastMessage?.getType()}: ${lastMessage?.text}`);
}
```
```
human: Hi there! My name is Will.
ai: Hello Will! It's nice to meet you. How can I assist you today? Is there anything specific you'd like to know or discuss?
```
!!! note
The config was provided as the **second parameter** when calling our graph. It importantly is _not_ nested within the graph inputs (`{"messages": []}`).
:::
## 4. Ask a follow up question
Ask a follow up question:
:::python
```python
user_input = "Remember my name?"
@@ -104,10 +171,37 @@ Remember my name?
Of course, I remember your name, Will. I always try to pay attention to important details that users share with me. Is there anything else you'd like to talk about or any questions you have? I'm here to help with a wide range of topics or tasks.
```
:::
:::js
```typescript
const userInput2 = "Remember my name?";
const events2 = await graph.stream(
{ messages: [{ type: "human", content: userInput2 }] },
{ configurable: { thread_id: "1" }, streamMode: "values" }
);
for await (const event of events2) {
const lastMessage = event.messages.at(-1);
console.log(`${lastMessage?.getType()}: ${lastMessage?.text}`);
}
```
```
human: Remember my name?
ai: Yes, your name is Will. How can I help you today?
```
:::
**Notice** that we aren't using an external list for memory: it's all handled by the checkpointer! You can inspect the full execution in this [LangSmith trace](https://smith.langchain.com/public/29ba22b5-6d40-4fbe-8d27-b369e3329c84/r) to see what's going on.
Don't believe me? Try this using a different config.
:::python
```python
# The only difference is we change the `thread_id` here to "2" instead of "1"
events = graph.stream(
@@ -129,10 +223,36 @@ Remember my name?
I apologize, but I don't have any previous context or memory of your name. As an AI assistant, I don't retain information from past conversations. Each interaction starts fresh. Could you please tell me your name so I can address you properly in this conversation?
```
:::
:::js
```typescript hl_lines="3-4"
const events3 = await graph.stream(
{ messages: [{ type: "human", content: userInput2 }] },
// The only difference is we change the `thread_id` here to "2" instead of "1"
{ configurable: { thread_id: "2" }, streamMode: "values" }
);
for await (const event of events3) {
const lastMessage = event.messages.at(-1);
console.log(`${lastMessage?.getType()}: ${lastMessage?.text}`);
}
```
```
human: Remember my name?
ai: I don't have the ability to remember personal information about users between interactions. However, I'm here to help you with any questions or topics you want to discuss!
```
:::
**Notice** that the **only** change we've made is to modify the `thread_id` in the config. See this call's [LangSmith trace](https://smith.langchain.com/public/51a62351-2f0a-4058-91cc-9996c5561428/r) for comparison.
## 5. Inspect the state
:::python
By now, we have made a few checkpoints across two different threads. But what goes into a checkpoint? To inspect a graph's `state` for a given config at any time, call `get_state(config)`.
```python
@@ -148,12 +268,94 @@ StateSnapshot(values={'messages': [HumanMessage(content='Hi there! My name is Wi
snapshot.next # (since the graph ended this turn, `next` is empty. If you fetch a state from within a graph invocation, next tells which node will execute next)
```
:::
:::js
By now, we have made a few checkpoints across two different threads. But what goes into a checkpoint? To inspect a graph's `state` for a given config at any time, call `getState(config)`.
```typescript
await graph.getState({ configurable: { thread_id: "1" } });
```
```typescript
{
values: {
messages: [
HumanMessage {
"id": "32fabcef-b3b8-481f-8bcb-fd83399a5f8d",
"content": "Hi there! My name is Will.",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"id": "chatcmpl-BrPbTsCJbVqBvXWySlYoTJvM75Kv8",
"content": "Hello Will! How can I assist you today?",
"additional_kwargs": {},
"response_metadata": {},
"tool_calls": [],
"invalid_tool_calls": []
},
HumanMessage {
"id": "561c3aad-f8fc-4fac-94a6-54269a220856",
"content": "Remember my name?",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"id": "chatcmpl-BrPbU4BhhsUikGbW37hYuF5vvnnE2",
"content": "Yes, I remember your name, Will! How can I help you today?",
"additional_kwargs": {},
"response_metadata": {},
"tool_calls": [],
"invalid_tool_calls": []
}
]
},
next: [],
tasks: [],
metadata: {
source: 'loop',
step: 4,
parents: {},
thread_id: '1'
},
config: {
configurable: {
thread_id: '1',
checkpoint_id: '1f05cccc-9bb6-6270-8004-1d2108bcec77',
checkpoint_ns: ''
}
},
createdAt: '2025-07-09T13:58:27.607Z',
parentConfig: {
configurable: {
thread_id: '1',
checkpoint_ns: '',
checkpoint_id: '1f05cccc-78fa-68d0-8003-ffb01a76b599'
}
}
}
```
```typescript
import * as assert from "node:assert";
// Since the graph ended this turn, `next` is empty.
// If you fetch a state from within a graph invocation, next tells which node will execute next)
assert.deepEqual(snapshot.next, []);
```
:::
The snapshot above contains the current state values, corresponding config, and the `next` node to process. In our case, the graph has reached an `END` state, so `next` is empty.
**Congratulations!** Your chatbot can now maintain conversation state across sessions thanks to LangGraph's checkpointing system. This opens up exciting possibilities for more natural, contextual interactions. LangGraph's checkpointing even handles **arbitrarily complex graph states**, which is much more expressive and powerful than simple chat memory.
Check out the code snippet below to review the graph from this tutorial:
:::python
{!snippets/chat_model_tabs.md!}
<!---
@@ -204,6 +406,43 @@ memory = MemorySaver()
graph = graph_builder.compile(checkpointer=memory)
```
:::
:::js
```typescript hl_lines="16 26"
import { END, MessagesZodState, START } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
import { TavilySearch } from "@langchain/tavily";
import { MemorySaver } from "@langchain/langgraph";
import { StateGraph } from "@langchain/langgraph";
import { ToolNode, toolsCondition } from "@langchain/langgraph/prebuilt";
import { z } from "zod";
const State = z.object({
messages: MessagesZodState.shape.messages,
});
const tools = [new TavilySearch({ maxResults: 2 })];
const llm = new ChatOpenAI({ model: "gpt-4o-mini" }).bindTools(tools);
// highlight-next-line
const memory = new MemorySaver();
const graph = new StateGraph(State)
.addNode("chatbot", async (state) => ({
messages: [await llm.invoke(state.messages)],
}))
.addNode("tools", new ToolNode(tools))
.addConditionalEdges("chatbot", toolsCondition, ["tools", END])
.addEdge("tools", "chatbot")
.addEdge(START, "chatbot")
// highlight-next-line
.compile({ checkpointer: memory });
```
:::
## Next steps
In the next tutorial, you will [add human-in-the-loop to the chatbot](./4-human-in-the-loop.md) to handle situations where it may need guidance or verification before proceeding.
@@ -2,7 +2,15 @@
Agents can be unreliable and may need human input to successfully accomplish tasks. Similarly, for some actions, you may want to require human approval before running to ensure that everything is running as intended.
LangGraph's [persistence](../../concepts/persistence.md) layer supports **human-in-the-loop** workflows, allowing execution to pause and resume based on user feedback. The primary interface to this functionality is the [`interrupt`](../../how-tos/human_in_the_loop/add-human-in-the-loop.md) function. Calling `interrupt` inside a node will pause execution. Execution can be resumed, together with new input from a human, by passing in a [Command](../../concepts/low_level.md#command). `interrupt` is ergonomically similar to Python's built-in `input()`, [with some caveats](../../how-tos/human_in_the_loop/add-human-in-the-loop.md).
LangGraph's [persistence](../../concepts/persistence.md) layer supports **human-in-the-loop** workflows, allowing execution to pause and resume based on user feedback. The primary interface to this functionality is the [`interrupt`](../../how-tos/human_in_the_loop/add-human-in-the-loop.md) function. Calling `interrupt` inside a node will pause execution. Execution can be resumed, together with new input from a human, by passing in a [Command](../../concepts/low_level.md#command).
:::python
`interrupt` is ergonomically similar to Python's built-in `input()`, [with some caveats](../../how-tos/human_in_the_loop/add-human-in-the-loop.md).
:::
:::js
`interrupt` is ergonomically similar to Node.js's built-in `readline.question()` function, [with some caveats](../../how-tos/human_in_the_loop/add-human-in-the-loop.md).
:::
!!! note
@@ -14,6 +22,7 @@ Starting with the existing code from the [Add memory to the chatbot](./3-add-mem
Let's first select a chat model:
:::python
{!snippets/chat_model_tabs.md!}
<!---
@@ -24,9 +33,22 @@ llm = init_chat_model("anthropic:claude-3-5-sonnet-latest")
```
-->
:::
:::js
```typescript
// Add your API key here
process.env.ANTHROPIC_API_KEY = "YOUR_API_KEY";
```
:::
We can now incorporate it into our `StateGraph` with an additional tool:
``` python hl_lines="12 19 20 21 22 23"
:::python
```python hl_lines="12 19 20 21 22 23"
from typing import Annotated
from langchain_tavily import TavilySearch
@@ -76,25 +98,97 @@ graph_builder.add_edge("tools", "chatbot")
graph_builder.add_edge(START, "chatbot")
```
:::
:::js
```typescript hl_lines="1 7-19"
import { interrupt, MessagesZodState } from "@langchain/langgraph";
import { ChatAnthropic } from "@langchain/anthropic";
import { TavilySearch } from "@langchain/tavily";
import { tool } from "@langchain/core/tools";
import { z } from "zod";
const humanAssistance = tool(
async ({ query }) => {
const humanResponse = interrupt({ query });
return humanResponse.data;
},
{
name: "humanAssistance",
description: "Request assistance from a human.",
schema: z.object({
query: z.string().describe("Human readable question for the human"),
}),
}
);
const searchTool = new TavilySearch({ maxResults: 2 });
const tools = [searchTool, humanAssistance];
const llmWithTools = new ChatAnthropic({
model: "claude-3-5-sonnet-latest",
}).bindTools(tools);
async function chatbot(state: z.infer<typeof MessagesZodState>) {
const message = await llmWithTools.invoke(state.messages);
// Because we will be interrupting during tool execution,
// we disable parallel tool calling to avoid repeating any
// tool invocations when we resume.
if (message.tool_calls && message.tool_calls.length > 1) {
throw new Error("Multiple tool calls not supported with interrupts");
}
return { messages: message };
}
```
:::
!!! tip
For more information and examples of human-in-the-loop workflows, see [Human-in-the-loop](../../concepts/human_in_the_loop.md). This includes how to [review and edit tool calls](../../how-tos/human_in_the_loop/review-tool-calls.ipynb) before they are executed.
For more information and examples of human-in-the-loop workflows, see [Human-in-the-loop](../../concepts/human_in_the_loop.md).
## 2. Compile the graph
We compile the graph with a checkpointer, as before:
:::python
```python
memory = MemorySaver()
graph = graph_builder.compile(checkpointer=memory)
```
:::
:::js
```typescript hl_lines="3 11"
import { StateGraph, MemorySaver, START, END } from "@langchain/langgraph";
const memory = new MemorySaver();
const graph = new StateGraph(MessagesZodState)
.addNode("chatbot", chatbot)
.addNode("tools", new ToolNode(tools))
.addConditionalEdges("chatbot", toolsCondition, ["tools", END])
.addEdge("tools", "chatbot")
.addEdge(START, "chatbot")
.compile({ checkpointer: memory });
```
:::
## 3. Visualize the graph (optional)
Visualizing the graph, you get the same layout as before just with the added tool!
``` python
:::python
```python
from IPython.display import Image, display
try:
@@ -104,12 +198,30 @@ except Exception:
pass
```
:::
:::js
```typescript
import * as fs from "node:fs/promises";
const drawableGraph = await graph.getGraphAsync();
const image = await drawableGraph.drawMermaidPng();
const imageBuffer = new Uint8Array(await image.arrayBuffer());
await fs.writeFile("chatbot-with-tools.png", imageBuffer);
```
:::
![chatbot-with-tools-diagram](chatbot-with-tools.png)
## 4. Prompt the chatbot
Now, prompt the chatbot with a question that will engage the new `human_assistance` tool:
:::python
```python
user_input = "I need some expert guidance for building an AI agent. Could you request assistance for me?"
config = {"configurable": {"thread_id": "1"}}
@@ -138,8 +250,58 @@ Tool Calls:
query: A user is requesting expert guidance for building an AI agent. Could you please provide some expert advice or resources on this topic?
```
:::
:::js
```typescript
import { isAIMessage } from "@langchain/core/messages";
const userInput =
"I need some expert guidance for building an AI agent. Could you request assistance for me?";
const events = await graph.stream(
{ messages: [{ role: "user", content: userInput }] },
{ configurable: { thread_id: "1" }, streamMode: "values" }
);
for await (const event of events) {
if ("messages" in event) {
const lastMessage = event.messages.at(-1);
console.log(`[${lastMessage?.getType()}]: ${lastMessage?.text}`);
if (
lastMessage &&
isAIMessage(lastMessage) &&
lastMessage.tool_calls?.length
) {
console.log("Tool calls:", lastMessage.tool_calls);
}
}
}
```
```
[human]: I need some expert guidance for building an AI agent. Could you request assistance for me?
[ai]: I'll help you request human assistance for guidance on building an AI agent.
Tool calls: [
{
name: 'humanAssistance',
args: {
query: 'I would like expert guidance on building an AI agent. Could you please provide assistance with this topic?'
},
id: 'toolu_01Bpxc8rFVMhSaRosS6b85Ts',
type: 'tool_call'
}
]
```
:::
The chatbot generated a tool call, but then execution has been interrupted. If you inspect the graph state, you see that it stopped at the tools node:
:::python
```python
snapshot = graph.get_state(config)
snapshot.next
@@ -149,8 +311,25 @@ snapshot.next
('tools',)
```
:::
:::js
```typescript
const snapshot = await graph.getState({ configurable: { thread_id: "1" } });
snapshot.next;
```
```json
["tools"]
```
:::
!!! info Additional information
:::python
Take a closer look at the `human_assistance` tool:
```python
@@ -162,12 +341,40 @@ snapshot.next
```
Similar to Python's built-in `input()` function, calling `interrupt` inside the tool will pause execution. Progress is persisted based on the [checkpointer](../../concepts/persistence.md#checkpointer-libraries); so if it is persisting with Postgres, it can resume at any time as long as the database is alive. In this example, it is persisting with the in-memory checkpointer and can resume any time if the Python kernel is running.
:::
:::js
Take a closer look at the `humanAssistance` tool:
```typescript hl_lines="3"
const humanAssistance = tool(
async ({ query }) => {
const humanResponse = interrupt({ query });
return humanResponse.data;
},
{
name: "humanAssistance",
description: "Request assistance from a human.",
schema: z.object({
query: z.string().describe("Human readable question for the human"),
}),
},
);
```
Calling `interrupt` inside the tool will pause execution. Progress is persisted based on the [checkpointer](../../concepts/persistence.md#checkpointer-libraries); so if it is persisting with Postgres, it can resume at any time as long as the database is alive. In this example, it is persisting with the in-memory checkpointer and can resume any time if the JavaScript runtime is running.
:::
## 5. Resume execution
To resume execution, pass a [`Command`](../../concepts/low_level.md#command) object containing data expected by the tool. The format of this data can be customized based on needs. For this example, use a dict with a key `"data"`:
To resume execution, pass a [`Command`](../../concepts/low_level.md#command) object containing data expected by the tool. The format of this data can be customized based on needs.
``` python
:::python
For this example, use a dict with a key `"data"`:
```python
human_response = (
"We, the experts are here to help! We'd recommend you check out LangGraph to build your agent."
" It's much more reliable and extensible than simple autonomous agents."
@@ -215,12 +422,60 @@ If you'd like more specific information about LangGraph or have any questions ab
Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings...
```
:::
:::js
For this example, use an object with a key `"data"`:
```typescript
import { Command } from "@langchain/langgraph";
const humanResponse =
"We, the experts are here to help! We'd recommend you check out LangGraph to build your agent." +
" It's much more reliable and extensible than simple autonomous agents.";
const humanCommand = new Command({ resume: { data: humanResponse } });
const resumeEvents = await graph.stream(humanCommand, {
configurable: { thread_id: "1" },
streamMode: "values",
});
for await (const event of resumeEvents) {
if ("messages" in event) {
const lastMessage = event.messages.at(-1);
console.log(`[${lastMessage?.getType()}]: ${lastMessage?.text}`);
}
}
```
```
[tool]: We, the experts are here to help! We'd recommend you check out LangGraph to build your agent. It's much more reliable and extensible than simple autonomous agents.
[ai]: Thank you for your patience. I've received some expert advice regarding your request for guidance on building an AI agent. Here's what the experts have suggested:
The experts recommend that you look into LangGraph for building your AI agent. They mention that LangGraph is a more reliable and extensible option compared to simple autonomous agents.
LangGraph is likely a framework or library designed specifically for creating AI agents with advanced capabilities. Here are a few points to consider based on this recommendation:
1. Reliability: The experts emphasize that LangGraph is more reliable than simpler autonomous agent approaches. This could mean it has better stability, error handling, or consistent performance.
2. Extensibility: LangGraph is described as more extensible, which suggests that it probably offers a flexible architecture that allows you to easily add new features or modify existing ones as your agent's requirements evolve.
3. Advanced capabilities: Given that it's recommended over "simple autonomous agents," LangGraph likely provides more sophisticated tools and techniques for building complex AI agents.
...
```
:::
The input has been received and processed as a tool message. Review this call's [LangSmith trace](https://smith.langchain.com/public/9f0f87e3-56a7-4dde-9c76-b71675624e91/r) to see the exact work that was done in the above call. Notice that the state is loaded in the first step so that our chatbot can continue where it left off.
**Congratulations!** You've used an `interrupt` to add human-in-the-loop execution to your chatbot, allowing for human oversight and intervention when needed. This opens up the potential UIs you can create with your AI systems. Since you have already added a **checkpointer**, as long as the underlying persistence layer is running, the graph can be paused **indefinitely** and resumed at any time as if nothing had happened.
Check out the code snippet below to review the graph from this tutorial:
:::python
{!snippets/chat_model_tabs.md!}
```python
@@ -272,6 +527,73 @@ memory = MemorySaver()
graph = graph_builder.compile(checkpointer=memory)
```
:::
:::js
```typescript
import {
interrupt,
MessagesZodState,
StateGraph,
MemorySaver,
START,
END,
} from "@langchain/langgraph";
import { ToolNode, toolsCondition } from "@langchain/langgraph/prebuilt";
import { isAIMessage } from "@langchain/core/messages";
import { ChatAnthropic } from "@langchain/anthropic";
import { TavilySearch } from "@langchain/tavily";
import { tool } from "@langchain/core/tools";
import { z } from "zod";
const humanAssistance = tool(
async ({ query }) => {
const humanResponse = interrupt({ query });
return humanResponse.data;
},
{
name: "humanAssistance",
description: "Request assistance from a human.",
schema: z.object({
query: z.string().describe("Human readable question for the human"),
}),
}
);
const searchTool = new TavilySearch({ maxResults: 2 });
const tools = [searchTool, humanAssistance];
const llmWithTools = new ChatAnthropic({
model: "claude-3-5-sonnet-latest",
}).bindTools(tools);
const chatbot = async (state: z.infer<typeof MessagesZodState>) => {
const message = await llmWithTools.invoke(state.messages);
// Because we will be interrupting during tool execution,
// we disable parallel tool calling to avoid repeating any
// tool invocations when we resume.
if (message.tool_calls && message.tool_calls.length > 1) {
throw new Error("Multiple tool calls not supported with interrupts");
}
return { messages: message };
};
const memory = new MemorySaver();
const graph = new StateGraph(MessagesZodState)
.addNode("chatbot", chatbot)
.addNode("tools", new ToolNode(tools))
.addConditionalEdges("chatbot", toolsCondition, ["tools", END])
.addEdge("tools", "chatbot")
.addEdge(START, "chatbot")
.compile({ checkpointer: memory });
```
:::
## Next steps
So far, the tutorial examples have relied on a simple state with one entry: a list of messages. You can go far with this simple state, but if you want to define complex behavior without relying on the message list, you can [add additional fields to the state](./5-customize-state.md).
So far, the tutorial examples have relied on a simple state with one entry: a list of messages. You can go far with this simple state, but if you want to define complex behavior without relying on the message list, you can [add additional fields to the state](./5-customize-state.md).
@@ -10,6 +10,8 @@ In this tutorial, you will add additional fields to the state to define complex
Update the chatbot to research the birthday of an entity by adding `name` and `birthday` keys to the state:
:::python
```python
from typing import Annotated
@@ -26,13 +28,34 @@ class State(TypedDict):
birthday: str
```
:::
:::js
```typescript
import { MessagesZodState } from "@langchain/langgraph";
import { z } from "zod";
const State = z.object({
messages: MessagesZodState.shape.messages,
// highlight-next-line
name: z.string(),
// highlight-next-line
birthday: z.string(),
});
```
:::
Adding this information to the state makes it easily accessible by other graph nodes (like a downstream node that stores or processes the information), as well as the graph's persistence layer.
## 2. Update the state inside the tool
:::python
Now, populate the state keys inside of the `human_assistance` tool. This allows a human to review the information before it is stored in the state. Use [`Command`](../../concepts/low_level.md#using-inside-tools) to issue a state update from inside the tool.
``` python
```python
from langchain_core.messages import ToolMessage
from langchain_core.tools import InjectedToolCallId, tool
@@ -76,10 +99,78 @@ def human_assistance(
return Command(update=state_update)
```
:::
:::js
Now, populate the state keys inside of the `humanAssistance` tool. This allows a human to review the information before it is stored in the state. Use [`Command`](../../concepts/low_level.md#using-inside-tools) to issue a state update from inside the tool.
```typescript
import { tool } from "@langchain/core/tools";
import { ToolMessage } from "@langchain/core/messages";
import { Command, interrupt } from "@langchain/langgraph";
const humanAssistance = tool(
async (input, config) => {
// Note that because we are generating a ToolMessage for a state update,
// we generally require the ID of the corresponding tool call.
// This is available in the tool's config.
const toolCallId = config?.toolCall?.id as string | undefined;
if (!toolCallId) throw new Error("Tool call ID is required");
const humanResponse = await interrupt({
question: "Is this correct?",
name: input.name,
birthday: input.birthday,
});
// We explicitly update the state with a ToolMessage inside the tool.
const stateUpdate = (() => {
// If the information is correct, update the state as-is.
if (humanResponse.correct?.toLowerCase().startsWith("y")) {
return {
name: input.name,
birthday: input.birthday,
messages: [
new ToolMessage({ content: "Correct", tool_call_id: toolCallId }),
],
};
}
// Otherwise, receive information from the human reviewer.
return {
name: humanResponse.name || input.name,
birthday: humanResponse.birthday || input.birthday,
messages: [
new ToolMessage({
content: `Made a correction: ${JSON.stringify(humanResponse)}`,
tool_call_id: toolCallId,
}),
],
};
})();
// We return a Command object in the tool to update our state.
return new Command({ update: stateUpdate });
},
{
name: "humanAssistance",
description: "Request assistance from a human.",
schema: z.object({
name: z.string().describe("The name of the entity"),
birthday: z.string().describe("The birthday/release date of the entity"),
}),
}
);
```
:::
The rest of the graph stays the same.
## 3. Prompt the chatbot
:::python
Prompt the chatbot to look up the "birthday" of the LangGraph library and direct the chatbot to reach out to the `human_assistance` tool once it has the required information. By setting `name` and `birthday` in the arguments for the tool, you force the chatbot to generate proposals for these fields.
```python
@@ -99,6 +190,51 @@ for event in events:
event["messages"][-1].pretty_print()
```
:::
:::js
Prompt the chatbot to look up the "birthday" of the LangGraph library and direct the chatbot to reach out to the `humanAssistance` tool once it has the required information. By setting `name` and `birthday` in the arguments for the tool, you force the chatbot to generate proposals for these fields.
```typescript
import { isAIMessage } from "@langchain/core/messages";
const userInput =
"Can you look up when LangGraph was released? " +
"When you have the answer, use the humanAssistance tool for review.";
const events = await graph.stream(
{ messages: [{ role: "user", content: userInput }] },
{ configurable: { thread_id: "1" }, streamMode: "values" }
);
for await (const event of events) {
if ("messages" in event) {
const lastMessage = event.messages.at(-1);
console.log(
"=".repeat(32),
`${lastMessage?.getType()} Message`,
"=".repeat(32)
);
console.log(lastMessage?.text);
if (
lastMessage &&
isAIMessage(lastMessage) &&
lastMessage.tool_calls?.length
) {
console.log("Tool Calls:");
for (const call of lastMessage.tool_calls) {
console.log(` ${call.name} (${call.id})`);
console.log(` Args: ${JSON.stringify(call.args)}`);
}
}
}
}
```
:::
```
================================ Human Message =================================
@@ -126,12 +262,20 @@ Tool Calls:
birthday: 2023-01-01
```
:::python
We've hit the `interrupt` in the `human_assistance` tool again.
:::
:::js
We've hit the `interrupt` in the `humanAssistance` tool again.
:::
## 4. Add human assistance
The chatbot failed to identify the correct date, so supply it with information:
:::python
```python
human_command = Command(
resume={
@@ -146,6 +290,53 @@ for event in events:
event["messages"][-1].pretty_print()
```
:::
:::js
```typescript
import { Command } from "@langchain/langgraph";
const humanCommand = new Command({
resume: {
name: "LangGraph",
birthday: "Jan 17, 2024",
},
});
const resumeEvents = await graph.stream(humanCommand, {
configurable: { thread_id: "1" },
streamMode: "values",
});
for await (const event of resumeEvents) {
if ("messages" in event) {
const lastMessage = event.messages.at(-1);
console.log(
"=".repeat(32),
`${lastMessage?.getType()} Message`,
"=".repeat(32)
);
console.log(lastMessage?.text);
if (
lastMessage &&
isAIMessage(lastMessage) &&
lastMessage.tool_calls?.length
) {
console.log("Tool Calls:");
for (const call of lastMessage.tool_calls) {
console.log(` ${call.name} (${call.id})`);
console.log(` Args: ${JSON.stringify(call.args)}`);
}
}
}
}
```
:::
```
================================== Ai Message ==================================
@@ -175,6 +366,8 @@ It's worth noting that LangGraph had been in development and use for some time b
Note that these fields are now reflected in the state:
:::python
```python
snapshot = graph.get_state(config)
@@ -185,13 +378,34 @@ snapshot = graph.get_state(config)
{'name': 'LangGraph', 'birthday': 'Jan 17, 2024'}
```
:::
:::js
```typescript
const snapshot = await graph.getState(config);
const relevantState = Object.fromEntries(
Object.entries(snapshot.values).filter(([k]) =>
["name", "birthday"].includes(k)
)
);
```
```
{ name: 'LangGraph', birthday: 'Jan 17, 2024' }
```
:::
This makes them easily accessible to downstream nodes (e.g., a node that further processes or stores the information).
## 5. Manually update the state
:::python
LangGraph gives a high degree of control over the application state. For instance, at any point (including when interrupted), you can manually override a key using `graph.update_state`:
``` python
```python
graph.update_state(config, {"name": "LangGraph (library)"})
```
@@ -201,11 +415,36 @@ graph.update_state(config, {"name": "LangGraph (library)"})
'checkpoint_id': '1efd4ec5-cf69-6352-8006-9278f1730162'}}
```
:::
:::js
LangGraph gives a high degree of control over the application state. For instance, at any point (including when interrupted), you can manually override a key using `graph.updateState`:
```typescript
await graph.updateState(
{ configurable: { thread_id: "1" } },
{ name: "LangGraph (library)" }
);
```
```typescript
{
configurable: {
thread_id: '1',
checkpoint_ns: '',
checkpoint_id: '1efd4ec5-cf69-6352-8006-9278f1730162'
}
}
```
:::
## 6. View the new value
:::python
If you call `graph.get_state`, you can see the new value is reflected:
``` python
```python
snapshot = graph.get_state(config)
{k: v for k, v in snapshot.values.items() if k in ("name", "birthday")}
@@ -215,12 +454,35 @@ snapshot = graph.get_state(config)
{'name': 'LangGraph (library)', 'birthday': 'Jan 17, 2024'}
```
Manual state updates will [generate a trace](https://smith.langchain.com/public/7ebb7827-378d-49fe-9f6c-5df0e90086c8/r) in LangSmith. If desired, they can also be used to [control human-in-the-loop workflows](../../how-tos/human_in_the_loop/edit-graph-state.md). Use of the `interrupt` function is generally recommended instead, as it allows data to be transmitted in a human-in-the-loop interaction independently of state updates.
:::
:::js
If you call `graph.getState`, you can see the new value is reflected:
```typescript
const updatedSnapshot = await graph.getState(config);
const updatedRelevantState = Object.fromEntries(
Object.entries(updatedSnapshot.values).filter(([k]) =>
["name", "birthday"].includes(k)
)
);
```
```typescript
{ name: 'LangGraph (library)', birthday: 'Jan 17, 2024' }
```
:::
Manual state updates will [generate a trace](https://smith.langchain.com/public/7ebb7827-378d-49fe-9f6c-5df0e90086c8/r) in LangSmith. If desired, they can also be used to [control human-in-the-loop workflows](../../how-tos/human_in_the_loop/add-human-in-the-loop.md). Use of the `interrupt` function is generally recommended instead, as it allows data to be transmitted in a human-in-the-loop interaction independently of state updates.
**Congratulations!** You've added custom keys to the state to facilitate a more complex workflow, and learned how to generate state updates from inside tools.
Check out the code snippet below to review the graph from this tutorial:
:::python
{!snippets/chat_model_tabs.md!}
<!---
@@ -305,7 +567,111 @@ memory = MemorySaver()
graph = graph_builder.compile(checkpointer=memory)
```
:::
:::js
```typescript
import {
Command,
interrupt,
MessagesZodState,
MemorySaver,
StateGraph,
END,
START,
} from "@langchain/langgraph";
import { ToolNode, toolsCondition } from "@langchain/langgraph/prebuilt";
import { ChatAnthropic } from "@langchain/anthropic";
import { TavilySearch } from "@langchain/tavily";
import { ToolMessage } from "@langchain/core/messages";
import { tool } from "@langchain/core/tools";
import { z } from "zod";
const State = z.object({
messages: MessagesZodState.shape.messages,
name: z.string(),
birthday: z.string(),
});
const humanAssistance = tool(
async (input, config) => {
// Note that because we are generating a ToolMessage for a state update, we
// generally require the ID of the corresponding tool call. This is available
// in the tool's config.
const toolCallId = config?.toolCall?.id as string | undefined;
if (!toolCallId) throw new Error("Tool call ID is required");
const humanResponse = await interrupt({
question: "Is this correct?",
name: input.name,
birthday: input.birthday,
});
// We explicitly update the state with a ToolMessage inside the tool.
const stateUpdate = (() => {
// If the information is correct, update the state as-is.
if (humanResponse.correct?.toLowerCase().startsWith("y")) {
return {
name: input.name,
birthday: input.birthday,
messages: [
new ToolMessage({ content: "Correct", tool_call_id: toolCallId }),
],
};
}
// Otherwise, receive information from the human reviewer.
return {
name: humanResponse.name || input.name,
birthday: humanResponse.birthday || input.birthday,
messages: [
new ToolMessage({
content: `Made a correction: ${JSON.stringify(humanResponse)}`,
tool_call_id: toolCallId,
}),
],
};
})();
// We return a Command object in the tool to update our state.
return new Command({ update: stateUpdate });
},
{
name: "humanAssistance",
description: "Request assistance from a human.",
schema: z.object({
name: z.string().describe("The name of the entity"),
birthday: z.string().describe("The birthday/release date of the entity"),
}),
}
);
const searchTool = new TavilySearch({ maxResults: 2 });
const tools = [searchTool, humanAssistance];
const llmWithTools = new ChatAnthropic({
model: "claude-3-5-sonnet-latest",
}).bindTools(tools);
const memory = new MemorySaver();
const chatbot = async (state: z.infer<typeof State>) => {
const message = await llmWithTools.invoke(state.messages);
return { messages: message };
};
const graph = new StateGraph(State)
.addNode("chatbot", chatbot)
.addNode("tools", new ToolNode(tools))
.addConditionalEdges("chatbot", toolsCondition, ["tools", END])
.addEdge("tools", "chatbot")
.addEdge(START, "chatbot")
.compile({ checkpointer: memory });
```
:::
## Next steps
There's one more concept to review before finishing the LangGraph basics tutorials: connecting `checkpointing` and `state updates` to [time travel](./6-time-travel.md).
There's one more concept to review before finishing the LangGraph basics tutorials: connecting `checkpointing` and `state updates` to [time travel](./6-time-travel.md).
+341 -17
View File
@@ -4,7 +4,7 @@ In a typical chatbot workflow, the user interacts with the bot one or more times
What if you want a user to be able to start from a previous response and explore a different outcome? Or what if you want users to be able to rewind your chatbot's work to fix mistakes or try a different strategy, something that is common in applications like autonomous software engineers?
You can create these types of experiences using LangGraph's built-in **time travel** functionality.
You can create these types of experiences using LangGraph's built-in **time travel** functionality.
!!! note
@@ -12,7 +12,15 @@ You can create these types of experiences using LangGraph's built-in **time trav
## 1. Rewind your graph
:::python
Rewind your graph by fetching a checkpoint using the graph's `get_state_history` method. You can then resume execution at this previous point in time.
:::
:::js
Rewind your graph by fetching a checkpoint using the graph's `getStateHistory` method. You can then resume execution at this previous point in time.
:::
:::python
{!snippets/chat_model_tabs.md!}
@@ -64,11 +72,49 @@ memory = MemorySaver()
graph = graph_builder.compile(checkpointer=memory)
```
:::
:::js
```typescript
import {
StateGraph,
START,
END,
MessagesZodState,
MemorySaver,
} from "@langchain/langgraph";
import { ToolNode, toolsCondition } from "@langchain/langgraph/prebuilt";
import { TavilySearch } from "@langchain/tavily";
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
const State = z.object({ messages: MessagesZodState.shape.messages });
const tools = [new TavilySearch({ maxResults: 2 })];
const llmWithTools = new ChatOpenAI({ model: "gpt-4o-mini" }).bindTools(tools);
const memory = new MemorySaver();
const graph = new StateGraph(State)
.addNode("chatbot", async (state) => ({
messages: [await llmWithTools.invoke(state.messages)],
}))
.addNode("tools", new ToolNode(tools))
.addConditionalEdges("chatbot", toolsCondition, ["tools", END])
.addEdge("tools", "chatbot")
.addEdge(START, "chatbot")
.compile({ checkpointer: memory });
```
:::
## 2. Add steps
Add steps to your graph. Every step will be checkpointed in its state history:
``` python
:::python
```python
config = {"configurable": {"thread_id": "1"}}
events = graph.stream(
{
@@ -159,7 +205,7 @@ Tool Calls:
================================= Tool Message =================================
Name: tavily_search_results_json
[{"url": "https://towardsdatascience.com/building-autonomous-multi-tool-agents-with-gemini-2-0-and-langgraph-ad3d7bd5e79d", "content": "Building Autonomous Multi-Tool Agents with Gemini 2.0 and LangGraph | by Youness Mansar | Jan, 2025 | Towards Data Science Building Autonomous Multi-Tool Agents with Gemini 2.0 and LangGraph A practical tutorial with full code examples for building and running multi-tool agents Towards Data Science LLMs are remarkable — they can memorize vast amounts of information, answer general knowledge questions, write code, generate stories, and even fix your grammar. In this tutorial, we are going to build a simple LLM agent that is equipped with four tools that it can use to answer a users question. This Agent will have the following specifications: Follow Published in Towards Data Science --------------------------------- Your home for data science and AI. Follow Follow Follow"}, {"url": "https://github.com/anmolaman20/Tools_and_Agents", "content": "GitHub - anmolaman20/Tools_and_Agents: This repository provides resources for building AI agents using Langchain and Langgraph. This repository provides resources for building AI agents using Langchain and Langgraph. This repository provides resources for building AI agents using Langchain and Langgraph. This repository serves as a comprehensive guide for building AI-powered agents using Langchain and Langgraph. It provides hands-on examples, practical tutorials, and resources for developers and AI enthusiasts to master building intelligent systems and workflows. AI Agent Development: Gain insights into creating intelligent systems that think, reason, and adapt in real time. This repository is ideal for AI practitioners, developers exploring language models, or anyone interested in building intelligent systems. This repository provides resources for building AI agents using Langchain and Langgraph."}]
[{"url": "https://towardsdatascience.com/building-autonomous-multi-tool-agents-with-gemini-2-0-and-langgraph-ad3d7bd5e79d", "content": "Building Autonomous Multi-Tool Agents with Gemini 2.0 and LangGraph | by Youness Mansar | Jan, 2025 | Towards Data Science Building Autonomous Multi-Tool Agents with Gemini 2.0 and LangGraph A practical tutorial with full code examples for building and running multi-tool agents Towards Data Science LLMs are remarkable — they can memorize vast amounts of information, answer general knowledge questions, write code, generate stories, and even fix your grammar. In this tutorial, we are going to build a simple LLM agent that is equipped with four tools that it can use to answer a user's question. This Agent will have the following specifications: Follow Published in Towards Data Science --------------------------------- Your home for data science and AI. Follow Follow Follow"}, {"url": "https://github.com/anmolaman20/Tools_and_Agents", "content": "GitHub - anmolaman20/Tools_and_Agents: This repository provides resources for building AI agents using Langchain and Langgraph. This repository provides resources for building AI agents using Langchain and Langgraph. This repository provides resources for building AI agents using Langchain and Langgraph. This repository serves as a comprehensive guide for building AI-powered agents using Langchain and Langgraph. It provides hands-on examples, practical tutorials, and resources for developers and AI enthusiasts to master building intelligent systems and workflows. AI Agent Development: Gain insights into creating intelligent systems that think, reason, and adapt in real time. This repository is ideal for AI practitioners, developers exploring language models, or anyone interested in building intelligent systems. This repository provides resources for building AI agents using Langchain and Langgraph."}]
================================== Ai Message ==================================
Great idea! Building an autonomous agent with LangGraph is definitely an exciting project. Based on the latest information I've found, here are some insights and tips for building autonomous agents with LangGraph:
@@ -177,11 +223,140 @@ Building an autonomous agent is an iterative process, so be prepared to refine a
Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings...
```
:::
:::js
```typescript
import { randomUUID } from "node:crypto";
const threadId = randomUUID();
let iter = 0;
for (const userInput of [
"I'm learning LangGraph. Could you do some research on it for me?",
"Ya that's helpful. Maybe I'll build an autonomous agent with it!",
]) {
iter += 1;
console.log(`\n--- Conversation Turn ${iter} ---\n`);
const events = await graph.stream(
{ messages: [{ role: "user", content: userInput }] },
{ configurable: { thread_id: threadId }, streamMode: "values" }
);
for await (const event of events) {
if ("messages" in event) {
const lastMessage = event.messages.at(-1);
console.log(
"=".repeat(32),
`${lastMessage?.getType()} Message`,
"=".repeat(32)
);
console.log(lastMessage?.text);
}
}
}
```
```
--- Conversation Turn 1 ---
================================ human Message ================================
I'm learning LangGraph.js. Could you do some research on it for me?
================================ ai Message ================================
I'll search for information about LangGraph.js for you.
================================ tool Message ================================
{
"query": "LangGraph.js framework TypeScript langchain what is it tutorial guide",
"follow_up_questions": null,
"answer": null,
"images": [],
"results": [
{
"url": "https://techcommunity.microsoft.com/blog/educatordeveloperblog/an-absolute-beginners-guide-to-langgraph-js/4212496",
"title": "An Absolute Beginner's Guide to LangGraph.js",
"content": "(...)",
"score": 0.79369855,
"raw_content": null
},
{
"url": "https://langchain-ai.github.io/langgraphjs/",
"title": "LangGraph.js",
"content": "(...)",
"score": 0.78154784,
"raw_content": null
}
],
"response_time": 2.37
}
================================ ai Message ================================
Let me provide you with an overview of LangGraph.js based on the search results:
LangGraph.js is a JavaScript/TypeScript library that's part of the LangChain ecosystem, specifically designed for creating and managing complex LLM (Large Language Model) based workflows. Here are the key points about LangGraph.js:
1. Purpose:
- It's a low-level orchestration framework for building controllable agents
- Particularly useful for creating agentic workflows where LLMs decide the course of action based on current state
- Helps model workflows as graphs with nodes and edges
(...)
--- Conversation Turn 2 ---
================================ human Message ================================
Ya that's helpful. Maybe I'll build an autonomous agent with it!
================================ ai Message ================================
Let me search for specific information about building autonomous agents with LangGraph.js.
================================ tool Message ================================
{
"query": "how to build autonomous agents with LangGraph.js examples tutorial react agent",
"follow_up_questions": null,
"answer": null,
"images": [],
"results": [
{
"url": "https://ai.google.dev/gemini-api/docs/langgraph-example",
"title": "ReAct agent from scratch with Gemini 2.5 and LangGraph",
"content": "(...)",
"score": 0.7602419,
"raw_content": null
},
{
"url": "https://www.youtube.com/watch?v=ZfjaIshGkmk",
"title": "Build Autonomous AI Agents with ReAct and LangGraph Tools",
"content": "(...)",
"score": 0.7471924,
"raw_content": null
}
],
"response_time": 1.98
}
================================ ai Message ================================
Based on the search results, I can provide you with a practical overview of how to build an autonomous agent with LangGraph.js. Here's what you need to know:
1. Basic Structure for Building an Agent:
- LangGraph.js provides a ReAct (Reason + Act) pattern implementation
- The basic components include:
- State management for conversation history
- Nodes for different actions
- Edges for decision-making flow
- Tools for specific functionalities
(...)
```
:::
## 3. Replay the full state history
Now that you have added steps to the chatbot, you can `replay` the full state history to see everything that occurred.
``` python
:::python
```python
to_replay = None
for state in graph.get_state_history(config):
print("Num Messages: ", len(state.values["messages"]), "Next: ", state.next)
@@ -214,10 +389,61 @@ Num Messages: 0 Next: ('__start__',)
--------------------------------------------------------------------------------
```
Checkpoints are saved for every step of the graph. This __spans invocations__ so you can rewind across a full thread's history.
:::
:::js
```typescript
import type { StateSnapshot } from "@langchain/langgraph";
let toReplay: StateSnapshot | undefined;
for await (const state of graph.getStateHistory({
configurable: { thread_id: threadId },
})) {
console.log(
`Num Messages: ${state.values.messages.length}, Next: ${JSON.stringify(
state.next
)}`
);
console.log("-".repeat(80));
if (state.values.messages.length === 6) {
// We are somewhat arbitrarily selecting a specific state based on the number of chat messages in the state.
toReplay = state;
}
}
```
```
Num Messages: 8, Next: []
--------------------------------------------------------------------------------
Num Messages: 7, Next: ["chatbot"]
--------------------------------------------------------------------------------
Num Messages: 6, Next: ["tools"]
--------------------------------------------------------------------------------
Num Messages: 5, Next: ["chatbot"]
--------------------------------------------------------------------------------
Num Messages: 4, Next: ["__start__"]
--------------------------------------------------------------------------------
Num Messages: 4, Next: []
--------------------------------------------------------------------------------
Num Messages: 3, Next: ["chatbot"]
--------------------------------------------------------------------------------
Num Messages: 2, Next: ["tools"]
--------------------------------------------------------------------------------
Num Messages: 1, Next: ["chatbot"]
--------------------------------------------------------------------------------
Num Messages: 0, Next: ["__start__"]
--------------------------------------------------------------------------------
```
:::
Checkpoints are saved for every step of the graph. This **spans invocations** so you can rewind across a full thread's history.
## Resume from a checkpoint
:::python
Resume from the `to_replay` state, which is after the `chatbot` node in the second graph invocation. Resuming from this point will call the **action** node next.
```python
@@ -230,12 +456,37 @@ print(to_replay.config)
{'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efd43e3-0c1f-6c4e-8006-891877d65740'}}
```
:::
:::js
Resume from the `toReplay` state, which is after the `chatbot` node in one of the graph invocations. Resuming from this point will call the next scheduled node.
```typescript
console.log(toReplay.next);
console.log(toReplay.config);
```
```
["tools"]
{
configurable: {
thread_id: "007708b8-ea9b-4ff7-a7ad-3843364dbf75",
checkpoint_ns: "",
checkpoint_id: "1efd43e3-0c1f-6c4e-8006-891877d65740"
}
}
```
:::
## 4. Load a state from a moment-in-time
:::python
The checkpoint's `to_replay.config` contains a `checkpoint_id` timestamp. Providing this `checkpoint_id` value tells LangGraph's checkpointer to **load** the state from that moment in time.
``` python
```python
# The `checkpoint_id` in the `to_replay.config` corresponds to a state we've persisted to our checkpointer.
for event in graph.stream(None, to_replay.config, stream_mode="values"):
if "messages" in event:
@@ -254,19 +505,16 @@ Tool Calls:
================================= Tool Message =================================
Name: tavily_search_results_json
[{"url": "https://towardsdatascience.com/building-autonomous-multi-tool-agents-with-gemini-2-0-and-langgraph-ad3d7bd5e79d", "content": "Building Autonomous Multi-Tool Agents with Gemini 2.0 and LangGraph | by Youness Mansar | Jan, 2025 | Towards Data Science Building Autonomous Multi-Tool Agents with Gemini 2.0 and LangGraph A practical tutorial with full code examples for building and running multi-tool agents Towards Data Science LLMs are remarkable — they can memorize vast amounts of information, answer general knowledge questions, write code, generate stories, and even fix your grammar. In this tutorial, we are going to build a simple LLM agent that is equipped with four tools that it can use to answer a users question. This Agent will have the following specifications: Follow Published in Towards Data Science --------------------------------- Your home for data science and AI. Follow Follow Follow"}, {"url": "https://github.com/anmolaman20/Tools_and_Agents", "content": "GitHub - anmolaman20/Tools_and_Agents: This repository provides resources for building AI agents using Langchain and Langgraph. This repository provides resources for building AI agents using Langchain and Langgraph. This repository provides resources for building AI agents using Langchain and Langgraph. This repository serves as a comprehensive guide for building AI-powered agents using Langchain and Langgraph. It provides hands-on examples, practical tutorials, and resources for developers and AI enthusiasts to master building intelligent systems and workflows. AI Agent Development: Gain insights into creating intelligent systems that think, reason, and adapt in real time. This repository is ideal for AI practitioners, developers exploring language models, or anyone interested in building intelligent systems. This repository provides resources for building AI agents using Langchain and Langgraph."}]
[{"url": "https://towardsdatascience.com/building-autonomous-multi-tool-agents-with-gemini-2-0-and-langgraph-ad3d7bd5e79d", "content": "Building Autonomous Multi-Tool Agents with Gemini 2.0 and LangGraph | by Youness Mansar | Jan, 2025 | Towards Data Science Building Autonomous Multi-Tool Agents with Gemini 2.0 and LangGraph A practical tutorial with full code examples for building and running multi-tool agents Towards Data Science LLMs are remarkable — they can memorize vast amounts of information, answer general knowledge questions, write code, generate stories, and even fix your grammar. In this tutorial, we are going to build a simple LLM agent that is equipped with four tools that it can use to answer a user's question. This Agent will have the following specifications: Follow Published in Towards Data Science --------------------------------- Your home for data science and AI. Follow Follow Follow"}, {"url": "https://github.com/anmolaman20/Tools_and_Agents", "content": "GitHub - anmolaman20/Tools_and_Agents: This repository provides resources for building AI agents using Langchain and Langgraph. This repository provides resources for building AI agents using Langchain and Langgraph. This repository provides resources for building AI agents using Langchain and Langgraph. This repository serves as a comprehensive guide for building AI-powered agents using Langchain and Langgraph. It provides hands-on examples, practical tutorials, and resources for developers and AI enthusiasts to master building intelligent systems and workflows. AI Agent Development: Gain insights into creating intelligent systems that think, reason, and adapt in real time. This repository is ideal for AI practitioners, developers exploring language models, or anyone interested in building intelligent systems. This repository provides resources for building AI agents using Langchain and Langgraph."}]
================================== Ai Message ==================================
Great idea! Building an autonomous agent with LangGraph is indeed an excellent way to apply and deepen your understanding of the technology. Based on the search results, I can provide you with some insights and resources to help you get started:
Great idea! Building an autonomous agent with LangGraph is definitely an exciting project. Based on the latest information I've found, here are some insights and tips for building autonomous agents with LangGraph:
1. Multi-Tool Agents:
LangGraph is well-suited for building autonomous agents that can use multiple tools. This allows your agent to have a variety of capabilities and choose the appropriate tool based on the task at hand.
1. Multi-Tool Agents: LangGraph is particularly well-suited for creating autonomous agents that can use multiple tools. This allows your agent to have a diverse set of capabilities and choose the right tool for each task.
2. Integration with Large Language Models (LLMs):
There's a tutorial that specifically mentions using Gemini 2.0 (Google's LLM) with LangGraph to build autonomous agents. This suggests that LangGraph can be integrated with various LLMs, giving you flexibility in choosing the language model that best fits your needs.
2. Integration with Large Language Models (LLMs): You can combine LangGraph with powerful LLMs like Gemini 2.0 to create more intelligent and capable agents. The LLM can serve as the "brain" of your agent, making decisions and generating responses.
3. Practical Tutorials:
There are tutorials available that provide full code examples for building and running multi-tool agents. These can be invaluable as you start your project, giving you a concrete starting point and demonstrating best practices.
3. Workflow Management: LangGraph excels at managing complex, multi-step AI workflows. This is crucial for autonomous agents that need to break down tasks into smaller steps and execute them in the right order.
...
Remember, building an autonomous agent is an iterative process. Start simple and gradually increase complexity as you become more comfortable with LangGraph and its capabilities.
@@ -275,7 +523,83 @@ Would you like more information on any specific aspect of building your autonomo
Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings...
```
The graph resumed execution from the `action` node. You can tell this is the case since the first value printed above is the response from our search engine tool.
The graph resumed execution from the `tools` node. You can tell this is the case since the first value printed above is the response from our search engine tool.
:::
:::js
The checkpoint's `toReplay.config` contains a `checkpoint_id` timestamp. Providing this `checkpoint_id` value tells LangGraph's checkpointer to **load** the state from that moment in time.
```typescript
// The `checkpoint_id` in the `toReplay.config` corresponds to a state we've persisted to our checkpointer.
for await (const event of await graph.stream(null, {
...toReplay?.config,
streamMode: "values",
})) {
if ("messages" in event) {
const lastMessage = event.messages.at(-1);
console.log(
"=".repeat(32),
`${lastMessage?.getType()} Message`,
"=".repeat(32)
);
console.log(lastMessage?.text);
}
}
```
```
================================ ai Message ================================
Let me search for specific information about building autonomous agents with LangGraph.js.
================================ tool Message ================================
{
"query": "how to build autonomous agents with LangGraph.js examples tutorial",
"follow_up_questions": null,
"answer": null,
"images": [],
"results": [
{
"url": "https://www.mongodb.com/developer/languages/typescript/build-javascript-ai-agent-langgraphjs-mongodb/",
"title": "Build a JavaScript AI Agent With LangGraph.js and MongoDB",
"content": "(...)",
"score": 0.7672197,
"raw_content": null
},
{
"url": "https://medium.com/@lorevanoudenhove/how-to-build-ai-agents-with-langgraph-a-step-by-step-guide-5d84d9c7e832",
"title": "How to Build AI Agents with LangGraph: A Step-by-Step Guide",
"content": "(...)",
"score": 0.7407191,
"raw_content": null
}
],
"response_time": 0.82
}
================================ ai Message ================================
Based on the search results, I can share some practical information about building autonomous agents with LangGraph.js. Here are some concrete examples and approaches:
1. Example HR Assistant Agent:
- Can handle HR-related queries using employee information
- Features include:
- Starting and continuing conversations
- Looking up information using vector search
- Persisting conversation state using checkpoints
- Managing threaded conversations
2. Energy Savings Calculator Agent:
- Functions as a lead generation tool for solar panel sales
- Capabilities include:
- Calculating potential energy savings
- Handling multi-step conversations
- Processing user inputs for personalized estimates
- Managing conversation state
(...)
```
The graph resumed execution from the `tools` node. You can tell this is the case since the first value printed above is the response from our search engine tool.
:::
**Congratulations!** You've now used time-travel checkpoint traversal in LangGraph. Being able to rewind and explore alternative paths opens up a world of possibilities for debugging, experimentation, and interactive applications.
@@ -285,4 +609,4 @@ Take your LangGraph journey further by exploring deployment and advanced feature
- **[LangGraph Server quickstart](../../tutorials/langgraph-platform/local-server.md)**: Launch a LangGraph server locally and interact with it using the REST API and LangGraph Studio Web UI.
- **[LangGraph Platform quickstart](../../cloud/quick_start.md)**: Deploy your LangGraph app using LangGraph Platform.
- **[LangGraph Platform concepts](../../concepts/langgraph_platform.md)**: Understand the foundational concepts of the LangGraph Platform.
- **[LangGraph Platform concepts](../../concepts/langgraph_platform.md)**: Understand the foundational concepts of the LangGraph Platform.
@@ -336,7 +336,8 @@
"rag_chain = prompt | llm | StrOutputParser()\n",
"\n",
"# Run\n",
"generation = rag_chain.invoke({\"context\": docs, \"question\": question})\n",
"docs_txt = format_docs(docs)\n",
"generation = rag_chain.invoke({\"context\": docs_txt, \"question\": question})\n",
"print(generation)"
]
},
@@ -625,7 +626,8 @@
" documents = state[\"documents\"]\n",
"\n",
" # RAG generation\n",
" generation = rag_chain.invoke({\"context\": documents, \"question\": question})\n",
" docs_txt = format_docs(documents)\n",
" generation = rag_chain.invoke({\"context\": docs_txt, \"question\": question})\n",
" return {\"documents\": documents, \"question\": question, \"generation\": generation}\n",
"\n",
"\n",
+1 -1
View File
@@ -648,7 +648,7 @@ With orchestrator-worker, an orchestrator breaks down a task and delegates each
Because orchestrator-worker workflows are common, LangGraph **has the `Send` API to support this**. It lets you dynamically create worker nodes and send each one a specific input. Each worker has its own state, and all worker outputs are written to a *shared state key* that is accessible to the orchestrator graph. This gives the orchestrator access to all worker output and allows it to synthesize them into a final output. As you can see below, we iterate over a list of sections and `Send` each to a worker node. See further documentation [here](https://langchain-ai.github.io/langgraph/how-tos/map-reduce/) and [here](https://langchain-ai.github.io/langgraph/concepts/low_level/#send).
```python
from langgraph.constants import Send
from langgraph.types import Send
# Graph state
+138 -157
View File
@@ -89,162 +89,152 @@ plugins:
- "!^_"
nav:
- Guides:
- Get started:
- index.md
- Get started:
- Quickstart: agents/agents.md
- Quickstarts:
- Agent: agents/agents.md
- LangGraph basics:
- concepts/why-langgraph.md
- Build a basic chatbot: tutorials/get-started/1-build-basic-chatbot.md
- tutorials/get-started/2-add-tools.md
- tutorials/get-started/3-add-memory.md
- Add human-in-the-loop: tutorials/get-started/4-human-in-the-loop.md
- tutorials/get-started/5-customize-state.md
- tutorials/get-started/6-time-travel.md
- Deployment: tutorials/deployment.md
- Prebuilt agents:
- Overview: agents/overview.md
- agents/run_agents.md
- agents/streaming.md
- agents/models.md
- agents/tools.md
- agents/mcp.md
- agents/context.md
- agents/memory.md
- agents/human-in-the-loop.md
- agents/multi-agent.md
- agents/evals.md
- agents/deployment.md
- agents/ui.md
- LangGraph framework:
- Agent architectures:
- Overview: concepts/agentic_concepts.md
- concepts/why-langgraph.md
- Build a basic chatbot: tutorials/get-started/1-build-basic-chatbot.md
- tutorials/get-started/2-add-tools.md
- tutorials/get-started/3-add-memory.md
- Add human-in-the-loop: tutorials/get-started/4-human-in-the-loop.md
- tutorials/get-started/5-customize-state.md
- tutorials/get-started/6-time-travel.md
- Local server: tutorials/langgraph-platform/local-server.md
- Deployment: cloud/quick_start.md
- General concepts:
- Common patterns:
- Agent architectures: concepts/agentic_concepts.md
- Workflows & agents: tutorials/workflows.md
- Graphs:
- Overview: concepts/low_level.md
- Runtime overview: concepts/pregel.md
- Use the Graph API: how-tos/graph-api.ipynb
- Streaming:
- Overview: concepts/streaming.md
- "Stream outputs": how-tos/streaming.md
- Persistence:
- Overview: concepts/persistence.md
- concepts/durable_execution.md
- how-tos/persistence.ipynb
- Memory:
- Overview: concepts/memory.md
- Manage memory: how-tos/memory.ipynb
- Human-in-the-loop:
- Overview: concepts/human_in_the_loop.md
- how-tos/human_in_the_loop/add-human-in-the-loop.md
- Breakpoints:
- Overview: concepts/breakpoints.md
- how-tos/human_in_the_loop/breakpoints.ipynb
- Time travel:
- Overview: concepts/time-travel.md
- how-tos/human_in_the_loop/time-travel.ipynb
- Tools:
- Overview: concepts/tools.md
- how-tos/tool-calling.ipynb
- Subgraphs:
- Overview: concepts/subgraphs.md
- how-tos/subgraph.ipynb
- Multi-agent:
- Overview: concepts/multi_agent.md
- how-tos/multi_agent.ipynb
- Functional API:
- Overview: concepts/functional_api.md
- how-tos/use-functional-api.md
- LangGraph Platform:
- Overview: concepts/langgraph_platform.md
- Get started:
- Quickstart: tutorials/langgraph-platform/local-server.md
- Deployment quickstart: cloud/quick_start.md
- Components:
- Overview: concepts/langgraph_components.md
- LangGraph Server:
- Overview: concepts/langgraph_server.md
- Application structure:
- Overview: concepts/application_structure.md
- cloud/deployment/setup.md
- cloud/deployment/setup_pyproject.md
- cloud/deployment/setup_javascript.md
- cloud/deployment/custom_docker.md
- LangGraph CLI: concepts/langgraph_cli.md
- LangGraph Studio:
- Overview: concepts/langgraph_studio.md
- Quickstart: cloud/how-tos/studio/quick_start.md
- cloud/how-tos/invoke_studio.md
- cloud/how-tos/studio/manage_assistants.md
- cloud/how-tos/threads_studio.md
- cloud/how-tos/iterate_graph_studio.md
- cloud/how-tos/clone_traces_studio.md
- cloud/how-tos/datasets_studio.md
- LangGraph SDK: concepts/sdk.md
- Data management:
- Add semantic search: cloud/deployment/semantic_search.md
- Add TTLs: how-tos/ttl/configure_ttl.md
- Agent development: agents/overview.md
- Workflow orchestration:
- Graph API: concepts/low_level.md
- Subgraphs: concepts/subgraphs.md
- Runtime: concepts/pregel.md
- Functional API: concepts/functional_api.md
- Core capabilities:
- Streaming: concepts/streaming.md
- Persistence: concepts/persistence.md
- Durable execution: concepts/durable_execution.md
- Memory: concepts/memory.md
- Tools: concepts/tools.md
- Human-in-the-loop: concepts/human_in_the_loop.md
- Breakpoints: concepts/breakpoints.md
- Time travel: concepts/time-travel.md
- Multi-agent: concepts/multi_agent.md
- Platform capabilities:
- LangGraph Platform:
- Overview: concepts/langgraph_platform.md
- Components:
- Overview: concepts/langgraph_components.md
- LangGraph Server:
- Overview: concepts/langgraph_server.md
- Data plane: concepts/langgraph_data_plane.md
- Control plane: concepts/langgraph_control_plane.md
- LangGraph CLI: concepts/langgraph_cli.md
- LangGraph Studio: concepts/langgraph_studio.md
- LangGraph SDK: concepts/sdk.md
- Plans & pricing: concepts/plans.md
- Application structure: concepts/application_structure.md
- Scalability & resilience: concepts/scalability_and_resilience.md
- Authentication & access control: concepts/auth.md
- Assistants: concepts/assistants.md
- Double-texting: concepts/double_texting.md
- Webhooks: cloud/concepts/webhooks.md
- Cron jobs: cloud/concepts/cron_jobs.md
- Deployment:
- Overview: concepts/deployment_options.md
- Deployment options:
- Cloud SaaS: concepts/langgraph_cloud.md
- Self-Hosted Data Plane: concepts/langgraph_self_hosted_data_plane.md
- Self-Hosted Control Plane: concepts/langgraph_self_hosted_control_plane.md
- Standalone Container: concepts/langgraph_standalone_container.md
- Guides:
- LangGraph APIs:
- Use the Graph API: how-tos/graph-api.ipynb
- Use the Functional API: how-tos/use-functional-api.md
- Models:
- Configure model: agents/models.md
- Streaming:
- Stream outputs: how-tos/streaming.md
- Use Server API: cloud/how-tos/streaming.md
- Context:
- Add context: agents/context.md
- Memory:
- Add memory: how-tos/memory/add-memory.md
- Human-in-the-loop:
- how-tos/human_in_the_loop/add-human-in-the-loop.md
- Use Server API: cloud/how-tos/add-human-in-the-loop.md
- Time travel:
- how-tos/human_in_the_loop/time-travel.md
- Use Server API: cloud/how-tos/human_in_the_loop_time_travel.md
- Breakpoints:
- Set breakpoints: how-tos/human_in_the_loop/breakpoints.md
- Use Server API: cloud/how-tos/human_in_the_loop_breakpoint.md
- Tools:
- Call tools: how-tos/tool-calling.md
- Subgraphs:
- Use subgraphs: how-tos/subgraph.ipynb
- Multi-agent:
- Prebuilt implementation: agents/multi-agent.md
- Custom implementation: how-tos/multi_agent.ipynb
- MCP:
- Use MCP: agents/mcp.md
- Server API: concepts/server-mcp.md
- Evaluation:
- Basic implementation: agents/evals.md
- Deployment:
- Basic deployment: agents/deployment.md
- Set up your application:
- Use requirements.txt: cloud/deployment/setup.md
- Use pyproject.toml: cloud/deployment/setup_pyproject.md
- Use JavaScript: cloud/deployment/setup_javascript.md
- Use custom Docker: cloud/deployment/custom_docker.md
- Rebuild graph at runtime: cloud/deployment/graph_rebuild.md
- Deploy to production:
- Cloud SaaS: cloud/deployment/cloud.md
- Self-Hosted Data Plane: cloud/deployment/self_hosted_data_plane.md
- Self-Hosted Control Plane: cloud/deployment/self_hosted_control_plane.md
- Standalone Container: cloud/deployment/standalone_container.md
- Platform capabilities:
- LangGraph Studio:
- Quickstart: cloud/how-tos/studio/quick_start.md
- cloud/how-tos/invoke_studio.md
- cloud/how-tos/studio/manage_assistants.md
- cloud/how-tos/threads_studio.md
- cloud/how-tos/iterate_graph_studio.md
- cloud/how-tos/studio/run_evals.md
- cloud/how-tos/clone_traces_studio.md
- cloud/how-tos/datasets_studio.md
- Authentication & access control:
- Overview: concepts/auth.md
- how-tos/auth/custom_auth.md
- how-tos/auth/openapi_security.md
- Assistants:
- Overview: concepts/assistants.md
- cloud/how-tos/configuration_cloud.md
- Threads:
- Overview: cloud/concepts/threads.md
- cloud/how-tos/use_threads.md
- Runs:
- Overview: cloud/concepts/runs.md
- cloud/how-tos/background_run.md
- cloud/how-tos/same-thread.md
- cloud/how-tos/cron_jobs.md
- cloud/how-tos/stateless_runs.md
- cloud/how-tos/configurable_headers.md
- Streaming:
- Overview: cloud/concepts/streaming.md
- cloud/how-tos/streaming.md
- Human-in-the-loop: cloud/how-tos/add-human-in-the-loop.md
- Breakpoints: cloud/how-tos/human_in_the_loop_breakpoint.md
- Time travel: cloud/how-tos/human_in_the_loop_time_travel.md
- MCP: concepts/server-mcp.md
- Threads: cloud/how-tos/use_threads.md
- Runs:
- cloud/how-tos/background_run.md
- cloud/how-tos/same-thread.md
- cloud/how-tos/cron_jobs.md
- cloud/how-tos/stateless_runs.md
- cloud/how-tos/configurable_headers.md
- Double-texting:
- Overview: concepts/double_texting.md
- cloud/how-tos/interrupt_concurrent.md
- cloud/how-tos/rollback_concurrent.md
- cloud/how-tos/reject_concurrent.md
- cloud/how-tos/enqueue_concurrent.md
- Webhooks:
- Overview: cloud/concepts/webhooks.md
- cloud/how-tos/webhooks.md
- Cron jobs:
- Overview: cloud/concepts/cron_jobs.md
- cloud/how-tos/cron_jobs.md
- Webhooks: cloud/how-tos/webhooks.md
- Cron jobs: cloud/how-tos/cron_jobs.md
- Server customization:
- how-tos/http/custom_lifespan.md
- how-tos/http/custom_middleware.md
- how-tos/http/custom_routes.md
- Deployment:
- Overview: concepts/deployment_options.md
- Data plane: concepts/langgraph_data_plane.md
- Control plane: concepts/langgraph_control_plane.md
- Deployment options:
- Cloud SaaS:
- Overview: concepts/langgraph_cloud.md
- Deploy Cloud SaaS: cloud/deployment/cloud.md
- Self-Hosted Data Plane:
- Overview: concepts/langgraph_self_hosted_data_plane.md
- Deploy Self-Hosted Data Plane: cloud/deployment/self_hosted_data_plane.md
- Self-Hosted Control Plane:
- Overview: concepts/langgraph_self_hosted_control_plane.md
- Deploy Self-Hosted Control Plane: cloud/deployment/self_hosted_control_plane.md
- Standalone Container:
- Overview: concepts/langgraph_standalone_container.md
- Deploy Standalone Container: cloud/deployment/standalone_container.md
- Scalability & resilience: concepts/scalability_and_resilience.md
- Plans & pricing: concepts/plans.md
- Data management:
- Add semantic search: cloud/deployment/semantic_search.md
- Add TTLs: how-tos/ttl/configure_ttl.md
- Reference:
- reference/index.md
- LangGraph:
@@ -273,16 +263,18 @@ nav:
- Environment variables: cloud/reference/env_var.md
- Examples:
- agents/run_agents.md
- Template applications: concepts/template_applications.md # TODO: make tutorial
- Agentic RAG: tutorials/rag/langgraph_agentic_rag.ipynb
- Agent Supervisor: tutorials/multi_agent/agent_supervisor.ipynb
- SQL agent: tutorials/sql-agent.ipynb
- Prebuilt chat UI: agents/ui.md
- Graph runs in LangSmith: how-tos/run-id-langsmith.ipynb
- LangGraph Platform:
- Authentication:
- tutorials/auth/getting_started.md
- tutorials/auth/resource_auth.md
- tutorials/auth/add_auth_server.md
- Rebuild graph at runtime: cloud/deployment/graph_rebuild.md
- Use RemoteGraph: how-tos/use-remote-graph.md
- Deploy CrewAI, AutoGen, and other frameworks: how-tos/autogen-langgraph-platform.ipynb
# combine with how-tos/autogen-integration.ipynb
@@ -290,11 +282,12 @@ nav:
- Integrate LangGraph into a React app: cloud/how-tos/use_stream_react.md
- Implement generative UI with LangGraph: cloud/how-tos/generative_ui_react.md
- Resources:
- concepts/faq.md
- Template applications: concepts/template_applications.md # TODO: make tutorial
- llms.txt: llms-txt-overview.md
- Additional resources:
- agents/prebuilt.md # NOTE: prebuilt.md is auto-generated by `make build-prebuilt`
- LangGraph Academy course: https://academy.langchain.com/courses/intro-to-langgraph
- Case studies: adopters.md
- concepts/faq.md
- llms.txt: llms-txt-overview.md
- Troubleshooting:
- Errors:
- troubleshooting/errors/index.md
@@ -305,9 +298,7 @@ nav:
- troubleshooting/errors/INVALID_CHAT_HISTORY.md
- troubleshooting/errors/INVALID_LICENSE.md
- LangGraph Studio: troubleshooting/studio.md
- Learn:
- LangGraph Academy course: https://academy.langchain.com/courses/intro-to-langgraph
- Case studies: adopters.md
markdown_extensions:
- abbr
@@ -364,16 +355,6 @@ markdown_extensions:
hooks:
- _scripts/notebook_hooks.py
extra:
consent:
title: Cookie consent
actions:
- accept
- reject
description: >-
We use cookies to recognize your repeated visits and preferences, as well
as to measure the effectiveness of our documentation and whether users
find what they're searching for. <strong>Clicking "Accept" makes our
documentation better. Thank you!</strong> ❤️
social:
- icon: fontawesome/brands/js
link: https://langchain-ai.github.io/langgraphjs/
+41
View File
@@ -0,0 +1,41 @@
.lang-python,
.lang-javascript {
display: none;
}
.language-switcher-global {
display: flex;
align-items: center;
padding-left: 0.5rem;
margin-right: 0.5rem;
}
/* Style the select to match the header */
.language-switcher-global select {
appearance: none;
font: inherit;
border: none;
padding: 0.25rem 0.6rem;
cursor: pointer;
outline: none;
font-weight: bolder;
}
/* Hover/focus effect */
.language-switcher-global select:hover,
.language-switcher-global select:focus {
text-decoration: underline;
}
/* Theme-specific overrides */
html[data-md-color-scheme="default"] .language-switcher-global select,
html[data-md-color-scheme="default"] .language-switcher-global option {
color: #333;
background-color: transparent;
}
html[data-md-color-scheme="slate"] .language-switcher-global select,
html[data-md-color-scheme="slate"] .language-switcher-global option {
color: #eee;
background-color: transparent;
}
+38
View File
@@ -0,0 +1,38 @@
function applyLanguageSwitching() {
const selector = document.getElementById("global-language-selector");
const langBlocks = {
python: document.querySelectorAll(".lang-python"),
javascript: document.querySelectorAll(".lang-javascript"),
};
const setLanguage = (lang) => {
for (const [key, blocks] of Object.entries(langBlocks)) {
blocks.forEach((block) => {
block.style.display = key === lang ? "block" : "none";
});
}
localStorage.setItem("preferredLang", lang);
};
const saved = localStorage.getItem("preferredLang") || "python";
if (selector) {
selector.value = saved;
selector.addEventListener("change", (e) => setLanguage(e.target.value));
}
setLanguage(saved);
}
// Run on initial load
document.addEventListener("DOMContentLoaded", applyLanguageSwitching);
// Re-run after client-side navigation (MkDocs Material)
document.addEventListener("pjax:success", applyLanguageSwitching);
// Optional: observe DOM changes (e.g., for late-loaded content)
if (window.MutationObserver) {
const observer = new MutationObserver(() => applyLanguageSwitching());
observer.observe(document.body, { childList: true, subtree: true });
}
@@ -0,0 +1,6 @@
<div class="md-header__button language-switcher-global" title="Select Language">
<select id="global-language-selector" aria-label="Select Language">
<option value="python">🐍 Python</option>
<option value="javascript">⚡️ JavaScript</option>
</select>
</div>
+15 -5
View File
@@ -1,6 +1,6 @@
=== "OpenAI"
```
```shell
pip install -U "langchain[openai]"
```
```python
@@ -12,9 +12,11 @@
llm = init_chat_model("openai:gpt-4.1")
```
👉 Read the [OpenAI integration docs](https://python.langchain.com/docs/integrations/chat/openai/)
=== "Anthropic"
```
```shell
pip install -U "langchain[anthropic]"
```
```python
@@ -26,9 +28,11 @@
llm = init_chat_model("anthropic:claude-3-5-sonnet-latest")
```
👉 Read the [Anthropic integration docs](https://python.langchain.com/docs/integrations/chat/anthropic/)
=== "Azure"
```
```shell
pip install -U "langchain[openai]"
```
```python
@@ -44,10 +48,12 @@
azure_deployment=os.environ["AZURE_OPENAI_DEPLOYMENT_NAME"],
)
```
👉 Read the [Azure integration docs](https://python.langchain.com/docs/integrations/chat/azure_chat_openai/)
=== "Google Gemini"
```
```shell
pip install -U "langchain[google-genai]"
```
```python
@@ -59,9 +65,11 @@
llm = init_chat_model("google_genai:gemini-2.0-flash")
```
👉 Read the [Google GenAI integration docs](https://python.langchain.com/docs/integrations/chat/google_generative_ai/)
=== "AWS Bedrock"
```
```shell
pip install -U "langchain[aws]"
```
```python
@@ -75,3 +83,5 @@
model_provider="bedrock_converse",
)
```
👉 Read the [AWS Bedrock integration docs](https://python.langchain.com/docs/integrations/chat/bedrock/)
@@ -0,0 +1,22 @@
from _scripts.notebook_hooks import _apply_conditional_rendering
CONDITIONAL_RENDERING = """
above
:::js
js-content
:::
between
:::python
python-content
:::
below
"""
def test_conditional_rendering() -> None:
"""Test logic for conditional rendering of content."""
output = _apply_conditional_rendering(CONDITIONAL_RENDERING, "js")
assert output.strip() == "above\njs-content\n\nbetween\n\nbelow"
output = _apply_conditional_rendering(CONDITIONAL_RENDERING, "python")
assert output.strip() == "above\n\nbetween\npython-content\n\nbelow"
Generated
+6 -4
View File
@@ -2590,7 +2590,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "0.4.7"
version = "0.5.0"
source = { editable = "../libs/langgraph" }
dependencies = [
{ name = "langchain-core" },
@@ -2641,7 +2641,7 @@ dev = [
[[package]]
name = "langgraph-checkpoint"
version = "2.0.26"
version = "2.1.0"
source = { editable = "../libs/checkpoint" }
dependencies = [
{ name = "langchain-core" },
@@ -2660,6 +2660,8 @@ dev = [
{ name = "dataclasses-json" },
{ name = "mypy" },
{ name = "numpy" },
{ name = "pandas" },
{ name = "pandas-stubs", specifier = ">=2.2.2.240807" },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-mock" },
@@ -2892,7 +2894,7 @@ test = [
[[package]]
name = "langgraph-prebuilt"
version = "0.2.2"
version = "0.5.1"
source = { editable = "../libs/prebuilt" }
dependencies = [
{ name = "langchain-core" },
@@ -2923,7 +2925,7 @@ dev = [
[[package]]
name = "langgraph-sdk"
version = "0.1.70"
version = "0.1.72"
source = { editable = "../libs/sdk-py" }
dependencies = [
{ name = "httpx" },
+1 -33
View File
@@ -1,33 +1 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "ac22b8de",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/human_in_the_loop/breakpoints.ipynb"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

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