mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-18 21:55:46 +02:00
Compare commits
231
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
602cddb155 | ||
|
|
a328d43c53 | ||
|
|
0711fccd44 | ||
|
|
50a9cc5fbc | ||
|
|
b3ec283b1e | ||
|
|
17f2f4df5d | ||
|
|
9378ad38dc | ||
|
|
e3d36cd59b | ||
|
|
9f12c142e5 | ||
|
|
54e64640de | ||
|
|
d1d4abf70e | ||
|
|
fadbe7d710 | ||
|
|
c861614337 | ||
|
|
c020f01425 | ||
|
|
14949c81c8 | ||
|
|
3af857922a | ||
|
|
7ec049e0c7 | ||
|
|
1923ff8d85 | ||
|
|
4b9b7d0b1c | ||
|
|
624247a51f | ||
|
|
b21b595927 | ||
|
|
f4633a0015 | ||
|
|
86017c010c | ||
|
|
2115cffc94 | ||
|
|
03726f9bc6 | ||
|
|
509dfd1f21 | ||
|
|
efca21070d | ||
|
|
dba20d0577 | ||
|
|
5145dac12b | ||
|
|
440c7ff12a | ||
|
|
5eef290c4e | ||
|
|
a8b3746356 | ||
|
|
7541331643 | ||
|
|
76814676c2 | ||
|
|
0804984f9d | ||
|
|
8f11b6a003 | ||
|
|
aa6b122e4c | ||
|
|
23491e5c9a | ||
|
|
1491f30a07 | ||
|
|
f63635d3c8 | ||
|
|
6672032568 | ||
|
|
311ce7b04f | ||
|
|
14b732740e | ||
|
|
370825a48a | ||
|
|
264bae5a7e | ||
|
|
f6aa19709e | ||
|
|
8495f6f95d | ||
|
|
6710908d40 | ||
|
|
d24ad3d980 | ||
|
|
2d8288fd0f | ||
|
|
c1ef10a0ec | ||
|
|
a3d7b6f44e | ||
|
|
bc9d45b476 | ||
|
|
7d3f0089aa | ||
|
|
ed678f4701 | ||
|
|
22411ba0fd | ||
|
|
5b9021ff37 | ||
|
|
aaff464115 | ||
|
|
3a23a256e2 | ||
|
|
f6857395b9 | ||
|
|
cdaa7ba003 | ||
|
|
39745ed794 | ||
|
|
738bb8a343 | ||
|
|
b2dde8d9af | ||
|
|
5312edc830 | ||
|
|
276310e116 | ||
|
|
39977ded8c | ||
|
|
439038fc3a | ||
|
|
2e5445c565 | ||
|
|
d38510ad03 | ||
|
|
951486d107 | ||
|
|
745a1e7a29 | ||
|
|
24731d6a28 | ||
|
|
a4689a5d10 | ||
|
|
b05ce0bf60 | ||
|
|
4501e41991 | ||
|
|
bc83287fc8 | ||
|
|
11547e1990 | ||
|
|
fadd9c4577 | ||
|
|
fd2933a792 | ||
|
|
40fa69f8ee | ||
|
|
0824161984 | ||
|
|
b028f502e1 | ||
|
|
9dc3fed6b8 | ||
|
|
0232201b7b | ||
|
|
869b0f2de4 | ||
|
|
90e3adcd71 | ||
|
|
cb918601d1 | ||
|
|
4a4c8db635 | ||
|
|
56a9ce57b1 | ||
|
|
d1ee1cf1f1 | ||
|
|
29c3a579b3 | ||
|
|
9e3cb1f034 | ||
|
|
508e333220 | ||
|
|
aa1bbe3d01 | ||
|
|
139cad373b | ||
|
|
2a86abb8c4 | ||
|
|
d1f0799002 | ||
|
|
be088801ba | ||
|
|
1ee6bfeb8d | ||
|
|
2153d36726 | ||
|
|
819eae891e | ||
|
|
457edaa75b | ||
|
|
90ba4c5205 | ||
|
|
b3c5298100 | ||
|
|
9d9476e664 | ||
|
|
03bec97767 | ||
|
|
dd9b5c42e8 | ||
|
|
951a3f2d1c | ||
|
|
adaa340c15 | ||
|
|
2c85cba9ca | ||
|
|
cb7b924006 | ||
|
|
c61ac946af | ||
|
|
ec0a30008c | ||
|
|
00c7909c27 | ||
|
|
58d396fcf1 | ||
|
|
9f0abf014d | ||
|
|
8d6cd15669 | ||
|
|
61676b8db0 | ||
|
|
3b85e53360 | ||
|
|
250a17d711 | ||
|
|
f63bec8578 | ||
|
|
dc0f0c5944 | ||
|
|
777fe692d4 | ||
|
|
fdbe31a3aa | ||
|
|
78a9933144 | ||
|
|
5717eefa79 | ||
|
|
5978012619 | ||
|
|
b447a1c7cf | ||
|
|
f87608a16f | ||
|
|
a5fe3316b6 | ||
|
|
e0699fbdaf | ||
|
|
b4eb57da67 | ||
|
|
cec3bef7ea | ||
|
|
adc732272c | ||
|
|
71dc92b349 | ||
|
|
79b4642e55 | ||
|
|
48446bcbd2 | ||
|
|
cf95c870fe | ||
|
|
a34c38a53d | ||
|
|
db5276ded1 | ||
|
|
9c67b9ce4b | ||
|
|
08667fe786 | ||
|
|
c6d674cd3e | ||
|
|
294078adab | ||
|
|
48dabc0538 | ||
|
|
d2cc02d789 | ||
|
|
92c66d13ec | ||
|
|
7f821deded | ||
|
|
12a601c8a3 | ||
|
|
d64447c4c2 | ||
|
|
0d2db35d93 | ||
|
|
6e9e1ca146 | ||
|
|
b290e1ffdc | ||
|
|
a5eb6a75bf | ||
|
|
7a136aaff6 | ||
|
|
e973e936c3 | ||
|
|
066f3b21f8 | ||
|
|
2eecaa8500 | ||
|
|
d935a2d110 | ||
|
+3 |
d5b8733a40 | ||
|
|
40c0e44b9a | ||
|
|
b5afec4b2a | ||
|
|
d674e1bddd | ||
|
|
8c68f739b9 | ||
|
|
1500ebd3d7 | ||
|
|
0837263542 | ||
|
|
18633bc99e | ||
|
|
2558f81889 | ||
|
|
b832fefc58 | ||
|
|
444d699fe8 | ||
|
|
e315fb7397 | ||
|
|
0d8dfa7bba | ||
|
|
2c2ace2a40 | ||
|
|
74218fadad | ||
|
|
7a39e5fc6e | ||
|
|
06144b3b13 | ||
|
|
b19572351e | ||
|
|
a5ce13eb45 | ||
|
|
e91f72dd48 | ||
|
|
3d9cb305b4 | ||
|
|
de1460937a | ||
|
|
e0bf4a7bc3 | ||
|
|
06f311fffb | ||
|
|
5bdb887638 | ||
|
|
6c4034420c | ||
|
|
fa5d93ac2f | ||
|
|
d14a6e2c04 | ||
|
|
54dadadac4 | ||
|
|
d073e1bd8a | ||
|
|
28c276f377 | ||
|
|
a71eb09488 | ||
|
|
579c7831b9 | ||
|
|
16da5f4779 | ||
|
|
f59a1339c9 | ||
|
|
d166e9b8e9 | ||
|
|
4da35babda | ||
|
|
c3d882e87a | ||
|
|
fbade9e300 | ||
|
|
0b6a9e345d | ||
|
|
9b9bf88aee | ||
|
|
5f00938aa2 | ||
|
|
67a86f2dc2 | ||
|
|
ad44d1fe66 | ||
|
|
5c45f7c330 | ||
|
|
bb2f448175 | ||
|
|
8a9f3dbf6f | ||
|
|
63f051ad28 | ||
|
|
e5ded1888b | ||
|
|
0ab9770056 | ||
|
|
8ca5e56f52 | ||
|
|
0733ec65ad | ||
|
|
543cbe9032 | ||
|
|
d1710e2eac | ||
|
|
6eace78c53 | ||
|
|
e5947bcd30 | ||
|
|
1240f8bdca | ||
|
|
4d7c107bb8 | ||
|
|
7a4fd25185 | ||
|
|
7a66213535 | ||
|
|
c8d32f104d | ||
|
|
2fee649980 | ||
|
|
b6dc566ec7 | ||
|
|
a84b744eb6 | ||
|
|
f5b888dd72 | ||
|
|
7a8f29847b | ||
|
|
f001246794 | ||
|
|
b7d11b4141 | ||
|
|
1d3fd9a46b | ||
|
|
8c4e698c5a | ||
|
|
c989f1c898 |
@@ -1,29 +1,29 @@
|
||||
name: "\U0001F41B Bug Report"
|
||||
description: Report a bug in LangGraph. To report a security issue, please instead use the security option below. For questions, please use the GitHub Discussions.
|
||||
description: Report a bug in LangGraph. To report a security issue, please instead use the security option below. For questions, please use the LangChain Forum at forum.langchain.com.
|
||||
labels: [pending,bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >
|
||||
value: |
|
||||
Thank you for taking the time to file a bug report.
|
||||
|
||||
Use this to report BUGS in LangGraph. For usage questions, feature requests and general design questions, please use [GitHub Discussions](https://github.com/langchain-ai/langgraph/discussions).
|
||||
Use this to report BUGS in LangGraph. For usage questions, feature requests and general design questions, please use the [LangChain Forum](https://forum.langchain.com/).
|
||||
|
||||
Relevant links to check before filing a bug report to see if your issue has already been reported, fixed or
|
||||
if there's another way to solve your problem:
|
||||
|
||||
[LangGraph Github Discussions](https://github.com/langchain-ai/langgraph/discussions),
|
||||
[LangGraph Github Issues](https://github.com/langchain-ai/langgraph/issues),
|
||||
[LangGraph how-to guides](https://langchain-ai.github.io/langgraph/how-tos/).
|
||||
[LangChain documentation with the integrated search](https://python.langchain.com/docs/get_started/introduction),
|
||||
[GitHub search](https://github.com/langchain-ai/langgraph),
|
||||
* [LangChain Forum](https://forum.langchain.com/),
|
||||
* [LangGraph Github Issues](https://github.com/langchain-ai/langgraph/issues),
|
||||
* [LangGraph how-to guides](https://langchain-ai.github.io/langgraph/how-tos/).
|
||||
* [LangChain documentation with the integrated search](https://python.langchain.com/docs/get_started/introduction),
|
||||
* [GitHub search](https://github.com/langchain-ai/langgraph),
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
attributes:
|
||||
label: Checked other resources
|
||||
description: Before submitting this issue, please confirm that you have completed all the steps below by checking each option. These steps help ensure your issue is well-defined, relevant, and actionable.
|
||||
options:
|
||||
- label: This is a bug, not a usage question. For questions, please use GitHub Discussions.
|
||||
- label: This is a bug, not a usage question. For questions, please use the LangChain Forum (https://forum.langchain.com/).
|
||||
required: true
|
||||
- label: I added a clear and detailed title that summarizes the issue.
|
||||
required: true
|
||||
@@ -38,7 +38,7 @@ body:
|
||||
attributes:
|
||||
label: Example Code
|
||||
description: |
|
||||
Please add a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with your use case.
|
||||
Please add a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with your use case. Replace this code with your own!
|
||||
placeholder: |
|
||||
from langgraph.graph import StateGraph
|
||||
|
||||
@@ -78,7 +78,7 @@ body:
|
||||
attributes:
|
||||
label: System Info
|
||||
description: |
|
||||
python -m langchain_core.sys_info
|
||||
Run on your machine: `python -m langchain_core.sys_info`
|
||||
placeholder: |
|
||||
python -m langchain_core.sys_info
|
||||
validations:
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
blank_issues_enabled: true
|
||||
blank_issues_enabled: false
|
||||
version: 2.1
|
||||
contact_links:
|
||||
- name: 🤔 Question or Problem
|
||||
about: Ask a question or ask about a problem in GitHub Discussions.
|
||||
url: https://github.com/langchain-ai/langgraph/discussions/categories/q-a
|
||||
- name: Feature Request
|
||||
url: https://github.com/langchain-ai/langgraph/discussions/categories/ideas
|
||||
about: Suggest a feature or an idea
|
||||
- name: Show and tell
|
||||
about: Show what you built with LangChain
|
||||
url: https://github.com/langchain-ai/langgraph/discussions/categories/show-and-tell
|
||||
- name: LangChain Forum
|
||||
url: https://forum.langchain.com/
|
||||
about: General community discussions and support
|
||||
about: General community discussions, support, and feature requests
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
name: 🔒 Privileged
|
||||
description: You are a LangChain maintainer, or was asked directly by a maintainer to create an issue here. If not, check the other options.
|
||||
description: You are a LangGraph maintainer, or was asked directly by a maintainer to create an issue here. If not, check the other options.
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for your interest in LangChain! 🚀
|
||||
|
||||
If you are not a LangChain maintainer or were not asked directly by a maintainer to create an issue, then please start the conversation in a [Question in GitHub Discussions](https://github.com/langchain-ai/langchain/discussions/categories/q-a) instead.
|
||||
|
||||
You are a LangChain maintainer if you maintain any of the packages inside of the LangChain repository
|
||||
or are a regular contributor to LangChain with previous merged merged pull requests.
|
||||
Thanks for your interest in LangGraph! 🚀
|
||||
|
||||
If you are not a LangGraph maintainer or were not asked directly by a maintainer to create an issue, then please start the conversation on the [LangChain Forum](https://forum.langchain.com/) instead.
|
||||
|
||||
You are a LangGraph maintainer if you maintain any of the packages inside of the LangGraph repository
|
||||
or are a regular contributor to LangGraph with previous merged merged pull requests.
|
||||
- type: checkboxes
|
||||
id: privileged
|
||||
attributes:
|
||||
label: Privileged issue
|
||||
description: Confirm that you are allowed to create an issue here.
|
||||
options:
|
||||
- label: I am a LangChain maintainer, or was asked directly by a LangChain maintainer to create an issue here.
|
||||
- label: I am a LangGraph maintainer, or was asked directly by a LangGraph maintainer to create an issue here.
|
||||
required: true
|
||||
- type: textarea
|
||||
id: content
|
||||
attributes:
|
||||
label: Issue Content
|
||||
description: Add the content of the issue here.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Community members should **NOT** work on Privileged issues unless these issues have been explicitly marked with a "help-wanted" tag.
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
Thank you for contributing to LangGraph! Follow these steps to mark your pull request as ready for review. **If any of these steps are not completed, your PR will not be considered for review.**
|
||||
|
||||
- [ ] **PR title**: Follows the format: {TYPE}({SCOPE}): {DESCRIPTION}
|
||||
- Examples:
|
||||
- feat(core): add multi-tenant support
|
||||
- fix(cli): resolve flag parsing error
|
||||
- docs(openai): update API usage examples
|
||||
- Allowed `{TYPE}` values:
|
||||
- feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert, release
|
||||
- Allowed `{SCOPE}` values (optional):
|
||||
- langgraph, docs, cli, checkpoint, checkpoint-postgres, checkpoint-sqlite, prebuilt, scheduler-kafka, sdk-py
|
||||
- Once you've written the title, please delete this checklist item; do not include it in the PR.
|
||||
|
||||
- [ ] **PR message**: ***Delete this entire checklist*** and replace with
|
||||
- **Description:** a description of the change. Include a [closing keyword](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) if applicable.
|
||||
- **Issue:** the issue # it fixes, if applicable
|
||||
- **Dependencies:** any dependencies required for this change
|
||||
- **Twitter handle:** if your PR gets announced, and you'd like a mention, we'll gladly shout you out!
|
||||
|
||||
- [ ] **Add tests and docs**: If you're adding a new integration, you must include:
|
||||
1. A test for the integration, preferably unit tests that do not rely on network access,
|
||||
2. An example notebook showing its use. It lives in `docs/docs/integrations` directory.
|
||||
|
||||
- [ ] **Lint and test**: Run `make format`, `make lint` and `make test` from the root of the package(s) you've modified. We will not consider a PR unless these three are passing in CI. See [contribution guidelines](https://github.com/langchain-ai/langgraph/blob/main/CONTRIBUTING.md) for more.
|
||||
|
||||
Additional guidelines:
|
||||
|
||||
- Make sure optional dependencies are imported within a function.
|
||||
- Please do not add dependencies to `pyproject.toml` files (even optional ones) unless they are **required** for unit tests.
|
||||
- Most PRs should not touch more than one package.
|
||||
- Changes should be backwards compatible.
|
||||
@@ -3,7 +3,7 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
branches: [main, v1]
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
LangChain
|
||||
LangGraph
|
||||
LangSmith
|
||||
thead
|
||||
stdio
|
||||
nd
|
||||
jupyter
|
||||
lets
|
||||
lite
|
||||
uis
|
||||
deque
|
||||
@@ -34,10 +34,16 @@
|
||||
id: extract_ignore_words
|
||||
|
||||
- name: Codespell
|
||||
uses: codespell-project/actions-codespell@v2
|
||||
uses: codespell-project/actions-codespell@v2.1
|
||||
with:
|
||||
skip: '*.ambr,*.lock,*.ipynb,*.yaml,*.zlib,*.md'
|
||||
skip: '*.ambr,*.lock,*.ipynb,*.yaml,*.zlib,*.css.map,*.js.map'
|
||||
ignore_words_list: ${{ steps.extract_ignore_words.outputs.ignore_words_list }}
|
||||
# We do this to avoid spellchecking cell outputs
|
||||
- name: Codespell Notebooks
|
||||
run: make codespell
|
||||
run: make codespell
|
||||
|
||||
- name: Codespell LangGraph Library
|
||||
run: |
|
||||
# Change to root directory to check the main LangGraph library
|
||||
cd ..
|
||||
codespell --skip="*.ambr,*.lock,*.ipynb,*.yaml,*.zlib,*.css.map,*.js.map,*.pyc,__pycache__/*" --ignore-words-list="${{ steps.extract_ignore_words.outputs.ignore_words_list }}" libs/langgraph/langgraph/
|
||||
@@ -35,16 +35,7 @@ jobs:
|
||||
with:
|
||||
filter: "docs/docs/**"
|
||||
|
||||
# TODO: Uncomment this to run on PRs
|
||||
# run-changed-notebooks:
|
||||
# needs: get-changed-files
|
||||
# uses: ./.github/workflows/run_notebooks.yml
|
||||
# secrets: inherit
|
||||
# with:
|
||||
# changed-files: ${{ needs.get-changed-files.outputs.changed-files }}
|
||||
|
||||
deploy:
|
||||
# needs: run-changed-notebooks
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10 # Job will be cancelled if it runs for more than 10 minutes
|
||||
env:
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
name: PR Title Lint
|
||||
|
||||
permissions:
|
||||
pull-requests: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, synchronize]
|
||||
|
||||
jobs:
|
||||
lint-pr-title:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Validate PR Title
|
||||
uses: amannn/action-semantic-pull-request@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
types: |
|
||||
feat
|
||||
fix
|
||||
docs
|
||||
style
|
||||
refactor
|
||||
perf
|
||||
test
|
||||
build
|
||||
ci
|
||||
chore
|
||||
revert
|
||||
release
|
||||
scopes: |
|
||||
checkpoint
|
||||
checkpoint-postgres
|
||||
checkpoint-sqlite
|
||||
cli
|
||||
langgraph
|
||||
prebuilt
|
||||
scheduler-kafka
|
||||
sdk-py
|
||||
docs
|
||||
ci
|
||||
requireScope: false
|
||||
ignoreLabels: |
|
||||
ignore-lint-pr-title
|
||||
@@ -137,7 +137,9 @@ jobs:
|
||||
needs:
|
||||
- build
|
||||
- release-notes
|
||||
permissions: write-all
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
uses: ./.github/workflows/_test_release.yml
|
||||
with:
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
|
||||
+8
-9
@@ -9,7 +9,7 @@ Here are some things to keep in mind for all types of contributions:
|
||||
- Follow the ["fork and pull request"](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project) workflow.
|
||||
- Fill out the checked-in pull request template when opening pull requests. Note related issues and tag relevant maintainers.
|
||||
- Ensure your PR passes formatting, linting, and testing checks before requesting a review.
|
||||
- If you would like comments or feedback, please open an issue or discussion and tag a maintainer.
|
||||
- If you would like comments or feedback, please tag a maintainer.
|
||||
- Backwards compatibility is key. Your changes must not be breaking, except in case of critical bug and security fixes.
|
||||
- Look for duplicate PRs or issues that have already been opened before opening a new one.
|
||||
- Keep scope as isolated as possible. As a general rule, your changes should not affect more than one package at a time.
|
||||
@@ -20,7 +20,7 @@ For bug fixes, please open up an issue before proposing a fix to ensure the prop
|
||||
|
||||
### New features
|
||||
|
||||
For new features, please start a new [discussion](https://github.com/langchain-ai/langgraph/discussions), where the maintainers will help with scoping out the necessary changes.
|
||||
For new features, please start a new [discussion](https://forum.langchain.com/), where the maintainers will help with scoping out the necessary changes.
|
||||
|
||||
## Contribute Documentation
|
||||
|
||||
@@ -111,7 +111,6 @@ in a more abstract way than how-to guides or tutorials, and should be geared tow
|
||||
gaining a deeper understanding of the framework. Try to avoid excessively large code examples. The goal here is to
|
||||
impart perspective to the user rather than to finish a practical project. These guides should cover **why** things work the way they do.
|
||||
|
||||
|
||||
To quote the Diataxis website:
|
||||
|
||||
> The perspective of explanation is higher and wider than that of the other types. It does not take the user’s eye-level view, as in a how-to guide, or a close-up view of the machinery, like reference material. Its scope in each case is a topic - “an area of knowledge”, that somehow has to be bounded in a reasonable, meaningful way.
|
||||
@@ -187,9 +186,9 @@ Be concise, including in code samples.
|
||||
|
||||
## Setup
|
||||
|
||||
LangChain documentation consists of two components:
|
||||
LangGraph documentation consists of two components:
|
||||
|
||||
1. Main Documentation: Hosted at [https://langchain-ai.github.io](https://langchain-ai.github.io/langgraph/),
|
||||
1. Main Documentation: Hosted at [https://langchain-ai.github.io/langgraph/](https://langchain-ai.github.io/langgraph/),
|
||||
this comprehensive resource serves as the primary user-facing documentation.
|
||||
It covers a wide array of topics, including tutorials, use cases, integrations,
|
||||
and more, offering extensive guidance on building with LangGraph.
|
||||
@@ -250,17 +249,17 @@ make serve-docs
|
||||
|
||||
#### Linting
|
||||
|
||||
The documentation is linted from the **monorepo root**. To lint it, run the following from there:
|
||||
To spell check the docs, run the following from the `docs` directory:
|
||||
|
||||
```bash
|
||||
make spellcheck
|
||||
codespell --skip="*.ambr,*.lock,*.ipynb,*.yaml,*.zlib,*.css.map,*.js.map" --ignore-words-list="infor,thead,stdio,nd,jupyter,lets,lite,uis,deque" .
|
||||
```
|
||||
|
||||
### ️In-code Documentation
|
||||
|
||||
The in-code documentation is autogenerated from docstrings.
|
||||
|
||||
For the API reference to be useful, the codebase must be well-documented. This means that all functions, classes, and methods should have a docstring that explains what they do, what the arguments are, and what the return value is. This is a good practice in general, but it is especially important for LangChain because the API reference is the primary resource for developers to understand how to use the codebase.
|
||||
For the API reference to be useful, the codebase must be well-documented. This means that all functions, classes, and methods should have a docstring that explains what they do, what the arguments are, and what the return value is. This is a good practice in general, but it is especially important for LangGraph because the API reference is the primary resource for developers to understand how to use the codebase.
|
||||
|
||||
We generally follow the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) for docstrings.
|
||||
|
||||
@@ -291,4 +290,4 @@ def my_function(arg1: int, arg2: str) -> float:
|
||||
This is a description of the return value.
|
||||
"""
|
||||
return 3.14
|
||||
```
|
||||
```
|
||||
|
||||
@@ -73,11 +73,12 @@ While LangGraph can be used standalone, it also integrates seamlessly with any L
|
||||
|
||||
- [Guides](https://langchain-ai.github.io/langgraph/how-tos/): Quick, actionable code snippets for topics such as streaming, adding memory & persistence, and design patterns (e.g. branching, subgraphs, etc.).
|
||||
- [Reference](https://langchain-ai.github.io/langgraph/reference/graphs/): Detailed reference on core classes, methods, how to use the graph and checkpointing APIs, and higher-level prebuilt components.
|
||||
- [Examples](https://langchain-ai.github.io/langgraph/tutorials/overview/): Guided examples on getting started with LangGraph.
|
||||
- [Examples](https://langchain-ai.github.io/langgraph/examples/): Guided examples on getting started with LangGraph.
|
||||
- [LangChain Forum](https://forum.langchain.com/): Connect with the community and share all of your technical questions, ideas, and feedback.
|
||||
- [LangChain Academy](https://academy.langchain.com/courses/intro-to-langgraph): Learn the basics of LangGraph in our free, structured course.
|
||||
- [Templates](https://langchain-ai.github.io/langgraph/concepts/template_applications/): Pre-built reference apps for common agentic workflows (e.g. ReAct agent, memory, retrieval etc.) that can be cloned and adapted.
|
||||
- [Case studies](https://www.langchain.com/built-with-langgraph): Hear how industry leaders use LangGraph to ship AI applications at scale.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
LangGraph is inspired by [Pregel](https://research.google/pubs/pub37252/) and [Apache Beam](https://beam.apache.org/). The public interface draws inspiration from [NetworkX](https://networkx.org/documentation/latest/). LangGraph is built by LangChain Inc, the creators of LangChain, but can be used without LangChain.
|
||||
LangGraph is inspired by [Pregel](https://research.google/pubs/pub37252/) and [Apache Beam](https://beam.apache.org/). The public interface draws inspiration from [NetworkX](https://networkx.org/documentation/latest/). LangGraph is built by LangChain Inc, the creators of LangChain, but can be used without LangChain.
|
||||
+2
-2
@@ -15,8 +15,8 @@ build-prebuilt:
|
||||
fi
|
||||
uv run python -m _scripts.third_party_page.create_third_party_page stats.yml docs/agents/prebuilt.md --language python
|
||||
|
||||
build-docs: build-prebuilt
|
||||
uv run python -m mkdocs build --clean -f mkdocs.yml --strict
|
||||
build-docs: build-typedoc build-prebuilt
|
||||
TARGET_LANGUAGE=python 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
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
Lifecycle events: https://www.mkdocs.org/dev-guide/plugins/#events
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import posixpath
|
||||
@@ -15,8 +16,8 @@ from mkdocs.structure.files import Files, File
|
||||
from mkdocs.structure.pages import Page
|
||||
|
||||
from _scripts.generate_api_reference_links import update_markdown_with_imports
|
||||
from _scripts.notebook_convert import convert_notebook
|
||||
from _scripts.link_map import JS_LINK_MAP
|
||||
from _scripts.notebook_convert import convert_notebook
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig()
|
||||
@@ -100,10 +101,6 @@ REDIRECT_MAP = {
|
||||
"how-tos/create-react-agent-memory.ipynb": "agents/memory.md",
|
||||
"how-tos/create-react-agent-system-prompt.ipynb": "agents/context.md#prompts",
|
||||
"how-tos/create-react-agent-structured-output.ipynb": "agents/agents.md#structured-output",
|
||||
# Time-travel
|
||||
"how-tos/human_in_the_loop/edit-graph-state.ipynb": "how-tos/human_in_the_loop/time-travel.md",
|
||||
# breakpoints
|
||||
"how-tos/human_in_the_loop/dynamic_breakpoints.ipynb": "how-tos/human_in_the_loop/breakpoints.md",
|
||||
# misc
|
||||
"prebuilt.md": "agents/prebuilt.md",
|
||||
"reference/prebuilt.md": "reference/agents.md",
|
||||
@@ -125,6 +122,11 @@ REDIRECT_MAP = {
|
||||
"how-tos/review-tool-calls-functional.ipynb": "how-tos/use-functional-api.md",
|
||||
"how-tos/create-react-agent-hitl.ipynb": "how-tos/human_in_the_loop/add-human-in-the-loop.md",
|
||||
"agents/human-in-the-loop.md": "how-tos/human_in_the_loop/add-human-in-the-loop.md",
|
||||
"how-tos/human_in_the_loop/dynamic_breakpoints.ipynb": "how-tos/human_in_the_loop/breakpoints.md",
|
||||
"concepts/breakpoints.md": "concepts/human_in_the_loop.md",
|
||||
"how-tos/human_in_the_loop/breakpoints.md": "how-tos/human_in_the_loop/add-human-in-the-loop.md",
|
||||
"cloud/how-tos/human_in_the_loop_breakpoint.md": "cloud/how-tos/add-human-in-the-loop.md",
|
||||
"how-tos/human_in_the_loop/edit-graph-state.ipynb": "how-tos/human_in_the_loop/time-travel.md",
|
||||
}
|
||||
|
||||
|
||||
@@ -308,6 +310,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,
|
||||
@@ -330,16 +338,15 @@ def _on_page_markdown_with_config(
|
||||
markdown = _highlight_code_blocks(markdown)
|
||||
|
||||
# Apply conditional rendering for code blocks
|
||||
target_language = kwargs.get("target_language", "python")
|
||||
markdown = _apply_conditional_rendering(markdown, target_language)
|
||||
if target_language == "js":
|
||||
markdown = _apply_conditional_rendering(markdown, TARGET_LANGUAGE)
|
||||
if TARGET_LANGUAGE == "js":
|
||||
markdown = _resolve_cross_references(markdown, JS_LINK_MAP)
|
||||
elif target_language == "python":
|
||||
elif TARGET_LANGUAGE == "python":
|
||||
# Via a dedicated plugin
|
||||
pass
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unsupported target language: {target_language}. "
|
||||
f"Unsupported target language: {TARGET_LANGUAGE}. "
|
||||
"Supported languages are 'python' and 'js'."
|
||||
)
|
||||
|
||||
@@ -356,12 +363,16 @@ def _on_page_markdown_with_config(
|
||||
|
||||
|
||||
def on_page_markdown(markdown: str, page: Page, **kwargs: Dict[str, Any]):
|
||||
return _on_page_markdown_with_config(
|
||||
markdown,
|
||||
page,
|
||||
add_api_references=True,
|
||||
**kwargs,
|
||||
finalized_markdown = (
|
||||
_on_page_markdown_with_config(
|
||||
markdown,
|
||||
page,
|
||||
add_api_references=True,
|
||||
**kwargs,
|
||||
)
|
||||
)
|
||||
page.meta["original_markdown"] = finalized_markdown
|
||||
return finalized_markdown
|
||||
|
||||
|
||||
# redirects
|
||||
@@ -431,20 +442,51 @@ height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
|
||||
else:
|
||||
return html # fallback if no <body> found
|
||||
|
||||
def _inject_markdown_into_html(html: str, page: Page) -> str:
|
||||
"""Inject the original markdown content into the HTML page as JSON."""
|
||||
original_markdown = page.meta.get("original_markdown", "")
|
||||
if not original_markdown:
|
||||
return html
|
||||
markdown_data = {
|
||||
"markdown": original_markdown,
|
||||
"title": page.title or "Page Content",
|
||||
"url": page.url or "",
|
||||
}
|
||||
|
||||
def on_post_page(output: str, page: Page, config: MkDocsConfig) -> str:
|
||||
# Properly escape the JSON for HTML
|
||||
json_content = json.dumps(markdown_data, ensure_ascii=False)
|
||||
|
||||
json_content = (
|
||||
json_content.replace("</", "\\u003c/")
|
||||
.replace("<script", "\\u003cscript")
|
||||
.replace("</script", "\\u003c/script")
|
||||
)
|
||||
|
||||
script_content = (
|
||||
f'<script id="page-markdown-content" '
|
||||
f'type="application/json">{json_content}</script>'
|
||||
)
|
||||
|
||||
# Insert before </head> if it exists, otherwise before </body>
|
||||
if "</head>" not in html:
|
||||
raise ValueError(
|
||||
"HTML does not contain </head> tag. Cannot inject markdown content."
|
||||
)
|
||||
return html.replace("</head>", f"{script_content}</head>")
|
||||
|
||||
def on_post_page(html: str, page: Page, config: MkDocsConfig) -> str:
|
||||
"""Inject Google Tag Manager noscript tag immediately after <body>.
|
||||
|
||||
Args:
|
||||
output: The HTML output of the page.
|
||||
html: The HTML output of the page.
|
||||
page: The page instance.
|
||||
config: The MkDocs configuration object.
|
||||
|
||||
Returns:
|
||||
modified HTML output with GTM code injected.
|
||||
"""
|
||||
return _inject_gtm(output)
|
||||
|
||||
html = _inject_markdown_into_html(html, page)
|
||||
return _inject_gtm(html)
|
||||
|
||||
# Create HTML files for redirects after site dir has been built
|
||||
def on_post_build(config):
|
||||
|
||||
@@ -8,4 +8,4 @@ This section contains additional resources for LangGraph.
|
||||
- [FAQ](../concepts/faq.md): A collection of frequently asked questions about LangGraph.
|
||||
- [llms.txt](../llms-txt-overview.md): A list of documentation files in the `llms.txt` format that allow LLMs and agents to access our documentation.
|
||||
- [LangChain Forum](https://forum.langchain.com/): A place to ask questions and get help from other LangGraph users.
|
||||
- [Troubleshooting](../troubleshooting/errors/index.md.md): A collection of troubleshooting guides for common issues.
|
||||
- [Troubleshooting](../troubleshooting/errors/index.md): A collection of troubleshooting guides for common issues.
|
||||
+40
-23
@@ -8,60 +8,75 @@ Context includes *any* data outside the message list that can shape behavior. Th
|
||||
- Internal state updated during a multi-step reasoning process.
|
||||
- Persistent memory or facts from previous interactions.
|
||||
|
||||
LangGraph provides **three** primary ways to supply context:
|
||||
LangGraph provides **three** primary ways to manage context:
|
||||
|
||||
| Type | Description | Mutable? | Lifetime |
|
||||
|------------------------------------------------------------------------------|-----------------------------------------------|----------|-------------------------|
|
||||
| [**Config**](#config-static-context) | data passed at the start of a run | ❌ | per run |
|
||||
| [**Runtime Context**](#runtime-context) | data passed at the start of a run | ❌ | per run |
|
||||
| [**Short-term memory (State)**](#short-term-memory-mutable-context) | dynamic data that can change during execution | ✅ | per run or conversation |
|
||||
| [**Long-term memory (Store)**](#long-term-memory-cross-conversation-context) | data that can be shared between conversations | ✅ | across conversations |
|
||||
|
||||
## Provide runtime context
|
||||
### Runtime Context
|
||||
|
||||
### Config (static context)
|
||||
Runtime context is for immutable data like user metadata, tools, db connections, etc. Use this when you have values that don't change mid-run.
|
||||
|
||||
Config is for immutable data like user metadata or API keys. Use
|
||||
when you have values that don't change mid-run.
|
||||
!!! version-added "New in LangGraph v0.6: `Runtime.context` replaces `config['configurable']`"
|
||||
|
||||
Specify configuration using a key called **"configurable"** which is reserved
|
||||
for this purpose:
|
||||
The `Runtime` object is recommended to access static context and runtime-specific information like the store and stream writer.
|
||||
|
||||
!!! note
|
||||
|
||||
Runtime context refers to local context: data and dependencies your code needs to run. It does not refer to:
|
||||
|
||||
* The LLM context, which is the data passed into the LLM's prompt.
|
||||
* The "context window", which is the maximum number of tokens that can be passed to the LLM.
|
||||
|
||||
You likely want to use the local context to optimize the LLM's context window. For example, you
|
||||
could use a user id to fetch a user's name and information from a database to populate the context window with relevant memories.
|
||||
|
||||
Specify static context via the `context` argument to `invoke` / `stream`, which is reserved for this purpose:
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class ContextSchema:
|
||||
user_name: str
|
||||
|
||||
graph.invoke( # (1)!
|
||||
{"messages": [{"role": "user", "content": "hi!"}]}, # (2)!
|
||||
# highlight-next-line
|
||||
config={"configurable": {"user_id": "user_123"}} # (3)!
|
||||
context={"user_name": "John Smith"} # (3)!
|
||||
)
|
||||
```
|
||||
|
||||
1. This is the invocation of the agent or graph. The `invoke` method runs the underlying graph with the provided input.
|
||||
2. This example uses messages as an input, which is common, but your application may use different input structures.
|
||||
3. This is where you pass the configuration data. The `config` parameter allows you to provide additional context that the agent can use during its execution.
|
||||
3. This is where you pass the runtime data. The `context` parameter allows you to provide additional dependencies that the agent can use during its execution.
|
||||
|
||||
=== "Agent prompt"
|
||||
|
||||
```python
|
||||
from langchain_core.messages import AnyMessage
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.runtime import get_runtime
|
||||
from langgraph.prebuilt.chat_agent_executor import AgentState
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
# highlight-next-line
|
||||
def prompt(state: AgentState, config: RunnableConfig) -> list[AnyMessage]:
|
||||
user_name = config["configurable"].get("user_name")
|
||||
system_msg = f"You are a helpful assistant. Address the user as {user_name}."
|
||||
def prompt(state: AgentState) -> list[AnyMessage]:
|
||||
runtime = get_runtime(ContextSchema)
|
||||
system_msg = f"You are a helpful assistant. Address the user as {runtime.context.user_name}."
|
||||
return [{"role": "system", "content": system_msg}] + state["messages"]
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_weather],
|
||||
prompt=prompt
|
||||
prompt=prompt,
|
||||
context_schema=ContextSchema
|
||||
)
|
||||
|
||||
agent.invoke(
|
||||
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
|
||||
# highlight-next-line
|
||||
config={"configurable": {"user_name": "John Smith"}}
|
||||
context={"user_name": "John Smith"}
|
||||
)
|
||||
```
|
||||
|
||||
@@ -70,11 +85,11 @@ graph.invoke( # (1)!
|
||||
=== "Workflow node"
|
||||
|
||||
```python
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.runtime import Runtime
|
||||
|
||||
# highlight-next-line
|
||||
def node(state: State, config: RunnableConfig):
|
||||
user_name = config["configurable"].get("user_name")
|
||||
def node(state: State, config: Runtime[ContextSchema]):
|
||||
user_name = runtime.context.user_name
|
||||
...
|
||||
```
|
||||
|
||||
@@ -83,14 +98,16 @@ graph.invoke( # (1)!
|
||||
=== "In a tool"
|
||||
|
||||
```python
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.runtime import get_runtime
|
||||
|
||||
@tool
|
||||
# highlight-next-line
|
||||
def get_user_info(config: RunnableConfig) -> str:
|
||||
def get_user_email() -> str:
|
||||
"""Retrieve user information based on user ID."""
|
||||
user_id = config["configurable"].get("user_id")
|
||||
return "User is John Smith" if user_id == "user_123" else "Unknown user"
|
||||
# simulate fetching user info from a database
|
||||
runtime = get_runtime(ContextSchema)
|
||||
email = get_user_email_from_db(runtime.context.user_name)
|
||||
return email
|
||||
```
|
||||
|
||||
See the [tool calling guide](../how-tos/tool-calling.md#configuration) for details.
|
||||
|
||||
@@ -15,7 +15,7 @@ To evaluate your agent's performance you can use `LangSmith` [evaluations](https
|
||||
def evaluator(*, outputs: dict, reference_outputs: dict):
|
||||
# compare agent outputs against reference outputs
|
||||
output_messages = outputs["messages"]
|
||||
reference_messages = reference["messages"]
|
||||
reference_messages = reference_outputs["messages"]
|
||||
score = compare_messages(output_messages, reference_messages)
|
||||
return {"key": "evaluator_score", "score": score}
|
||||
```
|
||||
|
||||
+41
-14
@@ -55,14 +55,16 @@ The `langchain-mcp-adapters` package enables agents to use tools defined across
|
||||
|
||||
=== "In a workflow"
|
||||
|
||||
```python
|
||||
```python title="Workflow using MCP tools with ToolNode"
|
||||
from langchain_mcp_adapters.client import MultiServerMCPClient
|
||||
from langgraph.graph import StateGraph, MessagesState, START
|
||||
from langgraph.prebuilt import ToolNode, tools_condition
|
||||
|
||||
from langchain.chat_models import init_chat_model
|
||||
model = init_chat_model("openai:gpt-4.1")
|
||||
from langgraph.graph import StateGraph, MessagesState, START, END
|
||||
from langgraph.prebuilt import ToolNode
|
||||
|
||||
# Initialize the model
|
||||
model = init_chat_model("anthropic:claude-3-5-sonnet-latest")
|
||||
|
||||
# Set up MCP client
|
||||
client = MultiServerMCPClient(
|
||||
{
|
||||
"math": {
|
||||
@@ -80,22 +82,47 @@ The `langchain-mcp-adapters` package enables agents to use tools defined across
|
||||
)
|
||||
tools = await client.get_tools()
|
||||
|
||||
def call_model(state: MessagesState):
|
||||
response = model.bind_tools(tools).invoke(state["messages"])
|
||||
return {"messages": response}
|
||||
# Bind tools to model
|
||||
model_with_tools = model.bind_tools(tools)
|
||||
|
||||
# Create ToolNode
|
||||
tool_node = ToolNode(tools)
|
||||
|
||||
def should_continue(state: MessagesState):
|
||||
messages = state["messages"]
|
||||
last_message = messages[-1]
|
||||
if last_message.tool_calls:
|
||||
return "tools"
|
||||
return END
|
||||
|
||||
# Define call_model function
|
||||
async def call_model(state: MessagesState):
|
||||
messages = state["messages"]
|
||||
response = await model_with_tools.ainvoke(messages)
|
||||
return {"messages": [response]}
|
||||
|
||||
# Build the graph
|
||||
builder = StateGraph(MessagesState)
|
||||
builder.add_node(call_model)
|
||||
builder.add_node(ToolNode(tools))
|
||||
builder.add_node("call_model", call_model)
|
||||
builder.add_node("tools", tool_node)
|
||||
|
||||
builder.add_edge(START, "call_model")
|
||||
builder.add_conditional_edges(
|
||||
"call_model",
|
||||
tools_condition,
|
||||
should_continue,
|
||||
)
|
||||
builder.add_edge("tools", "call_model")
|
||||
|
||||
# Compile the graph
|
||||
graph = builder.compile()
|
||||
math_response = await graph.ainvoke({"messages": "what's (3 + 5) x 12?"})
|
||||
weather_response = await graph.ainvoke({"messages": "what is the weather in nyc?"})
|
||||
|
||||
# Test the graph
|
||||
math_response = await graph.ainvoke(
|
||||
{"messages": [{"role": "user", "content": "what's (3 + 5) x 12?"}]}
|
||||
)
|
||||
weather_response = await graph.ainvoke(
|
||||
{"messages": [{"role": "user", "content": "what is the weather in nyc?"}]}
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -148,4 +175,4 @@ if __name__ == "__main__":
|
||||
|
||||
- [MCP documentation](https://modelcontextprotocol.io/introduction)
|
||||
- [MCP Transport documentation](https://modelcontextprotocol.io/docs/concepts/transports)
|
||||
- [langchain_mcp_adapters](https://github.com/langchain-ai/langchain-mcp-adapters)
|
||||
- [langchain_mcp_adapters](https://github.com/langchain-ai/langchain-mcp-adapters)
|
||||
|
||||
@@ -7,7 +7,7 @@ LangGraph provides built-in support for [LLMs (language models)](https://python.
|
||||
|
||||
Use [`init_chat_model`](https://python.langchain.com/docs/how_to/chat_models_universal_init/) to initialize models:
|
||||
|
||||
{!snippets/chat_model_tabs.md!}
|
||||
{% include-markdown "../../snippets/chat_model_tabs.md" %}
|
||||
|
||||
### Instantiate a model directly
|
||||
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
# Egress for Subscription Metrics and Operational Metadata
|
||||
|
||||
> **Important: Self Hosted Only**
|
||||
> This section only applies to customers who are not running in offline mode and assumes you are using a self-hosted LangGraph Platform instance.
|
||||
> This does not apply to SaaS or Hybrid deployments.
|
||||
|
||||
Self-Hosted LangGraph Platform instances store all information locally and will never send sensitive information outside of your network. We currently only track platform usage for billing purposes according to the entitlements in your order. In order to better remotely support our customers, we do require egress to `https://beacon.langchain.com`.
|
||||
|
||||
In the future, we will be introducing support diagnostics to help us ensure that the LangGraph Platform is running at an optimal level within your environment.
|
||||
|
||||
> **Warning**
|
||||
> **This will require egress to `https://beacon.langchain.com` from your network.**
|
||||
> **If using an API key, you will also need to allow egress to `https://api.smith.langchain.com` or `https://eu.api.smith.langchain.com` for API key verification.**
|
||||
|
||||
Generally, data that we send to Beacon can be categorized as follows:
|
||||
|
||||
- **Subscription Metrics**
|
||||
- Subscription metrics are used to determine level of access and utilization of LangSmith. This includes, but are not limited to:
|
||||
- Nodes Executed
|
||||
- Runs Executed
|
||||
- License Key Verification
|
||||
- **Operational Metadata**
|
||||
- This metadata will contain and collect the above subscription metrics to assist with remote support, allowing the LangChain team to diagnose and troubleshoot performance issues more effectively and proactively.
|
||||
|
||||
## Example Payloads
|
||||
|
||||
In an effort to maximize transparency, we provide sample payloads here:
|
||||
|
||||
### License Verification (If using an Enterprise License)
|
||||
|
||||
**Endpoint:**
|
||||
|
||||
`POST beacon.langchain.com/v1/beacon/verify`
|
||||
|
||||
**Request:**
|
||||
|
||||
```json
|
||||
{
|
||||
"license": "<YOUR_LICENSE_KEY>"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "Valid JWT" // Short-lived JWT token to avoid repeated license checks
|
||||
}
|
||||
```
|
||||
|
||||
### Api Key Verification (If using a LangSmith API Key)
|
||||
|
||||
**Endpoint:**
|
||||
`POST api.smith.langchain.com/auth`
|
||||
|
||||
**Request:**
|
||||
|
||||
```json
|
||||
"Headers": {
|
||||
X-Api-Key: <YOUR_API_KEY>
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"org_config": {
|
||||
"org_id": "3a1c2b6f-4430-4b92-8a5b-79b8b567bbc1",
|
||||
... // Additional organization details
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Usage Reporting
|
||||
|
||||
**Endpoint:**
|
||||
|
||||
`POST beacon.langchain.com/v1/metadata/submit`
|
||||
|
||||
**Request:**
|
||||
|
||||
```json
|
||||
{
|
||||
"license": "<YOUR_LICENSE_KEY>",
|
||||
"from_timestamp": "2025-01-06T09:00:00Z",
|
||||
"to_timestamp": "2025-01-06T10:00:00Z",
|
||||
"tags": {
|
||||
"langgraph.python.version": "0.1.0",
|
||||
"langgraph_api.version": "0.2.0",
|
||||
"langgraph.platform.revision": "abc123",
|
||||
"langgraph.platform.variant": "standard",
|
||||
"langgraph.platform.host": "host-1",
|
||||
"langgraph.platform.tenant_id": "3a1c2b6f-4430-4b92-8a5b-79b8b567bbc1",
|
||||
"langgraph.platform.project_id": "c5b5f53a-4716-4326-8967-d4f7f7799735",
|
||||
"langgraph.platform.plan": "enterprise",
|
||||
"user_app.uses_indexing": "true",
|
||||
"user_app.uses_custom_app": "false",
|
||||
"user_app.uses_custom_auth": "true",
|
||||
"user_app.uses_thread_ttl": "true",
|
||||
"user_app.uses_store_ttl": "false"
|
||||
},
|
||||
"measures": {
|
||||
"langgraph.platform.runs": 150,
|
||||
"langgraph.platform.nodes": 450
|
||||
},
|
||||
"logs": []
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
"204 No Content"
|
||||
```
|
||||
|
||||
## Our Commitment
|
||||
|
||||
LangChain will not store any sensitive information in the Subscription Metrics or Operational Metadata. Any data collected will not be shared with a third party. If you have any concerns about the data being sent, please reach out to your account team.
|
||||
@@ -23,6 +23,8 @@ Before deploying, review the [conceptual guide for the Self-Hosted Control Plane
|
||||
|
||||
kubectl get storageclass
|
||||
|
||||
1. Egress to `https://beacon.langchain.com` from your network. This is required for license verification and usage reporting if not running in air-gapped mode. See the [Egress documentation](../../cloud/deployment/egress.md) for more details.
|
||||
|
||||
## Setup
|
||||
|
||||
1. As part of configuring your Self-Hosted LangSmith instance, you enable the `langgraphPlatform` option. This will provision a few key resources.
|
||||
|
||||
@@ -35,7 +35,6 @@ Before deploying, review the [conceptual guide for the Self-Hosted Data Plane](.
|
||||
1. Configure your `langgraph-dataplane-values.yaml` file.
|
||||
|
||||
config:
|
||||
langgraphPlatformLicenseKey: "" # Your LangGraph Platform license key
|
||||
langsmithApiKey: "" # API Key of your Workspace
|
||||
langsmithWorkspaceId: "" # Workspace ID
|
||||
hostBackendUrl: "https://api.host.langchain.com" # Only override this if on EU
|
||||
|
||||
@@ -108,11 +108,11 @@ from langgraph.graph import StateGraph, END, START
|
||||
from my_agent.utils.nodes import call_model, should_continue, tool_node # import nodes
|
||||
from my_agent.utils.state import AgentState # import state
|
||||
|
||||
# Define the config
|
||||
class GraphConfig(TypedDict):
|
||||
# Define the runtime context
|
||||
class GraphContext(TypedDict):
|
||||
model_name: Literal["anthropic", "openai"]
|
||||
|
||||
workflow = StateGraph(AgentState, config_schema=GraphConfig)
|
||||
workflow = StateGraph(AgentState, context_schema=GraphContext)
|
||||
workflow.add_node("agent", call_model)
|
||||
workflow.add_node("action", tool_node)
|
||||
workflow.add_edge(START, "agent")
|
||||
|
||||
@@ -121,11 +121,11 @@ from langgraph.graph import StateGraph, END, START
|
||||
from my_agent.utils.nodes import call_model, should_continue, tool_node # import nodes
|
||||
from my_agent.utils.state import AgentState # import state
|
||||
|
||||
# Define the config
|
||||
class GraphConfig(TypedDict):
|
||||
# Define the runtime context
|
||||
class GraphContext(TypedDict):
|
||||
model_name: Literal["anthropic", "openai"]
|
||||
|
||||
workflow = StateGraph(AgentState, config_schema=GraphConfig)
|
||||
workflow = StateGraph(AgentState, context_schema=GraphContext)
|
||||
workflow.add_node("agent", call_model)
|
||||
workflow.add_node("action", tool_node)
|
||||
workflow.add_edge(START, "agent")
|
||||
|
||||
@@ -24,6 +24,7 @@ Before deploying, review the [conceptual guide for the Standalone Container](../
|
||||
1. `LANGSMITH_API_KEY`: (if using [Lite](../../concepts/langgraph_server.md#server-versions)) LangSmith API key. This will be used to authenticate ONCE at server start up.
|
||||
1. `LANGGRAPH_CLOUD_LICENSE_KEY`: (if using [Enterprise](../../concepts/langgraph_data_plane.md#licensing)) LangGraph Platform license key. This will be used to authenticate ONCE at server start up.
|
||||
1. `LANGSMITH_ENDPOINT`: To send traces to a [self-hosted LangSmith](https://docs.smith.langchain.com/self_hosting) instance, set `LANGSMITH_ENDPOINT` to the hostname of the self-hosted LangSmith instance.
|
||||
1. Egress to `https://beacon.langchain.com` from your network. This is required for license verification and usage reporting if not running in air-gapped mode. See the [Egress documentation](../../cloud/deployment/egress.md) for more details.
|
||||
|
||||
## Kubernetes (Helm)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
To review, edit, and approve tool calls in an agent or workflow, use LangGraph's [human-in-the-loop](../../concepts/human_in_the_loop.md) features.
|
||||
|
||||
## LangGraph API invoke & resume
|
||||
## Dynamic interrupts
|
||||
|
||||
=== "Python"
|
||||
|
||||
@@ -30,9 +30,7 @@ To review, edit, and approve tool calls in an agent or workflow, use LangGraph's
|
||||
# > [
|
||||
# > {
|
||||
# > 'value': {'text_to_revise': 'original text'},
|
||||
# > 'resumable': True,
|
||||
# > 'ns': ['human_node:fc722478-2f21-0578-c572-d9fc4dd07c3b'],
|
||||
# > 'when': 'during'
|
||||
# > 'id': '...',
|
||||
# > }
|
||||
# > ]
|
||||
|
||||
@@ -203,9 +201,7 @@ To review, edit, and approve tool calls in an agent or workflow, use LangGraph's
|
||||
# > [
|
||||
# > {
|
||||
# > 'value': {'text_to_revise': 'original text'},
|
||||
# > 'resumable': True,
|
||||
# > 'ns': ['human_node:fc722478-2f21-0578-c572-d9fc4dd07c3b'],
|
||||
# > 'when': 'during'
|
||||
# > 'id': '...',
|
||||
# > }
|
||||
# > ]
|
||||
|
||||
@@ -305,6 +301,185 @@ To review, edit, and approve tool calls in an agent or workflow, use LangGraph's
|
||||
}"
|
||||
```
|
||||
|
||||
## Static interrupts
|
||||
|
||||
Static interrupts (also known as static breakpoints) are triggered either before or after a node executes.
|
||||
|
||||
!!! warning
|
||||
|
||||
Static interrupts are **not** recommended for human-in-the-loop workflows. They are best used for debugging and testing.
|
||||
|
||||
You can set static interrupts by specifying `interrupt_before` and `interrupt_after` at compile time:
|
||||
|
||||
```python
|
||||
# highlight-next-line
|
||||
graph = graph_builder.compile( # (1)!
|
||||
# highlight-next-line
|
||||
interrupt_before=["node_a"], # (2)!
|
||||
# highlight-next-line
|
||||
interrupt_after=["node_b", "node_c"], # (3)!
|
||||
)
|
||||
```
|
||||
|
||||
1. The breakpoints are set during `compile` time.
|
||||
2. `interrupt_before` specifies the nodes where execution should pause before the node is executed.
|
||||
3. `interrupt_after` specifies the nodes where execution should pause after the node is executed.
|
||||
|
||||
Alternatively, you can set static interrupts at run time:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
# highlight-next-line
|
||||
await client.runs.wait( # (1)!
|
||||
thread_id,
|
||||
assistant_id,
|
||||
inputs=inputs,
|
||||
# highlight-next-line
|
||||
interrupt_before=["node_a"], # (2)!
|
||||
# highlight-next-line
|
||||
interrupt_after=["node_b", "node_c"] # (3)!
|
||||
)
|
||||
```
|
||||
|
||||
1. `client.runs.wait` is called with the `interrupt_before` and `interrupt_after` parameters. This is a run-time configuration and can be changed for every invocation.
|
||||
2. `interrupt_before` specifies the nodes where execution should pause before the node is executed.
|
||||
3. `interrupt_after` specifies the nodes where execution should pause after the node is executed.
|
||||
|
||||
=== "JavaScript"
|
||||
|
||||
```js
|
||||
// highlight-next-line
|
||||
await client.runs.wait( // (1)!
|
||||
threadID,
|
||||
assistantID,
|
||||
{
|
||||
input: input,
|
||||
// highlight-next-line
|
||||
interruptBefore: ["node_a"], // (2)!
|
||||
// highlight-next-line
|
||||
interruptAfter: ["node_b", "node_c"] // (3)!
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
1. `client.runs.wait` is called with the `interruptBefore` and `interruptAfter` parameters. This is a run-time configuration and can be changed for every invocation.
|
||||
2. `interruptBefore` specifies the nodes where execution should pause before the node is executed.
|
||||
3. `interruptAfter` specifies the nodes where execution should pause after the node is executed.
|
||||
|
||||
=== "cURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"interrupt_before\": [\"node_a\"],
|
||||
\"interrupt_after\": [\"node_b\", \"node_c\"],
|
||||
\"input\": <INPUT>
|
||||
}"
|
||||
```
|
||||
|
||||
The following example shows how to add static interrupts:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
|
||||
# Using the graph deployed with the name "agent"
|
||||
assistant_id = "agent"
|
||||
|
||||
# create a thread
|
||||
thread = await client.threads.create()
|
||||
thread_id = thread["thread_id"]
|
||||
|
||||
# Run the graph until the breakpoint
|
||||
result = await client.runs.wait(
|
||||
thread_id,
|
||||
assistant_id,
|
||||
input=inputs # (1)!
|
||||
)
|
||||
|
||||
# Resume the graph
|
||||
await client.runs.wait(
|
||||
thread_id,
|
||||
assistant_id,
|
||||
input=None # (2)!
|
||||
)
|
||||
```
|
||||
|
||||
1. The graph is run until the first breakpoint is hit.
|
||||
2. The graph is resumed by passing in `None` for the input. This will run the graph until the next breakpoint is hit.
|
||||
|
||||
=== "JavaScript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
|
||||
// Using the graph deployed with the name "agent"
|
||||
const assistantID = "agent";
|
||||
|
||||
// create a thread
|
||||
const thread = await client.threads.create();
|
||||
const threadID = thread["thread_id"];
|
||||
|
||||
// Run the graph until the breakpoint
|
||||
const result = await client.runs.wait(
|
||||
threadID,
|
||||
assistantID,
|
||||
{ input: input } // (1)!
|
||||
);
|
||||
|
||||
// Resume the graph
|
||||
await client.runs.wait(
|
||||
threadID,
|
||||
assistantID,
|
||||
{ input: null } // (2)!
|
||||
);
|
||||
```
|
||||
|
||||
1. The graph is run until the first breakpoint is hit.
|
||||
2. The graph is resumed by passing in `null` for the input. This will run the graph until the next breakpoint is hit.
|
||||
|
||||
=== "cURL"
|
||||
|
||||
Create a thread:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{}'
|
||||
```
|
||||
|
||||
Run the graph until the breakpoint:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": <INPUT>
|
||||
}"
|
||||
```
|
||||
|
||||
Resume the graph:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\"
|
||||
}"
|
||||
```
|
||||
|
||||
|
||||
## Learn more
|
||||
|
||||
- [Human-in-the-loop conceptual guide](../../concepts/human_in_the_loop.md): learn more about LangGraph human-in-the-loop features.
|
||||
|
||||
@@ -2,21 +2,20 @@
|
||||
|
||||
In this guide we will show how to create, configure, and manage an [assistant](../../concepts/assistants.md).
|
||||
|
||||
First, as a brief refresher on the concept of configurations, consider the following simple `call_model` node and configuration schema. Observe that this node tries to read and use the `model_name` as defined by the `config` object's `configurable`.
|
||||
First, as a brief refresher on the concept of runtime context, consider the following simple `call_model` node and context schema. Observe that this node tries to read and use the `model_provider` as defined by the `Runtime` object's `context` property.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class ContextSchema:
|
||||
llm_provider: str = "anthropic"
|
||||
|
||||
class ConfigSchema(TypedDict):
|
||||
model_name: str
|
||||
builder = StateGraph(AgentState, context_schema=ContextSchema)
|
||||
|
||||
builder = StateGraph(AgentState, config_schema=ConfigSchema)
|
||||
|
||||
def call_model(state, config):
|
||||
def call_model(state, runtime: Runtime[ContextSchema]):
|
||||
messages = state["messages"]
|
||||
model_name = config.get('configurable', {}).get("model_name", "anthropic")
|
||||
model = _get_model(model_name)
|
||||
model = _get_model(runtime.context.llm_provider)
|
||||
response = model.invoke(messages)
|
||||
# We return a list, because this will get added to the existing list
|
||||
return {"messages": [response]}
|
||||
@@ -44,7 +43,7 @@ First, as a brief refresher on the concept of configurations, consider the follo
|
||||
}
|
||||
```
|
||||
|
||||
For more information on configurations, [see here](../../concepts/low_level.md#configuration).
|
||||
For more information on runtime context, [see here](../../concepts/low_level.md#runtime-context).
|
||||
|
||||
## Create an assistant
|
||||
|
||||
|
||||
@@ -30,17 +30,33 @@ export default {
|
||||
|
||||
Next, define your UI components in your `langgraph.json` configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"node_version": "20",
|
||||
"graphs": {
|
||||
"agent": "./src/agent/index.ts:graph"
|
||||
},
|
||||
"ui": {
|
||||
"agent": "./src/agent/ui.tsx"
|
||||
}
|
||||
}
|
||||
```
|
||||
=== "Python agent"
|
||||
|
||||
```json title="langgraph.json"
|
||||
{
|
||||
"node_version": "20",
|
||||
"graphs": {
|
||||
"agent": "./src/agent.py:graph"
|
||||
},
|
||||
"ui": {
|
||||
"agent": "./src/agent/ui.tsx"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "JS agent"
|
||||
|
||||
```json title="langgraph.json"
|
||||
{
|
||||
"node_version": "20",
|
||||
"graphs": {
|
||||
"agent": "./src/agent/index.ts:graph"
|
||||
},
|
||||
"ui": {
|
||||
"agent": "./src/agent/ui.tsx"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `ui` section points to the UI components that will be used by graphs. By default, we recommend using the same key as the graph name, but you can split out the components however you like, see [Customise the namespace of UI components](#customise-the-namespace-of-ui-components) for more details.
|
||||
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
# Set breakpoints using Server API
|
||||
|
||||
[Breakpoints](../../concepts/breakpoints.md) pause graph execution at defined points and let you step through each stage. They use LangGraph's [**persistence layer**](../../concepts/persistence.md), which saves the graph state after each step.
|
||||
|
||||
With breakpoints, you can inspect the graph's state and node inputs at any point. Execution pauses indefinitely until you resume, as the checkpointer preserves the state.
|
||||
|
||||
!!! tip
|
||||
|
||||
For conceptual information on breakpoints, see [Breakpoints](../../concepts/breakpoints.md).
|
||||
|
||||
## Set static breakpoints
|
||||
|
||||
Static breakpoints are triggered either before or after a node executes. You can set static breakpoints by specifying `interrupt_before` and `interrupt_after` at compile time or run time.
|
||||
|
||||
=== "Compile time"
|
||||
|
||||
```python
|
||||
# highlight-next-line
|
||||
graph = graph_builder.compile( # (1)!
|
||||
# highlight-next-line
|
||||
interrupt_before=["node_a"], # (2)!
|
||||
# highlight-next-line
|
||||
interrupt_after=["node_b", "node_c"], # (3)!
|
||||
)
|
||||
```
|
||||
|
||||
1. The breakpoints are set during `compile` time.
|
||||
2. `interrupt_before` specifies the nodes where execution should pause before the node is executed.
|
||||
3. `interrupt_after` specifies the nodes where execution should pause after the node is executed.
|
||||
|
||||
=== "Run time"
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
# highlight-next-line
|
||||
await client.runs.wait( # (1)!
|
||||
thread_id,
|
||||
assistant_id,
|
||||
inputs=inputs,
|
||||
# highlight-next-line
|
||||
interrupt_before=["node_a"], # (2)!
|
||||
# highlight-next-line
|
||||
interrupt_after=["node_b", "node_c"] # (3)!
|
||||
)
|
||||
```
|
||||
|
||||
1. `client.runs.wait` is called with the `interrupt_before` and `interrupt_after` parameters. This is a run-time configuration and can be changed for every invocation.
|
||||
2. `interrupt_before` specifies the nodes where execution should pause before the node is executed.
|
||||
3. `interrupt_after` specifies the nodes where execution should pause after the node is executed.
|
||||
|
||||
=== "JavaScript"
|
||||
|
||||
```js
|
||||
// highlight-next-line
|
||||
await client.runs.wait( // (1)!
|
||||
threadID,
|
||||
assistantID,
|
||||
{
|
||||
input: input,
|
||||
// highlight-next-line
|
||||
interruptBefore: ["node_a"], // (2)!
|
||||
// highlight-next-line
|
||||
interruptAfter: ["node_b", "node_c"] // (3)!
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
1. `client.runs.wait` is called with the `interruptBefore` and `interruptAfter` parameters. This is a run-time configuration and can be changed for every invocation.
|
||||
2. `interruptBefore` specifies the nodes where execution should pause before the node is executed.
|
||||
3. `interruptAfter` specifies the nodes where execution should pause after the node is executed.
|
||||
|
||||
=== "cURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"interrupt_before\": [\"node_a\"],
|
||||
\"interrupt_after\": [\"node_b\", \"node_c\"],
|
||||
\"input\": <INPUT>
|
||||
}"
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
This example shows how to add **static** breakpoints. See [Use breakpoints](../../how-tos/human_in_the_loop/breakpoints.md) for more options on adding breakpoints.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
|
||||
# Using the graph deployed with the name "agent"
|
||||
assistant_id = "agent"
|
||||
|
||||
# create a thread
|
||||
thread = await client.threads.create()
|
||||
thread_id = thread["thread_id"]
|
||||
|
||||
# Run the graph until the breakpoint
|
||||
result = await client.runs.wait(
|
||||
thread_id,
|
||||
assistant_id,
|
||||
input=inputs # (1)!
|
||||
)
|
||||
|
||||
# Resume the graph
|
||||
await client.runs.wait(
|
||||
thread_id,
|
||||
assistant_id,
|
||||
input=None # (2)!
|
||||
)
|
||||
```
|
||||
|
||||
1. The graph is run until the first breakpoint is hit.
|
||||
2. The graph is resumed by passing in `None` for the input. This will run the graph until the next breakpoint is hit.
|
||||
|
||||
=== "JavaScript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
|
||||
// Using the graph deployed with the name "agent"
|
||||
const assistantID = "agent";
|
||||
|
||||
// create a thread
|
||||
const thread = await client.threads.create();
|
||||
const threadID = thread["thread_id"];
|
||||
|
||||
// Run the graph until the breakpoint
|
||||
const result = await client.runs.wait(
|
||||
threadID,
|
||||
assistantID,
|
||||
{ input: input } // (1)!
|
||||
);
|
||||
|
||||
// Resume the graph
|
||||
await client.runs.wait(
|
||||
threadID,
|
||||
assistantID,
|
||||
{ input: null } // (2)!
|
||||
);
|
||||
```
|
||||
|
||||
1. The graph is run until the first breakpoint is hit.
|
||||
2. The graph is resumed by passing in `null` for the input. This will run the graph until the next breakpoint is hit.
|
||||
|
||||
=== "cURL"
|
||||
|
||||
Create a thread:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{}'
|
||||
```
|
||||
|
||||
Run the graph until the breakpoint:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": <INPUT>
|
||||
}"
|
||||
```
|
||||
|
||||
Resume the graph:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\"
|
||||
}"
|
||||
```
|
||||
@@ -29,7 +29,7 @@ Click the dropdown next to "Submit" and click the toggle to enable/disable strea
|
||||
To run your graph with breakpoints, click the "Interrupt" button. Select a node and whether to pause before and/or after that node has executed. Click "Continue" in the thread log to resume execution.
|
||||
|
||||
|
||||
For more information on breakpoints see [here](../../concepts/breakpoints.md).
|
||||
For more information on breakpoints see [here](../../concepts/human_in_the_loop.md).
|
||||
|
||||
### Submit run
|
||||
|
||||
|
||||
@@ -140,6 +140,22 @@ https://my-server.app/my-webhook-endpoint?token=YOUR_SECRET_TOKEN
|
||||
|
||||
Your server should extract and validate this token before processing requests.
|
||||
|
||||
## Disable webhooks
|
||||
|
||||
As of `langgraph-api>=0.2.78`, developers can disable webhooks in the `langgraph.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"http": {
|
||||
"disable_webhooks": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This feature is primarily intended for self-hosted deployments, where platform administrators or developers may prefer to disable webhooks to simplify their security posture—especially if they are not configuring firewall rules or other network controls. Disabling webhooks helps prevent untrusted payloads from being sent to internal endpoints.
|
||||
|
||||
For full configuration details, refer to the [configuration file reference](https://langchain-ai.github.io/langgraph/cloud/reference/cli/?h=disable_webhooks#configuration-file).
|
||||
|
||||
## Test webhooks
|
||||
|
||||
You can test your webhook using online services like:
|
||||
|
||||
@@ -409,6 +409,8 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
|
||||
| Option | Default | Description |
|
||||
| ---------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| `--wait` | | Wait for services to start before returning. Implies --detach |
|
||||
| `--base-image TEXT` | `langchain/langgraph-api` | Base image to use for the LangGraph API server. Pin to specific versions using version tags. |
|
||||
| `--image TEXT` | | Docker image to use for the langgraph-api service. If specified, skips building and uses this image directly. |
|
||||
| `--postgres-uri TEXT` | Local database | Postgres URI to use for the database. |
|
||||
| `--watch` | | Restart on file changes |
|
||||
| `--debugger-base-url TEXT` | `http://127.0.0.1:[PORT]` | URL used by the debugger to access LangGraph API. |
|
||||
@@ -436,6 +438,8 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
|
||||
| Option | Default | Description |
|
||||
| ---------------------------------------------------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| <span style="white-space: nowrap;">`--wait`</span> | | Wait for services to start before returning. Implies --detach |
|
||||
| <span style="white-space: nowrap;">`--base-image TEXT`</span> | <span style="white-space: nowrap;">`langchain/langgraph-api`</span> | Base image to use for the LangGraph API server. Pin to specific versions using version tags. |
|
||||
| <span style="white-space: nowrap;">`--image TEXT`</span> | | Docker image to use for the langgraph-api service. If specified, skips building and uses this image directly. |
|
||||
| <span style="white-space: nowrap;">`--postgres-uri TEXT`</span> | Local database | Postgres URI to use for the database. |
|
||||
| <span style="white-space: nowrap;">`--watch`</span> | | Restart on file changes |
|
||||
| <span style="white-space: nowrap;">`-c, --config FILE`</span> | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. |
|
||||
|
||||
@@ -10,6 +10,10 @@ This environment variable should be set to `True` if the implementation of a gra
|
||||
|
||||
Defaults to `False`.
|
||||
|
||||
## `BG_JOB_SHUTDOWN_GRACE_PERIOD_SECS`
|
||||
|
||||
Specifies, in seconds, how long the server will wait for background jobs to finish after the queue receives a shutdown signal. After this period, the server will force termination. Defaults to `180` seconds. Set this to ensure jobs have enough time to complete cleanly during shutdown. Added in `langgraph-api==0.2.16`.
|
||||
|
||||
## `BG_JOB_TIMEOUT_SECS`
|
||||
|
||||
The timeout of a background run can be increased. However, the infrastructure for a Cloud SaaS deployment enforces a 1 hour timeout limit for API requests. This means the connection between client and server will timeout after 1 hour. This is not configurable.
|
||||
@@ -18,16 +22,15 @@ A background run can execute for longer than 1 hour, but a client must reconnect
|
||||
|
||||
Defaults to `3600`.
|
||||
|
||||
## `BG_JOB_SHUTDOWN_GRACE_PERIOD_SECS`
|
||||
|
||||
Specifies, in seconds, how long the server will wait for background jobs to finish after the queue receives a shutdown signal. After this period, the server will force termination. Defaults to `3600` seconds. Set this to ensure jobs have enough time to complete cleanly during shutdown. Added in `langgraph-api==0.2.16`.
|
||||
|
||||
## `DD_API_KEY`
|
||||
|
||||
Specify `DD_API_KEY` (your [Datadog API Key](https://docs.datadoghq.com/account_management/api-app-keys/)) to automatically enable Datadog tracing for the deployment. Specify other [`DD_*` environment variables](https://ddtrace.readthedocs.io/en/stable/configuration.html) to configure the tracing instrumentation.
|
||||
|
||||
If `DD_API_KEY` is specified, the application process is wrapped in the [`ddtrace-run` command](https://ddtrace.readthedocs.io/en/stable/installation_quickstart.html). Other `DD_*` environment variables (e.g. `DD_SITE`, `DD_ENV`, `DD_SERVICE`, `DD_TRACE_ENABLED`) are typically needed to properly configure the tracing instrumentation. See [`DD_*` environment variables](https://ddtrace.readthedocs.io/en/stable/configuration.html) for more details.
|
||||
|
||||
!!! note
|
||||
Enabling `DD_API_KEY` (and thus `ddtrace-run`) can override or interfere with other auto-instrumentation solutions (such as OpenTelemetry) that you may have instrumented into your application code.
|
||||
|
||||
## `LANGCHAIN_TRACING_SAMPLING_RATE`
|
||||
|
||||
Sampling rate for traces sent to LangSmith. Valid values: Any float between `0` and `1`.
|
||||
@@ -40,6 +43,14 @@ Type of authentication for the LangGraph Server deployment. Valid values: `langs
|
||||
|
||||
For deployments to LangGraph Platform, this environment variable is set automatically. For local development or deployments where authentication is handled externally (e.g. self-hosted), set this environment variable to `noop`.
|
||||
|
||||
## `LANGGRAPH_POSTGRES_POOL_MAX_SIZE`
|
||||
|
||||
Beginning with langgraph-api version `0.2.12`, the maximum size of the Postgres connection pool (per replica) can be controlled using the `LANGGRAPH_POSTGRES_POOL_MAX_SIZE` environment variable. By setting this variable, you can determine the upper bound on the number of simultaneous connections the server will establish with the Postgres database.
|
||||
|
||||
For example, if a deployment is scaled up to 10 replicas and `LANGGRAPH_POSTGRES_POOL_MAX_SIZE` is configured to `150`, then up to `1500` connections to Postgres can be established. This is particularly useful for deployments where database resources are limited (or more available) or where you need to tune connection behavior for performance or scaling reasons.
|
||||
|
||||
Defaults to `150` connections.
|
||||
|
||||
## `LANGSMITH_RUNS_ENDPOINTS`
|
||||
|
||||
For deployments with [self-hosted LangSmith](https://docs.smith.langchain.com/self_hosting) only.
|
||||
@@ -54,6 +65,10 @@ Set `LANGSMITH_TRACING` to `false` to disable tracing to LangSmith.
|
||||
|
||||
Defaults to `true`.
|
||||
|
||||
## `LOG_COLOR`
|
||||
|
||||
This is mainly relevant in the context of using the dev server via the `langgraph dev` command. Set `LOG_COLOR` to `true` to enable ANSI-colored console output when using the default console renderer. Disabling color output by setting this variable to `false` produces monochrome logs. Defaults to `true`.
|
||||
|
||||
## `LOG_LEVEL`
|
||||
|
||||
Configure [log level](https://docs.python.org/3/library/logging.html#logging-levels). Defaults to `INFO`.
|
||||
@@ -62,9 +77,14 @@ Configure [log level](https://docs.python.org/3/library/logging.html#logging-lev
|
||||
|
||||
Set `LOG_JSON` to `true` to render all log messages as JSON objects using the configured `JSONRenderer`. This produces structured logs that can be easily parsed or ingested by log management systems. Defaults to `false`.
|
||||
|
||||
## `LOG_COLOR`
|
||||
## `MOUNT_PREFIX`
|
||||
|
||||
This is mainly relevant in the context of using the dev server via the `langgraph dev` command. Set `LOG_COLOR` to `true` to enable ANSI-colored console output when using the default console renderer. Disabling color output by setting this variable to `false` produces monochrome logs. Defaults to `true`.
|
||||
!!! info "Only Allowed in Self-Hosted Deployments"
|
||||
The `MOUNT_PREFIX` environment variable is only allowed in Self-Hosted Deployment models, LangGraph Platform SaaS will not allow this environment variable.
|
||||
|
||||
Set `MOUNT_PREFIX` to serve the LangGraph Server under a specific path prefix. This is useful for deployments where the server is behind a reverse proxy or load balancer that requires a specific path prefix.
|
||||
|
||||
For example, if the server is to be served under `https://example.com/langgraph`, set `MOUNT_PREFIX` to `/langgraph`.
|
||||
|
||||
## `N_JOBS_PER_WORKER`
|
||||
|
||||
@@ -94,16 +114,14 @@ Database Connectivity:
|
||||
|
||||
- The custom Postgres instance must be accessible by the LangGraph Server. The user is responsible for ensuring connectivity.
|
||||
|
||||
## `LANGGRAPH_POSTGRES_POOL_MAX_SIZE`
|
||||
## `REDIS_CLUSTER`
|
||||
|
||||
Beginning with langgraph-api version `0.2.12`, the maximum size of the Postgres connection pool can be controlled using the `LANGGRAPH_POSTGRES_POOL_MAX_SIZE` environment variable. By setting this variable, you can determine the upper bound on the number of simultaneous connections the server will establish with the Postgres database. This is particularly useful for deployments where database resources are limited (or more available) or where you need to tune connection behavior for performance or scaling reasons. If not specified, the pool size defaults to 150 connections.
|
||||
!!! info "Only Allowed in Self-Hosted Deployments"
|
||||
Redis Cluster mode is only available in Self-Hosted Deployment models, LangGraph Platform SaaS will provision a redis instance for you by default.
|
||||
|
||||
## `REDIS_URI_CUSTOM`
|
||||
Set `REDIS_CLUSTER` to `True` to enable Redis Cluster mode. When enabled, the system will connect to Redis using cluster mode. This is useful when connecting to a Redis Cluster deployment.
|
||||
|
||||
!!! info "Only for Self-Hosted Data Plane and Self-Hosted Control Plane"
|
||||
Custom Redis instances are only available for [Self-Hosted Data Plane](../../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
|
||||
Specify `REDIS_URI_CUSTOM` to use a custom Redis instance. The value of `REDIS_URI_CUSTOM` must be a valid [Redis connection URI](https://redis-py.readthedocs.io/en/stable/connections.html#redis.Redis.from_url).
|
||||
Defaults to `False`.
|
||||
|
||||
## `REDIS_KEY_PREFIX`
|
||||
|
||||
@@ -114,20 +132,19 @@ Specify a prefix for Redis keys. This allows multiple LangGraph Server instances
|
||||
|
||||
Defaults to `''`.
|
||||
|
||||
## `REDIS_CLUSTER`
|
||||
## `REDIS_URI_CUSTOM`
|
||||
|
||||
!!! info "Only Allowed in Self-Hosted Deployments"
|
||||
Redis Cluster mode is only available in Self-Hosted Deployment models, LangGraph Platform SaaS will provision a redis instance for you by default.
|
||||
!!! info "Only for Self-Hosted Data Plane and Self-Hosted Control Plane"
|
||||
Custom Redis instances are only available for [Self-Hosted Data Plane](../../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
|
||||
Set `REDIS_CLUSTER` to `True` to enable Redis Cluster mode. When enabled, the system will connect to Redis using cluster mode. This is useful when connecting to a Redis Cluster deployment.
|
||||
Specify `REDIS_URI_CUSTOM` to use a custom Redis instance. The value of `REDIS_URI_CUSTOM` must be a valid [Redis connection URI](https://redis-py.readthedocs.io/en/stable/connections.html#redis.Redis.from_url).
|
||||
|
||||
Defaults to `False`.
|
||||
## `RESUMABLE_STREAM_TTL_SECONDS`
|
||||
|
||||
## `MOUNT_PREFIX`
|
||||
Time-to-live in seconds for resumable stream data in Redis.
|
||||
|
||||
!!! info "Only Allowed in Self-Hosted Deployments"
|
||||
The `MOUNT_PREFIX` environment variable is only allowed in Self-Hosted Deployment models, LangGraph Platform SaaS will not allow this environment variable.
|
||||
When a run is created and the output is streamed, the stream can be configured to be resumable (e.g. `stream_resumable=True`). If a stream is resumable, output from the stream is temporarily stored in Redis. The TTL for this data can be configured by setting `RESUMABLE_STREAM_TTL_SECONDS`.
|
||||
|
||||
Set `MOUNT_PREFIX` to serve the LangGraph Server under a specific path prefix. This is useful for deployments where the server is behind a reverse proxy or load balancer that requires a specific path prefix.
|
||||
See the [Python](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/#langgraph_sdk.client.RunsClient.stream) and [JS/TS](https://langchain-ai.github.io/langgraphjs/reference/classes/sdk_client.RunsClient.html#stream) SDKs for more details on how to implement resumable streams.
|
||||
|
||||
For example, if the server is to be served under `https://example.com/langgraph`, set `MOUNT_PREFIX` to `/langgraph`.
|
||||
Defaults to `120` seconds.
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
# LangGraph Server Changelog
|
||||
|
||||
[LangGraph Server](../../concepts/langgraph_server.md) is an API platform for creating and managing agent-based applications. It provides built-in persistence, a task queue, and supports deploying, configuring, and running assistants (agentic workflows) at scale. This changelog documents all notable updates, features, and fixes to LangGraph Server releases.
|
||||
|
||||
---
|
||||
|
||||
## v0.2.109 (2025-07-28)
|
||||
- Fixed an issue where missing config schema occurred when `config_type` was not set.
|
||||
|
||||
## v0.2.108 (2025-07-28)
|
||||
- Added compatibility for langgraph v0.6, including new context API support and a migration to enhance context handling in assistant operations.
|
||||
|
||||
## v0.2.107 (2025-07-27)
|
||||
- Implemented caching for authentication processes to improve performance.
|
||||
- Merged count and select queries to improve database query efficiency.
|
||||
|
||||
## v0.2.106 (2025-07-27)
|
||||
- Log whether run uses resumable streams.
|
||||
|
||||
## v0.2.105 (2025-07-27)
|
||||
- Added a `/heapdump` endpoint to capture and save JS process heap data.
|
||||
|
||||
## v0.2.103 (2025-07-25)
|
||||
- Corrected the metadata endpoint to ensure accurate data retrieval.
|
||||
|
||||
## v0.2.102 (2025-07-24)
|
||||
- Captured interrupt events in the wait method to preserve legacy behavior and stream updates by default.
|
||||
- Added support for SDK structlog in the JavaScript environment, enhancing logging capabilities.
|
||||
|
||||
## v0.2.101 (2025-07-24)
|
||||
- Used the correct metadata endpoint for self-hosted environments, resolving an access issue.
|
||||
|
||||
## v0.2.99 (2025-07-22)
|
||||
- Improved license validation by adding an in-memory cache and handling Redis connection errors more effectively.
|
||||
- Automatically remove agents from memory that are removed from `langgraph.json` to prevent persistence issues.
|
||||
- Ensured the UI namespace for generated UI is a valid JavaScript property name to prevent errors.
|
||||
- Raised a 422 error for improved request validation feedback.
|
||||
|
||||
## v0.2.98 (2025-07-19)
|
||||
- Added langgraph node context for improved log filtering and trace visibility.
|
||||
|
||||
## v0.2.97 (2025-07-19)
|
||||
- Fixed scheduling issue with ckpt ingestion worker that occurred on isolated background loops.
|
||||
- Ensured queue worker starts only after all migrations have completed.
|
||||
- Added more detailed error messages for thread state issues and improved response handling when state updates fail.
|
||||
- Exposed interrupt ID while retrieving thread state for enhanced API response details.
|
||||
|
||||
## v0.2.96 (2025-07-17)
|
||||
- Added a fallback mechanism for configurable header patterns to handle exclude/include settings more effectively.
|
||||
|
||||
## v0.2.95 (2025-07-17)
|
||||
- Avoided setting the future if it is already done to prevent redundant operations.
|
||||
- Resolved compatibility errors in CI by switching from `typing.TypedDict` to `typing_extensions.TypedDict` for Python versions below 3.12.
|
||||
|
||||
## v0.2.94 (2025-07-16)
|
||||
- Improved performance by omitting pending sends for langgraph versions 0.5 and above.
|
||||
- Improved server startup logs to provide clearer warnings when the DD_API_KEY environment variable is set.
|
||||
|
||||
## v0.2.93 (2025-07-16)
|
||||
- Removed the GIN index for run metadata to improve performance.
|
||||
|
||||
## v0.2.92 (2025-07-16)
|
||||
- Enabled copying functionality for blobs and checkpoints, improving data management flexibility.
|
||||
|
||||
## v0.2.91 (2025-07-16)
|
||||
- Reduced writes to the `checkpoint_blobs` table by inlining small values (null, numeric, str, etc.). This means we don't need to store extra values for channels that haven't been updated.
|
||||
|
||||
## v0.2.90 (2025-07-16)
|
||||
- Improve checkpoint writes via node-local background queueing.
|
||||
|
||||
|
||||
## v0.2.89 (2025-07-15)
|
||||
- Decoupled checkpoint writing from thread/run state by removing foreign keys and updated logger to prevent timeout-related failures.
|
||||
|
||||
## v0.2.88 (2025-07-14)
|
||||
- Removed the foreign key constraint for `thread` in the `run` table to simplify database schema.
|
||||
|
||||
## v0.2.87 (2025-07-14)
|
||||
- Added more detailed logs for Redis worker signaling to improve debugging.
|
||||
|
||||
## v0.2.86 (2025-07-11)
|
||||
- Honored tool descriptions in the `/mcp` endpoint to align with expected functionality.
|
||||
|
||||
## v0.2.85 (2025-07-10)
|
||||
- Added support for the `on_disconnect` field to `runs/wait` and included disconnect logs for better debugging.
|
||||
|
||||
## v0.2.84 (2025-07-09)
|
||||
- Removed unnecessary status updates to streamline thread handling and updated version to 0.2.84.
|
||||
|
||||
## v0.2.83 (2025-07-09)
|
||||
- Reduced the default time-to-live for resumable streams to 2 minutes.
|
||||
- Enhanced data submission logic to send data to both Beacon and LangSmith instance based on license configuration.
|
||||
- Enabled submission of self-hosted data to a Langsmith instance when the endpoint is configured.
|
||||
|
||||
## v0.2.82 (2025-07-03)
|
||||
- Addressed a race condition in background runs by implementing a lock using join, ensuring reliable execution across CTEs.
|
||||
|
||||
## v0.2.81 (2025-07-03)
|
||||
- Optimized run streams by reducing initial wait time to improve responsiveness for older or non-existent runs.
|
||||
|
||||
## v0.2.80 (2025-07-03)
|
||||
- Corrected parameter passing in the `logger.ainfo()` API call to resolve a TypeError.
|
||||
|
||||
## v0.2.79 (2025-07-02)
|
||||
- Fixed a JsonDecodeError in checkpointing with remote graph by correcting JSON serialization to handle trailing slashes properly.
|
||||
- Introduced a configuration flag to disable webhooks globally across all routes.
|
||||
|
||||
## v0.2.78 (2025-07-02)
|
||||
- Added timeout retries to webhook calls to improve reliability.
|
||||
- Added HTTP request metrics, including a request count and latency histogram, for enhanced monitoring capabilities.
|
||||
|
||||
## v0.2.77 (2025-07-02)
|
||||
- Added HTTP metrics to improve performance monitoring.
|
||||
- Changed the Redis cache delimiter to reduce conflicts with subgraph message names and updated caching behavior.
|
||||
|
||||
## v0.2.76 (2025-07-01)
|
||||
- Updated Redis cache delimiter to prevent conflicts with subgraph messages.
|
||||
|
||||
## v0.2.74 (2025-06-30)
|
||||
- Scheduled webhooks in an isolated loop to ensure thread-safe operations and prevent errors with PYTHONASYNCIODEBUG=1.
|
||||
|
||||
## v0.2.73 (2025-06-27)
|
||||
- Fixed an infinite frame loop issue and removed the dict_parser due to structlog's unexpected behavior.
|
||||
- Throw a 409 error on deadlock occurrence during run cancellations to handle lock conflicts gracefully.
|
||||
|
||||
## v0.2.72 (2025-06-27)
|
||||
- Ensured compatibility with future langgraph versions.
|
||||
- Implemented a 409 response status to handle deadlock issues during cancellation.
|
||||
|
||||
## v0.2.71 (2025-06-26)
|
||||
- Improved logging for better clarity and detail regarding log types.
|
||||
|
||||
## v0.2.70 (2025-06-26)
|
||||
- Improved error handling to better distinguish and log TimeoutErrors caused by users from internal run timeouts.
|
||||
|
||||
## v0.2.69 (2025-06-26)
|
||||
- Added sorting and pagination to the crons API and updated schema definitions for improved accuracy.
|
||||
|
||||
## v0.2.66 (2025-06-26)
|
||||
- Fixed a 404 error when creating multiple runs with the same thread_id using `on_not_exist="create"`.
|
||||
|
||||
## v0.2.65 (2025-06-25)
|
||||
- Ensured that only fields from `assistant_versions` are returned when necessary.
|
||||
- Ensured consistent data types for in-memory and PostgreSQL users, improving internal authentication handling.
|
||||
|
||||
## v0.2.64 (2025-06-24)
|
||||
- Added descriptions to version entries for better clarity.
|
||||
|
||||
## v0.2.62 (2025-06-23)
|
||||
- Improved user handling for custom authentication in the JS Studio.
|
||||
- Added Prometheus-format run statistics to the metrics endpoint for better monitoring.
|
||||
- Added run statistics in Prometheus format to the metrics endpoint.
|
||||
|
||||
## v0.2.61 (2025-06-20)
|
||||
- Set a maximum idle time for Redis connections to prevent unnecessary open connections.
|
||||
|
||||
## v0.2.60 (2025-06-20)
|
||||
- Enhanced error logging to include traceback details for dictionary operations.
|
||||
- Added a `/metrics` endpoint to expose queue worker metrics for monitoring.
|
||||
|
||||
## v0.2.57 (2025-06-18)
|
||||
- Removed CancelledError from retriable exceptions to allow local interrupts while maintaining retriability for workers.
|
||||
- Introduced middleware to gracefully shut down the server after completing in-flight requests upon receiving a SIGINT.
|
||||
- Reduced metadata stored in checkpoint to only include necessary information.
|
||||
- Improved error handling in join runs to return error details when present.
|
||||
|
||||
## v0.2.56 (2025-06-17)
|
||||
- Improved application stability by adding a handler for SIGTERM signals.
|
||||
|
||||
## v0.2.55 (2025-06-17)
|
||||
- Improved the handling of cancellations in the queue entrypoint.
|
||||
- Improved cancellation handling in the queue entry point.
|
||||
|
||||
## v0.2.54 (2025-06-16)
|
||||
- Enhanced error message for LuaLock timeout during license validation.
|
||||
- Fixed the $contains filter in custom auth by requiring an explicit ::text cast and updated tests accordingly.
|
||||
- Ensured project and tenant IDs are formatted as UUIDs for consistency.
|
||||
|
||||
## v0.2.53 (2025-06-13)
|
||||
- Resolved a timing issue to ensure the queue starts only after the graph is registered.
|
||||
- Improved performance by setting thread and run status in a single query and enhanced error handling during checkpoint writes.
|
||||
- Reduced the default background grace period to 3 minutes.
|
||||
|
||||
## v0.2.52 (2025-06-12)
|
||||
- Now logging expected graphs when one is omitted to improve traceability.
|
||||
- Implemented a time-to-live (TTL) feature for resumable streams.
|
||||
- Improved query efficiency and consistency by adding a unique index and optimizing row locking.
|
||||
|
||||
## v0.2.51 (2025-06-12)
|
||||
- Handled `CancelledError` by marking tasks as ready to retry, improving error management in worker processes.
|
||||
- Added LG API version and request ID to metadata and logs for better tracking.
|
||||
- Added LG API version and request ID to metadata and logs to improve traceability.
|
||||
- Improved database performance by creating indexes concurrently.
|
||||
- Ensured postgres write is committed only after the Redis running marker is set to prevent race conditions.
|
||||
- Enhanced query efficiency and reliability by adding a unique index on thread_id/running, optimizing row locks, and ensuring deterministic run selection.
|
||||
- Resolved a race condition by ensuring Postgres updates only occur after the Redis running marker is set.
|
||||
|
||||
## v0.2.46 (2025-06-07)
|
||||
- Introduced a new connection for each operation while preserving transaction characteristics in Threads state `update()` and `bulk()` commands.
|
||||
|
||||
## v0.2.45 (2025-06-05)
|
||||
- Enhanced streaming feature by incorporating tracing contexts.
|
||||
- Removed an unnecessary query from the Crons.search function.
|
||||
- Resolved connection reuse issue when scheduling next run for multiple cron jobs.
|
||||
- Removed an unnecessary query in the Crons.search function to improve efficiency.
|
||||
- Resolved an issue with scheduling the next cron run by improving connection reuse.
|
||||
|
||||
## v0.2.44 (2025-06-04)
|
||||
- Enhanced the worker logic to exit the pipeline before continuing when the Redis message limit is reached.
|
||||
- Introduced a ceiling for Redis message size with an option to skip messages larger than 128 MB for improved performance.
|
||||
- Ensured the pipeline always closes properly to prevent resource leaks.
|
||||
|
||||
## v0.2.43 (2025-06-04)
|
||||
- Improved performance by omitting logs in metadata calls and ensuring output schema compliance in value streaming.
|
||||
- Ensured the connection is properly closed after use.
|
||||
- Aligned output format to strictly adhere to the specified schema.
|
||||
- Stopped sending internal logs in metadata requests to improve privacy.
|
||||
|
||||
## v0.2.42 (2025-06-04)
|
||||
- Added timestamps to track the start and end of a request's run.
|
||||
- Added tracer information to the configuration settings.
|
||||
- Added support for streaming with tracing contexts.
|
||||
|
||||
## v0.2.41 (2025-06-03)
|
||||
- Added locking mechanism to prevent errors in pipelined executions.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Assistants
|
||||
|
||||
**Assistants** allow you to manage configurations (like prompts, LLM selection, tools) separately from your graph's core logic, enabling rapid changes that don't alter the graph architecture. It is a way to create multiple specialized versions of the same graph architecture, each optimized for different use cases through configuration variations rather than structural changes.
|
||||
**Assistants** allow you to manage configurations (like prompts, LLM selection, tools) separately from your graph's core logic, enabling rapid changes that don't alter the graph architecture. It is a way to create multiple specialized versions of the same graph architecture, each optimized for different use cases through context/configuration variations rather than structural changes.
|
||||
|
||||
For example, imagine a general-purpose writing agent built on a common graph architecture. While the structure remains the same, different writing styles—such as blog posts and tweets—require tailored configurations to optimize performance. To support these variations, you can create multiple assistants (e.g., one for blogs and another for tweets) that share the underlying graph but differ in model selection and system prompt.
|
||||
|
||||
@@ -14,8 +14,8 @@ The LangGraph Cloud API provides several endpoints for creating and managing ass
|
||||
|
||||
## Configuration
|
||||
|
||||
Assistants build on the LangGraph open source concept of [configuration](low_level.md#configuration).
|
||||
While configuration is available in the open source LangGraph library, assistants are only present in [LangGraph Platform](langgraph_platform.md). This is due to the fact that assistants are tightly coupled to your deployed graph. Upon deployment, LangGraph Server will automatically create a default assistant for each graph using the graph's default configuration settings.
|
||||
Assistants build on the LangGraph open source concepts of configuration and [runtime context](low_level.md#runtime-context).
|
||||
While these features are available in the open source LangGraph library, assistants are only present in [LangGraph Platform](langgraph_platform.md). This is due to the fact that assistants are tightly coupled to your deployed graph. Upon deployment, LangGraph Server will automatically create a default assistant for each graph using the graph's default context and configuration settings.
|
||||
|
||||
In practice, an assistant is just an _instance_ of a graph with a specific configuration. Therefore, multiple assistants can reference the same graph but can contain different configurations (e.g. prompts, models, tools). The LangGraph Server API provides several endpoints for creating and managing assistants. See the [API reference](../cloud/reference/api/api_ref.html) and [this how-to](../cloud/how-tos/configuration_cloud.md) for more details on how to create assistants.
|
||||
|
||||
@@ -26,6 +26,6 @@ Once you've created an assistant, subsequent edits to that assistant will create
|
||||
|
||||
## Execution
|
||||
|
||||
A **run** is an invocation of an assistant. Each run may have its own input, configuration, and metadata, which may affect execution and output of the underlying graph. A run can optionally be executed on a [thread](./persistence.md#threads).
|
||||
A **run** is an invocation of an assistant. Each run may have its own input, configuration, context, and metadata, which may affect execution and output of the underlying graph. A run can optionally be executed on a [thread](./persistence.md#threads).
|
||||
|
||||
The LangGraph Platform API provides several endpoints for creating and managing runs. See the [API reference](../cloud/reference/api/api_ref.html#tag/thread-runs/) for more details.
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Breakpoints
|
||||
|
||||
[Breakpoints](../how-tos/human_in_the_loop/breakpoints.md) pause graph execution at defined points and let you step through each stage. They use LangGraph's [**persistence layer**](./persistence.md), which saves the graph state after each step.
|
||||
|
||||
With breakpoints, you can inspect the graph's state and node inputs at any point. Execution pauses **indefinitely** until you resume, as the checkpointer preserves the state.
|
||||
|
||||
<figure markdown="1">
|
||||
{: style="max-height:400px"}
|
||||
<figcaption>An example graph consisting of 3 sequential steps with a breakpoint before step_3. </figcaption> </figure>
|
||||
|
||||
!!! tip
|
||||
|
||||
For information on how to use breakpoints, see [Set breakpoints](../how-tos/human_in_the_loop/breakpoints.md) and [Set breakpoints using Server API](../cloud/how-tos/human_in_the_loop_breakpoint.md).
|
||||
@@ -10,7 +10,7 @@ search:
|
||||
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.
|
||||
1. [Standalone Container (Lite)](../concepts/langgraph_standalone_container.md): A limited version of Standalone Container for deployments unlikely to see more than 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
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ If a [node](./low_level.md#nodes) contains multiple operations, you may find it
|
||||
from typing_extensions import TypedDict
|
||||
import uuid
|
||||
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
import requests
|
||||
|
||||
@@ -74,7 +74,7 @@ If a [node](./low_level.md#nodes) contains multiple operations, you may find it
|
||||
builder.add_edge("call_api", END)
|
||||
|
||||
# Specify a checkpointer
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
# Compile the graph with the checkpointer
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
@@ -94,7 +94,7 @@ If a [node](./low_level.md#nodes) contains multiple operations, you may find it
|
||||
from typing_extensions import TypedDict
|
||||
import uuid
|
||||
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.func import task
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
import requests
|
||||
@@ -129,7 +129,7 @@ If a [node](./low_level.md#nodes) contains multiple operations, you may find it
|
||||
builder.add_edge("call_api", END)
|
||||
|
||||
# Specify a checkpointer
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
# Compile the graph with the checkpointer
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
|
||||
@@ -39,7 +39,7 @@ Here are some key differences:
|
||||
Below we demonstrate a simple application that writes an essay and [interrupts](human_in_the_loop.md) to request human review.
|
||||
|
||||
```python
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.func import entrypoint, task
|
||||
from langgraph.types import interrupt
|
||||
|
||||
@@ -50,7 +50,7 @@ def write_essay(topic: str) -> str:
|
||||
time.sleep(1) # A placeholder for a long-running task.
|
||||
return f"An essay about topic: {topic}"
|
||||
|
||||
@entrypoint(checkpointer=MemorySaver())
|
||||
@entrypoint(checkpointer=InMemorySaver())
|
||||
def workflow(topic: str) -> dict:
|
||||
"""A simple workflow that writes an essay and asks for a review."""
|
||||
essay = write_essay("cat").result()
|
||||
@@ -79,51 +79,54 @@ def workflow(topic: str) -> dict:
|
||||
```python
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from langgraph.func import entrypoint, task
|
||||
from langgraph.types import interrupt
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
|
||||
@task
|
||||
def write_essay(topic: str) -> str:
|
||||
"""Write an essay about the given topic."""
|
||||
time.sleep(1) # This is a placeholder for a long-running task.
|
||||
time.sleep(1) # This is a placeholder for a long-running task.
|
||||
return f"An essay about topic: {topic}"
|
||||
|
||||
@entrypoint(checkpointer=MemorySaver())
|
||||
@entrypoint(checkpointer=InMemorySaver())
|
||||
def workflow(topic: str) -> dict:
|
||||
"""A simple workflow that writes an essay and asks for a review."""
|
||||
essay = write_essay("cat").result()
|
||||
is_approved = interrupt({
|
||||
# Any json-serializable payload provided to interrupt as argument.
|
||||
# It will be surfaced on the client side as an Interrupt when streaming data
|
||||
# from the workflow.
|
||||
"essay": essay, # The essay we want reviewed.
|
||||
# We can add any additional information that we need.
|
||||
# For example, introduce a key called "action" with some instructions.
|
||||
"action": "Please approve/reject the essay",
|
||||
})
|
||||
|
||||
is_approved = interrupt(
|
||||
{
|
||||
# Any json-serializable payload provided to interrupt as argument.
|
||||
# It will be surfaced on the client side as an Interrupt when streaming data
|
||||
# from the workflow.
|
||||
"essay": essay, # The essay we want reviewed.
|
||||
# We can add any additional information that we need.
|
||||
# For example, introduce a key called "action" with some instructions.
|
||||
"action": "Please approve/reject the essay",
|
||||
}
|
||||
)
|
||||
return {
|
||||
"essay": essay, # The essay that was generated
|
||||
"is_approved": is_approved, # Response from HIL
|
||||
"essay": essay, # The essay that was generated
|
||||
"is_approved": is_approved, # Response from HIL
|
||||
}
|
||||
|
||||
|
||||
thread_id = str(uuid.uuid4())
|
||||
|
||||
config = {
|
||||
"configurable": {
|
||||
"thread_id": thread_id
|
||||
}
|
||||
}
|
||||
|
||||
config = {"configurable": {"thread_id": thread_id}}
|
||||
for item in workflow.stream("cat", config):
|
||||
print(item)
|
||||
```
|
||||
|
||||
```pycon
|
||||
{'write_essay': 'An essay about topic: cat'}
|
||||
{'__interrupt__': (Interrupt(value={'essay': 'An essay about topic: cat', 'action': 'Please approve/reject the essay'}, resumable=True, ns=['workflow:f7b8508b-21c0-8b4c-5958-4e8de74d2684'], when='during'),)}
|
||||
# > {'write_essay': 'An essay about topic: cat'}
|
||||
# > {
|
||||
# > '__interrupt__': (
|
||||
# > Interrupt(
|
||||
# > value={
|
||||
# > 'essay': 'An essay about topic: cat',
|
||||
# > 'action': 'Please approve/reject the essay'
|
||||
# > },
|
||||
# > id='b9b2b9d788f482663ced6dc755c9e981'
|
||||
# > ),
|
||||
# > )
|
||||
# > }
|
||||
```
|
||||
|
||||
An essay has been written and is ready for review. Once the review is provided, we can resume the workflow:
|
||||
|
||||
@@ -23,9 +23,18 @@ To review, edit, and approve tool calls in an agent or workflow, [use LangGraph'
|
||||
|
||||
## Key capabilities
|
||||
|
||||
* **Persistent execution state**: LangGraph allows you to pause execution **indefinitely** — for minutes, hours, or even days—until human input is received. This is possible because LangGraph checkpoints the graph state after each step, which allows the system to persist execution context and later resume the workflow, continuing from where it left off. This supports asynchronous human review or input without time constraints.
|
||||
* **Persistent execution state**: Interrupts use LangGraph's [persistence](./persistence.md) layer, which saves the graph state, to indefinitely pause graph execution until you resume. This is possible because LangGraph checkpoints the graph state after each step, which allows the system to persist execution context and later resume the workflow, continuing from where it left off. This supports asynchronous human review or input without time constraints.
|
||||
|
||||
* **Flexible integration points**: HIL logic can be introduced at any point in the workflow. This allows targeted human involvement, such as approving API calls, correcting outputs, or guiding conversations.
|
||||
There are two ways to pause a graph:
|
||||
|
||||
- [Dynamic interrupts](../how-tos/human_in_the_loop/add-human-in-the-loop.md#pause-using-interrupt): Use `interrupt` to pause a graph from inside a specific node, based on the current state of the graph.
|
||||
- [Static interrupts](../how-tos/human_in_the_loop/add-human-in-the-loop.md#debug-with-interrupts): Use `interrupt_before` and `interrupt_after` to pause the graph at pre-defined points, either before or after a node executes.
|
||||
|
||||
<figure markdown="1">
|
||||
{: style="max-height:400px"}
|
||||
<figcaption>An example graph consisting of 3 sequential steps with a breakpoint before step_3. </figcaption> </figure>
|
||||
|
||||
* **Flexible integration points**: Human-in-the-loop logic can be introduced at any point in the workflow. This allows targeted human involvement, such as approving API calls, correcting outputs, or guiding conversations.
|
||||
|
||||
## Patterns
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 121 KiB |
@@ -119,6 +119,11 @@ These metrics are displayed as charts in the Control Plane UI.
|
||||
|
||||
### LangSmith Integration
|
||||
|
||||
A [LangSmith](https://docs.smith.langchain.com/) tracing project is automatically created for each deployment. The tracing project has the same name as the deployment. When creating a deployment, the `LANGCHAIN_TRACING` and `LANGSMITH_API_KEY`/`LANGCHAIN_API_KEY` environment variables do not need to be specified; they are set automatically by the control plane.
|
||||
A [LangSmith](https://docs.smith.langchain.com/) tracing project and LangSmith API key are automatically created for each deployment. The deployment uses the API key to automatically send traces to LangSmith.
|
||||
|
||||
When a deployment is deleted, the traces and the tracing project are not deleted.
|
||||
- The tracing project has the same name as the deployment.
|
||||
- The API key has the description `LangGraph Platform: <deployment_name>`.
|
||||
- The API key is never revealed and cannot be deleted manually.
|
||||
- When creating a deployment, the `LANGCHAIN_TRACING` and `LANGSMITH_API_KEY`/`LANGCHAIN_API_KEY` environment variables do not need to be specified; they are set automatically by the control plane.
|
||||
|
||||
When a deployment is deleted, the traces and the tracing project are not deleted. However, the API will be deleted when the deployment is deleted.
|
||||
|
||||
@@ -45,7 +45,7 @@ The first thing you do when you define a graph is define the `State` of the grap
|
||||
|
||||
### Schema
|
||||
|
||||
The main documented way to specify the schema of a graph is by using `TypedDict`. However, we also support [using a Pydantic BaseModel](../how-tos/graph-api.md#use-pydantic-models-for-graph-state) as your graph state to add **default values** and additional data validation.
|
||||
The main documented way to specify the schema of a graph is by using a [`TypedDict`](https://docs.python.org/3/library/typing.html#typing.TypedDict). If you want to provide default values in your state, use a [`dataclass`](https://docs.python.org/3/library/dataclasses.html). We also support using a Pydantic [BaseModel](../how-tos/graph-api.md#use-pydantic-models-for-graph-state) as your graph state if you want recursive data validation (though note that pydantic is less performant than a `TypedDict` or `dataclass`).
|
||||
|
||||
By default, the graph will have the same input and output schemas. If you want to change this, you can also specify explicit input and output schemas directly. This is useful when you have a lot of keys, and some are explicitly for input and others for output. See the [guide here](../how-tos/graph-api.md#define-input-and-output-schemas) for how to use.
|
||||
|
||||
@@ -192,35 +192,48 @@ class State(MessagesState):
|
||||
|
||||
## Nodes
|
||||
|
||||
In LangGraph, nodes are typically python functions (sync or async) where the **first** positional argument is the [state](#state), and (optionally), the **second** positional argument is a "config", containing optional [configurable parameters](#configuration) (such as a `thread_id`).
|
||||
In LangGraph, nodes are Python functions (either synchronous or asynchronous) that accept the following arguments:
|
||||
|
||||
1. `state`: The [state](#state) of the graph
|
||||
2. `config`: A `RunnableConfig` object that contains configuration information like `thread_id` and tracing information like `tags`
|
||||
3. `runtime`: A `Runtime` object that contains [runtime `context`](#runtime-context) and other information like `store` and `stream_writer`
|
||||
|
||||
|
||||
Similar to `NetworkX`, you add these nodes to a graph using the [add_node][langgraph.graph.StateGraph.add_node] method:
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.graph import StateGraph
|
||||
from langgraph.runtime import Runtime
|
||||
|
||||
class State(TypedDict):
|
||||
input: str
|
||||
results: str
|
||||
|
||||
@dataclass
|
||||
class Context:
|
||||
user_id: str
|
||||
|
||||
builder = StateGraph(State)
|
||||
|
||||
def plain_node(state: State):
|
||||
return state
|
||||
|
||||
def my_node(state: State, config: RunnableConfig):
|
||||
print("In node: ", config["configurable"]["user_id"])
|
||||
def node_with_runtime(state: State, runtime: Runtime[Context]):
|
||||
print("In node: ", runtime.context.user_id)
|
||||
return {"results": f"Hello, {state['input']}!"}
|
||||
|
||||
def node_with_config(state: State, config: RunnableConfig):
|
||||
print("In node with thread_id: ", config["configurable"]["thread_id"])
|
||||
return {"results": f"Hello, {state['input']}!"}
|
||||
|
||||
|
||||
# The second argument is optional
|
||||
def my_other_node(state: State):
|
||||
return state
|
||||
|
||||
|
||||
builder.add_node("my_node", my_node)
|
||||
builder.add_node("other_node", my_other_node)
|
||||
builder.add_node("plain_node", plain_node)
|
||||
builder.add_node("node_with_runtime", node_with_runtime)
|
||||
builder.add_node("node_with_config", node_with_config)
|
||||
...
|
||||
```
|
||||
|
||||
@@ -298,7 +311,7 @@ print(graph.invoke({"x": 5}, stream_mode='updates')) # (2)!
|
||||
[{'expensive_node': {'result': 10}, '__metadata__': {'cached': True}}]
|
||||
```
|
||||
|
||||
1. First run takes the full second to run (due to mocked expensive computation).
|
||||
1. First run takes two seconds to run (due to mocked expensive computation).
|
||||
2. Second run utilizes cache and returns quickly.
|
||||
|
||||
## Edges
|
||||
@@ -459,33 +472,32 @@ LangGraph can easily handle migrations of graph definitions (nodes, edges, and s
|
||||
- State keys that are renamed lose their saved state in existing threads
|
||||
- State keys whose types change in incompatible ways could currently cause issues in threads with state from before the change -- if this is a blocker please reach out and we can prioritize a solution.
|
||||
|
||||
## Configuration
|
||||
## Runtime Context
|
||||
|
||||
When creating a graph, you can also mark that certain parts of the graph are configurable. This is commonly done to enable easily switching between models or system prompts. This allows you to create a single "cognitive architecture" (the graph) but have multiple different instance of it.
|
||||
|
||||
You can optionally specify a `config_schema` when creating a graph.
|
||||
When creating a graph, you can specify a `context_schema` for runtime context passed to nodes. This is useful for passing
|
||||
information to nodes that is not part of the graph state. For example, you might want to pass dependencies such as model name or a database connection.
|
||||
|
||||
```python
|
||||
class ConfigSchema(TypedDict):
|
||||
llm: str
|
||||
@dataclass
|
||||
class ContextSchema:
|
||||
llm_provider: str = "openai"
|
||||
|
||||
graph = StateGraph(State, config_schema=ConfigSchema)
|
||||
graph = StateGraph(State, context_schema=ContextSchema)
|
||||
```
|
||||
|
||||
You can then pass this configuration into the graph using the `configurable` config field.
|
||||
You can then pass this context into the graph using the `context` parameter of the `invoke` method.
|
||||
|
||||
```python
|
||||
config = {"configurable": {"llm": "anthropic"}}
|
||||
|
||||
graph.invoke(inputs, config=config)
|
||||
graph.invoke(inputs, context={"llm_provider": "anthropic"})
|
||||
```
|
||||
|
||||
You can then access and use this configuration inside a node or conditional edge:
|
||||
You can then access and use this context inside a node or conditional edge:
|
||||
|
||||
```python
|
||||
def node_a(state, config):
|
||||
llm_type = config.get("configurable", {}).get("llm", "openai")
|
||||
llm = get_llm(llm_type)
|
||||
from langgraph.runtime import Runtime
|
||||
|
||||
def node_a(state: State, runtime: Runtime[ContextSchema]):
|
||||
llm = get_llm(runtime.context.llm_provider)
|
||||
...
|
||||
```
|
||||
|
||||
@@ -496,7 +508,7 @@ See [this guide](../how-tos/graph-api.md#add-runtime-configuration) for a full b
|
||||
The recursion limit sets the maximum number of [super-steps](#graphs) the graph can execute during a single execution. Once the limit is reached, LangGraph will raise `GraphRecursionError`. By default this value is set to 25 steps. The recursion limit can be set on any graph at runtime, and is passed to `.invoke`/`.stream` via the config dictionary. Importantly, `recursion_limit` is a standalone `config` key and should not be passed inside the `configurable` key as all other user-defined configuration. See the example below:
|
||||
|
||||
```python
|
||||
graph.invoke(inputs, config={"recursion_limit": 5, "configurable":{"llm": "anthropic"}})
|
||||
graph.invoke(inputs, config={"recursion_limit": 5}, context={"llm": "anthropic"})
|
||||
```
|
||||
|
||||
Read [this how-to](https://langchain-ai.github.io/langgraph/how-tos/recursion-limit/) to learn more about how the recursion limit works.
|
||||
|
||||
@@ -33,7 +33,7 @@ The state of a thread at a particular point in time is called a checkpoint. Chec
|
||||
- `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.md#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/add-human-in-the-loop.md#pause-using-interrupt) 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.
|
||||
|
||||
@@ -487,12 +487,12 @@ If you want to fallback to pickle for objects not currently supported by our msg
|
||||
you can use the `pickle_fallback` argument of the `JsonPlusSerializer`:
|
||||
|
||||
```python
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer
|
||||
|
||||
# ... Define the graph ...
|
||||
graph.compile(
|
||||
checkpointer=MemorySaver(serde=JsonPlusSerializer(pickle_fallback=True))
|
||||
checkpointer=InMemorySaver(serde=JsonPlusSerializer(pickle_fallback=True))
|
||||
)
|
||||
```
|
||||
|
||||
@@ -525,7 +525,7 @@ 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.md) 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 [the how-to guides](../how-tos/human_in_the_loop/add-human-in-the-loop.md) for examples.
|
||||
|
||||
### Memory
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Tracing
|
||||
|
||||
Traces are a series of steps that your application takes to go from input to output. Each of these individual steps is represented by a run. You can use [LangSmith](https://smith.langchain.com/) to visualize these execution steps. To use it, [enable tracing for your application](../how-tos/enable-tracing.md). This enables you to do the following:
|
||||
|
||||
- [Debug a locally running application](../cloud/how-tos/clone_traces_studio.md).
|
||||
- [Evaluate the application performance](../agents/evals.md).
|
||||
- [Monitor the application](https://docs.smith.langchain.com/observability/how_to_guides/dashboards).
|
||||
|
||||
To get started, sign up for a free account at [LangSmith](https://smith.langchain.com/).
|
||||
|
||||
## Learn more
|
||||
|
||||
- [Graph runs in LangSmith](../how-tos/run-id-langsmith.md)
|
||||
- [LangSmith Observability quickstart](https://docs.smith.langchain.com/observability)
|
||||
- [Trace with LangGraph](https://docs.smith.langchain.com/observability/how_to_guides/trace_with_langgraph)
|
||||
- [Tracing conceptual guide](https://docs.smith.langchain.com/observability/concepts#traces)
|
||||
|
||||
@@ -5,17 +5,17 @@ The pages in this section provide end-to-end examples for the following topics:
|
||||
## General
|
||||
|
||||
- [Template Applications](../concepts/template_applications.md): Create a LangGraph application from a template.
|
||||
- [Agentic RAG](./rag/langgraph_agentic_rag.md): Build a retrieval agent that can decide when to use a retriever tool.
|
||||
- [Agent Supervisor](./multi_agent/agent_supervisor.md): Build a supervisor agent that can manage a team of agents.
|
||||
- [SQL agent](./sql/sql-agent.md): Build a SQL agent that can execute SQL queries and return the results.
|
||||
- [Agentic RAG](../tutorials/rag/langgraph_agentic_rag.md): Build a retrieval agent that can decide when to use a retriever tool.
|
||||
- [Agent Supervisor](../tutorials/multi_agent/agent_supervisor.md): Build a supervisor agent that can manage a team of agents.
|
||||
- [SQL agent](../tutorials/sql/sql-agent.md): Build a SQL agent that can execute SQL queries and return the results.
|
||||
- [Prebuilt chat UI](../agents/ui.md): Use a prebuilt chat UI to interact with any LangGraph agent.
|
||||
- [Graph runs in LangSmith](../how-tos/run-id-langsmith.md): Use LangSmith to track and analyze graph runs.
|
||||
|
||||
## LangGraph Platform
|
||||
|
||||
- [Set up custom authentication](./auth/getting_started.md): Set up custom authentication for your LangGraph application.
|
||||
- [Make conversations private](./auth/resource_auth.md): Make conversations private by using resource-based authentication.
|
||||
- [Connect an authentication provider](./auth/add_auth_server.md): Connect an authentication provider to your LangGraph application.
|
||||
- [Set up custom authentication](../tutorials/auth/getting_started.md): Set up custom authentication for your LangGraph application.
|
||||
- [Make conversations private](../tutorials/auth/resource_auth.md): Make conversations private by using resource-based authentication.
|
||||
- [Connect an authentication provider](../tutorials/auth/add_auth_server.md): Connect an authentication provider to your LangGraph application.
|
||||
- [Rebuild graph at runtime](../cloud/deployment/graph_rebuild.md): Rebuild a graph at runtime.
|
||||
- [Use RemoteGraph](../how-tos/use-remote-graph.md): Use RemoteGraph to deploy your LangGraph application to a remote server.
|
||||
- [Deploy CrewAI, AutoGen, and other frameworks](../how-tos/autogen-integration.md): Deploy CrewAI, AutoGen, and other frameworks with LangGraph.
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
The pages in this section provide a conceptual overview and how-tos for the following topics:
|
||||
|
||||
## Agent development
|
||||
|
||||
- [Overview](../agents/overview.md): Use prebuilt components to build an agent.
|
||||
- [Run an agent](../agents/run_agents.md): Run an agent by providing input, interpreting output, enabling streaming, and controlling execution limits.
|
||||
|
||||
## LangGraph APIs
|
||||
|
||||
- [Graph API](../concepts/low_level.md): Use the Graph API to define workflows using a graph paradigm.
|
||||
@@ -19,8 +24,7 @@ These capabilities are available in both LangGraph OSS and the LangGraph Platfor
|
||||
- [Context](../agents/context.md): Pass outside data to a LangGraph graph to provide context for the graph execution.
|
||||
- [Models](../agents/models.md): Integrate various LLMs into your LangGraph application.
|
||||
- [Tools](../concepts/tools.md): Interface directly with external systems.
|
||||
- [Human-in-the-loop](../concepts/human_in_the_loop.md): Enable human intervention at any point in a workflow.
|
||||
- [Breakpoints](../concepts/breakpoints.md): Pause the execution of a LangGraph graph at a specific point.
|
||||
- [Human-in-the-loop](../concepts/human_in_the_loop.md): Pause a graph and wait for human input at any point in a workflow.
|
||||
- [Time travel](../concepts/time-travel.md): Travel back in time to a specific point in the execution of a LangGraph graph.
|
||||
- [Subgraphs](../concepts/subgraphs.md): Build modular graphs.
|
||||
- [Multi-agent](../concepts/multi_agent.md): Break down a complex workflow into multiple agents.
|
||||
@@ -31,11 +35,11 @@ These capabilities are available in both LangGraph OSS and the LangGraph Platfor
|
||||
|
||||
These capabilities are only available in [LangGraph Platform](../concepts/langgraph_platform.md).
|
||||
|
||||
- [Authentication and access control](../concepts/auth.md): Authenticate and authorize users to access a Langraph graph.
|
||||
- [Authentication and access control](../concepts/auth.md): Authenticate and authorize users to access a LangGraph graph.
|
||||
- [Assistants](../concepts/assistants.md): Build assistants that can be used to interact with a LangGraph graph.
|
||||
- [Double-texting](../concepts/double_texting.md): Handle double-texting (consecutive messages before a first response is returned) in a LangGraph graph.
|
||||
- [Webhooks](../cloud/concepts/webhooks.md): Send webhooks to a LangGraph graph.
|
||||
- [Cron jobs](../cloud/concepts/cron_jobs.md): Schedule jobs to run at a specific time.
|
||||
- [Server customization](../how-tos/http/custom_lifespan.md): Customize the server that runs a LangGraph graph.
|
||||
- [Data management](../cloud/concepts/data_storage_and_privacy.md): Manage data in a LangGraph graph.
|
||||
- [Deployment](../concepts/deployment_options.md): Deploy a LangGraph graph to a server.
|
||||
- [Deployment](../concepts/deployment_options.md): Deploy a LangGraph graph to a server.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 7.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
@@ -31,12 +31,12 @@ To leverage custom authentication and access user-level metadata in your deploym
|
||||
api_key = headers.get("x-api-key")
|
||||
if not api_key or not is_valid_key(api_key):
|
||||
raise Auth.exceptions.HTTPException(status_code=401, detail="Invalid API key")
|
||||
|
||||
# Fetch user-specific tokens from your secret store
|
||||
|
||||
# Fetch user-specific tokens from your secret store
|
||||
user_tokens = await fetch_user_tokens(api_key)
|
||||
|
||||
return { # (2)!
|
||||
"identity": api_key, # fetch user ID from LangSmith
|
||||
"identity": api_key, # fetch user ID from LangSmith
|
||||
"github_token" : user_tokens.github_token
|
||||
"jira_token" : user_tokens.jira_token
|
||||
# ... custom fields/secrets here
|
||||
@@ -50,14 +50,14 @@ To leverage custom authentication and access user-level metadata in your deploym
|
||||
|
||||
```json hl_lines="7-9"
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"agent": "./agent.py:graph"
|
||||
},
|
||||
"env": ".env",
|
||||
"auth": {
|
||||
},
|
||||
"env": ".env",
|
||||
"auth": {
|
||||
"path": "./auth.py:my_auth"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -80,7 +80,7 @@ To leverage custom authentication and access user-level metadata in your deploym
|
||||
|
||||
```python
|
||||
from langgraph.pregel.remote import RemoteGraph
|
||||
|
||||
|
||||
my_token = "your-token" # In practice, you would generate a signed token with your auth provider
|
||||
remote_graph = RemoteGraph(
|
||||
"agent",
|
||||
@@ -133,15 +133,44 @@ To allow an agent to perform authenticated actions on behalf of the user, access
|
||||
def my_node(state, config):
|
||||
user_config = config["configurable"].get("langgraph_auth_user")
|
||||
# token was resolved during the @auth.authenticate function
|
||||
token = user_config.get("github_token","")
|
||||
token = user_config.get("github_token","")
|
||||
...
|
||||
```
|
||||
|
||||
!!! note
|
||||
Fetch user credentials from a secure secret store. Storing secrets in graph state is not recommended.
|
||||
|
||||
### Authorizing a Studio user
|
||||
|
||||
By default, if you add custom authorization on your resources, this will also apply to interactions made from the Studio. If you want, you can handle logged-in Studio users differently by checking [is_studio_user()](../../reference/functions/sdk_auth.isStudioUser.html).
|
||||
|
||||
!!! note
|
||||
`is_studio_user` was added in version 0.1.73 of the langgraph-sdk. If you're on an older version, you can still check whether `isinstance(ctx.user, StudioUser)`.
|
||||
|
||||
```python
|
||||
from langgraph_sdk.auth import is_studio_user, Auth
|
||||
auth = Auth()
|
||||
|
||||
# ... Setup authenticate, etc.
|
||||
|
||||
@auth.on
|
||||
async def add_owner(
|
||||
ctx: Auth.types.AuthContext,
|
||||
value: dict # The payload being sent to this access method
|
||||
) -> dict: # Returns a filter dict that restricts access to resources
|
||||
if is_studio_user(ctx.user):
|
||||
return {}
|
||||
|
||||
filters = {"owner": ctx.user.identity}
|
||||
metadata = value.setdefault("metadata", {})
|
||||
metadata.update(filters)
|
||||
return filters
|
||||
```
|
||||
|
||||
Only use this if you want to permit developer access to a graph deployed on the managed LangGraph Platform SaaS.
|
||||
|
||||
## Learn more
|
||||
|
||||
* [Authentication & Access Control](../../concepts/auth.md)
|
||||
* [LangGraph Platform](../../concepts/langgraph_platform.md)
|
||||
* [Setting up custom authentication tutorial](../../tutorials/auth/getting_started.md)
|
||||
- [Authentication & Access Control](../../concepts/auth.md)
|
||||
- [LangGraph Platform](../../concepts/langgraph_platform.md)
|
||||
- [Setting up custom authentication tutorial](../../tutorials/auth/getting_started.md)
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdin",
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"OPENAI_API_KEY: ········\n"
|
||||
@@ -165,7 +165,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"execution_count": null,
|
||||
"id": "d129e4e1-3766-429a-b806-cde3d8bc0469",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -173,7 +173,7 @@
|
||||
"from langchain_core.messages import convert_to_openai_messages, BaseMessage\n",
|
||||
"from langgraph.func import entrypoint, task\n",
|
||||
"from langgraph.graph import add_messages\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@task\n",
|
||||
@@ -192,7 +192,7 @@
|
||||
"\n",
|
||||
"\n",
|
||||
"# add short-term memory for storing conversation history\n",
|
||||
"checkpointer = MemorySaver()\n",
|
||||
"checkpointer = InMemorySaver()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@entrypoint(checkpointer=checkpointer)\n",
|
||||
@@ -222,12 +222,12 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\u001B[33muser_proxy\u001B[0m (to assistant):\n",
|
||||
"\u001b[33muser_proxy\u001b[0m (to assistant):\n",
|
||||
"\n",
|
||||
"Find numbers between 10 and 30 in fibonacci sequence\n",
|
||||
"\n",
|
||||
"--------------------------------------------------------------------------------\n",
|
||||
"\u001B[33massistant\u001B[0m (to user_proxy):\n",
|
||||
"\u001b[33massistant\u001b[0m (to user_proxy):\n",
|
||||
"\n",
|
||||
"To find numbers between 10 and 30 in the Fibonacci sequence, we can generate the Fibonacci sequence and check which numbers fall within this range. Here's a plan:\n",
|
||||
"\n",
|
||||
@@ -253,9 +253,9 @@
|
||||
"This script will print the Fibonacci numbers between 10 and 30. Please execute the code to see the result.\n",
|
||||
"\n",
|
||||
"--------------------------------------------------------------------------------\n",
|
||||
"\u001B[31m\n",
|
||||
">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001B[0m\n",
|
||||
"\u001B[33muser_proxy\u001B[0m (to assistant):\n",
|
||||
"\u001b[31m\n",
|
||||
">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n",
|
||||
"\u001b[33muser_proxy\u001b[0m (to assistant):\n",
|
||||
"\n",
|
||||
"exitcode: 0 (execution succeeded)\n",
|
||||
"Code output: \n",
|
||||
@@ -264,7 +264,7 @@
|
||||
"\n",
|
||||
"\n",
|
||||
"--------------------------------------------------------------------------------\n",
|
||||
"\u001B[33massistant\u001B[0m (to user_proxy):\n",
|
||||
"\u001b[33massistant\u001b[0m (to user_proxy):\n",
|
||||
"\n",
|
||||
"The Fibonacci numbers between 10 and 30 are 13 and 21. \n",
|
||||
"\n",
|
||||
@@ -318,7 +318,7 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\u001B[33muser_proxy\u001B[0m (to assistant):\n",
|
||||
"\u001b[33muser_proxy\u001b[0m (to assistant):\n",
|
||||
"\n",
|
||||
"Multiply the last number by 3\n",
|
||||
"Context: \n",
|
||||
@@ -334,7 +334,7 @@
|
||||
"TERMINATE\n",
|
||||
"\n",
|
||||
"--------------------------------------------------------------------------------\n",
|
||||
"\u001B[33massistant\u001B[0m (to user_proxy):\n",
|
||||
"\u001b[33massistant\u001b[0m (to user_proxy):\n",
|
||||
"\n",
|
||||
"The last number in the Fibonacci sequence between 10 and 30 is 21. Multiplying 21 by 3 gives:\n",
|
||||
"\n",
|
||||
|
||||
@@ -75,7 +75,7 @@ We will now create a LangGraph chatbot graph that calls AutoGen agent.
|
||||
```python
|
||||
from langchain_core.messages import convert_to_openai_messages
|
||||
from langgraph.graph import StateGraph, MessagesState, START
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
def call_autogen_agent(state: MessagesState):
|
||||
# Convert LangGraph messages to OpenAI format for AutoGen
|
||||
@@ -101,7 +101,7 @@ def call_autogen_agent(state: MessagesState):
|
||||
return {"messages": {"role": "assistant", "content": final_content}}
|
||||
|
||||
# Create the graph with memory for persistence
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
# Build the graph
|
||||
builder = StateGraph(MessagesState)
|
||||
@@ -228,7 +228,7 @@ my-autogen-agent/
|
||||
import autogen
|
||||
from langchain_core.messages import convert_to_openai_messages
|
||||
from langgraph.graph import StateGraph, MessagesState, START
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
# AutoGen configuration
|
||||
config_list = [{"model": "gpt-4o", "api_key": os.environ["OPENAI_API_KEY"]}]
|
||||
@@ -276,7 +276,7 @@ my-autogen-agent/
|
||||
|
||||
# Create and compile the graph
|
||||
def create_graph():
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
builder = StateGraph(MessagesState)
|
||||
builder.add_node("autogen", call_autogen_agent)
|
||||
builder.add_edge(START, "autogen")
|
||||
@@ -290,7 +290,7 @@ my-autogen-agent/
|
||||
|
||||
```
|
||||
langgraph>=0.1.0
|
||||
pyautogen>=0.2.0
|
||||
ag2>=0.2.0
|
||||
langchain-core>=0.1.0
|
||||
langchain-openai>=0.0.5
|
||||
```
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"from langgraph.func import entrypoint, task\n",
|
||||
"from langgraph.graph import add_messages\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
"from langgraph.store.base import BaseStore\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -192,7 +192,7 @@
|
||||
"\n",
|
||||
"\n",
|
||||
"# NOTE: we're passing the store object here when creating a workflow via entrypoint()\n",
|
||||
"@entrypoint(checkpointer=MemorySaver(), store=in_memory_store)\n",
|
||||
"@entrypoint(checkpointer=InMemorySaver(), store=in_memory_store)\n",
|
||||
"def workflow(\n",
|
||||
" inputs: list[BaseMessage],\n",
|
||||
" *,\n",
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# Enable tracing for your application
|
||||
|
||||
To enable [tracing](../concepts/tracing.md) for your application, set the following environment variables:
|
||||
|
||||
```python
|
||||
export LANGSMITH_TRACING=true
|
||||
export LANGSMITH_API_KEY=<your-api-key>
|
||||
```
|
||||
|
||||
For more information, see [Trace with LangGraph](https://docs.smith.langchain.com/observability/how_to_guides/trace_with_langgraph).
|
||||
|
||||
## Learn more
|
||||
|
||||
- [Graph runs in LangSmith](../how-tos/run-id-langsmith.md)
|
||||
- [LangSmith Observability quickstart](https://docs.smith.langchain.com/observability)
|
||||
- [Tracing conceptual guide](https://docs.smith.langchain.com/observability/concepts#traces)
|
||||
@@ -328,14 +328,15 @@ Output of graph invocation: {'a': 'set by node_3'}
|
||||
|
||||
A [StateGraph](https://langchain-ai.github.io/langgraph/reference/graphs.md#langgraph.graph.StateGraph) accepts a `state_schema` argument on initialization that specifies the "shape" of the state that the nodes in the graph can access and update.
|
||||
|
||||
In our examples, we typically use a python-native `TypedDict` for `state_schema`, but `state_schema` can be any [type](https://docs.python.org/3/library/stdtypes.html#type-objects).
|
||||
In our examples, we typically use a python-native `TypedDict` or [`dataclass`](https://docs.python.org/3/library/dataclasses.html) for `state_schema`, but `state_schema` can be any [type](https://docs.python.org/3/library/stdtypes.html#type-objects).
|
||||
|
||||
Here, we'll see how a [Pydantic BaseModel](https://docs.pydantic.dev/latest/api/base_model/). can be used for `state_schema` to add run time validation on **inputs**.
|
||||
Here, we'll see how a [Pydantic BaseModel](https://docs.pydantic.dev/latest/api/base_model/) can be used for `state_schema` to add run-time validation on **inputs**.
|
||||
|
||||
!!! note "Known Limitations"
|
||||
- Currently, the output of the graph will **NOT** be an instance of a pydantic model.
|
||||
- Run-time validation only occurs on inputs into nodes, not on the outputs.
|
||||
- The validation error trace from pydantic does not show which node the error arises in.
|
||||
- Pydantic's recursive validation can be slow. For performance-sensitive applications, you may want to consider using a `dataclass` instead.
|
||||
|
||||
```python
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
@@ -513,12 +514,12 @@ To add runtime configuration:
|
||||
See below for a simple example:
|
||||
|
||||
```python
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.graph import END, StateGraph, START
|
||||
from langgraph.runtime import Runtime
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
# 1. Specify config schema
|
||||
class ConfigSchema(TypedDict):
|
||||
class ContextSchema(TypedDict):
|
||||
my_runtime_value: str
|
||||
|
||||
# 2. Define a graph that accesses the config in a node
|
||||
@@ -526,18 +527,18 @@ class State(TypedDict):
|
||||
my_state_value: str
|
||||
|
||||
# highlight-next-line
|
||||
def node(state: State, config: RunnableConfig):
|
||||
def node(state: State, runtime: Runtime[ContextSchema]):
|
||||
# highlight-next-line
|
||||
if config["configurable"]["my_runtime_value"] == "a":
|
||||
if runtime.context["my_runtime_value"] == "a":
|
||||
return {"my_state_value": 1}
|
||||
# highlight-next-line
|
||||
elif config["configurable"]["my_runtime_value"] == "b":
|
||||
elif runtime.context["my_runtime_value"] == "b":
|
||||
return {"my_state_value": 2}
|
||||
else:
|
||||
raise ValueError("Unknown values.")
|
||||
|
||||
# highlight-next-line
|
||||
builder = StateGraph(State, config_schema=ConfigSchema)
|
||||
builder = StateGraph(State, context_schema=ContextSchema)
|
||||
builder.add_node(node)
|
||||
builder.add_edge(START, "node")
|
||||
builder.add_edge("node", END)
|
||||
@@ -546,9 +547,9 @@ graph = builder.compile()
|
||||
|
||||
# 3. Pass in configuration at runtime:
|
||||
# highlight-next-line
|
||||
print(graph.invoke({}, {"configurable": {"my_runtime_value": "a"}}))
|
||||
print(graph.invoke({}, context={"my_runtime_value": "a"}))
|
||||
# highlight-next-line
|
||||
print(graph.invoke({}, {"configurable": {"my_runtime_value": "b"}}))
|
||||
print(graph.invoke({}, context={"my_runtime_value": "b"}))
|
||||
```
|
||||
```
|
||||
{'my_state_value': 1}
|
||||
@@ -559,27 +560,28 @@ print(graph.invoke({}, {"configurable": {"my_runtime_value": "b"}}))
|
||||
Below we demonstrate a practical example in which we configure what LLM to use at runtime. We will use both OpenAI and Anthropic models.
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
|
||||
from langchain.chat_models import init_chat_model
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.graph import MessagesState
|
||||
from langgraph.graph import END, StateGraph, START
|
||||
from langgraph.graph import MessagesState, END, StateGraph, START
|
||||
from langgraph.runtime import Runtime
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
class ConfigSchema(TypedDict):
|
||||
model: str
|
||||
@dataclass
|
||||
class ContextSchema:
|
||||
model_provider: str = "anthropic"
|
||||
|
||||
MODELS = {
|
||||
"anthropic": init_chat_model("anthropic:claude-3-5-haiku-latest"),
|
||||
"openai": init_chat_model("openai:gpt-4.1-mini"),
|
||||
}
|
||||
|
||||
def call_model(state: MessagesState, config: RunnableConfig):
|
||||
model = config["configurable"].get("model", "anthropic")
|
||||
model = MODELS[model]
|
||||
def call_model(state: MessagesState, runtime: Runtime[ContextSchema]):
|
||||
model = MODELS[runtime.context.model_provider]
|
||||
response = model.invoke(state["messages"])
|
||||
return {"messages": [response]}
|
||||
|
||||
builder = StateGraph(MessagesState, config_schema=ConfigSchema)
|
||||
builder = StateGraph(MessagesState, context_schema=ContextSchema)
|
||||
builder.add_node("model", call_model)
|
||||
builder.add_edge(START, "model")
|
||||
builder.add_edge("model", END)
|
||||
@@ -591,8 +593,7 @@ print(graph.invoke({}, {"configurable": {"my_runtime_value": "b"}}))
|
||||
# With no configuration, uses default (Anthropic)
|
||||
response_1 = graph.invoke({"messages": [input_message]})["messages"][-1]
|
||||
# Or, can set OpenAI
|
||||
config = {"configurable": {"model": "openai"}}
|
||||
response_2 = graph.invoke({"messages": [input_message]}, config=config)["messages"][-1]
|
||||
response_2 = graph.invoke({"messages": [input_message]}, context={"model_provider": "openai"})["messages"][-1]
|
||||
|
||||
print(response_1.response_metadata["model_name"])
|
||||
print(response_2.response_metadata["model_name"])
|
||||
@@ -606,32 +607,33 @@ print(graph.invoke({}, {"configurable": {"my_runtime_value": "b"}}))
|
||||
Below we demonstrate a practical example in which we configure two parameters: the LLM and system message to use at runtime.
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
from langchain.chat_models import init_chat_model
|
||||
from langchain_core.messages import SystemMessage
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.graph import END, MessagesState, StateGraph, START
|
||||
from langgraph.runtime import Runtime
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
class ConfigSchema(TypedDict):
|
||||
model: Optional[str]
|
||||
system_message: Optional[str]
|
||||
@dataclass
|
||||
class ContextSchema:
|
||||
model_provider: str = "anthropic"
|
||||
system_message: str | None = None
|
||||
|
||||
MODELS = {
|
||||
"anthropic": init_chat_model("anthropic:claude-3-5-haiku-latest"),
|
||||
"openai": init_chat_model("openai:gpt-4.1-mini"),
|
||||
}
|
||||
|
||||
def call_model(state: MessagesState, config: RunnableConfig):
|
||||
model = config["configurable"].get("model", "anthropic")
|
||||
model = MODELS[model]
|
||||
def call_model(state: MessagesState, runtime: Runtime[ContextSchema]):
|
||||
model = MODELS[runtime.context.model_provider]
|
||||
messages = state["messages"]
|
||||
if system_message := config["configurable"].get("system_message"):
|
||||
if (system_message := runtime.context.system_message):
|
||||
messages = [SystemMessage(system_message)] + messages
|
||||
response = model.invoke(messages)
|
||||
return {"messages": [response]}
|
||||
|
||||
builder = StateGraph(MessagesState, config_schema=ConfigSchema)
|
||||
builder = StateGraph(MessagesState, context_schema=ContextSchema)
|
||||
builder.add_node("model", call_model)
|
||||
builder.add_edge(START, "model")
|
||||
builder.add_edge("model", END)
|
||||
@@ -640,8 +642,7 @@ print(graph.invoke({}, {"configurable": {"my_runtime_value": "b"}}))
|
||||
|
||||
# Usage
|
||||
input_message = {"role": "user", "content": "hi"}
|
||||
config = {"configurable": {"model": "openai", "system_message": "Respond in Italian."}}
|
||||
response = graph.invoke({"messages": [input_message]}, config)
|
||||
response = graph.invoke({"messages": [input_message]}, context={"model_provider": "openai", "system_message": "Respond in Italian."})
|
||||
for message in response["messages"]:
|
||||
message.pretty_print()
|
||||
```
|
||||
@@ -1149,13 +1150,15 @@ Adding "C" to ['A']
|
||||
LangGraph supports map-reduce and other advanced branching patterns using the Send API. Here is an example of how to use it:
|
||||
|
||||
```python
|
||||
from langgraph.graph import StateGraph, START, END, Send
|
||||
from typing_extensions import TypedDict
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
from langgraph.types import Send
|
||||
from typing_extensions import TypedDict, Annotated
|
||||
import operator
|
||||
|
||||
class OverallState(TypedDict):
|
||||
topic: str
|
||||
subjects: list[str]
|
||||
jokes: list[str]
|
||||
jokes: Annotated[list[str], operator.add]
|
||||
best_selected_joke: str
|
||||
|
||||
def generate_topics(state: OverallState):
|
||||
@@ -1193,7 +1196,7 @@ from IPython.display import Image, display
|
||||
display(Image(graph.get_graph().draw_mermaid_png()))
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
```python
|
||||
# Call the graph: here we call it to generate a list of jokes
|
||||
@@ -1445,7 +1448,7 @@ Recursion Error
|
||||
display(Image(graph.get_graph().draw_mermaid_png()))
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
This graph looks complex, but can be conceptualized as loop of [supersteps](../concepts/low_level.md#graphs):
|
||||
|
||||
@@ -1507,7 +1510,7 @@ Because many LangChain objects implement the [Runnable Protocol](https://python.
|
||||
|
||||
See example below. To demonstrate async invocations of underlying LLMs, we will include a chat model:
|
||||
|
||||
{!snippets/chat_model_tabs.md!}
|
||||
{% include-markdown "../../snippets/chat_model_tabs.md" %}
|
||||
|
||||
```python
|
||||
from langchain.chat_models import init_chat_model
|
||||
@@ -1564,9 +1567,9 @@ class State(TypedDict):
|
||||
|
||||
def node_a(state: State) -> Command[Literal["node_b", "node_c"]]:
|
||||
print("Called A")
|
||||
value = random.choice(["a", "b"])
|
||||
value = random.choice(["b", "c"])
|
||||
# this is a replacement for a conditional edge function
|
||||
if value == "a":
|
||||
if value == "b":
|
||||
goto = "node_b"
|
||||
else:
|
||||
goto = "node_c"
|
||||
|
||||
@@ -11,11 +11,19 @@ hide:
|
||||
|
||||
# Enable human intervention
|
||||
|
||||
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.
|
||||
To review, edit, and approve tool calls in an agent or workflow, use interrupts to pause a graph and wait for human input. Interrupts use LangGraph's [persistence](../../concepts/persistence.md) layer, which saves the graph state, to indefinitely pause graph execution until you resume.
|
||||
|
||||
!!! info
|
||||
|
||||
For more information about human-in-the-loop workflows, see the [Human-in-the-Loop](../../concepts/human_in_the_loop.md) conceptual guide.
|
||||
|
||||
## 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.
|
||||
[Dynamic interrupts](../../concepts/human_in_the_loop.md#key-capabilities) (also known as dynamic breakpoints) are triggered based on the current state of the graph. You can set dynamic interrupts by calling [`interrupt` function][langgraph.types.interrupt] in the appropriate place. The graph will pause, which allows for human intervention, and then resumes the graph with their input. It's useful for tasks like approvals, edits, or gathering additional context.
|
||||
|
||||
!!! note
|
||||
|
||||
As of v1.0, `interrupt` is the recommended way to pause a graph. `NodeInterrupt` is deprecated and will be removed in v2.0.
|
||||
|
||||
To use `interrupt` in your graph, you need to:
|
||||
|
||||
@@ -46,13 +54,7 @@ graph = graph_builder.compile(checkpointer=checkpointer) # (4)!
|
||||
config = {"configurable": {"thread_id": "some_id"}}
|
||||
result = graph.invoke({"some_text": "original text"}, config=config) # (5)!
|
||||
print(result['__interrupt__']) # (6)!
|
||||
# > [
|
||||
# > Interrupt(
|
||||
# > value={'text_to_revise': 'original text'},
|
||||
# > resumable=True,
|
||||
# > ns=['human_node:6ce9e64f-edef-fe5d-f7dc-511fa9526960']
|
||||
# > )
|
||||
# > ]
|
||||
# > [Interrupt(value={'text_to_revise': 'original text'}, id='a0d9dd40440ac7be2720dc5c20858627')]
|
||||
|
||||
# highlight-next-line
|
||||
print(graph.invoke(Command(resume="Edited text"), config=config)) # (7)!
|
||||
@@ -72,25 +74,27 @@ print(graph.invoke(Command(resume="Edited text"), config=config)) # (7)!
|
||||
```python
|
||||
from typing import TypedDict
|
||||
import uuid
|
||||
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.constants import START
|
||||
from langgraph.graph import StateGraph
|
||||
|
||||
# highlight-next-line
|
||||
from langgraph.types import interrupt, Command
|
||||
|
||||
|
||||
class State(TypedDict):
|
||||
some_text: str
|
||||
|
||||
|
||||
def human_node(state: State):
|
||||
# highlight-next-line
|
||||
value = interrupt( # (1)!
|
||||
value = interrupt( # (1)!
|
||||
{
|
||||
"text_to_revise": state["some_text"] # (2)!
|
||||
"text_to_revise": state["some_text"] # (2)!
|
||||
}
|
||||
)
|
||||
return {
|
||||
"some_text": value # (3)!
|
||||
"some_text": value # (3)!
|
||||
}
|
||||
|
||||
|
||||
@@ -98,25 +102,15 @@ print(graph.invoke(Command(resume="Edited text"), config=config)) # (7)!
|
||||
graph_builder = StateGraph(State)
|
||||
graph_builder.add_node("human_node", human_node)
|
||||
graph_builder.add_edge(START, "human_node")
|
||||
|
||||
checkpointer = InMemorySaver() # (4)!
|
||||
|
||||
checkpointer = InMemorySaver() # (4)!
|
||||
graph = graph_builder.compile(checkpointer=checkpointer)
|
||||
|
||||
# Pass a thread ID to the graph to run it.
|
||||
config = {"configurable": {"thread_id": uuid.uuid4()}}
|
||||
|
||||
# Run the graph until the interrupt is hit.
|
||||
result = graph.invoke({"some_text": "original text"}, config=config) # (5)!
|
||||
result = graph.invoke({"some_text": "original text"}, config=config) # (5)!
|
||||
|
||||
print(result['__interrupt__']) # (6)!
|
||||
# > [
|
||||
# > Interrupt(
|
||||
# > value={'text_to_revise': 'original text'},
|
||||
# > resumable=True,
|
||||
# > ns=['human_node:6ce9e64f-edef-fe5d-f7dc-511fa9526960']
|
||||
# > )
|
||||
# > ]
|
||||
print(result["__interrupt__"]) # (6)!
|
||||
# > [Interrupt(value={'text_to_revise': 'original text'}, id='6d7c4048049254c83195429a3659661d')]
|
||||
|
||||
# highlight-next-line
|
||||
print(graph.invoke(Command(resume="Edited text"), config=config)) # (7)!
|
||||
@@ -134,19 +128,14 @@ print(graph.invoke(Command(resume="Edited text"), config=config)) # (7)!
|
||||
|
||||
!!! 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.
|
||||
`__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(s).
|
||||
|
||||
!!! 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.
|
||||
|
||||
Interrupts resemble Python's input() function in terms of developer experience, but 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.
|
||||
|
||||
## Resume using the `Command` primitive
|
||||
|
||||
!!! 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.
|
||||
|
||||
When the `interrupt` function is used within a graph, execution pauses at that point and awaits user input.
|
||||
|
||||
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.
|
||||
@@ -156,19 +145,67 @@ To resume execution, use the [`Command`][langgraph.types.Command] primitive, whi
|
||||
graph.invoke(Command(resume={"age": "25"}), thread_config)
|
||||
```
|
||||
|
||||
### Resume multiple interrupts with one invocation
|
||||
## Resuming Multiple interrupts
|
||||
|
||||
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.
|
||||
When nodes with interrupt conditions are run in parallel, it's possible to have multiple interrupts in the task queue.
|
||||
For example, the following graph has two nodes run in parallel that require human input:
|
||||
|
||||
<figure markdown="1">
|
||||
{: style="max-height:400px"}
|
||||
</figure>
|
||||
|
||||
Once your graph has been interrupted and is stalled, you can resume all the interrupts at once with `Command.resume`, passing a dictionary mapping of interrupt ids to resume values.
|
||||
|
||||
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
|
||||
}
|
||||
from typing import TypedDict
|
||||
import uuid
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.constants import START
|
||||
from langgraph.graph import StateGraph
|
||||
from langgraph.types import interrupt, Command
|
||||
|
||||
parent_graph.invoke(Command(resume=resume_map), config=thread_config)
|
||||
|
||||
class State(TypedDict):
|
||||
text_1: str
|
||||
text_2: str
|
||||
|
||||
|
||||
def human_node_1(state: State):
|
||||
value = interrupt({"text_to_revise": state["text_1"]})
|
||||
return {"text_1": value}
|
||||
|
||||
|
||||
def human_node_2(state: State):
|
||||
value = interrupt({"text_to_revise": state["text_2"]})
|
||||
return {"text_2": value}
|
||||
|
||||
|
||||
graph_builder = StateGraph(State)
|
||||
graph_builder.add_node("human_node_1", human_node_1)
|
||||
graph_builder.add_node("human_node_2", human_node_2)
|
||||
|
||||
# Add both nodes in parallel from START
|
||||
graph_builder.add_edge(START, "human_node_1")
|
||||
graph_builder.add_edge(START, "human_node_2")
|
||||
|
||||
checkpointer = InMemorySaver()
|
||||
graph = graph_builder.compile(checkpointer=checkpointer)
|
||||
|
||||
thread_id = str(uuid.uuid4())
|
||||
config: RunnableConfig = {"configurable": {"thread_id": thread_id}}
|
||||
result = graph.invoke(
|
||||
{"text_1": "original text 1", "text_2": "original text 2"}, config=config
|
||||
)
|
||||
|
||||
# Resume with mapping of interrupt IDs to values
|
||||
resume_map = {
|
||||
i.id: f"edited text for {i.value['text_to_revise']}"
|
||||
for i in result["__interrupt__"]
|
||||
}
|
||||
print(graph.invoke(Command(resume=resume_map), config=config))
|
||||
# > {'text_1': 'edited text for original text 1', 'text_2': 'edited text for original text 2'}
|
||||
```
|
||||
|
||||
## Common patterns
|
||||
@@ -223,7 +260,7 @@ graph.invoke(Command(resume=True), config=thread_config)
|
||||
from langgraph.constants import START, END
|
||||
from langgraph.graph import StateGraph
|
||||
from langgraph.types import interrupt, Command
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
# Define the shared graph state
|
||||
class State(TypedDict):
|
||||
@@ -268,7 +305,7 @@ graph.invoke(Command(resume=True), config=thread_config)
|
||||
builder.add_edge("approved_path", END)
|
||||
builder.add_edge("rejected_path", END)
|
||||
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
|
||||
# Run until interrupt
|
||||
@@ -336,7 +373,7 @@ graph.invoke(
|
||||
from langgraph.constants import START, END
|
||||
from langgraph.graph import StateGraph
|
||||
from langgraph.types import interrupt, Command
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
# Define the graph state
|
||||
class State(TypedDict):
|
||||
@@ -375,7 +412,7 @@ graph.invoke(
|
||||
builder.add_edge("downstream_use", END)
|
||||
|
||||
# Set up in-memory checkpointing for interrupt support
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
|
||||
# Invoke the graph until it hits the interrupt
|
||||
@@ -385,14 +422,15 @@ graph.invoke(
|
||||
# Output interrupt payload
|
||||
print(result["__interrupt__"])
|
||||
# Example output:
|
||||
# Interrupt(
|
||||
# value={
|
||||
# 'task': 'Please review and edit the generated summary if necessary.',
|
||||
# 'generated_summary': 'The cat sat on the mat and looked at the stars.'
|
||||
# },
|
||||
# resumable=True,
|
||||
# ...
|
||||
# )
|
||||
# > [
|
||||
# > Interrupt(
|
||||
# > value={
|
||||
# > 'task': 'Please review and edit the generated summary if necessary.',
|
||||
# > 'generated_summary': 'The cat sat on the mat and looked at the stars.'
|
||||
# > },
|
||||
# > id='...'
|
||||
# > )
|
||||
# > ]
|
||||
|
||||
# Resume the graph with human-edited input
|
||||
edited_summary = "The cat lay on the rug, gazing peacefully at the night sky."
|
||||
@@ -652,7 +690,7 @@ def human_node(state: State):
|
||||
from langgraph.constants import START, END
|
||||
from langgraph.graph import StateGraph
|
||||
from langgraph.types import interrupt, Command
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
# Define graph state
|
||||
class State(TypedDict):
|
||||
@@ -691,7 +729,7 @@ def human_node(state: State):
|
||||
builder.add_edge("report_age", END)
|
||||
|
||||
# Create the graph with a memory checkpointer
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
|
||||
# Run the graph until the first interrupt
|
||||
@@ -712,6 +750,162 @@ def human_node(state: State):
|
||||
print(final_result) # Should include the valid age
|
||||
```
|
||||
|
||||
## Debug with interrupts
|
||||
|
||||
To debug and test a graph, use [static interrupts](../../concepts/human_in_the_loop.md#key-capabilities) (also known as static breakpoints) to step through the graph execution one node at a time or to pause the graph execution at specific nodes. Static interrupts are triggered at defined points either before or after a node executes. You can set static interrupts by specifying `interrupt_before` and `interrupt_after` at compile time or run time.
|
||||
|
||||
!!! warning
|
||||
|
||||
Static interrupts are **not** recommended for human-in-the-loop workflows. Use [dynamic interrupts](#pause-using-interrupt) instead.
|
||||
|
||||
=== "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)
|
||||
```
|
||||
|
||||
### Use static interrupts in LangGraph Studio
|
||||
|
||||
You can use [LangGraph Studio](../../concepts/langgraph_studio.md) to debug your graph. You can set static breakpoints in the UI and then run the graph. You can also use the UI to inspect the graph state at any point in the execution.
|
||||
|
||||
{: style="max-height:400px"}
|
||||
|
||||
LangGraph Studio is free with [locally deployed applications](../../tutorials/langgraph-platform/local-server.md) using `langgraph dev`.
|
||||
|
||||
## Considerations
|
||||
|
||||
When using human-in-the-loop, there are some considerations to keep in mind.
|
||||
@@ -792,7 +986,7 @@ def node_in_parent_graph(state: State):
|
||||
from langgraph.graph import StateGraph
|
||||
from langgraph.constants import START
|
||||
from langgraph.types import interrupt, Command
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
|
||||
class State(TypedDict):
|
||||
@@ -818,7 +1012,7 @@ def node_in_parent_graph(state: State):
|
||||
print(f"Got an answer of {answer}")
|
||||
|
||||
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
subgraph_builder = StateGraph(State)
|
||||
subgraph_builder.add_node("some_node", node_in_subgraph)
|
||||
@@ -849,7 +1043,7 @@ def node_in_parent_graph(state: State):
|
||||
builder.add_edge(START, "parent_node")
|
||||
|
||||
# A checkpointer must be enabled for interrupts to work!
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
|
||||
config = {
|
||||
@@ -873,7 +1067,7 @@ def node_in_parent_graph(state: State):
|
||||
Entered `parent_node` a total of 1 times
|
||||
Entered `node_in_subgraph` a total of 1 times
|
||||
Entered human_node in sub-graph a total of 1 times
|
||||
{'__interrupt__': (Interrupt(value='what is your name?', resumable=True, ns=['parent_node:4c3a0248-21f0-1287-eacf-3002bc304db4', 'human_node:2fe86d52-6f70-2a3f-6b2f-b1eededd6348'], when='during'),)}
|
||||
{'__interrupt__': (Interrupt(value='what is your name?', id='...'),)}
|
||||
--- Resuming ---
|
||||
Entered `parent_node` a total of 2 times
|
||||
Entered human_node in sub-graph a total of 2 times
|
||||
@@ -881,7 +1075,7 @@ def node_in_parent_graph(state: State):
|
||||
{'parent_node': {'state_counter': 1}}
|
||||
```
|
||||
|
||||
### Using multiple interrupts
|
||||
### Using multiple interrupts in a single node
|
||||
|
||||
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.
|
||||
|
||||
@@ -898,7 +1092,7 @@ To avoid issues, refrain from dynamically changing the node's structure between
|
||||
from langgraph.graph import StateGraph
|
||||
from langgraph.constants import START
|
||||
from langgraph.types import interrupt, Command
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
|
||||
class State(TypedDict):
|
||||
@@ -932,7 +1126,7 @@ To avoid issues, refrain from dynamically changing the node's structure between
|
||||
builder.add_edge(START, "human_node")
|
||||
|
||||
# A checkpointer must be enabled for interrupts to work!
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
|
||||
config = {
|
||||
@@ -949,8 +1143,7 @@ To avoid issues, refrain from dynamically changing the node's structure between
|
||||
```
|
||||
|
||||
```pycon
|
||||
{'__interrupt__': (Interrupt(value='what is your name?', resumable=True, ns=['human_node:3a007ef9-c30d-c357-1ec1-86a1a70d8fba'], when='during'),)}
|
||||
{'__interrupt__': (Interrupt(value='what is your name?', id='...'),)}
|
||||
Name: N/A. Age: John
|
||||
{'human_node': {'age': 'John', 'name': 'N/A'}}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,342 +0,0 @@
|
||||
# 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>
|
||||
@@ -4,7 +4,7 @@ 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.
|
||||
Alternatively, set an [interrupt](../../how-tos/human_in_the_loop/add-human-in-the-loop.md) before the node(s) where you want execution to pause. You can then find the most recent checkpoint recorded up to that interrupt.
|
||||
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`.
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
"\n",
|
||||
"# highlight-next-line\n",
|
||||
"from langgraph.types import Command, interrupt\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
"from IPython.display import Image, display\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -157,7 +157,7 @@
|
||||
"builder.add_edge(\"step_3\", END)\n",
|
||||
"\n",
|
||||
"# Set up memory\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"memory = InMemorySaver()\n",
|
||||
"\n",
|
||||
"# Add\n",
|
||||
"graph = builder.compile(checkpointer=memory)\n",
|
||||
@@ -435,9 +435,9 @@
|
||||
"workflow.add_edge(\"ask_human\", \"agent\")\n",
|
||||
"\n",
|
||||
"# Set up memory\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
"\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"memory = InMemorySaver()\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
"memories = store.search((\"user_123\", \"memories\"), query=\"I like food?\", limit=5)\n",
|
||||
"\n",
|
||||
"for memory in memories:\n",
|
||||
" print(f'Memory: {memory.value[\"text\"]} (similarity: {memory.score})')"
|
||||
" print(f\"Memory: {memory.value['text']} (similarity: {memory.score})\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -224,7 +224,7 @@
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"from langgraph.graph import add_messages\n",
|
||||
"from langgraph.func import entrypoint, task\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
"from langgraph.types import interrupt, Command\n",
|
||||
"\n",
|
||||
"model = ChatAnthropic(model=\"claude-3-5-sonnet-latest\")\n",
|
||||
@@ -272,7 +272,7 @@
|
||||
" return response[\"messages\"]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"checkpointer = MemorySaver()\n",
|
||||
"checkpointer = InMemorySaver()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def string_to_uuid(input_string):\n",
|
||||
|
||||
@@ -375,7 +375,7 @@ def agent(state) -> Command[Literal["agent", "another_agent", "human"]]:
|
||||
from langgraph.graph import MessagesState, StateGraph, START
|
||||
from langgraph.prebuilt import create_react_agent, InjectedState
|
||||
from langgraph.types import Command, interrupt
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
|
||||
model = ChatAnthropic(model="claude-3-5-sonnet-latest")
|
||||
@@ -467,7 +467,7 @@ def agent(state) -> Command[Literal["agent", "another_agent", "human"]]:
|
||||
builder.add_edge(START, "travel_advisor")
|
||||
|
||||
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
```
|
||||
|
||||
|
||||
@@ -28,9 +28,9 @@
|
||||
"1. Create an instance of a checkpointer:\n",
|
||||
"\n",
|
||||
" ```python\n",
|
||||
" from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
" from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
" \n",
|
||||
" checkpointer = MemorySaver() \n",
|
||||
" checkpointer = InMemorySaver() \n",
|
||||
" ```\n",
|
||||
"\n",
|
||||
"2. Pass `checkpointer` instance to the `entrypoint()` decorator:\n",
|
||||
@@ -184,7 +184,7 @@
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"from langgraph.graph import add_messages\n",
|
||||
"from langgraph.func import entrypoint, task\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@task\n",
|
||||
@@ -193,7 +193,7 @@
|
||||
" return response\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"checkpointer = MemorySaver()\n",
|
||||
"checkpointer = InMemorySaver()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@entrypoint(checkpointer=checkpointer)\n",
|
||||
|
||||
@@ -261,7 +261,7 @@
|
||||
"\n",
|
||||
"To add thread-level persistence to our agent:\n",
|
||||
"\n",
|
||||
"1. Select a [checkpointer](../../concepts/persistence#checkpointer-libraries): here we will use [MemorySaver](../../reference/checkpoints/#langgraph.checkpoint.memory.MemorySaver), a simple in-memory checkpointer.\n",
|
||||
"1. Select a [checkpointer](../../concepts/persistence#checkpointer-libraries): here we will use [InMemorySaver](../../reference/checkpoints/#langgraph.checkpoint.memory.InMemorySaver), a simple in-memory checkpointer.\n",
|
||||
"2. Update our entrypoint to accept the previous messages state as a second argument. Here, we simply append the message updates to the previous sequence of messages.\n",
|
||||
"3. Choose which values will be returned from the workflow and which will be saved by the checkpointer as `previous` using `entrypoint.final` (optional)"
|
||||
]
|
||||
@@ -272,10 +272,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
"\n",
|
||||
"# highlight-next-line\n",
|
||||
"checkpointer = MemorySaver()\n",
|
||||
"checkpointer = InMemorySaver()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# highlight-next-line\n",
|
||||
|
||||
@@ -26,7 +26,7 @@ my_workflow.invoke({"value": 1, "another_value": 2})
|
||||
```python
|
||||
import uuid
|
||||
from langgraph.func import entrypoint, task
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
# Task that checks if a number is even
|
||||
@task
|
||||
@@ -39,7 +39,7 @@ my_workflow.invoke({"value": 1, "another_value": 2})
|
||||
return "The number is even." if is_even else "The number is odd."
|
||||
|
||||
# Create a checkpointer for persistence
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
@entrypoint(checkpointer=checkpointer)
|
||||
def workflow(inputs: dict) -> str:
|
||||
@@ -63,7 +63,7 @@ my_workflow.invoke({"value": 1, "another_value": 2})
|
||||
import uuid
|
||||
from langchain.chat_models import init_chat_model
|
||||
from langgraph.func import entrypoint, task
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
llm = init_chat_model('openai:gpt-3.5-turbo')
|
||||
|
||||
@@ -77,7 +77,7 @@ my_workflow.invoke({"value": 1, "another_value": 2})
|
||||
]).content
|
||||
|
||||
# Create a checkpointer for persistence
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
@entrypoint(checkpointer=checkpointer)
|
||||
def workflow(topic: str) -> str:
|
||||
@@ -114,7 +114,7 @@ def graph(numbers: list[int]) -> list[str]:
|
||||
import uuid
|
||||
from langchain.chat_models import init_chat_model
|
||||
from langgraph.func import entrypoint, task
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
# Initialize the LLM model
|
||||
llm = init_chat_model("openai:gpt-3.5-turbo")
|
||||
@@ -129,7 +129,7 @@ def graph(numbers: list[int]) -> list[str]:
|
||||
return response.content
|
||||
|
||||
# Create a checkpointer for persistence
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
@entrypoint(checkpointer=checkpointer)
|
||||
def workflow(topics: list[str]) -> str:
|
||||
@@ -176,7 +176,7 @@ def some_workflow(some_input: dict) -> int:
|
||||
import uuid
|
||||
from typing import TypedDict
|
||||
from langgraph.func import entrypoint
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.graph import StateGraph
|
||||
|
||||
# Define the shared state type
|
||||
@@ -194,7 +194,7 @@ def some_workflow(some_input: dict) -> int:
|
||||
graph = builder.compile()
|
||||
|
||||
# Define the functional API workflow
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
@entrypoint(checkpointer=checkpointer)
|
||||
def workflow(x: int) -> dict:
|
||||
@@ -227,10 +227,10 @@ def my_workflow(inputs: dict) -> int:
|
||||
```python
|
||||
import uuid
|
||||
from langgraph.func import entrypoint
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
# Initialize a checkpointer
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
# A reusable sub-workflow that multiplies a number
|
||||
@entrypoint()
|
||||
@@ -258,10 +258,10 @@ Example of using the streaming API to stream both updates and custom data.
|
||||
|
||||
```python
|
||||
from langgraph.func import entrypoint
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.config import get_stream_writer # (1)!
|
||||
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
@entrypoint(checkpointer=checkpointer)
|
||||
def main(inputs: dict) -> int:
|
||||
@@ -316,7 +316,7 @@ for mode, chunk in main.stream( # (5)!
|
||||
## Retry policy
|
||||
|
||||
```python
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.func import entrypoint, task
|
||||
from langgraph.types import RetryPolicy
|
||||
|
||||
@@ -337,7 +337,7 @@ def get_info():
|
||||
raise ValueError('Failure')
|
||||
return "OK"
|
||||
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
@entrypoint(checkpointer=checkpointer)
|
||||
def main(inputs, writer):
|
||||
@@ -392,7 +392,7 @@ for chunk in main.stream({"x": 5}, stream_mode="updates"):
|
||||
|
||||
```python
|
||||
import time
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.func import entrypoint, task
|
||||
from langgraph.types import StreamWriter
|
||||
|
||||
@@ -414,7 +414,7 @@ def get_info():
|
||||
return "OK"
|
||||
|
||||
# Initialize an in-memory checkpointer for persistence
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
@task
|
||||
def slow_task():
|
||||
@@ -504,9 +504,9 @@ def step_3(input_query):
|
||||
We can now compose these tasks in an [entrypoint](../concepts/functional_api.md#entrypoint):
|
||||
|
||||
```python
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
|
||||
@entrypoint(checkpointer=checkpointer)
|
||||
@@ -577,12 +577,12 @@ def review_tool_call(tool_call: ToolCall) -> Union[ToolCall, ToolMessage]:
|
||||
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.checkpoint.memory import InMemorySaver
|
||||
from langgraph.graph.message import add_messages
|
||||
from langgraph.types import Command, interrupt
|
||||
|
||||
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
|
||||
@entrypoint(checkpointer=checkpointer)
|
||||
@@ -757,9 +757,9 @@ Use `entrypoint.final` to decouple what is returned to the caller from what is p
|
||||
```python
|
||||
from typing import Optional
|
||||
from langgraph.func import entrypoint
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
@entrypoint(checkpointer=checkpointer)
|
||||
def accumulate(n: int, *, previous: Optional[int]) -> entrypoint.final[int, int]:
|
||||
@@ -777,14 +777,14 @@ print(accumulate.invoke(3, config=config)) # 3
|
||||
|
||||
### Chatbot example
|
||||
|
||||
An example of a simple chatbot using the functional API and the `MemorySaver` checkpointer.
|
||||
An example of a simple chatbot using the functional API and the `InMemorySaver` checkpointer.
|
||||
The bot is able to remember the previous conversation and continue from where it left off.
|
||||
|
||||
```python
|
||||
from langchain_core.messages import BaseMessage
|
||||
from langgraph.graph import add_messages
|
||||
from langgraph.func import entrypoint, task
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
|
||||
model = ChatAnthropic(model="claude-3-5-sonnet-latest")
|
||||
@@ -794,7 +794,7 @@ def call_model(messages: list[BaseMessage]):
|
||||
response = model.invoke(messages)
|
||||
return response
|
||||
|
||||
checkpointer = MemorySaver()
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
@entrypoint(checkpointer=checkpointer)
|
||||
def workflow(inputs: list[BaseMessage], *, previous: list[BaseMessage]):
|
||||
|
||||
+1
-1
@@ -28,4 +28,4 @@ title: LangGraph
|
||||
}
|
||||
</style>
|
||||
|
||||
{!../README.md!}
|
||||
{% include-markdown "../../README.md" %}
|
||||
@@ -2,5 +2,6 @@
|
||||
options:
|
||||
members:
|
||||
- TAG_HIDDEN
|
||||
- TAG_NOSTREAM
|
||||
- START
|
||||
- END
|
||||
- END
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Runtime
|
||||
|
||||
::: langgraph.runtime.Runtime
|
||||
options:
|
||||
show_root_heading: true
|
||||
show_root_full_path: false
|
||||
members:
|
||||
- context
|
||||
- store
|
||||
- stream_writer
|
||||
- previous
|
||||
|
||||
::: langgraph.runtime
|
||||
options:
|
||||
members:
|
||||
- get_runtime
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
=== "OpenAI"
|
||||
|
||||
```shell
|
||||
pip install -U "langchain[openai]"
|
||||
```
|
||||
```python
|
||||
import os
|
||||
from langchain.chat_models import init_chat_model
|
||||
|
||||
os.environ["OPENAI_API_KEY"] = "sk-..."
|
||||
|
||||
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
|
||||
import os
|
||||
from langchain.chat_models import init_chat_model
|
||||
|
||||
os.environ["ANTHROPIC_API_KEY"] = "sk-..."
|
||||
|
||||
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
|
||||
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"
|
||||
|
||||
llm = init_chat_model(
|
||||
"azure_openai:gpt-4.1",
|
||||
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
|
||||
import os
|
||||
from langchain.chat_models import init_chat_model
|
||||
|
||||
os.environ["GOOGLE_API_KEY"] = "..."
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
llm = init_chat_model(
|
||||
"anthropic.claude-3-5-sonnet-20240620-v1:0",
|
||||
model_provider="bedrock_converse",
|
||||
)
|
||||
```
|
||||
|
||||
👉 Read the [AWS Bedrock integration docs](https://python.langchain.com/docs/integrations/chat/bedrock/)
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* This file is used to override the navigation title for the LangGraph documentation.
|
||||
* It is used to change the title of the first and second items in the navigation menu.
|
||||
* The first item is the Guides page, and the second item is the Reference page.
|
||||
*/
|
||||
|
||||
.md-nav--primary > .md-nav__list > .md-nav__item:nth-child(1) > .md-nav__link .md-ellipsis {
|
||||
visibility: hidden !important;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.md-nav--primary > .md-nav__list > .md-nav__item:nth-child(1) > .md-nav__link .md-ellipsis::after {
|
||||
content: "Home";
|
||||
visibility: visible;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.md-nav--primary > .md-nav__list > .md-nav__item:nth-child(2) > .md-nav__link .md-ellipsis {
|
||||
visibility: hidden !important;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.md-nav--primary > .md-nav__list > .md-nav__item:nth-child(2) > .md-nav__link .md-ellipsis::after {
|
||||
content: "Home";
|
||||
visibility: visible;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
}
|
||||
@@ -256,7 +256,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
"from langgraph.graph import StateGraph, START\n",
|
||||
"from langgraph.graph.message import add_messages\n",
|
||||
"from typing import Annotated\n",
|
||||
@@ -267,7 +267,7 @@
|
||||
" messages: Annotated[list, add_messages]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"memory = InMemorySaver()\n",
|
||||
"workflow = StateGraph(State)\n",
|
||||
"workflow.add_node(\"info\", info_chain)\n",
|
||||
"workflow.add_node(\"prompt\", prompt_gen_chain)\n",
|
||||
|
||||
@@ -1124,7 +1124,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
"from langgraph.graph import END, StateGraph, START\n",
|
||||
"from langgraph.prebuilt import tools_condition\n",
|
||||
"\n",
|
||||
@@ -1144,7 +1144,7 @@
|
||||
"\n",
|
||||
"# The checkpointer lets the graph persist its state\n",
|
||||
"# this is a complete memory for the entire graph.\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"memory = InMemorySaver()\n",
|
||||
"part_1_graph = builder.compile(checkpointer=memory)"
|
||||
]
|
||||
},
|
||||
@@ -1943,7 +1943,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"from langgraph.prebuilt import tools_condition\n",
|
||||
"\n",
|
||||
@@ -1967,7 +1967,7 @@
|
||||
")\n",
|
||||
"builder.add_edge(\"tools\", \"assistant\")\n",
|
||||
"\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"memory = InMemorySaver()\n",
|
||||
"part_2_graph = builder.compile(\n",
|
||||
" checkpointer=memory,\n",
|
||||
" # NEW: The graph will always halt before executing the \"tools\" node.\n",
|
||||
@@ -2532,7 +2532,7 @@
|
||||
"source": [
|
||||
"from typing import Literal\n",
|
||||
"\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"from langgraph.prebuilt import tools_condition\n",
|
||||
"\n",
|
||||
@@ -2576,7 +2576,7 @@
|
||||
"builder.add_edge(\"safe_tools\", \"assistant\")\n",
|
||||
"builder.add_edge(\"sensitive_tools\", \"assistant\")\n",
|
||||
"\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"memory = InMemorySaver()\n",
|
||||
"part_3_graph = builder.compile(\n",
|
||||
" checkpointer=memory,\n",
|
||||
" # NEW: The graph will always halt before executing the \"tools\" node.\n",
|
||||
@@ -3477,7 +3477,7 @@
|
||||
"source": [
|
||||
"from typing import Literal\n",
|
||||
"\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"from langgraph.prebuilt import tools_condition\n",
|
||||
"\n",
|
||||
@@ -3841,7 +3841,7 @@
|
||||
"builder.add_conditional_edges(\"fetch_user_info\", route_to_workflow)\n",
|
||||
"\n",
|
||||
"# Compile graph\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"memory = InMemorySaver()\n",
|
||||
"part_4_graph = builder.compile(\n",
|
||||
" checkpointer=memory,\n",
|
||||
" # Let the user approve or deny the use of sensitive tools\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. Let’s 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,45 @@ 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).
|
||||
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 +59,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,20 +80,39 @@ 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:
|
||||
|
||||
@@ -73,9 +126,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 +158,133 @@ 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 `CompiledStateGraph` we can invoke on our state.
|
||||
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 +297,35 @@ 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("basic-chatbot.png", imageBuffer);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||

|
||||
|
||||
## 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,15 +348,90 @@ while True:
|
||||
break
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { HumanMessage } from "@langchain/core/messages";
|
||||
|
||||
async function streamGraphUpdates(userInput: string) {
|
||||
const stream = await graph.stream({
|
||||
messages: [new HumanMessage(userInput)],
|
||||
});
|
||||
|
||||
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) {
|
||||
for (const value of Object.values(event)) {
|
||||
console.log(
|
||||
"Assistant:",
|
||||
value.messages[value.messages.length - 1].content
|
||||
);
|
||||
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.
|
||||
```
|
||||
|
||||
:::python
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
|
||||
@@ -207,8 +465,44 @@ graph_builder.add_edge("chatbot", END)
|
||||
graph = graph_builder.compile()
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { Annotation } from "@langchain/langgraph";
|
||||
import { StateGraph, START, END } from "@langchain/langgraph";
|
||||
import { BaseMessage, HumanMessage } from "@langchain/core/messages";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
|
||||
const State = Annotation.Root({
|
||||
messages: Annotation<BaseMessage[]>({
|
||||
reducer: (x, y) => x.concat(y),
|
||||
}),
|
||||
});
|
||||
|
||||
const graphBuilder = new StateGraph(State);
|
||||
|
||||
const llm = new ChatOpenAI({
|
||||
model: "gpt-4o",
|
||||
temperature: 0,
|
||||
});
|
||||
|
||||
const chatbot = async (state: typeof State.State) => {
|
||||
return { messages: [await llm.invoke(state.messages)] };
|
||||
};
|
||||
|
||||
// The first argument is the unique node name
|
||||
// The second argument is the function or object that will be called whenever
|
||||
// the node is used.
|
||||
graphBuilder.addNode("chatbot", chatbot);
|
||||
graphBuilder.addEdge(START, "chatbot");
|
||||
graphBuilder.addEdge("chatbot", END);
|
||||
const graph = graphBuilder.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.
|
||||
|
||||
|
||||
|
||||
@@ -10,24 +10,65 @@ 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
|
||||
def _set_env(var: str):
|
||||
if not os.environ.get(var):
|
||||
os.environ[var] = getpass.getpass(f"{var}: ")
|
||||
:::python
|
||||
|
||||
```bash
|
||||
_set_env("TAVILY_API_KEY")
|
||||
```
|
||||
|
||||
@@ -35,10 +76,22 @@ _set_env("TAVILY_API_KEY")
|
||||
os.environ["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
|
||||
|
||||
@@ -47,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,
|
||||
@@ -67,9 +137,47 @@ 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 Beginner’s 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:
|
||||
|
||||
@@ -83,8 +191,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
|
||||
|
||||
@@ -108,9 +230,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
|
||||
@@ -152,16 +296,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,
|
||||
@@ -201,10 +409,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
|
||||
@@ -217,12 +476,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);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||

|
||||
|
||||
## 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}]}):
|
||||
@@ -245,7 +523,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:
|
||||
@@ -276,18 +554,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!}
|
||||
{% include-markdown "../../../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
|
||||
@@ -327,7 +686,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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -10,47 +10,83 @@ We will see later that **checkpointing** is _much_ more powerful than simple cha
|
||||
|
||||
This tutorial builds on [Add tools](./2-add-tools.md).
|
||||
|
||||
## 1. Create a `MemorySaver` checkpointer
|
||||
## 1. Create a `InMemorySaver` checkpointer
|
||||
|
||||
Create a `MemorySaver` checkpointer:
|
||||
Create a `InMemorySaver` checkpointer:
|
||||
|
||||
``` python
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
:::python
|
||||
|
||||
memory = MemorySaver()
|
||||
```python
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
memory = InMemorySaver()
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::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,13 +268,95 @@ 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:
|
||||
|
||||
{!snippets/chat_model_tabs.md!}
|
||||
:::python
|
||||
|
||||
{% include-markdown "../../../snippets/chat_model_tabs.md" %}
|
||||
|
||||
<!---
|
||||
```python
|
||||
@@ -172,7 +374,7 @@ from langchain_tavily import TavilySearch
|
||||
from langchain_core.messages import BaseMessage
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.graph import StateGraph
|
||||
from langgraph.graph.message import add_messages
|
||||
from langgraph.prebuilt import ToolNode, tools_condition
|
||||
@@ -200,10 +402,47 @@ graph_builder.add_conditional_edges(
|
||||
)
|
||||
graph_builder.add_edge("tools", "chatbot")
|
||||
graph_builder.set_entry_point("chatbot")
|
||||
memory = MemorySaver()
|
||||
memory = InMemorySaver()
|
||||
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,7 +22,8 @@ Starting with the existing code from the [Add memory to the chatbot](./3-add-mem
|
||||
|
||||
Let's first select a chat model:
|
||||
|
||||
{!snippets/chat_model_tabs.md!}
|
||||
:::python
|
||||
{% include-markdown "../../../snippets/chat_model_tabs.md" %}
|
||||
|
||||
<!---
|
||||
```python
|
||||
@@ -24,16 +33,31 @@ 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"
|
||||
|
||||
```python hl_lines="12 19 20 21 22 23"
|
||||
from typing import Annotated
|
||||
|
||||
from langchain_tavily import TavilySearch
|
||||
from langchain_core.tools import tool
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
from langgraph.graph.message import add_messages
|
||||
from langgraph.prebuilt import ToolNode, tools_condition
|
||||
@@ -74,7 +98,103 @@ graph_builder.add_conditional_edges(
|
||||
)
|
||||
graph_builder.add_edge("tools", "chatbot")
|
||||
graph_builder.add_edge(START, "chatbot")
|
||||
```
|
||||
````
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
````typescript hl_lines="12 19 20 21 22 23"
|
||||
import { TavilySearchResults } from "@langchain/community/tools/tavily_search";
|
||||
|
||||
```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";
|
||||
|
||||
import { MemorySaver } from "@langchain/langgraph";
|
||||
import {
|
||||
StateGraph,
|
||||
START,
|
||||
END,
|
||||
MessagesAnnotation,
|
||||
} from "@langchain/langgraph";
|
||||
import { ToolNode } from "@langchain/langgraph/prebuilt";
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
|
||||
import { Command, interrupt } from "@langchain/langgraph";
|
||||
|
||||
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 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] };
|
||||
}
|
||||
|
||||
const graphBuilder = new StateGraph(MessagesAnnotation).addNode(
|
||||
"chatbot",
|
||||
chatbot
|
||||
);
|
||||
|
||||
const toolNode = new ToolNode(tools);
|
||||
graphBuilder.addNode("tools", toolNode);
|
||||
|
||||
const shouldContinue = (state: typeof MessagesAnnotation.State) => {
|
||||
const messages = state.messages;
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
if ("tool_calls" in lastMessage && lastMessage.tool_calls?.length) {
|
||||
return "tools";
|
||||
}
|
||||
return END;
|
||||
};
|
||||
|
||||
graphBuilder.addConditionalEdges("chatbot", shouldContinue);
|
||||
graphBuilder.addEdge("tools", "chatbot");
|
||||
graphBuilder.addEdge(START, "chatbot");
|
||||
````
|
||||
|
||||
:::
|
||||
|
||||
!!! tip
|
||||
|
||||
@@ -84,17 +204,39 @@ graph_builder.add_edge(START, "chatbot")
|
||||
|
||||
We compile the graph with a checkpointer, as before:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
memory = MemorySaver()
|
||||
memory = InMemorySaver()
|
||||
|
||||
graph = graph_builder.compile(checkpointer=memory)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
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 +246,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);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||

|
||||
|
||||
## 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 +298,60 @@ 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
|
||||
const userInput =
|
||||
"I need some expert guidance for building an AI agent. Could you request assistance for me?";
|
||||
const config = {
|
||||
configurable: { thread_id: "1" },
|
||||
streamMode: "values" as const,
|
||||
};
|
||||
|
||||
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 +361,28 @@ snapshot.next
|
||||
('tools',)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
const snapshot = await graph.getState({ configurable: { thread_id: "1" } });
|
||||
snapshot.next;
|
||||
```
|
||||
|
||||
```json
|
||||
["tools"]
|
||||
```
|
||||
|
||||
['tools']
|
||||
|
||||
````
|
||||
:::
|
||||
|
||||
!!! info Additional information
|
||||
|
||||
:::python
|
||||
|
||||
Take a closer look at the `human_assistance` tool:
|
||||
|
||||
```python
|
||||
@@ -162,12 +394,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."
|
||||
@@ -179,7 +439,7 @@ events = graph.stream(human_command, config, stream_mode="values")
|
||||
for event in events:
|
||||
if "messages" in event:
|
||||
event["messages"][-1].pretty_print()
|
||||
```
|
||||
````
|
||||
|
||||
```
|
||||
================================== Ai Message ==================================
|
||||
@@ -215,12 +475,57 @@ 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
|
||||
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
|
||||
@@ -230,7 +535,7 @@ from langchain_tavily import TavilySearch
|
||||
from langchain_core.tools import tool
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
from langgraph.graph.message import add_messages
|
||||
from langgraph.prebuilt import ToolNode, tools_condition
|
||||
@@ -268,10 +573,98 @@ graph_builder.add_conditional_edges(
|
||||
graph_builder.add_edge("tools", "chatbot")
|
||||
graph_builder.add_edge(START, "chatbot")
|
||||
|
||||
memory = MemorySaver()
|
||||
memory = InMemorySaver()
|
||||
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 graphBuilder = new StateGraph(MessagesAnnotation).addNode(
|
||||
"chatbot",
|
||||
chatbot
|
||||
);
|
||||
|
||||
const toolNode = new ToolNode(tools);
|
||||
graphBuilder.addNode("tools", toolNode);
|
||||
|
||||
const shouldContinue = (state: typeof MessagesAnnotation.State) => {
|
||||
const messages = state.messages;
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
if ("tool_calls" in lastMessage && lastMessage.tool_calls?.length) {
|
||||
return "tools";
|
||||
}
|
||||
return END;
|
||||
};
|
||||
|
||||
graphBuilder.addConditionalEdges("chatbot", shouldContinue);
|
||||
graphBuilder.addEdge("tools", "chatbot");
|
||||
graphBuilder.addEdge(START, "chatbot");
|
||||
|
||||
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,13 +454,36 @@ snapshot = graph.get_state(config)
|
||||
{'name': 'LangGraph (library)', 'birthday': 'Jan 17, 2024'}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::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:
|
||||
|
||||
{!snippets/chat_model_tabs.md!}
|
||||
:::python
|
||||
|
||||
{% include-markdown "../../../snippets/chat_model_tabs.md" %}
|
||||
|
||||
<!---
|
||||
```python
|
||||
@@ -239,7 +501,7 @@ from langchain_core.messages import ToolMessage
|
||||
from langchain_core.tools import InjectedToolCallId, tool
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
from langgraph.graph.message import add_messages
|
||||
from langgraph.prebuilt import ToolNode, tools_condition
|
||||
@@ -301,11 +563,115 @@ graph_builder.add_conditional_edges(
|
||||
graph_builder.add_edge("tools", "chatbot")
|
||||
graph_builder.add_edge(START, "chatbot")
|
||||
|
||||
memory = MemorySaver()
|
||||
memory = InMemorySaver()
|
||||
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).
|
||||
|
||||
@@ -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,9 +12,17 @@ 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.
|
||||
:::
|
||||
|
||||
{!snippets/chat_model_tabs.md!}
|
||||
:::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
|
||||
|
||||
{% include-markdown "../../../snippets/chat_model_tabs.md" %}
|
||||
|
||||
<!---
|
||||
```python
|
||||
@@ -31,7 +39,7 @@ from langchain_tavily import TavilySearch
|
||||
from langchain_core.messages import BaseMessage
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
from langgraph.graph.message import add_messages
|
||||
from langgraph.prebuilt import ToolNode, tools_condition
|
||||
@@ -60,15 +68,53 @@ graph_builder.add_conditional_edges(
|
||||
graph_builder.add_edge("tools", "chatbot")
|
||||
graph_builder.add_edge(START, "chatbot")
|
||||
|
||||
memory = MemorySaver()
|
||||
memory = InMemorySaver()
|
||||
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 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."}]
|
||||
[{"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 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."}]
|
||||
[{"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.
|
||||
|
||||
@@ -12,9 +12,9 @@ Before you begin, ensure you have the following:
|
||||
|
||||
=== "Python server"
|
||||
|
||||
```shell
|
||||
# Python >= 3.11 is required.
|
||||
Python >= 3.11 is required.
|
||||
|
||||
```shell
|
||||
pip install --upgrade "langgraph-cli[inmem]"
|
||||
```
|
||||
|
||||
|
||||
@@ -540,11 +540,11 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"================================\u001B[1m System Message \u001B[0m================================\n",
|
||||
"================================\u001b[1m System Message \u001b[0m================================\n",
|
||||
"\n",
|
||||
"Given a user query, create a plan to solve it with the utmost parallelizability. Each plan should comprise an action from the following \u001B[33;1m\u001B[1;3m{num_tools}\u001B[0m types:\n",
|
||||
"\u001B[33;1m\u001B[1;3m{tool_descriptions}\u001B[0m\n",
|
||||
"\u001B[33;1m\u001B[1;3m{num_tools}\u001B[0m. join(): Collects and combines results from prior actions.\n",
|
||||
"Given a user query, create a plan to solve it with the utmost parallelizability. Each plan should comprise an action from the following \u001b[33;1m\u001b[1;3m{num_tools}\u001b[0m types:\n",
|
||||
"\u001b[33;1m\u001b[1;3m{tool_descriptions}\u001b[0m\n",
|
||||
"\u001b[33;1m\u001b[1;3m{num_tools}\u001b[0m. join(): Collects and combines results from prior actions.\n",
|
||||
"\n",
|
||||
" - An LLM agent is called upon invoking join() to either finalize the user query or wait until the plans are executed.\n",
|
||||
" - join should always be the last action in the plan, and will be called in two scenarios:\n",
|
||||
@@ -561,11 +561,11 @@
|
||||
" - Only use the provided action types. If a query cannot be addressed using these, invoke the join action for the next steps.\n",
|
||||
" - Never introduce new actions other than the ones provided.\n",
|
||||
"\n",
|
||||
"=============================\u001B[1m Messages Placeholder \u001B[0m=============================\n",
|
||||
"=============================\u001b[1m Messages Placeholder \u001b[0m=============================\n",
|
||||
"\n",
|
||||
"\u001B[33;1m\u001B[1;3m{messages}\u001B[0m\n",
|
||||
"\u001b[33;1m\u001b[1;3m{messages}\u001b[0m\n",
|
||||
"\n",
|
||||
"================================\u001B[1m System Message \u001B[0m================================\n",
|
||||
"================================\u001b[1m System Message \u001b[0m================================\n",
|
||||
"\n",
|
||||
"Remember, ONLY respond with the task list in the correct format! E.g.:\n",
|
||||
"idx. tool(arg_name=args)\n",
|
||||
@@ -605,7 +605,7 @@
|
||||
" llm: BaseChatModel, tools: Sequence[BaseTool], base_prompt: ChatPromptTemplate\n",
|
||||
"):\n",
|
||||
" tool_descriptions = \"\\n\".join(\n",
|
||||
" f\"{i+1}. {tool.description}\\n\"\n",
|
||||
" f\"{i + 1}. {tool.description}\\n\"\n",
|
||||
" for i, tool in enumerate(\n",
|
||||
" tools\n",
|
||||
" ) # +1 to offset the 0 starting index, we want it count normally from 1.\n",
|
||||
|
||||
@@ -378,7 +378,7 @@
|
||||
"\n",
|
||||
"async def execute_step(state: PlanExecute):\n",
|
||||
" plan = state[\"plan\"]\n",
|
||||
" plan_str = \"\\n\".join(f\"{i+1}. {step}\" for i, step in enumerate(plan))\n",
|
||||
" plan_str = \"\\n\".join(f\"{i + 1}. {step}\" for i, step in enumerate(plan))\n",
|
||||
" task = plan[0]\n",
|
||||
" task_formatted = f\"\"\"For the following plan:\n",
|
||||
"{plan_str}\\n\\nYou are tasked with executing step {1}, {task}.\"\"\"\n",
|
||||
|
||||
@@ -322,7 +322,7 @@
|
||||
"from typing import Annotated, List, Sequence\n",
|
||||
"from langgraph.graph import END, StateGraph, START\n",
|
||||
"from langgraph.graph.message import add_messages\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -361,7 +361,7 @@
|
||||
"\n",
|
||||
"builder.add_conditional_edges(\"generate\", should_continue)\n",
|
||||
"builder.add_edge(\"reflect\", \"generate\")\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"memory = InMemorySaver()\n",
|
||||
"graph = builder.compile(checkpointer=memory)"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -302,7 +302,7 @@
|
||||
"def format_docs(docs: List[Doc]) -> str:\n",
|
||||
" xml_table = \"<conversations>\\n\"\n",
|
||||
" for doc in docs:\n",
|
||||
" xml_table += f'<conv_summ id={doc[\"id\"]}>{doc[\"summary\"]}</conv_summ>\\n'\n",
|
||||
" xml_table += f\"<conv_summ id={doc['id']}>{doc['summary']}</conv_summ>\\n\"\n",
|
||||
" xml_table += \"</conversations>\"\n",
|
||||
" return xml_table\n",
|
||||
"\n",
|
||||
@@ -311,9 +311,9 @@
|
||||
" xml = \"<cluster_table>\\n\"\n",
|
||||
" for label in clusters:\n",
|
||||
" xml += \" <cluster>\\n\"\n",
|
||||
" xml += f' <id>{label[\"id\"]}</id>\\n'\n",
|
||||
" xml += f' <name>{label[\"name\"]}</name>\\n'\n",
|
||||
" xml += f' <description>{label[\"description\"]}</description>\\n'\n",
|
||||
" xml += f\" <id>{label['id']}</id>\\n\"\n",
|
||||
" xml += f\" <name>{label['name']}</name>\\n\"\n",
|
||||
" xml += f\" <description>{label['description']}</description>\\n\"\n",
|
||||
" xml += \" </cluster>\\n\"\n",
|
||||
" xml += \"</cluster_table>\"\n",
|
||||
" return xml\n",
|
||||
@@ -600,13 +600,13 @@
|
||||
" turns.append(\n",
|
||||
" f\"\"\"\n",
|
||||
"<human idx={idx}>\n",
|
||||
"{run.inputs['question']}\n",
|
||||
"{run.inputs[\"question\"]}\n",
|
||||
"</human>\"\"\"\n",
|
||||
" )\n",
|
||||
" if run.outputs and run.outputs[\"output\"]:\n",
|
||||
" turns.append(\n",
|
||||
" f\"\"\"<ai idx={idx+1}>\n",
|
||||
"{run.outputs['output']}\n",
|
||||
" f\"\"\"<ai idx={idx + 1}>\n",
|
||||
"{run.outputs[\"output\"]}\n",
|
||||
"</ai>\"\"\"\n",
|
||||
" )\n",
|
||||
" return {\n",
|
||||
|
||||
@@ -272,7 +272,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
@@ -280,10 +280,10 @@
|
||||
"from typing import Optional, Dict, Any\n",
|
||||
"from typing_extensions import Annotated, TypedDict\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"from langgraph.runtime import Runtime\n",
|
||||
"\n",
|
||||
"from langchain_core.runnables import RunnableConfig\n",
|
||||
"from langgraph.constants import Send\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
"from langgraph.types import Send\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def update_candidates(\n",
|
||||
@@ -307,22 +307,27 @@
|
||||
" depth: Annotated[int, operator.add]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class Configuration(TypedDict, total=False):\n",
|
||||
"class Context(TypedDict, total=False):\n",
|
||||
" max_depth: int\n",
|
||||
" threshold: float\n",
|
||||
" k: int\n",
|
||||
" beam_size: int\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _ensure_configurable(config: RunnableConfig) -> Configuration:\n",
|
||||
"class EnsuredContext(TypedDict):\n",
|
||||
" max_depth: int\n",
|
||||
" threshold: float\n",
|
||||
" k: int\n",
|
||||
" beam_size: int\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _ensure_context(ctx: Context) -> EnsuredContext:\n",
|
||||
" \"\"\"Get params that configure the search algorithm.\"\"\"\n",
|
||||
" configurable = config.get(\"configurable\", {})\n",
|
||||
" return {\n",
|
||||
" **configurable,\n",
|
||||
" \"max_depth\": configurable.get(\"max_depth\", 10),\n",
|
||||
" \"threshold\": config.get(\"threshold\", 0.9),\n",
|
||||
" \"k\": configurable.get(\"k\", 5),\n",
|
||||
" \"beam_size\": configurable.get(\"beam_size\", 3),\n",
|
||||
" \"max_depth\": ctx.get(\"max_depth\", 10),\n",
|
||||
" \"threshold\": ctx.get(\"threshold\", 0.9),\n",
|
||||
" \"k\": ctx.get(\"k\", 5),\n",
|
||||
" \"beam_size\": ctx.get(\"beam_size\", 3),\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -330,9 +335,11 @@
|
||||
" seed: Optional[Candidate]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def expand(state: ExpansionState, *, config: RunnableConfig) -> Dict[str, List[str]]:\n",
|
||||
"def expand(\n",
|
||||
" state: ExpansionState, *, runtime: Runtime[Context]\n",
|
||||
") -> Dict[str, List[Candidate]]:\n",
|
||||
" \"\"\"Generate the next state.\"\"\"\n",
|
||||
" configurable = _ensure_configurable(config)\n",
|
||||
" ctx = _ensure_context(runtime.context)\n",
|
||||
" if not state.get(\"seed\"):\n",
|
||||
" candidate_str = \"\"\n",
|
||||
" else:\n",
|
||||
@@ -342,9 +349,8 @@
|
||||
" {\n",
|
||||
" \"problem\": state[\"problem\"],\n",
|
||||
" \"candidate\": candidate_str,\n",
|
||||
" \"k\": configurable[\"k\"],\n",
|
||||
" \"k\": ctx[\"k\"],\n",
|
||||
" },\n",
|
||||
" config=config,\n",
|
||||
" )\n",
|
||||
" except Exception:\n",
|
||||
" return {\"candidates\": []}\n",
|
||||
@@ -354,7 +360,7 @@
|
||||
" return {\"candidates\": new_candidates}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def score(state: ToTState) -> Dict[str, List[float]]:\n",
|
||||
"def score(state: ToTState) -> Dict[str, Any]:\n",
|
||||
" \"\"\"Evaluate the candidate generations.\"\"\"\n",
|
||||
" candidates = state[\"candidates\"]\n",
|
||||
" scored = []\n",
|
||||
@@ -363,11 +369,9 @@
|
||||
" return {\"scored_candidates\": scored, \"candidates\": \"clear\"}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def prune(\n",
|
||||
" state: ToTState, *, config: RunnableConfig\n",
|
||||
") -> Dict[str, List[Dict[str, Any]]]:\n",
|
||||
"def prune(state: ToTState, *, runtime: Runtime[Context]) -> Dict[str, Any]:\n",
|
||||
" scored_candidates = state[\"scored_candidates\"]\n",
|
||||
" beam_size = _ensure_configurable(config)[\"beam_size\"]\n",
|
||||
" beam_size = _ensure_context(runtime.context)[\"beam_size\"]\n",
|
||||
" organized = sorted(\n",
|
||||
" scored_candidates, key=lambda candidate: candidate[1], reverse=True\n",
|
||||
" )\n",
|
||||
@@ -383,11 +387,11 @@
|
||||
"\n",
|
||||
"\n",
|
||||
"def should_terminate(\n",
|
||||
" state: ToTState, config: RunnableConfig\n",
|
||||
" state: ToTState, runtime: Runtime[Context]\n",
|
||||
") -> Union[Literal[\"__end__\"], Send]:\n",
|
||||
" configurable = _ensure_configurable(config)\n",
|
||||
" solved = state[\"candidates\"][0].score >= configurable[\"threshold\"]\n",
|
||||
" if solved or state[\"depth\"] >= configurable[\"max_depth\"]:\n",
|
||||
" ctx = _ensure_context(runtime.context)\n",
|
||||
" solved = state[\"candidates\"][0].score >= ctx[\"threshold\"]\n",
|
||||
" if solved or state[\"depth\"] >= ctx[\"max_depth\"]:\n",
|
||||
" return \"__end__\"\n",
|
||||
" return [\n",
|
||||
" Send(\"expand\", {**state, \"somevalseed\": candidate})\n",
|
||||
@@ -396,7 +400,7 @@
|
||||
"\n",
|
||||
"\n",
|
||||
"# Create the graph\n",
|
||||
"builder = StateGraph(state_schema=ToTState, config_schema=Configuration)\n",
|
||||
"builder = StateGraph(state_schema=ToTState, context_schema=Context)\n",
|
||||
"\n",
|
||||
"# Add nodes\n",
|
||||
"builder.add_node(expand)\n",
|
||||
@@ -412,7 +416,7 @@
|
||||
"builder.add_edge(\"__start__\", \"expand\")\n",
|
||||
"\n",
|
||||
"# Compile the graph\n",
|
||||
"graph = builder.compile(checkpointer=MemorySaver())"
|
||||
"graph = builder.compile(checkpointer=InMemorySaver())"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -467,13 +471,11 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"config = {\n",
|
||||
" \"configurable\": {\n",
|
||||
" \"thread_id\": \"test_1\",\n",
|
||||
" \"depth\": 10,\n",
|
||||
" }\n",
|
||||
"}\n",
|
||||
"for step in graph.stream({\"problem\": puzzles[42]}, config):\n",
|
||||
"for step in graph.stream(\n",
|
||||
" {\"problem\": puzzles[42]},\n",
|
||||
" config={\"configurable\": {\"thread_id\": \"test_1\"}},\n",
|
||||
" context={\"depth\": 10},\n",
|
||||
"):\n",
|
||||
" print(step)"
|
||||
]
|
||||
},
|
||||
@@ -491,7 +493,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"final_state = graph.get_state(config)\n",
|
||||
"final_state = graph.get_state({\"configurable\": {\"thread_id\": \"test_1\"}})\n",
|
||||
"winning_solution = final_state.values[\"candidates\"][0]\n",
|
||||
"search_depth = final_state.values[\"depth\"]\n",
|
||||
"if winning_solution[1] == 1:\n",
|
||||
|
||||
@@ -1029,7 +1029,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
"from langgraph.graph import END, StateGraph, START\n",
|
||||
"\n",
|
||||
"builder = StateGraph(State)\n",
|
||||
@@ -1053,7 +1053,7 @@
|
||||
"builder.add_conditional_edges(\"evaluate\", control_edge, {END: END, \"solve\": \"solve\"})\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"checkpointer = MemorySaver()\n",
|
||||
"checkpointer = InMemorySaver()\n",
|
||||
"graph = builder.compile(checkpointer=checkpointer)"
|
||||
]
|
||||
},
|
||||
@@ -1327,7 +1327,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# This is all the same as before\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
"from langgraph.graph import END, StateGraph, START\n",
|
||||
"\n",
|
||||
"builder = StateGraph(State)\n",
|
||||
@@ -1353,7 +1353,7 @@
|
||||
"\n",
|
||||
"\n",
|
||||
"builder.add_conditional_edges(\"evaluate\", control_edge, {END: END, \"solve\": \"solve\"})\n",
|
||||
"checkpointer = MemorySaver()"
|
||||
"checkpointer = InMemorySaver()"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
+11
-12
@@ -54,6 +54,7 @@ plugins:
|
||||
separator: '[\s\u200b\-,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;'
|
||||
- autorefs
|
||||
- tags
|
||||
- include-markdown
|
||||
- mkdocstrings:
|
||||
custom_templates: templates
|
||||
handlers:
|
||||
@@ -102,14 +103,15 @@ nav:
|
||||
- 5. Customize state: tutorials/get-started/5-customize-state.md
|
||||
- 6. Time travel: tutorials/get-started/6-time-travel.md
|
||||
- Run a local server: tutorials/langgraph-platform/local-server.md
|
||||
- Agent development:
|
||||
- General concepts:
|
||||
- Workflows & agents: tutorials/workflows.md
|
||||
- Prebuilt components: agents/overview.md
|
||||
- Run an agent: agents/run_agents.md
|
||||
- Agent architectures: concepts/agentic_concepts.md
|
||||
|
||||
- Guides:
|
||||
- guides/index.md
|
||||
- Agent development:
|
||||
- Overview: agents/overview.md
|
||||
- Run an agent: agents/run_agents.md
|
||||
- LangGraph APIs:
|
||||
- Graph API:
|
||||
- Overview: concepts/low_level.md
|
||||
@@ -141,10 +143,6 @@ nav:
|
||||
- Overview: concepts/human_in_the_loop.md
|
||||
- Add human intervention: how-tos/human_in_the_loop/add-human-in-the-loop.md
|
||||
- Use Server API: cloud/how-tos/add-human-in-the-loop.md
|
||||
- Breakpoints:
|
||||
- Overview: concepts/breakpoints.md
|
||||
- Set breakpoints: how-tos/human_in_the_loop/breakpoints.md
|
||||
- Use Server API: cloud/how-tos/human_in_the_loop_breakpoint.md
|
||||
- Time travel:
|
||||
- Overview: concepts/time-travel.md
|
||||
- Use time travel: how-tos/human_in_the_loop/time-travel.md
|
||||
@@ -160,8 +158,10 @@ nav:
|
||||
- Overview: concepts/mcp.md
|
||||
- Use MCP: agents/mcp.md
|
||||
- Server API: concepts/server-mcp.md
|
||||
- Evaluation:
|
||||
- Basic implementation: agents/evals.md
|
||||
- Tracing:
|
||||
- Overview: concepts/tracing.md
|
||||
- Enable tracing: how-tos/enable-tracing.md
|
||||
- Evaluate performance: agents/evals.md
|
||||
- Platform-only capabilities:
|
||||
- LangGraph Platform:
|
||||
- Overview: concepts/langgraph_platform.md
|
||||
@@ -250,6 +250,7 @@ nav:
|
||||
- Storage: reference/store.md
|
||||
- Caching: reference/cache.md
|
||||
- Types: reference/types.md
|
||||
- Runtime: reference/runtime.md
|
||||
- Config: reference/config.md
|
||||
- Errors: reference/errors.md
|
||||
- Constants: reference/constants.md
|
||||
@@ -261,6 +262,7 @@ nav:
|
||||
- MCP Adapters: reference/mcp.md
|
||||
- LangGraph Platform:
|
||||
- Server API: cloud/reference/api/api_ref.md
|
||||
- Server changelog: cloud/reference/langgraph_server_changelog.md
|
||||
- Control Plane API: cloud/reference/api/api_ref_control_plane.md
|
||||
- CLI: cloud/reference/cli.md
|
||||
- SDK (Python): cloud/reference/sdk/python_sdk_ref.md
|
||||
@@ -356,8 +358,6 @@ markdown_extensions:
|
||||
combine_header_slug: true
|
||||
- pymdownx.tasklist:
|
||||
custom_checkbox: true
|
||||
- markdown_include.include:
|
||||
base_path: ./
|
||||
- github-callouts
|
||||
hooks:
|
||||
- _scripts/notebook_hooks.py
|
||||
@@ -381,7 +381,6 @@ validation:
|
||||
copyright: >
|
||||
Copyright © 2025 LangChain, Inc | <a href="#__consent">Consent Preferences</a>
|
||||
extra_css:
|
||||
- stylesheets/navigation_title_ovverides.css
|
||||
- stylesheets/version_admonitions.css
|
||||
- stylesheets/logos.css
|
||||
- stylesheets/sticky_navigation.css
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/* Minimal CSS for copy page button */
|
||||
.copy-page-btn {
|
||||
background: transparent;
|
||||
border: 1px solid var(--md-default-fg-color--lightest);
|
||||
padding: 6px 12px;
|
||||
margin-right: 8px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: var(--md-default-fg-color);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.copy-page-btn:hover {
|
||||
background: var(--md-default-fg-color--lightest);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Simple copy page functionality - just copy the markdown content
|
||||
function copyPageAsMarkdown() {
|
||||
const markdownScript = document.getElementById('page-markdown-content');
|
||||
if (!markdownScript) {
|
||||
alert('Markdown content not available for this page');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = JSON.parse(markdownScript.textContent);
|
||||
const content = `# ${data.title}\n\nSource: ${window.location.href}\n\n${data.markdown}`;
|
||||
|
||||
navigator.clipboard.writeText(content).then(() => {
|
||||
// Simple notification
|
||||
const notification = document.createElement('div');
|
||||
notification.textContent = 'Page content copied to clipboard';
|
||||
notification.style.cssText = 'position:fixed;top:20px;right:20px;background:#4CAF50;color:white;padding:10px;border-radius:4px;z-index:9999;';
|
||||
document.body.appendChild(notification);
|
||||
setTimeout(() => notification.remove(), 3000);
|
||||
}).catch(() => {
|
||||
alert('Failed to copy content');
|
||||
});
|
||||
} catch (e) {
|
||||
alert('Failed to parse page content');
|
||||
}
|
||||
}
|
||||
|
||||
// Add button to header - simpler approach
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const headerSource = document.querySelector('.md-header__source');
|
||||
if (headerSource) {
|
||||
const button = document.createElement('button');
|
||||
button.textContent = 'Copy page';
|
||||
button.onclick = copyPageAsMarkdown;
|
||||
button.style.cssText = 'background:none;border:1px solid #ddd;padding:6px 12px;margin-right:8px;border-radius:4px;cursor:pointer;';
|
||||
headerSource.parentNode.insertBefore(button, headerSource);
|
||||
}
|
||||
});
|
||||
@@ -13,6 +13,130 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
|
||||
{% block extrahead %}
|
||||
<meta name="algolia-site-verification" content="165B7E7C89E49946" />
|
||||
<script>
|
||||
// Simple copy page functionality - uses original markdown source
|
||||
function copyPageAsMarkdown() {
|
||||
const markdownScript = document.getElementById('page-markdown-content');
|
||||
if (!markdownScript) {
|
||||
alert('Markdown content not available for this page');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let rawContent = markdownScript.textContent;
|
||||
|
||||
// Safe HTML entity decoding function
|
||||
function decodeHtmlEntities(text) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(text, 'text/html');
|
||||
return doc.documentElement.textContent || '';
|
||||
}
|
||||
|
||||
// Always decode HTML entities since the browser might encode them
|
||||
rawContent = decodeHtmlEntities(rawContent);
|
||||
|
||||
|
||||
const data = JSON.parse(rawContent);
|
||||
const content = `Source: ${window.location.href}\n\n${data.markdown}`;
|
||||
|
||||
navigator.clipboard.writeText(content).then(() => {
|
||||
// Simple notification
|
||||
const notification = document.createElement('div');
|
||||
notification.textContent = 'Page content copied to clipboard';
|
||||
notification.style.cssText = 'position:fixed;top:20px;right:20px;background:#4CAF50;color:white;padding:10px 16px;border-radius:4px;z-index:9999;box-shadow:0 2px 10px rgba(0,0,0,0.2);';
|
||||
document.body.appendChild(notification);
|
||||
setTimeout(() => notification.remove(), 3000);
|
||||
}).catch(() => {
|
||||
alert('Failed to copy content');
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to parse page content:', e);
|
||||
alert('Failed to parse page content: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Add dropdown button to header when page loads
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const headerSource = document.querySelector('.md-header__source');
|
||||
if (headerSource) {
|
||||
// Create dropdown container
|
||||
const dropdownContainer = document.createElement('div');
|
||||
dropdownContainer.style.cssText = 'position:relative;display:inline-block;margin-left:8px;';
|
||||
|
||||
// Create main button
|
||||
const button = document.createElement('button');
|
||||
button.innerHTML = 'Copy page <span style="margin-left:8px;font-size:12px;color:#9ca3af;">▾</span>';
|
||||
button.style.cssText = 'background:transparent;border:1px solid #d1d5db;padding:6px 12px;border-radius:4px;cursor:pointer;font-size:14px;color:#374151;transition:all 0.2s ease;white-space:nowrap;display:flex;align-items:center;';
|
||||
|
||||
// Create dropdown menu
|
||||
const dropdown = document.createElement('div');
|
||||
dropdown.className = 'copy-page-dropdown';
|
||||
dropdown.style.cssText = 'position:absolute;top:100%;left:0;background:white;border:1px solid #e5e7eb;border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,0.15);z-index:1000;min-width:180px;display:none;padding:4px 0;';
|
||||
|
||||
// Create dropdown options
|
||||
const option1 = document.createElement('div');
|
||||
option1.textContent = 'Copy as Markdown for LLMs';
|
||||
option1.className = 'copy-page-option';
|
||||
option1.style.cssText = 'padding:8px 16px;cursor:pointer;font-size:14px;color:#374151;margin:2px 0;';
|
||||
option1.onmouseover = function() {
|
||||
this.style.background = document.documentElement.getAttribute('data-md-color-scheme') === 'slate' ? '#4a5568' : '#f8fafc';
|
||||
};
|
||||
option1.onmouseout = function() { this.style.background = 'transparent'; };
|
||||
option1.onclick = function() {
|
||||
// Check if we're on a reference page
|
||||
if (window.location.pathname.includes('/reference/')) {
|
||||
alert('Copy Page not yet available in API reference pages.');
|
||||
} else {
|
||||
copyPageAsMarkdown();
|
||||
}
|
||||
dropdown.style.display = 'none';
|
||||
};
|
||||
|
||||
const option2 = document.createElement('div');
|
||||
option2.textContent = "View LangGraph's llms.txt";
|
||||
option2.className = 'copy-page-option';
|
||||
option2.style.cssText = 'padding:8px 16px;cursor:pointer;font-size:14px;color:#374151;margin:2px 0;';
|
||||
option2.onmouseover = function() {
|
||||
this.style.background = document.documentElement.getAttribute('data-md-color-scheme') === 'slate' ? '#4a5568' : '#f8fafc';
|
||||
};
|
||||
option2.onmouseout = function() { this.style.background = 'transparent'; };
|
||||
option2.onclick = function() {
|
||||
window.open('/langgraph/llms-txt-overview/', '_blank');
|
||||
dropdown.style.display = 'none';
|
||||
};
|
||||
|
||||
// Add options to dropdown
|
||||
dropdown.appendChild(option1);
|
||||
dropdown.appendChild(option2);
|
||||
|
||||
// Button hover effects
|
||||
button.onmouseover = function() {
|
||||
this.style.background = '#f3f4f6';
|
||||
this.style.borderColor = '#9ca3af';
|
||||
};
|
||||
button.onmouseout = function() {
|
||||
this.style.background = 'transparent';
|
||||
this.style.borderColor = '#d1d5db';
|
||||
};
|
||||
|
||||
// Toggle dropdown
|
||||
button.onclick = function(e) {
|
||||
e.stopPropagation();
|
||||
dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none';
|
||||
};
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', function() {
|
||||
dropdown.style.display = 'none';
|
||||
});
|
||||
|
||||
// Assemble dropdown
|
||||
dropdownContainer.appendChild(button);
|
||||
dropdownContainer.appendChild(dropdown);
|
||||
headerSource.parentNode.insertBefore(dropdownContainer, headerSource.nextSibling);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
@import url("https://fonts.googleapis.com/css2?family=Public+Sans&display=swap");
|
||||
:root {
|
||||
@@ -198,6 +322,17 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
/* Copy page dropdown dark mode support */
|
||||
[data-md-color-scheme="slate"] .copy-page-dropdown {
|
||||
background: #1f2937 !important;
|
||||
border-color: #374151 !important;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.5) !important;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .copy-page-option {
|
||||
color: #e5e7eb !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
+2
-1
@@ -9,7 +9,8 @@
|
||||
"@langchain/core": "^0.3.38",
|
||||
"@langchain/openai": "^0.4.2",
|
||||
"msgpack-lite": "^0.1.26",
|
||||
"nock": "^14.0.1"
|
||||
"nock": "^14.0.1",
|
||||
"he": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/recommended": "^1.0.8",
|
||||
|
||||
+5
-2
@@ -7,7 +7,7 @@ name = "langgraph-docs"
|
||||
version = "0.0.1"
|
||||
description = "LangGraph docs"
|
||||
authors = []
|
||||
requires-python = "~=3.10"
|
||||
requires-python = "~=3.11"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
dependencies = [
|
||||
@@ -48,6 +48,7 @@ docs = [
|
||||
"ruff",
|
||||
"jupyter",
|
||||
"langchain-cohere",
|
||||
"mkdocs-include-markdown-plugin>=7.1.6",
|
||||
]
|
||||
test = [
|
||||
"langchain",
|
||||
@@ -111,4 +112,6 @@ extend-include = ["*.ipynb"]
|
||||
[tool.codespell]
|
||||
# https://mypy.readthedocs.io/en/stable/config_file.html
|
||||
# comma-separated list
|
||||
ignore-words-list = "infor"
|
||||
ignore-words-list = "infor,thead,stdio,nd,jupyter,lets,lite,uis,deque"
|
||||
# Exclude generated files and directories
|
||||
skip = "*.ambr,*.lock,*.ipynb,*.yaml,*.zlib,*.css.map,*.js.map"
|
||||
|
||||
Generated
+1466
-2088
File diff suppressed because it is too large
Load Diff
+112
-3
@@ -152,6 +152,14 @@ base64-js@^1.5.1:
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
|
||||
call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
|
||||
integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
|
||||
dependencies:
|
||||
es-errors "^1.3.0"
|
||||
function-bind "^1.1.2"
|
||||
|
||||
camelcase@6:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
|
||||
@@ -201,6 +209,42 @@ delayed-stream@~1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
|
||||
|
||||
dunder-proto@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
|
||||
integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
|
||||
dependencies:
|
||||
call-bind-apply-helpers "^1.0.1"
|
||||
es-errors "^1.3.0"
|
||||
gopd "^1.2.0"
|
||||
|
||||
es-define-property@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
|
||||
integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
|
||||
|
||||
es-errors@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
|
||||
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
|
||||
|
||||
es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1"
|
||||
integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
|
||||
dependencies:
|
||||
es-errors "^1.3.0"
|
||||
|
||||
es-set-tostringtag@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d"
|
||||
integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==
|
||||
dependencies:
|
||||
es-errors "^1.3.0"
|
||||
get-intrinsic "^1.2.6"
|
||||
has-tostringtag "^1.0.2"
|
||||
hasown "^2.0.2"
|
||||
|
||||
event-lite@^0.1.1:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/event-lite/-/event-lite-0.1.3.tgz#3dfe01144e808ac46448f0c19b4ab68e403a901d"
|
||||
@@ -222,12 +266,14 @@ form-data-encoder@1.7.2:
|
||||
integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==
|
||||
|
||||
form-data@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48"
|
||||
integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4"
|
||||
integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
es-set-tostringtag "^2.1.0"
|
||||
hasown "^2.0.2"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
formdata-node@^4.3.2:
|
||||
@@ -238,11 +284,69 @@ formdata-node@^4.3.2:
|
||||
node-domexception "1.0.0"
|
||||
web-streams-polyfill "4.0.0-beta.3"
|
||||
|
||||
function-bind@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
|
||||
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
|
||||
|
||||
get-intrinsic@^1.2.6:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
|
||||
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
|
||||
dependencies:
|
||||
call-bind-apply-helpers "^1.0.2"
|
||||
es-define-property "^1.0.1"
|
||||
es-errors "^1.3.0"
|
||||
es-object-atoms "^1.1.1"
|
||||
function-bind "^1.1.2"
|
||||
get-proto "^1.0.1"
|
||||
gopd "^1.2.0"
|
||||
has-symbols "^1.1.0"
|
||||
hasown "^2.0.2"
|
||||
math-intrinsics "^1.1.0"
|
||||
|
||||
get-proto@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
|
||||
integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
|
||||
dependencies:
|
||||
dunder-proto "^1.0.1"
|
||||
es-object-atoms "^1.0.0"
|
||||
|
||||
gopd@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
|
||||
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
|
||||
|
||||
has-flag@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
||||
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
||||
|
||||
has-symbols@^1.0.3, has-symbols@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
|
||||
integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
|
||||
|
||||
has-tostringtag@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc"
|
||||
integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==
|
||||
dependencies:
|
||||
has-symbols "^1.0.3"
|
||||
|
||||
hasown@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
|
||||
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
|
||||
dependencies:
|
||||
function-bind "^1.1.2"
|
||||
|
||||
he@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
|
||||
humanize-ms@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
|
||||
@@ -295,6 +399,11 @@ json-stringify-safe@^5.0.1:
|
||||
semver "^7.6.3"
|
||||
uuid "^10.0.0"
|
||||
|
||||
math-intrinsics@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
|
||||
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
|
||||
|
||||
mime-db@1.52.0:
|
||||
version "1.52.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
"id": "2dff2209-44c7-4e2c-b607-ba6675f9e45f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["from langgraph.checkpoint.memory import MemorySaver\nfrom langgraph.graph import END, StateGraph, START\n\nbuilder = StateGraph(GraphState)\n\n# Define the nodes\nbuilder.add_node(\"generate\", generate) # generation solution\nbuilder.add_node(\"check_code\", code_check) # check code\n\n# Build graph\nbuilder.add_edge(START, \"generate\")\nbuilder.add_edge(\"generate\", \"check_code\")\nbuilder.add_conditional_edges(\n \"check_code\",\n decide_to_finish,\n {\n \"end\": END,\n \"generate\": \"generate\",\n },\n)\n\nmemory = MemorySaver()\ngraph = builder.compile(checkpointer=memory)"]
|
||||
"source": ["from langgraph.checkpoint.memory import InMemorySaver\nfrom langgraph.graph import END, StateGraph, START\n\nbuilder = StateGraph(GraphState)\n\n# Define the nodes\nbuilder.add_node(\"generate\", generate) # generation solution\nbuilder.add_node(\"check_code\", code_check) # check code\n\n# Build graph\nbuilder.add_edge(START, \"generate\")\nbuilder.add_edge(\"generate\", \"check_code\")\nbuilder.add_conditional_edges(\n \"check_code\",\n decide_to_finish,\n {\n \"end\": END,\n \"generate\": \"generate\",\n },\n)\n\nmemory = InMemorySaver()\ngraph = builder.compile(checkpointer=memory)"]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
|
||||
@@ -284,11 +284,9 @@ class PostgresSaver(BasePostgresSaver):
|
||||
configurable = config["configurable"].copy()
|
||||
thread_id = configurable.pop("thread_id")
|
||||
checkpoint_ns = configurable.pop("checkpoint_ns")
|
||||
checkpoint_id = configurable.pop(
|
||||
"checkpoint_id", configurable.pop("thread_ts", None)
|
||||
)
|
||||
|
||||
checkpoint_id = configurable.pop("checkpoint_id", None)
|
||||
copy = checkpoint.copy()
|
||||
copy["channel_values"] = copy["channel_values"].copy()
|
||||
next_config = {
|
||||
"configurable": {
|
||||
"thread_id": thread_id,
|
||||
@@ -297,16 +295,28 @@ class PostgresSaver(BasePostgresSaver):
|
||||
}
|
||||
}
|
||||
|
||||
# inline primitive values in checkpoint table
|
||||
# others are stored in blobs table
|
||||
blob_values = {}
|
||||
for k, v in checkpoint["channel_values"].items():
|
||||
if v is None or isinstance(v, (str, int, float, bool)):
|
||||
pass
|
||||
else:
|
||||
blob_values[k] = copy["channel_values"].pop(k)
|
||||
|
||||
with self._cursor(pipeline=True) as cur:
|
||||
cur.executemany(
|
||||
self.UPSERT_CHECKPOINT_BLOBS_SQL,
|
||||
self._dump_blobs(
|
||||
thread_id,
|
||||
checkpoint_ns,
|
||||
copy.pop("channel_values"), # type: ignore[misc]
|
||||
new_versions,
|
||||
),
|
||||
)
|
||||
if blob_versions := {
|
||||
k: v for k, v in new_versions.items() if k in blob_values
|
||||
}:
|
||||
cur.executemany(
|
||||
self.UPSERT_CHECKPOINT_BLOBS_SQL,
|
||||
self._dump_blobs(
|
||||
thread_id,
|
||||
checkpoint_ns,
|
||||
blob_values,
|
||||
blob_versions,
|
||||
),
|
||||
)
|
||||
cur.execute(
|
||||
self.UPSERT_CHECKPOINTS_SQL,
|
||||
(
|
||||
@@ -439,7 +449,10 @@ class PostgresSaver(BasePostgresSaver):
|
||||
},
|
||||
{
|
||||
**value["checkpoint"],
|
||||
"channel_values": self._load_blobs(value["channel_values"]),
|
||||
"channel_values": {
|
||||
**value["checkpoint"].get("channel_values"),
|
||||
**self._load_blobs(value["channel_values"]),
|
||||
},
|
||||
},
|
||||
value["metadata"],
|
||||
(
|
||||
|
||||
@@ -240,11 +240,10 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
||||
configurable = config["configurable"].copy()
|
||||
thread_id = configurable.pop("thread_id")
|
||||
checkpoint_ns = configurable.pop("checkpoint_ns")
|
||||
checkpoint_id = configurable.pop(
|
||||
"checkpoint_id", configurable.pop("thread_ts", None)
|
||||
)
|
||||
checkpoint_id = configurable.pop("checkpoint_id", None)
|
||||
|
||||
copy = checkpoint.copy()
|
||||
copy["channel_values"] = copy["channel_values"].copy()
|
||||
next_config = {
|
||||
"configurable": {
|
||||
"thread_id": thread_id,
|
||||
@@ -253,17 +252,29 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
||||
}
|
||||
}
|
||||
|
||||
# inline primitive values in checkpoint table
|
||||
# others are stored in blobs table
|
||||
blob_values = {}
|
||||
for k, v in checkpoint["channel_values"].items():
|
||||
if v is None or isinstance(v, (str, int, float, bool)):
|
||||
pass
|
||||
else:
|
||||
blob_values[k] = copy["channel_values"].pop(k)
|
||||
|
||||
async with self._cursor(pipeline=True) as cur:
|
||||
await cur.executemany(
|
||||
self.UPSERT_CHECKPOINT_BLOBS_SQL,
|
||||
await asyncio.to_thread(
|
||||
self._dump_blobs,
|
||||
thread_id,
|
||||
checkpoint_ns,
|
||||
copy.pop("channel_values"), # type: ignore[misc]
|
||||
new_versions,
|
||||
),
|
||||
)
|
||||
if blob_versions := {
|
||||
k: v for k, v in new_versions.items() if k in blob_values
|
||||
}:
|
||||
await cur.executemany(
|
||||
self.UPSERT_CHECKPOINT_BLOBS_SQL,
|
||||
await asyncio.to_thread(
|
||||
self._dump_blobs,
|
||||
thread_id,
|
||||
checkpoint_ns,
|
||||
blob_values,
|
||||
blob_versions,
|
||||
),
|
||||
)
|
||||
await cur.execute(
|
||||
self.UPSERT_CHECKPOINTS_SQL,
|
||||
(
|
||||
@@ -397,7 +408,10 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
||||
},
|
||||
{
|
||||
**value["checkpoint"],
|
||||
"channel_values": self._load_blobs(value["channel_values"]),
|
||||
"channel_values": {
|
||||
**value["checkpoint"].get("channel_values"),
|
||||
**self._load_blobs(value["channel_values"]),
|
||||
},
|
||||
},
|
||||
value["metadata"],
|
||||
(
|
||||
|
||||
@@ -191,7 +191,7 @@ class ShallowPostgresSaver(BasePostgresSaver):
|
||||
) -> None:
|
||||
warnings.warn(
|
||||
"ShallowPostgresSaver is deprecated as of version 2.0.20 and will be removed in 3.0.0. "
|
||||
"Use PostgresSaver instead, and invoke the graph with `graph.invoke(..., checkpoint_during=False)`.",
|
||||
"Use PostgresSaver instead, and invoke the graph with `graph.invoke(..., durability='exit')`.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
@@ -547,7 +547,7 @@ class AsyncShallowPostgresSaver(BasePostgresSaver):
|
||||
) -> None:
|
||||
warnings.warn(
|
||||
"AsyncShallowPostgresSaver is deprecated as of version 2.0.20 and will be removed in 3.0.0. "
|
||||
"Use AsyncPostgresSaver instead, and invoke the graph with `await graph.ainvoke(..., checkpoint_during=False)`.",
|
||||
"Use AsyncPostgresSaver instead, and invoke the graph with `await graph.ainvoke(..., durability='exit')`.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
@@ -557,7 +557,7 @@ class BasePostgresStore(Generic[C]):
|
||||
) -> list[tuple[str, Sequence]]:
|
||||
queries: list[tuple[str, Sequence]] = []
|
||||
for _, op in list_ops:
|
||||
query = """
|
||||
query = r"""
|
||||
SELECT DISTINCT ON (truncated_prefix) truncated_prefix, prefix
|
||||
FROM (
|
||||
SELECT
|
||||
@@ -756,6 +756,7 @@ class PostgresStore(BaseStore, BasePostgresStore[_pg_internal.Conn]):
|
||||
If provided, will create a connection pool and use it instead of a single connection.
|
||||
This overrides the `pipeline` argument.
|
||||
index: The index configuration for the store.
|
||||
ttl: The TTL configuration for the store.
|
||||
|
||||
Returns:
|
||||
PostgresStore: A new PostgresStore instance.
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "langgraph-checkpoint-postgres"
|
||||
version = "2.0.21"
|
||||
version = "2.0.23"
|
||||
description = "Library with a Postgres implementation of LangGraph checkpoint saver."
|
||||
authors = []
|
||||
requires-python = ">=3.9"
|
||||
|
||||
@@ -161,8 +161,7 @@ def test_data():
|
||||
config_1: RunnableConfig = {
|
||||
"configurable": {
|
||||
"thread_id": "thread-1",
|
||||
# for backwards compatibility testing
|
||||
"thread_ts": "1",
|
||||
"checkpoint_id": "1",
|
||||
"checkpoint_ns": "",
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user