Compare commits

..
Author SHA1 Message Date
Arjun Natarajan 91bca2ea1d first pass at data storage docs 2025-06-23 20:22:33 -04:00
lc-arjunandGitHub bd206c2fcd docs: fix assistants links (#5172)
* docs: fix assistants links

* fix another page
2025-06-23 13:48:20 -07:00
lc-arjunandGitHub 14ec895046 docs: Fix LGP sdk typo (#5170)
docs: fix typo in sdk docs
2025-06-23 10:15:15 -07:00
hari-dhanushkodiandGitHub fb66736ccb add more docs for lgp deployment metrics (#5168) 2025-06-23 07:11:57 -07:00
Nuno Campos c3544024b9 If FuturesDict callback has been GCed, don't call it 2025-06-17 14:06:54 -07:00
b0d1234737 docs: missing lgp docs (#5130)
* chore: add docs for lgp deployment monitoring (#5104)

* docs: studio evals (#5129)

* docs: studio evals

* docs: added studio evals images (#5076)

* docs: added studio evals images

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

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

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

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

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

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

* docs: updated studio evals

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

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

* docs: removed images

---------

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

* final changes

* i think its this

---------

Co-authored-by: Marco Perini <perinim.98@gmail.com>

---------

Co-authored-by: hari-dhanushkodi <hari@langchain.dev>
Co-authored-by: Marco Perini <perinim.98@gmail.com>
2025-06-17 13:10:27 -07:00
Lauren Hirata Singh 53e1a238db Remove cookie consent 2025-06-16 18:42:56 -04:00
langchain-infraandGitHub fcdeafd0d1 docs: fix langgraph docs (#5065)
docs: fix config section
2025-06-11 13:23:16 -04:00
langchain-infraandGitHub 28c529feb2 docs: add mount prefix environment variable (#5061) 2025-06-11 11:21:53 -04:00
infra 91ebc8d3ed docs: add mount prefix environment variable 2025-06-11 11:19:40 -04:00
Eugene YurtsevandGitHub 6e08f4c12e v0: port GTM to v0 (#5056)
This was lost when the v0 branch was cut out and docs started being deployed from v0
2025-06-11 10:21:29 -04:00
Nuno CamposandGitHub f2dc0653f1 docs: list CipherProtocol in API (#5048) 2025-06-10 14:28:39 -07:00
Nuno CamposandNuno Campos 67177a5610 docs: list CipherProtocol in API 2025-06-10 14:25:26 -07:00
Asamu DavidandGitHub b1b238c7ea add docs for image_distro cli option (#4981) 2025-06-06 16:32:21 +01:00
David Asamu 598796ef86 add docs for image_distro cli option 2025-06-06 16:26:25 +01:00
Sydney RunkleandGitHub 3c7981201e docs: remove usage of StateGraph(dict) (#4967)
docs: remove references to `StateGraph(dict)` (#4964)

remove StateGraph(dict)
2025-06-04 21:30:39 -04:00
Sydney RunkleandGitHub 109c0dfb93 docs: deploy from v0 branch for now (#4960) (#4961)
only deploy docs on v0
2025-06-04 13:30:34 -04:00
Nuno Campos d7c364c5bb Port step_timeout/GraphBubbleUp fix to v0
See fix and tests in original PR https://github.com/langchain-ai/langgraph/pull/4950
2025-06-03 17:23:45 -07:00
Nuno Campos 746142fb07 One more 2025-06-02 16:16:11 -07:00
Nuno Campos b9c9c32c31 Allow releases from v0 2025-06-02 16:12:54 -07:00
Nuno Campos c89fe4c45d 0.4.8 2025-06-02 16:11:09 -07:00
Nuno CamposandGitHub b0e28851a6 v0: Fix Command(graph=PARENT) when used together w checkpointer=True (#4920) 2025-06-02 16:10:00 -07:00
Nuno Campos 48fb91deda Fix Command(graph=PARENT) when used together w checkpointer=True 2025-06-02 16:02:35 -07:00
587 changed files with 85794 additions and 62391 deletions
+12 -12
View File
@@ -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 LangChain Forum at forum.langchain.com.
labels: [pending,bug]
description: Report a bug in LangGraph. To report a security issue, please instead use the security option below. For questions, please use the GitHub Discussions.
labels: ["02 Bug Report"]
body:
- type: markdown
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 the [LangChain Forum](https://forum.langchain.com/).
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).
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:
* [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),
[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),
- 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 the LangChain Forum (https://forum.langchain.com/).
- label: This is a bug, not a usage question. For questions, please use GitHub Discussions.
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. Replace this code with your own!
Please add a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with your use case.
placeholder: |
from langgraph.graph import StateGraph
@@ -78,7 +78,7 @@ body:
attributes:
label: System Info
description: |
Run on your machine: `python -m langchain_core.sys_info`
python -m langchain_core.sys_info
placeholder: |
python -m langchain_core.sys_info
validations:
+12 -3
View File
@@ -1,6 +1,15 @@
blank_issues_enabled: false
version: 2.1
contact_links:
- name: LangChain Forum
url: https://forum.langchain.com/
about: General community discussions, support, and feature requests
- 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: Slack
url: https://www.langchain.com/join-community
about: General community discussions
+1 -1
View File
@@ -1,7 +1,7 @@
name: Documentation
description: Report an issue related to the LangGraph documentation.
title: "DOC: <Please write a comprehensive title after the 'DOC: ' prefix>"
labels: [documentation]
labels: [03 - Documentation]
body:
- type: textarea
+8 -12
View File
@@ -1,29 +1,25 @@
name: 🔒 Privileged
description: You are a LangGraph maintainer, or was asked directly by a maintainer to create an issue here. If not, check the other options.
description: You are a LangChain 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 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.
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.
- type: checkboxes
id: privileged
attributes:
label: Privileged issue
description: Confirm that you are allowed to create an issue here.
options:
- label: I am a LangGraph maintainer, or was asked directly by a LangGraph maintainer to create an issue here.
- label: I am a LangChain maintainer, or was asked directly by a LangChain 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.
-31
View File
@@ -1,31 +0,0 @@
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.
-18
View File
@@ -1,18 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "pip"
directories:
- "libs/checkpoint"
- "libs/checkpoint-postgres"
- "libs/checkpoint-sqlite"
- "libs/cli"
- "libs/langgraph"
- "libs/prebuilt"
- "libs/sdk-py"
schedule:
interval: "weekly"
-3
View File
@@ -3,9 +3,6 @@ name: CLI integration test
on:
workflow_call:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
+2 -5
View File
@@ -8,9 +8,6 @@ on:
type: string
description: "From which folder this pipeline executes"
permissions:
contents: read
env:
# This env var allows us to get inline annotations when ruff has complaints.
RUFF_OUTPUT_FORMAT: github
@@ -52,7 +49,7 @@ jobs:
- name: Get .mypy_cache to speed up mypy
if: steps.changed-files.outputs.all
uses: actions/cache@v4
uses: actions/cache@v3
env:
SEGMENT_DOWNLOAD_TIMEOUT_MIN: "2"
with:
@@ -78,7 +75,7 @@ jobs:
- name: Get .mypy_cache_test to speed up mypy
if: steps.changed-files.outputs.all
uses: actions/cache@v4
uses: actions/cache@v3
env:
SEGMENT_DOWNLOAD_TIMEOUT_MIN: "2"
with:
-3
View File
@@ -8,9 +8,6 @@ on:
type: string
description: "From which folder this pipeline executes"
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
-3
View File
@@ -3,9 +3,6 @@ name: test
on:
workflow_call:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
+1 -4
View File
@@ -11,12 +11,9 @@ on:
env:
PYTHON_VERSION: "3.10"
permissions:
contents: read
jobs:
build:
if: github.ref == 'refs/heads/main'
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/v0'
runs-on: ubuntu-latest
outputs:
@@ -0,0 +1,52 @@
name: test
on:
workflow_call:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version:
- "3.11"
- "3.12"
defaults:
run:
working-directory: libs/scheduler-kafka
name: "test #${{ matrix.python-version }}"
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: astral-sh/setup-uv@v6
with:
python-version: ${{ matrix.python-version }}
enable-cache: true
cache-suffix: "test-scheduler-kafka"
- name: Login to Docker Hub
uses: docker/login-action@v3
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_RO_TOKEN }}
- name: Install dependencies
shell: bash
run: uv sync --frozen --group dev
- name: Run tests
shell: bash
run: make test
- name: Ensure the tests did not create any additional files
shell: bash
run: |
set -eu
STATUS="$(git status)"
echo "$STATUS"
# grep will exit non-zero if the target message isn't found,
# and `set -e` above will cause the step to fail.
echo "$STATUS" | grep 'nothing to commit, working tree clean'
-3
View File
@@ -7,9 +7,6 @@ on:
paths:
- "libs/**"
permissions:
contents: read
jobs:
benchmark:
runs-on: ubuntu-latest
-3
View File
@@ -5,9 +5,6 @@ on:
paths:
- "libs/**"
permissions:
contents: read
jobs:
benchmark:
runs-on: ubuntu-latest
+71 -13
View File
@@ -3,12 +3,9 @@ name: CI
on:
push:
branches: [main, v1]
branches: [main]
pull_request:
permissions:
contents: read
# If another push to the same PR or branch happens while this workflow is still running,
# cancel the earlier run in favor of the next run.
#
@@ -24,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
outputs:
python: ${{ steps.filter.outputs.python }}
deps: ${{ steps.filter.outputs.deps }}
sdk-js: ${{ steps.filter.outputs.sdk-js }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
@@ -38,10 +35,10 @@ jobs:
- 'libs/checkpoint/**'
- 'libs/checkpoint-sqlite/**'
- 'libs/checkpoint-postgres/**'
- 'libs/scheduler-kafka/**'
- 'libs/prebuilt/**'
deps:
- '**/pyproject.toml'
- '**/uv.lock'
sdk-js:
- 'libs/sdk-js/**'
lint:
needs: changes
@@ -56,10 +53,10 @@ jobs:
"libs/checkpoint",
"libs/checkpoint-sqlite",
"libs/checkpoint-postgres",
"libs/scheduler-kafka",
"libs/prebuilt",
]
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
if: needs.changes.outputs.python == 'true'
uses: ./.github/workflows/_lint.yml
with:
working-directory: ${{ matrix.working-directory }}
@@ -78,7 +75,7 @@ jobs:
"libs/checkpoint-postgres",
"libs/prebuilt",
]
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
if: needs.changes.outputs.python == 'true'
uses: ./.github/workflows/_test.yml
with:
working-directory: ${{ matrix.working-directory }}
@@ -87,11 +84,19 @@ jobs:
# NOTE: we're testing langgraph separately because it requires a different matrix
test-langgraph:
needs: changes
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
if: needs.changes.outputs.python == 'true'
name: "cd libs/langgraph"
uses: ./.github/workflows/_test_langgraph.yml
secrets: inherit
# NOTE: we're testing scheduler-kafka separately because it requires a different matrix
test-scheduler-kafka:
needs: changes
if: needs.changes.outputs.python == 'true'
name: "cd libs/scheduler-kafka"
uses: ./.github/workflows/_test_scheduler_kafka.yml
secrets: inherit
check-sdk-methods:
needs: changes
if: needs.changes.outputs.python == 'true'
@@ -144,21 +149,74 @@ jobs:
integration-test:
needs: changes
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
if: needs.changes.outputs.python == 'true'
name: CLI integration test
uses: ./.github/workflows/_integration_test.yml
secrets: inherit
lint-js:
needs: changes
if: needs.changes.outputs.sdk-js == 'true'
runs-on: ubuntu-latest
strategy:
matrix:
working-directory:
- "libs/sdk-js"
defaults:
run:
working-directory: ${{ matrix.working-directory }}
steps:
- uses: actions/checkout@v3
- name: Setup Node.js (LTS)
uses: actions/setup-node@v3
with:
node-version: "20"
cache: "yarn"
cache-dependency-path: ${{ matrix.working-directory }}/yarn.lock
- name: Install dependencies
run: yarn install
- name: Run lint
run: yarn lint
- name: Build
run: yarn build
test-js:
needs: changes
if: needs.changes.outputs.sdk-js == 'true'
runs-on: ubuntu-latest
strategy:
matrix:
working-directory:
- "libs/sdk-js"
defaults:
run:
working-directory: ${{ matrix.working-directory }}
steps:
- uses: actions/checkout@v3
- name: Setup Node.js (LTS)
uses: actions/setup-node@v3
with:
node-version: "20"
cache: "yarn"
cache-dependency-path: ${{ matrix.working-directory }}/yarn.lock
- name: Install dependencies
run: yarn install
- name: Run tests
run: yarn test
ci_success:
name: "CI Success"
needs:
[
lint,
lint-js,
test,
test-langgraph,
test-scheduler-kafka,
check-sdk-methods,
check-schema,
integration-test,
test-js,
]
if: |
always()
@@ -1,11 +0,0 @@
LangChain
LangGraph
LangSmith
thead
stdio
nd
jupyter
lets
lite
uis
deque
+3 -9
View File
@@ -34,16 +34,10 @@
id: extract_ignore_words
- name: Codespell
uses: codespell-project/actions-codespell@v2.1
uses: codespell-project/actions-codespell@v2
with:
skip: '*.ambr,*.lock,*.ipynb,*.yaml,*.zlib,*.css.map,*.js.map'
skip: '*.ambr,*.lock,*.ipynb,*.yaml,*.zlib,*.md'
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
- 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/
run: make codespell
+15 -4
View File
@@ -4,9 +4,11 @@ on:
push:
branches:
- main
- v0
pull_request:
branches:
- main
- v0
workflow_dispatch:
permissions:
@@ -35,7 +37,16 @@ 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:
@@ -73,9 +84,9 @@ jobs:
run: make llms-text
- name: Build site
run: |
# If this is main branch, then we want to download stats. we do this
# If this is v0 branch, then we want to download stats. we do this
# with the env variable DOWNLOAD_STATS=true
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
if [ "${{ github.ref }}" == "refs/heads/v0" ]; then
DOWNLOAD_STATS=true make build-docs
else
make build-docs
@@ -135,7 +146,7 @@ jobs:
fi
- name: Configure GitHub Pages
if: github.ref == 'refs/heads/main'
if: github.ref == 'refs/heads/v0'
uses: actions/configure-pages@v5
- name: Upload Pages Artifact
@@ -145,6 +156,6 @@ jobs:
path: ./docs/site/
- name: Deploy to GitHub Pages
if: github.ref == 'refs/heads/main'
if: github.ref == 'refs/heads/v0'
id: deployment
uses: actions/deploy-pages@v4
-3
View File
@@ -11,9 +11,6 @@ on:
- cron: "0 5 * * *"
workflow_dispatch:
permissions:
contents: read
jobs:
markdown-link-check:
runs-on: ubuntu-latest
-45
View File
@@ -1,45 +0,0 @@
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
+2 -7
View File
@@ -8,15 +8,12 @@ on:
type: string
default: "libs/langgraph"
permissions:
contents: read
env:
PYTHON_VERSION: "3.11"
jobs:
build:
if: github.ref == 'refs/heads/main'
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/v0'
runs-on: ubuntu-latest
outputs:
@@ -137,9 +134,7 @@ jobs:
needs:
- build
- release-notes
permissions:
contents: read
id-token: write
permissions: write-all
uses: ./.github/workflows/_test_release.yml
with:
working-directory: ${{ inputs.working-directory }}
+38
View File
@@ -0,0 +1,38 @@
name: JS Release
on:
workflow_dispatch:
jobs:
publish:
# Disallow publishing from branches that aren't `main`.
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
strategy:
matrix:
working-directory:
- "libs/sdk-js"
defaults:
run:
working-directory: ${{ matrix.working-directory }}
steps:
- uses: actions/checkout@v4
# JS Build
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: "20"
cache: "yarn"
cache-dependency-path: ${{ matrix.working-directory }}/yarn.lock
- name: Install dependencies
run: yarn install
- name: Build
run: yarn build
- name: Publish package to NPM
run: |
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc
npm publish
-3
View File
@@ -11,9 +11,6 @@ on:
schedule:
- cron: "0 13 * * *"
permissions:
contents: read
defaults:
run:
working-directory: docs
-45
View File
@@ -1,45 +0,0 @@
name: UV Lock Upgrade
on:
schedule:
# run at midnight every Sunday
- cron: '0 0 * * 0'
# allow manual triggering
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
upgrade-dependencies:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up uv
uses: astral-sh/setup-uv@v6
with:
# use minimum supported Python version
python-version: "3.9"
enable-cache: true
cache-suffix: "uv-lock-upgrade"
- name: Run uv lock --upgrade in all Python packages
run: make lock-upgrade
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "chore[deps]: upgrade dependencies with `uv lock --upgrade`"
title: "chore[deps]: upgrade dependencies with `uv lock --upgrade`"
body: |
This PR updates the dependencies in all Python packages using `uv lock --upgrade`.
This is an automated PR created by the UV Lock Upgrade workflow.
branch: deps/uv-lock-upgrade
delete-branch: true
labels: |
dependencies
-1
View File
@@ -181,4 +181,3 @@ Chinook.db
.vercel
.turbo
.editorconfig
.scratch
-55
View File
@@ -1,55 +0,0 @@
# AGENTS Instructions
This repository is a monorepo. Each library lives in a subdirectory under `libs/`.
When you modify code in any library, run the following commands in that library's directory before creating a pull request:
- `make format` run code formatters
- `make lint` run the linter
- `make test` execute the test suite
To run a particular test file or to pass additional pytest options you can specify the `TEST` variable:
```
TEST=path/to/test.py make test
```
Other pytest arguments can also be supplied inside the `TEST` variable.
## Libraries
The repository contains several Python and JavaScript/TypeScript libraries.
Below is a high-level overview:
- **checkpoint** base interfaces for LangGraph checkpointers.
- **checkpoint-postgres** Postgres implementation of the checkpoint saver.
- **checkpoint-sqlite** SQLite implementation of the checkpoint saver.
- **cli** official command-line interface for LangGraph.
- **langgraph** core framework for building stateful, multi-actor agents.
- **prebuilt** high-level APIs for creating and running agents and tools.
- **sdk-js** JS/TS SDK for interacting with the LangGraph REST API.
- **sdk-py** Python SDK for the LangGraph Platform API.
### Dependency map
The diagram below lists downstream libraries for each production dependency as
declared in that library's `pyproject.toml` (or `package.json`).
```text
checkpoint
├── checkpoint-postgres
├── checkpoint-sqlite
├── prebuilt
└── langgraph
prebuilt
└── langgraph
sdk-py
├── langgraph
└── cli
sdk-js (standalone)
```
Changes to a library may impact all of its dependents shown above.
+11 -10
View File
@@ -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 tag a maintainer.
- If you would like comments or feedback, please open an issue or discussion and 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://forum.langchain.com/), where the maintainers will help with scoping out the necessary changes.
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.
## Contribute Documentation
@@ -60,7 +60,7 @@ In LangGraph, these are often higher level guides that show off end-to-end use c
Some examples include:
- [Build a Customer Support Bot](https://langchain-ai.github.io/langgraph/tutorials/customer-support/customer-support/)
- [Build a SQL Agent](https://langchain-ai.github.io/langgraph/tutorials/sql/sql-agent/)
- [Build a SQL Agent](https://langchain-ai.github.io/langgraph/tutorials/sql-agent/)
Here are some high-level tips on writing a good tutorial:
@@ -109,7 +109,8 @@ Here are some high-level tips on writing a good how-to guide:
LangGraph's conceptual guides fall under the **Explanation** quadrant of Diataxis. They should cover LangChain terms and concepts
in a more abstract way than how-to guides or tutorials, and should be geared towards curious users interested in
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.
impart perspective to the user rather than to finish a practical project. These guides should cover **why** things work they way they do.
To quote the Diataxis website:
@@ -186,9 +187,9 @@ Be concise, including in code samples.
## Setup
LangGraph documentation consists of two components:
LangChain documentation consists of two components:
1. Main Documentation: Hosted at [https://langchain-ai.github.io/langgraph/](https://langchain-ai.github.io/langgraph/),
1. Main Documentation: Hosted at [https://langchain-ai.github.io](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.
@@ -249,17 +250,17 @@ make serve-docs
#### Linting
To spell check the docs, run the following from the `docs` directory:
The documentation is linted from the **monorepo root**. To lint it, run the following from there:
```bash
codespell --skip="*.ambr,*.lock,*.ipynb,*.yaml,*.zlib,*.css.map,*.js.map" --ignore-words-list="infor,thead,stdio,nd,jupyter,lets,lite,uis,deque" .
make spellcheck
```
### 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 LangGraph 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 LangChain 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.
@@ -290,4 +291,4 @@ def my_function(arg1: int, arg2: str) -> float:
This is a description of the return value.
"""
return 3.14
```
```
-68
View File
@@ -1,68 +0,0 @@
# Define the directories containing projects
LIBS_DIRS := $(wildcard libs/*)
# Default target
.PHONY: all
all: lint format lock test
# Install dependencies for all projects
.PHONY: install
install:
@echo "Creating virtual environment..."
@uv venv
@for dir in $(LIBS_DIRS); do \
if [ -f $$dir/pyproject.toml ]; then \
echo "Installing dependencies for $$dir"; \
uv pip install -e $$dir; \
fi; \
done
# Lint all projects
.PHONY: lint
lint:
@for dir in $(LIBS_DIRS); do \
if [ -f $$dir/Makefile ]; then \
echo "Running lint in $$dir"; \
$(MAKE) -C $$dir lint; \
fi; \
done
# Format all projects
.PHONY: format
format:
@for dir in $(LIBS_DIRS); do \
if [ -f $$dir/Makefile ]; then \
echo "Running format in $$dir"; \
$(MAKE) -C $$dir format; \
fi; \
done
# Lock all projects
.PHONY: lock
lock:
@for dir in $(LIBS_DIRS); do \
if [ -f $$dir/Makefile ]; then \
echo "Running lock in $$dir"; \
(cd $$dir && uv lock); \
fi; \
done
# Lock all projects and upgrade dependencies
.PHONY: lock-upgrade
lock-upgrade:
@for dir in $(LIBS_DIRS); do \
if [ -f $$dir/Makefile ]; then \
echo "Running lock-upgrade in $$dir"; \
(cd $$dir && uv lock --upgrade); \
fi; \
done
# Test all projects
.PHONY: test
test:
@for dir in $(LIBS_DIRS); do \
if [ -f $$dir/Makefile ]; then \
echo "Running test in $$dir"; \
$(MAKE) -C $$dir test; \
fi; \
done
+4 -4
View File
@@ -12,6 +12,7 @@
[![Downloads](https://static.pepy.tech/badge/langgraph/month)](https://pepy.tech/project/langgraph)
[![Open Issues](https://img.shields.io/github/issues-raw/langchain-ai/langgraph)](https://github.com/langchain-ai/langgraph/issues)
[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://langchain-ai.github.io/langgraph/)
[![GitMCP](https://img.shields.io/endpoint?url=https://gitmcp.io/badge/langchain-ai/langgraph)](https://gitmcp.io/langchain-ai/langgraph)
Trusted by companies shaping the future of agents including Klarna, Replit, Elastic, and more LangGraph is a low-level orchestration framework for building, managing, and deploying long-running, stateful agents.
@@ -63,7 +64,7 @@ LangGraph provides low-level supporting infrastructure for *any* long-running, s
While LangGraph can be used standalone, it also integrates seamlessly with any LangChain product, giving developers a full suite of tools for building agents. To improve your LLM application development, pair LangGraph with:
- [LangSmith](http://www.langchain.com/langsmith) — Helpful for agent evals and observability. Debug poor-performing LLM app runs, evaluate agent trajectories, gain visibility in production, and improve performance over time.
- [LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) — Deploy and scale agents effortlessly with a purpose-built deployment platform for long running, stateful workflows. Discover, reuse, configure, and share agents across teams — and iterate quickly with visual prototyping in [LangGraph Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/).
- [LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/#langgraph-platform) — Deploy and scale agents effortlessly with a purpose-built deployment platform for long running, stateful workflows. Discover, reuse, configure, and share agents across teams — and iterate quickly with visual prototyping in [LangGraph Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/).
- [LangChain](https://python.langchain.com/docs/introduction/) Provides integrations and composable components to streamline LLM application development.
> [!NOTE]
@@ -73,12 +74,11 @@ 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/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.
- [Examples](https://langchain-ai.github.io/langgraph/tutorials/overview/): Guided examples on getting started with LangGraph.
- [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.
+1
View File
@@ -1,3 +1,4 @@
site/
docs/cloud/reference/sdk/js_ts_sdk_ref.md
.vercel
+11 -5
View File
@@ -1,4 +1,10 @@
.PHONY: lint-docs format-docs build-docs serve-docs serve-clean-docs clean-docs codespell llms-text build-prebuilt tests
.PHONY: lint-docs format-docs build-docs serve-docs serve-clean-docs clean-docs codespell build-typedoc llms-text build-prebuilt tests
build-typedoc:
cd ../libs/sdk-js && yarn install --include-dev && yarn typedoc
cd ../libs/sdk-js && yarn --silent concat-md --decrease-title-levels --ignore=js_ts_sdk_ref.md --start-title-level-at 2 docs > ../../docs/docs/cloud/reference/sdk/js_ts_sdk_ref.md 2>/dev/null
# Add links to the monorepo
sed -e '1,10s|@langchain/langgraph-sdk|[@langchain/langgraph-sdk](https://github.com/langchain-ai/langgraph/tree/main/libs/sdk-js)|g' docs/cloud/reference/sdk/js_ts_sdk_ref.md > temp_file && mv temp_file docs/cloud/reference/sdk/js_ts_sdk_ref.md
build-prebuilt:
# Use to create an update to date prebuilt page.
@@ -13,10 +19,10 @@ build-prebuilt:
uv run python -m _scripts.third_party_page.get_download_stats --fake stats.yml; \
set +x; \
fi
uv run python -m _scripts.third_party_page.create_third_party_page stats.yml docs/agents/prebuilt.md
uv run python -m _scripts.third_party_page.create_third_party_page stats.yml docs/agents/prebuilt.md --language python
build-docs: build-prebuilt
TARGET_LANGUAGE=python uv run python -m mkdocs build --clean -f mkdocs.yml --strict
build-docs: build-typedoc build-prebuilt
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
@@ -39,7 +45,7 @@ vercel-build-docs: install-vercel-deps
serve-clean-docs: clean-docs
uv run python -m mkdocs serve -c -f mkdocs.yml --strict -w ../libs/langgraph
serve-docs:
serve-docs: build-typedoc
uv run python -m mkdocs serve -f mkdocs.yml -w ../libs/langgraph -w ../libs/checkpoint -w ../libs/sdk-py --dirty
clean-docs:
+157
View File
@@ -0,0 +1,157 @@
"""Add typescript translation to a given markdown file."""
import argparse
import re
import requests
from langchain_anthropic import ChatAnthropic
URL = "https://gist.githubusercontent.com/eyurtsev/e7486731415463a9bc5b4682358859c8/raw/b5a5fda9c7e3387cfcb781f25082814d43675d50/gistfile1.txt"
response = requests.get(URL)
response.raise_for_status()
reference_snippets = response.text
model = ChatAnthropic(model="claude-3-5-sonnet-latest")
def _get_tqdm():
try:
from tqdm import tqdm
except ImportError:
# If not available return a simple identity function
def tqdm(iterable, *args, **kwargs):
return iterable
return tqdm
_tqdm = _get_tqdm()
opening_pattern = re.compile(r"^\s*```python(?:\s+.*)?\s*$")
closing_pattern = re.compile(r"^\s*```\s*$")
def extract_python_snippets(markdown: str) -> list[str]:
"""
Extract all python code blocks (including their fence lines) from the markdown content.
A python block is defined as any block that starts with a line containing an opening fence
with '```python' (optionally with extra parameters) and ends with a closing fence '```'.
"""
snippets = []
inside_block = False
current_snippet = []
for line in markdown.splitlines(keepends=True):
if not inside_block:
if opening_pattern.match(line):
inside_block = True
current_snippet = [line]
else:
current_snippet.append(line)
if closing_pattern.match(line):
inside_block = False
snippets.append("".join(current_snippet))
current_snippet = []
return snippets
def translate_snippet(python_snippet: str) -> str:
"""Translate a python code block into a TypeScript code block using Langchain.
The response is expected to be a properly fenced TypeScript code block (i.e.
starting with ```typescript and ending with ```).
"""
ai_message = model.invoke(
[
{
"role": "system",
"content": (
f"You have access to the following up-to-date example TypeScript code "
f"snippets that show examples of building with langgraph "
f"and langchain:\n\n{reference_snippets}\n\n"
"Use this context to translate the following Python code to equivalent "
"TypeScript. Ensure that your output is a valid fenced TypeScript "
"code block (i.e. starts with ```typescript and ends with ```)."
),
},
{
"role": "user",
"content": f"Translate this Python snippet to TypeScript:\n\n{python_snippet}",
},
]
)
# Use a regular expression to search for a TypeScript code block in the response.
pattern = r"```typescript\s*(.*?)\s*```"
match = re.search(pattern, ai_message.content, re.DOTALL)
if match:
# Reconstruct the code block with proper fences.
typescript_code = match.group(1).strip()
return f"```typescript\n{typescript_code}\n```"
else:
raise ValueError("No TypeScript code block found in the model's response.")
def insert_translations_into_markdown(
markdown: str, typescript_snippets: list[str]
) -> str:
"""Walks through the original markdown content and, after each
Python snippet block, inserts the corresponding translated TypeScript snippet.
It assumes that the ordering of the Python snippets
(from extract_python_snippets) matches the order they appear in the markdown.
"""
output_lines = []
lines = markdown.splitlines(keepends=True)
inside_block = False
snippet_index = 0
for line in lines:
output_lines.append(line)
if not inside_block and opening_pattern.match(line):
# We've encountered the start of a python code block.
inside_block = True
elif inside_block:
if closing_pattern.match(line):
# End of a python snippet block.
inside_block = False
if snippet_index < len(typescript_snippets):
# Insert an extra newline for clarity, then the translated TypeScript snippet.
output_lines.append("\n")
output_lines.append(typescript_snippets[snippet_index])
output_lines.append("\n")
snippet_index += 1
return "".join(output_lines)
def main(file_path: str) -> None:
# Read the markdown file.
with open(file_path, "r") as f:
markdown_content = f.read()
# 1. Extract all Python snippets.
python_snippets = extract_python_snippets(markdown_content)[:1]
# 2. Translate each Python snippet to TypeScript.
typescript_snippets = []
# Replace with .batch() for faster translation
for python_snippet in _tqdm(python_snippets):
ts_snippet = translate_snippet(python_snippet)
typescript_snippets.append(ts_snippet)
# 3. Insert the TypeScript translations after their respective Python snippets.
updated_markdown = insert_translations_into_markdown(
markdown_content, typescript_snippets
)
# Overwrite the original markdown file with the updated content.
with open(file_path, "w") as f:
f.write(updated_markdown)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Translate Python snippets in a markdown file to TypeScript and insert them after each Python snippet."
)
parser.add_argument("file_path", type=str, help="Path to the markdown file.")
args = parser.parse_args()
main(args.file_path)
+5 -6
View File
@@ -3,15 +3,16 @@
import asyncio
import glob
import os
import re
from typing import TypedDict, List, Optional
import pydantic
import re
from pydantic import BaseModel, Field
from langchain_core.rate_limiters import InMemoryRateLimiter
import yaml
from langchain.chat_models import init_chat_model
from langchain_core.rate_limiters import InMemoryRateLimiter
from mkdocs.structure.files import File
from mkdocs.structure.pages import Page
from pydantic import BaseModel, Field
from yaml import SafeLoader
from _scripts.notebook_hooks import _on_page_markdown_with_config
@@ -210,9 +211,7 @@ async def process_nav_items(nav_items: list[NavItem]) -> list[NavItem]:
# Remove any items that start with http:// or https:// looking only for
# local file at this stages.
nav_items = [
item
for item in nav_items
if not item["url"].startswith(("http://", "https://"))
item for item in nav_items if not item["url"].startswith(("http://", "https://"))
]
# Process items in parallel
tasks = [process_single_item(item) for item in nav_items]
-181
View File
@@ -1,181 +0,0 @@
"""Logic to identify and transform cross-reference links in markdown files.
This module allows supporting custom markdown syntax for "autolinks". These are links
that will be transformed based on the current scope context, such as "global", "python",
or "js" into an appropriate markdown link format.
For example,
```markdown
@[StateGraph]
```
May be transformed into:
```markdown
[StateGraph](some_path/api-reference/state-graph.md)
```
The transformation value depends on the scope in which the link is used.
"""
import logging
import re
from typing import Optional
from _scripts.link_map import SCOPE_LINK_MAPS
logger = logging.getLogger(__name__)
def _transform_link(
link_name: str, scope: str, file_path: str, line_number: int, custom_title: Optional[str] = None
) -> Optional[str]:
"""Transform a cross-reference link based on the current scope.
Args:
link_name: The name of the link to transform (e.g., "StateGraph").
scope: The current scope context ("global", "python", "js", etc.).
file_path: The file path for error reporting.
line_number: The line number for error reporting.
custom_title: Optional custom title for the link. If None, uses link_name.
Returns:
A formatted markdown link if the link is found in the scope mapping,
None otherwise.
Example:
>>> _transform_link("StateGraph", "python", "file.md", 5)
"[StateGraph](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph)"
>>> _transform_link("StateGraph", "python", "file.md", 5, "Custom Title")
"[Custom Title](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph)"
>>> _transform_link("unknown-link", "python", "file.md", 5)
None
"""
if scope == "global":
# Special scope that is composed of both Python and JS links
# For now, we will substitute in the python scope!
# But we need to add support for handling both scopes.
scope = "python"
logger.error(
"Encountered unhandled 'global' scope. Defaulting to 'python'."
"In file: %s, line %d, link_name: %s",
file_path,
line_number,
link_name,
)
link_map = SCOPE_LINK_MAPS.get(scope, {})
url = link_map.get(link_name)
if url:
title = custom_title if custom_title is not None else link_name
return f"[{title}]({url})"
else:
# Log error with file location information
logger.info(
# Using %s
"Link '%s' not found in scope '%s'. "
"In file: %s, line %d. Available links in scope: %s",
link_name,
scope,
file_path,
line_number,
list(link_map.keys() if link_map else []),
)
return None
CONDITIONAL_FENCE_PATTERN = re.compile(
r"""
^ # Start of line
(?P<indent>[ \t]*) # Optional indentation (spaces or tabs)
::: # Literal fence marker
(?P<language>\w+)? # Optional language identifier (named group: language)
\s* # Optional trailing whitespace
$ # End of line
""",
re.VERBOSE,
)
CROSS_REFERENCE_PATTERN = re.compile(
r"""
(?: # Non-capturing group for two possible formats:
@\[ # @ symbol followed by opening bracket for title
(?P<title>[^\]]+) # Custom title - one or more non-bracket characters
\] # Closing bracket for title
\[ # Opening bracket for link name
(?P<link_name_with_title>[^\]]+) # Link name - one or more non-bracket characters
\] # Closing bracket for link name
| # OR
@\[ # @ symbol followed by opening bracket
(?P<link_name>[^\]]+) # Link name - one or more non-bracket characters
\] # Closing bracket
)
""",
re.VERBOSE,
)
def _replace_autolinks(markdown: str, file_path: str, *, default_scope: str = "python") -> str:
"""Preprocess markdown lines to handle @[links] with conditional fence scopes.
This function processes markdown content to transform @[link_name] references
based on the current conditional fence scope. Conditional fences use the
syntax :::language to define scope boundaries.
Args:
markdown: The markdown content to process.
file_path: The file path for error reporting.
default_scope: The default scope to use if no scope is matched.
Returns:
Processed markdown content with @[references] transformed to proper
markdown links or left unchanged if not found.
Example:
Input:
"@[StateGraph]\\n:::python\\n@[Command]\\n:::\\n"
Output:
"[StateGraph](url)\\n:::python\\n[Command](url)\\n:::\\n"
"""
# Track the current scope context
current_scope = default_scope
lines = markdown.splitlines(keepends=True)
processed_lines = []
for line_number, line in enumerate(lines, 1):
line_stripped = line.strip()
# Check if this line defines a new conditional fence scope
fence_match = CONDITIONAL_FENCE_PATTERN.match(line_stripped)
if fence_match:
language = fence_match.group("language")
# Set scope to the specified language, or reset to global if no language
current_scope = language.lower() if language else default_scope
processed_lines.append(line)
continue
# Transform all @[link_name] references in this line based on current scope
def replace_cross_reference(match: re.Match[str]) -> str:
"""Replace a single @[link_name] with the scoped equivalent."""
# Check if this is the @[title][ref] format or @[ref] format
title = match.group("title")
if title is not None:
# This is @[title][ref] format
link_name = match.group("link_name_with_title")
custom_title = title
else:
# This is @[ref] format
link_name = match.group("link_name")
custom_title = None
transformed = _transform_link(
link_name, current_scope, file_path, line_number, custom_title
)
return transformed if transformed is not None else match.group(0)
transformed_line = CROSS_REFERENCE_PATTERN.sub(replace_cross_reference, line)
processed_lines.append(transformed_line)
return "".join(processed_lines)
@@ -1,236 +0,0 @@
"""Translate Python markdown to TypeScript and/or consolidate Python-JS markdown into a single document."""
import argparse
import requests
from langchain_anthropic import ChatAnthropic
from textwrap import dedent
# Load reference TypeScript snippets
URL = "https://gist.githubusercontent.com/dqbd/b35d49e2ceec80e654fe1c5ab61ec477/raw/f4768aeedb67628190a4e06d063a938afc8e7672/snippets.md"
response = requests.get(URL)
response.raise_for_status()
reference_snippets = response.text
# Initialize model
model = ChatAnthropic(model="claude-sonnet-4-0", max_tokens=64_000)
FLUENT_INTERFACE_PROMPT = (
"CRITICAL: Always use method chaining (fluent interface) for StateGraph operations in TypeScript. "
"Never create separate variables for the graph builder or call methods individually. "
"The fluent interface provides better type safety and is the preferred pattern.\n\n"
"CORRECT examples with fluent interface:\n"
+ dedent(
"""
```typescript
const graph = new StateGraph(MyState)
.addNode('node1', node1)
.addNode('node2', node2)
.addEdge(START, 'node1')
.addEdge('node1', 'node2')
.addEdge('node2', END)
.compile()
```
```typescript
const graph = new StateGraph(MyState)
.addNode('chatbot', chatbot)
.addEdge(START, 'chatbot')
.addEdge('chatbot', END)
.compile()
```
```typescript
const graph = new StateGraph(MyState)
.addNode('chatbot', chatbot)
.addEdge(START, 'chatbot')
.addEdge('chatbot', END)
.compile()
```
"""
)
+ "\n"
+ "INCORRECT examples to avoid:\n"
+ dedent(
"""
```typescript
// WRONG: Creating separate builder variable
const graphBuilder = new StateGraph(MyState)
graphBuilder.addNode('node1', node1)
graphBuilder.addEdge(START, 'node1')
const graph = graphBuilder.compile()
```
```typescript
// WRONG: Using Python-style method names
const workflow = new StateGraph(MyState)
workflow.add_node('node1', node1)
workflow.add_edge(START, 'node1')
const graph = workflow.compile()
```
```typescript
// WRONG: Calling methods individually
const graphBuilder = new StateGraph(MyState)
graphBuilder.addNode('chatbot', chatbot)
graphBuilder.addEdge(START, 'chatbot')
graphBuilder.addEdge('chatbot', END)
const graph = graphBuilder.compile()
```
"""
)
+ "\n"
+ "Key rules:\n"
+ "- Always chain methods directly on the StateGraph constructor\n"
+ "- Use camelCase method names (addNode, addEdge, not add_node, add_edge)\n"
+ "- Always end with .compile()\n"
+ "- Never store the builder in a separate variable\n"
)
TRANSLATION_PROMPT = (
"You are a helpful assistant that translates Python-based technical "
"documentation written in Markdown to equivalent TypeScript-based documentation. "
"The input is a Markdown file written in mkdocs format. It contains "
"Python code snippets embedded in prose. "
"Your task is to rewrite the content by translating the Python code to "
"idiomatic TypeScript, using the provided TypeScript reference snippets "
"to ensure accurate and consistent usage (e.g., correct imports, function "
"names, and patterns). "
"Remove the original Python code and replace it with the corresponding "
"TypeScript version. "
"Do not alter the surrounding prose unless a change is necessary to "
"reflect differences between Python and TypeScript. "
"Preserve the structure and formatting of the original Markdown document. "
"Do not make stylistic or structural changes unless they directly support "
"the translation. "
"Use the reference TypeScript snippets as guidance whenever possible to "
"maintain alignment with existing conventions.\n\n"
"IMPORTANT REQUIREMENTS:\n"
"- Use Zod for state definition for StateGraph. Avoid using Annotation since it will be deprecated in the future.\n"
"- ALWAYS use fluent interface (method chaining) for StateGraph operations - this is CRITICAL\n"
"- Never create separate variables for graph builders\n"
"- Always chain methods directly on the StateGraph constructor and end with .compile()\n\n"
f"{FLUENT_INTERFACE_PROMPT}\n\n"
f"Here are the reference TypeScript snippets:\n\n{reference_snippets}\n\n"
)
CONSOLIDATION_PROMPT = (
"You are a helpful assistant that consolidates parallel Python and JavaScript (TypeScript) technical documentation "
"written in Markdown into a single unified Markdown document. "
"The input consists of two documents: the first is for Python users, and the second is for JavaScript/TypeScript users. "
"Your task is to merge these into one Markdown file using language-specific fenced blocks to separate the content where needed. "
"Use the following syntax to distinguish content for each language:\n\n"
":::python\n"
"# Python-specific content\n"
":::\n\n"
":::js\n"
"# JavaScript/TypeScript-specific content\n"
":::\n\n"
"Follow these consolidation rules:\n"
"- When content (prose or code) is the same or nearly identical in both versions, include it only once—outside of any fenced block.\n"
"- When content differs between the Python and JS versions, wrap each version in its corresponding fenced block.\n"
"- Prefer **paragraph-level separation** of language-specific content. Do not combine Python and JS snippets or terminology in the same sentence or paragraph using conditional phrases.\n"
" For example, avoid inline constructs like:\n"
" `The :::python add_messages ::: :::js reducer ::: function...`\n"
" Instead, write two distinct paragraphs:\n\n"
" :::python\n"
" The `add_messages` function in our `State` will append the LLM's response messages to whatever messages are already in the state.\n"
" ::: \n\n"
" :::js\n"
" The `reducer` function in our `StateAnnotation` will append the LLM's response messages to whatever messages are already in the state.\n"
" :::\n\n"
"- Preserve the overall structure, ordering, and formatting of the original Markdown documents.\n"
"- Do not rephrase or unify content unless it is logically and semantically identical.\n"
"- Use the fenced blocks for both prose and code as needed, and ensure output is clean, readable Markdown suitable for tools that parse these directives.\n"
"Your goal is to produce a cleanly merged documentation file that serves both Python and JavaScript users without redundancy, while maximizing clarity and separation of language-specific details."
)
def translate_python_to_ts(markdown_content: str) -> str:
response = model.invoke(
[
{
"role": "system",
"content": TRANSLATION_PROMPT,
"cache_control": {"type": "ephemeral"},
},
{"role": "user", "content": markdown_content},
]
)
return response.content
def consolidate_python_and_ts(combined_content: str) -> str:
response = model.invoke(
[
{
"role": "system",
"content": CONSOLIDATION_PROMPT,
"cache_control": {"type": "ephemeral"},
},
{"role": "user", "content": combined_content},
]
)
return response.content
def main(file_path: str, translate_only: bool, consolidate_only: bool) -> None:
with open(file_path, "r", encoding="utf-8") as f:
markdown_content = f.read()
if translate_only:
translated = translate_python_to_ts(markdown_content)
output_path = file_path.replace(".md", ".translated.md")
with open(output_path, "w", encoding="utf-8") as f:
f.write(translated)
print(f"Translated JS/TS version written to: {output_path}")
elif consolidate_only:
consolidated = consolidate_python_and_ts(markdown_content)
with open(file_path, "w", encoding="utf-8") as f:
f.write(consolidated)
print(f"Consolidated content written to: {file_path}")
else:
# Default behavior: translate first, then consolidate both
translated = translate_python_to_ts(markdown_content)
combined = f"{markdown_content.strip()}\n\n\n{translated.strip()}"
consolidated = consolidate_python_and_ts(combined)
with open(file_path, "w", encoding="utf-8") as f:
f.write(consolidated)
print(f"Translated and consolidated content written to: {file_path}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description=(
"Translate Python markdown to TypeScript and/or consolidate "
"Python-JS markdown into one file."
)
)
parser.add_argument("file_path", type=str, help="Path to the markdown file.")
parser.add_argument(
"--translate-only",
action="store_true",
help="Only generate the JS translation.",
)
parser.add_argument(
"--consolidate-only",
action="store_true",
help="Only consolidate pre-paired Python and JS content.",
)
args = parser.parse_args()
if args.translate_only and args.consolidate_only:
raise ValueError(
"Cannot use both --translate-only and --consolidate-only at the same time."
)
main(
args.file_path,
translate_only=args.translate_only,
consolidate_only=args.consolidate_only,
)
@@ -1,6 +0,0 @@
.prettierrc
.eslint.config.mjs
package.json
README.md
tsconfig.json
yarn.lock
@@ -1,19 +0,0 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"quoteProps": "as-needed",
"jsxSingleQuote": false,
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "always",
"requirePragma": false,
"insertPragma": false,
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css",
"vueIndentScriptAndStyle": false,
"endOfLine": "lf"
}
@@ -1 +0,0 @@
# \_codeblocks
@@ -1,14 +0,0 @@
import js from "@eslint/js";
import globals from "globals";
import tseslint from "typescript-eslint";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
plugins: { js },
extends: ["js/recommended"],
languageOptions: { globals: globals.browser },
},
tseslint.configs.recommended,
]);
@@ -1,27 +0,0 @@
{
"name": "_codeblocks",
"packageManager": "yarn@4.6.0",
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"format:fix": "prettier --write . --fix"
},
"dependencies": {
"@langchain/anthropic": "^0.3.24",
"@langchain/core": "^0.3.66",
"@langchain/langgraph": "^0.3.11",
"@langchain/langgraph-api": "^0.0.52",
"@langchain/langgraph-sdk": "^0.0.102",
"@langchain/openai": "^0.6.3",
"zod": "^4.0.10"
},
"devDependencies": {
"@eslint/js": "^9.32.0",
"eslint": "^9.32.0",
"globals": "^16.3.0",
"jiti": "^2.5.1",
"typescript": "^5.8.3",
"typescript-eslint": "^8.38.0"
}
}
@@ -1,114 +0,0 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "esnext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "libReplacement": true, /* Enable lib replacement. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "nodenext", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "nodenext", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "noUncheckedSideEffectImports": true, /* Check side effect imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
// "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": false, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
""
}
}
File diff suppressed because it is too large Load Diff
@@ -1,150 +0,0 @@
#!/usr/bin/env python
"""Extracts typescript code blocks from a markdown file."""
import argparse
import json
import re
import os
from typing import List, TypedDict, Literal
class CodeBlock(TypedDict):
"""A code block extracted from a markdown file."""
starting_line: int
"""The line number where the code block starts in the source file"""
ending_line: int
"""The line number where the code block ends in the source file"""
indentation: int
"""Number of spaces/tabs used for indentation of the code block"""
source_file: str
"""Path to the markdown file containing this code block"""
frontmatter: str
"""Any metadata or frontmatter specified after the opening code fence"""
code: str
"""The actual code content within the code block"""
language: str
"""The language of the code block (e.g. typescript, javascript)"""
def extract_code_blocks(markdown_content: str, source_file: str) -> List[CodeBlock]:
"""Extracts code blocks from a markdown file.
Args:
markdown_content: The content of the markdown file.
source_file: The path to the markdown file.
Returns:
A list of TypedDicts, where each dict represents a code block.
"""
# Regex to find code blocks with specified languages, capturing indentation
# and frontmatter.
pattern = re.compile(
r"^(?P<indentation>\s*)```(?P<language>typescript|javascript|ts|js)(?P<frontmatter>[^\n]*)\n(?P<code>.*?)\n^(?P=indentation)```\s*$",
re.DOTALL | re.MULTILINE,
)
code_blocks: List[CodeBlock] = []
for match in pattern.finditer(markdown_content):
start_pos = match.start()
# Calculate line numbers
starting_line = markdown_content.count("\n", 0, start_pos) + 1
ending_line = starting_line + match.group(0).count("\n")
indentation_str = match.group("indentation")
code_block: CodeBlock = {
"starting_line": starting_line,
"ending_line": ending_line,
"indentation": len(indentation_str),
"source_file": source_file,
"frontmatter": match.group("frontmatter").strip(),
"code": match.group("code"),
"language": match.group("language"),
}
code_blocks.append(code_block)
return code_blocks
def dump_code_blocks(input_file: str, output_file: str, format: Literal["json", "inline"]) -> None:
"""Function to extract and save code blocks from a markdown file.
Args:
input_file: Path to the input markdown file.
output_file: Path to the output JSON file for the extracted code blocks.
format: Output format - either "json" or "inline"
"""
with open(input_file, "r", encoding="utf-8") as f:
markdown_content = f.read()
extracted_code = extract_code_blocks(markdown_content, input_file)
if len(extracted_code) == 0:
print(f"No code blocks found in {input_file}")
return
if format == "json":
with open(output_file, "w", encoding="utf-8") as f:
json.dump(extracted_code, f, indent=2)
elif format == "inline":
with open(output_file, "w", encoding="utf-8") as f:
for code_block in extracted_code:
f.write(f"// {json.dumps({k:v for k,v in code_block.items() if k != 'code'})}\n")
f.write("\n")
f.write(code_block["code"])
f.write("\n")
print(f"Extracted {len(extracted_code)} code blocks from {input_file} to {output_file}")
def main(input_path: str, output_path: str, format: Literal["json", "inline"]) -> None:
"""Main function to extract code blocks from a markdown file.
Args:
input_file: Path to the input markdown file.
output_file: Path to the output JSON file for the extracted code blocks.
format: Output format - either "json" or "inline"
"""
# Check if input path is a directory
if os.path.isdir(input_path):
if os.path.isfile(output_path):
raise ValueError("If input_path is a directory, output_path must also be a directory")
if not os.path.isdir(output_path):
os.makedirs(output_path, exist_ok=True)
# Process each markdown file in the directory recursively
for root, _, files in os.walk(input_path):
for filename in files:
if filename.endswith(".md"):
# Get relative path to maintain directory structure
rel_path = os.path.relpath(root, input_path)
input_file = os.path.join(root, filename)
# Create output directory if it doesn't exist
output_dir = os.path.join(output_path, rel_path)
os.makedirs(output_dir, exist_ok=True)
output_file = os.path.join(output_dir, filename.replace(".md", ".ts"))
dump_code_blocks(input_file, output_file, format)
else:
# Process single file
dump_code_blocks(input_path, output_path, format)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Extract typescript code blocks from a markdown file."
)
parser.add_argument(
"input_file",
help="Path to the input markdown file.",
)
parser.add_argument(
"output_file",
help="Path to the output JSON file for the extracted code blocks.",
)
parser.add_argument(
"--format",
choices=["json", "inline"],
default="json",
help="Output format - either 'json' or 'inline'",
)
args = parser.parse_args()
main(args.input_file, args.output_file, args.format)
-143
View File
@@ -1,143 +0,0 @@
"""Link mapping for cross-reference resolution across different scopes.
This module provides link mappings for different language/framework scopes
to resolve @[link_name] references to actual URLs.
"""
# Python-specific link mappings
# Python-specific link mappings
PYTHON_LINK_MAP = {
"StateGraph": "reference/graphs/#langgraph.graph.StateGraph",
"add_conditional_edges": "reference/graphs/#langgraph.graph.StateGraph.add_conditional_edges",
"add_edge": "reference/graphs/#langgraph.graph.StateGraph.add_edge",
"add_node": "reference/graphs/#langgraph.graph.StateGraph.add_node",
"add_messages": "reference/messages/#langgraph.graph.message.add_messages",
"ToolNode": "reference/prebuilt/#langgraph.prebuilt.tool_node.ToolNode",
"CompiledStateGraph.astream": "reference/graphs/#langgraph.graph.state.CompiledStateGraph.astream",
"Pregel.astream": "reference/graphs/#langgraph.pregel.Pregel.astream",
"AsyncPostgresSaver": "reference/checkpoints/#langgraph.checkpoint.postgres.aio.AsyncPostgresSaver",
"AsyncSqliteSaver": "reference/checkpoints/#langgraph.checkpoint.sqlite.aio.AsyncSqliteSaver",
"BaseCheckpointSaver": "reference/checkpoints/#langgraph.checkpoint.base.BaseCheckpointSaver",
"BaseStore": "reference/stores/#langgraph.store.base.BaseStore",
"BaseStore.put": "reference/stores/#langgraph.store.base.BaseStore.put",
"BinaryOperatorAggregate": "reference/channels/#langgraph.channels.BinaryOperatorAggregate",
"CipherProtocol": "reference/checkpoints/#langgraph.checkpoint.serde.base.CipherProtocol",
"client.runs.stream": "reference/client/#langgraph_sdk.client.RunsClient.stream",
"client.runs.wait": "reference/client/#langgraph_sdk.client.RunsClient.wait",
"client.threads.get_history": "reference/client/#langgraph_sdk.client.ThreadsClient.get_history",
"client.threads.update_state": "reference/client/#langgraph_sdk.client.ThreadsClient.update_state",
"Command": "reference/types/#langgraph.types.Command",
"CompiledStateGraph": "reference/graphs/#langgraph.graph.state.CompiledStateGraph",
"create_react_agent": "reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent",
"create_supervisor": "reference/supervisor/#langgraph_supervisor.supervisor.create_supervisor",
"EncryptedSerializer": "reference/checkpoints/#langgraph.checkpoint.serde.encrypted.EncryptedSerializer",
"entrypoint.final": "reference/functions/#langgraph.func.entrypoint.final",
"entrypoint": "reference/functions/#langgraph.func.entrypoint",
"from_pycryptodome_aes": "reference/checkpoints/#langgraph.checkpoint.serde.encrypted.EncryptedSerializer.from_pycryptodome_aes",
# "getContextVariable": "<insert-ref>",
"get_state_history": "reference/graphs/#langgraph.graph.state.CompiledStateGraph.get_state_history",
"get_stream_writer": "reference/config/#langgraph.config.get_stream_writer",
"HumanInterrupt": "reference/prebuilt/#langgraph.prebuilt.interrupt.HumanInterrupt",
"InjectedState": "reference/prebuilt/#langgraph.prebuilt.InjectedState",
"InMemorySaver": "reference/checkpoints/#langgraph.checkpoint.memory.InMemorySaver",
"interrupt": "reference/graphs/#langgraph.graph.interrupt",
"CompiledStateGraph.invoke": "reference/graphs/#langgraph.graph.state.CompiledStateGraph.invoke",
"JsonPlusSerializer": "reference/checkpoints/#langgraph.checkpoint.serde.jsonplus.JsonPlusSerializer",
"langgraph.json": "reference/configuration/#configuration-file",
"LastValue": "reference/channels/#langgraph.channels.LastValue",
# "MemorySaver": "<insert-ref>",
# "messagesStateReducer": "<insert-ref>",
"PostgresSaver": "reference/checkpoints/#langgraph.checkpoint.postgres.PostgresSaver",
"Pregel": "reference/graphs/#langgraph.pregel.Pregel",
"Pregel.stream": "reference/graphs/#langgraph.pregel.Pregel.stream",
"pre_model_hook": "reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent",
"protocol": "reference/checkpoints/#langgraph.checkpoint.serde.base.SerializerProtocol",
"Send": "reference/types/#langgraph.types.Send",
"SerializerProtocol": "reference/checkpoints/#langgraph.checkpoint.serde.base.SerializerProtocol",
"SqliteSaver": "reference/checkpoints/#langgraph.checkpoint.sqlite.SqliteSaver",
"START": "reference/constants/#langgraph.constants.START",
"CompiledStateGraph.stream": "reference/graphs/#langgraph.graph.state.CompiledStateGraph.stream",
"task": "reference/functions/#langgraph.func.task",
"Topic": "reference/channels/#langgraph.channels.Topic",
"update_state": "reference/graphs/#langgraph.graph.state.CompiledStateGraph.update_state",
}
# JavaScript-specific link mappings
JS_LINK_MAP = {
"Auth": "reference/classes/sdk_auth.Auth.html",
"StateGraph": "reference/classes/langgraph.StateGraph.html",
"add_conditional_edges": "reference/functions/langgraph_StateGraph.addConditionalEdges.html",
"add_edge": "reference/functions/langgraph_StateGraph.addEdge.html",
"add_node": "reference/functions/langgraph_StateGraph.addNode.html",
"add_messages": "reference/functions/langgraph_message.addMessages.html",
"ToolNode": "reference/classes/langgraph_prebuilt.ToolNode.html",
"CompiledStateGraph.astream()": "reference/functions/langgraph_CompiledStateGraph.astream.html",
"Pregel.astream": "reference/functions/langgraph_Pregel.astream.html",
"AsyncPostgresSaver": "reference/classes/langgraph_checkpoint_postgres_aio.AsyncPostgresSaver.html",
"AsyncSqliteSaver": "reference/classes/langgraph_checkpoint_sqlite_aio.AsyncSqliteSaver.html",
"BaseCheckpointSaver": "reference/classes/langgraph_checkpoint_base.BaseCheckpointSaver.html",
"BaseStore": "reference/classes/langgraph_store_base.BaseStore.html",
"BaseStore.put": "reference/functions/langgraph_store_base.BaseStore.put.html",
"BinaryOperatorAggregate": "reference/classes/langgraph_channels.BinaryOperatorAggregate.html",
"CipherProtocol": "reference/classes/langgraph_checkpoint_serde_base.CipherProtocol.html",
"client.runs.stream": "reference/functions/langgraph_sdk_client.RunsClient.stream.html",
"client.runs.wait": "reference/functions/langgraph_sdk_client.RunsClient.wait.html",
"client.threads.get_history": "reference/functions/langgraph_sdk_client.ThreadsClient.getHistory.html",
"client.threads.update_state": "reference/functions/langgraph_sdk_client.ThreadsClient.updateState.html",
"Command": "reference/classes/langgraph.Command.html",
"CompiledStateGraph": "reference/classes/langgraph.CompiledStateGraph.html",
"create_react_agent": "reference/functions/langgraph_prebuilt.createReactAgent.html",
"create_supervisor": "reference/functions/langgraph_supervisor.createSupervisor.html",
"EncryptedSerializer": "reference/classes/langgraph_checkpoint_serde_encrypted.EncryptedSerializer.html",
"entrypoint.final": "reference/functions/langgraph_func.entrypoint.final.html",
"entrypoint": "reference/functions/langgraph_func.entrypoint.html",
"from_pycryptodome_aes": "reference/functions/langgraph_checkpoint_serde_encrypted.EncryptedSerializer.fromPycryptodomeAes.html",
"getContextVariable": "https://v03.api.js.langchain.com/functions/_langchain_core.context.getContextVariable.html",
"get_state_history": "reference/functions/langgraph_CompiledStateGraph.getStateHistory.html",
"get_stream_writer": "reference/functions/langgraph_config.getStreamWriter.html",
"HumanInterrupt": "reference/classes/langgraph_prebuilt.HumanInterrupt.html",
"InjectedState": "reference/classes/langgraph_prebuilt.InjectedState.html",
"InMemorySaver": "reference/classes/langgraph_checkpoint_memory.InMemorySaver.html",
"interrupt": "reference/functions/langgraph.interrupt-2.html",
"CompiledStateGraph.invoke": "reference/functions/langgraph_CompiledStateGraph.invoke.html",
"JsonPlusSerializer": "reference/classes/langgraph_checkpoint_serde_jsonplus.JsonPlusSerializer.html",
"langgraph.json": "reference/configuration.html",
"LastValue": "reference/classes/langgraph_channels.LastValue.html",
"MemorySaver": "reference/classes/checkpoint.MemorySaver.html",
"messagesStateReducer": "reference/functions/langgraph.messagesStateReducer.html",
"PostgresSaver": "reference/classes/langgraph_checkpoint_postgres.PostgresSaver.html",
"Pregel": "reference/classes/langgraph.Pregel.html",
"Pregel.stream": "reference/functions/langgraph_Pregel.stream.html",
"pre_model_hook": "reference/functions/langgraph_prebuilt.createReactAgent.html",
"protocol": "reference/classes/langgraph_checkpoint_serde_base.SerializerProtocol.html",
"Send": "reference/classes/langgraph.Send.html",
"SerializerProtocol": "reference/classes/langgraph_checkpoint_serde_base.SerializerProtocol.html",
"SqliteSaver": "reference/classes/langgraph_checkpoint_sqlite.SqliteSaver.html",
"START": "reference/constants.html#START",
"CompiledStateGraph.stream": "reference/functions/langgraph_CompiledStateGraph.stream.html",
"task": "reference/functions/langgraph_func.task.html",
"Topic": "reference/classes/langgraph_channels.Topic.html",
"update_state": "reference/functions/langgraph_CompiledStateGraph.updateState.html",
}
# TODO: Allow updating these to localhost for local development
PY_REFERENCE_HOST = "https://langchain-ai.github.io/langgraph/"
JS_REFERENCE_HOST = "https://langchain-ai.github.io/langgraphjs/"
for key, value in PYTHON_LINK_MAP.items():
# Ensure the link is absolute
if not value.startswith("http"):
PYTHON_LINK_MAP[key] = f"{PY_REFERENCE_HOST}{value}"
for key, value in JS_LINK_MAP.items():
# Ensure the link is absolute
if not value.startswith("http"):
JS_LINK_MAP[key] = f"{JS_REFERENCE_HOST}{value}"
# Global scope is assembled from the Python and JS mappings
# Combined mapping by scope
SCOPE_LINK_MAPS = {
"python": PYTHON_LINK_MAP,
"js": JS_LINK_MAP,
}
+38 -180
View File
@@ -3,7 +3,6 @@
Lifecycle events: https://www.mkdocs.org/dev-guide/plugins/#events
"""
import json
import logging
import os
import posixpath
@@ -16,7 +15,6 @@ 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.handle_auto_links import _replace_autolinks
from _scripts.notebook_convert import convert_notebook
logger = logging.getLogger(__name__)
@@ -35,49 +33,43 @@ REDIRECT_MAP = {
"how-tos/streaming-from-final-node.ipynb": "how-tos/streaming-specific-nodes.ipynb",
"how-tos/streaming-events-from-within-tools-without-langchain.ipynb": "how-tos/streaming-events-from-within-tools.ipynb#example-without-langchain",
# graph-api
"how-tos/state-reducers.ipynb": "how-tos/graph-api.md#define-and-update-state",
"how-tos/sequence.ipynb": "how-tos/graph-api.md#create-a-sequence-of-steps",
"how-tos/branching.ipynb": "how-tos/graph-api.md#create-branches",
"how-tos/recursion-limit.ipynb": "how-tos/graph-api.md#create-and-control-loops",
"how-tos/visualization.ipynb": "how-tos/graph-api.md#visualize-your-graph",
"how-tos/input_output_schema.ipynb": "how-tos/graph-api.md#define-input-and-output-schemas",
"how-tos/pass_private_state.ipynb": "how-tos/graph-api.md#pass-private-state-between-nodes",
"how-tos/state-model.ipynb": "how-tos/graph-api.md#use-pydantic-models-for-graph-state",
"how-tos/map-reduce.ipynb": "how-tos/graph-api.md#map-reduce-and-the-send-api",
"how-tos/command.ipynb": "how-tos/graph-api.md#combine-control-flow-and-state-updates-with-command",
"how-tos/configuration.ipynb": "how-tos/graph-api.md#add-runtime-configuration",
"how-tos/node-retries.ipynb": "how-tos/graph-api.md#add-retry-policies",
"how-tos/return-when-recursion-limit-hits.ipynb": "how-tos/graph-api.md#impose-a-recursion-limit",
"how-tos/async.ipynb": "how-tos/graph-api.md#async",
"how-tos/state-reducers.ipynb": "how-tos/graph-api#define-and-update-state",
"how-tos/sequence.ipynb": "how-tos/graph-api#create-a-sequence-of-steps",
"how-tos/branching.ipynb": "how-tos/graph-api#create-branches",
"how-tos/recursion-limit.ipynb": "how-tos/graph-api#create-and-control-loops",
"how-tos/visualization.ipynb": "how-tos/graph-api#visualize-your-graph",
"how-tos/input_output_schema.ipynb": "how-tos/graph-api#define-input-and-output-schemas",
"how-tos/pass_private_state.ipynb": "how-tos/graph-api#pass-private-state-between-nodes",
"how-tos/state-model.ipynb": "how-tos/graph-api#use-pydantic-models-for-graph-state",
"how-tos/map-reduce.ipynb": "how-tos/graph-api/#map-reduce-and-the-send-api",
"how-tos/command.ipynb": "how-tos/graph-api/#combine-control-flow-and-state-updates-with-command",
"how-tos/configuration.ipynb": "how-tos/graph-api/#add-runtime-configuration",
"how-tos/node-retries.ipynb": "how-tos/graph-api/#add-retry-policies",
"how-tos/return-when-recursion-limit-hits.ipynb": "how-tos/graph-api/#impose-a-recursion-limit",
"how-tos/async.ipynb": "how-tos/graph-api/#async",
# memory how-tos
"how-tos/memory/manage-conversation-history.ipynb": "how-tos/memory/add-memory.md",
"how-tos/memory/delete-messages.ipynb": "how-tos/memory/add-memory.md#delete-messages",
"how-tos/memory/add-summary-conversation-history.ipynb": "how-tos/memory/add-memory.md#summarize-messages",
"how-tos/memory.ipynb": "how-tos/memory/add-memory.md",
"agents/memory.ipynb": "how-tos/memory/add-memory.md",
"how-tos/memory/manage-conversation-history.ipynb": "how-tos/memory.ipynb",
"how-tos/memory/delete-messages.ipynb": "how-tos/memory.ipynb#delete-messages",
"how-tos/memory/add-summary-conversation-history.ipynb": "how-tos/memory.ipynb#summarize-messages",
# subgraph how-tos
"how-tos/subgraph-transform-state.ipynb": "how-tos/subgraph.md#different-state-schemas",
"how-tos/subgraphs-manage-state.ipynb": "how-tos/subgraph.md#add-persistence",
"how-tos/subgraph-transform-state.ipynb": "how-tos/subgraph.ipynb#different-state-schemas",
"how-tos/subgraphs-manage-state.ipynb": "how-tos/subgraph.ipynb#add-persistence",
# persistence how-tos
"how-tos/persistence_postgres.ipynb": "how-tos/memory/add-memory.md#use-in-production",
"how-tos/persistence_mongodb.ipynb": "how-tos/memory/add-memory.md#use-in-production",
"how-tos/persistence_redis.ipynb": "how-tos/memory/add-memory.md#use-in-production",
"how-tos/subgraph-persistence.ipynb": "how-tos/memory/add-memory.md#use-with-subgraphs",
"how-tos/cross-thread-persistence.ipynb": "how-tos/memory/add-memory.md#add-long-term-memory",
"how-tos/persistence_postgres.ipynb": "how-tos/persistence.ipynb#use-in-production",
"how-tos/persistence_mongodb.ipynb": "how-tos/persistence.ipynb#use-in-production",
"how-tos/persistence_redis.ipynb": "how-tos/persistence.ipynb#use-in-production",
"how-tos/subgraph-persistence.ipynb": "how-tos/persistence.ipynb#use-with-subgraphs",
"how-tos/cross-thread-persistence.ipynb": "how-tos/persistence.ipynb#add-long-term-memory",
"cloud/how-tos/copy_threads": "cloud/how-tos/use_threads",
"cloud/how-tos/check-thread-status": "cloud/how-tos/use_threads",
"cloud/concepts/threads.md": "concepts/persistence.md#threads",
"how-tos/persistence.ipynb": "how-tos/memory/add-memory.md",
# tool calling how-tos
"how-tos/tool-calling-errors.ipynb": "how-tos/tool-calling.ipynb#handle-errors",
"how-tos/pass-config-to-tools.ipynb": "how-tos/tool-calling.ipynb#access-config",
"how-tos/pass-run-time-values-to-tools.ipynb": "how-tos/tool-calling.ipynb#read-state",
"how-tos/update-state-from-tools.ipynb": "how-tos/tool-calling.ipynb#update-state",
"agents/tools.md": "how-tos/tool-calling.md",
# multi-agent how-tos
"how-tos/agent-handoffs.ipynb": "how-tos/multi_agent.md#handoffs",
"how-tos/multi-agent-network.ipynb": "how-tos/multi_agent.md#use-in-a-multi-agent-system",
"how-tos/multi-agent-multi-turn-convo.ipynb": "how-tos/multi_agent.md#multi-turn-conversation",
"how-tos/agent-handoffs.ipynb": "how-tos/multi_agent.ipynb#handoffs",
"how-tos/multi-agent-network.ipynb": "how-tos/multi_agent.ipynb#use-in-a-multi-agent-system",
"how-tos/multi-agent-multi-turn-convo.ipynb": "how-tos/multi_agent.ipynb#multi-turn-conversation",
# cloud redirects
"cloud/index.md": "index.md",
"cloud/how-tos/index.md": "concepts/langgraph_platform",
@@ -94,13 +86,16 @@ REDIRECT_MAP = {
"cloud/how-tos/stream_events.md": "cloud/how-tos/streaming.md#stream-events",
"cloud/how-tos/stream_debug.md": "cloud/how-tos/streaming.md#debug",
"cloud/how-tos/stream_multiple.md": "cloud/how-tos/streaming.md#stream-multiple-modes",
"cloud/concepts/streaming.md": "concepts/streaming.md",
"agents/streaming.md": "how-tos/streaming.md",
# prebuilt redirects
# prebuit redirects
"how-tos/create-react-agent.ipynb": "agents/agents.md#basic-configuration",
"how-tos/create-react-agent-memory.ipynb": "agents/memory.md",
"how-tos/create-react-agent-system-prompt.ipynb": "agents/context.md#prompts",
"how-tos/create-react-agent-hitl.ipynb": "agents/human-in-the-loop.md",
"how-tos/create-react-agent-structured-output.ipynb": "agents/agents.md#structured-output",
# Time-travel
"how-tos/human_in_the_loop/edit-graph-state.ipynb": "how-tos/human_in_the_loop/time-travel.ipynb",
# breakpoints
"how-tos/human_in_the_loop/dynamic_breakpoints.ipynb": "how-tos/human_in_the_loop/breakpoints.ipynb",
# misc
"prebuilt.md": "agents/prebuilt.md",
"reference/prebuilt.md": "reference/agents.md",
@@ -109,48 +104,11 @@ REDIRECT_MAP = {
"concepts/v0-human-in-the-loop.md": "concepts/human-in-the-loop.md",
"how-tos/index.md": "index.md",
"tutorials/introduction.ipynb": "concepts/why-langgraph.md",
"agents/deployment.md": "tutorials/langgraph-platform/local-server.md",
# deployment redirects
"how-tos/deploy-self-hosted.md": "cloud/deployment/self_hosted_data_plane.md",
"concepts/self_hosted.md": "concepts/langgraph_self_hosted_data_plane.md",
"tutorials/deployment.md": "concepts/deployment_options.md",
# assistant redirects
"cloud/how-tos/assistant_versioning.md": "cloud/how-tos/configuration_cloud.md",
"cloud/concepts/runs.md": "concepts/assistants.md#execution",
# hitl redirects
"how-tos/wait-user-input-functional.ipynb": "how-tos/use-functional-api.md",
"how-tos/review-tool-calls-functional.ipynb": "how-tos/use-functional-api.md",
"how-tos/create-react-agent-hitl.ipynb": "how-tos/human_in_the_loop/add-human-in-the-loop.md",
"agents/human-in-the-loop.md": "how-tos/human_in_the_loop/add-human-in-the-loop.md",
"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",
# LGP migration-related redirects - once LG is also migrated, we can add a redirect for the whole site
"concepts/langgraph_platform.md": "https://docs.langchain.com/langgraph-platform",
"concepts/langgraph_components.md": "https://docs.langchain.com/langgraph-platform/components",
"concepts/langgraph_server.md": "https://docs.langchain.com/langgraph-platform/langgraph-server",
"concepts/langgraph_studio.md": "https://docs.langchain.com/langgraph-platform/langgraph-studio",
"cloud/how-tos/invoke_studio.md": "https://docs.langchain.com/langgraph-platform/invoke-studio",
"concepts/langgraph_cli.md": "https://docs.langchain.com/langgraph-platform/langgraph-cli",
"cloud/how-tos/studio/quick_start.md": "https://docs.langchain.com/langgraph-platform/quick-start-studio",
"concepts/sdk.md": "https://docs.langchain.com/langgraph-platform/sdk",
"concepts/auth.md": "https://docs.langchain.com/langgraph-platform/auth",
"concepts/assistants.md": "https://docs.langchain.com/langgraph-platform/assistants",
"concepts/deployment_options.md": "https://docs.langchain.com/langgraph-platform/deployment-options",
"cloud/quick_start.md": "https://docs.langchain.com/langgraph-platform/deployment-quickstart",
"cloud/deployment/setup.md": "https://docs.langchain.com/langgraph-platform/setup-app-requirements-txt",
"concepts/langgraph_self_hosted_data_plane.md": "https://docs.langchain.com/langgraph-platform/self-hosted-data-plane",
"concepts/langgraph_self_hosted_control_plane.md": "https://docs.langchain.com/langgraph-platform/self-hosted-control-plane",
"concepts/langgraph_standalone_container.md": "https://docs.langchain.com/langgraph-platform/standalone-container",
"cloud/deployment/self_hosted_data_plane.md": "https://docs.langchain.com/langgraph-platform/deploy-self-hosted-data-plane",
"cloud/deployment/self_hosted_control_plane.md": "https://docs.langchain.com/langgraph-platform/deploy-self-hosted-control-plane",
"cloud/deployment/standalone_container.md": "https://docs.langchain.com/langgraph-platform/deploy-standalone-container",
"concepts/server-mcp.md": "https://docs.langchain.com/langgraph-platform/server-mcp",
"cloud/reference/cli.md": "https://docs.langchain.com/langgraph-platform/cli",
"cloud/how-tos/use_stream_react.md": "https://docs.langchain.com/langgraph-platform/use-stream-react",
"cloud/how-tos/generative-ui-react.md": "https://docs.langchain.com/langgraph-platform/generative-ui-react",
}
@@ -200,38 +158,6 @@ def _add_path_to_code_blocks(markdown: str, page: Page) -> str:
return code_block_pattern.sub(replace_code_block_header, markdown)
# Compiled regex patterns for better performance and readability
def _apply_conditional_rendering(md_text: str, target_language: str) -> str:
if target_language not in {"python", "js"}:
raise ValueError("target_language must be 'python' or 'js'")
pattern = re.compile(
r"(?P<indent>[ \t]*):::(?P<language>\w+)\s*\n"
r"(?P<content>((?:.*\n)*?))" # Capture the content inside the block
r"(?P=indent)[ \t]*:::" # Match closing with the same indentation + any additional whitespace
)
def replace_conditional_blocks(match: re.Match) -> str:
"""Keep active conditionals."""
language = match.group("language")
content = match.group("content")
if language not in {"python", "js"}:
# If the language is not supported, return the original block
return match.group(0)
if language == target_language:
return content
# If the language does not match, return an empty string
return ""
processed = pattern.sub(replace_conditional_blocks, md_text)
return processed
def _highlight_code_blocks(markdown: str) -> str:
"""Find code blocks with highlight comments and add hl_lines attribute.
@@ -295,7 +221,7 @@ def _highlight_code_blocks(markdown: str) -> str:
opening_fence += f" {attributes}"
if highlighted_lines:
opening_fence += f' hl_lines="{" ".join(highlighted_lines)}"'
opening_fence += f" hl_lines=\"{' '.join(highlighted_lines)}\""
return (
# The indent and opening fence
@@ -310,21 +236,6 @@ def _highlight_code_blocks(markdown: str) -> str:
return markdown
def _save_page_output(markdown: str, output_path: str):
"""Save markdown content to a file, creating parent directories if needed.
Args:
markdown: The markdown content to save
output_path: The file path to save to
"""
# Create parent directories recursively if they don't exist
os.makedirs(os.path.dirname(output_path), exist_ok=True)
# Write the markdown content to the file
with open(output_path, "w", encoding="utf-8") as f:
f.write(markdown)
def _on_page_markdown_with_config(
markdown: str,
page: Page,
@@ -340,23 +251,12 @@ def _on_page_markdown_with_config(
# logger.info("Processing Jupyter notebook: %s", page.file.src_path)
markdown = convert_notebook(page.file.abs_src_path)
target_language = kwargs.get(
"target_language",
os.environ.get("TARGET_LANGUAGE", "python")
)
# Apply cross-reference preprocessing to all markdown content
markdown = _replace_autolinks(markdown, page.file.src_path, default_scope=target_language)
# Append API reference links to code blocks
if add_api_references:
markdown = update_markdown_with_imports(markdown, page.file.abs_src_path)
# Apply highlight comments to code blocks
markdown = _highlight_code_blocks(markdown)
# Apply conditional rendering for code blocks
markdown = _apply_conditional_rendering(markdown, target_language)
# Add file path as an attribute to code blocks that are executable.
# This file path is used to associate fixtures with the executable code
# which can be used in CI to test the docs without making network requests.
@@ -370,20 +270,12 @@ def _on_page_markdown_with_config(
def on_page_markdown(markdown: str, page: Page, **kwargs: Dict[str, Any]):
finalized_markdown = _on_page_markdown_with_config(
return _on_page_markdown_with_config(
markdown,
page,
add_api_references=True,
**kwargs,
)
page.meta["original_markdown"] = finalized_markdown
output_path = os.environ.get("MD_OUTPUT_PATH")
if output_path:
file_path = os.path.join(output_path, page.file.src_path)
_save_page_output(finalized_markdown, file_path)
return finalized_markdown
# redirects
@@ -454,52 +346,18 @@ height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
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 "",
}
# 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:
def on_post_page(output: str, page: Page, config: MkDocsConfig) -> str:
"""Inject Google Tag Manager noscript tag immediately after <body>.
Args:
html: The HTML output of the page.
output: The HTML output of the page.
page: The page instance.
config: The MkDocs configuration object.
Returns:
modified HTML output with GTM code injected.
"""
html = _inject_markdown_into_html(html, page)
return _inject_gtm(html)
return _inject_gtm(output)
# Create HTML files for redirects after site dir has been built
@@ -15,10 +15,9 @@ If youre looking for other prebuilt libraries, explore the community-built op
below. These libraries can extend LangGraph's functionality in various ways.
## 📚 Available Libraries
[//]: # (This file is automatically generated using a script in docs/_scripts. Do not edit this file directly!)
:::python
{python_library_list}
{library_list}
## ✨ Contributing Your Library
@@ -29,39 +28,16 @@ To share your project, simply open a Pull Request adding an entry for your packa
**Guidelines**
- Your repo must be distributed as an installable package on PyPI 📦
- Your repo must be distributed as an installable package (e.g., PyPI for Python, npm
for JavaScript/TypeScript, etc.) 📦
- The repo should either use the Graph API (exposing a `StateGraph` instance) or
the Functional API (exposing an `entrypoint`).
- The package must include documentation (e.g., a `README.md` or docs site)
explaining how to use it.
We'll review your contribution and merge it in!
Thanks for contributing! 🚀
:::
:::js
{js_library_list}
## ✨ Contributing Your Library
Have you built an awesome open-source library using LangGraph? We'd love to feature
your project on the official LangGraph documentation pages! 🏆
To share your project, simply open a Pull Request adding an entry for your package in our [packages.yml]({langgraph_url}) file.
**Guidelines**
- Your repo must be distributed as an installable package on npm 📦
- The repo should either use the Graph API (exposing a `StateGraph` instance) or
the Functional API (exposing an `entrypoint`).
- The package must include documentation (e.g., a `README.md` or docs site)
explaining how to use it.
We'll review your contribution and merge it in!
Thanks for contributing! 🚀
:::
"""
@@ -70,18 +46,36 @@ class ResolvedPackage(TypedDict):
"""The name of the package."""
repo: str
"""Repository ID within github. Format is: [orgname]/[repo_name]."""
monorepo_path: str | None
"""Optional: The path to the package in the monorepo. Must be relative to the root of the monorepo."""
language: str
"""The language of the package. (either 'python' or 'js')"""
weekly_downloads: int | None
"""The weekly download count of the package."""
description: str
"""A brief description of what the package does."""
def generate_package_table(resolved_packages: List[ResolvedPackage]) -> str:
"""Generate the package table for the third party page.
def generate_markdown(resolved_packages: List[ResolvedPackage], language: str) -> str:
"""Generate the markdown content for the third party page.
Args:
resolved_packages: A list of resolved package information.
language: str
Returns:
The markdown content as a string.
"""
# Update the URL to the actual file once the initial version is merged
if language == "python":
langgraph_url = (
"https://github.com/langchain-ai/langgraph/blob/main/docs"
"/_scripts/third_party_page/packages.yml"
)
elif language == "js":
langgraph_url = (
"https://github.com/langchain-ai/langgraphjs/blob/main/docs"
"/_scripts/third_party/packages.yml"
)
else:
raise ValueError(f"Invalid language '{language}'. Expected 'python' or 'js'.")
sorted_packages = sorted(
resolved_packages, key=lambda p: p["weekly_downloads"] or 0, reverse=True
)
@@ -91,15 +85,7 @@ def generate_package_table(resolved_packages: List[ResolvedPackage]) -> str:
]
for package in sorted_packages:
name = f"**{package['name']}**"
monorepo_path = package.get("monorepo_path", "")
if monorepo_path:
monorepo_path = monorepo_path[1:] if monorepo_path.startswith('/') else monorepo_path
repo_url_suffix = f"/tree/main/{monorepo_path}"
else:
repo_url_suffix = ""
repo_url = f"https://github.com/{package['repo']}{repo_url_suffix}"
repo_url = f"[{package['repo']}](https://github.com/{package['repo']})"
stars_badge = (
f"https://img.shields.io/github/stars/{package['repo']}?style=social"
)
@@ -107,39 +93,13 @@ def generate_package_table(resolved_packages: List[ResolvedPackage]) -> str:
downloads = package["weekly_downloads"] or "-"
row = f"| {name} | {repo_url} | {package['description']} | {downloads} | {stars}"
rows.append(row)
return "\n".join(rows)
def generate_markdown(resolved_packages: List[ResolvedPackage]) -> str:
"""Generate the markdown content for the third party page.
Args:
resolved_packages: A list of resolved package information.
Returns:
The markdown content as a string.
"""
# Update the URL to the actual file once the initial version is merged
langgraph_url = (
"https://github.com/langchain-ai/langgraph/blob/main/docs"
"/_scripts/third_party_page/packages.yml"
)
python_library_list = generate_package_table(
[p for p in resolved_packages if p["language"] == "python"]
)
js_library_list = generate_package_table(
[p for p in resolved_packages if p["language"] == "js"]
)
markdown_content = MARKDOWN.format(
python_library_list=python_library_list,
js_library_list=js_library_list,
langgraph_url=langgraph_url,
library_list="\n".join(rows), langgraph_url=langgraph_url
)
return markdown_content
def main(input_file: str, output_file: str) -> None:
def main(input_file: str, output_file: str, language: str) -> None:
"""Main function to create the third party page.
Args:
@@ -151,7 +111,7 @@ def main(input_file: str, output_file: str) -> None:
with open(input_file, "r") as f:
resolved_packages: List[ResolvedPackage] = yaml.safe_load(f)
markdown_content = generate_markdown(resolved_packages)
markdown_content = generate_markdown(resolved_packages, language)
# Write the markdown content to the output file
with open(output_file, "w", encoding="utf-8") as f:
@@ -167,6 +127,12 @@ if __name__ == "__main__":
parser.add_argument(
"output_file", help="Path to the output file for the third party page."
)
parser.add_argument(
"--language",
choices=["python", "js"],
default="python",
help="The language for which to generate the third party page. Defaults to 'python'.",
)
args = parser.parse_args()
main(args.input_file, args.output_file)
main(args.input_file, args.output_file, args.language)
@@ -11,146 +11,101 @@ import yaml
class Package(TypedDict):
"""A TypedDict representing a package"""
name: str
"""The name of the package."""
repo: str
"""Repository ID within github. Format is: [orgname]/[repo_name]."""
monorepo_path: str | None
"""The path to the package in the monorepo. Only used for JS packages."""
description: str
"""A brief description of what the package does."""
class ResolvedPackage(Package):
weekly_downloads: int | None
"""The weekly download count of the package."""
language: str
"""The language of the package. (either 'python' or 'js')"""
HERE = pathlib.Path(__file__).parent
PACKAGES_FILE = HERE / "packages.yml"
PACKAGES = yaml.safe_load(PACKAGES_FILE.read_text())["packages"]
PACKAGES = yaml.safe_load(PACKAGES_FILE.read_text())['packages']
def _get_pypi_downloads(package: Package) -> int:
"""Retrieve the weekly download count for a package from PyPIStats."""
# First check if package exists on PyPI
pypi_url = f"https://pypi.org/pypi/{package['name']}/json"
try:
pypi_response = requests.get(pypi_url)
pypi_response.raise_for_status()
except requests.exceptions.HTTPError:
raise AssertionError(f"Package {package['name']} does not exist on PyPI")
# Get first release date
pypi_data = pypi_response.json()
releases = pypi_data["releases"]
first_release_date = None
for version_releases in releases.values():
if version_releases: # Some versions may be empty lists
upload_time = datetime.fromisoformat(version_releases[0]["upload_time"])
if first_release_date is None or upload_time < first_release_date:
first_release_date = upload_time
if first_release_date is None:
raise AssertionError(f"Package {package['name']} has no releases yet")
# If package was published in last 48 hours, skip download stats
if (datetime.now() - first_release_date).total_seconds() >= 48 * 3600:
url = f"https://pypistats.org/api/packages/{package['name']}/overall"
response = requests.get(url)
response.raise_for_status()
data = response.json()
sorted_data = sorted(
data["data"],
key=lambda x: datetime.strptime(x["date"], "%Y-%m-%d"),
reverse=True,
)
# Sum the last 7 days of downloads
return sum(entry["downloads"] for entry in sorted_data[:7])
else:
return None
def _get_npm_downloads(package: Package) -> int:
"""Retrieve the weekly download count for a package on the npm registry."""
# Check if package exists on the npm registry
npm_url = f"https://registry.npmjs.org/{package['name']}"
try:
npm_response = requests.get(npm_url)
npm_response.raise_for_status()
except requests.exceptions.HTTPError:
raise AssertionError(f"Package {package['name']} does not exist on npm registry")
npm_data = npm_response.json()
# Retrieve the first publish date using the 'created' timestamp from the 'time' field.
created_str = npm_data.get("time", {}).get("created")
if created_str is None:
raise AssertionError(f"Package {package['name']} has no creation time in registry data")
# Remove the trailing 'Z' if present and parse the ISO format timestamp
first_publish_date = datetime.fromisoformat(created_str.rstrip("Z"))
# If package was published more than 48 hours ago, fetch download stats.
if (datetime.now() - first_publish_date).total_seconds() >= 48 * 3600:
stats_url = f"https://api.npmjs.org/downloads/point/last-week/{package['name']}"
stats_response = requests.get(stats_url)
stats_response.raise_for_status()
stats_data = stats_response.json()
return stats_data.get("downloads", None)
else:
return None
def _get_weekly_downloads(packages: dict[str, list[Package]], fake: bool) -> list[ResolvedPackage]:
"""Retrieve the weekly download count for a dictionary of python or js packages."""
def _get_weekly_downloads(packages: list[Package], fake: bool) -> list[ResolvedPackage]:
"""Retrieve the monthly download count for a list of packages from PyPIStats."""
resolved_packages: list[ResolvedPackage] = []
if fake:
# To avoid making network requests during testing, return fake download counts
for language, package_list in packages.items():
for package in package_list:
resolved_packages.append(
{
"name": package["name"],
"repo": package["repo"],
"monorepo_path": package.get("monorepo_path", None),
"language": language,
"description": package["description"],
"weekly_downloads": -12345,
}
)
return resolved_packages
for language, package_list in packages.items():
for package in package_list:
if language == "python":
num_downloads = _get_pypi_downloads(package)
elif language == "js":
num_downloads = _get_npm_downloads(package)
else:
num_downloads = None
for package in packages:
resolved_packages.append(
{
"name": package["name"],
"repo": package["repo"],
"monorepo_path": package.get("monorepo_path", None),
"language": language,
"weekly_downloads": -12345,
"description": package["description"],
"weekly_downloads": num_downloads,
}
)
return resolved_packages
for package in packages:
# First check if package exists on PyPI
pypi_url = f"https://pypi.org/pypi/{package['name']}/json"
try:
pypi_response = requests.get(pypi_url)
pypi_response.raise_for_status()
except requests.exceptions.HTTPError:
raise AssertionError(f"Package {package['name']} does not exist on PyPI")
# Get first release date
pypi_data = pypi_response.json()
releases = pypi_data["releases"]
first_release_date = None
for version_releases in releases.values():
if version_releases: # Some versions may be empty lists
upload_time = datetime.fromisoformat(version_releases[0]["upload_time"])
if first_release_date is None or upload_time < first_release_date:
first_release_date = upload_time
if first_release_date is None:
raise AssertionError(f"Package {package['name']} has no releases yet")
# If package was published in last 48 hours, skip download stats
if (datetime.now() - first_release_date).total_seconds() >= 48 * 3600:
url = f"https://pypistats.org/api/packages/{package['name']}/overall"
response = requests.get(url)
response.raise_for_status()
data = response.json()
sorted_data = sorted(
data["data"],
key=lambda x: datetime.strptime(x["date"], "%Y-%m-%d"),
reverse=True,
)
# Sum the last 7 days of downloads
num_downloads = sum(entry["downloads"] for entry in sorted_data[:7])
else:
num_downloads = None
resolved_packages.append(
{
"name": package["name"],
"repo": package["repo"],
"weekly_downloads": num_downloads,
"description": package["description"],
}
)
return resolved_packages
def main(output_file: str, fake: bool) -> None:
"""Main function to generate package download information.
Args:
output_file: Path to the output YAML file.
fake: If True, use fake download counts for testing purposes.
"""
resolved_packages: list[ResolvedPackage] = _get_weekly_downloads(PACKAGES, fake)
+39 -56
View File
@@ -1,58 +1,41 @@
#A list of third-party packages to surface on the third-party page.
packages:
python:
- name: "trustcall"
repo: "hinthornw/trustcall"
description: "Tenacious tool calling built on LangGraph."
- name: "breeze-agent"
repo: "andrestorres123/breeze-agent"
description: "A streamlined research system built inspired on STORM and built on LangGraph."
- name: "langgraph-supervisor"
repo: "langchain-ai/langgraph-supervisor-py"
description: "Build supervisor multi-agent systems with LangGraph."
- name: "langmem"
repo: "langchain-ai/langmem"
description: "Build agents that learn and adapt from interactions over time."
- name: "langchain-mcp-adapters"
repo: "langchain-ai/langchain-mcp-adapters"
description: "Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents."
- name: "open-deep-research"
repo: "langchain-ai/open_deep_research"
description: "Open source assistant for iterative web research and report writing."
- name: "langgraph-swarm"
repo: "langchain-ai/langgraph-swarm-py"
description: "Build swarm-style multi-agent systems using LangGraph."
- name: "delve-taxonomy-generator"
repo: "andrestorres123/delve"
description: "A taxonomy generator for unstructured data"
- name: "nodeology"
repo: "xyin-anl/Nodeology"
description: "Enable researcher to build scientific workflows easily with simplified interface."
- name: "langgraph-bigtool"
repo: "langchain-ai/langgraph-bigtool"
description: "Build LangGraph agents with large numbers of tools."
- name: "ai-data-science-team"
repo: "business-science/ai-data-science-team"
description: "An AI-powered data science team of agents to help you perform common data science tasks 10X faster."
- name: "langgraph-reflection"
repo: "langchain-ai/langgraph-reflection"
description: "LangGraph agent that runs a reflection step."
- name: "langgraph-codeact"
repo: "langchain-ai/langgraph-codeact"
description: "LangGraph implementation of CodeAct agent that generates and executes code instead of tool calling."
js:
- name: "@langchain/mcp-adapters"
repo: "langchain-ai/langchainjs"
description: "Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents."
- name: "@langchain/langgraph-supervisor"
repo: "langchain-ai/langgraphjs"
monorepo_path: "libs/langgraph-supervisor"
description: "Build supervisor multi-agent systems with LangGraph"
- name: "@langchain/langgraph-swarm"
repo: "langchain-ai/langgraphjs"
monorepo_path: "libs/langgraph-swarm"
description: "Build multi-agent swarms with LangGraph"
- name: "@langchain/langgraph-cua"
repo: "langchain-ai/langgraphjs"
monorepo_path: "libs/langgraph-cua"
description: "Build computer use agents with LangGraph"
- name: "trustcall"
repo: "hinthornw/trustcall"
description: "Tenacious tool calling built on LangGraph."
- name: "breeze-agent"
repo: "andrestorres123/breeze-agent"
description: "A streamlined research system built inspired on STORM and built on LangGraph."
- name: "langgraph-supervisor"
repo: "langchain-ai/langgraph-supervisor-py"
description: "Build supervisor multi-agent systems with LangGraph."
- name: "langmem"
repo: "langchain-ai/langmem"
description: "Build agents that learn and adapt from interactions over time."
- name: "langchain-mcp-adapters"
repo: "langchain-ai/langchain-mcp-adapters"
description: "Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents."
- name: "open-deep-research"
repo: "langchain-ai/open_deep_research"
description: "Open source assistant for iterative web research and report writing."
- name: "langgraph-swarm"
repo: "langchain-ai/langgraph-swarm-py"
description: "Build swarm-style multi-agent systems using LangGraph."
- name: "delve-taxonomy-generator"
repo: "andrestorres123/delve"
description: "A taxonomy generator for unstructured data"
- name: "nodeology"
repo: "xyin-anl/Nodeology"
description: "Enable researcher to build scientific workflows easily with simplified interface."
- name: "langgraph-bigtool"
repo: "langchain-ai/langgraph-bigtool"
description: "Build LangGraph agents with large numbers of tools."
- name: "ai-data-science-team"
repo: "business-science/ai-data-science-team"
description: "An AI-powered data science team of agents to help you perform common data science tasks 10X faster."
- name: "langgraph-reflection"
repo: "langchain-ai/langgraph-reflection"
description: "LangGraph agent that runs a reflection step."
- name: "langgraph-codeact"
repo: "langchain-ai/langgraph-codeact"
description: "LangGraph implementation of CodeAct agent that generates and executes code instead of tool calling."
-11
View File
@@ -1,11 +0,0 @@
# Additional resources
This section contains additional resources for LangGraph.
- [Community agents](../agents/prebuilt.md): A collection of prebuilt libraries that you can use in your LangGraph applications.
- [LangGraph Academy](https://academy.langchain.com/courses/intro-to-langgraph): A collection of courses that teach you how to use LangGraph.
- [Case studies](../adopters.md): A collection of case studies that show how LangGraph is used in production.
- [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): A collection of troubleshooting guides for common issues.
+6 -23
View File
@@ -8,41 +8,24 @@ This list of companies using LangGraph and their success stories is compiled fro
| [AirTop](https://www.airtop.ai/) | Software & Technology (GenAI Native) | Browser automation for AI agents | [Case study, 2024](https://blog.langchain.dev/customers-airtop/) |
| [AppFolio](https://www.appfolio.com/) | Real Estate | Copilot for domain-specific task | [Case study, 2024](https://blog.langchain.dev/customers-appfolio/) |
| [Athena Intelligence](https://www.athenaintel.com/) | Software & Technology (GenAI Native) | Research & summarization | [Case study, 2024](https://blog.langchain.dev/customers-athena-intelligence/) |
| [BlackRock](https://www.blackrock.com/) | Financial Services | Copilot for domain-specific task | [Interrupt talk, 2025](https://youtu.be/oyqeCHFM5U4?feature=shared) |
| [Captide](https://www.captide.co/) | Software & Technology (GenAI Native) | Data extraction | [Case study, 2025](https://blog.langchain.dev/how-captide-is-redefining-equity-research-with-agentic-workflows-built-on-langgraph-and-langsmith/) |
| [Cisco CX](https://www.cisco.com/site/us/en/services/modern-data-center/index.html?CCID=cc005911&DTID=eivtotr001480&OID=srwsas032775) | Software & Technology | Customer support | [Interrupt Talk, 2025](https://youtu.be/gPhyPRtIMn0?feature=shared) |
| [Cisco Outshift](https://outshift.cisco.com/) | Software & Technology | DevOps | [Video story, 2025](https://www.youtube.com/watch?v=htcb-vGR_x0); [Case study, 2025](https://blog.langchain.com/cisco-outshift/); [Blog post, 2025](https://outshift.cisco.com/blog/build-react-agent-application-for-devops-tasks-using-rest-apis) |
| [Cisco TAC](https://www.cisco.com/c/en/us/support/index.html) | Software & Technology | Customer support | [Video story, 2025](https://youtu.be/EAj0HBDGqaE?feature=shared) |
| [City of Hope](https://www.cityofhope.org/) | Non-profit | Copilot for domain-specific task | [Video story, 2025](https://youtu.be/9ABwtK2gIZU?feature=shared) |
| [Cisco Outshift](https://outshift.cisco.com/) | Software & Technology | DevOps | [Blog post, 2025](https://outshift.cisco.com/blog/build-react-agent-application-for-devops-tasks-using-rest-apis) |
| [C.H. Robinson](https://www.chrobinson.com/en-us/) | Logistics | Automation | [Case study, 2025](https://blog.langchain.dev/customers-chrobinson/) |
| [Definely](https://www.definely.com/) | Legal | Copilot for domain-specific task | [Case study, 2025](https://blog.langchain.com/customers-definely/) |
| [Docent Pro](https://docentpro.com/) | Travel | GenAI embedded product experiences | [Case study, 2025](https://blog.langchain.com/customers-docentpro/) |
| [Elastic](https://www.elastic.co/) | Software & Technology | Copilot for domain-specific task | [Blog post, 2025](https://www.elastic.co/blog/elastic-security-generative-ai-features) |
| [Exa](https://exa.ai/) | Software & Technology (GenAI Native) | Search | [Case study, 2025](https://blog.langchain.com/exa/) |
| [GitLab](https://about.gitlab.com/) | Software & Technology | Code generation | [Duo workflow docs](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/duo_workflow/) |
| [Harmonic](https://harmonic.ai/) | Software & Technology | Search | [Case study, 2025](https://blog.langchain.com/customers-harmonic/) |
| [Inconvo](https://inconvo.ai/?ref=blog.langchain.dev) | Software & Technology | Code generation | [Case study, 2025](https://blog.langchain.dev/customers-inconvo/) |
| [Infor](https://infor.com/) | Software & Technology | GenAI embedded product experiences; customer support; copilot | [Case study, 2025](https://blog.langchain.dev/customers-infor/) |
| [J.P. Morgan](https://www.jpmorganchase.com/) | Financial Services | Copilot for domain-specific task | [Interrupt talk, 2025](https://youtu.be/yMalr0jiOAc?feature=shared) |
| [Klarna](https://www.klarna.com/) | Fintech | Copilot for domain-specific task | [Case study, 2025](https://blog.langchain.dev/customers-klarna/) |
| [Komodo Health](https://www.komodohealth.com/) | Healthcare | Copilot for domain-specific task | [Blog post](https://www.komodohealth.com/perspectives/new-gen-ai-assistant-empowers-the-enterprise/) |
| [LinkedIn](https://www.linkedin.com/) | Social Media | Code generation; Search & discovery | [Interrupt talk, 2025](https://youtu.be/NmblVxyBhi8?feature=shared); [Blog post, 2025](https://www.linkedin.com/blog/engineering/ai/practical-text-to-sql-for-data-analytics); [Blog post, 2024](https://www.linkedin.com/blog/engineering/generative-ai/behind-the-platform-the-journey-to-create-the-linkedin-genai-application-tech-stack) |
| [LinkedIn](https://www.linkedin.com/) | Social Media | Code generation; Search & discovery | [Blog post, 2025](https://www.linkedin.com/blog/engineering/ai/practical-text-to-sql-for-data-analytics); [Blog post, 2024](https://www.linkedin.com/blog/engineering/generative-ai/behind-the-platform-the-journey-to-create-the-linkedin-genai-application-tech-stack) |
| [Minimal](https://gominimal.ai/) | E-commerce | Customer support | [Case study, 2025](https://blog.langchain.dev/how-minimal-built-a-multi-agent-customer-support-system-with-langgraph-langsmith/) |
| [Modern Treasury](https://www.moderntreasury.com/) | Fintech | GenAI embedded product experiences | [Video story, 2025](https://youtu.be/AwAiffXqaCU?feature=shared) |
| [Monday](https://monday.com/) | Software & Technology | GenAI embedded product experiences | [Interrupt talk, 2025](https://blog.langchain.dev/how-minimal-built-a-multi-agent-customer-support-system-with-langgraph-langsmith/) |
| [Morningstar](https://www.morningstar.com/) | Financial Services | Research & summarization | [Video story, 2025](https://youtu.be/6LidoFXCJPs?feature=shared) |
| [OpenRecovery](https://www.openrecovery.com/) | Healthcare | Copilot for domain-specific task | [Case study, 2024](https://blog.langchain.dev/customers-openrecovery/) |
| [Pigment](https://www.pigment.com/) | Fintech | GenAI embedded product experiences | [Video story, 2025](https://youtu.be/5JVSO2KYOmE?feature=shared) |
| [Prosper](https://www.prosper.com/) | Fintech | Customer support | [Video story, 2025](https://youtu.be/9RFNOYtkwsc?feature=shared) |
| [Qodo](https://www.qodo.ai/) | Software & Technology (GenAI Native) | Code generation | [Blog post, 2025](https://www.qodo.ai/blog/why-we-chose-langgraph-to-build-our-coding-agent/) |
| [Rakuten](https://www.rakuten.com/) | E-commerce / Fintech | Copilot for domain-specific task | [Video story, 2025](https://youtu.be/gD1LIjCkuA8?feature=shared); [Blog post, 2025](https://rakuten.today/blog/from-ai-hype-to-real-world-tools-rakuten-teams-up-with-langchain.html) |
| [Rakuten](https://www.rakuten.com/) | E-commerce / Fintech | Copilot for domain-specific task | [Blog post, 2025](https://rakuten.today/blog/from-ai-hype-to-real-world-tools-rakuten-teams-up-with-langchain.html) |
| [Replit](https://replit.com/) | Software & Technology | Code generation | [Blog post, 2024](https://blog.langchain.dev/customers-replit/); [Breakout agent story, 2024](https://www.langchain.com/breakoutagents/replit); [Fireside chat video, 2024](https://www.youtube.com/watch?v=ViykMqljjxU) |
| [Rexera](https://www.rexera.com/) | Real Estate (GenAI Native) | Copilot for domain-specific task | [Case study, 2024](https://blog.langchain.dev/customers-rexera/) |
| [Abu Dhabi Government](https://www.tamm.abudhabi/) | Government | Search | [Case study, 2025](https://blog.langchain.com/customers-abu-dhabi-government/) |
| [Tradestack](https://www.tradestack.uk/) | Software & Technology (GenAI Native) | Copilot for domain-specific task | [Case study, 2024](https://blog.langchain.dev/customers-tradestack/) |
| [Uber](https://www.uber.com/) | Transportation | Developer productivity; Code generation | [Interrupt talk, 2025](https://youtu.be/Bugs0dVcNI8?feature=shared); [Presentation, 2024](https://dpe.org/sessions/ty-smith-adam-huda/this-year-in-ubers-ai-driven-developer-productivity-revolution/); [Video, 2024](https://www.youtube.com/watch?v=8rkA5vWUE4Y) |
| [Unify](https://www.unifygtm.com/) | Software & Technology (GenAI Native) | Copilot for domain-specific task | [Interrupt talk, 2025](https://youtu.be/pKk-LfhujwI?feature=shared); [Blog post, 2024](https://blog.langchain.dev/unify-launches-agents-for-account-qualification-using-langgraph-and-langsmith/) |
| [Vizient](https://www.vizientinc.com/) | Healthcare | Copilot for domain-specific task | [Video story, 2025](https://www.youtube.com/watch?v=vrjJ6NuyTWA); [Case study, 2025](https://blog.langchain.dev/p/3d2cd58c-13a5-4df9-bd84-7d54ed0ed82c/) |
| [Uber](https://www.uber.com/) | Transportation | Developer productivity; Code generation | [Presentation, 2024](https://dpe.org/sessions/ty-smith-adam-huda/this-year-in-ubers-ai-driven-developer-productivity-revolution/); [Video, 2024](https://www.youtube.com/watch?v=8rkA5vWUE4Y) |
| [Unify](https://www.unifygtm.com/) | Software & Technology (GenAI Native) | Copilot for domain-specific task | [Blog post, 2024](https://blog.langchain.dev/unify-launches-agents-for-account-qualification-using-langgraph-and-langsmith/) |
| [Vizient](https://www.vizientinc.com/) | Healthcare | Copilot for domain-specific task | [Case study, 2025](https://blog.langchain.dev/p/3d2cd58c-13a5-4df9-bd84-7d54ed0ed82c/) |
| [Vodafone](https://www.vodafone.com/) | Telecommunications | Code generation; internal search | [Case study, 2025](https://blog.langchain.dev/customers-vodafone/) |
| [WebToon](https://www.webtoons.com/en/) | Media & Entertainment | Data extraction | [Case study, 2025](https://blog.langchain.com/customers-webtoon/) |
| [11x](https://www.11x.ai/) | Software & Technology (GenAI Native) | Research & outreach | [Interrupt talk, 2025](https://youtu.be/fegwPmaAPQk?feature=shared) |
+11 -235
View File
@@ -15,40 +15,23 @@ This guide shows you how to set up and use LangGraph's **prebuilt**, **reusable*
Before you start this tutorial, ensure you have the following:
- An [Anthropic](https://console.anthropic.com/settings/keys) API key
- An [Anthropic](https://console.anthropic.com/settings/keys) API key
## 1. Install dependencies
If you haven't already, install LangGraph and LangChain:
:::python
```
pip install -U langgraph "langchain[anthropic]"
```
!!! info
!!! info
LangChain is installed so the agent can call the [model](https://python.langchain.com/docs/integrations/chat/).
:::
:::js
```bash
npm install @langchain/langgraph @langchain/core @langchain/anthropic
```
!!! info
LangChain is installed so the agent can call the [model](https://js.langchain.com/docs/integrations/chat/).
:::
## 2. Create an agent
:::python
To create an agent, use @[`create_react_agent`][create_react_agent]:
To create an agent, use [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent]:
```python
from langgraph.prebuilt import create_react_agent
@@ -69,56 +52,13 @@ agent.invoke(
)
```
1. Define a tool for the agent to use. Tools can be defined as vanilla Python functions. For more advanced tool usage and customization, check the [tools](../how-tos/tool-calling.md) page.
1. Define a tool for the agent to use. Tools can be defined as vanilla Python functions. For more advanced tool usage and customization, check the [tools](./tools.md) page.
2. Provide a language model for the agent to use. To learn more about configuring language models for the agents, check the [models](./models.md) page.
3. Provide a list of tools for the model to use.
4. Provide a system prompt (instructions) to the language model used by the agent.
:::
:::js
To create an agent, use [`createReactAgent`](https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph_prebuilt.createReactAgent.html):
```typescript
import { ChatAnthropic } from "@langchain/anthropic";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { tool } from "@langchain/core/tools";
import { z } from "zod";
const getWeather = tool(
// (1)!
async ({ city }) => {
return `It's always sunny in ${city}!`;
},
{
name: "get_weather",
description: "Get weather for a given city.",
schema: z.object({
city: z.string().describe("The city to get weather for"),
}),
}
);
const agent = createReactAgent({
llm: new ChatAnthropic({ model: "anthropic:claude-3-5-sonnet-latest" }), // (2)!
tools: [getWeather], // (3)!
stateModifier: "You are a helpful assistant", // (4)!
});
// Run the agent
await agent.invoke({
messages: [{ role: "user", content: "what is the weather in sf" }],
});
```
1. Define a tool for the agent to use. Tools can be defined using the `tool` function. For more advanced tool usage and customization, check the [tools](./tools.md) page.
2. Provide a language model for the agent to use. To learn more about configuring language models for the agents, check the [models](./models.md) page.
3. Provide a list of tools for the model to use.
4. Provide a system prompt (instructions) to the language model used by the agent.
:::
## 3. Configure an LLM
:::python
To configure an LLM with specific parameters, such as temperature, use [init_chat_model](https://python.langchain.com/api_reference/langchain/chat_models/langchain.chat_models.base.init_chat_model.html):
```python
@@ -139,45 +79,19 @@ agent = create_react_agent(
)
```
:::
:::js
To configure an LLM with specific parameters, such as temperature, use a model instance:
```typescript
import { ChatAnthropic } from "@langchain/anthropic";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
// highlight-next-line
const model = new ChatAnthropic({
model: "claude-3-5-sonnet-latest",
// highlight-next-line
temperature: 0,
});
const agent = createReactAgent({
// highlight-next-line
llm: model,
tools: [getWeather],
});
```
:::
For more information on how to configure LLMs, see [Models](./models.md).
## 4. Add a custom prompt
Prompts instruct the LLM how to behave. Add one of the following types of prompts:
- **Static**: A string is interpreted as a **system message**.
- **Dynamic**: A list of messages generated at **runtime**, based on input or configuration.
* **Static**: A string is interpreted as a **system message**.
* **Dynamic**: A list of messages generated at **runtime**, based on input or configuration.
=== "Static prompt"
Define a fixed prompt string or list of messages:
:::python
```python
from langgraph.prebuilt import create_react_agent
@@ -193,30 +107,9 @@ Prompts instruct the LLM how to behave. Add one of the following types of prompt
{"messages": [{"role": "user", "content": "what is the weather in sf"}]}
)
```
:::
:::js
```typescript
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { ChatAnthropic } from "@langchain/anthropic";
const agent = createReactAgent({
llm: new ChatAnthropic({ model: "anthropic:claude-3-5-sonnet-latest" }),
tools: [getWeather],
// A static prompt that never changes
// highlight-next-line
stateModifier: "Never answer questions about the weather."
});
await agent.invoke({
messages: [{ role: "user", content: "what is the weather in sf" }]
});
```
:::
=== "Dynamic prompt"
:::python
Define a function that returns a message list based on the agent's state and configuration:
```python
@@ -251,52 +144,12 @@ Prompts instruct the LLM how to behave. Add one of the following types of prompt
- Internal agent state updated during a multi-step reasoning process (using `state`).
Dynamic prompts can be defined as functions that take `state` and `config` and return a list of messages to send to the LLM.
:::
:::js
Define a function that returns messages based on the agent's state and configuration:
```typescript
import { type BaseMessageLike } from "@langchain/core/messages";
import { type RunnableConfig } from "@langchain/core/runnables";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
// highlight-next-line
const dynamicPrompt = (state: { messages: BaseMessageLike[] }, config: RunnableConfig): BaseMessageLike[] => { // (1)!
const userName = config.configurable?.user_name;
const systemMsg = `You are a helpful assistant. Address the user as ${userName}.`;
return [{ role: "system", content: systemMsg }, ...state.messages];
};
const agent = createReactAgent({
llm: "anthropic:claude-3-5-sonnet-latest",
tools: [getWeather],
// highlight-next-line
stateModifier: dynamicPrompt
});
await agent.invoke(
{ messages: [{ role: "user", content: "what is the weather in sf" }] },
// highlight-next-line
{ configurable: { user_name: "John Smith" } }
);
```
1. Dynamic prompts allow including non-message [context](./context.md) when constructing an input to the LLM, such as:
- Information passed at runtime, like a `user_id` or API credentials (using `config`).
- Internal agent state updated during a multi-step reasoning process (using `state`).
Dynamic prompts can be defined as functions that take `state` and `config` and return a list of messages to send to the LLM.
:::
For more information, see [Context](./context.md).
## 5. Add memory
To allow multi-turn conversations with an agent, you need to enable [persistence](../concepts/persistence.md) by providing a checkpointer when creating an agent. At runtime, you need to provide a config containing `thread_id` — a unique identifier for the conversation (session):
:::python
To allow multi-turn conversations with an agent, you need to enable [persistence](../concepts/persistence.md) by providing a `checkpointer` when creating an agent. At runtime, you need to provide a config containing `thread_id` — a unique identifier for the conversation (session):
```python
from langgraph.prebuilt import create_react_agent
@@ -327,60 +180,17 @@ ny_response = agent.invoke(
)
```
1. `checkpointer` allows the agent to store its state at every step in the tool calling loop. This enables [short-term memory](../how-tos/memory/add-memory.md#add-short-term-memory) and [human-in-the-loop](../concepts/human_in_the_loop.md) capabilities.
1. `checkpointer` allows the agent to store its state at every step in the tool calling loop. This enables [short-term memory](./memory.md#short-term-memory) and [human-in-the-loop](./human-in-the-loop.md) capabilities.
2. Pass configuration with `thread_id` to be able to resume the same conversation on future agent invocations.
:::
:::js
```typescript
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { MemorySaver } from "@langchain/langgraph";
// highlight-next-line
const checkpointer = new MemorySaver();
const agent = createReactAgent({
llm: "anthropic:claude-3-5-sonnet-latest",
tools: [getWeather],
// highlight-next-line
checkpointSaver: checkpointer, // (1)!
});
// Run the agent
// highlight-next-line
const config = { configurable: { thread_id: "1" } };
const sfResponse = await agent.invoke(
{ messages: [{ role: "user", content: "what is the weather in sf" }] },
// highlight-next-line
config // (2)!
);
const nyResponse = await agent.invoke(
{ messages: [{ role: "user", content: "what about new york?" }] },
// highlight-next-line
config
);
```
1. `checkpointSaver` allows the agent to store its state at every step in the tool calling loop. This enables [short-term memory](../how-tos/memory/add-memory.md#add-short-term-memory) and [human-in-the-loop](../concepts/human_in_the_loop.md) capabilities.
2. Pass configuration with `thread_id` to be able to resume the same conversation on future agent invocations.
:::
:::python
When you enable the checkpointer, it stores agent state at every step in the provided checkpointer database (or in memory, if using `InMemorySaver`).
:::
:::js
When you enable the checkpointer, it stores agent state at every step in the provided checkpointer database (or in memory, if using `MemorySaver`).
:::
Note that in the above example, when the agent is invoked the second time with the same `thread_id`, the original message history from the first conversation is automatically included, together with the new user input.
For more information, see [Memory](../how-tos/memory/add-memory.md).
For more information, see [Memory](./memory.md).
## 6. Configure structured output
:::python
To produce structured responses conforming to a schema, use the `response_format` parameter. The schema can be defined with a `Pydantic` model or `TypedDict`. The result will be accessible via the `structured_response` field.
```python
@@ -405,43 +215,9 @@ response = agent.invoke(
response["structured_response"]
```
1. When `response_format` is provided, a separate step is added at the end of the agent loop: agent message history is passed to an LLM with structured output to generate a structured response.
1. When `response_format` is provided, a separate step is added at the end of the agent loop: agent message history is passed to an LLM with structured output to generate a structured response.
To provide a system prompt to this LLM, use a tuple `(prompt, schema)`, e.g., `response_format=(prompt, WeatherResponse)`.
:::
:::js
To produce structured responses conforming to a schema, use the `responseFormat` parameter. The schema can be defined with a `Zod` schema. The result will be accessible via the `structuredResponse` field.
```typescript
import { z } from "zod";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
const WeatherResponse = z.object({
conditions: z.string(),
});
const agent = createReactAgent({
llm: "anthropic:claude-3-5-sonnet-latest",
tools: [getWeather],
// highlight-next-line
responseFormat: WeatherResponse, // (1)!
});
const response = await agent.invoke({
messages: [{ role: "user", content: "what is the weather in sf" }],
});
// highlight-next-line
response.structuredResponse;
```
1. When `responseFormat` is provided, a separate step is added at the end of the agent loop: agent message history is passed to an LLM with structured output to generate a structured response.
To provide a system prompt to this LLM, use an object `{ prompt, schema }`, e.g., `responseFormat: { prompt, schema: WeatherResponse }`.
:::
To provide a system prompt to this LLM, use a tuple `(prompt, schema)`, e.g., `response_format=(prompt, WeatherResponse)`.
!!! Note "LLM post-processing"
+155 -227
View File
@@ -1,176 +1,147 @@
---
search:
boost: 2
tags:
- agent
hide:
- tags
---
# Context
**Context engineering** is the practice of building dynamic systems that provide the right information and tools, in the right format, so that an AI application can accomplish a task. Context can be characterized along two key dimensions:
Agents often require more than a list of messages to function effectively. They need **context**.
1. By **mutability**:
- **Static context**: Immutable data that doesn't change during execution (e.g., user metadata, database connections, tools)
- **Dynamic context**: Mutable data that evolves as the application runs (e.g., conversation history, intermediate results, tool call observations)
2. By **lifetime**:
- **Runtime context**: Data scoped to a single run or invocation
- **Cross-conversation context**: Data that persists across multiple conversations or sessions
Context includes *any* data outside the message list that can shape agent behavior or tool execution. This can be:
!!! tip "Runtime context vs LLM context"
- Information passed at runtime, like a `user_id` or API credentials.
- Internal state updated during a multi-step reasoning process.
- Persistent memory or facts from previous interactions.
Runtime context refers to local context: data and dependencies your code needs to run. It does **not** refer to:
LangGraph provides **three** primary ways to supply context:
* 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.
| Type | Description | Mutable? | Lifetime |
|------------------------------------------------------------------------------|-----------------------------------------------|----------|-------------------------|
| [**Config**](#config-static-context) | data passed at the start of a run | ❌ | per run |
| [**State**](#state-mutable-context) | dynamic data that can change during execution | ✅ | per run or conversation |
| [**Long-term Memory (Store)**](#long-term-memory-cross-conversation-context) | data that can be shared between conversations | ✅ | across conversations |
Runtime context can be used to optimize the LLM context. For example, you can use user metadata
in the runtime context to fetch user preferences and feed them into the context window.
You can use context to:
LangGraph provides three ways to manage context, which combines the mutability and lifetime dimensions:
- Adjust the system prompt the model sees
- Feed tools with necessary inputs
- Track facts during an ongoing conversation
:::python
## Providing Runtime Context
| Context type | Description | Mutability | Lifetime | Access method |
| ------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ---------- | ------------------ | --------------------------------------- |
| [**Static runtime context**](#static-runtime-context) | User metadata, tools, db connections passed at startup | Static | Single run | `context` argument to `invoke`/`stream` |
| [**Dynamic runtime context (state)**](#dynamic-runtime-context-state) | Mutable data that evolves during a single run | Dynamic | Single run | LangGraph state object |
| [**Dynamic cross-conversation context (store)**](#dynamic-cross-conversation-context-store) | Persistent data shared across conversations | Dynamic | Cross-conversation | LangGraph store |
Use this when you need to inject data into an agent at runtime.
## Static runtime context
### Config (static context)
**Static runtime context** represents immutable data like user metadata, tools, and database connections that are passed to an application at the start of a run via the `context` argument to `invoke`/`stream`. This data does not change during execution.
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: `context` replaces `config['configurable']`"
Runtime context is now passed to the `context` argument of `invoke`/`stream`,
which replaces the previous pattern of passing application configuration to `config['configurable']`.
Specify configuration using a key called **"configurable"** which is reserved
for this purpose:
```python
@dataclass
class ContextSchema:
user_name: str
graph.invoke( # (1)!
{"messages": [{"role": "user", "content": "hi!"}]}, # (2)!
agent.invoke(
{"messages": [{"role": "user", "content": "hi!"}]},
# highlight-next-line
context={"user_name": "John Smith"} # (3)!
config={"configurable": {"user_id": "user_123"}}
)
```
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 runtime data. The `context` parameter allows you to provide additional dependencies that the agent can use during its execution.
### State (mutable context)
=== "Agent prompt"
```python
from langchain_core.messages import AnyMessage
from langgraph.runtime import get_runtime
from langgraph.prebuilt.chat_agent_executor import AgentState
from langgraph.prebuilt import create_react_agent
State acts as short-term memory during a run. It holds dynamic data that can evolve during execution, such as values derived from tools or LLM outputs.
```python
class CustomState(AgentState):
# highlight-next-line
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,
context_schema=ContextSchema
)
agent.invoke(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
context={"user_name": "John Smith"}
)
```
* See [Agents](../agents/agents.md) for details.
=== "Workflow node"
```python
from langgraph.runtime import Runtime
user_name: str
agent = create_react_agent(
# Other agent parameters...
# highlight-next-line
def node(state: State, config: Runtime[ContextSchema]):
user_name = runtime.context.user_name
...
```
state_schema=CustomState,
)
* See [the Graph API](https://langchain-ai.github.io/langgraph/how-tos/graph-api/#add-runtime-configuration) for details.
=== "In a tool"
```python
from langgraph.runtime import get_runtime
@tool
# highlight-next-line
def get_user_email() -> str:
"""Retrieve user information based on user ID."""
# 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.
!!! tip
The `Runtime` object can be used to access static context and other utilities like the active store and stream writer.
See the [Runtime][langgraph.runtime.Runtime] documentation for details.
:::
:::js
| Context type | Description | Mutability | Lifetime |
| ------------------------------------------------------------------------------------------- | --------------------------------------------- | ---------- | ------------------ |
| [**Config**](#config-static-context) | data passed at the start of a run | Static | Single run |
| [**Dynamic runtime context (state)**](#dynamic-runtime-context-state) | Mutable data that evolves during a single run | Dynamic | Single run |
| [**Dynamic cross-conversation context (store)**](#dynamic-cross-conversation-context-store) | Persistent data shared across conversations | Dynamic | Cross-conversation |
## Config (static context)
Config is for immutable data like user metadata or API keys. Use this when you have values that don't change mid-run.
Specify configuration using a key called **"configurable"** which is reserved for this purpose.
```typescript
await graph.invoke(
// (1)!
{ messages: [{ role: "user", content: "hi!" }] }, // (2)!
// highlight-next-line
{ configurable: { user_id: "user_123" } } // (3)!
);
agent.invoke({
"messages": "hi!",
"user_name": "Jane"
})
```
:::
!!! tip "Turning on memory"
## Dynamic runtime context (state)
Please see the [memory guide](./memory.md) for more details on how to enable memory. This is a powerful feature that allows you to persist the agent's state across multiple invocations.
Otherwise, the state is scoped only to a single agent run.
**Dynamic runtime context** represents mutable data that can evolve during a single run and is managed through the LangGraph state object. This includes conversation history, intermediate results, and values derived from tools or LLM outputs. In LangGraph, the state object acts as [short-term memory](../concepts/memory.md) during a run.
=== "In an agent"
Example shows how to incorporate state into an agent **prompt**.
### Long-Term Memory (cross-conversation context)
State can also be accessed by the agent's **tools**, which can read or update the state as needed. See [tool calling guide](../how-tos/tool-calling.md#short-term-memory) for details.
For context that spans *across* conversations or sessions, LangGraph allows access to **long-term memory** via a `store`. This can be used to read or update persistent facts (e.g., user profiles, preferences, prior interactions). For more, see the [Memory guide](./memory.md).
## Customizing Prompts with Context { #prompts }
Prompts define how the agent behaves. To incorporate runtime context, you can dynamically generate prompts based on the agent's state or config.
Common use cases:
- Personalization
- Role or goal customization
- Conditional behavior (e.g., user is admin)
=== "Using config"
:::python
```python
from langchain_core.messages import AnyMessage
from langchain_core.runnables import RunnableConfig
from langgraph.prebuilt import create_react_agent
from langgraph.prebuilt.chat_agent_executor import AgentState
# highlight-next-line
class CustomState(AgentState): # (1)!
def prompt(
state: AgentState,
# highlight-next-line
config: RunnableConfig,
) -> list[AnyMessage]:
# highlight-next-line
user_name = config["configurable"].get("user_name")
system_msg = f"You are a helpful assistant. User's name is {user_name}"
return [{"role": "system", "content": system_msg}] + state["messages"]
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
# highlight-next-line
prompt=prompt
)
agent.invoke(
...,
# highlight-next-line
config={"configurable": {"user_name": "John Smith"}}
)
```
=== "Using state"
```python
from langchain_core.messages import AnyMessage
from langchain_core.runnables import RunnableConfig
from langgraph.prebuilt import create_react_agent
from langgraph.prebuilt.chat_agent_executor import AgentState
class CustomState(AgentState):
# highlight-next-line
user_name: str
def prompt(
# highlight-next-line
state: CustomState
) -> list[AnyMessage]:
# highlight-next-line
user_name = state["user_name"]
system_msg = f"You are a helpful assistant. User's name is {user_name}"
return [{"role": "system", "content": system_msg}] + state["messages"]
@@ -179,130 +150,87 @@ await graph.invoke(
model="anthropic:claude-3-7-sonnet-latest",
tools=[...],
# highlight-next-line
state_schema=CustomState, # (2)!
state_schema=CustomState,
# highlight-next-line
prompt=prompt
)
agent.invoke({
"messages": "hi!",
# highlight-next-line
"user_name": "John Smith"
})
```
1. Define a custom state schema that extends `AgentState` or `MessagesState`.
2. Pass the custom state schema to the agent. This allows the agent to access and modify the state during execution.
:::
## Accessing Context in Tools { #tools }
:::js
```typescript
import type { BaseMessage } from "@langchain/core/messages";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { MessagesZodState } from "@langchain/langgraph";
import { z } from "zod";
Tools can access context through special parameter **annotations**.
// highlight-next-line
const CustomState = z.object({ // (1)!
messages: MessagesZodState.shape.messages,
userName: z.string(),
});
* Use `RunnableConfig` for config access
* Use `Annotated[StateSchema, InjectedState]` for agent state
const prompt = (
// highlight-next-line
state: z.infer<typeof CustomState>
): BaseMessage[] => {
const userName = state.userName;
const systemMsg = `You are a helpful assistant. User's name is ${userName}`;
return [{ role: "system", content: systemMsg }, ...state.messages];
};
const agent = createReactAgent({
llm: model,
tools: [...],
// highlight-next-line
stateSchema: CustomState, // (2)!
stateModifier: prompt,
});
!!! tip
await agent.invoke({
messages: [{ role: "user", content: "hi!" }],
userName: "John Smith",
});
```
These annotations prevent LLMs from attempting to fill in the values. These parameters will be **hidden** from the LLM.
1. Define a custom state schema that extends `MessagesZodState` or creates a new schema.
2. Pass the custom state schema to the agent. This allows the agent to access and modify the state during execution.
:::
=== "Using config"
=== "In a workflow"
:::python
```python
from typing_extensions import TypedDict
from langchain_core.messages import AnyMessage
from langgraph.graph import StateGraph
def get_user_info(
# highlight-next-line
config: RunnableConfig,
) -> str:
"""Look up user info."""
# highlight-next-line
user_id = config["configurable"].get("user_id")
return "User is John Smith" if user_id == "user_123" else "Unknown user"
# highlight-next-line
class CustomState(TypedDict): # (1)!
messages: list[AnyMessage]
extra_field: int
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_user_info],
)
# highlight-next-line
def node(state: CustomState): # (2)!
messages = state["messages"]
...
return { # (3)!
# highlight-next-line
"extra_field": state["extra_field"] + 1
}
builder = StateGraph(State)
builder.add_node(node)
builder.set_entry_point("node")
graph = builder.compile()
agent.invoke(
{"messages": [{"role": "user", "content": "look up user information"}]},
# highlight-next-line
config={"configurable": {"user_id": "user_123"}}
)
```
1. Define a custom state
2. Access the state in any node or tool
3. The Graph API is designed to work as easily as possible with state. The return value of a node represents a requested update to the state.
:::
=== "Using State"
:::js
```typescript
import type { BaseMessage } from "@langchain/core/messages";
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
import { z } from "zod";
```python
from typing import Annotated
from langgraph.prebuilt import InjectedState
// highlight-next-line
const CustomState = z.object({ // (1)!
messages: MessagesZodState.shape.messages,
extraField: z.number(),
});
class CustomState(AgentState):
# highlight-next-line
user_id: str
const builder = new StateGraph(CustomState)
.addNode("node", async (state) => { // (2)!
const messages = state.messages;
// ...
return { // (3)!
// highlight-next-line
extraField: state.extraField + 1,
};
})
.addEdge(START, "node");
def get_user_info(
# highlight-next-line
state: Annotated[CustomState, InjectedState]
) -> str:
"""Look up user info."""
# highlight-next-line
user_id = state["user_id"]
return "User is John Smith" if user_id == "user_123" else "Unknown user"
const graph = builder.compile();
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_user_info],
# highlight-next-line
state_schema=CustomState,
)
agent.invoke({
"messages": "look up user information",
# highlight-next-line
"user_id": "user_123"
})
```
1. Define a custom state
2. Access the state in any node or tool
3. The Graph API is designed to work as easily as possible with state. The return value of a node represents a requested update to the state.
:::
### Update Context from Tools
!!! tip "Turning on memory"
Please see the [memory guide](../how-tos/memory/add-memory.md) for more details on how to enable memory. This is a powerful feature that allows you to persist the agent's state across multiple invocations. Otherwise, the state is scoped only to a single run.
## Dynamic cross-conversation context (store)
**Dynamic cross-conversation context** represents persistent, mutable data that spans across multiple conversations or sessions and is managed through the LangGraph store. This includes user profiles, preferences, and historical interactions. The LangGraph store acts as [long-term memory](../concepts/memory.md#long-term-memory) across multiple runs. This can be used to read or update persistent facts (e.g., user profiles, preferences, prior interactions).
For more information, see the [Memory guide](../how-tos/memory/add-memory.md).
Tools can update agent's context (state and long-term memory) during execution. This is useful for persisting intermediate results or making information accessible to subsequent tools or prompts. See [Memory](./memory.md#read-short-term) guide for more information.
+92
View File
@@ -0,0 +1,92 @@
---
search:
boost: 2
tags:
- agent
hide:
- tags
---
# Deployment
To deploy your LangGraph agent, create and configure a LangGraph app. This setup supports both local development and production deployments.
Features:
* 🖥️ Local server for development
* 🧩 Studio Web UI for visual debugging
* ☁️ Cloud and 🔧 self-hosted deployment options
* 📊 LangSmith integration for tracing and observability
!!! info "Requirements"
- ✅ You **must** have a [LangSmith account](https://www.langchain.com/langsmith). You can sign up for **free** and get started with the free tier.
## Create a LangGraph app
```bash
pip install -U "langgraph-cli[inmem]"
langgraph new path/to/your/app --template new-langgraph-project-python
```
This will create an empty LangGraph project. You can modify it by replacing the code in `src/agent/graph.py` with your agent code. For example:
```python
from langgraph.prebuilt import create_react_agent
def get_weather(city: str) -> str:
"""Get weather for a given city."""
return f"It's always sunny in {city}!"
graph = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
prompt="You are a helpful assistant"
)
```
### Install dependencies
In the root of your new LangGraph app, install the dependencies in `edit` mode so your local changes are used by the server:
```shell
pip install -e .
```
### Create an `.env` file
You will find a `.env.example` in the root of your new LangGraph app. Create
a `.env` file in the root of your new LangGraph app and copy the contents of the `.env.example` file into it, filling in the necessary API keys:
```bash
LANGSMITH_API_KEY=lsv2...
ANTHROPIC_API_KEY=sk-
```
## Launch LangGraph server locally
```shell
langgraph dev
```
This will start up the LangGraph API server locally. If this runs successfully, you should see something like:
> Ready!
>
> - API: [http://localhost:2024](http://localhost:2024/)
>
> - Docs: http://localhost:2024/docs
>
> - LangGraph Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
See this [tutorial](https://langchain-ai.github.io/langgraph/tutorials/langgraph-platform/local-server/) to learn more about running LangGraph app locally.
## LangGraph Studio Web UI
LangGraph Studio Web is a specialized UI that you can connect to LangGraph API server to enable visualization, interaction, and debugging of your application locally. Test your graph in the LangGraph Studio Web UI by visiting the URL provided in the output of the `langgraph dev` command.
> - LangGraph Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
## Deployment
Once your LangGraph app is running locally, you can deploy it using LangGraph Platform. Refer to the [deployment options guide](../tutorials/deployment.md) for detailed instructions on all supported deployment models.
+3 -140
View File
@@ -11,62 +11,25 @@ hide:
To evaluate your agent's performance you can use `LangSmith` [evaluations](https://docs.smith.langchain.com/evaluation). You would need to first define an evaluator function to judge the results from an agent, such as final outputs or trajectory. Depending on your evaluation technique, this may or may not involve a reference output:
:::python
```python
def evaluator(*, outputs: dict, reference_outputs: dict):
# compare agent outputs against reference outputs
output_messages = outputs["messages"]
reference_messages = reference_outputs["messages"]
reference_messages = reference["messages"]
score = compare_messages(output_messages, reference_messages)
return {"key": "evaluator_score", "score": score}
```
:::
:::js
```typescript
type EvaluatorParams = {
outputs: Record<string, any>;
referenceOutputs: Record<string, any>;
};
function evaluator({ outputs, referenceOutputs }: EvaluatorParams) {
// compare agent outputs against reference outputs
const outputMessages = outputs.messages;
const referenceMessages = referenceOutputs.messages;
const score = compareMessages(outputMessages, referenceMessages);
return { key: "evaluator_score", score: score };
}
```
:::
To get started, you can use prebuilt evaluators from `AgentEvals` package:
:::python
```bash
pip install -U agentevals
```
:::
:::js
```bash
npm install agentevals
```
:::
## Create evaluator
A common way to evaluate agent performance is by comparing its trajectory (the order in which it calls its tools) against a reference trajectory:
:::python
```python
import json
# highlight-next-line
@@ -117,72 +80,15 @@ result = evaluator(
)
```
:::
:::js
```typescript
import { createTrajectoryMatchEvaluator } from "agentevals/trajectory/match";
const outputs = [
{
role: "assistant",
tool_calls: [
{
function: {
name: "get_weather",
arguments: JSON.stringify({ city: "san francisco" }),
},
},
{
function: {
name: "get_directions",
arguments: JSON.stringify({ destination: "presidio" }),
},
},
],
},
];
const referenceOutputs = [
{
role: "assistant",
tool_calls: [
{
function: {
name: "get_weather",
arguments: JSON.stringify({ city: "san francisco" }),
},
},
],
},
];
// Create the evaluator
const evaluator = createTrajectoryMatchEvaluator({
// Specify how the trajectories will be compared. `superset` will accept output trajectory as valid if it's a superset of the reference one. Other options include: strict, unordered and subset
trajectoryMatchMode: "superset", // (1)!
});
// Run the evaluator
const result = evaluator({
outputs: outputs,
referenceOutputs: referenceOutputs,
});
```
:::
1. Specify how the trajectories will be compared. `superset` will accept output trajectory as valid if it's a superset of the reference one. Other options include: [strict](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#strict-match), [unordered](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#unordered-match) and [subset](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#subset-and-superset-match)
As a next step, learn more about how to [customize trajectory match evaluator](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#agent-trajectory-match).
### LLM-as-a-judge
You can use LLM-as-a-judge evaluator that uses an LLM to compare the trajectory against the reference outputs and output a score:
:::python
```python
import json
from agentevals.trajectory.llm import (
@@ -197,24 +103,6 @@ evaluator = create_trajectory_llm_as_judge(
)
```
:::
:::js
```typescript
import {
createTrajectoryLlmAsJudge,
TRAJECTORY_ACCURACY_PROMPT_WITH_REFERENCE,
} from "agentevals/trajectory/llm";
const evaluator = createTrajectoryLlmAsJudge({
prompt: TRAJECTORY_ACCURACY_PROMPT_WITH_REFERENCE,
model: "openai:o3-mini",
});
```
:::
## Run evaluator
To run an evaluator, you will first need to create a [LangSmith dataset](https://docs.smith.langchain.com/evaluation/concepts#datasets). To use the prebuilt AgentEvals evaluators, you will need a dataset with the following schema:
@@ -222,8 +110,6 @@ To run an evaluator, you will first need to create a [LangSmith dataset](https:/
- **input**: `{"messages": [...]}` input messages to call the agent with.
- **output**: `{"messages": [...]}` expected message history in the agent output. For trajectory evaluation, you can choose to keep only assistant messages.
:::python
```python
from langsmith import Client
from langgraph.prebuilt import create_react_agent
@@ -239,27 +125,4 @@ experiment_results = client.evaluate(
data="<Name of your dataset>",
evaluators=[evaluator]
)
```
:::
:::js
```typescript
import { Client } from "langsmith";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { createTrajectoryMatchEvaluator } from "agentevals/trajectory/match";
const client = new Client();
const agent = createReactAgent({...});
const evaluator = createTrajectoryMatchEvaluator({...});
const experimentResults = await client.evaluate(
(inputs) => agent.invoke(inputs),
// replace with your dataset name
{ data: "<Name of your dataset>" },
{ evaluators: [evaluator] }
);
```
:::
```
+238
View File
@@ -0,0 +1,238 @@
---
search:
boost: 2
tags:
- human-in-the-loop
- hil
- agent
hide:
- tags
---
# Human-in-the-loop
To review, edit and approve tool calls in an agent you can use LangGraph's built-in [Human-In-the-Loop (HIL)](../concepts/human_in_the_loop.md) features, specifically the [`interrupt()`][langgraph.types.interrupt] primitive.
LangGraph allows you to pause execution **indefinitely** — for minutes, hours, or even days—until human input is received.
This is possible because the agent state is **checkpointed into a database**, which allows the system to persist execution context and later resume the workflow, continuing from where it left off.
For a deeper dive into the **human-in-the-loop** concept, see the [concept guide](../concepts/human_in_the_loop.md).
<figure markdown="1">
![image](../concepts/img/human_in_the_loop/tool-call-review.png){: style="max-height:400px"}
<figcaption>
A human can review and edit the output from the agent before proceeding. This is particularly critical in applications where the tool calls requested may be sensitive or require human oversight.
</figcaption>
</figure>
## Review tool calls
To add a human approval step to a tool:
1. Use `interrupt()` in the tool to pause execution.
2. Resume with a `Command(resume=...)` to continue based on human input.
```python
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import interrupt
from langgraph.prebuilt import create_react_agent
# An example of a sensitive tool that requires human review / approval
def book_hotel(hotel_name: str):
"""Book a hotel"""
# highlight-next-line
response = interrupt( # (1)!
f"Trying to call `book_hotel` with args {{'hotel_name': {hotel_name}}}. "
"Please approve or suggest edits."
)
if response["type"] == "accept":
pass
elif response["type"] == "edit":
hotel_name = response["args"]["hotel_name"]
else:
raise ValueError(f"Unknown response type: {response['type']}")
return f"Successfully booked a stay at {hotel_name}."
# highlight-next-line
checkpointer = InMemorySaver() # (2)!
agent = create_react_agent(
model="anthropic:claude-3-5-sonnet-latest",
tools=[book_hotel],
# highlight-next-line
checkpointer=checkpointer, # (3)!
)
```
1. The [`interrupt` function][langgraph.types.interrupt] pauses the agent graph at a specific node. In this case, we call `interrupt()` at the beginning of the tool function, which pauses the graph at the node that executes the tool. The information inside `interrupt()` (e.g., tool calls) can be presented to a human, and the graph can be resumed with the user input (tool call approval, edit or feedback).
2. The `InMemorySaver` is used to store the agent state at every step in the tool calling loop. This enables [short-term memory](./memory.md#short-term-memory) and [human-in-the-loop](./human-in-the-loop.md) capabilities. In this example, we use `InMemorySaver` to store the agent state in memory. In a production application, the agent state will be stored in a database.
3. Initialize the agent with the `checkpointer`.
Run the agent with the `stream()` method, passing the `config` object to specify the thread ID. This allows the agent to resume the same conversation on future invocations.
```python
config = {
"configurable": {
# highlight-next-line
"thread_id": "1"
}
}
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "book a stay at McKittrick hotel"}]},
# highlight-next-line
config
):
print(chunk)
print("\n")
```
> You should see that the agent runs until it reaches the `interrupt()` call, at which point it pauses and waits for human input.
Resume the agent with a `Command(resume=...)` to continue based on human input.
```python
from langgraph.types import Command
for chunk in agent.stream(
# highlight-next-line
Command(resume={"type": "accept"}), # (1)!
# Command(resume={"type": "edit", "args": {"hotel_name": "McKittrick Hotel"}}),
config
):
print(chunk)
print("\n")
```
1. The [`interrupt` function][langgraph.types.interrupt] is used in conjunction with the [`Command`][langgraph.types.Command] object to resume the graph with a value provided by the human.
## Using with Agent Inbox
You can create a wrapper to add interrupts to *any* tool.
The example below provides a reference implementation compatible with [Agent Inbox UI](https://github.com/langchain-ai/agent-inbox) and [Agent Chat UI](https://github.com/langchain-ai/agent-chat-ui).
```python title="Wrapper that adds human-in-the-loop to any tool"
from typing import Callable
from langchain_core.tools import BaseTool, tool as create_tool
from langchain_core.runnables import RunnableConfig
from langgraph.types import interrupt
from langgraph.prebuilt.interrupt import HumanInterruptConfig, HumanInterrupt
def add_human_in_the_loop(
tool: Callable | BaseTool,
*,
interrupt_config: HumanInterruptConfig = None,
) -> BaseTool:
"""Wrap a tool to support human-in-the-loop review."""
if not isinstance(tool, BaseTool):
tool = create_tool(tool)
if interrupt_config is None:
interrupt_config = {
"allow_accept": True,
"allow_edit": True,
"allow_respond": True,
}
@create_tool( # (1)!
tool.name,
description=tool.description,
args_schema=tool.args_schema
)
def call_tool_with_interrupt(config: RunnableConfig, **tool_input):
request: HumanInterrupt = {
"action_request": {
"action": tool.name,
"args": tool_input
},
"config": interrupt_config,
"description": "Please review the tool call"
}
# highlight-next-line
response = interrupt([request])[0] # (2)!
# approve the tool call
if response["type"] == "accept":
tool_response = tool.invoke(tool_input, config)
# update tool call args
elif response["type"] == "edit":
tool_input = response["args"]["args"]
tool_response = tool.invoke(tool_input, config)
# respond to the LLM with user feedback
elif response["type"] == "response":
user_feedback = response["args"]
tool_response = user_feedback
else:
raise ValueError(f"Unsupported interrupt response type: {response['type']}")
return tool_response
return call_tool_with_interrupt
```
1. This wrapper creates a new tool that calls `interrupt()` **before** executing the wrapped tool.
2. `interrupt()` is using special input and output format that's expected by [Agent Inbox UI](https://github.com/langchain-ai/agent-inbox):
- a list of [`HumanInterrupt`][langgraph.prebuilt.interrupt.HumanInterrupt] objects is sent to `AgentInbox` render interrupt information to the end user
- resume value is provided by `AgentInbox` as a list (i.e., `Command(resume=[...])`)
You can use the `add_human_in_the_loop` wrapper to add `interrupt()` to any tool without having to add it *inside* the tool:
```python
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.prebuilt import create_react_agent
# highlight-next-line
checkpointer = InMemorySaver()
def book_hotel(hotel_name: str):
"""Book a hotel"""
return f"Successfully booked a stay at {hotel_name}."
agent = create_react_agent(
model="anthropic:claude-3-5-sonnet-latest",
tools=[
# highlight-next-line
add_human_in_the_loop(book_hotel), # (1)!
],
# highlight-next-line
checkpointer=checkpointer,
)
config = {"configurable": {"thread_id": "1"}}
# Run the agent
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "book a stay at McKittrick hotel"}]},
# highlight-next-line
config
):
print(chunk)
print("\n")
```
1. The `add_human_in_the_loop` wrapper is used to add `interrupt()` to the tool. This allows the agent to pause execution and wait for human input before proceeding with the tool call.
> You should see that the agent runs until it reaches the `interrupt()` call,
> at which point it pauses and waits for human input.
Resume the agent with a `Command(resume=...)` to continue based on human input.
```python
from langgraph.types import Command
for chunk in agent.stream(
# highlight-next-line
Command(resume=[{"type": "accept"}]),
# Command(resume=[{"type": "edit", "args": {"args": {"hotel_name": "McKittrick Hotel"}}}]),
config
):
print(chunk)
print("\n")
```
## Additional resources
* [Human-in-the-loop in LangGraph](../concepts/human_in_the_loop.md)
+34 -442
View File
@@ -7,250 +7,60 @@ hide:
- tags
---
# Use MCP
# MCP Integration
[Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol that standardizes how applications provide tools and context to language models. LangGraph agents can use tools defined on MCP servers through the `langchain-mcp-adapters` library.
![MCP](./assets/mcp.png)
:::python
Install the `langchain-mcp-adapters` library to use MCP tools in LangGraph:
```bash
pip install langchain-mcp-adapters
```
:::
:::js
Install the `@langchain/mcp-adapters` library to use MCP tools in LangGraph:
```bash
npm install langchain-mcp-adapters
```
:::
## Use MCP tools
:::python
The `langchain-mcp-adapters` package enables agents to use tools defined across one or more MCP servers.
=== "In an agent"
```python title="Agent using tools defined on MCP servers"
# highlight-next-line
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
```python title="Agent using tools defined on MCP servers"
# highlight-next-line
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
# highlight-next-line
client = MultiServerMCPClient(
{
"math": {
"command": "python",
# Replace with absolute path to your math_server.py file
"args": ["/path/to/math_server.py"],
"transport": "stdio",
},
"weather": {
# Ensure you start your weather server on port 8000
"url": "http://localhost:8000/mcp",
"transport": "streamable_http",
}
# highlight-next-line
client = MultiServerMCPClient(
{
"math": {
"command": "python",
# Replace with absolute path to your math_server.py file
"args": ["/path/to/math_server.py"],
"transport": "stdio",
},
"weather": {
# Ensure your start your weather server on port 8000
"url": "http://localhost:8000/mcp",
"transport": "streamable_http",
}
)
}
)
# highlight-next-line
tools = await client.get_tools()
agent = create_react_agent(
"anthropic:claude-3-7-sonnet-latest",
# highlight-next-line
tools = await client.get_tools()
agent = create_react_agent(
"anthropic:claude-3-7-sonnet-latest",
# highlight-next-line
tools
)
math_response = await agent.ainvoke(
{"messages": [{"role": "user", "content": "what's (3 + 5) x 12?"}]}
)
weather_response = await agent.ainvoke(
{"messages": [{"role": "user", "content": "what is the weather in nyc?"}]}
)
```
=== "In a workflow"
```python title="Workflow using MCP tools with ToolNode"
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.chat_models import init_chat_model
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": {
"command": "python",
# Make sure to update to the full absolute path to your math_server.py file
"args": ["./examples/math_server.py"],
"transport": "stdio",
},
"weather": {
# make sure you start your weather server on port 8000
"url": "http://localhost:8000/mcp/",
"transport": "streamable_http",
}
}
)
tools = await client.get_tools()
# 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", call_model)
builder.add_node("tools", tool_node)
builder.add_edge(START, "call_model")
builder.add_conditional_edges(
"call_model",
should_continue,
)
builder.add_edge("tools", "call_model")
# Compile the graph
graph = builder.compile()
# 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?"}]}
)
```
:::
:::js
The `@langchain/mcp-adapters` package enables agents to use tools defined across one or more MCP servers.
=== "In an agent"
```typescript title="Agent using tools defined on MCP servers"
// highlight-next-line
import { MultiServerMCPClient } from "langchain-mcp-adapters/client";
import { ChatAnthropic } from "@langchain/langgraph/prebuilt";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
// highlight-next-line
const client = new MultiServerMCPClient({
math: {
command: "node",
// Replace with absolute path to your math_server.js file
args: ["/path/to/math_server.js"],
transport: "stdio",
},
weather: {
// Ensure you start your weather server on port 8000
url: "http://localhost:8000/mcp",
transport: "streamable_http",
},
});
// highlight-next-line
const tools = await client.getTools();
const agent = createReactAgent({
llm: new ChatAnthropic({ model: "claude-3-7-sonnet-latest" }),
// highlight-next-line
tools,
});
const mathResponse = await agent.invoke({
messages: [{ role: "user", content: "what's (3 + 5) x 12?" }],
});
const weatherResponse = await agent.invoke({
messages: [{ role: "user", content: "what is the weather in nyc?" }],
});
```
=== "In a workflow"
```typescript
import { MultiServerMCPClient } from "langchain-mcp-adapters/client";
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
import { ToolNode } from "@langchain/langgraph/prebuilt";
import { ChatOpenAI } from "@langchain/openai";
import { AIMessage } from "@langchain/core/messages";
import { z } from "zod";
const model = new ChatOpenAI({ model: "gpt-4" });
const client = new MultiServerMCPClient({
math: {
command: "node",
// Make sure to update to the full absolute path to your math_server.js file
args: ["./examples/math_server.js"],
transport: "stdio",
},
weather: {
// make sure you start your weather server on port 8000
url: "http://localhost:8000/mcp/",
transport: "streamable_http",
},
});
const tools = await client.getTools();
const builder = new StateGraph(MessagesZodState)
.addNode("callModel", async (state) => {
const response = await model.bindTools(tools).invoke(state.messages);
return { messages: [response] };
})
.addNode("tools", new ToolNode(tools))
.addEdge(START, "callModel")
.addConditionalEdges("callModel", (state) => {
const lastMessage = state.messages.at(-1) as AIMessage | undefined;
if (!lastMessage?.tool_calls?.length) {
return "__end__";
}
return "tools";
})
.addEdge("tools", "callModel");
const graph = builder.compile();
const mathResponse = await graph.invoke({
messages: [{ role: "user", content: "what's (3 + 5) x 12?" }],
});
const weatherResponse = await graph.invoke({
messages: [{ role: "user", content: "what is the weather in nyc?" }],
});
```
:::
tools
)
math_response = await agent.ainvoke(
{"messages": [{"role": "user", "content": "what's (3 + 5) x 12?"}]}
)
weather_response = await agent.ainvoke(
{"messages": [{"role": "user", "content": "what is the weather in nyc?"}]}
)
```
## Custom MCP servers
:::python
To create your own MCP servers, you can use the `mcp` library. This library provides a simple way to define tools and run them as servers.
Install the MCP library:
@@ -258,24 +68,8 @@ Install the MCP library:
```bash
pip install mcp
```
:::
:::js
To create your own MCP servers, you can use the `@modelcontextprotocol/sdk` library. This library provides a simple way to define tools and run them as servers.
Install the MCP SDK:
```bash
npm install @modelcontextprotocol/sdk
```
:::
Use the following reference implementations to test your agent with MCP tool servers.
:::python
```python title="Example Math Server (stdio transport)"
from mcp.server.fastmcp import FastMCP
@@ -295,115 +89,6 @@ if __name__ == "__main__":
mcp.run(transport="stdio")
```
:::
:::js
```typescript title="Example Math Server (stdio transport)"
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
const server = new Server(
{
name: "math-server",
version: "0.1.0",
},
{
capabilities: {
tools: {},
},
}
);
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "add",
description: "Add two numbers",
inputSchema: {
type: "object",
properties: {
a: {
type: "number",
description: "First number",
},
b: {
type: "number",
description: "Second number",
},
},
required: ["a", "b"],
},
},
{
name: "multiply",
description: "Multiply two numbers",
inputSchema: {
type: "object",
properties: {
a: {
type: "number",
description: "First number",
},
b: {
type: "number",
description: "Second number",
},
},
required: ["a", "b"],
},
},
],
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case "add": {
const { a, b } = request.params.arguments as { a: number; b: number };
return {
content: [
{
type: "text",
text: String(a + b),
},
],
};
}
case "multiply": {
const { a, b } = request.params.arguments as { a: number; b: number };
return {
content: [
{
type: "text",
text: String(a * b),
},
],
};
}
default:
throw new Error(`Unknown tool: ${request.params.name}`);
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Math MCP server running on stdio");
}
main();
```
:::
:::python
```python title="Example Weather Server (Streamable HTTP transport)"
from mcp.server.fastmcp import FastMCP
@@ -418,100 +103,7 @@ if __name__ == "__main__":
mcp.run(transport="streamable-http")
```
:::
:::js
```typescript title="Example Weather Server (HTTP transport)"
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import express from "express";
const app = express();
app.use(express.json());
const server = new Server(
{
name: "weather-server",
version: "0.1.0",
},
{
capabilities: {
tools: {},
},
}
);
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "get_weather",
description: "Get weather for location",
inputSchema: {
type: "object",
properties: {
location: {
type: "string",
description: "Location to get weather for",
},
},
required: ["location"],
},
},
],
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case "get_weather": {
const { location } = request.params.arguments as { location: string };
return {
content: [
{
type: "text",
text: `It's always sunny in ${location}`,
},
],
};
}
default:
throw new Error(`Unknown tool: ${request.params.name}`);
}
});
app.post("/mcp", async (req, res) => {
const transport = new SSEServerTransport("/mcp", res);
await server.connect(transport);
});
const PORT = process.env.PORT || 8000;
app.listen(PORT, () => {
console.log(`Weather MCP server running on port ${PORT}`);
});
```
:::
:::python
## Additional resources
- [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)
:::
:::js
## Additional resources
- [MCP documentation](https://modelcontextprotocol.io/introduction)
- [MCP Transport documentation](https://modelcontextprotocol.io/docs/concepts/transports)
- [`@langchain/mcp-adapters`](https://npmjs.com/package/@langchain/mcp-adapters)
:::
- [MCP Transport documentation](https://modelcontextprotocol.io/docs/concepts/transports)
+423
View File
@@ -0,0 +1,423 @@
---
search:
boost: 2
tags:
- agent
hide:
- tags
---
# Memory
LangGraph supports two types of memory essential for building conversational agents:
- **[Short-term memory](#short-term-memory)**: Tracks the ongoing conversation by maintaining message history within a session.
- **[Long-term memory](#long-term-memory)**: Stores user-specific or application-level data across sessions.
This guide demonstrates how to use both memory types with agents in LangGraph. For a deeper
understanding of memory concepts, refer to the [LangGraph memory documentation](../concepts/memory.md).
<figure markdown="1">
![image](./assets/memory.png){: style="max-height:400px"}
<figcaption>Both <strong>short-term</strong> and <strong>long-term</strong> memory require persistent storage to maintain continuity across LLM interactions. In production environments, this data is typically stored in a database.</figcaption>
</figure>
!!! note "Terminology"
In LangGraph:
- *Short-term memory* is also referred to as **thread-level memory**.
- *Long-term memory* is also called **cross-thread memory**.
A [thread](../concepts/persistence.md#threads) represents a sequence of related runs
grouped by the same `thread_id`.
## Short-term memory
Short-term memory enables agents to track multi-turn conversations. To use it, you must:
1. Provide a `checkpointer` when creating the agent. The `checkpointer` enables [persistence](../concepts/persistence.md) of the agent's state.
2. Supply a `thread_id` in the config when running the agent. The `thread_id` is a unique identifier for the conversation session.
```python
from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import InMemorySaver
# highlight-next-line
checkpointer = InMemorySaver() # (1)!
def get_weather(city: str) -> str:
"""Get weather for a given city."""
return f"It's always sunny in {city}!"
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
# highlight-next-line
checkpointer=checkpointer # (2)!
)
# Run the agent
config = {
"configurable": {
# highlight-next-line
"thread_id": "1" # (3)!
}
}
sf_response = agent.invoke(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
config
)
# Continue the conversation using the same thread_id
ny_response = agent.invoke(
{"messages": [{"role": "user", "content": "what about new york?"}]},
# highlight-next-line
config # (4)!
)
```
1. The `InMemorySaver` is a checkpointer that stores the agent's state in memory. In a production setting, you would typically use a database or other persistent storage. Please review the [checkpointer documentation](../reference/checkpoints.md) for more options. If you're deploying with **LangGraph Platform**, the platform will provide a production-ready checkpointer for you.
2. The `checkpointer` is passed to the agent. This enables the agent to persist its state across invocations.
3. A unique `thread_id` is provided in the config. This ID is used to identify the conversation session. The value is controlled by the user and can be any string.
4. The agent will continue the conversation using the same `thread_id`. This will allow the agent to infer that the user is asking specifically about the **weather** in New York.
When the agent is invoked the second time with the same `thread_id`, the original message history from the first conversation is automatically included, allowing the agent to infer that the user is asking specifically about the **weather** in New York.
!!! Note "LangGraph Platform providers a production-ready checkpointer"
If you're using [LangGraph Platform](./deployment.md), during deployment your checkpointer will be automatically configured to use a production-ready database.
### Manage message history
Long conversations can exceed the LLM's context window. Common solutions are:
* [Summarization](#summarize-message-history): Maintain a running summary of the conversation
* [Trimming](#trim-message-history): Remove first or last N messages in the history
This allows the agent to keep track of the conversation without exceeding the LLM's context window.
To manage message history, specify `pre_model_hook` — a function ([node](../concepts/low_level.md#nodes)) that will always run before calling the language model.
#### Summarize message history
<figure markdown="1">
![image](./assets/summary.png){: style="max-height:400px"}
<figcaption>Long conversations can exceed the LLM's context window. A common solution is to maintain a running summary of the conversation. This allows the agent to keep track of the conversation without exceeding the LLM's context window.
</figcaption>
</figure>
To summarize message history, you can use [`pre_model_hook`][langgraph.prebuilt.chat_agent_executor.create_react_agent] with a prebuilt [`SummarizationNode`](https://langchain-ai.github.io/langmem/reference/short_term/#langmem.short_term.SummarizationNode):
```python
from langchain_anthropic import ChatAnthropic
from langmem.short_term import SummarizationNode
from langchain_core.messages.utils import count_tokens_approximately
from langgraph.prebuilt import create_react_agent
from langgraph.prebuilt.chat_agent_executor import AgentState
from langgraph.checkpoint.memory import InMemorySaver
from typing import Any
model = ChatAnthropic(model="claude-3-7-sonnet-latest")
summarization_node = SummarizationNode( # (1)!
token_counter=count_tokens_approximately,
model=model,
max_tokens=384,
max_summary_tokens=128,
output_messages_key="llm_input_messages",
)
class State(AgentState):
# NOTE: we're adding this key to keep track of previous summary information
# to make sure we're not summarizing on every LLM call
# highlight-next-line
context: dict[str, Any] # (2)!
checkpointer = InMemorySaver() # (3)!
agent = create_react_agent(
model=model,
tools=tools,
# highlight-next-line
pre_model_hook=summarization_node, # (4)!
# highlight-next-line
state_schema=State, # (5)!
checkpointer=checkpointer,
)
```
1. The `InMemorySaver` is a checkpointer that stores the agent's state in memory. In a production setting, you would typically use a database or other persistent storage. Please review the [checkpointer documentation](../reference/checkpoints.md) for more options. If you're deploying with **LangGraph Platform**, the platform will provide a production-ready checkpointer for you.
2. The `context` key is added to the agent's state. The key contains book-keeping information for the summarization node. It is used to keep track of the last summary information and ensure that the agent doesn't summarize on every LLM call, which can be inefficient.
3. The `checkpointer` is passed to the agent. This enables the agent to persist its state across invocations.
4. The `pre_model_hook` is set to the `SummarizationNode`. This node will summarize the message history before sending it to the LLM. The summarization node will automatically handle the summarization process and update the agent's state with the new summary. You can replace this with a custom implementation if you prefer. Please see the [create_react_agent][langgraph.prebuilt.chat_agent_executor.create_react_agent] API reference for more details.
5. The `state_schema` is set to the `State` class, which is the custom state that contains an extra `context` key.
#### Trim message history
To trim message history, you can use [`pre_model_hook`][langgraph.prebuilt.chat_agent_executor.create_react_agent] with [`trim_messages`](https://python.langchain.com/api_reference/core/messages/langchain_core.messages.utils.trim_messages.html) function:
```python
# highlight-next-line
from langchain_core.messages.utils import (
# highlight-next-line
trim_messages,
# highlight-next-line
count_tokens_approximately
# highlight-next-line
)
from langgraph.prebuilt import create_react_agent
# This function will be called every time before the node that calls LLM
def pre_model_hook(state):
trimmed_messages = trim_messages(
state["messages"],
strategy="last",
token_counter=count_tokens_approximately,
max_tokens=384,
start_on="human",
end_on=("human", "tool"),
)
# highlight-next-line
return {"llm_input_messages": trimmed_messages}
checkpointer = InMemorySaver()
agent = create_react_agent(
model,
tools,
# highlight-next-line
pre_model_hook=pre_model_hook,
checkpointer=checkpointer,
)
```
To learn more about using `pre_model_hook` for managing message history, see this [how-to guide](../how-tos/create-react-agent-manage-message-history.ipynb)
### Read in tools { #read-short-term }
LangGraph allows agent to access its short-term memory (state) inside the tools.
```python
from typing import Annotated
from langgraph.prebuilt import InjectedState, create_react_agent
class CustomState(AgentState):
# highlight-next-line
user_id: str
def get_user_info(
# highlight-next-line
state: Annotated[CustomState, InjectedState]
) -> str:
"""Look up user info."""
# highlight-next-line
user_id = state["user_id"]
return "User is John Smith" if user_id == "user_123" else "Unknown user"
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_user_info],
# highlight-next-line
state_schema=CustomState,
)
agent.invoke({
"messages": "look up user information",
# highlight-next-line
"user_id": "user_123"
})
```
See the [Context](./context.md#__tabbed_2_2) guide for more information.
### Write from tools { #write-short-term }
To modify the agent's short-term memory (state) during execution, you can return state updates directly from the tools. This is useful for persisting intermediate results or making information accessible to subsequent tools or prompts.
```python
from typing import Annotated
from langchain_core.tools import InjectedToolCallId
from langchain_core.runnables import RunnableConfig
from langchain_core.messages import ToolMessage
from langgraph.prebuilt import InjectedState, create_react_agent
from langgraph.prebuilt.chat_agent_executor import AgentState
from langgraph.types import Command
class CustomState(AgentState):
# highlight-next-line
user_name: str
def update_user_info(
tool_call_id: Annotated[str, InjectedToolCallId],
config: RunnableConfig
) -> Command:
"""Look up and update user info."""
user_id = config["configurable"].get("user_id")
name = "John Smith" if user_id == "user_123" else "Unknown user"
# highlight-next-line
return Command(update={
# highlight-next-line
"user_name": name,
# update the message history
"messages": [
ToolMessage(
"Successfully looked up user information",
tool_call_id=tool_call_id
)
]
})
def greet(
# highlight-next-line
state: Annotated[CustomState, InjectedState]
) -> str:
"""Use this to greet the user once you found their info."""
user_name = state["user_name"]
return f"Hello {user_name}!"
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[update_user_info, greet],
# highlight-next-line
state_schema=CustomState
)
agent.invoke(
{"messages": [{"role": "user", "content": "greet the user"}]},
# highlight-next-line
config={"configurable": {"user_id": "user_123"}}
)
```
For more details, see [how to update state from tools](../how-tos/tool-calling.ipynb#update).
## Long-term memory
Use long-term memory to store user-specific or application-specific data across conversations. This is useful for applications like chatbots, where you want to remember user preferences or other information.
To use long-term memory, you need to:
1. [Configure a store](../how-tos/persistence.ipynb#add-long-term-memory) to persist data across invocations.
2. Use the [`get_store`][langgraph.config.get_store] function to access the store from within tools or prompts.
### Read { #read-long-term }
```python title="A tool the agent can use to look up user information"
from langchain_core.runnables import RunnableConfig
from langgraph.config import get_store
from langgraph.prebuilt import create_react_agent
from langgraph.store.memory import InMemoryStore
# highlight-next-line
store = InMemoryStore() # (1)!
# highlight-next-line
store.put( # (2)!
("users",), # (3)!
"user_123", # (4)!
{
"name": "John Smith",
"language": "English",
} # (5)!
)
def get_user_info(config: RunnableConfig) -> str:
"""Look up user info."""
# Same as that provided to `create_react_agent`
# highlight-next-line
store = get_store() # (6)!
user_id = config["configurable"].get("user_id")
# highlight-next-line
user_info = store.get(("users",), user_id) # (7)!
return str(user_info.value) if user_info else "Unknown user"
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_user_info],
# highlight-next-line
store=store # (8)!
)
# Run the agent
agent.invoke(
{"messages": [{"role": "user", "content": "look up user information"}]},
# highlight-next-line
config={"configurable": {"user_id": "user_123"}}
)
```
1. The `InMemoryStore` is a store that stores data in memory. In a production setting, you would typically use a database or other persistent storage. Please review the [store documentation](../reference/store.md) for more options. If you're deploying with **LangGraph Platform**, the platform will provide a production-ready store for you.
2. For this example, we write some sample data to the store using the `put` method. Please see the [BaseStore.put][langgraph.store.base.BaseStore.put] API reference for more details.
3. The first argument is the namespace. This is used to group related data together. In this case, we are using the `users` namespace to group user data.
4. A key within the namespace. This example uses a user ID for the key.
5. The data that we want to store for the given user.
6. The `get_store` function is used to access the store. You can call it from anywhere in your code, including tools and prompts. This function returns the store that was passed to the agent when it was created.
7. The `get` method is used to retrieve data from the store. The first argument is the namespace, and the second argument is the key. This will return a `StoreValue` object, which contains the value and metadata about the value.
8. The `store` is passed to the agent. This enables the agent to access the store when running tools. You can also use the `get_store` function to access the store from anywhere in your code.
### Write { #write-long-term }
```python title="Example of a tool that updates user information"
from typing_extensions import TypedDict
from langgraph.config import get_store
from langgraph.prebuilt import create_react_agent
from langgraph.store.memory import InMemoryStore
store = InMemoryStore() # (1)!
class UserInfo(TypedDict): # (2)!
name: str
def save_user_info(user_info: UserInfo, config: RunnableConfig) -> str: # (3)!
"""Save user info."""
# Same as that provided to `create_react_agent`
# highlight-next-line
store = get_store() # (4)!
user_id = config["configurable"].get("user_id")
# highlight-next-line
store.put(("users",), user_id, user_info) # (5)!
return "Successfully saved user info."
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[save_user_info],
# highlight-next-line
store=store
)
# Run the agent
agent.invoke(
{"messages": [{"role": "user", "content": "My name is John Smith"}]},
# highlight-next-line
config={"configurable": {"user_id": "user_123"}} # (6)!
)
# You can access the store directly to get the value
store.get(("users",), "user_123").value
```
1. The `InMemoryStore` is a store that stores data in memory. In a production setting, you would typically use a database or other persistent storage. Please review the [store documentation](../reference/store.md) for more options. If you're deploying with **LangGraph Platform**, the platform will provide a production-ready store for you.
2. The `UserInfo` class is a `TypedDict` that defines the structure of the user information. The LLM will use this to format the response according to the schema.
3. The `save_user_info` function is a tool that allows an agent to update user information. This could be useful for a chat application where the user wants to update their profile information.
4. The `get_store` function is used to access the store. You can call it from anywhere in your code, including tools and prompts. This function returns the store that was passed to the agent when it was created.
5. The `put` method is used to store data in the store. The first argument is the namespace, and the second argument is the key. This will store the user information in the store.
6. The `user_id` is passed in the config. This is used to identify the user whose information is being updated.
### Semantic search
LangGraph also allows you to [search](https://langchain-ai.github.io/langgraph/how-tos/memory/semantic-search/#using-in-create-react-agent) for items in long-term memory by semantic similarity.
### Prebuilt memory tools
**LangMem** is a LangChain-maintained library that offers tools for managing long-term memories in your agent. See the [LangMem documentation](https://langchain-ai.github.io/langmem/) for usage examples.
## Additional resources
* [Memory in LangGraph](../concepts/memory.md)
+193 -209
View File
@@ -1,155 +1,234 @@
---
search:
boost: 2
tags:
- anthropic
- openai
- agent
hide:
- tags
---
# Models
LangGraph provides built-in support for [LLMs (language models)](https://python.langchain.com/docs/concepts/chat_models/) via the LangChain library. This makes it easy to integrate various LLMs into your agents and workflows.
This page describes how to configure the chat model used by an agent.
## Initialize a model
## Tool calling support
:::python
Use [`init_chat_model`](https://python.langchain.com/docs/how_to/chat_models_universal_init/) to initialize models:
To enable tool-calling agents, the underlying LLM must support [tool calling](https://python.langchain.com/docs/concepts/tool_calling/).
{% include-markdown "../../snippets/chat_model_tabs.md" %}
:::
Compatible models can be found in the [LangChain integrations directory](https://python.langchain.com/docs/integrations/chat/).
:::js
Use model provider classes to initialize models:
## Specifying a model by name
You can configure an agent with a model name string:
=== "OpenAI"
```typescript
import { ChatOpenAI } from "@langchain/openai";
```python
import os
from langgraph.prebuilt import create_react_agent
const model = new ChatOpenAI({
model: "gpt-4o",
temperature: 0,
});
os.environ["OPENAI_API_KEY"] = "sk-..."
agent = create_react_agent(
# highlight-next-line
model="openai:gpt-4.1",
# other parameters
)
```
=== "Anthropic"
```typescript
import { ChatAnthropic } from "@langchain/anthropic";
```python
import os
from langgraph.prebuilt import create_react_agent
const model = new ChatAnthropic({
model: "claude-3-5-sonnet-20240620",
temperature: 0,
maxTokens: 2048,
});
os.environ["ANTHROPIC_API_KEY"] = "sk-..."
agent = create_react_agent(
# highlight-next-line
model="anthropic:claude-3-7-sonnet-latest",
# other parameters
)
```
=== "Google"
=== "Azure"
```typescript
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
```python
import os
from langgraph.prebuilt import create_react_agent
const model = new ChatGoogleGenerativeAI({
model: "gemini-1.5-pro",
temperature: 0,
});
os.environ["AZURE_OPENAI_API_KEY"] = "..."
os.environ["AZURE_OPENAI_ENDPOINT"] = "..."
os.environ["OPENAI_API_VERSION"] = "2025-03-01-preview"
agent = create_react_agent(
# highlight-next-line
model="azure_openai:gpt-4.1",
# other parameters
)
```
=== "Groq"
=== "Google Gemini"
```typescript
import { ChatGroq } from "@langchain/groq";
```python
import os
from langgraph.prebuilt import create_react_agent
const model = new ChatGroq({
model: "llama-3.1-70b-versatile",
temperature: 0,
});
os.environ["GOOGLE_API_KEY"] = "..."
agent = create_react_agent(
# highlight-next-line
model="google_genai:gemini-2.0-flash",
# other parameters
)
```
:::
=== "AWS Bedrock"
:::python
```python
from langgraph.prebuilt import create_react_agent
### Instantiate a model directly
# Follow the steps here to configure your credentials:
# https://docs.aws.amazon.com/bedrock/latest/userguide/getting-started.html
agent = create_react_agent(
# highlight-next-line
model="bedrock_converse:anthropic.claude-3-5-sonnet-20240620-v1:0",
# other parameters
)
```
## Using `init_chat_model`
The [`init_chat_model`](https://python.langchain.com/docs/how_to/chat_models_universal_init/) utility simplifies model initialization with configurable parameters:
=== "OpenAI"
```
pip install -U "langchain[openai]"
```
```python
import os
from langchain.chat_models import init_chat_model
os.environ["OPENAI_API_KEY"] = "sk-..."
model = init_chat_model(
"openai:gpt-4.1",
temperature=0,
# other parameters
)
```
=== "Anthropic"
```
pip install -U "langchain[anthropic]"
```
```python
import os
from langchain.chat_models import init_chat_model
os.environ["ANTHROPIC_API_KEY"] = "sk-..."
model = init_chat_model(
"anthropic:claude-3-5-sonnet-latest",
temperature=0,
# other parameters
)
```
=== "Azure"
```
pip install -U "langchain[openai]"
```
```python
import os
from langchain.chat_models import init_chat_model
os.environ["AZURE_OPENAI_API_KEY"] = "..."
os.environ["AZURE_OPENAI_ENDPOINT"] = "..."
os.environ["OPENAI_API_VERSION"] = "2025-03-01-preview"
model = init_chat_model(
"azure_openai:gpt-4.1",
azure_deployment=os.environ["AZURE_OPENAI_DEPLOYMENT_NAME"],
temperature=0,
# other parameters
)
```
=== "Google Gemini"
```
pip install -U "langchain[google-genai]"
```
```python
import os
from langchain.chat_models import init_chat_model
os.environ["GOOGLE_API_KEY"] = "..."
model = init_chat_model(
"google_genai:gemini-2.0-flash",
temperature=0,
# other parameters
)
```
=== "AWS Bedrock"
```
pip install -U "langchain[aws]"
```
```python
from langchain.chat_models import init_chat_model
# Follow the steps here to configure your credentials:
# https://docs.aws.amazon.com/bedrock/latest/userguide/getting-started.html
model = init_chat_model(
"anthropic.claude-3-5-sonnet-20240620-v1:0",
model_provider="bedrock_converse",
temperature=0,
# other parameters
)
```
Refer to the [API reference](https://python.langchain.com/api_reference/langchain/chat_models/langchain.chat_models.base.init_chat_model.html) for advanced options.
## Using provider-specific LLMs
If a model provider is not available via `init_chat_model`, you can instantiate the provider's model class directly. The model must implement the [BaseChatModel interface](https://python.langchain.com/api_reference/core/language_models/langchain_core.language_models.chat_models.BaseChatModel.html) and support tool calling:
```python
# Anthropic is already supported by `init_chat_model`,
# but you can also instantiate it directly.
from langchain_anthropic import ChatAnthropic
from langgraph.prebuilt import create_react_agent
model = ChatAnthropic(
model="claude-3-7-sonnet-latest",
temperature=0,
max_tokens=2048
model="claude-3-7-sonnet-latest",
temperature=0,
max_tokens=2048
)
agent = create_react_agent(
# highlight-next-line
model=model,
# other parameters
)
```
:::
!!! note "Illustrative example"
!!! important "Tool calling support"
The example above uses `ChatAnthropic`, which is already supported by `init_chat_model`. This pattern is shown to illustrate how to manually instantiate a model not available through init_chat_model.
If you are building an agent or workflow that requires the model to call external tools, ensure that the underlying
language model supports [tool calling](../concepts/tools.md). Compatible models can be found in the [LangChain integrations directory](https://python.langchain.com/docs/integrations/chat/).
## Disable streaming
## Use in an agent
:::python
When using `create_react_agent` you can specify the model by its name string, which is a shorthand for initializing the model using `init_chat_model`. This allows you to use the model without needing to import or instantiate it directly.
=== "model name"
```python
from langgraph.prebuilt import create_react_agent
create_react_agent(
# highlight-next-line
model="anthropic:claude-3-7-sonnet-latest",
# other parameters
)
```
=== "model instance"
```python
from langchain_anthropic import ChatAnthropic
from langgraph.prebuilt import create_react_agent
model = ChatAnthropic(
model="claude-3-7-sonnet-latest",
temperature=0,
max_tokens=2048
)
# Alternatively
# model = init_chat_model("anthropic:claude-3-7-sonnet-latest")
agent = create_react_agent(
# highlight-next-line
model=model,
# other parameters
)
```
:::
:::js
When using `createReactAgent` you can pass the model instance directly:
```typescript
import { ChatOpenAI } from "@langchain/openai";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
const model = new ChatOpenAI({
model: "gpt-4o",
temperature: 0,
});
const agent = createReactAgent({
llm: model,
tools: tools,
});
```
:::
## Advanced model configuration
### Disable streaming
:::python
To disable streaming of the individual LLM tokens, set `disable_streaming=True` when initializing the model:
=== "`init_chat_model`"
@@ -177,25 +256,9 @@ To disable streaming of the individual LLM tokens, set `disable_streaming=True`
```
Refer to the [API reference](https://python.langchain.com/api_reference/core/language_models/langchain_core.language_models.chat_models.BaseChatModel.html#langchain_core.language_models.chat_models.BaseChatModel.disable_streaming) for more information on `disable_streaming`
:::
:::js
To disable streaming of the individual LLM tokens, set `streaming: false` when initializing the model:
## Adding model fallbacks
```typescript
import { ChatOpenAI } from "@langchain/openai";
const model = new ChatOpenAI({
model: "gpt-4o",
streaming: false,
});
```
:::
### Add model fallbacks
:::python
You can add a fallback to a different model or a different LLM provider using `model.with_fallbacks([...])`:
=== "`init_chat_model`"
@@ -228,87 +291,8 @@ You can add a fallback to a different model or a different LLM provider using `m
```
See this [guide](https://python.langchain.com/docs/how_to/fallbacks/#fallback-to-better-model) for more information on model fallbacks.
:::
:::js
You can add a fallback to a different model or a different LLM provider using `model.withFallbacks([...])`:
```typescript
import { ChatOpenAI } from "@langchain/openai";
import { ChatAnthropic } from "@langchain/anthropic";
const modelWithFallbacks = new ChatOpenAI({
model: "gpt-4o",
}).withFallbacks([
new ChatAnthropic({
model: "claude-3-5-sonnet-20240620",
}),
]);
```
See this [guide](https://js.langchain.com/docs/how_to/fallbacks/#fallback-to-better-model) for more information on model fallbacks.
:::
:::python
### Use the built-in rate limiter
Langchain includes a built-in in-memory rate limiter. This rate limiter is thread safe and can be shared by multiple threads in the same process.
```python
from langchain_core.rate_limiters import InMemoryRateLimiter
from langchain_anthropic import ChatAnthropic
rate_limiter = InMemoryRateLimiter(
requests_per_second=0.1, # <-- Super slow! We can only make a request once every 10 seconds!!
check_every_n_seconds=0.1, # Wake up every 100 ms to check whether allowed to make a request,
max_bucket_size=10, # Controls the maximum burst size.
)
model = ChatAnthropic(
model_name="claude-3-opus-20240229",
rate_limiter=rate_limiter
)
```
See the LangChain docs for more information on how to [handle rate limiting](https://python.langchain.com/docs/how_to/chat_model_rate_limiting/).
:::
## Bring your own model
If your desired LLM isn't officially supported by LangChain, consider these options:
:::python
1. **Implement a custom LangChain chat model**: Create a model conforming to the [LangChain chat model interface](https://python.langchain.com/docs/how_to/custom_chat_model/). This enables full compatibility with LangGraph's agents and workflows but requires understanding of the LangChain framework.
:::
:::js
1. **Implement a custom LangChain chat model**: Create a model conforming to the [LangChain chat model interface](https://js.langchain.com/docs/how_to/custom_chat/). This enables full compatibility with LangGraph's agents and workflows but requires understanding of the LangChain framework.
:::
2. **Direct invocation with custom streaming**: Use your model directly by [adding custom streaming logic](../how-tos/streaming.md#use-with-any-llm) with `StreamWriter`.
Refer to the [custom streaming documentation](../how-tos/streaming.md#use-with-any-llm) for guidance. This approach suits custom workflows where prebuilt agent integration is not necessary.
## Additional resources
:::python
- [Multimodal inputs](https://python.langchain.com/docs/how_to/multimodal_inputs/)
- [Structured outputs](https://python.langchain.com/docs/how_to/structured_output/)
- [Model integration directory](https://python.langchain.com/docs/integrations/chat/)
- [Force model to call a specific tool](https://python.langchain.com/docs/how_to/tool_choice/)
- [All chat model how-to guides](https://python.langchain.com/docs/how_to/#chat-models)
- [Chat model integrations](https://python.langchain.com/docs/integrations/chat/)
:::
:::js
- [Multimodal inputs](https://js.langchain.com/docs/how_to/multimodal_inputs/)
- [Structured outputs](https://js.langchain.com/docs/how_to/structured_output/)
- [Model integration directory](https://js.langchain.com/docs/integrations/chat/)
- [Force model to call a specific tool](https://js.langchain.com/docs/how_to/tool_choice/)
- [All chat model how-to guides](https://js.langchain.com/docs/how_to/#chat-models)
- [Chat model integrations](https://js.langchain.com/docs/integrations/chat/)
:::
- [Universal initialization with `init_chat_model`](https://python.langchain.com/docs/how_to/chat_models_universal_init/)
+6 -335
View File
@@ -9,7 +9,7 @@ hide:
# Multi-agent
A single agent might struggle if it needs to specialize in multiple domains or manage many tools. To tackle this, you can break your agent into smaller, independent agents and compose them into a [multi-agent system](../concepts/multi_agent.md).
A single agent might struggle if it needs to specialize in multiple domains or manage many tools. To tackle this, you can break your agent into smaller, independent agents and composing them into a [multi-agent system](../concepts/multi_agent.md).
In multi-agent systems, agents need to communicate between each other. They do so via [handoffs](#handoffs) — a primitive that describes which agent to hand control to and the payload to send to that agent.
@@ -22,7 +22,6 @@ Two of the most popular multi-agent architectures are:
![Supervisor](./assets/supervisor.png)
:::python
Use [`langgraph-supervisor`](https://github.com/langchain-ai/langgraph-supervisor-py) library to create a supervisor multi-agent system:
```bash
@@ -83,76 +82,10 @@ for chunk in supervisor.stream(
print("\n")
```
:::
:::js
Use [`@langchain/langgraph-supervisor`](https://github.com/langchain-ai/langgraphjs/tree/main/libs/langgraph-supervisor) library to create a supervisor multi-agent system:
```bash
npm install @langchain/langgraph-supervisor
```
```typescript
import { ChatOpenAI } from "@langchain/openai";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
// highlight-next-line
import { createSupervisor } from "langgraph-supervisor";
function bookHotel(hotelName: string) {
/**Book a hotel*/
return `Successfully booked a stay at ${hotelName}.`;
}
function bookFlight(fromAirport: string, toAirport: string) {
/**Book a flight*/
return `Successfully booked a flight from ${fromAirport} to ${toAirport}.`;
}
const flightAssistant = createReactAgent({
llm: "openai:gpt-4o",
tools: [bookFlight],
stateModifier: "You are a flight booking assistant",
// highlight-next-line
name: "flight_assistant",
});
const hotelAssistant = createReactAgent({
llm: "openai:gpt-4o",
tools: [bookHotel],
stateModifier: "You are a hotel booking assistant",
// highlight-next-line
name: "hotel_assistant",
});
// highlight-next-line
const supervisor = createSupervisor({
agents: [flightAssistant, hotelAssistant],
llm: new ChatOpenAI({ model: "gpt-4o" }),
systemPrompt:
"You manage a hotel booking assistant and a " +
"flight booking assistant. Assign work to them.",
});
for await (const chunk of supervisor.stream({
messages: [
{
role: "user",
content: "book a flight from BOS to JFK and a stay at McKittrick Hotel",
},
],
})) {
console.log(chunk);
console.log("\n");
}
```
:::
## Swarm
![Swarm](./assets/swarm.png)
:::python
Use [`langgraph-swarm`](https://github.com/langchain-ai/langgraph-swarm-py) library to create a swarm multi-agent system:
```bash
@@ -210,82 +143,18 @@ for chunk in swarm.stream(
print("\n")
```
:::
:::js
Use [`@langchain/langgraph-swarm`](https://github.com/langchain-ai/langgraphjs/tree/main/libs/langgraph-swarm) library to create a swarm multi-agent system:
```bash
npm install @langchain/langgraph-swarm
```
```typescript
import { createReactAgent } from "@langchain/langgraph/prebuilt";
// highlight-next-line
import { createSwarm, createHandoffTool } from "@langchain/langgraph-swarm";
const transferToHotelAssistant = createHandoffTool({
agentName: "hotel_assistant",
description: "Transfer user to the hotel-booking assistant.",
});
const transferToFlightAssistant = createHandoffTool({
agentName: "flight_assistant",
description: "Transfer user to the flight-booking assistant.",
});
const flightAssistant = createReactAgent({
llm: "anthropic:claude-3-5-sonnet-latest",
// highlight-next-line
tools: [bookFlight, transferToHotelAssistant],
stateModifier: "You are a flight booking assistant",
// highlight-next-line
name: "flight_assistant",
});
const hotelAssistant = createReactAgent({
llm: "anthropic:claude-3-5-sonnet-latest",
// highlight-next-line
tools: [bookHotel, transferToFlightAssistant],
stateModifier: "You are a hotel booking assistant",
// highlight-next-line
name: "hotel_assistant",
});
// highlight-next-line
const swarm = createSwarm({
agents: [flightAssistant, hotelAssistant],
defaultActiveAgent: "flight_assistant",
});
for await (const chunk of swarm.stream({
messages: [
{
role: "user",
content: "book a flight from BOS to JFK and a stay at McKittrick Hotel",
},
],
})) {
console.log(chunk);
console.log("\n");
}
```
:::
## Handoffs
A common pattern in multi-agent interactions is **handoffs**, where one agent _hands off_ control to another. Handoffs allow you to specify:
A common pattern in multi-agent interactions is **handoffs**, where one agent *hands off* control to another. Handoffs allow you to specify:
- **destination**: target agent to navigate to
- **payload**: information to pass to that agent
:::python
This is used both by `langgraph-supervisor` (supervisor hands off to individual agents) and `langgraph-swarm` (an individual agent can hand off to other agents).
To implement handoffs with `create_react_agent`, you need to:
1. Create a special tool that can transfer control to a different agent
1. Create a special tool that can transfer control to a different agent
```python
def transfer_to_bob():
@@ -304,7 +173,7 @@ To implement handoffs with `create_react_agent`, you need to:
)
```
2. Create individual agents that have access to handoff tools:
1. Create individual agents that have access to handoff tools:
```python
flight_assistant = create_react_agent(
@@ -315,7 +184,7 @@ To implement handoffs with `create_react_agent`, you need to:
)
```
3. Define a parent graph that contains individual agents as nodes:
1. Define a parent graph that contains individual agents as nodes:
```python
from langgraph.graph import StateGraph, MessagesState
@@ -327,60 +196,8 @@ To implement handoffs with `create_react_agent`, you need to:
)
```
:::
:::js
This is used both by `@langchain/langgraph-supervisor` (supervisor hands off to individual agents) and `@langchain/langgraph-swarm` (an individual agent can hand off to other agents).
To implement handoffs with `createReactAgent`, you need to:
1. Create a special tool that can transfer control to a different agent
```typescript
function transferToBob() {
/**Transfer to bob.*/
return new Command({
// name of the agent (node) to go to
// highlight-next-line
goto: "bob",
// data to send to the agent
// highlight-next-line
update: { messages: [...] },
// indicate to LangGraph that we need to navigate to
// agent node in a parent graph
// highlight-next-line
graph: Command.PARENT,
});
}
```
2. Create individual agents that have access to handoff tools:
```typescript
const flightAssistant = createReactAgent({
..., tools: [bookFlight, transferToHotelAssistant]
});
const hotelAssistant = createReactAgent({
..., tools: [bookHotel, transferToFlightAssistant]
});
```
3. Define a parent graph that contains individual agents as nodes:
```typescript
import { StateGraph, MessagesZodState } from "@langchain/langgraph";
const multiAgentGraph = new StateGraph(MessagesZodState)
.addNode("flight_assistant", flightAssistant)
.addNode("hotel_assistant", hotelAssistant)
// ...
```
:::
Putting this together, here is how you can implement a simple multi-agent system with two agents — a flight booking assistant and a hotel booking assistant:
:::python
```python
from typing import Annotated
from langchain_core.tools import tool, InjectedToolCallId
@@ -481,157 +298,11 @@ for chunk in multi_agent_graph.stream(
3. Name of the agent or node to hand off to.
4. Take the agent's messages and **add** them to the parent's **state** as part of the handoff. The next agent will see the parent state.
5. Indicate to LangGraph that we need to navigate to agent node in a **parent** multi-agent graph.
:::
:::js
```typescript
import { tool } from "@langchain/core/tools";
import { ChatAnthropic } from "@langchain/anthropic";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import {
StateGraph,
START,
MessagesZodState,
Command,
} from "@langchain/langgraph";
import { z } from "zod";
function createHandoffTool({
agentName,
description,
}: {
agentName: string;
description?: string;
}) {
const name = `transfer_to_${agentName}`;
const toolDescription = description || `Transfer to ${agentName}`;
return tool(
async (_, config) => {
const toolMessage = {
role: "tool" as const,
content: `Successfully transferred to ${agentName}`,
name: name,
tool_call_id: config.toolCall?.id!,
};
return new Command({
// (2)!
// highlight-next-line
goto: agentName, // (3)!
// highlight-next-line
update: { messages: [toolMessage] }, // (4)!
// highlight-next-line
graph: Command.PARENT, // (5)!
});
},
{
name,
description: toolDescription,
schema: z.object({}),
}
);
}
// Handoffs
const transferToHotelAssistant = createHandoffTool({
agentName: "hotel_assistant",
description: "Transfer user to the hotel-booking assistant.",
});
const transferToFlightAssistant = createHandoffTool({
agentName: "flight_assistant",
description: "Transfer user to the flight-booking assistant.",
});
// Simple agent tools
const bookHotel = tool(
async ({ hotelName }) => {
/**Book a hotel*/
return `Successfully booked a stay at ${hotelName}.`;
},
{
name: "book_hotel",
description: "Book a hotel",
schema: z.object({
hotelName: z.string().describe("Name of the hotel to book"),
}),
}
);
const bookFlight = tool(
async ({ fromAirport, toAirport }) => {
/**Book a flight*/
return `Successfully booked a flight from ${fromAirport} to ${toAirport}.`;
},
{
name: "book_flight",
description: "Book a flight",
schema: z.object({
fromAirport: z.string().describe("Departure airport code"),
toAirport: z.string().describe("Arrival airport code"),
}),
}
);
// Define agents
const flightAssistant = createReactAgent({
llm: new ChatAnthropic({ model: "anthropic:claude-3-5-sonnet-latest" }),
// highlight-next-line
tools: [bookFlight, transferToHotelAssistant],
stateModifier: "You are a flight booking assistant",
// highlight-next-line
name: "flight_assistant",
});
const hotelAssistant = createReactAgent({
llm: new ChatAnthropic({ model: "anthropic:claude-3-5-sonnet-latest" }),
// highlight-next-line
tools: [bookHotel, transferToFlightAssistant],
stateModifier: "You are a hotel booking assistant",
// highlight-next-line
name: "hotel_assistant",
});
// Define multi-agent graph
const multiAgentGraph = new StateGraph(MessagesZodState)
.addNode("flight_assistant", flightAssistant)
.addNode("hotel_assistant", hotelAssistant)
.addEdge(START, "flight_assistant")
.compile();
// Run the multi-agent graph
for await (const chunk of multiAgentGraph.stream({
messages: [
{
role: "user",
content: "book a flight from BOS to JFK and a stay at McKittrick Hotel",
},
],
})) {
console.log(chunk);
console.log("\n");
}
```
1. Access agent's state
2. The `Command` primitive allows specifying a state update and a node transition as a single operation, making it useful for implementing handoffs.
3. Name of the agent or node to hand off to.
4. Take the agent's messages and **add** them to the parent's **state** as part of the handoff. The next agent will see the parent state.
5. Indicate to LangGraph that we need to navigate to agent node in a **parent** multi-agent graph.
:::
!!! Note
This handoff implementation assumes that:
- each agent receives overall message history (across all agents) in the multi-agent system as its input
- each agent outputs its internal messages history to the overall message history of the multi-agent system
:::python
Check out LangGraph [supervisor](https://github.com/langchain-ai/langgraph-supervisor-py#customizing-handoff-tools) and [swarm](https://github.com/langchain-ai/langgraph-swarm-py#customizing-handoff-tools) documentation to learn how to customize handoffs.
:::
:::js
Check out LangGraph [supervisor](https://github.com/langchain-ai/langgraphjs/tree/main/libs/langgraph-supervisor#customizing-handoff-tools) and [swarm](https://github.com/langchain-ai/langgraphjs/tree/main/libs/langgraph-swarm#customizing-handoff-tools) documentation to learn how to customize handoffs.
:::
Check out LangGraph [supervisor](https://github.com/langchain-ai/langgraph-supervisor-py#customizing-handoff-tools) and [swarm](https://github.com/langchain-ai/langgraph-swarm-py#customizing-handoff-tools) documentation to learn how to customize handoffs.
+25 -181
View File
@@ -8,13 +8,13 @@ hide:
- tags
---
# Agent development using prebuilt components
# Agent development with LangGraph
LangGraph provides both low-level primitives and high-level prebuilt components for building agent-based applications. This section focuses on the prebuilt, ready-to-use components designed to help you construct agentic systems quickly and reliably—without the need to implement orchestration, memory, or human feedback handling from scratch.
**LangGraph** provides both low-level primitives and high-level prebuilt components for building agent-based applications. This section focuses on the **prebuilt**, **reusable** components designed to help you construct agentic systems quickly and reliably—without the need to implement orchestration, memory, or human feedback handling from scratch.
## What is an agent?
An _agent_ consists of three components: a **large language model (LLM)**, a set of **tools** it can use, and a **prompt** that provides instructions.
An *agent* consists of three components: a **large language model (LLM)**, a set of **tools** it can use, and a **prompt** that provides instructions.
The LLM operates in a loop. In each iteration, it selects a tool to invoke, provides input, receives the result (an observation), and uses that observation to inform the next action. The loop continues until a stopping condition is met — typically when the agent has gathered enough information to respond to the user.
@@ -27,12 +27,12 @@ The LLM operates in a loop. In each iteration, it selects a tool to invoke, prov
LangGraph includes several capabilities essential for building robust, production-ready agentic systems:
- [**Memory integration**](../how-tos/memory/add-memory.md): Native support for _short-term_ (session-based) and _long-term_ (persistent across sessions) memory, enabling stateful behaviors in chatbots and assistants.
- [**Human-in-the-loop control**](../concepts/human_in_the_loop.md): Execution can pause _indefinitely_ to await human feedback—unlike websocket-based solutions limited to real-time interaction. This enables asynchronous approval, correction, or intervention at any point in the workflow.
- [**Streaming support**](../how-tos/streaming.md): Real-time streaming of agent state, model tokens, tool outputs, or combined streams.
- [**Deployment tooling**](../tutorials/langgraph-platform/local-server.md): Includes infrastructure-free deployment tools. [**LangGraph Platform**](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) supports testing, debugging, and deployment.
- **[Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/)**: A visual IDE for inspecting and debugging workflows.
- Supports multiple [**deployment options**](https://langchain-ai.github.io/langgraph/concepts/deployment_options.md) for production.
- [**Memory integration**](./memory.md): Native support for *short-term* (session-based) and *long-term* (persistent across sessions) memory, enabling stateful behaviors in chatbots and assistants.
- [**Human-in-the-loop control**](./human-in-the-loop.md): Execution can pause *indefinitely* to await human feedback—unlike websocket-based solutions limited to real-time interaction. This enables asynchronous approval, correction, or intervention at any point in the workflow.
- [**Streaming support**](./streaming.md): Real-time streaming of agent state, model tokens, tool outputs, or combined streams.
- [**Deployment tooling**](./deployment.md): Includes infrastructure-free deployment tools. [**LangGraph Platform**](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) supports testing, debugging, and deployment.
- **[Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/)**: A visual IDE for inspecting and debugging workflows.
- Supports multiple [**deployment options**](https://langchain-ai.github.io/langgraph/tutorials/deployment/) for production.
## High-level building blocks
@@ -40,32 +40,30 @@ LangGraph comes with a set of prebuilt components that implement common agent be
Using LangGraph for agent development allows you to focus on your application's logic and behavior, instead of building and maintaining the supporting infrastructure for state, memory, and human feedback.
:::python
## Package ecosystem
The high-level components are organized into several packages, each with a specific focus.
| Package | Description | Installation |
| ------------------------------------------ | ---------------------------------------------------------------------------------------- | --------------------------------------- |
| `langgraph-prebuilt` (part of `langgraph`) | Prebuilt components to [**create agents**](./agents.md) | `pip install -U langgraph langchain` |
| `langgraph-supervisor` | Tools for building [**supervisor**](./multi-agent.md#supervisor) agents | `pip install -U langgraph-supervisor` |
| `langgraph-swarm` | Tools for building a [**swarm**](./multi-agent.md#swarm) multi-agent system | `pip install -U langgraph-swarm` |
| `langchain-mcp-adapters` | Interfaces to [**MCP servers**](./mcp.md) for tool and resource integration | `pip install -U langchain-mcp-adapters` |
| `langmem` | Agent memory management: [**short-term and long-term**](../how-tos/memory/add-memory.md) | `pip install -U langmem` |
| `agentevals` | Utilities to [**evaluate agent performance**](./evals.md) | `pip install -U agentevals` |
| Package | Description | Installation |
|--------------------------------------------|-----------------------------------------------------------------------------|-----------------------------------------|
| `langgraph-prebuilt` (part of `langgraph`) | Prebuilt components to [**create agents**](./agents.md) | `pip install -U langgraph langchain` |
| `langgraph-supervisor` | Tools for building [**supervisor**](./multi-agent.md#supervisor) agents | `pip install -U langgraph-supervisor` |
| `langgraph-swarm` | Tools for building a [**swarm**](./multi-agent.md#swarm) multi-agent system | `pip install -U langgraph-swarm` |
| `langchain-mcp-adapters` | Interfaces to [**MCP servers**](./mcp.md) for tool and resource integration | `pip install -U langchain-mcp-adapters` |
| `langmem` | Agent memory management: [**short-term and long-term**](./memory.md) | `pip install -U langmem` |
| `agentevals` | Utilities to [**evaluate agent performance**](./evals.md) | `pip install -U agentevals` |
## Visualize an agent graph
Use the following tool to visualize the graph generated by
@[`create_react_agent`][create_react_agent]
[`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent]
and to view an outline of the corresponding code.
It allows you to explore the infrastructure of the agent as defined by the presence of:
- [`tools`](../how-tos/tool-calling.md): A list of tools (functions, APIs, or other callable objects) that the agent can use to perform tasks.
- [`pre_model_hook`](../how-tos/create-react-agent-manage-message-history.ipynb): A function that is called before the model is invoked. It can be used to condense messages or perform other preprocessing tasks.
- `post_model_hook`: A function that is called after the model is invoked. It can be used to implement guardrails, human-in-the-loop flows, or other postprocessing tasks.
- [`response_format`](../agents/agents.md#6-configure-structured-output): A data structure used to constrain the type of the final output, e.g., a `pydantic` `BaseModel`.
* [`tools`](../agents/tools.md): A list of tools (functions, APIs, or other callable objects) that the agent can use to perform tasks.
* [`pre_model_hook`](../how-tos/create-react-agent-manage-message-history.ipynb): A function that is called before the model is invoked. It can be used to condense messages or perform other preprocessing tasks.
* `post_model_hook`: A function that is called after the model is invoked. It can be used to implement guardrails, human-in-the-loop flows, or other postprocessing tasks.
* [`response_format`](../agents/agents.md#6-configure-structured-output): A data structure used to constrain the type of the final output, e.g., a `pydantic` `BaseModel`.
<div class="agent-layout">
<div class="agent-graph-features-container">
@@ -84,13 +82,15 @@ It allows you to explore the infrastructure of the agent as defined by the prese
</div>
</div>
The following code snippet shows how to create the above agent (and underlying graph) with
@[`create_react_agent`][create_react_agent]:
[`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent]:
<div class="language-python">
<pre><code id="agent-code" class="language-python"></code></pre>
</div>
<script>
function getCheckedValue(id) {
return document.getElementById(id).checked ? "1" : "0";
@@ -189,159 +189,3 @@ function initializeWidget() {
window.addEventListener("DOMContentLoaded", initializeWidget);
document$.subscribe(initializeWidget);
</script>
:::
:::js
## Package ecosystem
The high-level components are organized into several packages, each with a specific focus.
| Package | Description | Installation |
| ------------------------ | --------------------------------------------------------------------------- | -------------------------------------------------- |
| `langgraph` | Prebuilt components to [**create agents**](./agents.md) | `npm install @langchain/langgraph @langchain/core` |
| `langgraph-supervisor` | Tools for building [**supervisor**](./multi-agent.md#supervisor) agents | `npm install @langchain/langgraph-supervisor` |
| `langgraph-swarm` | Tools for building a [**swarm**](./multi-agent.md#swarm) multi-agent system | `npm install @langchain/langgraph-swarm` |
| `langchain-mcp-adapters` | Interfaces to [**MCP servers**](./mcp.md) for tool and resource integration | `npm install @langchain/mcp-adapters` |
| `agentevals` | Utilities to [**evaluate agent performance**](./evals.md) | `npm install agentevals` |
## Visualize an agent graph
Use the following tool to visualize the graph generated by @[`createReactAgent`][create_react_agent] and to view an outline of the corresponding code. It allows you to explore the infrastructure of the agent as defined by the presence of:
- [`tools`](./tools.md): A list of tools (functions, APIs, or other callable objects) that the agent can use to perform tasks.
- `preModelHook`: A function that is called before the model is invoked. It can be used to condense messages or perform other preprocessing tasks.
- `postModelHook`: A function that is called after the model is invoked. It can be used to implement guardrails, human-in-the-loop flows, or other postprocessing tasks.
- [`responseFormat`](./agents.md#6-configure-structured-output): A data structure used to constrain the type of the final output (via Zod schemas).
<div class="agent-layout">
<div class="agent-graph-features-container">
<div class="agent-graph-features">
<h3 class="agent-section-title">Features</h3>
<label><input type="checkbox" id="tools" checked> <code>tools</code></label>
<label><input type="checkbox" id="preModelHook"> <code>preModelHook</code></label>
<label><input type="checkbox" id="postModelHook"> <code>postModelHook</code></label>
<label><input type="checkbox" id="responseFormat"> <code>responseFormat</code></label>
</div>
</div>
<div class="agent-graph-container">
<h3 class="agent-section-title">Graph</h3>
<img id="agent-graph-img" src="../assets/react_agent_graphs/0001.svg" alt="graph image" style="max-width: 100%;"/>
</div>
</div>
The following code snippet shows how to create the above agent (and underlying graph) with @[`createReactAgent`][create_react_agent]:
<div class="language-typescript">
<pre><code id="agent-code" class="language-typescript"></code></pre>
</div>
<script>
function getCheckedValue(id) {
return document.getElementById(id).checked ? "1" : "0";
}
function getKey() {
return [
getCheckedValue("responseFormat"),
getCheckedValue("postModelHook"),
getCheckedValue("preModelHook"),
getCheckedValue("tools")
].join("");
}
function dedent(strings, ...values) {
const str = String.raw({ raw: strings }, ...values)
const [space] = str.split("\n").filter(Boolean).at(0).match(/^(\s*)/)
const spaceLen = space.length
return str.split("\n").map(line => line.slice(spaceLen)).join("\n").trim()
}
Object.assign(dedent, {
offset: (size) => (strings, ...values) => {
return dedent(strings, ...values).split("\n").map(line => " ".repeat(size) + line).join("\n")
}
})
function generateCodeSnippet({ tools, pre, post, response }) {
const lines = []
lines.push(dedent`
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { ChatOpenAI } from "@langchain/openai";
`)
if (tools) lines.push(`import { tool } from "@langchain/core/tools";`);
if (response || tools) lines.push(`import { z } from "zod";`);
lines.push("", dedent`
const agent = createReactAgent({
llm: new ChatOpenAI({ model: "o4-mini" }),
`)
if (tools) {
lines.push(dedent.offset(2)`
tools: [
tool(() => "Sample tool output", {
name: "sampleTool",
schema: z.object({}),
}),
],
`)
}
if (pre) {
lines.push(dedent.offset(2)`
preModelHook: (state) => ({ llmInputMessages: state.messages }),
`)
}
if (post) {
lines.push(dedent.offset(2)`
postModelHook: (state) => state,
`)
}
if (response) {
lines.push(dedent.offset(2)`
responseFormat: z.object({ result: z.string() }),
`)
}
lines.push(`});`);
return lines.join("\n");
}
function render() {
const key = getKey();
document.getElementById("agent-graph-img").src = `../assets/react_agent_graphs/${key}.svg`;
const state = {
tools: document.getElementById("tools").checked,
pre: document.getElementById("preModelHook").checked,
post: document.getElementById("postModelHook").checked,
response: document.getElementById("responseFormat").checked
};
document.getElementById("agent-code").textContent = generateCodeSnippet(state);
}
function initializeWidget() {
render(); // no need for `await` here
document.querySelectorAll(".agent-graph-features input").forEach((input) => {
input.addEventListener("change", render);
});
}
// Init for both full reload and SPA nav (used by MkDocs Material)
window.addEventListener("DOMContentLoaded", initializeWidget);
document$.subscribe(initializeWidget);
</script>
:::
+20 -49
View File
@@ -1,30 +1,29 @@
[//]: # (This file is automatically generated using a script in docs/_scripts. Do not edit this file directly!)
# Community Agents
# Community agents
If youre looking for other prebuilt libraries, explore the community-built options
below. These libraries can extend LangGraph's functionality in various ways.
## 📚 Available Libraries
## 📚 Available libraries
[//]: # (This file is automatically generated using a script in docs/_scripts. Do not edit this file directly!)
:::python
| Name | GitHub URL | Description | Weekly Downloads | Stars |
| --- | --- | --- | --- | --- |
| **trustcall** | https://github.com/hinthornw/trustcall | Tenacious tool calling built on LangGraph. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/hinthornw/trustcall?style=social)
| **breeze-agent** | https://github.com/andrestorres123/breeze-agent | A streamlined research system built inspired on STORM and built on LangGraph. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/andrestorres123/breeze-agent?style=social)
| **langgraph-supervisor** | https://github.com/langchain-ai/langgraph-supervisor-py | Build supervisor multi-agent systems with LangGraph. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langgraph-supervisor-py?style=social)
| **langmem** | https://github.com/langchain-ai/langmem | Build agents that learn and adapt from interactions over time. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langmem?style=social)
| **langchain-mcp-adapters** | https://github.com/langchain-ai/langchain-mcp-adapters | Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langchain-mcp-adapters?style=social)
| **open-deep-research** | https://github.com/langchain-ai/open_deep_research | Open source assistant for iterative web research and report writing. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/open_deep_research?style=social)
| **langgraph-swarm** | https://github.com/langchain-ai/langgraph-swarm-py | Build swarm-style multi-agent systems using LangGraph. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langgraph-swarm-py?style=social)
| **delve-taxonomy-generator** | https://github.com/andrestorres123/delve | A taxonomy generator for unstructured data | -12345 | ![GitHub stars](https://img.shields.io/github/stars/andrestorres123/delve?style=social)
| **nodeology** | https://github.com/xyin-anl/Nodeology | Enable researcher to build scientific workflows easily with simplified interface. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/xyin-anl/Nodeology?style=social)
| **langgraph-bigtool** | https://github.com/langchain-ai/langgraph-bigtool | Build LangGraph agents with large numbers of tools. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langgraph-bigtool?style=social)
| **ai-data-science-team** | https://github.com/business-science/ai-data-science-team | An AI-powered data science team of agents to help you perform common data science tasks 10X faster. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/business-science/ai-data-science-team?style=social)
| **langgraph-reflection** | https://github.com/langchain-ai/langgraph-reflection | LangGraph agent that runs a reflection step. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langgraph-reflection?style=social)
| **langgraph-codeact** | https://github.com/langchain-ai/langgraph-codeact | LangGraph implementation of CodeAct agent that generates and executes code instead of tool calling. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langgraph-codeact?style=social)
| **trustcall** | [hinthornw/trustcall](https://github.com/hinthornw/trustcall) | Tenacious tool calling built on LangGraph. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/hinthornw/trustcall?style=social)
| **breeze-agent** | [andrestorres123/breeze-agent](https://github.com/andrestorres123/breeze-agent) | A streamlined research system built inspired on STORM and built on LangGraph. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/andrestorres123/breeze-agent?style=social)
| **langgraph-supervisor** | [langchain-ai/langgraph-supervisor-py](https://github.com/langchain-ai/langgraph-supervisor-py) | Build supervisor multi-agent systems with LangGraph. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langgraph-supervisor-py?style=social)
| **langmem** | [langchain-ai/langmem](https://github.com/langchain-ai/langmem) | Build agents that learn and adapt from interactions over time. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langmem?style=social)
| **langchain-mcp-adapters** | [langchain-ai/langchain-mcp-adapters](https://github.com/langchain-ai/langchain-mcp-adapters) | Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langchain-mcp-adapters?style=social)
| **open-deep-research** | [langchain-ai/open_deep_research](https://github.com/langchain-ai/open_deep_research) | Open source assistant for iterative web research and report writing. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/open_deep_research?style=social)
| **langgraph-swarm** | [langchain-ai/langgraph-swarm-py](https://github.com/langchain-ai/langgraph-swarm-py) | Build swarm-style multi-agent systems using LangGraph. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langgraph-swarm-py?style=social)
| **delve-taxonomy-generator** | [andrestorres123/delve](https://github.com/andrestorres123/delve) | A taxonomy generator for unstructured data | -12345 | ![GitHub stars](https://img.shields.io/github/stars/andrestorres123/delve?style=social)
| **nodeology** | [xyin-anl/Nodeology](https://github.com/xyin-anl/Nodeology) | Enable researcher to build scientific workflows easily with simplified interface. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/xyin-anl/Nodeology?style=social)
| **langgraph-bigtool** | [langchain-ai/langgraph-bigtool](https://github.com/langchain-ai/langgraph-bigtool) | Build LangGraph agents with large numbers of tools. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langgraph-bigtool?style=social)
| **ai-data-science-team** | [business-science/ai-data-science-team](https://github.com/business-science/ai-data-science-team) | An AI-powered data science team of agents to help you perform common data science tasks 10X faster. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/business-science/ai-data-science-team?style=social)
| **langgraph-reflection** | [langchain-ai/langgraph-reflection](https://github.com/langchain-ai/langgraph-reflection) | LangGraph agent that runs a reflection step. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langgraph-reflection?style=social)
| **langgraph-codeact** | [langchain-ai/langgraph-codeact](https://github.com/langchain-ai/langgraph-codeact) | LangGraph implementation of CodeAct agent that generates and executes code instead of tool calling. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langgraph-codeact?style=social)
## ✨ Contributing Your Library
## ✨ Contributing your library
Have you built an awesome open-source library using LangGraph? We'd love to feature
your project on the official LangGraph documentation pages! 🏆
@@ -33,41 +32,13 @@ To share your project, simply open a Pull Request adding an entry for your packa
**Guidelines**
- Your repo must be distributed as an installable package on PyPI 📦
- Your repo must be distributed as an installable package (e.g., PyPI for Python, npm
for JavaScript/TypeScript, etc.) 📦
- The repo should either use the Graph API (exposing a `StateGraph` instance) or
the Functional API (exposing an `entrypoint`).
- The package must include documentation (e.g., a `README.md` or docs site)
explaining how to use it.
We'll review your contribution and merge it in!
Thanks for contributing! 🚀
:::
:::js
| Name | GitHub URL | Description | Weekly Downloads | Stars |
| --- | --- | --- | --- | --- |
| **@langchain/mcp-adapters** | https://github.com/langchain-ai/langchainjs | Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langchainjs?style=social)
| **@langchain/langgraph-supervisor** | https://github.com/langchain-ai/langgraphjs/tree/main/libs/langgraph-supervisor | Build supervisor multi-agent systems with LangGraph | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langgraphjs?style=social)
| **@langchain/langgraph-swarm** | https://github.com/langchain-ai/langgraphjs/tree/main/libs/langgraph-swarm | Build multi-agent swarms with LangGraph | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langgraphjs?style=social)
| **@langchain/langgraph-cua** | https://github.com/langchain-ai/langgraphjs/tree/main/libs/langgraph-cua | Build computer use agents with LangGraph | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langgraphjs?style=social)
## ✨ Contributing Your Library
Have you built an awesome open-source library using LangGraph? We'd love to feature
your project on the official LangGraph documentation pages! 🏆
To share your project, simply open a Pull Request adding an entry for your package in our [packages.yml](https://github.com/langchain-ai/langgraph/blob/main/docs/_scripts/third_party_page/packages.yml) file.
**Guidelines**
- Your repo must be distributed as an installable package on npm 📦
- The repo should either use the Graph API (exposing a `StateGraph` instance) or
the Functional API (exposing an `entrypoint`).
- The package must include documentation (e.g., a `README.md` or docs site)
explaining how to use it.
We'll review your contribution and merge it in!
Thanks for contributing! 🚀
:::
+11 -166
View File
@@ -9,27 +9,18 @@ hide:
# Running agents
Agents support both synchronous and asynchronous execution using either `.invoke()` / `await .ainvoke()` for full responses, or `.stream()` / `.astream()` for **incremental** [streaming](../how-tos/streaming.md) output. This section explains how to provide input, interpret output, enable streaming, and control execution limits.
Agents support both synchronous and asynchronous execution using either `.invoke()` / `await .ainvoke()` for full responses, or `.stream()` / `.astream()` for **incremental** [streaming](streaming.md) output. This section explains how to provide input, interpret output, enable streaming, and control execution limits.
## Basic usage
Agents can be executed in two primary modes:
:::python
- **Synchronous** using `.invoke()` or `.stream()`
- **Asynchronous** using `await .ainvoke()` or `async for` with `.astream()`
:::
:::js
- **Synchronous** using `.invoke()` or `.stream()`
- **Asynchronous** using `await .invoke()` or `for await` with `.stream()`
:::
:::python
=== "Sync invocation"
```python
from langgraph.prebuilt import create_react_agent
@@ -40,7 +31,6 @@ Agents can be executed in two primary modes:
```
=== "Async invocation"
```python
from langgraph.prebuilt import create_react_agent
@@ -49,24 +39,6 @@ Agents can be executed in two primary modes:
response = await agent.ainvoke({"messages": [{"role": "user", "content": "what is the weather in sf"}]})
```
:::
:::js
```typescript
import { createReactAgent } from "@langchain/langgraph/prebuilt";
const agent = createReactAgent(...);
// highlight-next-line
const response = await agent.invoke({
"messages": [
{ "role": "user", "content": "what is the weather in sf" }
]
});
```
:::
## Inputs and outputs
Agents use a language model that expects a list of `messages` as an input. Therefore, agent inputs and outputs are stored as a list of `messages` under the `messages` key in the agent [state](../concepts/low_level.md#working-with-messages-in-graph-state).
@@ -75,73 +47,33 @@ Agents use a language model that expects a list of `messages` as an input. There
Agent input must be a dictionary with a `messages` key. Supported formats are:
:::python
| Format | Example |
| Format | Example |
|--------------------|-------------------------------------------------------------------------------------------------------------------------------|
| String | `{"messages": "Hello"}` — Interpreted as a [HumanMessage](https://python.langchain.com/docs/concepts/messages/#humanmessage) |
| Message dictionary | `{"messages": {"role": "user", "content": "Hello"}}` |
| List of messages | `{"messages": [{"role": "user", "content": "Hello"}]}` |
| With custom state | `{"messages": [{"role": "user", "content": "Hello"}], "user_name": "Alice"}` — If using a custom `state_schema` |
:::
| String | `{"messages": "Hello"}` — Interpreted as a [HumanMessage](https://python.langchain.com/docs/concepts/messages/#humanmessage) |
| Message dictionary | `{"messages": {"role": "user", "content": "Hello"}}` |
| List of messages | `{"messages": [{"role": "user", "content": "Hello"}]}` |
| With custom state | `{"messages": [{"role": "user", "content": "Hello"}], "user_name": "Alice"}` — If using a custom `state_schema` |
:::js
| Format | Example |
|--------------------|-------------------------------------------------------------------------------------------------------------------------------|
| String | `{"messages": "Hello"}` — Interpreted as a [HumanMessage](https://js.langchain.com/docs/concepts/messages/#humanmessage) |
| Message dictionary | `{"messages": {"role": "user", "content": "Hello"}}` |
| List of messages | `{"messages": [{"role": "user", "content": "Hello"}]}` |
| With custom state | `{"messages": [{"role": "user", "content": "Hello"}], "user_name": "Alice"}` — If using a custom state definition |
:::
:::python
Messages are automatically converted into LangChain's internal message format. You can read
more about [LangChain messages](https://python.langchain.com/docs/concepts/messages/#langchain-messages) in the LangChain documentation.
:::
:::js
Messages are automatically converted into LangChain's internal message format. You can read
more about [LangChain messages](https://js.langchain.com/docs/concepts/messages/#langchain-messages) in the LangChain documentation.
:::
!!! tip "Using custom agent state"
:::python
You can provide additional fields defined in your agent's state schema directly in the input dictionary. This allows dynamic behavior based on runtime data or prior tool outputs.
You can provide additional fields defined in your agents state schema directly in the input dictionary. This allows dynamic behavior based on runtime data or prior tool outputs.
See the [context guide](./context.md) for full details.
:::
:::js
You can provide additional fields defined in your agent's state directly in the state definition. This allows dynamic behavior based on runtime data or prior tool outputs.
See the [context guide](./context.md) for full details.
:::
!!! note
:::python
A string input for `messages` is converted to a [HumanMessage](https://python.langchain.com/docs/concepts/messages/#humanmessage). This behavior differs from the `prompt` parameter in `create_react_agent`, which is interpreted as a [SystemMessage](https://python.langchain.com/docs/concepts/messages/#systemmessage) when passed as a string.
:::
:::js
A string input for `messages` is converted to a [HumanMessage](https://js.langchain.com/docs/concepts/messages/#humanmessage). This behavior differs from the `prompt` parameter in `createReactAgent`, which is interpreted as a [SystemMessage](https://js.langchain.com/docs/concepts/messages/#systemmessage) when passed as a string.
:::
## Output format
:::python
Agent output is a dictionary containing:
- `messages`: A list of all messages exchanged during execution (user input, assistant replies, tool invocations).
- Optionally, `structured_response` if [structured output](./agents.md#6-configure-structured-output) is configured.
- If using a custom `state_schema`, additional keys corresponding to your defined fields may also be present in the output. These can hold updated state values from tool execution or prompt logic.
:::
:::js
Agent output is a dictionary containing:
- `messages`: A list of all messages exchanged during execution (user input, assistant replies, tool invocations).
- Optionally, `structuredResponse` if [structured output](./agents.md#6-configure-structured-output) is configured.
- If using a custom state definition, additional keys corresponding to your defined fields may also be present in the output. These can hold updated state values from tool execution or prompt logic.
:::
See the [context guide](./context.md) for more details on working with custom state schemas and accessing context.
@@ -155,7 +87,6 @@ Agents support streaming responses for more responsive applications. This includ
Streaming is available in both sync and async modes:
:::python
=== "Sync streaming"
```python
@@ -176,36 +107,14 @@ Streaming is available in both sync and async modes:
print(chunk)
```
:::
:::js
```typescript
for await (const chunk of agent.stream(
{ messages: [{ role: "user", content: "what is the weather in sf" }] },
{ streamMode: "updates" }
)) {
console.log(chunk);
}
```
:::
!!! tip
For full details, see the [streaming guide](../how-tos/streaming.md).
For full details, see the [streaming guide](./streaming.md).
## Max iterations
:::python
To control agent execution and avoid infinite loops, set a recursion limit. This defines the maximum number of steps the agent can take before raising a `GraphRecursionError`. You can configure `recursion_limit` at runtime or when defining agent via `.with_config()`:
:::
:::js
To control agent execution and avoid infinite loops, set a recursion limit. This defines the maximum number of steps the agent can take before raising a `GraphRecursionError`. You can configure `recursionLimit` at runtime or when defining agent via `.withConfig()`:
:::
:::python
=== "Runtime"
```python
@@ -254,70 +163,6 @@ To control agent execution and avoid infinite loops, set a recursion limit. This
print("Agent stopped due to max iterations.")
```
:::
:::js
=== "Runtime"
```typescript
import { GraphRecursionError } from "@langchain/langgraph";
import { ChatAnthropic } from "@langchain/langgraph/prebuilt";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
const maxIterations = 3;
// highlight-next-line
const recursionLimit = 2 * maxIterations + 1;
const agent = createReactAgent({
llm: new ChatAnthropic({ model: "claude-3-5-haiku-latest" }),
tools: [getWeather]
});
try {
const response = await agent.invoke(
{"messages": [{"role": "user", "content": "what's the weather in sf"}]},
// highlight-next-line
{ recursionLimit }
);
} catch (error) {
if (error instanceof GraphRecursionError) {
console.log("Agent stopped due to max iterations.");
}
}
```
=== "`.withConfig()`"
```typescript
import { GraphRecursionError } from "@langchain/langgraph";
import { ChatAnthropic } from "@langchain/langgraph/prebuilt";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
const maxIterations = 3;
// highlight-next-line
const recursionLimit = 2 * maxIterations + 1;
const agent = createReactAgent({
llm: new ChatAnthropic({ model: "claude-3-5-haiku-latest" }),
tools: [getWeather]
});
// highlight-next-line
const agentWithRecursionLimit = agent.withConfig({ recursionLimit });
try {
const response = await agentWithRecursionLimit.invoke(
{"messages": [{"role": "user", "content": "what's the weather in sf"}]},
);
} catch (error) {
if (error instanceof GraphRecursionError) {
console.log("Agent stopped due to max iterations.");
}
}
```
:::
:::python
## Additional Resources
- [Async programming in LangChain](https://python.langchain.com/docs/concepts/async)
:::
* [Async programming in LangChain](https://python.langchain.com/docs/concepts/async)
+223
View File
@@ -0,0 +1,223 @@
---
search:
boost: 2
tags:
- agent
hide:
- tags
---
# Streaming
Streaming is key to building responsive applications. There are a few types of data youll want to stream:
1. [**Agent progress**](#agent-progress) — get updates after each node in the agent graph is executed.
2. [**LLM tokens**](#llm-tokens) — stream tokens as they are generated by the language model.
3. [**Custom updates**](#tool-updates) — emit custom data from tools during execution (e.g., "Fetched 10/100 records")
You can stream [more than one type of data](#stream-multiple-modes) at a time.
<figure markdown="1">
![image](./assets/fast_parrot.png){: style="max-height:300px"}
<figcaption>
Waiting is for pigeons.
</figcaption>
</figure>
## Agent progress
To stream agent progress, use the [`stream()`][langgraph.graph.state.CompiledStateGraph.stream] or [`astream()`][langgraph.graph.state.CompiledStateGraph.astream] methods with [`stream_mode="updates"`](https://langchain-ai.github.io/langgraph/how-tos/streaming/#updates). This emits an event after every agent step.
For example, if you have an agent that calls a tool once, you should see the following updates:
* **LLM node**: AI message with tool call requests
* **Tool node**: Tool message with execution result
* **LLM node**: Final AI response
=== "Sync"
```python
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
# highlight-next-line
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode="updates"
):
print(chunk)
print("\n")
```
=== "Async"
```python
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
# highlight-next-line
async for chunk in agent.astream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode="updates"
):
print(chunk)
print("\n")
```
## LLM tokens
To stream tokens as they are produced by the LLM, use `stream_mode="messages"`:
=== "Sync"
```python
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
# highlight-next-line
for token, metadata in agent.stream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode="messages"
):
print("Token", token)
print("Metadata", metadata)
print("\n")
```
=== "Async"
```python
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
# highlight-next-line
async for token, metadata in agent.astream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode="messages"
):
print("Token", token)
print("Metadata", metadata)
print("\n")
```
## Tool updates
To stream updates from tools as they are executed, you can use [get_stream_writer][langgraph.config.get_stream_writer].
=== "Sync"
```python
# highlight-next-line
from langgraph.config import get_stream_writer
def get_weather(city: str) -> str:
"""Get weather for a given city."""
# highlight-next-line
writer = get_stream_writer()
# stream any arbitrary data
# highlight-next-line
writer(f"Looking up data for city: {city}")
return f"It's always sunny in {city}!"
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode="custom"
):
print(chunk)
print("\n")
```
=== "Async"
```python
# highlight-next-line
from langgraph.config import get_stream_writer
def get_weather(city: str) -> str:
"""Get weather for a given city."""
# highlight-next-line
writer = get_stream_writer()
# stream any arbitrary data
# highlight-next-line
writer(f"Looking up data for city: {city}")
return f"It's always sunny in {city}!"
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
async for chunk in agent.astream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode="custom"
):
print(chunk)
print("\n")
```
!!! Note
If you add `get_stream_writer` inside your tool, you won't be able to invoke the tool outside of a LangGraph execution context.
## Stream multiple modes
You can specify multiple streaming modes by passing stream mode as a list: `stream_mode=["updates", "messages", "custom"]`:
=== "Sync"
```python
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
for stream_mode, chunk in agent.stream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode=["updates", "messages", "custom"]
):
print(chunk)
print("\n")
```
=== "Async"
```python
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_weather],
)
async for stream_mode, chunk in agent.astream(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
# highlight-next-line
stream_mode=["updates", "messages", "custom"]
):
print(chunk)
print("\n")
```
## Disable streaming
In some applications you might need to disable streaming of individual tokens for a given model. This is useful in [multi-agent](./multi-agent.md) systems to control which agents stream their output.
See the [Models](./models.md#disable-streaming) guide to learn how to disable streaming.
## Additional resources
* [Streaming in LangGraph](https://langchain-ai.github.io/langgraph/how-tos/streaming)
+310
View File
@@ -0,0 +1,310 @@
---
search:
boost: 2
tags:
- agent
hide:
- tags
---
# Tools
[Tools](https://python.langchain.com/docs/concepts/tools/) are a way to encapsulate a function and its input schema in a way that can be passed to a chat model that supports tool calling. This allows the model to request the execution of this function with specific inputs.
You can either [define your own tools](#define-simple-tools) or use [prebuilt integrations](#prebuilt-tools) that LangChain provides.
## Define simple tools
You can pass a vanilla function to `create_react_agent` to use as a tool:
```python
from langgraph.prebuilt import create_react_agent
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
create_react_agent(
model="anthropic:claude-3-7-sonnet",
tools=[multiply]
)
```
`create_react_agent` automatically converts vanilla functions to [LangChain tools](https://python.langchain.com/docs/concepts/tools/#tool-interface).
## Customize tools
For more control over tool behavior, use the `@tool` decorator:
```python
# highlight-next-line
from langchain_core.tools import tool
# highlight-next-line
@tool("multiply_tool", parse_docstring=True)
def multiply(a: int, b: int) -> int:
"""Multiply two numbers.
Args:
a: First operand
b: Second operand
"""
return a * b
```
You can also define a custom input schema using Pydantic:
```python
from pydantic import BaseModel, Field
class MultiplyInputSchema(BaseModel):
"""Multiply two numbers"""
a: int = Field(description="First operand")
b: int = Field(description="Second operand")
# highlight-next-line
@tool("multiply_tool", args_schema=MultiplyInputSchema)
def multiply(a: int, b: int) -> int:
return a * b
```
For additional customization, refer to the [custom tools guide](https://python.langchain.com/docs/how_to/custom_tools/).
## Hide arguments from the model
Some tools require runtime-only arguments (e.g., user ID or session context) that should not be controllable by the model.
You can put these arguments in the `state` or `config` of the agent, and access
this information inside the tool:
```python
from langgraph.prebuilt import InjectedState
from langgraph.prebuilt.chat_agent_executor import AgentState
from langchain_core.runnables import RunnableConfig
def my_tool(
# This will be populated by an LLM
tool_arg: str,
# access information that's dynamically updated inside the agent
# highlight-next-line
state: Annotated[AgentState, InjectedState],
# access static data that is passed at agent invocation
# highlight-next-line
config: RunnableConfig,
) -> str:
"""My tool."""
do_something_with_state(state["messages"])
do_something_with_config(config)
...
```
## Disable parallel tool calling
Some model providers support executing multiple tools in parallel, but
allow users to disable this feature.
For supported providers, you can disable parallel tool calling by setting `parallel_tool_calls=False` via the `model.bind_tools()` method:
```python
from langchain.chat_models import init_chat_model
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
model = init_chat_model("anthropic:claude-3-5-sonnet-latest", temperature=0)
tools = [add, multiply]
agent = create_react_agent(
# disable parallel tool calls
# highlight-next-line
model=model.bind_tools(tools, parallel_tool_calls=False),
tools=tools
)
agent.invoke(
{"messages": [{"role": "user", "content": "what's 3 + 5 and 4 * 7?"}]}
)
```
## Return tool results directly
Use `return_direct=True` to return tool results immediately and stop the agent loop:
```python
from langchain_core.tools import tool
# highlight-next-line
@tool(return_direct=True)
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[add]
)
agent.invoke(
{"messages": [{"role": "user", "content": "what's 3 + 5?"}]}
)
```
## Force tool use
To force the agent to use specific tools, you can set the `tool_choice` option in `model.bind_tools()`:
```python
from langchain_core.tools import tool
# highlight-next-line
@tool(return_direct=True)
def greet(user_name: str) -> int:
"""Greet user."""
return f"Hello {user_name}!"
tools = [greet]
agent = create_react_agent(
# highlight-next-line
model=model.bind_tools(tools, tool_choice={"type": "tool", "name": "greet"}),
tools=tools
)
agent.invoke(
{"messages": [{"role": "user", "content": "Hi, I am Bob"}]}
)
```
!!! Warning "Avoid infinite loops"
Forcing tool usage without stopping conditions can create infinite loops. Use one of the following safeguards:
- Mark the tool with [`return_direct=True`](#return-tool-results-directly) to end the loop after execution.
- Set [`recursion_limit`](../concepts/low_level.md#recursion-limit) to restrict the number of execution steps.
## Handle tool errors
By default, the agent will catch all exceptions raised during tool calls and will pass those as tool messages to the LLM. To control how the errors are handled, you can use the prebuilt [`ToolNode`][langgraph.prebuilt.tool_node.ToolNode] — the node that executes tools inside `create_react_agent` — via its `handle_tool_errors` parameter:
=== "Enable error handling (default)"
```python
from langgraph.prebuilt import create_react_agent
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
if a == 42:
raise ValueError("The ultimate error")
return a * b
# Run with error handling (default)
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[multiply]
)
agent.invoke(
{"messages": [{"role": "user", "content": "what's 42 x 7?"}]}
)
```
=== "Disable error handling"
```python
from langgraph.prebuilt import create_react_agent, ToolNode
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
if a == 42:
raise ValueError("The ultimate error")
return a * b
# highlight-next-line
tool_node = ToolNode(
[multiply],
# highlight-next-line
handle_tool_errors=False # (1)!
)
agent_no_error_handling = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=tool_node
)
agent_no_error_handling.invoke(
{"messages": [{"role": "user", "content": "what's 42 x 7?"}]}
)
```
1. This disables error handling (enabled by default). See all available strategies in the [API reference][langgraph.prebuilt.tool_node.ToolNode].
=== "Custom error handling"
```python
from langgraph.prebuilt import create_react_agent, ToolNode
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
if a == 42:
raise ValueError("The ultimate error")
return a * b
# highlight-next-line
tool_node = ToolNode(
[multiply],
# highlight-next-line
handle_tool_errors=(
"Can't use 42 as a first operand, you must switch operands!" # (1)!
)
)
agent_custom_error_handling = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=tool_node
)
agent_custom_error_handling.invoke(
{"messages": [{"role": "user", "content": "what's 42 x 7?"}]}
)
```
1. This provides a custom message to send to the LLM in case of an exception. See all available strategies in the [API reference][langgraph.prebuilt.tool_node.ToolNode].
See [API reference][langgraph.prebuilt.tool_node.ToolNode] for more information on different tool error handling options.
## Working with memory
LangGraph allows access to short-term and long-term memory from tools. See [Memory](./memory.md) guide for more information on:
* how to [read](./memory.md#read-short-term) from and [write](./memory.md#write-short-term) to **short-term** memory
* how to [read](./memory.md#read-long-term) from and [write](./memory.md#write-long-term) to **long-term** memory
## Prebuilt tools
You can use prebuilt tools from model providers by passing a dictionary with tool specs to the `tools` parameter of `create_react_agent`. For example, to use the `web_search_preview` tool from OpenAI:
```python
from langgraph.prebuilt import create_react_agent
agent = create_react_agent(
model="openai:gpt-4o-mini",
tools=[{"type": "web_search_preview"}]
)
response = agent.invoke(
{"messages": ["What was a positive news story from today?"]}
)
```
Additionally, LangChain supports a wide range of prebuilt tool integrations for interacting with APIs, databases, file systems, web data, and more. These tools extend the functionality of agents and enable rapid development.
You can browse the full list of available integrations in the [LangChain integrations directory](https://python.langchain.com/docs/integrations/tools/).
Some commonly used tool categories include:
- **Search**: Bing, SerpAPI, Tavily
- **Code interpreters**: Python REPL, Node.js REPL
- **Databases**: SQL, MongoDB, Redis
- **Web data**: Web scraping and browsing
- **APIs**: OpenWeatherMap, NewsAPI, and others
These integrations can be configured and added to your agents using the same `tools` parameter shown in the examples above.
+3 -3
View File
@@ -13,7 +13,7 @@ You can use a prebuilt chat UI for interacting with any LangGraph agent through
## Run agent in UI
First, set up LangGraph API server [locally](../tutorials/langgraph-platform/local-server.md) or deploy your agent on [LangGraph Platform](https://langchain-ai.github.io/langgraph/cloud/quick_start/).
First, set up LangGraph API server [locally](./deployment.md#launch-langgraph-server-locally) or deploy your agent on [LangGraph Platform](https://langchain-ai.github.io/langgraph/cloud/quick_start/).
Then, navigate to [Agent Chat UI](https://agentchat.vercel.app), or clone the repository and [run the dev server locally](https://github.com/langchain-ai/agent-chat-ui?tab=readme-ov-file#setup):
@@ -25,13 +25,13 @@ Then, navigate to [Agent Chat UI](https://agentchat.vercel.app), or clone the re
## Add human-in-the-loop
Agent Chat UI has full support for [human-in-the-loop](../concepts/human_in_the_loop.md) workflows. To try it out, replace the agent code in `src/agent/graph.py` (from the [deployment](../tutorials/langgraph-platform/local-server.md) guide) with this [agent implementation](../how-tos/human_in_the_loop/add-human-in-the-loop.md#add-interrupts-to-any-tool):
Agent Chat UI has full support for [human-in-the-loop](../concepts/human_in_the_loop.md) workflows. To try it out, replace the agent code in `src/agent/graph.py` (from the [deployment](./deployment.md) guide) with this [agent implementation](./human-in-the-loop.md#using-with-agent-inbox):
<video controls src="../assets/interrupt-chat-ui.mp4" type="video/mp4"></video>
!!! Important
Agent Chat UI works best if your LangGraph agent interrupts using the @[`HumanInterrupt` schema][HumanInterrupt]. If you do not use that schema, the Agent Chat UI will be able to render the input passed to the `interrupt` function, but it will not have full support for resuming your graph.
Agent Chat UI works best if your LangGraph agent interrupts using the [`HumanInterrupt` schema][langgraph.prebuilt.interrupt.HumanInterrupt]. If you do not use that schema, the Agent Chat UI will be able to render the input passed to the `interrupt` function, but it will not have full support for resuming your graph.
## Generative UI
+15
View File
@@ -0,0 +1,15 @@
## Cron jobs
There are many situations in which it is useful to run an assistant on a schedule.
For example, say that you're building an assistant that runs daily and sends an email summary
of the day's news. You could use a cron job to run the assistant every day at 8:00 PM.
LangGraph Platform supports cron jobs, which run on a user-defined schedule. The user specifies a schedule, an assistant, and some input. After that, on the specified schedule, the server will:
- Create a new thread with the specified assistant
- Send the specified input to that thread
Note that this sends the same input to the thread every time. See the [how-to guide](../../cloud/how-tos/cron_jobs.md) for creating cron jobs.
The LangGraph Platform API provides several endpoints for creating and managing cron jobs. See the [API reference](../../cloud/reference/api/api_ref.html#tag/runscreate/POST/threads/{thread_id}/runs/crons) for more details.
+5
View File
@@ -0,0 +1,5 @@
# Runs
A run is an invocation of an [assistant](../../concepts/assistants.md). Each run may have its own input, configuration, and metadata, which may affect execution and output of the underlying graph. A run can optionally be executed on a [thread](./threads.md).
The LangGraph Platform API provides several endpoints for creating and managing runs. See the [API reference](../../cloud/reference/api/api_ref.html#tag/thread-runs/) for more details.
+138
View File
@@ -0,0 +1,138 @@
# Streaming
Streaming is critical for making LLM applications feel responsive to end users.
When creating a streaming run, the **streaming mode** determines what kinds of data are streamed back to the API client.
## Supported streaming modes
LangGraph Platform supports the following streaming modes:
| Mode | Description | LangGraph Library Method |
|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------|
| **`values`** | Stream the full graph state after each [super-step](https://langchain-ai.github.io/langgraph/concepts/low_level/#graphs). [Guide](../how-tos/streaming.md#stream-graph-state) | `.stream()` / `.astream()` with `stream_mode="values"` |
| **`updates`** | Stream only the updates to the graph state after each node. [Guide](../how-tos/streaming.md#stream-graph-state) | `.stream()` / `.astream()` with `stream_mode="updates"` |
| **`messages-tuple`** | Stream LLM tokens for any messages generated inside the graph (useful for chat apps). [Guide](../how-tos/streaming.md#messages) | `.stream()` / `.astream()` with `stream_mode="messages"` |
| **`debug`** | Stream debug information throughout graph execution. [Guide](../how-tos/streaming.md#debug) | `.stream()` / `.astream()` with `stream_mode="debug"` |
| **`custom`** | Stream custom data. [Guide](../../how-tos/streaming.md#stream-custom-data) | `.stream()` / `.astream()` with `stream_mode="custom"` |
| **`events`** | Stream all events (including the state of the graph); mainly useful when migrating large LCEL apps. [Guide](../how-tos/streaming.md#stream-events) | `.astream_events()` |
✅ You can also **combine multiple modes** at the same time. See the [how-to guide](../how-tos/streaming.md#stream-multiple-modes) for configuration details.
## Stateless runs
If you don't want to **persist the outputs** of a streaming run in the [checkpointer](../../concepts/persistence.md) DB, you can create a stateless run without creating a thread:
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>, api_key=<API_KEY>)
async for chunk in client.runs.stream(
# highlight-next-line
None, # (1)!
assistant_id,
input=inputs,
stream_mode="updates"
):
print(chunk.data)
```
1. We are passing `None` instead of a `thread_id` UUID.
=== "JavaScript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL>, apiKey: <API_KEY> });
// create a streaming run
// highlight-next-line
const streamResponse = client.runs.stream(
// highlight-next-line
null, // (1)!
assistantID,
{
input,
streamMode: "updates"
}
);
for await (const chunk of streamResponse) {
console.log(chunk.data);
}
```
1. We are passing `None` instead of a `thread_id` UUID.
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/runs/stream \
--header 'Content-Type: application/json' \
--header 'x-api-key: <API_KEY>'
--data "{
\"assistant_id\": \"agent\",
\"input\": <inputs>,
\"stream_mode\": \"updates\"
}"
```
## Join and stream
LangGraph Platform allows you to join an active [background run](../how-tos/background_run.md) and stream outputs from it. To do so, you can use [LangGraph SDK's](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/) `client.runs.join_stream` method:
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>, api_key=<API_KEY>)
# highlight-next-line
async for chunk in client.runs.join_stream(
thread_id,
# highlight-next-line
run_id, # (1)!
):
print(chunk)
```
1. This is the `run_id` of an existing run you want to join.
=== "JavaScript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL>, apiKey: <API_KEY> });
// highlight-next-line
const streamResponse = client.runs.joinStream(
threadID,
// highlight-next-line
runId // (1)!
);
for await (const chunk of streamResponse) {
console.log(chunk);
}
```
1. This is the `run_id` of an existing run you want to join.
=== "cURL"
```bash
curl --request GET \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/<RUN_ID>/stream \
--header 'Content-Type: application/json' \
--header 'x-api-key: <API_KEY>'
```
!!! warning "Outputs not buffered"
When you use `.join_stream`, output is not buffered, so any output produced before joining will not be received.
## API Reference
For API usage and implementation, refer to the [API reference](../reference/api/api_ref.html#tag/thread-runs/POST/threads/{thread_id}/runs/stream).
+12
View File
@@ -0,0 +1,12 @@
# Threads
A thread contains the accumulated state of a sequence of [runs](./runs.md). When a run is executed, the [state](../../concepts/low_level.md#state) of the underlying graph of the assistant will be persisted to the thread.
A thread's current and historical state can be retrieved. To persist state, a thread must be created prior to executing a run.
The state of a thread at a particular point in time is called a [checkpoint](../../concepts/persistence.md#checkpoints). Checkpoints are persisted and can be used to restore the state of a thread at a later time.
## Learn more
* For more on threads and checkpoints, see this section of the [LangGraph conceptual guide](../../concepts/persistence.md).
* The LangGraph Platform API provides several endpoints for creating and managing threads and thread state. See the [API reference](../../cloud/reference/api/api_ref.html#tag/threads) for more details.
+7
View File
@@ -0,0 +1,7 @@
# Webhooks
Webhooks enable event-driven communication from your LangGraph Platform application to external services. For example, you may want to issue an update to a separate service once an API call to LangGraph Platform has finished running.
Many LangGraph Platform endpoints accept a `webhook` parameter. If this parameter is specified by an endpoint that can accept POST requests, LangGraph Platform will send a request at the completion of a run.
See the corresponding [how-to guide](../../cloud/how-tos/webhooks.md) for more detail.
+128
View File
@@ -0,0 +1,128 @@
# How to Deploy to Cloud SaaS
Before deploying, review the [conceptual guide for the Cloud SaaS](../../concepts/langgraph_cloud.md) deployment option.
## Prerequisites
1. LangGraph Platform applications are deployed from GitHub repositories. Configure and upload a LangGraph Platform application to a GitHub repository in order to deploy it to LangGraph Platform.
1. [Verify that the LangGraph API runs locally](../../tutorials/langgraph-platform/local-server.md). If the API does not run successfully (i.e. `langgraph dev`), deploying to LangGraph Platform will fail as well.
## Create New Deployment
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
1. In the left-hand navigation panel, select `LangGraph Platform`. The `LangGraph Platform` view contains a list of existing LangGraph Platform deployments.
1. In the top-right corner, select `+ New Deployment` to create a new deployment.
1. In the `Create New Deployment` panel, fill out the required fields.
1. `Deployment details`
1. Select `Import from GitHub` and follow the GitHub OAuth workflow to install and authorize LangChain's `hosted-langserve` GitHub app to access the selected repositories. After installation is complete, return to the `Create New Deployment` panel and select the GitHub repository to deploy from the dropdown menu. **Note**: The GitHub user installing LangChain's `hosted-langserve` GitHub app must be an [owner](https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/roles-in-an-organization#organization-owners) of the organization or account.
1. Specify a name for the deployment.
1. Specify the desired `Git Branch`. A deployment is linked to a branch. When a new revision is created, code for the linked branch will be deployed. The branch can be updated later in the [Deployment Settings](#deployment-settings).
1. Specify the full path to the [LangGraph API config file](../reference/cli.md#configuration-file) including the file name. For example, if the file `langgraph.json` is in the root of the repository, simply specify `langgraph.json`.
1. Check/uncheck checkbox to `Automatically update deployment on push to branch`. If checked, the deployment will automatically be updated when changes are pushed to the specified `Git Branch`. This setting can be enabled/disabled later in the [Deployment Settings](#deployment-settings).
1. Select the desired `Deployment Type`.
1. `Development` deployments are meant for non-production use cases and are provisioned with minimal resources.
1. `Production` deployments can serve up to 500 requests/second and are provisioned with highly available storage with automatic backups.
1. Determine if the deployment should be `Shareable through LangGraph Studio`.
1. If unchecked, the deployment will only be accessible with a valid LangSmith API key for the workspace.
1. If checked, the deployment will be accessible through LangGraph Studio to any LangSmith user. A direct URL to LangGraph Studio for the deployment will be provided to share with other LangSmith users.
1. Specify `Environment Variables` and secrets. See the [Environment Variables reference](../reference/env_var.md) to configure additional variables for the deployment.
1. Sensitive values such as API keys (e.g. `OPENAI_API_KEY`) should be specified as secrets.
1. Additional non-secret environment variables can be specified as well.
1. A new LangSmith `Tracing Project` is automatically created with the same name as the deployment.
1. In the top-right corner, select `Submit`. After a few seconds, the `Deployment` view appears and the new deployment will be queued for provisioning.
## Create New Revision
When [creating a new deployment](#create-new-deployment), a new revision is created by default. Subsequent revisions can be created to deploy new code changes.
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
1. In the left-hand navigation panel, select `LangGraph Platform`. The `LangGraph Platform` view contains a list of existing LangGraph Platform deployments.
1. Select an existing deployment to create a new revision for.
1. In the `Deployment` view, in the top-right corner, select `+ New Revision`.
1. In the `New Revision` modal, fill out the required fields.
1. Specify the full path to the [LangGraph API config file](../reference/cli.md#configuration-file) including the file name. For example, if the file `langgraph.json` is in the root of the repository, simply specify `langgraph.json`.
1. Determine if the deployment should be `Shareable through LangGraph Studio`.
1. If unchecked, the deployment will only be accessible with a valid LangSmith API key for the workspace.
1. If checked, the deployment will be accessible through LangGraph Studio to any LangSmith user. A direct URL to LangGraph Studio for the deployment will be provided to share with other LangSmith users.
1. Specify `Environment Variables` and secrets. Existing secrets and environment variables are prepopulated. See the [Environment Variables reference](../reference/env_var.md) to configure additional variables for the revision.
1. Add new secrets or environment variables.
1. Remove existing secrets or environment variables.
1. Update the value of existing secrets or environment variables.
1. Select `Submit`. After a few seconds, the `New Revision` modal will close and the new revision will be queued for deployment.
## View Build and Server Logs
Build and server logs are available for each revision.
Starting from the `LangGraph Platform` view...
1. Select the desired revision from the `Revisions` table. A panel slides open from the right-hand side and the `Build` tab is selected by default, which displays build logs for the revision.
1. In the panel, select the `Server` tab to view server logs for the revision. Server logs are only available after a revision has been deployed.
1. Within the `Server` tab, adjust the date/time range picker as needed. By default, the date/time range picker is set to the `Last 7 days`.
## View Deployment Metrics
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
1. In the left-hand navigation panel, select `LangGraph Platform`. The `LangGraph Platform` view contains a list of existing LangGraph Platform deployments.
1. Select an existing deployment to monitor.
1. Select the `Monitoring` tab to view the deployment metrics. See a list of [all available metrics](../../concepts/langgraph_control_plane.md#monitoring).
1. Within the `Monitoring` tab, use the date/time range picker as needed. By default, the date/time range picker is set to the `Last 15 minutes`.
## Interrupt Revision
Interrupting a revision will stop deployment of the revision.
!!! warning "Undefined Behavior"
Interrupted revisions have undefined behavior. This is only useful if you need to deploy a new revision and you already have a revision "stuck" in progress. In the future, this feature may be removed.
Starting from the `LangGraph Platform` view...
1. Select the menu icon (three dots) on the right-hand side of the row for the desired revision from the `Revisions` table.
1. Select `Interrupt` from the menu.
1. A modal will appear. Review the confirmation message. Select `Interrupt revision`.
## Delete Deployment
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
1. In the left-hand navigation panel, select `LangGraph Platform`. The `LangGraph Platform` view contains a list of existing LangGraph Platform deployments.
1. Select the menu icon (three dots) on the right-hand side of the row for the desired deployment and select `Delete`.
1. A `Confirmation` modal will appear. Select `Delete`.
## Deployment Settings
Starting from the `LangGraph Platform` view...
1. In the top-right corner, select the gear icon (`Deployment Settings`).
1. Update the `Git Branch` to the desired branch.
1. Check/uncheck checkbox to `Automatically update deployment on push to branch`.
1. Branch creation/deletion and tag creation/deletion events will not trigger an update. Only pushes to an existing branch will trigger an update.
1. Pushes in quick succession to a branch will not trigger subsequent updates. In the future, this functionality may be changed/improved.
## Add or Remove GitHub Repositories
After installing and authorizing LangChain's `hosted-langserve` GitHub app, repository access for the app can be modified to add new repositories or remove existing repositories. If a new repository is created, it may need to be added explicitly.
1. From the GitHub profile, navigate to `Settings` > `Applications` > `hosted-langserve` > click `Configure`.
1. Under `Repository access`, select `All repositories` or `Only select repositories`. If `Only select repositories` is selected, new repositories must be explicitly added.
1. Click `Save`.
1. When creating a new deployment, the list of GitHub repositories in the dropdown menu will be updated to reflect the repository access changes.
## Whitelisting IP Addresses
All traffic from `LangGraph Platform` deployments created after January 6th 2025 will come through a NAT gateway.
This NAT gateway will have several static ip addresses depending on the region you are deploying in. Refer to the table below for the list of IP addresses to whitelist:
| US | EU |
|----------------|-----------------|
| 35.197.29.146 | 34.90.213.236 |
| 34.145.102.123 | 34.13.244.114 |
| 34.169.45.153 | 34.32.180.189 |
| 34.82.222.17 | 34.34.69.108 |
| 35.227.171.135 | 34.32.145.240 |
| 34.169.88.30 | 34.90.157.44 |
| 34.19.93.202 | 34.141.242.180 |
| 34.19.34.50 | 34.32.141.108 |
@@ -0,0 +1,19 @@
# How to customize Dockerfile
Users can add an array of additional lines to add to the Dockerfile following the import from the parent LangGraph image. In order to do this, you simply need to modify your `langgraph.json` file by passing in the commands you want run to the `dockerfile_lines` key. For example, if we wanted to use `Pillow` in our graph you would need to add the following dependencies:
```
{
"dependencies": ["."],
"graphs": {
"openai_agent": "./openai_agent.py:agent",
},
"env": "./.env",
"dockerfile_lines": [
"RUN apt-get update && apt-get install -y libjpeg-dev zlib1g-dev libpng-dev",
"RUN pip install Pillow"
]
}
```
This would install the system packages required to use Pillow if we were working with `jpeq` or `png` image formats.
+147
View File
@@ -0,0 +1,147 @@
# Rebuild Graph at Runtime
You might need to rebuild your graph with a different configuration for a new run. For example, you might need to use a different graph state or graph structure depending on the config. This guide shows how you can do this.
!!! note "Note"
In most cases, customizing behavior based on the config should be handled by a single graph where each node can read a config and change its behavior based on it
## Prerequisites
Make sure to check out [this how-to guide](./setup.md) on setting up your app for deployment first.
## Define graphs
Let's say you have an app with a simple graph that calls an LLM and returns the response to the user. The app file directory looks like the following:
```
my-app/
|-- requirements.txt
|-- .env
|-- openai_agent.py # code for your graph
```
where the graph is defined in `openai_agent.py`.
### No rebuild
In the standard LangGraph API configuration, the server uses the compiled graph instance that's defined at the top level of `openai_agent.py`, which looks like the following:
```python
from langchain_openai import ChatOpenAI
from langgraph.graph import END, START, MessageGraph
model = ChatOpenAI(temperature=0)
graph_workflow = MessageGraph()
graph_workflow.add_node("agent", model)
graph_workflow.add_edge("agent", END)
graph_workflow.add_edge(START, "agent")
agent = graph_workflow.compile()
```
To make the server aware of your graph, you need to specify a path to the variable that contains the `CompiledStateGraph` instance in your LangGraph API configuration (`langgraph.json`), e.g.:
```
{
"dependencies": ["."],
"graphs": {
"openai_agent": "./openai_agent.py:agent",
},
"env": "./.env"
}
```
### Rebuild
To make your graph rebuild on each new run with custom configuration, you need to rewrite `openai_agent.py` to instead provide a _function_ that takes a config and returns a graph (or compiled graph) instance. Let's say we want to return our existing graph for user ID '1', and a tool-calling agent for other users. We can modify `openai_agent.py` as follows:
```python
from typing import Annotated
from typing_extensions import TypedDict
from langchain_openai import ChatOpenAI
from langgraph.graph import END, START, MessageGraph
from langgraph.graph.state import StateGraph
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
from langchain_core.tools import tool
from langchain_core.messages import BaseMessage
from langchain_core.runnables import RunnableConfig
class State(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
model = ChatOpenAI(temperature=0)
def make_default_graph():
"""Make a simple LLM agent"""
graph_workflow = StateGraph(State)
def call_model(state):
return {"messages": [model.invoke(state["messages"])]}
graph_workflow.add_node("agent", call_model)
graph_workflow.add_edge("agent", END)
graph_workflow.add_edge(START, "agent")
agent = graph_workflow.compile()
return agent
def make_alternative_graph():
"""Make a tool-calling agent"""
@tool
def add(a: float, b: float):
"""Adds two numbers."""
return a + b
tool_node = ToolNode([add])
model_with_tools = model.bind_tools([add])
def call_model(state):
return {"messages": [model_with_tools.invoke(state["messages"])]}
def should_continue(state: State):
if state["messages"][-1].tool_calls:
return "tools"
else:
return END
graph_workflow = StateGraph(State)
graph_workflow.add_node("agent", call_model)
graph_workflow.add_node("tools", tool_node)
graph_workflow.add_edge("tools", "agent")
graph_workflow.add_edge(START, "agent")
graph_workflow.add_conditional_edges("agent", should_continue)
agent = graph_workflow.compile()
return agent
# this is the graph making function that will decide which graph to
# build based on the provided config
def make_graph(config: RunnableConfig):
user_id = config.get("configurable", {}).get("user_id")
# route to different graph state / structure based on the user ID
if user_id == "1":
return make_default_graph()
else:
return make_alternative_graph()
```
Finally, you need to specify the path to your graph-making function (`make_graph`) in `langgraph.json`:
```
{
"dependencies": ["."],
"graphs": {
"openai_agent": "./openai_agent.py:make_graph",
},
"env": "./.env"
}
```
See more info on LangGraph API configuration file [here](../reference/cli.md#configuration-file)
Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 KiB

@@ -0,0 +1,51 @@
# How to Deploy Self-Hosted Control Plane
Before deploying, review the [conceptual guide for the Self-Hosted Control Plane](../../concepts/langgraph_self_hosted_control_plane.md) deployment option.
!!! info "Important"
The Self-Hosted Control Plane deployment option is currently in beta stage and requires an [Enterprise](../../concepts/plans.md) plan.
## Prerequisites
1. You are using Kubernetes.
1. You have self-hosted LangSmith deployed.
1. Use the [LangGraph CLI](../../concepts/langgraph_cli.md) to [test your application locally](../../tutorials/langgraph-platform/local-server.md).
1. Use the [LangGraph CLI](../../concepts/langgraph_cli.md) to build a Docker image (i.e. `langgraph build`) and push it to a registry your Kubernetes cluster has access to.
1. `KEDA` is installed on your cluster.
helm repo add kedacore https://kedacore.github.io/charts
helm install keda kedacore/keda --namespace keda --create-namespace
1. Ingress Configuration
1. You must set up an ingress for your LangSmith instance. All agents will be deployed as Kubernetes services behind this ingress.
1. You can use this guide to [set up an ingress](https://docs.smith.langchain.com/self_hosting/configuration/ingress) for your instance.
1. You have slack space in your cluster for multiple deployments. `Cluster-Autoscaler` is recommended to automatically provision new nodes.
1. A valid Dynamic PV provisioner or PVs available on your cluster. You can verify this by running:
kubectl get storageclass
## Setup
1. As part of configuring your Self-Hosted LangSmith instance, you enable the `langgraphPlatform` option. This will provision a few key resources.
1. `listener`: This is a service that listens to the [control plane](../../concepts/langgraph_control_plane.md) for changes to your deployments and creates/updates downstream CRDs.
1. `LangGraphPlatform CRD`: A CRD for LangGraph Platform deployments. This contains the spec for managing an instance of a LangGraph platform deployment.
1. `operator`: This operator handles changes to your LangGraph Platform CRDs.
1. `host-backend`: This is the [control plane](../../concepts/langgraph_control_plane.md).
1. Two additional images will be used by the chart. Use the images that are specified in the latest release.
hostBackendImage:
repository: "docker.io/langchain/hosted-langserve-backend"
pullPolicy: IfNotPresent
operatorImage:
repository: "docker.io/langchain/langgraph-operator"
pullPolicy: IfNotPresent
1. In your config file for langsmith (usually `langsmith_config.yaml`, enable the `langgraphPlatform` option. Note that you must also have a valid ingress setup:
config:
langgraphPlatform:
enabled: true
langgraphPlatformLicenseKey: "YOUR_LANGGRAPH_PLATFORM_LICENSE_KEY"
1. In your `values.yaml` file, configure the `hostBackendImage` and `operatorImage` options (if you need to mirror images)
1. You can also configure base templates for your agents by overriding the base templates [here](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/values.yaml#L898).
1. You create a deployment from the [control plane UI](../../concepts/langgraph_control_plane.md#control-plane-ui).
@@ -0,0 +1,56 @@
# How to Deploy Self-Hosted Data Plane
Before deploying, review the [conceptual guide for the Self-Hosted Data Plane](../../concepts/langgraph_self_hosted_data_plane.md) deployment option.
!!! info "Important"
The Self-Hosted Data Plane deployment option is currently in beta stage and requires an [Enterprise](../../concepts/plans.md) plan.
## Prerequisites
1. Use the [LangGraph CLI](../../concepts/langgraph_cli.md) to [test your application locally](../../tutorials/langgraph-platform/local-server.md).
1. Use the [LangGraph CLI](../../concepts/langgraph_cli.md) to build a Docker image (i.e. `langgraph build`) and push it to a registry your Kubernetes cluster or Amazon ECS cluster has access to.
## Kubernetes
### Prerequisites
1. `KEDA` is installed on your cluster.
helm repo add kedacore https://kedacore.github.io/charts
helm install keda kedacore/keda --namespace keda --create-namespace
1. A valid `Ingress` controller is installed on your cluster.
1. You have slack space in your cluster for multiple deployments. `Cluster-Autoscaler` is recommended to automatically provision new nodes.
### Setup
1. You give us your LangSmith organization ID. We will enable the Self-Hosted Data Plane for your organization.
1. We provide you a [Helm chart](https://github.com/langchain-ai/helm/tree/main/charts/langgraph-dataplane) which you run to setup your Kubernetes cluster. This chart contains a few important components.
1. `langgraph-listener`: This is a service that listens to LangChain's [control plane](../../concepts/langgraph_control_plane.md) for changes to your deployments and creates/updates downstream CRDs.
1. `LangGraphPlatform CRD`: A CRD for LangGraph Platform deployments. This contains the spec for managing an instance of a LangGraph Platform deployment.
1. `langgraph-platform-operator`: This operator handles changes to your LangGraph Platform CRDs.
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
smithBackendUrl: "https://api.smith.langchain.com" # Only override this if on EU
1. Deploy `langgraph-dataplane` Helm chart.
helm repo add langchain https://langchain-ai.github.io/helm/
helm repo update
helm upgrade -i langgraph-dataplane langchain/langgraph-dataplane --values langgraph-dataplane-values.yaml
1. If successful, you will see two services start up in your namespace.
NAME READY STATUS RESTARTS AGE
langgraph-dataplane-listener-7fccd788-wn2dx 0/1 Running 0 9s
langgraph-dataplane-redis-0 0/1 ContainerCreating 0 9s
1. You create a deployment from the [control plane UI](../../concepts/langgraph_control_plane.md#control-plane-ui).
## Amazon ECS
Coming soon!
@@ -0,0 +1,123 @@
# How to add semantic search to your LangGraph deployment
This guide explains how to add semantic search to your LangGraph deployment's cross-thread [store](../../concepts/persistence.md#memory-store), so that your agent can search for memories and other documents by semantic similarity.
## Prerequisites
- A LangGraph deployment (see [how to deploy](setup_pyproject.md))
- API keys for your embedding provider (in this case, OpenAI)
- `langchain >= 0.3.8` (if you specify using the string format below)
## Steps
1. Update your `langgraph.json` configuration file to include the store configuration:
```json
{
...
"store": {
"index": {
"embed": "openai:text-embedding-3-small",
"dims": 1536,
"fields": ["$"]
}
}
}
```
This configuration:
- Uses OpenAI's text-embedding-3-small model for generating embeddings
- Sets the embedding dimension to 1536 (matching the model's output)
- Indexes all fields in your stored data (`["$"]` means index everything, or specify specific fields like `["text", "metadata.title"]`)
2. To use the string embedding format above, make sure your dependencies include `langchain >= 0.3.8`:
```toml
# In pyproject.toml
[project]
dependencies = [
"langchain>=0.3.8"
]
```
Or if using requirements.txt:
```
langchain>=0.3.8
```
## Usage
Once configured, you can use semantic search in your LangGraph nodes. The store requires a namespace tuple to organize memories:
```python
def search_memory(state: State, *, store: BaseStore):
# Search the store using semantic similarity
# The namespace tuple helps organize different types of memories
# e.g., ("user_facts", "preferences") or ("conversation", "summaries")
results = store.search(
namespace=("memory", "facts"), # Organize memories by type
query="your search query",
limit=3 # number of results to return
)
return results
```
## Custom Embeddings
If you want to use custom embeddings, you can pass a path to a custom embedding function:
```json
{
...
"store": {
"index": {
"embed": "path/to/embedding_function.py:embed",
"dims": 1536,
"fields": ["$"]
}
}
}
```
The deployment will look for the function in the specified path. The function must be async and accept a list of strings:
```python
# path/to/embedding_function.py
from openai import AsyncOpenAI
client = AsyncOpenAI()
async def aembed_texts(texts: list[str]) -> list[list[float]]:
"""Custom embedding function that must:
1. Be async
2. Accept a list of strings
3. Return a list of float arrays (embeddings)
"""
response = await client.embeddings.create(
model="text-embedding-3-small",
input=texts
)
return [e.embedding for e in response.data]
```
## Querying via the API
You can also query the store using the LangGraph SDK. Since the SDK uses async operations:
```python
from langgraph_sdk import get_client
async def search_store():
client = get_client()
results = await client.store.search_items(
("memory", "facts"),
query="your search query",
limit=3 # number of results to return
)
return results
# Use in an async context
results = await search_store()
```
+188
View File
@@ -0,0 +1,188 @@
# How to Set Up a LangGraph Application with requirements.txt
A LangGraph application must be configured with a [LangGraph configuration file](../reference/cli.md#configuration-file) in order to be deployed to LangGraph Platform (or to be self-hosted). This how-to guide discusses the basic steps to setup a LangGraph application for deployment using `requirements.txt` to specify project dependencies.
This walkthrough is based on [this repository](https://github.com/langchain-ai/langgraph-example), which you can play around with to learn more about how to setup your LangGraph application for deployment.
!!! tip "Setup with pyproject.toml"
If you prefer using poetry for dependency management, check out [this how-to guide](./setup_pyproject.md) on using `pyproject.toml` for LangGraph Platform.
!!! tip "Setup with a Monorepo"
If you are interested in deploying a graph located inside a monorepo, take a look at [this repository](https://github.com/langchain-ai/langgraph-example-monorepo) for an example of how to do so.
The final repository structure will look something like this:
```bash
my-app/
├── my_agent # all project code lies within here
│ ├── utils # utilities for your graph
│ │ ├── __init__.py
│ │ ├── tools.py # tools for your graph
│ │ ├── nodes.py # node functions for you graph
│ │ └── state.py # state definition of your graph
│   ├── requirements.txt # package dependencies
│   ├── __init__.py
│   └── agent.py # code for constructing your graph
├── .env # environment variables
└── langgraph.json # configuration file for LangGraph
```
After each step, an example file directory is provided to demonstrate how code can be organized.
## Specify Dependencies
Dependencies can optionally be specified in one of the following files: `pyproject.toml`, `setup.py`, or `requirements.txt`. If none of these files is created, then dependencies can be specified later in the [LangGraph configuration file](#create-langgraph-configuration-file).
The dependencies below will be included in the image, you can also use them in your code, as long as with a compatible version range:
```
langgraph>=0.3.27
langgraph-sdk>=0.1.66
langgraph-checkpoint>=2.0.23
langchain-core>=0.2.38
langsmith>=0.1.63
orjson>=3.9.7,<3.10.17
httpx>=0.25.0
tenacity>=8.0.0
uvicorn>=0.26.0
sse-starlette>=2.1.0,<2.2.0
uvloop>=0.18.0
httptools>=0.5.0
jsonschema-rs>=0.20.0
structlog>=24.1.0
cloudpickle>=3.0.0
```
Example `requirements.txt` file:
```
langgraph
langchain_anthropic
tavily-python
langchain_community
langchain_openai
```
Example file directory:
```bash
my-app/
├── my_agent # all project code lies within here
│   └── requirements.txt # package dependencies
```
## Specify Environment Variables
Environment variables can optionally be specified in a file (e.g. `.env`). See the [Environment Variables reference](../reference/env_var.md) to configure additional variables for a deployment.
Example `.env` file:
```
MY_ENV_VAR_1=foo
MY_ENV_VAR_2=bar
OPENAI_API_KEY=key
```
Example file directory:
```bash
my-app/
├── my_agent # all project code lies within here
│   └── requirements.txt # package dependencies
└── .env # environment variables
```
## Define Graphs
Implement your graphs! Graphs can be defined in a single file or multiple files. Make note of the variable names of each [CompiledGraph][langgraph.graph.graph.CompiledGraph] to be included in the LangGraph application. The variable names will be used later when creating the [LangGraph configuration file](../reference/cli.md#configuration-file).
Example `agent.py` file, which shows how to import from other modules you define (code for the modules is not shown here, please see [this repository](https://github.com/langchain-ai/langgraph-example) to see their implementation):
```python
# my_agent/agent.py
from typing import Literal
from typing_extensions import TypedDict
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):
model_name: Literal["anthropic", "openai"]
workflow = StateGraph(AgentState, config_schema=GraphConfig)
workflow.add_node("agent", call_model)
workflow.add_node("action", tool_node)
workflow.add_edge(START, "agent")
workflow.add_conditional_edges(
"agent",
should_continue,
{
"continue": "action",
"end": END,
},
)
workflow.add_edge("action", "agent")
graph = workflow.compile()
```
Example file directory:
```bash
my-app/
├── my_agent # all project code lies within here
│ ├── utils # utilities for your graph
│ │ ├── __init__.py
│ │ ├── tools.py # tools for your graph
│ │ ├── nodes.py # node functions for you graph
│ │ └── state.py # state definition of your graph
│   ├── requirements.txt # package dependencies
│   ├── __init__.py
│   └── agent.py # code for constructing your graph
└── .env # environment variables
```
## Create LangGraph Configuration File
Create a [LangGraph configuration file](../reference/cli.md#configuration-file) called `langgraph.json`. See the [LangGraph configuration file reference](../reference/cli.md#configuration-file) for detailed explanations of each key in the JSON object of the configuration file.
Example `langgraph.json` file:
```json
{
"dependencies": ["./my_agent"],
"graphs": {
"agent": "./my_agent/agent.py:graph"
},
"env": ".env"
}
```
Note that the variable name of the `CompiledGraph` appears at the end of the value of each subkey in the top-level `graphs` key (i.e. `:<variable_name>`).
!!! warning "Configuration File Location"
The LangGraph configuration file must be placed in a directory that is at the same level or higher than the Python files that contain compiled graphs and associated dependencies.
Example file directory:
```bash
my-app/
├── my_agent # all project code lies within here
│ ├── utils # utilities for your graph
│ │ ├── __init__.py
│ │ ├── tools.py # tools for your graph
│ │ ├── nodes.py # node functions for you graph
│ │ └── state.py # state definition of your graph
│   ├── requirements.txt # package dependencies
│   ├── __init__.py
│   └── agent.py # code for constructing your graph
├── .env # environment variables
└── langgraph.json # configuration file for LangGraph
```
## Next
After you setup your project and place it in a GitHub repository, it's time to [deploy your app](./cloud.md).
@@ -0,0 +1,199 @@
# How to Set Up a LangGraph.js Application
A [LangGraph.js](https://langchain-ai.github.io/langgraphjs/) application must be configured with a [LangGraph configuration file](../reference/cli.md#configuration-file) in order to be deployed to LangGraph Platform (or to be self-hosted). This how-to guide discusses the basic steps to setup a LangGraph.js application for deployment using `package.json` to specify project dependencies.
This walkthrough is based on [this repository](https://github.com/langchain-ai/langgraphjs-studio-starter), which you can play around with to learn more about how to setup your LangGraph application for deployment.
The final repository structure will look something like this:
```bash
my-app/
├── src # all project code lies within here
│ ├── utils # optional utilities for your graph
│ │ ├── tools.ts # tools for your graph
│ │ ├── nodes.ts # node functions for you graph
│ │ └── state.ts # state definition of your graph
│   └── agent.ts # code for constructing your graph
├── package.json # package dependencies
├── .env # environment variables
└── langgraph.json # configuration file for LangGraph
```
After each step, an example file directory is provided to demonstrate how code can be organized.
## Specify Dependencies
Dependencies can be specified in a `package.json`. If none of these files is created, then dependencies can be specified later in the [LangGraph configuration file](#create-langgraph-api-config).
Example `package.json` file:
```json
{
"name": "langgraphjs-studio-starter",
"packageManager": "yarn@1.22.22",
"dependencies": {
"@langchain/community": "^0.2.31",
"@langchain/core": "^0.2.31",
"@langchain/langgraph": "^0.2.0",
"@langchain/openai": "^0.2.8"
}
}
```
When deploying your app, the dependencies will be installed using the package manager of your choice, provided they adhere to the compatible version ranges listed below:
```
"@langchain/core": "^0.3.42",
"@langchain/langgraph": "^0.2.57",
"@langchain/langgraph-checkpoint": "~0.0.16",
```
Example file directory:
```bash
my-app/
└── package.json # package dependencies
```
## Specify Environment Variables
Environment variables can optionally be specified in a file (e.g. `.env`). See the [Environment Variables reference](../reference/env_var.md) to configure additional variables for a deployment.
Example `.env` file:
```
MY_ENV_VAR_1=foo
MY_ENV_VAR_2=bar
OPENAI_API_KEY=key
TAVILY_API_KEY=key_2
```
Example file directory:
```bash
my-app/
├── package.json
└── .env # environment variables
```
## Define Graphs
Implement your graphs! Graphs can be defined in a single file or multiple files. Make note of the variable names of each compiled graph to be included in the LangGraph application. The variable names will be used later when creating the [LangGraph configuration file](../reference/cli.md#configuration-file).
Here is an example `agent.ts`:
```ts
import type { AIMessage } from "@langchain/core/messages";
import { TavilySearchResults } from "@langchain/community/tools/tavily_search";
import { ChatOpenAI } from "@langchain/openai";
import { MessagesAnnotation, StateGraph } from "@langchain/langgraph";
import { ToolNode } from "@langchain/langgraph/prebuilt";
const tools = [new TavilySearchResults({ maxResults: 3 })];
// Define the function that calls the model
async function callModel(state: typeof MessagesAnnotation.State) {
/**
* Call the LLM powering our agent.
* Feel free to customize the prompt, model, and other logic!
*/
const model = new ChatOpenAI({
model: "gpt-4o",
}).bindTools(tools);
const response = await model.invoke([
{
role: "system",
content: `You are a helpful assistant. The current date is ${new Date().getTime()}.`,
},
...state.messages,
]);
// MessagesAnnotation supports returning a single message or array of messages
return { messages: response };
}
// Define the function that determines whether to continue or not
function routeModelOutput(state: typeof MessagesAnnotation.State) {
const messages = state.messages;
const lastMessage: AIMessage = messages[messages.length - 1];
// If the LLM is invoking tools, route there.
if ((lastMessage?.tool_calls?.length ?? 0) > 0) {
return "tools";
}
// Otherwise end the graph.
return "__end__";
}
// Define a new graph.
// See https://langchain-ai.github.io/langgraphjs/how-tos/define-state/#getting-started for
// more on defining custom graph states.
const workflow = new StateGraph(MessagesAnnotation)
// Define the two nodes we will cycle between
.addNode("callModel", callModel)
.addNode("tools", new ToolNode(tools))
// Set the entrypoint as `callModel`
// This means that this node is the first one called
.addEdge("__start__", "callModel")
.addConditionalEdges(
// First, we define the edges' source node. We use `callModel`.
// This means these are the edges taken after the `callModel` node is called.
"callModel",
// Next, we pass in the function that will determine the sink node(s), which
// will be called after the source node is called.
routeModelOutput,
// List of the possible destinations the conditional edge can route to.
// Required for conditional edges to properly render the graph in Studio
["tools", "__end__"]
)
// This means that after `tools` is called, `callModel` node is called next.
.addEdge("tools", "callModel");
// Finally, we compile it!
// This compiles it into a graph you can invoke and deploy.
export const graph = workflow.compile();
```
Example file directory:
```bash
my-app/
├── src # all project code lies within here
│ ├── utils # optional utilities for your graph
│ │ ├── tools.ts # tools for your graph
│ │ ├── nodes.ts # node functions for you graph
│ │ └── state.ts # state definition of your graph
│   └── agent.ts # code for constructing your graph
├── package.json # package dependencies
├── .env # environment variables
└── langgraph.json # configuration file for LangGraph
```
## Create LangGraph API Config
Create a [LangGraph configuration file](../reference/cli.md#configuration-file) called `langgraph.json`. See the [LangGraph configuration file reference](../reference/cli.md#configuration-file) for detailed explanations of each key in the JSON object of the configuration file.
Example `langgraph.json` file:
```json
{
"node_version": "20",
"dockerfile_lines": [],
"dependencies": ["."],
"graphs": {
"agent": "./src/agent.ts:graph"
},
"env": ".env"
}
```
Note that the variable name of the `CompiledGraph` appears at the end of the value of each subkey in the top-level `graphs` key (i.e. `:<variable_name>`).
!!! info "Configuration Location"
The LangGraph configuration file must be placed in a directory that is at the same level or higher than the TypeScript files that contain compiled graphs and associated dependencies.
## Next
After you setup your project and place it in a GitHub repository, it's time to [deploy your app](./cloud.md).
@@ -0,0 +1,201 @@
# How to Set Up a LangGraph Application with pyproject.toml
A LangGraph application must be configured with a [LangGraph configuration file](../reference/cli.md#configuration-file) in order to be deployed to LangGraph Platform (or to be self-hosted). This how-to guide discusses the basic steps to setup a LangGraph application for deployment using `pyproject.toml` to define your package's dependencies.
This walkthrough is based on [this repository](https://github.com/langchain-ai/langgraph-example-pyproject), which you can play around with to learn more about how to setup your LangGraph application for deployment.
!!! tip "Setup with requirements.txt"
If you prefer using `requirements.txt` for dependency management, check out [this how-to guide](./setup.md).
!!! tip "Setup with a Monorepo"
If you are interested in deploying a graph located inside a monorepo, take a look at [this](https://github.com/langchain-ai/langgraph-example-monorepo) repository for an example of how to do so.
The final repository structure will look something like this:
```bash
my-app/
├── my_agent # all project code lies within here
│ ├── utils # utilities for your graph
│ │ ├── __init__.py
│ │ ├── tools.py # tools for your graph
│ │ ├── nodes.py # node functions for you graph
│ │ └── state.py # state definition of your graph
│   ├── __init__.py
│   └── agent.py # code for constructing your graph
├── .env # environment variables
├── langgraph.json # configuration file for LangGraph
└── pyproject.toml # dependencies for your project
```
After each step, an example file directory is provided to demonstrate how code can be organized.
## Specify Dependencies
Dependencies can optionally be specified in one of the following files: `pyproject.toml`, `setup.py`, or `requirements.txt`. If none of these files is created, then dependencies can be specified later in the [LangGraph configuration file](#create-langgraph-configuration-file).
The dependencies below will be included in the image, you can also use them in your code, as long as with a compatible version range:
```
langgraph>=0.3.27
langgraph-sdk>=0.1.66
langgraph-checkpoint>=2.0.23
langchain-core>=0.2.38
langsmith>=0.1.63
orjson>=3.9.7,<3.10.17
httpx>=0.25.0
tenacity>=8.0.0
uvicorn>=0.26.0
sse-starlette>=2.1.0,<2.2.0
uvloop>=0.18.0
httptools>=0.5.0
jsonschema-rs>=0.20.0
structlog>=24.1.0
cloudpickle>=3.0.0
```
Example `pyproject.toml` file:
```toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "my-agent"
version = "0.0.1"
description = "An excellent agent build for LangGraph Platform."
authors = [
{name = "Polly the parrot", email = "1223+polly@users.noreply.github.com"}
]
license = {text = "MIT"}
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
"langgraph>=0.2.0",
"langchain-fireworks>=0.1.3"
]
[tool.hatch.build.targets.wheel]
packages = ["my_agent"]
```
Example file directory:
```bash
my-app/
└── pyproject.toml # Python packages required for your graph
```
## Specify Environment Variables
Environment variables can optionally be specified in a file (e.g. `.env`). See the [Environment Variables reference](../reference/env_var.md) to configure additional variables for a deployment.
Example `.env` file:
```
MY_ENV_VAR_1=foo
MY_ENV_VAR_2=bar
FIREWORKS_API_KEY=key
```
Example file directory:
```bash
my-app/
├── .env # file with environment variables
└── pyproject.toml
```
## Define Graphs
Implement your graphs! Graphs can be defined in a single file or multiple files. Make note of the variable names of each [CompiledGraph][langgraph.graph.graph.CompiledGraph] to be included in the LangGraph application. The variable names will be used later when creating the [LangGraph configuration file](../reference/cli.md#configuration-file).
Example `agent.py` file, which shows how to import from other modules you define (code for the modules is not shown here, please see [this repository](https://github.com/langchain-ai/langgraph-example-pyproject) to see their implementation):
```python
# my_agent/agent.py
from typing import Literal
from typing_extensions import TypedDict
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):
model_name: Literal["anthropic", "openai"]
workflow = StateGraph(AgentState, config_schema=GraphConfig)
workflow.add_node("agent", call_model)
workflow.add_node("action", tool_node)
workflow.add_edge(START, "agent")
workflow.add_conditional_edges(
"agent",
should_continue,
{
"continue": "action",
"end": END,
},
)
workflow.add_edge("action", "agent")
graph = workflow.compile()
```
Example file directory:
```bash
my-app/
├── my_agent # all project code lies within here
│ ├── utils # utilities for your graph
│ │ ├── __init__.py
│ │ ├── tools.py # tools for your graph
│ │ ├── nodes.py # node functions for you graph
│ │ └── state.py # state definition of your graph
│   ├── __init__.py
│   └── agent.py # code for constructing your graph
├── .env
└── pyproject.toml
```
## Create LangGraph Configuration File
Create a [LangGraph configuration file](../reference/cli.md#configuration-file) called `langgraph.json`. See the [LangGraph configuration file reference](../reference/cli.md#configuration-file) for detailed explanations of each key in the JSON object of the configuration file.
Example `langgraph.json` file:
```json
{
"dependencies": ["."],
"graphs": {
"agent": "./my_agent/agent.py:graph"
},
"env": ".env"
}
```
Note that the variable name of the `CompiledGraph` appears at the end of the value of each subkey in the top-level `graphs` key (i.e. `:<variable_name>`).
!!! warning "Configuration File Location"
The LangGraph configuration file must be placed in a directory that is at the same level or higher than the Python files that contain compiled graphs and associated dependencies.
Example file directory:
```bash
my-app/
├── my_agent # all project code lies within here
│ ├── utils # utilities for your graph
│ │ ├── __init__.py
│ │ ├── tools.py # tools for your graph
│ │ ├── nodes.py # node functions for you graph
│ │ └── state.py # state definition of your graph
│   ├── __init__.py
│   └── agent.py # code for constructing your graph
├── .env # environment variables
├── langgraph.json # configuration file for LangGraph
└── pyproject.toml # dependencies for your project
```
## Next
After you setup your project and place it in a GitHub repository, it's time to [deploy your app](./cloud.md).
@@ -0,0 +1,110 @@
# How to Deploy a Standalone Container
Before deploying, review the [conceptual guide for the Standalone Container](../../concepts/langgraph_standalone_container.md) deployment option.
## Prerequisites
1. Use the [LangGraph CLI](../../concepts/langgraph_cli.md) to [test your application locally](../../tutorials/langgraph-platform/local-server.md).
1. Use the [LangGraph CLI](../../concepts/langgraph_cli.md) to build a Docker image (i.e. `langgraph build`).
1. The following environment variables are needed for a standalone container deployment.
1. `REDIS_URI`: Connection details to a Redis instance. Redis will be used as a pub-sub broker to enable streaming real time output from background runs. The value of `REDIS_URI` must be a valid [Redis connection URI](https://redis-py.readthedocs.io/en/stable/connections.html#redis.Redis.from_url).
!!! Note "Shared Redis Instance"
Multiple self-hosted deployments can share the same Redis instance. For example, for `Deployment A`, `REDIS_URI` can be set to `redis://<hostname_1>:<port>/1` and for `Deployment B`, `REDIS_URI` can be set to `redis://<hostname_1>:<port>/2`.
`1` and `2` are different database numbers within the same instance, but `<hostname_1>` is shared. **The same database number cannot be used for separate deployments**.
1. `DATABASE_URI`: Postgres connection details. Postgres will be used to store assistants, threads, runs, persist thread state and long term memory, and to manage the state of the background task queue with 'exactly once' semantics. The value of `DATABASE_URI` must be a valid [Postgres connection URI](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-URIS).
!!! Note "Shared Postgres Instance"
Multiple self-hosted deployments can share the same Postgres instance. For example, for `Deployment A`, `DATABASE_URI` can be set to `postgres://<user>:<password>@/<database_name_1>?host=<hostname_1>` and for `Deployment B`, `DATABASE_URI` can be set to `postgres://<user>:<password>@/<database_name_2>?host=<hostname_1>`.
`<database_name_1>` and `database_name_2` are different databases within the same instance, but `<hostname_1>` is shared. **The same database cannot be used for separate deployments**.
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.
## Kubernetes (Helm)
Use this [Helm chart](https://github.com/langchain-ai/helm/blob/main/charts/langgraph-cloud/README.md) to deploy a LangGraph Server to a Kubernetes cluster.
## Docker
Run the following `docker` command:
```shell
docker run \
--env-file .env \
-p 8123:8000 \
-e REDIS_URI="foo" \
-e DATABASE_URI="bar" \
-e LANGSMITH_API_KEY="baz" \
my-image
```
!!! note
* You need to replace `my-image` with the name of the image you built in the prerequisite steps (from `langgraph build`)
and you should provide appropriate values for `REDIS_URI`, `DATABASE_URI`, and `LANGSMITH_API_KEY`.
* If your application requires additional environment variables, you can pass them in a similar way.
## Docker Compose
Docker Compose YAML file:
```yml
volumes:
langgraph-data:
driver: local
services:
langgraph-redis:
image: redis:6
healthcheck:
test: redis-cli ping
interval: 5s
timeout: 1s
retries: 5
langgraph-postgres:
image: postgres:16
ports:
- "5433:5432"
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- langgraph-data:/var/lib/postgresql/data
healthcheck:
test: pg_isready -U postgres
start_period: 10s
timeout: 1s
retries: 5
interval: 5s
langgraph-api:
image: ${IMAGE_NAME}
ports:
- "8123:8000"
depends_on:
langgraph-redis:
condition: service_healthy
langgraph-postgres:
condition: service_healthy
env_file:
- .env
environment:
REDIS_URI: redis://langgraph-redis:6379
LANGSMITH_API_KEY: ${LANGSMITH_API_KEY}
POSTGRES_URI: postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable
```
You can run the command `docker compose up` with this Docker Compose file in the same folder.
This will launch a LangGraph Server on port `8123` (if you want to change this, you can change this by changing the ports in the `langgraph-api` volume). You can test if the application is healthy by running:
```shell
curl --request GET --url 0.0.0.0:8123/ok
```
Assuming everything is running correctly, you should see a response like:
```shell
{"ok":true}
```
@@ -0,0 +1,342 @@
# Human-in-the-loop
LangGraph supports robust **human-in-the-loop (HIL)** workflows, enabling human intervention at any point in an automated process. This is especially useful in large language model (LLM)-driven applications where model output may require validation, correction, or additional context.
Please see [the overview of LangGraph human-in-the-loop](../../concepts/human_in_the_loop.md) features for more information.
## `interrupt`
The [`interrupt` function][langgraph.types.interrupt] in LangGraph enables human-in-the-loop workflows by pausing the graph at a specific node, presenting information to a human, and resuming the graph with their input. It's useful for tasks like approvals, edits, or gathering additional context.
The graph is resumed using a [`Command`][langgraph.types.Command] object that provides the human's response.
**Graph node with `interrupt`:**
```python
# highlight-next-line
from langgraph.types import interrupt, Command
def human_node(state: State):
# highlight-next-line
value = interrupt( # (1)!
{
"text_to_revise": state["some_text"] # (2)!
}
)
return {
"some_text": value # (3)!
}
```
1. `interrupt(...)` pauses execution at `human_node`, surfacing the given payload to a human.
2. Any JSON serializable value can be passed to the `interrupt` function. Here, a dict containing the text to revise.
3. Once resumed, the return value of `interrupt(...)` is the human-provided input, which is used to update the state.
**LangGraph API invoke & resume:**
=== "Python"
```python
from langgraph_sdk import get_client
# highlight-next-line
from langgraph_sdk.schema import Command
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 interrupt is hit.
result = await client.runs.wait(
thread_id,
assistant_id,
input={"some_text": "original text"} # (1)!
)
print(result['__interrupt__']) # (2)!
# > [
# > {
# > 'value': {'text_to_revise': 'original text'},
# > 'resumable': True,
# > 'ns': ['human_node:fc722478-2f21-0578-c572-d9fc4dd07c3b'],
# > 'when': 'during'
# > }
# > ]
# Resume the graph
print(await client.runs.wait(
thread_id,
assistant_id,
# highlight-next-line
command=Command(resume="Edited text") # (3)!
))
# > {'some_text': 'Edited text'}
```
1. The graph is invoked with some initial state.
2. When the graph hits the interrupt, it returns an interrupt object with the payload and metadata.
3. The graph is resumed with a `Command(resume=...)`, injecting the human's input and continuing execution.
=== "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 interrupt is hit.
const result = await client.runs.wait(
threadID,
assistantID,
{ input: { "some_text": "original text" } } // (1)!
);
console.log(result['__interrupt__']); // (2)!
// > [
// > {
// > 'value': {'text_to_revise': 'original text'},
// > 'resumable': True,
// > 'ns': ['human_node:fc722478-2f21-0578-c572-d9fc4dd07c3b'],
// > 'when': 'during'
// > }
// > ]
// Resume the graph
console.log(await client.runs.wait(
threadID,
assistantID,
// highlight-next-line
{ command: { resume: "Edited text" }} // (3)!
));
// > {'some_text': 'Edited text'}
```
1. The graph is invoked with some initial state.
2. When the graph hits the interrupt, it returns an interrupt object with the payload and metadata.
3. The graph is resumed with a `{ resume: ... }` command object, injecting the human's input and continuing execution.
=== "cURL"
Create a thread:
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
Run the graph until the interrupt is hit.:
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"some_text\": \"original text\"}
}"
```
Resume the graph:
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"command\": {
\"resume\": \"Edited text\"
}
}"
```
??? example "Extended example: using `interrupt`"
This is an example graph you can run in the LangGraph API server.
See [LangGraph Platform quickstart](../quick_start.md) for more details.
```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)!
{
"text_to_revise": state["some_text"] # (2)!
}
)
return {
"some_text": value # (3)!
}
# Build the graph
graph_builder = StateGraph(State)
graph_builder.add_node("human_node", human_node)
graph_builder.add_edge(START, "human_node")
graph = graph_builder.compile()
```
1. `interrupt(...)` pauses execution at `human_node`, surfacing the given payload to a human.
2. Any JSON serializable value can be passed to the `interrupt` function. Here, a dict containing the text to revise.
3. Once resumed, the return value of `interrupt(...)` is the human-provided input, which is used to update the state.
Once you have a running LangGraph API server, you can interact with it using
[LangGraph SDK](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/)
=== "Python"
```python
from langgraph_sdk import get_client
# highlight-next-line
from langgraph_sdk.schema import Command
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 interrupt is hit.
result = await client.runs.wait(
thread_id,
assistant_id,
input={"some_text": "original text"} # (1)!
)
print(result['__interrupt__']) # (2)!
# > [
# > {
# > 'value': {'text_to_revise': 'original text'},
# > 'resumable': True,
# > 'ns': ['human_node:fc722478-2f21-0578-c572-d9fc4dd07c3b'],
# > 'when': 'during'
# > }
# > ]
# Resume the graph
print(await client.runs.wait(
thread_id,
assistant_id,
# highlight-next-line
command=Command(resume="Edited text") # (3)!
))
# > {'some_text': 'Edited text'}
```
1. The graph is invoked with some initial state.
2. When the graph hits the interrupt, it returns an interrupt object with the payload and metadata.
3. The graph is resumed with a `Command(resume=...)`, injecting the human's input and continuing execution.
=== "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 interrupt is hit.
const result = await client.runs.wait(
threadID,
assistantID,
{ input: { "some_text": "original text" } } // (1)!
);
console.log(result['__interrupt__']); // (2)!
// > [
// > {
// > 'value': {'text_to_revise': 'original text'},
// > 'resumable': True,
// > 'ns': ['human_node:fc722478-2f21-0578-c572-d9fc4dd07c3b'],
// > 'when': 'during'
// > }
// > ]
// Resume the graph
console.log(await client.runs.wait(
threadID,
assistantID,
// highlight-next-line
{ command: { resume: "Edited text" }} // (3)!
));
// > {'some_text': 'Edited text'}
```
1. The graph is invoked with some initial state.
2. When the graph hits the interrupt, it returns an interrupt object with the payload and metadata.
3. The graph is resumed with a `{ resume: ... }` command object, injecting the human's input and continuing execution.
=== "cURL"
Create a thread:
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
Run the graph until the interrupt is hit:
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"some_text\": \"original text\"}
}"
```
Resume the graph:
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"command\": {
\"resume\": \"Edited text\"
}
}"
```
## Learn more
- [**LangGraph human-in-the-loop overview**](../../concepts/human_in_the_loop.md): learn more about LangGraph human-in-the-loop features.
- [**Design patterns**](../../how-tos/human_in_the_loop/add-human-in-the-loop.md#design-patterns): learn how to implement patterns like approving/rejecting actions, requesting user input, and more.
- [**How to review tool calls**](./human_in_the_loop_review_tool_calls.md): detailed examples of how to review and approve/edit tool calls or provide feedback to the tool-calling LLM.
+451
View File
@@ -0,0 +1,451 @@
# How to kick off background runs
This guide covers how to kick off background runs for your agent.
This can be useful for long running jobs.
## Setup
First let's set up our client and thread:
=== "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 thread
thread = await client.threads.create()
print(thread)
```
=== "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 thread
const thread = await client.threads.create();
console.log(thread);
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
Output:
{
'thread_id': '5cb1e8a1-34b3-4a61-a34e-71a9799bd00d',
'created_at': '2024-08-30T20:35:52.062934+00:00',
'updated_at': '2024-08-30T20:35:52.062934+00:00',
'metadata': {},
'status': 'idle',
'config': {},
'values': None
}
## Check runs on thread
If we list the current runs on this thread, we will see that it's empty:
=== "Python"
```python
runs = await client.runs.list(thread["thread_id"])
print(runs)
```
=== "Javascript"
```js
let runs = await client.runs.list(thread['thread_id']);
console.log(runs);
```
=== "CURL"
```bash
curl --request GET \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs
```
Output:
[]
## Start runs on thread
Now let's kick off a run:
=== "Python"
```python
input = {"messages": [{"role": "user", "content": "what's the weather in sf"}]}
run = await client.runs.create(thread["thread_id"], assistant_id, input=input)
```
=== "Javascript"
```js
let input = {"messages": [{"role": "user", "content": "what's the weather in sf"}]};
let run = await client.runs.create(thread["thread_id"], assistantID, { input });
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs \
--header 'Content-Type: application/json' \
--data '{
"assistant_id": <ASSISTANT_ID>
}'
```
The first time we poll it, we can see `status=pending`:
=== "Python"
```python
print(await client.runs.get(thread["thread_id"], run["run_id"]))
```
=== "Javascript"
```js
console.log(await client.runs.get(thread["thread_id"], run["run_id"]));
```
=== "CURL"
```bash
curl --request GET \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/<RUN_ID>
```
Output:
{
"run_id": "1ef6a5f8-bd86-6763-bbd6-bff042db7b1b",
"thread_id": "7885f0cf-94ad-4040-91d7-73f7ba007c8a",
"assistant_id": "fe096781-5601-53d2-b2f6-0d3403f7e9ca",
"created_at": "2024-09-04T01:46:47.244887+00:00",
"updated_at": "2024-09-04T01:46:47.244887+00:00",
"metadata": {},
"status": "pending",
"kwargs": {
"input": {
"messages": [
{
"role": "user",
"content": "what's the weather in sf"
}
]
},
"config": {
"metadata": {
"created_by": "system"
},
"configurable": {
"run_id": "1ef6a5f8-bd86-6763-bbd6-bff042db7b1b",
"user_id": "",
"graph_id": "agent",
"thread_id": "7885f0cf-94ad-4040-91d7-73f7ba007c8a",
"assistant_id": "fe096781-5601-53d2-b2f6-0d3403f7e9ca",
"checkpoint_id": null
}
},
"webhook": null,
"temporary": false,
"stream_mode": [
"values"
],
"feedback_keys": null,
"interrupt_after": null,
"interrupt_before": null
},
"multitask_strategy": "reject"
}
Now we can join the run, wait for it to finish and check that status again:
=== "Python"
```python
await client.runs.join(thread["thread_id"], run["run_id"])
print(await client.runs.get(thread["thread_id"], run["run_id"]))
```
=== "Javascript"
```js
await client.runs.join(thread["thread_id"], run["run_id"]);
console.log(await client.runs.get(thread["thread_id"], run["run_id"]));
```
=== "CURL"
```bash
curl --request GET \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/<RUN_ID>/join &&
curl --request GET \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/<RUN_ID>
```
Output:
{
"run_id": "1ef6a5f8-bd86-6763-bbd6-bff042db7b1b",
"thread_id": "7885f0cf-94ad-4040-91d7-73f7ba007c8a",
"assistant_id": "fe096781-5601-53d2-b2f6-0d3403f7e9ca",
"created_at": "2024-09-04T01:46:47.244887+00:00",
"updated_at": "2024-09-04T01:46:47.244887+00:00",
"metadata": {},
"status": "success",
"kwargs": {
"input": {
"messages": [
{
"role": "user",
"content": "what's the weather in sf"
}
]
},
"config": {
"metadata": {
"created_by": "system"
},
"configurable": {
"run_id": "1ef6a5f8-bd86-6763-bbd6-bff042db7b1b",
"user_id": "",
"graph_id": "agent",
"thread_id": "7885f0cf-94ad-4040-91d7-73f7ba007c8a",
"assistant_id": "fe096781-5601-53d2-b2f6-0d3403f7e9ca",
"checkpoint_id": null
}
},
"webhook": null,
"temporary": false,
"stream_mode": [
"values"
],
"feedback_keys": null,
"interrupt_after": null,
"interrupt_before": null
},
"multitask_strategy": "reject"
}
Perfect! The run succeeded as we would expect. We can double check that the run worked as expected by printing out the final state:
=== "Python"
```python
final_result = await client.threads.get_state(thread["thread_id"])
print(final_result)
```
=== "Javascript"
```js
let finalResult = await client.threads.getState(thread["thread_id"]);
console.log(finalResult);
```
=== "CURL"
```bash
curl --request GET \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/state
```
Output:
{
"values": {
"messages": [
{
"content": "what's the weather in sf",
"additional_kwargs": {},
"response_metadata": {},
"type": "human",
"name": null,
"id": "beba31bf-320d-4125-9c37-cadf526ac47a",
"example": false
},
{
"content": [
{
"id": "toolu_01AaNPSPzqia21v7aAKwbKYm",
"input": {},
"name": "tavily_search_results_json",
"type": "tool_use",
"index": 0,
"partial_json": "{\"query\": \"weather in san francisco\"}"
}
],
"additional_kwargs": {},
"response_metadata": {
"stop_reason": "tool_use",
"stop_sequence": null
},
"type": "ai",
"name": null,
"id": "run-f220faf8-1d27-4f73-ad91-6bb3f47e8639",
"example": false,
"tool_calls": [
{
"name": "tavily_search_results_json",
"args": {
"query": "weather in san francisco"
},
"id": "toolu_01AaNPSPzqia21v7aAKwbKYm",
"type": "tool_call"
}
],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 273,
"output_tokens": 61,
"total_tokens": 334
}
},
{
"content": "[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{'location': {'name': 'San Francisco', 'region': 'California', 'country': 'United States of America', 'lat': 37.78, 'lon': -122.42, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1725052131, 'localtime': '2024-08-30 14:08'}, 'current': {'last_updated_epoch': 1725051600, 'last_updated': '2024-08-30 14:00', 'temp_c': 21.1, 'temp_f': 70.0, 'is_day': 1, 'condition': {'text': 'Partly cloudy', 'icon': '//cdn.weatherapi.com/weather/64x64/day/116.png', 'code': 1003}, 'wind_mph': 11.9, 'wind_kph': 19.1, 'wind_degree': 290, 'wind_dir': 'WNW', 'pressure_mb': 1018.0, 'pressure_in': 30.07, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 59, 'cloud': 25, 'feelslike_c': 21.1, 'feelslike_f': 70.0, 'windchill_c': 18.6, 'windchill_f': 65.5, 'heatindex_c': 18.6, 'heatindex_f': 65.5, 'dewpoint_c': 12.2, 'dewpoint_f': 54.0, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 5.0, 'gust_mph': 15.0, 'gust_kph': 24.2}}\"}]",
"additional_kwargs": {},
"response_metadata": {},
"type": "tool",
"name": "tavily_search_results_json",
"id": "686b2487-f332-4e58-9508-89b3a814cd81",
"tool_call_id": "toolu_01AaNPSPzqia21v7aAKwbKYm",
"artifact": {
"query": "weather in san francisco",
"follow_up_questions": null,
"answer": null,
"images": [],
"results": [
{
"title": "Weather in San Francisco",
"url": "https://www.weatherapi.com/",
"content": "{'location': {'name': 'San Francisco', 'region': 'California', 'country': 'United States of America', 'lat': 37.78, 'lon': -122.42, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1725052131, 'localtime': '2024-08-30 14:08'}, 'current': {'last_updated_epoch': 1725051600, 'last_updated': '2024-08-30 14:00', 'temp_c': 21.1, 'temp_f': 70.0, 'is_day': 1, 'condition': {'text': 'Partly cloudy', 'icon': '//cdn.weatherapi.com/weather/64x64/day/116.png', 'code': 1003}, 'wind_mph': 11.9, 'wind_kph': 19.1, 'wind_degree': 290, 'wind_dir': 'WNW', 'pressure_mb': 1018.0, 'pressure_in': 30.07, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 59, 'cloud': 25, 'feelslike_c': 21.1, 'feelslike_f': 70.0, 'windchill_c': 18.6, 'windchill_f': 65.5, 'heatindex_c': 18.6, 'heatindex_f': 65.5, 'dewpoint_c': 12.2, 'dewpoint_f': 54.0, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 5.0, 'gust_mph': 15.0, 'gust_kph': 24.2}}",
"score": 0.976148,
"raw_content": null
}
],
"response_time": 3.07
},
"status": "success"
},
{
"content": [
{
"text": "\n\nThe search results provide the current weather conditions in San Francisco. According to the data, as of 2:00 PM on August 30, 2024, the temperature in San Francisco is 70\u00b0F (21.1\u00b0C) with partly cloudy skies. The wind is blowing from the west-northwest at around 12 mph (19 km/h). The humidity is 59% and visibility is 9 miles (16 km). Overall, it looks like a nice late summer day in San Francisco with comfortable temperatures and partly sunny conditions.",
"type": "text",
"index": 0
}
],
"additional_kwargs": {},
"response_metadata": {
"stop_reason": "end_turn",
"stop_sequence": null
},
"type": "ai",
"name": null,
"id": "run-8fecc61d-3d9f-4e16-8e8a-92f702be498a",
"example": false,
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 837,
"output_tokens": 124,
"total_tokens": 961
}
}
]
},
"next": [],
"tasks": [],
"metadata": {
"step": 3,
"run_id": "1ef67140-eb23-684b-8253-91d4c90bb05e",
"source": "loop",
"writes": {
"agent": {
"messages": [
{
"id": "run-8fecc61d-3d9f-4e16-8e8a-92f702be498a",
"name": null,
"type": "ai",
"content": [
{
"text": "\n\nThe search results provide the current weather conditions in San Francisco. According to the data, as of 2:00 PM on August 30, 2024, the temperature in San Francisco is 70\u00b0F (21.1\u00b0C) with partly cloudy skies. The wind is blowing from the west-northwest at around 12 mph (19 km/h). The humidity is 59% and visibility is 9 miles (16 km). Overall, it looks like a nice late summer day in San Francisco with comfortable temperatures and partly sunny conditions.",
"type": "text",
"index": 0
}
],
"example": false,
"tool_calls": [],
"usage_metadata": {
"input_tokens": 837,
"total_tokens": 961,
"output_tokens": 124
},
"additional_kwargs": {},
"response_metadata": {
"stop_reason": "end_turn",
"stop_sequence": null
},
"invalid_tool_calls": []
}
]
}
},
"user_id": "",
"graph_id": "agent",
"thread_id": "5cb1e8a1-34b3-4a61-a34e-71a9799bd00d",
"created_by": "system",
"assistant_id": "fe096781-5601-53d2-b2f6-0d3403f7e9ca"
},
"created_at": "2024-08-30T21:09:00.079909+00:00",
"checkpoint_id": "1ef67141-3ca2-6fae-8003-fe96832e57d6",
"parent_checkpoint_id": "1ef67141-2129-6b37-8002-61fc3bf69cb5"
}
We can also just print the content of the last AIMessage:
=== "Python"
```python
print(final_result['values']['messages'][-1]['content'][0]['text'])
```
=== "Javascript"
```js
console.log(finalResult['values']['messages'][finalResult['values']['messages'].length-1]['content'][0]['text']);
```
=== "CURL"
```bash
curl --request GET \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/state | jq -r '.values.messages[-1].content.[0].text'
```
Output:
The search results provide the current weather conditions in San Francisco. According to the data, as of 2:00 PM on August 30, 2024, the temperature in San Francisco is 70°F (21.1°C) with partly cloudy skies. The wind is blowing from the west-northwest at around 12 mph (19 km/h). The humidity is 59% and visibility is 9 miles (16 km). Overall, it looks like a nice late summer day in San Francisco with comfortable temperatures and partly sunny conditions.
@@ -0,0 +1,36 @@
# Debug LangSmith traces
This guide explains how to open LangSmith traces in LangGraph Studio for interactive investigation and debugging.
## Open deployed threads
1. Open the LangSmith trace, selecting the root run.
2. Click "Run in Studio".
This will open LangGraph Studio connected to the associated LangGraph Platform deployment with the trace's parent thread selected.
## Testing local agents with remote traces
This section explains how to test a local agent against remote traces from LangSmith. This enables you to use production traces as input for local testing, allowing you to debug and verify agent modifications in your development environment.
### Requirements
- A LangSmith traced thread
- A locally running agent. See [here](../how-tos/studio/quick_start.md#local-development-server) for setup
instructions.
!!! info "Local agent requirements"
- langgraph>=0.3.18
- langgraph-api>=0.0.32
- Contains the same set of nodes present in the remote trace
### Cloning Thread
1. Open the LangSmith trace, selecting the root run.
2. Click the dropdown next to "Run in Studio".
3. Enter your local agent's URL.
4. Select "Clone thread locally".
5. If multiple graphs exist, select the target graph.
A new thread will be created in your local agent with the thread history inferred and copied from the remote thread, and you will be navigated to LangGraph Studio for your locally running application.
@@ -0,0 +1,84 @@
# Configurable Headers
LangGraph allows runtime configuration to modify agent behavior and permissions dynamically. When using the [LangGraph Platform](../quick_start.md), you can pass this configuration in the request body (`config`) or specific request headers. This enables adjustments based on user identity or other request data.
For privacy, control which headers are passed to the runtime configuration via the `http.configurable_headers` section in your `langgraph.json` file.
Here's how to customize the included and excluded headers:
```json
{
"http": {
"configurable_headers": {
"include": ["x-user-id", "x-organization-id", "my-prefix-*"],
"exclude": ["authorization", "x-api-key"]
}
}
}
```
The `include` and `exclude` lists accept exact header names or patterns using `*` to match any number of characters. For your security, no other regex patterns are supported.
## Using within your graph
You can access the included headers in your graph using the `config` argument of any node.
```python
def my_node(state, config):
organization_id = config["configurable"].get("x-organization-id")
...
```
Or by fetching from context (useful in tools and or within other nested functions).
```python
from langgraph.config import get_config
def search_everything(query: str):
organization_id = get_config()["configurable"].get("x-organization-id")
...
```
You can even use this to dynamically compile the graph.
```python
# my_graph.py.
import contextlib
@contextlib.asynccontextmanager
async def generate_agent(config):
organization_id = config["configurable"].get("x-organization-id")
if organization_id == "org1":
graph = ...
yield graph
else:
graph = ...
yield graph
```
```json
{
"graphs": {"agent": "my_grph.py:generate_agent"}
}
```
### Opt-out of configurable headers
If you'd like to opt-out of configurable headers, you can simply set a wildcard pattern in the `exclude` list:
```json
{
"http": {
"configurable_headers": {
"exclude": ["*"]
}
}
}
```
This will exclude all headers from being added to your run's configuration.
Note that exclusions take precedence over inclusions.
@@ -0,0 +1,327 @@
# Manage assistants
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`.
=== "Python"
```python
class ConfigSchema(TypedDict):
model_name: str
builder = StateGraph(AgentState, config_schema=ConfigSchema)
def call_model(state, config):
messages = state["messages"]
model_name = config.get('configurable', {}).get("model_name", "anthropic")
model = _get_model(model_name)
response = model.invoke(messages)
# We return a list, because this will get added to the existing list
return {"messages": [response]}
```
=== "Javascript"
```js
import { Annotation } from "@langchain/langgraph";
const ConfigSchema = Annotation.Root({
model_name: Annotation<string>,
system_prompt:
});
const builder = new StateGraph(AgentState, ConfigSchema)
function callModel(state: State, config: RunnableConfig) {
const messages = state.messages;
const modelName = config.configurable?.model_name ?? "anthropic";
const model = _getModel(modelName);
const response = model.invoke(messages);
// We return a list, because this will get added to the existing list
return { messages: [response] };
}
```
For more information on configurations, [see here](../../concepts/low_level.md#configuration).
## Create an assistant
### LangGraph SDK
To create an assistant, use the [LangGraph SDK](../../concepts/sdk.md) `create` method. See the [Python](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/#langgraph_sdk.client.AssistantsClient.create) and [JS](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#create) SDK reference docs for more information.
This example uses the same configuration schema as above, and creates an assistant with `model_name` set to `openai`.
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>)
openai_assistant = await client.assistants.create(
# "agent" is the name of a graph we deployed
"agent", config={"configurable": {"model_name": "openai"}}, name="Open AI Assistant"
)
print(openai_assistant)
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
const openAIAssistant = await client.assistants.create({
graphId: 'agent',
name: "Open AI Assistant",
config: { "configurable": { "model_name": "openai" } },
});
console.log(openAIAssistant);
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/assistants \
--header 'Content-Type: application/json' \
--data '{"graph_id":"agent", "config":{"configurable":{"model_name":"openai"}}, "name": "Open AI Assistant"}'
```
Output:
{
"assistant_id": "62e209ca-9154-432a-b9e9-2d75c7a9219b",
"graph_id": "agent",
"name": "Open AI Assistant"
"config": {
"configurable": {
"model_name": "openai"
}
},
"metadata": {}
"created_at": "2024-08-31T03:09:10.230718+00:00",
"updated_at": "2024-08-31T03:09:10.230718+00:00",
}
### LangGraph Platform UI
You can also create assistants from the LangGraph Platform UI.
Inside your deployment, select the "Assistants" tab. This will load a table of all of the assistants in your deployment, across all graphs.
To create a new assistant, select the "+ New assistant" button. This will open a form where you can specify the graph this assistant is for, as well as provide a name, description, and the desired configuration for the assistant based on the configuration schema for that graph.
To confirm, click "Create assistant". This will take you to [LangGraph Studio](../../concepts/langgraph_studio.md) where you can test the assistant. If you go back to the "Assistants" tab in the deployment, you will see the newly created assistant in the table.
## Use an assistant
### LangGraph SDK
We have now created an assistant called "Open AI Assistant" that has `model_name` defined as `openai`. We can now use this assistant with this configuration:
=== "Python"
```python
thread = await client.threads.create()
input = {"messages": [{"role": "user", "content": "who made you?"}]}
async for event in client.runs.stream(
thread["thread_id"],
# this is where we specify the assistant id to use
openai_assistant["assistant_id"],
input=input,
stream_mode="updates",
):
print(f"Receiving event of type: {event.event}")
print(event.data)
print("\n\n")
```
=== "Javascript"
```js
const thread = await client.threads.create();
const input = { "messages": [{ "role": "user", "content": "who made you?" }] };
const streamResponse = client.runs.stream(
thread["thread_id"],
// this is where we specify the assistant id to use
openAIAssistant["assistant_id"],
{
input,
streamMode: "updates"
}
);
for await (const event of streamResponse) {
console.log(`Receiving event of type: ${event.event}`);
console.log(event.data);
console.log("\n\n");
}
```
=== "CURL"
```bash
thread_id=$(curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}' | jq -r '.thread_id') && \
curl --request POST \
--url "<DEPLOYMENT_URL>/threads/${thread_id}/runs/stream" \
--header 'Content-Type: application/json' \
--data '{
"assistant_id": <OPENAI_ASSISTANT_ID>,
"input": {
"messages": [
{
"role": "user",
"content": "who made you?"
}
]
},
"stream_mode": [
"updates"
]
}' | \
sed 's/\r$//' | \
awk '
/^event:/ {
if (data_content != "") {
print data_content "\n"
}
sub(/^event: /, "Receiving event of type: ", $0)
printf "%s...\n", $0
data_content = ""
}
/^data:/ {
sub(/^data: /, "", $0)
data_content = $0
}
END {
if (data_content != "") {
print data_content "\n\n"
}
}
'
```
Output:
Receiving event of type: metadata
{'run_id': '1ef6746e-5893-67b1-978a-0f1cd4060e16'}
Receiving event of type: updates
{'agent': {'messages': [{'content': 'I was created by OpenAI, a research organization focused on developing and advancing artificial intelligence technology.', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-e1a6b25c-8416-41f2-9981-f9cfe043f414', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
### LangGraph Platform UI
Inside your deployment, select the "Assistants" tab. For the assistant you would like to use, click the "Studio" button. This will open LangGraph Studio with the selected assistant. When you submit an input (either in Graph or Chat mode), the selected assistant and its configuration will be used.
## Create a new version for your assistant
### LangGraph SDK
To edit the assistant, use the `update` method. This will create a new version of the assistant with the provided edits. See the [Python](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/#langgraph_sdk.client.AssistantsClient.update) and [JS](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#update) SDK reference docs for more information.
!!! note "Note"
You must pass in the ENTIRE config (and metadata if you are using it). The update endpoint creates new versions completely from scratch and does not rely on previous versions.
For example, to update your assistant's system prompt:
=== "Python"
```python
openai_assistant_v2 = await client.assistants.update(
openai_assistant["assistant_id"],
config={
"configurable": {
"model_name": "openai",
"system_prompt": "You are an unhelpful assistant!",
}
},
)
```
=== "Javascript"
```js
const openaiAssistantV2 = await client.assistants.update(
openai_assistant["assistant_id"],
{
config: {
configurable: {
model_name: 'openai',
system_prompt: 'You are an unhelpful assistant!',
},
},
});
```
=== "CURL"
```bash
curl --request PATCH \
--url <DEPOLYMENT_URL>/assistants/<ASSISTANT_ID> \
--header 'Content-Type: application/json' \
--data '{
"config": {"model_name": "openai", "system_prompt": "You are an unhelpful assistant!"}
}'
```
This will create a new version of the assistant with the updated parameters and set this as the active version of your assistant. If you now run your graph and pass in this assistant id, it will use this latest version.
### LangGraph Platform UI
You can also edit assistants from the LangGraph Platform UI.
Inside your deployment, select the "Assistants" tab. This will load a table of all of the assistants in your deployment, across all graphs.
To edit an existing assistant, select the "Edit" button for the specified assistant. This will open a form where you can edit the assistant's name, description, and configuration.
Additionally, if using LangGraph Studio, you can edit the assistants and create new versions via the "Manage Assistants" button.
## Use a previous assistant version
### LangGraph SDK
You can also change the active version of your assistant. To do so, use the `setLatest` method.
In the example above, to rollback to the first version of the assistant:
=== "Python"
```python
await client.assistants.set_latest(openai_assistant['assistant_id'], 1)
```
=== "Javascript"
```js
await client.assistants.setLatest(openaiAssistant['assistant_id'], 1);
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/assistants/<ASSISTANT_ID>/latest \
--header 'Content-Type: application/json' \
--data '{
"version": 1
}'
```
If you now run your graph and pass in this assistant id, it will use the first version of the assistant.
### LangGraph Platform UI
If using LangGraph Studio, to set the active version of your assistant, click the "Manage Assistants" button and locate the assistant you would like to use. Select the assistant and the version, and then click the "Active" toggle. This will update the assistant to make the selected version active.
!!! warning "Deleting Assistants"
Deleting as assistant will delete ALL of its versions. There is currently no way to delete a single version, but by pointing your assistant to the correct version you can skip any versions that you don't wish to use.
+184
View File
@@ -0,0 +1,184 @@
# Use cron jobs
Sometimes you don't want to run your graph based on user interaction, but rather you would like to schedule your graph to run on a schedule - for example if you wish for your graph to compose and send out a weekly email of to-dos for your team. LangGraph Platform allows you to do this without having to write your own script by using the `Crons` client. To schedule a graph job, you need to pass a [cron expression](https://crontab.cronhub.io/) to inform the client when you want to run the graph. `Cron` jobs are run in the background and do not interfere with normal invocations of the graph.
## Setup
First, let's set up our SDK client, assistant, and thread:
=== "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 thread
thread = await client.threads.create()
print(thread)
```
=== "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 thread
const thread = await client.threads.create();
console.log(thread);
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/assistants/search \
--header 'Content-Type: application/json' \
--data '{
"limit": 10,
"offset": 0
}' | jq -c 'map(select(.config == null or .config == {})) | .[0].graph_id' && \
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
Output:
{
'thread_id': '9dde5490-2b67-47c8-aa14-4bfec88af217',
'created_at': '2024-08-30T23:07:38.242730+00:00',
'updated_at': '2024-08-30T23:07:38.242730+00:00',
'metadata': {},
'status': 'idle',
'config': {},
'values': None
}
## Cron job on a thread
To create a cron job associated with a specific thread, you can write:
=== "Python"
```python
# This schedules a job to run at 15:27 (3:27PM) every day
cron_job = await client.crons.create_for_thread(
thread["thread_id"],
assistant_id,
schedule="27 15 * * *",
input={"messages": [{"role": "user", "content": "What time is it?"}]},
)
```
=== "Javascript"
```js
// This schedules a job to run at 15:27 (3:27PM) every day
const cronJob = await client.crons.create_for_thread(
thread["thread_id"],
assistantId,
{
schedule: "27 15 * * *",
input: { messages: [{ role: "user", content: "What time is it?" }] }
}
);
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/crons \
--header 'Content-Type: application/json' \
--data '{
"assistant_id": <ASSISTANT_ID>,
}'
```
Note that it is **very** important to delete `Cron` jobs that are no longer useful. Otherwise you could rack up unwanted API charges to the LLM! You can delete a `Cron` job using the following code:
=== "Python"
```python
await client.crons.delete(cron_job["cron_id"])
```
=== "Javascript"
```js
await client.crons.delete(cronJob["cron_id"]);
```
=== "CURL"
```bash
curl --request DELETE \
--url <DEPLOYMENT_URL>/runs/crons/<CRON_ID>
```
## Cron job stateless
You can also create stateless cron jobs by using the following code:
=== "Python"
```python
# This schedules a job to run at 15:27 (3:27PM) every day
cron_job_stateless = await client.crons.create(
assistant_id,
schedule="27 15 * * *",
input={"messages": [{"role": "user", "content": "What time is it?"}]},
)
```
=== "Javascript"
```js
// This schedules a job to run at 15:27 (3:27PM) every day
const cronJobStateless = await client.crons.create(
assistantId,
{
schedule: "27 15 * * *",
input: { messages: [{ role: "user", content: "What time is it?" }] }
}
);
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/runs/crons \
--header 'Content-Type: application/json' \
--data '{
"assistant_id": <ASSISTANT_ID>,
}'
```
Again, remember to delete your job once you are done with it!
=== "Python"
```python
await client.crons.delete(cron_job_stateless["cron_id"])
```
=== "Javascript"
```js
await client.crons.delete(cronJobStateless["cron_id"]);
```
=== "CURL"
```bash
curl --request DELETE \
--url <DEPLOYMENT_URL>/runs/crons/<CRON_ID>
```
@@ -0,0 +1,12 @@
# Add node to dataset
This guide shows how to add examples to [LangSmith datasets](https://docs.smith.langchain.com/evaluation/how_to_guides#dataset-management) from nodes in the thread log. This is useful to evaluate individual steps of the agent.
1. Select a thread.
2. Click on the `Add to Dataset` button.
3. Select nodes whose input/output you want to add to a dataset.
4. For each selected node, select the target dataset to create the example in. By default a dataset for the specific assistant and node will be selected. If this dataset does not yet exist, it will be created.
5. Edit the example's input/output as needed before adding it to the dataset.
6. Select "Add to dataset" at the bottom of the page to add all selected nodes to their respective datasets.
See [Evaluating intermediate steps](https://docs.smith.langchain.com/evaluation/how_to_guides/langgraph#evaluating-intermediate-steps) for more details on how to evaluate intermediate steps.
@@ -0,0 +1,255 @@
# Enqueue
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../../concepts/double_texting.md).
The guide covers the `enqueue` option for double texting, which adds the interruptions to a queue and executes them in the order they are received by the client. Below is a quick example of using the `enqueue` option.
## Setup
First, we will define a quick helper function for printing out JS and CURL model outputs (you can skip this if using Python):
=== "Javascript"
```js
function prettyPrint(m) {
const padded = " " + m['type'] + " ";
const sepLen = Math.floor((80 - padded.length) / 2);
const sep = "=".repeat(sepLen);
const secondSep = sep + (padded.length % 2 ? "=" : "");
console.log(`${sep}${padded}${secondSep}`);
console.log("\n\n");
console.log(m.content);
}
```
=== "CURL"
```bash
# PLACE THIS IN A FILE CALLED pretty_print.sh
pretty_print() {
local type="$1"
local content="$2"
local padded=" $type "
local total_width=80
local sep_len=$(( (total_width - ${#padded}) / 2 ))
local sep=$(printf '=%.0s' $(eval "echo {1.."${sep_len}"}"))
local second_sep=$sep
if (( (total_width - ${#padded}) % 2 )); then
second_sep="${second_sep}="
fi
echo "${sep}${padded}${second_sep}"
echo
echo "$content"
}
```
Then, let's import our required packages and instantiate our client, assistant, and thread.
=== "Python"
```python
import asyncio
import httpx
from langchain_core.messages import convert_to_messages
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
thread = await client.threads.create()
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
// Using the graph deployed with the name "agent"
const assistantId = "agent";
const thread = await client.threads.create();
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
## Create runs
Now let's start two runs, with the second interrupting the first one with a multitask strategy of "enqueue":
=== "Python"
```python
first_run = await client.runs.create(
thread["thread_id"],
assistant_id,
input={"messages": [{"role": "user", "content": "what's the weather in sf?"}]},
)
second_run = await client.runs.create(
thread["thread_id"],
assistant_id,
input={"messages": [{"role": "user", "content": "what's the weather in nyc?"}]},
multitask_strategy="enqueue",
)
```
=== "Javascript"
```js
const firstRun = await client.runs.create(
thread["thread_id"],
assistantId,
input={"messages": [{"role": "user", "content": "what's the weather in sf?"}]},
)
const secondRun = await client.runs.create(
thread["thread_id"],
assistantId,
input={"messages": [{"role": "user", "content": "what's the weather in nyc?"}]},
multitask_strategy="enqueue",
)
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOY<ENT_URL>>/threads/<THREAD_ID>/runs \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what\'s the weather in sf?\"}]},
}" && curl --request POST \
--url <DEPLOY<ENT_URL>>/threads/<THREAD_ID>/runs \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what\'s the weather in nyc?\"}]},
\"multitask_strategy\": \"enqueue\"
}"
```
## View run results
Verify that the thread has data from both runs:
=== "Python"
```python
# wait until the second run completes
await client.runs.join(thread["thread_id"], second_run["run_id"])
state = await client.threads.get_state(thread["thread_id"])
for m in convert_to_messages(state["values"]["messages"]):
m.pretty_print()
```
=== "Javascript"
```js
await client.runs.join(thread["thread_id"], secondRun["run_id"]);
const state = await client.threads.getState(thread["thread_id"]);
for (const m of state["values"]["messages"]) {
prettyPrint(m);
}
```
=== "CURL"
```bash
source pretty_print.sh && curl --request GET \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/<RUN_ID>/join && \
curl --request GET --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/state | \
jq -c '.values.messages[]' | while read -r element; do
type=$(echo "$element" | jq -r '.type')
content=$(echo "$element" | jq -r '.content | if type == "array" then tostring else . end')
pretty_print "$type" "$content"
done
```
Output:
================================ Human Message =================================
what's the weather in sf?
================================== Ai Message ==================================
[{'id': 'toolu_01Dez1sJre4oA2Y7NsKJV6VT', 'input': {'query': 'weather in san francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
Tool Calls:
tavily_search_results_json (toolu_01Dez1sJre4oA2Y7NsKJV6VT)
Call ID: toolu_01Dez1sJre4oA2Y7NsKJV6VT
Args:
query: weather in san francisco
================================= Tool Message =================================
Name: tavily_search_results_json
[{"url": "https://www.accuweather.com/en/us/san-francisco/94103/weather-forecast/347629", "content": "Get the current and future weather conditions for San Francisco, CA, including temperature, precipitation, wind, air quality and more. See the hourly and 10-day outlook, radar maps, alerts and allergy information."}]
================================== Ai Message ==================================
According to AccuWeather, the current weather conditions in San Francisco are:
Temperature: 57°F (14°C)
Conditions: Mostly Sunny
Wind: WSW 10 mph
Humidity: 72%
The forecast for the next few days shows partly sunny skies with highs in the upper 50s to mid 60s F (14-18°C) and lows in the upper 40s to low 50s F (9-11°C). Typical mild, dry weather for San Francisco this time of year.
Some key details from the AccuWeather forecast:
Today: Mostly sunny, high of 62°F (17°C)
Tonight: Partly cloudy, low of 49°F (9°C)
Tomorrow: Partly sunny, high of 59°F (15°C)
Saturday: Mostly sunny, high of 64°F (18°C)
Sunday: Partly sunny, high of 61°F (16°C)
So in summary, expect seasonable spring weather in San Francisco over the next several days, with a mix of sun and clouds and temperatures ranging from the upper 40s at night to the low 60s during the days. Typical dry conditions with no rain in the forecast.
================================ Human Message =================================
what's the weather in nyc?
================================== Ai Message ==================================
[{'text': 'Here are the current weather conditions and forecast for New York City:', 'type': 'text'}, {'id': 'toolu_01FFft5Sx9oS6AdVJuRWWcGp', 'input': {'query': 'weather in new york city'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
Tool Calls:
tavily_search_results_json (toolu_01FFft5Sx9oS6AdVJuRWWcGp)
Call ID: toolu_01FFft5Sx9oS6AdVJuRWWcGp
Args:
query: weather in new york city
================================= Tool Message =================================
Name: tavily_search_results_json
[{"url": "https://www.weatherapi.com/", "content": "{'location': {'name': 'New York', 'region': 'New York', 'country': 'United States of America', 'lat': 40.71, 'lon': -74.01, 'tz_id': 'America/New_York', 'localtime_epoch': 1718734479, 'localtime': '2024-06-18 14:14'}, 'current': {'last_updated_epoch': 1718733600, 'last_updated': '2024-06-18 14:00', 'temp_c': 29.4, 'temp_f': 84.9, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 2.2, 'wind_kph': 3.6, 'wind_degree': 158, 'wind_dir': 'SSE', 'pressure_mb': 1025.0, 'pressure_in': 30.26, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 63, 'cloud': 0, 'feelslike_c': 31.3, 'feelslike_f': 88.3, 'windchill_c': 28.3, 'windchill_f': 82.9, 'heatindex_c': 29.6, 'heatindex_f': 85.3, 'dewpoint_c': 18.4, 'dewpoint_f': 65.2, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 7.0, 'gust_mph': 16.5, 'gust_kph': 26.5}}"}]
================================== Ai Message ==================================
According to the weather data from WeatherAPI:
Current Conditions in New York City (as of 2:00 PM local time):
- Temperature: 85°F (29°C)
- Conditions: Sunny
- Wind: 2 mph (4 km/h) from the SSE
- Humidity: 63%
- Heat Index: 85°F (30°C)
The forecast shows sunny and warm conditions persisting over the next few days:
Today: Sunny, high of 85°F (29°C)
Tonight: Clear, low of 68°F (20°C)
Tomorrow: Sunny, high of 88°F (31°C)
Thursday: Mostly sunny, high of 90°F (32°C)
Friday: Partly cloudy, high of 87°F (31°C)
So New York City is experiencing beautiful sunny weather with seasonably warm temperatures in the mid-to-upper 80s Fahrenheit (around 30°C). Humidity is moderate in the 60% range. Overall, ideal late spring/early summer conditions for being outdoors in the city over the next several days.
@@ -0,0 +1,522 @@
# How to implement Generative User Interfaces with LangGraph
!!! info "Prerequisites"
- [LangGraph Platform](../../concepts/langgraph_platform.md)
- [LangGraph Server](../../concepts/langgraph_server.md)
- [`useStream()` React Hook](./use_stream_react.md)
Generative user interfaces (Generative UI) allows agents to go beyond text and generate rich user interfaces. This enables creating more interactive and context-aware applications where the UI adapts based on the conversation flow and AI responses.
![Generative UI Sample](./img/generative_ui_sample.jpg)
LangGraph Platform supports colocating your React components with your graph code. This allows you to focus on building specific UI components for your graph while easily plugging into existing chat interfaces such as [Agent Chat](https://agentchat.vercel.app) and loading the code only when actually needed.
## Tutorial
### 1. Define and configure UI components
First, create your first UI component. For each component you need to provide an unique identifier that will be used to reference the component in your graph code.
```tsx title="src/agent/ui.tsx"
const WeatherComponent = (props: { city: string }) => {
return <div>Weather for {props.city}</div>;
};
export default {
weather: WeatherComponent,
};
```
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"
}
}
```
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.
LangGraph Platform will automatically bundle your UI components code and styles and serve them as external assets that can be loaded by the `LoadExternalComponent` component. Some dependencies such as `react` and `react-dom` will be automatically excluded from the bundle.
CSS and Tailwind 4.x is also supported out of the box, so you can freely use Tailwind classes as well as `shadcn/ui` in your UI components.
=== "`src/agent/ui.tsx`"
```tsx
import "./styles.css";
const WeatherComponent = (props: { city: string }) => {
return <div className="bg-red-500">Weather for {props.city}</div>;
};
export default {
weather: WeatherComponent,
};
```
=== "`src/agent/styles.css`"
```css
@import "tailwindcss";
```
### 2. Send the UI components in your graph
=== "Python"
```python title="src/agent.py"
import uuid
from typing import Annotated, Sequence, TypedDict
from langchain_core.messages import AIMessage, BaseMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph
from langgraph.graph.message import add_messages
from langgraph.graph.ui import AnyUIMessage, ui_message_reducer, push_ui_message
class AgentState(TypedDict): # noqa: D101
messages: Annotated[Sequence[BaseMessage], add_messages]
ui: Annotated[Sequence[AnyUIMessage], ui_message_reducer]
async def weather(state: AgentState):
class WeatherOutput(TypedDict):
city: str
weather: WeatherOutput = (
await ChatOpenAI(model="gpt-4o-mini")
.with_structured_output(WeatherOutput)
.with_config({"tags": ["nostream"]})
.ainvoke(state["messages"])
)
message = AIMessage(
id=str(uuid.uuid4()),
content=f"Here's the weather for {weather['city']}",
)
# Emit UI elements associated with the message
push_ui_message("weather", weather, message=message)
return {"messages": [message]}
workflow = StateGraph(AgentState)
workflow.add_node(weather)
workflow.add_edge("__start__", "weather")
graph = workflow.compile()
```
=== "JS"
Use the `typedUi` utility to emit UI elements from your agent nodes:
```typescript title="src/agent/index.ts"
import {
typedUi,
uiMessageReducer,
} from "@langchain/langgraph-sdk/react-ui/server";
import { ChatOpenAI } from "@langchain/openai";
import { v4 as uuidv4 } from "uuid";
import { z } from "zod";
import type ComponentMap from "./ui.js";
import {
Annotation,
MessagesAnnotation,
StateGraph,
type LangGraphRunnableConfig,
} from "@langchain/langgraph";
const AgentState = Annotation.Root({
...MessagesAnnotation.spec,
ui: Annotation({ reducer: uiMessageReducer, default: () => [] }),
});
export const graph = new StateGraph(AgentState)
.addNode("weather", async (state, config) => {
// Provide the type of the component map to ensure
// type safety of `ui.push()` calls as well as
// pushing the messages to the `ui` and sending a custom event as well.
const ui = typedUi<typeof ComponentMap>(config);
const weather = await new ChatOpenAI({ model: "gpt-4o-mini" })
.withStructuredOutput(z.object({ city: z.string() }))
.withConfig({ tags: ["nostream"] })
.invoke(state.messages);
const response = {
id: uuidv4(),
type: "ai",
content: `Here's the weather for ${weather.city}`,
};
// Emit UI elements associated with the AI message
ui.push({ name: "weather", props: weather }, { message: response });
return { messages: [response] };
})
.addEdge("__start__", "weather")
.compile();
```
### 3. Handle UI elements in your React application
On the client side, you can use `useStream()` and `LoadExternalComponent` to display the UI elements.
```tsx title="src/app/page.tsx"
"use client";
import { useStream } from "@langchain/langgraph-sdk/react";
import { LoadExternalComponent } from "@langchain/langgraph-sdk/react-ui";
export default function Page() {
const { thread, values } = useStream({
apiUrl: "http://localhost:2024",
assistantId: "agent",
});
return (
<div>
{thread.messages.map((message) => (
<div key={message.id}>
{message.content}
{values.ui
?.filter((ui) => ui.metadata?.message_id === message.id)
.map((ui) => (
<LoadExternalComponent key={ui.id} stream={thread} message={ui} />
))}
</div>
))}
</div>
);
}
```
Behind the scenes, `LoadExternalComponent` will fetch the JS and CSS for the UI components from LangGraph Platform and render them in a shadow DOM, thus ensuring style isolation from the rest of your application.
## How-to guides
### Provide custom components on the client side
If you already have the components loaded in your client application, you can provide a map of such components to be rendered directly without fetching the UI code from LangGraph Platform.
```tsx
const clientComponents = {
weather: WeatherComponent,
};
<LoadExternalComponent
stream={thread}
message={ui}
components={clientComponents}
/>;
```
### Show loading UI when components are loading
You can provide a fallback UI to be rendered when the components are loading.
```tsx
<LoadExternalComponent
stream={thread}
message={ui}
fallback={<div>Loading...</div>}
/>
```
### Customise the namespace of UI components.
By default `LoadExternalComponent` will use the `assistantId` from `useStream()` hook to fetch the code for UI components. You can customise this by providing a `namespace` prop to the `LoadExternalComponent` component.
=== "`src/app/page.tsx`"
```tsx
<LoadExternalComponent
stream={thread}
message={ui}
namespace="custom-namespace"
/>
```
=== "`langgraph.json`"
```json
{
"ui": {
"custom-namespace": "./src/agent/ui.tsx"
}
}
```
### Access and interact with the thread state from the UI component
You can access the thread state inside the UI component by using the `useStreamContext` hook.
```tsx
import { useStreamContext } from "@langchain/langgraph-sdk/react-ui";
const WeatherComponent = (props: { city: string }) => {
const { thread, submit } = useStreamContext();
return (
<>
<div>Weather for {props.city}</div>
<button
onClick={() => {
const newMessage = {
type: "human",
content: `What's the weather in ${props.city}?`,
};
submit({ messages: [newMessage] });
}}
>
Retry
</button>
</>
);
};
```
### Pass additional context to the client components
You can pass additional context to the client components by providing a `meta` prop to the `LoadExternalComponent` component.
```tsx
<LoadExternalComponent stream={thread} message={ui} meta={{ userId: "123" }} />
```
Then, you can access the `meta` prop in the UI component by using the `useStreamContext` hook.
```tsx
import { useStreamContext } from "@langchain/langgraph-sdk/react-ui";
const WeatherComponent = (props: { city: string }) => {
const { meta } = useStreamContext<
{ city: string },
{ MetaType: { userId?: string } }
>();
return (
<div>
Weather for {props.city} (user: {meta?.userId})
</div>
);
};
```
### Streaming UI messages from the server
You can stream UI messages before the node execution is finished by using the `onCustomEvent` callback of the `useStream()` hook. This is especially useful when updating the UI component as the LLM is generating the response.
```tsx
import { uiMessageReducer } from "@langchain/langgraph-sdk/react-ui";
const { thread, submit } = useStream({
apiUrl: "http://localhost:2024",
assistantId: "agent",
onCustomEvent: (event, options) => {
options.mutate((prev) => {
const ui = uiMessageReducer(prev.ui ?? [], event);
return { ...prev, ui };
});
},
});
```
Then you can push updates to the UI component by calling `ui.push()` / `push_ui_message()` with the same ID as the UI message you wish to update.
=== "Python"
```python
from typing import Annotated, Sequence, TypedDict
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import AIMessage, AIMessageChunk, BaseMessage
from langgraph.graph import StateGraph
from langgraph.graph.message import add_messages
from langgraph.graph.ui import AnyUIMessage, push_ui_message, ui_message_reducer
class AgentState(TypedDict): # noqa: D101
messages: Annotated[Sequence[BaseMessage], add_messages]
ui: Annotated[Sequence[AnyUIMessage], ui_message_reducer]
class CreateTextDocument(TypedDict):
"""Prepare a document heading for the user."""
title: str
async def writer_node(state: AgentState):
model = ChatAnthropic(model="claude-3-5-sonnet-latest")
message: AIMessage = await model.bind_tools(
tools=[CreateTextDocument],
tool_choice={"type": "tool", "name": "CreateTextDocument"},
).ainvoke(state["messages"])
tool_call = next(
(x["args"] for x in message.tool_calls if x["name"] == "CreateTextDocument"),
None,
)
if tool_call:
ui_message = push_ui_message("writer", tool_call, message=message)
ui_message_id = ui_message["id"]
# We're already streaming the LLM response to the client through UI messages
# so we don't need to stream it again to the `messages` stream mode.
content_stream = model.with_config({"tags": ["nostream"]}).astream(
f"Create a document with the title: {tool_call['title']}"
)
content: AIMessageChunk | None = None
async for chunk in content_stream:
content = content + chunk if content else chunk
push_ui_message(
"writer",
{"content": content.text()},
id=ui_message_id,
message=message,
# Use `merge=rue` to merge props with the existing UI message
merge=True,
)
return {"messages": [message]}
```
=== "JS"
```tsx
import {
Annotation,
MessagesAnnotation,
type LangGraphRunnableConfig,
} from "@langchain/langgraph";
import { z } from "zod";
import { ChatAnthropic } from "@langchain/anthropic";
import {
typedUi,
uiMessageReducer,
} from "@langchain/langgraph-sdk/react-ui/server";
import type { AIMessageChunk } from "@langchain/core/messages";
import type ComponentMap from "./ui";
const AgentState = Annotation.Root({
...MessagesAnnotation.spec,
ui: Annotation({ reducer: uiMessageReducer, default: () => [] }),
});
async function writerNode(
state: typeof AgentState.State,
config: LangGraphRunnableConfig
): Promise<typeof AgentState.Update> {
const ui = typedUi<typeof ComponentMap>(config);
const model = new ChatAnthropic({ model: "claude-3-5-sonnet-latest" });
const message = await model
.bindTools(
[
{
name: "create_text_document",
description: "Prepare a document heading for the user.",
schema: z.object({ title: z.string() }),
},
],
{ tool_choice: { type: "tool", name: "create_text_document" } }
)
.invoke(state.messages);
type ToolCall = { name: "create_text_document"; args: { title: string } };
const toolCall = message.tool_calls?.find(
(tool): tool is ToolCall => tool.name === "create_text_document"
);
if (toolCall) {
const { id, name } = ui.push(
{ name: "writer", props: { title: toolCall.args.title } },
{ message }
);
const contentStream = await model
// We're already streaming the LLM response to the client through UI messages
// so we don't need to stream it again to the `messages` stream mode.
.withConfig({ tags: ["nostream"] })
.stream(`Create a short poem with the topic: ${message.text}`);
let content: AIMessageChunk | undefined;
for await (const chunk of contentStream) {
content = content?.concat(chunk) ?? chunk;
ui.push(
{ id, name, props: { content: content?.text } },
// Use `merge: true` to merge props with the existing UI message
{ message, merge: true }
);
}
}
return { messages: [message] };
}
```
=== "`ui.tsx`"
```tsx
function WriterComponent(props: { title: string; content?: string }) {
return (
<article>
<h2>{props.title}</h2>
<p style={{ whiteSpace: "pre-wrap" }}>{props.content}</p>
</article>
);
}
export default {
weather: WriterComponent,
};
```
### Remove UI messages from state
Similar to how messages can be removed from the state by appending a RemoveMessage you can remove an UI message from the state by calling `remove_ui_message` / `ui.delete` with the ID of the UI message.
=== "Python"
```python
from langgraph.graph.ui import push_ui_message, delete_ui_message
# push message
message = push_ui_message("weather", {"city": "London"})
# remove said message
delete_ui_message(message["id"])
```
=== "JS"
```tsx
// push message
const message = ui.push({ name: "weather", props: { city: "London" } });
// remove said message
ui.delete(message.id);
```
## Learn more
- [JS/TS SDK Reference](../reference/sdk/js_ts_sdk_ref.md)
@@ -0,0 +1,184 @@
# Breakpoints
[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.
## Set breakpoints
=== "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>
}"
```
!!! tip
This example shows how to add **static** breakpoints. See [this guide](../../how-tos/human_in_the_loop/breakpoints.ipynb) for more options for how to add breakpoints.
=== "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
- [**LangGraph breakpoints guide**](../../how-tos/human_in_the_loop/breakpoints.ipynb): learn more about adding breakpoints in LangGraph.
@@ -0,0 +1,549 @@
# How to review tool calls
!!! tip "Prerequisites"
This guide assumes familiarity with the following concepts:
* [Tool calling](https://python.langchain.com/docs/concepts/tool_calling/)
* [Human-in-the-loop](../../concepts/human_in_the_loop.md)
* [LangGraph Glossary](../../concepts/low_level.md)
Human-in-the-loop (HIL) interactions are crucial for [agentic systems](../../concepts/agentic_concepts.md). A common pattern is to add some human in the loop step after certain tool calls. These tool calls often lead to either a function call or saving of some information. Examples include:
- A tool call to execute SQL, which will then be run by the tool
- A tool call to generate a summary, which will then be saved to the State of the graph
Note that using tool calls is common **whether actually calling tools or not**.
There are typically a few different interactions you may want to do here:
1. Approve the tool call and continue
2. Modify the tool call manually and then continue
3. Give natural language feedback, and then pass that back to the agent
We can implement these in LangGraph using the [`interrupt()`][langgraph.types.interrupt] function. `interrupt` allows us to stop graph execution to collect input from a user and continue execution with collected input:
```python
def human_review_node(state) -> Command[Literal["call_llm", "run_tool"]]:
# this is the value we'll be providing via Command(resume=<human_review>)
human_review = interrupt(
{
"question": "Is this correct?",
# Surface tool calls for review
"tool_call": tool_call
}
)
review_action, review_data = human_review
# Approve the tool call and continue
if review_action == "continue":
return Command(goto="run_tool")
# Modify the tool call manually and then continue
elif review_action == "update":
...
updated_msg = get_updated_msg(review_data)
return Command(goto="run_tool", update={"messages": [updated_message]})
# Give natural language feedback, and then pass that back to the agent
elif review_action == "feedback":
...
feedback_msg = get_feedback_msg(review_data)
return Command(goto="call_llm", update={"messages": [feedback_msg]})
```
## Setup
We are not going to show the full code for the graph we are hosting, but you can see it [here](../../how-tos/human_in_the_loop/review-tool-calls.ipynb). Once this graph is hosted, we are ready to invoke it and wait for user input.
### SDK initialization
First, we need to setup our client so that we can communicate with our hosted graph:
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
thread = await client.threads.create()
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
// Using the graph deployed with the name "agent"
const assistantId = "agent";
const thread = await client.threads.create();
```
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
## Example of approving tool
First, let's run the agent with an input that requires tool calls with approval:
=== "Python"
```python
input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]}
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
input=input,
stream_mode="updates",
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "Javascript"
```js
const input = { "messages": [{ "role": "user", "content": "what's the weather in sf?" }] };
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
input: input,
streamMode: "updates"
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf?\"}]},
\"stream_mode\": [
\"updates\"
]
}"
```
Output:
{'call_llm': {'messages': [{'content': [{'text': "I'll help you check the weather in San Francisco.", 'type': 'text'}, {'id': 'toolu_01142G3woscA8JjFTLdqymtn', 'input': {'city': 'San Francisco'}, 'name': 'weather_search', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {'id': 'msg_01Tdfufy4nZYXMbVZvgyNbhc', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 379, 'output_tokens': 66}, 'model_name': 'claude-3-5-sonnet-20241022'}, 'type': 'ai', 'name': None, 'id': 'run-a33434b2-f5ca-40c6-98e2-6288d349d4ce-0', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco'}, 'id': 'toolu_01142G3woscA8JjFTLdqymtn', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 379, 'output_tokens': 66, 'total_tokens': 445, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}}}]}}
{'__interrupt__': [{'value': {'question': 'Is this correct?', 'tool_call': {'name': 'weather_search', 'args': {'city': 'San Francisco'}, 'id': 'toolu_01142G3woscA8JjFTLdqymtn', 'type': 'tool_call'}}, 'resumable': True, 'ns': ['human_review_node:9caf42cf-1371-7213-a331-e6fe5d026be8'], 'when': 'during'}]}
To approve the tool call, we need to let `human_review_node` know what value to use for the `human_review` variable we defined inside the node. We can provide this value by invoking the graph with a `Command(resume=<human_review>)` input. Since we're approving the tool call, we'll provide `resume` value of `{"action": "continue"}` to navigate to `run_tool` node:
=== "Python"
```python
# highlight-next-line
from langgraph_sdk.schema import Command
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
# highlight-next-line
command=Command(resume={"action": "continue"}),
stream_mode="updates",
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "Javascript"
```js
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
// highlight-next-line
command: { resume: { "action": "continue" } },
streamMode: "updates"
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"command\": {
\"resume\": { \"action\": \"continue\"}
},
\"stream_mode\": [
\"updates\"
]
}"
```
Output:
{'human_review_node': None}
{'run_tool': {'messages': [{'role': 'tool', 'name': 'weather_search', 'content': 'Sunny!', 'tool_call_id': 'toolu_01142G3woscA8JjFTLdqymtn'}]}}
{'call_llm': {'messages': [{'content': "According to the search, it's sunny in San Francisco right now!", 'additional_kwargs': {}, 'response_metadata': {'id': 'msg_01JJE9AtT4a9Lob91RRiW9rU', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 458, 'output_tokens': 18}, 'model_name': 'claude-3-5-sonnet-20241022'}, 'type': 'ai', 'name': None, 'id': 'run-5e8d80b5-c46a-4aad-af37-b01f8bb15963-0', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 458, 'output_tokens': 18, 'total_tokens': 476, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}}}]}}
## Edit Tool Call
Let's now say we want to edit the tool call. E.g. change some of the parameters (or even the tool called!) but then execute that tool.
=== "Python"
```python
input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]}
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
input=input,
stream_mode="updates",
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "Javascript"
```js
const input = { "messages": [{ "role": "user", "content": "what's the weather in sf?" }] };
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
input: input,
streamMode: "updates",
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf?\"}]},
\"stream_mode\": [
\"updates\"
]
}"
```
To do this, we will use `Command` with a different resume value of `{"action": "update", "data": <tool call args>}`. This will do the following:
* combine existing tool call with user-provided tool call arguments and update the existing AI message with the new tool call
* navigate to `run_tool` node with the updated AI message and continue execution
=== "Python"
```python
# highlight-next-line
from langgraph_sdk.schema import Command
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
# highlight-next-line
command=Command(
# highlight-next-line
resume={"action": "update", "data": {"city": "San Francisco, USA"}}
# highlight-next-line
),
stream_mode="updates",
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "Javascript"
```js
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
// highlight-next-line
command: {
// highlight-next-line
resume: { "action": "update", "data": { "city": "San Francisco, USA" } }
// highlight-next-line
},
streamMode: "updates"
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"command\": {
\"resume\": { \"action\": \"update\", \"data\": { \"city\": \"San Francisco, USA\" } }
},
\"stream_mode\": [
\"updates\"
]
}"
```
Output:
{'human_review_node': {'messages': [{'role': 'ai', 'content': [{'text': "I'll help you check the weather in San Francisco.", 'type': 'text'}, {'id': 'toolu_016L4EDPcaQRzzZxiB4Wq2wa', 'input': {'city': 'San Francisco'}, 'name': 'weather_search', 'type': 'tool_use'}], 'tool_calls': [{'id': 'toolu_016L4EDPcaQRzzZxiB4Wq2wa', 'name': 'weather_search', 'args': {'city': 'San Francisco, USA'}}], 'id': 'run-b07f0c35-4e93-43a5-9b48-363767ada3ca-0'}]}}
{'run_tool': {'messages': [{'role': 'tool', 'name': 'weather_search', 'content': 'Sunny!', 'tool_call_id': 'toolu_016L4EDPcaQRzzZxiB4Wq2wa'}]}}
{'call_llm': {'messages': [{'content': "According to the search, it's sunny in San Francisco right now!", 'additional_kwargs': {}, 'response_metadata': {'id': 'msg_01De5HurjNUMwMUpfRtMLbX1', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 460, 'output_tokens': 18}, 'model_name': 'claude-3-5-sonnet-20241022'}, 'type': 'ai', 'name': None, 'id': 'run-85e2aaaa-6f61-4fa0-b594-b6e57129d7e7-0', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 460, 'output_tokens': 18, 'total_tokens': 478, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}}}]}}
## Give feedback to a tool call
Sometimes, you may not want to execute a tool call, but you also may not want to ask the user to manually modify the tool call. In that case it may be better to get natural language feedback from the user. You can then insert this feedback as a mock **RESULT** of the tool call.
There are multiple ways to do this:
1. You could add a new message to the state (representing the "result" of a tool call)
2. You could add TWO new messages to the state - one representing an "error" from the tool call, other HumanMessage representing the feedback
Both are similar in that they involve adding messages to the state. The main difference lies in the logic AFTER the `human_review_node` and how it handles different types of messages.
For this example we will just add a single tool call representing the feedback (see `human_review_node` implementation). Let's see this in action!
=== "Python"
```python
input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]}
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
input=input,
stream_mode="updates",
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "Javascript"
```js
const input = { "messages": [{ "role": "user", "content": "what's the weather in sf?" }] };
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
input: input,
streamMode: "updates"
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf?\"}]},
\"stream_mode\": [
\"updates\"
]
}"
```
To do this, we will use `Command` with a different resume value of `{"action": "feedback", "data": <feedback string>}`. This will do the following:
* create a new tool message that combines existing tool call from LLM with the with user-provided feedback as content
* navigate to `call_llm` node with the updated tool message and continue execution
=== "Python"
```python
# highlight-next-line
from langgraph_sdk.schema import Command
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
# highlight-next-line
command=Command(
resume={
"action": "feedback",
"data": "User requested changes: use <city, country> format for location"
}
),
stream_mode="updates",
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "Javascript"
```js
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
// highlight-next-line
command: {
resume: {
"action": "feedback",
"data": "User requested changes: use <city, country> format for location"
}
},
streamMode: "updates"
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"command\": {
\"resume\": { \"action\": \"feedback\", \"data\": \"User requested changes: use <city, country> format for location\" }
},
\"stream_mode\": [
\"updates\"
]
}"
```
Output:
{'human_review_node': {'messages': [{'role': 'tool', 'content': 'User requested changes: use <city, country> format for location', 'name': 'weather_search', 'tool_call_id': 'toolu_01RkPHCjpfoUvPAktaq4Cqhm'}]}}
{'call_llm': {'messages': [{'content': [{'text': 'Let me try that again with the correct format:', 'type': 'text'}, {'id': 'toolu_01Rdrag6cVufHZG26BwVaiE7', 'input': {'city': 'San Francisco, USA'}, 'name': 'weather_search', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {'id': 'msg_01EBan969yY5f6iGk6sPgKcj', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 469, 'output_tokens': 68}, 'model_name': 'claude-3-5-sonnet-20241022'}, 'type': 'ai', 'name': None, 'id': 'run-64bbc255-d126-4db0-8ae5-3197cf29bed1-0', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco, USA'}, 'id': 'toolu_01Rdrag6cVufHZG26BwVaiE7', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 469, 'output_tokens': 68, 'total_tokens': 537, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}}}]}}
{'__interrupt__': [{'value': {'question': 'Is this correct?', 'tool_call': {'name': 'weather_search', 'args': {'city': 'San Francisco, USA'}, 'id': 'toolu_01Rdrag6cVufHZG26BwVaiE7', 'type': 'tool_call'}}, 'resumable': True, 'ns': ['human_review_node:e9856878-e28c-5dd1-d353-4d83aa1a3a2b'], 'when': 'during'}]}
We can see that we now get to another interrupt - because it went back to the model and got an entirely new prediction of what to call. Let's now approve this one and continue.
=== "Python"
```python
# highlight-next-line
from langgraph_sdk.schema import Command
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
# highlight-next-line
command=Command(resume={"action": "continue"}),
stream_mode="updates",
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "Javascript"
```js
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
// highlight-next-line
command: { resume: { "action": "continue" } },
streamMode: "updates"
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"command\": {
\"resume\": { \"action\": \"continue\"}
},
\"stream_mode\": [
\"updates\"
]
}"
```
Output:
{'human_review_node': None}
{'run_tool': {'messages': [{'role': 'tool', 'name': 'weather_search', 'content': 'Sunny!', 'tool_call_id': 'toolu_01Rdrag6cVufHZG26BwVaiE7'}]}}
{'call_llm': {'messages': [{'content': 'The weather in San Francisco is sunny!', 'additional_kwargs': {}, 'response_metadata': {'id': 'msg_013WTDHhbg8WiYLiQ9n2CaTk', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 550, 'output_tokens': 12}, 'model_name': 'claude-3-5-sonnet-20241022'}, 'type': 'ai', 'name': None, 'id': 'run-b6c815f0-989a-47cf-b150-33e3bbc4eab7-0', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 550, 'output_tokens': 12, 'total_tokens': 562, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}}}]}}
@@ -0,0 +1,240 @@
# Time travel
LangGraph provides [**time travel**](../../concepts/time-travel.md) functionality to **resume execution from a prior checkpoint** — either replaying the same state or modifying it to explore alternatives. In all cases, resuming past execution produces a **new fork** in the history.
## Use time travel
To use time-travel in LangGraph:
1. **Run the graph** with initial inputs using [LangGraph SDK](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/)'s [`client.runs.wait`][langgraph_sdk.client.RunsClient.wait] or [`client.runs.stream`][langgraph_sdk.client.RunsClient.stream] APIs.
2. **Identify a checkpoint in an existing thread**: Use [`client.threads.get_history`][langgraph_sdk.client.ThreadsClient.get_history] method to retrieve the execution history for a specific `thread_id` and locate the desired `checkpoint_id`.
Alternatively, set a [breakpoint](./human_in_the_loop_breakpoint.md) before the node(s) where you want execution to pause. You can then find the most recent checkpoint recorded up to that breakpoint.
3. **(Optional) modify the graph state**: Use the [`client.threads.update_state`][langgraph_sdk.client.ThreadsClient.update_state] method to modify the graphs state at the checkpoint and resume execution from alternative state.
4. **Resume execution from the checkpoint**: Use the [`client.runs.wait`][langgraph_sdk.client.RunsClient.wait] or [`client.runs.stream`][langgraph_sdk.client.RunsClient.stream] APIs with an input of `None` and the appropriate `thread_id` and `checkpoint_id`.
## Example
??? example "Example graph"
```python
from typing_extensions import TypedDict, NotRequired
from langgraph.graph import StateGraph, START, END
from langchain.chat_models import init_chat_model
from langgraph.checkpoint.memory import InMemorySaver
class State(TypedDict):
topic: NotRequired[str]
joke: NotRequired[str]
llm = init_chat_model(
"anthropic:claude-3-7-sonnet-latest",
temperature=0,
)
def generate_topic(state: State):
"""LLM call to generate a topic for the joke"""
msg = llm.invoke("Give me a funny topic for a joke")
return {"topic": msg.content}
def write_joke(state: State):
"""LLM call to write a joke based on the topic"""
msg = llm.invoke(f"Write a short joke about {state['topic']}")
return {"joke": msg.content}
# Build workflow
builder = StateGraph(State)
# Add nodes
builder.add_node("generate_topic", generate_topic)
builder.add_node("write_joke", write_joke)
# Add edges to connect nodes
builder.add_edge(START, "generate_topic")
builder.add_edge("generate_topic", "write_joke")
# Compile
graph = builder.compile()
```
### 1. Run the graph
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
# create a thread
thread = await client.threads.create()
thread_id = thread["thread_id"]
# Run the graph
result = await client.runs.wait(
thread_id,
assistant_id,
input={}
)
```
=== "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
const result = await client.runs.wait(
threadID,
assistantID,
{ input: {}}
);
```
=== "cURL"
Create a thread:
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
Run the graph:
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {}
}"
```
### 2. Identify a checkpoint
=== "Python"
```python
# The states are returned in reverse chronological order.
states = await client.threads.get_history(thread_id)
selected_state = states[1]
print(selected_state)
```
=== "JavaScript"
```js
// The states are returned in reverse chronological order.
const states = await client.threads.getHistory(threadID);
const selectedState = states[1];
console.log(selectedState);
```
=== "cURL"
```bash
curl --request GET \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/history \
--header 'Content-Type: application/json'
```
### 3. Update the state (optional)
`update_state` will create a new checkpoint. The new checkpoint will be associated with the same thread, but a new checkpoint ID.
=== "Python"
```python
new_config = await client.threads.update_state(
thread_id,
{"topic": "chickens"},
# highlight-next-line
checkpoint_id=selected_state["checkpoint_id"]
)
print(new_config)
```
=== "JavaScript"
```js
const newConfig = await client.threads.updateState(
threadID,
{
values: { "topic": "chickens" },
checkpointId: selectedState["checkpoint_id"]
}
);
console.log(newConfig);
```
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/state \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"checkpoint_id\": <CHECKPOINT_ID>,
\"values\": {\"topic\": \"chickens\"}
}"
```
### 4. Resume execution from the checkpoint
=== "Python"
```python
await client.runs.wait(
thread_id,
assistant_id,
# highlight-next-line
input=None,
# highlight-next-line
checkpoint_id=new_config["checkpoint_id"]
)
```
=== "JavaScript"
```js
await client.runs.wait(
threadID,
assistantID,
{
// highlight-next-line
input: null,
// highlight-next-line
checkpointId: newConfig["checkpoint_id"]
}
);
```
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"checkpoint_id\": <CHECKPOINT_ID>
}"
```
## Learn more
- [**LangGraph time travel guide**](../../how-tos/human_in_the_loop/time-travel.ipynb): learn more about using time travel in LangGraph.
Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

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