Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc929cbf29 | ||
|
|
0d8a8c5847 | ||
|
|
9cb6365914 | ||
|
|
fb1c0ae9f9 | ||
|
|
4de1bd6e66 | ||
|
+8 |
f21fc056bf | ||
|
|
1f1d032430 | ||
|
|
6f86a8c4cb | ||
|
|
d5ab8b42e0 | ||
|
|
87f2e69395 | ||
|
|
4c73b176ff | ||
|
|
4321ed0f87 | ||
|
|
cba4d9e3bc | ||
|
|
fa36a50444 | ||
|
|
5413f9db9f | ||
|
|
8118e90543 | ||
|
|
c13c474626 | ||
|
|
cd58fad69d | ||
|
|
f3a7925d86 | ||
|
|
de6c25689d | ||
|
|
141afa8c62 | ||
|
|
e99f6292c5 | ||
|
|
1800df7048 | ||
|
|
7f57e00975 | ||
|
|
844417591d | ||
|
|
c9966c4feb | ||
|
|
4abf948462 | ||
|
|
269590c4d9 | ||
|
|
d8756f257e | ||
|
|
cac0cd5522 | ||
|
|
c91208429e | ||
|
|
690b6f4ea1 | ||
|
|
07cd4d83e1 | ||
|
|
c7badc06ae | ||
|
|
fcdfc1d5e4 | ||
|
|
dc95d1af88 | ||
|
|
042e8ef315 | ||
|
|
37d1ac1dce | ||
|
|
ecfabdf73a | ||
|
|
543e4c4e7e | ||
|
|
22e09d2739 | ||
|
|
89451f4ea2 | ||
|
|
813a1d6d0c | ||
|
|
e28af0ffc3 | ||
|
|
339de4c204 | ||
|
|
0885e7833b | ||
|
|
669cf817e8 | ||
|
|
000f5c3043 | ||
|
|
b3708bd7f6 | ||
|
|
8271e39e00 | ||
|
|
60560ea755 | ||
|
|
e2acfb24cc | ||
|
|
191192b142 | ||
|
|
df368bdd30 | ||
|
|
4ec897033f | ||
|
|
c16e42e6d5 | ||
|
|
376469ea90 | ||
|
|
7e2af0ce8d | ||
|
|
1b205a99cb | ||
|
|
0a8ba20f5f | ||
|
|
048cb3584c | ||
|
|
22c35b7bc8 | ||
|
|
f3ed32e611 | ||
|
|
276675b618 | ||
|
|
882de42996 | ||
|
|
70be50f37b | ||
|
|
1c7234e9c5 | ||
|
|
3d88f75254 | ||
|
|
407abbe9ff | ||
|
|
6182cd1dcb | ||
|
|
1d276dd753 | ||
|
|
a48d8cb69b | ||
|
|
a05a251caf | ||
|
|
c7bbb26ac0 | ||
|
|
ac9b6c416e | ||
|
|
d4b4eebe4a | ||
|
|
f8e1e803e1 | ||
|
|
141a6af4f7 | ||
|
|
8a763ad358 | ||
|
|
16d02e63a1 | ||
|
|
03421c2b04 | ||
|
|
2508aa45ea | ||
|
|
9e035264f8 | ||
|
|
84e14f47bf | ||
|
|
83bbe42eab | ||
|
|
70894af72c | ||
|
|
e466327524 | ||
|
|
b6655fe083 | ||
|
|
9b7cc1c82e | ||
|
|
80d6bddd1b | ||
|
|
c406aede96 | ||
|
|
96bfc8bad9 | ||
|
|
9a0cee5cd0 | ||
|
|
78bef6bc0c | ||
|
|
7bd364c457 |
@@ -10,6 +10,6 @@ contact_links:
|
||||
- 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
|
||||
- name: LangChain Forum
|
||||
url: https://forum.langchain.com/
|
||||
about: General community discussions and support
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
# and
|
||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
|
||||
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,6 +3,9 @@ name: CLI integration test
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -8,6 +8,9 @@ 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
|
||||
|
||||
@@ -8,6 +8,9 @@ on:
|
||||
type: string
|
||||
description: "From which folder this pipeline executes"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -3,6 +3,9 @@ name: test
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -11,6 +11,9 @@ on:
|
||||
env:
|
||||
PYTHON_VERSION: "3.10"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.ref == 'refs/heads/main'
|
||||
|
||||
@@ -7,6 +7,9 @@ on:
|
||||
paths:
|
||||
- "libs/**"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -5,6 +5,9 @@ on:
|
||||
paths:
|
||||
- "libs/**"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -6,6 +6,9 @@ on:
|
||||
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.
|
||||
#
|
||||
@@ -21,7 +24,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
python: ${{ steps.filter.outputs.python }}
|
||||
sdk-js: ${{ steps.filter.outputs.sdk-js }}
|
||||
deps: ${{ steps.filter.outputs.deps }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dorny/paths-filter@v3
|
||||
@@ -36,8 +39,9 @@ jobs:
|
||||
- 'libs/checkpoint-sqlite/**'
|
||||
- 'libs/checkpoint-postgres/**'
|
||||
- 'libs/prebuilt/**'
|
||||
sdk-js:
|
||||
- 'libs/sdk-js/**'
|
||||
deps:
|
||||
- '**/pyproject.toml'
|
||||
- '**/uv.lock'
|
||||
|
||||
lint:
|
||||
needs: changes
|
||||
@@ -55,7 +59,7 @@ jobs:
|
||||
|
||||
"libs/prebuilt",
|
||||
]
|
||||
if: needs.changes.outputs.python == 'true'
|
||||
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
|
||||
uses: ./.github/workflows/_lint.yml
|
||||
with:
|
||||
working-directory: ${{ matrix.working-directory }}
|
||||
@@ -74,7 +78,7 @@ jobs:
|
||||
"libs/checkpoint-postgres",
|
||||
"libs/prebuilt",
|
||||
]
|
||||
if: needs.changes.outputs.python == 'true'
|
||||
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
|
||||
uses: ./.github/workflows/_test.yml
|
||||
with:
|
||||
working-directory: ${{ matrix.working-directory }}
|
||||
@@ -83,7 +87,7 @@ jobs:
|
||||
# NOTE: we're testing langgraph separately because it requires a different matrix
|
||||
test-langgraph:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python == 'true'
|
||||
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
|
||||
name: "cd libs/langgraph"
|
||||
uses: ./.github/workflows/_test_langgraph.yml
|
||||
secrets: inherit
|
||||
@@ -140,73 +144,21 @@ jobs:
|
||||
|
||||
integration-test:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python == 'true'
|
||||
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == '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@v4
|
||||
- name: Setup Node.js (LTS)
|
||||
uses: actions/setup-node@v4
|
||||
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@v4
|
||||
- name: Setup Node.js (LTS)
|
||||
uses: actions/setup-node@v4
|
||||
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,
|
||||
check-sdk-methods,
|
||||
check-schema,
|
||||
integration-test,
|
||||
test-js,
|
||||
]
|
||||
if: |
|
||||
always()
|
||||
|
||||
@@ -11,6 +11,9 @@ on:
|
||||
- cron: "0 5 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
markdown-link-check:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -8,6 +8,9 @@ on:
|
||||
type: string
|
||||
default: "libs/langgraph"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.11"
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
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@v4
|
||||
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
|
||||
@@ -11,6 +11,9 @@ on:
|
||||
schedule:
|
||||
- cron: "0 13 * * *"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
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
|
||||
@@ -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-agent/)
|
||||
- [Build a SQL Agent](https://langchain-ai.github.io/langgraph/tutorials/sql/sql-agent/)
|
||||
|
||||
Here are some high-level tips on writing a good tutorial:
|
||||
|
||||
|
||||
@@ -47,6 +47,16 @@ 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:
|
||||
|
||||
@@ -63,7 +63,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]
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
site/
|
||||
docs/cloud/reference/sdk/js_ts_sdk_ref.md
|
||||
|
||||
.vercel
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
.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
|
||||
.PHONY: lint-docs format-docs build-docs serve-docs serve-clean-docs clean-docs codespell llms-text build-prebuilt tests
|
||||
|
||||
build-prebuilt:
|
||||
# Use to create an update to date prebuilt page.
|
||||
@@ -21,8 +15,8 @@ build-prebuilt:
|
||||
fi
|
||||
uv run python -m _scripts.third_party_page.create_third_party_page stats.yml docs/agents/prebuilt.md --language python
|
||||
|
||||
build-docs: build-typedoc build-prebuilt
|
||||
TARGET_LANGUAGE=js uv run python -m mkdocs build --clean -f mkdocs.yml --strict
|
||||
build-docs: 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
|
||||
@@ -45,7 +39,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: build-typedoc
|
||||
serve-docs:
|
||||
uv run python -m mkdocs serve -f mkdocs.yml -w ../libs/langgraph -w ../libs/checkpoint -w ../libs/sdk-py --dirty
|
||||
|
||||
clean-docs:
|
||||
|
||||
@@ -4,11 +4,9 @@ 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"
|
||||
URL = "https://gist.githubusercontent.com/eyurtsev/e7486731415463a9bc5b4682358859c8/raw/b5a5fda9c7e3387cfcb781f25082814d43675d50/gistfile1.txt"
|
||||
response = requests.get(URL)
|
||||
response.raise_for_status()
|
||||
reference_snippets = response.text
|
||||
@@ -16,80 +14,6 @@ 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. "
|
||||
@@ -108,12 +32,6 @@ TRANSLATION_PROMPT = (
|
||||
"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"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import * as path from "node:path";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as url from "node:url";
|
||||
|
||||
const mdPath = url.fileURLToPath(
|
||||
new URL("./add_translation_js_ref_updated.md", import.meta.url)
|
||||
);
|
||||
|
||||
const extractedDir = url.fileURLToPath(
|
||||
new URL(
|
||||
"../../../oap-langgraphjs-tools-agent/src/add_transaction_js",
|
||||
import.meta.url
|
||||
)
|
||||
);
|
||||
|
||||
const files = (await fs.readdir(extractedDir, { withFileTypes: true })).sort(
|
||||
(a, b) => {
|
||||
const aInt = Number.parseInt(a.name.split(".")[0], 10);
|
||||
const bInt = Number.parseInt(b.name.split(".")[0], 10);
|
||||
return aInt - bInt;
|
||||
}
|
||||
);
|
||||
|
||||
let count = 0;
|
||||
|
||||
let lines = [];
|
||||
|
||||
for (let file of files) {
|
||||
if (file.isDirectory() || !file.name.endsWith(".mts")) continue;
|
||||
count += 1;
|
||||
|
||||
const content = await fs.readFile(path.resolve(extractedDir, file.name), {
|
||||
encoding: "utf-8",
|
||||
});
|
||||
|
||||
lines = lines.concat(
|
||||
content
|
||||
.split("\n")
|
||||
.reduce((acc, line) => {
|
||||
if (line.trimStart().startsWith("// ```")) acc.push([]);
|
||||
acc.at(-1)?.push(line);
|
||||
return acc;
|
||||
}, [])
|
||||
.map((i) => {
|
||||
const tag = i[0].trimStart().slice("// ```".length);
|
||||
return ["```" + tag, ...i.slice(1), "```"].join("\n");
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
await fs.writeFile(mdPath, lines.join("\n\n"));
|
||||
@@ -1,46 +0,0 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import * as url from "node:url";
|
||||
|
||||
const mdPath = url.fileURLToPath(
|
||||
new URL("./add_translation_js_ref.md", import.meta.url)
|
||||
);
|
||||
|
||||
const extractedDir = url.fileURLToPath(
|
||||
new URL(
|
||||
"../../../oap-langgraphjs-tools-agent/src/add_transaction_js",
|
||||
import.meta.url
|
||||
)
|
||||
);
|
||||
|
||||
await fs.mkdir(extractedDir, { recursive: true });
|
||||
|
||||
const md = (await fs.readFile(mdPath, { encoding: "utf-8" })).split("\n");
|
||||
|
||||
const chunks = [];
|
||||
let current = [];
|
||||
|
||||
for (let line of md) {
|
||||
if (line.trimStart().startsWith("```")) {
|
||||
if (current.length > 0) {
|
||||
chunks.push(current.join("\n"));
|
||||
current = [];
|
||||
} else {
|
||||
current.push("// " + line.trimStart());
|
||||
}
|
||||
} else if (current.length > 0) {
|
||||
current.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (current.length > 0) {
|
||||
chunks.push(current.join("\n"));
|
||||
}
|
||||
|
||||
for (let i = 0; i < chunks.length; i += 1) {
|
||||
await fs.writeFile(path.resolve(extractedDir, `${i}.mts`), chunks[i], {
|
||||
encoding: "utf-8",
|
||||
});
|
||||
}
|
||||
|
||||
console.log("finished");
|
||||
@@ -14,10 +14,7 @@ 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,
|
||||
_apply_conditional_rendering,
|
||||
)
|
||||
from _scripts.notebook_hooks import _on_page_markdown_with_config
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
# Get source directory (parent of HERE / docs)
|
||||
|
||||
@@ -15,8 +15,8 @@ from mkdocs.structure.files import Files, File
|
||||
from mkdocs.structure.pages import Page
|
||||
|
||||
from _scripts.generate_api_reference_links import update_markdown_with_imports
|
||||
from _scripts.link_map import JS_LINK_MAP
|
||||
from _scripts.notebook_convert import convert_notebook
|
||||
from _scripts.link_map import JS_LINK_MAP
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig()
|
||||
@@ -34,20 +34,20 @@ 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#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",
|
||||
"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",
|
||||
# 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",
|
||||
@@ -55,8 +55,8 @@ REDIRECT_MAP = {
|
||||
"how-tos/memory.ipynb": "how-tos/memory/add-memory.md",
|
||||
"agents/memory.ipynb": "how-tos/memory/add-memory.md",
|
||||
# subgraph how-tos
|
||||
"how-tos/subgraph-transform-state.ipynb": "how-tos/subgraph.ipynb#different-state-schemas",
|
||||
"how-tos/subgraphs-manage-state.ipynb": "how-tos/subgraph.ipynb#add-persistence",
|
||||
"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",
|
||||
# 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",
|
||||
@@ -72,10 +72,11 @@ REDIRECT_MAP = {
|
||||
"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.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",
|
||||
"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",
|
||||
# cloud redirects
|
||||
"cloud/index.md": "index.md",
|
||||
"cloud/how-tos/index.md": "concepts/langgraph_platform",
|
||||
@@ -111,6 +112,7 @@ 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",
|
||||
@@ -306,12 +308,6 @@ def _highlight_code_blocks(markdown: str) -> str:
|
||||
return markdown
|
||||
|
||||
|
||||
TARGET_LANGUAGE = os.environ.get("TARGET_LANGUAGE", "python")
|
||||
|
||||
if TARGET_LANGUAGE not in {"python", "js"}:
|
||||
raise ValueError(f"TARGET_LANGUAGE must be 'python' or 'js', got {TARGET_LANGUAGE}")
|
||||
|
||||
|
||||
def _on_page_markdown_with_config(
|
||||
markdown: str,
|
||||
page: Page,
|
||||
@@ -334,15 +330,16 @@ def _on_page_markdown_with_config(
|
||||
markdown = _highlight_code_blocks(markdown)
|
||||
|
||||
# Apply conditional rendering for code blocks
|
||||
markdown = _apply_conditional_rendering(markdown, TARGET_LANGUAGE)
|
||||
if TARGET_LANGUAGE == "js":
|
||||
target_language = kwargs.get("target_language", "python")
|
||||
markdown = _apply_conditional_rendering(markdown, target_language)
|
||||
if target_language == "js":
|
||||
markdown = _resolve_cross_references(markdown, JS_LINK_MAP)
|
||||
elif TARGET_LANGUAGE == "python":
|
||||
elif target_language == "python":
|
||||
# Via a dedicated plugin
|
||||
pass
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unsupported target language: {TARGET_LANGUAGE}. "
|
||||
f"Unsupported target language: {target_language}. "
|
||||
"Supported languages are 'python' and 'js'."
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# 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.md): A collection of troubleshooting guides for common issues.
|
||||
@@ -8,24 +8,41 @@ 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 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) |
|
||||
| [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) |
|
||||
| [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 | [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 | [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) |
|
||||
| [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 | [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 | [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) |
|
||||
| [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 | [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/) |
|
||||
| [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/) |
|
||||
| [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) |
|
||||
|
||||
@@ -52,7 +52,7 @@ 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](./tools.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](../how-tos/tool-calling.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.
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
---
|
||||
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](../concepts/deployment_options.md) for detailed instructions on all supported deployment models.
|
||||
@@ -9,21 +9,12 @@ hide:
|
||||
|
||||
# Use MCP
|
||||
|
||||
[Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol that standardizes how applications provide tools and context to language models. LangGraph agents can use tools defined on MCP servers through the `langchain-mcp-adapters` library.
|
||||
|
||||

|
||||
|
||||
Install the `langchain-mcp-adapters` library to use MCP tools in LangGraph:
|
||||
|
||||
```bash
|
||||
pip install langchain-mcp-adapters
|
||||
```
|
||||
The Model Context Protocol (MCP) 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.
|
||||
|
||||
## Use MCP tools
|
||||
|
||||
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"
|
||||
|
||||
@@ -8,9 +8,9 @@ hide:
|
||||
- tags
|
||||
---
|
||||
|
||||
# Agent development with LangGraph
|
||||
# Agent development using prebuilt components
|
||||
|
||||
**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.
|
||||
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.
|
||||
|
||||
## What is an agent?
|
||||
|
||||
@@ -30,7 +30,7 @@ LangGraph includes several capabilities essential for building robust, productio
|
||||
- [**Memory integration**](../how-tos/memory/add-memory.md): Native support for *short-term* (session-based) and *long-term* (persistent across sessions) memory, enabling stateful behaviors in chatbots and assistants.
|
||||
- [**Human-in-the-loop control**](../concepts/human_in_the_loop.md): Execution can pause *indefinitely* to await human feedback—unlike websocket-based solutions limited to real-time interaction. This enables asynchronous approval, correction, or intervention at any point in the workflow.
|
||||
- [**Streaming support**](../how-tos/streaming.md): Real-time streaming of agent state, model tokens, tool outputs, or combined streams.
|
||||
- [**Deployment tooling**](./deployment.md): Includes infrastructure-free deployment tools. [**LangGraph Platform**](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) supports testing, debugging, and deployment.
|
||||
- [**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.
|
||||
|
||||
@@ -60,7 +60,7 @@ Use the following tool to visualize the graph generated by
|
||||
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`](../agents/tools.md): A list of tools (functions, APIs, or other callable objects) that the agent can use to perform tasks.
|
||||
* [`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`.
|
||||
|
||||
@@ -1,310 +0,0 @@
|
||||
---
|
||||
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](../how-tos/memory/add-memory.md) guide for more information on:
|
||||
|
||||
* how to [read](../how-tos/memory/add-memory.md#read-short-term) from and [write](../how-tos/memory/add-memory.md#write-short-term) to **short-term** memory
|
||||
* how to [read](../how-tos/memory/add-memory.md#read-long-term) from and [write](../how-tos/memory/add-memory.md#write-long-term) to **long-term** memory
|
||||
|
||||
## Prebuilt tools
|
||||
|
||||
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.
|
||||
|
||||
@@ -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](./deployment.md#launch-langgraph-server-locally) or deploy your agent on [LangGraph Platform](https://langchain-ai.github.io/langgraph/cloud/quick_start/).
|
||||
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/).
|
||||
|
||||
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,7 +25,7 @@ Then, navigate to [Agent Chat UI](https://agentchat.vercel.app), or clone the re
|
||||
|
||||
## Add human-in-the-loop
|
||||
|
||||
Agent Chat UI has full support for [human-in-the-loop](../concepts/human_in_the_loop.md) workflows. To try it out, replace the agent code in `src/agent/graph.py` (from the [deployment](./deployment.md) guide) with this [agent implementation](../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](../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):
|
||||
|
||||
<video controls src="../assets/interrupt-chat-ui.mp4" type="video/mp4"></video>
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# Data Storage and Privacy
|
||||
|
||||
This document describes how data is processed in the LangGraph CLI and the LangGraph Server for both the in-memory server (`langgraph dev`) and the local Docker server (`langgraph up`). It also describes what data is tracked when interacting with the hosted LangGraph Studio frontend.
|
||||
|
||||
## CLI
|
||||
|
||||
LangGraph **CLI** is the command-line interface for building and running LangGraph applications; see the [CLI guide](../../concepts/langgraph_cli.md) to learn more.
|
||||
|
||||
By default, calls to most CLI commands log a single analytics event upon invocation. This helps us better prioritize improvements to the CLI experience. Each telemetry event contains the calling process's OS, OS version, Python version, the CLI version, the command name (`dev`, `up`, `run`, etc.), and booleans representing whether a flag was passed to the command. You can see the full analytics logic [here](https://github.com/langchain-ai/langgraph/blob/main/libs/cli/langgraph_cli/analytics.py).
|
||||
|
||||
You can disable all CLI telemetry by setting `LANGGRAPH_CLI_NO_ANALYTICS=1`.
|
||||
|
||||
## LangGraph Server (in-memory & docker)
|
||||
|
||||
The [LangGraph Server](../../concepts/langgraph_server.md) provides a durable execution runtime that relies on persisting checkpoints of your application state, long-term memories, thread metadata, assistants, and similar resources to the local file system or a database. Unless you have deliberately customized the storage location, this information is either written to local disk (for `langgraph dev`) or a PostgreSQL database (for `langgraph up` and in all deployments).
|
||||
|
||||
### LangSmith Tracing
|
||||
|
||||
When running the LangGraph server (either in-memory or in Docker), LangSmith tracing may be enabled to facilitate faster debugging and offer observability of graph state and LLM prompts in production. You can always disable tracing by setting `LANGSMITH_TRACING=false` in your server's runtime environment.
|
||||
|
||||
### In-memory development server (`langgraph dev`)
|
||||
|
||||
`langgraph dev` runs an [in-memory development server](../../tutorials/langgraph-platform/local-server.md) as a single Python process, designed for quick development and testing. It saves all checkpointing and memory data to disk within a `.langgraph_api` directory in the current working directory. Apart from the telemetry data described in the [CLI](#cli) section, no data leaves the machine unless you have enabled tracing or your graph code explicitly contacts an external service.
|
||||
|
||||
### Standalone Container (`langgraph up`)
|
||||
|
||||
`langgraph up` builds your local package into a Docker image and runs the server as a [standalone container](../../concepts/deployment_options.md#standalone-container) consisting of three containers: the API server, a PostgreSQL container, and a Redis container. All persistent data (checkpoints, assistants, etc.) are stored in the PostgreSQL database. Redis is used as a pubsub connection for real-time streaming of events. You can encrypt all checkpoints before saving to the database by setting a valid `LANGGRAPH_AES_KEY` environment variable. You can also specify [TTLs](../../how-tos/ttl/configure_ttl.md) for checkpoints and cross-thread memories in `langgraph.json` to control how long data is stored. All persisted threads, memories, and other data can be deleted via the relevant API endpoints.
|
||||
|
||||
Additional API calls are made to confirm that the server has a valid license and to track the number of executed runs and tasks. Periodically, the API server validates the provided license key (or API key).
|
||||
|
||||
If you've disabled [tracing](#langsmith-tracing), no user data is persisted externally unless your graph code explicitly contacts an external service.
|
||||
|
||||
## Studio
|
||||
|
||||
[LangGraph Studio](../../concepts/langgraph_studio.md) is a graphical interface for interacting with your LangGraph server. It does not persist any private data (the data you send to your server is not sent to LangSmith). Though the studio interface is served at [smith.langchain.com](https://smith.langchain.com), it is run in your browser and connects directly to your local LangGraph server so that no data needs to be sent to LangSmith.
|
||||
|
||||
If you are logged in, LangSmith does collect some usage analytics to help improve studio's user experience. This includes:
|
||||
|
||||
- Page visits and navigation patterns
|
||||
- User actions (button clicks)
|
||||
- Browser type and version
|
||||
- Screen resolution and viewport size
|
||||
|
||||
Importantly, no application data or code (or other sensitive configuration details) are collected. All of that is stored in the persistence layer of your LangGraph server. When using Studio anonymously, no account creation is required and usage analytics are not collected.
|
||||
|
||||
## Quick reference
|
||||
|
||||
In summary, you can opt-out of server-side telemetry by turning off CLI analytics and disabling tracing.
|
||||
|
||||
| Variable | Purpose | Default |
|
||||
| ------------------------------ | ------------------------- | -------------------------------- |
|
||||
| `LANGGRAPH_CLI_NO_ANALYTICS=1` | Disable CLI analytics | Analytics enabled |
|
||||
| `LANGSMITH_API_KEY` | Enable LangSmith tracing | Tracing disabled |
|
||||
| `LANGSMITH_TRACING=false` | Disable LangSmith tracing | Depends on environment |
|
||||
@@ -1,12 +0,0 @@
|
||||
# Threads
|
||||
|
||||
A thread contains the accumulated state of a sequence of [runs](../../concepts/assistants.md#execution). When a run is executed, the [state](../../concepts/low_level.md#state) of the underlying graph of the assistant will be persisted to the thread.
|
||||
|
||||
A thread's current and historical state can be retrieved. To persist state, a thread must be created prior to executing a run.
|
||||
|
||||
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.
|
||||
@@ -3,7 +3,7 @@
|
||||
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.
|
||||
The Self-Hosted Control Plane deployment option requires an [Enterprise](../../concepts/plans.md) plan.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
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.
|
||||
The Self-Hosted Data Plane deployment option requires an [Enterprise](../../concepts/plans.md) plan.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -15,11 +15,15 @@ Before deploying, review the [conceptual guide for the Self-Hosted Data Plane](.
|
||||
### Prerequisites
|
||||
1. `KEDA` is installed on your cluster.
|
||||
|
||||
helm repo add kedacore https://kedacore.github.io/charts
|
||||
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.
|
||||
1. You will need to enable egress to two control plane URLs. The listener polls these endpoints for deployments:
|
||||
|
||||
https://api.host.langchain.com
|
||||
https://api.smith.langchain.com
|
||||
|
||||
### Setup
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
How to integrate LangGraph into your React application# How to integrate LangGraph into your React application
|
||||
# How to integrate LangGraph into your React application
|
||||
|
||||
!!! info "Prerequisites"
|
||||
|
||||
@@ -503,6 +503,74 @@ const handleSubmit = (text: string) => {
|
||||
};
|
||||
```
|
||||
|
||||
### Cached Thread Display
|
||||
|
||||
Use the `initialValues` option to display cached thread data immediately while the history is being loaded from the server. This improves user experience by showing cached data instantly when navigating to existing threads.
|
||||
|
||||
```tsx
|
||||
import { useStream } from "@langchain/langgraph-sdk/react";
|
||||
|
||||
const CachedThreadExample = ({ threadId, cachedThreadData }) => {
|
||||
const stream = useStream({
|
||||
apiUrl: "http://localhost:2024",
|
||||
assistantId: "agent",
|
||||
threadId,
|
||||
// Show cached data immediately while history loads
|
||||
initialValues: cachedThreadData?.values,
|
||||
messagesKey: "messages",
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
{stream.messages.map((message) => (
|
||||
<div key={message.id}>{message.content as string}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Optimistic Thread Creation
|
||||
|
||||
Use the `threadId` option in `submit` function to enable optimistic UI patterns where you need to know the thread ID before the thread is actually created.
|
||||
|
||||
```tsx
|
||||
import { useState } from "react";
|
||||
import { useStream } from "@langchain/langgraph-sdk/react";
|
||||
|
||||
const OptimisticThreadExample = () => {
|
||||
const [threadId, setThreadId] = useState<string | null>(null);
|
||||
const [optimisticThreadId] = useState(() => crypto.randomUUID());
|
||||
|
||||
const stream = useStream({
|
||||
apiUrl: "http://localhost:2024",
|
||||
assistantId: "agent",
|
||||
threadId,
|
||||
onThreadId: setThreadId, // (3) Updated after thread has been created.
|
||||
messagesKey: "messages",
|
||||
});
|
||||
|
||||
const handleSubmit = (text: string) => {
|
||||
// (1) Perform a soft navigation to /threads/${optimisticThreadId}
|
||||
// without waiting for thread creation.
|
||||
window.history.pushState({}, "", `/threads/${optimisticThreadId}`);
|
||||
|
||||
// (2) Submit message to create thread with the predetermined ID.
|
||||
stream.submit(
|
||||
{ messages: [{ type: "human", content: text }] },
|
||||
{ threadId: optimisticThreadId }
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Thread ID: {threadId ?? optimisticThreadId}</p>
|
||||
{/* Rest of component */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### TypeScript
|
||||
|
||||
The `useStream()` hook is friendly for apps written in TypeScript and you can specify types for the state to get better type safety and IDE support.
|
||||
|
||||
@@ -154,8 +154,9 @@ You can now test the API:
|
||||
|
||||
```bash
|
||||
curl -s --request POST \
|
||||
--url <DEPLOYMENT_URL> \
|
||||
--url <DEPLOYMENT_URL>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header "X-Api-Key: <LANGSMITH API KEY> \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# API Reference
|
||||
# LangGraph Server API Reference
|
||||
|
||||
The LangGraph Platform API reference is available with each deployment at the `/docs` URL path (e.g. `http://localhost:8124/docs`).
|
||||
The LangGraph Server API reference is available within each deployment at the `/docs` endpoint (e.g. `http://localhost:8124/docs`).
|
||||
|
||||
Click <a href="/langgraph/cloud/reference/api/api_ref.html" target="_blank">here</a> to view the API reference.
|
||||
|
||||
## Authentication
|
||||
|
||||
For deployments to LangGraph Platform, authentication is required. Pass the `X-Api-Key` header with each request to the LangGraph Platform API. The value of the header should be set to a valid LangSmith API key for the organization where the API is deployed.
|
||||
For deployments to LangGraph Platform, authentication is required. Pass the `X-Api-Key` header with each request to the LangGraph Server. The value of the header should be set to a valid LangSmith API key for the organization where the LangGraph Server is deployed.
|
||||
|
||||
Example `curl` command:
|
||||
```shell
|
||||
@@ -18,5 +18,5 @@ curl --request POST \
|
||||
"metadata": {},
|
||||
"limit": 10,
|
||||
"offset": 0
|
||||
}'
|
||||
}'
|
||||
```
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
# LangGraph Control Plane API Reference
|
||||
|
||||
The LangGraph Control Plane API is used to programmatically create and manage LangGraph Server deployments. For example, the APIs can be orchestrated to create custom CI/CD workflows.
|
||||
|
||||
Click <a href="https://api.host.langchain.com/docs" target="_blank">here</a> to view the API reference.
|
||||
|
||||
## Host
|
||||
|
||||
LangGraph Control Plane hosts for Cloud SaaS data regions:
|
||||
|
||||
| US | EU |
|
||||
|----|----|
|
||||
| `https://api.host.langchain.com` | `https://eu.api.host.langchain.com` |
|
||||
|
||||
**Note**: Self-hosted deployments of LangGraph Platform will have a custom host for the LangGraph Control Plane.
|
||||
|
||||
## Authentication
|
||||
|
||||
To authenticate with the LangGraph Control Plane API, set the `X-Api-Key` header to a valid LangSmith API key.
|
||||
|
||||
Example `curl` command:
|
||||
```shell
|
||||
curl --request GET \
|
||||
--url http://localhost:8124/v2/deployments \
|
||||
--header 'X-Api-Key: LANGSMITH_API_KEY'
|
||||
```
|
||||
|
||||
## Versioning
|
||||
|
||||
Each endpoint path is prefixed with a version (e.g. `v1`, `v2`).
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Call `POST /v2/deployments` to create a new Deployment. The response body contains the Deployment ID (`id`) and the ID of the latest (and first) revision (`latest_revision_id`).
|
||||
1. Call `GET /v2/deployments/{deployment_id}` to retrieve the Deployment. Set `deployment_id` in the URL to the value of Deployment ID (`id`).
|
||||
1. Poll for revision `status` until `status` is `DEPLOYED` by calling `GET /v2/deployments/{deployment_id}/revisions/{latest_revision_id}`.
|
||||
1. Call `PATCH /v2/deployments/{deployment_id}` to update the deployment.
|
||||
|
||||
## Example Code
|
||||
Below is example Python code that demonstrates how to orchestrate the LangGraph Control Plane APIs to create a deployment, update the deployment, and delete the deployment.
|
||||
```python
|
||||
import os
|
||||
import time
|
||||
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# required environment variables
|
||||
CONTROL_PLANE_HOST = os.getenv("CONTROL_PLANE_HOST")
|
||||
LANGSMITH_API_KEY = os.getenv("LANGSMITH_API_KEY")
|
||||
INTEGRATION_ID = os.getenv("INTEGRATION_ID")
|
||||
MAX_WAIT_TIME = 1800 # 30 mins
|
||||
|
||||
|
||||
def get_headers() -> dict:
|
||||
"""Return common headers for requests to LangGraph Control Plane API."""
|
||||
return {
|
||||
"X-Api-Key": LANGSMITH_API_KEY,
|
||||
}
|
||||
|
||||
|
||||
def create_deployment() -> str:
|
||||
"""Create deployment. Return deployment ID."""
|
||||
headers = get_headers()
|
||||
headers["Content-Type"] = "application/json"
|
||||
|
||||
deployment_name = "my_deployment"
|
||||
|
||||
request_body = {
|
||||
"name": deployment_name,
|
||||
"source": "github",
|
||||
"source_config": {
|
||||
"integration_id": INTEGRATION_ID,
|
||||
"repo_url": "https://github.com/langchain-ai/langgraph-example",
|
||||
"deployment_type": "dev",
|
||||
"build_on_push": False,
|
||||
"custom_url": None,
|
||||
"resource_spec": None,
|
||||
},
|
||||
"source_revision_config": {
|
||||
"repo_ref": "main",
|
||||
"langgraph_config_path": "langgraph.json",
|
||||
"image_uri": None,
|
||||
},
|
||||
"secrets": [
|
||||
{
|
||||
"name": "OPENAI_API_KEY",
|
||||
"value": "test_openai_api_key",
|
||||
},
|
||||
{
|
||||
"name": "ANTHROPIC_API_KEY",
|
||||
"value": "test_anthropic_api_key",
|
||||
},
|
||||
{
|
||||
"name": "TAVILY_API_KEY",
|
||||
"value": "test_tavily_api_key",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
url=f"{CONTROL_PLANE_HOST}/v2/deployments",
|
||||
headers=headers,
|
||||
json=request_body,
|
||||
)
|
||||
|
||||
if response.status_code != 201:
|
||||
raise Exception(f"Failed to create deployment: {response.text}")
|
||||
|
||||
deployment_id = response.json()["id"]
|
||||
print(f"Created deployment {deployment_name} ({deployment_id})")
|
||||
return deployment_id
|
||||
|
||||
|
||||
def get_deployment(deployment_id: str) -> dict:
|
||||
"""Get deployment."""
|
||||
response = requests.get(
|
||||
url=f"{CONTROL_PLANE_HOST}/v2/deployments/{deployment_id}",
|
||||
headers=get_headers(),
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"Failed to get deployment ID {deployment_id}: {response.text}")
|
||||
|
||||
return response.json()
|
||||
|
||||
|
||||
def list_revisions(deployment_id: str) -> list[dict]:
|
||||
"""List revisions.
|
||||
|
||||
Return list is sorted by created_at in descending order (latest first).
|
||||
"""
|
||||
response = requests.get(
|
||||
url=f"{CONTROL_PLANE_HOST}/v2/deployments/{deployment_id}/revisions",
|
||||
headers=get_headers(),
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise Exception(
|
||||
f"Failed to list revisions for deployment ID {deployment_id}: {response.text}"
|
||||
)
|
||||
|
||||
return response.json()
|
||||
|
||||
|
||||
def get_revision(
|
||||
deployment_id: str,
|
||||
revision_id: str,
|
||||
) -> dict:
|
||||
"""Get revision."""
|
||||
response = requests.get(
|
||||
url=f"{CONTROL_PLANE_HOST}/v2/deployments/{deployment_id}/revisions/{revision_id}",
|
||||
headers=get_headers(),
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"Failed to get revision ID {revision_id}: {response.text}")
|
||||
|
||||
return response.json()
|
||||
|
||||
|
||||
def patch_deployment(deployment_id: str) -> None:
|
||||
"""Patch deployment."""
|
||||
headers = get_headers()
|
||||
headers["Content-Type"] = "application/json"
|
||||
|
||||
response = requests.patch(
|
||||
url=f"{CONTROL_PLANE_HOST}/v2/deployments/{deployment_id}",
|
||||
headers=headers,
|
||||
json={
|
||||
"source_config": {
|
||||
"build_on_push": True,
|
||||
},
|
||||
"source_revision_config": {
|
||||
"repo_ref": "main",
|
||||
"langgraph_config_path": "langgraph.json",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"Failed to patch deployment: {response.text}")
|
||||
|
||||
print(f"Patched deployment ID {deployment_id}")
|
||||
|
||||
|
||||
def wait_for_deployment(deployment_id: str, revision_id: str) -> None:
|
||||
"""Wait for revision status to be DEPLOYED."""
|
||||
start_time = time.time()
|
||||
revision, status = None, None
|
||||
while time.time() - start_time < MAX_WAIT_TIME:
|
||||
revision = get_revision(deployment_id, revision_id)
|
||||
status = revision["status"]
|
||||
if status == "DEPLOYED":
|
||||
break
|
||||
elif "FAILED" in status:
|
||||
raise Exception(f"Revision ID {revision_id} failed: {revision}")
|
||||
|
||||
print(f"Waiting for revision ID {revision_id} to be DEPLOYED...")
|
||||
time.sleep(60)
|
||||
|
||||
if status != "DEPLOYED":
|
||||
raise Exception(
|
||||
f"Timeout waiting for revision ID {revision_id} to be DEPLOYED: {revision}"
|
||||
)
|
||||
|
||||
|
||||
def delete_deployment(deployment_id: str) -> None:
|
||||
"""Delete deployment."""
|
||||
response = requests.delete(
|
||||
url=f"{CONTROL_PLANE_HOST}/v2/deployments/{deployment_id}",
|
||||
headers=get_headers(),
|
||||
)
|
||||
|
||||
if response.status_code != 204:
|
||||
raise Exception(
|
||||
f"Failed to delete deployment ID {deployment_id}: {response.text}"
|
||||
)
|
||||
|
||||
print(f"Deployment ID {deployment_id} deleted")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# create deployment and get the latest revision
|
||||
deployment_id = create_deployment()
|
||||
revisions = list_revisions(deployment_id)
|
||||
latest_revision = revisions["resources"][0]
|
||||
latest_revision_id = latest_revision["id"]
|
||||
|
||||
# wait for latest revision to be DEPLOYED
|
||||
wait_for_deployment(deployment_id, latest_revision_id)
|
||||
|
||||
# patch the deployment and get the latest revision
|
||||
patch_deployment(deployment_id)
|
||||
revisions = list_revisions(deployment_id)
|
||||
latest_revision = revisions["resources"][0]
|
||||
latest_revision_id = latest_revision["id"]
|
||||
|
||||
# wait for latest revision to be DEPLOYED
|
||||
wait_for_deployment(deployment_id, latest_revision_id)
|
||||
|
||||
# delete the deployment
|
||||
delete_deployment(deployment_id)
|
||||
```
|
||||
@@ -51,9 +51,10 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
|
||||
| <span style="white-space: nowrap;">`node_version`</span> | Specify `node_version: 20` to use LangGraph.js. |
|
||||
| <span style="white-space: nowrap;">`pip_config_file`</span> | Path to `pip` config file. |
|
||||
| <span style="white-space: nowrap;">`pip_installer`</span> | _(Added in v0.3)_ Optional. Python package installer selector. It can be set to `"auto"`, `"pip"`, or `"uv"`. From version 0.3 onward the default strategy is to run `uv pip`, which typically delivers faster builds while remaining a drop-in replacement. In the uncommon situation where `uv` cannot handle your dependency graph or the structure of your `pyproject.toml`, specify `"pip"` here to revert to the earlier behaviour. |
|
||||
| <span style="white-space: nowrap;">`keep_pkg_tools`</span> | _(Added in v0.3.4)_ Optional. Control whether to retain Python packaging tools (`pip`, `setuptools`, `wheel`) in the final image. Accepted values: <ul><li><code>true</code> : Keep all three tools (skip uninstall).</li><li><code>false</code> / omitted : Uninstall all three tools (default behaviour).</li><li><code>list[str]</code> : Names of tools <strong>to retain</strong>. Each value must be one of "pip", "setuptools", "wheel".</li></ul>. By default, all three tools are uninstalled. |
|
||||
| <span style="white-space: nowrap;">`dockerfile_lines`</span> | Array of additional lines to add to Dockerfile following the import from parent image. |
|
||||
| <span style="white-space: nowrap;">`checkpointer`</span> | Configuration for the checkpointer. Contains a `ttl` field which is an object with the following keys: <ul><li>`strategy`: How to handle expired checkpoints (e.g., `"delete"`).</li><li>`sweep_interval_minutes`: How often to check for expired checkpoints (integer).</li><li>`default_ttl`: Default time-to-live for checkpoints in **minutes** (integer). Defines how long checkpoints are kept before the specified strategy is applied.</li></ul> |
|
||||
| <span style="white-space: nowrap;">`http`</span> | HTTP server configuration with the following fields: <ul><li>`app`: Path to custom Starlette/FastAPI app (e.g., `"./src/agent/webapp.py:app"`). See [custom routes guide](../../how-tos/http/custom_routes.md).</li><li>`disable_assistants`: Disable `/assistants` routes</li><li>`disable_threads`: Disable `/threads` routes</li><li>`disable_runs`: Disable `/runs` routes</li><li>`disable_store`: Disable `/store` routes</li><li>`disable_meta`: Disable `/ok`, `/info`, `/metrics`, and `/docs` routes</li><li>`disable_mcp`: Disable `/mcp` routes</li><li>`cors`: CORS configuration with fields for `allow_origins`, `allow_methods`, `allow_headers`, etc.</li><li>`configurable_headers`: Define which request headers to exclude or include as a run's configurable values.</li></ul> |
|
||||
| <span style="white-space: nowrap;">`http`</span> | HTTP server configuration with the following fields: <ul><li>`app`: Path to custom Starlette/FastAPI app (e.g., `"./src/agent/webapp.py:app"`). See [custom routes guide](../../how-tos/http/custom_routes.md).</li><li>`cors`: CORS configuration with fields for `allow_origins`, `allow_methods`, `allow_headers`, etc.</li><li>`configurable_headers`: Define which request headers to exclude or include as a run's configurable values.</li><li>`disable_assistants`: Disable `/assistants` routes</li><li>`disable_mcp`: Disable `/mcp` routes</li><li>`disable_meta`: Disable `/ok`, `/info`, `/metrics`, and `/docs` routes</li><li>`disable_runs`: Disable `/runs` routes</li><li>`disable_store`: Disable `/store` routes</li><li>`disable_threads`: Disable `/threads` routes</li><li>`disable_ui`: Disable `/ui` routes</li><li>`disable_webhooks`: Disable webhooks calls on run completion in all routes</li><li>`mount_prefix`: Prefix for mounted routes (e.g., "/my-deployment/api")</li></ul> |
|
||||
|
||||
=== "JS"
|
||||
|
||||
@@ -395,7 +396,7 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
|
||||
|
||||
=== "Python"
|
||||
|
||||
Start LangGraph API server. For local testing, requires a LangSmith API key with access to LangGraph Platform closed beta. Requires a license key for production use.
|
||||
Start LangGraph API server. For local testing, requires a LangSmith API key with access to LangGraph Platform. Requires a license key for production use.
|
||||
|
||||
**Usage**
|
||||
|
||||
@@ -422,7 +423,7 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
|
||||
|
||||
=== "JS"
|
||||
|
||||
Start LangGraph API server. For local testing, requires a LangSmith API key with access to LangGraph Platform closed beta. Requires a license key for production use.
|
||||
Start LangGraph API server. For local testing, requires a LangSmith API key with access to LangGraph Platform. Requires a license key for production use.
|
||||
|
||||
**Usage**
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ Parallel processing is vital for efficient multi-agent systems and complex tasks
|
||||
- Implementation of map-reduce-like operations
|
||||
- Efficient handling of independent subtasks
|
||||
|
||||
For practical implementation, see our [map-reduce tutorial](../how-tos/graph-api.ipynb#map-reduce-and-the-send-api)
|
||||
For practical implementation, see our [map-reduce tutorial](../how-tos/graph-api.md#map-reduce-and-the-send-api)
|
||||
|
||||
### Subgraphs
|
||||
|
||||
@@ -107,7 +107,7 @@ For practical implementation, see our [map-reduce tutorial](../how-tos/graph-api
|
||||
- Hierarchical organization of agent teams
|
||||
- Controlled communication between agents and the main system
|
||||
|
||||
Subgraphs communicate with the parent graph through overlapping keys in the state schema. This enables flexible, modular agent design. For implementation details, refer to our [subgraph how-to guide](../how-tos/subgraph.ipynb).
|
||||
Subgraphs communicate with the parent graph through overlapping keys in the state schema. This enables flexible, modular agent design. For implementation details, refer to our [subgraph how-to guide](../how-tos/subgraph.md).
|
||||
|
||||
### Reflection
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ Below are examples of directory structures for Python and JavaScript application
|
||||
│ ├── utils # utilities for your graph
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── tools.py # tools for your graph
|
||||
│ │ ├── nodes.py # node functions for you graph
|
||||
│ │ ├── nodes.py # node functions for your graph
|
||||
│ │ └── state.py # state definition of your graph
|
||||
│ ├── __init__.py
|
||||
│ └── agent.py # code for constructing your graph
|
||||
@@ -64,7 +64,7 @@ Below are examples of directory structures for Python and JavaScript application
|
||||
├── 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
|
||||
│ │ ├── nodes.ts # node functions for your graph
|
||||
│ │ └── state.ts # state definition of your graph
|
||||
│ └── agent.ts # code for constructing your graph
|
||||
├── package.json # package dependencies
|
||||
|
||||
@@ -143,6 +143,54 @@ The returned user information is available:
|
||||
In many of our tutorials, we will just show the "authorization" parameter to be concise, but you can opt to accept more information as needed
|
||||
to implement your custom authentication scheme.
|
||||
|
||||
### Agent authentication
|
||||
|
||||
Custom authentication permits delegated access. The values you return in `@auth.authenticate` are added to the run context, giving agents user-scoped credentials lets them access resources on the user’s behalf.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
%% Actors
|
||||
participant ClientApp as Client
|
||||
participant AuthProv as Auth Provider
|
||||
participant LangGraph as LangGraph Backend
|
||||
participant SecretStore as Secret Store
|
||||
participant ExternalService as External Service
|
||||
|
||||
%% Platform login / AuthN
|
||||
ClientApp ->> AuthProv: 1. Login (username / password)
|
||||
AuthProv -->> ClientApp: 2. Return token
|
||||
ClientApp ->> LangGraph: 3. Request with token
|
||||
|
||||
Note over LangGraph: 4. Validate token (@auth.authenticate)
|
||||
LangGraph -->> AuthProv: 5. Fetch user info
|
||||
AuthProv -->> LangGraph: 6. Confirm validity
|
||||
|
||||
%% Fetch user tokens from secret store
|
||||
LangGraph ->> SecretStore: 6a. Fetch user tokens
|
||||
SecretStore -->> LangGraph: 6b. Return tokens
|
||||
|
||||
Note over LangGraph: 7. Apply access control (@auth.on.*)
|
||||
|
||||
%% External Service round-trip
|
||||
LangGraph ->> ExternalService: 8. Call external service (with header)
|
||||
Note over ExternalService: 9. External service validates header and executes action
|
||||
ExternalService -->> LangGraph: 10. Service response
|
||||
|
||||
%% Return to caller
|
||||
LangGraph -->> ClientApp: 11. Return resources
|
||||
```
|
||||
|
||||
After authentication, the platform creates a special configuration object that is passed to your graph and all nodes via the configurable context.
|
||||
This object contains information about the current user, including any custom fields you return from your [`@auth.authenticate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler.
|
||||
|
||||
To enable an agent to act on behalf of the user, use [custom authentication middleware](../how-tos/auth/custom_auth.md). This will allow the agent to interact with external systems like MCP servers, external databases, and even other agents on behalf of the user.
|
||||
|
||||
For more information, see the [Use custom auth](../how-tos/auth/custom_auth.md#enable-agent-authentication) guide.
|
||||
|
||||
### Agent authentication with MCP
|
||||
|
||||
For information on how to authenticate an agent to an MCP server, see the [MCP conceptual guide](../concepts/mcp.md).
|
||||
|
||||
## Authorization
|
||||
|
||||
After authentication, LangGraph calls your [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handlers to control access to specific resources (e.g., threads, assistants, crons). These handlers can:
|
||||
|
||||
@@ -18,9 +18,9 @@ There are 4 main options for deploying with the [LangGraph Platform](langgraph_p
|
||||
|
||||
1. [Cloud SaaS](#cloud-saas)
|
||||
|
||||
1. [Self-Hosted Data Plane<sup>(Beta)</sup>](#self-hosted-data-plane)
|
||||
1. [Self-Hosted Data Plane](#self-hosted-data-plane)
|
||||
|
||||
1. [Self-Hosted Control Plane<sup>(Beta)</sup>](#self-hosted-control-plane)
|
||||
1. [Self-Hosted Control Plane](#self-hosted-control-plane)
|
||||
|
||||
1. [Standalone Container](#standalone-container)
|
||||
|
||||
@@ -50,7 +50,7 @@ For more information, please see:
|
||||
## Self-Hosted Data Plane
|
||||
|
||||
!!! info "Important"
|
||||
The Self-Hosted Data Plane deployment option is currently in beta stage and requires an [Enterprise](../concepts/plans.md) plan.
|
||||
The Self-Hosted Data Plane deployment option requires an [Enterprise](../concepts/plans.md) plan.
|
||||
|
||||
The [Self-Hosted Data Plane](./langgraph_self_hosted_data_plane.md) deployment option is a "hybrid" model for deployment where we manage the [control plane](./langgraph_control_plane.md) in our cloud and you manage the [data plane](./langgraph_data_plane.md) in your cloud. This option provides a way to securely manage your data plane infrastructure, while offloading control plane management to us.
|
||||
|
||||
@@ -66,7 +66,7 @@ For more information, please see:
|
||||
## Self-Hosted Control Plane
|
||||
|
||||
!!! info "Important"
|
||||
The Self-Hosted Control Plane deployment option is currently in beta stage and requires an [Enterprise](../concepts/plans.md) plan.
|
||||
The Self-Hosted Control Plane deployment option requires an [Enterprise](../concepts/plans.md) plan.
|
||||
|
||||
The [Self-Hosted Control Plane](./langgraph_self_hosted_control_plane.md) deployment option is a fully self-hosted model for deployment where you manage the [control plane](./langgraph_control_plane.md) and [data plane](./langgraph_data_plane.md) in your cloud. This option gives you full control and responsibility of the control plane and data plane infrastructure.
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ LangGraph is a stateful, orchestration framework that brings added control to ag
|
||||
|
||||
No. LangGraph Platform is proprietary software.
|
||||
|
||||
There is a free, self-hosted version of LangGraph Platform with access to basic features. The Cloud SaaS deployment option is free while in beta, but will eventually be a paid service. We will always give ample notice before charging for a service and reward our early adopters with preferential pricing. The Self-Hosted deployment options are paid services. [Contact our sales team](https://www.langchain.com/contact-sales) to learn more.
|
||||
There is a free, self-hosted version of LangGraph Platform with access to basic features. The Cloud SaaS deployment option and the Self-Hosted deployment options are paid services. [Contact our sales team](https://www.langchain.com/contact-sales) to learn more.
|
||||
|
||||
For more information, see our [LangGraph Platform pricing page](https://www.langchain.com/pricing-langgraph-platform).
|
||||
|
||||
|
||||
@@ -18,10 +18,21 @@ The Functional API uses two key building blocks:
|
||||
|
||||
This provides a minimal abstraction for building workflows with state management and streaming.
|
||||
|
||||
!!! tip
|
||||
!!! tip
|
||||
|
||||
For information on how to use the functional API, see [Use Functional API](../how-tos/use-functional-api.md).
|
||||
|
||||
## Functional API vs. Graph API
|
||||
|
||||
For users who prefer a more declarative approach, LangGraph's [Graph API](./low_level.md) allows you to define workflows using a Graph paradigm. Both APIs share the same underlying runtime, so you can use them together in the same application.
|
||||
|
||||
Here are some key differences:
|
||||
|
||||
- **Control flow**: The Functional API does not require thinking about graph structure. You can use standard Python constructs to define workflows. This will usually trim the amount of code you need to write.
|
||||
- **Short-term memory**: The **GraphAPI** requires declaring a [**State**](./low_level.md#state) and may require defining [**reducers**](./low_level.md#reducers) to manage updates to the graph state. `@entrypoint` and `@tasks` do not require explicit state management as their state is scoped to the function and is not shared across functions.
|
||||
- **Checkpointing**: Both APIs generate and use checkpoints. In the **Graph API** a new checkpoint is generated after every [superstep](./low_level.md). In the **Functional API**, when tasks are executed, their results are saved to an existing checkpoint associated with the given entrypoint instead of creating a new checkpoint.
|
||||
- **Visualization**: The Graph API makes it easy to visualize the workflow as a graph which can be useful for debugging, understanding the workflow, and sharing with others. The Functional API does not support visualization as the graph is dynamically generated during runtime.
|
||||
|
||||
For users who prefer a more declarative approach, LangGraph's [Graph API](./low_level.md) allows you to define workflows using a Graph paradigm. Both APIs share the same underlying runtime, so you can use them together in the same application.
|
||||
Please see the [Functional API vs. Graph API](#functional-api-vs-graph-api) section for a comparison of the two paradigms.
|
||||
|
||||
## Example
|
||||
|
||||
@@ -532,15 +543,6 @@ While different runs of a workflow can produce different results, resuming a **s
|
||||
|
||||
Idempotency ensures that running the same operation multiple times produces the same result. This helps prevent duplicate API calls and redundant processing if a step is rerun due to a failure. Always place API calls inside **tasks** functions for checkpointing, and design them to be idempotent in case of re-execution. Re-execution can occur if a **task** starts, but does not complete successfully. Then, if the workflow is resumed, the **task** will run again. Use idempotency keys or verify existing results to avoid duplication.
|
||||
|
||||
## Functional API vs. Graph API
|
||||
|
||||
The **Functional API** and the [Graph APIs (StateGraph)](./low_level.md#stategraph) provide two different paradigms to create applications with LangGraph. Here are some key differences:
|
||||
|
||||
- **Control flow**: The Functional API does not require thinking about graph structure. You can use standard Python constructs to define workflows. This will usually trim the amount of code you need to write.
|
||||
- **Short-term memory**: The **GraphAPI** requires declaring a [**State**](./low_level.md#state) and may require defining [**reducers**](./low_level.md#reducers) to manage updates to the graph state. `@entrypoint` and `@tasks` do not require explicit state management as their state is scoped to the function and is not shared across functions.
|
||||
- **Checkpointing**: Both APIs generate and use checkpoints. In the **Graph API** a new checkpoint is generated after every [superstep](./low_level.md). In the **Functional API**, when tasks are executed, their results are saved to an existing checkpoint associated with the given entrypoint instead of creating a new checkpoint.
|
||||
- **Visualization**: The Graph API makes it easy to visualize the workflow as a graph which can be useful for debugging, understanding the workflow, and sharing with others. The Functional API does not support visualization as the graph is dynamically generated during runtime.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### Handling side effects
|
||||
|
||||
@@ -26,7 +26,7 @@ The Control Plane UI is embedded in [LangSmith](https://docs.smith.langchain.com
|
||||
|
||||
## Control Plane API
|
||||
|
||||
This section describes data model of the control plane API. The API is used to create, update, and delete deployments. However, they are not publicly accessible.
|
||||
This section describes the data model of the control plane API. The API is used to create, update, and delete deployments. See the [control plane API reference](../cloud/reference/api/api_ref_control_plane.md) for more details.
|
||||
|
||||
### Deployment
|
||||
|
||||
@@ -34,11 +34,7 @@ A deployment is an instance of a LangGraph Server. A single deployment can have
|
||||
|
||||
### Revision
|
||||
|
||||
A revision is an iteration of a deployment. When a new deployment is created, an initial revision is automatically created. To deploy code changes or update environment variables for a deployment, a new revision must be created.
|
||||
|
||||
### Environment Variable
|
||||
|
||||
Environment variables are set for a deployment. All environment variables are stored as secrets (i.e. saved in a secrets store).
|
||||
A revision is an iteration of a deployment. When a new deployment is created, an initial revision is automatically created. To deploy code changes or update secrets for a deployment, a new revision must be created.
|
||||
|
||||
## Control Plane Features
|
||||
|
||||
@@ -50,21 +46,40 @@ For simplicity, the control plane offers two deployment types with different res
|
||||
|
||||
| **Deployment Type** | **CPU/Memory** | **Scaling** | **Database** |
|
||||
|---------------------|-----------------|---------------------|----------------------------------------------------------------------------------|
|
||||
| Development | 1 CPU, 1 GB RAM | Up to 1 container | 10 GB disk, no backups |
|
||||
| Production | 2 CPU, 2 GB RAM | Up to 10 containers | Autoscaling disk, automatic backups, highly available (multi-zone configuration) |
|
||||
| Development | 1 CPU, 1 GB RAM | Up to 1 replica | 10 GB disk, no backups |
|
||||
| Production | 2 CPU, 2 GB RAM | Up to 10 replicas | Autoscaling disk, automatic backups, highly available (multi-zone configuration) |
|
||||
|
||||
CPU and memory resources are per container.
|
||||
CPU and memory resources are per replica.
|
||||
|
||||
!!! warning "Immutable Deployment Type"
|
||||
|
||||
Once a deployment is created, the deployment type cannot be changed.
|
||||
|
||||
!!! info "Resource Customization"
|
||||
For `Production` type deployments, resources can be manually increased on a case-by-case basis depending on use case and capacity constraints. Contact support@langchain.dev to request an increase in resources.
|
||||
!!! info "Self-Hosted Deployment"
|
||||
Resources for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments can be fully customized. Deployment types are only applicable for [Cloud SaaS](../concepts/langgraph_cloud.md) deployments.
|
||||
|
||||
For `Development` types deployments, database disk size can be manually increased on a case-by-case basis depending on use case and capacity constraints. For most use cases, [TTLs](../how-tos/ttl/configure_ttl.md) should be configured to manage disk usage. Contact support@langchain.dev to request an increase in resources.
|
||||
#### Production
|
||||
|
||||
Resources for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments can be fully customized.
|
||||
`Production` type deployments are suitable for "production" workloads. For example, select `Production` for customer-facing applications in the critical path.
|
||||
|
||||
Resources for `Production` type deployments can be manually increased on a case-by-case basis depending on use case and capacity constraints. Contact support@langchain.dev to request an increase in resources.
|
||||
|
||||
#### Development
|
||||
|
||||
`Development` type deployments are suitable development and testing. For example, select `Development` for internal testing environments. `Development` type deployments are not suitable for "production" workloads.
|
||||
|
||||
!!! danger "Preemptible Compute Infrastructure"
|
||||
`Development` type deployments (API server, queue server, and database) are provisioned on preemptible compute infrastructure. This means the compute infrastructure **may be terminated at any time without notice**. This may result in intermittent...
|
||||
|
||||
- Redis connection timeouts/errors
|
||||
- Postgres connection timeouts/errors
|
||||
- Failed or retrying background runs
|
||||
|
||||
This behavior is expected. Preemptible compute infrastructure **significantly reduces the cost to provision a `Development` type deployment**. By design, LangGraph Server is fault-tolerant. The implementation will automatically attempt to recover from Redis/Postgres connection errors and retry failed background runs.
|
||||
|
||||
`Production` type deployments are provisioned on durable compute infrastructure, not preemptible compute infrastructure.
|
||||
|
||||
Database disk size for `Development` type deployments can be manually increased on a case-by-case basis depending on use case and capacity constraints. For most use cases, [TTLs](../how-tos/ttl/configure_ttl.md) should be configured to manage disk usage. Contact support@langchain.dev to request an increase in resources.
|
||||
|
||||
### Database Provisioning
|
||||
|
||||
@@ -97,6 +112,8 @@ After a deployment is ready, the control plane monitors the deployment and recor
|
||||
- Number of container restarts.
|
||||
- Number of replicas (this will increase with [autoscaling](../concepts/langgraph_data_plane.md#autoscaling)).
|
||||
- [Postgres](../concepts/langgraph_data_plane.md#postgres) CPU, memory usage, and disk usage.
|
||||
- [LangGraph Server queue](../concepts/langgraph_server.md#persistence-and-task-queue) pending/active run count.
|
||||
- [LangGraph Server API](../concepts/langgraph_server.md) success response count, error response count, and latency.
|
||||
|
||||
These metrics are displayed as charts in the Control Plane UI.
|
||||
|
||||
|
||||
@@ -50,6 +50,15 @@ Runs in a LangGraph Server may be retried for specific failures (currently only
|
||||
|
||||
This section describes various features of the data plane.
|
||||
|
||||
### Data Region
|
||||
|
||||
!!! info "Only for Cloud SaaS"
|
||||
Data regions are only applicable for [Cloud SaaS](../concepts/langgraph_cloud.md) deployments.
|
||||
|
||||
Deployments can be created in 2 data regions: US and EU
|
||||
|
||||
The data region for a deployment is implied by the data region of the LangSmith organization where the deployment is created. Deployments and the underlying database for the deployments cannot be migrated between data regions.
|
||||
|
||||
### Autoscaling
|
||||
|
||||
[`Production` type](../concepts/langgraph_control_plane.md#deployment-types) deployments automatically scale up to 10 containers. Scaling is based on 3 metrics:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](./deployment_options.md#self-hosted-data-plane) and [Self-Hosted Control Plane](./deployment_options.md#self-hosted-control-plane).
|
||||
|
||||
!!! info "Important"
|
||||
The Self-Hosted Control Plane deployment option is currently in beta stage and requires an [Enterprise](plans.md) plan.
|
||||
The Self-Hosted Control Plane deployment option requires an [Enterprise](plans.md) plan.
|
||||
|
||||
## Requirements
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ search:
|
||||
There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](./deployment_options.md#self-hosted-data-plane) and [Self-Hosted Control Plane](./deployment_options.md#self-hosted-control-plane).
|
||||
|
||||
!!! info "Important"
|
||||
The Self-Hosted Data Plane deployment option is currently in beta stage and requires an [Enterprise](plans.md) plan.
|
||||
The Self-Hosted Data Plane deployment option requires an [Enterprise](plans.md) plan.
|
||||
|
||||
## Requirements
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ The Standalone Container deployment option is the least restrictive model for de
|
||||
|
||||
!!! warning
|
||||
|
||||
LangGraph Platform should not be deployed in serverless environments.
|
||||
LangGraph Platform should not be deployed in serverless environments. Scale to zero may cause task loss and scaling up will not work reliably.
|
||||
|
||||
## Architecture
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ search:
|
||||
|
||||
At its core, LangGraph models agent workflows as graphs. You define the behavior of your agents using three key components:
|
||||
|
||||
:::python
|
||||
1. [`State`](#state): A shared data structure that represents the current snapshot of your application. It can be any Python type, but is typically a `TypedDict` or Pydantic `BaseModel`.
|
||||
|
||||
2. [`Nodes`](#nodes): Python functions that encode the logic of your agents. They receive the current `State` as input, perform some computation or side-effect, and return an updated `State`.
|
||||
@@ -17,17 +16,6 @@ At its core, LangGraph models agent workflows as graphs. You define the behavior
|
||||
3. [`Edges`](#edges): Python functions that determine which `Node` to execute next based on the current `State`. They can be conditional branches or fixed transitions.
|
||||
|
||||
By composing `Nodes` and `Edges`, you can create complex, looping workflows that evolve the `State` over time. The real power, though, comes from how LangGraph manages that `State`. To emphasize: `Nodes` and `Edges` are nothing more than Python functions - they can contain an LLM or just good ol' Python code.
|
||||
:::
|
||||
|
||||
:::js
|
||||
1. [`State`](#state): A shared data structure that represents the current snapshot of your application. It can be any TypeScript type, but is typically a Zod schema or TypeScript interface.
|
||||
|
||||
2. [`Nodes`](#nodes): TypeScript functions that encode the logic of your agents. They receive the current `State` as input, perform some computation or side-effect, and return an updated `State`.
|
||||
|
||||
3. [`Edges`](#edges): TypeScript functions that determine which `Node` to execute next based on the current `State`. They can be conditional branches or fixed transitions.
|
||||
|
||||
By composing `Nodes` and `Edges`, you can create complex, looping workflows that evolve the `State` over time. The real power, though, comes from how LangGraph manages that `State`. To emphasize: `Nodes` and `Edges` are nothing more than TypeScript functions - they can contain an LLM or just good ol' TypeScript code.
|
||||
:::
|
||||
|
||||
In short: _nodes do the work, edges tell what to do next_.
|
||||
|
||||
@@ -43,53 +31,23 @@ The `StateGraph` class is the main graph class to use. This is parameterized by
|
||||
|
||||
To build your graph, you first define the [state](#state), you then add [nodes](#nodes) and [edges](#edges), and then you compile it. What exactly is compiling your graph and why is it needed?
|
||||
|
||||
Compiling is a pretty simple step. It provides a few basic checks on the structure of your graph (no orphaned nodes, etc). It is also where you can specify runtime args like [checkpointers](./persistence.md) and breakpoints.
|
||||
|
||||
:::python
|
||||
You compile your graph by just calling the `.compile` method:
|
||||
Compiling is a pretty simple step. It provides a few basic checks on the structure of your graph (no orphaned nodes, etc). It is also where you can specify runtime args like [checkpointers](./persistence.md) and breakpoints. You compile your graph by just calling the `.compile` method:
|
||||
|
||||
```python
|
||||
graph = graph_builder.compile(...)
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
You compile your graph by just calling the `.compile()` method:
|
||||
|
||||
```typescript
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("node1", node1)
|
||||
.addNode("node2", node2)
|
||||
.addEdge(START, "node1")
|
||||
.addEdge("node1", "node2")
|
||||
.addEdge("node2", END)
|
||||
.compile();
|
||||
```
|
||||
:::
|
||||
|
||||
You **MUST** compile your graph before you can use it.
|
||||
|
||||
## State
|
||||
|
||||
:::python
|
||||
The first thing you do when you define a graph is define the `State` of the graph. The `State` consists of the [schema of the graph](#schema) as well as [`reducer` functions](#reducers) which specify how to apply updates to the state. The schema of the `State` will be the input schema to all `Nodes` and `Edges` in the graph, and can be either a `TypedDict` or a `Pydantic` model. All `Nodes` will emit updates to the `State` which are then applied using the specified `reducer` function.
|
||||
:::
|
||||
|
||||
:::js
|
||||
The first thing you do when you define a graph is define the `State` of the graph. The `State` consists of the [schema of the graph](#schema) as well as [`reducer` functions](#reducers) which specify how to apply updates to the state. The schema of the `State` will be the input schema to all `Nodes` and `Edges` in the graph, and can be either a Zod schema or a TypeScript interface. All `Nodes` will emit updates to the `State` which are then applied using the specified `reducer` function.
|
||||
:::
|
||||
|
||||
### Schema
|
||||
|
||||
:::python
|
||||
The main documented way to specify the schema of a graph is by using `TypedDict`. However, we also support [using a Pydantic BaseModel](../how-tos/graph-api.ipynb#use-pydantic-models-for-graph-state) as your graph state to add **default values** and additional data validation.
|
||||
:::
|
||||
The main documented way to specify the schema of a graph is by using `TypedDict`. However, we also support [using a Pydantic BaseModel](../how-tos/graph-api.md#use-pydantic-models-for-graph-state) as your graph state to add **default values** and additional data validation.
|
||||
|
||||
:::js
|
||||
The main documented way to specify the schema of a graph is by using Zod schemas. However, we also support [using TypeScript interfaces](../how-tos/graph-api.ipynb#use-typescript-interfaces-for-graph-state) as your graph state to add **default values** and additional data validation.
|
||||
:::
|
||||
|
||||
By default, the graph will have the same input and output schemas. If you want to change this, you can also specify explicit input and output schemas directly. This is useful when you have a lot of keys, and some are explicitly for input and others for output. See the [guide here](../how-tos/graph-api.ipynb#define-input-and-output-schemas) for how to use.
|
||||
By default, the graph will have the same input and output schemas. If you want to change this, you can also specify explicit input and output schemas directly. This is useful when you have a lot of keys, and some are explicitly for input and others for output. See the [guide here](../how-tos/graph-api.md#define-input-and-output-schemas) for how to use.
|
||||
|
||||
#### Multiple schemas
|
||||
|
||||
@@ -98,13 +56,12 @@ Typically, all graph nodes communicate with a single schema. This means that the
|
||||
- Internal nodes can pass information that is not required in the graph's input / output.
|
||||
- We may also want to use different input / output schemas for the graph. The output might, for example, only contain a single relevant output key.
|
||||
|
||||
It is possible to have nodes write to private state channels inside the graph for internal node communication. We can simply define a private schema, `PrivateState`. See [this guide](../how-tos/graph-api.ipynb#pass-private-state-between-nodes) for more detail.
|
||||
It is possible to have nodes write to private state channels inside the graph for internal node communication. We can simply define a private schema, `PrivateState`. See [this guide](../how-tos/graph-api.md#pass-private-state-between-nodes) for more detail.
|
||||
|
||||
It is also possible to define explicit input and output schemas for a graph. In these cases, we define an "internal" schema that contains _all_ keys relevant to graph operations. But, we also define `input` and `output` schemas that are sub-sets of the "internal" schema to constrain the input and output of the graph. See [this guide](../how-tos/graph-api.ipynb#define-input-and-output-schemas) for more detail.
|
||||
It is also possible to define explicit input and output schemas for a graph. In these cases, we define an "internal" schema that contains _all_ keys relevant to graph operations. But, we also define `input` and `output` schemas that are sub-sets of the "internal" schema to constrain the input and output of the graph. See [this guide](../how-tos/graph-api.md#define-input-and-output-schemas) for more detail.
|
||||
|
||||
Let's look at an example:
|
||||
|
||||
:::python
|
||||
```python
|
||||
class InputState(TypedDict):
|
||||
user_input: str
|
||||
@@ -145,77 +102,12 @@ graph = builder.compile()
|
||||
graph.invoke({"user_input":"My"})
|
||||
{'graph_output': 'My name is Lance'}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { z } from "zod";
|
||||
|
||||
const InputState = z.object({
|
||||
userInput: z.string(),
|
||||
});
|
||||
|
||||
const OutputState = z.object({
|
||||
graphOutput: z.string(),
|
||||
});
|
||||
|
||||
const OverallState = z.object({
|
||||
foo: z.string(),
|
||||
userInput: z.string(),
|
||||
graphOutput: z.string(),
|
||||
});
|
||||
|
||||
const PrivateState = z.object({
|
||||
bar: z.string(),
|
||||
});
|
||||
|
||||
const node1 = (state: z.infer<typeof InputState>): Partial<z.infer<typeof OverallState>> => {
|
||||
// Write to OverallState
|
||||
return { foo: state.userInput + " name" };
|
||||
};
|
||||
|
||||
const node2 = (state: z.infer<typeof OverallState>): Partial<z.infer<typeof PrivateState>> => {
|
||||
// Read from OverallState, write to PrivateState
|
||||
return { bar: state.foo + " is" };
|
||||
};
|
||||
|
||||
const node3 = (state: z.infer<typeof PrivateState>): Partial<z.infer<typeof OutputState>> => {
|
||||
// Read from PrivateState, write to OutputState
|
||||
return { graphOutput: state.bar + " Lance" };
|
||||
};
|
||||
|
||||
const graph = new StateGraph({
|
||||
state: OverallState,
|
||||
input: InputState,
|
||||
output: OutputState,
|
||||
})
|
||||
.addNode("node1", node1)
|
||||
.addNode("node2", node2)
|
||||
.addNode("node3", node3)
|
||||
.addEdge(START, "node1")
|
||||
.addEdge("node1", "node2")
|
||||
.addEdge("node2", "node3")
|
||||
.addEdge("node3", END)
|
||||
.compile();
|
||||
|
||||
await graph.invoke({ userInput: "My" });
|
||||
// { graphOutput: 'My name is Lance' }
|
||||
```
|
||||
:::
|
||||
|
||||
There are two subtle and important points to note here:
|
||||
|
||||
:::python
|
||||
1. We pass `state: InputState` as the input schema to `node_1`. But, we write out to `foo`, a channel in `OverallState`. How can we write out to a state channel that is not included in the input schema? This is because a node _can write to any state channel in the graph state._ The graph state is the union of the state channels defined at initialization, which includes `OverallState` and the filters `InputState` and `OutputState`.
|
||||
|
||||
2. We initialize the graph with `StateGraph(OverallState,input_schema=InputState,output_schema=OutputState)`. So, how can we write to `PrivateState` in `node_2`? How does the graph gain access to this schema if it was not passed in the `StateGraph` initialization? We can do this because _nodes can also declare additional state channels_ as long as the state schema definition exists. In this case, the `PrivateState` schema is defined, so we can add `bar` as a new state channel in the graph and write to it.
|
||||
:::
|
||||
|
||||
:::js
|
||||
1. We pass `state: z.infer<typeof InputState>` as the input schema to `node1`. But, we write out to `foo`, a channel in `OverallState`. How can we write out to a state channel that is not included in the input schema? This is because a node _can write to any state channel in the graph state._ The graph state is the union of the state channels defined at initialization, which includes `OverallState` and the filters `InputState` and `OutputState`.
|
||||
|
||||
2. We initialize the graph with `new StateGraph({ state: OverallState, input: InputState, output: OutputState })`. So, how can we write to `PrivateState` in `node2`? How does the graph gain access to this schema if it was not passed in the `StateGraph` initialization? We can do this because _nodes can also declare additional state channels_ as long as the state schema definition exists. In this case, the `PrivateState` schema is defined, so we can add `bar` as a new state channel in the graph and write to it.
|
||||
:::
|
||||
|
||||
### Reducers
|
||||
|
||||
@@ -227,7 +119,6 @@ These two examples show how to use the default reducer:
|
||||
|
||||
**Example A:**
|
||||
|
||||
:::python
|
||||
```python
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
@@ -237,24 +128,9 @@ class State(TypedDict):
|
||||
```
|
||||
|
||||
In this example, no reducer functions are specified for any key. Let's assume the input to the graph is `{"foo": 1, "bar": ["hi"]}`. Let's then assume the first `Node` returns `{"foo": 2}`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{"foo": 2, "bar": ["hi"]}`. If the second node returns `{"bar": ["bye"]}` then the `State` would then be `{"foo": 2, "bar": ["bye"]}`
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({
|
||||
foo: z.number(),
|
||||
bar: z.array(z.string()),
|
||||
});
|
||||
```
|
||||
|
||||
In this example, no reducer functions are specified for any key. Let's assume the input to the graph is `{ foo: 1, bar: ["hi"] }`. Let's then assume the first `Node` returns `{ foo: 2 }`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{ foo: 2, bar: ["hi"] }`. If the second node returns `{ bar: ["bye"] }` then the `State` would then be `{ foo: 2, bar: ["bye"] }`
|
||||
:::
|
||||
|
||||
**Example B:**
|
||||
|
||||
:::python
|
||||
```python
|
||||
from typing import Annotated
|
||||
from typing_extensions import TypedDict
|
||||
@@ -266,51 +142,21 @@ class State(TypedDict):
|
||||
```
|
||||
|
||||
In this example, we've used the `Annotated` type to specify a reducer function (`operator.add`) for the second key (`bar`). Note that the first key remains unchanged. Let's assume the input to the graph is `{"foo": 1, "bar": ["hi"]}`. Let's then assume the first `Node` returns `{"foo": 2}`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{"foo": 2, "bar": ["hi"]}`. If the second node returns `{"bar": ["bye"]}` then the `State` would then be `{"foo": 2, "bar": ["hi", "bye"]}`. Notice here that the `bar` key is updated by adding the two lists together.
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { z } from "zod";
|
||||
import "@langchain/langgraph/zod";
|
||||
|
||||
const State = z.object({
|
||||
foo: z.number(),
|
||||
bar: z.array(z.string()).langgraph.reducer((x, y) => x.concat(y)),
|
||||
});
|
||||
```
|
||||
|
||||
In this example, we've used the `.langgraph.reducer()` method to specify a reducer function for the second key (`bar`). Note that the first key remains unchanged. Let's assume the input to the graph is `{ foo: 1, bar: ["hi"] }`. Let's then assume the first `Node` returns `{ foo: 2 }`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{ foo: 2, bar: ["hi"] }`. If the second node returns `{ bar: ["bye"] }` then the `State` would then be `{ foo: 2, bar: ["hi", "bye"] }`. Notice here that the `bar` key is updated by concatenating the two arrays together.
|
||||
:::
|
||||
|
||||
### Working with Messages in Graph State
|
||||
|
||||
#### Why use messages?
|
||||
|
||||
:::python
|
||||
Most modern LLM providers have a chat model interface that accepts a list of messages as input. LangChain's [`ChatModel`](https://python.langchain.com/docs/concepts/#chat-models) in particular accepts a list of `Message` objects as inputs. These messages come in a variety of forms such as `HumanMessage` (user input) or `AIMessage` (LLM response). To read more about what message objects are, please refer to [this](https://python.langchain.com/docs/concepts/#messages) conceptual guide.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Most modern LLM providers have a chat model interface that accepts a list of messages as input. LangChain's [`ChatModel`](https://js.langchain.com/docs/concepts/#chat-models) in particular accepts a list of `Message` objects as inputs. These messages come in a variety of forms such as `HumanMessage` (user input) or `AIMessage` (LLM response). To read more about what message objects are, please refer to [this](https://js.langchain.com/docs/concepts/#messages) conceptual guide.
|
||||
:::
|
||||
|
||||
#### Using Messages in your Graph
|
||||
|
||||
:::python
|
||||
In many cases, it is helpful to store prior conversation history as a list of messages in your graph state. To do so, we can add a key (channel) to the graph state that stores a list of `Message` objects and annotate it with a reducer function (see `messages` key in the example below). The reducer function is vital to telling the graph how to update the list of `Message` objects in the state with each state update (for example, when a node sends an update). If you don't specify a reducer, every state update will overwrite the list of messages with the most recently provided value. If you wanted to simply append messages to the existing list, you could use `operator.add` as a reducer.
|
||||
|
||||
However, you might also want to manually update messages in your graph state (e.g. human-in-the-loop). If you were to use `operator.add`, the manual state updates you send to the graph would be appended to the existing list of messages, instead of updating existing messages. To avoid that, you need a reducer that can keep track of message IDs and overwrite existing messages, if updated. To achieve this, you can use the prebuilt `add_messages` function. For brand new messages, it will simply append to existing list, but it will also handle the updates for existing messages correctly.
|
||||
:::
|
||||
|
||||
:::js
|
||||
In many cases, it is helpful to store prior conversation history as a list of messages in your graph state. To do so, we can add a key (channel) to the graph state that stores a list of `Message` objects and annotate it with a reducer function (see `messages` key in the example below). The reducer function is vital to telling the graph how to update the list of `Message` objects in the state with each state update (for example, when a node sends an update). If you don't specify a reducer, every state update will overwrite the list of messages with the most recently provided value. If you wanted to simply append messages to the existing list, you could use `(x, y) => x.concat(y)` as a reducer.
|
||||
|
||||
However, you might also want to manually update messages in your graph state (e.g. human-in-the-loop). If you were to use `(x, y) => x.concat(y)`, the manual state updates you send to the graph would be appended to the existing list of messages, instead of updating existing messages. To avoid that, you need a reducer that can keep track of message IDs and overwrite existing messages, if updated. To achieve this, you can use the prebuilt `messagesStateReducer` function. For brand new messages, it will simply append to existing list, but it will also handle the updates for existing messages correctly.
|
||||
:::
|
||||
|
||||
#### Serialization
|
||||
|
||||
:::python
|
||||
In addition to keeping track of message IDs, the `add_messages` function will also try to deserialize messages into LangChain `Message` objects whenever a state update is received on the `messages` channel. See more information on LangChain serialization/deserialization [here](https://python.langchain.com/docs/how_to/serialization/). This allows sending graph inputs / state updates in the following format:
|
||||
|
||||
```python
|
||||
@@ -332,36 +178,9 @@ from typing_extensions import TypedDict
|
||||
class GraphState(TypedDict):
|
||||
messages: Annotated[list[AnyMessage], add_messages]
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
In addition to keeping track of message IDs, the `messagesStateReducer` function will also try to deserialize messages into LangChain `Message` objects whenever a state update is received on the `messages` channel. See more information on LangChain serialization/deserialization [here](https://js.langchain.com/docs/how_to/serialization/). This allows sending graph inputs / state updates in the following format:
|
||||
|
||||
```typescript
|
||||
// this is supported
|
||||
{ messages: [new HumanMessage("message")] }
|
||||
|
||||
// and this is also supported
|
||||
{ messages: [{ role: "human", content: "message" }] }
|
||||
```
|
||||
|
||||
Since the state updates are always deserialized into LangChain `Messages` when using `messagesStateReducer`, you should use dot notation to access message attributes, like `state.messages[state.messages.length - 1].content`. Below is an example of a graph that uses `messagesStateReducer` as its reducer function.
|
||||
|
||||
```typescript
|
||||
import { BaseMessage } from "@langchain/core/messages";
|
||||
import { messagesStateReducer } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
import "@langchain/langgraph/zod";
|
||||
|
||||
const GraphState = z.object({
|
||||
messages: z.array(z.any()).langgraph.reducer(messagesStateReducer),
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
#### MessagesState
|
||||
|
||||
:::python
|
||||
Since having a list of messages in your state is so common, there exists a prebuilt state called `MessagesState` which makes it easy to use messages. `MessagesState` is defined with a single `messages` key which is a list of `AnyMessage` objects and uses the `add_messages` reducer. Typically, there is more state to track than just messages, so we see people subclass this state and add more fields, like:
|
||||
|
||||
```python
|
||||
@@ -370,25 +189,9 @@ from langgraph.graph import MessagesState
|
||||
class State(MessagesState):
|
||||
documents: list[str]
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
Since having a list of messages in your state is so common, there exists a prebuilt state called `MessagesZodState` which makes it easy to use messages. `MessagesZodState` is defined with a single `messages` key which is a list of `BaseMessage` objects and uses the `messagesStateReducer` reducer. Typically, there is more state to track than just messages, so we see people merge this state with other schemas, like:
|
||||
|
||||
```typescript
|
||||
import { MessagesZodState } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({
|
||||
messages: MessagesZodState.shape.messages,
|
||||
documents: z.array(z.string()),
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
## Nodes
|
||||
|
||||
:::python
|
||||
In LangGraph, nodes are typically python functions (sync or async) where the **first** positional argument is the [state](#state), and (optionally), the **second** positional argument is a "config", containing optional [configurable parameters](#configuration) (such as a `thread_id`).
|
||||
|
||||
Similar to `NetworkX`, you add these nodes to a graph using the [add_node][langgraph.graph.StateGraph.add_node] method:
|
||||
@@ -429,88 +232,26 @@ If you add a node to a graph without specifying a name, it will be given a defau
|
||||
builder.add_node(my_node)
|
||||
# You can then create edges to/from this node by referencing it as `"my_node"`
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
In LangGraph, nodes are typically TypeScript functions (sync or async) where the **first** positional argument is the [state](#state), and (optionally), the **second** positional argument is a "config", containing optional [configurable parameters](#configuration) (such as a `thread_id`).
|
||||
|
||||
Similar to `NetworkX`, you add these nodes to a graph using the `addNode` method:
|
||||
|
||||
```typescript
|
||||
import { z } from "zod";
|
||||
import { RunnableConfig } from "@langchain/core/runnables";
|
||||
import { StateGraph } from "@langchain/langgraph";
|
||||
|
||||
const State = z.object({
|
||||
input: z.string(),
|
||||
results: z.string(),
|
||||
});
|
||||
|
||||
const myNode = (state: z.infer<typeof State>, config: RunnableConfig) => {
|
||||
console.log("In node: ", config?.configurable?.user_id);
|
||||
return { results: `Hello, ${state.input}!` };
|
||||
};
|
||||
|
||||
// The second argument is optional
|
||||
const myOtherNode = (state: z.infer<typeof State>) => {
|
||||
return state;
|
||||
};
|
||||
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("myNode", myNode)
|
||||
.addNode("otherNode", myOtherNode)
|
||||
// ...
|
||||
.compile();
|
||||
```
|
||||
|
||||
Behind the scenes, functions are converted to [RunnableLambda](https://js.langchain.com/docs/concepts/#runnable-lambda)s, which add batch and async support to your function, along with native tracing and debugging.
|
||||
:::
|
||||
|
||||
### `START` Node
|
||||
|
||||
The `START` Node is a special node that represents the node that sends user input to the graph. The main purpose for referencing this node is to determine which nodes should be called first.
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.graph import START
|
||||
|
||||
graph.add_edge(START, "node_a")
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { START } from "@langchain/langgraph";
|
||||
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("nodeA", nodeA)
|
||||
.addEdge(START, "nodeA")
|
||||
.compile();
|
||||
```
|
||||
:::
|
||||
|
||||
### `END` Node
|
||||
|
||||
The `END` Node is a special node that represents a terminal node. This node is referenced when you want to denote which edges have no actions after they are done.
|
||||
|
||||
:::python
|
||||
```
|
||||
from langgraph.graph import END
|
||||
|
||||
graph.add_edge("node_a", END)
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { END } from "@langchain/langgraph";
|
||||
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("nodeA", nodeA)
|
||||
.addEdge("nodeA", END)
|
||||
.compile();
|
||||
```
|
||||
:::
|
||||
|
||||
### Node Caching
|
||||
|
||||
@@ -523,7 +264,6 @@ LangGraph supports caching of tasks/nodes based on the input to the node. To use
|
||||
|
||||
For example:
|
||||
|
||||
:::python
|
||||
```py
|
||||
import time
|
||||
from typing_extensions import TypedDict
|
||||
@@ -560,40 +300,6 @@ print(graph.invoke({"x": 5}, stream_mode='updates')) # (2)!
|
||||
|
||||
1. First run takes the full second to run (due to mocked expensive computation).
|
||||
2. Second run utilizes cache and returns quickly.
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { z } from "zod";
|
||||
import { StateGraph } from "@langchain/langgraph";
|
||||
import { InMemoryCache } from "@langchain/langgraph";
|
||||
|
||||
const State = z.object({
|
||||
x: z.number(),
|
||||
result: z.number(),
|
||||
});
|
||||
|
||||
const expensiveNode = (state: z.infer<typeof State>) => {
|
||||
// expensive computation
|
||||
return { result: state.x * 2 };
|
||||
};
|
||||
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("expensiveNode", expensiveNode, {
|
||||
cachePolicy: { ttl: 3 }
|
||||
})
|
||||
.addEdge("__start__", "expensiveNode")
|
||||
.compile({ cache: new InMemoryCache() });
|
||||
|
||||
console.log(await graph.invoke({ x: 5 }, { streamMode: "updates" })); // (1)!
|
||||
// [{ expensiveNode: { result: 10 } }]
|
||||
console.log(await graph.invoke({ x: 5 }, { streamMode: "updates" })); // (2)!
|
||||
// [{ expensiveNode: { result: 10 }, __metadata__: { cached: true } }]
|
||||
```
|
||||
|
||||
1. First run takes the full computation time.
|
||||
2. Second run utilizes cache and returns quickly.
|
||||
:::
|
||||
|
||||
## Edges
|
||||
|
||||
@@ -608,29 +314,14 @@ A node can have MULTIPLE outgoing edges. If a node has multiple out-going edges,
|
||||
|
||||
### Normal Edges
|
||||
|
||||
:::python
|
||||
If you **always** want to go from node A to node B, you can use the [add_edge][langgraph.graph.StateGraph.add_edge] method directly.
|
||||
|
||||
```python
|
||||
graph.add_edge("node_a", "node_b")
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
If you **always** want to go from node A to node B, you can use the `addEdge` method directly.
|
||||
|
||||
```typescript
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("nodeA", nodeA)
|
||||
.addNode("nodeB", nodeB)
|
||||
.addEdge("nodeA", "nodeB")
|
||||
.compile();
|
||||
```
|
||||
:::
|
||||
|
||||
### Conditional Edges
|
||||
|
||||
:::python
|
||||
If you want to **optionally** route to 1 or more edges (or optionally terminate), you can use the [add_conditional_edges][langgraph.graph.StateGraph.add_conditional_edges] method. This method accepts the name of a node and a "routing function" to call after that node is executed:
|
||||
|
||||
```python
|
||||
@@ -646,45 +337,12 @@ You can optionally provide a dictionary that maps the `routing_function`'s outpu
|
||||
```python
|
||||
graph.add_conditional_edges("node_a", routing_function, {True: "node_b", False: "node_c"})
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
If you want to **optionally** route to 1 or more edges (or optionally terminate), you can use the `addConditionalEdges` method. This method accepts the name of a node and a "routing function" to call after that node is executed:
|
||||
|
||||
```typescript
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("nodeA", nodeA)
|
||||
.addNode("nodeB", nodeB)
|
||||
.addNode("nodeC", nodeC)
|
||||
.addConditionalEdges("nodeA", routingFunction)
|
||||
.compile();
|
||||
```
|
||||
|
||||
Similar to nodes, the `routingFunction` accepts the current `state` of the graph and returns a value.
|
||||
|
||||
By default, the return value `routingFunction` is used as the name of the node (or list of nodes) to send the state to next. All those nodes will be run in parallel as a part of the next superstep.
|
||||
|
||||
You can optionally provide a dictionary that maps the `routingFunction`'s output to the name of the next node.
|
||||
|
||||
```typescript
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("nodeA", nodeA)
|
||||
.addNode("nodeB", nodeB)
|
||||
.addNode("nodeC", nodeC)
|
||||
.addConditionalEdges("nodeA", routingFunction, {
|
||||
true: "nodeB",
|
||||
false: "nodeC",
|
||||
})
|
||||
.compile();
|
||||
```
|
||||
:::
|
||||
|
||||
!!! tip
|
||||
Use [`Command`](#command) instead of conditional edges if you want to combine state updates and routing in a single function.
|
||||
|
||||
### Entry Point
|
||||
|
||||
:::python
|
||||
The entry point is the first node(s) that are run when the graph starts. You can use the [`add_edge`][langgraph.graph.StateGraph.add_edge] method from the virtual [`START`][langgraph.constants.START] node to the first node to execute to specify where to enter the graph.
|
||||
|
||||
```python
|
||||
@@ -692,24 +350,9 @@ from langgraph.graph import START
|
||||
|
||||
graph.add_edge(START, "node_a")
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
The entry point is the first node(s) that are run when the graph starts. You can use the `addEdge` method from the virtual `START` node to the first node to execute to specify where to enter the graph.
|
||||
|
||||
```typescript
|
||||
import { START } from "@langchain/langgraph";
|
||||
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("nodeA", nodeA)
|
||||
.addEdge(START, "nodeA")
|
||||
.compile();
|
||||
```
|
||||
:::
|
||||
|
||||
### Conditional Entry Point
|
||||
|
||||
:::python
|
||||
A conditional entry point lets you start at different nodes depending on custom logic. You can use [`add_conditional_edges`][langgraph.graph.StateGraph.add_conditional_edges] from the virtual [`START`][langgraph.constants.START] node to accomplish this.
|
||||
|
||||
```python
|
||||
@@ -723,42 +366,11 @@ You can optionally provide a dictionary that maps the `routing_function`'s outpu
|
||||
```python
|
||||
graph.add_conditional_edges(START, routing_function, {True: "node_b", False: "node_c"})
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
A conditional entry point lets you start at different nodes depending on custom logic. You can use `addConditionalEdges` from the virtual `START` node to accomplish this.
|
||||
|
||||
```typescript
|
||||
import { START } from "@langchain/langgraph";
|
||||
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("nodeB", nodeB)
|
||||
.addNode("nodeC", nodeC)
|
||||
.addConditionalEdges(START, routingFunction)
|
||||
.compile();
|
||||
```
|
||||
|
||||
You can optionally provide a dictionary that maps the `routingFunction`'s output to the name of the next node.
|
||||
|
||||
```typescript
|
||||
import { START } from "@langchain/langgraph";
|
||||
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("nodeB", nodeB)
|
||||
.addNode("nodeC", nodeC)
|
||||
.addConditionalEdges(START, routingFunction, {
|
||||
true: "nodeB",
|
||||
false: "nodeC",
|
||||
})
|
||||
.compile();
|
||||
```
|
||||
:::
|
||||
|
||||
## `Send`
|
||||
|
||||
By default, `Nodes` and `Edges` are defined ahead of time and operate on the same shared state. However, there can be cases where the exact edges are not known ahead of time and/or you may want different versions of `State` to exist at the same time. A common example of this is with [map-reduce](https://langchain-ai.github.io/langgraph/how-tos/map-reduce/) design patterns. In this design pattern, a first node may generate a list of objects, and you may want to apply some other node to all those objects. The number of objects may be unknown ahead of time (meaning the number of edges may not be known) and the input `State` to the downstream `Node` should be different (one for each generated object).
|
||||
|
||||
:::python
|
||||
To support this design pattern, LangGraph supports returning [`Send`][langgraph.types.Send] objects from conditional edges. `Send` takes two arguments: first is the name of the node, and second is the state to pass to that node.
|
||||
|
||||
```python
|
||||
@@ -767,31 +379,11 @@ def continue_to_jokes(state: OverallState):
|
||||
|
||||
graph.add_conditional_edges("node_a", continue_to_jokes)
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
To support this design pattern, LangGraph supports returning `Send` objects from conditional edges. `Send` takes two arguments: first is the name of the node, and second is the state to pass to that node.
|
||||
|
||||
```typescript
|
||||
import { Send } from "@langchain/langgraph";
|
||||
|
||||
const continueToJokes = (state: OverallState) => {
|
||||
return state.subjects.map((subject) => new Send("generateJoke", { subject }));
|
||||
};
|
||||
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("nodeA", nodeA)
|
||||
.addNode("generateJoke", generateJoke)
|
||||
.addConditionalEdges("nodeA", continueToJokes)
|
||||
.compile();
|
||||
```
|
||||
:::
|
||||
|
||||
## `Command`
|
||||
|
||||
It can be useful to combine control flow (edges) and state updates (nodes). For example, you might want to BOTH perform state updates AND decide which node to go to next in the SAME node. LangGraph provides a way to do so by returning a [`Command`][langgraph.types.Command] object from node functions:
|
||||
|
||||
:::python
|
||||
```python
|
||||
def my_node(state: State) -> Command[Literal["my_other_node"]]:
|
||||
return Command(
|
||||
@@ -801,71 +393,20 @@ def my_node(state: State) -> Command[Literal["my_other_node"]]:
|
||||
goto="my_other_node"
|
||||
)
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
const myNode = (state: State): Command => {
|
||||
return new Command({
|
||||
// state update
|
||||
update: { foo: "bar" },
|
||||
// control flow
|
||||
goto: "myOtherNode",
|
||||
});
|
||||
};
|
||||
```
|
||||
:::
|
||||
|
||||
With `Command` you can also achieve dynamic control flow behavior (identical to [conditional edges](#conditional-edges)):
|
||||
|
||||
:::python
|
||||
```python
|
||||
def my_node(state: State) -> Command[Literal["my_other_node"]]:
|
||||
if state["foo"] == "bar":
|
||||
return Command(update={"foo": "baz"}, goto="my_other_node")
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
const myNode = (state: State): Command => {
|
||||
if (state.foo === "bar") {
|
||||
return new Command({
|
||||
update: { foo: "baz" },
|
||||
goto: "myOtherNode",
|
||||
});
|
||||
}
|
||||
return new Command({
|
||||
update: { foo: "qux" },
|
||||
goto: "myThirdNode",
|
||||
});
|
||||
};
|
||||
```
|
||||
:::
|
||||
|
||||
!!! important
|
||||
|
||||
:::python
|
||||
When returning `Command` in your node functions, you must add return type annotations with the list of node names the node is routing to, e.g. `Command[Literal["my_other_node"]]`. This is necessary for the graph rendering and tells LangGraph that `my_node` can navigate to `my_other_node`.
|
||||
:::
|
||||
|
||||
:::js
|
||||
When returning `Command` in your node functions, you must specify the `ends` option when adding the node to ensure proper graph rendering and tell LangGraph which nodes this node can navigate to.
|
||||
|
||||
```typescript
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("myNode", myNode, {
|
||||
ends: ["myOtherNode", "myThirdNode"],
|
||||
})
|
||||
.compile();
|
||||
```
|
||||
:::
|
||||
|
||||
Check out this [how-to guide](../how-tos/graph-api.ipynb#combine-control-flow-and-state-updates-with-command) for an end-to-end example of how to use `Command`.
|
||||
Check out this [how-to guide](../how-tos/graph-api.md#combine-control-flow-and-state-updates-with-command) for an end-to-end example of how to use `Command`.
|
||||
|
||||
### When should I use Command instead of conditional edges?
|
||||
|
||||
@@ -877,7 +418,6 @@ Use [conditional edges](#conditional-edges) to route between nodes conditionally
|
||||
|
||||
If you are using [subgraphs](./subgraphs.md), you might want to navigate from a node within a subgraph to a different subgraph (i.e. a different node in the parent graph). To do so, you can specify `graph=Command.PARENT` in `Command`:
|
||||
|
||||
:::python
|
||||
```python
|
||||
def my_node(state: State) -> Command[Literal["other_subgraph"]]:
|
||||
return Command(
|
||||
@@ -886,21 +426,6 @@ def my_node(state: State) -> Command[Literal["other_subgraph"]]:
|
||||
graph=Command.PARENT
|
||||
)
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
const myNode = (state: State): Command => {
|
||||
return new Command({
|
||||
update: { foo: "bar" },
|
||||
goto: "otherSubgraph", // where `otherSubgraph` is a node in the parent graph
|
||||
graph: Command.PARENT,
|
||||
});
|
||||
};
|
||||
```
|
||||
:::
|
||||
|
||||
!!! note
|
||||
|
||||
@@ -908,27 +433,21 @@ const myNode = (state: State): Command => {
|
||||
|
||||
!!! important "State updates with `Command.PARENT`"
|
||||
|
||||
When you send updates from a subgraph node to a parent graph node for a key that's shared by both parent and subgraph [state schemas](#schema), you **must** define a [reducer](#reducers) for the key you're updating in the parent graph state. See this [example](../how-tos/graph-api.ipynb#navigate-to-a-node-in-a-parent-graph).
|
||||
When you send updates from a subgraph node to a parent graph node for a key that's shared by both parent and subgraph [state schemas](#schema), you **must** define a [reducer](#reducers) for the key you're updating in the parent graph state. See this [example](../how-tos/graph-api.md#navigate-to-a-node-in-a-parent-graph).
|
||||
|
||||
This is particularly useful when implementing [multi-agent handoffs](./multi_agent.md#handoffs).
|
||||
|
||||
Check out [this guide](../how-tos/graph-api.ipynb#navigate-to-a-node-in-a-parent-graph) for detail.
|
||||
Check out [this guide](../how-tos/graph-api.md#navigate-to-a-node-in-a-parent-graph) for detail.
|
||||
|
||||
### Using inside tools
|
||||
|
||||
A common use case is updating graph state from inside a tool. For example, in a customer support application you might want to look up customer information based on their account number or ID in the beginning of the conversation.
|
||||
|
||||
Refer to [this guide](../how-tos/graph-api.ipynb#use-inside-tools) for detail.
|
||||
Refer to [this guide](../how-tos/graph-api.md#use-inside-tools) for detail.
|
||||
|
||||
### Human-in-the-loop
|
||||
|
||||
:::python
|
||||
`Command` is an important part of human-in-the-loop workflows: when using `interrupt()` to collect user input, `Command` is then used to supply the input and resume execution via `Command(resume="User input")`. Check out [this conceptual guide](./human_in_the_loop.md) for more information.
|
||||
:::
|
||||
|
||||
:::js
|
||||
`Command` is an important part of human-in-the-loop workflows: when using `interrupt()` to collect user input, `Command` is then used to supply the input and resume execution via `new Command({ resume: "User input" })`. Check out [this conceptual guide](./human_in_the_loop.md) for more information.
|
||||
:::
|
||||
|
||||
## Graph Migrations
|
||||
|
||||
@@ -944,7 +463,6 @@ LangGraph can easily handle migrations of graph definitions (nodes, edges, and s
|
||||
|
||||
When creating a graph, you can also mark that certain parts of the graph are configurable. This is commonly done to enable easily switching between models or system prompts. This allows you to create a single "cognitive architecture" (the graph) but have multiple different instance of it.
|
||||
|
||||
:::python
|
||||
You can optionally specify a `config_schema` when creating a graph.
|
||||
|
||||
```python
|
||||
@@ -970,69 +488,19 @@ def node_a(state, config):
|
||||
llm = get_llm(llm_type)
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
You can optionally specify a `configSchema` when creating a graph.
|
||||
|
||||
```typescript
|
||||
import { z } from "zod";
|
||||
|
||||
const ConfigSchema = z.object({
|
||||
llm: z.string(),
|
||||
});
|
||||
|
||||
const graph = new StateGraph(State, ConfigSchema)
|
||||
.addNode("nodeA", nodeA)
|
||||
.compile();
|
||||
```
|
||||
|
||||
You can then pass this configuration into the graph using the `configurable` config field.
|
||||
|
||||
```typescript
|
||||
const config = { configurable: { llm: "anthropic" } };
|
||||
|
||||
await graph.invoke(inputs, config);
|
||||
```
|
||||
|
||||
You can then access and use this configuration inside a node or conditional edge:
|
||||
|
||||
```typescript
|
||||
const nodeA = (state: State, config: RunnableConfig) => {
|
||||
const llmType = config?.configurable?.llm || "openai";
|
||||
const llm = getLlm(llmType);
|
||||
// ...
|
||||
};
|
||||
```
|
||||
:::
|
||||
|
||||
See [this guide](../how-tos/graph-api.ipynb#add-runtime-configuration) for a full breakdown on configuration.
|
||||
See [this guide](../how-tos/graph-api.md#add-runtime-configuration) for a full breakdown on configuration.
|
||||
|
||||
### Recursion Limit
|
||||
|
||||
The recursion limit sets the maximum number of [super-steps](#graphs) the graph can execute during a single execution. Once the limit is reached, LangGraph will raise `GraphRecursionError`. By default this value is set to 25 steps.
|
||||
|
||||
:::python
|
||||
The recursion limit can be set on any graph at runtime, and is passed to `.invoke`/`.stream` via the config dictionary. Importantly, `recursion_limit` is a standalone `config` key and should not be passed inside the `configurable` key as all other user-defined configuration. See the example below:
|
||||
The recursion limit sets the maximum number of [super-steps](#graphs) the graph can execute during a single execution. Once the limit is reached, LangGraph will raise `GraphRecursionError`. By default this value is set to 25 steps. The recursion limit can be set on any graph at runtime, and is passed to `.invoke`/`.stream` via the config dictionary. Importantly, `recursion_limit` is a standalone `config` key and should not be passed inside the `configurable` key as all other user-defined configuration. See the example below:
|
||||
|
||||
```python
|
||||
graph.invoke(inputs, config={"recursion_limit": 5, "configurable":{"llm": "anthropic"}})
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
The recursion limit can be set on any graph at runtime, and is passed to `.invoke`/`.stream` via the config dictionary. Importantly, `recursionLimit` is a standalone `config` key and should not be passed inside the `configurable` key as all other user-defined configuration. See the example below:
|
||||
|
||||
```typescript
|
||||
await graph.invoke(inputs, {
|
||||
recursionLimit: 5,
|
||||
configurable: { llm: "anthropic" },
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
Read [this how-to](https://langchain-ai.github.io/langgraph/how-tos/recursion-limit/) to learn more about how the recursion limit works.
|
||||
|
||||
## Visualization
|
||||
|
||||
It's often nice to be able to visualize graphs, especially as they get more complex. LangGraph comes with several built-in ways to visualize graphs. See [this how-to guide](../how-tos/graph-api.ipynb#visualize-your-graph) for more info.
|
||||
It's often nice to be able to visualize graphs, especially as they get more complex. LangGraph comes with several built-in ways to visualize graphs. See [this how-to guide](../how-tos/graph-api.md#visualize-your-graph) for more info.
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# MCP
|
||||
|
||||
[Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol that standardizes how applications provide tools and context to language models. LangGraph agents can use tools defined on MCP servers through the `langchain-mcp-adapters` library.
|
||||
|
||||

|
||||
|
||||
Install the `langchain-mcp-adapters` library to use MCP tools in LangGraph:
|
||||
|
||||
```bash
|
||||
pip install langchain-mcp-adapters
|
||||
```
|
||||
|
||||
## Authenticate to an MCP server
|
||||
|
||||
You can set up [custom authentication middleware](../how-tos/auth/custom_auth.md) to authenticate a user with an MCP server to get access to user-scoped tools within your LangGraph Platform deployment.
|
||||
|
||||
!!! note
|
||||
Custom authentication is a LangGraph Platform feature.
|
||||
|
||||
An example architecture for this flow:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
%% Actors
|
||||
participant ClientApp as Client
|
||||
participant AuthProv as Auth Provider
|
||||
participant LangGraph as LangGraph Backend
|
||||
participant SecretStore as Secret Store
|
||||
participant MCPServer as MCP Server
|
||||
|
||||
%% Platform login / AuthN
|
||||
ClientApp ->> AuthProv: 1. Login (username / password)
|
||||
AuthProv -->> ClientApp: 2. Return token
|
||||
ClientApp ->> LangGraph: 3. Request with token
|
||||
|
||||
Note over LangGraph: 4. Validate token (@auth.authenticate)
|
||||
LangGraph -->> AuthProv: 5. Fetch user info
|
||||
AuthProv -->> LangGraph: 6. Confirm validity
|
||||
|
||||
%% Fetch user tokens from secret store
|
||||
LangGraph ->> SecretStore: 6a. Fetch user tokens
|
||||
SecretStore -->> LangGraph: 6b. Return tokens
|
||||
|
||||
Note over LangGraph: 7. Apply access control (@auth.on.*)
|
||||
|
||||
%% MCP round-trip
|
||||
Note over LangGraph: 8. Build MCP client with user token
|
||||
LangGraph ->> MCPServer: 9. Call MCP tool (with header)
|
||||
Note over MCPServer: 10. MCP validates header and runs tool
|
||||
MCPServer -->> LangGraph: 11. Tool response
|
||||
|
||||
%% Return to caller
|
||||
LangGraph -->> ClientApp: 12. Return resources / tool output
|
||||
```
|
||||
|
||||
For more information, see [MCP endpoint in LangGraph Server](../concepts/server-mcp.md#use-user-scoped-mcp-tools-in-your-deployment).
|
||||
|
||||
@@ -26,7 +26,7 @@ The primary benefits of using multi-agent systems are:
|
||||
There are several ways to connect agents in a multi-agent system:
|
||||
|
||||
- **Network**: each agent can communicate with [every other agent](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/multi-agent-collaboration/). Any agent can decide which other agent to call next.
|
||||
- **Supervisor**: each agent communicates with a single [supervisor](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/agent_supervisor/) agent. Supervisor agent makes decisions on which agent should be called next.
|
||||
- **Supervisor**: each agent communicates with a single [supervisor](../tutorials/multi_agent/agent_supervisor.md) agent. Supervisor agent makes decisions on which agent should be called next.
|
||||
- **Supervisor (tool-calling)**: this is a special case of supervisor architecture. Individual agents can be represented as tools. In this case, a supervisor agent uses a tool-calling LLM to decide which of the agent tools to call, as well as the arguments to pass to those agents.
|
||||
- **Hierarchical**: you can define a multi-agent system with [a supervisor of supervisors](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/hierarchical_agent_teams/). This is a generalization of the supervisor architecture and allows for more complex control flows.
|
||||
- **Custom multi-agent workflow**: each agent communicates with only a subset of agents. Parts of the flow are deterministic, and only some agents can decide which other agents to call next.
|
||||
@@ -166,7 +166,7 @@ network = builder.compile()
|
||||
|
||||
### Supervisor
|
||||
|
||||
In this architecture, we define agents as nodes and add a supervisor node (LLM) that decides which agent nodes should be called next. We use [`Command`](./low_level.md#command) to route execution to the appropriate agent node based on supervisor's decision. This architecture also lends itself well to running multiple agents in parallel or using [map-reduce](../how-tos/graph-api.ipynb#map-reduce-and-the-send-api) pattern.
|
||||
In this architecture, we define agents as nodes and add a supervisor node (LLM) that decides which agent nodes should be called next. We use [`Command`](./low_level.md#command) to route execution to the appropriate agent node based on supervisor's decision. This architecture also lends itself well to running multiple agents in parallel or using [map-reduce](../how-tos/graph-api.md#map-reduce-and-the-send-api) pattern.
|
||||
|
||||
```python
|
||||
from typing import Literal
|
||||
@@ -211,7 +211,7 @@ builder.add_edge(START, "supervisor")
|
||||
supervisor = builder.compile()
|
||||
```
|
||||
|
||||
Check out this [tutorial](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/agent_supervisor/) for an example of supervisor multi-agent architecture.
|
||||
Check out this [tutorial](../tutorials/multi_agent/agent_supervisor.md) for an example of supervisor multi-agent architecture.
|
||||
|
||||
### Supervisor (tool-calling)
|
||||
|
||||
@@ -414,5 +414,5 @@ There are two high-level approaches to achieve that:
|
||||
|
||||
An agent might need to have a different state schema from the rest of the agents. For example, a search agent might only need to keep track of queries and retrieved documents. There are two ways to achieve this in LangGraph:
|
||||
|
||||
- Define [subgraph](./subgraphs.md) agents with a separate state schema. If there are no shared state keys (channels) between the subgraph and the parent graph, it’s important to [add input / output transformations](../how-tos/subgraph.ipynb#different-state-schemas) so that the parent graph knows how to communicate with the subgraphs.
|
||||
- Define agent node functions with a [private input state schema](../how-tos/graph-api.ipynb/#pass-private-state-between-nodes) that is distinct from the overall graph state schema. This allows passing information that is only needed for executing that particular agent.
|
||||
- Define [subgraph](./subgraphs.md) agents with a separate state schema. If there are no shared state keys (channels) between the subgraph and the parent graph, it’s important to [add input / output transformations](../how-tos/subgraph.md#different-state-schemas) so that the parent graph knows how to communicate with the subgraphs.
|
||||
- Define agent node functions with a [private input state schema](../how-tos/graph-api.md/#pass-private-state-between-nodes) that is distinct from the overall graph state schema. This allows passing information that is only needed for executing that particular agent.
|
||||
|
||||
@@ -5,7 +5,7 @@ search:
|
||||
|
||||
# Persistence
|
||||
|
||||
LangGraph has a built-in persistence layer, implemented through checkpointers. When you compile graph with a checkpointer, the checkpointer saves a `checkpoint` of the graph state at every super-step. Those checkpoints are saved to a `thread`, which can be accessed after graph execution. Because `threads` allow access to graph's state after execution, several powerful capabilities including human-in-the-loop, memory, time travel, and fault-tolerance are all possible. Below, we'll discuss each of these concepts in more detail.
|
||||
LangGraph has a built-in persistence layer, implemented through checkpointers. When you compile a graph with a checkpointer, the checkpointer saves a `checkpoint` of the graph state at every super-step. Those checkpoints are saved to a `thread`, which can be accessed after graph execution. Because `threads` allow access to graph's state after execution, several powerful capabilities including human-in-the-loop, memory, time travel, and fault-tolerance are all possible. Below, we'll discuss each of these concepts in more detail.
|
||||
|
||||

|
||||
|
||||
@@ -78,7 +78,7 @@ After we run the graph, we expect to see exactly 4 checkpoints:
|
||||
* checkpoint with the outputs of `node_a` `{'foo': 'a', 'bar': ['a']}` and `node_b` as the next node to be executed
|
||||
* checkpoint with the outputs of `node_b` `{'foo': 'b', 'bar': ['a', 'b']}` and no next nodes to be executed
|
||||
|
||||
Note that we `bar` channel values contain outputs from both nodes as we have a reducer for `bar` channel.
|
||||
Note that the `bar` channel values contain outputs from both nodes as we have a reducer for `bar` channel.
|
||||
|
||||
### Get state
|
||||
|
||||
|
||||
@@ -8,8 +8,7 @@ hide:
|
||||
|
||||
# MCP endpoint in LangGraph Server
|
||||
|
||||
The **Model Context Protocol (MCP)** is an open protocol for describing tools and data sources in a model-agnostic format, enabling LLMs to discover
|
||||
and use them via a structured API.
|
||||
The [Model Context Protocol (MCP)](./mcp.md) is an open protocol for describing tools and data sources in a model-agnostic format, enabling LLMs to discover and use them via a structured API.
|
||||
|
||||
[LangGraph Server](./langgraph_server.md) implements MCP using the [Streamable HTTP transport](https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/transports/#streamable-http). This allows LangGraph **agents** to be exposed as **MCP tools**, making them usable with any MCP-compliant client supporting Streamable HTTP.
|
||||
|
||||
@@ -28,79 +27,6 @@ Install them with:
|
||||
pip install "langgraph-api>=0.2.3" "langgraph-sdk>=0.1.61"
|
||||
```
|
||||
|
||||
## Exposing an agent as MCP tool
|
||||
|
||||
|
||||
When deployed, your agent will appear as a tool in the MCP endpoint
|
||||
with this configuration:
|
||||
|
||||
- **Tool name**: The agent's name.
|
||||
- **Tool description**: The agent's description.
|
||||
- **Tool input schema**: The agent's input schema.
|
||||
|
||||
### Setting name and description
|
||||
|
||||
You can set the name and description of your agent in `langgraph.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"graphs": {
|
||||
"my_agent": {
|
||||
"path": "./my_agent/agent.py:graph",
|
||||
"description": "A description of what the agent does"
|
||||
}
|
||||
},
|
||||
"env": ".env"
|
||||
}
|
||||
```
|
||||
|
||||
After deployment, you can update the name and description using the LangGraph SDK.
|
||||
|
||||
### Schema
|
||||
|
||||
Define clear, minimal input and output schemas to avoid exposing unnecessary internal complexity to the LLM.
|
||||
|
||||
The default [MessagesState](./low_level.md#messagesstate) uses `AnyMessage`, which supports many message types but is too general for direct LLM exposure.
|
||||
|
||||
Instead, define **custom agents or workflows** that use explicitly typed input and output structures.
|
||||
|
||||
For example, a workflow answering documentation questions might look like this:
|
||||
|
||||
```python
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
# Define input schema
|
||||
class InputState(TypedDict):
|
||||
question: str
|
||||
|
||||
# Define output schema
|
||||
class OutputState(TypedDict):
|
||||
answer: str
|
||||
|
||||
# Combine input and output
|
||||
class OverallState(InputState, OutputState):
|
||||
pass
|
||||
|
||||
# Define the processing node
|
||||
def answer_node(state: InputState):
|
||||
# Replace with actual logic and do something useful
|
||||
return {"answer": "bye", "question": state["question"]}
|
||||
|
||||
# Build the graph with explicit schemas
|
||||
builder = StateGraph(OverallState, input_schema=InputState, output_schema=OutputState)
|
||||
builder.add_node(answer_node)
|
||||
builder.add_edge(START, "answer_node")
|
||||
builder.add_edge("answer_node", END)
|
||||
graph = builder.compile()
|
||||
|
||||
# Run the graph
|
||||
print(graph.invoke({"question": "hi"}))
|
||||
```
|
||||
|
||||
For more details, see the [low-level concepts guide](https://langchain-ai.github.io/langgraph/concepts/low_level/#state).
|
||||
|
||||
|
||||
## Usage overview
|
||||
|
||||
To enable MCP:
|
||||
@@ -201,6 +127,114 @@ Use an MCP-compliant client to connect to the LangGraph server. The following ex
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Expose an agent as MCP tool
|
||||
|
||||
When deployed, your agent will appear as a tool in the MCP endpoint
|
||||
with this configuration:
|
||||
|
||||
- **Tool name**: The agent's name.
|
||||
- **Tool description**: The agent's description.
|
||||
- **Tool input schema**: The agent's input schema.
|
||||
|
||||
### Setting name and description
|
||||
|
||||
You can set the name and description of your agent in `langgraph.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"graphs": {
|
||||
"my_agent": {
|
||||
"path": "./my_agent/agent.py:graph",
|
||||
"description": "A description of what the agent does"
|
||||
}
|
||||
},
|
||||
"env": ".env"
|
||||
}
|
||||
```
|
||||
|
||||
After deployment, you can update the name and description using the LangGraph SDK.
|
||||
|
||||
### Schema
|
||||
|
||||
Define clear, minimal input and output schemas to avoid exposing unnecessary internal complexity to the LLM.
|
||||
|
||||
The default [MessagesState](./low_level.md#messagesstate) uses `AnyMessage`, which supports many message types but is too general for direct LLM exposure.
|
||||
|
||||
Instead, define **custom agents or workflows** that use explicitly typed input and output structures.
|
||||
|
||||
For example, a workflow answering documentation questions might look like this:
|
||||
|
||||
```python
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
# Define input schema
|
||||
class InputState(TypedDict):
|
||||
question: str
|
||||
|
||||
# Define output schema
|
||||
class OutputState(TypedDict):
|
||||
answer: str
|
||||
|
||||
# Combine input and output
|
||||
class OverallState(InputState, OutputState):
|
||||
pass
|
||||
|
||||
# Define the processing node
|
||||
def answer_node(state: InputState):
|
||||
# Replace with actual logic and do something useful
|
||||
return {"answer": "bye", "question": state["question"]}
|
||||
|
||||
# Build the graph with explicit schemas
|
||||
builder = StateGraph(OverallState, input_schema=InputState, output_schema=OutputState)
|
||||
builder.add_node(answer_node)
|
||||
builder.add_edge(START, "answer_node")
|
||||
builder.add_edge("answer_node", END)
|
||||
graph = builder.compile()
|
||||
|
||||
# Run the graph
|
||||
print(graph.invoke({"question": "hi"}))
|
||||
```
|
||||
|
||||
For more details, see the [low-level concepts guide](https://langchain-ai.github.io/langgraph/concepts/low_level/#state).
|
||||
|
||||
## Use user-scoped MCP tools in your deployment
|
||||
|
||||
!!! tip "Prerequisites"
|
||||
|
||||
You have added your own [custom auth middleware](https://langchain-ai.github.io/langgraph/how-tos/auth/custom_auth/) that populates the `langgraph_auth_user` object, making it accessible through configurable context for every node in your graph.
|
||||
|
||||
To make user-scoped tools available to your LangGraph Platform deployment, start with implementing a snippet like the following:
|
||||
|
||||
```python
|
||||
from langchain_mcp_adapters.client import MultiServerMCPClient
|
||||
|
||||
def mcp_tools_node(state, config):
|
||||
user = config["configurable"].get("langgraph_auth_user")
|
||||
# e.g., user["github_token"], user["email"], etc.
|
||||
|
||||
client = MultiServerMCPClient({
|
||||
"github": {
|
||||
"transport": "streamable_http", # (1)
|
||||
"url": "https://my-github-mcp-server/mcp", # (2)
|
||||
"headers": {
|
||||
"Authorization": f"Bearer {user['github_token']}"
|
||||
}
|
||||
}
|
||||
})
|
||||
tools = await client.get_tools() # (3)
|
||||
|
||||
# Your tool-calling logic here
|
||||
|
||||
tool_messages = ...
|
||||
return {"messages": tool_messages}
|
||||
```
|
||||
|
||||
1. MCP only supports adding headers to requests made to `streamable_http` and `sse` `transport` servers.
|
||||
2. Your MCP server URL.
|
||||
3. Get available tools from your MCP server.
|
||||
|
||||
_This can also be done by [rebuilding your graph at runtime](https://langchain-ai.github.io/langgraph/cloud/deployment/graph_rebuild/) to have a different configuration for a new run_
|
||||
|
||||
## Session behavior
|
||||
|
||||
@@ -210,7 +244,7 @@ The current LangGraph MCP implementation does not support sessions. Each `/mcp`
|
||||
|
||||
The `/mcp` endpoint uses the same authentication as the rest of the LangGraph API. Refer to the [authentication guide](./auth.md) for setup details.
|
||||
|
||||
## Disabling MCP
|
||||
## Disable MCP
|
||||
|
||||
To disable the MCP endpoint, set `disable_mcp` to `true` in your `langgraph.json` configuration file:
|
||||
|
||||
@@ -222,4 +256,4 @@ To disable the MCP endpoint, set `disable_mcp` to `true` in your `langgraph.json
|
||||
}
|
||||
```
|
||||
|
||||
This will prevent the server from exposing the `/mcp` endpoint.
|
||||
This will prevent the server from exposing the `/mcp` endpoint.
|
||||
|
||||
@@ -12,7 +12,7 @@ Some reasons for using subgraphs are:
|
||||
|
||||
The main question when adding subgraphs is how the parent graph and subgraph communicate, i.e. how they pass the [state](./low_level.md#state) between each other during the graph execution. There are two scenarios:
|
||||
|
||||
* parent and subgraph have **shared state keys** in their state [schemas](./low_level.md#state). In this case, you can [include the subgraph as a node in the parent graph](../how-tos/subgraph.ipynb#shared-state-schemas)
|
||||
* parent and subgraph have **shared state keys** in their state [schemas](./low_level.md#state). In this case, you can [include the subgraph as a node in the parent graph](../how-tos/subgraph.md#shared-state-schemas)
|
||||
|
||||
```python
|
||||
from langgraph.graph import StateGraph, MessagesState, START
|
||||
@@ -40,7 +40,7 @@ The main question when adding subgraphs is how the parent graph and subgraph com
|
||||
graph.invoke({"messages": [{"role": "user", "content": "hi!"}]})
|
||||
```
|
||||
|
||||
* parent graph and subgraph have **different schemas** (no shared state keys in their state [schemas](./low_level.md#state)). In this case, you have to [call the subgraph from inside a node in the parent graph](../how-tos/subgraph.ipynb#different-state-schemas): this is useful when the parent graph and the subgraph have different state schemas and you need to transform state before or after calling the subgraph
|
||||
* parent graph and subgraph have **different schemas** (no shared state keys in their state [schemas](./low_level.md#state)). In this case, you have to [call the subgraph from inside a node in the parent graph](../how-tos/subgraph.md#different-state-schemas): this is useful when the parent graph and the subgraph have different state schemas and you need to transform state before or after calling the subgraph
|
||||
|
||||
```python
|
||||
from typing_extensions import TypedDict, Annotated
|
||||
|
||||
@@ -64,7 +64,7 @@ To create a new app from a template, use the `langgraph new` command.
|
||||
=== "JS"
|
||||
|
||||
```bash
|
||||
npx @langchain/langgraph-cli new
|
||||
npm create langgraph@latest
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Examples
|
||||
|
||||
The pages in this section provide end-to-end examples for the following topics:
|
||||
|
||||
## General
|
||||
|
||||
- [Template Applications](../concepts/template_applications.md): Create a LangGraph application from a template.
|
||||
- [Agentic RAG](./rag/langgraph_agentic_rag.md): Build a retrieval agent that can decide when to use a retriever tool.
|
||||
- [Agent Supervisor](./multi_agent/agent_supervisor.md): Build a supervisor agent that can manage a team of agents.
|
||||
- [SQL agent](./sql/sql-agent.md): Build a SQL agent that can execute SQL queries and return the results.
|
||||
- [Prebuilt chat UI](../agents/ui.md): Use a prebuilt chat UI to interact with any LangGraph agent.
|
||||
- [Graph runs in LangSmith](../how-tos/run-id-langsmith.md): Use LangSmith to track and analyze graph runs.
|
||||
|
||||
## LangGraph Platform
|
||||
|
||||
- [Set up custom authentication](./auth/getting_started.md): Set up custom authentication for your LangGraph application.
|
||||
- [Make conversations private](./auth/resource_auth.md): Make conversations private by using resource-based authentication.
|
||||
- [Connect an authentication provider](./auth/add_auth_server.md): Connect an authentication provider to your LangGraph application.
|
||||
- [Rebuild graph at runtime](../cloud/deployment/graph_rebuild.md): Rebuild a graph at runtime.
|
||||
- [Use RemoteGraph](../how-tos/use-remote-graph.md): Use RemoteGraph to deploy your LangGraph application to a remote server.
|
||||
- [Deploy CrewAI, AutoGen, and other frameworks](../how-tos/autogen-integration.md): Deploy CrewAI, AutoGen, and other frameworks with LangGraph.
|
||||
- [Integrate LangGraph into a React app](../cloud/how-tos/use_stream_react.md)
|
||||
- [Implement Generative User Interfaces with LangGraph](../cloud/how-tos/generative_ui_react.md)
|
||||
@@ -0,0 +1,41 @@
|
||||
# Guides
|
||||
|
||||
The pages in this section provide a conceptual overview and how-tos for the following topics:
|
||||
|
||||
## LangGraph APIs
|
||||
|
||||
- [Graph API](../concepts/low_level.md): Use the Graph API to define workflows using a graph paradigm.
|
||||
- [Functional API](../concepts/functional_api.md): Use Functional API to build workflows using a functional paradigm without thinking about the graph structure.
|
||||
- [Runtime](../concepts/pregel.md): Pregel implements LangGraph's runtime, managing the execution of LangGraph applications.
|
||||
|
||||
## Core capabilities
|
||||
|
||||
These capabilities are available in both LangGraph OSS and the LangGraph Platform.
|
||||
|
||||
- [Streaming](../concepts/streaming.md): Stream outputs from a LangGraph graph.
|
||||
- [Persistence](../concepts/persistence.md): Persist the state of a LangGraph graph.
|
||||
- [Durable execution](../concepts/durable_execution.md): Save progress at key points in the graph execution.
|
||||
- [Memory](../concepts/memory.md): Remember information about previous interactions.
|
||||
- [Context](../agents/context.md): Pass outside data to a LangGraph graph to provide context for the graph execution.
|
||||
- [Models](../agents/models.md): Integrate various LLMs into your LangGraph application.
|
||||
- [Tools](../concepts/tools.md): Interface directly with external systems.
|
||||
- [Human-in-the-loop](../concepts/human_in_the_loop.md): Enable human intervention at any point in a workflow.
|
||||
- [Breakpoints](../concepts/breakpoints.md): Pause the execution of a LangGraph graph at a specific point.
|
||||
- [Time travel](../concepts/time-travel.md): Travel back in time to a specific point in the execution of a LangGraph graph.
|
||||
- [Subgraphs](../concepts/subgraphs.md): Build modular graphs.
|
||||
- [Multi-agent](../concepts/multi_agent.md): Break down a complex workflow into multiple agents.
|
||||
- [MCP](../concepts/mcp.md): Use MCP servers in a LangGraph graph.
|
||||
- [Evaluation](../agents/evals.md): Use LangSmith to evaluate your graph's performance.
|
||||
|
||||
## Platform-only capabilities
|
||||
|
||||
These capabilities are only available in [LangGraph Platform](../concepts/langgraph_platform.md).
|
||||
|
||||
- [Authentication and access control](../concepts/auth.md): Authenticate and authorize users to access a Langraph graph.
|
||||
- [Assistants](../concepts/assistants.md): Build assistants that can be used to interact with a LangGraph graph.
|
||||
- [Double-texting](../concepts/double_texting.md): Handle double-texting (consecutive messages before a first response is returned) in a LangGraph graph.
|
||||
- [Webhooks](../cloud/concepts/webhooks.md): Send webhooks to a LangGraph graph.
|
||||
- [Cron jobs](../cloud/concepts/cron_jobs.md): Schedule jobs to run at a specific time.
|
||||
- [Server customization](../how-tos/http/custom_lifespan.md): Customize the server that runs a LangGraph graph.
|
||||
- [Data management](../cloud/concepts/data_storage_and_privacy.md): Manage data in a LangGraph graph.
|
||||
- [Deployment](../concepts/deployment_options.md): Deploy a LangGraph graph to a server.
|
||||
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
@@ -1,138 +1,147 @@
|
||||
# Add custom authentication
|
||||
|
||||
!!! tip "Prerequisites"
|
||||
|
||||
This guide assumes familiarity with the following concepts:
|
||||
|
||||
* [**Authentication & Access Control**](../../concepts/auth.md)
|
||||
* [**LangGraph Platform**](../../concepts/langgraph_platform.md)
|
||||
|
||||
For a more guided walkthrough, see [**setting up custom authentication**](../../tutorials/auth/getting_started.md) tutorial.
|
||||
|
||||
???+ note "Support by deployment type"
|
||||
|
||||
Custom auth is supported for all deployments in the **managed LangGraph Platform**, as well as **Enterprise** self-hosted plans. It is not supported for **Lite** self-hosted plans.
|
||||
|
||||
This guide shows how to add custom authentication to your LangGraph Platform application. This guide applies to both LangGraph Platform and self-hosted deployments. It does not apply to isolated usage of the LangGraph open source library in your own custom server.
|
||||
|
||||
## 1. Implement authentication
|
||||
!!! note
|
||||
|
||||
Custom auth is supported for all **managed LangGraph Platform** deployments, as well as **Enterprise** self-hosted plans. It is not supported for **Lite** self-hosted plans.
|
||||
|
||||
## Add custom authentication to your deployment
|
||||
|
||||
To leverage custom authentication and access user-level metadata in your deployments, set up custom authentication to automatically populate the `config["configurable"]["langgraph_auth_user"]` object through a custom authentication handler. You can then access this object in your graph with the `langgraph_auth_user` key to [allow an agent to perform authenticated actions on behalf of the user](#enable-agent-authentication).
|
||||
|
||||
1. Implement authentication:
|
||||
|
||||
!!! note
|
||||
|
||||
Without a custom `@auth.authenticate` handler, LangGraph sees only the API-key owner (usually the developer), so requests aren’t scoped to individual end-users. To propagate custom tokens, you must implement your own handler.
|
||||
|
||||
```python
|
||||
from langgraph_sdk import Auth
|
||||
import requests
|
||||
|
||||
auth = Auth()
|
||||
|
||||
def is_valid_key(api_key: str) -> bool:
|
||||
is_valid = # your API key validation logic
|
||||
return is_valid
|
||||
|
||||
@auth.authenticate # (1)!
|
||||
async def authenticate(headers: dict) -> Auth.types.MinimalUserDict:
|
||||
api_key = headers.get("x-api-key")
|
||||
if not api_key or not is_valid_key(api_key):
|
||||
raise Auth.exceptions.HTTPException(status_code=401, detail="Invalid API key")
|
||||
|
||||
# Fetch user-specific tokens from your secret store
|
||||
user_tokens = await fetch_user_tokens(api_key)
|
||||
|
||||
return { # (2)!
|
||||
"identity": api_key, # fetch user ID from LangSmith
|
||||
"github_token" : user_tokens.github_token
|
||||
"jira_token" : user_tokens.jira_token
|
||||
# ... custom fields/secrets here
|
||||
}
|
||||
```
|
||||
|
||||
1. This handler receives the request (headers, etc.), validates the user, and returns a dictionary with at least an identity field.
|
||||
2. You can add any custom fields you want (e.g., OAuth tokens, roles, org IDs, etc.).
|
||||
|
||||
2. In your `langgraph.json`, add the path to your auth file:
|
||||
|
||||
```json hl_lines="7-9"
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"agent": "./agent.py:graph"
|
||||
},
|
||||
"env": ".env",
|
||||
"auth": {
|
||||
"path": "./auth.py:my_auth"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Once you've set up authentication in your server, requests must include the required authorization information based on your chosen scheme. Assuming you are using JWT token authentication, you could access your deployments using any of the following methods:
|
||||
|
||||
=== "Python Client"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
my_token = "your-token" # In practice, you would generate a signed token with your auth provider
|
||||
client = get_client(
|
||||
url="http://localhost:2024",
|
||||
headers={"Authorization": f"Bearer {my_token}"}
|
||||
)
|
||||
threads = await client.threads.search()
|
||||
```
|
||||
|
||||
=== "Python RemoteGraph"
|
||||
|
||||
```python
|
||||
from langgraph.pregel.remote import RemoteGraph
|
||||
|
||||
my_token = "your-token" # In practice, you would generate a signed token with your auth provider
|
||||
remote_graph = RemoteGraph(
|
||||
"agent",
|
||||
url="http://localhost:2024",
|
||||
headers={"Authorization": f"Bearer {my_token}"}
|
||||
)
|
||||
threads = await remote_graph.ainvoke(...)
|
||||
```
|
||||
|
||||
=== "JavaScript Client"
|
||||
|
||||
```javascript
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
const my_token = "your-token"; // In practice, you would generate a signed token with your auth provider
|
||||
const client = new Client({
|
||||
apiUrl: "http://localhost:2024",
|
||||
defaultHeaders: { Authorization: `Bearer ${my_token}` },
|
||||
});
|
||||
const threads = await client.threads.search();
|
||||
```
|
||||
|
||||
=== "JavaScript RemoteGraph"
|
||||
|
||||
```javascript
|
||||
import { RemoteGraph } from "@langchain/langgraph/remote";
|
||||
|
||||
const my_token = "your-token"; // In practice, you would generate a signed token with your auth provider
|
||||
const remoteGraph = new RemoteGraph({
|
||||
graphId: "agent",
|
||||
url: "http://localhost:2024",
|
||||
headers: { Authorization: `Bearer ${my_token}` },
|
||||
});
|
||||
const threads = await remoteGraph.invoke(...);
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer ${your-token}" http://localhost:2024/threads
|
||||
```
|
||||
|
||||
## Enable agent authentication
|
||||
|
||||
After [authentication](#add-custom-authentication-to-your-deployment), the platform creates a special configuration object (`config`) that is passed to LangGraph Platform deployment. This object contains information about the current user, including any custom fields you return from your `@auth.authenticate` handler.
|
||||
|
||||
To allow an agent to perform authenticated actions on behalf of the user, access this object in your graph with the `langgraph_auth_user` key:
|
||||
|
||||
```python
|
||||
from langgraph_sdk import Auth
|
||||
|
||||
my_auth = Auth()
|
||||
|
||||
@my_auth.authenticate
|
||||
async def authenticate(authorization: str) -> str:
|
||||
token = authorization.split(" ", 1)[-1] # "Bearer <token>"
|
||||
try:
|
||||
# Verify token with your auth provider
|
||||
user_id = await verify_token(token)
|
||||
return user_id
|
||||
except Exception:
|
||||
raise Auth.exceptions.HTTPException(
|
||||
status_code=401,
|
||||
detail="Invalid token"
|
||||
)
|
||||
|
||||
# Add authorization rules to actually control access to resources
|
||||
@my_auth.on
|
||||
async def add_owner(
|
||||
ctx: Auth.types.AuthContext,
|
||||
value: dict,
|
||||
):
|
||||
"""Add owner to resource metadata and filter by owner."""
|
||||
filters = {"owner": ctx.user.identity}
|
||||
metadata = value.setdefault("metadata", {})
|
||||
metadata.update(filters)
|
||||
return filters
|
||||
|
||||
# Assumes you organize information in store like (user_id, resource_type, resource_id)
|
||||
@my_auth.on.store()
|
||||
async def authorize_store(ctx: Auth.types.AuthContext, value: dict):
|
||||
namespace: tuple = value["namespace"]
|
||||
assert namespace[0] == ctx.user.identity, "Not authorized"
|
||||
|
||||
def my_node(state, config):
|
||||
user_config = config["configurable"].get("langgraph_auth_user")
|
||||
# token was resolved during the @auth.authenticate function
|
||||
token = user_config.get("github_token","")
|
||||
...
|
||||
```
|
||||
|
||||
## 2. Update configuration
|
||||
!!! note
|
||||
Fetch user credentials from a secure secret store. Storing secrets in graph state is not recommended.
|
||||
|
||||
In your `langgraph.json`, add the path to your auth file:
|
||||
## Learn more
|
||||
|
||||
```json hl_lines="7-9"
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"agent": "./agent.py:graph"
|
||||
},
|
||||
"env": ".env",
|
||||
"auth": {
|
||||
"path": "./auth.py:my_auth"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Connect from the client
|
||||
|
||||
Once you've set up authentication in your server, requests must include the required authorization information based on your chosen scheme.
|
||||
Assuming you are using JWT token authentication, you could access your deployments using any of the following methods:
|
||||
|
||||
=== "Python Client"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
my_token = "your-token" # In practice, you would generate a signed token with your auth provider
|
||||
client = get_client(
|
||||
url="http://localhost:2024",
|
||||
headers={"Authorization": f"Bearer {my_token}"}
|
||||
)
|
||||
threads = await client.threads.search()
|
||||
```
|
||||
|
||||
=== "Python RemoteGraph"
|
||||
|
||||
```python
|
||||
from langgraph.pregel.remote import RemoteGraph
|
||||
|
||||
my_token = "your-token" # In practice, you would generate a signed token with your auth provider
|
||||
remote_graph = RemoteGraph(
|
||||
"agent",
|
||||
url="http://localhost:2024",
|
||||
headers={"Authorization": f"Bearer {my_token}"}
|
||||
)
|
||||
threads = await remote_graph.ainvoke(...)
|
||||
```
|
||||
|
||||
=== "JavaScript Client"
|
||||
|
||||
```javascript
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
const my_token = "your-token"; // In practice, you would generate a signed token with your auth provider
|
||||
const client = new Client({
|
||||
apiUrl: "http://localhost:2024",
|
||||
defaultHeaders: { Authorization: `Bearer ${my_token}` },
|
||||
});
|
||||
const threads = await client.threads.search();
|
||||
```
|
||||
|
||||
=== "JavaScript RemoteGraph"
|
||||
|
||||
```javascript
|
||||
import { RemoteGraph } from "@langchain/langgraph/remote";
|
||||
|
||||
const my_token = "your-token"; // In practice, you would generate a signed token with your auth provider
|
||||
const remoteGraph = new RemoteGraph({
|
||||
graphId: "agent",
|
||||
url: "http://localhost:2024",
|
||||
headers: { Authorization: `Bearer ${my_token}` },
|
||||
});
|
||||
const threads = await remoteGraph.invoke(...);
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer ${your-token}" http://localhost:2024/threads
|
||||
```
|
||||
* [Authentication & Access Control](../../concepts/auth.md)
|
||||
* [LangGraph Platform](../../concepts/langgraph_platform.md)
|
||||
* [Setting up custom authentication tutorial](../../tutorials/auth/getting_started.md)
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
# How to integrate LangGraph with AutoGen, CrewAI, and other frameworks
|
||||
|
||||
This guide shows how to integrate AutoGen agents with LangGraph to leverage features like persistence, streaming, and memory, and then deploy the integrated solution to LangGraph Platform for scalable production use. In this guide we show how to build a LangGraph chatbot that integrates with AutoGen, but you can follow the same approach with other frameworks.
|
||||
|
||||
Integrating AutoGen with LangGraph provides several benefits:
|
||||
|
||||
- Enhanced features: Add [persistence](../concepts/persistence.md), [streaming](../concepts/streaming.md), [short and long-term memory](../concepts/memory.md) and more to your AutoGen agents.
|
||||
- Multi-agent systems: Build [multi-agent systems](../concepts/multi_agent.md) where individual agents are built with different frameworks.
|
||||
- Production deployment: Deploy your integrated solution to [LangGraph Platform](../concepts/langgraph_platform.md) for scalable production use.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.9+
|
||||
- Autogen: `pip install autogen`
|
||||
- LangGraph: `pip install langgraph`
|
||||
- OpenAI API key
|
||||
|
||||
## Setup
|
||||
|
||||
Set your your environment:
|
||||
|
||||
```python
|
||||
import getpass
|
||||
import os
|
||||
|
||||
|
||||
def _set_env(var: str):
|
||||
if not os.environ.get(var):
|
||||
os.environ[var] = getpass.getpass(f"{var}: ")
|
||||
|
||||
|
||||
_set_env("OPENAI_API_KEY")
|
||||
```
|
||||
|
||||
## 1. Define AutoGen agent
|
||||
|
||||
Create an AutoGen agent that can execute code. This example is adapted from AutoGen's [official tutorials](https://github.com/microsoft/autogen/blob/0.2/notebook/agentchat_web_info.ipynb):
|
||||
|
||||
```python
|
||||
import autogen
|
||||
import os
|
||||
|
||||
config_list = [{"model": "gpt-4o", "api_key": os.environ["OPENAI_API_KEY"]}]
|
||||
|
||||
llm_config = {
|
||||
"timeout": 600,
|
||||
"cache_seed": 42,
|
||||
"config_list": config_list,
|
||||
"temperature": 0,
|
||||
}
|
||||
|
||||
autogen_agent = autogen.AssistantAgent(
|
||||
name="assistant",
|
||||
llm_config=llm_config,
|
||||
)
|
||||
|
||||
user_proxy = autogen.UserProxyAgent(
|
||||
name="user_proxy",
|
||||
human_input_mode="NEVER",
|
||||
max_consecutive_auto_reply=10,
|
||||
is_termination_msg=lambda x: x.get("content", "").rstrip().endswith("TERMINATE"),
|
||||
code_execution_config={
|
||||
"work_dir": "web",
|
||||
"use_docker": False,
|
||||
}, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.
|
||||
llm_config=llm_config,
|
||||
system_message="Reply TERMINATE if the task has been solved at full satisfaction. Otherwise, reply CONTINUE, or the reason why the task is not solved yet.",
|
||||
)
|
||||
```
|
||||
|
||||
## 2. Create the graph
|
||||
|
||||
We will now create a LangGraph chatbot graph that calls AutoGen agent.
|
||||
|
||||
```python
|
||||
from langchain_core.messages import convert_to_openai_messages
|
||||
from langgraph.graph import StateGraph, MessagesState, START
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
|
||||
def call_autogen_agent(state: MessagesState):
|
||||
# Convert LangGraph messages to OpenAI format for AutoGen
|
||||
messages = convert_to_openai_messages(state["messages"])
|
||||
|
||||
# Get the last user message
|
||||
last_message = messages[-1]
|
||||
|
||||
# Pass previous message history as context (excluding the last message)
|
||||
carryover = messages[:-1] if len(messages) > 1 else []
|
||||
|
||||
# Initiate chat with AutoGen
|
||||
response = user_proxy.initiate_chat(
|
||||
autogen_agent,
|
||||
message=last_message,
|
||||
carryover=carryover
|
||||
)
|
||||
|
||||
# Extract the final response from the agent
|
||||
final_content = response.chat_history[-1]["content"]
|
||||
|
||||
# Return the response in LangGraph format
|
||||
return {"messages": {"role": "assistant", "content": final_content}}
|
||||
|
||||
# Create the graph with memory for persistence
|
||||
checkpointer = MemorySaver()
|
||||
|
||||
# Build the graph
|
||||
builder = StateGraph(MessagesState)
|
||||
builder.add_node("autogen", call_autogen_agent)
|
||||
builder.add_edge(START, "autogen")
|
||||
|
||||
# Compile with checkpointer for persistence
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
```
|
||||
|
||||
```python
|
||||
from IPython.display import display, Image
|
||||
|
||||
display(Image(graph.get_graph().draw_mermaid_png()))
|
||||
```
|
||||
|
||||

|
||||
|
||||
## 3. Test the graph locally
|
||||
|
||||
Before deploying to LangGraph Platform, you can test the graph locally:
|
||||
|
||||
```python
|
||||
# pass the thread ID to persist agent outputs for future interactions
|
||||
# highlight-next-line
|
||||
config = {"configurable": {"thread_id": "1"}}
|
||||
|
||||
for chunk in graph.stream(
|
||||
{
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Find numbers between 10 and 30 in fibonacci sequence",
|
||||
}
|
||||
]
|
||||
},
|
||||
# highlight-next-line
|
||||
config,
|
||||
):
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```
|
||||
user_proxy (to assistant):
|
||||
|
||||
Find numbers between 10 and 30 in fibonacci sequence
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
assistant (to user_proxy):
|
||||
|
||||
To find numbers between 10 and 30 in the Fibonacci sequence, we can generate the Fibonacci sequence and check which numbers fall within this range. Here's a plan:
|
||||
|
||||
1. Generate Fibonacci numbers starting from 0.
|
||||
2. Continue generating until the numbers exceed 30.
|
||||
3. Collect and print the numbers that are between 10 and 30.
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Since we're leveraging LangGraph's [persistence](https://langchain-ai.github.io/langgraph/concepts/persistence/) features we can now continue the conversation using the same thread ID -- LangGraph will automatically pass previous history to the AutoGen agent:
|
||||
|
||||
```python
|
||||
for chunk in graph.stream(
|
||||
{
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Multiply the last number by 3",
|
||||
}
|
||||
]
|
||||
},
|
||||
# highlight-next-line
|
||||
config,
|
||||
):
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```
|
||||
user_proxy (to assistant):
|
||||
|
||||
Multiply the last number by 3
|
||||
Context:
|
||||
Find numbers between 10 and 30 in fibonacci sequence
|
||||
The Fibonacci numbers between 10 and 30 are 13 and 21.
|
||||
|
||||
These numbers are part of the Fibonacci sequence, which is generated by adding the two preceding numbers to get the next number, starting from 0 and 1.
|
||||
|
||||
The sequence goes: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...
|
||||
|
||||
As you can see, 13 and 21 are the only numbers in this sequence that fall between 10 and 30.
|
||||
|
||||
TERMINATE
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
assistant (to user_proxy):
|
||||
|
||||
The last number in the Fibonacci sequence between 10 and 30 is 21. Multiplying 21 by 3 gives:
|
||||
|
||||
21 * 3 = 63
|
||||
|
||||
TERMINATE
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
{'call_autogen_agent': {'messages': {'role': 'assistant', 'content': 'The last number in the Fibonacci sequence between 10 and 30 is 21. Multiplying 21 by 3 gives:\n\n21 * 3 = 63\n\nTERMINATE'}}}
|
||||
```
|
||||
|
||||
## 4. Prepare for deployment
|
||||
|
||||
To deploy to LangGraph Platform, create a file structure like the following:
|
||||
|
||||
```
|
||||
my-autogen-agent/
|
||||
├── agent.py # Your main agent code
|
||||
├── requirements.txt # Python dependencies
|
||||
└── langgraph.json # LangGraph configuration
|
||||
```
|
||||
|
||||
=== "agent.py"
|
||||
|
||||
```python
|
||||
import os
|
||||
import autogen
|
||||
from langchain_core.messages import convert_to_openai_messages
|
||||
from langgraph.graph import StateGraph, MessagesState, START
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
|
||||
# AutoGen configuration
|
||||
config_list = [{"model": "gpt-4o", "api_key": os.environ["OPENAI_API_KEY"]}]
|
||||
|
||||
llm_config = {
|
||||
"timeout": 600,
|
||||
"cache_seed": 42,
|
||||
"config_list": config_list,
|
||||
"temperature": 0,
|
||||
}
|
||||
|
||||
# Create AutoGen agents
|
||||
autogen_agent = autogen.AssistantAgent(
|
||||
name="assistant",
|
||||
llm_config=llm_config,
|
||||
)
|
||||
|
||||
user_proxy = autogen.UserProxyAgent(
|
||||
name="user_proxy",
|
||||
human_input_mode="NEVER",
|
||||
max_consecutive_auto_reply=10,
|
||||
is_termination_msg=lambda x: x.get("content", "").rstrip().endswith("TERMINATE"),
|
||||
code_execution_config={
|
||||
"work_dir": "/tmp/autogen_work",
|
||||
"use_docker": False,
|
||||
},
|
||||
llm_config=llm_config,
|
||||
system_message="Reply TERMINATE if the task has been solved at full satisfaction.",
|
||||
)
|
||||
|
||||
def call_autogen_agent(state: MessagesState):
|
||||
"""Node function that calls the AutoGen agent"""
|
||||
messages = convert_to_openai_messages(state["messages"])
|
||||
last_message = messages[-1]
|
||||
carryover = messages[:-1] if len(messages) > 1 else []
|
||||
|
||||
response = user_proxy.initiate_chat(
|
||||
autogen_agent,
|
||||
message=last_message,
|
||||
carryover=carryover
|
||||
)
|
||||
|
||||
final_content = response.chat_history[-1]["content"]
|
||||
return {"messages": {"role": "assistant", "content": final_content}}
|
||||
|
||||
# Create and compile the graph
|
||||
def create_graph():
|
||||
checkpointer = MemorySaver()
|
||||
builder = StateGraph(MessagesState)
|
||||
builder.add_node("autogen", call_autogen_agent)
|
||||
builder.add_edge(START, "autogen")
|
||||
return builder.compile(checkpointer=checkpointer)
|
||||
|
||||
# Export the graph for LangGraph Platform
|
||||
graph = create_graph()
|
||||
```
|
||||
|
||||
=== "requirements.txt"
|
||||
|
||||
```
|
||||
langgraph>=0.1.0
|
||||
pyautogen>=0.2.0
|
||||
langchain-core>=0.1.0
|
||||
langchain-openai>=0.0.5
|
||||
```
|
||||
|
||||
=== "langgraph.json"
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"autogen_agent": "./agent.py:graph"
|
||||
},
|
||||
"env": ".env"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 5. Deploy to LangGraph Platform
|
||||
|
||||
Deploy the graph with the LangGraph Platform CLI:
|
||||
|
||||
```
|
||||
pip install -U langgraph-cli
|
||||
```
|
||||
|
||||
```
|
||||
langgraph deploy --config langgraph.json
|
||||
```
|
||||
@@ -1,171 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8381b6e0-29a6-48c5-b451-5d2549351249",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to use LangGraph Platform to deploy CrewAI, AutoGen, and other frameworks\n",
|
||||
"\n",
|
||||
"[LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) provides infrastructure for deploying agents. This integrates seamlessly with LangGraph, but can also work with other frameworks. The way to make this work is to wrap the agent in a single LangGraph node, and have that be the entire graph.\n",
|
||||
"\n",
|
||||
"Doing so will allow you to deploy to LangGraph Platform, and allows you to get a lot of the [benefits](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/). You get horizontally scalable infrastructure, a task queue to handle bursty operations, a persistence layer to power short term memory, and long term memory support.\n",
|
||||
"\n",
|
||||
"In this guide we show how to do this with an AutoGen agent, but this method should work for agents defined in other frameworks like CrewAI, LlamaIndex, and others as well."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1113cb16-b538-448c-924c-85731ce96ebd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "f05993fa-9d03-4f45-bc13-0a8d87260d86",
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%pip install autogen langgraph"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f4e0ca12-1714-4776-a30a-9527e519799b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
" if not os.environ.get(var):\n",
|
||||
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"_set_env(\"OPENAI_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1926bbc3-6b06-41e0-9604-860a2bbf8fa3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Define autogen agent\n",
|
||||
"\n",
|
||||
"Here we define our AutoGen agent. From https://github.com/microsoft/autogen/blob/0.2/notebook/agentchat_web_info.ipynb"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d4a14dc7-d565-4207-8788-525f85b9fb27",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import autogen\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"config_list = [{\"model\": \"gpt-4o\", \"api_key\": os.environ[\"OPENAI_API_KEY\"]}]\n",
|
||||
"\n",
|
||||
"llm_config = {\n",
|
||||
" \"timeout\": 600,\n",
|
||||
" \"cache_seed\": 42,\n",
|
||||
" \"config_list\": config_list,\n",
|
||||
" \"temperature\": 0,\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"autogen_agent = autogen.AssistantAgent(\n",
|
||||
" name=\"assistant\",\n",
|
||||
" llm_config=llm_config,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"user_proxy = autogen.UserProxyAgent(\n",
|
||||
" name=\"user_proxy\",\n",
|
||||
" human_input_mode=\"NEVER\",\n",
|
||||
" max_consecutive_auto_reply=10,\n",
|
||||
" is_termination_msg=lambda x: x.get(\"content\", \"\").rstrip().endswith(\"TERMINATE\"),\n",
|
||||
" code_execution_config={\n",
|
||||
" \"work_dir\": \"web\",\n",
|
||||
" \"use_docker\": False,\n",
|
||||
" }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n",
|
||||
" llm_config=llm_config,\n",
|
||||
" system_message=\"Reply TERMINATE if the task has been solved at full satisfaction. Otherwise, reply CONTINUE, or the reason why the task is not solved yet.\",\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b1170836-f23e-4e4c-ab83-ce791cd7fbd2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Wrap in LangGraph\n",
|
||||
"\n",
|
||||
"We now wrap the AutoGen agent in a single LangGraph node, and make that the entire graph.\n",
|
||||
"The main thing this involves is defining an Input and Output schema for the node, which you would need to do if deploying this manually, so it's no extra work"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "7b417c16-ff4e-4d5c-a9a9-0aaeeef6ede5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, MessagesState\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def call_autogen_agent(state: MessagesState):\n",
|
||||
" last_message = state[\"messages\"][-1]\n",
|
||||
" response = user_proxy.initiate_chat(autogen_agent, message=last_message.content)\n",
|
||||
" # get the final response from the agent\n",
|
||||
" content = response.chat_history[-1][\"content\"]\n",
|
||||
" return {\"messages\": {\"role\": \"assistant\", \"content\": content}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"graph = StateGraph(MessagesState)\n",
|
||||
"graph.add_node(call_autogen_agent)\n",
|
||||
"graph.set_entry_point(\"call_autogen_agent\")\n",
|
||||
"graph = graph.compile()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f6a18377-ac29-478f-a76a-b213f1a3c85d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Deploy with LangGraph Platform\n",
|
||||
"\n",
|
||||
"You can now deploy this as you normally would with LangGraph Platform. See [these instructions](https://langchain-ai.github.io/langgraph/concepts/deployment_options/) for more details."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -1351,7 +1351,7 @@ The problem with trimming or removing messages, as shown above, is that you may
|
||||
|
||||
```python
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langmem.short_term import SummarizationNode
|
||||
from langmem.short_term import SummarizationNode, RunningSummary
|
||||
from langchain_core.messages.utils import count_tokens_approximately
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from langgraph.prebuilt.chat_agent_executor import AgentState
|
||||
@@ -1372,7 +1372,7 @@ The problem with trimming or removing messages, as shown above, is that you may
|
||||
# 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)!
|
||||
context: dict[str, RunningSummary] # (2)!
|
||||
|
||||
|
||||
checkpointer = InMemorySaver() # (3)!
|
||||
@@ -1447,18 +1447,18 @@ The problem with trimming or removing messages, as shown above, is that you may
|
||||
from langgraph.graph import StateGraph, START, MessagesState
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
# highlight-next-line
|
||||
from langmem.short_term import SummarizationNode
|
||||
from langmem.short_term import SummarizationNode, RunningSummary
|
||||
|
||||
model = init_chat_model("anthropic:claude-3-7-sonnet-latest")
|
||||
summarization_model = model.bind(max_tokens=128)
|
||||
|
||||
class State(MessagesState):
|
||||
# highlight-next-line
|
||||
context: dict[str, Any] # (1)!
|
||||
context: dict[str, RunningSummary] # (1)!
|
||||
|
||||
class LLMInputState(TypedDict): # (2)!
|
||||
summarized_messages: list[AnyMessage]
|
||||
context: dict[str, Any]
|
||||
context: dict[str, RunningSummary]
|
||||
|
||||
# highlight-next-line
|
||||
summarization_node = SummarizationNode(
|
||||
|
||||
@@ -1,657 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "34d3d54e-9a2b-481e-bccd-74aca7a53f9a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Build multi-agent systems"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "3f0b4f70-f14e-4026-82c0-874786789ee8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"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).\n",
|
||||
"\n",
|
||||
"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.\n",
|
||||
"\n",
|
||||
"This guide covers the following:\n",
|
||||
"\n",
|
||||
"* implementing [handoffs](#handoffs) between agents\n",
|
||||
"* using handoffs and the prebuilt [agent](../../agents/agents) to [build a custom multi-agent system](#build-a-multi-agent-system)\n",
|
||||
"\n",
|
||||
"To get started with building multi-agent systems, check out LangGraph [prebuilt implementations](#prebuilt-implementations) of two of the most popular multi-agent architectures — [supervisor](../../agents/multi-agent#supervisor) and [swarm](../../agents/multi-agent#swarm)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "7d43e110-16fc-4899-97f1-015d5b804b87",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Handoffs\n",
|
||||
"\n",
|
||||
"To set up communication between the agents in a multi-agent system you can use [**handoffs**](../../concepts/multi_agent#handoffs) — a pattern where one agent *hands off* control to another. Handoffs allow you to specify:\n",
|
||||
"\n",
|
||||
"- **destination**: target agent to navigate to (e.g., name of the LangGraph node to go to)\n",
|
||||
"- **payload**: information to pass to that agent (e.g., state update)\n",
|
||||
"\n",
|
||||
"### Create handoffs\n",
|
||||
"\n",
|
||||
"To implement handoffs, you can return [`Command`](../command) objects from your agent nodes or tools:\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"from typing import Annotated\n",
|
||||
"from langchain_core.tools import tool, InjectedToolCallId\n",
|
||||
"from langgraph.prebuilt import create_react_agent, InjectedState\n",
|
||||
"from langgraph.graph import StateGraph, START, MessagesState\n",
|
||||
"from langgraph.types import Command\n",
|
||||
"\n",
|
||||
"def create_handoff_tool(*, agent_name: str, description: str | None = None):\n",
|
||||
" name = f\"transfer_to_{agent_name}\"\n",
|
||||
" description = description or f\"Transfer to {agent_name}\"\n",
|
||||
"\n",
|
||||
" @tool(name, description=description)\n",
|
||||
" def handoff_tool(\n",
|
||||
" # highlight-next-line\n",
|
||||
" state: Annotated[MessagesState, InjectedState], # (1)!\n",
|
||||
" # highlight-next-line\n",
|
||||
" tool_call_id: Annotated[str, InjectedToolCallId],\n",
|
||||
" ) -> Command:\n",
|
||||
" tool_message = {\n",
|
||||
" \"role\": \"tool\",\n",
|
||||
" \"content\": f\"Successfully transferred to {agent_name}\",\n",
|
||||
" \"name\": name,\n",
|
||||
" \"tool_call_id\": tool_call_id,\n",
|
||||
" }\n",
|
||||
" return Command( # (2)!\n",
|
||||
" # highlight-next-line\n",
|
||||
" goto=agent_name, # (3)!\n",
|
||||
" # highlight-next-line\n",
|
||||
" update={\"messages\": state[\"messages\"] + [tool_message]}, # (4)!\n",
|
||||
" # highlight-next-line\n",
|
||||
" graph=Command.PARENT, # (5)!\n",
|
||||
" )\n",
|
||||
" return handoff_tool\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"1. Access the [state](../../concepts/low_level#state) of the agent that is calling the handoff tool using the [InjectedState][langgraph.prebuilt.InjectedState] annotation. See [this guide](../tool-calling/#read-state) for more information.\n",
|
||||
"2. The `Command` primitive allows specifying a state update and a node transition as a single operation, making it useful for implementing handoffs.\n",
|
||||
"3. Name of the agent or node to hand off to.\n",
|
||||
"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.\n",
|
||||
"5. Indicate to LangGraph that we need to navigate to agent node in a **parent** multi-agent graph.\n",
|
||||
"\n",
|
||||
"!!! tip\n",
|
||||
"\n",
|
||||
" If you want to use tools that return `Command`, you can either use prebuilt [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent] / [`ToolNode`][langgraph.prebuilt.tool_node.ToolNode] components, or implement your own tool-executing node that collects `Command` objects returned by the tools and returns a list of them, e.g.:\n",
|
||||
" \n",
|
||||
" ```python\n",
|
||||
" def call_tools(state):\n",
|
||||
" ...\n",
|
||||
" commands = [tools_by_name[tool_call[\"name\"]].invoke(tool_call) for tool_call in tool_calls]\n",
|
||||
" return commands\n",
|
||||
" ```\n",
|
||||
"\n",
|
||||
"!!! Important\n",
|
||||
"\n",
|
||||
" This handoff implementation assumes that:\n",
|
||||
" \n",
|
||||
" - each agent receives overall message history (across all agents) in the multi-agent system as its input. If you want more control over agent inputs, see [this section](#control-agent-inputs)\n",
|
||||
" - each agent outputs its internal messages history to the overall message history of the multi-agent system. If you want more control over **how agent outputs are added**, wrap the agent in a separate node function:\n",
|
||||
"\n",
|
||||
" ```python\n",
|
||||
" def call_hotel_assistant(state):\n",
|
||||
" # return agent's final response,\n",
|
||||
" # excluding inner monologue\n",
|
||||
" response = hotel_assistant.invoke(state)\n",
|
||||
" # highlight-next-line\n",
|
||||
" return {\"messages\": response[\"messages\"][-1]}\n",
|
||||
" ```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3956f12d-285a-4799-a0a5-db13def58a15",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Control agent inputs\n",
|
||||
"\n",
|
||||
"You can use the [`Send()`][langgraph.types.Send] primitive to directly send data to the worker agents during the handoff. For example, you can request that the calling agent populate a task description for the next agent:\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"\n",
|
||||
"from typing import Annotated\n",
|
||||
"from langchain_core.tools import tool, InjectedToolCallId\n",
|
||||
"from langgraph.prebuilt import InjectedState\n",
|
||||
"from langgraph.graph import StateGraph, START, MessagesState\n",
|
||||
"# highlight-next-line\n",
|
||||
"from langgraph.types import Command, Send\n",
|
||||
"\n",
|
||||
"def create_task_description_handoff_tool(\n",
|
||||
" *, agent_name: str, description: str | None = None\n",
|
||||
"):\n",
|
||||
" name = f\"transfer_to_{agent_name}\"\n",
|
||||
" description = description or f\"Ask {agent_name} for help.\"\n",
|
||||
"\n",
|
||||
" @tool(name, description=description)\n",
|
||||
" def handoff_tool(\n",
|
||||
" # this is populated by the calling agent\n",
|
||||
" task_description: Annotated[\n",
|
||||
" str,\n",
|
||||
" \"Description of what the next agent should do, including all of the relevant context.\",\n",
|
||||
" ],\n",
|
||||
" # these parameters are ignored by the LLM\n",
|
||||
" state: Annotated[MessagesState, InjectedState],\n",
|
||||
" ) -> Command:\n",
|
||||
" task_description_message = {\"role\": \"user\", \"content\": task_description}\n",
|
||||
" agent_input = {**state, \"messages\": [task_description_message]}\n",
|
||||
" return Command(\n",
|
||||
" # highlight-next-line\n",
|
||||
" goto=[Send(agent_name, agent_input)],\n",
|
||||
" graph=Command.PARENT,\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" return handoff_tool\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"See the multi-agent [supervisor](../tutorials/agent_supervisor.ipynb#4-create-delegation-tasks) tutorial for a full example of using [`Send()`][langgraph.types.Send] in handoffs."
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "21511f57-7bf3-4223-9a17-ce9fc84c40ab",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Build a multi-agent system\n",
|
||||
"\n",
|
||||
"You can use handoffs in any agents built with LangGraph. We recommend using the prebuilt [agent](../../agents/overview) or [`ToolNode`](../tool-calling#use-prebuilt-toolnode), as they natively support handoffs tools returning `Command`. Below is an example of how you can implement a multi-agent system for booking travel using handoffs:\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"from langgraph.graph import StateGraph, START, MessagesState\n",
|
||||
"\n",
|
||||
"def create_handoff_tool(*, agent_name: str, description: str | None = None):\n",
|
||||
" # same implementation as above\n",
|
||||
" ...\n",
|
||||
" return Command(...)\n",
|
||||
"\n",
|
||||
"# Handoffs\n",
|
||||
"transfer_to_hotel_assistant = create_handoff_tool(agent_name=\"hotel_assistant\")\n",
|
||||
"transfer_to_flight_assistant = create_handoff_tool(agent_name=\"flight_assistant\")\n",
|
||||
"\n",
|
||||
"# Define agents\n",
|
||||
"flight_assistant = create_react_agent(\n",
|
||||
" model=\"anthropic:claude-3-5-sonnet-latest\",\n",
|
||||
" # highlight-next-line\n",
|
||||
" tools=[..., transfer_to_hotel_assistant],\n",
|
||||
" # highlight-next-line\n",
|
||||
" name=\"flight_assistant\"\n",
|
||||
")\n",
|
||||
"hotel_assistant = create_react_agent(\n",
|
||||
" model=\"anthropic:claude-3-5-sonnet-latest\",\n",
|
||||
" # highlight-next-line\n",
|
||||
" tools=[..., transfer_to_flight_assistant],\n",
|
||||
" # highlight-next-line\n",
|
||||
" name=\"hotel_assistant\"\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Define multi-agent graph\n",
|
||||
"multi_agent_graph = (\n",
|
||||
" StateGraph(MessagesState)\n",
|
||||
" # highlight-next-line\n",
|
||||
" .add_node(flight_assistant)\n",
|
||||
" # highlight-next-line\n",
|
||||
" .add_node(hotel_assistant)\n",
|
||||
" .add_edge(START, \"flight_assistant\")\n",
|
||||
" .compile()\n",
|
||||
")\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"??? example \"Full example: Multi-agent system for booking travel\"\n",
|
||||
"\n",
|
||||
" ```python\n",
|
||||
" from typing import Annotated\n",
|
||||
" from langchain_core.messages import convert_to_messages\n",
|
||||
" from langchain_core.tools import tool, InjectedToolCallId\n",
|
||||
" from langgraph.prebuilt import create_react_agent, InjectedState\n",
|
||||
" from langgraph.graph import StateGraph, START, MessagesState\n",
|
||||
" from langgraph.types import Command\n",
|
||||
" \n",
|
||||
" # We'll use `pretty_print_messages` helper to render the streamed agent outputs nicely\n",
|
||||
" \n",
|
||||
" def pretty_print_message(message, indent=False):\n",
|
||||
" pretty_message = message.pretty_repr(html=True)\n",
|
||||
" if not indent:\n",
|
||||
" print(pretty_message)\n",
|
||||
" return\n",
|
||||
" \n",
|
||||
" indented = \"\\n\".join(\"\\t\" + c for c in pretty_message.split(\"\\n\"))\n",
|
||||
" print(indented)\n",
|
||||
" \n",
|
||||
" \n",
|
||||
" def pretty_print_messages(update, last_message=False):\n",
|
||||
" is_subgraph = False\n",
|
||||
" if isinstance(update, tuple):\n",
|
||||
" ns, update = update\n",
|
||||
" # skip parent graph updates in the printouts\n",
|
||||
" if len(ns) == 0:\n",
|
||||
" return\n",
|
||||
" \n",
|
||||
" graph_id = ns[-1].split(\":\")[0]\n",
|
||||
" print(f\"Update from subgraph {graph_id}:\")\n",
|
||||
" print(\"\\n\")\n",
|
||||
" is_subgraph = True\n",
|
||||
" \n",
|
||||
" for node_name, node_update in update.items():\n",
|
||||
" update_label = f\"Update from node {node_name}:\"\n",
|
||||
" if is_subgraph:\n",
|
||||
" update_label = \"\\t\" + update_label\n",
|
||||
" \n",
|
||||
" print(update_label)\n",
|
||||
" print(\"\\n\")\n",
|
||||
" \n",
|
||||
" messages = convert_to_messages(node_update[\"messages\"])\n",
|
||||
" if last_message:\n",
|
||||
" messages = messages[-1:]\n",
|
||||
" \n",
|
||||
" for m in messages:\n",
|
||||
" pretty_print_message(m, indent=is_subgraph)\n",
|
||||
" print(\"\\n\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" def create_handoff_tool(*, agent_name: str, description: str | None = None):\n",
|
||||
" name = f\"transfer_to_{agent_name}\"\n",
|
||||
" description = description or f\"Transfer to {agent_name}\"\n",
|
||||
" \n",
|
||||
" @tool(name, description=description)\n",
|
||||
" def handoff_tool(\n",
|
||||
" # highlight-next-line\n",
|
||||
" state: Annotated[MessagesState, InjectedState], # (1)!\n",
|
||||
" # highlight-next-line\n",
|
||||
" tool_call_id: Annotated[str, InjectedToolCallId],\n",
|
||||
" ) -> Command:\n",
|
||||
" tool_message = {\n",
|
||||
" \"role\": \"tool\",\n",
|
||||
" \"content\": f\"Successfully transferred to {agent_name}\",\n",
|
||||
" \"name\": name,\n",
|
||||
" \"tool_call_id\": tool_call_id,\n",
|
||||
" }\n",
|
||||
" return Command( # (2)!\n",
|
||||
" # highlight-next-line\n",
|
||||
" goto=agent_name, # (3)!\n",
|
||||
" # highlight-next-line\n",
|
||||
" update={\"messages\": state[\"messages\"] + [tool_message]}, # (4)!\n",
|
||||
" # highlight-next-line\n",
|
||||
" graph=Command.PARENT, # (5)!\n",
|
||||
" )\n",
|
||||
" return handoff_tool\n",
|
||||
" \n",
|
||||
" # Handoffs\n",
|
||||
" transfer_to_hotel_assistant = create_handoff_tool(\n",
|
||||
" agent_name=\"hotel_assistant\",\n",
|
||||
" description=\"Transfer user to the hotel-booking assistant.\",\n",
|
||||
" )\n",
|
||||
" transfer_to_flight_assistant = create_handoff_tool(\n",
|
||||
" agent_name=\"flight_assistant\",\n",
|
||||
" description=\"Transfer user to the flight-booking assistant.\",\n",
|
||||
" )\n",
|
||||
" \n",
|
||||
" # Simple agent tools\n",
|
||||
" def book_hotel(hotel_name: str):\n",
|
||||
" \"\"\"Book a hotel\"\"\"\n",
|
||||
" return f\"Successfully booked a stay at {hotel_name}.\"\n",
|
||||
" \n",
|
||||
" def book_flight(from_airport: str, to_airport: str):\n",
|
||||
" \"\"\"Book a flight\"\"\"\n",
|
||||
" return f\"Successfully booked a flight from {from_airport} to {to_airport}.\"\n",
|
||||
" \n",
|
||||
" # Define agents\n",
|
||||
" flight_assistant = create_react_agent(\n",
|
||||
" model=\"anthropic:claude-3-5-sonnet-latest\",\n",
|
||||
" # highlight-next-line\n",
|
||||
" tools=[book_flight, transfer_to_hotel_assistant],\n",
|
||||
" prompt=\"You are a flight booking assistant\",\n",
|
||||
" # highlight-next-line\n",
|
||||
" name=\"flight_assistant\"\n",
|
||||
" )\n",
|
||||
" hotel_assistant = create_react_agent(\n",
|
||||
" model=\"anthropic:claude-3-5-sonnet-latest\",\n",
|
||||
" # highlight-next-line\n",
|
||||
" tools=[book_hotel, transfer_to_flight_assistant],\n",
|
||||
" prompt=\"You are a hotel booking assistant\",\n",
|
||||
" # highlight-next-line\n",
|
||||
" name=\"hotel_assistant\"\n",
|
||||
" )\n",
|
||||
" \n",
|
||||
" # Define multi-agent graph\n",
|
||||
" multi_agent_graph = (\n",
|
||||
" StateGraph(MessagesState)\n",
|
||||
" .add_node(flight_assistant)\n",
|
||||
" .add_node(hotel_assistant)\n",
|
||||
" .add_edge(START, \"flight_assistant\")\n",
|
||||
" .compile()\n",
|
||||
" )\n",
|
||||
" \n",
|
||||
" # Run the multi-agent graph\n",
|
||||
" for chunk in multi_agent_graph.stream(\n",
|
||||
" {\n",
|
||||
" \"messages\": [\n",
|
||||
" {\n",
|
||||
" \"role\": \"user\",\n",
|
||||
" \"content\": \"book a flight from BOS to JFK and a stay at McKittrick Hotel\"\n",
|
||||
" }\n",
|
||||
" ]\n",
|
||||
" },\n",
|
||||
" # highlight-next-line\n",
|
||||
" subgraphs=True\n",
|
||||
" ):\n",
|
||||
" pretty_print_messages(chunk)\n",
|
||||
" ```\n",
|
||||
"\n",
|
||||
" 1. Access agent's state\n",
|
||||
" 2. The `Command` primitive allows specifying a state update and a node transition as a single operation, making it useful for implementing handoffs.\n",
|
||||
" 3. Name of the agent or node to hand off to.\n",
|
||||
" 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.\n",
|
||||
" 5. Indicate to LangGraph that we need to navigate to agent node in a **parent** multi-agent graph."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e8314da4-9971-429b-9e70-58b40795de74",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Multi-turn conversation\n",
|
||||
"\n",
|
||||
"Users might want to engage in a *multi-turn conversation* with one or more agents. To build a system that can handle this, you can create a node that uses an [`interrupt`][langgraph.types.interrupt] to collect user input and routes back to the **active** agent.\n",
|
||||
"\n",
|
||||
"The agents can then be implemented as nodes in a graph that executes agent steps and determines the next action:\n",
|
||||
"\n",
|
||||
"1. **Wait for user input** to continue the conversation, or \n",
|
||||
"2. **Route to another agent** (or back to itself, such as in a loop) via a [handoff](#handoffs)\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"def human(state) -> Command[Literal[\"agent\", \"another_agent\"]]:\n",
|
||||
" \"\"\"A node for collecting user input.\"\"\"\n",
|
||||
" user_input = interrupt(value=\"Ready for user input.\")\n",
|
||||
"\n",
|
||||
" # Determine the active agent.\n",
|
||||
" active_agent = ...\n",
|
||||
"\n",
|
||||
" ...\n",
|
||||
" return Command(\n",
|
||||
" update={\n",
|
||||
" \"messages\": [{\n",
|
||||
" \"role\": \"human\",\n",
|
||||
" \"content\": user_input,\n",
|
||||
" }]\n",
|
||||
" },\n",
|
||||
" goto=active_agent\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"def agent(state) -> Command[Literal[\"agent\", \"another_agent\", \"human\"]]:\n",
|
||||
" # The condition for routing/halting can be anything, e.g. LLM tool call / structured output, etc.\n",
|
||||
" goto = get_next_agent(...) # 'agent' / 'another_agent'\n",
|
||||
" if goto:\n",
|
||||
" return Command(goto=goto, update={\"my_state_key\": \"my_state_value\"})\n",
|
||||
" else:\n",
|
||||
" return Command(goto=\"human\") # Go to human node\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"??? example \"Full example: multi-agent system for travel recommendations\"\n",
|
||||
"\n",
|
||||
" In this example, we will build a team of travel assistant agents that can communicate with each other via handoffs.\n",
|
||||
" \n",
|
||||
" We will create 2 agents:\n",
|
||||
" \n",
|
||||
" * travel_advisor: can help with travel destination recommendations. Can ask hotel_advisor for help.\n",
|
||||
" * hotel_advisor: can help with hotel recommendations. Can ask travel_advisor for help.\n",
|
||||
"\n",
|
||||
" ```python\n",
|
||||
" from langchain_anthropic import ChatAnthropic\n",
|
||||
" from langgraph.graph import MessagesState, StateGraph, START\n",
|
||||
" from langgraph.prebuilt import create_react_agent, InjectedState\n",
|
||||
" from langgraph.types import Command, interrupt\n",
|
||||
" from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
" \n",
|
||||
" \n",
|
||||
" model = ChatAnthropic(model=\"claude-3-5-sonnet-latest\")\n",
|
||||
"\n",
|
||||
" class MultiAgentState(MessagesState):\n",
|
||||
" last_active_agent: str\n",
|
||||
" \n",
|
||||
" \n",
|
||||
" # Define travel advisor tools and ReAct agent\n",
|
||||
" travel_advisor_tools = [\n",
|
||||
" get_travel_recommendations,\n",
|
||||
" make_handoff_tool(agent_name=\"hotel_advisor\"),\n",
|
||||
" ]\n",
|
||||
" travel_advisor = create_react_agent(\n",
|
||||
" model,\n",
|
||||
" travel_advisor_tools,\n",
|
||||
" prompt=(\n",
|
||||
" \"You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc). \"\n",
|
||||
" \"If you need hotel recommendations, ask 'hotel_advisor' for help. \"\n",
|
||||
" \"You MUST include human-readable response before transferring to another agent.\"\n",
|
||||
" ),\n",
|
||||
" )\n",
|
||||
" \n",
|
||||
" \n",
|
||||
" def call_travel_advisor(\n",
|
||||
" state: MultiAgentState,\n",
|
||||
" ) -> Command[Literal[\"hotel_advisor\", \"human\"]]:\n",
|
||||
" # You can also add additional logic like changing the input to the agent / output from the agent, etc.\n",
|
||||
" # NOTE: we're invoking the ReAct agent with the full history of messages in the state\n",
|
||||
" response = travel_advisor.invoke(state)\n",
|
||||
" update = {**response, \"last_active_agent\": \"travel_advisor\"}\n",
|
||||
" return Command(update=update, goto=\"human\")\n",
|
||||
" \n",
|
||||
" \n",
|
||||
" # Define hotel advisor tools and ReAct agent\n",
|
||||
" hotel_advisor_tools = [\n",
|
||||
" get_hotel_recommendations,\n",
|
||||
" make_handoff_tool(agent_name=\"travel_advisor\"),\n",
|
||||
" ]\n",
|
||||
" hotel_advisor = create_react_agent(\n",
|
||||
" model,\n",
|
||||
" hotel_advisor_tools,\n",
|
||||
" prompt=(\n",
|
||||
" \"You are a hotel expert that can provide hotel recommendations for a given destination. \"\n",
|
||||
" \"If you need help picking travel destinations, ask 'travel_advisor' for help.\"\n",
|
||||
" \"You MUST include human-readable response before transferring to another agent.\"\n",
|
||||
" ),\n",
|
||||
" )\n",
|
||||
" \n",
|
||||
" \n",
|
||||
" def call_hotel_advisor(\n",
|
||||
" state: MultiAgentState,\n",
|
||||
" ) -> Command[Literal[\"travel_advisor\", \"human\"]]:\n",
|
||||
" response = hotel_advisor.invoke(state)\n",
|
||||
" update = {**response, \"last_active_agent\": \"hotel_advisor\"}\n",
|
||||
" return Command(update=update, goto=\"human\")\n",
|
||||
" \n",
|
||||
" \n",
|
||||
" def human_node(\n",
|
||||
" state: MultiAgentState, config\n",
|
||||
" ) -> Command[Literal[\"hotel_advisor\", \"travel_advisor\", \"human\"]]:\n",
|
||||
" \"\"\"A node for collecting user input.\"\"\"\n",
|
||||
" \n",
|
||||
" user_input = interrupt(value=\"Ready for user input.\")\n",
|
||||
" active_agent = state[\"last_active_agent\"]\n",
|
||||
" \n",
|
||||
" return Command(\n",
|
||||
" update={\n",
|
||||
" \"messages\": [\n",
|
||||
" {\n",
|
||||
" \"role\": \"human\",\n",
|
||||
" \"content\": user_input,\n",
|
||||
" }\n",
|
||||
" ]\n",
|
||||
" },\n",
|
||||
" goto=active_agent,\n",
|
||||
" )\n",
|
||||
" \n",
|
||||
" \n",
|
||||
" builder = StateGraph(MultiAgentState)\n",
|
||||
" builder.add_node(\"travel_advisor\", call_travel_advisor)\n",
|
||||
" builder.add_node(\"hotel_advisor\", call_hotel_advisor)\n",
|
||||
" \n",
|
||||
" # This adds a node to collect human input, which will route\n",
|
||||
" # back to the active agent.\n",
|
||||
" builder.add_node(\"human\", human_node)\n",
|
||||
" \n",
|
||||
" # We'll always start with a general travel advisor.\n",
|
||||
" builder.add_edge(START, \"travel_advisor\")\n",
|
||||
" \n",
|
||||
" \n",
|
||||
" checkpointer = MemorySaver()\n",
|
||||
" graph = builder.compile(checkpointer=checkpointer)\n",
|
||||
" ```\n",
|
||||
" \n",
|
||||
" Let's test a multi turn conversation with this application.\n",
|
||||
"\n",
|
||||
" ```python\n",
|
||||
" import uuid\n",
|
||||
" \n",
|
||||
" thread_config = {\"configurable\": {\"thread_id\": str(uuid.uuid4())}}\n",
|
||||
" \n",
|
||||
" inputs = [\n",
|
||||
" # 1st round of conversation,\n",
|
||||
" {\n",
|
||||
" \"messages\": [\n",
|
||||
" {\"role\": \"user\", \"content\": \"i wanna go somewhere warm in the caribbean\"}\n",
|
||||
" ]\n",
|
||||
" },\n",
|
||||
" # Since we're using `interrupt`, we'll need to resume using the Command primitive.\n",
|
||||
" # 2nd round of conversation,\n",
|
||||
" Command(\n",
|
||||
" resume=\"could you recommend a nice hotel in one of the areas and tell me which area it is.\"\n",
|
||||
" ),\n",
|
||||
" # 3rd round of conversation,\n",
|
||||
" Command(\n",
|
||||
" resume=\"i like the first one. could you recommend something to do near the hotel?\"\n",
|
||||
" ),\n",
|
||||
" ]\n",
|
||||
" \n",
|
||||
" for idx, user_input in enumerate(inputs):\n",
|
||||
" print()\n",
|
||||
" print(f\"--- Conversation Turn {idx + 1} ---\")\n",
|
||||
" print()\n",
|
||||
" print(f\"User: {user_input}\")\n",
|
||||
" print()\n",
|
||||
" for update in graph.stream(\n",
|
||||
" user_input,\n",
|
||||
" config=thread_config,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
" ):\n",
|
||||
" for node_id, value in update.items():\n",
|
||||
" if isinstance(value, dict) and value.get(\"messages\", []):\n",
|
||||
" last_message = value[\"messages\"][-1]\n",
|
||||
" if isinstance(last_message, dict) or last_message.type != \"ai\":\n",
|
||||
" continue\n",
|
||||
" print(f\"{node_id}: {last_message.content}\")\n",
|
||||
" ```\n",
|
||||
" \n",
|
||||
" ```\n",
|
||||
" --- Conversation Turn 1 ---\n",
|
||||
" \n",
|
||||
" User: {'messages': [{'role': 'user', 'content': 'i wanna go somewhere warm in the caribbean'}]}\n",
|
||||
" \n",
|
||||
" travel_advisor: Based on the recommendations, Aruba would be an excellent choice for your Caribbean getaway! Aruba is known as \"One Happy Island\" and offers:\n",
|
||||
" - Year-round warm weather with consistent temperatures around 82°F (28°C)\n",
|
||||
" - Beautiful white sand beaches like Eagle Beach and Palm Beach\n",
|
||||
" - Clear turquoise waters perfect for swimming and snorkeling\n",
|
||||
" - Minimal rainfall and location outside the hurricane belt\n",
|
||||
" - A blend of Caribbean and Dutch culture\n",
|
||||
" - Great dining options and nightlife\n",
|
||||
" - Various water sports and activities\n",
|
||||
" \n",
|
||||
" Would you like me to get some specific hotel recommendations in Aruba for your stay? I can transfer you to our hotel advisor who can help with accommodations.\n",
|
||||
" \n",
|
||||
" --- Conversation Turn 2 ---\n",
|
||||
" \n",
|
||||
" User: Command(resume='could you recommend a nice hotel in one of the areas and tell me which area it is.')\n",
|
||||
" \n",
|
||||
" hotel_advisor: Based on the recommendations, I can suggest two excellent options:\n",
|
||||
" \n",
|
||||
" 1. The Ritz-Carlton, Aruba - Located in Palm Beach\n",
|
||||
" - This luxury resort is situated in the vibrant Palm Beach area\n",
|
||||
" - Known for its exceptional service and amenities\n",
|
||||
" - Perfect if you want to be close to dining, shopping, and entertainment\n",
|
||||
" - Features multiple restaurants, a casino, and a world-class spa\n",
|
||||
" - Located on a pristine stretch of Palm Beach\n",
|
||||
" \n",
|
||||
" 2. Bucuti & Tara Beach Resort - Located in Eagle Beach\n",
|
||||
" - An adults-only boutique resort on Eagle Beach\n",
|
||||
" - Known for being more intimate and peaceful\n",
|
||||
" - Award-winning for its sustainability practices\n",
|
||||
" - Perfect for a romantic getaway or peaceful vacation\n",
|
||||
" - Located on one of the most beautiful beaches in the Caribbean\n",
|
||||
" \n",
|
||||
" Would you like more specific information about either of these properties or their locations?\n",
|
||||
" \n",
|
||||
" --- Conversation Turn 3 ---\n",
|
||||
" \n",
|
||||
" User: Command(resume='i like the first one. could you recommend something to do near the hotel?')\n",
|
||||
" \n",
|
||||
" travel_advisor: Near the Ritz-Carlton in Palm Beach, here are some highly recommended activities:\n",
|
||||
" \n",
|
||||
" 1. Visit the Palm Beach Plaza Mall - Just a short walk from the hotel, featuring shopping, dining, and entertainment\n",
|
||||
" 2. Try your luck at the Stellaris Casino - It's right in the Ritz-Carlton\n",
|
||||
" 3. Take a sunset sailing cruise - Many depart from the nearby pier\n",
|
||||
" 4. Visit the California Lighthouse - A scenic landmark just north of Palm Beach\n",
|
||||
" 5. Enjoy water sports at Palm Beach:\n",
|
||||
" - Jet skiing\n",
|
||||
" - Parasailing\n",
|
||||
" - Snorkeling\n",
|
||||
" - Stand-up paddleboarding\n",
|
||||
" \n",
|
||||
" Would you like more specific information about any of these activities or would you like to know about other options in the area?\n",
|
||||
" ```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "04d18c63-a0eb-45ac-86dc-0cc5bd683973",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Prebuilt implementations"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e0e4ce57-f8de-4f37-836e-c1e1a02dd7b7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"LangGraph comes with prebuilt implementations of two of the most popular multi-agent architectures:\n",
|
||||
"\n",
|
||||
"- [supervisor](../../agents/multi-agent#supervisor) — individual agents are coordinated by a central supervisor agent. The supervisor controls all communication flow and task delegation, making decisions about which agent to invoke based on the current context and task requirements. You can use [`langgraph-supervisor`](https://github.com/langchain-ai/langgraph-supervisor-py) library to create a supervisor multi-agent systems.\n",
|
||||
"- [swarm](../../agents/multi-agent#supervisor) — agents dynamically hand off control to one another based on their specializations. The system remembers which agent was last active, ensuring that on subsequent interactions, the conversation resumes with that agent. You can use [`langgraph-swarm`](https://github.com/langchain-ai/langgraph-swarm-py) library to create a swarm multi-agent systems."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,580 @@
|
||||
# Build multi-agent systems
|
||||
|
||||
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.
|
||||
|
||||
This guide covers the following:
|
||||
|
||||
* implementing [handoffs](#handoffs) between agents
|
||||
* using handoffs and the prebuilt [agent](../agents/agents.md) to [build a custom multi-agent system](#build-a-multi-agent-system)
|
||||
|
||||
To get started with building multi-agent systems, check out LangGraph [prebuilt implementations](#prebuilt-implementations) of two of the most popular multi-agent architectures — [supervisor](../agents/multi-agent.md#supervisor) and [swarm](../agents/multi-agent.md#swarm).
|
||||
|
||||
## Handoffs
|
||||
|
||||
To set up communication between the agents in a multi-agent system you can use [**handoffs**](../concepts/multi_agent.md#handoffs) — a pattern where one agent *hands off* control to another. Handoffs allow you to specify:
|
||||
|
||||
- **destination**: target agent to navigate to (e.g., name of the LangGraph node to go to)
|
||||
- **payload**: information to pass to that agent (e.g., state update)
|
||||
|
||||
### Create handoffs
|
||||
|
||||
To implement handoffs, you can return `Command` objects from your agent nodes or tools:
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from langchain_core.tools import tool, InjectedToolCallId
|
||||
from langgraph.prebuilt import create_react_agent, InjectedState
|
||||
from langgraph.graph import StateGraph, START, MessagesState
|
||||
from langgraph.types import Command
|
||||
|
||||
def create_handoff_tool(*, agent_name: str, description: str | None = None):
|
||||
name = f"transfer_to_{agent_name}"
|
||||
description = description or f"Transfer to {agent_name}"
|
||||
|
||||
@tool(name, description=description)
|
||||
def handoff_tool(
|
||||
# highlight-next-line
|
||||
state: Annotated[MessagesState, InjectedState], # (1)!
|
||||
# highlight-next-line
|
||||
tool_call_id: Annotated[str, InjectedToolCallId],
|
||||
) -> Command:
|
||||
tool_message = {
|
||||
"role": "tool",
|
||||
"content": f"Successfully transferred to {agent_name}",
|
||||
"name": name,
|
||||
"tool_call_id": tool_call_id,
|
||||
}
|
||||
return Command( # (2)!
|
||||
# highlight-next-line
|
||||
goto=agent_name, # (3)!
|
||||
# highlight-next-line
|
||||
update={"messages": state["messages"] + [tool_message]}, # (4)!
|
||||
# highlight-next-line
|
||||
graph=Command.PARENT, # (5)!
|
||||
)
|
||||
return handoff_tool
|
||||
```
|
||||
|
||||
1. Access the [state](../concepts/low_level.md#state) of the agent that is calling the handoff tool using the [InjectedState][langgraph.prebuilt.InjectedState] annotation.
|
||||
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.
|
||||
|
||||
!!! tip
|
||||
|
||||
If you want to use tools that return `Command`, you can either use prebuilt [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent] / [`ToolNode`][langgraph.prebuilt.tool_node.ToolNode] components, or implement your own tool-executing node that collects `Command` objects returned by the tools and returns a list of them, e.g.:
|
||||
|
||||
```python
|
||||
def call_tools(state):
|
||||
...
|
||||
commands = [tools_by_name[tool_call["name"]].invoke(tool_call) for tool_call in tool_calls]
|
||||
return commands
|
||||
```
|
||||
|
||||
!!! Important
|
||||
|
||||
This handoff implementation assumes that:
|
||||
|
||||
- each agent receives overall message history (across all agents) in the multi-agent system as its input. If you want more control over agent inputs, see [this section](#control-agent-inputs)
|
||||
- each agent outputs its internal messages history to the overall message history of the multi-agent system. If you want more control over **how agent outputs are added**, wrap the agent in a separate node function:
|
||||
|
||||
```python
|
||||
def call_hotel_assistant(state):
|
||||
# return agent's final response,
|
||||
# excluding inner monologue
|
||||
response = hotel_assistant.invoke(state)
|
||||
# highlight-next-line
|
||||
return {"messages": response["messages"][-1]}
|
||||
```
|
||||
|
||||
### Control agent inputs
|
||||
|
||||
You can use the [`Send()`][langgraph.types.Send] primitive to directly send data to the worker agents during the handoff. For example, you can request that the calling agent populate a task description for the next agent:
|
||||
|
||||
```python
|
||||
|
||||
from typing import Annotated
|
||||
from langchain_core.tools import tool, InjectedToolCallId
|
||||
from langgraph.prebuilt import InjectedState
|
||||
from langgraph.graph import StateGraph, START, MessagesState
|
||||
# highlight-next-line
|
||||
from langgraph.types import Command, Send
|
||||
|
||||
def create_task_description_handoff_tool(
|
||||
*, agent_name: str, description: str | None = None
|
||||
):
|
||||
name = f"transfer_to_{agent_name}"
|
||||
description = description or f"Ask {agent_name} for help."
|
||||
|
||||
@tool(name, description=description)
|
||||
def handoff_tool(
|
||||
# this is populated by the calling agent
|
||||
task_description: Annotated[
|
||||
str,
|
||||
"Description of what the next agent should do, including all of the relevant context.",
|
||||
],
|
||||
# these parameters are ignored by the LLM
|
||||
state: Annotated[MessagesState, InjectedState],
|
||||
) -> Command:
|
||||
task_description_message = {"role": "user", "content": task_description}
|
||||
agent_input = {**state, "messages": [task_description_message]}
|
||||
return Command(
|
||||
# highlight-next-line
|
||||
goto=[Send(agent_name, agent_input)],
|
||||
graph=Command.PARENT,
|
||||
)
|
||||
|
||||
return handoff_tool
|
||||
```
|
||||
|
||||
See the multi-agent [supervisor](../tutorials/multi_agent/agent_supervisor.md#4-create-delegation-tasks) example for a full example of using [`Send()`][langgraph.types.Send] in handoffs.
|
||||
|
||||
## Build a multi-agent system
|
||||
|
||||
You can use handoffs in any agents built with LangGraph. We recommend using the prebuilt [agent](../agents/overview.md) or [`ToolNode`](./tool-calling.md#toolnode), as they natively support handoffs tools returning `Command`. Below is an example of how you can implement a multi-agent system for booking travel using handoffs:
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from langgraph.graph import StateGraph, START, MessagesState
|
||||
|
||||
def create_handoff_tool(*, agent_name: str, description: str | None = None):
|
||||
# same implementation as above
|
||||
...
|
||||
return Command(...)
|
||||
|
||||
# Handoffs
|
||||
transfer_to_hotel_assistant = create_handoff_tool(agent_name="hotel_assistant")
|
||||
transfer_to_flight_assistant = create_handoff_tool(agent_name="flight_assistant")
|
||||
|
||||
# Define agents
|
||||
flight_assistant = create_react_agent(
|
||||
model="anthropic:claude-3-5-sonnet-latest",
|
||||
# highlight-next-line
|
||||
tools=[..., transfer_to_hotel_assistant],
|
||||
# highlight-next-line
|
||||
name="flight_assistant"
|
||||
)
|
||||
hotel_assistant = create_react_agent(
|
||||
model="anthropic:claude-3-5-sonnet-latest",
|
||||
# highlight-next-line
|
||||
tools=[..., transfer_to_flight_assistant],
|
||||
# highlight-next-line
|
||||
name="hotel_assistant"
|
||||
)
|
||||
|
||||
# Define multi-agent graph
|
||||
multi_agent_graph = (
|
||||
StateGraph(MessagesState)
|
||||
# highlight-next-line
|
||||
.add_node(flight_assistant)
|
||||
# highlight-next-line
|
||||
.add_node(hotel_assistant)
|
||||
.add_edge(START, "flight_assistant")
|
||||
.compile()
|
||||
)
|
||||
```
|
||||
|
||||
??? example "Full example: Multi-agent system for booking travel"
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from langchain_core.messages import convert_to_messages
|
||||
from langchain_core.tools import tool, InjectedToolCallId
|
||||
from langgraph.prebuilt import create_react_agent, InjectedState
|
||||
from langgraph.graph import StateGraph, START, MessagesState
|
||||
from langgraph.types import Command
|
||||
|
||||
# We'll use `pretty_print_messages` helper to render the streamed agent outputs nicely
|
||||
|
||||
def pretty_print_message(message, indent=False):
|
||||
pretty_message = message.pretty_repr(html=True)
|
||||
if not indent:
|
||||
print(pretty_message)
|
||||
return
|
||||
|
||||
indented = "\n".join("\t" + c for c in pretty_message.split("\n"))
|
||||
print(indented)
|
||||
|
||||
|
||||
def pretty_print_messages(update, last_message=False):
|
||||
is_subgraph = False
|
||||
if isinstance(update, tuple):
|
||||
ns, update = update
|
||||
# skip parent graph updates in the printouts
|
||||
if len(ns) == 0:
|
||||
return
|
||||
|
||||
graph_id = ns[-1].split(":")[0]
|
||||
print(f"Update from subgraph {graph_id}:")
|
||||
print("\n")
|
||||
is_subgraph = True
|
||||
|
||||
for node_name, node_update in update.items():
|
||||
update_label = f"Update from node {node_name}:"
|
||||
if is_subgraph:
|
||||
update_label = "\t" + update_label
|
||||
|
||||
print(update_label)
|
||||
print("\n")
|
||||
|
||||
messages = convert_to_messages(node_update["messages"])
|
||||
if last_message:
|
||||
messages = messages[-1:]
|
||||
|
||||
for m in messages:
|
||||
pretty_print_message(m, indent=is_subgraph)
|
||||
print("\n")
|
||||
|
||||
|
||||
def create_handoff_tool(*, agent_name: str, description: str | None = None):
|
||||
name = f"transfer_to_{agent_name}"
|
||||
description = description or f"Transfer to {agent_name}"
|
||||
|
||||
@tool(name, description=description)
|
||||
def handoff_tool(
|
||||
# highlight-next-line
|
||||
state: Annotated[MessagesState, InjectedState], # (1)!
|
||||
# highlight-next-line
|
||||
tool_call_id: Annotated[str, InjectedToolCallId],
|
||||
) -> Command:
|
||||
tool_message = {
|
||||
"role": "tool",
|
||||
"content": f"Successfully transferred to {agent_name}",
|
||||
"name": name,
|
||||
"tool_call_id": tool_call_id,
|
||||
}
|
||||
return Command( # (2)!
|
||||
# highlight-next-line
|
||||
goto=agent_name, # (3)!
|
||||
# highlight-next-line
|
||||
update={"messages": state["messages"] + [tool_message]}, # (4)!
|
||||
# highlight-next-line
|
||||
graph=Command.PARENT, # (5)!
|
||||
)
|
||||
return handoff_tool
|
||||
|
||||
# Handoffs
|
||||
transfer_to_hotel_assistant = create_handoff_tool(
|
||||
agent_name="hotel_assistant",
|
||||
description="Transfer user to the hotel-booking assistant.",
|
||||
)
|
||||
transfer_to_flight_assistant = create_handoff_tool(
|
||||
agent_name="flight_assistant",
|
||||
description="Transfer user to the flight-booking assistant.",
|
||||
)
|
||||
|
||||
# Simple agent tools
|
||||
def book_hotel(hotel_name: str):
|
||||
"""Book a hotel"""
|
||||
return f"Successfully booked a stay at {hotel_name}."
|
||||
|
||||
def book_flight(from_airport: str, to_airport: str):
|
||||
"""Book a flight"""
|
||||
return f"Successfully booked a flight from {from_airport} to {to_airport}."
|
||||
|
||||
# Define agents
|
||||
flight_assistant = create_react_agent(
|
||||
model="anthropic:claude-3-5-sonnet-latest",
|
||||
# highlight-next-line
|
||||
tools=[book_flight, transfer_to_hotel_assistant],
|
||||
prompt="You are a flight booking assistant",
|
||||
# highlight-next-line
|
||||
name="flight_assistant"
|
||||
)
|
||||
hotel_assistant = create_react_agent(
|
||||
model="anthropic:claude-3-5-sonnet-latest",
|
||||
# highlight-next-line
|
||||
tools=[book_hotel, transfer_to_flight_assistant],
|
||||
prompt="You are a hotel booking assistant",
|
||||
# highlight-next-line
|
||||
name="hotel_assistant"
|
||||
)
|
||||
|
||||
# Define multi-agent graph
|
||||
multi_agent_graph = (
|
||||
StateGraph(MessagesState)
|
||||
.add_node(flight_assistant)
|
||||
.add_node(hotel_assistant)
|
||||
.add_edge(START, "flight_assistant")
|
||||
.compile()
|
||||
)
|
||||
|
||||
# Run the multi-agent graph
|
||||
for chunk in multi_agent_graph.stream(
|
||||
{
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "book a flight from BOS to JFK and a stay at McKittrick Hotel"
|
||||
}
|
||||
]
|
||||
},
|
||||
# highlight-next-line
|
||||
subgraphs=True
|
||||
):
|
||||
pretty_print_messages(chunk)
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## Multi-turn conversation
|
||||
|
||||
Users might want to engage in a *multi-turn conversation* with one or more agents. To build a system that can handle this, you can create a node that uses an [`interrupt`][langgraph.types.interrupt] to collect user input and routes back to the **active** agent.
|
||||
|
||||
The agents can then be implemented as nodes in a graph that executes agent steps and determines the next action:
|
||||
|
||||
1. **Wait for user input** to continue the conversation, or
|
||||
2. **Route to another agent** (or back to itself, such as in a loop) via a [handoff](#handoffs)
|
||||
|
||||
```python
|
||||
def human(state) -> Command[Literal["agent", "another_agent"]]:
|
||||
"""A node for collecting user input."""
|
||||
user_input = interrupt(value="Ready for user input.")
|
||||
|
||||
# Determine the active agent.
|
||||
active_agent = ...
|
||||
|
||||
...
|
||||
return Command(
|
||||
update={
|
||||
"messages": [{
|
||||
"role": "human",
|
||||
"content": user_input,
|
||||
}]
|
||||
},
|
||||
goto=active_agent
|
||||
)
|
||||
|
||||
def agent(state) -> Command[Literal["agent", "another_agent", "human"]]:
|
||||
# The condition for routing/halting can be anything, e.g. LLM tool call / structured output, etc.
|
||||
goto = get_next_agent(...) # 'agent' / 'another_agent'
|
||||
if goto:
|
||||
return Command(goto=goto, update={"my_state_key": "my_state_value"})
|
||||
else:
|
||||
return Command(goto="human") # Go to human node
|
||||
```
|
||||
|
||||
??? example "Full example: multi-agent system for travel recommendations"
|
||||
|
||||
In this example, we will build a team of travel assistant agents that can communicate with each other via handoffs.
|
||||
|
||||
We will create 2 agents:
|
||||
|
||||
* travel_advisor: can help with travel destination recommendations. Can ask hotel_advisor for help.
|
||||
* hotel_advisor: can help with hotel recommendations. Can ask travel_advisor for help.
|
||||
|
||||
```python
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langgraph.graph import MessagesState, StateGraph, START
|
||||
from langgraph.prebuilt import create_react_agent, InjectedState
|
||||
from langgraph.types import Command, interrupt
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
|
||||
|
||||
model = ChatAnthropic(model="claude-3-5-sonnet-latest")
|
||||
|
||||
class MultiAgentState(MessagesState):
|
||||
last_active_agent: str
|
||||
|
||||
|
||||
# Define travel advisor tools and ReAct agent
|
||||
travel_advisor_tools = [
|
||||
get_travel_recommendations,
|
||||
make_handoff_tool(agent_name="hotel_advisor"),
|
||||
]
|
||||
travel_advisor = create_react_agent(
|
||||
model,
|
||||
travel_advisor_tools,
|
||||
prompt=(
|
||||
"You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc). "
|
||||
"If you need hotel recommendations, ask 'hotel_advisor' for help. "
|
||||
"You MUST include human-readable response before transferring to another agent."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def call_travel_advisor(
|
||||
state: MultiAgentState,
|
||||
) -> Command[Literal["hotel_advisor", "human"]]:
|
||||
# You can also add additional logic like changing the input to the agent / output from the agent, etc.
|
||||
# NOTE: we're invoking the ReAct agent with the full history of messages in the state
|
||||
response = travel_advisor.invoke(state)
|
||||
update = {**response, "last_active_agent": "travel_advisor"}
|
||||
return Command(update=update, goto="human")
|
||||
|
||||
|
||||
# Define hotel advisor tools and ReAct agent
|
||||
hotel_advisor_tools = [
|
||||
get_hotel_recommendations,
|
||||
make_handoff_tool(agent_name="travel_advisor"),
|
||||
]
|
||||
hotel_advisor = create_react_agent(
|
||||
model,
|
||||
hotel_advisor_tools,
|
||||
prompt=(
|
||||
"You are a hotel expert that can provide hotel recommendations for a given destination. "
|
||||
"If you need help picking travel destinations, ask 'travel_advisor' for help."
|
||||
"You MUST include human-readable response before transferring to another agent."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def call_hotel_advisor(
|
||||
state: MultiAgentState,
|
||||
) -> Command[Literal["travel_advisor", "human"]]:
|
||||
response = hotel_advisor.invoke(state)
|
||||
update = {**response, "last_active_agent": "hotel_advisor"}
|
||||
return Command(update=update, goto="human")
|
||||
|
||||
|
||||
def human_node(
|
||||
state: MultiAgentState, config
|
||||
) -> Command[Literal["hotel_advisor", "travel_advisor", "human"]]:
|
||||
"""A node for collecting user input."""
|
||||
|
||||
user_input = interrupt(value="Ready for user input.")
|
||||
active_agent = state["last_active_agent"]
|
||||
|
||||
return Command(
|
||||
update={
|
||||
"messages": [
|
||||
{
|
||||
"role": "human",
|
||||
"content": user_input,
|
||||
}
|
||||
]
|
||||
},
|
||||
goto=active_agent,
|
||||
)
|
||||
|
||||
|
||||
builder = StateGraph(MultiAgentState)
|
||||
builder.add_node("travel_advisor", call_travel_advisor)
|
||||
builder.add_node("hotel_advisor", call_hotel_advisor)
|
||||
|
||||
# This adds a node to collect human input, which will route
|
||||
# back to the active agent.
|
||||
builder.add_node("human", human_node)
|
||||
|
||||
# We'll always start with a general travel advisor.
|
||||
builder.add_edge(START, "travel_advisor")
|
||||
|
||||
|
||||
checkpointer = MemorySaver()
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
```
|
||||
|
||||
Let's test a multi turn conversation with this application.
|
||||
|
||||
```python
|
||||
import uuid
|
||||
|
||||
thread_config = {"configurable": {"thread_id": str(uuid.uuid4())}}
|
||||
|
||||
inputs = [
|
||||
# 1st round of conversation,
|
||||
{
|
||||
"messages": [
|
||||
{"role": "user", "content": "i wanna go somewhere warm in the caribbean"}
|
||||
]
|
||||
},
|
||||
# Since we're using `interrupt`, we'll need to resume using the Command primitive.
|
||||
# 2nd round of conversation,
|
||||
Command(
|
||||
resume="could you recommend a nice hotel in one of the areas and tell me which area it is."
|
||||
),
|
||||
# 3rd round of conversation,
|
||||
Command(
|
||||
resume="i like the first one. could you recommend something to do near the hotel?"
|
||||
),
|
||||
]
|
||||
|
||||
for idx, user_input in enumerate(inputs):
|
||||
print()
|
||||
print(f"--- Conversation Turn {idx + 1} ---")
|
||||
print()
|
||||
print(f"User: {user_input}")
|
||||
print()
|
||||
for update in graph.stream(
|
||||
user_input,
|
||||
config=thread_config,
|
||||
stream_mode="updates",
|
||||
):
|
||||
for node_id, value in update.items():
|
||||
if isinstance(value, dict) and value.get("messages", []):
|
||||
last_message = value["messages"][-1]
|
||||
if isinstance(last_message, dict) or last_message.type != "ai":
|
||||
continue
|
||||
print(f"{node_id}: {last_message.content}")
|
||||
```
|
||||
|
||||
```
|
||||
--- Conversation Turn 1 ---
|
||||
|
||||
User: {'messages': [{'role': 'user', 'content': 'i wanna go somewhere warm in the caribbean'}]}
|
||||
|
||||
travel_advisor: Based on the recommendations, Aruba would be an excellent choice for your Caribbean getaway! Aruba is known as "One Happy Island" and offers:
|
||||
- Year-round warm weather with consistent temperatures around 82°F (28°C)
|
||||
- Beautiful white sand beaches like Eagle Beach and Palm Beach
|
||||
- Clear turquoise waters perfect for swimming and snorkeling
|
||||
- Minimal rainfall and location outside the hurricane belt
|
||||
- A blend of Caribbean and Dutch culture
|
||||
- Great dining options and nightlife
|
||||
- Various water sports and activities
|
||||
|
||||
Would you like me to get some specific hotel recommendations in Aruba for your stay? I can transfer you to our hotel advisor who can help with accommodations.
|
||||
|
||||
--- Conversation Turn 2 ---
|
||||
|
||||
User: Command(resume='could you recommend a nice hotel in one of the areas and tell me which area it is.')
|
||||
|
||||
hotel_advisor: Based on the recommendations, I can suggest two excellent options:
|
||||
|
||||
1. The Ritz-Carlton, Aruba - Located in Palm Beach
|
||||
- This luxury resort is situated in the vibrant Palm Beach area
|
||||
- Known for its exceptional service and amenities
|
||||
- Perfect if you want to be close to dining, shopping, and entertainment
|
||||
- Features multiple restaurants, a casino, and a world-class spa
|
||||
- Located on a pristine stretch of Palm Beach
|
||||
|
||||
2. Bucuti & Tara Beach Resort - Located in Eagle Beach
|
||||
- An adults-only boutique resort on Eagle Beach
|
||||
- Known for being more intimate and peaceful
|
||||
- Award-winning for its sustainability practices
|
||||
- Perfect for a romantic getaway or peaceful vacation
|
||||
- Located on one of the most beautiful beaches in the Caribbean
|
||||
|
||||
Would you like more specific information about either of these properties or their locations?
|
||||
|
||||
--- Conversation Turn 3 ---
|
||||
|
||||
User: Command(resume='i like the first one. could you recommend something to do near the hotel?')
|
||||
|
||||
travel_advisor: Near the Ritz-Carlton in Palm Beach, here are some highly recommended activities:
|
||||
|
||||
1. Visit the Palm Beach Plaza Mall - Just a short walk from the hotel, featuring shopping, dining, and entertainment
|
||||
2. Try your luck at the Stellaris Casino - It's right in the Ritz-Carlton
|
||||
3. Take a sunset sailing cruise - Many depart from the nearby pier
|
||||
4. Visit the California Lighthouse - A scenic landmark just north of Palm Beach
|
||||
5. Enjoy water sports at Palm Beach:
|
||||
- Jet skiing
|
||||
- Parasailing
|
||||
- Snorkeling
|
||||
- Stand-up paddleboarding
|
||||
|
||||
Would you like more specific information about any of these activities or would you like to know about other options in the area?
|
||||
```
|
||||
|
||||
## Prebuilt implementations
|
||||
|
||||
LangGraph comes with prebuilt implementations of two of the most popular multi-agent architectures:
|
||||
|
||||
- [supervisor](../agents/multi-agent.md#supervisor) — individual agents are coordinated by a central supervisor agent. The supervisor controls all communication flow and task delegation, making decisions about which agent to invoke based on the current context and task requirements. You can use [`langgraph-supervisor`](https://github.com/langchain-ai/langgraph-supervisor-py) library to create a supervisor multi-agent systems.
|
||||
- [swarm](../agents/multi-agent.md#supervisor) — agents dynamically hand off control to one another based on their specializations. The system remembers which agent was last active, ensuring that on subsequent interactions, the conversation resumes with that agent. You can use [`langgraph-swarm`](https://github.com/langchain-ai/langgraph-swarm-py) library to create a swarm multi-agent systems.
|
||||
@@ -0,0 +1,155 @@
|
||||
# How to pass custom run ID or set tags and metadata for graph runs in LangSmith
|
||||
|
||||
!!! tip "Prerequisites"
|
||||
This guide assumes familiarity with the following:
|
||||
|
||||
- [LangSmith Documentation](https://docs.smith.langchain.com)
|
||||
- [LangSmith Platform](https://smith.langchain.com)
|
||||
- [RunnableConfig](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.config.RunnableConfig.html#langchain_core.runnables.config.RunnableConfig)
|
||||
- [Add metadata and tags to traces](https://docs.smith.langchain.com/how_to_guides/tracing/trace_with_langchain#add-metadata-and-tags-to-traces)
|
||||
- [Customize run name](https://docs.smith.langchain.com/how_to_guides/tracing/trace_with_langchain#customize-run-name)
|
||||
|
||||
Debugging graph runs can sometimes be difficult to do in an IDE or terminal. [LangSmith](https://docs.smith.langchain.com) lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read the [LangSmith documentation](https://docs.smith.langchain.com) for more information on how to get started.
|
||||
|
||||
To make it easier to identify and analyzed traces generated during graph invocation, you can set additional configuration at run time (see [RunnableConfig](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.config.RunnableConfig.html#langchain_core.runnables.config.RunnableConfig)):
|
||||
|
||||
| **Field** | **Type** | **Description** |
|
||||
|-------------|---------------------|--------------------------------------------------------------------------------------------------------------------|
|
||||
| run_name | `str` | Name for the tracer run for this call. Defaults to the name of the class. |
|
||||
| run_id | `UUID` | Unique identifier for the tracer run for this call. If not provided, a new UUID will be generated. |
|
||||
| tags | `List[str]` | Tags for this call and any sub-calls (e.g., a Chain calling an LLM). You can use these to filter calls. |
|
||||
| metadata | `Dict[str, Any]` | Metadata for this call and any sub-calls (e.g., a Chain calling an LLM). Keys should be strings, values should be JSON-serializable. |
|
||||
|
||||
LangGraph graphs implement the [LangChain Runnable Interface](https://python.langchain.com/api_reference/core/runnables/langchain_core.runnables.base.Runnable.html) and accept a second argument (`RunnableConfig`) in methods like `invoke`, `ainvoke`, `stream` etc.
|
||||
|
||||
The LangSmith platform will allow you to search and filter traces based on `run_name`, `run_id`, `tags` and `metadata`.
|
||||
|
||||
## TLDR
|
||||
|
||||
```python
|
||||
import uuid
|
||||
# Generate a random UUID -- it must be a UUID
|
||||
config = {"run_id": uuid.uuid4()}, "tags": ["my_tag1"], "metadata": {"a": 5}}
|
||||
# Works with all standard Runnable methods
|
||||
# like invoke, batch, ainvoke, astream_events etc
|
||||
graph.stream(inputs, config, stream_mode="values")
|
||||
```
|
||||
|
||||
The rest of the how to guide will show a full agent.
|
||||
|
||||
## Setup
|
||||
|
||||
First, let's install the required packages and set our API keys
|
||||
|
||||
```python
|
||||
%%capture --no-stderr
|
||||
%pip install --quiet -U langgraph langchain_openai
|
||||
```
|
||||
|
||||
```python
|
||||
import getpass
|
||||
import os
|
||||
|
||||
|
||||
def _set_env(var: str):
|
||||
if not os.environ.get(var):
|
||||
os.environ[var] = getpass.getpass(f"{var}: ")
|
||||
|
||||
|
||||
_set_env("OPENAI_API_KEY")
|
||||
_set_env("LANGSMITH_API_KEY")
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. [LangSmith](https://docs.smith.langchain.com) lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started [here](https://docs.smith.langchain.com).
|
||||
|
||||
## Define the graph
|
||||
|
||||
For this example we will use the [prebuilt ReAct agent](https://langchain-ai.github.io/langgraph/how-tos/create-react-agent/).
|
||||
|
||||
```python
|
||||
from langchain_openai import ChatOpenAI
|
||||
from typing import Literal
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from langchain_core.tools import tool
|
||||
|
||||
# First we initialize the model we want to use.
|
||||
model = ChatOpenAI(model="gpt-4o", temperature=0)
|
||||
|
||||
|
||||
# For this tutorial we will use custom tool that returns pre-defined values for weather in two cities (NYC & SF)
|
||||
@tool
|
||||
def get_weather(city: Literal["nyc", "sf"]):
|
||||
"""Use this to get weather information."""
|
||||
if city == "nyc":
|
||||
return "It might be cloudy in nyc"
|
||||
elif city == "sf":
|
||||
return "It's always sunny in sf"
|
||||
else:
|
||||
raise AssertionError("Unknown city")
|
||||
|
||||
|
||||
tools = [get_weather]
|
||||
|
||||
|
||||
# Define the graph
|
||||
graph = create_react_agent(model, tools=tools)
|
||||
```
|
||||
|
||||
## Run your graph
|
||||
|
||||
Now that we've defined our graph let's run it once and view the trace in LangSmith. In order for our trace to be easily accessible in LangSmith, we will pass in a custom `run_id` in the config.
|
||||
|
||||
This assumes that you have set your `LANGSMITH_API_KEY` environment variable.
|
||||
|
||||
Note that you can also configure what project to trace to by setting the `LANGCHAIN_PROJECT` environment variable, by default runs will be traced to the `default` project.
|
||||
|
||||
```python
|
||||
import uuid
|
||||
|
||||
|
||||
def print_stream(stream):
|
||||
for s in stream:
|
||||
message = s["messages"][-1]
|
||||
if isinstance(message, tuple):
|
||||
print(message)
|
||||
else:
|
||||
message.pretty_print()
|
||||
|
||||
|
||||
inputs = {"messages": [("user", "what is the weather in sf")]}
|
||||
|
||||
config = {"run_name": "agent_007", "tags": ["cats are awesome"]}
|
||||
|
||||
print_stream(graph.stream(inputs, config, stream_mode="values"))
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```
|
||||
================================ Human Message ==================================
|
||||
|
||||
what is the weather in sf
|
||||
================================== Ai Message ===================================
|
||||
Tool Calls:
|
||||
get_weather (call_9ZudXyMAdlUjptq9oMGtQo8o)
|
||||
Call ID: call_9ZudXyMAdlUjptq9oMGtQo8o
|
||||
Args:
|
||||
city: sf
|
||||
================================= Tool Message ==================================
|
||||
Name: get_weather
|
||||
|
||||
It's always sunny in sf
|
||||
================================== Ai Message ===================================
|
||||
|
||||
The weather in San Francisco is currently sunny.
|
||||
```
|
||||
|
||||
## View the trace in LangSmith
|
||||
|
||||
Now that we've ran our graph, let's head over to LangSmith and view our trace. First click into the project that you traced to (in our case the default project). You should see a run with the custom run name "agent_007".
|
||||
|
||||

|
||||
|
||||
In addition, you will be able to filter traces after the fact using the tags or metadata provided. For example,
|
||||
|
||||

|
||||
@@ -1,559 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Use subgraphs\n",
|
||||
"\n",
|
||||
"This guide explains the mechanics of using [subgraphs](../../concepts/subgraphs). A common application of subgraphs is to build [multi-agent](../../concepts/multi_agent) systems.\n",
|
||||
"\n",
|
||||
"When adding subgraphs, you need to define how the parent graph and the subgraph communicate:\n",
|
||||
"\n",
|
||||
"* [Shared state schemas](#shared-state-schemas) — parent and subgraph have **shared state keys** in their state [schemas](../../concepts/low_level#state)\n",
|
||||
"* [Different state schemas](#different-state-schemas) — **no shared state keys** in parent and subgraph [schemas](../../concepts/low_level#state)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U langgraph"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div class=\"admonition tip\">\n",
|
||||
" <p class=\"admonition-title\">Set up <a href=\"https://smith.langchain.com\">LangSmith</a> for LangGraph development</p>\n",
|
||||
" <p style=\"padding-top: 5px;\">\n",
|
||||
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
|
||||
" </p>\n",
|
||||
"</div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Shared state schemas\n",
|
||||
"\n",
|
||||
"A common case is for the parent graph and subgraph to communicate over a shared state key (channel) in the [schema](../../concepts/low_level#state). For example, in [multi-agent](../../concepts/multi_agent) systems, the agents often communicate over a shared [messages](https://langchain-ai.github.io/langgraph/concepts/low_level/#why-use-messages) key.\n",
|
||||
"\n",
|
||||
"If your subgraph shares state keys with the parent graph, you can follow these steps to add it to your graph:\n",
|
||||
"\n",
|
||||
"1. Define the subgraph workflow (`subgraph_builder` in the example below) and compile it\n",
|
||||
"2. Pass compiled subgraph to the `.add_node` method when defining the parent graph workflow\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from langgraph.graph.state import StateGraph, START\n",
|
||||
"\n",
|
||||
"class State(TypedDict):\n",
|
||||
" foo: str\n",
|
||||
"\n",
|
||||
"# Subgraph\n",
|
||||
"\n",
|
||||
"def subgraph_node_1(state: State):\n",
|
||||
" return {\"foo\": \"hi! \" + state[\"foo\"]}\n",
|
||||
"\n",
|
||||
"subgraph_builder = StateGraph(State)\n",
|
||||
"subgraph_builder.add_node(subgraph_node_1)\n",
|
||||
"subgraph_builder.add_edge(START, \"subgraph_node_1\")\n",
|
||||
"# highlight-next-line\n",
|
||||
"subgraph = subgraph_builder.compile()\n",
|
||||
"\n",
|
||||
"# Parent graph\n",
|
||||
"\n",
|
||||
"builder = StateGraph(State)\n",
|
||||
"# highlight-next-line\n",
|
||||
"builder.add_node(\"node_1\", subgraph)\n",
|
||||
"builder.add_edge(START, \"node_1\")\n",
|
||||
"graph = builder.compile()\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"??? example \"Full example: shared state schemas\"\n",
|
||||
"\n",
|
||||
" ```python\n",
|
||||
" from typing_extensions import TypedDict\n",
|
||||
" from langgraph.graph.state import StateGraph, START\n",
|
||||
"\n",
|
||||
" # Define subgraph\n",
|
||||
" class SubgraphState(TypedDict):\n",
|
||||
" foo: str # (1)! \n",
|
||||
" bar: str # (2)!\n",
|
||||
" \n",
|
||||
" def subgraph_node_1(state: SubgraphState):\n",
|
||||
" return {\"bar\": \"bar\"}\n",
|
||||
" \n",
|
||||
" def subgraph_node_2(state: SubgraphState):\n",
|
||||
" # note that this node is using a state key ('bar') that is only available in the subgraph\n",
|
||||
" # and is sending update on the shared state key ('foo')\n",
|
||||
" return {\"foo\": state[\"foo\"] + state[\"bar\"]}\n",
|
||||
" \n",
|
||||
" subgraph_builder = StateGraph(SubgraphState)\n",
|
||||
" subgraph_builder.add_node(subgraph_node_1)\n",
|
||||
" subgraph_builder.add_node(subgraph_node_2)\n",
|
||||
" subgraph_builder.add_edge(START, \"subgraph_node_1\")\n",
|
||||
" subgraph_builder.add_edge(\"subgraph_node_1\", \"subgraph_node_2\")\n",
|
||||
" subgraph = subgraph_builder.compile()\n",
|
||||
" \n",
|
||||
" # Define parent graph\n",
|
||||
" class ParentState(TypedDict):\n",
|
||||
" foo: str\n",
|
||||
" \n",
|
||||
" def node_1(state: ParentState):\n",
|
||||
" return {\"foo\": \"hi! \" + state[\"foo\"]}\n",
|
||||
" \n",
|
||||
" builder = StateGraph(ParentState)\n",
|
||||
" builder.add_node(\"node_1\", node_1)\n",
|
||||
" # highlight-next-line\n",
|
||||
" builder.add_node(\"node_2\", subgraph)\n",
|
||||
" builder.add_edge(START, \"node_1\")\n",
|
||||
" builder.add_edge(\"node_1\", \"node_2\")\n",
|
||||
" graph = builder.compile()\n",
|
||||
" \n",
|
||||
" for chunk in graph.stream({\"foo\": \"foo\"}):\n",
|
||||
" print(chunk)\n",
|
||||
" ```\n",
|
||||
"\n",
|
||||
" 1. This key is shared with the parent graph state\n",
|
||||
" 2. This key is private to the `SubgraphState` and is not visible to the parent graph\n",
|
||||
" \n",
|
||||
" ```\n",
|
||||
" {'node_1': {'foo': 'hi! foo'}}\n",
|
||||
" {'node_2': {'foo': 'hi! foobar'}}\n",
|
||||
" ```\n",
|
||||
"\n",
|
||||
" ```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Different state schemas\n",
|
||||
"\n",
|
||||
"For more complex systems you might want to define subgraphs that have a **completely different schema** from the parent graph (no shared keys). For example, you might want to keep a private message history for each of the agents in a [multi-agent](../concepts/multi_agent.md) system.\n",
|
||||
"\n",
|
||||
"If that's the case for your application, you need to define a node **function that invokes the subgraph**. This function needs to transform the input (parent) state to the subgraph state before invoking the subgraph, and transform the results back to the parent state before returning the state update from the node.\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"from langgraph.graph.state import StateGraph, START\n",
|
||||
"\n",
|
||||
"class SubgraphState(TypedDict):\n",
|
||||
" bar: str\n",
|
||||
"\n",
|
||||
"# Subgraph\n",
|
||||
"\n",
|
||||
"def subgraph_node_1(state: SubgraphState):\n",
|
||||
" return {\"bar\": \"hi! \" + state[\"bar\"]}\n",
|
||||
"\n",
|
||||
"subgraph_builder = StateGraph(SubgraphState)\n",
|
||||
"subgraph_builder.add_node(subgraph_node_1)\n",
|
||||
"subgraph_builder.add_edge(START, \"subgraph_node_1\")\n",
|
||||
"# highlight-next-line\n",
|
||||
"subgraph = subgraph_builder.compile()\n",
|
||||
"\n",
|
||||
"# Parent graph\n",
|
||||
"\n",
|
||||
"class State(TypedDict):\n",
|
||||
" foo: str\n",
|
||||
"\n",
|
||||
"def call_subgraph(state: State):\n",
|
||||
" # highlight-next-line\n",
|
||||
" subgraph_output = subgraph.invoke({\"bar\": state[\"foo\"]}) # (1)!\n",
|
||||
" # highlight-next-line\n",
|
||||
" return {\"foo\": subgraph_output[\"bar\"]} # (2)!\n",
|
||||
"\n",
|
||||
"builder = StateGraph(State)\n",
|
||||
"# highlight-next-line\n",
|
||||
"builder.add_node(\"node_1\", call_subgraph)\n",
|
||||
"builder.add_edge(START, \"node_1\")\n",
|
||||
"graph = builder.compile()\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"1. Transform the state to the subgraph state\n",
|
||||
"2. Transform response back to the parent state\n",
|
||||
"\n",
|
||||
"??? example \"Full example: different state schemas\"\n",
|
||||
"\n",
|
||||
" ```python\n",
|
||||
" from typing_extensions import TypedDict\n",
|
||||
" from langgraph.graph.state import StateGraph, START\n",
|
||||
"\n",
|
||||
" # Define subgraph\n",
|
||||
" class SubgraphState(TypedDict):\n",
|
||||
" # note that none of these keys are shared with the parent graph state\n",
|
||||
" bar: str\n",
|
||||
" baz: str\n",
|
||||
" \n",
|
||||
" def subgraph_node_1(state: SubgraphState):\n",
|
||||
" return {\"baz\": \"baz\"}\n",
|
||||
" \n",
|
||||
" def subgraph_node_2(state: SubgraphState):\n",
|
||||
" return {\"bar\": state[\"bar\"] + state[\"baz\"]}\n",
|
||||
" \n",
|
||||
" subgraph_builder = StateGraph(SubgraphState)\n",
|
||||
" subgraph_builder.add_node(subgraph_node_1)\n",
|
||||
" subgraph_builder.add_node(subgraph_node_2)\n",
|
||||
" subgraph_builder.add_edge(START, \"subgraph_node_1\")\n",
|
||||
" subgraph_builder.add_edge(\"subgraph_node_1\", \"subgraph_node_2\")\n",
|
||||
" subgraph = subgraph_builder.compile()\n",
|
||||
" \n",
|
||||
" # Define parent graph\n",
|
||||
" class ParentState(TypedDict):\n",
|
||||
" foo: str\n",
|
||||
" \n",
|
||||
" def node_1(state: ParentState):\n",
|
||||
" return {\"foo\": \"hi! \" + state[\"foo\"]}\n",
|
||||
" \n",
|
||||
" def node_2(state: ParentState):\n",
|
||||
" # highlight-next-line\n",
|
||||
" response = subgraph.invoke({\"bar\": state[\"foo\"]}) # (1)!\n",
|
||||
" # highlight-next-line\n",
|
||||
" return {\"foo\": response[\"bar\"]} # (2)!\n",
|
||||
" \n",
|
||||
" \n",
|
||||
" builder = StateGraph(ParentState)\n",
|
||||
" builder.add_node(\"node_1\", node_1)\n",
|
||||
" # highlight-next-line\n",
|
||||
" builder.add_node(\"node_2\", node_2)\n",
|
||||
" builder.add_edge(START, \"node_1\")\n",
|
||||
" builder.add_edge(\"node_1\", \"node_2\")\n",
|
||||
" graph = builder.compile()\n",
|
||||
" \n",
|
||||
" for chunk in graph.stream({\"foo\": \"foo\"}, subgraphs=True):\n",
|
||||
" print(chunk)\n",
|
||||
" ```\n",
|
||||
"\n",
|
||||
" 1. Transform the state to the subgraph state\n",
|
||||
" 2. Transform response back to the parent state\n",
|
||||
"\n",
|
||||
" ```\n",
|
||||
" ((), {'node_1': {'foo': 'hi! foo'}})\n",
|
||||
" (('node_2:9c36dd0f-151a-cb42-cbad-fa2f851f9ab7',), {'subgraph_node_1': {'baz': 'baz'}})\n",
|
||||
" (('node_2:9c36dd0f-151a-cb42-cbad-fa2f851f9ab7',), {'subgraph_node_2': {'bar': 'hi! foobaz'}})\n",
|
||||
" ((), {'node_2': {'foo': 'hi! foobaz'}})\n",
|
||||
" ```\n",
|
||||
"\n",
|
||||
"??? example \"Full example: different state schemas (two levels of subgraphs)\"\n",
|
||||
"\n",
|
||||
" This is an example with two levels of subgraphs: parent -> child -> grandchild.\n",
|
||||
"\n",
|
||||
" ```python\n",
|
||||
" # Grandchild graph\n",
|
||||
" from typing_extensions import TypedDict\n",
|
||||
" from langgraph.graph.state import StateGraph, START, END\n",
|
||||
" \n",
|
||||
" class GrandChildState(TypedDict):\n",
|
||||
" my_grandchild_key: str\n",
|
||||
" \n",
|
||||
" def grandchild_1(state: GrandChildState) -> GrandChildState:\n",
|
||||
" # NOTE: child or parent keys will not be accessible here\n",
|
||||
" return {\"my_grandchild_key\": state[\"my_grandchild_key\"] + \", how are you\"}\n",
|
||||
" \n",
|
||||
" \n",
|
||||
" grandchild = StateGraph(GrandChildState)\n",
|
||||
" grandchild.add_node(\"grandchild_1\", grandchild_1)\n",
|
||||
" \n",
|
||||
" grandchild.add_edge(START, \"grandchild_1\")\n",
|
||||
" grandchild.add_edge(\"grandchild_1\", END)\n",
|
||||
" \n",
|
||||
" grandchild_graph = grandchild.compile()\n",
|
||||
" \n",
|
||||
" # Child graph\n",
|
||||
" class ChildState(TypedDict):\n",
|
||||
" my_child_key: str\n",
|
||||
" \n",
|
||||
" def call_grandchild_graph(state: ChildState) -> ChildState:\n",
|
||||
" # NOTE: parent or grandchild keys won't be accessible here\n",
|
||||
" grandchild_graph_input = {\"my_grandchild_key\": state[\"my_child_key\"]} # (1)!\n",
|
||||
" # highlight-next-line\n",
|
||||
" grandchild_graph_output = grandchild_graph.invoke(grandchild_graph_input)\n",
|
||||
" return {\"my_child_key\": grandchild_graph_output[\"my_grandchild_key\"] + \" today?\"} # (2)!\n",
|
||||
" \n",
|
||||
" child = StateGraph(ChildState)\n",
|
||||
" # highlight-next-line\n",
|
||||
" child.add_node(\"child_1\", call_grandchild_graph) # (3)!\n",
|
||||
" child.add_edge(START, \"child_1\")\n",
|
||||
" child.add_edge(\"child_1\", END)\n",
|
||||
" child_graph = child.compile()\n",
|
||||
" \n",
|
||||
" # Parent graph\n",
|
||||
" class ParentState(TypedDict):\n",
|
||||
" my_key: str\n",
|
||||
" \n",
|
||||
" def parent_1(state: ParentState) -> ParentState:\n",
|
||||
" # NOTE: child or grandchild keys won't be accessible here\n",
|
||||
" return {\"my_key\": \"hi \" + state[\"my_key\"]}\n",
|
||||
" \n",
|
||||
" def parent_2(state: ParentState) -> ParentState:\n",
|
||||
" return {\"my_key\": state[\"my_key\"] + \" bye!\"}\n",
|
||||
" \n",
|
||||
" def call_child_graph(state: ParentState) -> ParentState:\n",
|
||||
" child_graph_input = {\"my_child_key\": state[\"my_key\"]} # (4)!\n",
|
||||
" # highlight-next-line\n",
|
||||
" child_graph_output = child_graph.invoke(child_graph_input)\n",
|
||||
" return {\"my_key\": child_graph_output[\"my_child_key\"]} # (5)!\n",
|
||||
" \n",
|
||||
" parent = StateGraph(ParentState)\n",
|
||||
" parent.add_node(\"parent_1\", parent_1)\n",
|
||||
" # highlight-next-line\n",
|
||||
" parent.add_node(\"child\", call_child_graph) # (6)!\n",
|
||||
" parent.add_node(\"parent_2\", parent_2)\n",
|
||||
" \n",
|
||||
" parent.add_edge(START, \"parent_1\")\n",
|
||||
" parent.add_edge(\"parent_1\", \"child\")\n",
|
||||
" parent.add_edge(\"child\", \"parent_2\")\n",
|
||||
" parent.add_edge(\"parent_2\", END)\n",
|
||||
" \n",
|
||||
" parent_graph = parent.compile()\n",
|
||||
" \n",
|
||||
" for chunk in parent_graph.stream({\"my_key\": \"Bob\"}, subgraphs=True):\n",
|
||||
" print(chunk)\n",
|
||||
" ```\n",
|
||||
"\n",
|
||||
" 1. We're transforming the state from the child state channels (`my_child_key`) to the child state channels (`my_grandchild_key`)\n",
|
||||
" 2. We're transforming the state from the grandchild state channels (`my_grandchild_key`) back to the child state channels (`my_child_key`)\n",
|
||||
" 3. We're passing a function here instead of just compiled graph (`grandchild_graph`)\n",
|
||||
" 4. We're transforming the state from the parent state channels (`my_key`) to the child state channels (`my_child_key`)\n",
|
||||
" 5. We're transforming the state from the child state channels (`my_child_key`) back to the parent state channels (`my_key`)\n",
|
||||
" 6. We're passing a function here instead of just a compiled graph (`child_graph`)\n",
|
||||
"\n",
|
||||
" ```\n",
|
||||
" ((), {'parent_1': {'my_key': 'hi Bob'}})\n",
|
||||
" (('child:2e26e9ce-602f-862c-aa66-1ea5a4655e3b', 'child_1:781bb3b1-3971-84ce-810b-acf819a03f9c'), {'grandchild_1': {'my_grandchild_key': 'hi Bob, how are you'}})\n",
|
||||
" (('child:2e26e9ce-602f-862c-aa66-1ea5a4655e3b',), {'child_1': {'my_child_key': 'hi Bob, how are you today?'}})\n",
|
||||
" ((), {'child': {'my_key': 'hi Bob, how are you today?'}})\n",
|
||||
" ((), {'parent_2': {'my_key': 'hi Bob, how are you today? bye!'}})\n",
|
||||
" ```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Add persistence \n",
|
||||
"\n",
|
||||
"You only need to **provide the checkpointer when compiling the parent graph**. LangGraph will automatically propagate the checkpointer to the child subgraphs.\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"from langgraph.graph import START, StateGraph\n",
|
||||
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"class State(TypedDict):\n",
|
||||
" foo: str\n",
|
||||
"\n",
|
||||
"# Subgraph\n",
|
||||
"\n",
|
||||
"def subgraph_node_1(state: State):\n",
|
||||
" return {\"foo\": state[\"foo\"] + \"bar\"}\n",
|
||||
"\n",
|
||||
"subgraph_builder = StateGraph(State)\n",
|
||||
"subgraph_builder.add_node(subgraph_node_1)\n",
|
||||
"subgraph_builder.add_edge(START, \"subgraph_node_1\")\n",
|
||||
"# highlight-next-line\n",
|
||||
"subgraph = subgraph_builder.compile()\n",
|
||||
"\n",
|
||||
"# Parent graph\n",
|
||||
"\n",
|
||||
"builder = StateGraph(State)\n",
|
||||
"# highlight-next-line\n",
|
||||
"builder.add_node(\"node_1\", subgraph)\n",
|
||||
"builder.add_edge(START, \"node_1\")\n",
|
||||
"\n",
|
||||
"checkpointer = InMemorySaver()\n",
|
||||
"# highlight-next-line\n",
|
||||
"graph = builder.compile(checkpointer=checkpointer)\n",
|
||||
"``` \n",
|
||||
"\n",
|
||||
"If you want the subgraph to **have its own memory**, you can compile it `with checkpointer=True`. This is useful in [multi-agent](../../concepts/multi_agent) systems, if you want agents to keep track of their internal message histories:\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"subgraph_builder = StateGraph(...)\n",
|
||||
"# highlight-next-line\n",
|
||||
"subgraph = subgraph_builder.compile(checkpointer=True)\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## View subgraph state\n",
|
||||
"\n",
|
||||
"When you enable [persistence](../persistence), you can [inspect the graph state](../persistence#manage-checkpoints) (checkpoint) via `graph.get_state(config)`. To view the subgraph state, you can use `graph.get_state(config, subgraphs=True)`.\n",
|
||||
"\n",
|
||||
"!!! important \"Available **only** when interrupted\"\n",
|
||||
"\n",
|
||||
" Subgraph state can only be viewed **when the subgraph is interrupted**. Once you resume the graph, you won't be able to access the subgraph state.\n",
|
||||
"\n",
|
||||
"??? example \"View interrupted subgraph state\"\n",
|
||||
"\n",
|
||||
" ```python\n",
|
||||
" from langgraph.graph import START, StateGraph\n",
|
||||
" from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
" from langgraph.types import interrupt, Command\n",
|
||||
" from typing_extensions import TypedDict\n",
|
||||
" \n",
|
||||
" class State(TypedDict):\n",
|
||||
" foo: str\n",
|
||||
" \n",
|
||||
" # Subgraph\n",
|
||||
" \n",
|
||||
" def subgraph_node_1(state: State):\n",
|
||||
" # highlight-next-line\n",
|
||||
" value = interrupt(\"Provide value:\")\n",
|
||||
" return {\"foo\": state[\"foo\"] + value}\n",
|
||||
" \n",
|
||||
" subgraph_builder = StateGraph(State)\n",
|
||||
" subgraph_builder.add_node(subgraph_node_1)\n",
|
||||
" subgraph_builder.add_edge(START, \"subgraph_node_1\")\n",
|
||||
" \n",
|
||||
" subgraph = subgraph_builder.compile()\n",
|
||||
" \n",
|
||||
" # Parent graph\n",
|
||||
" \n",
|
||||
" builder = StateGraph(State)\n",
|
||||
" # highlight-next-line\n",
|
||||
" builder.add_node(\"node_1\", subgraph)\n",
|
||||
" builder.add_edge(START, \"node_1\")\n",
|
||||
" \n",
|
||||
" checkpointer = InMemorySaver()\n",
|
||||
" # highlight-next-line\n",
|
||||
" graph = builder.compile(checkpointer=checkpointer)\n",
|
||||
" \n",
|
||||
" config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
|
||||
" \n",
|
||||
" graph.invoke({\"foo\": \"\"}, config)\n",
|
||||
" parent_state = graph.get_state(config)\n",
|
||||
" # highlight-next-line\n",
|
||||
" subgraph_state = graph.get_state(config, subgraphs=True).tasks[0].state # (1)!\n",
|
||||
" \n",
|
||||
" # resume the subgraph\n",
|
||||
" graph.invoke(Command(resume=\"bar\"), config)\n",
|
||||
" ```\n",
|
||||
" \n",
|
||||
" 1. This will be available only when the subgraph is interrupted. Once you resume the graph, you won't be able to access the subgraph state."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Stream subgraph outputs\n",
|
||||
"\n",
|
||||
"To include outputs from [subgraphs](../concepts/low_level.md#subgraphs) in the streamed outputs, you can set `subgraphs=True` in the `.stream()` method of the parent graph. This will stream outputs from both the parent graph and any subgraphs.\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"for chunk in graph.stream(\n",
|
||||
" {\"foo\": \"foo\"},\n",
|
||||
" # highlight-next-line\n",
|
||||
" subgraphs=True, # (1)!\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
"):\n",
|
||||
" print(chunk)\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"1. Set `subgraphs=True` to stream outputs from subgraphs.\n",
|
||||
"\n",
|
||||
"??? example \"Stream from subgraphs\"\n",
|
||||
"\n",
|
||||
" ```python\n",
|
||||
" from typing_extensions import TypedDict\n",
|
||||
" from langgraph.graph.state import StateGraph, START\n",
|
||||
"\n",
|
||||
" # Define subgraph\n",
|
||||
" class SubgraphState(TypedDict):\n",
|
||||
" foo: str\n",
|
||||
" bar: str\n",
|
||||
" \n",
|
||||
" def subgraph_node_1(state: SubgraphState):\n",
|
||||
" return {\"bar\": \"bar\"}\n",
|
||||
" \n",
|
||||
" def subgraph_node_2(state: SubgraphState):\n",
|
||||
" # note that this node is using a state key ('bar') that is only available in the subgraph\n",
|
||||
" # and is sending update on the shared state key ('foo')\n",
|
||||
" return {\"foo\": state[\"foo\"] + state[\"bar\"]}\n",
|
||||
" \n",
|
||||
" subgraph_builder = StateGraph(SubgraphState)\n",
|
||||
" subgraph_builder.add_node(subgraph_node_1)\n",
|
||||
" subgraph_builder.add_node(subgraph_node_2)\n",
|
||||
" subgraph_builder.add_edge(START, \"subgraph_node_1\")\n",
|
||||
" subgraph_builder.add_edge(\"subgraph_node_1\", \"subgraph_node_2\")\n",
|
||||
" subgraph = subgraph_builder.compile()\n",
|
||||
" \n",
|
||||
" # Define parent graph\n",
|
||||
" class ParentState(TypedDict):\n",
|
||||
" foo: str\n",
|
||||
" \n",
|
||||
" def node_1(state: ParentState):\n",
|
||||
" return {\"foo\": \"hi! \" + state[\"foo\"]}\n",
|
||||
" \n",
|
||||
" builder = StateGraph(ParentState)\n",
|
||||
" builder.add_node(\"node_1\", node_1)\n",
|
||||
" # highlight-next-line\n",
|
||||
" builder.add_node(\"node_2\", subgraph)\n",
|
||||
" builder.add_edge(START, \"node_1\")\n",
|
||||
" builder.add_edge(\"node_1\", \"node_2\")\n",
|
||||
" graph = builder.compile()\n",
|
||||
"\n",
|
||||
" for chunk in graph.stream(\n",
|
||||
" {\"foo\": \"foo\"},\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
" # highlight-next-line\n",
|
||||
" subgraphs=True, # (1)!\n",
|
||||
" ):\n",
|
||||
" print(chunk)\n",
|
||||
" ```\n",
|
||||
" \n",
|
||||
" 1. Set `subgraphs=True` to stream outputs from subgraphs.\n",
|
||||
"\n",
|
||||
" ```\n",
|
||||
" ((), {'node_1': {'foo': 'hi! foo'}})\n",
|
||||
" (('node_2:e58e5673-a661-ebb0-70d4-e298a7fc28b7',), {'subgraph_node_1': {'bar': 'bar'}})\n",
|
||||
" (('node_2:e58e5673-a661-ebb0-70d4-e298a7fc28b7',), {'subgraph_node_2': {'foo': 'hi! foobar'}})\n",
|
||||
" ((), {'node_2': {'foo': 'hi! foobar'}})\n",
|
||||
" ```"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
@@ -0,0 +1,453 @@
|
||||
# Use subgraphs
|
||||
|
||||
This guide explains the mechanics of using [subgraphs](../concepts/subgraphs.md). A common application of subgraphs is to build [multi-agent](../concepts/multi_agent.md) systems.
|
||||
|
||||
When adding subgraphs, you need to define how the parent graph and the subgraph communicate:
|
||||
|
||||
* [Shared state schemas](#shared-state-schemas) — parent and subgraph have **shared state keys** in their state [schemas](../concepts/low_level.md#state)
|
||||
* [Different state schemas](#different-state-schemas) — **no shared state keys** in parent and subgraph [schemas](../concepts/low_level.md#state)
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
pip install -U langgraph
|
||||
```
|
||||
|
||||
!!! tip "Set up LangSmith for LangGraph development"
|
||||
Sign up for [LangSmith](https://smith.langchain.com) to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started [here](https://docs.smith.langchain.com).
|
||||
|
||||
## Shared state schemas
|
||||
|
||||
A common case is for the parent graph and subgraph to communicate over a shared state key (channel) in the [schema](../concepts/low_level.md#state). For example, in [multi-agent](../concepts/multi_agent.md) systems, the agents often communicate over a shared [messages](https://langchain-ai.github.io/langgraph/concepts/low_level.md#why-use-messages) key.
|
||||
|
||||
If your subgraph shares state keys with the parent graph, you can follow these steps to add it to your graph:
|
||||
|
||||
1. Define the subgraph workflow (`subgraph_builder` in the example below) and compile it
|
||||
2. Pass compiled subgraph to the `.add_node` method when defining the parent graph workflow
|
||||
|
||||
```python
|
||||
from typing_extensions import TypedDict
|
||||
from langgraph.graph.state import StateGraph, START
|
||||
|
||||
class State(TypedDict):
|
||||
foo: str
|
||||
|
||||
# Subgraph
|
||||
|
||||
def subgraph_node_1(state: State):
|
||||
return {"foo": "hi! " + state["foo"]}
|
||||
|
||||
subgraph_builder = StateGraph(State)
|
||||
subgraph_builder.add_node(subgraph_node_1)
|
||||
subgraph_builder.add_edge(START, "subgraph_node_1")
|
||||
subgraph = subgraph_builder.compile()
|
||||
|
||||
# Parent graph
|
||||
|
||||
builder = StateGraph(State)
|
||||
builder.add_node("node_1", subgraph)
|
||||
builder.add_edge(START, "node_1")
|
||||
graph = builder.compile()
|
||||
```
|
||||
|
||||
??? example "Full example: shared state schemas"
|
||||
|
||||
```python
|
||||
from typing_extensions import TypedDict
|
||||
from langgraph.graph.state import StateGraph, START
|
||||
|
||||
# Define subgraph
|
||||
class SubgraphState(TypedDict):
|
||||
foo: str # (1)!
|
||||
bar: str # (2)!
|
||||
|
||||
def subgraph_node_1(state: SubgraphState):
|
||||
return {"bar": "bar"}
|
||||
|
||||
def subgraph_node_2(state: SubgraphState):
|
||||
# note that this node is using a state key ('bar') that is only available in the subgraph
|
||||
# and is sending update on the shared state key ('foo')
|
||||
return {"foo": state["foo"] + state["bar"]}
|
||||
|
||||
subgraph_builder = StateGraph(SubgraphState)
|
||||
subgraph_builder.add_node(subgraph_node_1)
|
||||
subgraph_builder.add_node(subgraph_node_2)
|
||||
subgraph_builder.add_edge(START, "subgraph_node_1")
|
||||
subgraph_builder.add_edge("subgraph_node_1", "subgraph_node_2")
|
||||
subgraph = subgraph_builder.compile()
|
||||
|
||||
# Define parent graph
|
||||
class ParentState(TypedDict):
|
||||
foo: str
|
||||
|
||||
def node_1(state: ParentState):
|
||||
return {"foo": "hi! " + state["foo"]}
|
||||
|
||||
builder = StateGraph(ParentState)
|
||||
builder.add_node("node_1", node_1)
|
||||
builder.add_node("node_2", subgraph)
|
||||
builder.add_edge(START, "node_1")
|
||||
builder.add_edge("node_1", "node_2")
|
||||
graph = builder.compile()
|
||||
|
||||
for chunk in graph.stream({"foo": "foo"}):
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
1. This key is shared with the parent graph state
|
||||
2. This key is private to the `SubgraphState` and is not visible to the parent graph
|
||||
|
||||
```
|
||||
{'node_1': {'foo': 'hi! foo'}}
|
||||
{'node_2': {'foo': 'hi! foobar'}}
|
||||
```
|
||||
|
||||
## Different state schemas
|
||||
|
||||
For more complex systems you might want to define subgraphs that have a **completely different schema** from the parent graph (no shared keys). For example, you might want to keep a private message history for each of the agents in a [multi-agent](../concepts/multi_agent.md) system.
|
||||
|
||||
If that's the case for your application, you need to define a node **function that invokes the subgraph**. This function needs to transform the input (parent) state to the subgraph state before invoking the subgraph, and transform the results back to the parent state before returning the state update from the node.
|
||||
|
||||
```python
|
||||
from typing_extensions import TypedDict
|
||||
from langgraph.graph.state import StateGraph, START
|
||||
|
||||
class SubgraphState(TypedDict):
|
||||
bar: str
|
||||
|
||||
# Subgraph
|
||||
|
||||
def subgraph_node_1(state: SubgraphState):
|
||||
return {"bar": "hi! " + state["bar"]}
|
||||
|
||||
subgraph_builder = StateGraph(SubgraphState)
|
||||
subgraph_builder.add_node(subgraph_node_1)
|
||||
subgraph_builder.add_edge(START, "subgraph_node_1")
|
||||
subgraph = subgraph_builder.compile()
|
||||
|
||||
# Parent graph
|
||||
|
||||
class State(TypedDict):
|
||||
foo: str
|
||||
|
||||
def call_subgraph(state: State):
|
||||
subgraph_output = subgraph.invoke({"bar": state["foo"]}) # (1)!
|
||||
return {"foo": subgraph_output["bar"]} # (2)!
|
||||
|
||||
builder = StateGraph(State)
|
||||
builder.add_node("node_1", call_subgraph)
|
||||
builder.add_edge(START, "node_1")
|
||||
graph = builder.compile()
|
||||
```
|
||||
|
||||
1. Transform the state to the subgraph state
|
||||
2. Transform response back to the parent state
|
||||
|
||||
??? example "Full example: different state schemas"
|
||||
|
||||
```python
|
||||
from typing_extensions import TypedDict
|
||||
from langgraph.graph.state import StateGraph, START
|
||||
|
||||
# Define subgraph
|
||||
class SubgraphState(TypedDict):
|
||||
# note that none of these keys are shared with the parent graph state
|
||||
bar: str
|
||||
baz: str
|
||||
|
||||
def subgraph_node_1(state: SubgraphState):
|
||||
return {"baz": "baz"}
|
||||
|
||||
def subgraph_node_2(state: SubgraphState):
|
||||
return {"bar": state["bar"] + state["baz"]}
|
||||
|
||||
subgraph_builder = StateGraph(SubgraphState)
|
||||
subgraph_builder.add_node(subgraph_node_1)
|
||||
subgraph_builder.add_node(subgraph_node_2)
|
||||
subgraph_builder.add_edge(START, "subgraph_node_1")
|
||||
subgraph_builder.add_edge("subgraph_node_1", "subgraph_node_2")
|
||||
subgraph = subgraph_builder.compile()
|
||||
|
||||
# Define parent graph
|
||||
class ParentState(TypedDict):
|
||||
foo: str
|
||||
|
||||
def node_1(state: ParentState):
|
||||
return {"foo": "hi! " + state["foo"]}
|
||||
|
||||
def node_2(state: ParentState):
|
||||
response = subgraph.invoke({"bar": state["foo"]}) # (1)!
|
||||
return {"foo": response["bar"]} # (2)!
|
||||
|
||||
|
||||
builder = StateGraph(ParentState)
|
||||
builder.add_node("node_1", node_1)
|
||||
builder.add_node("node_2", node_2)
|
||||
builder.add_edge(START, "node_1")
|
||||
builder.add_edge("node_1", "node_2")
|
||||
graph = builder.compile()
|
||||
|
||||
for chunk in graph.stream({"foo": "foo"}, subgraphs=True):
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
1. Transform the state to the subgraph state
|
||||
2. Transform response back to the parent state
|
||||
|
||||
```
|
||||
((), {'node_1': {'foo': 'hi! foo'}})
|
||||
(('node_2:9c36dd0f-151a-cb42-cbad-fa2f851f9ab7',), {'grandchild_1': {'my_grandchild_key': 'hi Bob, how are you'}})
|
||||
(('node_2:9c36dd0f-151a-cb42-cbad-fa2f851f9ab7',), {'grandchild_2': {'bar': 'hi! foobaz'}})
|
||||
((), {'node_2': {'foo': 'hi! foobaz'}})
|
||||
```
|
||||
|
||||
??? example "Full example: different state schemas (two levels of subgraphs)"
|
||||
|
||||
This is an example with two levels of subgraphs: parent -> child -> grandchild.
|
||||
|
||||
```python
|
||||
# Grandchild graph
|
||||
from typing_extensions import TypedDict
|
||||
from langgraph.graph.state import StateGraph, START, END
|
||||
|
||||
class GrandChildState(TypedDict):
|
||||
my_grandchild_key: str
|
||||
|
||||
def grandchild_1(state: GrandChildState) -> GrandChildState:
|
||||
# NOTE: child or parent keys will not be accessible here
|
||||
return {"my_grandchild_key": state["my_grandchild_key"] + ", how are you"}
|
||||
|
||||
|
||||
grandchild = StateGraph(GrandChildState)
|
||||
grandchild.add_node("grandchild_1", grandchild_1)
|
||||
|
||||
grandchild.add_edge(START, "grandchild_1")
|
||||
grandchild.add_edge("grandchild_1", END)
|
||||
|
||||
grandchild_graph = grandchild.compile()
|
||||
|
||||
# Child graph
|
||||
class ChildState(TypedDict):
|
||||
my_child_key: str
|
||||
|
||||
def call_grandchild_graph(state: ChildState) -> ChildState:
|
||||
# NOTE: parent or grandchild keys won't be accessible here
|
||||
grandchild_graph_input = {"my_grandchild_key": state["my_child_key"]} # (1)!
|
||||
grandchild_graph_output = grandchild_graph.invoke(grandchild_graph_input)
|
||||
return {"my_child_key": grandchild_graph_output["my_grandchild_key"] + " today?"} # (2)!
|
||||
|
||||
child = StateGraph(ChildState)
|
||||
child.add_node("child_1", call_grandchild_graph) # (3)!
|
||||
child.add_edge(START, "child_1")
|
||||
child.add_edge("child_1", END)
|
||||
child_graph = child.compile()
|
||||
|
||||
# Parent graph
|
||||
class ParentState(TypedDict):
|
||||
my_key: str
|
||||
|
||||
def parent_1(state: ParentState) -> ParentState:
|
||||
# NOTE: child or grandchild keys won't be accessible here
|
||||
return {"my_key": "hi " + state["my_key"]}
|
||||
|
||||
def parent_2(state: ParentState) -> ParentState:
|
||||
return {"my_key": state["my_key"] + " bye!"}
|
||||
|
||||
def call_child_graph(state: ParentState) -> ParentState:
|
||||
child_graph_input = {"my_child_key": state["my_key"]} # (4)!
|
||||
child_graph_output = child_graph.invoke(child_graph_input)
|
||||
return {"my_key": child_graph_output["my_child_key"]} # (5)!
|
||||
|
||||
parent = StateGraph(ParentState)
|
||||
parent.add_node("parent_1", parent_1)
|
||||
parent.add_node("child", call_child_graph) # (6)!
|
||||
parent.add_node("parent_2", parent_2)
|
||||
|
||||
parent.add_edge(START, "parent_1")
|
||||
parent.add_edge("parent_1", "child")
|
||||
parent.add_edge("child", "parent_2")
|
||||
parent.add_edge("parent_2", END)
|
||||
|
||||
parent_graph = parent.compile()
|
||||
|
||||
for chunk in parent_graph.stream({"my_key": "Bob"}, subgraphs=True):
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
1. We're transforming the state from the child state channels (`my_child_key`) to the child state channels (`my_grandchild_key`)
|
||||
2. We're transforming the state from the grandchild state channels (`my_grandchild_key`) back to the child state channels (`my_child_key`)
|
||||
3. We're passing a function here instead of just compiled graph (`grandchild_graph`)
|
||||
4. We're transforming the state from the parent state channels (`my_key`) to the child state channels (`my_child_key`)
|
||||
5. We're transforming the state from the child state channels (`my_child_key`) back to the parent state channels (`my_key`)
|
||||
6. We're passing a function here instead of just a compiled graph (`child_graph`)
|
||||
|
||||
```
|
||||
((), {'parent_1': {'my_key': 'hi Bob'}})
|
||||
(('child:2e26e9ce-602f-862c-aa66-1ea5a4655e3b', 'child_1:781bb3b1-3971-84ce-810b-acf819a03f9c'), {'grandchild_1': {'my_grandchild_key': 'hi Bob, how are you'}})
|
||||
(('child:2e26e9ce-602f-862c-aa66-1ea5a4655e3b',), {'child_1': {'my_child_key': 'hi Bob, how are you today?'}})
|
||||
((), {'child': {'my_key': 'hi Bob, how are you today?'}})
|
||||
((), {'parent_2': {'my_key': 'hi Bob, how are you today? bye!'}})
|
||||
```
|
||||
|
||||
## Add persistence
|
||||
|
||||
You only need to **provide the checkpointer when compiling the parent graph**. LangGraph will automatically propagate the checkpointer to the child subgraphs.
|
||||
|
||||
```python
|
||||
from langgraph.graph import START, StateGraph
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
class State(TypedDict):
|
||||
foo: str
|
||||
|
||||
# Subgraph
|
||||
|
||||
def subgraph_node_1(state: State):
|
||||
return {"foo": state["foo"] + "bar"}
|
||||
|
||||
subgraph_builder = StateGraph(State)
|
||||
subgraph_builder.add_node(subgraph_node_1)
|
||||
subgraph_builder.add_edge(START, "subgraph_node_1")
|
||||
subgraph = subgraph_builder.compile()
|
||||
|
||||
# Parent graph
|
||||
|
||||
builder = StateGraph(State)
|
||||
builder.add_node("node_1", subgraph)
|
||||
builder.add_edge(START, "node_1")
|
||||
|
||||
checkpointer = InMemorySaver()
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
```
|
||||
|
||||
If you want the subgraph to **have its own memory**, you can compile it `with checkpointer=True`. This is useful in [multi-agent](../concepts/multi_agent.md) systems, if you want agents to keep track of their internal message histories:
|
||||
|
||||
```python
|
||||
subgraph_builder = StateGraph(...)
|
||||
subgraph = subgraph_builder.compile(checkpointer=True)
|
||||
```
|
||||
|
||||
## View subgraph state
|
||||
|
||||
When you enable [persistence](../concepts/persistence.md), you can [inspect the graph state](../concepts/persistence.md#checkpoints) (checkpoint) via `graph.get_state(config)`. To view the subgraph state, you can use `graph.get_state(config, subgraphs=True)`.
|
||||
|
||||
!!! important "Available **only** when interrupted"
|
||||
|
||||
Subgraph state can only be viewed **when the subgraph is interrupted**. Once you resume the graph, you won't be able to access the subgraph state.
|
||||
|
||||
??? example "View interrupted subgraph state"
|
||||
|
||||
```python
|
||||
from langgraph.graph import START, StateGraph
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.types import interrupt, Command
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
class State(TypedDict):
|
||||
foo: str
|
||||
|
||||
# Subgraph
|
||||
|
||||
def subgraph_node_1(state: State):
|
||||
value = interrupt("Provide value:")
|
||||
return {"foo": state["foo"] + value}
|
||||
|
||||
subgraph_builder = StateGraph(State)
|
||||
subgraph_builder.add_node(subgraph_node_1)
|
||||
subgraph_builder.add_edge(START, "subgraph_node_1")
|
||||
|
||||
subgraph = subgraph_builder.compile()
|
||||
|
||||
# Parent graph
|
||||
|
||||
builder = StateGraph(State)
|
||||
builder.add_node("node_1", subgraph)
|
||||
builder.add_edge(START, "node_1")
|
||||
|
||||
checkpointer = InMemorySaver()
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
|
||||
config = {"configurable": {"thread_id": "1"}}
|
||||
|
||||
graph.invoke({"foo": ""}, config)
|
||||
parent_state = graph.get_state(config)
|
||||
subgraph_state = graph.get_state(config, subgraphs=True).tasks[0].state # (1)!
|
||||
|
||||
# resume the subgraph
|
||||
graph.invoke(Command(resume="bar"), config)
|
||||
```
|
||||
|
||||
1. This will be available only when the subgraph is interrupted. Once you resume the graph, you won't be able to access the subgraph state.
|
||||
|
||||
## Stream subgraph outputs
|
||||
|
||||
To include outputs from subgraphs in the streamed outputs, you can set `subgraphs=True` in the `.stream()` method of the parent graph. This will stream outputs from both the parent graph and any subgraphs.
|
||||
|
||||
```python
|
||||
for chunk in graph.stream(
|
||||
{"foo": "foo"},
|
||||
subgraphs=True, # (1)!
|
||||
stream_mode="updates",
|
||||
):
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
1. Set `subgraphs=True` to stream outputs from subgraphs.
|
||||
|
||||
??? example "Stream from subgraphs"
|
||||
|
||||
```python
|
||||
from typing_extensions import TypedDict
|
||||
from langgraph.graph.state import StateGraph, START
|
||||
|
||||
# Define subgraph
|
||||
class SubgraphState(TypedDict):
|
||||
foo: str
|
||||
bar: str
|
||||
|
||||
def subgraph_node_1(state: SubgraphState):
|
||||
return {"bar": "bar"}
|
||||
|
||||
def subgraph_node_2(state: SubgraphState):
|
||||
# note that this node is using a state key ('bar') that is only available in the subgraph
|
||||
# and is sending update on the shared state key ('foo')
|
||||
return {"foo": state["foo"] + state["bar"]}
|
||||
|
||||
subgraph_builder = StateGraph(SubgraphState)
|
||||
subgraph_builder.add_node(subgraph_node_1)
|
||||
subgraph_builder.add_node(subgraph_node_2)
|
||||
subgraph_builder.add_edge(START, "subgraph_node_1")
|
||||
subgraph_builder.add_edge("subgraph_node_1", "subgraph_node_2")
|
||||
subgraph = subgraph_builder.compile()
|
||||
|
||||
# Define parent graph
|
||||
class ParentState(TypedDict):
|
||||
foo: str
|
||||
|
||||
def node_1(state: ParentState):
|
||||
return {"foo": "hi! " + state["foo"]}
|
||||
|
||||
builder = StateGraph(ParentState)
|
||||
builder.add_node("node_1", node_1)
|
||||
builder.add_node("node_2", subgraph)
|
||||
builder.add_edge(START, "node_1")
|
||||
builder.add_edge("node_1", "node_2")
|
||||
graph = builder.compile()
|
||||
|
||||
for chunk in graph.stream(
|
||||
{"foo": "foo"},
|
||||
stream_mode="updates",
|
||||
subgraphs=True, # (1)!
|
||||
):
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
1. Set `subgraphs=True` to stream outputs from subgraphs.
|
||||
|
||||
```
|
||||
((), {'node_1': {'foo': 'hi! foo'}})
|
||||
(('node_2:e58e5673-a661-ebb0-70d4-e298a7fc28b7',), {'subgraph_node_1': {'bar': 'bar'}})
|
||||
(('node_2:e58e5673-a661-ebb0-70d4-e298a7fc28b7',), {'subgraph_node_2': {'foo': 'hi! foobar'}})
|
||||
((), {'node_2': {'foo': 'hi! foobar'}})
|
||||
|
||||
@@ -344,6 +344,8 @@ tool_node.invoke({"messages": [...]})
|
||||
|
||||
## Tool customization
|
||||
|
||||
For more control over tool behavior, use the `@tool` decorator.
|
||||
|
||||
### Parameter descriptions
|
||||
|
||||
Auto-generate descriptions from docstrings:
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# Use the functional API
|
||||
|
||||
The [**Functional API**](../concepts/functional_api.md) allows you to add LangGraph's key features — [persistence](../concepts/persistence.md), [memory](../how-tos/memory/add-memory.md), [human-in-the-loop](../concepts/human_in_the_loop.md), and [streaming](../concepts/streaming.md) — to your applications with minimal changes to your existing code.
|
||||
|
||||
!!! tip
|
||||
|
||||
For conceptual information on the functional API, see [Functional API](../concepts/functional_api.md).
|
||||
|
||||
|
||||
## Creating a simple workflow
|
||||
|
||||
When defining an `entrypoint`, input is restricted to the first argument of the function. To pass multiple inputs, you can use a dictionary.
|
||||
@@ -832,4 +839,4 @@ for chunk in workflow.stream([input_message], config, stream_mode="values"):
|
||||
|
||||
## Integrate with other libraries
|
||||
|
||||
* [Add LangGraph's features to other frameworks using the functional API](./autogen-integration-functional.ipynb): Add LangGraph features like persistence, memory and streaming to other agent frameworks that do not provide them out of the box.
|
||||
* [Add LangGraph's features to other frameworks using the functional API](./autogen-integration-functional.ipynb): Add LangGraph features like persistence, memory and streaming to other agent frameworks that do not provide them out of the box.
|
||||
|
||||
@@ -17,7 +17,7 @@ Safari blocks plain-HTTP traffic on localhost. When running Studio with `langgra
|
||||
|
||||
```shell
|
||||
# Requires @langchain/langgraph-cli>=0.0.26
|
||||
npx @langchain/langgraph-cli dev
|
||||
npx @langchain/langgraph-cli dev --tunnel
|
||||
```
|
||||
|
||||
The command outputs a URL in this format:
|
||||
@@ -55,7 +55,7 @@ Disable Brave Shields for LangSmith using the Brave icon in the URL bar.
|
||||
|
||||
```shell
|
||||
# Requires @langchain/langgraph-cli>=0.0.26
|
||||
npx @langchain/langgraph-cli dev
|
||||
npx @langchain/langgraph-cli dev --tunnel
|
||||
```
|
||||
|
||||
The command outputs a URL in this format:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Build a basic chatbot
|
||||
|
||||
In this tutorial, you will build a basic chatbot. This chatbot is the basis for the following series of tutorials where you will progressively add more sophisticated capabilities, and be introduced to key LangGraph concepts along the way. Let's dive in! 🌟
|
||||
In this tutorial, you will build a basic chatbot. This chatbot is the basis for the following series of tutorials where you will progressively add more sophisticated capabilities, and be introduced to key LangGraph concepts along the way. Let’s dive in! 🌟
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -13,44 +13,13 @@ tool-calling features, such as [OpenAI](https://platform.openai.com/api-keys),
|
||||
|
||||
Install the required packages:
|
||||
|
||||
:::python
|
||||
|
||||
```bash
|
||||
pip install -U langgraph langsmith
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
=== "npm"
|
||||
|
||||
```bash
|
||||
npm install @langchain/langgraph @langchain/core zod
|
||||
```
|
||||
|
||||
=== "yarn"
|
||||
|
||||
```bash
|
||||
yarn add @langchain/langgraph @langchain/core zod
|
||||
```
|
||||
|
||||
=== "pnpm"
|
||||
|
||||
```bash
|
||||
pnpm add @langchain/langgraph @langchain/core zod
|
||||
```
|
||||
|
||||
=== "bun"
|
||||
|
||||
```bash
|
||||
bun add @langchain/langgraph @langchain/core zod
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! tip
|
||||
|
||||
Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph. For more information on how to get started, see [LangSmith docs](https://docs.smith.langchain.com).
|
||||
Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph. For more information on how to get started, see [LangSmith docs](https://docs.smith.langchain.com).
|
||||
|
||||
## 2. Create a `StateGraph`
|
||||
|
||||
@@ -58,8 +27,6 @@ Now you can create a basic chatbot using LangGraph. This chatbot will respond di
|
||||
|
||||
Start by creating a `StateGraph`. A `StateGraph` object defines the structure of our chatbot as a "state machine". We'll add `nodes` to represent the llm and functions our chatbot can call and `edges` to specify how the bot should transition between these functions.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
|
||||
@@ -79,41 +46,23 @@ class State(TypedDict):
|
||||
graph_builder = StateGraph(State)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({ messages: MessagesZodState.shape.messages });
|
||||
|
||||
const graph = new StateGraph(State).compile();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Our graph can now handle two key tasks:
|
||||
|
||||
1. Each `node` can receive the current `State` as input and output an update to the state.
|
||||
2. Updates to `messages` will be appended to the existing list rather than overwriting it, thanks to the prebuilt reducer function.
|
||||
2. Updates to `messages` will be appended to the existing list rather than overwriting it, thanks to the prebuilt [`add_messages`](https://langchain-ai.github.io/langgraph/reference/graphs/?h=add+messages#add_messages) function used with the `Annotated` syntax.
|
||||
|
||||
---
|
||||
------
|
||||
|
||||
!!! tip "Concept"
|
||||
|
||||
When defining a graph, the first step is to define its `State`. The `State` includes the graph's schema and [reducer functions](https://langchain-ai.github.io/langgraph/concepts/low_level/#reducers) that handle state updates. In our example, `State` is a schema with one key: `messages`. The reducer function is used to append new messages to the list instead of overwriting it. Keys without a reducer annotation will overwrite previous values.
|
||||
|
||||
To learn more about state, reducers, and related concepts, see [LangGraph reference docs](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.message.add_messages).
|
||||
When defining a graph, the first step is to define its `State`. The `State` includes the graph's schema and [reducer functions](https://langchain-ai.github.io/langgraph/concepts/low_level/#reducers) that handle state updates. In our example, `State` is a `TypedDict` with one key: `messages`. The [`add_messages`](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.message.add_messages) reducer function is used to append new messages to the list instead of overwriting it. Keys without a reducer annotation will overwrite previous values. To learn more about state, reducers, and related concepts, see [LangGraph reference docs](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.message.add_messages).
|
||||
|
||||
## 3. Add a node
|
||||
|
||||
Next, add a "`chatbot`" node. **Nodes** represent units of work and are typically regular functions.
|
||||
Next, add a "`chatbot`" node. **Nodes** represent units of work and are typically regular Python functions.
|
||||
|
||||
Let's first select a chat model:
|
||||
|
||||
:::python
|
||||
{!snippets/chat_model_tabs.md!}
|
||||
|
||||
<!---
|
||||
@@ -124,26 +73,9 @@ llm = init_chat_model("anthropic:claude-3-5-sonnet-latest")
|
||||
```
|
||||
-->
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
// or import { ChatAnthropic } from "@langchain/anthropic";
|
||||
|
||||
const llm = new ChatOpenAI({
|
||||
model: "gpt-4o",
|
||||
temperature: 0,
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
We can now incorporate the chat model into a simple node:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
|
||||
def chatbot(state: State):
|
||||
@@ -156,132 +88,38 @@ def chatbot(state: State):
|
||||
graph_builder.add_node("chatbot", chatbot)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript hl_lines="7-9"
|
||||
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({ messages: MessagesZodState.shape.messages });
|
||||
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("chatbot", async (state: z.infer<typeof State>) => {
|
||||
return { messages: [await llm.invoke(state.messages)] };
|
||||
})
|
||||
.compile();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
**Notice** how the `chatbot` node function takes the current `State` as input and returns a dictionary containing an updated `messages` list under the key "messages". This is the basic pattern for all LangGraph node functions.
|
||||
|
||||
:::python
|
||||
The `add_messages` function in our `State` will append the LLM's response messages to whatever messages are already in the state.
|
||||
:::
|
||||
|
||||
:::js
|
||||
The `addMessages` function used within `MessagesZodState` will append the LLM's response messages to whatever messages are already in the state.
|
||||
:::
|
||||
|
||||
## 4. Add an `entry` point
|
||||
|
||||
Add an `entry` point to tell the graph **where to start its work** each time it is run:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
graph_builder.add_edge(START, "chatbot")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript hl_lines="10"
|
||||
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({ messages: MessagesZodState.shape.messages });
|
||||
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("chatbot", async (state: z.infer<typeof State>) => {
|
||||
return { messages: [await llm.invoke(state.messages)] };
|
||||
})
|
||||
.addEdge(START, "chatbot")
|
||||
.compile();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 5. Add an `exit` point
|
||||
|
||||
Add an `exit` point to indicate **where the graph should finish execution**. This is helpful for more complex flows, but even in a simple graph like this, adding an end node improves clarity.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
graph_builder.add_edge("chatbot", END)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript hl_lines="11"
|
||||
import { StateGraph, MessagesZodState, START, END } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({ messages: MessagesZodState.shape.messages });
|
||||
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("chatbot", async (state: z.infer<typeof State>) => {
|
||||
return { messages: [await llm.invoke(state.messages)] };
|
||||
})
|
||||
.addEdge(START, "chatbot")
|
||||
.addEdge("chatbot", END)
|
||||
.compile();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
This tells the graph to terminate after running the chatbot node.
|
||||
|
||||
## 6. Compile the graph
|
||||
|
||||
Before running the graph, we'll need to compile it. We can do so by calling `compile()` on the graph builder. This creates a `CompiledGraph` we can invoke on our state.
|
||||
|
||||
:::python
|
||||
Before running the graph, we'll need to compile it. We can do so by calling `compile()`
|
||||
on the graph builder. This creates a `CompiledStateGraph` we can invoke on our state.
|
||||
|
||||
```python
|
||||
graph = graph_builder.compile()
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript hl_lines="12"
|
||||
import { StateGraph, MessagesZodState, START, END } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({ messages: MessagesZodState.shape.messages });
|
||||
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("chatbot", async (state: z.infer<typeof State>) => {
|
||||
return { messages: [await llm.invoke(state.messages)] };
|
||||
})
|
||||
.addEdge(START, "chatbot")
|
||||
.addEdge("chatbot", END)
|
||||
.compile();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 7. Visualize the graph (optional)
|
||||
|
||||
:::python
|
||||
You can visualize the graph using the `get_graph` method and one of the "draw" methods, like `draw_ascii` or `draw_png`. The `draw` methods each require additional dependencies.
|
||||
|
||||
```python
|
||||
@@ -294,35 +132,17 @@ except Exception:
|
||||
pass
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
You can visualize the graph using the `getGraph` method and render the graph with the `drawMermaidPng` method.
|
||||
|
||||
```typescript
|
||||
import * as fs from "node:fs/promises";
|
||||
|
||||
const drawableGraph = await graph.getGraphAsync();
|
||||
const image = await drawableGraph.drawMermaidPng();
|
||||
const imageBuffer = new Uint8Array(await image.arrayBuffer());
|
||||
|
||||
await fs.writeFile("basic-chatbot.png", imageBuffer);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||

|
||||
|
||||
|
||||
## 8. Run the chatbot
|
||||
|
||||
Now run the chatbot!
|
||||
Now run the chatbot!
|
||||
|
||||
!!! tip
|
||||
|
||||
You can exit the chat loop at any time by typing `quit`, `exit`, or `q`.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
def stream_graph_updates(user_input: str):
|
||||
for event in graph.stream({"messages": [{"role": "user", "content": user_input}]}):
|
||||
@@ -345,66 +165,13 @@ while True:
|
||||
break
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import * as readline from "node:readline/promises";
|
||||
import { StateGraph, MessagesZodState, START, END } from "@langchain/langgraph";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { z } from "zod";
|
||||
|
||||
const llm = new ChatOpenAI({ model: "gpt-4o-mini" });
|
||||
|
||||
const State = z.object({ messages: MessagesZodState.shape.messages });
|
||||
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("chatbot", async (state: z.infer<typeof State>) => {
|
||||
return { messages: [await llm.invoke(state.messages)] };
|
||||
})
|
||||
.addEdge(START, "chatbot")
|
||||
.addEdge("chatbot", END)
|
||||
.compile();
|
||||
|
||||
async function generateText(content: string) {
|
||||
const stream = await graph.stream(
|
||||
{ messages: [{ type: "human", content }] },
|
||||
{ streamMode: "values" }
|
||||
);
|
||||
|
||||
for await (const event of stream) {
|
||||
const lastMessage = event.messages.at(-1);
|
||||
if (lastMessage?.getType() === "ai") {
|
||||
console.log(`Assistant: ${lastMessage.text}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const prompt = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
while (true) {
|
||||
const human = await prompt.question("User: ");
|
||||
if (["quit", "exit", "q"].includes(human.trim())) break;
|
||||
await generateText(human || "What do you know about LangGraph?");
|
||||
}
|
||||
|
||||
prompt.close();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
```
|
||||
Assistant: LangGraph is a library designed to help build stateful multi-agent applications using language models. It provides tools for creating workflows and state machines to coordinate multiple AI agents or language model interactions. LangGraph is built on top of LangChain, leveraging its components while adding graph-based coordination capabilities. It's particularly useful for developing more complex, stateful AI applications that go beyond simple query-response interactions.
|
||||
Goodbye!
|
||||
```
|
||||
|
||||
**Congratulations!** You've built your first chatbot using LangGraph. This bot can engage in basic conversation by taking user input and generating responses using an LLM. You can inspect a [LangSmith Trace](https://smith.langchain.com/public/7527e308-9502-4894-b347-f34385740d5a/r) for the call above.
|
||||
|
||||
:::python
|
||||
|
||||
Below is the full code for this tutorial:
|
||||
|
||||
```python
|
||||
@@ -440,8 +207,8 @@ graph_builder.add_edge("chatbot", END)
|
||||
graph = graph_builder.compile()
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Next steps
|
||||
|
||||
You may have noticed that the bot's knowledge is limited to what's in its training data. In the next part, we'll [add a web search tool](./2-add-tools.md) to expand the bot's knowledge and make it more capable.
|
||||
|
||||
|
||||
|
||||
@@ -10,88 +10,35 @@ To handle queries that your chatbot can't answer "from memory", integrate a web
|
||||
|
||||
Before you start this tutorial, ensure you have the following:
|
||||
|
||||
:::python
|
||||
|
||||
- An API key for the [Tavily Search Engine](https://python.langchain.com/docs/integrations/tools/tavily_search/).
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- An API key for the [Tavily Search Engine](https://js.langchain.com/docs/integrations/tools/tavily_search/).
|
||||
|
||||
:::
|
||||
|
||||
## 1. Install the search engine
|
||||
|
||||
:::python
|
||||
Install the requirements to use the [Tavily Search Engine](https://python.langchain.com/docs/integrations/tools/tavily_search/):
|
||||
|
||||
```bash
|
||||
pip install -U langchain-tavily
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Install the requirements to use the [Tavily Search Engine](https://docs.tavily.com/):
|
||||
|
||||
=== "npm"
|
||||
|
||||
```bash
|
||||
npm install @langchain/tavily
|
||||
```
|
||||
|
||||
=== "yarn"
|
||||
|
||||
```bash
|
||||
yarn add @langchain/tavily
|
||||
```
|
||||
|
||||
=== "pnpm"
|
||||
|
||||
```bash
|
||||
pnpm add @langchain/tavily
|
||||
```
|
||||
|
||||
=== "bun"
|
||||
|
||||
```bash
|
||||
bun add @langchain/tavily
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 2. Configure your environment
|
||||
|
||||
Configure your environment with your search engine API key:
|
||||
|
||||
:::python
|
||||
```python
|
||||
def _set_env(var: str):
|
||||
if not os.environ.get(var):
|
||||
os.environ[var] = getpass.getpass(f"{var}: ")
|
||||
|
||||
```bash
|
||||
_set_env("TAVILY_API_KEY")
|
||||
```
|
||||
|
||||
```
|
||||
TAVILY_API_KEY: ········
|
||||
os.environ["TAVILY_API_KEY"]: "········"
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
process.env.TAVILY_API_KEY = "tvly-...";
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 3. Define the tool
|
||||
|
||||
Define the web search tool:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langchain_tavily import TavilySearch
|
||||
|
||||
@@ -100,25 +47,8 @@ tools = [tool]
|
||||
tool.invoke("What's a 'node' in LangGraph?")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { TavilySearch } from "@langchain/tavily";
|
||||
|
||||
const tool = new TavilySearch({ maxResults: 2 });
|
||||
const tools = [tool];
|
||||
|
||||
await tool.invoke({ query: "What's a 'node' in LangGraph?" });
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
The results are page summaries our chat bot can use to answer questions:
|
||||
|
||||
:::python
|
||||
|
||||
```
|
||||
{'query': "What's a 'node' in LangGraph?",
|
||||
'follow_up_questions': None,
|
||||
@@ -137,51 +67,12 @@ The results are page summaries our chat bot can use to answer questions:
|
||||
'response_time': 1.38}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```json
|
||||
{
|
||||
"query": "What's a 'node' in LangGraph?",
|
||||
"follow_up_questions": null,
|
||||
"answer": null,
|
||||
"images": [],
|
||||
"results": [
|
||||
{
|
||||
"url": "https://blog.langchain.dev/langgraph/",
|
||||
"title": "LangGraph - LangChain Blog",
|
||||
"content": "TL;DR: LangGraph is module built on top of LangChain to better enable creation of cyclical graphs, often needed for agent runtimes. This state is updated by nodes in the graph, which return operations to attributes of this state (in the form of a key-value store). After adding nodes, you can then add edges to create the graph. An example of this may be in the basic agent runtime, where we always want the model to be called after we call a tool. The state of this graph by default contains concepts that should be familiar to you if you've used LangChain agents: `input`, `chat_history`, `intermediate_steps` (and `agent_outcome` to represent the most recent agent outcome)",
|
||||
"score": 0.7407191,
|
||||
"raw_content": null
|
||||
},
|
||||
{
|
||||
"url": "https://medium.com/@cplog/introduction-to-langgraph-a-beginners-guide-14f9be027141",
|
||||
"title": "Introduction to LangGraph: A Beginner's Guide - Medium",
|
||||
"content": "* **Stateful Graph:** LangGraph revolves around the concept of a stateful graph, where each node in the graph represents a step in your computation, and the graph maintains a state that is passed around and updated as the computation progresses. LangGraph supports conditional edges, allowing you to dynamically determine the next node to execute based on the current state of the graph. Image 10: Introduction to AI Agent with LangChain and LangGraph: A Beginner’s Guide Image 18: How to build LLM Agent with LangGraph — StateGraph and Reducer Image 20: Simplest Graphs using LangGraph Framework Image 24: Building a ReAct Agent with Langgraph: A Step-by-Step Guide Image 28: Building an Agentic RAG with LangGraph: A Step-by-Step Guide",
|
||||
"score": 0.65279555,
|
||||
"raw_content": null
|
||||
}
|
||||
],
|
||||
"response_time": 1.34
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 4. Define the graph
|
||||
|
||||
:::python
|
||||
For the `StateGraph` you created in the [first tutorial](./1-build-basic-chatbot.md), add `bind_tools` on the LLM. This lets the LLM know the correct JSON format to use if it wants to use the search engine.
|
||||
:::
|
||||
|
||||
:::js
|
||||
For the `StateGraph` you created in the [first tutorial](./1-build-basic-chatbot.md), add `bindTools` on the LLM. This lets the LLM know the correct JSON format to use if it wants to use the search engine.
|
||||
:::
|
||||
|
||||
Let's first select our LLM:
|
||||
|
||||
:::python
|
||||
{!snippets/chat_model_tabs.md!}
|
||||
|
||||
<!---
|
||||
@@ -192,22 +83,8 @@ llm = init_chat_model("anthropic:claude-3-5-sonnet-latest")
|
||||
```
|
||||
-->
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
|
||||
const llm = new ChatAnthropic({ model: "claude-3-5-sonnet-latest" });
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
We can now incorporate it into a `StateGraph`:
|
||||
|
||||
:::python
|
||||
|
||||
```python hl_lines="15"
|
||||
from typing import Annotated
|
||||
|
||||
@@ -231,31 +108,9 @@ def chatbot(state: State):
|
||||
graph_builder.add_node("chatbot", chatbot)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript hl_lines="7-8"
|
||||
import { StateGraph, MessagesZodState } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({ messages: MessagesZodState.shape.messages });
|
||||
|
||||
const chatbot = async (state: z.infer<typeof State>) => {
|
||||
// Modification: tell the LLM which tools it can call
|
||||
const llmWithTools = llm.bindTools(tools);
|
||||
|
||||
return { messages: [await llmWithTools.invoke(state.messages)] };
|
||||
};
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 5. Create a function to run the tools
|
||||
|
||||
:::python
|
||||
|
||||
Now, create a function to run the tools if they are called. Do this by adding the tools to a new node called `BasicToolNode` that checks the most recent message in the state and calls tools if the message contains `tool_calls`. It relies on the LLM's `tool_calling` support, which is available in Anthropic, OpenAI, Google Gemini, and a number of other LLM providers.
|
||||
Now, create a function to run the tools if they are called. Do this by adding the tools to a new node called`BasicToolNode` that checks the most recent message in the state and calls tools if the message contains `tool_calls`. It relies on the LLM's `tool_calling` support, which is available in Anthropic, OpenAI, Google Gemini, and a number of other LLM providers.
|
||||
|
||||
```python
|
||||
import json
|
||||
@@ -297,80 +152,16 @@ graph_builder.add_node("tools", tool_node)
|
||||
|
||||
If you do not want to build this yourself in the future, you can use LangGraph's prebuilt [ToolNode](https://langchain-ai.github.io/langgraph/reference/agents/#langgraph.prebuilt.tool_node.ToolNode).
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
Now, create a function to run the tools if they are called. Do this by adding the tools to a new node called `"tools"` that checks the most recent message in the state and calls tools if the message contains `tool_calls`. It relies on the LLM's tool calling support, which is available in Anthropic, OpenAI, Google Gemini, and a number of other LLM providers.
|
||||
|
||||
```typescript
|
||||
import type { StructuredToolInterface } from "@langchain/core/tools";
|
||||
import { isAIMessage, ToolMessage } from "@langchain/core/messages";
|
||||
|
||||
function createToolNode(tools: StructuredToolInterface[]) {
|
||||
const toolByName: Record<string, StructuredToolInterface> = {};
|
||||
for (const tool of tools) {
|
||||
toolByName[tool.name] = tool;
|
||||
}
|
||||
|
||||
return async (inputs: z.infer<typeof State>) => {
|
||||
const { messages } = inputs;
|
||||
if (!messages || messages.length === 0) {
|
||||
throw new Error("No message found in input");
|
||||
}
|
||||
|
||||
const message = messages.at(-1);
|
||||
if (!message || !isAIMessage(message) || !message.tool_calls) {
|
||||
throw new Error("Last message is not an AI message with tool calls");
|
||||
}
|
||||
|
||||
const outputs: ToolMessage[] = [];
|
||||
for (const toolCall of message.tool_calls) {
|
||||
if (!toolCall.id) throw new Error("Tool call ID is required");
|
||||
|
||||
const tool = toolByName[toolCall.name];
|
||||
if (!tool) throw new Error(`Tool ${toolCall.name} not found`);
|
||||
|
||||
const result = await tool.invoke(toolCall.args);
|
||||
|
||||
outputs.push(
|
||||
new ToolMessage({
|
||||
content: JSON.stringify(result),
|
||||
name: toolCall.name,
|
||||
tool_call_id: toolCall.id,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return { messages: outputs };
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
If you do not want to build this yourself in the future, you can use LangGraph's prebuilt [ToolNode](https://langchain-ai.github.io/langgraphjs/reference/classes/langgraph_prebuilt.ToolNode.html).
|
||||
|
||||
:::
|
||||
|
||||
## 6. Define the `conditional_edges`
|
||||
|
||||
With the tool node added, now you can define the `conditional_edges`.
|
||||
With the tool node added, now you can define the `conditional_edges`.
|
||||
|
||||
**Edges** route the control flow from one node to the next. **Conditional edges** start from a single node and usually contain "if" statements to route to different nodes depending on the current graph state. These functions receive the current graph `state` and return a string or list of strings indicating which node(s) to call next.
|
||||
|
||||
:::python
|
||||
Next, define a router function called `route_tools` that checks for `tool_calls` in the chatbot's output. Provide this function to the graph by calling `add_conditional_edges`, which tells the graph that whenever the `chatbot` node completes to check this function to see where to go next.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Next, define a router function called `routeTools` that checks for `tool_calls` in the chatbot's output. Provide this function to the graph by calling `addConditionalEdges`, which tells the graph that whenever the `chatbot` node completes to check this function to see where to go next.
|
||||
:::
|
||||
Next, define a router function called `route_tools` that checks for `tool_calls` in the chatbot's output. Provide this function to the graph by calling `add_conditional_edges`, which tells the graph that whenever the `chatbot` node completes to check this function to see where to go next.
|
||||
|
||||
The condition will route to `tools` if tool calls are present and `END` if not. Because the condition can return `END`, you do not need to explicitly set a `finish_point` this time.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
def route_tools(
|
||||
state: State,
|
||||
@@ -410,61 +201,10 @@ graph = graph_builder.compile()
|
||||
|
||||
!!! note
|
||||
|
||||
You can replace this with the prebuilt [tools_condition](https://langchain-ai.github.io/langgraph/reference/prebuilt/#tools_condition) to be more concise.
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { END, START } from "@langchain/langgraph";
|
||||
|
||||
const routeTools = (state: z.infer<typeof State>) => {
|
||||
/**
|
||||
* Use as conditional edge to route to the ToolNode if the last message
|
||||
* has tool calls.
|
||||
*/
|
||||
const lastMessage = state.messages.at(-1);
|
||||
if (
|
||||
lastMessage &&
|
||||
isAIMessage(lastMessage) &&
|
||||
lastMessage.tool_calls?.length
|
||||
) {
|
||||
return "tools";
|
||||
}
|
||||
|
||||
/** Otherwise, route to the end. */
|
||||
return END;
|
||||
};
|
||||
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("chatbot", chatbot)
|
||||
|
||||
// The `routeTools` function returns "tools" if the chatbot asks to use a tool, and "END" if
|
||||
// it is fine directly responding. This conditional routing defines the main agent loop.
|
||||
.addNode("tools", createToolNode(tools))
|
||||
|
||||
// Start the graph with the chatbot
|
||||
.addEdge(START, "chatbot")
|
||||
|
||||
// The `routeTools` function returns "tools" if the chatbot asks to use a tool, and "END" if
|
||||
// it is fine directly responding.
|
||||
.addConditionalEdges("chatbot", routeTools, ["tools", END])
|
||||
|
||||
// Any time a tool is called, we need to return to the chatbot
|
||||
.addEdge("tools", "chatbot")
|
||||
.compile();
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
You can replace this with the prebuilt [toolsCondition](https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph_prebuilt.toolsCondition.html) to be more concise.
|
||||
|
||||
:::
|
||||
You can replace this with the prebuilt [tools_condition](https://langchain-ai.github.io/langgraph/reference/prebuilt/#tools_condition) to be more concise.
|
||||
|
||||
## 7. Visualize the graph (optional)
|
||||
|
||||
:::python
|
||||
You can visualize the graph using the `get_graph` method and one of the "draw" methods, like `draw_ascii` or `draw_png`. The `draw` methods each require additional dependencies.
|
||||
|
||||
```python
|
||||
@@ -477,31 +217,12 @@ except Exception:
|
||||
pass
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
You can visualize the graph using the `getGraph` method and render the graph with the `drawMermaidPng` method.
|
||||
|
||||
```typescript
|
||||
import * as fs from "node:fs/promises";
|
||||
|
||||
const drawableGraph = await graph.getGraphAsync();
|
||||
const image = await drawableGraph.drawMermaidPng();
|
||||
const imageBuffer = new Uint8Array(await image.arrayBuffer());
|
||||
|
||||
await fs.writeFile("chatbot-with-tools.png", imageBuffer);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||

|
||||
|
||||
## 8. Ask the bot questions
|
||||
|
||||
Now you can ask the chatbot questions outside its training data:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
def stream_graph_updates(user_input: str):
|
||||
for event in graph.stream({"messages": [{"role": "user", "content": user_input}]}):
|
||||
@@ -524,7 +245,7 @@ while True:
|
||||
break
|
||||
```
|
||||
|
||||
```
|
||||
```
|
||||
Assistant: [{'text': "To provide you with accurate and up-to-date information about LangGraph, I'll need to search for the latest details. Let me do that for you.", 'type': 'text'}, {'id': 'toolu_01Q588CszHaSvvP2MxRq9zRD', 'input': {'query': 'LangGraph AI tool information'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
|
||||
Assistant: [{"url": "https://www.langchain.com/langgraph", "content": "LangGraph sets the foundation for how we can build and scale AI workloads \u2014 from conversational agents, complex task automation, to custom LLM-backed experiences that 'just work'. The next chapter in building complex production-ready features with LLMs is agentic, and with LangGraph and LangSmith, LangChain delivers an out-of-the-box solution ..."}, {"url": "https://github.com/langchain-ai/langgraph", "content": "Overview. LangGraph is a library for building stateful, multi-actor applications with LLMs, used to create agent and multi-agent workflows. Compared to other LLM frameworks, it offers these core benefits: cycles, controllability, and persistence. LangGraph allows you to define flows that involve cycles, essential for most agentic architectures ..."}]
|
||||
Assistant: Based on the search results, I can provide you with information about LangGraph:
|
||||
@@ -555,99 +276,18 @@ Assistant: Based on the search results, I can provide you with information about
|
||||
|
||||
LangGraph appears to be a significant tool in the evolving landscape of LLM-based application development, offering developers new ways to create more complex, stateful, and interactive AI systems.
|
||||
Goodbye!
|
||||
Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings...
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import readline from "node:readline/promises";
|
||||
|
||||
const prompt = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
async function generateText(content: string) {
|
||||
const stream = await graph.stream(
|
||||
{ messages: [{ type: "human", content }] },
|
||||
{ streamMode: "values" }
|
||||
);
|
||||
|
||||
for await (const event of stream) {
|
||||
const lastMessage = event.messages.at(-1);
|
||||
|
||||
if (lastMessage?.getType() === "ai" || lastMessage?.getType() === "tool") {
|
||||
console.log(`Assistant: ${lastMessage?.text}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const human = await prompt.question("User: ");
|
||||
if (["quit", "exit", "q"].includes(human.trim())) break;
|
||||
await generateText(human || "What do you know about LangGraph?");
|
||||
}
|
||||
|
||||
prompt.close();
|
||||
```
|
||||
|
||||
```
|
||||
User: What do you know about LangGraph?
|
||||
Assistant: I'll search for the latest information about LangGraph for you.
|
||||
Assistant: [{"title":"Introduction to LangGraph: A Beginner's Guide - Medium","url":"https://medium.com/@cplog/introduction-to-langgraph-a-beginners-guide-14f9be027141","content":"..."}]
|
||||
Assistant: Based on the search results, I can provide you with information about LangGraph:
|
||||
|
||||
LangGraph is a library within the LangChain ecosystem designed for building stateful, multi-actor applications with Large Language Models (LLMs). Here are the key aspects:
|
||||
|
||||
**Core Purpose:**
|
||||
- LangGraph is specifically designed for creating agent and multi-agent workflows
|
||||
- It provides a framework for defining, coordinating, and executing multiple LLM agents in a structured manner
|
||||
|
||||
**Key Features:**
|
||||
1. **Stateful Graph Architecture**: LangGraph revolves around a stateful graph where each node represents a step in computation, and the graph maintains state that is passed around and updated as the computation progresses
|
||||
|
||||
2. **Conditional Edges**: It supports conditional edges, allowing you to dynamically determine the next node to execute based on the current state of the graph
|
||||
|
||||
3. **Cycles**: Unlike other LLM frameworks, LangGraph allows you to define flows that involve cycles, which is essential for most agentic architectures
|
||||
|
||||
4. **Controllability**: It offers enhanced control over the application flow
|
||||
|
||||
5. **Persistence**: The library provides ways to maintain state and persistence in LLM-based applications
|
||||
|
||||
**Use Cases:**
|
||||
- Conversational agents
|
||||
- Complex task automation
|
||||
- Custom LLM-backed experiences
|
||||
- Multi-agent systems that perform complex tasks
|
||||
|
||||
**Benefits:**
|
||||
LangGraph allows developers to focus on the high-level logic of their applications rather than the intricacies of agent coordination, making it easier to build complex, production-ready features with LLMs.
|
||||
|
||||
This makes LangGraph a significant tool in the evolving landscape of LLM-based application development.
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 9. Use prebuilts
|
||||
|
||||
For ease of use, adjust your code to replace the following with LangGraph prebuilt components. These have built in functionality like parallel API execution.
|
||||
|
||||
:::python
|
||||
|
||||
- `BasicToolNode` is replaced with the prebuilt [ToolNode](https://langchain-ai.github.io/langgraph/reference/prebuilt/#toolnode)
|
||||
- `route_tools` is replaced with the prebuilt [tools_condition](https://langchain-ai.github.io/langgraph/reference/prebuilt/#tools_condition)
|
||||
|
||||
{!snippets/chat_model_tabs.md!}
|
||||
|
||||
<!---
|
||||
```python
|
||||
from langchain.chat_models import init_chat_model
|
||||
|
||||
llm = init_chat_model("anthropic:claude-3-5-sonnet-latest")
|
||||
```
|
||||
-->
|
||||
|
||||
```python hl_lines="25 30"
|
||||
from typing import Annotated
|
||||
@@ -687,46 +327,7 @@ graph_builder.add_edge(START, "chatbot")
|
||||
graph = graph_builder.compile()
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- `createToolNode` is replaced with the prebuilt [ToolNode](https://langchain-ai.github.io/langgraphjs/reference/classes/langgraph_prebuilt.ToolNode.html)
|
||||
- `routeTools` is replaced with the prebuilt [toolsCondition](https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph_prebuilt.toolsCondition.html)
|
||||
|
||||
```typescript
|
||||
import { TavilySearch } from "@langchain/tavily";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { StateGraph, START, MessagesZodState, END } from "@langchain/langgraph";
|
||||
import { ToolNode, toolsCondition } from "@langchain/langgraph/prebuilt";
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({ messages: MessagesZodState.shape.messages });
|
||||
|
||||
const tools = [new TavilySearch({ maxResults: 2 })];
|
||||
|
||||
const llm = new ChatOpenAI({ model: "gpt-4o-mini" }).bindTools(tools);
|
||||
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("chatbot", async (state) => ({
|
||||
messages: [await llm.invoke(state.messages)],
|
||||
}))
|
||||
.addNode("tools", new ToolNode(tools))
|
||||
.addConditionalEdges("chatbot", toolsCondition, ["tools", END])
|
||||
.addEdge("tools", "chatbot")
|
||||
.addEdge(START, "chatbot")
|
||||
.compile();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
**Congratulations!** You've created a conversational agent in LangGraph that can use a search engine to retrieve updated information when needed. Now it can handle a wider range of user queries.
|
||||
|
||||
:::python
|
||||
|
||||
To inspect all the steps your agent just took, check out this [LangSmith trace](https://smith.langchain.com/public/4fbd7636-25af-4638-9587-5a02fdbb0172/r).
|
||||
|
||||
:::
|
||||
**Congratulations!** You've created a conversational agent in LangGraph that can use a search engine to retrieve updated information when needed. Now it can handle a wider range of user queries. To inspect all the steps your agent just took, check out this [LangSmith trace](https://smith.langchain.com/public/4fbd7636-25af-4638-9587-5a02fdbb0172/r).
|
||||
|
||||
## Next steps
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
The chatbot can now [use tools](./2-add-tools.md) to answer user questions, but it does not remember the context of previous interactions. This limits its ability to have coherent, multi-turn conversations.
|
||||
|
||||
LangGraph solves this problem through **persistent checkpointing**. If you provide a `checkpointer` when compiling the graph and a `thread_id` when calling your graph, LangGraph automatically saves the state after each step. When you invoke the graph again using the same `thread_id`, the graph loads its saved state, allowing the chatbot to pick up where it left off.
|
||||
LangGraph solves this problem through **persistent checkpointing**. If you provide a `checkpointer` when compiling the graph and a `thread_id` when calling your graph, LangGraph automatically saves the state after each step. When you invoke the graph again using the same `thread_id`, the graph loads its saved state, allowing the chatbot to pick up where it left off.
|
||||
|
||||
We will see later that **checkpointing** is _much_ more powerful than simple chat memory - it lets you save and resume complex state at any time for error recovery, human-in-the-loop workflows, time travel interactions, and more. But first, let's add checkpointing to enable multi-turn conversations.
|
||||
|
||||
@@ -14,79 +14,43 @@ We will see later that **checkpointing** is _much_ more powerful than simple cha
|
||||
|
||||
Create a `MemorySaver` checkpointer:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
``` python
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
|
||||
memory = MemorySaver()
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { MemorySaver } from "@langchain/langgraph";
|
||||
|
||||
const memory = new MemorySaver();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
This is in-memory checkpointer, which is convenient for the tutorial. However, in a production application, you would likely change this to use `SqliteSaver` or `PostgresSaver` and connect a database.
|
||||
|
||||
## 2. Compile the graph
|
||||
|
||||
Compile the graph with the provided checkpointer, which will checkpoint the `State` as the graph works through each node:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
``` python
|
||||
graph = graph_builder.compile(checkpointer=memory)
|
||||
```
|
||||
|
||||
:::
|
||||
``` python
|
||||
from IPython.display import Image, display
|
||||
|
||||
:::js
|
||||
|
||||
```typescript hl_lines="7"
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("chatbot", chatbot)
|
||||
.addNode("tools", new ToolNode(tools))
|
||||
.addConditionalEdges("chatbot", toolsCondition, ["tools", END])
|
||||
.addEdge("tools", "chatbot")
|
||||
.addEdge(START, "chatbot")
|
||||
.compile({ checkpointer: memory });
|
||||
try:
|
||||
display(Image(graph.get_graph().draw_mermaid_png()))
|
||||
except Exception:
|
||||
# This requires some extra dependencies and is optional
|
||||
pass
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 3. Interact with your chatbot
|
||||
|
||||
Now you can interact with your bot!
|
||||
|
||||
1. Pick a thread to use as the key for this conversation.
|
||||
|
||||
:::python
|
||||
1. Pick a thread to use as the key for this conversation.
|
||||
|
||||
```python
|
||||
config = {"configurable": {"thread_id": "1"}}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
const config = { configurable: { thread_id: "1" } };
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
2. Call your chatbot:
|
||||
|
||||
:::python
|
||||
2. Call your chatbot:
|
||||
|
||||
```python
|
||||
user_input = "Hi there! My name is Will."
|
||||
@@ -110,45 +74,14 @@ Now you can interact with your bot!
|
||||
Hello Will! It's nice to meet you. How can I assist you today? Is there anything specific you'd like to know or discuss?
|
||||
```
|
||||
|
||||
!!! note
|
||||
!!! note
|
||||
|
||||
The config was provided as the **second positional argument** when calling our graph. It importantly is _not_ nested within the graph inputs (`{'messages': []}`).
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
const userInput = "Hi there! My name is Will.";
|
||||
|
||||
const events = await graph.stream(
|
||||
{ messages: [{ type: "human", content: userInput }] },
|
||||
{ configurable: { thread_id: "1" }, streamMode: "values" }
|
||||
);
|
||||
|
||||
for await (const event of events) {
|
||||
const lastMessage = event.messages.at(-1);
|
||||
console.log(`${lastMessage?.getType()}: ${lastMessage?.text}`);
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
human: Hi there! My name is Will.
|
||||
ai: Hello Will! It's nice to meet you. How can I assist you today? Is there anything specific you'd like to know or discuss?
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
The config was provided as the **second parameter** when calling our graph. It importantly is _not_ nested within the graph inputs (`{"messages": []}`).
|
||||
|
||||
:::
|
||||
|
||||
## 4. Ask a follow up question
|
||||
|
||||
Ask a follow up question:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
user_input = "Remember my name?"
|
||||
|
||||
@@ -171,37 +104,10 @@ Remember my name?
|
||||
Of course, I remember your name, Will. I always try to pay attention to important details that users share with me. Is there anything else you'd like to talk about or any questions you have? I'm here to help with a wide range of topics or tasks.
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
const userInput2 = "Remember my name?";
|
||||
|
||||
const events2 = await graph.stream(
|
||||
{ messages: [{ type: "human", content: userInput2 }] },
|
||||
{ configurable: { thread_id: "1" }, streamMode: "values" }
|
||||
);
|
||||
|
||||
for await (const event of events2) {
|
||||
const lastMessage = event.messages.at(-1);
|
||||
console.log(`${lastMessage?.getType()}: ${lastMessage?.text}`);
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
human: Remember my name?
|
||||
ai: Yes, your name is Will. How can I help you today?
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
**Notice** that we aren't using an external list for memory: it's all handled by the checkpointer! You can inspect the full execution in this [LangSmith trace](https://smith.langchain.com/public/29ba22b5-6d40-4fbe-8d27-b369e3329c84/r) to see what's going on.
|
||||
|
||||
Don't believe me? Try this using a different config.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
# The only difference is we change the `thread_id` here to "2" instead of "1"
|
||||
events = graph.stream(
|
||||
@@ -223,36 +129,10 @@ Remember my name?
|
||||
I apologize, but I don't have any previous context or memory of your name. As an AI assistant, I don't retain information from past conversations. Each interaction starts fresh. Could you please tell me your name so I can address you properly in this conversation?
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript hl_lines="3-4"
|
||||
const events3 = await graph.stream(
|
||||
{ messages: [{ type: "human", content: userInput2 }] },
|
||||
// The only difference is we change the `thread_id` here to "2" instead of "1"
|
||||
{ configurable: { thread_id: "2" }, streamMode: "values" }
|
||||
);
|
||||
|
||||
for await (const event of events3) {
|
||||
const lastMessage = event.messages.at(-1);
|
||||
console.log(`${lastMessage?.getType()}: ${lastMessage?.text}`);
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
human: Remember my name?
|
||||
ai: I don't have the ability to remember personal information about users between interactions. However, I'm here to help you with any questions or topics you want to discuss!
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
**Notice** that the **only** change we've made is to modify the `thread_id` in the config. See this call's [LangSmith trace](https://smith.langchain.com/public/51a62351-2f0a-4058-91cc-9996c5561428/r) for comparison.
|
||||
|
||||
## 5. Inspect the state
|
||||
|
||||
:::python
|
||||
|
||||
By now, we have made a few checkpoints across two different threads. But what goes into a checkpoint? To inspect a graph's `state` for a given config at any time, call `get_state(config)`.
|
||||
|
||||
```python
|
||||
@@ -268,94 +148,12 @@ StateSnapshot(values={'messages': [HumanMessage(content='Hi there! My name is Wi
|
||||
snapshot.next # (since the graph ended this turn, `next` is empty. If you fetch a state from within a graph invocation, next tells which node will execute next)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
By now, we have made a few checkpoints across two different threads. But what goes into a checkpoint? To inspect a graph's `state` for a given config at any time, call `getState(config)`.
|
||||
|
||||
```typescript
|
||||
await graph.getState({ configurable: { thread_id: "1" } });
|
||||
```
|
||||
|
||||
```typescript
|
||||
{
|
||||
values: {
|
||||
messages: [
|
||||
HumanMessage {
|
||||
"id": "32fabcef-b3b8-481f-8bcb-fd83399a5f8d",
|
||||
"content": "Hi there! My name is Will.",
|
||||
"additional_kwargs": {},
|
||||
"response_metadata": {}
|
||||
},
|
||||
AIMessage {
|
||||
"id": "chatcmpl-BrPbTsCJbVqBvXWySlYoTJvM75Kv8",
|
||||
"content": "Hello Will! How can I assist you today?",
|
||||
"additional_kwargs": {},
|
||||
"response_metadata": {},
|
||||
"tool_calls": [],
|
||||
"invalid_tool_calls": []
|
||||
},
|
||||
HumanMessage {
|
||||
"id": "561c3aad-f8fc-4fac-94a6-54269a220856",
|
||||
"content": "Remember my name?",
|
||||
"additional_kwargs": {},
|
||||
"response_metadata": {}
|
||||
},
|
||||
AIMessage {
|
||||
"id": "chatcmpl-BrPbU4BhhsUikGbW37hYuF5vvnnE2",
|
||||
"content": "Yes, I remember your name, Will! How can I help you today?",
|
||||
"additional_kwargs": {},
|
||||
"response_metadata": {},
|
||||
"tool_calls": [],
|
||||
"invalid_tool_calls": []
|
||||
}
|
||||
]
|
||||
},
|
||||
next: [],
|
||||
tasks: [],
|
||||
metadata: {
|
||||
source: 'loop',
|
||||
step: 4,
|
||||
parents: {},
|
||||
thread_id: '1'
|
||||
},
|
||||
config: {
|
||||
configurable: {
|
||||
thread_id: '1',
|
||||
checkpoint_id: '1f05cccc-9bb6-6270-8004-1d2108bcec77',
|
||||
checkpoint_ns: ''
|
||||
}
|
||||
},
|
||||
createdAt: '2025-07-09T13:58:27.607Z',
|
||||
parentConfig: {
|
||||
configurable: {
|
||||
thread_id: '1',
|
||||
checkpoint_ns: '',
|
||||
checkpoint_id: '1f05cccc-78fa-68d0-8003-ffb01a76b599'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
import * as assert from "node:assert";
|
||||
|
||||
// Since the graph ended this turn, `next` is empty.
|
||||
// If you fetch a state from within a graph invocation, next tells which node will execute next)
|
||||
assert.deepEqual(snapshot.next, []);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
The snapshot above contains the current state values, corresponding config, and the `next` node to process. In our case, the graph has reached an `END` state, so `next` is empty.
|
||||
|
||||
**Congratulations!** Your chatbot can now maintain conversation state across sessions thanks to LangGraph's checkpointing system. This opens up exciting possibilities for more natural, contextual interactions. LangGraph's checkpointing even handles **arbitrarily complex graph states**, which is much more expressive and powerful than simple chat memory.
|
||||
|
||||
|
||||
Check out the code snippet below to review the graph from this tutorial:
|
||||
|
||||
:::python
|
||||
|
||||
{!snippets/chat_model_tabs.md!}
|
||||
|
||||
<!---
|
||||
@@ -406,43 +204,6 @@ memory = MemorySaver()
|
||||
graph = graph_builder.compile(checkpointer=memory)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript hl_lines="16 26"
|
||||
import { END, MessagesZodState, START } from "@langchain/langgraph";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { TavilySearch } from "@langchain/tavily";
|
||||
|
||||
import { MemorySaver } from "@langchain/langgraph";
|
||||
import { StateGraph } from "@langchain/langgraph";
|
||||
import { ToolNode, toolsCondition } from "@langchain/langgraph/prebuilt";
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({
|
||||
messages: MessagesZodState.shape.messages,
|
||||
});
|
||||
|
||||
const tools = [new TavilySearch({ maxResults: 2 })];
|
||||
const llm = new ChatOpenAI({ model: "gpt-4o-mini" }).bindTools(tools);
|
||||
// highlight-next-line
|
||||
const memory = new MemorySaver();
|
||||
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("chatbot", async (state) => ({
|
||||
messages: [await llm.invoke(state.messages)],
|
||||
}))
|
||||
.addNode("tools", new ToolNode(tools))
|
||||
.addConditionalEdges("chatbot", toolsCondition, ["tools", END])
|
||||
.addEdge("tools", "chatbot")
|
||||
.addEdge(START, "chatbot")
|
||||
// highlight-next-line
|
||||
.compile({ checkpointer: memory });
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Next steps
|
||||
|
||||
In the next tutorial, you will [add human-in-the-loop to the chatbot](./4-human-in-the-loop.md) to handle situations where it may need guidance or verification before proceeding.
|
||||
|
||||
@@ -2,15 +2,7 @@
|
||||
|
||||
Agents can be unreliable and may need human input to successfully accomplish tasks. Similarly, for some actions, you may want to require human approval before running to ensure that everything is running as intended.
|
||||
|
||||
LangGraph's [persistence](../../concepts/persistence.md) layer supports **human-in-the-loop** workflows, allowing execution to pause and resume based on user feedback. The primary interface to this functionality is the [`interrupt`](../../how-tos/human_in_the_loop/add-human-in-the-loop.md) function. Calling `interrupt` inside a node will pause execution. Execution can be resumed, together with new input from a human, by passing in a [Command](../../concepts/low_level.md#command).
|
||||
|
||||
:::python
|
||||
`interrupt` is ergonomically similar to Python's built-in `input()`, [with some caveats](../../how-tos/human_in_the_loop/add-human-in-the-loop.md).
|
||||
:::
|
||||
|
||||
:::js
|
||||
`interrupt` is ergonomically similar to Node.js's built-in `readline.question()` function, [with some caveats](../../how-tos/human_in_the_loop/add-human-in-the-loop.md).
|
||||
:::
|
||||
LangGraph's [persistence](../../concepts/persistence.md) layer supports **human-in-the-loop** workflows, allowing execution to pause and resume based on user feedback. The primary interface to this functionality is the [`interrupt`](../../how-tos/human_in_the_loop/add-human-in-the-loop.md) function. Calling `interrupt` inside a node will pause execution. Execution can be resumed, together with new input from a human, by passing in a [Command](../../concepts/low_level.md#command). `interrupt` is ergonomically similar to Python's built-in `input()`, [with some caveats](../../how-tos/human_in_the_loop/add-human-in-the-loop.md).
|
||||
|
||||
!!! note
|
||||
|
||||
@@ -22,7 +14,6 @@ Starting with the existing code from the [Add memory to the chatbot](./3-add-mem
|
||||
|
||||
Let's first select a chat model:
|
||||
|
||||
:::python
|
||||
{!snippets/chat_model_tabs.md!}
|
||||
|
||||
<!---
|
||||
@@ -33,22 +24,9 @@ llm = init_chat_model("anthropic:claude-3-5-sonnet-latest")
|
||||
```
|
||||
-->
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
// Add your API key here
|
||||
process.env.ANTHROPIC_API_KEY = "YOUR_API_KEY";
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
We can now incorporate it into our `StateGraph` with an additional tool:
|
||||
|
||||
:::python
|
||||
|
||||
```python hl_lines="12 19 20 21 22 23"
|
||||
``` python hl_lines="12 19 20 21 22 23"
|
||||
from typing import Annotated
|
||||
|
||||
from langchain_tavily import TavilySearch
|
||||
@@ -98,54 +76,6 @@ graph_builder.add_edge("tools", "chatbot")
|
||||
graph_builder.add_edge(START, "chatbot")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript hl_lines="1 7-19"
|
||||
import { interrupt, MessagesZodState } from "@langchain/langgraph";
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { TavilySearch } from "@langchain/tavily";
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { z } from "zod";
|
||||
|
||||
const humanAssistance = tool(
|
||||
async ({ query }) => {
|
||||
const humanResponse = interrupt({ query });
|
||||
return humanResponse.data;
|
||||
},
|
||||
{
|
||||
name: "humanAssistance",
|
||||
description: "Request assistance from a human.",
|
||||
schema: z.object({
|
||||
query: z.string().describe("Human readable question for the human"),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
const searchTool = new TavilySearch({ maxResults: 2 });
|
||||
const tools = [searchTool, humanAssistance];
|
||||
|
||||
const llmWithTools = new ChatAnthropic({
|
||||
model: "claude-3-5-sonnet-latest",
|
||||
}).bindTools(tools);
|
||||
|
||||
async function chatbot(state: z.infer<typeof MessagesZodState>) {
|
||||
const message = await llmWithTools.invoke(state.messages);
|
||||
|
||||
// Because we will be interrupting during tool execution,
|
||||
// we disable parallel tool calling to avoid repeating any
|
||||
// tool invocations when we resume.
|
||||
if (message.tool_calls && message.tool_calls.length > 1) {
|
||||
throw new Error("Multiple tool calls not supported with interrupts");
|
||||
}
|
||||
|
||||
return { messages: message };
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! tip
|
||||
|
||||
For more information and examples of human-in-the-loop workflows, see [Human-in-the-loop](../../concepts/human_in_the_loop.md).
|
||||
@@ -154,41 +84,17 @@ async function chatbot(state: z.infer<typeof MessagesZodState>) {
|
||||
|
||||
We compile the graph with a checkpointer, as before:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
memory = MemorySaver()
|
||||
|
||||
graph = graph_builder.compile(checkpointer=memory)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript hl_lines="3 11"
|
||||
import { StateGraph, MemorySaver, START, END } from "@langchain/langgraph";
|
||||
|
||||
const memory = new MemorySaver();
|
||||
|
||||
const graph = new StateGraph(MessagesZodState)
|
||||
.addNode("chatbot", chatbot)
|
||||
.addNode("tools", new ToolNode(tools))
|
||||
.addConditionalEdges("chatbot", toolsCondition, ["tools", END])
|
||||
.addEdge("tools", "chatbot")
|
||||
.addEdge(START, "chatbot")
|
||||
.compile({ checkpointer: memory });
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 3. Visualize the graph (optional)
|
||||
|
||||
Visualizing the graph, you get the same layout as before – just with the added tool!
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
``` python
|
||||
from IPython.display import Image, display
|
||||
|
||||
try:
|
||||
@@ -198,30 +104,12 @@ except Exception:
|
||||
pass
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import * as fs from "node:fs/promises";
|
||||
|
||||
const drawableGraph = await graph.getGraphAsync();
|
||||
const image = await drawableGraph.drawMermaidPng();
|
||||
const imageBuffer = new Uint8Array(await image.arrayBuffer());
|
||||
|
||||
await fs.writeFile("chatbot-with-tools.png", imageBuffer);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||

|
||||
|
||||
## 4. Prompt the chatbot
|
||||
|
||||
Now, prompt the chatbot with a question that will engage the new `human_assistance` tool:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
user_input = "I need some expert guidance for building an AI agent. Could you request assistance for me?"
|
||||
config = {"configurable": {"thread_id": "1"}}
|
||||
@@ -250,58 +138,8 @@ Tool Calls:
|
||||
query: A user is requesting expert guidance for building an AI agent. Could you please provide some expert advice or resources on this topic?
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { isAIMessage } from "@langchain/core/messages";
|
||||
|
||||
const userInput =
|
||||
"I need some expert guidance for building an AI agent. Could you request assistance for me?";
|
||||
|
||||
const events = await graph.stream(
|
||||
{ messages: [{ role: "user", content: userInput }] },
|
||||
{ configurable: { thread_id: "1" }, streamMode: "values" }
|
||||
);
|
||||
|
||||
for await (const event of events) {
|
||||
if ("messages" in event) {
|
||||
const lastMessage = event.messages.at(-1);
|
||||
console.log(`[${lastMessage?.getType()}]: ${lastMessage?.text}`);
|
||||
|
||||
if (
|
||||
lastMessage &&
|
||||
isAIMessage(lastMessage) &&
|
||||
lastMessage.tool_calls?.length
|
||||
) {
|
||||
console.log("Tool calls:", lastMessage.tool_calls);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
[human]: I need some expert guidance for building an AI agent. Could you request assistance for me?
|
||||
[ai]: I'll help you request human assistance for guidance on building an AI agent.
|
||||
Tool calls: [
|
||||
{
|
||||
name: 'humanAssistance',
|
||||
args: {
|
||||
query: 'I would like expert guidance on building an AI agent. Could you please provide assistance with this topic?'
|
||||
},
|
||||
id: 'toolu_01Bpxc8rFVMhSaRosS6b85Ts',
|
||||
type: 'tool_call'
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
The chatbot generated a tool call, but then execution has been interrupted. If you inspect the graph state, you see that it stopped at the tools node:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
snapshot = graph.get_state(config)
|
||||
snapshot.next
|
||||
@@ -311,25 +149,8 @@ snapshot.next
|
||||
('tools',)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
const snapshot = await graph.getState({ configurable: { thread_id: "1" } });
|
||||
snapshot.next;
|
||||
```
|
||||
|
||||
```json
|
||||
["tools"]
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! info Additional information
|
||||
|
||||
:::python
|
||||
|
||||
Take a closer look at the `human_assistance` tool:
|
||||
|
||||
```python
|
||||
@@ -341,40 +162,12 @@ snapshot.next;
|
||||
```
|
||||
|
||||
Similar to Python's built-in `input()` function, calling `interrupt` inside the tool will pause execution. Progress is persisted based on the [checkpointer](../../concepts/persistence.md#checkpointer-libraries); so if it is persisting with Postgres, it can resume at any time as long as the database is alive. In this example, it is persisting with the in-memory checkpointer and can resume any time if the Python kernel is running.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
Take a closer look at the `humanAssistance` tool:
|
||||
|
||||
```typescript hl_lines="3"
|
||||
const humanAssistance = tool(
|
||||
async ({ query }) => {
|
||||
const humanResponse = interrupt({ query });
|
||||
return humanResponse.data;
|
||||
},
|
||||
{
|
||||
name: "humanAssistance",
|
||||
description: "Request assistance from a human.",
|
||||
schema: z.object({
|
||||
query: z.string().describe("Human readable question for the human"),
|
||||
}),
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
Calling `interrupt` inside the tool will pause execution. Progress is persisted based on the [checkpointer](../../concepts/persistence.md#checkpointer-libraries); so if it is persisting with Postgres, it can resume at any time as long as the database is alive. In this example, it is persisting with the in-memory checkpointer and can resume any time if the JavaScript runtime is running.
|
||||
:::
|
||||
|
||||
## 5. Resume execution
|
||||
|
||||
To resume execution, pass a [`Command`](../../concepts/low_level.md#command) object containing data expected by the tool. The format of this data can be customized based on needs.
|
||||
To resume execution, pass a [`Command`](../../concepts/low_level.md#command) object containing data expected by the tool. The format of this data can be customized based on needs. For this example, use a dict with a key `"data"`:
|
||||
|
||||
:::python
|
||||
|
||||
For this example, use a dict with a key `"data"`:
|
||||
|
||||
```python
|
||||
``` python
|
||||
human_response = (
|
||||
"We, the experts are here to help! We'd recommend you check out LangGraph to build your agent."
|
||||
" It's much more reliable and extensible than simple autonomous agents."
|
||||
@@ -422,60 +215,12 @@ If you'd like more specific information about LangGraph or have any questions ab
|
||||
Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings...
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
For this example, use an object with a key `"data"`:
|
||||
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
const humanResponse =
|
||||
"We, the experts are here to help! We'd recommend you check out LangGraph to build your agent." +
|
||||
" It's much more reliable and extensible than simple autonomous agents.";
|
||||
|
||||
const humanCommand = new Command({ resume: { data: humanResponse } });
|
||||
|
||||
const resumeEvents = await graph.stream(humanCommand, {
|
||||
configurable: { thread_id: "1" },
|
||||
streamMode: "values",
|
||||
});
|
||||
|
||||
for await (const event of resumeEvents) {
|
||||
if ("messages" in event) {
|
||||
const lastMessage = event.messages.at(-1);
|
||||
console.log(`[${lastMessage?.getType()}]: ${lastMessage?.text}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
[tool]: We, the experts are here to help! We'd recommend you check out LangGraph to build your agent. It's much more reliable and extensible than simple autonomous agents.
|
||||
[ai]: Thank you for your patience. I've received some expert advice regarding your request for guidance on building an AI agent. Here's what the experts have suggested:
|
||||
|
||||
The experts recommend that you look into LangGraph for building your AI agent. They mention that LangGraph is a more reliable and extensible option compared to simple autonomous agents.
|
||||
|
||||
LangGraph is likely a framework or library designed specifically for creating AI agents with advanced capabilities. Here are a few points to consider based on this recommendation:
|
||||
|
||||
1. Reliability: The experts emphasize that LangGraph is more reliable than simpler autonomous agent approaches. This could mean it has better stability, error handling, or consistent performance.
|
||||
|
||||
2. Extensibility: LangGraph is described as more extensible, which suggests that it probably offers a flexible architecture that allows you to easily add new features or modify existing ones as your agent's requirements evolve.
|
||||
|
||||
3. Advanced capabilities: Given that it's recommended over "simple autonomous agents," LangGraph likely provides more sophisticated tools and techniques for building complex AI agents.
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
The input has been received and processed as a tool message. Review this call's [LangSmith trace](https://smith.langchain.com/public/9f0f87e3-56a7-4dde-9c76-b71675624e91/r) to see the exact work that was done in the above call. Notice that the state is loaded in the first step so that our chatbot can continue where it left off.
|
||||
|
||||
**Congratulations!** You've used an `interrupt` to add human-in-the-loop execution to your chatbot, allowing for human oversight and intervention when needed. This opens up the potential UIs you can create with your AI systems. Since you have already added a **checkpointer**, as long as the underlying persistence layer is running, the graph can be paused **indefinitely** and resumed at any time as if nothing had happened.
|
||||
|
||||
Check out the code snippet below to review the graph from this tutorial:
|
||||
|
||||
:::python
|
||||
|
||||
{!snippets/chat_model_tabs.md!}
|
||||
|
||||
```python
|
||||
@@ -527,73 +272,6 @@ memory = MemorySaver()
|
||||
graph = graph_builder.compile(checkpointer=memory)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import {
|
||||
interrupt,
|
||||
MessagesZodState,
|
||||
StateGraph,
|
||||
MemorySaver,
|
||||
START,
|
||||
END,
|
||||
} from "@langchain/langgraph";
|
||||
import { ToolNode, toolsCondition } from "@langchain/langgraph/prebuilt";
|
||||
import { isAIMessage } from "@langchain/core/messages";
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { TavilySearch } from "@langchain/tavily";
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { z } from "zod";
|
||||
|
||||
const humanAssistance = tool(
|
||||
async ({ query }) => {
|
||||
const humanResponse = interrupt({ query });
|
||||
return humanResponse.data;
|
||||
},
|
||||
{
|
||||
name: "humanAssistance",
|
||||
description: "Request assistance from a human.",
|
||||
schema: z.object({
|
||||
query: z.string().describe("Human readable question for the human"),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
const searchTool = new TavilySearch({ maxResults: 2 });
|
||||
const tools = [searchTool, humanAssistance];
|
||||
|
||||
const llmWithTools = new ChatAnthropic({
|
||||
model: "claude-3-5-sonnet-latest",
|
||||
}).bindTools(tools);
|
||||
|
||||
const chatbot = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const message = await llmWithTools.invoke(state.messages);
|
||||
|
||||
// Because we will be interrupting during tool execution,
|
||||
// we disable parallel tool calling to avoid repeating any
|
||||
// tool invocations when we resume.
|
||||
if (message.tool_calls && message.tool_calls.length > 1) {
|
||||
throw new Error("Multiple tool calls not supported with interrupts");
|
||||
}
|
||||
|
||||
return { messages: message };
|
||||
};
|
||||
|
||||
const memory = new MemorySaver();
|
||||
|
||||
const graph = new StateGraph(MessagesZodState)
|
||||
.addNode("chatbot", chatbot)
|
||||
.addNode("tools", new ToolNode(tools))
|
||||
.addConditionalEdges("chatbot", toolsCondition, ["tools", END])
|
||||
.addEdge("tools", "chatbot")
|
||||
.addEdge(START, "chatbot")
|
||||
.compile({ checkpointer: memory });
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Next steps
|
||||
|
||||
So far, the tutorial examples have relied on a simple state with one entry: a list of messages. You can go far with this simple state, but if you want to define complex behavior without relying on the message list, you can [add additional fields to the state](./5-customize-state.md).
|
||||
So far, the tutorial examples have relied on a simple state with one entry: a list of messages. You can go far with this simple state, but if you want to define complex behavior without relying on the message list, you can [add additional fields to the state](./5-customize-state.md).
|
||||
@@ -10,8 +10,6 @@ In this tutorial, you will add additional fields to the state to define complex
|
||||
|
||||
Update the chatbot to research the birthday of an entity by adding `name` and `birthday` keys to the state:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
|
||||
@@ -28,34 +26,13 @@ class State(TypedDict):
|
||||
birthday: str
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { MessagesZodState } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({
|
||||
messages: MessagesZodState.shape.messages,
|
||||
// highlight-next-line
|
||||
name: z.string(),
|
||||
// highlight-next-line
|
||||
birthday: z.string(),
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Adding this information to the state makes it easily accessible by other graph nodes (like a downstream node that stores or processes the information), as well as the graph's persistence layer.
|
||||
|
||||
## 2. Update the state inside the tool
|
||||
|
||||
:::python
|
||||
|
||||
Now, populate the state keys inside of the `human_assistance` tool. This allows a human to review the information before it is stored in the state. Use [`Command`](../../concepts/low_level.md#using-inside-tools) to issue a state update from inside the tool.
|
||||
|
||||
```python
|
||||
``` python
|
||||
from langchain_core.messages import ToolMessage
|
||||
from langchain_core.tools import InjectedToolCallId, tool
|
||||
|
||||
@@ -99,78 +76,10 @@ def human_assistance(
|
||||
return Command(update=state_update)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
Now, populate the state keys inside of the `humanAssistance` tool. This allows a human to review the information before it is stored in the state. Use [`Command`](../../concepts/low_level.md#using-inside-tools) to issue a state update from inside the tool.
|
||||
|
||||
```typescript
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { ToolMessage } from "@langchain/core/messages";
|
||||
import { Command, interrupt } from "@langchain/langgraph";
|
||||
|
||||
const humanAssistance = tool(
|
||||
async (input, config) => {
|
||||
// Note that because we are generating a ToolMessage for a state update,
|
||||
// we generally require the ID of the corresponding tool call.
|
||||
// This is available in the tool's config.
|
||||
const toolCallId = config?.toolCall?.id as string | undefined;
|
||||
if (!toolCallId) throw new Error("Tool call ID is required");
|
||||
|
||||
const humanResponse = await interrupt({
|
||||
question: "Is this correct?",
|
||||
name: input.name,
|
||||
birthday: input.birthday,
|
||||
});
|
||||
|
||||
// We explicitly update the state with a ToolMessage inside the tool.
|
||||
const stateUpdate = (() => {
|
||||
// If the information is correct, update the state as-is.
|
||||
if (humanResponse.correct?.toLowerCase().startsWith("y")) {
|
||||
return {
|
||||
name: input.name,
|
||||
birthday: input.birthday,
|
||||
messages: [
|
||||
new ToolMessage({ content: "Correct", tool_call_id: toolCallId }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise, receive information from the human reviewer.
|
||||
return {
|
||||
name: humanResponse.name || input.name,
|
||||
birthday: humanResponse.birthday || input.birthday,
|
||||
messages: [
|
||||
new ToolMessage({
|
||||
content: `Made a correction: ${JSON.stringify(humanResponse)}`,
|
||||
tool_call_id: toolCallId,
|
||||
}),
|
||||
],
|
||||
};
|
||||
})();
|
||||
|
||||
// We return a Command object in the tool to update our state.
|
||||
return new Command({ update: stateUpdate });
|
||||
},
|
||||
{
|
||||
name: "humanAssistance",
|
||||
description: "Request assistance from a human.",
|
||||
schema: z.object({
|
||||
name: z.string().describe("The name of the entity"),
|
||||
birthday: z.string().describe("The birthday/release date of the entity"),
|
||||
}),
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
The rest of the graph stays the same.
|
||||
|
||||
## 3. Prompt the chatbot
|
||||
|
||||
:::python
|
||||
Prompt the chatbot to look up the "birthday" of the LangGraph library and direct the chatbot to reach out to the `human_assistance` tool once it has the required information. By setting `name` and `birthday` in the arguments for the tool, you force the chatbot to generate proposals for these fields.
|
||||
|
||||
```python
|
||||
@@ -190,51 +99,6 @@ for event in events:
|
||||
event["messages"][-1].pretty_print()
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Prompt the chatbot to look up the "birthday" of the LangGraph library and direct the chatbot to reach out to the `humanAssistance` tool once it has the required information. By setting `name` and `birthday` in the arguments for the tool, you force the chatbot to generate proposals for these fields.
|
||||
|
||||
```typescript
|
||||
import { isAIMessage } from "@langchain/core/messages";
|
||||
|
||||
const userInput =
|
||||
"Can you look up when LangGraph was released? " +
|
||||
"When you have the answer, use the humanAssistance tool for review.";
|
||||
|
||||
const events = await graph.stream(
|
||||
{ messages: [{ role: "user", content: userInput }] },
|
||||
{ configurable: { thread_id: "1" }, streamMode: "values" }
|
||||
);
|
||||
|
||||
for await (const event of events) {
|
||||
if ("messages" in event) {
|
||||
const lastMessage = event.messages.at(-1);
|
||||
|
||||
console.log(
|
||||
"=".repeat(32),
|
||||
`${lastMessage?.getType()} Message`,
|
||||
"=".repeat(32)
|
||||
);
|
||||
console.log(lastMessage?.text);
|
||||
|
||||
if (
|
||||
lastMessage &&
|
||||
isAIMessage(lastMessage) &&
|
||||
lastMessage.tool_calls?.length
|
||||
) {
|
||||
console.log("Tool Calls:");
|
||||
for (const call of lastMessage.tool_calls) {
|
||||
console.log(` ${call.name} (${call.id})`);
|
||||
console.log(` Args: ${JSON.stringify(call.args)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
```
|
||||
================================ Human Message =================================
|
||||
|
||||
@@ -262,20 +126,12 @@ Tool Calls:
|
||||
birthday: 2023-01-01
|
||||
```
|
||||
|
||||
:::python
|
||||
We've hit the `interrupt` in the `human_assistance` tool again.
|
||||
:::
|
||||
|
||||
:::js
|
||||
We've hit the `interrupt` in the `humanAssistance` tool again.
|
||||
:::
|
||||
|
||||
## 4. Add human assistance
|
||||
|
||||
The chatbot failed to identify the correct date, so supply it with information:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
human_command = Command(
|
||||
resume={
|
||||
@@ -290,53 +146,6 @@ for event in events:
|
||||
event["messages"][-1].pretty_print()
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
const humanCommand = new Command({
|
||||
resume: {
|
||||
name: "LangGraph",
|
||||
birthday: "Jan 17, 2024",
|
||||
},
|
||||
});
|
||||
|
||||
const resumeEvents = await graph.stream(humanCommand, {
|
||||
configurable: { thread_id: "1" },
|
||||
streamMode: "values",
|
||||
});
|
||||
|
||||
for await (const event of resumeEvents) {
|
||||
if ("messages" in event) {
|
||||
const lastMessage = event.messages.at(-1);
|
||||
|
||||
console.log(
|
||||
"=".repeat(32),
|
||||
`${lastMessage?.getType()} Message`,
|
||||
"=".repeat(32)
|
||||
);
|
||||
console.log(lastMessage?.text);
|
||||
|
||||
if (
|
||||
lastMessage &&
|
||||
isAIMessage(lastMessage) &&
|
||||
lastMessage.tool_calls?.length
|
||||
) {
|
||||
console.log("Tool Calls:");
|
||||
for (const call of lastMessage.tool_calls) {
|
||||
console.log(` ${call.name} (${call.id})`);
|
||||
console.log(` Args: ${JSON.stringify(call.args)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
```
|
||||
================================== Ai Message ==================================
|
||||
|
||||
@@ -366,8 +175,6 @@ It's worth noting that LangGraph had been in development and use for some time b
|
||||
|
||||
Note that these fields are now reflected in the state:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
snapshot = graph.get_state(config)
|
||||
|
||||
@@ -378,34 +185,13 @@ snapshot = graph.get_state(config)
|
||||
{'name': 'LangGraph', 'birthday': 'Jan 17, 2024'}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
const snapshot = await graph.getState(config);
|
||||
|
||||
const relevantState = Object.fromEntries(
|
||||
Object.entries(snapshot.values).filter(([k]) =>
|
||||
["name", "birthday"].includes(k)
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
```
|
||||
{ name: 'LangGraph', birthday: 'Jan 17, 2024' }
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
This makes them easily accessible to downstream nodes (e.g., a node that further processes or stores the information).
|
||||
|
||||
## 5. Manually update the state
|
||||
|
||||
:::python
|
||||
LangGraph gives a high degree of control over the application state. For instance, at any point (including when interrupted), you can manually override a key using `graph.update_state`:
|
||||
|
||||
```python
|
||||
``` python
|
||||
graph.update_state(config, {"name": "LangGraph (library)"})
|
||||
```
|
||||
|
||||
@@ -415,36 +201,11 @@ graph.update_state(config, {"name": "LangGraph (library)"})
|
||||
'checkpoint_id': '1efd4ec5-cf69-6352-8006-9278f1730162'}}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
LangGraph gives a high degree of control over the application state. For instance, at any point (including when interrupted), you can manually override a key using `graph.updateState`:
|
||||
|
||||
```typescript
|
||||
await graph.updateState(
|
||||
{ configurable: { thread_id: "1" } },
|
||||
{ name: "LangGraph (library)" }
|
||||
);
|
||||
```
|
||||
|
||||
```typescript
|
||||
{
|
||||
configurable: {
|
||||
thread_id: '1',
|
||||
checkpoint_ns: '',
|
||||
checkpoint_id: '1efd4ec5-cf69-6352-8006-9278f1730162'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 6. View the new value
|
||||
|
||||
:::python
|
||||
If you call `graph.get_state`, you can see the new value is reflected:
|
||||
|
||||
```python
|
||||
``` python
|
||||
snapshot = graph.get_state(config)
|
||||
|
||||
{k: v for k, v in snapshot.values.items() if k in ("name", "birthday")}
|
||||
@@ -454,35 +215,12 @@ snapshot = graph.get_state(config)
|
||||
{'name': 'LangGraph (library)', 'birthday': 'Jan 17, 2024'}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
If you call `graph.getState`, you can see the new value is reflected:
|
||||
|
||||
```typescript
|
||||
const updatedSnapshot = await graph.getState(config);
|
||||
|
||||
const updatedRelevantState = Object.fromEntries(
|
||||
Object.entries(updatedSnapshot.values).filter(([k]) =>
|
||||
["name", "birthday"].includes(k)
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
```typescript
|
||||
{ name: 'LangGraph (library)', birthday: 'Jan 17, 2024' }
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Manual state updates will [generate a trace](https://smith.langchain.com/public/7ebb7827-378d-49fe-9f6c-5df0e90086c8/r) in LangSmith. If desired, they can also be used to [control human-in-the-loop workflows](../../how-tos/human_in_the_loop/add-human-in-the-loop.md). Use of the `interrupt` function is generally recommended instead, as it allows data to be transmitted in a human-in-the-loop interaction independently of state updates.
|
||||
|
||||
**Congratulations!** You've added custom keys to the state to facilitate a more complex workflow, and learned how to generate state updates from inside tools.
|
||||
|
||||
Check out the code snippet below to review the graph from this tutorial:
|
||||
|
||||
:::python
|
||||
|
||||
{!snippets/chat_model_tabs.md!}
|
||||
|
||||
<!---
|
||||
@@ -567,111 +305,7 @@ memory = MemorySaver()
|
||||
graph = graph_builder.compile(checkpointer=memory)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import {
|
||||
Command,
|
||||
interrupt,
|
||||
MessagesZodState,
|
||||
MemorySaver,
|
||||
StateGraph,
|
||||
END,
|
||||
START,
|
||||
} from "@langchain/langgraph";
|
||||
import { ToolNode, toolsCondition } from "@langchain/langgraph/prebuilt";
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { TavilySearch } from "@langchain/tavily";
|
||||
import { ToolMessage } from "@langchain/core/messages";
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({
|
||||
messages: MessagesZodState.shape.messages,
|
||||
name: z.string(),
|
||||
birthday: z.string(),
|
||||
});
|
||||
|
||||
const humanAssistance = tool(
|
||||
async (input, config) => {
|
||||
// Note that because we are generating a ToolMessage for a state update, we
|
||||
// generally require the ID of the corresponding tool call. This is available
|
||||
// in the tool's config.
|
||||
const toolCallId = config?.toolCall?.id as string | undefined;
|
||||
if (!toolCallId) throw new Error("Tool call ID is required");
|
||||
|
||||
const humanResponse = await interrupt({
|
||||
question: "Is this correct?",
|
||||
name: input.name,
|
||||
birthday: input.birthday,
|
||||
});
|
||||
|
||||
// We explicitly update the state with a ToolMessage inside the tool.
|
||||
const stateUpdate = (() => {
|
||||
// If the information is correct, update the state as-is.
|
||||
if (humanResponse.correct?.toLowerCase().startsWith("y")) {
|
||||
return {
|
||||
name: input.name,
|
||||
birthday: input.birthday,
|
||||
messages: [
|
||||
new ToolMessage({ content: "Correct", tool_call_id: toolCallId }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise, receive information from the human reviewer.
|
||||
return {
|
||||
name: humanResponse.name || input.name,
|
||||
birthday: humanResponse.birthday || input.birthday,
|
||||
messages: [
|
||||
new ToolMessage({
|
||||
content: `Made a correction: ${JSON.stringify(humanResponse)}`,
|
||||
tool_call_id: toolCallId,
|
||||
}),
|
||||
],
|
||||
};
|
||||
})();
|
||||
|
||||
// We return a Command object in the tool to update our state.
|
||||
return new Command({ update: stateUpdate });
|
||||
},
|
||||
{
|
||||
name: "humanAssistance",
|
||||
description: "Request assistance from a human.",
|
||||
schema: z.object({
|
||||
name: z.string().describe("The name of the entity"),
|
||||
birthday: z.string().describe("The birthday/release date of the entity"),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
const searchTool = new TavilySearch({ maxResults: 2 });
|
||||
|
||||
const tools = [searchTool, humanAssistance];
|
||||
const llmWithTools = new ChatAnthropic({
|
||||
model: "claude-3-5-sonnet-latest",
|
||||
}).bindTools(tools);
|
||||
|
||||
const memory = new MemorySaver();
|
||||
|
||||
const chatbot = async (state: z.infer<typeof State>) => {
|
||||
const message = await llmWithTools.invoke(state.messages);
|
||||
return { messages: message };
|
||||
};
|
||||
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("chatbot", chatbot)
|
||||
.addNode("tools", new ToolNode(tools))
|
||||
.addConditionalEdges("chatbot", toolsCondition, ["tools", END])
|
||||
.addEdge("tools", "chatbot")
|
||||
.addEdge(START, "chatbot")
|
||||
.compile({ checkpointer: memory });
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Next steps
|
||||
|
||||
There's one more concept to review before finishing the LangGraph basics tutorials: connecting `checkpointing` and `state updates` to [time travel](./6-time-travel.md).
|
||||
There's one more concept to review before finishing the LangGraph basics tutorials: connecting `checkpointing` and `state updates` to [time travel](./6-time-travel.md).
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ In a typical chatbot workflow, the user interacts with the bot one or more times
|
||||
|
||||
What if you want a user to be able to start from a previous response and explore a different outcome? Or what if you want users to be able to rewind your chatbot's work to fix mistakes or try a different strategy, something that is common in applications like autonomous software engineers?
|
||||
|
||||
You can create these types of experiences using LangGraph's built-in **time travel** functionality.
|
||||
You can create these types of experiences using LangGraph's built-in **time travel** functionality.
|
||||
|
||||
!!! note
|
||||
|
||||
@@ -12,15 +12,7 @@ You can create these types of experiences using LangGraph's built-in **time trav
|
||||
|
||||
## 1. Rewind your graph
|
||||
|
||||
:::python
|
||||
Rewind your graph by fetching a checkpoint using the graph's `get_state_history` method. You can then resume execution at this previous point in time.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Rewind your graph by fetching a checkpoint using the graph's `getStateHistory` method. You can then resume execution at this previous point in time.
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
{!snippets/chat_model_tabs.md!}
|
||||
|
||||
@@ -72,49 +64,11 @@ memory = MemorySaver()
|
||||
graph = graph_builder.compile(checkpointer=memory)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import {
|
||||
StateGraph,
|
||||
START,
|
||||
END,
|
||||
MessagesZodState,
|
||||
MemorySaver,
|
||||
} from "@langchain/langgraph";
|
||||
import { ToolNode, toolsCondition } from "@langchain/langgraph/prebuilt";
|
||||
import { TavilySearch } from "@langchain/tavily";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({ messages: MessagesZodState.shape.messages });
|
||||
|
||||
const tools = [new TavilySearch({ maxResults: 2 })];
|
||||
const llmWithTools = new ChatOpenAI({ model: "gpt-4o-mini" }).bindTools(tools);
|
||||
const memory = new MemorySaver();
|
||||
|
||||
const graph = new StateGraph(State)
|
||||
.addNode("chatbot", async (state) => ({
|
||||
messages: [await llmWithTools.invoke(state.messages)],
|
||||
}))
|
||||
.addNode("tools", new ToolNode(tools))
|
||||
.addConditionalEdges("chatbot", toolsCondition, ["tools", END])
|
||||
.addEdge("tools", "chatbot")
|
||||
.addEdge(START, "chatbot")
|
||||
.compile({ checkpointer: memory });
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 2. Add steps
|
||||
|
||||
Add steps to your graph. Every step will be checkpointed in its state history:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
``` python
|
||||
config = {"configurable": {"thread_id": "1"}}
|
||||
events = graph.stream(
|
||||
{
|
||||
@@ -205,7 +159,7 @@ Tool Calls:
|
||||
================================= Tool Message =================================
|
||||
Name: tavily_search_results_json
|
||||
|
||||
[{"url": "https://towardsdatascience.com/building-autonomous-multi-tool-agents-with-gemini-2-0-and-langgraph-ad3d7bd5e79d", "content": "Building Autonomous Multi-Tool Agents with Gemini 2.0 and LangGraph | by Youness Mansar | Jan, 2025 | Towards Data Science Building Autonomous Multi-Tool Agents with Gemini 2.0 and LangGraph A practical tutorial with full code examples for building and running multi-tool agents Towards Data Science LLMs are remarkable — they can memorize vast amounts of information, answer general knowledge questions, write code, generate stories, and even fix your grammar. In this tutorial, we are going to build a simple LLM agent that is equipped with four tools that it can use to answer a user's question. This Agent will have the following specifications: Follow Published in Towards Data Science --------------------------------- Your home for data science and AI. Follow Follow Follow"}, {"url": "https://github.com/anmolaman20/Tools_and_Agents", "content": "GitHub - anmolaman20/Tools_and_Agents: This repository provides resources for building AI agents using Langchain and Langgraph. This repository provides resources for building AI agents using Langchain and Langgraph. This repository provides resources for building AI agents using Langchain and Langgraph. This repository serves as a comprehensive guide for building AI-powered agents using Langchain and Langgraph. It provides hands-on examples, practical tutorials, and resources for developers and AI enthusiasts to master building intelligent systems and workflows. AI Agent Development: Gain insights into creating intelligent systems that think, reason, and adapt in real time. This repository is ideal for AI practitioners, developers exploring language models, or anyone interested in building intelligent systems. This repository provides resources for building AI agents using Langchain and Langgraph."}]
|
||||
[{"url": "https://towardsdatascience.com/building-autonomous-multi-tool-agents-with-gemini-2-0-and-langgraph-ad3d7bd5e79d", "content": "Building Autonomous Multi-Tool Agents with Gemini 2.0 and LangGraph | by Youness Mansar | Jan, 2025 | Towards Data Science Building Autonomous Multi-Tool Agents with Gemini 2.0 and LangGraph A practical tutorial with full code examples for building and running multi-tool agents Towards Data Science LLMs are remarkable — they can memorize vast amounts of information, answer general knowledge questions, write code, generate stories, and even fix your grammar. In this tutorial, we are going to build a simple LLM agent that is equipped with four tools that it can use to answer a user’s question. This Agent will have the following specifications: Follow Published in Towards Data Science --------------------------------- Your home for data science and AI. Follow Follow Follow"}, {"url": "https://github.com/anmolaman20/Tools_and_Agents", "content": "GitHub - anmolaman20/Tools_and_Agents: This repository provides resources for building AI agents using Langchain and Langgraph. This repository provides resources for building AI agents using Langchain and Langgraph. This repository provides resources for building AI agents using Langchain and Langgraph. This repository serves as a comprehensive guide for building AI-powered agents using Langchain and Langgraph. It provides hands-on examples, practical tutorials, and resources for developers and AI enthusiasts to master building intelligent systems and workflows. AI Agent Development: Gain insights into creating intelligent systems that think, reason, and adapt in real time. This repository is ideal for AI practitioners, developers exploring language models, or anyone interested in building intelligent systems. This repository provides resources for building AI agents using Langchain and Langgraph."}]
|
||||
================================== Ai Message ==================================
|
||||
|
||||
Great idea! Building an autonomous agent with LangGraph is definitely an exciting project. Based on the latest information I've found, here are some insights and tips for building autonomous agents with LangGraph:
|
||||
@@ -223,140 +177,11 @@ Building an autonomous agent is an iterative process, so be prepared to refine a
|
||||
Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings...
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { randomUUID } from "node:crypto";
|
||||
const threadId = randomUUID();
|
||||
|
||||
let iter = 0;
|
||||
|
||||
for (const userInput of [
|
||||
"I'm learning LangGraph. Could you do some research on it for me?",
|
||||
"Ya that's helpful. Maybe I'll build an autonomous agent with it!",
|
||||
]) {
|
||||
iter += 1;
|
||||
|
||||
console.log(`\n--- Conversation Turn ${iter} ---\n`);
|
||||
const events = await graph.stream(
|
||||
{ messages: [{ role: "user", content: userInput }] },
|
||||
{ configurable: { thread_id: threadId }, streamMode: "values" }
|
||||
);
|
||||
|
||||
for await (const event of events) {
|
||||
if ("messages" in event) {
|
||||
const lastMessage = event.messages.at(-1);
|
||||
|
||||
console.log(
|
||||
"=".repeat(32),
|
||||
`${lastMessage?.getType()} Message`,
|
||||
"=".repeat(32)
|
||||
);
|
||||
console.log(lastMessage?.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
--- Conversation Turn 1 ---
|
||||
|
||||
================================ human Message ================================
|
||||
I'm learning LangGraph.js. Could you do some research on it for me?
|
||||
================================ ai Message ================================
|
||||
I'll search for information about LangGraph.js for you.
|
||||
================================ tool Message ================================
|
||||
{
|
||||
"query": "LangGraph.js framework TypeScript langchain what is it tutorial guide",
|
||||
"follow_up_questions": null,
|
||||
"answer": null,
|
||||
"images": [],
|
||||
"results": [
|
||||
{
|
||||
"url": "https://techcommunity.microsoft.com/blog/educatordeveloperblog/an-absolute-beginners-guide-to-langgraph-js/4212496",
|
||||
"title": "An Absolute Beginner's Guide to LangGraph.js",
|
||||
"content": "(...)",
|
||||
"score": 0.79369855,
|
||||
"raw_content": null
|
||||
},
|
||||
{
|
||||
"url": "https://langchain-ai.github.io/langgraphjs/",
|
||||
"title": "LangGraph.js",
|
||||
"content": "(...)",
|
||||
"score": 0.78154784,
|
||||
"raw_content": null
|
||||
}
|
||||
],
|
||||
"response_time": 2.37
|
||||
}
|
||||
================================ ai Message ================================
|
||||
Let me provide you with an overview of LangGraph.js based on the search results:
|
||||
|
||||
LangGraph.js is a JavaScript/TypeScript library that's part of the LangChain ecosystem, specifically designed for creating and managing complex LLM (Large Language Model) based workflows. Here are the key points about LangGraph.js:
|
||||
|
||||
1. Purpose:
|
||||
- It's a low-level orchestration framework for building controllable agents
|
||||
- Particularly useful for creating agentic workflows where LLMs decide the course of action based on current state
|
||||
- Helps model workflows as graphs with nodes and edges
|
||||
|
||||
(...)
|
||||
|
||||
--- Conversation Turn 2 ---
|
||||
|
||||
================================ human Message ================================
|
||||
Ya that's helpful. Maybe I'll build an autonomous agent with it!
|
||||
================================ ai Message ================================
|
||||
Let me search for specific information about building autonomous agents with LangGraph.js.
|
||||
================================ tool Message ================================
|
||||
{
|
||||
"query": "how to build autonomous agents with LangGraph.js examples tutorial react agent",
|
||||
"follow_up_questions": null,
|
||||
"answer": null,
|
||||
"images": [],
|
||||
"results": [
|
||||
{
|
||||
"url": "https://ai.google.dev/gemini-api/docs/langgraph-example",
|
||||
"title": "ReAct agent from scratch with Gemini 2.5 and LangGraph",
|
||||
"content": "(...)",
|
||||
"score": 0.7602419,
|
||||
"raw_content": null
|
||||
},
|
||||
{
|
||||
"url": "https://www.youtube.com/watch?v=ZfjaIshGkmk",
|
||||
"title": "Build Autonomous AI Agents with ReAct and LangGraph Tools",
|
||||
"content": "(...)",
|
||||
"score": 0.7471924,
|
||||
"raw_content": null
|
||||
}
|
||||
],
|
||||
"response_time": 1.98
|
||||
}
|
||||
================================ ai Message ================================
|
||||
Based on the search results, I can provide you with a practical overview of how to build an autonomous agent with LangGraph.js. Here's what you need to know:
|
||||
|
||||
1. Basic Structure for Building an Agent:
|
||||
- LangGraph.js provides a ReAct (Reason + Act) pattern implementation
|
||||
- The basic components include:
|
||||
- State management for conversation history
|
||||
- Nodes for different actions
|
||||
- Edges for decision-making flow
|
||||
- Tools for specific functionalities
|
||||
|
||||
(...)
|
||||
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 3. Replay the full state history
|
||||
|
||||
Now that you have added steps to the chatbot, you can `replay` the full state history to see everything that occurred.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
``` python
|
||||
to_replay = None
|
||||
for state in graph.get_state_history(config):
|
||||
print("Num Messages: ", len(state.values["messages"]), "Next: ", state.next)
|
||||
@@ -389,61 +214,10 @@ Num Messages: 0 Next: ('__start__',)
|
||||
--------------------------------------------------------------------------------
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import type { StateSnapshot } from "@langchain/langgraph";
|
||||
|
||||
let toReplay: StateSnapshot | undefined;
|
||||
for await (const state of graph.getStateHistory({
|
||||
configurable: { thread_id: threadId },
|
||||
})) {
|
||||
console.log(
|
||||
`Num Messages: ${state.values.messages.length}, Next: ${JSON.stringify(
|
||||
state.next
|
||||
)}`
|
||||
);
|
||||
console.log("-".repeat(80));
|
||||
if (state.values.messages.length === 6) {
|
||||
// We are somewhat arbitrarily selecting a specific state based on the number of chat messages in the state.
|
||||
toReplay = state;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
Num Messages: 8, Next: []
|
||||
--------------------------------------------------------------------------------
|
||||
Num Messages: 7, Next: ["chatbot"]
|
||||
--------------------------------------------------------------------------------
|
||||
Num Messages: 6, Next: ["tools"]
|
||||
--------------------------------------------------------------------------------
|
||||
Num Messages: 5, Next: ["chatbot"]
|
||||
--------------------------------------------------------------------------------
|
||||
Num Messages: 4, Next: ["__start__"]
|
||||
--------------------------------------------------------------------------------
|
||||
Num Messages: 4, Next: []
|
||||
--------------------------------------------------------------------------------
|
||||
Num Messages: 3, Next: ["chatbot"]
|
||||
--------------------------------------------------------------------------------
|
||||
Num Messages: 2, Next: ["tools"]
|
||||
--------------------------------------------------------------------------------
|
||||
Num Messages: 1, Next: ["chatbot"]
|
||||
--------------------------------------------------------------------------------
|
||||
Num Messages: 0, Next: ["__start__"]
|
||||
--------------------------------------------------------------------------------
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Checkpoints are saved for every step of the graph. This **spans invocations** so you can rewind across a full thread's history.
|
||||
Checkpoints are saved for every step of the graph. This __spans invocations__ so you can rewind across a full thread's history.
|
||||
|
||||
## Resume from a checkpoint
|
||||
|
||||
:::python
|
||||
|
||||
Resume from the `to_replay` state, which is after the `chatbot` node in the second graph invocation. Resuming from this point will call the **action** node next.
|
||||
|
||||
```python
|
||||
@@ -456,37 +230,12 @@ print(to_replay.config)
|
||||
{'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efd43e3-0c1f-6c4e-8006-891877d65740'}}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
Resume from the `toReplay` state, which is after the `chatbot` node in one of the graph invocations. Resuming from this point will call the next scheduled node.
|
||||
|
||||
```typescript
|
||||
console.log(toReplay.next);
|
||||
console.log(toReplay.config);
|
||||
```
|
||||
|
||||
```
|
||||
["tools"]
|
||||
{
|
||||
configurable: {
|
||||
thread_id: "007708b8-ea9b-4ff7-a7ad-3843364dbf75",
|
||||
checkpoint_ns: "",
|
||||
checkpoint_id: "1efd43e3-0c1f-6c4e-8006-891877d65740"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 4. Load a state from a moment-in-time
|
||||
|
||||
:::python
|
||||
|
||||
The checkpoint's `to_replay.config` contains a `checkpoint_id` timestamp. Providing this `checkpoint_id` value tells LangGraph's checkpointer to **load** the state from that moment in time.
|
||||
|
||||
```python
|
||||
|
||||
``` python
|
||||
# The `checkpoint_id` in the `to_replay.config` corresponds to a state we've persisted to our checkpointer.
|
||||
for event in graph.stream(None, to_replay.config, stream_mode="values"):
|
||||
if "messages" in event:
|
||||
@@ -505,16 +254,19 @@ Tool Calls:
|
||||
================================= Tool Message =================================
|
||||
Name: tavily_search_results_json
|
||||
|
||||
[{"url": "https://towardsdatascience.com/building-autonomous-multi-tool-agents-with-gemini-2-0-and-langgraph-ad3d7bd5e79d", "content": "Building Autonomous Multi-Tool Agents with Gemini 2.0 and LangGraph | by Youness Mansar | Jan, 2025 | Towards Data Science Building Autonomous Multi-Tool Agents with Gemini 2.0 and LangGraph A practical tutorial with full code examples for building and running multi-tool agents Towards Data Science LLMs are remarkable — they can memorize vast amounts of information, answer general knowledge questions, write code, generate stories, and even fix your grammar. In this tutorial, we are going to build a simple LLM agent that is equipped with four tools that it can use to answer a user's question. This Agent will have the following specifications: Follow Published in Towards Data Science --------------------------------- Your home for data science and AI. Follow Follow Follow"}, {"url": "https://github.com/anmolaman20/Tools_and_Agents", "content": "GitHub - anmolaman20/Tools_and_Agents: This repository provides resources for building AI agents using Langchain and Langgraph. This repository provides resources for building AI agents using Langchain and Langgraph. This repository provides resources for building AI agents using Langchain and Langgraph. This repository serves as a comprehensive guide for building AI-powered agents using Langchain and Langgraph. It provides hands-on examples, practical tutorials, and resources for developers and AI enthusiasts to master building intelligent systems and workflows. AI Agent Development: Gain insights into creating intelligent systems that think, reason, and adapt in real time. This repository is ideal for AI practitioners, developers exploring language models, or anyone interested in building intelligent systems. This repository provides resources for building AI agents using Langchain and Langgraph."}]
|
||||
[{"url": "https://towardsdatascience.com/building-autonomous-multi-tool-agents-with-gemini-2-0-and-langgraph-ad3d7bd5e79d", "content": "Building Autonomous Multi-Tool Agents with Gemini 2.0 and LangGraph | by Youness Mansar | Jan, 2025 | Towards Data Science Building Autonomous Multi-Tool Agents with Gemini 2.0 and LangGraph A practical tutorial with full code examples for building and running multi-tool agents Towards Data Science LLMs are remarkable — they can memorize vast amounts of information, answer general knowledge questions, write code, generate stories, and even fix your grammar. In this tutorial, we are going to build a simple LLM agent that is equipped with four tools that it can use to answer a user’s question. This Agent will have the following specifications: Follow Published in Towards Data Science --------------------------------- Your home for data science and AI. Follow Follow Follow"}, {"url": "https://github.com/anmolaman20/Tools_and_Agents", "content": "GitHub - anmolaman20/Tools_and_Agents: This repository provides resources for building AI agents using Langchain and Langgraph. This repository provides resources for building AI agents using Langchain and Langgraph. This repository provides resources for building AI agents using Langchain and Langgraph. This repository serves as a comprehensive guide for building AI-powered agents using Langchain and Langgraph. It provides hands-on examples, practical tutorials, and resources for developers and AI enthusiasts to master building intelligent systems and workflows. AI Agent Development: Gain insights into creating intelligent systems that think, reason, and adapt in real time. This repository is ideal for AI practitioners, developers exploring language models, or anyone interested in building intelligent systems. This repository provides resources for building AI agents using Langchain and Langgraph."}]
|
||||
================================== Ai Message ==================================
|
||||
|
||||
Great idea! Building an autonomous agent with LangGraph is definitely an exciting project. Based on the latest information I've found, here are some insights and tips for building autonomous agents with LangGraph:
|
||||
Great idea! Building an autonomous agent with LangGraph is indeed an excellent way to apply and deepen your understanding of the technology. Based on the search results, I can provide you with some insights and resources to help you get started:
|
||||
|
||||
1. Multi-Tool Agents: LangGraph is particularly well-suited for creating autonomous agents that can use multiple tools. This allows your agent to have a diverse set of capabilities and choose the right tool for each task.
|
||||
1. Multi-Tool Agents:
|
||||
LangGraph is well-suited for building autonomous agents that can use multiple tools. This allows your agent to have a variety of capabilities and choose the appropriate tool based on the task at hand.
|
||||
|
||||
2. Integration with Large Language Models (LLMs): You can combine LangGraph with powerful LLMs like Gemini 2.0 to create more intelligent and capable agents. The LLM can serve as the "brain" of your agent, making decisions and generating responses.
|
||||
2. Integration with Large Language Models (LLMs):
|
||||
There's a tutorial that specifically mentions using Gemini 2.0 (Google's LLM) with LangGraph to build autonomous agents. This suggests that LangGraph can be integrated with various LLMs, giving you flexibility in choosing the language model that best fits your needs.
|
||||
|
||||
3. Workflow Management: LangGraph excels at managing complex, multi-step AI workflows. This is crucial for autonomous agents that need to break down tasks into smaller steps and execute them in the right order.
|
||||
3. Practical Tutorials:
|
||||
There are tutorials available that provide full code examples for building and running multi-tool agents. These can be invaluable as you start your project, giving you a concrete starting point and demonstrating best practices.
|
||||
...
|
||||
|
||||
Remember, building an autonomous agent is an iterative process. Start simple and gradually increase complexity as you become more comfortable with LangGraph and its capabilities.
|
||||
@@ -523,83 +275,7 @@ Would you like more information on any specific aspect of building your autonomo
|
||||
Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings...
|
||||
```
|
||||
|
||||
The graph resumed execution from the `tools` node. You can tell this is the case since the first value printed above is the response from our search engine tool.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
The checkpoint's `toReplay.config` contains a `checkpoint_id` timestamp. Providing this `checkpoint_id` value tells LangGraph's checkpointer to **load** the state from that moment in time.
|
||||
|
||||
```typescript
|
||||
// The `checkpoint_id` in the `toReplay.config` corresponds to a state we've persisted to our checkpointer.
|
||||
for await (const event of await graph.stream(null, {
|
||||
...toReplay?.config,
|
||||
streamMode: "values",
|
||||
})) {
|
||||
if ("messages" in event) {
|
||||
const lastMessage = event.messages.at(-1);
|
||||
|
||||
console.log(
|
||||
"=".repeat(32),
|
||||
`${lastMessage?.getType()} Message`,
|
||||
"=".repeat(32)
|
||||
);
|
||||
console.log(lastMessage?.text);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
================================ ai Message ================================
|
||||
Let me search for specific information about building autonomous agents with LangGraph.js.
|
||||
================================ tool Message ================================
|
||||
{
|
||||
"query": "how to build autonomous agents with LangGraph.js examples tutorial",
|
||||
"follow_up_questions": null,
|
||||
"answer": null,
|
||||
"images": [],
|
||||
"results": [
|
||||
{
|
||||
"url": "https://www.mongodb.com/developer/languages/typescript/build-javascript-ai-agent-langgraphjs-mongodb/",
|
||||
"title": "Build a JavaScript AI Agent With LangGraph.js and MongoDB",
|
||||
"content": "(...)",
|
||||
"score": 0.7672197,
|
||||
"raw_content": null
|
||||
},
|
||||
{
|
||||
"url": "https://medium.com/@lorevanoudenhove/how-to-build-ai-agents-with-langgraph-a-step-by-step-guide-5d84d9c7e832",
|
||||
"title": "How to Build AI Agents with LangGraph: A Step-by-Step Guide",
|
||||
"content": "(...)",
|
||||
"score": 0.7407191,
|
||||
"raw_content": null
|
||||
}
|
||||
],
|
||||
"response_time": 0.82
|
||||
}
|
||||
================================ ai Message ================================
|
||||
Based on the search results, I can share some practical information about building autonomous agents with LangGraph.js. Here are some concrete examples and approaches:
|
||||
|
||||
1. Example HR Assistant Agent:
|
||||
- Can handle HR-related queries using employee information
|
||||
- Features include:
|
||||
- Starting and continuing conversations
|
||||
- Looking up information using vector search
|
||||
- Persisting conversation state using checkpoints
|
||||
- Managing threaded conversations
|
||||
|
||||
2. Energy Savings Calculator Agent:
|
||||
- Functions as a lead generation tool for solar panel sales
|
||||
- Capabilities include:
|
||||
- Calculating potential energy savings
|
||||
- Handling multi-step conversations
|
||||
- Processing user inputs for personalized estimates
|
||||
- Managing conversation state
|
||||
|
||||
(...)
|
||||
```
|
||||
|
||||
The graph resumed execution from the `tools` node. You can tell this is the case since the first value printed above is the response from our search engine tool.
|
||||
:::
|
||||
The graph resumed execution from the `action` node. You can tell this is the case since the first value printed above is the response from our search engine tool.
|
||||
|
||||
**Congratulations!** You've now used time-travel checkpoint traversal in LangGraph. Being able to rewind and explore alternative paths opens up a world of possibilities for debugging, experimentation, and interactive applications.
|
||||
|
||||
@@ -609,4 +285,4 @@ Take your LangGraph journey further by exploring deployment and advanced feature
|
||||
|
||||
- **[LangGraph Server quickstart](../../tutorials/langgraph-platform/local-server.md)**: Launch a LangGraph server locally and interact with it using the REST API and LangGraph Studio Web UI.
|
||||
- **[LangGraph Platform quickstart](../../cloud/quick_start.md)**: Deploy your LangGraph app using LangGraph Platform.
|
||||
- **[LangGraph Platform concepts](../../concepts/langgraph_platform.md)**: Understand the foundational concepts of the LangGraph Platform.
|
||||
- **[LangGraph Platform concepts](../../concepts/langgraph_platform.md)**: Understand the foundational concepts of the LangGraph Platform.
|
||||
@@ -1,4 +1,4 @@
|
||||
# LangGraph Platform quickstart
|
||||
# Run a local server
|
||||
|
||||
This guide shows you how to run a LangGraph application locally.
|
||||
|
||||
|
||||