Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b6c021d6a | ||
|
|
ca4ad62762 | ||
|
|
d9bfe22f00 | ||
|
|
b8e6483770 | ||
|
|
7e54aba3e7 | ||
|
|
6929afda8a | ||
|
|
c326ad019d | ||
|
|
2bf4a72d9d | ||
|
|
b066b6ffa1 | ||
|
|
36f5e49693 | ||
|
|
19087ed6d8 | ||
|
|
0a2a64a830 | ||
|
|
bccea30c2a | ||
|
|
5fa2172066 | ||
|
|
00259d6217 | ||
|
|
cd670d9d59 | ||
|
|
294426de62 | ||
|
|
8932a1e59e | ||
|
|
2c8d4b4e62 | ||
|
|
90335613e9 | ||
|
|
c917664f60 | ||
|
|
e0d341e966 | ||
|
|
1f1e681242 | ||
|
|
a9f20c2339 | ||
|
|
0db341e566 | ||
|
|
4768bdb0c8 | ||
|
|
7973df6216 | ||
|
|
655de27e07 | ||
|
|
d5c7d8c5d9 | ||
|
|
545abbe38a | ||
|
|
a783abc684 | ||
|
|
b0ed8fdd3b | ||
|
|
0bccc19a50 | ||
|
|
a6e25ba7c1 | ||
|
|
4de8c5a7ce | ||
|
|
ebad91e2e1 | ||
|
|
1a58af81b3 | ||
|
|
8e611b42aa | ||
|
|
6ae5958164 | ||
|
|
307c0cfe09 | ||
|
|
c6466da098 | ||
|
|
32e8a2dc50 | ||
|
|
8d103667c8 | ||
|
|
2657ebb57c | ||
|
|
33d099989b | ||
|
|
29a040dc21 | ||
|
|
5697f07163 | ||
|
|
fb5f3c972a | ||
|
|
86c556d01e | ||
|
|
b7580abf45 | ||
|
|
72ac58d1c2 | ||
|
|
000a5c5b83 | ||
|
|
6caea79fba | ||
|
|
fae97d9fba | ||
|
|
d16ec4f436 | ||
|
|
66b728e83a | ||
|
|
feca5e1970 | ||
|
|
c217e4a58d | ||
|
|
fcdf7a8ced | ||
|
|
720ea986d3 | ||
|
|
ab54ae2c23 | ||
|
|
a5650e1d88 | ||
|
|
e49f3f5434 | ||
|
|
f9c720f25a | ||
|
|
5db7a2dc6a | ||
|
|
ac34a4724f | ||
|
|
c13e755c15 | ||
|
|
11bf3bde54 | ||
|
|
7490b2b38b | ||
|
|
97ccdd92b9 | ||
|
|
09b0af0630 | ||
|
|
6a2a81be1c | ||
|
|
4fb297471b | ||
|
|
317664be63 | ||
|
|
265f5012c1 | ||
|
|
93faaea7bd | ||
|
|
f867315c2d | ||
|
|
e80ddab3a5 | ||
|
|
5e8d4e584b | ||
|
|
ebc9cfb694 | ||
|
|
b3907b33b2 | ||
|
|
3e7d32cedb | ||
|
|
11cf3b0510 | ||
|
|
66ae1ff9b0 | ||
|
|
393a0e741c | ||
|
|
d9b4d021bd | ||
|
|
20f84f2041 | ||
|
|
070b339b67 | ||
|
|
f39a22098d | ||
|
|
73e39fcba9 | ||
|
|
06fe906719 | ||
|
|
616c343034 | ||
|
|
a5aef5c450 | ||
|
|
53fd0321cb | ||
|
|
a514ad41d4 | ||
|
|
b4d815e32a | ||
|
|
cf2bda49a0 | ||
|
|
99fa57666e | ||
|
|
571f1bac27 | ||
|
|
44510532ee | ||
|
|
6abcbd5a74 | ||
|
|
a29f354d9b | ||
|
|
19dd293c3f | ||
|
|
c99b63b9d8 |
@@ -12,6 +12,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
name: "CLI integration test"
|
||||
defaults:
|
||||
@@ -19,51 +20,41 @@ jobs:
|
||||
working-directory: libs/cli
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: Ana06/get-changed-files@v2.2.0
|
||||
with:
|
||||
filter: "libs/cli/**"
|
||||
- name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }}
|
||||
if: steps.changed-files.outputs.all
|
||||
uses: "./.github/actions/poetry_setup"
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
poetry-version: ${{ env.POETRY_VERSION }}
|
||||
working-directory: libs/cli
|
||||
cache-key: integration-test-cli
|
||||
|
||||
- name: Setup env
|
||||
if: steps.changed-files.outputs.all
|
||||
working-directory: libs/cli/examples
|
||||
run: cat .env.example > .env
|
||||
- name: Install cli globally
|
||||
if: steps.changed-files.outputs.all
|
||||
run: pip install -e .
|
||||
- name: Start service A
|
||||
if: steps.changed-files.outputs.all
|
||||
run: |
|
||||
langgraph up -c examples/langgraph.json --wait --verbose
|
||||
- name: Stop service A
|
||||
run: |
|
||||
langgraph down -c examples/langgraph.json
|
||||
sudo rm -rf .langgraph-data
|
||||
timeout 60 langgraph test -c examples/langgraph.json --verbose || (exit "$(($? == 124 ? 0 : $?))")
|
||||
- name: Start service B
|
||||
if: steps.changed-files.outputs.all
|
||||
working-directory: libs/cli/examples/graphs
|
||||
run: |
|
||||
langgraph up --wait --verbose
|
||||
- name: Stop service B
|
||||
working-directory: libs/cli/examples/graphs
|
||||
run: |
|
||||
langgraph down
|
||||
sudo rm -rf .langgraph-data
|
||||
timeout 60 langgraph test --verbose || (exit "$(($? == 124 ? 0 : $?))")
|
||||
- name: Start service C
|
||||
if: steps.changed-files.outputs.all
|
||||
working-directory: libs/cli/examples/graphs_reqs_a
|
||||
run: |
|
||||
langgraph up --wait -d compose.yml --verbose
|
||||
- name: Stop service C
|
||||
working-directory: libs/cli/examples/graphs_reqs_a
|
||||
run: |
|
||||
langgraph down
|
||||
sudo rm -rf .langgraph-data
|
||||
timeout 60 langgraph test --verbose || (exit "$(($? == 124 ? 0 : $?))")
|
||||
- name: Start service D
|
||||
if: steps.changed-files.outputs.all
|
||||
working-directory: libs/cli/examples/graphs_reqs_b
|
||||
run: |
|
||||
langgraph up --wait -d compose.yml --verbose
|
||||
- name: Stop service D
|
||||
working-directory: libs/cli/examples/graphs_reqs_b
|
||||
run: |
|
||||
langgraph down
|
||||
sudo rm -rf .langgraph-data
|
||||
timeout 60 langgraph test --verbose || (exit "$(($? == 124 ? 0 : $?))")
|
||||
|
||||
@@ -32,8 +32,13 @@ jobs:
|
||||
name: "lint #${{ matrix.python-version }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: Ana06/get-changed-files@v2.2.0
|
||||
with:
|
||||
filter: "${{ inputs.working-directory }}/**"
|
||||
- name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }}
|
||||
if: steps.changed-files.outputs.all
|
||||
uses: "./.github/actions/poetry_setup"
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
@@ -42,16 +47,19 @@ jobs:
|
||||
cache-key: lint-with-extras
|
||||
|
||||
- name: Check Poetry File
|
||||
if: steps.changed-files.outputs.all
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: poetry check
|
||||
|
||||
- name: Check lock file
|
||||
if: steps.changed-files.outputs.all
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: poetry lock --check
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.changed-files.outputs.all
|
||||
# Also installs dev/lint/test/typing dependencies, to ensure we have
|
||||
# type hints for as many of our libraries as possible.
|
||||
# This helps catch errors that require dependencies to be spotted, for example:
|
||||
@@ -64,6 +72,7 @@ jobs:
|
||||
run: poetry install --with dev
|
||||
|
||||
- name: Get .mypy_cache to speed up mypy
|
||||
if: steps.changed-files.outputs.all
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
SEGMENT_DOWNLOAD_TIMEOUT_MIN: "2"
|
||||
@@ -73,6 +82,7 @@ jobs:
|
||||
key: mypy-lint-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-${{ inputs.working-directory }}-${{ hashFiles(format('{0}/poetry.lock', inputs.working-directory)) }}
|
||||
|
||||
- name: Analysing package code with our lint
|
||||
if: steps.changed-files.outputs.all
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: |
|
||||
if make lint_package > /dev/null 2>&1; then
|
||||
@@ -83,6 +93,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Install test dependencies
|
||||
if: steps.changed-files.outputs.all
|
||||
# Also installs dev/lint/test/typing dependencies, to ensure we have
|
||||
# type hints for as many of our libraries as possible.
|
||||
# This helps catch errors that require dependencies to be spotted, for example:
|
||||
@@ -96,6 +107,7 @@ jobs:
|
||||
poetry install --with dev
|
||||
|
||||
- name: Get .mypy_cache_test to speed up mypy
|
||||
if: steps.changed-files.outputs.all
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
SEGMENT_DOWNLOAD_TIMEOUT_MIN: "2"
|
||||
@@ -105,6 +117,7 @@ jobs:
|
||||
key: mypy-test-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-${{ inputs.working-directory }}-${{ hashFiles(format('{0}/poetry.lock', inputs.working-directory)) }}
|
||||
|
||||
- name: Analysing tests with our lint
|
||||
if: steps.changed-files.outputs.all
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: |
|
||||
if make lint_tests > /dev/null 2>&1; then
|
||||
|
||||
@@ -24,8 +24,13 @@ jobs:
|
||||
name: "test #${{ matrix.python-version }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: Ana06/get-changed-files@v2.2.0
|
||||
with:
|
||||
filter: "${{ inputs.working-directory }}/**"
|
||||
- name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }}
|
||||
if: steps.changed-files.outputs.all
|
||||
uses: "./.github/actions/poetry_setup"
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
@@ -34,17 +39,20 @@ jobs:
|
||||
cache-key: core
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.changed-files.outputs.all
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: poetry install --with dev
|
||||
|
||||
- name: Run core tests
|
||||
if: steps.changed-files.outputs.all
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: |
|
||||
make test
|
||||
|
||||
- name: Ensure the tests did not create any additional files
|
||||
if: steps.changed-files.outputs.all
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: |
|
||||
|
||||
@@ -26,10 +26,10 @@ jobs:
|
||||
- name: Check links in Markdown files
|
||||
uses: gaurav-nelson/github-action-markdown-link-check@v1
|
||||
with:
|
||||
folder-path: 'examples/'
|
||||
folder-path: "examples/"
|
||||
check-modified-files-only: ${{ github.event_name != 'schedule' }}
|
||||
file-path: './README.md'
|
||||
config-file: './.markdown-link-check.config.json'
|
||||
file-path: "./README.md"
|
||||
config-file: "./.markdown-link-check.config.json"
|
||||
|
||||
notebook-link-check:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -49,18 +49,23 @@ jobs:
|
||||
poetry install --with docs
|
||||
poetry run pip install -U pytest pytest-check-links langsmith langchain GitPython
|
||||
|
||||
# - name: Check links in notebooks
|
||||
# env:
|
||||
# LANGCHAIN_API_KEY: test
|
||||
# run: |
|
||||
# if [ "${{ github.event_name }}" != "schedule" ]; then
|
||||
# git fetch origin main
|
||||
# CHANGED_FILES=$(git diff --name-only origin/main | grep '\.ipynb$')
|
||||
# if [ -n "$CHANGED_FILES" ]; then
|
||||
# poetry run pytest -o python_files=non_python_only --check-links --check-links-ignore "https://(api|web)\.smith\.langchain\.com/.*" --check-links-ignore "https://x.com/.*" $CHANGED_FILES
|
||||
# else
|
||||
# echo "No notebook files changed."
|
||||
# fi
|
||||
# else
|
||||
# poetry run pytest -o python_files=non_python_only --check-links --ignore="*.py" -k .ipynb --check-links-ignore "https://(api|web)\.smith\.langchain\.com/.*" --check-links-ignore "https://x.com/.*" ./examples
|
||||
# fi
|
||||
- name: Check links in notebooks
|
||||
env:
|
||||
LANGCHAIN_API_KEY: test
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "schedule" ] || [ "${{ github.event_name }}" == "workflow_dispatch" ] || ([ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref }}" == "refs/heads/main" ]); then
|
||||
echo "Running link check on all notebooks in examples directory..."
|
||||
poetry run pytest -v --check-links-ignore "https://(api|web)\.smith\.langchain\.com/.*" --check-links-ignore "https://x.com/.*" --check-links examples
|
||||
else
|
||||
echo "Fetching changes from origin/main..."
|
||||
git fetch origin main
|
||||
echo "Checking for changed notebook files..."
|
||||
CHANGED_FILES=$(git diff --name-only origin/main | grep '\.ipynb$' || true)
|
||||
echo "Changed files: ${CHANGED_FILES}"
|
||||
if [ -n "${CHANGED_FILES}" ]; then
|
||||
echo "Running link check on changed notebook files..."
|
||||
poetry run pytest -v --check-links-ignore "https://(api|web)\.smith\.langchain\.com/.*" --check-links-ignore "https://x.com/.*" --check-links ${CHANGED_FILES} || ([ $? = 5 ] && exit 0 || exit $?)
|
||||
else
|
||||
echo "No notebook files changed."
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -19,6 +19,7 @@ jobs:
|
||||
|
||||
outputs:
|
||||
pkg-name: ${{ steps.check-version.outputs.pkg-name }}
|
||||
short-pkg-name: ${{ steps.check-version.outputs.short-pkg-name }}
|
||||
version: ${{ steps.check-version.outputs.version }}
|
||||
tag: ${{ steps.check-version.outputs.tag }}
|
||||
|
||||
@@ -68,6 +69,7 @@ jobs:
|
||||
TAG="${SHORT_PKG_NAME}==${VERSION}"
|
||||
fi
|
||||
echo pkg-name="$PKG_NAME" >> $GITHUB_OUTPUT
|
||||
echo short-pkg-name="$SHORT_PKG_NAME" >> $GITHUB_OUTPUT
|
||||
echo version="$VERSION" >> $GITHUB_OUTPUT
|
||||
echo tag="$TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
@@ -92,12 +94,18 @@ jobs:
|
||||
working-directory: langgraph/${{ inputs.working-directory }}
|
||||
env:
|
||||
PKG_NAME: ${{ needs.build.outputs.pkg-name }}
|
||||
SHORT_PKG_NAME: ${{ needs.build.outputs.short-pkg-name }}
|
||||
VERSION: ${{ needs.build.outputs.version }}
|
||||
TAG: ${{ needs.build.outputs.tag }}
|
||||
run: |
|
||||
REGEX="^$PKG_NAME==\\d+\\.\\d+\\.\\d+\$"
|
||||
if [ -z $SHORT_PKG_NAME ]; then
|
||||
REGEX="^\\d+\\.\\d+\\.\\d+((a|b|rc)\\d+)?\$"
|
||||
else
|
||||
REGEX="^$SHORT_PKG_NAME==\\d+\\.\\d+\\.\\d+((a|b|rc)\\d+)?\$"
|
||||
fi
|
||||
echo $REGEX
|
||||
PREV_TAG=$(git tag --sort=-creatordate | grep -P $REGEX || true | head -1)
|
||||
PREV_TAG=$(git tag --sort=-creatordate | grep -P $REGEX | head -1)
|
||||
echo $PREV_TAG
|
||||
if [ "$TAG" == "$PREV_TAG" ]; then
|
||||
echo "No new version to release"
|
||||
exit 1
|
||||
@@ -114,13 +122,12 @@ jobs:
|
||||
run: |
|
||||
{
|
||||
echo 'release-body<<EOF'
|
||||
echo "# Release $TAG"
|
||||
if [ -z "$PREV_TAG" ]; then
|
||||
echo "Initial release"
|
||||
else
|
||||
echo "Changes since $PREV_TAG"
|
||||
echo
|
||||
git log --format="%s" "$PREV_TAG"..HEAD -- $WORKING_DIR
|
||||
git log --format="%s" "$PREV_TAG"..HEAD -- $WORKING_DIR | awk '{print "* " $0}'
|
||||
fi
|
||||
echo EOF
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
# Contributing to LangGraph
|
||||
|
||||
Thank you for being interested in contributing to LangGraph!
|
||||
|
||||
## General guidelines
|
||||
|
||||
Here are some things to keep in mind for all types of contributions:
|
||||
|
||||
- Follow the ["fork and pull request"](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project) workflow.
|
||||
- Fill out the checked-in pull request template when opening pull requests. Note related issues and tag relevant maintainers.
|
||||
- Ensure your PR passes formatting, linting, and testing checks before requesting a review.
|
||||
- If you would like comments or feedback, please open an issue or discussion and tag a maintainer.
|
||||
- Backwards compatibility is key. Your changes must not be breaking, except in case of critical bug and security fixes.
|
||||
- Look for duplicate PRs or issues that have already been opened before opening a new one.
|
||||
- Keep scope as isolated as possible. As a general rule, your changes should not affect more than one package at a time.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
For bug fixes, please open up an issue before proposing a fix to ensure the proposal properly addresses the underlying problem. In general, bug fixes should all have an accompanying unit test that fails before the fix.
|
||||
|
||||
### New features
|
||||
|
||||
For new features, please start a new [discussion](https://github.com/langchain-ai/langgraph/discussions), where the maintainers will help with scoping out the necessary changes.
|
||||
|
||||
## Contribute Documentation
|
||||
|
||||
Documentation is a vital part of LangGraph. We welcome both new documentation for new features and
|
||||
community improvements to our current documentation. Please read the resources below before getting started:
|
||||
|
||||
- [Documentation style guide](#documentation-style-guide)
|
||||
- [Documentation setup](#setup)
|
||||
|
||||
## Documentation Style Guide
|
||||
|
||||
As LangGraph continues to grow, the surface area of documentation required to cover it continues to grow too.
|
||||
This page provides guidelines for anyone writing documentation for LangGraph, as well as some of our philosophies around organization and structure.
|
||||
|
||||
## Philosophy
|
||||
|
||||
LangGraph's documentation follows the [Diataxis framework](https://diataxis.fr).
|
||||
Under this framework, all documentation falls under one of four categories: [Tutorials](#tutorials),
|
||||
[How-to guides](#how-to-guides),
|
||||
[References](#references), and [Explanations (aka conceptual guides)](#conceptual-guide).
|
||||
|
||||
### Tutorials
|
||||
|
||||
Tutorials are lessons that take the reader through a practical activity. Their purpose is to help the user
|
||||
gain understanding of concepts and how they interact by showing one way to achieve some goal in a hands-on way.
|
||||
|
||||
They should **avoid** giving
|
||||
multiple permutations of ways to achieve that goal in-depth. Choice is burdensome. Instead, they should guide a new user through a recommended path to accomplishing a concrete goal. While the end result of a tutorial does not necessarily need to
|
||||
be completely production-ready, it should be useful and practically satisfy the the goal that you clearly stated in the tutorial's introduction.
|
||||
|
||||
To quote the Diataxis website:
|
||||
|
||||
> A tutorial serves the user’s *acquisition* of skills and knowledge - their study. Its purpose is not to help the user get something done, but to help them learn.
|
||||
|
||||
In LangGraph, these are often higher level guides that show off end-to-end use cases.
|
||||
|
||||
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/)
|
||||
|
||||
Here are some high-level tips on writing a good tutorial:
|
||||
|
||||
- Focus on guiding the user to get something done, but keep in mind the end-goal is more to impart principles than to create a perfect production system.
|
||||
- Be specific, not abstract and follow one path.
|
||||
- No need to go deeply into alternative approaches, but it’s ok to reference them, ideally with a link to an appropriate how-to guide.
|
||||
- Get "a point on the board" as soon as possible - something the user can run that outputs something.
|
||||
- You can iterate and expand afterwards.
|
||||
- Try to frequently checkpoint at given steps where the user can run code and see progress.
|
||||
- Focus on results, not technical explanation.
|
||||
- Crosslink heavily to appropriate conceptual/reference pages
|
||||
- The first time you mention a LangGraph concept, use its full name (e.g. "human-in-the-loop"), and link to its conceptual/other documentation page.
|
||||
- It's also helpful to add a prerequisite callout that links to any pages with necessary background information.
|
||||
- End with a recap/next steps section summarizing what the tutorial covered and future reading, such as related how-to guides.
|
||||
- Use phrases like "Next we can run X & Y. We will expect Z.". Then afterwards, use language like "Notice Z" that recalls our expectations and directs the reader's attention to the topic we are trying to teach.
|
||||
- Do not shy away from repetition.
|
||||
|
||||
### How-to guides
|
||||
|
||||
A how-to guide, as the name implies, demonstrates how to do something discrete and specific.
|
||||
It should assume that the user is already familiar with underlying concepts, and is trying to solve an immediate problem, but
|
||||
should still give some background or list the scenarios where the information contained within can be relevant.
|
||||
They can and should discuss alternatives if one approach may be better than another in certain cases.
|
||||
|
||||
To quote the Diataxis website:
|
||||
|
||||
> A how-to guide serves the work of the already-competent user, whom you can assume to know what they want to do, and to be able to follow your instructions correctly.
|
||||
|
||||
Some examples include:
|
||||
|
||||
- [How to add persistence to your graph](https://langchain-ai.github.io/langgraph/how-tos/persistence/)
|
||||
- [How to view and update past graph state](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/time-travel/)
|
||||
|
||||
Here are some high-level tips on writing a good how-to guide:
|
||||
|
||||
- Clearly explain what you are guiding the user through at the start
|
||||
- Assume higher intent than a tutorial and show what the user needs to do to get that task done
|
||||
- Assume familiarity of concepts, but explain why suggested actions are helpful
|
||||
- Crosslink heavily to conceptual/reference pages
|
||||
- Discuss alternatives and responses to real-world tradeoffs that may arise when solving a problem
|
||||
- Use lots of example code, ideally within complete code blocks that the reader can copy and run.
|
||||
- End with a recap/next steps section summarizing what the tutorial covered and future reading, such as other related how-to guides
|
||||
|
||||
### Conceptual guides
|
||||
|
||||
LangGraph's conceptual guides fall under the **Explanation** quadrant of Diataxis. They should cover LangChain terms and concepts
|
||||
in a more abstract way than how-to guides or tutorials, and should be geared towards curious users interested in
|
||||
gaining a deeper understanding of the framework. Try to avoid excessively large code examples. The goal here is to
|
||||
impart perspective to the user rather than to finish a practical project. These guides should cover **why** things work they way they do.
|
||||
|
||||
|
||||
To quote the Diataxis website:
|
||||
|
||||
> The perspective of explanation is higher and wider than that of the other types. It does not take the user’s eye-level view, as in a how-to guide, or a close-up view of the machinery, like reference material. Its scope in each case is a topic - “an area of knowledge”, that somehow has to be bounded in a reasonable, meaningful way.
|
||||
|
||||
Some examples include:
|
||||
|
||||
- [What does it mean to be agentic?](https://langchain-ai.github.io/langgraph/concepts/high_level/)
|
||||
- [Tool calling](https://langchain-ai.github.io/langgraph/concepts/agentic_concepts/#tool-calling)
|
||||
|
||||
Here are some high-level tips on writing a good conceptual guide:
|
||||
|
||||
- Explain design decisions. Why does concept X exist and why was it designed this way?
|
||||
- Use analogies and reference other concepts and alternatives
|
||||
- Avoid blending in too much reference content
|
||||
- You can and should reference content covered in other guides, but make sure to link to them
|
||||
|
||||
### References
|
||||
|
||||
References contain detailed, low-level information that describes exactly what functionality exists and how to use it.
|
||||
In LangGraph, this is mainly our API reference pages, which are populated from docstrings within code.
|
||||
References pages are generally not read end-to-end, but are consulted as necessary when a user needs to know
|
||||
how to use something specific.
|
||||
|
||||
To quote the Diataxis website:
|
||||
|
||||
> The only purpose of a reference guide is to describe, as succinctly as possible, and in an orderly way. Whereas the content of tutorials and how-to guides are led by needs of the user, reference material is led by the product it describes.
|
||||
|
||||
Many of the reference pages in LangChain are automatically generated from code,
|
||||
but here are some high-level tips on writing a good docstring:
|
||||
|
||||
- Be concise
|
||||
- Discuss special cases and deviations from a user's expectations
|
||||
- Go into detail on required inputs and outputs
|
||||
- Light details on when one might use the feature are fine, but in-depth details belong in other sections.
|
||||
|
||||
Each category serves a distinct purpose and requires a specific approach to writing and structuring the content.
|
||||
|
||||
## General guidelines
|
||||
|
||||
Here are some other guidelines you should think about when writing and organizing documentation.
|
||||
|
||||
We generally do not merge new tutorials from outside contributors without an actue need.
|
||||
We welcome updates as well as new integration docs, how-tos, and references.
|
||||
|
||||
### Avoid duplication
|
||||
|
||||
Multiple pages that cover the same material in depth are difficult to maintain and cause confusion. There should
|
||||
be only one (very rarely two), canonical pages for a given concept or feature. Instead, you should link to other guides.
|
||||
|
||||
### Link to other sections
|
||||
|
||||
Because sections of the docs do not exist in a vacuum, it is important to link to other sections as often as possible
|
||||
to allow a developer to learn more about an unfamiliar topic inline.
|
||||
|
||||
This includes linking to the API references as well as conceptual sections!
|
||||
|
||||
### Be concise
|
||||
|
||||
In general, take a less-is-more approach. If a section with a good explanation of a concept already exists, you should link to it rather than
|
||||
re-explain it, unless the concept you are documenting presents some new wrinkle.
|
||||
|
||||
Be concise, including in code samples.
|
||||
|
||||
### General style
|
||||
|
||||
- Use active voice and present tense whenever possible
|
||||
- Use examples and code snippets to illustrate concepts and usage
|
||||
- Use appropriate header levels (`#`, `##`, `###`, etc.) to organize the content hierarchically
|
||||
- Use fewer cells with more code to make copy/paste easier
|
||||
- Use bullet points and numbered lists to break down information into easily digestible chunks
|
||||
- Use tables (especially for **Reference** sections) and diagrams often to present information visually
|
||||
- Include the table of contents for longer documentation pages to help readers navigate the content, but hide it for shorter pages
|
||||
|
||||
## Setup
|
||||
|
||||
LangChain documentation consists of two components:
|
||||
|
||||
1. Main Documentation: Hosted at [https://langchain-ai.github.io](https://langchain-ai.github.io/langgraph/),
|
||||
this comprehensive resource serves as the primary user-facing documentation.
|
||||
It covers a wide array of topics, including tutorials, use cases, integrations,
|
||||
and more, offering extensive guidance on building with LangGraph.
|
||||
The content for this documentation lives in the `/docs` directory of the monorepo.
|
||||
2. In-code Documentation: This is documentation of the codebase itself, which is also
|
||||
used to generate the externally facing [API Reference](https://langchain-ai.github.io/langgraph/reference/graphs/).
|
||||
The content for the API reference is autogenerated by scanning the docstrings in the codebase. For this reason we ask that developers document their code well.
|
||||
|
||||
We appreciate all contributions to the documentation, whether it be fixing a typo,
|
||||
adding a new tutorial or example and whether it be in the main documentation or the API Reference.
|
||||
|
||||
### 📜 Main Documentation
|
||||
|
||||
The content for the main documentation is located in the `/docs` directory of the monorepo.
|
||||
|
||||
The documentation is written using a combination of ipython notebooks (`.ipynb` files)
|
||||
and markdown (`.md` files). The notebooks are converted to markdown
|
||||
and then built using [MkDocs](https://www.mkdocs.org/).
|
||||
|
||||
Feel free to make contributions to the main documentation! 🥰
|
||||
|
||||
After modifying the documentation:
|
||||
|
||||
1. Run the linting and formatting commands (see below) to ensure that the documentation is well-formatted and free of errors.
|
||||
2. Optionally build the documentation locally to verify that the changes look good.
|
||||
3. Make a pull request with the changes.
|
||||
|
||||
### ⚒️ Linting and Building Documentation Locally
|
||||
|
||||
After writing up the documentation, you may want to lint and build the documentation
|
||||
locally to ensure that it looks good and is free of errors.
|
||||
|
||||
If you're unable to build it locally that's okay as well, as you will be able to
|
||||
see a preview of the documentation on the pull request page.
|
||||
|
||||
From the **monorepo root**, run the following command to install the dependencies:
|
||||
|
||||
```bash
|
||||
poetry install --with docs --no-root
|
||||
```
|
||||
|
||||
#### Building
|
||||
|
||||
The code that builds the documentation is located in the `/docs` directory of the monorepo.
|
||||
|
||||
Before building the documentation, it is always a good idea to clean the build directory:
|
||||
|
||||
```bash
|
||||
make clean-docs
|
||||
```
|
||||
|
||||
You can build and preview the documentation as outlined below:
|
||||
|
||||
```bash
|
||||
make serve-docs
|
||||
```
|
||||
|
||||
#### Linting
|
||||
|
||||
The documentation is linted from the **monorepo root**. To lint it, run the following from there:
|
||||
|
||||
```bash
|
||||
make spellcheck
|
||||
```
|
||||
|
||||
### ️In-code Documentation
|
||||
|
||||
The in-code documentation is autogenerated from docstrings.
|
||||
|
||||
For the API reference to be useful, the codebase must be well-documented. This means that all functions, classes, and methods should have a docstring that explains what they do, what the arguments are, and what the return value is. This is a good practice in general, but it is especially important for LangChain because the API reference is the primary resource for developers to understand how to use the codebase.
|
||||
|
||||
We generally follow the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) for docstrings.
|
||||
|
||||
Here is an example of a well-documented function:
|
||||
|
||||
```python
|
||||
|
||||
def my_function(arg1: int, arg2: str) -> float:
|
||||
"""This is a short description of the function. (It should be a single sentence.)
|
||||
|
||||
This is a longer description of the function. It should explain what
|
||||
the function does, what the arguments are, and what the return value is.
|
||||
It should wrap at 88 characters.
|
||||
|
||||
Examples:
|
||||
This is a section for examples of how to use the function.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
my_function(1, "hello")
|
||||
|
||||
Args:
|
||||
arg1: This is a description of arg1. We do not need to specify the type since
|
||||
it is already specified in the function signature.
|
||||
arg2: This is a description of arg2.
|
||||
|
||||
Returns:
|
||||
This is a description of the return value.
|
||||
"""
|
||||
return 3.14
|
||||
```
|
||||
@@ -8,6 +8,9 @@
|
||||
|
||||
⚡ Building language agents as graphs ⚡
|
||||
|
||||
> [!NOTE]
|
||||
> Looking for the JS version? Click [here](https://github.com/langchain-ai/langgraphjs) ([JS docs](https://langchain-ai.github.io/langgraphjs/)).
|
||||
|
||||
## Overview
|
||||
|
||||
[LangGraph](https://langchain-ai.github.io/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, differentiating it from DAG-based solutions. As a very low-level framework, it provides fine-grained control over both the flow and state of your application, crucial for creating reliable agents. Additionally, LangGraph includes built-in persistence, enabling advanced human-in-the-loop and memory features.
|
||||
@@ -69,7 +72,7 @@ tool_node = ToolNode(tools)
|
||||
model = ChatOpenAI(temperature=0).bind_tools(tools)
|
||||
|
||||
# Define the function that determines whether to continue or not
|
||||
def should_continue(state: AgentState) -> Literal["tools", END]:
|
||||
def should_continue(state: MessagesState) -> Literal["tools", END]:
|
||||
messages = state['messages']
|
||||
last_message = messages[-1]
|
||||
# If the LLM makes a tool call, then we route to the "tools" node
|
||||
@@ -80,7 +83,7 @@ def should_continue(state: AgentState) -> Literal["tools", END]:
|
||||
|
||||
|
||||
# Define the function that calls the model
|
||||
def call_model(state: AgentState):
|
||||
def call_model(state: MessagesState):
|
||||
messages = state['messages']
|
||||
response = model.invoke(messages)
|
||||
# We return a list, because this will get added to the existing list
|
||||
@@ -146,27 +149,31 @@ final_state["messages"][-1].content
|
||||
'The current weather in New York is as follows:\n- Temperature: 20.3°C (68.5°F)\n- Condition: Overcast\n- Wind: 2.2 mph from the north\n- Humidity: 65%\n- Cloud Cover: 100%\n- UV Index: 5.0\n\nFor more details, you can visit [Weather API](https://www.weatherapi.com/).'
|
||||
```
|
||||
|
||||
### Step-by-step Breakdown:
|
||||
### Step-by-step Breakdown
|
||||
|
||||
1. <details>
|
||||
<summary>Initialize the model and tools.</summary>
|
||||
|
||||
- we use `ChatOpenAI` as our LLM. **NOTE:** we need make sure the model knows that it has these tools available to call. We can do this by converting the LangChain tools into the format for OpenAI tool calling using the `.bind_tools()` method.
|
||||
- we define the tools we want to use -- a web search tool in our case. It is really easy to create your own tools - see documentation here on how to do that [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools).
|
||||
- we define the tools we want to use - a web search tool in our case. It is really easy to create your own tools - see documentation here on how to do that [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools).
|
||||
</details>
|
||||
|
||||
2. <details>
|
||||
<summary>Initialize graph with state.</summary>
|
||||
|
||||
- we initialize graph (`StateGraph`) by passing state schema (in our case `MessagesState`)
|
||||
- `MessagesState` is a prebuilt state schema that has one attribute -- a list of LangChain `Message` objects, as well as logic for merging the updates from each node into the state
|
||||
</details>
|
||||
|
||||
3. <details>
|
||||
<summary>Define graph nodes.</summary>
|
||||
|
||||
There are two main nodes we need:
|
||||
|
||||
- The `agent` node: responsible for deciding what (if any) actions to take.
|
||||
- The `tools` node that invokes tools: if the agent decides to take an action, this node will then execute that action.
|
||||
</details>
|
||||
|
||||
4. <details>
|
||||
<summary>Define entry point and graph edges.</summary>
|
||||
|
||||
@@ -179,12 +186,14 @@ final_state["messages"][-1].content
|
||||
- b. Finish (respond to the user) if the agent did not ask to run tools
|
||||
- Normal edge: after the tools are invoked, the graph should always return to the agent to decide what to do next
|
||||
</details>
|
||||
|
||||
5. <details>
|
||||
<summary>Compile the graph.</summary>
|
||||
|
||||
- When we compile the graph, we turn it into a LangChain [Runnable](https://python.langchain.com/v0.2/docs/concepts/#runnable-interface), which automatically enables calling `.invoke()`, `.stream()` and `.batch()` with your inputs
|
||||
- We can also optionally pass checkpointer object for persisting state between graph runs, and enabling memory, human-in-the-loop workflows, time travel and more. In our case we use `MemorySaver` - a simple in-memory checkpointer
|
||||
</details>
|
||||
|
||||
6. <details>
|
||||
<summary>Execute the graph.</summary>
|
||||
|
||||
@@ -192,8 +201,10 @@ final_state["messages"][-1].content
|
||||
2. The `"agent"` node executes, invoking the chat model.
|
||||
3. The chat model returns an `AIMessage`. LangGraph adds this to the state.
|
||||
4. Graph cycles the following steps until there are no more `tool_calls` on `AIMessage`:
|
||||
- If `AIMessage` has `tool_calls`, `"tools"` node executes
|
||||
- The `"agent"` node executes again and returns `AIMessage`
|
||||
|
||||
- If `AIMessage` has `tool_calls`, `"tools"` node executes
|
||||
- The `"agent"` node executes again and returns `AIMessage`
|
||||
|
||||
5. Execution progresses to the special `END` value and outputs the final state.
|
||||
And as a result, we get a list of all our chat messages as output.
|
||||
</details>
|
||||
@@ -204,4 +215,9 @@ final_state["messages"][-1].content
|
||||
* [Tutorials](https://langchain-ai.github.io/langgraph/tutorials/): Learn to build with LangGraph through guided examples.
|
||||
* [How-to Guides](https://langchain-ai.github.io/langgraph/how-tos/): Accomplish specific things within LangGraph, from streaming, to adding memory & persistence, to common design patterns (branching, subgraphs, etc.), these are the place to go if you want to copy and run a specific code snippet.
|
||||
* [Conceptual Guides](https://langchain-ai.github.io/langgraph/concepts/): In-depth explanations of the key concepts and principles behind LangGraph, such as nodes, edges, state and more.
|
||||
* [API Reference](https://langchain-ai.github.io/langgraph/reference/graphs/): Review important classes and methods, simple examples of how to use the graph and checkpointing APIs, higher-level prebuilt components and more.
|
||||
* [API Reference](https://langchain-ai.github.io/langgraph/reference/graphs/): Review important classes and methods, simple examples of how to use the graph and checkpointing APIs, higher-level prebuilt components and more.
|
||||
* [Cloud (beta)](https://langchain-ai.github.io/langgraph/cloud/): With one click, deploy LangGraph applications to LangGraph Cloud.
|
||||
|
||||
## Contributing
|
||||
|
||||
For more information on how to contribute, see [here](https://github.com/langchain-ai/langgraph/blob/main/CONTRIBUTING.md).
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
@@ -14,10 +16,16 @@ cloud_sdk_dir = docs_dir / "cloud"
|
||||
_MANUAL = {
|
||||
"how-tos": [
|
||||
"async.ipynb",
|
||||
"stream-values.ipynb",
|
||||
"stream-updates.ipynb",
|
||||
"stream-multiple.ipynb",
|
||||
"streaming-tokens.ipynb",
|
||||
"human-in-the-loop.ipynb",
|
||||
"streaming-content.ipynb",
|
||||
"streaming-events-from-within-tools.ipynb",
|
||||
"streaming-from-final-node.ipynb",
|
||||
"persistence.ipynb",
|
||||
"time-travel.ipynb",
|
||||
"managing-conversation-history.ipynb",
|
||||
"persistence_postgres.ipynb",
|
||||
"visualization.ipynb",
|
||||
"state-model.ipynb",
|
||||
"subgraph.ipynb",
|
||||
@@ -30,25 +38,27 @@ _MANUAL = {
|
||||
"dynamically-returning-directly.ipynb",
|
||||
"configuration.ipynb",
|
||||
"map-reduce.ipynb",
|
||||
"extraction/retries.ipynb",
|
||||
"create-react-agent.ipynb",
|
||||
"create-react-agent-system-prompt.ipynb",
|
||||
"create-react-agent-memory.ipynb",
|
||||
"create-react-agent-hitl.ipynb",
|
||||
"human_in_the_loop/breakpoints.ipynb",
|
||||
"human_in_the_loop/time-travel.ipynb",
|
||||
"human_in_the_loop/edit-graph-state.ipynb",
|
||||
"human_in_the_loop/wait-user-input.ipynb",
|
||||
],
|
||||
"tutorials": [
|
||||
"introduction.ipynb",
|
||||
"customer-support/customer-support.ipynb",
|
||||
"tutorials/tnt-llm/tnt-llm.ipynb",
|
||||
"tutorials/sql-agent.ipynb"
|
||||
"tutorials/sql-agent.ipynb",
|
||||
],
|
||||
}
|
||||
_MANUAL_INVERSE = {v: docs_dir / k for k, vs in _MANUAL.items() for v in vs}
|
||||
_HOW_TOS = {"agent_executor", "chat_agent_executor_with_function_calling", "docs"}
|
||||
_MAP = {
|
||||
"persistence_postgres.ipynb": "tutorial",
|
||||
}
|
||||
_HIDE = set(
|
||||
str(examples_dir / f)
|
||||
for f in [
|
||||
"persistence_postgres.ipynb",
|
||||
"agent_executor/base.ipynb",
|
||||
"agent_executor/force-calling-a-tool-first.ipynb",
|
||||
"agent_executor/high-level.ipynb",
|
||||
@@ -68,6 +78,19 @@ _HIDE = set(
|
||||
"rag/langgraph_rag_agent_llama3_local.ipynb",
|
||||
"rag/langgraph_self_rag_pinecone_movies.ipynb",
|
||||
"rag/langgraph_adaptive_rag_cohere.ipynb",
|
||||
"dynamically-returning-directly.ipynb",
|
||||
"force-calling-a-tool-first.ipynb",
|
||||
"managing-agent-steps.ipynb",
|
||||
"pass-run-time-values-to-tools.ipynb",
|
||||
"respond-in-format.ipynb",
|
||||
"quickstart.ipynb",
|
||||
"human-in-the-loop.ipynb",
|
||||
"learning.ipynb",
|
||||
"docs/quickstart.ipynb",
|
||||
"tutorials/rag-agent-testing.ipynb",
|
||||
"state-context-key.ipynb",
|
||||
"time-travel.ipynb",
|
||||
"code_assistant/langgraph_code_assistant_mistral.ipynb",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -89,6 +112,44 @@ def clean_notebooks():
|
||||
os.rmdir(root)
|
||||
|
||||
|
||||
def update_notebook_links(notebook_path):
|
||||
with open(notebook_path, "r", encoding="utf-8") as f:
|
||||
notebook = json.load(f)
|
||||
|
||||
for cell in notebook["cells"]:
|
||||
if cell["cell_type"] == "markdown":
|
||||
for i, source in enumerate(cell["source"]):
|
||||
# Update relative notebook links
|
||||
cell["source"][i] = re.sub(
|
||||
r"\[([^\]]+)\]\(([^:)]+\.ipynb)\)",
|
||||
lambda m: transform_link(m.group(1), m.group(2)),
|
||||
source,
|
||||
)
|
||||
|
||||
with open(notebook_path, "w", encoding="utf-8") as f:
|
||||
json.dump(notebook, f, indent=2)
|
||||
|
||||
|
||||
def transform_link(text, link):
|
||||
dir_path, filename = os.path.split(link)
|
||||
|
||||
# Remove the .ipynb extension
|
||||
filename_without_ext = os.path.splitext(filename)[0]
|
||||
|
||||
# If it's a local link (starts with ./)
|
||||
if link.startswith("./"):
|
||||
# Change to parent directory and remove ./ prefix
|
||||
new_link = f"../{filename_without_ext}/"
|
||||
elif dir_path:
|
||||
# If there's a directory path, keep it and add one more level up
|
||||
new_link = f"../{dir_path}/{filename_without_ext}/"
|
||||
else:
|
||||
# If it's just a filename, simply go one level up
|
||||
new_link = f"../{filename_without_ext}/"
|
||||
|
||||
return f"[{text}]({new_link})"
|
||||
|
||||
|
||||
def copy_notebooks():
|
||||
# Nested ones are mostly tutorials rn
|
||||
for root, dirs, files in os.walk(examples_dir):
|
||||
@@ -98,17 +159,15 @@ def copy_notebooks():
|
||||
continue
|
||||
if any(path in _HOW_TOS for path in root.split(os.sep)):
|
||||
dst_dir = how_tos_dir
|
||||
elif 'sdk' in root.split(os.sep):
|
||||
elif "sdk" in root.split(os.sep):
|
||||
dst_dir = cloud_sdk_dir
|
||||
elif 'cloud_examples' in root.split(os.sep):
|
||||
elif "cloud_examples" in root.split(os.sep):
|
||||
dst_dir = cloud_how_tos_dir
|
||||
else:
|
||||
dst_dir = tutorials_dir
|
||||
for file in files:
|
||||
dst_dir_ = dst_dir
|
||||
if file.endswith((".ipynb", ".png")):
|
||||
if file in _MAP:
|
||||
dst_dir = os.path.join(dst_dir, _MAP[file])
|
||||
src_path = os.path.join(root, file)
|
||||
if src_path in _HIDE:
|
||||
print("Hiding:", src_path)
|
||||
@@ -139,16 +198,9 @@ def copy_notebooks():
|
||||
content = content.replace('src=\\"./img/', 'src=\\"../img/')
|
||||
with open(dst_path, "w") as f:
|
||||
f.write(content)
|
||||
update_notebook_links(dst_path)
|
||||
dst_dir = dst_dir_
|
||||
|
||||
# Top level notebooks are "how-to's"
|
||||
# for file in examples_dir.iterdir():
|
||||
# if file.suffix.endswith(".ipynb") and not os.path.isdir(
|
||||
# os.path.join(examples_dir, file)
|
||||
# ):
|
||||
# src_path = os.path.join(examples_dir, file)
|
||||
# dst_path = os.path.join(docs_dir, "how-tos", file.name)
|
||||
# shutil.copy(src_path, dst_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
clean_notebooks()
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
# API Concepts
|
||||
This page discusses high-level concepts of the LangGraph Cloud.
|
||||
|
||||
## Assistant
|
||||
An assistant is a configured instance of a [`CompiledGraph`](../reference/graphs/#compiledgraph). It abstracts the cognitive architecture of the graph and contains instance specific configuration and metadata. Multiple assistants can reference the same graph but can contain different configuration and metadata, which may differentiate the behavior of the assistants.
|
||||
|
||||
An assistant (i.e. the graph) is invoked as part of a [run](#run).
|
||||
|
||||
## Thread
|
||||
A thread contains the accumulated state of a group of [runs](#run). If a run is executed on a thread, then the [state](../concepts/#state-management) of the underlying graph of the [assistant](#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.
|
||||
|
||||
## Run
|
||||
A run is an invocation of an [assistant](#assistant). Each run may have its own input, configuration, and metadata, which may affect execution and output of the underlying graph. A run can optionally be executed on a [thread](#thread).
|
||||
|
||||
## Streaming
|
||||
Streaming is critical in making applications based on LLMs feel responsive to end-users. There are three different ways to stream with graphs: by [values](../how_tos/cloud_examples/stream_values/), by [messages](../how_tos/cloud_examples/stream_messages/), and by [updates](../how_tos/cloud_examples/stream_updates/).
|
||||
|
||||
## Human-in-the-Loop
|
||||
There are many occasions where the graph cannot run completely autonomously. For instance, the user might need to input some additional arguments to a function call, or select the next edge for the graph to continue on. In these instances, we need to insert some human in the loop interaction, which you can learn about in [this how-to](../how_tos/cloud_examples/human-in-the-loop_cloud).
|
||||
|
||||
## Multi-Tasking
|
||||
Many times users might interact with your graph in unintended ways. For instance, a user interacting with a graph that has chat output could send one message and before the graph has finished running send a second message. To solve this issue of "double-texting" (i.e. prompting the graph a second time before the first run has finished), Langgraph has provided four different solutions, all of which are covered in the [Double Texting how-tos](../how_tos/cloud_examples/interrupt_concurrent/).
|
||||
@@ -0,0 +1,103 @@
|
||||
# API Concepts
|
||||
|
||||
This page describes the high-level concepts of the LangGraph Cloud API. The conceptual guide of LangGraph (Python library) is [here](../../concepts/index.md).
|
||||
|
||||
## Data Models
|
||||
|
||||
The LangGraph Cloud API consists of a few core data models: [Assistants](#assistants), [Threads](#threads), [Runs](#runs), and [Cron Jobs](#cron-jobs).
|
||||
|
||||
### Assistants
|
||||
|
||||
An assistant is a configured instance of a [`CompiledGraph`][compiledgraph]. It abstracts the cognitive architecture of the graph and contains instance specific configuration and metadata. Multiple assistants can reference the same graph but can contain different configuration and metadata, which may differentiate the behavior of the assistants. An assistant (i.e. the graph) is invoked as part of a run.
|
||||
|
||||
The LangGraph Cloud API provides several endpoints for creating and managing assistants. See the <a href="../reference/api/api_ref.html#tag/assistantscreate" target="_blank">API reference</a> for more details.
|
||||
|
||||
### Threads
|
||||
|
||||
A thread contains the accumulated state of a group of runs. If a run is executed on a thread, then the [state][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.
|
||||
|
||||
For more on threads and checkpoints, see this section of the [LangGraph conceptual guide](https://langchain-ai.github.io/langgraph/concepts/low_level/#checkpointer).
|
||||
|
||||
The LangGraph Cloud API provides several endpoints for creating and managing threads and thread state. See the <a href="../reference/api/api_ref.html#tag/threadscreate" target="_blank">API reference</a> for more details.
|
||||
|
||||
### Runs
|
||||
|
||||
A run is an invocation of an assistant. Each run may have its own input, configuration, and metadata, which may affect execution and output of the underlying graph. A run can optionally be executed on a thread.
|
||||
|
||||
The LangGraph Cloud API provides several endpoints for creating and managing runs. See the <a href="../reference/api/api_ref.html#tag/runscreate" target="_blank">API reference</a> for more details.
|
||||
|
||||
### Cron Jobs
|
||||
|
||||
It's often useful to run graphs on some schedule. LangGraph Cloud supports cron jobs, which run on a user defined schedule. The user specifies a schedule, an assistant, and some input. After than, on the specified schedule LangGraph cloud will:
|
||||
|
||||
- Create a new thread with the specified assistant
|
||||
- Send the specified input to that thread
|
||||
|
||||
Note that this sends the same input to the thread every time. See the [how-to guide](../how-tos/cloud_examples/cron_jobs.ipynb) for creating cron jobs.
|
||||
|
||||
The LangGraph Cloud API provides several endpoints for creating and managing cron jobs. See the <a href="../reference/api/api_ref.html#tag/runscreate/POST/threads/{thread_id}/runs/crons" target="_blank">API reference</a> for more details.
|
||||
|
||||
## Features
|
||||
|
||||
The LangGraph Cloud API offers several features to support complex agent architectures.
|
||||
|
||||
### Streaming
|
||||
|
||||
Streaming is critical for making LLM applications feel responsive to end users. When creating a streaming run, the streaming mode determines what data is streamed back to the API client. The LangGraph Cloud API supports five streaming modes.
|
||||
|
||||
- `values`: Stream the full state of the graph after each node is executed. See the [how-to guide](../how-tos/cloud_examples/stream_values.ipynb) for streaming values.
|
||||
- `messages`: Stream complete messages (at the end of node execution) as well as tokens for any messages generated inside a node. This mode is primarily meant for powering chat applications. This is only an option if your graph contains a `messages` key. See the [how-to guide](../how-tos/cloud_examples/stream_messages.ipynb) for streaming messages.
|
||||
- `updates`: Streams updates to the state of the graph after each node is executed. See the [how-to guide](../how-tos/cloud_examples/stream_updates.ipynb) for streaming updates.
|
||||
- `events`: Stream all events (including the state of the graph) after each node is executed. See the [how-to guide](../how-tos/cloud_examples/stream_events.ipynb) for streaming events. This can be used to do token-by-token streaming for LLMs.
|
||||
- `debug`: Stream debug events after each node is executed. See the [how-to guide](../how-tos/cloud_examples/stream_debug.ipynb) for streaming debug events.
|
||||
|
||||
You can also specify multiple streaming modes at the same time. See the [how-to guide](../how-tos/cloud_examples/stream_multiple.ipynb) for configuring multiple streaming modes at the same time.
|
||||
|
||||
See the <a href="../reference/api/api_ref.html#tag/runscreate/POST/threads/{thread_id}/runs/stream" target="_blank">API reference</a> for how to create streaming runs.
|
||||
|
||||
### Human-in-the-Loop
|
||||
|
||||
There are many occasions where the graph cannot run completely autonomously. For instance, the user might need to input some additional arguments to a function call, or select the next edge for the graph to continue on. In these instances, we need to insert some human in the loop interaction, which you can learn about in the [human in the loop how-tos](../how-tos/index.md#human-in-the-loop).
|
||||
|
||||
### Double Texting
|
||||
|
||||
Many times users might interact with your graph in unintended ways. For instance, a user may send one message and before the graph has finished running send a second message. To solve this issue of "double-texting" (i.e. prompting the graph a second time before the first run has finished), Langgraph has provided four different solutions, all of which are covered in the [Double Texting how-tos](../how-tos/index.md#double-texting). These options are:
|
||||
|
||||
- `reject`: This is the simplest option, this just rejects any follow up runs and does not allow double texting. See the [how-to guide](../how-tos/cloud_examples/reject_concurrent.ipynb) for configuring the reject double text option.
|
||||
- `enqueue`: This is a relatively simple option which continues the first run until it completes the whole run, then sends the new input as a separate run. See the [how-to guide](../how-tos/cloud_examples/enqueue_concurrent.ipynb) for configuring the enqueue double text option.
|
||||
- `interrupt`: This option interrupts the current execution but saves all the work done up until that point. It then inserts the user input and continues from there. If you enable this option, your graph should be able to handle weird edge cases that may arise. See the [how-to guide](../how-tos/cloud_examples/interrupt_concurrent.ipynb) for configuring the interrupt double text option.
|
||||
- `rollback`: This option rolls back all work done up until that point. It then sends the user input in, basically as if it just followed the original run input. See the [how-to guide](../how-tos/cloud_examples/rollback_concurrent.ipynb) for configuring the rollback double text option.
|
||||
|
||||
### Stateless Runs
|
||||
|
||||
All runs use the built-in checkpointer to store checkpoints for runs. However, it can often be useful to just kick off a run without worrying about explicitly creating a thread and without wanting to keep those checkpointers around. Stateless runs allow you to do this by exposing an endpoint that:
|
||||
|
||||
- Takes in user input
|
||||
- Under the hood, creates a thread
|
||||
- Runs the agent but skips all checkpointing steps
|
||||
- Cleans up the thread afterwards
|
||||
|
||||
Stateless runs are still retried as regular retries are per node, while everything still in memory, so doesn't use checkpoints.
|
||||
|
||||
The only difference is in stateless background runs, if the task worker dies halfway (not because the run itself failed, for some external reason) then the whole run will be retried like any background run, but
|
||||
|
||||
- whereas a stateful background run would retry from the last successful checkpoint
|
||||
- a stateless background run would retry from the beginning
|
||||
|
||||
See the [how-to guide](../how-tos/cloud_examples/stateless_runs.ipynb) for creating stateless runs.
|
||||
|
||||
### Webhooks
|
||||
|
||||
For all types of runs, langgraph cloud supports completion webhooks. When you create the run you can pass a webhook URL to be called when the completes (successfully or not). This is especially useful for background runs and cron jobs, as the webhook can give you an indication the run has completed and you can perform further actions for your appilcation.
|
||||
|
||||
See this [how-to guide](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/webhooks/) to learn about how to use webhooks with LangGraph Cloud.
|
||||
|
||||
## Deployment
|
||||
|
||||
The LangGraph Cloud offers several features to support secure and robost deployments.
|
||||
|
||||
### Authentication
|
||||
|
||||
LangGraph applications deployed to LangGraph Cloud are automatically configured with LangSmith authentication. In order to call the API, a valid <a href="https://docs.smith.langchain.com/how_to_guides/setup/create_account_api_key#api-keys" target="_blank">LangSmith API key</a> is required.
|
||||
@@ -0,0 +1,52 @@
|
||||
# How to Deploy to LangGraph Cloud
|
||||
|
||||
LangGraph Cloud is available within <a href="https://www.langchain.com/langsmith" target="_blank">LangSmith</a>. To deploy a LangGraph Cloud API, navigate to the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>.
|
||||
|
||||
## Setup GitHub Repository
|
||||
|
||||
LangGraph Cloud applications are deployed from GitHub repositories. Configure and upload a LangGraph Cloud application to a GitHub repository in order to deploy it to LangGraph Cloud.
|
||||
|
||||
## Create New Deployment
|
||||
|
||||
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
|
||||
|
||||
1. In the left-hand navigation panel, select `Deployments`. The `Deployments` view contains a list of existing LangGraph Cloud deployments.
|
||||
1. In the top-right corner, select `+ New Deployment` to create a new deployment.
|
||||
1. In the `Create New Deployment` panel, fill out the required fields.
|
||||
1. `Deployment details`
|
||||
1. Select `Import from GitHub` and follow the GitHub OAuth workflow to install and authorize LangChain's `hosted-langserve` GitHub app to access the selected repositories. After installation is complete, return to the `Create New Deployment` panel and select the GitHub repository to deploy from the dropdown menu.
|
||||
1. Specify a name for the deployment.
|
||||
1. Specify the full path to the [LangGraph API config file](../reference/cli.md#configuration-file) including the file name. For example, if the file `langgraph.json` is in the root of the repository, simply specify `langgraph.json`.
|
||||
1. Specify the desired `git` reference (e.g. branch name). For example, different branches of the repository can be deployed.
|
||||
1. Select the desired `Deployment Type`.
|
||||
1. `Development` deployments are meant for non-production use cases and are provisioned with minimal resources.
|
||||
1. `Production` deployments can serve up to 500 requests/second and are provisioned with highly available storage with automatic backups.
|
||||
1. Specify `Environment Variables` and secrets. See the [Environment Variables reference](../reference/env_var.md) to configure additional variables for the deployment.
|
||||
1. Sensitive values such as API keys (e.g. `OPENAI_API_KEY`) should be specified as secrets.
|
||||
1. Additional non-secret environment variables can be specified as well.
|
||||
1. A new LangSmith `Tracing Project` is automatically created with the same name as the deployment.
|
||||
1. In the top-right corner, select `Submit`. After a few seconds, the `Deployment` view appears and the new deployment will be queued for provisioning.
|
||||
|
||||
## Create New Revision
|
||||
|
||||
When [creating a new deployment](#create-new-deployment), a new revision is created by default. Subsequent revisions can be created to deploy new code changes.
|
||||
|
||||
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
|
||||
|
||||
1. In the left-hand navigation panel, select `Deployments`. The `Deployments` view contains a list of existing LangGraph Cloud deployments.
|
||||
1. Select an existing deployment to create a new revision for.
|
||||
1. In the `Deployment` view, in the top-right corner, select `+ New Revision`.
|
||||
1. In the `New Revision` modal, fill out the required fields.
|
||||
1. Specify the full path to the [LangGraph API config file](../reference/cli.md#configuration-file) including the file name. For example, if the file `langgraph.json` is in the root of the repository, simply specify `langgraph.json`.
|
||||
1. Specify the desired `git` reference (e.g. branch name). For example, different branches of the repository can be deployed.
|
||||
1. Specify `Environment Variables` and secrets. Existing secrets and environment variables are prepopulated. See the [Environment Variables reference](../reference/env_var.md) to configure additional variables for the revision.
|
||||
1. Add new secrets or environment variables.
|
||||
1. Remove existing secrets or environment variables.
|
||||
1. Update the value of existing secrets or environment variables.
|
||||
1. Select `Submit`. After a few seconds, the `New Revision` modal will close and the new revision will be queued for deployment.
|
||||
|
||||
## Asynchronous Deployment
|
||||
|
||||
New [deployments](#create-new-deployment) and [revisions](#create-new-revision) are provisioned and deployed asynchronously. They are not deployed immediately after submission. Currently, deployment can take up to several minutes.
|
||||
|
||||
The `Deployment` view continually updates the status of pending revisions.
|
||||
|
After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 66 KiB |
@@ -1,157 +0,0 @@
|
||||
# Deploy custom LangGraph code with LangGraph Cloud (Python)
|
||||
|
||||
## Set up your application code
|
||||
|
||||
### Create a new application
|
||||
|
||||
To create a new app called <my-app> create a directory with the following structure
|
||||
|
||||
```
|
||||
<my-app>/
|
||||
|-- agent.py # code for your LangGraph agent
|
||||
|-- requirements.txt # python packages required for your graph
|
||||
|-- langgraph.json # configuration file for langgraph
|
||||
|-- .env # environment files with API keys
|
||||
```
|
||||
|
||||
### Agent File
|
||||
|
||||
In your agent file, you can define as many graphs (agents) as you would like. For our example we are going to create the simplest graph possible: a one node graph. You can read about adding more complexity to your graphs in the [docs](https://langchain-ai.github.io/langgraph/tutorials/introduction/).
|
||||
|
||||
Here is what our `agent.py` file looks like for this example:
|
||||
|
||||
```python
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import END, MessageGraph
|
||||
|
||||
model = ChatOpenAI(temperature=0)
|
||||
|
||||
graph_workflow = MessageGraph()
|
||||
|
||||
graph_workflow.add_node("oracle", model)
|
||||
graph_workflow.add_edge("oracle", END)
|
||||
|
||||
graph_workflow.set_entry_point("oracle")
|
||||
graph = graph_workflow.compile()
|
||||
```
|
||||
|
||||
### Configuration file
|
||||
|
||||
- `langgraph.json` is a configuration file with three parts:
|
||||
- `graphs`
|
||||
- Pass in the graphs you want to host on your deployment, using the graph_id as the key and the path to the agent (a CompiledGraph) as the value. In our example we only use one graph, so the json looks like so:
|
||||
|
||||
```json
|
||||
"graphs": {
|
||||
"agent": "./agent.py:graph"
|
||||
},
|
||||
```
|
||||
|
||||
- `dependencies`
|
||||
- Pass in a list of the dependencies you would like to be installed in order to host your app. In our case, we don’t need any additional dependencies besides our `requirements.txt` file, but if we did we could append them to the dependencies list using the names of the additional packages we want installed.
|
||||
|
||||
```json
|
||||
"dependencies": ["."],
|
||||
```
|
||||
|
||||
- `env`
|
||||
- This is simply a path to our environment file containing all variables/files to load.
|
||||
|
||||
```json
|
||||
"env": ".env"
|
||||
```
|
||||
|
||||
|
||||
Putting it all together, our `langgraph.json` file should look like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"agent": "./agent.py:agent"
|
||||
},
|
||||
"env": ".env"
|
||||
}
|
||||
```
|
||||
|
||||
### Environment and Package Requirement files
|
||||
|
||||
- The `.env` contains any environment variables that are needed to run your code. In our example the file looks like follows:
|
||||
|
||||
```python
|
||||
OPENAI_API_KEY= ...
|
||||
LANGCHAIN_API_KEY= ...
|
||||
LANGCHAIN_TRACING_V2=true
|
||||
```
|
||||
|
||||
- The `requirements.txt` file lists python package dependencies for your project (along with the associated versions if necessary). In our example the file looks like this:
|
||||
|
||||
```
|
||||
langgraph
|
||||
langchain_openai
|
||||
```
|
||||
|
||||
### Push your code to GitHub
|
||||
|
||||
Create a git repo in the `<my-app>` directory, and verify it’s existence. You can use the GitHub CLI if you like, or just create a repo manually.
|
||||
|
||||
## Host your code on LangGraph Cloud
|
||||
|
||||
### Deploy from GitHub with LangGraph Cloud
|
||||
|
||||
Head to LangSmith and click on the 🚀 icon on the left navbar to create a new deployment. Click the `+ New Deployment` button.
|
||||
|
||||
***If you have not deployed to LangGraph Cloud before:*** there will be a button that shows up saying Import from GitHub. You’ll need to follow that flow to connect LangGraph Cloud to GitHub.
|
||||
|
||||
***Once you have set up your GitHub connection:*** the new deployment page will look as follows
|
||||
|
||||

|
||||
|
||||
To deploy your application, you should do the following:
|
||||
|
||||
1. Select your GitHub username or organization from the selector
|
||||
2. Search for your repo to deploy in the search bar and select it
|
||||
3. Choose any name
|
||||
4. In the `LangGraph API config file` field, enter the path to your `langgraph.json` file (if left blank langsmith will automatically search for it on deployment)
|
||||
5. For Git Reference, you can select either the git branch for the code you want to deploy, or the exact commit SHA.
|
||||
6. If your chain relies on environment variables (for example, an OPENAI_API_KEY), add those in. They will be propagated to the underlying server so your code can access them.
|
||||
|
||||
Putting this all together, you should have something as follows for your deployment details:
|
||||
|
||||

|
||||
|
||||
Hit `Submit` and your application will start deploying!
|
||||
|
||||
## Inspect Traces + Monitor Service
|
||||
|
||||
### Deployments View
|
||||
|
||||
After your deployment is complete, your deployments page should look as follows:
|
||||
|
||||

|
||||
|
||||
You can see that by default, you get access to the `Trace Count` monitoring chart and `Recent Traces` run view. These are powered by LangSmith.
|
||||
|
||||
You can click on `All Charts` to view all monitoring info for your server, or click on `See tracing project` to get more information on an individual trace.
|
||||
|
||||
### Access the Docs
|
||||
|
||||
You can access the docs by clicking on the API DOCS link, which should send you to a page that looks like this:
|
||||
|
||||

|
||||
|
||||
You won’t actually be able to test any of the API endpoints without authorizing first. To do so, click on the Authorize button in the top right corner, input your `LANGCHAIN_API_KEY` in the `API Key` box, and then click `Authorize` to finish the process. You should now be able to select any of the API endpoints, click `Try it out` , enter the parameters you would like to pass, and then click `Execute` to view the results of the API call.
|
||||
|
||||
## Interact with your deployment via LangGraph Studio
|
||||
|
||||
### Access Studio
|
||||
|
||||
If you click on your deployment you should see a blue button in the top right that says `LangGraph Studio`. Clicking on this button will take you to a page that looks like this:
|
||||
|
||||

|
||||
|
||||
On this page you can test out your graph by passing in starting states and clicking `Start Run` (this should behave identically to calling `.invoke`). You will then be able to look into the execution thread for each run and explore the steps your graph is taking to produce its output.
|
||||
|
||||
## Deploy new code
|
||||
|
||||
To deploy new code that you push to GitHub, simply navigate to the deployments page, and hit `+ New Revision`. LangGraph Cloud releases what it calls a new “revision” every time you deploy code. Therefore, your first deployment automatically showed revisions. A Revision always corresponds to a new piece of code being deployed. A modal will pop up to enter new revision info. This can be thought of as a partial update on the last revision, so you do not need to enter any fields that didn’t change (*note: environment variables are not saved between revisions, you must re-enter them for each new revision)*.
|
||||
@@ -1,9 +1,35 @@
|
||||
### Run your server locally
|
||||
# How to Self-Host LangGraph Cloud API
|
||||
|
||||
First, make sure that Docker is up and running. Test that your server works by running:
|
||||
!!! warning "Enterprise License Required"
|
||||
Self-hosting LangGraph Cloud API requires a license key. Please contact sales@langchain.dev for more details.
|
||||
|
||||
```python
|
||||
langgraph up -c langgraph.json
|
||||
```
|
||||
LangGraph Cloud APIs can be self-hosted with a valid LangGraph Cloud license key. Self-hosted deployments are built with Docker and deployed with Helm (on Kubernetes) or with Docker Compose. Ensure that the [Docker CLI](https://docs.docker.com/engine/reference/commandline/cli/) is installed.
|
||||
|
||||
This will bring up a local server with your graph! Access the auto-generated server for your playground to confirm everything works as planned at [http://localhost:8124](http://localhost:8124) .
|
||||
LangGraph Cloud license key should be passed to the service as an environment variable named LANGGRAPH_CLOUD_LICENSE_KEY.
|
||||
|
||||
## Build Docker Image
|
||||
|
||||
1. Follow the [How-to Guide](setup.md) for setting up a LangGraph application for deployment. Your LangGraph application will vary from the example in the How-to Guide. However, ensure that the [LangGraph API configuration file](../reference/cli.md#configuration-file) is created.
|
||||
1. Install the [LangGraph CLI](../reference/cli.md#installation).
|
||||
1. Run the following LangGraph CLI `build` command to build a Docker image. Specify the image tag (`-t`) and other desired [options](../reference/cli.md#build).
|
||||
|
||||
langgraph build -t tag_name
|
||||
|
||||
!!! info "Build Platform"
|
||||
When building the Docker image, ensure that the image is built for the platform of the target Kubernetes cluster: `langgraph build -t tag_name --platform linux/amd64,linux/arm64`
|
||||
|
||||
## Self-Host on Kubernetes
|
||||
|
||||
This section is for self-hosting LangGraph Cloud API on Kubernetes via Helm. A Kubernetes cluster must be provisioned before proceeding with these steps. The public Helm chart for LangGraph Cloud is available [here](https://github.com/langchain-ai/helm/tree/main/charts/langgraph-cloud).
|
||||
|
||||
1. Publish the built Docker image to a repository that can be accessed by the target Kubernetes cluster.
|
||||
1. Ensure that the [Helm client](https://github.com/helm/helm?tab=readme-ov-file#install) is installed.
|
||||
1. Make note of all environment variables that are needed for the application. These values will need to be set in the Helm `values` YAML configuration.
|
||||
1. Follow [these instructions](https://github.com/langchain-ai/helm/tree/main/charts/langgraph-cloud#readme) to configure the Helm chart and deploy to Kubernetes.
|
||||
|
||||
## Self-Host with Docker
|
||||
|
||||
!!! warning "Under Construction"
|
||||
This section of the documentation is in progress.
|
||||
|
||||
Docker Compose can be used to deploy LangGraph Cloud to the compute infrastructure of your choice (e.g. VM).
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
# How to Set Up a LangGraph Application for Deployment
|
||||
|
||||
A LangGraph application must be configured with a [LangGraph API configuration file](../reference/cli.md#configuration-file) in order to be deployed to LangGraph Cloud (or to be self-hosted). This how-to guide discusses the basic steps to setup a LangGraph application for deployment using `requirements.txt` to specify project dependencies. If you prefer using poetry for dependency management, check out [this how-to guide](./setup_pyproject.md) on using `pyproject.toml` for LangGraph Cloud.
|
||||
|
||||
The final repo structure will look something like this:
|
||||
|
||||
```bash
|
||||
my-app/
|
||||
|-- requirements.txt # package dependencies
|
||||
|-- .env # environment variables
|
||||
|-- openai_agent.py # code for an agent
|
||||
|-- anthropic_agent.py # code for another agent
|
||||
|-- langgraph.json # configuration file for LangGraph
|
||||
```
|
||||
|
||||
After each step, an example file directory is provided to demonstrate how code can be organized.
|
||||
|
||||
## Specify Dependencies
|
||||
|
||||
Dependencies can optionally be specified in one of the following files: `pyproject.toml`, `setup.py`, or `requirements.txt`. If none of these files is created, then dependencies can be specified later in the [LangGraph API configuration file](#create-langgraph-api-config).
|
||||
|
||||
Example `requirements.txt` file:
|
||||
```
|
||||
langgraph
|
||||
langchain_openai
|
||||
```
|
||||
|
||||
Example file directory:
|
||||
```
|
||||
my-app/
|
||||
|-- requirements.txt # Python packages required for your graph
|
||||
```
|
||||
|
||||
## Specify Environment Variables
|
||||
|
||||
Environment variables can optionally be specified in a file (e.g. `.env`). See the [Environment Variables reference](../reference/env_var.md) to configure additional variables for a deployment.
|
||||
|
||||
Example `.env` file:
|
||||
```
|
||||
MY_ENV_VAR_1=foo
|
||||
MY_ENV_VAR_2=bar
|
||||
OPENAI_API_KEY=key
|
||||
```
|
||||
|
||||
Example file directory:
|
||||
```
|
||||
my-app/
|
||||
|-- requirements.txt
|
||||
|-- .env # file with environment variables
|
||||
```
|
||||
|
||||
## Define Graphs
|
||||
|
||||
Implement your graphs! Graphs can be defined in a single file or multiple files. Make note of the variable names of each [CompiledGraph][compiledgraph] to be included in the LangGraph application. The variable names will be used later when creating the [LangGraph API configuration file](../reference/cli.md#configuration-file).
|
||||
|
||||
Example `openai_agent.py` file:
|
||||
```python
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import END, MessageGraph
|
||||
|
||||
model = ChatOpenAI(temperature=0)
|
||||
|
||||
graph_workflow = MessageGraph()
|
||||
|
||||
graph_workflow.add_node("agent", model)
|
||||
graph_workflow.add_edge("agent", END)
|
||||
graph_workflow.set_entry_point("agent")
|
||||
|
||||
agent = graph_workflow.compile()
|
||||
```
|
||||
|
||||
!!! warning "Assign `CompiledGraph` to Variable"
|
||||
The build process for LangGraph Cloud requires that the `CompiledGraph` object be assigned to a variable at the top-level of a Python module.
|
||||
|
||||
Example file directory:
|
||||
```
|
||||
my-app/
|
||||
|-- requirements.txt
|
||||
|-- .env
|
||||
|-- openai_agent.py # code for your graph
|
||||
|-- anthropic_agent.py # code for your graph
|
||||
```
|
||||
|
||||
## Create LangGraph API Config
|
||||
|
||||
Create a [LangGraph API configuration file](../reference/cli.md#configuration-file) called `langgraph.json`. See the [LangGraph CLI reference](../reference/cli.md#configuration-file) for detailed explanations of each key in the JSON object of the configuration file.
|
||||
|
||||
Example `langgraph.json` file:
|
||||
```json
|
||||
{
|
||||
"dependencies": [
|
||||
"."
|
||||
],
|
||||
"graphs": {
|
||||
"openai_agent": "./openai_agent.py:agent",
|
||||
"anthropic_agent": "./anthropic_agent.py:agent"
|
||||
},
|
||||
"env": "./.env"
|
||||
}
|
||||
```
|
||||
|
||||
Note that the variable name of the `CompiledGraph` appears at the end of the value of each subkey in the top-level `graphs` key (i.e. `:<variable_name>`).
|
||||
|
||||
Example file directory:
|
||||
|
||||
```bash
|
||||
my-app/
|
||||
|-- requirements.txt
|
||||
|-- .env
|
||||
|-- openai_agent.py
|
||||
|-- anthropic_agent.py
|
||||
|-- langgraph.json # configuration file for LangGraph
|
||||
```
|
||||
|
||||
## Upload to GitHub
|
||||
|
||||
To deploy the LangGraph application to LangGraph Cloud, the code must be uploaded to a GitHub repository.
|
||||
|
||||
## Next
|
||||
|
||||
After you setup your repo, it's time to [deploy your app](./cloud.md).
|
||||
@@ -0,0 +1,154 @@
|
||||
# How to Set Up a LangGraph Application for Deployment
|
||||
|
||||
A LangGraph application must be configured with a [LangGraph API configuration file](../reference/cli.md#configuration-file) in order to be deployed to LangGraph Cloud (or to be self-hosted). This how-to guide discusses the basic steps to setup a LangGraph application for deployment using `pyproject.toml` to define your package's dependencies. If you prefer using `requirements.txt` for dependency management, check out [this how-to guide](./setup.md).
|
||||
|
||||
The final repo structure will look something like this:
|
||||
|
||||
```bash
|
||||
my-app/
|
||||
├── my_agent # all project code lies within here
|
||||
│ ├── __init__.py
|
||||
│ └── agent.py # code for your graph
|
||||
│-- .env # environment variables
|
||||
│-- langgraph.json # configuration file for LangGraph
|
||||
└── pyproject.toml # dependencies for your project
|
||||
```
|
||||
|
||||
After each step, an example file directory is provided to demonstrate how code can be organized.
|
||||
|
||||
## Specify Dependencies
|
||||
|
||||
Dependencies can optionally be specified in one of the following files: `pyproject.toml`, `setup.py`, or `requirements.txt`. If none of these files is created, then dependencies can be specified later in the [LangGraph API configuration file](#create-langgraph-api-config).
|
||||
|
||||
Example `pyproject.toml` file:
|
||||
|
||||
```toml
|
||||
[tool.poetry]
|
||||
name = "my-agent"
|
||||
version = "0.0.1"
|
||||
description = "An excellent agent build for LangGraph cloud."
|
||||
authors = ["Polly the parrot <1223+polly@users.noreply.github.com>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.9.0,<3.13"
|
||||
langgraph = "^0.1.0"
|
||||
langchain-fireworks = "^0.1.3"
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
```
|
||||
|
||||
Example file directory:
|
||||
|
||||
```bash
|
||||
my-app/
|
||||
├── my_agent
|
||||
│ ├── __init__.py
|
||||
│ └── agent.py
|
||||
└── pyproject.toml # Python packages required for your graph
|
||||
```
|
||||
|
||||
## Specify Environment Variables
|
||||
|
||||
Environment variables can optionally be specified in a file (e.g. `.env`). See the [Environment Variables reference](../reference/env_var.md) to configure additional variables for a deployment.
|
||||
|
||||
Example `.env` file:
|
||||
|
||||
```
|
||||
MY_ENV_VAR_1=foo
|
||||
MY_ENV_VAR_2=bar
|
||||
FIREWORKS_API_KEY=key
|
||||
```
|
||||
|
||||
Example file directory:
|
||||
|
||||
```bash
|
||||
my-app/
|
||||
├── my_agent
|
||||
│ ├── __init__.py
|
||||
│ └── agent.py
|
||||
|-- .env # file with environment variables
|
||||
└── pyproject.toml
|
||||
```
|
||||
|
||||
## Define Graphs
|
||||
|
||||
Implement your graphs! Graphs can be defined in a single file or multiple files. Make note of the variable names of each [CompiledGraph][compiledgraph] to be included in the LangGraph application. The variable names will be used later when creating the [LangGraph API configuration file](../reference/cli.md#configuration-file).
|
||||
|
||||
Example `agent.py` file:
|
||||
|
||||
```python
|
||||
# my_agent/agent.py
|
||||
from langchain_fireworks import ChatFireworks
|
||||
from langgraph.graph import END, StateGraph, add_messages
|
||||
from typing_extensions import TypedDict, Annotated
|
||||
|
||||
model = ChatFireworks(model="accounts/fireworks/models/firefunction-v2", temperature=0)
|
||||
|
||||
class State(TypedDict):
|
||||
messages: Annotated[list, add_messages]
|
||||
|
||||
graph_workflow = StateGraph(State)
|
||||
|
||||
graph_workflow.add_node("agent", model)
|
||||
graph_workflow.add_edge("agent", END)
|
||||
graph_workflow.set_entry_point("agent")
|
||||
|
||||
agent = graph_workflow.compile()
|
||||
```
|
||||
|
||||
!!! warning "Assign `CompiledGraph` to Variable"
|
||||
The build process for LangGraph Cloud requires that the `CompiledGraph` object be assigned to a variable at the top-level of a Python module.
|
||||
|
||||
Example file directory:
|
||||
|
||||
```bash
|
||||
my-app/
|
||||
├── my_agent
|
||||
│ ├── __init__.py
|
||||
│ └── agent.py # code for your graph
|
||||
|-- .env
|
||||
└── pyproject.toml
|
||||
```
|
||||
|
||||
## Create LangGraph API Config
|
||||
|
||||
Create a [LangGraph API configuration file](../reference/cli.md#configuration-file) called `langgraph.json`. See the [LangGraph CLI reference](../reference/cli.md#configuration-file) for detailed explanations of each key in the JSON object of the configuration file.
|
||||
|
||||
Example `langgraph.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"my_fantastic_agent": "./my_agent/agent.py:agent"
|
||||
},
|
||||
"env": "./.env"
|
||||
}
|
||||
```
|
||||
|
||||
Note that the variable name of the `CompiledGraph` appears at the end of the value of each subkey in the top-level `graphs` key (i.e. `:<variable_name>`).
|
||||
|
||||
Example file directory:
|
||||
|
||||
```bash
|
||||
my-app/
|
||||
├── my_agent
|
||||
│ ├── __init__.py
|
||||
│ └── agent.py # code for your graph
|
||||
│-- .env
|
||||
│-- langgraph.json # configuration file for LangGraph
|
||||
└── pyproject.toml
|
||||
```
|
||||
|
||||
## Upload to GitHub
|
||||
|
||||
To deploy the LangGraph application to LangGraph Cloud, the code must be uploaded to a GitHub repository.
|
||||
|
||||
## Next
|
||||
|
||||
After you setup your repo, it's time to [deploy your app](./cloud.md).
|
||||
|
After Width: | Height: | Size: 20 MiB |
|
After Width: | Height: | Size: 15 MiB |
|
After Width: | Height: | Size: 26 MiB |
|
After Width: | Height: | Size: 4.9 MiB |
@@ -0,0 +1,70 @@
|
||||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
|
||||
# How-to Guides
|
||||
|
||||
Welcome to the LangGraph Cloud how-to guides! These guides provide practical, step-by-step instructions for accomplishing key tasks in LangGraph Cloud.
|
||||
|
||||
## Deployment
|
||||
|
||||
LangGraph Cloud gives you best in class observability, testing, and hosting services. Read more about them in these how to guides:
|
||||
|
||||
- [How to set up app for deployment](https://langchain-ai.github.io/langgraph/cloud/deployment/setup/)
|
||||
- [How to deploy to LangGraph cloud](https://langchain-ai.github.io/langgraph/cloud/deployment/cloud/)
|
||||
- [How to self-host](https://langchain-ai.github.io/langgraph/cloud/deployment/self_hosted/)
|
||||
|
||||
## Streaming
|
||||
|
||||
Streaming the results of your LLM application is vital for ensuring a good user experience, especially when your graph may call multiple models and take a long time to fully complete a run. Read about how to stream values from your graph in these how to guides:
|
||||
|
||||
- [How to stream values](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/stream_values/)
|
||||
- [How to stream updates](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/stream_updates/)
|
||||
- [How to stream messages](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/stream_messages/)
|
||||
- [How to stream events](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/stream_events/)
|
||||
- [How to stream in debug mode](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/stream_debug/)
|
||||
- [How to stream multiple modes](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/stream_multiple/)
|
||||
|
||||
## Double-texting
|
||||
|
||||
Graph execution can take a while, and sometimes users may change their mind about the input they wanted to send before their original input has finished running. For example, a user might notice a typo in their original request and will edit the prompt and resend it. Deciding what to do in these cases is important for ensuring a smooth user experience and preventing your graphs from behaving in unexpected ways. The following how-to guides provide information on the various options LangGraph Cloud gives you for dealing with double-texting:
|
||||
|
||||
- [How to use the interrupt option](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/interrupt_concurrent/)
|
||||
- [How to use the rollback option](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/rollback_concurrent/)
|
||||
- [How to use the reject option](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/reject_concurrent/)
|
||||
- [How to use the rnqueue option](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/enqueue_concurrent/)
|
||||
|
||||
## Human-in-the-loop
|
||||
|
||||
When creating complex graphs, leaving every decision up to the LLM can be dangerous, especially when the decisions involve invoking certain tools or accessing specific documents. To remedy this, LangGraph allows you to insert human-in-the-loop behavior to ensure your graph does not have undesired outcomes. Read more about the different ways you can add human-in-the-loop capabilities to your LangGraph Cloud projects in these how-to guides:
|
||||
|
||||
- [How to add a breakpoint](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/human_in_the_loop_breakpoint/)
|
||||
- [How to wait for user input](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/human_in_the_loop_user_input/)
|
||||
- [How to edit graph state](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/human_in_the_loop_edit_state/)
|
||||
- [How to replay and branch from prior states](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/human_in_the_loop_time_travel/)
|
||||
|
||||
## LangGraph Studio
|
||||
|
||||
LangGraph Studio is a built-in UI for visualizing, testing, and debugging your agents.
|
||||
|
||||
- [How to enter LangGraph Studio](https://langchain-ai.github.io/langgraph/cloud/how-tos/test_deployment/)
|
||||
- [How to test your graph in LangGraph Studio](https://langchain-ai.github.io/langgraph/cloud/how-tos/invoke_studio/)
|
||||
- [Interact with threads in LangGraph Studio](https://langchain-ai.github.io/langgraph/cloud/how-tos/threads_studio/)
|
||||
|
||||
## Different Types of Runs:
|
||||
|
||||
LangGraph Cloud supports multiple types of runs besides streaming runs.
|
||||
|
||||
- [How to run an agent in the background](cloud_examples/background_run.ipynb)
|
||||
- [How to run multiple agents in the same thread](cloud_examples/same-thread.ipynb)
|
||||
- [How to create cron jobs](cloud_examples/cron_jobs.ipynb)
|
||||
- [How to create stateless runs](cloud_examples/stateless_runs.ipynb)
|
||||
|
||||
## Other
|
||||
|
||||
Other guides that may prove helpful!
|
||||
|
||||
- [How to configure agents](cloud_examples/configuration_cloud.ipynb)
|
||||
- [How to convert LangGraph calls to LangGraph cloud calls](cloud_examples/langgraph_to_langgraph_cloud.ipynb)
|
||||
- [How to integrate webhooks](cloud_examples/webhooks.ipynb)
|
||||
@@ -0,0 +1,13 @@
|
||||
# Invoke Assistant
|
||||
|
||||
The LangGraph Studio lets you test different configurations and inputs to your graph. The UI allows you to see exactly how your
|
||||
|
||||
1. The LangGraph Studio UI displays a visualization of the selected assistant.
|
||||
1. In the top-right dropdown menu of the left-hand pane, select an assistant.
|
||||
1. In the bottom of the left-hand pane, edit the `Input` and `Configure` the assistant.
|
||||
1. Select `Submit` to invoke the selected assistant.
|
||||
1. View output of the invocation in the right-hand pane.
|
||||
|
||||
The following GIF shows these exact steps being carried out:
|
||||
|
||||

|
||||
@@ -0,0 +1,14 @@
|
||||
# Test Cloud Deployment
|
||||
|
||||
The LangGraph Studio UI connects directly to LangGraph Cloud deployments.
|
||||
|
||||
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
|
||||
|
||||
1. In the left-hand navigation panel, select `Deployments`. The `Deployments` view contains a list of existing LangGraph Cloud deployments.
|
||||
1. Select an existing deployment to test with LangGraph Studio.
|
||||
1. In the top-right corner, select `Open LangGraph Studio`.
|
||||
1. [Invoke an assistant](./invoke_studio.md) or [view an existing thread](./threads_studio.md).
|
||||
|
||||
The following GIF shows these exact steps being carried out:
|
||||
|
||||

|
||||
@@ -0,0 +1,19 @@
|
||||
# Interacting with Threads in Studio
|
||||
|
||||
## View Thread
|
||||
|
||||
1. In the top of the right-hand pane, select the `New Thread` dropdown menu to view existing threads.
|
||||
1. View the state of the thread (i.e. the output) in the right-hand pane.
|
||||
1. To create a new thread, select `+ New Thread`.
|
||||
|
||||
The following GIF shows these exact steps being carried out:
|
||||
|
||||

|
||||
|
||||
## Edit Thread State
|
||||
|
||||
The LangGraph Studio UI contains features for editing thread state. Explore these features in the right-hand pane. Select the `Edit` icon, modify the desired state, and then select `Fork` to invoke the assistant with the updated state.
|
||||
|
||||
The following GIF shows how to edit a thread in the studio:
|
||||
|
||||

|
||||
|
After Width: | Height: | Size: 405 KiB |
@@ -1,11 +1,35 @@
|
||||
# LangGraph Cloud (alpha)
|
||||
# LangGraph Cloud (beta)
|
||||
|
||||
!!! danger "Important"
|
||||
LangGraph Cloud is a closed source, paid product in an invite-only stage. We are currently focused on providing high bandwidth support to make our select early customers successful. If you are interested in applying for access, please fill out [this form](https://airtable.com/app5PiMJxXukqPLq3/pagveJsW7XOjDspqw/form).
|
||||
LangGraph Cloud is a closed source, paid product in an invite-only stage. We are currently focused on providing high bandwidth support to make our select early customers successful. If you are interested in applying for access, please fill out [this form](https://www.langchain.com/langgraph-cloud-beta).
|
||||
|
||||
!!! warning "Under Construction"
|
||||
LangGraph Cloud documentation is under construction. Contents may change until general availability.
|
||||
|
||||
LangGraph Cloud is a managed service for deploying and hosting LangGraph applications. Deploying your application with LangGraph Cloud shortens the time-to-market for developers. With one click, start a production-ready HTTP microservice with built-in persistence for your LangGraph application.
|
||||

|
||||
|
||||
## Overview
|
||||
|
||||
LangGraph Cloud is a managed service for deploying and hosting LangGraph applications. Deploying applications with LangGraph Cloud shortens the time-to-market for developers. With one click, deploy a production-ready API with built-in persistence for your LangGraph application. LangGraph Cloud APIs are horizontally scalable and deployed with durable storage.
|
||||
|
||||
The LangGraph Cloud API exposes functionality of your LangGraph application through [Assistants](./concepts/index.md#assistants). An assistant abstracts the cognitive architecture of your graph. Invoke an assistant by calling the pre-built [API endpoints](./reference/api/api_ref.md).
|
||||
|
||||
LangGraph Cloud is seamlessly integrated with [LangSmith](https://www.langchain.com/langsmith) and is accessible from within the LangSmith UI.
|
||||
|
||||
## Key Features
|
||||
|
||||
The LangGraph Cloud API supports key LangGraph features in addition to new functionality for enabling complex, agentic workflows.
|
||||
|
||||
- **Assistants and Threads**: Assistants abstract the cognitive architecture of graphs and threads track the state/history of graphs.
|
||||
- **Streaming**: API support for [LangGraph streaming modes](../concepts/low_level.md#streaming) including setting multiple streaming modes at the same time.
|
||||
- **Human-in-the-Loop**: API support for [LangGraph human-in-the-loop features](../concepts/agentic_concepts.md#human-in-the-loop).
|
||||
- **Double Texting**: Configure how assistants respond when new input is received while processing a previous input. Interrupt, rollback, reject, or enqueue.
|
||||
- **Background Runs/Cron Jobs**: A built-in task queue enables background runs and scheduled cron jobs.
|
||||
- **Stateless Runs**: For simpler use cases, invoke an assistant without needing to create a thread.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Tutorials](./quick_start.md): Learn to build and deploy applications for LangGraph Cloud.
|
||||
- [How-to Guides](./how-tos/index.md): Learn how to set up a LangGraph application for deployment and implement features of the LangGraph Cloud API such as streaming tokens, configuring double texting, and creating cron jobs. Go here if you want to copy and run a specific code snippet.
|
||||
- [Conceptual Guides](./concepts/index.md): In-depth explanations of the core data models (e.g. assistants) and key features (e.g. double texting) of the LangGraph Cloud API.
|
||||
- [Reference](./reference/api/api_ref.md): References for the LangGraph Cloud API, the corresponding Python and JS/TS SDKs, the LangGraph CLI, and deployment environment variables.
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
# Quick Start
|
||||
This quick start guide will cover how to develop an application for LangGraph Cloud, run it locally in Docker, and call the APIs to invoke a graph.
|
||||
This quick start guide will cover how to build a simple agent that can look up things on the internet. We will then deploy it to LangGraph Cloud, use the LangGraph Studio to visualize and test it out, and use the LangGraph SDK to interact with it.
|
||||
|
||||
Alternatively, clone or fork the [`langgraph/example`](https://github.com/langchain-ai/langgraph-example) GitHub repository and follow the instructions in the `README`.
|
||||
## Set up requirements
|
||||
|
||||
This tutorial will use:
|
||||
|
||||
- Anthropic for the LLM - sign up and get an API key [here](https://console.anthropic.com/)
|
||||
- Tavily for the search engine - sign up and get an API key [here](https://app.tavily.com/)
|
||||
- LangSmith for hosting - sign up and get an API key [here](https://smith.langchain.com/)
|
||||
|
||||
|
||||
## Set up local files
|
||||
|
||||
## Develop
|
||||
1. Create a new application with the following directory and files:
|
||||
|
||||
<my-app>/
|
||||
@@ -12,29 +20,28 @@ Alternatively, clone or fork the [`langgraph/example`](https://github.com/langch
|
||||
|-- langgraph.json # configuration file for LangGraph
|
||||
|-- .env # environment files with API keys
|
||||
|
||||
2. The `agent.py` file should contain the following Python code for defining a simple graph:
|
||||
2. The `agent.py` file should contain Python code for defining your graph. The following code is a simple example, the important thing is that at some point in your file you compile your graph and assign the compiled graph to a variable (in this case the `graph` variable). This example code uses `create_react_agent`, a prebuilt agent, read more about it [here](..//concepts/agentic_concepts.md#react-agent).
|
||||
|
||||
```python
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import END, MessageGraph
|
||||
|
||||
model = ChatOpenAI(temperature=0)
|
||||
|
||||
graph_workflow = MessageGraph()
|
||||
|
||||
graph_workflow.add_node("agent", model)
|
||||
graph_workflow.add_edge("agent", END)
|
||||
graph_workflow.set_entry_point("agent")
|
||||
|
||||
graph = graph_workflow.compile()
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
model = ChatAnthropic(model="claude-3-5-sonnet-20240620")
|
||||
|
||||
tools = [TavilySearchResults(max_results=2)]
|
||||
|
||||
graph = create_react_agent(model, tools)
|
||||
```
|
||||
|
||||
3. The `requirements.txt` file should contain the following dependencies:
|
||||
3. The `requirements.txt` file should contain any dependencies for your graph(s). In this case we only require four packages for our graph to run:
|
||||
|
||||
langgraph
|
||||
langchain_openai
|
||||
langchain_anthropic
|
||||
tavily-python
|
||||
langchain_community
|
||||
|
||||
4. The `langgraph.json` file should contain the following JSON object:
|
||||
4. The `langgraph.json` file is a configuration file that describes what graph(s) you are going to host. In this case we only have one graph to host: the compiled `graph` object from `agent.py`.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -47,24 +54,201 @@ Alternatively, clone or fork the [`langgraph/example`](https://github.com/langch
|
||||
```
|
||||
|
||||
Learn more about the LangGraph CLI configuration file [here](./reference/cli.md#configuration-file).
|
||||
5. The `.env` file should have any environment variables needed to run your graph. This will only be used for local testing, so if you are not testing locally you can skip this step. NOTE: if you do add this, you should NOT check this into git. For this graph, we need two environment variables:
|
||||
|
||||
5. The `.env` file should contain the environment variables:
|
||||
```shell
|
||||
ANTHROPIC_API_KEY=...
|
||||
TAVILY_API_KEY=...
|
||||
```
|
||||
|
||||
OPENAI_API_KEY=<add your key here>
|
||||
LANGGRAPH_AUTH_TYPE=noop
|
||||
Now that we have set everything up on our local file system, we are ready to host our graph.
|
||||
|
||||
!!! warning "Disable Authentication"
|
||||
When testing locally, set `LANGGRAPH_AUTH_TYPE` to `noop` to disable authentication.
|
||||
## Test the graph build locally
|
||||
|
||||
## Run Locally
|
||||
1. Install the [LangGraph CLI](./reference/cli.md#installation).
|
||||
Before deploying to the cloud, we probably want to test the building of our graph locally. This is useful to make sure we have configured our CLI configuration file correctly and our graph runs.
|
||||
|
||||
2. Run the following command to start the API server in Docker:
|
||||
In order to do this we can first install the LangGraph CLI
|
||||
|
||||
langgraph up -c langgraph.json
|
||||
```shell
|
||||
pip install langgraph-cli
|
||||
```
|
||||
|
||||
3. The API server is now running at `http://localhost:8123`. Navigate to [`http://localhost:8123/docs`](http://localhost:8123/docs) to view the API docs.
|
||||
We can then stand up a simple test server. The server this stands up is INCREDIBLY simple - it is just a single endpoint and has no persistence. **This should not be used for hosting your application, only for testing the build and basic functionality.**
|
||||
|
||||
```shell
|
||||
langgraph test
|
||||
```
|
||||
|
||||
This will test building of the agent server. If this runs successfully, you should see something like:
|
||||
|
||||
```shell
|
||||
Ready!
|
||||
- API: http://localhost:8123
|
||||
2024-06-26 19:20:41,056:INFO:uvicorn.access 127.0.0.1:44138 - "GET /ok HTTP/1.1" 200
|
||||
```
|
||||
|
||||
You can now test this out! Again, we only expose a single simple endpoint (for streaming stateless runs). This is intended to allow you to test that the agent is properly set up, but should **NOT** but used for production purposes. To test it out, you can go to another terminal window and run:
|
||||
|
||||
```shell
|
||||
curl --request POST \
|
||||
--url http://localhost:8123/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"assistant_id": "agent",
|
||||
"input": {
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "How are you?"
|
||||
}
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"config": {
|
||||
"configurable": {}
|
||||
},
|
||||
"multitask_strategy": "reject",
|
||||
"stream_mode": [
|
||||
"values"
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
If you get back a valid response, then all is functioning properly!
|
||||
|
||||
## Deploy to Cloud
|
||||
|
||||
Follow [these instructions](./deployment/managed.md#deploy-from-github-with-hosted-langgraph) to deploy to LangGraph Cloud.
|
||||
### Push your code to GitHub
|
||||
|
||||
Turn the `<my-app>` directory into a GitHub repo. You can use the GitHub CLI if you like, or just create a repo manually (if unfamiliar, instructions [here](https://docs.github.com/en/migrations/importing-source-code/using-the-command-line-to-import-source-code/adding-locally-hosted-code-to-github)).
|
||||
|
||||
### Deploy from GitHub with LangGraph Cloud
|
||||
|
||||
Once you have created your github repository with a Python file containing your compiled graph as well as a `langgraph.json` file containing the configuration for hosting your graph, you can head over to LangSmith and click on the 🚀 icon on the left navbar to create a new deployment. Then click the `+ New Deployment` button.
|
||||
|
||||

|
||||
|
||||
***If you have not deployed to LangGraph Cloud before:*** there will be a button that shows up saying Import from GitHub. You’ll need to follow that flow to connect LangGraph Cloud to GitHub.
|
||||
|
||||
***Once you have set up your GitHub connection:*** the new deployment page will look as follows:
|
||||
|
||||

|
||||
|
||||
To deploy your application, you should do the following:
|
||||
|
||||
1. Select your GitHub username or organization from the selector
|
||||
2. Search for your repo to deploy in the search bar and select it
|
||||
3. Choose any name
|
||||
4. In the `LangGraph API config file` field, enter the path to your `langgraph.json` file (which in this case is just `langgraph.json`)
|
||||
5. For Git Reference, you can select either the git branch for the code you want to deploy, or the exact commit SHA.
|
||||
6. If your chain relies on environment variables, add those in. They will be propagated to the underlying server so your code can access them. In this case, we need `ANTHROPIC_API_KEY` and `TAVILY_API_KEY`.
|
||||
|
||||
Putting this all together, you should have something as follows for your deployment details:
|
||||
|
||||

|
||||
|
||||
Hit `Submit` and your application will start deploying!
|
||||
|
||||
## Inspect Traces + Monitor Service
|
||||
|
||||
### Deployments View
|
||||
|
||||
After your deployment is complete, your deployments page should look as follows:
|
||||
|
||||

|
||||
|
||||
You can see that by default, you get access to the `Trace Count` monitoring chart and `Recent Traces` run view. These are powered by LangSmith.
|
||||
|
||||
You can click on `All Charts` to view all monitoring info for your server, or click on `See tracing project` to get more information on an individual trace.
|
||||
|
||||
### Access the Docs
|
||||
|
||||
You can access the docs by clicking on the API docs link, which should send you to a page that looks like this:
|
||||
|
||||

|
||||
|
||||
You won’t actually be able to test any of the API endpoints without authorizing first. To do so, grab your Langsmith API key and add it at the top where it says `API KEY (X-API-KEY)`. You should now be able to select any of the API endpoints, click `Test Request`, enter the parameters you would like to pass, and then click `Send` to view the results of the API call.
|
||||
|
||||
## Interact with your deployment via LangGraph Studio
|
||||
|
||||
If you click on your deployment you should see a blue button in the top right that says `LangGraph Studio`. Clicking on this button will take you to a page that looks like this:
|
||||
|
||||

|
||||
|
||||
On this page you can test out your graph by passing in starting states and clicking `Start Run` (this should behave identically to calling `.invoke`). You will then be able to look into the execution thread for each run and explore the steps your graph is taking to produce its output.
|
||||
|
||||

|
||||
|
||||
## Use with the SDK
|
||||
|
||||
Once you have tested that your hosted graph works as expected using LangGraph Studio, you can start using your hosted graph all over your organization by using the LangGraph SDK. Let's see how we can access our hosted graph and execute our run from a python file.
|
||||
|
||||
First, make sure you have the SDK installed by calling `pip install langgraph_sdk`.
|
||||
|
||||
Before using, you need to get the URL of your LangGraph deployment. You can find this on the auto generated documentation page here:
|
||||
|
||||

|
||||
|
||||
You also need to make sure you have set up your API key properly so you can authenticate with LangGraph Cloud.
|
||||
|
||||
```shell
|
||||
export LANGCHAIN_API_KEY=...
|
||||
```
|
||||
|
||||
The first thing to do when using the SDK is to setup our client, access our assistant, and create a thread to execute a run on:
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
# Replace this with the URL of your own deployed graph
|
||||
URL = "https://chatbot-23a570f3210f52a7b167f09f6158e3b3-ffoprvkqsa-uc.a.run.app"
|
||||
client = get_client(url=URL)
|
||||
|
||||
# Search all hosted graphs
|
||||
assistants = await client.assistants.search()
|
||||
# In this example we select the first assistant since we are only hosting a single graph
|
||||
assistant = assistants[0]
|
||||
|
||||
# We create a thread for tracking the state of our run
|
||||
thread = await client.threads.create()
|
||||
```
|
||||
|
||||
We can then execute a run on the thread:
|
||||
|
||||
```python
|
||||
input = {"messages":[{"role": "user", "content": "Hello! My name is Bagatur and I am 26 years old."}]}
|
||||
|
||||
async for chunk in client.runs.stream(
|
||||
thread['thread_id'],
|
||||
assistant["assistant_id"],
|
||||
input=input,
|
||||
stream_mode="updates",
|
||||
):
|
||||
if chunk.data and "run_id" not in chunk.data:
|
||||
print(chunk.data)
|
||||
```
|
||||
|
||||
{'agent': {'messages': [{'content': "Hi Bagatur! It's nice to meet you. How can I assist you today?", 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_9cb5d38cf7'}, 'type': 'ai', 'name': None, 'id': 'run-c89118b7-1b1e-42b9-a85d-c43fe99881cd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
|
||||
|
||||
|
||||
## What's Next
|
||||
|
||||
Congratulations! If you've worked your way through this tutorial you are well on your way to becoming a LangGraph Cloud expert. Here are some other resources to check out to help you out on the path to expertise:
|
||||
|
||||
### LangGraph Cloud How-tos
|
||||
|
||||
If you want to learn more about streaming from hosted graphs, check out the Streaming [how-to guides](https://langchain-ai.github.io/langgraph/cloud/how-tos/#streaming).
|
||||
|
||||
To learn more about double-texting and all the ways you can handle it in your application, read up on these [how-to guides](https://langchain-ai.github.io/langgraph/cloud/how-tos/#double-texting).
|
||||
|
||||
To learn about how to include different human-in-the-loop behavior in your graph, take a look at [these how-tos](https://langchain-ai.github.io/langgraph/cloud/how-tos/#human-in-the-loop).
|
||||
|
||||
### LangGraph Tutorials
|
||||
|
||||
Before hosting, you have to write a graph to host. Here are some tutorials to get you more comfortable with writing LangGraph graphs and give you inspiration for the types of graphs you want to host.
|
||||
|
||||
[This tutorial](https://langchain-ai.github.io/langgraph/tutorials/customer-support/customer-support/) walks you through how to write a customer support bot using LangGraph.
|
||||
|
||||
If you are interested in writing a SQL agent, check out [this tutorial](https://langchain-ai.github.io/langgraph/tutorials/sql-agent/).
|
||||
|
||||
Check out the [LangGraph tutorials](https://langchain-ai.github.io/langgraph/tutorials/) page to read about more exciting use cases.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>LangGraph Cloud API Reference</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1" />
|
||||
</head>
|
||||
<body>
|
||||
<script id="api-reference" data-url="./openapi.json"></script>
|
||||
<script>
|
||||
var configuration = {}
|
||||
document.getElementById('api-reference').dataset.configuration =
|
||||
JSON.stringify(configuration)
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,5 @@
|
||||
# API Reference
|
||||
|
||||
The LangGraph Cloud API reference is available with each deployment at the `/docs` URL path (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.
|
||||
@@ -1,2 +0,0 @@
|
||||
# API Reference
|
||||
Coming soon
|
||||
@@ -1,17 +1,17 @@
|
||||
# LangGraph CLI
|
||||
The LangGraph CLI includes commands to build and run a LangGraph Cloud server locally in [Docker](https://www.docker.com/). For development and testing, use the CLI to deploy a local API server.
|
||||
The LangGraph CLI includes commands to build and run a LangGraph Cloud API server locally in [Docker](https://www.docker.com/). For development and testing, use the CLI to deploy a local API server.
|
||||
|
||||
## Installation
|
||||
1. Ensure that Docker is installed (e.g. `docker --version`).
|
||||
1. Install the `langgraph-cli` Python package (e.g. `pip install langgraph-cli`).
|
||||
1. Run the command `langgraph --help` to confirm that the CLI is installed.
|
||||
2. Install the `langgraph-cli` Python package (e.g. `pip install langgraph-cli`).
|
||||
3. Run the command `langgraph --help` to confirm that the CLI is installed.
|
||||
|
||||
## Configuration File
|
||||
The LangGraph CLI requires a JSON configuration file with the following keys:
|
||||
|
||||
| Key | Description |
|
||||
| --- | ----------- |
|
||||
| `dependencies` | **Required**. Array of dependencies for LangGraph Deploy API server. Dependencies can be one of the following: (1) `"."`, which will look for local Python packages, (2) `pyproject.toml`, `setup.py` or `requirements.txt` in the app directory `"./local_package"`, or (3) a package name. |
|
||||
| `dependencies` | **Required**. Array of dependencies for LangGraph Cloud API server. Dependencies can be one of the following: (1) `"."`, which will look for local Python packages, (2) `pyproject.toml`, `setup.py` or `requirements.txt` in the app directory `"./local_package"`, or (3) a package name. |
|
||||
| `graphs` | **Required**. Mapping from graph ID to path where the compiled graph is defined. Example: `./your_package/your_file.py:variable`, where `variable` is an instance of `langgraph.graph.graph.CompiledGraph`. |
|
||||
| `env` | Path to `.env` file or a mapping from environment variable to its value. |
|
||||
| `python_version` | `3.11` or `3.12`. Defaults to `3.11`. |
|
||||
@@ -65,7 +65,7 @@ langgraph [OPTIONS] COMMAND [ARGS]
|
||||
```
|
||||
|
||||
### `build`
|
||||
Build LangGraph Deploy API server Docker image.
|
||||
Build LangGraph Cloud API server Docker image.
|
||||
|
||||
**Usage**
|
||||
```
|
||||
@@ -78,65 +78,24 @@ langgraph build [OPTIONS]
|
||||
| ------ | ------- | ----------- |
|
||||
| `--platform TEXT` | | Target platform(s) to build the Docker image for. Example: `langgraph build --platform linux/amd64,linux/arm64` |
|
||||
| `-t, --tag TEXT` | | **Required**. Tag for the Docker image. Example: `langgraph build -t my-image` |
|
||||
| `--pull / --no-pull` | `--pull` | Build with latest remote Docker image. Use `--no-pull` for running the LangGraph Deploy API server with locally built images. |
|
||||
| `--pull / --no-pull` | `--pull` | Build with latest remote Docker image. Use `--no-pull` for running the LangGraph Cloud API server with locally built images. |
|
||||
| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. |
|
||||
| `--help` | | Display command documentation. |
|
||||
|
||||
### `down`
|
||||
Stop LangGraph Deploy API server.
|
||||
### `test`
|
||||
Test your LangGraph in the cloud. The only function you can call from the SDK after testing your graph is `client.runs.stream(thread_id=None, ...)`
|
||||
|
||||
**Usage**
|
||||
```
|
||||
langgraph down [OPTIONS]
|
||||
langgraph test [OPTIONS]
|
||||
```
|
||||
|
||||
**Options**
|
||||
|
||||
| Option | Default | Description |
|
||||
| ------ | ------- | ----------- |
|
||||
| `--debugger-port INTEGER` | | Pull the debugger image locally and serve the UI on specified port. |
|
||||
| `--verbose` | | Show more output from the server logs. |
|
||||
| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. |
|
||||
| `-d, --docker-compose FILE` | | Advanced. Path to `docker-compose.yml` file with additional services to launch. |
|
||||
| `-p, --port INTEGER` | `8123` | Port to expose. Example: `langgraph up --port 8000` |
|
||||
| `--help` | | Display command documentation. |
|
||||
|
||||
### `logs`
|
||||
Show LangGraph Deploy API server logs.
|
||||
|
||||
**Usage**
|
||||
```
|
||||
langgraph logs [OPTIONS]
|
||||
```
|
||||
|
||||
**Options**
|
||||
|
||||
| Option | Default | Description |
|
||||
| ------ | ------- | ----------- |
|
||||
| `-f, --follow` | | Follow logs. |
|
||||
| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. |
|
||||
| `-d, --docker-compose FILE` | | Advanced. Path to `docker-compose.yml` file with additional services to launch. |
|
||||
| `--help` | | Display command documentation. |
|
||||
|
||||
### `up`
|
||||
Start LangGraph Deploy API server.
|
||||
|
||||
**Usage**
|
||||
```
|
||||
langgraph up [OPTIONS]
|
||||
```
|
||||
|
||||
**Options**
|
||||
|
||||
| Option | Default | Description |
|
||||
| ------ | ------- | ----------- |
|
||||
| `--wait` | | Wait for services to start before returning. Implies `--detach`. |
|
||||
| `--watch` | | Restart on file changes. |
|
||||
| `--debugger-port INTEGER` | | Pull the debugger image locally and serve the UI on specified port. |
|
||||
| `--verbose` | | Show more output from the server logs. |
|
||||
| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. |
|
||||
| `-d, --docker-compose FILE` | | Advanced. Path to `docker-compose.yml` file with additional services to launch. |
|
||||
| `-p, --port INTEGER` | `8123` | Port to expose. Example: `langgraph up --port 8000` |
|
||||
| `--pull / --no-pull` | `--pull` | Build with latest remote Docker image. Use `--no-pull` for running the LangGraph Deploy API server with locally built images. |
|
||||
| `--recreate / --no-recreate` | `--no-recreate` | Recreate containers even if their configuration and image haven't changed. |
|
||||
| `--help` | | Display command documentation. |
|
||||
| `-p, --port INTEGER` | `8123` | Port to expose. Example: `langgraph test --port 8000` |
|
||||
| `--pull / --no-pull` | `pull` | Pull latest images. Use --no-pull for running the server with locally-built images. Example: `langgraph up --no-pull` |
|
||||
| `--help` | | Display command documentation. |
|
||||
@@ -0,0 +1,13 @@
|
||||
# Environment Variables
|
||||
|
||||
The LangGraph Cloud API supports specific environment variables for configuring a deployment.
|
||||
|
||||
## `LANGGRAPH_AUTH_TYPE`
|
||||
|
||||
Type of authentication for the LangGraph Cloud API deployment. Valid values: `langsmith`, `noop`.
|
||||
|
||||
For deployments to LangGraph Cloud, this environment variable is set automatically. For local development or deployments where authentication is handled externally (e.g. self-hosted), set this environment variable to `noop`.
|
||||
|
||||
## `N_JOBS_PER_WORKER`
|
||||
|
||||
Number of jobs per worker for the LangGraph Cloud task queue. Defaults to `10`.
|
||||
@@ -1,2 +1,79 @@
|
||||
# Python SDK Reference
|
||||
Coming soon
|
||||
|
||||
The Python SDK provides four underlying clients (`AssistantsClient`, `ThreadsClient`, `RunsClient`, `CronClient`) that correspond to each of the core API models and one top-level client (`LangGraphClient`) to access them.
|
||||
|
||||
## get_client()
|
||||
|
||||
The `get_client()` function returns the top-level `LangGraphClient` client.
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
# get top-level LangGraphClient
|
||||
client = get_client(url="http://localhost:8123")
|
||||
|
||||
# example usage: client.<model>.<method_name>()
|
||||
assistants = await client.assistants.get(assistant_id="some_uuid")
|
||||
```
|
||||
|
||||
::: langgraph_sdk.client.get_client
|
||||
handler: python
|
||||
|
||||
## LangGraphClient
|
||||
|
||||
`LangGraphClient` is the top-level client for accessing `AssistantsClient`, `ThreadsClient`, `RunsClient`, and `CronClient`.
|
||||
|
||||
::: langgraph_sdk.client.LangGraphClient
|
||||
handler: python
|
||||
|
||||
## AssistantsClient
|
||||
|
||||
Access the `AssistantsClient` via the `LangGraphClient.assistants` attribute.
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
client = get_client(url="http://localhost:8123")
|
||||
await client.assistants.<method_name>()
|
||||
```
|
||||
|
||||
::: langgraph_sdk.client.AssistantsClient
|
||||
handler: python
|
||||
|
||||
## ThreadsClient
|
||||
|
||||
Access the `ThreadsClient` via the `LangGraphClient.threads` attribute.
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
client = get_client(url="http://localhost:8123")
|
||||
await client.threads.<method_name>()
|
||||
```
|
||||
|
||||
::: langgraph_sdk.client.ThreadsClient
|
||||
handler: python
|
||||
|
||||
## RunsClient
|
||||
|
||||
Access the `RunsClient` via the `LangGraphClient.runs` attribute.
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
client = get_client(url="http://localhost:8123")
|
||||
await client.runs.<method_name>()
|
||||
```
|
||||
|
||||
::: langgraph_sdk.client.RunsClient
|
||||
handler: python
|
||||
|
||||
## CronClient
|
||||
|
||||
Access the `CronClient` via the `LangGraphClient.crons` attribute.
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
client = get_client(url="http://localhost:8123")
|
||||
await client.crons.<method_name>()
|
||||
```
|
||||
|
||||
::: langgraph_sdk.client.CronClient
|
||||
handler: python
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# Common Agentic Patterns
|
||||
|
||||
## Structured Output
|
||||
|
||||
It's pretty common to want LLMs inside nodes to return structured output when building agents. This is because that structured output can often be used to route to the next step (e.g. choose between two different edges) or update specific keys of the state.
|
||||
|
||||
Since LangGraph nodes can be arbitrary Python functions, you can do this however you want. If you want to use LangChain, [this how-to guide](https://python.langchain.com/v0.2/docs/how_to/structured_output/) is a starting point.
|
||||
|
||||
## Tool calling
|
||||
|
||||
It's extremely common to want agents to do tool calling. Tool calling refers to choosing from several available tools, and specifying which ones to call and what the inputs should be. This is extremely common in agents, as you often want to let the LLM decide which tools to call and then call those tools.
|
||||
|
||||
Since LangGraph nodes can be arbitrary Python functions, you can do this however you want. If you want to use LangChain, [this how-to guide](https://python.langchain.com/v0.2/docs/how_to/tool_calling/) is a starting point.
|
||||
|
||||
## Memory
|
||||
|
||||
Memory is a key concept to agentic applications. Memory is important because end users often expect the application they are interacting with remember previous interactions. The most simple example of this is chatbots - they clearly need to remember previous messages in a conversation.
|
||||
|
||||
LangGraph is perfectly suited to give you full control over the memory of your application. With user defined [`State`](./low_level.md#state) you can specify the exact schema of the memory you want to retain. With [checkpointers](./low_level.md#checkpointer) you can store checkpoints of previous interactions and resume from there in follow up interactions.
|
||||
|
||||
See [this guide](../how-tos/persistence.ipynb) for how to add memory to your graph.
|
||||
|
||||
## Human-in-the-loop
|
||||
|
||||
Agentic systems often require some human-in-the-loop (or "on-the-loop") interaction patterns. This is because agentic systems are still not super reliable, so having a human involved is required for any sensitive tasks/actions. These are all easily enabled in LangGraph, largely due to [checkpointers](./low_level.md#checkpointer). The reason a checkpointer is necessary is that a lot of these interaction patterns involve running a graph up until a certain point, waiting for some sort of human feedback, and then continuing. When you want to "continue" you will need to access the state of the graph previous to getting interrupted, and checkpointers are a built in, highly convenient way to do that.
|
||||
|
||||
There are a few common human-in-the-loop interaction patterns we see emerging.
|
||||
|
||||
### Approval
|
||||
|
||||
A basic one is to have the agent wait for approval before executing certain tools. This may be all tools, or just a subset of tools. This is generally recommend for more sensitive actions (like writing to a database). This can easily be done in LangGraph by setting a [breakpoint](./low_level.md#breakpoints) before specific nodes.
|
||||
|
||||
See [this guide](../how-tos/human_in_the_loop/breakpoints.ipynb) for how do this in LangGraph.
|
||||
|
||||
### Wait for input
|
||||
|
||||
A similar one is to have the agent wait for human input. This can be done by:
|
||||
|
||||
1. Create a node specifically for human input
|
||||
2. Add a breakpoint before the node
|
||||
3. Get user input
|
||||
4. Update the state with that user input, acting as that node
|
||||
5. Resume execution
|
||||
|
||||
See [this guide](../how-tos/human_in_the_loop/wait-user-input.ipynb) for how do this in LangGraph.
|
||||
|
||||
### Edit agent actions
|
||||
|
||||
This is a more advanced interaction pattern. In this interaction pattern the human can actually edit some of the agent's previous decisions. This can be done either during the flow (after a [breakpoint](./low_level.md#breakpoints), part of the [approval](#approval) flow) or after the fact (as part of [time-travel](#time-travel))
|
||||
|
||||
See [this guide](../how-tos/human_in_the_loop/edit-graph-state.ipynb) for how do this in LangGraph.
|
||||
|
||||
### Time travel
|
||||
|
||||
This is a pretty advanced interaction pattern. In this interaction pattern, the human can look back at the list of previous checkpoints, find one they like, optionally [edit it](#edit-agent-actions), and then resume execution from there.
|
||||
|
||||
See [this guide](../how-tos/human_in_the_loop/time-travel.ipynb) for how to do this in LangGraph.
|
||||
|
||||
## Map-Reduce
|
||||
|
||||
A common pattern in agents is to generate a list of objects, do some work on each of those objects, and then combine the results. This is very similar to the common [map-reduce](https://en.wikipedia.org/wiki/MapReduce) operation. This can be tricky for a few reasons. First, it can be tough to define a structured graph ahead of time because the length of the list of objects may be unknown. Second, in order to do this map-reduce you need multiple versions of the state to exist... but the graph shares a common shared state, so how can this be?
|
||||
|
||||
LangGraph supports this via the [Send](./low_level.md#send) api. This can be used to allow a conditional edge to Send multiple different states to multiple nodes. The state it sends can be different from the state of the core graph.
|
||||
|
||||
See a how-to guide for this [here](../how-tos/map-reduce.ipynb)
|
||||
|
||||
## Multi-agent
|
||||
|
||||
A term you may have heard is "multi-agent" architectures. What exactly does this mean?
|
||||
|
||||
Given that it is hard to even define an "agent", it's almost impossible to exactly define a "multi-agent" architecture. When most people talk about a multi-agent architecture, they typically mean a system where there are multiple different LLM-based systems. These LLM-based systems can be as simple as a prompt and an LLM call, or as complex as a [ReAct agent](#react-agent).
|
||||
|
||||
The big question in multi-agent systems is how they communicate. This involves both the schema of how they communicate, as well as the sequence in which they communicate. LangGraph is perfect for orchestrating these types of systems. It allows you to define multiple agents (each one is a node) an arbitrary state (to encapsulate the schema of how they communicate) as well as the edges (to control the sequence in which they communicate).
|
||||
|
||||
## Planning
|
||||
|
||||
One of the big things that agentic systems struggle with is long term planning. A common technique to overcome this is to have an explicit planning this. This generally involves calling an LLM to come up with a series of steps to execute. From there, the system then tries to execute the series of tasks (this could use a sub-agent to do so). Optionally, you can revisit the plan after each step and update it if needed.
|
||||
|
||||
## Reflection
|
||||
|
||||
Agents often struggle to produce reliable results. Therefore, it can be helpful to check whether the agent has completed a task correctly or not. If it has - then you can finish. If it hasn't - then you can take the feedback on why it's not correct and pass it back into another iteration of the agent.
|
||||
|
||||
This "reflection" step often uses an LLM, but doesn't have to. A good example of where using an LLM may not be necessary is in coding, when you can try to compile the generated code and use any errors as the feedback.
|
||||
|
||||
## ReAct Agent
|
||||
|
||||
One of the most common agent architectures is what is commonly called the ReAct agent architecture. In this architecture, an LLM is called repeatedly in a while-loop. At each step the agent decides which tools to call, and what the inputs to those tools should be. Those tools are then executed, and the outputs are fed back into the LLM as observations. The while-loop terminates when the agent decides it is not worth calling any more tools.
|
||||
|
||||
One of the few high level, pre-built agents we have in LangGraph - you can use it with [`create_react_agent`](../reference/prebuilt.md#create_react_agent)
|
||||
|
||||
This is named after and based on the [ReAct](https://arxiv.org/abs/2210.03629) paper. However, there are several differences between this paper and our implementation:
|
||||
|
||||
- First, we use [tool-calling](#tool-calling) to have LLMs call tools, whereas the paper used prompting + parsing of raw output. This is because tool calling did not exist when the paper was written, but is generally better and more reliable.
|
||||
- Second, we use messages to prompt the LLM, whereas the paper used string formatting. This is because at the time of writing, LLMs didn't even expose a message-based interface, whereas now that's the only interface they expose.
|
||||
- Third, the paper required all inputs to the tools to be a single string. This was largely due to LLMs not being super capable at the time, and only really being able to generate a single input. Our implementation allows for using tools that require multiple inputs.
|
||||
- Forth, the paper only looks at calling a single tool at the time, largely due to limitations in LLMs performance at the time. Our implementation allows for calling multiple tools at a time.
|
||||
- Finally, the paper asked the LLM to explicitly generate a "Thought" step before deciding which tools to call. This is the "Reasoning" part of "ReAct". Our implementation does not do this by default, largely because LLMs have gotten much better and that is not as necessary. Of course, if you wish to prompt it do so, you certainly can.
|
||||
|
||||
See [this guide](../how-tos/human_in_the_loop/time-travel.ipynb) for a full walkthrough of how to use the prebuilt ReAct agent.
|
||||
@@ -0,0 +1,15 @@
|
||||
# FAQ
|
||||
|
||||
Common questions and their answers!
|
||||
|
||||
## Do I need to use LangChain in order to use LangGraph?
|
||||
|
||||
No! LangGraph is a general-purpose framework - the nodes and edges are nothing more than Python functions. You can use LangChain, raw HTTP requests, or even other frameworks inside these nodes and edges.
|
||||
|
||||
## Does LangGraph work with LLMs that don't support tool calling?
|
||||
|
||||
Yes! You can use LangGraph with any LLMs. The main reason we use LLMs that support tool calling is that this is often the most convenient way to have the LLM make its decision about what to do. If your LLM does not support tool calling, you can still use it - you just need to write a bit of logic to convert the raw LLM string response to a decision about what to do.
|
||||
|
||||
## Does LangGraph work with OSS LLMs?
|
||||
|
||||
Yes! LangGraph is totally ambivalent to what LLMs are used under the hood. The main reason we use closed LLMs in most of the tutorials is that they seamlessly support tool calling, while OSS LLMs often don't. But tool calling is not necessary (see [this section](#does-langgraph-work-with-llms-that-dont-support-tool-calling)) so you can totally use LangGraph with OSS LLMs.
|
||||
@@ -0,0 +1,55 @@
|
||||
# LangGraph for Agentic Applications
|
||||
|
||||
## What does it mean to be agentic?
|
||||
|
||||
Other people may talk about a system being an "agent" - we prefer to talk about systems being "agentic". But what does this actually mean?
|
||||
|
||||
When we talk about systems being "agentic", we are talking about systems that use an LLM to decide the control flow of an application. There are different levels that an LLM can be used to decide the control flow, and this spectrum of "agentic" makes more sense to us than defining an arbitrary cutoff for what is or isn't an agent.
|
||||
|
||||
Examples of using an LLM to decide the control of an application:
|
||||
|
||||
- Using an LLM to route between two potential paths
|
||||
- Using an LLM to decide which of many tools to call
|
||||
- Using an LLM to decide whether the generated answer is sufficient or more work is need
|
||||
|
||||
The more times these types of decisions are made inside an application, the more agentic it is.
|
||||
If these decisions are being made in a loop, then its even more agentic!
|
||||
|
||||
There are other concepts often associated with being agentic, but we would argue these are a by-product of the above definition:
|
||||
|
||||
- [Tool calling](agentic_concepts.md#tool-calling): this is often how LLMs make decisions
|
||||
- Action taking: often times, the LLMs' outputs are used as the input to an action
|
||||
- [Memory](agentic_concepts.md#memory): reliable systems need to have knowledge of things that occurred
|
||||
- [Planning](agentic_concepts.md#planning): planning steps (either explicit or implicit) are useful for ensuring that the LLM, when making decisions, makes them in the highest fidelity way.
|
||||
|
||||
## Why LangGraph?
|
||||
|
||||
LangGraph has several core principles that we believe make it the most suitable framework for building agentic applications:
|
||||
|
||||
- [Controllability](../how-tos/index.md#controllability)
|
||||
- [Human-in-the-Loop](../how-tos/index.md#human-in-the-loop)
|
||||
- [Streaming First](../how-tos/index.md#streaming)
|
||||
|
||||
**Controllability**
|
||||
|
||||
LangGraph is extremely low level. This gives you a high degree of control over what the system you are building actually does. We believe this is important because it is still hard to get agentic systems to work reliably, and we've seen that the more control you exercise over them, the more likely it is that they will "work".
|
||||
|
||||
**Human-in-the-Loop**
|
||||
|
||||
LangGraph comes with a built-in persistence layer as a first-class concept. This enables several different human-in-the-loop interaction patterns. We believe that "Human-Agent Interaction" patterns will be the new "Human-Computer Interaction", and have built LangGraph with built in persistence to enable this.
|
||||
|
||||
**Streaming First**
|
||||
|
||||
LangGraph comes with first class support for streaming. Agentic applications often take a while to run, and so giving the user some idea of what is happening is important, and streaming is a great way to do that. LangGraph supports streaming of both events ([like a tool call being taken](../how-tos/stream-updates.ipynb)) as well as of [tokens that an LLM may emit](../how-tos/streaming-tokens.ipynb).
|
||||
|
||||
## Deployment
|
||||
|
||||
So you've built your LangGraph object - now what?
|
||||
|
||||
Now you need to deploy it.
|
||||
There are many ways to deploy LangGraph objects, and the right solution depends on your needs and use case.
|
||||
We'll highlight two ways here: using [LangGraph Cloud](../cloud/index.md) or rolling your own solution.
|
||||
|
||||
[LangGraph Cloud](../cloud/index.md) is an opinionated way to deploy LangGraph objects from the LangChain team. Please see the [LangGraph Cloud documentation](../cloud/index.md) for all the details about what it involves, to see if it is a good fit for you.
|
||||
|
||||
If it is not a good fit, you may want to roll your own deployment. In this case, we would recommend using [FastAPI](https://fastapi.tiangolo.com/) to stand up a server. You can then call this graph from inside the FastAPI server as you see fit.
|
||||
@@ -1,427 +1,57 @@
|
||||
# Conceptual Guides
|
||||
|
||||
Welcome to LangGraph, a Python library for building complex, scalable AI agents using graph-based state machines. In this guide, we'll explore the core concepts behind LangGraph and why it's uniquely suited for creating reliable, fault-tolerant agent systems. We assume you have already learned the basic covered in the [introduction tutorial](https://langchain-ai.github.io/langgraph/tutorials/introduction/#requirements) and want to deepen your understanding of LangGraph's underlying design and inner workings.
|
||||
|
||||
First off, why graphs?
|
||||
|
||||
## Background: Agents & AI Workflows as Graphs
|
||||
|
||||
While everyone has a slightly different definition of what constitutes an "AI Agent", we will take "agent" to mean any system that tasks a language model with controlling a looping workflow and takes actions. The prototypical LLM agent uses a ~["reasoning and action" (ReAct)](https://arxiv.org/abs/2210.03629)-style design, applying an LLM to power a basic loop with the following steps:
|
||||
|
||||
- reason and plan actions to take
|
||||
- take actions using tools (regular software functions)
|
||||
- observe the effects of the tools and re-plan or react as appropriate
|
||||
|
||||
While LLM agents are surprisingly effective at this, the naive agent loop doesn't deliver the [reliability users expect at scale](https://en.wikipedia.org/wiki/High_availability). They're beautifully stochastic. Well-designed systems take advantage of that randomness and apply it sensibly within a well-designed composite system and make that system **tolerant** to mistakes in the LLM's outputs, because mistakes **will** occur.
|
||||
|
||||
We think agents are exciting and new, but AI design patterns should apply applicable good engineering practices from Software 2.0. Some similarities include:
|
||||
|
||||
- AI applications must balance autonomous operations with user control.
|
||||
- Agent applications resemble distributed systems in their need for error tolerance and correction.
|
||||
- Multi-agent systems resemble multi-player web apps in their need for parallelism + conflict resolution.
|
||||
- Everyone loves an undo button and version control.
|
||||
|
||||
LangGraph's primary [StateGraph](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph) abstraction is designed to support these and other needs, providing an API that is lower level than other agent frameworks such as LangChain's [AgentExecutor](https://python.langchain.com/v0.1/docs/modules/agents/) to give you full control of where and how to apply "AI."
|
||||
|
||||
It extends Google's [Pregel](https://research.google/pubs/pregel-a-system-for-large-scale-graph-processing/) graph processing framework to provide fault tolerance and recovery when running long or error-prone workloads. When developing, you can focus on a local action or task-specific agent, and the system composes these actions to form a more capable and scalable application.
|
||||
|
||||
Its parallelism and `State` reduction functionality let you control what happens if, for example, multiple agents return conflicting information.
|
||||
|
||||
And finally, its persistent, versioned checkpointing system lets you roll back the agent's state, explore other paths, and maintain full control of what is going on.
|
||||
|
||||
The following sections go into greater detail about how and why all of this works.
|
||||
|
||||
## Core Design
|
||||
|
||||
At its core, LangGraph models agent workflows as state machines. You define the behavior of your agents using three key components:
|
||||
|
||||
1. `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`: 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`.
|
||||
|
||||
3. `Edges`: Control flow rules 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`.
|
||||
|
||||
Or in short: _nodes do the work. edges tell what to do next_.
|
||||
|
||||
LangGraph's underlying graph algorithm uses [message passing](https://en.wikipedia.org/wiki/Message_passing) to define a general program. When a `Node` completes, it sends a message along one or more edges to other node(s). These nodes run their functions, pass the resulting messages to the next set of nodes, and on and on it goes. Inspired by [Pregel](https://research.google/pubs/pregel-a-system-for-large-scale-graph-processing/), the program proceeds in discrete "super-steps" that are all executed conceptually in parallel. Whenever the graph is run, all the nodes start in an `inactive` state. Whenever an incoming edge (or "channel") receives a new message (state), the node becomes `active`, runs the function, and responds with updates. At the end of each superstep, each node votes to `halt` by marking itself as `inactive` if it has no more incoming messages. The graph terminates when all nodes are `inactive` and when no messages are in transit.
|
||||
|
||||
We will go through a full execution of a StateGraph later, but first, lets explore these concepts in more detail.
|
||||
|
||||
## Nodes
|
||||
|
||||
In StateGraph, nodes are typically python functions (sync or `async`) where the **first** positional argument is the [state](#state-management), 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](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.MessageGraph) method:
|
||||
|
||||
```python
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.graph import END, START, StateGraph
|
||||
|
||||
builder = StateGraph(dict)
|
||||
|
||||
|
||||
def my_node(state: dict, config: RunnableConfig):
|
||||
print("In node: ", config["configurable"]["user_id"])
|
||||
return {"results": f"Hello, {state['input']}!"}
|
||||
|
||||
|
||||
# The second argument is optional
|
||||
def my_other_node(state: dict):
|
||||
return state
|
||||
|
||||
|
||||
builder.add_node("my_node", my_node)
|
||||
builder.add_node("other_node", my_other_node)
|
||||
builder.add_edge(START, "my_node")
|
||||
builder.add_edge("my_node", "other_node")
|
||||
builder.add_edge("other_node", END)
|
||||
graph = builder.compile()
|
||||
graph.invoke({"input": "Will"}, {"configurable": {"user_id": "abcd-123"}})
|
||||
# In node: abcd-123
|
||||
# {'results': 'Hello, Will!'}
|
||||
```
|
||||
|
||||
Behind the scenes, functions are converted to [RunnableLambda's](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.RunnableLambda.html#langchain_core.runnables.base.RunnableLambda), which add batch and async support to your function, along with native tracing and debugging.
|
||||
|
||||
## Edges
|
||||
|
||||
Edges define how the logic is routed and how the graph decides to stop. Similar to nodes, they accept the current `state` of the graph and return a value.
|
||||
|
||||
By default, the value is the name of the node or nodes to send the state to next. All those nodes will be run in parallel as a part of the next superstep.
|
||||
|
||||
If you want to reuse an edge, you can optionally provide a dictionary that maps the edge's output to the name of the next node.
|
||||
|
||||
If you **always** want to go from node A to node B, you can use the [add_edge](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph.add_edge) method directly.
|
||||
|
||||
If you want to **optionally** route to 1 or more edges (or optionally terminate), you can use the [add_conditional_edges](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph.add_conditional_edges) method.
|
||||
|
||||
If a node has multiple out-going edges, **all** of those destination nodes will be executed in parallel as a part of the next superstep.
|
||||
|
||||
## State Management
|
||||
|
||||
LangGraph introduces two key ideas to state management: state schemas and reducers.
|
||||
|
||||
The state schema defines the type of the object that is given to each of the graph's `Node`.
|
||||
|
||||
Reducers define how to apply `Node` outputs to the current `State`. For example, you might use a reducer to merge a new dialogue response into a conversation history, or average together outputs from multiple agent nodes. By annotating your `State` fields with reducer functions, you can precisely control how data flows through your application.
|
||||
|
||||
We'll illustrate how reducers work with an example. Compare the following two `State`. Can you guess the output in both case?
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.graph import END, START, StateGraph
|
||||
|
||||
|
||||
class StateA(TypedDict):
|
||||
value: int
|
||||
|
||||
|
||||
builder = StateGraph(StateA)
|
||||
builder.add_node("my_node", lambda state: {"value": 1})
|
||||
builder.add_edge(START, "my_node")
|
||||
builder.add_edge("my_node", END)
|
||||
graph = builder.compile()
|
||||
graph.invoke({"value": 5})
|
||||
```
|
||||
|
||||
And `StateB`:
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.graph import END, START, StateGraph
|
||||
|
||||
|
||||
|
||||
def add(existing: int, new: int):
|
||||
return existing + new
|
||||
|
||||
|
||||
class StateB(TypedDict):
|
||||
# highlight-next-line
|
||||
value: Annotated[int, add]
|
||||
|
||||
|
||||
builder = StateGraph(StateB)
|
||||
builder.add_node("my_node", lambda state: {"value": 1})
|
||||
builder.add_edge(START, "my_node")
|
||||
builder.add_edge("my_node", END)
|
||||
graph = builder.compile()
|
||||
graph.invoke({"value": 5})
|
||||
```
|
||||
|
||||
If you guesed "1" and "6", then you're correct!
|
||||
|
||||
In the first case (`StateA`), the result is "1", since the default **reducer** for your state is a direct overwrite.
|
||||
In the second case (`StateB`), the result is "6" since we have have created the `add` function as the **reducer**. This function takes the existing state (for that field) and the state update (if provided) and returns the updated value for that state.
|
||||
|
||||
In general, **reducers** provided as annotations tell the graph **how to process updates for this field**.
|
||||
|
||||
While we typically use `TypedDict` as the graph's `state_schema` (i.e., `State`), it can be almost any [type](https://docs.python.org/3/library/stdtypes.html#type-objects), meaning the following graph is also completely valid:
|
||||
|
||||
```python
|
||||
# Analogous to StateA above
|
||||
builder = StateGraph(int)
|
||||
builder.add_node("my_node", lambda state: 1)
|
||||
builder.add_edge(START, "my_node")
|
||||
builder.add_edge("my_node", END)
|
||||
builder.compile().invoke(5)
|
||||
|
||||
# Analogous to StateB
|
||||
def add(left, right):
|
||||
return left + right
|
||||
|
||||
|
||||
builder = StateGraph(Annotated[int, add])
|
||||
builder.add_node("my_node", lambda state: 1)
|
||||
builder.add_edge(START, "my_node")
|
||||
builder.add_edge("my_node", END)
|
||||
graph = builder.compile()
|
||||
graph.invoke(5)
|
||||
```
|
||||
|
||||
This also means you can [use a Pydantic BaseModel](https://langchain-ai.github.io/langgraph/how-tos/state-model/) as your graph state to add **default values** and additional data validation.
|
||||
|
||||
When building simple chatbots like ChatGPT, the state can be as simple as a list of chat messages. This is the state used by [MessageGraph](https://langchain-ai.github.io/langgraph/reference/graphs/?h=message+graph#langgraph.graph.MessageGraph) (a light wrapper of `StateGraph`), which is only slightly more involved than the following:
|
||||
|
||||
```python
|
||||
builder = StateGraph(Annotated[list, add])
|
||||
```
|
||||
|
||||
Using a shared state within a graph comes with some design tradeoffs. For instance, you may think it feels like using dreaded global variables (though this can be addressed by namespacing arguments). However, sharing a typed state provides a number of benefits relevant to building AI workflows, including:
|
||||
|
||||
1. The data flow is fully inspectable before and after each "superstep".
|
||||
2. The state is mutable, making it easy to let users or other software write to the same state between supersteps to control an agent's direction (using [update_state](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.graph.CompiledGraph.update_state)).
|
||||
3. It is well-defined when checkpointing, making it easy to save and resume or even fully version control the execution of your entire workflows in whatever storage backend you wish.
|
||||
|
||||
We will talk about checkpointing more in the next section.
|
||||
|
||||
## Persistence
|
||||
|
||||
Any "intelligent" system needs memory to function. AI agents are no different, requiring memory across one or more timeframes:
|
||||
|
||||
- they _always_ need to remember the steps already taken **within this task** (to avoid repeating itself when answering a given query).
|
||||
- they _typically_ need to remember the previous turns within a multi-turn conversation with a user (for coreference resolution and additional context).
|
||||
- they _ideally_ need to "remember" context from previous interactions with the user and from actions in a given "environment" (such as an application context) to be more personalized and efficient in its behavior.
|
||||
|
||||
That last form of memory covers a lot (personalization, optimization, continual learning, etc.) and is beyond the scope of this conversation, although it can be easily integrated in any LangGraph workflow, and we are actively exploring the best way to expose this functionality natively.
|
||||
|
||||
The first two forms of memory are natively supported by the [StateGraph](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph) API via [checkpointers](https://langchain-ai.github.io/langgraph/reference/checkpoints/#basecheckpointsaver).
|
||||
|
||||
#### Checkpoints
|
||||
|
||||
A checkpoint represents the state of a `thread` within a (potentially) multi-turn interaction between your application and a user (or users or other systems). Checkpoints that are made _within_ a single run will have a set of `next` nodes that will be executed when starting from this state. Checkpoints that are made at the end of a given run are identical, except there are no `next` nodes to transition to (the graph is awaiting user input).
|
||||
|
||||
Checkpointing supports chat memory and much more, letting you tag and persist every state your system has taken, regardless of whether it is within a single run or across many turns. Let's explore a bit why that is useful.
|
||||
|
||||
#### Single-turn Memory
|
||||
|
||||
**Within** a given run, each step of the agent is checkpointed. This means you could ask your agent to go create world peace. In the likely scenario that it runs into an error as it fails to do so, you can resume its quest at any time by resuming from one of its saved checkpoints.
|
||||
|
||||
This also lets you build **human-in-the-loop** workflows, common in use cases like [customer support bots](https://langchain-ai.github.io/langgraph/tutorials/customer-support/customer-support/), [programming assistants](https://langchain-ai.github.io/langgraph/tutorials/usaco/usaco/), and other applications. Before or after executing a given node, you can `interrupt` the graph's execution and "escalate" control to a user or support person. That person may respond immediately. Or they could respond a month from now. Either way, your workflow can resume at any time as if no time had passed at all.
|
||||
|
||||
#### Multi-turn Memory
|
||||
|
||||
Checkpoints are saved under a "thread_id" to support multi-turn interactions between users and your system. To the developer, there is absolutely no difference in how you configure your graph to add multi-turn memory support, since the checkpointing works the same throughout.
|
||||
|
||||
If you have some portion of state that you want to retain across turns and some state that you want to treat as "ephemeral", you can always clear the relevant state in the graph's final node.
|
||||
|
||||
Using checkpointing is as easy as calling `compile(checkpointer=my_checkpointer)` and then invoking it with a `thread_id` within its `configurable` parameters. You can see more in the following sections!
|
||||
|
||||
## Threads
|
||||
|
||||
Threads in LangGraph represent separate **sessions** of a graph. They organize state checkpoints within discrete sessions to facilitate multi-conversation and multi-user support in an application.
|
||||
|
||||
A typical chat bot application would have multiple threads for each user. Each thread represents a single conversation, with its own persistent chat history and other state. Checkpoints within a thread can be rewound and branched as needed.
|
||||
|
||||
Threads in LangGraph are distinct from [operating system threads](https://docs.python.org/3/library/threading.html), which are units of execution managed by the OS. They are more akin to a [conversational thread](<https://en.wikipedia.org/wiki/Thread_(online_communication)>) in email, twitter, and other messaging apps.
|
||||
|
||||
When a `StateGraph` is compiled with a checkpointer, each invocation of the graph requires a `thread_id` to be provided via [configuration (see below)](#configuration).
|
||||
|
||||
## Configuration
|
||||
|
||||
For any given graph deployment, you'll likely want some amount of configurable values that you can control at runtime. These differ from the graph **inputs** in that they aren't meant to be treated as state variables. They are more akin to "[out-of-band](https://en.wikipedia.org/wiki/Out-of-band)" communication.
|
||||
|
||||
A common example is a conversational `thread_id`, a `user_id`, a choice of which LLM to use, how many documents to return in a retriever, etc. While you **could** pass this within the state, it is nicer to separate out from the regular data flow. Configurable values are also automatically added to LangSmith traces as [metadata](https://docs.smith.langchain.com/concepts/tracing#metadata).
|
||||
|
||||
#### Example
|
||||
|
||||
Let's review another example to see how our multi-turn memory works! Can you guess what `result` and `result2` look like if you run this graph?
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.graph import END, START, StateGraph
|
||||
|
||||
|
||||
def add(left, right):
|
||||
return left + right
|
||||
|
||||
|
||||
class State(TypedDict):
|
||||
total: Annotated[int, add]
|
||||
turn: str
|
||||
|
||||
|
||||
builder = StateGraph(State)
|
||||
builder.add_node("add_one", lambda x: {"total": 1})
|
||||
builder.add_edge(START, "add_one")
|
||||
builder.add_edge("add_one", END)
|
||||
|
||||
memory = MemorySaver()
|
||||
graph = builder.compile(checkpointer=memory)
|
||||
thread_id = "some-thread"
|
||||
config = {"configurable": {"thread_id": thread_id}}
|
||||
result = graph.invoke({"total": 1, "turn": "First Turn"}, config)
|
||||
result2 = graph.invoke({"turn": "Next Turn"}, config)
|
||||
result3 = graph.invoke({"total": 5}, config)
|
||||
result4 = graph.invoke({"total": 5}, {"configurable": {"thread_id": "new-thread-id"}})
|
||||
```
|
||||
|
||||
If you guessed the following, you're correct!
|
||||
|
||||
```python
|
||||
>>> result
|
||||
{'total': 2, 'turn': 'First Turn'}
|
||||
>>> result2
|
||||
{'total': 3, 'turn': 'Next Turn'}
|
||||
>>> result3
|
||||
{'total': 9, 'turn': 'Next Turn'}
|
||||
>>> result4
|
||||
{'total': 6}
|
||||
|
||||
```
|
||||
|
||||
For the first run, no checkpoint existed, so the graph ran on the raw input. The "total" value is incremented from 1 to 2, and the "turn" is set to "First Turn".
|
||||
|
||||
For the second run, the user provides an update to "turn" but no total! Since we are loading from the state, the previous result is incremented by one (in our "add_one" node), and the "turn" is overwritten by the user.
|
||||
|
||||
For the third run, the "turn" remains the same, since it is loaded from the checkpoint but not overwritten by the user. The "total" is incremented by the value provided by the user, since this is **reduced** (i.e., used to update the existing value) by the `add` function.
|
||||
|
||||
For the fourth run, we are using a **new thread id** for which no checkpoint is found, so the result is just the user's provided **total** incremented by one.
|
||||
|
||||
You probably noticed that this user-facing behavior is equivalent to running the following **without a checkpointer**.
|
||||
|
||||
```python
|
||||
graph = builder.compile()
|
||||
result = graph.invoke({"total": 1, "turn": "First Turn"})
|
||||
result2 = graph.invoke({**result, "turn": "Next Turn"})
|
||||
result3 = graph.invoke({**result2, "total": result2["total"] + 5})
|
||||
result4 = graph.invoke({"total": 5})
|
||||
```
|
||||
|
||||
Run this for yourself to confirm equivalence. User inputs and checkpoint loading is treated more or less the same as any other **state update**.
|
||||
|
||||
Now that we've introduced the core concepts behind LangGraph, it may be instructive to walk through an end-to-end example to see how all the pieces fit together.
|
||||
|
||||
## Data flow of a single execution of a StateGraph
|
||||
|
||||
As engineers, we are never really satisfied until we know what's going on "under the hood". In the previous sections, we explained some of the LangGraph's core concepts. Now it's time to really show how they fit together.
|
||||
|
||||
Let's extend our toy example above with a conditional edge and then walk through two consecutive invocations.
|
||||
|
||||
```python
|
||||
from typing import Annotated, Literal
|
||||
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.graph import END, START, StateGraph
|
||||
|
||||
|
||||
|
||||
def add(left, right):
|
||||
return left + right
|
||||
|
||||
|
||||
class State(TypedDict):
|
||||
total: Annotated[int, add]
|
||||
|
||||
|
||||
builder = StateGraph(State)
|
||||
builder.add_node("add_one", lambda x: {"total": 1})
|
||||
builder.add_node("double", lambda x: {"total": x["total"]})
|
||||
builder.add_edge(START, "add_one")
|
||||
|
||||
|
||||
def route(state: State) -> Literal["double", "__end__"]:
|
||||
if state["total"] < 6:
|
||||
return "double"
|
||||
return "__end__" # This is what END is
|
||||
|
||||
|
||||
builder.add_conditional_edges("add_one", route)
|
||||
builder.add_edge("double", "add_one")
|
||||
|
||||
memory = MemorySaver()
|
||||
graph = builder.compile(checkpointer=memory)
|
||||
```
|
||||
|
||||
...
|
||||
|
||||
And then call it for the first time:
|
||||
|
||||
```python
|
||||
thread_id = "some-thread"
|
||||
config = {"configurable": {"thread_id": thread_id}}
|
||||
for step in graph.stream({"total": 1}, config, stream_mode="debug"):
|
||||
print(step["step"], step["type"], step["payload"].get("values"))
|
||||
# 0 checkpoint {'total': 1}
|
||||
# 1 task None
|
||||
# 1 task_result None
|
||||
# 1 checkpoint {'total': 2}
|
||||
# 2 task None
|
||||
# 2 task_result None
|
||||
# 2 checkpoint {'total': 4}
|
||||
# 3 task None
|
||||
# 3 task_result None
|
||||
# 3 checkpoint {'total': 5}
|
||||
# 4 task None
|
||||
# 4 task_result None
|
||||
# 4 checkpoint {'total': 10}
|
||||
# 5 task None
|
||||
# 5 task_result None
|
||||
# 5 checkpoint {'total': 11}
|
||||
```
|
||||
|
||||
To inspect the trace of this run, check out the [LangSmith link here](https://smith.langchain.com/public/0c543370-d459-4b8d-9962-058f67bdc9ce/r). We'll walk through the execution below:
|
||||
|
||||
1. First, the graph looks for a checkpoint. None is found, so the state is thus initialized with a total of 0.
|
||||
2. Next, the graph applies the user's input as an update to the state. The reducer adds the input (1) to the existing value (0). At the end of this superstep, the total is (1).
|
||||
3. After that, the "add_one" node is called, returning 1.
|
||||
4. Next, the reducer adds this update to the existing total (1). The state is now 2.
|
||||
5. Then, the conditional edge "`route`" is called. Since the value is less than 6, we continue to the 'double' node.
|
||||
6. Double takes the existing state (2), and returns it. The reducer is then called and adds it to the existing state. The state is now 4.
|
||||
7. The graph then loops back through add_one (5), checks the conditional edge and proceeds to since it's < 6. After doubling, the total is (10).
|
||||
8. The fixed edge loops back to add_one (11), checks the conditional edge, and since it is greater than 6, the program terminates.
|
||||
|
||||
For our second run, we will use the same configuration:
|
||||
|
||||
```python
|
||||
for step in graph.stream(
|
||||
{"total": -2, "turn": "First Turn"}, config, stream_mode="debug"
|
||||
):
|
||||
print(step["step"], step["type"], step["payload"].get("values"))
|
||||
# 7 checkpoint {'total': 9}
|
||||
# 8 task None
|
||||
# 8 task_result None
|
||||
# 8 checkpoint {'total': 10}
|
||||
```
|
||||
|
||||
To inspect the trace of this run, check out the [LangSmith link here](https://smith.langchain.com/public/494f1817-46f5-4051-b41c-2dc416ce8b4d/r). We'll walk through the execution below:
|
||||
|
||||
1. First, it applies the update from the user's input. The `add` **reducer** updates the total from 0 to -2.
|
||||
2. Next, the graph looks for the checkpoint. It loads it to memory as the initial state. Total is (9) now ((-2) + 11).
|
||||
3. After that, the 'add_one' node is called with this state. It returns 10.
|
||||
4. That update is applied using the reducer, raising the value to 10.
|
||||
5. Next, the "route" conditional edge is triggered. Since the value is greater than 6, we terminate the program, ending where we started at (11).
|
||||
In this guide we will explore the concepts behind build agentic and multi-agent systems with LangGraph. We assume you have already learned the basic covered in the [introduction tutorial](https://langchain-ai.github.io/langgraph/tutorials/introduction) and want to deepen your understanding of LangGraph's underlying design and inner workings.
|
||||
|
||||
There are three main parts to this concept guide. First, we'll discuss at a very high level what it means to be agentic. Next, we'll look at lower-level concepts in LangGraph that are core for understanding how to build your own agentic systems. Finally, we'll discuss common agentic patterns and how you can achieve those with LangGraph. These will be mostly conceptual guides - for more technical, hands-on guides see our [how-to guides](https://langchain-ai.github.io/langgraph/how-tos/)
|
||||
|
||||
|
||||
LangGraph for Agentic Applications
|
||||
|
||||
- [What does it mean to be agentic?](high_level.md#what-does-it-mean-to-be-agentic)
|
||||
- [Why LangGraph](high_level.md#why-langgraph)
|
||||
- [Deployment](high_level.md#deployment)
|
||||
|
||||
Low Level Concepts
|
||||
|
||||
- [Graphs](low_level.md#graphs)
|
||||
- [StateGraph](low_level.md#stategraph)
|
||||
- [MessageGraph](low_level.md#messagegraph)
|
||||
- [Compiling Your Graph](low_level.md#compiling-your-graph)
|
||||
- [State](low_level.md#state)
|
||||
- [Schema](low_level.md#schema)
|
||||
- [Reducers](low_level.md#reducers)
|
||||
- [MessageState](low_level.md#messagestate)
|
||||
- [Nodes](low_level.md#nodes)
|
||||
- [`START` node](low_level.md#start-node)
|
||||
- [`END` node](low_level.md#end-node)
|
||||
- [Edges](low_level.md#edges)
|
||||
- [Normal Edges](low_level.md#normal-edges)
|
||||
- [Conditional Edges](low_level.md#conditional-edges)
|
||||
- [Entry Point](low_level.md#entry-point)
|
||||
- [Conditional Entry Point](low_level.md#conditional-entry-point)
|
||||
- [Send](low_level.md#send)
|
||||
- [Checkpointer](low_level.md#checkpointer)
|
||||
- [Threads](low_level.md#threads)
|
||||
- [Checkpointer states](low_level.md#checkpointer-state)
|
||||
- [Get state](low_level.md#get-state)
|
||||
- [Get state history](low_level.md#get-state-history)
|
||||
- [Update state](low_level.md#update-state)
|
||||
- [Configuration](low_level.md#configuration)
|
||||
- [Visualization](low_level.md#visualization)
|
||||
- [Streaming](low_level.md#streaming)
|
||||
|
||||
Common Agentic Patterns
|
||||
|
||||
- [Structured output](agentic_concepts.md#structured-output)
|
||||
- [Tool calling](agentic_concepts.md#tool-calling)
|
||||
- [Memory](agentic_concepts.md#memory)
|
||||
- [Human in the loop](agentic_concepts.md#human-in-the-loop)
|
||||
- [Approval](agentic_concepts.md#approval)
|
||||
- [Wait for input](agentic_concepts.md#wait-for-input)
|
||||
- [Edit agent actions](agentic_concepts.md#edit-agent-actions)
|
||||
- [Time travel](agentic_concepts.md#time-travel)
|
||||
- [Map-Reduce](agentic_concepts.md#map-reduce)
|
||||
- [Multi-agent](agentic_concepts.md#multi-agent)
|
||||
- [Planning](agentic_concepts.md#planning)
|
||||
- [Reflection](agentic_concepts.md#reflection)
|
||||
- [Off-the-shelf ReAct Agent](agentic_concepts.md#react-agent)
|
||||
|
||||
@@ -0,0 +1,405 @@
|
||||
# Low Level Conceptual Guide
|
||||
|
||||
## Graphs
|
||||
|
||||
At its core, LangGraph models agent workflows as graphs. You define the behavior of your agents using three key components:
|
||||
|
||||
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`.
|
||||
|
||||
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.
|
||||
|
||||
In short: _nodes do the work. edges tell what to do next_.
|
||||
|
||||
LangGraph's underlying graph algorithm uses [message passing](https://en.wikipedia.org/wiki/Message_passing) to define a general program. When a `Node` completes, it sends a message along one or more edges to other node(s). These nodes run their functions, pass the resulting messages to the next set of nodes, and on and on it goes. Inspired by [Pregel](https://research.google/pubs/pregel-a-system-for-large-scale-graph-processing/), the program proceeds in discrete "super-steps" that are all executed conceptually in parallel. Whenever the graph is run, all the nodes start in an `inactive` state. Whenever an incoming edge (or "channel") receives a new message (state), the node becomes `active`, runs the function, and responds with updates. At the end of each superstep, each node votes to `halt` by marking itself as `inactive` if it has no more incoming messages. The graph terminates when all nodes are `inactive` and when no messages are in transit.
|
||||
|
||||
### StateGraph
|
||||
|
||||
The `StateGraph` class is the main graph class to uses. This is parameterized by a user defined `State` object.
|
||||
|
||||
### MessageGraph
|
||||
|
||||
The `MessageGraph` class is a special type of graph. The `State` of a `MessageGraph` is ONLY a list of messages. This class is rarely used except for chatbots, as most applications require the `State` to be more complex than a list of messages.
|
||||
|
||||
### Compiling your graph
|
||||
|
||||
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](#checkpointer) and [breakpoints](#breakpoints). You compile your graph by just calling the `.compile` method:
|
||||
|
||||
```python
|
||||
graph = graph_builder.compile(...)
|
||||
```
|
||||
|
||||
You **MUST** compile your graph before you can use it.
|
||||
|
||||
## State
|
||||
|
||||
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.
|
||||
|
||||
### Schema
|
||||
|
||||
The main documented way to specify the schema of a graph is by using `TypedDict`. However, we also support [using a Pydantic BaseModel](../how-tos/state-model.ipynb) as your graph state to add **default values** and additional data validation.
|
||||
|
||||
### Reducers
|
||||
|
||||
Reducers are key to understanding how updates from nodes are applied to the `State`. Each key in the `State` has its own independent reducer function. If no reducer function is explicitly specified then it is assumed that all updates to that key should override it. Let's take a look at a few examples to understand them better.
|
||||
|
||||
**Example A:**
|
||||
|
||||
```python
|
||||
from typing import TypedDict
|
||||
|
||||
class State(TypedDict):
|
||||
foo: int
|
||||
bar: list[str]
|
||||
```
|
||||
|
||||
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
|
||||
from typing import TypedDict, Annotated
|
||||
from operator import add
|
||||
|
||||
class State(TypedDict):
|
||||
foo: int
|
||||
bar: Annotated[list[str], add]
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
### MessageState
|
||||
|
||||
`MessageState` is one of the few opinionated components in LangGraph. `MessageState` is a special state designed to make it easy to use a list of messages as a key in your state. Specifically, `MessageState` is defined as:
|
||||
|
||||
```python
|
||||
from langchain_core.messages import AnyMessage
|
||||
from langgraph.graph.message import add_messages
|
||||
from typing import Annotated, TypedDict
|
||||
|
||||
class MessagesState(TypedDict):
|
||||
messages: Annotated[list[AnyMessage], add_messages]
|
||||
```
|
||||
|
||||
What this is doing is creating a `TypedDict` with a single key: `messages`. This is a list of `Message` objects, with `add_messages` as a reducer. `add_messages` basically adds messages to the existing list (it also does some nice extra things, like convert from OpenAI message format to the standard LangChain message format, handle updates based on message IDs, etc).
|
||||
|
||||
We often see a list of messages being a key component of state, so this prebuilt state is intended to make it easy to use messages. Typically, there is more state to track than just messages, so we see people subclass this state and add more fields, like:
|
||||
|
||||
```python
|
||||
from langgraph.graph import MessagesState
|
||||
|
||||
class State(MessagesState):
|
||||
documents: list[str]
|
||||
```
|
||||
|
||||
## Nodes
|
||||
|
||||
In LangGraph, nodes are typically python functions (sync or `async`) where the **first** positional argument is the [state](#state), and (optionally), the **second** positional argument is a "config", containing optional [configurable parameters](#configuration) (such as a `thread_id`).
|
||||
|
||||
Similar to `NetworkX`, you add these nodes to a graph using the [add_node][langgraph.graph.StateGraph.add_node] method:
|
||||
|
||||
```python
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.graph import StateGraph
|
||||
|
||||
builder = StateGraph(dict)
|
||||
|
||||
|
||||
def my_node(state: dict, config: RunnableConfig):
|
||||
print("In node: ", config["configurable"]["user_id"])
|
||||
return {"results": f"Hello, {state['input']}!"}
|
||||
|
||||
|
||||
# The second argument is optional
|
||||
def my_other_node(state: dict):
|
||||
return state
|
||||
|
||||
|
||||
builder.add_node("my_node", my_node)
|
||||
builder.add_node("other_node", my_other_node)
|
||||
...
|
||||
```
|
||||
|
||||
Behind the scenes, functions are converted to [RunnableLambda's](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.RunnableLambda.html#langchain_core.runnables.base.RunnableLambda), which add batch and async support to your function, along with native tracing and debugging.
|
||||
|
||||
If you add a node to graph without specifying a name, it will be given a default name equivalent to the function name.
|
||||
|
||||
```python
|
||||
builder.add_node(my_node)
|
||||
# You can then create edges to/from this node by referencing it as `"my_node"`
|
||||
```
|
||||
|
||||
### `START` Node
|
||||
|
||||
The `START` Node is a special node that represents the node sends user input to the graph. The main purpose for referencing this node is to determine which nodes should be called first.
|
||||
|
||||
```python
|
||||
from langgraph.graph import START
|
||||
|
||||
graph.add_edge(START, "node_a")
|
||||
```
|
||||
|
||||
### `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.
|
||||
|
||||
```
|
||||
from langgraph.graph import END
|
||||
|
||||
graph.add_edge("node_a", END)
|
||||
```
|
||||
|
||||
## Edges
|
||||
|
||||
Edges define how the logic is routed and how the graph decides to stop. This is a big part of how your agents work and how different nodes communicate with each other. There are a few key types of edges:
|
||||
|
||||
- Normal Edges: Go directly from one node to the next.
|
||||
- Conditional Edges: Call a function to determine which node(s) to go to next.
|
||||
- Entry Point: Which node to call first when user input arrives.
|
||||
- Conditional Entry Point: Call a function to determine which node(s) to call first when user input arrives.
|
||||
|
||||
A node can have MULTIPLE outgoing edges. If a node has multiple out-going edges, **all** of those destination nodes will be executed in parallel as a part of the next superstep.
|
||||
|
||||
### Normal Edges
|
||||
|
||||
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")
|
||||
```
|
||||
|
||||
### Conditional Edges
|
||||
|
||||
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
|
||||
graph.add_edge("node_a", routing_function)
|
||||
```
|
||||
|
||||
Similar to nodes, the `routing_function` accept the current `state` of the graph and return a value.
|
||||
|
||||
By default, the return value `routing_function` is used as the name of the node (or a 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 `routing_function`'s output to the name of the next node.
|
||||
|
||||
```python
|
||||
graph.add_edge("node_a", routing_function, {True: "node_b", False: "node_c"})
|
||||
```
|
||||
|
||||
### Entry Point
|
||||
|
||||
The entry point is first node to call when the graph starts. You can use [`set_entry_point`][langgraph.graph.StateGraph.set_entry_point] to specify this.
|
||||
|
||||
```python
|
||||
graph.set_entry_point("node_a")
|
||||
```
|
||||
|
||||
This is equivalent to adding an edge between the `START` node and this node. You may want to use `START` directly when you want to have **multiple** nodes be called first.
|
||||
|
||||
```python
|
||||
from langgraph.graph import START
|
||||
|
||||
graph.add_edge(START, "node_a")
|
||||
```
|
||||
|
||||
### Conditional Entry Point
|
||||
|
||||
The conditional entry point is used when you want to specify a function to call to determine which node(s) should be called first.
|
||||
You can use [`set_conditional_entry_point`][langgraph.graph.StateGraph.set_conditional_entry_point] to specify this.
|
||||
|
||||
```python
|
||||
graph.set_conditional_entry_point(routing_function)
|
||||
```
|
||||
|
||||
You can optionally provide a dictionary that maps the `routing_function`'s output to the name of the next node.
|
||||
|
||||
```python
|
||||
graph.set_conditional_entry_point(routing_function, {True: "node_b", False: "node_c"})
|
||||
```
|
||||
|
||||
## `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 of example of this is with `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).
|
||||
|
||||
To support this design pattern, LangGraph supports returning [`Send`](../reference/graphs.md#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
|
||||
def continue_to_jokes(state: OverallState):
|
||||
return [Send("generate_joke", {"subject": s}) for s in state['subjects']]
|
||||
|
||||
graph.add_conditional_edges("node_a", continue_to_jokes)
|
||||
```
|
||||
|
||||
## Checkpointer
|
||||
|
||||
One of the main benefits of LangGraph is that it comes backed by a persistence layer. This is accomplished via [checkpointers][basecheckpointsaver].
|
||||
|
||||
Checkpointers can be used to save a _checkpoint_ of the state of a graph after all steps of the graph. This allows for several things.
|
||||
|
||||
First, it allows for [human-in-the-loop workflows](agentic_concepts.md#human-in-the-loop), as it allows humans to inspect, interrupt, and approve steps. Checkpointers are needed for these workflows as the human has to be able to view the state of a graph at any point in time, and the graph has to be to resume execution after the human has made any updates to the state.
|
||||
|
||||
Second, it allows for ["memory"](agentic_concepts.md#memory) between interactions. You can use checkpointers to create threads and save the state of a thread after a graph executes. In the case of repeated human interactions (like conversations) any follow up messages can be sent to that checkpoint, which will retain its memory of previous ones.
|
||||
|
||||
See [this guide](../how-tos/persistence.ipynb) for how to add a checkpointer to your graph.
|
||||
|
||||
## Threads
|
||||
|
||||
When using a checkpointer, you must specify a `thread_id` or `thread_ts` when running the graph.
|
||||
Threads are used to checkpoint multiple different runs. This can be used to enable a multi-tenant chat applications.
|
||||
|
||||
`thread_id` is simply the ID of a thread. This is always required
|
||||
|
||||
`thread_ts` can optionally be passed. This identifier refers to a specific checkpoint within a thread. This can be used to kick of a run of a graph from some point halfway through a thread.
|
||||
|
||||
You must pass these when invoking the graph as part of the configurable part of the config.
|
||||
|
||||
```python
|
||||
config = {"configurable": {"thread_id": "a"}}
|
||||
graph.invoke(inputs, config=config)
|
||||
```
|
||||
|
||||
See [this guide](../how-tos/persistence.ipynb) for how to use threads.
|
||||
|
||||
## Checkpointer state
|
||||
|
||||
When you use a checkpointer with a graph, you can interact with the state of that graph.
|
||||
This usually done when enabling different human-in-the-loop interaction patterns.
|
||||
Each time you run the graph, the checkpointer creates several checkpoints every time a
|
||||
node or set of nodes finishes running.
|
||||
The most recent checkpoint is the current state of the thread.
|
||||
When interacting with the checkpointer state, you must specify a [thread identifier](#threads).
|
||||
|
||||
Each checkpoint has two properties:
|
||||
|
||||
- **values**: This is the value of the state at this point in time.
|
||||
- **next**: This is a tuple of the nodes to execute next in the graph.
|
||||
|
||||
### Get state
|
||||
|
||||
You can get the state of a checkpointer by calling `graph.get_state(config)`. The config should contain `thread_id`, and the state will be fetched for that thread.
|
||||
|
||||
### Get state history
|
||||
|
||||
You can also call `graph.get_state_history(config)` to get a list of the history of the graph. The config should contain `thread_id`, and the state history will be fetched for that thread.
|
||||
|
||||
### Update state
|
||||
|
||||
You can also interact with the state directly and update it. This takes three different components:
|
||||
|
||||
- config
|
||||
- values
|
||||
- `as_node`
|
||||
|
||||
**config**
|
||||
|
||||
The config should contain `thread_id` specifying which thread to update.
|
||||
|
||||
**values**
|
||||
|
||||
These are the values that will be used to update the state. Note that this update is treated exactly as any update from a node is treated. This means that these values will be passed to the [reducer](#reducers) functions that are part of the state. So this does NOT automatically overwrite the state. Let's walk through an example.
|
||||
|
||||
Let's assume you have defined the state of your graph as:
|
||||
|
||||
```python
|
||||
from typing import TypedDict, Annotated
|
||||
from operator import add
|
||||
|
||||
class State(TypedDict):
|
||||
foo: int
|
||||
bar: Annotated[list[str], add]
|
||||
```
|
||||
|
||||
Let's now assume the current state of the graph is
|
||||
|
||||
```
|
||||
{"foo": 1, "bar": ["a"]}
|
||||
```
|
||||
|
||||
If you update the state as below:
|
||||
|
||||
```
|
||||
graph.update_state(config, {"foo": 2, "bar": ["b"]})
|
||||
```
|
||||
|
||||
Then the new state of the graph will be:
|
||||
|
||||
```
|
||||
{"foo": 2, "bar": ["a", "b"]}
|
||||
```
|
||||
|
||||
The `foo` key is completely changed (because there is no reducer specified for that key, so it overwrites it). However, there is a reducer specified for the `bar` key, and so it appends `"b"` to the state of `bar`.
|
||||
|
||||
**`as_node`**
|
||||
|
||||
The final thing you specify when calling `update_state` is `as_node`. This update will be applied as if it came from node `as_node`. If `as_node` is not provided, it will be set to the last node that updated the state, if not ambiguous.
|
||||
|
||||
The reason this matters is that the next steps in the graph to execute depend on the last node to have given an update, so this can be used to control which node executes next.
|
||||
|
||||
## Configuration
|
||||
|
||||
When creating a graph, you can also mark that certain parts of the graph are configurable. This is commonly done to enable easily switching between models or system prompts. This allows you to create a single "cognitive architecture" (the graph) but have multiple different instance of it.
|
||||
|
||||
You can optionally specify a `config_schema` when creating a graph.
|
||||
|
||||
```python
|
||||
class ConfigSchema(TypedDict):
|
||||
llm: str
|
||||
|
||||
graph = StateGraph(State, config_schema=ConfigSchema)
|
||||
```
|
||||
|
||||
You can then pass this configuration into the graph using the `configurable` config field.
|
||||
|
||||
```python
|
||||
config = {"configurable": {"llm": "anthropic"}}
|
||||
|
||||
graph.invoke(inputs, config=config)
|
||||
```
|
||||
|
||||
You can then access and use this configuration inside a node:
|
||||
|
||||
```python
|
||||
def node_a(state, config):
|
||||
llm_type = config.get("configurable", {}).get("llm", "openai")
|
||||
llm = get_llm(llm_type)
|
||||
...
|
||||
```
|
||||
|
||||
See [this guide](../how-tos/configuration.ipynb) for a full breakdown on configuration
|
||||
|
||||
## Breakpoints
|
||||
|
||||
It can often be useful to set breakpoints before or after certain nodes execute. This can be used to wait for human approval before continuing. These can be set when you ["compile" a graph](#compiling-your-graph). You can set breakpoints either _before_ a node executes (using `interrupt_before`) or after a node executes (using `interrupt_after`.)
|
||||
|
||||
You **MUST** use a [checkpoiner](#checkpointer) when using breakpoints. This is because your graph needs to be able to resume execution.
|
||||
|
||||
In order to resume execution, you can just invoke your graph with `None` as the input.
|
||||
|
||||
```python
|
||||
# Initial run of graph
|
||||
graph.invoke(inputs, config=config)
|
||||
|
||||
# Let's assume it hit a breakpoint somewhere, you can then resume by passing in None
|
||||
graph.invoke(None, config=config)
|
||||
```
|
||||
|
||||
See [this guide](../how-tos/human_in_the_loop/breakpoints.ipynb) for a full walkthrough of how to add breakpoints.
|
||||
|
||||
## Visualization
|
||||
|
||||
It's often nice to be able to visualize graphs, especially as they get more complex. LangGraph comes with several built-in ways to visualize graphs. See [this how-to guide](../how-tos/visualization.ipynb) for more info.
|
||||
|
||||
## Streaming
|
||||
|
||||
LangGraph is built with first class support for streaming. There are several different streaming modes that LangGraph supports:
|
||||
|
||||
- [`"values"`](../how-tos/stream-values.ipynb): This streams the full value of the state after each step of the graph.
|
||||
- [`"updates`](../how-tos/stream-updates.ipynb): This streams the updates to the state after each step of the graph. If multiple updates are made in the same step (e.g. multiple nodes are run) then those updates are streamed separately.
|
||||
- `"debug"`: This streams as much information as possible throughout the execution of the graph.
|
||||
|
||||
In addition, you can use the [`astream_events`](../how-tos/streaming-events-from-within-tools.ipynb) method to stream back events that happen _inside_ nodes. This is useful for [streaming tokens of LLM calls](../how-tos/streaming-tokens.ipynb).
|
||||
@@ -1,37 +1,65 @@
|
||||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
|
||||
# How-to guides
|
||||
|
||||
Welcome to the LangGraph how-to guides! These guides provide practical, step-by-step instructions for accomplishing key tasks in LangGraph.
|
||||
|
||||
## Core
|
||||
## Controllability
|
||||
|
||||
The core guides show how to address common needs when building out AI workflows, with special focus placed on [ReAct](https://arxiv.org/abs/2210.03629)-style agents with [tool calling](https://python.langchain.com/docs/modules/model_io/chat/function_calling/) (agents that <strong>Re</strong>ason and **Act** to accomplish tasks).
|
||||
|
||||
- [How to create a ReAct agent](create-react-agent.ipynb)
|
||||
- [How to add persistence ("memory") to your graph](persistence.ipynb)
|
||||
- [How to view and update graph state](time-travel.ipynb)
|
||||
- [How to run graph asynchronously](async.ipynb)
|
||||
- [How to stream graph responses](streaming-tokens.ipynb)
|
||||
- [How to visualize your graph](visualization.ipynb)
|
||||
- [How to add runtime configuration to your graph](configuration.ipynb)
|
||||
|
||||
### Design patterns
|
||||
|
||||
Recipes showing how to apply common design patterns in your workflows:
|
||||
LangGraph is known for being a highly controllable agent framework.
|
||||
These how-to guides show how to achieve that controllability.
|
||||
|
||||
- [How to create subgraphs](subgraph.ipynb)
|
||||
- [How to create branches for parallel execution](branching.ipynb)
|
||||
- [How to create map-reduce branches for parallel execution](map-reduce.ipynb)
|
||||
- [How to add human-in-the-loop](human-in-the-loop.ipynb)
|
||||
|
||||
The following examples are useful especially if you are used to LangChain's `AgentExecutor` configurations.
|
||||
|
||||
- [How to force an agent to call a tool](force-calling-a-tool-first.ipynb)
|
||||
- [How to pass runtime values to tools](pass-run-time-values-to-tools.ipynb)
|
||||
- [How to let agent return tool results directly](dynamically-returning-directly.ipynb)
|
||||
- [How to have agent respond in structured format](respond-in-format.ipynb)
|
||||
- [How to manage agent steps](managing-agent-steps.ipynb)
|
||||
## Persistence
|
||||
|
||||
### Advanced
|
||||
LangGraph makes it easy to persist state across graph runs. The guide below shows how to add persistence to your graph.
|
||||
|
||||
- [How to add persistence ("memory") to your graph](persistence.ipynb)
|
||||
- [How to manage conversation history](managing-conversation-history.ipynb)
|
||||
- [How to create a custom checkpointer using Postgres](persistence_postgres.ipynb)
|
||||
|
||||
## Human in the Loop
|
||||
|
||||
One of LangGraph's main benefits is that it makes human-in-the-loop workflows easy.
|
||||
These guides cover common examples of that.
|
||||
|
||||
- [How to add breakpoints](human_in_the_loop/breakpoints.ipynb)
|
||||
- [How to edit graph state](human_in_the_loop/edit-graph-state.ipynb)
|
||||
- [How to wait for user input](human_in_the_loop/wait-user-input.ipynb)
|
||||
- [How to view and update past graph state](human_in_the_loop/time-travel.ipynb)
|
||||
|
||||
## Streaming
|
||||
|
||||
LangGraph is built to be streaming first.
|
||||
These guides show how to use different streaming modes.
|
||||
|
||||
- [How to stream full state of your graph](stream-values.ipynb)
|
||||
- [How to stream state updates of your graph](stream-updates.ipynb)
|
||||
- [How to stream LLM tokens](streaming-tokens.ipynb)
|
||||
- [How to stream arbitrarily nested content](streaming-content.ipynb)
|
||||
- [How to configure multiple streaming modes at the same time](stream-multiple.ipynb)
|
||||
- [How to stream events from within a tool](streaming-events-from-within-tools.ipynb)
|
||||
- [How to stream events from the final node](streaming-from-final-node.ipynb)
|
||||
|
||||
## Other
|
||||
- [How to run graph asynchronously](async.ipynb)
|
||||
- [How to visualize your graph](visualization.ipynb)
|
||||
- [How to add runtime configuration to your graph](configuration.ipynb)
|
||||
- [How to use a Pydantic model as your state](state-model.ipynb)
|
||||
- [How to extract structured output with re-prompting](./extraction/retries.ipynb)
|
||||
|
||||
## Prebuilt ReAct Agent
|
||||
|
||||
These guides show how to use the prebuilt ReAct agent.
|
||||
Please note that here will we use a **prebuilt agent**. One of the big benefits of LangGraph is that you can easily create your own agent architectures. So while it's fine to start here to build an agent quickly, we would strongly recommend learning how to build your own agent so that you can take full advantage of LangGraph.
|
||||
|
||||
- [How to create a ReAct agent](create-react-agent.ipynb)
|
||||
- [How to add memory to a ReAct agent](create-react-agent-memory.ipynb)
|
||||
- [How to add a custom system prompt to a ReAct agent](create-react-agent-system-prompt.ipynb)
|
||||
- [How to add human-in-the-loop processes to a ReAct agent](create-react-agent-hitl.ipynb)
|
||||
|
||||
@@ -1,131 +1,7 @@
|
||||
---
|
||||
hide_comments: true
|
||||
hide:
|
||||
- navigation
|
||||
---
|
||||
# 🦜🕸️LangGraph
|
||||
|
||||

|
||||
[](https://pepy.tech/project/langgraph)
|
||||
[](https://github.com/langchain-ai/langgraph/issues)
|
||||
[](https://discord.com/channels/1038097195422978059/1170024642245832774)
|
||||
|
||||
|
||||
⚡ Build language agents as graphs ⚡
|
||||
|
||||
!!! note "Python version :material-language-python:"
|
||||
|
||||
Looking for the JS version? Click [:fontawesome-brands-square-js: here](https://github.com/langchain-ai/langgraphjs) ([:simple-readme: JS docs](https://langchain-ai.github.io/langgraphjs/)).
|
||||
|
||||
## Overview
|
||||
|
||||
Suppose you're building a customer support assistant. You want your assistant to be able to:
|
||||
|
||||
1. Use tools to respond to questions
|
||||
2. Connect with a human if needed
|
||||
3. Be able to pause the process indefinitely and resume whenever the human responds
|
||||
|
||||
LangGraph makes this all easy. First install:
|
||||
|
||||
```bash
|
||||
pip install -U langgraph
|
||||
```
|
||||
|
||||
Then define your assistant:
|
||||
|
||||
```python
|
||||
import json
|
||||
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
|
||||
from langgraph.checkpoint.sqlite import SqliteSaver
|
||||
from langgraph.graph import END, MessageGraph
|
||||
from langgraph.prebuilt.tool_node import ToolNode
|
||||
|
||||
|
||||
# Define the function that determines whether to continue or not
|
||||
def should_continue(messages):
|
||||
last_message = messages[-1]
|
||||
# If there is no function call, then we finish
|
||||
if not last_message.tool_calls:
|
||||
return END
|
||||
else:
|
||||
return "action"
|
||||
|
||||
|
||||
# Define a new graph
|
||||
workflow = MessageGraph()
|
||||
|
||||
tools = [TavilySearchResults(max_results=1)]
|
||||
model = ChatAnthropic(model="claude-3-haiku-20240307").bind_tools(tools)
|
||||
workflow.add_node("agent", model)
|
||||
workflow.add_node("action", ToolNode(tools))
|
||||
|
||||
workflow.set_entry_point("agent")
|
||||
|
||||
# Conditional agent -> action OR agent -> END
|
||||
workflow.add_conditional_edges(
|
||||
"agent",
|
||||
should_continue,
|
||||
)
|
||||
|
||||
# Always transition `action` -> `agent`
|
||||
workflow.add_edge("action", "agent")
|
||||
|
||||
memory = SqliteSaver.from_conn_string(":memory:") # Here we only save in-memory
|
||||
|
||||
# Setting the interrupt means that any time an action is called, the machine will stop
|
||||
app = workflow.compile(checkpointer=memory, interrupt_before=["action"])
|
||||
```
|
||||
|
||||
Now, run the graph:
|
||||
|
||||
```python
|
||||
# Run the graph
|
||||
thread = {"configurable": {"thread_id": "4"}}
|
||||
for event in app.stream("what is the weather in sf currently", thread, stream_mode="values"):
|
||||
event[-1].pretty_print()
|
||||
|
||||
```
|
||||
We configured the graph to **wait** before executing the `action`. The `SqliteSaver` persists the state. Resume at any time.
|
||||
|
||||
```python
|
||||
for event in app.stream(None, thread, stream_mode="values"):
|
||||
event[-1].pretty_print()
|
||||
```
|
||||
|
||||
The graph orchestrates everything:
|
||||
|
||||
- The `MessageGraph` contains the agent's "Memory"
|
||||
- Conditional edges enable dynamic routing between the chatbot, tools, and the user
|
||||
- Persistence makes it easy to stop, resume, and even rewind for full control over your application
|
||||
|
||||
With LangGraph, you can build complex, stateful agents without getting bogged down in manual state and interrupt management. Just define your nodes, edges, and state schema - and let the graph take care of the rest.
|
||||
|
||||
|
||||
## Tutorials
|
||||
|
||||
Consult the [Tutorials](tutorials/index.md) to learn more about building with LangGraph, including advanced use cases.
|
||||
|
||||
|
||||
## How-To Guides
|
||||
|
||||
Check out the [How-To Guides](how-tos/index.md) for instructions on handling common tasks with LangGraph
|
||||
|
||||
## Reference
|
||||
|
||||
For documentation on the core APIs, check out the [Reference](reference/graphs.md) docs.
|
||||
|
||||
## Conceptual Guides
|
||||
|
||||
Once you've learned the basics, if you want to further understand LangGraph's core abstractions, check out the [Conceptual Guides](./concepts/index.md).
|
||||
|
||||
## Why LangGraph?
|
||||
|
||||
LangGraph is framework agnostic (each node is a regular python function). It extends the core Runnable API (shared interface for streaming, async, and batch calls) to make it easy to:
|
||||
|
||||
- Seamless state management across multiple turns of conversation or tool usage
|
||||
- The ability to flexibly route between nodes based on dynamic criteria
|
||||
- Smooth switching between LLMs and human intervention
|
||||
- Persistence for long-running, multi-session applications
|
||||
|
||||
If you're building a straightforward DAG, Runnables are a great fit. But for more complex, stateful applications with nonlinear flows, LangGraph is the perfect tool for the job.
|
||||
{!README.md!}
|
||||
@@ -13,17 +13,19 @@ graph = StateGraph(MyState)
|
||||
```
|
||||
|
||||
::: langgraph.graph.StateGraph
|
||||
handler: python
|
||||
handler: python
|
||||
|
||||
## MessageGraph
|
||||
|
||||
::: langgraph.graph.message.MessageGraph
|
||||
|
||||
|
||||
## CompiledGraph
|
||||
|
||||
::: langgraph.graph.graph.CompiledGraph
|
||||
handler: python
|
||||
|
||||
## StreamMode
|
||||
|
||||
::: langgraph.pregel.StreamMode
|
||||
|
||||
## Constants
|
||||
|
||||
@@ -63,5 +65,4 @@ builder.add_conditional_edges("my_node", my_condition)
|
||||
|
||||
## Send
|
||||
|
||||
::: langgraph.constants.Send
|
||||
handler: python
|
||||
::: langgraph.constants.Send
|
||||
@@ -1,12 +1,17 @@
|
||||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
|
||||
# Tutorials
|
||||
|
||||
Welcome to the LangGraph Tutorials! These notebooks introduce LangGraph through building various language agents and applications.
|
||||
|
||||
## Introduction to LangGraph
|
||||
## Quick Start
|
||||
|
||||
Learn the basics of LangGraph through the onboarding tutorials.
|
||||
Learn the basics of LangGraph through a comprehensive quick start in which you will build an agent from scratch.
|
||||
|
||||
- [Introduction to LangGraph](introduction.ipynb)
|
||||
- [Quick Start](introduction.ipynb)
|
||||
|
||||
## Use cases
|
||||
|
||||
@@ -58,4 +63,6 @@ Learn from example implementations of graphs designed for specific scenarios and
|
||||
- [Web Research (STORM)](storm/storm.ipynb): Generate Wikipedia-like articles via research and multi-perspective QA
|
||||
- [TNT-LLM](tnt-llm/tnt-llm.ipynb): Build rich, interpretable taxonomies of user intentand using the classification system developed by Microsoft for their Bing Copilot application.
|
||||
- [Web Navigation](web-navigation/web_voyager.ipynb): Build an agent that can navigate and interact with websites
|
||||
- [Competitive Programming](usaco/usaco.ipynb): Build an agent with few-shot "episodic memory" and human-in-the-loop collaboration to solve problems from the USA Computing Olympiad; adapted from the ["Can Language Models Solve Olympiad Programming?"](https://arxiv.org/abs/2404.10952v1) paper by Shi, Tang, Narasimhan, and Yao.
|
||||
- [Competitive Programming](usaco/usaco.ipynb): Build an agent with few-shot "episodic memory" and human-in-the-loop collaboration to solve problems from the USA Computing Olympiad; adapted from the ["Can Language Models Solve Olympiad Programming?"](https://arxiv.org/abs/2404.10952v1) paper by Shi, Tang, Narasimhan, and Yao.
|
||||
- [Complex data extraction](extraction/retries.ipynb): Build an agent that can use function calling to do complex extraction tasks
|
||||
-
|
||||
@@ -34,18 +34,18 @@ theme:
|
||||
- search.suggest
|
||||
- toc.follow
|
||||
palette:
|
||||
- scheme: default
|
||||
primary: white
|
||||
accent: gray
|
||||
toggle:
|
||||
icon: material/brightness-7
|
||||
name: Switch to dark mode
|
||||
- scheme: slate
|
||||
primary: grey
|
||||
accent: white
|
||||
toggle:
|
||||
icon: material/brightness-4
|
||||
name: Switch to light mode
|
||||
- scheme: default
|
||||
primary: white
|
||||
accent: gray
|
||||
toggle:
|
||||
icon: material/brightness-7
|
||||
name: Switch to dark mode
|
||||
- scheme: slate
|
||||
primary: grey
|
||||
accent: white
|
||||
toggle:
|
||||
icon: material/brightness-4
|
||||
name: Switch to light mode
|
||||
font:
|
||||
text: "Public Sans"
|
||||
code: "Roboto Mono"
|
||||
@@ -60,7 +60,7 @@ plugins:
|
||||
- https://docs.python.org/3/objects.inv
|
||||
- https://api.python.langchain.com/en/latest/objects.inv
|
||||
options:
|
||||
members_order: source
|
||||
members_order: source
|
||||
allow_inspection: true
|
||||
heading_level: 3
|
||||
show_bases: true
|
||||
@@ -68,9 +68,9 @@ plugins:
|
||||
inherited_members: true
|
||||
# merge_init_into_class: true
|
||||
selection:
|
||||
docstring_style: google
|
||||
docstring_style: google
|
||||
docstring_section_style: list
|
||||
show_root_toc_entry: false
|
||||
show_root_toc_entry: false
|
||||
# show_signature_annotations: true
|
||||
# show_symbol_type_heading: true
|
||||
show_symbol_type_toc: true
|
||||
@@ -80,22 +80,12 @@ plugins:
|
||||
execute: false
|
||||
include_source: True
|
||||
include_requirejs: true
|
||||
- git-committers:
|
||||
repository: langchain-ai/langgraph
|
||||
branch: main
|
||||
docs_path: docs/docs/
|
||||
token: !ENV ["MKDOCS_GIT_COMMITTERS_APIKEY"]
|
||||
# TODO: Add minify plugin once it works alright with code block copying
|
||||
# - minify:
|
||||
# minify_html: true
|
||||
nav:
|
||||
- Home:
|
||||
- 'index.md'
|
||||
- Quick Start: how-tos/docs/quickstart.ipynb
|
||||
- Intro to LangGraph: tutorials/introduction.ipynb
|
||||
- Home:
|
||||
- "index.md"
|
||||
- Tutorials:
|
||||
- 'tutorials/index.md'
|
||||
- Introduction: tutorials/introduction.ipynb
|
||||
- "tutorials/index.md"
|
||||
- Quick Start: tutorials/introduction.ipynb
|
||||
- Chatbots:
|
||||
- Customer Support: tutorials/customer-support/customer-support.ipynb
|
||||
- Prompt Generation from User Requirements: tutorials/chatbots/information-gather-prompting.ipynb
|
||||
@@ -110,92 +100,123 @@ nav:
|
||||
- Self-RAG using local LLMs: tutorials/rag/langgraph_self_rag_local.ipynb
|
||||
- SQL Agent: tutorials/sql-agent.ipynb
|
||||
- Agent Architectures:
|
||||
- Multi-Agent Systems:
|
||||
- Collaboration: tutorials/multi_agent/multi-agent-collaboration.ipynb
|
||||
- Supervision: tutorials/multi_agent/agent_supervisor.ipynb
|
||||
- Hierarchical Teams: tutorials/multi_agent/hierarchical_agent_teams.ipynb
|
||||
- Planning Agents:
|
||||
- Plan-and-Execute: tutorials/plan-and-execute/plan-and-execute.ipynb
|
||||
- Reasoning without Observation: tutorials/rewoo/rewoo.ipynb
|
||||
- LLMCompiler: tutorials/llm-compiler/LLMCompiler.ipynb
|
||||
- Reflection & Critique:
|
||||
- Basic Reflection: tutorials/reflection/reflection.ipynb
|
||||
- Reflexion: tutorials/reflexion/reflexion.ipynb
|
||||
- Language Agent Tree Search: tutorials/lats/lats.ipynb
|
||||
- Self-Discover Agent: tutorials/self-discover/self-discover.ipynb
|
||||
- Multi-Agent Systems:
|
||||
- Collaboration: tutorials/multi_agent/multi-agent-collaboration.ipynb
|
||||
- Supervision: tutorials/multi_agent/agent_supervisor.ipynb
|
||||
- Hierarchical Teams: tutorials/multi_agent/hierarchical_agent_teams.ipynb
|
||||
- Planning Agents:
|
||||
- Plan-and-Execute: tutorials/plan-and-execute/plan-and-execute.ipynb
|
||||
- Reasoning without Observation: tutorials/rewoo/rewoo.ipynb
|
||||
- LLMCompiler: tutorials/llm-compiler/LLMCompiler.ipynb
|
||||
- Reflection & Critique:
|
||||
- Basic Reflection: tutorials/reflection/reflection.ipynb
|
||||
- Reflexion: tutorials/reflexion/reflexion.ipynb
|
||||
- Language Agent Tree Search: tutorials/lats/lats.ipynb
|
||||
- Self-Discover Agent: tutorials/self-discover/self-discover.ipynb
|
||||
- Evaluation & Analysis:
|
||||
- Chatbot Evaluation via Simulation:
|
||||
- Agent-based: tutorials/chatbot-simulation-evaluation/agent-simulation-evaluation.ipynb
|
||||
- In LangSmith: tutorials/chatbot-simulation-evaluation/langsmith-agent-simulation-evaluation.ipynb
|
||||
- Experimental:
|
||||
- Web Research (STORM): tutorials/storm/storm.ipynb
|
||||
- TNT-LLM: tutorials/tnt-llm/tnt-llm.ipynb
|
||||
- Web Navigation: tutorials/web-navigation/web_voyager.ipynb
|
||||
- Competitive Programming: tutorials/usaco/usaco.ipynb
|
||||
- Web Research (STORM): tutorials/storm/storm.ipynb
|
||||
- TNT-LLM: tutorials/tnt-llm/tnt-llm.ipynb
|
||||
- Web Navigation: tutorials/web-navigation/web_voyager.ipynb
|
||||
- Competitive Programming: tutorials/usaco/usaco.ipynb
|
||||
- Extract structured output: tutorials/extraction/retries.ipynb
|
||||
|
||||
- "How-to Guides":
|
||||
- 'how-tos/index.md'
|
||||
- Core:
|
||||
- Create a ReAct agent: how-tos/create-react-agent.ipynb
|
||||
- Add persistence ("memory"): how-tos/persistence.ipynb
|
||||
- View and update graph state: how-tos/time-travel.ipynb
|
||||
- Run graph asynchronously: how-tos/async.ipynb
|
||||
- Stream graph responses: how-tos/streaming-tokens.ipynb
|
||||
- Visualize your graph: how-tos/visualization.ipynb
|
||||
- Add runtime configuration: how-tos/configuration.ipynb
|
||||
- Design Patterns:
|
||||
- Create subgraphs: how-tos/subgraph.ipynb
|
||||
- Create branches for parallel execution: how-tos/branching.ipynb
|
||||
- Create map-reduce branches for parallel execution: how-tos/map-reduce.ipynb
|
||||
- Add human-in-the-loop: how-tos/human-in-the-loop.ipynb
|
||||
- Force an agent to call a tool: how-tos/force-calling-a-tool-first.ipynb
|
||||
- Pass runtime values to tools: how-tos/pass-run-time-values-to-tools.ipynb
|
||||
- Let agent return tool results directly: how-tos/dynamically-returning-directly.ipynb
|
||||
- Have agent respond in structured format: how-tos/respond-in-format.ipynb
|
||||
- Manage agent steps: how-tos/managing-agent-steps.ipynb
|
||||
- Advanced:
|
||||
- Use Pydantic model as state: how-tos/state-model.ipynb
|
||||
- Extract structured output with re-prompting: how-tos/extraction/retries.ipynb
|
||||
- 'Conceptual Guides':
|
||||
- 'concepts/index.md'
|
||||
- "how-tos/index.md"
|
||||
- Controllability:
|
||||
- Create subgraphs: how-tos/subgraph.ipynb
|
||||
- Create branches for parallel execution: how-tos/branching.ipynb
|
||||
- Create map-reduce branches for parallel execution: how-tos/map-reduce.ipynb
|
||||
- Persistence:
|
||||
- Add persistence ("memory"): how-tos/persistence.ipynb
|
||||
- Manage conversation history: how-tos/managing-conversation-history.ipynb
|
||||
- Create custom checkpointer using Postgres: how-tos/persistence_postgres.ipynb
|
||||
- Human-in-the-loop:
|
||||
- Add breakpoints: how-tos/human_in_the_loop/breakpoints.ipynb
|
||||
- Wait for user input: how-tos/human_in_the_loop/wait-user-input.ipynb
|
||||
- View and update past graph state: how-tos/human_in_the_loop/time-travel.ipynb
|
||||
- Edit graph state: how-tos/human_in_the_loop/edit-graph-state.ipynb
|
||||
- Streaming:
|
||||
- Stream full state: how-tos/stream-values.ipynb
|
||||
- Stream state updates: how-tos/stream-updates.ipynb
|
||||
- Stream LLM tokens: how-tos/streaming-tokens.ipynb
|
||||
- Stream arbitrarily nested content: how-tos/streaming-content.ipynb
|
||||
- Configure multiple streaming modes: how-tos/stream-multiple.ipynb
|
||||
- Stream events from within tools: how-tos/streaming-events-from-within-tools.ipynb
|
||||
- Stream events from the final node: how-tos/streaming-from-final-node.ipynb
|
||||
- Other:
|
||||
- Run graph asynchronously: how-tos/async.ipynb
|
||||
- Visualize your graph: how-tos/visualization.ipynb
|
||||
- Add runtime configuration: how-tos/configuration.ipynb
|
||||
- Use Pydantic model as state: how-tos/state-model.ipynb
|
||||
- Prebuilt ReAct Agent:
|
||||
- Create a ReAct agent: how-tos/create-react-agent.ipynb
|
||||
- Add memory to a ReAct agent: how-tos/create-react-agent-memory.ipynb
|
||||
- Add a system prompt to a ReAct agent: how-tos/create-react-agent-system-prompt.ipynb
|
||||
- Add human-in-the-Loop to a ReAct agent: how-tos/create-react-agent-hitl.ipynb
|
||||
- "Conceptual Guides":
|
||||
- "concepts/index.md"
|
||||
- LangGraph for Agentic Applications: concepts/high_level.md
|
||||
- Low Level LangGraph Concepts: concepts/low_level.md
|
||||
- Common Agentic Patterns: concepts/agentic_concepts.md
|
||||
- FAQ: concepts/faq.md
|
||||
- Reference:
|
||||
- Graphs: reference/graphs.md
|
||||
- Checkpointing: reference/checkpoints.md
|
||||
- Prebuilt Components: reference/prebuilt.md
|
||||
- Errors: reference/errors.md
|
||||
- 'Cloud (alpha)':
|
||||
- 'cloud/index.md'
|
||||
- "Cloud (beta)":
|
||||
- "cloud/index.md"
|
||||
- Tutorials:
|
||||
- Quick Start: 'cloud/quick_start.md'
|
||||
- SDK:
|
||||
- Python: 'cloud/sdk/python_sdk.ipynb'
|
||||
- JS/TS: 'cloud/sdk/js_sdk.ipynb'
|
||||
- Quick Start: "cloud/quick_start.md"
|
||||
- How-to Guides:
|
||||
- "cloud/how-tos/index.md"
|
||||
- Deployment:
|
||||
- Self-Hosted: 'cloud/deployment/self_hosted.md'
|
||||
- Managed: 'cloud/deployment/managed.md'
|
||||
- How-to Guides:
|
||||
- Setup App: "cloud/deployment/setup.md"
|
||||
- Setup App (pyproject.toml): "cloud/deployment/setup_pyproject.md"
|
||||
- Deploy to Cloud: "cloud/deployment/cloud.md"
|
||||
- Self-Host: "cloud/deployment/self_hosted.md"
|
||||
- Streaming:
|
||||
- Stream Messages: 'cloud/how-tos/cloud_examples/stream_messages.ipynb'
|
||||
- Stream Values: 'cloud/how-tos/cloud_examples/stream_values.ipynb'
|
||||
- Stream Updates: 'cloud/how-tos/cloud_examples/stream_updates.ipynb'
|
||||
- Double Texting:
|
||||
- Interrupt: 'cloud/how-tos/cloud_examples/interrupt_concurrent.ipynb'
|
||||
- Rollback: 'cloud/how-tos/cloud_examples/rollback_concurrent.ipynb'
|
||||
- Reject: 'cloud/how-tos/cloud_examples/reject_concurrent.ipynb'
|
||||
- Enqueue: 'cloud/how-tos/cloud_examples/enqueue_concurrent.ipynb'
|
||||
- Run Agent in Background: 'cloud/how-tos/cloud_examples/background_run.ipynb'
|
||||
- Run Multiple Agents in Thread: 'cloud/how-tos/cloud_examples/same-thread.ipynb'
|
||||
- Human in the loop: 'cloud/how-tos/cloud_examples/human-in-the-loop_cloud.ipynb'
|
||||
- Create Agents with Configuration: 'cloud/how-tos/cloud_examples/configuration_cloud.ipynb'
|
||||
- Conceptual Guides:
|
||||
- API Concepts: 'cloud/api_concepts.md'
|
||||
- Stream Values: "cloud/how-tos/cloud_examples/stream_values.ipynb"
|
||||
- Stream Updates: "cloud/how-tos/cloud_examples/stream_updates.ipynb"
|
||||
- Stream Messages: "cloud/how-tos/cloud_examples/stream_messages.ipynb"
|
||||
- Stream Events: "cloud/how-tos/cloud_examples/stream_events.ipynb"
|
||||
- Stream Debug: "cloud/how-tos/cloud_examples/stream_debug.ipynb"
|
||||
- Multiple Modes: "cloud/how-tos/cloud_examples/stream_multiple.ipynb"
|
||||
- Double Texting:
|
||||
- Interrupt: "cloud/how-tos/cloud_examples/interrupt_concurrent.ipynb"
|
||||
- Rollback: "cloud/how-tos/cloud_examples/rollback_concurrent.ipynb"
|
||||
- Reject: "cloud/how-tos/cloud_examples/reject_concurrent.ipynb"
|
||||
- Enqueue: "cloud/how-tos/cloud_examples/enqueue_concurrent.ipynb"
|
||||
- Human-in-the-Loop:
|
||||
- Add Breakpoint: "cloud/how-tos/cloud_examples/human_in_the_loop_breakpoint.ipynb"
|
||||
- Wait for User Input: "cloud/how-tos/cloud_examples/human_in_the_loop_user_input.ipynb"
|
||||
- Edit Graph State: "cloud/how-tos/cloud_examples/human_in_the_loop_edit_state.ipynb"
|
||||
- Replay and Branch from Prior States: "cloud/how-tos/cloud_examples/human_in_the_loop_time_travel.ipynb"
|
||||
- LangGraph Studio:
|
||||
- Test Cloud Deployment: "cloud/how-tos/test_deployment.md"
|
||||
- Invoke graph in LangGraph Studio: "cloud/how-tos/invoke_studio.md"
|
||||
- Interact with threads in LangGraph Studio: "cloud/how-tos/threads_studio.md"
|
||||
- Different Types of Runs:
|
||||
- Run an Agent in the Background: "cloud/how-tos/cloud_examples/background_run.ipynb"
|
||||
- Run Multiple Agents in Same Thread: "cloud/how-tos/cloud_examples/same-thread.ipynb"
|
||||
- Create Cron Jobs: "cloud/how-tos/cloud_examples/cron_jobs.ipynb"
|
||||
- Create Stateless Runs: "cloud/how-tos/cloud_examples/stateless_runs.ipynb"
|
||||
- Other:
|
||||
- Configure Agents: "cloud/how-tos/cloud_examples/configuration_cloud.ipynb"
|
||||
- Convert LangGraph calls to LangGraph Cloud calls: "cloud/how-tos/cloud_examples/langgraph_to_langgraph_cloud.ipynb"
|
||||
- Integrate Webhooks: 'cloud/how-tos/cloud_examples/webhooks.ipynb'
|
||||
- Conceptual Guides: "cloud/concepts/index.md"
|
||||
- Reference:
|
||||
- API: 'cloud/reference/api_ref.md'
|
||||
- API: "cloud/reference/api/api_ref.md"
|
||||
- SDK:
|
||||
- Python: 'cloud/reference/sdk/python_sdk_ref.md'
|
||||
- JS/TS: 'cloud/reference/sdk/js_ts_sdk_ref.md'
|
||||
- CLI: 'cloud/reference/cli.md'
|
||||
|
||||
- Python: "cloud/reference/sdk/python_sdk_ref.md"
|
||||
- JS/TS: "cloud/reference/sdk/js_ts_sdk_ref.md"
|
||||
- CLI: "cloud/reference/cli.md"
|
||||
- Environment Variables: "cloud/reference/env_var.md"
|
||||
|
||||
markdown_extensions:
|
||||
- abbr
|
||||
@@ -226,7 +247,7 @@ markdown_extensions:
|
||||
- pymdownx.magiclink:
|
||||
normalize_issue_symbols: true
|
||||
repo_url_shorthand: true
|
||||
user: langchain-ai
|
||||
user: langchain-ai
|
||||
repo: langgraph
|
||||
- pymdownx.mark
|
||||
- pymdownx.smartsymbols
|
||||
@@ -243,12 +264,15 @@ markdown_extensions:
|
||||
combine_header_slug: true
|
||||
- pymdownx.tasklist:
|
||||
custom_checkbox: true
|
||||
- markdown_include.include:
|
||||
base_path: ./
|
||||
- github-callouts
|
||||
extra_css:
|
||||
- css/mkdocstrings.css
|
||||
|
||||
extra:
|
||||
social:
|
||||
- icon: fontawesome/brands/js
|
||||
- icon: fontawesome/brands/js
|
||||
link: https://langchain-ai.github.io/langgraphjs/
|
||||
- icon: fontawesome/brands/github
|
||||
link: https://github.com/langchain-ai/langgraph
|
||||
@@ -268,5 +292,10 @@ extra:
|
||||
- icon: material/emoticon-sad-outline
|
||||
name: This page could be improved
|
||||
data: 0
|
||||
note: >-
|
||||
note: >-
|
||||
Thanks for your feedback! Please help us improve this page by adding to the discussion below.
|
||||
validation:
|
||||
omitted_files: warn
|
||||
absolute_links: warn
|
||||
unrecognized_links: warn
|
||||
anchors: warn
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 1,
|
||||
"id": "b8e6408a-b37e-428f-9567-077fa55d58e8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -26,22 +26,22 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"execution_count": 2,
|
||||
"id": "4947e9bc-111f-4991-8c41-1041da9bf0ba",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n",
|
||||
" 'graph_id': 'agent',\n",
|
||||
" 'config': {},\n",
|
||||
" 'created_at': '2024-05-18T00:19:39.688822+00:00',\n",
|
||||
" 'updated_at': '2024-05-18T00:19:39.688822+00:00',\n",
|
||||
" 'metadata': {'created_by': 'system'}}]"
|
||||
"{'assistant_id': 'e90fee30-be91-43aa-a33c-d54bd219072e',\n",
|
||||
" 'graph_id': 'agent',\n",
|
||||
" 'created_at': '2024-06-18T18:06:55.102231+00:00',\n",
|
||||
" 'updated_at': '2024-06-18T18:06:55.102231+00:00',\n",
|
||||
" 'config': {'configurable': {'model_name': 'anthropic'}},\n",
|
||||
" 'metadata': {}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
@@ -49,53 +49,36 @@
|
||||
"source": [
|
||||
"# List available assistants\n",
|
||||
"assistants = await client.assistants.search()\n",
|
||||
"assistants"
|
||||
"assistants[0]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"execution_count": 3,
|
||||
"id": "230c0464-a6e5-420f-9e38-ca514e5634ce",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n",
|
||||
" 'graph_id': 'agent',\n",
|
||||
" 'config': {},\n",
|
||||
" 'created_at': '2024-05-18T00:19:39.688822+00:00',\n",
|
||||
" 'updated_at': '2024-05-18T00:19:39.688822+00:00',\n",
|
||||
" 'metadata': {'created_by': 'system'}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Get the first assistant, we will use this one\n",
|
||||
"assistant = assistants[0]\n",
|
||||
"assistant"
|
||||
"# NOTE: we can use `assistant_id` UUID from the above response, or just pass graph ID instead when creating runs. we'll use graph ID here\n",
|
||||
"assistant_id = \"agent\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"execution_count": 4,
|
||||
"id": "56aa5159-5583-4134-9210-709b969bda6f",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n",
|
||||
" 'created_at': '2024-05-18T00:50:26.367620+00:00',\n",
|
||||
" 'updated_at': '2024-05-18T00:50:26.367620+00:00',\n",
|
||||
"{'thread_id': '5fc20631-47b7-48cd-8aa2-9f2eace9778d',\n",
|
||||
" 'created_at': '2024-06-21T14:58:02.079462+00:00',\n",
|
||||
" 'updated_at': '2024-06-21T14:58:02.079462+00:00',\n",
|
||||
" 'metadata': {}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 10,
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
@@ -108,7 +91,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"execution_count": 5,
|
||||
"id": "147c3f98-f889-4f05-a090-6b31f2a0b291",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -118,450 +101,238 @@
|
||||
"[]"
|
||||
]
|
||||
},
|
||||
"execution_count": 11,
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# If we list runs on this thread, we can see it is empty\n",
|
||||
"runs = await client.runs.list(thread['thread_id'])\n",
|
||||
"runs = await client.runs.list(thread[\"thread_id\"])\n",
|
||||
"runs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 6,
|
||||
"id": "8c7b44ef-4816-496d-88a1-2f7327cf576d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Let's kick off a run\n",
|
||||
"input = {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf\"}]}\n",
|
||||
"run = await client.runs.create(thread['thread_id'], assistant[\"assistant_id\"], input=input)\n"
|
||||
"run = await client.runs.create(thread[\"thread_id\"], assistant_id, input=input)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"execution_count": 7,
|
||||
"id": "d84b4d80-b0aa-4d9f-a05d-0744b2fe8f72",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n",
|
||||
" 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n",
|
||||
"{'run_id': '1ef2fdea-814c-6165-8b2a-a40e2a028198',\n",
|
||||
" 'thread_id': '5fc20631-47b7-48cd-8aa2-9f2eace9778d',\n",
|
||||
" 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n",
|
||||
" 'created_at': '2024-05-18T00:50:27.618761+00:00',\n",
|
||||
" 'updated_at': '2024-05-18T00:50:27.618761+00:00',\n",
|
||||
" 'created_at': '2024-06-21T14:58:02.095911+00:00',\n",
|
||||
" 'updated_at': '2024-06-21T14:58:02.095911+00:00',\n",
|
||||
" 'metadata': {},\n",
|
||||
" 'status': 'pending',\n",
|
||||
" 'metadata': {}}"
|
||||
" 'kwargs': {'input': {'messages': [{'role': 'human',\n",
|
||||
" 'content': 'what's the weather in sf'}]},\n",
|
||||
" 'config': {'metadata': {'created_by': 'system'},\n",
|
||||
" 'configurable': {'run_id': '1ef2fdea-814c-6165-8b2a-a40e2a028198',\n",
|
||||
" 'user_id': '',\n",
|
||||
" 'graph_id': 'agent',\n",
|
||||
" 'thread_id': '5fc20631-47b7-48cd-8aa2-9f2eace9778d',\n",
|
||||
" 'thread_ts': None,\n",
|
||||
" 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}},\n",
|
||||
" 'webhook': None,\n",
|
||||
" 'temporary': False,\n",
|
||||
" 'stream_mode': ['events'],\n",
|
||||
" 'feedback_keys': None,\n",
|
||||
" 'interrupt_after': None,\n",
|
||||
" 'interrupt_before': None},\n",
|
||||
" 'multitask_strategy': 'reject'}"
|
||||
]
|
||||
},
|
||||
"execution_count": 13,
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# The first time we poll it, we can see `status=pending`\n",
|
||||
"await client.runs.get(thread['thread_id'], run['run_id'])"
|
||||
"await client.runs.get(thread[\"thread_id\"], run[\"run_id\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"id": "ce124bd3-f197-4b73-9ff6-bb36730dd003",
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[{'event_id': '3ac6d963-442f-481c-9fde-b8a27bc0e277',\n",
|
||||
" 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n",
|
||||
" 'received_at': '2024-05-18T00:50:29.570649+00:00',\n",
|
||||
" 'span_id': 'd4e4a6ee-da2f-4b5f-b656-1a1f73065161',\n",
|
||||
" 'event': 'on_tool_start',\n",
|
||||
" 'name': 'tavily_search_results_json',\n",
|
||||
" 'data': {'input': {'query': 'weather in san francisco'}},\n",
|
||||
" 'metadata': {'graph_id': 'agent',\n",
|
||||
" 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n",
|
||||
" 'created_by': 'system',\n",
|
||||
" 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'},\n",
|
||||
" 'tags': ['seq:step:1']},\n",
|
||||
" {'event_id': 'f8961760-6f13-40e6-9eef-6d4d68e0ed19',\n",
|
||||
" 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n",
|
||||
" 'received_at': '2024-05-18T00:50:29.569708+00:00',\n",
|
||||
" 'span_id': '73295bb1-6cd3-403d-abc1-4f9d96a63894',\n",
|
||||
" 'event': 'on_chain_start',\n",
|
||||
" 'name': 'action',\n",
|
||||
" 'data': {},\n",
|
||||
" 'metadata': {'graph_id': 'agent',\n",
|
||||
" 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n",
|
||||
" 'created_by': 'system',\n",
|
||||
" 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'},\n",
|
||||
" 'tags': ['graph:step:2']},\n",
|
||||
" {'event_id': '44b5f815-f60c-468f-8d23-96dfdaa0ed20',\n",
|
||||
" 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n",
|
||||
" 'received_at': '2024-05-18T00:50:29.568202+00:00',\n",
|
||||
" 'span_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n",
|
||||
" 'event': 'on_chain_stream',\n",
|
||||
" 'name': 'LangGraph',\n",
|
||||
" 'data': {'chunk': {'messages': [{'id': '46b31c3a-01bf-4946-bd5a-fa6a7f6c97ce',\n",
|
||||
" 'name': None,\n",
|
||||
" 'type': 'human',\n",
|
||||
" 'content': 'what's the weather in sf',\n",
|
||||
" 'example': False,\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {}},\n",
|
||||
" {'id': 'run-4885d5a0-cd89-4f00-8558-e85b542a710c',\n",
|
||||
" 'name': None,\n",
|
||||
" 'type': 'ai',\n",
|
||||
" 'content': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n",
|
||||
" 'name': 'tavily_search_results_json',\n",
|
||||
" 'type': 'tool_use',\n",
|
||||
" 'input': {'query': 'weather in san francisco'}}],\n",
|
||||
" 'example': False,\n",
|
||||
" 'tool_calls': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n",
|
||||
" 'args': {'query': 'weather in san francisco'},\n",
|
||||
" 'name': 'tavily_search_results_json'}],\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {},\n",
|
||||
" 'invalid_tool_calls': []}]}},\n",
|
||||
" 'metadata': {'graph_id': 'agent',\n",
|
||||
" 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n",
|
||||
" 'created_by': 'system',\n",
|
||||
" 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'},\n",
|
||||
" 'tags': []},\n",
|
||||
" {'event_id': '0917b31d-f49d-43d4-a8ed-a9eebd8904e8',\n",
|
||||
" 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n",
|
||||
" 'received_at': '2024-05-18T00:50:29.566761+00:00',\n",
|
||||
" 'span_id': 'f469ac2e-17a3-491c-bc03-0c56aa30a68b',\n",
|
||||
" 'event': 'on_chain_end',\n",
|
||||
" 'name': 'agent',\n",
|
||||
" 'data': {'input': {'messages': [{'role': 'human',\n",
|
||||
" 'content': 'what's the weather in sf'}]},\n",
|
||||
" 'output': {'messages': [{'id': 'run-4885d5a0-cd89-4f00-8558-e85b542a710c',\n",
|
||||
" 'name': None,\n",
|
||||
" 'type': 'ai',\n",
|
||||
" 'content': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n",
|
||||
" 'name': 'tavily_search_results_json',\n",
|
||||
" 'type': 'tool_use',\n",
|
||||
" 'input': {'query': 'weather in san francisco'}}],\n",
|
||||
" 'example': False,\n",
|
||||
" 'tool_calls': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n",
|
||||
" 'args': {'query': 'weather in san francisco'},\n",
|
||||
" 'name': 'tavily_search_results_json'}],\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {},\n",
|
||||
" 'invalid_tool_calls': []}]}},\n",
|
||||
" 'metadata': {'graph_id': 'agent',\n",
|
||||
" 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n",
|
||||
" 'created_by': 'system',\n",
|
||||
" 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'},\n",
|
||||
" 'tags': ['graph:step:1']},\n",
|
||||
" {'event_id': 'bbe33ebf-04ba-43d5-8718-e2da23295675',\n",
|
||||
" 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n",
|
||||
" 'received_at': '2024-05-18T00:50:29.566076+00:00',\n",
|
||||
" 'span_id': 'f469ac2e-17a3-491c-bc03-0c56aa30a68b',\n",
|
||||
" 'event': 'on_chain_stream',\n",
|
||||
" 'name': 'agent',\n",
|
||||
" 'data': {'chunk': {'messages': [{'id': 'run-4885d5a0-cd89-4f00-8558-e85b542a710c',\n",
|
||||
" 'name': None,\n",
|
||||
" 'type': 'ai',\n",
|
||||
" 'content': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n",
|
||||
" 'name': 'tavily_search_results_json',\n",
|
||||
" 'type': 'tool_use',\n",
|
||||
" 'input': {'query': 'weather in san francisco'}}],\n",
|
||||
" 'example': False,\n",
|
||||
" 'tool_calls': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n",
|
||||
" 'args': {'query': 'weather in san francisco'},\n",
|
||||
" 'name': 'tavily_search_results_json'}],\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {},\n",
|
||||
" 'invalid_tool_calls': []}]}},\n",
|
||||
" 'metadata': {'graph_id': 'agent',\n",
|
||||
" 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n",
|
||||
" 'created_by': 'system',\n",
|
||||
" 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'},\n",
|
||||
" 'tags': ['graph:step:1']},\n",
|
||||
" {'event_id': '38333127-fa97-4830-8157-f76264778d81',\n",
|
||||
" 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n",
|
||||
" 'received_at': '2024-05-18T00:50:29.564195+00:00',\n",
|
||||
" 'span_id': 'ef55c681-be15-4f3d-9aee-3a9ff05d8746',\n",
|
||||
" 'event': 'on_chain_end',\n",
|
||||
" 'name': 'should_continue',\n",
|
||||
" 'data': {'input': {'messages': [{'id': 'abc3581e-417b-4ca1-ab31-de7108e64b3b',\n",
|
||||
" 'name': None,\n",
|
||||
" 'type': 'human',\n",
|
||||
" 'content': 'what's the weather in sf',\n",
|
||||
" 'example': False,\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {}},\n",
|
||||
" {'id': 'run-4885d5a0-cd89-4f00-8558-e85b542a710c',\n",
|
||||
" 'name': None,\n",
|
||||
" 'type': 'ai',\n",
|
||||
" 'content': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n",
|
||||
" 'name': 'tavily_search_results_json',\n",
|
||||
" 'type': 'tool_use',\n",
|
||||
" 'input': {'query': 'weather in san francisco'}}],\n",
|
||||
" 'example': False,\n",
|
||||
" 'tool_calls': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n",
|
||||
" 'args': {'query': 'weather in san francisco'},\n",
|
||||
" 'name': 'tavily_search_results_json'}],\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {},\n",
|
||||
" 'invalid_tool_calls': []}]},\n",
|
||||
" 'output': 'continue'},\n",
|
||||
" 'metadata': {'graph_id': 'agent',\n",
|
||||
" 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n",
|
||||
" 'created_by': 'system',\n",
|
||||
" 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'},\n",
|
||||
" 'tags': ['seq:step:3']},\n",
|
||||
" {'event_id': '408a7785-c715-4bcb-a5aa-950f414baa77',\n",
|
||||
" 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n",
|
||||
" 'received_at': '2024-05-18T00:50:29.563289+00:00',\n",
|
||||
" 'span_id': 'ef55c681-be15-4f3d-9aee-3a9ff05d8746',\n",
|
||||
" 'event': 'on_chain_start',\n",
|
||||
" 'name': 'should_continue',\n",
|
||||
" 'data': {'input': {'messages': [{'id': 'abc3581e-417b-4ca1-ab31-de7108e64b3b',\n",
|
||||
" 'name': None,\n",
|
||||
" 'type': 'human',\n",
|
||||
" 'content': 'what's the weather in sf',\n",
|
||||
" 'example': False,\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {}},\n",
|
||||
" {'id': 'run-4885d5a0-cd89-4f00-8558-e85b542a710c',\n",
|
||||
" 'name': None,\n",
|
||||
" 'type': 'ai',\n",
|
||||
" 'content': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n",
|
||||
" 'name': 'tavily_search_results_json',\n",
|
||||
" 'type': 'tool_use',\n",
|
||||
" 'input': {'query': 'weather in san francisco'}}],\n",
|
||||
" 'example': False,\n",
|
||||
" 'tool_calls': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n",
|
||||
" 'args': {'query': 'weather in san francisco'},\n",
|
||||
" 'name': 'tavily_search_results_json'}],\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {},\n",
|
||||
" 'invalid_tool_calls': []}]}},\n",
|
||||
" 'metadata': {'graph_id': 'agent',\n",
|
||||
" 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n",
|
||||
" 'created_by': 'system',\n",
|
||||
" 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'},\n",
|
||||
" 'tags': ['seq:step:3']},\n",
|
||||
" {'event_id': '679c8ae7-5cd2-4462-936e-20f0ea45cfb8',\n",
|
||||
" 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n",
|
||||
" 'received_at': '2024-05-18T00:50:29.560628+00:00',\n",
|
||||
" 'span_id': '4885d5a0-cd89-4f00-8558-e85b542a710c',\n",
|
||||
" 'event': 'on_chat_model_end',\n",
|
||||
" 'name': 'ChatAnthropic',\n",
|
||||
" 'data': {'input': {'messages': [[{'id': None,\n",
|
||||
" 'name': None,\n",
|
||||
" 'type': 'human',\n",
|
||||
" 'content': 'what's the weather in sf',\n",
|
||||
" 'example': False,\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {}}]]},\n",
|
||||
" 'output': {'run': None,\n",
|
||||
" 'llm_output': None,\n",
|
||||
" 'generations': [[{'text': '',\n",
|
||||
" 'type': 'ChatGeneration',\n",
|
||||
" 'message': {'id': 'run-4885d5a0-cd89-4f00-8558-e85b542a710c',\n",
|
||||
" 'name': None,\n",
|
||||
" 'type': 'ai',\n",
|
||||
" 'content': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n",
|
||||
" 'name': 'tavily_search_results_json',\n",
|
||||
" 'type': 'tool_use',\n",
|
||||
" 'input': {'query': 'weather in san francisco'}}],\n",
|
||||
" 'example': False,\n",
|
||||
" 'tool_calls': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n",
|
||||
" 'args': {'query': 'weather in san francisco'},\n",
|
||||
" 'name': 'tavily_search_results_json'}],\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {},\n",
|
||||
" 'invalid_tool_calls': []},\n",
|
||||
" 'generation_info': None}]]}},\n",
|
||||
" 'metadata': {'graph_id': 'agent',\n",
|
||||
" 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n",
|
||||
" 'created_by': 'system',\n",
|
||||
" 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n",
|
||||
" 'ls_model_type': 'chat'},\n",
|
||||
" 'tags': ['seq:step:1']},\n",
|
||||
" {'event_id': '055fcf73-36e7-444b-990d-6263ec50925c',\n",
|
||||
" 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n",
|
||||
" 'received_at': '2024-05-18T00:50:29.559491+00:00',\n",
|
||||
" 'span_id': '4885d5a0-cd89-4f00-8558-e85b542a710c',\n",
|
||||
" 'event': 'on_chat_model_stream',\n",
|
||||
" 'name': 'ChatAnthropic',\n",
|
||||
" 'data': {'chunk': {'id': 'run-4885d5a0-cd89-4f00-8558-e85b542a710c',\n",
|
||||
" 'name': None,\n",
|
||||
" 'type': 'AIMessageChunk',\n",
|
||||
" 'content': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n",
|
||||
" 'name': 'tavily_search_results_json',\n",
|
||||
" 'type': 'tool_use',\n",
|
||||
" 'input': {'query': 'weather in san francisco'}}],\n",
|
||||
" 'example': False,\n",
|
||||
" 'tool_calls': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n",
|
||||
" 'args': {'query': 'weather in san francisco'},\n",
|
||||
" 'name': 'tavily_search_results_json'}],\n",
|
||||
" 'tool_call_chunks': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n",
|
||||
" 'args': '{\"query\": \"weather in san francisco\"}',\n",
|
||||
" 'name': 'tavily_search_results_json',\n",
|
||||
" 'index': 0}],\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {},\n",
|
||||
" 'invalid_tool_calls': []}},\n",
|
||||
" 'metadata': {'graph_id': 'agent',\n",
|
||||
" 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n",
|
||||
" 'created_by': 'system',\n",
|
||||
" 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n",
|
||||
" 'ls_model_type': 'chat'},\n",
|
||||
" 'tags': ['seq:step:1']},\n",
|
||||
" {'event_id': 'cf90f755-5a12-46bd-8f60-adc862388635',\n",
|
||||
" 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n",
|
||||
" 'received_at': '2024-05-18T00:50:27.873602+00:00',\n",
|
||||
" 'span_id': '4885d5a0-cd89-4f00-8558-e85b542a710c',\n",
|
||||
" 'event': 'on_chat_model_start',\n",
|
||||
" 'name': 'ChatAnthropic',\n",
|
||||
" 'data': {'input': {'messages': [[{'id': None,\n",
|
||||
" 'name': None,\n",
|
||||
" 'type': 'human',\n",
|
||||
" 'content': 'what's the weather in sf',\n",
|
||||
" 'example': False,\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {}}]]}},\n",
|
||||
" 'metadata': {'graph_id': 'agent',\n",
|
||||
" 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n",
|
||||
" 'created_by': 'system',\n",
|
||||
" 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n",
|
||||
" 'ls_model_type': 'chat'},\n",
|
||||
" 'tags': ['seq:step:1']}]"
|
||||
]
|
||||
},
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"execution_count": 8,
|
||||
"id": "3639da3c-bfe5-454c-ab1e-8ed7af394dfe",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# We can list events for the run\n",
|
||||
"await client.runs.list_events(thread['thread_id'], run['run_id'])"
|
||||
"# Wait until the run finishes\n",
|
||||
"await client.runs.join(thread[\"thread_id\"], run[\"run_id\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"execution_count": 9,
|
||||
"id": "8fa206ed-515e-4607-9a80-bebafe76cc24",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n",
|
||||
" 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n",
|
||||
"{'run_id': '1ef2fdea-814c-6165-8b2a-a40e2a028198',\n",
|
||||
" 'thread_id': '5fc20631-47b7-48cd-8aa2-9f2eace9778d',\n",
|
||||
" 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n",
|
||||
" 'created_at': '2024-05-18T00:50:27.618761+00:00',\n",
|
||||
" 'updated_at': '2024-05-18T00:50:27.618761+00:00',\n",
|
||||
" 'created_at': '2024-06-21T14:58:02.095911+00:00',\n",
|
||||
" 'updated_at': '2024-06-21T14:58:02.095911+00:00',\n",
|
||||
" 'metadata': {},\n",
|
||||
" 'status': 'success',\n",
|
||||
" 'metadata': {}}"
|
||||
" 'kwargs': {'input': {'messages': [{'role': 'human',\n",
|
||||
" 'content': 'what's the weather in sf'}]},\n",
|
||||
" 'config': {'metadata': {'created_by': 'system'},\n",
|
||||
" 'configurable': {'run_id': '1ef2fdea-814c-6165-8b2a-a40e2a028198',\n",
|
||||
" 'user_id': '',\n",
|
||||
" 'graph_id': 'agent',\n",
|
||||
" 'thread_id': '5fc20631-47b7-48cd-8aa2-9f2eace9778d',\n",
|
||||
" 'thread_ts': None,\n",
|
||||
" 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}},\n",
|
||||
" 'webhook': None,\n",
|
||||
" 'temporary': False,\n",
|
||||
" 'stream_mode': ['events'],\n",
|
||||
" 'feedback_keys': None,\n",
|
||||
" 'interrupt_after': None,\n",
|
||||
" 'interrupt_before': None},\n",
|
||||
" 'multitask_strategy': 'reject'}"
|
||||
]
|
||||
},
|
||||
"execution_count": 15,
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Eventually, it should finish and we should see `status=success`\n",
|
||||
"await client.runs.get(thread['thread_id'], run['run_id'])"
|
||||
"await client.runs.get(thread[\"thread_id\"], run[\"run_id\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"execution_count": 10,
|
||||
"id": "8de4495f-7873-487c-b1a8-ad2a78a1ff35",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# We can get the final results\n",
|
||||
"results = await client.runs.list_events(thread['thread_id'], run['run_id'])"
|
||||
"final_result = await client.threads.get_state(thread[\"thread_id\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 21,
|
||||
"execution_count": 11,
|
||||
"id": "9da76fce-66e4-4f1b-8c24-09759889e50e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The results are sorted by time, so the most recent (final) step is the 0 index\n",
|
||||
"final_result = results[0]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 22,
|
||||
"id": "02279ff3-c153-4ec4-be4d-1613a0dff4ee",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'event_id': '1af2076e-8ec7-4f2e-bc2c-6fbbf586397c',\n",
|
||||
" 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n",
|
||||
" 'received_at': '2024-05-18T00:50:35.557925+00:00',\n",
|
||||
" 'span_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n",
|
||||
" 'event': 'on_chain_end',\n",
|
||||
" 'name': 'LangGraph',\n",
|
||||
" 'data': {'output': {'messages': [{'id': '46b31c3a-01bf-4946-bd5a-fa6a7f6c97ce',\n",
|
||||
" 'name': None,\n",
|
||||
" 'type': 'human',\n",
|
||||
" 'content': 'what's the weather in sf',\n",
|
||||
" 'example': False,\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {}},\n",
|
||||
" {'id': 'run-4885d5a0-cd89-4f00-8558-e85b542a710c',\n",
|
||||
" 'name': None,\n",
|
||||
" 'type': 'ai',\n",
|
||||
" 'content': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n",
|
||||
" 'name': 'tavily_search_results_json',\n",
|
||||
" 'type': 'tool_use',\n",
|
||||
" 'input': {'query': 'weather in san francisco'}}],\n",
|
||||
" 'example': False,\n",
|
||||
" 'tool_calls': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n",
|
||||
" 'args': {'query': 'weather in san francisco'},\n",
|
||||
" 'name': 'tavily_search_results_json'}],\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {},\n",
|
||||
" 'invalid_tool_calls': []},\n",
|
||||
" {'id': '045e936d-ee47-4236-95ff-793b6b32b590',\n",
|
||||
" 'name': 'tavily_search_results_json',\n",
|
||||
" 'type': 'tool',\n",
|
||||
" 'content': '[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1715993410, \\'localtime\\': \\'2024-05-17 17:50\\'}, \\'current\\': {\\'last_updated_epoch\\': 1715993100, \\'last_updated\\': \\'2024-05-17 17:45\\', \\'temp_c\\': 17.8, \\'temp_f\\': 64.0, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Partly cloudy\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/116.png\\', \\'code\\': 1003}, \\'wind_mph\\': 15.0, \\'wind_kph\\': 24.1, \\'wind_degree\\': 300, \\'wind_dir\\': \\'WNW\\', \\'pressure_mb\\': 1013.0, \\'pressure_in\\': 29.9, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 65, \\'cloud\\': 25, \\'feelslike_c\\': 17.8, \\'feelslike_f\\': 64.0, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 5.0, \\'gust_mph\\': 16.2, \\'gust_kph\\': 26.1}}\"}]',\n",
|
||||
" 'tool_call_id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {}},\n",
|
||||
" {'id': 'run-069f1ddd-64b3-451d-bb84-75dec23a7286',\n",
|
||||
" 'name': None,\n",
|
||||
" 'type': 'ai',\n",
|
||||
" 'content': \"The search results provide the current weather conditions in San Francisco. According to the data, as of 5:45pm on May 17, 2024, the weather in San Francisco is partly cloudy with a temperature of around 64°F (17.8°C). The wind is blowing from the west-northwest at 15 mph (24 km/h) with gusts up to 16 mph (26 km/h). The humidity is 65% and visibility is 9 miles (16 km). The UV index is 5.\\n\\nSo in summary, it's a partly cloudy spring day in San Francisco with mild temperatures and moderate winds. The weather seems pleasant for being outdoors during the daytime hours.\",\n",
|
||||
" 'example': False,\n",
|
||||
" 'tool_calls': [],\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {},\n",
|
||||
" 'invalid_tool_calls': []}]}},\n",
|
||||
" 'metadata': {'graph_id': 'agent',\n",
|
||||
" 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n",
|
||||
"{'values': {'messages': [{'content': 'what's the weather in sf',\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {},\n",
|
||||
" 'type': 'human',\n",
|
||||
" 'name': None,\n",
|
||||
" 'id': 'bfe07fff-cb40-40be-84d5-a061d2c40006',\n",
|
||||
" 'example': False},\n",
|
||||
" {'content': [{'id': 'toolu_01QUzhhfDQkpbPSediUrXvQb',\n",
|
||||
" 'input': {'query': 'weather in san francisco'},\n",
|
||||
" 'name': 'tavily_search_results_json',\n",
|
||||
" 'type': 'tool_use'}],\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {},\n",
|
||||
" 'type': 'ai',\n",
|
||||
" 'name': None,\n",
|
||||
" 'id': 'run-6d8665ca-a77d-4b44-9a7b-4e975b155fb1',\n",
|
||||
" 'example': False,\n",
|
||||
" 'tool_calls': [{'name': 'tavily_search_results_json',\n",
|
||||
" 'args': {'query': 'weather in san francisco'},\n",
|
||||
" 'id': 'toolu_01QUzhhfDQkpbPSediUrXvQb'}],\n",
|
||||
" 'invalid_tool_calls': [],\n",
|
||||
" 'usage_metadata': None},\n",
|
||||
" {'content': '[{\"url\": \"https://www.timeanddate.com/weather/usa/san-francisco/historic\", \"content\": \"San Francisco Weather History for the Previous 24 Hours Show weather for: Previous 24 hours June 17, 2024 June 16, 2024 June 15, 2024 June 14, 2024 June 13, 2024 June 12, 2024 June 11, 2024 June 10, 2024 June 9, 2024 June 8, 2024 June 7, 2024 June 6, 2024 June 5, 2024 June 4, 2024 June 3, 2024 June 2, 2024\"}]',\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {},\n",
|
||||
" 'type': 'tool',\n",
|
||||
" 'name': 'tavily_search_results_json',\n",
|
||||
" 'id': '257a1f29-2f66-4f9e-b35d-c8818dbbaa3f',\n",
|
||||
" 'tool_call_id': 'toolu_01QUzhhfDQkpbPSediUrXvQb'},\n",
|
||||
" {'content': [{'text': 'The search results provide historic weather data for San Francisco, but do not give the current weather conditions. To get the current weather forecast for San Francisco, I would need to refine my search query. Here is an updated search:',\n",
|
||||
" 'type': 'text'},\n",
|
||||
" {'id': 'toolu_01RLJEcWYRvRoBhiHdrhoRZx',\n",
|
||||
" 'input': {'query': 'san francisco weather forecast today'},\n",
|
||||
" 'name': 'tavily_search_results_json',\n",
|
||||
" 'type': 'tool_use'}],\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {},\n",
|
||||
" 'type': 'ai',\n",
|
||||
" 'name': None,\n",
|
||||
" 'id': 'run-ca41dbf8-7e89-4ff2-a245-87098d7928ba',\n",
|
||||
" 'example': False,\n",
|
||||
" 'tool_calls': [{'name': 'tavily_search_results_json',\n",
|
||||
" 'args': {'query': 'san francisco weather forecast today'},\n",
|
||||
" 'id': 'toolu_01RLJEcWYRvRoBhiHdrhoRZx'}],\n",
|
||||
" 'invalid_tool_calls': [],\n",
|
||||
" 'usage_metadata': None},\n",
|
||||
" {'content': '[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1718981382, \\'localtime\\': \\'2024-06-21 7:49\\'}, \\'current\\': {\\'last_updated_epoch\\': 1718981100, \\'last_updated\\': \\'2024-06-21 07:45\\', \\'temp_c\\': 12.8, \\'temp_f\\': 55.0, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Overcast\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/122.png\\', \\'code\\': 1009}, \\'wind_mph\\': 6.9, \\'wind_kph\\': 11.2, \\'wind_degree\\': 200, \\'wind_dir\\': \\'SSW\\', \\'pressure_mb\\': 1011.0, \\'pressure_in\\': 29.84, \\'precip_mm\\': 0.01, \\'precip_in\\': 0.0, \\'humidity\\': 86, \\'cloud\\': 100, \\'feelslike_c\\': 12.2, \\'feelslike_f\\': 53.9, \\'windchill_c\\': 11.2, \\'windchill_f\\': 52.1, \\'heatindex_c\\': 12.0, \\'heatindex_f\\': 53.5, \\'dewpoint_c\\': 9.4, \\'dewpoint_f\\': 48.8, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 3.0, \\'gust_mph\\': 7.6, \\'gust_kph\\': 12.2}}\"}]',\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {},\n",
|
||||
" 'type': 'tool',\n",
|
||||
" 'name': 'tavily_search_results_json',\n",
|
||||
" 'id': 'c80a3720-6a9f-4ff0-9ce2-6112e66a6f81',\n",
|
||||
" 'tool_call_id': 'toolu_01RLJEcWYRvRoBhiHdrhoRZx'},\n",
|
||||
" {'content': 'The updated search provides the current weather forecast for San Francisco. According to the results, as of 7:49am on June 21, 2024 in San Francisco, the temperature is 55°F (12.8°C), it is overcast with 100% cloud cover, and there are light winds from the south-southwest around 7 mph (11 km/h). The forecast also shows low precipitation of 0.01 mm, high humidity of 86%, and visibility of 9 miles (16 km).\\n\\nIn summary, the current weather in San Francisco is cool, overcast, and breezy based on this weather forecast data. Let me know if you need any other details!',\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {},\n",
|
||||
" 'type': 'ai',\n",
|
||||
" 'name': None,\n",
|
||||
" 'id': 'run-4f23b53d-a8ec-4038-b3ed-08b2560bf81c',\n",
|
||||
" 'example': False,\n",
|
||||
" 'tool_calls': [],\n",
|
||||
" 'invalid_tool_calls': [],\n",
|
||||
" 'usage_metadata': None}]},\n",
|
||||
" 'next': [],\n",
|
||||
" 'config': {'configurable': {'thread_id': '5fc20631-47b7-48cd-8aa2-9f2eace9778d',\n",
|
||||
" 'thread_ts': '1ef2fdea-f879-65a5-8005-443b6a4039aa'}},\n",
|
||||
" 'metadata': {'step': 5,\n",
|
||||
" 'run_id': '1ef2fdea-814c-6165-8b2a-a40e2a028198',\n",
|
||||
" 'source': 'loop',\n",
|
||||
" 'writes': {'agent': {'messages': [{'id': 'run-4f23b53d-a8ec-4038-b3ed-08b2560bf81c',\n",
|
||||
" 'name': None,\n",
|
||||
" 'type': 'ai',\n",
|
||||
" 'content': 'The updated search provides the current weather forecast for San Francisco. According to the results, as of 7:49am on June 21, 2024 in San Francisco, the temperature is 55°F (12.8°C), it is overcast with 100% cloud cover, and there are light winds from the south-southwest around 7 mph (11 km/h). The forecast also shows low precipitation of 0.01 mm, high humidity of 86%, and visibility of 9 miles (16 km).\\n\\nIn summary, the current weather in San Francisco is cool, overcast, and breezy based on this weather forecast data. Let me know if you need any other details!',\n",
|
||||
" 'example': False,\n",
|
||||
" 'tool_calls': [],\n",
|
||||
" 'usage_metadata': None,\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {},\n",
|
||||
" 'invalid_tool_calls': []}]}},\n",
|
||||
" 'user_id': '',\n",
|
||||
" 'graph_id': 'agent',\n",
|
||||
" 'thread_id': '5fc20631-47b7-48cd-8aa2-9f2eace9778d',\n",
|
||||
" 'created_by': 'system',\n",
|
||||
" 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'},\n",
|
||||
" 'tags': []}"
|
||||
" 'created_at': '2024-06-21T14:58:14.591805+00:00',\n",
|
||||
" 'parent_config': {'configurable': {'thread_id': '5fc20631-47b7-48cd-8aa2-9f2eace9778d',\n",
|
||||
" 'thread_ts': '1ef2fdea-d44c-6fc4-8004-d2713436777d'}}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 22,
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
@@ -572,40 +343,32 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 28,
|
||||
"execution_count": 12,
|
||||
"id": "ddd6e698-4609-4389-b84a-bb8939fff08b",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"\"The search results provide the current weather conditions in San Francisco. According to the data, as of 5:45pm on May 17, 2024, the weather in San Francisco is partly cloudy with a temperature of around 64°F (17.8°C). The wind is blowing from the west-northwest at 15 mph (24 km/h) with gusts up to 16 mph (26 km/h). The humidity is 65% and visibility is 9 miles (16 km). The UV index is 5.\\n\\nSo in summary, it's a partly cloudy spring day in San Francisco with mild temperatures and moderate winds. The weather seems pleasant for being outdoors during the daytime hours.\""
|
||||
"'The updated search provides the current weather forecast for San Francisco. According to the results, as of 7:49am on June 21, 2024 in San Francisco, the temperature is 55°F (12.8°C), it is overcast with 100% cloud cover, and there are light winds from the south-southwest around 7 mph (11 km/h). The forecast also shows low precipitation of 0.01 mm, high humidity of 86%, and visibility of 9 miles (16 km).\\n\\nIn summary, the current weather in San Francisco is cool, overcast, and breezy based on this weather forecast data. Let me know if you need any other details!'"
|
||||
]
|
||||
},
|
||||
"execution_count": 28,
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# We can get the content of the final message\n",
|
||||
"final_result['data']['output']['messages'][-1]['content']"
|
||||
"final_result[\"values\"][\"messages\"][-1][\"content\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "535638f7-48a9-49bb-9a0b-57a5b36d0696",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"display_name": "langgraph-example-dev",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
"name": "langgraph-example-dev"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
@@ -617,7 +380,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.1"
|
||||
"version": "3.11.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"# We can do this by getting the default assistant\n",
|
||||
"# There should always be a default assistant with no configuration\n",
|
||||
"assistants = await client.assistants.search()\n",
|
||||
"assistants = [a for a in assistants if not a['config']]\n",
|
||||
"assistants = [a for a in assistants if not a[\"config\"]]\n",
|
||||
"base_assistant = assistants[0]"
|
||||
]
|
||||
},
|
||||
@@ -93,10 +93,12 @@
|
||||
],
|
||||
"source": [
|
||||
"# We can now call `.get_schemas` to get schemas associated with this graph\n",
|
||||
"schemas = await client.assistants.get_schemas(assistant_id=base_assistant[\"assistant_id\"])\n",
|
||||
"schemas = await client.assistants.get_schemas(\n",
|
||||
" assistant_id=base_assistant[\"assistant_id\"]\n",
|
||||
")\n",
|
||||
"# There are multiple types of schemas\n",
|
||||
"# We can get the `config_schema` to look at the the configurable parameters\n",
|
||||
"schemas['config_schema']['definitions']['Configurable']['properties']"
|
||||
"schemas[\"config_schema\"][\"definitions\"][\"Configurable\"][\"properties\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -106,7 +108,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"assistant = await client.assistants.create(graph_id=\"agent\", config={\"configurable\": {\"model_name\": \"openai\"}})"
|
||||
"assistant = await client.assistants.create(\n",
|
||||
" graph_id=\"agent\", config={\"configurable\": {\"model_name\": \"openai\"}}\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -163,7 +167,9 @@
|
||||
"source": [
|
||||
"thread = await client.threads.create()\n",
|
||||
"input = {\"messages\": [{\"role\": \"user\", \"content\": \"who made you?\"}]}\n",
|
||||
"async for event in client.runs.stream(thread['thread_id'], assistant['assistant_id'], input=input):\n",
|
||||
"async for event in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"], assistant[\"assistant_id\"], input=input\n",
|
||||
"):\n",
|
||||
" print(event)"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Cron Jobs\n",
|
||||
"\n",
|
||||
"Sometimes you don't want to run your graph based on user interaction, but rather you would like to schedule your graph to run on a schedule - for example if you wish for your graph to compose and send out a weekly email of to-dos for your team. LangGraph Cloud allows you to do this without having to write your own script by using the `Crons` client. To schedule a graph job, you need to pass a [cron expression](https://crontab.cronhub.io/) to inform the client when you want to run the graph. `Cron` jobs are run in the background and do not interfere with normal invocations of the graph.\n",
|
||||
"\n",
|
||||
"## Setup\n",
|
||||
"\n",
|
||||
"First, let's setup our SDK client, assistant, and thread:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 110,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph_sdk import get_client\n",
|
||||
"client = get_client()\n",
|
||||
"assistants = await client.assistants.search()\n",
|
||||
"assistants = [a for a in assistants if not a['config']]\n",
|
||||
"assistant = assistants[0]\n",
|
||||
"thread = await client.threads.create()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Cron job on a thread \n",
|
||||
"\n",
|
||||
"To create a cron job associated with a specific thread, you can write:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# This schedules a job to run at 15:27 (3:27PM) every day\n",
|
||||
"cron_1 = await client.crons.create_for_thread(thread['thread_id'],assistant['assistant_id'],schedule=\"27 15 * * *\",input={'messages':[{\"role\":\"user\",\"content\":\"What time is it?\"}]})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Note that it is **very** important to delete `Cron` jobs that are no longer useful. Otherwise you could rack up unwanted API charges to the LLM! You can delete a `Cron` job using the following code:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"await client.crons.delete(cron_1['cron_id'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Cron job stateless\n",
|
||||
"\n",
|
||||
"You can also create stateless cron jobs by using the following code:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# This schedules a job to run at 15:27 (3:27PM) every day\n",
|
||||
"cron_2 = await client.crons.create(assistant['assistant_id'],schedule=\"27 15 * * *\",input={'messages':[{\"role\":\"user\",\"content\":\"What time is it?\"}]})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Again, remember to delete your job once you are done with it!"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"await client.crons.delete(cron_2['cron_id'])"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -6,7 +6,9 @@
|
||||
"source": [
|
||||
"## Enqueue\n",
|
||||
"\n",
|
||||
"There are several strategies for handling concurrent runs in your graph. This notebook covers how to use the `enqueue` option - please see the other how-to guides in the \"Double Texting\" directory to learn about the other methods.\n",
|
||||
"This notebook assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](https://langchain-ai.github.io/langgraph/cloud/concepts/#double-texting).\n",
|
||||
"\n",
|
||||
"The guide covers the `enqueue` option for double texting, which adds the interruptions to a queue and executes them in the order they are received by the client. Below is a quick example of using the `enqueue` option.\n",
|
||||
"\n",
|
||||
"First, let's import our required packages and instantiate our client, assistant, and thread."
|
||||
]
|
||||
@@ -28,7 +30,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"client = get_client()\n",
|
||||
"assistant = await client.assistants.create(\"agent\")\n",
|
||||
"assistant_id = \"agent\"\n",
|
||||
"thread = await client.threads.create()"
|
||||
]
|
||||
},
|
||||
@@ -41,7 +43,7 @@
|
||||
"# this run will be interrupted\n",
|
||||
"first_run = await client.runs.create(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"],\n",
|
||||
" assistant_id,\n",
|
||||
" input={\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf?\"}]},\n",
|
||||
")"
|
||||
]
|
||||
@@ -54,7 +56,7 @@
|
||||
"source": [
|
||||
"second_run = await client.runs.create(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"],\n",
|
||||
" assistant_id,\n",
|
||||
" input={\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in nyc?\"}]},\n",
|
||||
" multitask_strategy=\"enqueue\",\n",
|
||||
")"
|
||||
@@ -175,10 +177,14 @@
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "",
|
||||
"name": ""
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
|
||||
@@ -1,685 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to have a human in the loop\n",
|
||||
"\n",
|
||||
"With it's built in persistence layer, LangGraph API is perfect for human-in-the-loop workflows.\n",
|
||||
"Here we cover a few such examples:\n",
|
||||
"\n",
|
||||
"1. Having a human in the loop to approve a tool call\n",
|
||||
"2. Having a human in the loop to edit a tool call\n",
|
||||
"3. Having a human in the loop to edit an old state and resume execution from there\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "521d975b-e94b-4c37-bfa1-82d969e2a4dc",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph_sdk import get_client"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "27a1392b-86c3-464e-99a8-90ffc965f3ec",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"client = get_client()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "4947e9bc-111f-4991-8c41-1041da9bf0ba",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n",
|
||||
" 'graph_id': 'agent',\n",
|
||||
" 'config': {},\n",
|
||||
" 'created_at': '2024-05-18T00:19:39.688822+00:00',\n",
|
||||
" 'updated_at': '2024-05-18T00:19:39.688822+00:00',\n",
|
||||
" 'metadata': {'created_by': 'system'}}]"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# There should always be a default assistant with no configuration\n",
|
||||
"assistants = await client.assistants.search()\n",
|
||||
"assistants = [a for a in assistants if not a['config']]\n",
|
||||
"assistants"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "230c0464-a6e5-420f-9e38-ca514e5634ce",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n",
|
||||
" 'graph_id': 'agent',\n",
|
||||
" 'config': {},\n",
|
||||
" 'created_at': '2024-05-18T00:19:39.688822+00:00',\n",
|
||||
" 'updated_at': '2024-05-18T00:19:39.688822+00:00',\n",
|
||||
" 'metadata': {'created_by': 'system'}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"assistant = assistants[0]\n",
|
||||
"assistant"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e0209129-239b-452e-a59a-47be716bbf8c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Approve a tool call"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "56aa5159-5583-4134-9210-709b969bda6f",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'thread_id': '54ed0901-6767-46c9-a5f9-b65c1c5fd89c',\n",
|
||||
" 'created_at': '2024-05-18T22:46:16.724701+00:00',\n",
|
||||
" 'updated_at': '2024-05-18T22:46:16.724701+00:00',\n",
|
||||
" 'metadata': {}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"thread = await client.threads.create()\n",
|
||||
"thread"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "147c3f98-f889-4f05-a090-6b31f2a0b291",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[]"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"runs = await client.runs.list(thread['thread_id'])\n",
|
||||
"runs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "77dae6ad-bb7b-468d-b7fd-9b8a35f13ccb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We now want to add a human-in-the-loop step before a tool is called.\n",
|
||||
"We can do this by adding `interrupt_before=[\"action\"]`, which tells us to interrupt before calling the action node.\n",
|
||||
"We can do this either when compiling the graph or when kicking off a run.\n",
|
||||
"Here we will do it when kicking of a run."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "7da70e20-1a4e-4df2-b996-1927f474c835",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Receiving new event of type: metadata...\n",
|
||||
"{'run_id': '3b77ef83-687a-4840-8858-0371f91a92c3'}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: data...\n",
|
||||
"{'agent': {'messages': [{'content': [{'id': 'toolu_01HwZqM1ptX6E15A5LAmyZTB', 'input': {'query': 'weather in san francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-e5d17791-4d37-4ad2-815f-a0c4cba62585', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in san francisco'}, 'id': 'toolu_01HwZqM1ptX6E15A5LAmyZTB'}], 'invalid_tool_calls': []}]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: end...\n",
|
||||
"None\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf\"}]}\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread['thread_id'], assistant['assistant_id'], input=input, stream_mode=\"updates\", interrupt_before=['action']\n",
|
||||
"):\n",
|
||||
" print(f\"Receiving new event of type: {chunk.event}...\")\n",
|
||||
" print(chunk.data)\n",
|
||||
" print(\"\\n\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a36ac0d6-7843-4fab-909c-0b5b6e725a7f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can now kick off a new run on the same thread with `None` as the input in order to just continue the existing thread."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "bded66c7-b56e-4db5-809f-fa5a31d8a012",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Receiving new event of type: metadata...\n",
|
||||
"{'run_id': 'a46f733d-cf5b-4ee3-9e07-08612468c8df'}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: data...\n",
|
||||
"{'action': {'messages': [{'content': '[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1716072201, \\'localtime\\': \\'2024-05-18 15:43\\'}, \\'current\\': {\\'last_updated_epoch\\': 1716071400, \\'last_updated\\': \\'2024-05-18 15:30\\', \\'temp_c\\': 18.9, \\'temp_f\\': 66.0, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Partly cloudy\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/116.png\\', \\'code\\': 1003}, \\'wind_mph\\': 18.6, \\'wind_kph\\': 29.9, \\'wind_degree\\': 280, \\'wind_dir\\': \\'W\\', \\'pressure_mb\\': 1015.0, \\'pressure_in\\': 29.96, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 59, \\'cloud\\': 25, \\'feelslike_c\\': 18.9, \\'feelslike_f\\': 66.0, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 5.0, \\'gust_mph\\': 23.0, \\'gust_kph\\': 37.1}}\"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': '8be98ff3-6d61-41c5-8384-8db6b7abdbfb', 'tool_call_id': 'toolu_01HwZqM1ptX6E15A5LAmyZTB'}]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: data...\n",
|
||||
"{'agent': {'messages': [{'content': \"The weather in San Francisco is currently partly cloudy with a temperature of around 66°F (18.9°C). There are westerly winds of 18.6 mph (29.9 km/h) with gusts up to 23 mph (37.1 km/h). The humidity is 59% and visibility is good at 9 miles (16 km). UV levels are moderate at 5.0.\\n\\nIn summary, it's a nice partly cloudy spring day in San Francisco with comfortable temperatures and a moderate breeze. The weather conditions seem ideal for being outdoors and enjoying the city.\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-7a8a2ff8-d0d6-4200-b0a5-926f2b6a4798', 'example': False, 'tool_calls': [], 'invalid_tool_calls': []}]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: end...\n",
|
||||
"None\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = None\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread['thread_id'], assistant['assistant_id'], input=input, stream_mode=\"updates\", interrupt_before=['action']\n",
|
||||
"):\n",
|
||||
" print(f\"Receiving new event of type: {chunk.event}...\")\n",
|
||||
" print(chunk.data)\n",
|
||||
" print(\"\\n\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2072ce5a-8771-42f9-b2de-5d3a7a9c817b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Edit a tool call\n",
|
||||
"\n",
|
||||
"What if we want to edit the tool call?\n",
|
||||
"We can also do that.\n",
|
||||
"Let's kick off another run, with the same `interrupt_before=['action']`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "b226b687-02da-4eef-9286-46dba92b17ba",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Receiving new event of type: metadata...\n",
|
||||
"{'run_id': 'c7c8e313-dad9-47d9-bd03-e112c94eff9e'}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: data...\n",
|
||||
"{'agent': {'messages': [{'content': [{'id': 'toolu_01NGhKmeciaT7TfhBSwUT3mi', 'input': {'query': 'weather in los angeles'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-3d417aa5-e9c1-4b76-90f8-597519c28af9', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in los angeles'}, 'id': 'toolu_01NGhKmeciaT7TfhBSwUT3mi'}], 'invalid_tool_calls': []}]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: end...\n",
|
||||
"None\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in la?\"}]}\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread['thread_id'], assistant['assistant_id'], input=input, stream_mode=\"updates\", interrupt_before=['action']\n",
|
||||
"):\n",
|
||||
" print(f\"Receiving new event of type: {chunk.event}...\")\n",
|
||||
" print(chunk.data)\n",
|
||||
" print(\"\\n\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ab338423-c18d-446c-9aa3-3ad2f16d742a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can now inspect the state of the thread"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "bd9ca1f4-c3b0-4fa3-8c91-233a9129a142",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"thread_state = await client.threads.get_state(thread['thread_id'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "31e82414-afd2-46c4-a605-ce3eb46df485",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's get the last message of the thread - this is the one we want to update"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "fe832ec1-7ae0-4d11-8408-d4da88d4dced",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"last_message = thread_state['values']['messages'][-1]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "434253fe-7397-45e2-8be8-91d002088a96",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[{'id': 'toolu_01NGhKmeciaT7TfhBSwUT3mi',\n",
|
||||
" 'input': {'query': 'weather in los angeles'},\n",
|
||||
" 'name': 'tavily_search_results_json',\n",
|
||||
" 'type': 'tool_use'}]"
|
||||
]
|
||||
},
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"last_message['content']"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6d007b31-c8a2-465c-bc78-a5909ca7931c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's now modify the tool call to say Louisiana"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"id": "55fcb316-450b-4b8c-9ae9-e7ee395acc55",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"last_message['tool_calls'] = [{\n",
|
||||
" 'id': last_message['tool_calls'][0]['id'],\n",
|
||||
" 'name': 'tavily_search_results_json',\n",
|
||||
" # We change the query to say temperature\n",
|
||||
" 'args': {'query': 'weather in Louisiana'}\n",
|
||||
"}]\n",
|
||||
"# last_message['content'] = [{\n",
|
||||
"# 'id': last_message['content'][0]['id'],\n",
|
||||
"# 'name': 'tavily_search_results_json',\n",
|
||||
"# # We change the query to say temperature\n",
|
||||
"# 'input': {'query': 'weather in Louisiana'},\n",
|
||||
"# 'type': 'tool_use'\n",
|
||||
"# }]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d49be54e-5334-47be-8dfb-78b8a8155e98",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can now update the state - we only need to pass in the last updated message because our graph will handle the update."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"id": "0438f997-bad3-48f6-b532-9ac3a95263c2",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'configurable': {'thread_id': '54ed0901-6767-46c9-a5f9-b65c1c5fd89c',\n",
|
||||
" 'thread_ts': '1ef15688-1dbd-68f5-8007-75dc0e110124'}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"await client.threads.update_state(thread['thread_id'], values={\"messages\": [last_message]})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c96668ab-80fa-4ae6-a90b-773a943ba331",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's now check the state of the thread again, and in particular the final message"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"id": "31936711-4af4-4bd1-ac10-9ce52922dd2f",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[{'name': 'tavily_search_results_json',\n",
|
||||
" 'args': {'query': 'weather in Louisiana'},\n",
|
||||
" 'id': 'toolu_01NGhKmeciaT7TfhBSwUT3mi'}]"
|
||||
]
|
||||
},
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"thread_state = await client.threads.get_state(thread['thread_id'])\n",
|
||||
"thread_state['values']['messages'][-1]['tool_calls']"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "20aa8ff3-7876-4db2-9333-c5396cd637ac",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Great! We changed it. If we now resume execution (by kicking off a new run with null inputs on the same thread) it should use that new tool call."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"id": "8e2c4eeb-2888-4979-9877-aa4a53dec5ea",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Receiving new event of type: metadata...\n",
|
||||
"{'run_id': '1a1ebed1-3581-418a-81be-e834b40c5c82'}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: data...\n",
|
||||
"{'action': {'messages': [{'content': '[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'Louisiana\\', \\'region\\': \\'Missouri\\', \\'country\\': \\'USA United States of America\\', \\'lat\\': 39.44, \\'lon\\': -91.06, \\'tz_id\\': \\'America/Chicago\\', \\'localtime_epoch\\': 1716072393, \\'localtime\\': \\'2024-05-18 17:46\\'}, \\'current\\': {\\'last_updated_epoch\\': 1716072300, \\'last_updated\\': \\'2024-05-18 17:45\\', \\'temp_c\\': 29.0, \\'temp_f\\': 84.2, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Partly cloudy\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/116.png\\', \\'code\\': 1003}, \\'wind_mph\\': 6.9, \\'wind_kph\\': 11.2, \\'wind_degree\\': 220, \\'wind_dir\\': \\'SW\\', \\'pressure_mb\\': 1011.0, \\'pressure_in\\': 29.86, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 46, \\'cloud\\': 50, \\'feelslike_c\\': 31.4, \\'feelslike_f\\': 88.6, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 7.0, \\'gust_mph\\': 7.4, \\'gust_kph\\': 11.9}}\"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': '728f8ac9-729e-4bf7-b560-b332a73c8f47', 'tool_call_id': 'toolu_01NGhKmeciaT7TfhBSwUT3mi'}]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: data...\n",
|
||||
"{'agent': {'messages': [{'content': [{'text': 'The search results seem to be for the weather in Louisiana, Missouri rather than Los Angeles, California. Let me try the search again:', 'type': 'text'}, {'id': 'toolu_019YAXWMK33tG9DaxMzrowc8', 'input': {'query': 'weather in los angeles california'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-c42a3b14-2611-4a1d-8907-95dcdb18f07f', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in los angeles california'}, 'id': 'toolu_019YAXWMK33tG9DaxMzrowc8'}], 'invalid_tool_calls': []}]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: end...\n",
|
||||
"None\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = None\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread['thread_id'], assistant['assistant_id'], input=input, stream_mode=\"updates\", interrupt_before=['action']\n",
|
||||
"):\n",
|
||||
" print(f\"Receiving new event of type: {chunk.event}...\")\n",
|
||||
" print(chunk.data)\n",
|
||||
" print(\"\\n\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "065f8165-43d8-4876-86af-0cfffd712fee",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Edit an old state\n",
|
||||
"\n",
|
||||
"Let's now imagine we want to go back in time and edit the tool call after we had already made it.\n",
|
||||
"In order to do this, we can get first get the full history of the thread."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 46,
|
||||
"id": "de050efd-73a4-441e-91e0-18e08f773a42",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"thread_history = await client.threads.get_history(thread['thread_id'], limit=100)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 47,
|
||||
"id": "07e15435-4a5f-4c2a-b748-0e0f7ab02a28",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"11"
|
||||
]
|
||||
},
|
||||
"execution_count": 47,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"len(thread_history)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a292e721-36c4-41b8-85e4-378f0770652a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"After that, we can get the correct state we want to be in. The 0th index state is the most recent one, while the -1 index state is the first.\n",
|
||||
"In this case, we want to go to the state where the last message had the tool calls for `weather in los angeles`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 48,
|
||||
"id": "132d207c-11cb-4efb-a330-88ebdfc612c8",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[{'name': 'tavily_search_results_json',\n",
|
||||
" 'args': {'query': 'weather in los angeles'},\n",
|
||||
" 'id': 'toolu_01FnuDKhUfagwoqhNfiTYTfS'}]"
|
||||
]
|
||||
},
|
||||
"execution_count": 48,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"rewind_state = thread_history[3]\n",
|
||||
"rewind_state['values']['messages'][-1]['tool_calls']"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 49,
|
||||
"id": "45e01ddf-2ccf-4029-b431-e5fce2235b59",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'configurable': {'thread_id': 'df85453d-cb86-48c8-ae84-12081faa1bdf',\n",
|
||||
" 'thread_ts': '1ef15582-3442-6db7-8006-9166bbb0e80f'}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 49,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"rewind_state['config']"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d229468e-2f94-4b29-b56b-1d402554dcfb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"If we want to, we can now resume execution from that place in time"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 50,
|
||||
"id": "94ebc63e-f2cf-4da1-bc8d-52c4731ab0c6",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Receiving new event of type: metadata...\n",
|
||||
"{'run_id': 'a1cc9263-ef0a-4c04-9194-6f01624d0ef0'}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: data...\n",
|
||||
"{'action': {'messages': [{'content': '[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'Los Angeles\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 34.05, \\'lon\\': -118.24, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1716071728, \\'localtime\\': \\'2024-05-18 15:35\\'}, \\'current\\': {\\'last_updated_epoch\\': 1716071400, \\'last_updated\\': \\'2024-05-18 15:30\\', \\'temp_c\\': 20.0, \\'temp_f\\': 68.0, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Partly cloudy\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/116.png\\', \\'code\\': 1003}, \\'wind_mph\\': 2.2, \\'wind_kph\\': 3.6, \\'wind_degree\\': 226, \\'wind_dir\\': \\'SW\\', \\'pressure_mb\\': 1016.0, \\'pressure_in\\': 29.99, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 61, \\'cloud\\': 50, \\'feelslike_c\\': 20.0, \\'feelslike_f\\': 68.0, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 6.0, \\'gust_mph\\': 12.6, \\'gust_kph\\': 20.3}}\"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': '7137b2e5-566b-418b-b642-b3c6b64c5224', 'tool_call_id': 'toolu_01FnuDKhUfagwoqhNfiTYTfS'}]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: data...\n",
|
||||
"{'agent': {'messages': [{'content': 'The search results show the current weather conditions in Los Angeles. As of 3:30pm on May 18, 2024, the weather in Los Angeles is partly cloudy with a temperature around 68°F (20°C). Winds are light from the southwest around 2-3 mph. The humidity is 61% and visibility is good at 9 miles. Overall, it appears to be a nice spring day in LA with partly sunny skies and comfortable temperatures in the upper 60s.', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-3966b68a-c381-4933-a852-e6a4697c962c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': []}]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: end...\n",
|
||||
"None\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = None\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread['thread_id'], \n",
|
||||
" assistant['assistant_id'], \n",
|
||||
" input=input, \n",
|
||||
" stream_mode=\"updates\", \n",
|
||||
" interrupt_before=['action'],\n",
|
||||
" config=rewind_state['config']\n",
|
||||
"):\n",
|
||||
" print(f\"Receiving new event of type: {chunk.event}...\")\n",
|
||||
" print(chunk.data)\n",
|
||||
" print(\"\\n\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "492f1d37-0979-4210-8dd7-bc70cdc308f3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.1"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to Add Breakpoints\n",
|
||||
"\n",
|
||||
"When creating LangGraph agents, it is often nice to add a human-in-the-loop component.\n",
|
||||
"This can be helpful when giving them access to tools.\n",
|
||||
"Often in these situations you may want to manually approve an action before taking.\n",
|
||||
"\n",
|
||||
"This can be in several ways, but the primary supported way is to add an \"interrupt\" before a node is executed.\n",
|
||||
"This interrupts execution at that node.\n",
|
||||
"You can then resume from that spot to continue. "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup\n",
|
||||
"\n",
|
||||
"### Code for your graph\n",
|
||||
"\n",
|
||||
"In this how-to we use a simple ReAct style hosted graph (you can see the full code for defining it [here](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/breakpoints/)). The important thing is that there are two nodes (one named `agent` that calls the LLM, and one named `action` that calls the tool), and a routing function from `agent` that determines whether to call `action` next or just end the graph run (the `action` node always calls the `agent` node after execution).\n",
|
||||
"\n",
|
||||
"### SDK Initialization"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph_sdk import get_client\n",
|
||||
"client = get_client()\n",
|
||||
"assistants = await client.assistants.search()\n",
|
||||
"assistants = [a for a in assistants if not a['config']]\n",
|
||||
"assistant = assistants[0]\n",
|
||||
"assistant_id = assistant['assistant_id']\n",
|
||||
"thread = await client.threads.create()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Adding a breakpoint\n",
|
||||
"\n",
|
||||
"We now want to add a breakpoint in our graph run, which we will do before a tool is called.\n",
|
||||
"We can do this by adding `interrupt_before=[\"action\"]`, which tells us to interrupt before calling the action node.\n",
|
||||
"We can do this either when compiling the graph or when kicking off a run.\n",
|
||||
"Here we will do it when kicking of a run, if you would like to to do it at compile time you need to edit the python file where your graph is defined and add the `interrupt_before` parameter when you call `.compile`.\n",
|
||||
"\n",
|
||||
"First let's access our hosted Langgraph instance through the SDK:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"And, now let's compile it with a breakpoint before the tool node:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Receiving new event of type: metadata...\n",
|
||||
"{'run_id': '3b77ef83-687a-4840-8858-0371f91a92c3'}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: data...\n",
|
||||
"{'agent': {'messages': [{'content': [{'id': 'toolu_01HwZqM1ptX6E15A5LAmyZTB', 'input': {'query': 'weather in san francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-e5d17791-4d37-4ad2-815f-a0c4cba62585', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in san francisco'}, 'id': 'toolu_01HwZqM1ptX6E15A5LAmyZTB'}], 'invalid_tool_calls': []}]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: end...\n",
|
||||
"None\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf\"}]}\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant_id,\n",
|
||||
" input=input,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
" interrupt_before=[\"action\"],\n",
|
||||
"):\n",
|
||||
" print(f\"Receiving new event of type: {chunk.event}...\")\n",
|
||||
" print(chunk.data)\n",
|
||||
" print(\"\\n\\n\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to Edit State of a Deployed Graph\n",
|
||||
"\n",
|
||||
"When creating LangGraph agents, it is often nice to add a human-in-the-loop component. This can be helpful when giving them access to tools. Often in these situations you may want to edit the graph state before continuing (for example, to edit what tool is being called, or how it is being called).\n",
|
||||
"\n",
|
||||
"This can be in several ways, but the primary supported way is to add an \"interrupt\" before a node is executed. This interrupts execution at that node. You can then use update_state to update the state, and then resume from that spot to continue.\n",
|
||||
"\n",
|
||||
"## Setup\n",
|
||||
"\n",
|
||||
"We are not going to show the full code for the graph we are hosting, but you can see it [here](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/edit-graph-state/#build-the-agent) if you want to. Once this graph is hosted, we are ready to invoke it and wait for user input. \n",
|
||||
"\n",
|
||||
"### SDK initialization\n",
|
||||
"\n",
|
||||
"First, we need to setup our client so that we can communicate with our hosted graph:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 37,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph_sdk import get_client\n",
|
||||
"client = get_client()\n",
|
||||
"assistants = await client.assistants.search()\n",
|
||||
"assistants = [a for a in assistants if not a['config']]\n",
|
||||
"assistant = assistants[0]\n",
|
||||
"thread = await client.threads.create()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Editing state\n",
|
||||
"\n",
|
||||
"### Initial invocation\n",
|
||||
"\n",
|
||||
"Now let's invoke our graph, making sure to interrupt before the `action` node."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 38,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'agent': {'messages': [{'content': [{'text': \"Certainly! I'll search for the current weather in San Francisco for you using the search function. Here's how I'll do that:\", 'type': 'text'}, {'id': 'toolu_01KEJMBFozSiZoS4mAcPZeqQ', 'input': {'query': 'current weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-6dbb0167-f8f6-4e2a-ab68-229b2d1fbb64', 'example': False, 'tool_calls': [{'name': 'search', 'args': {'query': 'current weather in San Francisco'}, 'id': 'toolu_01KEJMBFozSiZoS4mAcPZeqQ'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = {'messages':[{\"role\":\"user\",\"content\":\"search for weather in SF\"}]}\n",
|
||||
"\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" input=input,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
" interrupt_before=[\"action\"],\n",
|
||||
"):\n",
|
||||
" if chunk.data and \"run_id\" not in chunk.data:\n",
|
||||
" print(chunk.data)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Edit the state\n",
|
||||
"\n",
|
||||
"Now, let's assume we actually meant to search for the weather in Sidi Frej (another city with the initials SF). We can edit the state to properly reflect that:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 39,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'configurable': {'thread_id': '88d58d3f-4151-47a9-a8e0-e42fdd3527b8',\n",
|
||||
" 'thread_ts': '1ef3274b-a809-6913-8002-91536ce6554d'}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 39,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# First, lets get the current state\n",
|
||||
"current_state = await client.threads.get_state(thread['thread_id'])\n",
|
||||
"\n",
|
||||
"# Let's now get the last message in the state\n",
|
||||
"# This is the one with the tool calls that we want to update\n",
|
||||
"last_message = current_state['values']['messages'][-1]\n",
|
||||
"\n",
|
||||
"# Let's now update the args for that tool call\n",
|
||||
"last_message['tool_calls'][0]['args'] = {'query': 'current weather in Sidi Frej'}\n",
|
||||
"\n",
|
||||
"# Let's now call `update_state` to pass in this message in the `messages` key\n",
|
||||
"# This will get treated as any other update to the state\n",
|
||||
"# It will get passed to the reducer function for the `messages` key\n",
|
||||
"# That reducer function will use the ID of the message to update it\n",
|
||||
"# It's important that it has the right ID! Otherwise it would get appended\n",
|
||||
"# as a new message\n",
|
||||
"await client.threads.update_state(thread['thread_id'], {\"messages\": last_message})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Resume invocation\n",
|
||||
"\n",
|
||||
"Now we can resume our graph run but with the updated state:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 40,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'action': {'messages': [{'content': '[\"I looked up: current weather in Sidi Frej. Result: It\\'s sunny in San Francisco, but you better look out if you\\'re a Gemini 😈.\"]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': '1161b8d1-bee4-4188-9be8-698aecb69f10', 'tool_call_id': 'toolu_01KEJMBFozSiZoS4mAcPZeqQ'}]}}\n",
|
||||
"{'agent': {'messages': [{'content': [{'text': 'I apologize for the confusion in my search query. It seems the search function interpreted \"SF\" as \"Sidi Frej\" instead of \"San Francisco\" as we intended. Let me search again with the full city name to get the correct information:', 'type': 'text'}, {'id': 'toolu_0111rrwgfAcmurHZn55qjqTR', 'input': {'query': 'current weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-b8c25779-cfb4-46fc-a421-48553551242f', 'example': False, 'tool_calls': [{'name': 'search', 'args': {'query': 'current weather in San Francisco'}, 'id': 'toolu_0111rrwgfAcmurHZn55qjqTR'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n",
|
||||
"{'action': {'messages': [{'content': '[\"I looked up: current weather in San Francisco. Result: It\\'s sunny in San Francisco, but you better look out if you\\'re a Gemini 😈.\"]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': '6bc632ae-5ee6-4d01-9532-79c524a2d443', 'tool_call_id': 'toolu_0111rrwgfAcmurHZn55qjqTR'}]}}\n",
|
||||
"{'agent': {'messages': [{'content': \"Now, based on the search results, I can provide you with information about the current weather in San Francisco:\\n\\nThe weather in San Francisco is currently sunny. \\n\\nIt's worth noting that the search result included an unusual comment about Gemini, which doesn't seem directly related to the weather. This might be due to the search engine including some astrological information or a joke in its results. However, for the purpose of weather information, we can focus on the fact that it's sunny in San Francisco right now.\\n\\nIs there anything else you'd like to know about the weather in San Francisco or any other location?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-227a042b-dd97-476e-af32-76a3703af5d8', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" input=None,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
"):\n",
|
||||
" if chunk.data and \"run_id\" not in chunk.data:\n",
|
||||
" print(chunk.data)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"As you can see it now looks up the current weather in Sidi Frej (although our dummy search node still returns results for SF because we don't actually do a search in this example, we just return the same \"It's sunny in San Francisco ...\" result every time)."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to Replay and Branch from Prior States\n",
|
||||
"\n",
|
||||
"With Langgraph Cloud you have the ability to return to any of your prior states and either re-run the graph to reproduce issues noticed during testing, or branch out in a different way from what was originally done in the prior states. In this guide we will show a quick example of how to rerun past states and how to branch off from previous states as well.\n",
|
||||
"\n",
|
||||
"## Setup\n",
|
||||
"\n",
|
||||
"We are not going to show the full code for the graph we are hosting, but you can see it [here](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/time-travel/#build-the-agent) if you want to. Once this graph is hosted, we are ready to invoke it and wait for user input. \n",
|
||||
"\n",
|
||||
"### SDK initialization\n",
|
||||
"\n",
|
||||
"First, we need to setup our client so that we can communicate with our hosted graph:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 99,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph_sdk import get_client\n",
|
||||
"client = get_client()\n",
|
||||
"assistants = await client.assistants.search()\n",
|
||||
"assistants = [a for a in assistants if not a['config']]\n",
|
||||
"assistant = assistants[0]\n",
|
||||
"thread = await client.threads.create()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Replay a state\n",
|
||||
"\n",
|
||||
"### Initial invocation\n",
|
||||
"\n",
|
||||
"Before replaying a state - we need to create states to replay from! In order to do this, let's invoke our graph with a simple message:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 100,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'agent': {'messages': [{'content': [{'text': \"Certainly! I'll use the search function to look up the current weather in San Francisco for you. Let me do that now.\", 'type': 'text'}, {'id': 'toolu_011vroKUtWU7SBdrngpgpFMn', 'input': {'query': 'current weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ee639877-d97d-40f8-96dc-d0d1ae22d203', 'example': False, 'tool_calls': [{'name': 'search', 'args': {'query': 'current weather in San Francisco'}, 'id': 'toolu_011vroKUtWU7SBdrngpgpFMn'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n",
|
||||
"{'action': {'messages': [{'content': '[\"I looked up: current weather in San Francisco. Result: It\\'s sunny in San Francisco, but you better look out if you\\'re a Gemini 😈.\"]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': '7bad0e72-5ebe-4b08-9b8a-b99b0fe22fb7', 'tool_call_id': 'toolu_011vroKUtWU7SBdrngpgpFMn'}]}}\n",
|
||||
"{'agent': {'messages': [{'content': \"Based on the search results, I can provide you with information about the current weather in San Francisco:\\n\\nThe weather in San Francisco is currently sunny. This is great news for outdoor activities and enjoying the city's beautiful sights.\\n\\nIt's worth noting that the search result included an unusual comment about Geminis, which isn't typically part of a weather report. This might be due to the search engine including some astrological information or a joke in its results. However, for the purpose of answering your question about the weather, we can focus on the fact that it's sunny in San Francisco.\\n\\nIf you need any more specific information about the weather in San Francisco, such as temperature, wind speed, or forecast for the coming days, please let me know, and I'd be happy to search for that information for you.\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-dbac539a-33c8-4f0c-9e20-91f318371e7c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = {'messages':[{\"role\":\"user\",\"content\":\"Please search the weather in SF\"}]}\n",
|
||||
"\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" input=input,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
"):\n",
|
||||
" if chunk.data and \"run_id\" not in chunk.data:\n",
|
||||
" print(chunk.data)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's get our list of states, and invoke from the third state (right before the tool get called):"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 101,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"states = await client.threads.get_history(thread['thread_id'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 102,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"['action']"
|
||||
]
|
||||
},
|
||||
"execution_count": 102,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# We can confirm that this state is correct by checking the 'next' attribute and seeing that it is the tool call node\n",
|
||||
"state_to_replay = states[2]\n",
|
||||
"state_to_replay['next']"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"To rerun from a state, we need to pass in the `checkpoint_id` into the config of the run like follows:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 103,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'action': {'messages': [{'content': '[\"I looked up: current weather in San Francisco. Result: It\\'s sunny in San Francisco, but you better look out if you\\'re a Gemini 😈.\"]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': 'eba650e5-400e-4938-8508-f878dcbcc532', 'tool_call_id': 'toolu_011vroKUtWU7SBdrngpgpFMn'}]}}\n",
|
||||
"{'agent': {'messages': [{'content': \"Based on the search results, I can provide you with information about the current weather in San Francisco:\\n\\nThe weather in San Francisco is currently sunny. This is great news if you're planning any outdoor activities or simply want to enjoy a pleasant day in the city.\\n\\nIt's worth noting that the search result included an unusual comment about Geminis, which doesn't seem directly related to the weather. This appears to be a playful or humorous addition to the weather report, possibly from the source where this information was obtained.\\n\\nIs there anything else you'd like to know about the weather in San Francisco or any other information you need?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-bc6dca3f-a1e2-4f59-a69b-fe0515a348bb', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" input=None,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
" config={\"configurable\":{\"thread_ts\":state_to_replay['checkpoint_id']}}\n",
|
||||
"):\n",
|
||||
" if chunk.data and \"run_id\" not in chunk.data:\n",
|
||||
" print(chunk.data)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"As we can see, the graph restarted from the tool node with the same input as our original graph run.\n",
|
||||
"\n",
|
||||
"## Branch off from previous state\n",
|
||||
"\n",
|
||||
"Using LangGraph's checkpointing, you can do more than just replay past states. You can branch off previous locations to let the agent explore alternate trajectories or to let a user \"version control\" changes in a workflow.\n",
|
||||
"\n",
|
||||
"Let's show how to do this to edit the state at a particular point in time. Let's update the state to change the input to the tool"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 104,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Let's now get the last message in the state\n",
|
||||
"# This is the one with the tool calls that we want to update\n",
|
||||
"last_message = state_to_replay['values']['messages'][-1]\n",
|
||||
"\n",
|
||||
"# Let's now update the args for that tool call\n",
|
||||
"last_message['tool_calls'][0]['args'] = {'query': 'current weather in SF'}\n",
|
||||
"\n",
|
||||
"new_state = await client.threads.update_state(thread['thread_id'],{\"messages\":[last_message]},checkpoint_id=state_to_replay['checkpoint_id'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now we can rerun our graph with this new config, starting from the `new_state`, which is a branch of our `state_to_replay`:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 105,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'action': {'messages': [{'content': '[\"I looked up: current weather in SF. Result: It\\'s sunny in San Francisco, but you better look out if you\\'re a Gemini 😈.\"]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': '2baf9941-4fda-4081-9f87-d76795d289f1', 'tool_call_id': 'toolu_011vroKUtWU7SBdrngpgpFMn'}]}}\n",
|
||||
"{'agent': {'messages': [{'content': \"Based on the search results, I can provide you with information about the current weather in San Francisco (SF):\\n\\nThe weather in San Francisco is currently sunny. This means it's a clear day with plenty of sunshine. \\n\\nIt's worth noting that the specific temperature wasn't provided in the search result, but sunny weather in San Francisco typically means comfortable temperatures. San Francisco is known for its mild climate, so even on sunny days, it's often not too hot.\\n\\nThe search result also included a playful reference to astrological signs, mentioning Gemini. However, this is likely just a joke or part of the search engine's presentation and not related to the actual weather conditions.\\n\\nIs there any specific information about the weather in San Francisco you'd like to know more about? I'd be happy to perform another search if you need details on temperature, wind conditions, or the forecast for the coming days.\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-a83de52d-ed18-4402-9384-75c462485743', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" input=None,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
" config={\"configurable\":{\"thread_ts\":new_state['configurable']['thread_ts']}}\n",
|
||||
"):\n",
|
||||
" if chunk.data and \"run_id\" not in chunk.data:\n",
|
||||
" print(chunk.data)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"As we can see, the search query changed from San Francisco to SF, just as we had hoped!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to Wait for User Input\n",
|
||||
"\n",
|
||||
"One of the main human-in-the-loop interaction patterns is waiting for human input. A key use case involves asking the user clarifying questions. One way to accomplish this is simply go to the `END` node and exit the graph. Then, any user response comes back in as fresh invocation of the graph. This is basically just creating a chatbot architecture.\n",
|
||||
"\n",
|
||||
"The issue with this is it is tough to resume back in a particular point in the graph. Often times the agent is halfway through some process, and just needs a bit of a user input. Although it is possible to design your graph in such a way where you have a `conditional_entry_point` to route user messages back to the right place, that is not super scalable (as it essentially involves having a routing function that can end up almost anywhere).\n",
|
||||
"\n",
|
||||
"A separate way to do this is to have a node explicitly for getting user input. This is easy to implement in a notebook setting - you just put an `input()` call in the node. But that isn't exactly production ready.\n",
|
||||
"\n",
|
||||
"Luckily, LangGraph makes it possible to do similar things in a production way. The basic idea is:\n",
|
||||
"\n",
|
||||
"- Set up a node that represents human input. This can have specific incoming/outgoing edges (as you desire). There shouldn't actually be any logic inside this node.\n",
|
||||
"- Add a breakpoint before the node. This will stop the graph before this node executes (which is good, because there's no real logic in it anyways)\n",
|
||||
"- Use `.update_state` to update the state of the graph. Pass in whatever human response you get. The key here is to use the `as_node` parameter to apply this update **as if you were that node**. This will have the effect of making it so that when you resume execution next it resumes as if that node just acted, and not from the beginning."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup\n",
|
||||
"\n",
|
||||
"We are not going to show the full code for the graph we are hosting, but you can see it [here](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/wait-user-input/#build-the-agent) if you want to. Once this graph is hosted, we are ready to invoke it and wait for user input. "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### SDK initialization\n",
|
||||
"\n",
|
||||
"First, we need to setup our client so that we can communicate with our hosted graph:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph_sdk import get_client\n",
|
||||
"client = get_client()\n",
|
||||
"assistants = await client.assistants.search()\n",
|
||||
"assistants = [a for a in assistants if not a['config']]\n",
|
||||
"assistant = assistants[0]\n",
|
||||
"thread = await client.threads.create()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Waiting for user input\n",
|
||||
"\n",
|
||||
"### Initial invocation\n",
|
||||
"\n",
|
||||
"Now, let's invoke our graph by interrupting before `ask_human` node:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'agent': {'messages': [{'content': [{'text': \"Certainly! I'll use the AskHuman function to ask the user about their location, and then I'll use the search function to look up the weather for that location. Let's start by asking the user where they are.\", 'type': 'text'}, {'id': 'toolu_01RFahzYPvnPWTb2USk2RdKR', 'input': {'question': 'Where are you currently located?'}, 'name': 'AskHuman', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-a8422215-71d3-4093-afb4-9db141c94ddb', 'example': False, 'tool_calls': [{'name': 'AskHuman', 'args': {'question': 'Where are you currently located?'}, 'id': 'toolu_01RFahzYPvnPWTb2USk2RdKR'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = {'messages':[{\"role\":\"user\",\"content\":\"Use the search tool to ask the user where they are, then look up the weather there\"}]}\n",
|
||||
"\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" input=input,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
" interrupt_before=[\"ask_human\"],\n",
|
||||
"):\n",
|
||||
" if chunk.data and \"run_id\" not in chunk.data:\n",
|
||||
" print(chunk.data)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Adding user input to state\n",
|
||||
"\n",
|
||||
"We now want to update this thread with a response from the user. We then can kick off another run.\n",
|
||||
"\n",
|
||||
"Because we are treating this as a tool call, we will need to update the state as if it is a response from a tool call. In order to do this, we will need to check the state to get the ID of the tool call."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'configurable': {'thread_id': '10d0ee61-db47-48fc-a58c-109a1e68cd73',\n",
|
||||
" 'thread_ts': '1ef32729-3cc3-6647-8002-14dcb621b46e'}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"state = await client.threads.get_state(thread['thread_id'])\n",
|
||||
"tool_call_id = state['values']['messages'][-1]['tool_calls'][0]['id']\n",
|
||||
"\n",
|
||||
"# We now create the tool call with the id and the response we want\n",
|
||||
"tool_message = [{\"tool_call_id\": tool_call_id, \"type\": \"tool\", \"content\": \"san francisco\"}]\n",
|
||||
"\n",
|
||||
"await client.threads.update_state(thread['thread_id'], {\"messages\": tool_message}, as_node=\"ask_human\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Invoking after receiving human input\n",
|
||||
"\n",
|
||||
"We can now tell the agent to continue. We can just pass in None as the input to the graph, since no additional input is needed:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'agent': {'messages': [{'content': [{'text': \"Thank you for letting me know that you're in San Francisco. Now, I'll use the search function to look up the weather in San Francisco.\", 'type': 'text'}, {'id': 'toolu_01K57ofmgG2wyJ8tYJjbq5k7', 'input': {'query': 'current weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-241baed7-db5e-44ce-ac3c-56431705c22b', 'example': False, 'tool_calls': [{'name': 'search', 'args': {'query': 'current weather in San Francisco'}, 'id': 'toolu_01K57ofmgG2wyJ8tYJjbq5k7'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n",
|
||||
"{'action': {'messages': [{'content': '[\"I looked up: current weather in San Francisco. Result: It\\'s sunny in San Francisco, but you better look out if you\\'re a Gemini 😈.\"]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': '8b699b95-8546-4557-8e66-14ea71a15ed8', 'tool_call_id': 'toolu_01K57ofmgG2wyJ8tYJjbq5k7'}]}}\n",
|
||||
"{'agent': {'messages': [{'content': \"Based on the search results, I can provide you with information about the current weather in San Francisco:\\n\\nThe weather in San Francisco is currently sunny. It's a beautiful day in the city! \\n\\nHowever, I should note that the search result included an unusual comment about Gemini zodiac signs. This appears to be either a joke or potentially irrelevant information added by the search engine. For accurate and detailed weather information, you might want to check a reliable weather service or app for San Francisco.\\n\\nIs there anything else you'd like to know about the weather or San Francisco?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-b4d7309f-f849-46aa-b6ef-475bcabd2be9', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" input=None,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
"):\n",
|
||||
" if chunk.data and \"run_id\" not in chunk.data:\n",
|
||||
" print(chunk.data)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
|
After Width: | Height: | Size: 322 KiB |
@@ -6,7 +6,9 @@
|
||||
"source": [
|
||||
"## Interrupt\n",
|
||||
"\n",
|
||||
"There are several strategies for handling concurrent runs in your graph. This notebook covers how to use the `interrupt` option - please see the other how-to guides in the \"Double Texting\" directory to learn about the other methods.\n",
|
||||
"This notebook assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](https://langchain-ai.github.io/langgraph/cloud/concepts/#double-texting).\n",
|
||||
"\n",
|
||||
"The guide covers the `interrupt` option for double texting, which interrupts the prior run of the graph and starts a new one with the double-text. This option does not delete the first run, but rather keeps it in the database but sets its status to `interrupted`. Below is a quick example of using the `interrupt` option.\n",
|
||||
"\n",
|
||||
"First, let's import our required packages and instantiate our client, assistant, and thread."
|
||||
]
|
||||
@@ -30,7 +32,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"client = get_client()\n",
|
||||
"assistant = await client.assistants.create(\"agent\")\n",
|
||||
"assistant_id = \"agent\"\n",
|
||||
"thread = await client.threads.create()"
|
||||
]
|
||||
},
|
||||
@@ -43,13 +45,13 @@
|
||||
"# the first run will be interrupted\n",
|
||||
"interrupted_run = await client.runs.create(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"],\n",
|
||||
" assistant_id,\n",
|
||||
" input={\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf?\"}]},\n",
|
||||
")\n",
|
||||
"await asyncio.sleep(2)\n",
|
||||
"run = await client.runs.create(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"],\n",
|
||||
" assistant_id,\n",
|
||||
" input={\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in nyc?\"}]},\n",
|
||||
" multitask_strategy=\"interrupt\",\n",
|
||||
")"
|
||||
@@ -166,10 +168,14 @@
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "",
|
||||
"name": ""
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
"source": [
|
||||
"## Reject\n",
|
||||
"\n",
|
||||
"There are several strategies for handling concurrent runs in your graph. This notebook covers how to use the `reject` option - please see the other how-to guides in the \"Double Texting\" directory to learn about the other methods.\n",
|
||||
"This notebook assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](https://langchain-ai.github.io/langgraph/cloud/concepts/#double-texting).\n",
|
||||
"\n",
|
||||
"The guide covers the `reject` option for double texting, which rejects the new run of the graph by throwing an error and continues with the original run until completion. Below is a quick example of using the `reject` option.\n",
|
||||
"\n",
|
||||
"First, let's import our required packages and instantiate our client, assistant, and thread."
|
||||
]
|
||||
@@ -28,24 +30,8 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"client = get_client()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"assistant = await client.assistants.create(\"agent\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"client = get_client()\n",
|
||||
"assistant_id = \"agent\"\n",
|
||||
"thread = await client.threads.create()"
|
||||
]
|
||||
},
|
||||
@@ -57,7 +43,7 @@
|
||||
"source": [
|
||||
"run = await client.runs.create(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"],\n",
|
||||
" assistant_id,\n",
|
||||
" input={\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf?\"}]},\n",
|
||||
")"
|
||||
]
|
||||
@@ -80,8 +66,10 @@
|
||||
"try:\n",
|
||||
" await client.runs.create(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"],\n",
|
||||
" input={\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in nyc?\"}]},\n",
|
||||
" assistant_id,\n",
|
||||
" input={\n",
|
||||
" \"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in nyc?\"}]\n",
|
||||
" },\n",
|
||||
" multitask_strategy=\"reject\",\n",
|
||||
" )\n",
|
||||
"except httpx.HTTPStatusError as e:\n",
|
||||
@@ -163,10 +151,14 @@
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "",
|
||||
"name": ""
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
"source": [
|
||||
"## Rollback\n",
|
||||
"\n",
|
||||
"There are several strategies for handling concurrent runs in your graph. This notebook covers how to use the `rollback` option - please see the other how-to guides in the \"Double Texting\" directory to learn about the other methods.\n",
|
||||
"This notebook assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](https://langchain-ai.github.io/langgraph/cloud/concepts/#double-texting).\n",
|
||||
"\n",
|
||||
"The guide covers the `rollback` option for double texting, which interrupts the prior run of the graph and starts a new one with the double-text. This option is very similar to the `interrupt` option, but in this case the first run is completely deleted from the database and cannot be restarted. Below is a quick example of using the `rollback` option.\n",
|
||||
"\n",
|
||||
"First, let's import our required packages and instantiate our client, assistant, and thread."
|
||||
]
|
||||
@@ -31,7 +33,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"client = get_client()\n",
|
||||
"assistant = await client.assistants.create(\"agent\")\n",
|
||||
"assistant_id = \"agent\"\n",
|
||||
"thread = await client.threads.create()"
|
||||
]
|
||||
},
|
||||
@@ -44,13 +46,13 @@
|
||||
"# the first run will be interrupted\n",
|
||||
"rolled_back_run = await client.runs.create(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"],\n",
|
||||
" assistant_id,\n",
|
||||
" input={\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf?\"}]},\n",
|
||||
")\n",
|
||||
"await asyncio.sleep(2)\n",
|
||||
"run = await client.runs.create(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"],\n",
|
||||
" assistant_id,\n",
|
||||
" input={\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in nyc?\"}]},\n",
|
||||
" multitask_strategy=\"rollback\",\n",
|
||||
")"
|
||||
@@ -146,10 +148,14 @@
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "",
|
||||
"name": ""
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
|
||||
@@ -7,9 +7,13 @@
|
||||
"source": [
|
||||
"# How to run multiple agents on the same thread\n",
|
||||
"\n",
|
||||
"In LangGraph API, a thread is not explicitly associated with a particular agent.\n",
|
||||
"This means that you can run multiple agents on the same thread.\n",
|
||||
"In this example, we will create two agents and then call them both on the same thread."
|
||||
"In LangGraph Cloud, a thread is not explicitly associated with a particular agent.\n",
|
||||
"This means that you can run multiple agents on the same thread, which allows a different\n",
|
||||
"agent to continue from an initial agent's progress.\n",
|
||||
"\n",
|
||||
"In this example, we will create two agents and then call them both on the same thread.\n",
|
||||
"You'll see that the second agent will respond using information from the [checkpoint](https://langchain-ai.github.io/langgraph/concepts/low_level/#checkpointer-state) generated in the thread\n",
|
||||
"by the first agent as context."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -23,11 +27,13 @@
|
||||
"\n",
|
||||
"client = get_client()\n",
|
||||
"\n",
|
||||
"openai_assistant = await client.assistants.create(graph_id=\"agent\", config={\"configurable\": {\"model_name\": \"openai\"}})\n",
|
||||
"openai_assistant = await client.assistants.create(\n",
|
||||
" graph_id=\"agent\", config={\"configurable\": {\"model_name\": \"openai\"}}\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# There should always be a default assistant with no configuration\n",
|
||||
"assistants = await client.assistants.search()\n",
|
||||
"default_assistant = [a for a in assistants if not a['config']][0]"
|
||||
"default_assistant = [a for a in assistants if not a[\"config\"]][0]"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -35,7 +41,7 @@
|
||||
"id": "4f10d346-69e6-44f4-8ff0-ef539ba938df",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can see that these agents are different"
|
||||
"We can see that these agents are different:"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -95,7 +101,7 @@
|
||||
"id": "5e655e61-c2ee-488a-90f6-6189c84841da",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can now run it on the OpenAI assistant first."
|
||||
"We can now run the OpenAI assistant on the thread first."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -117,7 +123,12 @@
|
||||
"source": [
|
||||
"thread = await client.threads.create()\n",
|
||||
"input = {\"messages\": [{\"role\": \"user\", \"content\": \"who made you?\"}]}\n",
|
||||
"async for event in client.runs.stream(thread['thread_id'], openai_assistant['assistant_id'], input=input, stream_mode='updates'):\n",
|
||||
"async for event in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" openai_assistant[\"assistant_id\"],\n",
|
||||
" input=input,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
"):\n",
|
||||
" print(event)"
|
||||
]
|
||||
},
|
||||
@@ -126,7 +137,7 @@
|
||||
"id": "c53709e9-ddb2-4429-9042-456eb6c91244",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now, we can run it on a different Anthropic-based assistant."
|
||||
"Now, we can run it on a second Anthropic-based assistant and see that this second assistant is aware of the initial question, and can answer the question, `and you?`:"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -147,7 +158,12 @@
|
||||
],
|
||||
"source": [
|
||||
"input = {\"messages\": [{\"role\": \"user\", \"content\": \"and you?\"}]}\n",
|
||||
"async for event in client.runs.stream(thread['thread_id'], default_assistant['assistant_id'], input=input, stream_mode='updates'):\n",
|
||||
"async for event in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" default_assistant[\"assistant_id\"],\n",
|
||||
" input=input,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
"):\n",
|
||||
" print(event)"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Stateless Runs\n",
|
||||
"\n",
|
||||
"Most of the time, you provide a `thread_id` to your client when you run your graph in order to keep track of prior runs through the persistent state implemented in LangGraph Cloud. However, if you have your own database to save runs and don't need to use the built in persistent state, you can create stateless runs.\n",
|
||||
"\n",
|
||||
"## Setup\n",
|
||||
"\n",
|
||||
"First, let's setup our client"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 106,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph_sdk import get_client\n",
|
||||
"client = get_client()\n",
|
||||
"assistants = await client.assistants.search()\n",
|
||||
"assistants = [a for a in assistants if not a['config']]\n",
|
||||
"assistant = assistants[0]\n",
|
||||
"thread = await client.threads.create()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Stateless streaming\n",
|
||||
"\n",
|
||||
"We can stream the results of a stateless run in an almost identical fashion to how we stream from a run with the state attribute, but instead of passing a value to the `thread_id` parameter, we pass `None`:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 107,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'agent': {'messages': [{'content': \"Hello Bagatur! It's nice to meet you. Thank you for introducing yourself and sharing your age. Is there anything specific you'd like to know or discuss? I'm here to help with any questions or topics you're interested in.\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-489ec573-1645-4ce2-a3b8-91b391d50a71', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = {\"messages\":[{\"role\": \"user\", \"content\": \"Hello! My name is Bagatur and I am 26 years old.\"}]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" # Don't pass in a thread_id and the stream will be stateless\n",
|
||||
" None,\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" input=input,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
"):\n",
|
||||
" if chunk.data and \"run_id\" not in chunk.data:\n",
|
||||
" print(chunk.data)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Waiting for stateless results\n",
|
||||
"\n",
|
||||
"In addition to streaming, you can also wait for a stateless result by using the `.wait` function like follows:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 108,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"stateless_run_result = await client.runs.wait(\n",
|
||||
" None,\n",
|
||||
" assistant[\"assistant_id\"], # graph_id\n",
|
||||
" input=input,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 109,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'messages': [{'content': 'Hello! My name is Bagatur and I am 26 years old.',\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {},\n",
|
||||
" 'type': 'human',\n",
|
||||
" 'name': None,\n",
|
||||
" 'id': '5e088543-62c2-43de-9d95-6086ad7f8b48',\n",
|
||||
" 'example': False},\n",
|
||||
" {'content': \"Hello Bagatur! It's nice to meet you. Thank you for introducing yourself and sharing your age. Is there anything specific you'd like to know or discuss? I'm here to help with any questions or topics you'd like to explore.\",\n",
|
||||
" 'additional_kwargs': {},\n",
|
||||
" 'response_metadata': {},\n",
|
||||
" 'type': 'ai',\n",
|
||||
" 'name': None,\n",
|
||||
" 'id': 'run-d6361e8d-4d4c-45bd-ba47-39520257f773',\n",
|
||||
" 'example': False,\n",
|
||||
" 'tool_calls': [],\n",
|
||||
" 'invalid_tool_calls': [],\n",
|
||||
" 'usage_metadata': None}]}"
|
||||
]
|
||||
},
|
||||
"execution_count": 109,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"stateless_run_result"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to stream debug events\n",
|
||||
"This guide covers how to stream debug events from your graph (`stream_mode=\"debug\"`)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph_sdk import get_client\n",
|
||||
"\n",
|
||||
"client = get_client()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3',\n",
|
||||
" 'created_at': '2024-06-21T22:10:27.696862+00:00',\n",
|
||||
" 'updated_at': '2024-06-21T22:10:27.696862+00:00',\n",
|
||||
" 'metadata': {}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# create thread\n",
|
||||
"thread = await client.threads.create()\n",
|
||||
"thread"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Streaming debug events produces responses containing `type` and `timestamp` keys. Debug events correspond to different steps in the graph's execution (e.g. `task`, `task_result`, `checkpoint`)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Receiving new event of type: metadata...\n",
|
||||
"{'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a'}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'checkpoint', 'timestamp': '2024-06-21T22:11:09.256850+00:00', 'step': -1, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', 'thread_ts': '1ef301b2-9a2e-6bb6-bfff-8423bcf47561', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a'}, 'values': {'messages': []}, 'metadata': {'source': 'input', 'step': -1, 'writes': {'messages': [{'role': 'human', 'content': \"What's the weather in SF?\"}]}}}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'checkpoint', 'timestamp': '2024-06-21T22:11:09.259723+00:00', 'step': 0, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', 'thread_ts': '1ef301b2-9a35-6c86-8000-f4a85315dbeb', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a'}, 'values': {'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '906529f7-fbf2-41c9-a28c-b1fe8f891e4e', 'example': False}]}, 'metadata': {'source': 'loop', 'step': 0, 'writes': None}}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'task', 'timestamp': '2024-06-21T22:11:09.260021+00:00', 'step': 1, 'payload': {'id': '12ab1026-a551-5f96-9ad3-43424f094774', 'name': 'agent', 'input': {'some_bytes': None, 'some_byte_array': None, 'dict_with_bytes': None, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '906529f7-fbf2-41c9-a28c-b1fe8f891e4e', 'example': False}], 'sleep': None}, 'triggers': ['start:agent']}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'task_result', 'timestamp': '2024-06-21T22:11:09.267632+00:00', 'step': 1, 'payload': {'id': '12ab1026-a551-5f96-9ad3-43424f094774', 'name': 'agent', 'result': [['some_bytes', 'c29tZV9ieXRlcw=='], ['some_byte_array', 'c29tZV9ieXRlX2FycmF5'], ['dict_with_bytes', {'more_bytes': 'bW9yZV9ieXRlcw=='}], ['messages', [{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-54bd965b-734a-4a0a-8d4d-840865054810', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]]]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'checkpoint', 'timestamp': '2024-06-21T22:11:09.268469+00:00', 'step': 1, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', 'thread_ts': '1ef301b2-9a4b-60ae-8001-dd378f965bf7', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a'}, 'values': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '906529f7-fbf2-41c9-a28c-b1fe8f891e4e', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-54bd965b-734a-4a0a-8d4d-840865054810', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}, 'metadata': {'source': 'loop', 'step': 1, 'writes': {'agent': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-54bd965b-734a-4a0a-8d4d-840865054810', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}}}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'task', 'timestamp': '2024-06-21T22:11:09.268659+00:00', 'step': 2, 'payload': {'id': '494ad427-fe8d-5654-91e6-50495a2699f5', 'name': 'tool', 'input': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '906529f7-fbf2-41c9-a28c-b1fe8f891e4e', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-54bd965b-734a-4a0a-8d4d-840865054810', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'sleep': None}, 'triggers': ['branch:agent:should_continue:tool']}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'task_result', 'timestamp': '2024-06-21T22:11:09.272916+00:00', 'step': 2, 'payload': {'id': '494ad427-fe8d-5654-91e6-50495a2699f5', 'name': 'tool', 'result': [['messages', [{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '222ed3b8-450f-41cb-ac40-905def3c700a', 'tool_call_id': 'tool_call_id'}]]]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'checkpoint', 'timestamp': '2024-06-21T22:11:09.273113+00:00', 'step': 2, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', 'thread_ts': '1ef301b2-9a56-6832-8002-8ab17e662980', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a'}, 'values': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '906529f7-fbf2-41c9-a28c-b1fe8f891e4e', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-54bd965b-734a-4a0a-8d4d-840865054810', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '222ed3b8-450f-41cb-ac40-905def3c700a', 'tool_call_id': 'tool_call_id'}]}, 'metadata': {'source': 'loop', 'step': 2, 'writes': {'tool': {'messages': [{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '222ed3b8-450f-41cb-ac40-905def3c700a', 'tool_call_id': 'tool_call_id'}]}}}}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'task', 'timestamp': '2024-06-21T22:11:09.273192+00:00', 'step': 3, 'payload': {'id': '677de327-99b7-5d97-9bbd-0092abb62d46', 'name': 'agent', 'input': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '906529f7-fbf2-41c9-a28c-b1fe8f891e4e', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-54bd965b-734a-4a0a-8d4d-840865054810', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '222ed3b8-450f-41cb-ac40-905def3c700a', 'tool_call_id': 'tool_call_id'}], 'sleep': None}, 'triggers': ['tool']}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'task_result', 'timestamp': '2024-06-21T22:11:09.277262+00:00', 'step': 3, 'payload': {'id': '677de327-99b7-5d97-9bbd-0092abb62d46', 'name': 'agent', 'result': [['some_bytes', 'c29tZV9ieXRlcw=='], ['some_byte_array', 'c29tZV9ieXRlX2FycmF5'], ['dict_with_bytes', {'more_bytes': 'bW9yZV9ieXRlcw=='}], ['messages', [{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-006e1758-b1ca-4c90-9ff3-d2e75b9ca9a7', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]]]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'checkpoint', 'timestamp': '2024-06-21T22:11:09.277519+00:00', 'step': 3, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', 'thread_ts': '1ef301b2-9a61-6462-8003-1316d9875b7f', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef301b2-9a0c-68d6-bbb1-0763efc8489a'}, 'values': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '906529f7-fbf2-41c9-a28c-b1fe8f891e4e', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-54bd965b-734a-4a0a-8d4d-840865054810', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '222ed3b8-450f-41cb-ac40-905def3c700a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-006e1758-b1ca-4c90-9ff3-d2e75b9ca9a7', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}, 'metadata': {'source': 'loop', 'step': 3, 'writes': {'agent': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-006e1758-b1ca-4c90-9ff3-d2e75b9ca9a7', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}}}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: end...\n",
|
||||
"None\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# create input\n",
|
||||
"input = {\n",
|
||||
" \"messages\": [\n",
|
||||
" {\n",
|
||||
" \"role\": \"human\",\n",
|
||||
" \"content\": \"What's the weather in SF?\",\n",
|
||||
" }\n",
|
||||
" ]\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"# stream events\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread_id=thread[\"thread_id\"],\n",
|
||||
" assistant_id=\"agent\",\n",
|
||||
" input=input,\n",
|
||||
" stream_mode=\"debug\",\n",
|
||||
"):\n",
|
||||
" print(f\"Receiving new event of type: {chunk.event}...\")\n",
|
||||
" print(chunk.data)\n",
|
||||
" print(\"\\n\\n\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "env",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to stream events\n",
|
||||
"This guide covers how to stream events from your graph (`stream_mode=\"events\"`). Depending on the use case and user experience of your LangGraph application, your application may process event types differently."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph_sdk import get_client\n",
|
||||
"\n",
|
||||
"client = get_client()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 27,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'thread_id': '3f4c64e0-f792-4a5e-aa07-a4404e06e0bd',\n",
|
||||
" 'created_at': '2024-06-24T22:16:29.301522+00:00',\n",
|
||||
" 'updated_at': '2024-06-24T22:16:29.301522+00:00',\n",
|
||||
" 'metadata': {}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 27,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# create thread\n",
|
||||
"thread = await client.threads.create()\n",
|
||||
"thread"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Streaming events produces responses containing an `event` key (in addition to other keys such as `data`). See the LangChain [`Runnable.astream_events()` reference](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.Runnable.html#langchain_core.runnables.base.Runnable.astream_events) for all event types."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Receiving new event of type: metadata...\n",
|
||||
"{'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8'}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_start', 'data': {'input': {'messages': [{'role': 'human', 'content': \"What's the weather in SF?\"}]}}, 'name': 'LangGraph', 'tags': [], 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'parent_ids': []}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_start', 'data': {}, 'name': 'agent', 'tags': ['graph:step:6'], 'run_id': '7bb08493-d507-4e28-b9e6-4a5eda9d04f0', 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_start', 'data': {'input': {'messages': [[{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}]]}}, 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'run_id': 'cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '7bb08493-d507-4e28-b9e6-4a5eda9d04f0']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'b', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': 'cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '7bb08493-d507-4e28-b9e6-4a5eda9d04f0']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'e', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': 'cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '7bb08493-d507-4e28-b9e6-4a5eda9d04f0']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'g', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': 'cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '7bb08493-d507-4e28-b9e6-4a5eda9d04f0']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'i', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': 'cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '7bb08493-d507-4e28-b9e6-4a5eda9d04f0']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'n', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': 'cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '7bb08493-d507-4e28-b9e6-4a5eda9d04f0']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_end', 'data': {'output': {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, 'input': {'messages': [[{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}]]}}, 'run_id': 'cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '7bb08493-d507-4e28-b9e6-4a5eda9d04f0']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_start', 'data': {'input': {'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'name': 'should_continue', 'tags': ['seq:step:3'], 'run_id': 'c7fe4d2d-3fb8-4e53-946d-03de13527853', 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '7bb08493-d507-4e28-b9e6-4a5eda9d04f0']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_end', 'data': {'output': 'tool', 'input': {'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'run_id': 'c7fe4d2d-3fb8-4e53-946d-03de13527853', 'name': 'should_continue', 'tags': ['seq:step:3'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '7bb08493-d507-4e28-b9e6-4a5eda9d04f0']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': '7bb08493-d507-4e28-b9e6-4a5eda9d04f0', 'name': 'agent', 'tags': ['graph:step:6'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0}, 'data': {'chunk': {'messages': [{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_end', 'data': {'output': {'messages': [{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}, 'input': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}], 'sleep': None}}, 'run_id': '7bb08493-d507-4e28-b9e6-4a5eda9d04f0', 'name': 'agent', 'tags': ['graph:step:6'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 6, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_start', 'data': {}, 'name': 'tool', 'tags': ['graph:step:7'], 'run_id': 'f044fd3d-7271-488f-b8aa-e01572ff9112', 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 7, 'langgraph_node': 'tool', 'langgraph_triggers': ['branch:agent:should_continue:tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': 'f044fd3d-7271-488f-b8aa-e01572ff9112', 'name': 'tool', 'tags': ['graph:step:7'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 7, 'langgraph_node': 'tool', 'langgraph_triggers': ['branch:agent:should_continue:tool'], 'langgraph_task_idx': 0}, 'data': {'chunk': {'messages': [{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': None, 'tool_call_id': 'tool_call_id'}]}}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_end', 'data': {'output': {'messages': [{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1c9a16d2-5f0a-4eba-a0d2-240484a4ce7e', 'tool_call_id': 'tool_call_id'}]}, 'input': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'sleep': None}}, 'run_id': 'f044fd3d-7271-488f-b8aa-e01572ff9112', 'name': 'tool', 'tags': ['graph:step:7'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 7, 'langgraph_node': 'tool', 'langgraph_triggers': ['branch:agent:should_continue:tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_start', 'data': {}, 'name': 'agent', 'tags': ['graph:step:8'], 'run_id': '1f4f95d0-0ce1-4061-85d4-946446bbd3e5', 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 8, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_start', 'data': {'input': {'messages': [[{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1c9a16d2-5f0a-4eba-a0d2-240484a4ce7e', 'tool_call_id': 'tool_call_id'}]]}}, 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'run_id': '028a68fb-6435-4b46-a156-c3326f73985c', 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 8, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '1f4f95d0-0ce1-4061-85d4-946446bbd3e5']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'e', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-028a68fb-6435-4b46-a156-c3326f73985c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '028a68fb-6435-4b46-a156-c3326f73985c', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 8, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '1f4f95d0-0ce1-4061-85d4-946446bbd3e5']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'n', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-028a68fb-6435-4b46-a156-c3326f73985c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '028a68fb-6435-4b46-a156-c3326f73985c', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 8, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '1f4f95d0-0ce1-4061-85d4-946446bbd3e5']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'd', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-028a68fb-6435-4b46-a156-c3326f73985c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '028a68fb-6435-4b46-a156-c3326f73985c', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 8, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '1f4f95d0-0ce1-4061-85d4-946446bbd3e5']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_end', 'data': {'output': {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-028a68fb-6435-4b46-a156-c3326f73985c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, 'input': {'messages': [[{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1c9a16d2-5f0a-4eba-a0d2-240484a4ce7e', 'tool_call_id': 'tool_call_id'}]]}}, 'run_id': '028a68fb-6435-4b46-a156-c3326f73985c', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 8, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '1f4f95d0-0ce1-4061-85d4-946446bbd3e5']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_start', 'data': {'input': {'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1c9a16d2-5f0a-4eba-a0d2-240484a4ce7e', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-028a68fb-6435-4b46-a156-c3326f73985c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'name': 'should_continue', 'tags': ['seq:step:3'], 'run_id': 'f2b2dfaf-475d-422b-8bf5-02a31bcc7d1a', 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 8, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '1f4f95d0-0ce1-4061-85d4-946446bbd3e5']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_end', 'data': {'output': '__end__', 'input': {'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1c9a16d2-5f0a-4eba-a0d2-240484a4ce7e', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-028a68fb-6435-4b46-a156-c3326f73985c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'run_id': 'f2b2dfaf-475d-422b-8bf5-02a31bcc7d1a', 'name': 'should_continue', 'tags': ['seq:step:3'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 8, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8', '1f4f95d0-0ce1-4061-85d4-946446bbd3e5']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': '1f4f95d0-0ce1-4061-85d4-946446bbd3e5', 'name': 'agent', 'tags': ['graph:step:8'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 8, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0}, 'data': {'chunk': {'messages': [{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-028a68fb-6435-4b46-a156-c3326f73985c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_end', 'data': {'output': {'messages': [{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-028a68fb-6435-4b46-a156-c3326f73985c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}, 'input': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1c9a16d2-5f0a-4eba-a0d2-240484a4ce7e', 'tool_call_id': 'tool_call_id'}], 'sleep': None}}, 'run_id': '1f4f95d0-0ce1-4061-85d4-946446bbd3e5', 'name': 'agent', 'tags': ['graph:step:8'], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 8, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef301a5-b867-67de-9e9e-a32e53c5b1f8']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_end', 'data': {'output': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '51f2874d-f8c7-4040-8b3b-8f15429a56ae', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-5f556aa0-26ea-42e2-b9e4-7ece3a00974e', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1faf5dd0-ae97-4235-963f-5075083a027a', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ae383611-6a42-475a-912a-09d5972e9e94', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c67e08e6-e7af-4c4a-aa5e-50c8340ae341', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-cb1b98c1-c9e2-4a30-9d7a-38fa1f6224bd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '1c9a16d2-5f0a-4eba-a0d2-240484a4ce7e', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-028a68fb-6435-4b46-a156-c3326f73985c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}, 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'name': 'LangGraph', 'tags': [], 'metadata': {'graph_id': 'agent', 'created_by': 'system', 'run_id': '1ef301a5-b867-67de-9e9e-a32e53c5b1f8', 'user_id': '', 'thread_id': '7196a3aa-763c-4a8d-bfda-12fbfe1cd727', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'parent_ids': []}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: end...\n",
|
||||
"None\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# create input\n",
|
||||
"input = {\n",
|
||||
" \"messages\": [\n",
|
||||
" {\n",
|
||||
" \"role\": \"human\",\n",
|
||||
" \"content\": \"What's the weather in SF?\",\n",
|
||||
" }\n",
|
||||
" ]\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"# stream events\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread_id=thread[\"thread_id\"],\n",
|
||||
" assistant_id=\"agent\",\n",
|
||||
" input=input,\n",
|
||||
" stream_mode=\"events\",\n",
|
||||
"):\n",
|
||||
" print(f\"Receiving new event of type: {chunk.event}...\")\n",
|
||||
" print(chunk.data)\n",
|
||||
" print(\"\\n\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Token-by-Token Streaming\n",
|
||||
"\n",
|
||||
"Token-by-token streaming can be implemented with the `events` streaming mode. The `on_chat_model_stream` event type should be processed to stream LLM responses token-by-token."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 28,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"b\n",
|
||||
"be\n",
|
||||
"beg\n",
|
||||
"begi\n",
|
||||
"begin\n",
|
||||
"begine\n",
|
||||
"beginen\n",
|
||||
"beginend\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# create input\n",
|
||||
"input = {\n",
|
||||
" \"messages\": [\n",
|
||||
" {\n",
|
||||
" \"role\": \"human\",\n",
|
||||
" \"content\": \"What's the weather in SF?\",\n",
|
||||
" }\n",
|
||||
" ]\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"llm_response = \"\"\n",
|
||||
"\n",
|
||||
"# stream token-by-token\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread_id=thread[\"thread_id\"],\n",
|
||||
" assistant_id=\"agent\",\n",
|
||||
" input=input,\n",
|
||||
" stream_mode=\"events\",\n",
|
||||
"):\n",
|
||||
" if (\n",
|
||||
" chunk.event == \"events\" and\n",
|
||||
" chunk.data[\"event\"] == \"on_chat_model_stream\"\n",
|
||||
" ):\n",
|
||||
" llm_response += chunk.data[\"data\"][\"chunk\"][\"content\"]\n",
|
||||
" print(llm_response)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "env",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to configure multiple streaming modes at the same time\n",
|
||||
"\n",
|
||||
"This guide covers how to configure multiple streaming modes at the same time."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph_sdk import get_client\n",
|
||||
"\n",
|
||||
"client = get_client()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4',\n",
|
||||
" 'created_at': '2024-06-24T21:30:07.980789+00:00',\n",
|
||||
" 'updated_at': '2024-06-24T21:30:07.980789+00:00',\n",
|
||||
" 'metadata': {}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# create thread\n",
|
||||
"thread = await client.threads.create()\n",
|
||||
"thread"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"When configuring multiple streaming modes for a run, responses for each respective mode will be produced. In the following example, note that a `list` of modes (`messages`, `events`, `debug`) is passed to the `stream_mode` parameter and the response contains `events`, `debug`, `messages/complete`, `messages/metadata`, and `messages/partial` event types."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Receiving new event of type: metadata...\n",
|
||||
"{'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_start', 'data': {'input': {'messages': [{'role': 'human', 'content': \"What's the weather in SF?\"}]}}, 'name': 'LangGraph', 'tags': [], 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'parent_ids': []}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.116009+00:00', 'step': -1, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bc7c-6daa-bfff-6b9027c1a50e', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'messages': []}, 'metadata': {'source': 'input', 'step': -1, 'writes': {'messages': [{'role': 'human', 'content': \"What's the weather in SF?\"}]}}}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.116009+00:00', 'step': -1, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bc7c-6daa-bfff-6b9027c1a50e', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'messages': []}, 'metadata': {'source': 'input', 'step': -1, 'writes': {'messages': [{'role': 'human', 'content': \"What's the weather in SF?\"}]}}}}]}, 'parent_ids': []}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['values', {'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}]}]}, 'parent_ids': []}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: messages/complete...\n",
|
||||
"[{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.117924+00:00', 'step': 0, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bc81-68c8-8000-4e18ae7d67a5', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}]}, 'metadata': {'source': 'loop', 'step': 0, 'writes': None}}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.117924+00:00', 'step': 0, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bc81-68c8-8000-4e18ae7d67a5', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}]}, 'metadata': {'source': 'loop', 'step': 0, 'writes': None}}}]}, 'parent_ids': []}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'task', 'timestamp': '2024-06-24T21:34:06.118042+00:00', 'step': 1, 'payload': {'id': '212ed9c2-a454-50c5-a202-12066bbbe7b8', 'name': 'agent', 'input': {'some_bytes': None, 'some_byte_array': None, 'dict_with_bytes': None, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}], 'sleep': None}, 'triggers': ['start:agent']}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'task', 'timestamp': '2024-06-24T21:34:06.118042+00:00', 'step': 1, 'payload': {'id': '212ed9c2-a454-50c5-a202-12066bbbe7b8', 'name': 'agent', 'input': {'some_bytes': None, 'some_byte_array': None, 'dict_with_bytes': None, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}], 'sleep': None}, 'triggers': ['start:agent']}}]}, 'parent_ids': []}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_start', 'data': {}, 'name': 'agent', 'tags': ['graph:step:1'], 'run_id': '72b74d24-5792-48da-a887-102100d6e2c0', 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_start', 'data': {'input': {'messages': [[{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}]]}}, 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'run_id': '2424dd6d-5cf5-4244-8d98-357640ce6e12', 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', '72b74d24-5792-48da-a887-102100d6e2c0']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'b', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '2424dd6d-5cf5-4244-8d98-357640ce6e12', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', '72b74d24-5792-48da-a887-102100d6e2c0']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: messages/metadata...\n",
|
||||
"{'run-2424dd6d-5cf5-4244-8d98-357640ce6e12': {'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: messages/partial...\n",
|
||||
"[{'content': 'b', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'e', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '2424dd6d-5cf5-4244-8d98-357640ce6e12', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', '72b74d24-5792-48da-a887-102100d6e2c0']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: messages/partial...\n",
|
||||
"[{'content': 'be', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'g', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '2424dd6d-5cf5-4244-8d98-357640ce6e12', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', '72b74d24-5792-48da-a887-102100d6e2c0']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: messages/partial...\n",
|
||||
"[{'content': 'beg', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'i', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '2424dd6d-5cf5-4244-8d98-357640ce6e12', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', '72b74d24-5792-48da-a887-102100d6e2c0']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: messages/partial...\n",
|
||||
"[{'content': 'begi', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'n', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '2424dd6d-5cf5-4244-8d98-357640ce6e12', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', '72b74d24-5792-48da-a887-102100d6e2c0']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: messages/partial...\n",
|
||||
"[{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_end', 'data': {'output': {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, 'input': {'messages': [[{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}]]}}, 'run_id': '2424dd6d-5cf5-4244-8d98-357640ce6e12', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', '72b74d24-5792-48da-a887-102100d6e2c0']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_start', 'data': {'input': {'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'name': 'should_continue', 'tags': ['seq:step:3'], 'run_id': '227afb0f-f909-4d54-a042-556ca6d98a69', 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', '72b74d24-5792-48da-a887-102100d6e2c0']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_end', 'data': {'output': 'tool', 'input': {'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'run_id': '227afb0f-f909-4d54-a042-556ca6d98a69', 'name': 'should_continue', 'tags': ['seq:step:3'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', '72b74d24-5792-48da-a887-102100d6e2c0']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': '72b74d24-5792-48da-a887-102100d6e2c0', 'name': 'agent', 'tags': ['graph:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0}, 'data': {'chunk': {'messages': [{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_end', 'data': {'output': {'messages': [{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}, 'input': {'some_bytes': None, 'some_byte_array': None, 'dict_with_bytes': None, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}], 'sleep': None}}, 'run_id': '72b74d24-5792-48da-a887-102100d6e2c0', 'name': 'agent', 'tags': ['graph:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'task_result', 'timestamp': '2024-06-24T21:34:06.124350+00:00', 'step': 1, 'payload': {'id': '212ed9c2-a454-50c5-a202-12066bbbe7b8', 'name': 'agent', 'result': [['some_bytes', 'c29tZV9ieXRlcw=='], ['some_byte_array', 'c29tZV9ieXRlX2FycmF5'], ['dict_with_bytes', {'more_bytes': 'bW9yZV9ieXRlcw=='}], ['messages', [{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]]]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'task_result', 'timestamp': '2024-06-24T21:34:06.124350+00:00', 'step': 1, 'payload': {'id': '212ed9c2-a454-50c5-a202-12066bbbe7b8', 'name': 'agent', 'result': [['some_bytes', 'c29tZV9ieXRlcw=='], ['some_byte_array', 'c29tZV9ieXRlX2FycmF5'], ['dict_with_bytes', {'more_bytes': 'bW9yZV9ieXRlcw=='}], ['messages', [{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]]]}}]}, 'parent_ids': []}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['values', {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}]}, 'parent_ids': []}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: messages/complete...\n",
|
||||
"[{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.124510+00:00', 'step': 1, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bc91-6a34-8001-26353c117c25', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}, 'metadata': {'source': 'loop', 'step': 1, 'writes': {'agent': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}}}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.124510+00:00', 'step': 1, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bc91-6a34-8001-26353c117c25', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}, 'metadata': {'source': 'loop', 'step': 1, 'writes': {'agent': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}}}}]}, 'parent_ids': []}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'task', 'timestamp': '2024-06-24T21:34:06.124572+00:00', 'step': 2, 'payload': {'id': '44139125-a1be-57c2-9cb2-19eb62bbaf2f', 'name': 'tool', 'input': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'sleep': None}, 'triggers': ['branch:agent:should_continue:tool']}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'task', 'timestamp': '2024-06-24T21:34:06.124572+00:00', 'step': 2, 'payload': {'id': '44139125-a1be-57c2-9cb2-19eb62bbaf2f', 'name': 'tool', 'input': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'sleep': None}, 'triggers': ['branch:agent:should_continue:tool']}}]}, 'parent_ids': []}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_start', 'data': {}, 'name': 'tool', 'tags': ['graph:step:2'], 'run_id': '91575720-886e-485e-ae2d-d6817e5346bf', 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 2, 'langgraph_node': 'tool', 'langgraph_triggers': ['branch:agent:should_continue:tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': '91575720-886e-485e-ae2d-d6817e5346bf', 'name': 'tool', 'tags': ['graph:step:2'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 2, 'langgraph_node': 'tool', 'langgraph_triggers': ['branch:agent:should_continue:tool'], 'langgraph_task_idx': 0}, 'data': {'chunk': {'messages': [{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': None, 'tool_call_id': 'tool_call_id'}]}}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_end', 'data': {'output': {'messages': [{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]}, 'input': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'sleep': None}}, 'run_id': '91575720-886e-485e-ae2d-d6817e5346bf', 'name': 'tool', 'tags': ['graph:step:2'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 2, 'langgraph_node': 'tool', 'langgraph_triggers': ['branch:agent:should_continue:tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'task_result', 'timestamp': '2024-06-24T21:34:06.126828+00:00', 'step': 2, 'payload': {'id': '44139125-a1be-57c2-9cb2-19eb62bbaf2f', 'name': 'tool', 'result': [['messages', [{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]]]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'task_result', 'timestamp': '2024-06-24T21:34:06.126828+00:00', 'step': 2, 'payload': {'id': '44139125-a1be-57c2-9cb2-19eb62bbaf2f', 'name': 'tool', 'result': [['messages', [{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]]]}}]}, 'parent_ids': []}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['values', {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]}]}, 'parent_ids': []}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: messages/complete...\n",
|
||||
"[{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.126966+00:00', 'step': 2, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bc97-6a06-8002-8e9ffc1ea75a', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]}, 'metadata': {'source': 'loop', 'step': 2, 'writes': {'tool': {'messages': [{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]}}}}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.126966+00:00', 'step': 2, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bc97-6a06-8002-8e9ffc1ea75a', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]}, 'metadata': {'source': 'loop', 'step': 2, 'writes': {'tool': {'messages': [{'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]}}}}}]}, 'parent_ids': []}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'task', 'timestamp': '2024-06-24T21:34:06.127034+00:00', 'step': 3, 'payload': {'id': 'f1ccf371-63b3-5268-a837-7f360a93c4ec', 'name': 'agent', 'input': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}], 'sleep': None}, 'triggers': ['tool']}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'task', 'timestamp': '2024-06-24T21:34:06.127034+00:00', 'step': 3, 'payload': {'id': 'f1ccf371-63b3-5268-a837-7f360a93c4ec', 'name': 'agent', 'input': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}], 'sleep': None}, 'triggers': ['tool']}}]}, 'parent_ids': []}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_start', 'data': {}, 'name': 'agent', 'tags': ['graph:step:3'], 'run_id': 'b7d0900c-bfc2-43e4-b760-99bbc5bad84e', 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_start', 'data': {'input': {'messages': [[{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]]}}, 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'run_id': '0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', 'b7d0900c-bfc2-43e4-b760-99bbc5bad84e']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'e', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', 'b7d0900c-bfc2-43e4-b760-99bbc5bad84e']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: messages/metadata...\n",
|
||||
"{'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575': {'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: messages/partial...\n",
|
||||
"[{'content': 'e', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'n', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', 'b7d0900c-bfc2-43e4-b760-99bbc5bad84e']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: messages/partial...\n",
|
||||
"[{'content': 'en', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_stream', 'data': {'chunk': {'content': 'd', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'AIMessageChunk', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None, 'tool_call_chunks': []}}, 'run_id': '0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', 'b7d0900c-bfc2-43e4-b760-99bbc5bad84e']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: messages/partial...\n",
|
||||
"[{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chat_model_end', 'data': {'output': {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, 'input': {'messages': [[{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}]]}}, 'run_id': '0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'name': 'FakeListChatModel', 'tags': ['seq:step:1'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0, 'ls_model_type': 'chat'}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', 'b7d0900c-bfc2-43e4-b760-99bbc5bad84e']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_start', 'data': {'input': {'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'name': 'should_continue', 'tags': ['seq:step:3'], 'run_id': '8af814e9-8136-4aab-acbc-dffc5bcafdfd', 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', 'b7d0900c-bfc2-43e4-b760-99bbc5bad84e']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_end', 'data': {'output': '__end__', 'input': {'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'run_id': '8af814e9-8136-4aab-acbc-dffc5bcafdfd', 'name': 'should_continue', 'tags': ['seq:step:3'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25', 'b7d0900c-bfc2-43e4-b760-99bbc5bad84e']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': 'b7d0900c-bfc2-43e4-b760-99bbc5bad84e', 'name': 'agent', 'tags': ['graph:step:3'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0}, 'data': {'chunk': {'messages': [{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_end', 'data': {'output': {'messages': [{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}], 'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}}, 'input': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}], 'sleep': None}}, 'run_id': 'b7d0900c-bfc2-43e4-b760-99bbc5bad84e', 'name': 'agent', 'tags': ['graph:step:3'], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 3, 'langgraph_node': 'agent', 'langgraph_triggers': ['tool'], 'langgraph_task_idx': 0}, 'parent_ids': ['1ef32717-bc30-6cf2-8a26-33f63567bc25']}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'task_result', 'timestamp': '2024-06-24T21:34:06.133991+00:00', 'step': 3, 'payload': {'id': 'f1ccf371-63b3-5268-a837-7f360a93c4ec', 'name': 'agent', 'result': [['some_bytes', 'c29tZV9ieXRlcw=='], ['some_byte_array', 'c29tZV9ieXRlX2FycmF5'], ['dict_with_bytes', {'more_bytes': 'bW9yZV9ieXRlcw=='}], ['messages', [{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]]]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'task_result', 'timestamp': '2024-06-24T21:34:06.133991+00:00', 'step': 3, 'payload': {'id': 'f1ccf371-63b3-5268-a837-7f360a93c4ec', 'name': 'agent', 'result': [['some_bytes', 'c29tZV9ieXRlcw=='], ['some_byte_array', 'c29tZV9ieXRlX2FycmF5'], ['dict_with_bytes', {'more_bytes': 'bW9yZV9ieXRlcw=='}], ['messages', [{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]]]}}]}, 'parent_ids': []}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['values', {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}]}, 'parent_ids': []}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: messages/complete...\n",
|
||||
"[{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.134190+00:00', 'step': 3, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bca9-6418-8003-8d0d0b06845c', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}, 'metadata': {'source': 'loop', 'step': 3, 'writes': {'agent': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}}}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['debug', {'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.134190+00:00', 'step': 3, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bca9-6418-8003-8d0d0b06845c', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}, 'metadata': {'source': 'loop', 'step': 3, 'writes': {'agent': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}}}}]}, 'parent_ids': []}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: events...\n",
|
||||
"{'event': 'on_chain_end', 'data': {'output': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': \"What's the weather in SF?\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'parent_ids': []}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: end...\n",
|
||||
"None\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# create input\n",
|
||||
"input = {\n",
|
||||
" \"messages\": [\n",
|
||||
" {\n",
|
||||
" \"role\": \"human\",\n",
|
||||
" \"content\": \"What's the weather in SF?\",\n",
|
||||
" }\n",
|
||||
" ]\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"# stream events with multiple streaming modes\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread_id=thread[\"thread_id\"],\n",
|
||||
" assistant_id=\"agent\",\n",
|
||||
" input=input,\n",
|
||||
" stream_mode=[\"messages\", \"events\", \"debug\"],\n",
|
||||
"):\n",
|
||||
" print(f\"Receiving new event of type: {chunk.event}...\")\n",
|
||||
" print(chunk.data)\n",
|
||||
" print(\"\\n\\n\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "env",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -5,16 +5,16 @@
|
||||
"id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to stream updates from your graph\n",
|
||||
"# How to stream state updates of your graph\n",
|
||||
"\n",
|
||||
"There are multiple different streaming modes.\n",
|
||||
"LangGraph Cloud supports multiple streaming modes. The main ones are:\n",
|
||||
"\n",
|
||||
"- `values`: This streaming mode streams back values of the graph. This is the **full state of the graph** after each node is called.\n",
|
||||
"- `updates`: This streaming mode streams back updates to the graph. This is the **update to the state of the graph** after each node is called.\n",
|
||||
"- `messages`: This streaming mode streams back messages - both complete messages (at the end of a node) as well as **tokens** for any messages generated inside a node. This mode is primarily meant for powering chat applications.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"This notebook covers `streaming_mode=\"updates\"`."
|
||||
"This guide covers `stream_mode=\"updates\"`."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -40,75 +40,29 @@
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "4947e9bc-111f-4991-8c41-1041da9bf0ba",
|
||||
"id": "230c0464-a6e5-420f-9e38-ca514e5634ce",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n",
|
||||
" 'graph_id': 'agent',\n",
|
||||
" 'config': {},\n",
|
||||
" 'created_at': '2024-05-18T00:19:39.688822+00:00',\n",
|
||||
" 'updated_at': '2024-05-18T00:19:39.688822+00:00',\n",
|
||||
" 'metadata': {'created_by': 'system'}}]"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# There should always be a default assistant with no configuration\n",
|
||||
"assistants = await client.assistants.search()\n",
|
||||
"assistants = [a for a in assistants if not a['config']]\n",
|
||||
"assistants"
|
||||
"assistant_id = \"agent\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "230c0464-a6e5-420f-9e38-ca514e5634ce",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n",
|
||||
" 'graph_id': 'agent',\n",
|
||||
" 'config': {},\n",
|
||||
" 'created_at': '2024-05-18T00:19:39.688822+00:00',\n",
|
||||
" 'updated_at': '2024-05-18T00:19:39.688822+00:00',\n",
|
||||
" 'metadata': {'created_by': 'system'}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"assistant = assistants[0]\n",
|
||||
"assistant"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "56aa5159-5583-4134-9210-709b969bda6f",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'thread_id': '1eee9bd4-ec61-4300-a38a-2e13b8925d39',\n",
|
||||
" 'created_at': '2024-05-18T19:57:36.509105+00:00',\n",
|
||||
" 'updated_at': '2024-05-18T19:57:36.509105+00:00',\n",
|
||||
"{'thread_id': '979e3c89-a702-4882-87c2-7a59a250ce16',\n",
|
||||
" 'created_at': '2024-06-21T15:22:07.453100+00:00',\n",
|
||||
" 'updated_at': '2024-06-21T15:22:07.453100+00:00',\n",
|
||||
" 'metadata': {}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
@@ -120,7 +74,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"execution_count": 5,
|
||||
"id": "147c3f98-f889-4f05-a090-6b31f2a0b291",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -130,13 +84,13 @@
|
||||
"[]"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"runs = await client.runs.list(thread['thread_id'])\n",
|
||||
"runs = await client.runs.list(thread[\"thread_id\"])\n",
|
||||
"runs"
|
||||
]
|
||||
},
|
||||
@@ -180,7 +134,12 @@
|
||||
],
|
||||
"source": [
|
||||
"input = {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in la\"}]}\n",
|
||||
"async for chunk in client.runs.stream(thread['thread_id'], assistant['assistant_id'], input=input, stream_mode=\"updates\", ):\n",
|
||||
"async for chunk in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant_id,\n",
|
||||
" input=input,\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
"):\n",
|
||||
" print(f\"Receiving new event of type: {chunk.event}...\")\n",
|
||||
" print(chunk.data)\n",
|
||||
" print(\"\\n\\n\")"
|
||||
@@ -211,7 +170,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.1"
|
||||
"version": "3.12.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
"id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to stream values from your graph\n",
|
||||
"# How to stream full state of your graph\n",
|
||||
"\n",
|
||||
"There are multiple different streaming modes.\n",
|
||||
"LangGraph Cloud supports multiple streaming modes. The main ones are:\n",
|
||||
"\n",
|
||||
"- `values`: This streaming mode streams back values of the graph. This is the **full state of the graph** after each node is called.\n",
|
||||
"- `updates`: This streaming mode streams back updates to the graph. This is the **update to the state of the graph** after each node is called.\n",
|
||||
"- `messages`: This streaming mode streams back messages - both complete messages (at the end of a node) as well as **tokens** for any messages generated inside a node. This mode is primarily meant for powering chat applications.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"This notebook covers `streaming_mode=\"values\"`."
|
||||
"This guide covers `stream_mode=\"values\"`."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -40,57 +40,11 @@
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "4947e9bc-111f-4991-8c41-1041da9bf0ba",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n",
|
||||
" 'graph_id': 'agent',\n",
|
||||
" 'config': {},\n",
|
||||
" 'created_at': '2024-05-18T00:19:39.688822+00:00',\n",
|
||||
" 'updated_at': '2024-05-18T00:19:39.688822+00:00',\n",
|
||||
" 'metadata': {'created_by': 'system'}}]"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# There should always be a default assistant with no configuration\n",
|
||||
"assistants = await client.assistants.search()\n",
|
||||
"assistants = [a for a in assistants if not a['config']]\n",
|
||||
"assistants"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "230c0464-a6e5-420f-9e38-ca514e5634ce",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n",
|
||||
" 'graph_id': 'agent',\n",
|
||||
" 'config': {},\n",
|
||||
" 'created_at': '2024-05-18T00:19:39.688822+00:00',\n",
|
||||
" 'updated_at': '2024-05-18T00:19:39.688822+00:00',\n",
|
||||
" 'metadata': {'created_by': 'system'}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"assistant = assistants[0]\n",
|
||||
"assistant"
|
||||
"assistant_id = \"agent\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -139,7 +93,7 @@
|
||||
"source": [
|
||||
"input = {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in la\"}]}\n",
|
||||
"thread = await client.threads.create()\n",
|
||||
"async for chunk in client.runs.stream(thread['thread_id'], assistant['assistant_id'], input=input):\n",
|
||||
"async for chunk in client.runs.stream(thread[\"thread_id\"], assistant_id, input=input):\n",
|
||||
" print(f\"Receiving new event of type: {chunk.event}...\")\n",
|
||||
" print(chunk.data)\n",
|
||||
" print(\"\\n\\n\")"
|
||||
@@ -163,7 +117,7 @@
|
||||
"input = {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in la\"}]}\n",
|
||||
"thread = await client.threads.create()\n",
|
||||
"final_answer = None\n",
|
||||
"async for chunk in client.runs.stream(thread['thread_id'], assistant['assistant_id'], input=input):\n",
|
||||
"async for chunk in client.runs.stream(thread[\"thread_id\"], assistant_id, input=input):\n",
|
||||
" if chunk.event == \"values\":\n",
|
||||
" final_answer = chunk.data"
|
||||
]
|
||||
@@ -250,7 +204,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.1"
|
||||
"version": "3.12.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Use Webhooks\n",
|
||||
"\n",
|
||||
"You may wish to use webhooks in your client, especially when using async streams in case you want to update something in your service once the API call to Langgraph Cloud has finished running. To do so, you will need to expose an endpoint that can accept POST requests, and then pass it to your API request in the \"webhook\" parameter.\n",
|
||||
"\n",
|
||||
"Currently, the SDK has not exposed this endpoint but you can access it through curl commands as follows.\n",
|
||||
"\n",
|
||||
"The following endpoints accept `webhook` as a parameter: \n",
|
||||
"\n",
|
||||
"- Create Run -> POST /thread/{thread_id}/runs\n",
|
||||
"- Create Thread Cron -> POST /thread/{thread_id}/runs/crons\n",
|
||||
"- Stream Run -> POST /thread/{thread_id}/runs/stream\n",
|
||||
"- Wait Run -> POST /thread/{thread_id}/runs/wait\n",
|
||||
"- Create Cron -> POST /runs/crons\n",
|
||||
"- Stream Run Stateless -> POST /runs/stream\n",
|
||||
"- Wait Run Stateless -> POST /runs/wait\n",
|
||||
"\n",
|
||||
"The following example uses a url from a public website that allows users to create free webhooks, but you should pass in the webhook that you wish to use. "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"curl --request POST \\\n",
|
||||
" --url http://localhost:8123/threads/b76d1e94-f251-40e3-8933-796d775cdb4c/runs/stream \\\n",
|
||||
" --header 'Content-Type: application/json' \\\n",
|
||||
" --data '{\n",
|
||||
" \"assistant_id\": \"fe096781-5601-53d2-b2f6-0d3403f7e9ca\",\n",
|
||||
" \"input\" : {\"messages\":[{\"role\": \"user\", \"content\": \"Hello! My name is Bagatur and I am 26 years old.\"}]},\n",
|
||||
" \"metadata\": {},\n",
|
||||
" \"config\": {\n",
|
||||
" \"configurable\": {}\n",
|
||||
" },\n",
|
||||
" \"multitask_strategy\": \"reject\",\n",
|
||||
" \"stream_mode\": [\n",
|
||||
" \"values\"\n",
|
||||
" ],\n",
|
||||
" \"webhook\": \"https://webhook.site/6ca33471-dd65-4103-a851-0a252dae0f2a\"\n",
|
||||
"}'"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"To check that this worked as intended, we can go to the website where our webhook was created and confirm that it received a POST request:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
""
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -82,7 +82,7 @@
|
||||
"\n",
|
||||
"### Code solution\n",
|
||||
"\n",
|
||||
"Try OpenAI and [Claude3](https://docs.anthropic.com/claude/docs/models-overview) with function calling.\n",
|
||||
"Try OpenAI and [Claude3](https://docs.anthropic.com/en/docs/about-claude/models) with function calling.\n",
|
||||
"\n",
|
||||
"Create `code_gen_chain` w/ either OpenAI or Claude and test here."
|
||||
]
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "992c4695-ec4f-428d-bd05-fb3b5fbd70f4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to add human-in-the-loop processes to the prebuilt ReAct agent\n",
|
||||
"\n",
|
||||
"This tutorial will show how to add human-in-the-loop processes to the prebuilt ReAct agent. Please see [this tutorial](./create-react-agent.ipynb) for how to get started with the prebuilt ReAct agent\n",
|
||||
"\n",
|
||||
"You can add a a breakpoint before tools are called by passing `interrupt_before=[\"tools\"]` to `create_react_agent`. Note that you need to be using a checkpointer for this to work."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7be3889f-3c17-4fa1-bd2b-84114a2c7247",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "a213e11a-5c62-4ddb-a707-490d91add383",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U langgraph langchain-openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "23a1885c-04ab-4750-aefa-105891fddf3e",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"OPENAI_API_KEY: ········\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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\")\n",
|
||||
"\n",
|
||||
"# Recommended\n",
|
||||
"_set_env(\"LANGCHAIN_API_KEY\")\n",
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_PROJECT\"] = \"Create ReAct Agent Tutorial\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "03c0f089-070c-4cd4-87e0-6c51f2477b82",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "7a154152-973e-4b5d-aa13-48c617744a4c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# First we initialize the model we want to use.\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"model = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# For this tutorial we will use custom tool that returns pre-defined values for weather in two cities (NYC & SF)\n",
|
||||
"\n",
|
||||
"from typing import Literal\n",
|
||||
"\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def get_weather(city: Literal[\"nyc\", \"sf\"]):\n",
|
||||
" \"\"\"Use this to get weather information.\"\"\"\n",
|
||||
" if city == \"nyc\":\n",
|
||||
" return \"It might be cloudy in nyc\"\n",
|
||||
" elif city == \"sf\":\n",
|
||||
" return \"It's always sunny in sf\"\n",
|
||||
" else:\n",
|
||||
" raise AssertionError(\"Unknown city\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [get_weather]\n",
|
||||
"\n",
|
||||
"# We need a checkpointer to enable human-in-the-loop patterns\n",
|
||||
"from langgraph.checkpoint import MemorySaver\n",
|
||||
"\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"\n",
|
||||
"# Define the graph\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"\n",
|
||||
"graph = create_react_agent(model, tools=tools, interrupt_before=[\"tools\"], checkpointer=memory)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "00407425-506d-4ffd-9c86-987921d8c844",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Usage\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "16636975-5f2d-4dc7-ab8e-d0bea0830a28",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def print_stream(stream):\n",
|
||||
" for s in stream:\n",
|
||||
" message = s[\"messages\"][-1]\n",
|
||||
" if isinstance(message, tuple):\n",
|
||||
" print(message)\n",
|
||||
" else:\n",
|
||||
" message.pretty_print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "9ffff6c3-a4f5-47c9-b51d-97caaee85cd6",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||||
"\n",
|
||||
"What's the weather in SF?\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"Tool Calls:\n",
|
||||
" get_weather (call_0OMmuTLec9t8kxMVkllZCSxo)\n",
|
||||
" Call ID: call_0OMmuTLec9t8kxMVkllZCSxo\n",
|
||||
" Args:\n",
|
||||
" city: sf\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"config = {\"configurable\": {\"thread_id\": \"42\"}}\n",
|
||||
"inputs = {\"messages\": [(\"user\", \"What's the weather in SF?\")]}\n",
|
||||
"\n",
|
||||
"print_stream(graph.stream(inputs, config, stream_mode=\"values\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "3decf001-7228-4ed5-8779-2b9ed98a74ea",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Next step: ('tools',)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"snapshot = graph.get_state(config)\n",
|
||||
"print(\"Next step: \", snapshot.next)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "83148e08-63e8-49e5-a08b-02dc907bed1d",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
||||
"Name: get_weather\n",
|
||||
"\n",
|
||||
"It's always sunny in sf\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"The weather in San Francisco is currently sunny.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print_stream(graph.stream(None, config, stream_mode=\"values\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6f6f8965-b016-4e25-be63-31c00fc0a6de",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.1"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "992c4695-ec4f-428d-bd05-fb3b5fbd70f4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to add memory to the prebuilt ReAct agent\n",
|
||||
"\n",
|
||||
"This tutorial will show how to add memory to the prebuilt ReAct agent. Please see [this tutorial](./create-react-agent.ipynb) for how to get started with the prebuilt ReAct agent\n",
|
||||
"\n",
|
||||
"All we need to do to enable memory is pass in a checkpointer to `create_react_agents`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7be3889f-3c17-4fa1-bd2b-84114a2c7247",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "a213e11a-5c62-4ddb-a707-490d91add383",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U langgraph langchain-openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "23a1885c-04ab-4750-aefa-105891fddf3e",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"OPENAI_API_KEY: ········\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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\")\n",
|
||||
"\n",
|
||||
"# Recommended\n",
|
||||
"_set_env(\"LANGCHAIN_API_KEY\")\n",
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_PROJECT\"] = \"Create ReAct Agent Tutorial\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "03c0f089-070c-4cd4-87e0-6c51f2477b82",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "7a154152-973e-4b5d-aa13-48c617744a4c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# First we initialize the model we want to use.\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"model = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# For this tutorial we will use custom tool that returns pre-defined values for weather in two cities (NYC & SF)\n",
|
||||
"\n",
|
||||
"from typing import Literal\n",
|
||||
"\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def get_weather(city: Literal[\"nyc\", \"sf\"]):\n",
|
||||
" \"\"\"Use this to get weather information.\"\"\"\n",
|
||||
" if city == \"nyc\":\n",
|
||||
" return \"It might be cloudy in nyc\"\n",
|
||||
" elif city == \"sf\":\n",
|
||||
" return \"It's always sunny in sf\"\n",
|
||||
" else:\n",
|
||||
" raise AssertionError(\"Unknown city\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [get_weather]\n",
|
||||
"\n",
|
||||
"# We can add \"chat memory\" to the graph with LangGraph's checkpointer\n",
|
||||
"# to retain the chat context between interactions\n",
|
||||
"from langgraph.checkpoint import MemorySaver\n",
|
||||
"\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"\n",
|
||||
"# Define the graph\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"\n",
|
||||
"graph = create_react_agent(model, tools=tools, checkpointer=memory)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "00407425-506d-4ffd-9c86-987921d8c844",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Usage\n",
|
||||
"\n",
|
||||
"Let's interact with it multiple times to show that it can remember"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "16636975-5f2d-4dc7-ab8e-d0bea0830a28",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def print_stream(stream):\n",
|
||||
" for s in stream:\n",
|
||||
" message = s[\"messages\"][-1]\n",
|
||||
" if isinstance(message, tuple):\n",
|
||||
" print(message)\n",
|
||||
" else:\n",
|
||||
" message.pretty_print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "9ffff6c3-a4f5-47c9-b51d-97caaee85cd6",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||||
"\n",
|
||||
"What's the weather in NYC?\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"Tool Calls:\n",
|
||||
" get_weather (call_mdovy4yXSSYrmSlnlVSUacVn)\n",
|
||||
" Call ID: call_mdovy4yXSSYrmSlnlVSUacVn\n",
|
||||
" Args:\n",
|
||||
" city: nyc\n",
|
||||
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
||||
"Name: get_weather\n",
|
||||
"\n",
|
||||
"It might be cloudy in nyc\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"The weather in NYC might be cloudy.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
|
||||
"inputs = {\"messages\": [(\"user\", \"What's the weather in NYC?\")]}\n",
|
||||
"\n",
|
||||
"print_stream(graph.stream(inputs, config=config, stream_mode=\"values\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "838a043f-90ad-4e69-9d1d-6e22db2c346c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Notice that when we pass the same the same thread ID, the chat history is preserved"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "187479f9-32fa-4611-9487-cf816ba2e147",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||||
"\n",
|
||||
"What's it known for?\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"New York City (NYC) is known for many things, including:\n",
|
||||
"\n",
|
||||
"1. **Landmarks and Attractions**: The Statue of Liberty, Times Square, Central Park, Empire State Building, and Brooklyn Bridge.\n",
|
||||
"2. **Cultural Institutions**: Broadway theaters, Metropolitan Museum of Art, Museum of Modern Art (MoMA), and the American Museum of Natural History.\n",
|
||||
"3. **Diverse Neighborhoods**: Areas like Chinatown, Little Italy, Harlem, and Greenwich Village.\n",
|
||||
"4. **Financial Hub**: Wall Street and the New York Stock Exchange.\n",
|
||||
"5. **Cuisine**: A melting pot of global cuisines, famous for its pizza, bagels, and street food.\n",
|
||||
"6. **Media and Entertainment**: Home to major media companies, TV networks, and film studios.\n",
|
||||
"7. **Fashion**: A global fashion capital, hosting New York Fashion Week.\n",
|
||||
"8. **Sports**: Teams like the New York Yankees, New York Mets, New York Knicks, and New York Rangers.\n",
|
||||
"9. **Public Transportation**: An extensive subway system and iconic yellow taxis.\n",
|
||||
"10. **Events**: New Year's Eve celebration in Times Square, Macy's Thanksgiving Day Parade, and various cultural festivals.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [(\"user\", \"What's it known for?\")]}\n",
|
||||
"print_stream(graph.stream(inputs, config=config, stream_mode=\"values\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3decf001-7228-4ed5-8779-2b9ed98a74ea",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.1"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "992c4695-ec4f-428d-bd05-fb3b5fbd70f4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to add a custom system prompt to the prebuilt ReAct agent\n",
|
||||
"\n",
|
||||
"This tutorial will show how to add a custom system prompt to the prebuilt ReAct agent. Please see [this tutorial](./create-react-agent.ipynb) for how to get started with the prebuilt ReAct agent\n",
|
||||
"\n",
|
||||
"You can add a custom system prompt by passing a string to the `messages_modifier` param."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7be3889f-3c17-4fa1-bd2b-84114a2c7247",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "a213e11a-5c62-4ddb-a707-490d91add383",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U langgraph langchain-openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "23a1885c-04ab-4750-aefa-105891fddf3e",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"OPENAI_API_KEY: ········\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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\")\n",
|
||||
"\n",
|
||||
"# Recommended\n",
|
||||
"_set_env(\"LANGCHAIN_API_KEY\")\n",
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_PROJECT\"] = \"Create ReAct Agent Tutorial\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "03c0f089-070c-4cd4-87e0-6c51f2477b82",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "7a154152-973e-4b5d-aa13-48c617744a4c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# First we initialize the model we want to use.\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"model = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# For this tutorial we will use custom tool that returns pre-defined values for weather in two cities (NYC & SF)\n",
|
||||
"\n",
|
||||
"from typing import Literal\n",
|
||||
"\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def get_weather(city: Literal[\"nyc\", \"sf\"]):\n",
|
||||
" \"\"\"Use this to get weather information.\"\"\"\n",
|
||||
" if city == \"nyc\":\n",
|
||||
" return \"It might be cloudy in nyc\"\n",
|
||||
" elif city == \"sf\":\n",
|
||||
" return \"It's always sunny in sf\"\n",
|
||||
" else:\n",
|
||||
" raise AssertionError(\"Unknown city\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [get_weather]\n",
|
||||
"\n",
|
||||
"# We can add our system prompt here\n",
|
||||
"\n",
|
||||
"prompt = \"Respond in Italian\"\n",
|
||||
"\n",
|
||||
"# Define the graph\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"\n",
|
||||
"graph = create_react_agent(model, tools=tools, messages_modifier=prompt)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "00407425-506d-4ffd-9c86-987921d8c844",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Usage\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "16636975-5f2d-4dc7-ab8e-d0bea0830a28",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def print_stream(stream):\n",
|
||||
" for s in stream:\n",
|
||||
" message = s[\"messages\"][-1]\n",
|
||||
" if isinstance(message, tuple):\n",
|
||||
" print(message)\n",
|
||||
" else:\n",
|
||||
" message.pretty_print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "9ffff6c3-a4f5-47c9-b51d-97caaee85cd6",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||||
"\n",
|
||||
"What's the weather in NYC?\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"Tool Calls:\n",
|
||||
" get_weather (call_b02uzBRrIm2uciJa8zDXCDxT)\n",
|
||||
" Call ID: call_b02uzBRrIm2uciJa8zDXCDxT\n",
|
||||
" Args:\n",
|
||||
" city: nyc\n",
|
||||
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
||||
"Name: get_weather\n",
|
||||
"\n",
|
||||
"It might be cloudy in nyc\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"A New York potrebbe essere nuvoloso.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [(\"user\", \"What's the weather in NYC?\")]}\n",
|
||||
"\n",
|
||||
"print_stream(graph.stream(inputs, stream_mode=\"values\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3decf001-7228-4ed5-8779-2b9ed98a74ea",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.1"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"id": "992c4695-ec4f-428d-bd05-fb3b5fbd70f4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to create a ReAct agent"
|
||||
"# How to use the prebuilt ReAct agent"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -16,7 +16,14 @@
|
||||
"In this how-to we'll create a simple [ReAct](https://arxiv.org/abs/2210.03629) agent app that can check the weather. The app consists of an agent (LLM) and tools. As we interact with the app, we will first call the agent (LLM) to decide if we should use tools. Then we will run a loop: \n",
|
||||
"\n",
|
||||
"1. If the agent said to take an action (i.e. call tool), we'll run the tools and pass the results back to the agent\n",
|
||||
"2. If the agent did not ask to run tools, we will finish (respond to the user)"
|
||||
"2. If the agent did not ask to run tools, we will finish (respond to the user)\n",
|
||||
"\n",
|
||||
"<div class=\"admonition warning\">\n",
|
||||
" <p class=\"admonition-title\">Prebuilt Agent</p>\n",
|
||||
" <p>\n",
|
||||
"Please note that here will we use a prebuilt agent. One of the big benefits of LangGraph is that you can easily create your own agent architectures. So while it's fine to start here to build an agent quickly, we would strongly recommend learning how to build your own agent so that you can take full advantage of LangGraph.\n",
|
||||
" </p>\n",
|
||||
"</div> "
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -75,40 +82,29 @@
|
||||
"id": "03c0f089-070c-4cd4-87e0-6c51f2477b82",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## How to create a simple ReAct agent with `create_react_agent`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "91efaf20-711b-4b94-837e-dcef10d8abdd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In our example we'll use `ChatOpenAI` as our agent and a custom tool that returns pre-defined values for weather in two cities (NYC & SF)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6238f7d4-7da1-4e48-bc5e-a592280271e0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Define model and tools"
|
||||
"## Code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "029c838a-c6e7-4679-9a8b-953703fe3041",
|
||||
"execution_count": 1,
|
||||
"id": "7a154152-973e-4b5d-aa13-48c617744a4c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Literal\n",
|
||||
"\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"# First we initialize the model we want to use.\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"model = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# For this tutorial we will use custom tool that returns pre-defined values for weather in two cities (NYC & SF)\n",
|
||||
"\n",
|
||||
"from typing import Literal\n",
|
||||
"\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def get_weather(city: Literal[\"nyc\", \"sf\"]):\n",
|
||||
" \"\"\"Use this to get weather information.\"\"\"\n",
|
||||
@@ -120,32 +116,29 @@
|
||||
" raise AssertionError(\"Unknown city\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [get_weather]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "47570e6c-655b-45ae-ad4a-b278e58f1b94",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Define the graph"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "3d2df9e7-4c2c-494a-9249-5a2ee32eca3e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"tools = [get_weather]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the graph\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"\n",
|
||||
"graph = create_react_agent(model, tools=tools)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "00407425-506d-4ffd-9c86-987921d8c844",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Usage\n",
|
||||
"\n",
|
||||
"First, let's visualize the graph we just created"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"execution_count": 4,
|
||||
"id": "fa16de4c-aac0-4ff4-ab69-60d399f75423",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -168,7 +161,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"execution_count": 5,
|
||||
"id": "16636975-5f2d-4dc7-ab8e-d0bea0830a28",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -192,7 +185,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"execution_count": 6,
|
||||
"id": "9ffff6c3-a4f5-47c9-b51d-97caaee85cd6",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -205,8 +198,8 @@
|
||||
"what is the weather in sf\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"Tool Calls:\n",
|
||||
" get_weather (call_g6w9lHn3fxYo2ABE3Ihhprbr)\n",
|
||||
" Call ID: call_g6w9lHn3fxYo2ABE3Ihhprbr\n",
|
||||
" get_weather (call_jgO5OOUnugRkhRi3wAOHl8Et)\n",
|
||||
" Call ID: call_jgO5OOUnugRkhRi3wAOHl8Et\n",
|
||||
" Args:\n",
|
||||
" city: sf\n",
|
||||
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
||||
@@ -234,7 +227,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"execution_count": 7,
|
||||
"id": "187479f9-32fa-4611-9487-cf816ba2e147",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -255,383 +248,13 @@
|
||||
"inputs = {\"messages\": [(\"user\", \"who built you?\")]}\n",
|
||||
"print_stream(graph.stream(inputs, stream_mode=\"values\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "82b06aa3-6414-48e6-ba11-07bf065a316a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## How to add system prompt to `create_react_agent`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a3823699-1050-40fc-a8cc-ecbdc03c1cbe",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"There are several ways to customize prompt, all of which are controlled by `messages_modifier` param. You can pass:\n",
|
||||
"- system message string / `SystemMessage` that will be prepended to the list of messages\n",
|
||||
"- a function that takes a list of messages and transforms them into an output that can be passed to the language model"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "23ea0a31-3e6b-433b-a832-036655579ebe",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||||
"\n",
|
||||
"What's your name? And what's the weather in SF?\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"Tool Calls:\n",
|
||||
" get_weather (call_PGwTiytTVAAvNKWp4nznPi4s)\n",
|
||||
" Call ID: call_PGwTiytTVAAvNKWp4nznPi4s\n",
|
||||
" Args:\n",
|
||||
" city: sf\n",
|
||||
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
||||
"Name: get_weather\n",
|
||||
"\n",
|
||||
"It's always sunny in sf\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"My name is Fred. The weather in San Francisco is currently sunny.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"system_prompt = \"You are a helpful bot named Fred.\"\n",
|
||||
"graph = create_react_agent(model, tools, messages_modifier=system_prompt)\n",
|
||||
"\n",
|
||||
"inputs = {\"messages\": [(\"user\", \"What's your name? And what's the weather in SF?\")]}\n",
|
||||
"print_stream(graph.stream(inputs, stream_mode=\"values\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d8e797ce-74bf-4a30-a388-08104138fd52",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can also add a more complex prompt for the LLM:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "23c40ab3-c574-47be-a80f-654e9035cd6d",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||||
"\n",
|
||||
"What's my name? And what's the weather in SF?\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"Tool Calls:\n",
|
||||
" get_weather (call_scpzIjdK3l411zcEn2T00Xm0)\n",
|
||||
" Call ID: call_scpzIjdK3l411zcEn2T00Xm0\n",
|
||||
" Args:\n",
|
||||
" city: sf\n",
|
||||
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
||||
"Name: get_weather\n",
|
||||
"\n",
|
||||
"It's always sunny in sf\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"Your name is Joe. The weather in San Francisco is always sunny.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.prompts import ChatPromptTemplate\n",
|
||||
"\n",
|
||||
"prompt = ChatPromptTemplate.from_messages(\n",
|
||||
" [\n",
|
||||
" (\"system\", \"You are a helpful bot named Fred.\"),\n",
|
||||
" (\"user\", \"My name is Joe\"),\n",
|
||||
" (\"placeholder\", \"{messages}\"),\n",
|
||||
" ]\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def modify_messages(messages: list):\n",
|
||||
" # You can do more complex modifications here\n",
|
||||
" return prompt.invoke({\"messages\": messages})\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"graph = create_react_agent(model, tools, messages_modifier=modify_messages)\n",
|
||||
"\n",
|
||||
"inputs = {\"messages\": [(\"user\", \"What's my name? And what's the weather in SF?\")]}\n",
|
||||
"print_stream(graph.stream(inputs, stream_mode=\"values\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0af418b8-bdd3-4e10-a552-9d8d2a409a57",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## How to add memory to `create_react_agent`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4eef6365-530b-4d33-9874-41273ebee510",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can add \"chat memory\" to the graph with LangGraph's checkpointer, to retain the chat context between interactions"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "c43d071a-a91e-4646-a035-b3fed9cd9f0c",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||||
"\n",
|
||||
"What's the weather in NYC?\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"Tool Calls:\n",
|
||||
" get_weather (call_C5yEOD1GhlVPX9Gc5nnqgUXF)\n",
|
||||
" Call ID: call_C5yEOD1GhlVPX9Gc5nnqgUXF\n",
|
||||
" Args:\n",
|
||||
" city: nyc\n",
|
||||
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
||||
"Name: get_weather\n",
|
||||
"\n",
|
||||
"It might be cloudy in nyc\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"The weather in NYC might be cloudy.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langgraph.checkpoint import MemorySaver\n",
|
||||
"\n",
|
||||
"graph = create_react_agent(model, tools, checkpointer=MemorySaver())\n",
|
||||
"\n",
|
||||
"config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
|
||||
"inputs = {\"messages\": [(\"user\", \"What's the weather in NYC?\")]}\n",
|
||||
"\n",
|
||||
"print_stream(graph.stream(inputs, config=config, stream_mode=\"values\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4541ea58-ee6a-4685-a988-c915a42284e6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Notice that when we pass the same the same thread ID, the chat history is preserved"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"id": "b509417b-70f3-4736-b838-5d1729ed759e",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||||
"\n",
|
||||
"What's it known for?\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"New York City (NYC) is known for many things, including:\n",
|
||||
"\n",
|
||||
"1. **Landmarks and Attractions**: \n",
|
||||
" - **Statue of Liberty**: A symbol of freedom and democracy.\n",
|
||||
" - **Times Square**: Known for its bright lights, Broadway theaters, and bustling atmosphere.\n",
|
||||
" - **Central Park**: A large urban park offering various recreational activities.\n",
|
||||
" - **Empire State Building**: An iconic skyscraper with an observation deck offering panoramic views of the city.\n",
|
||||
" - **Brooklyn Bridge**: A historic bridge connecting Manhattan and Brooklyn.\n",
|
||||
"\n",
|
||||
"2. **Cultural Diversity**: NYC is a melting pot of cultures, languages, and cuisines, making it one of the most diverse cities in the world.\n",
|
||||
"\n",
|
||||
"3. **Arts and Entertainment**: \n",
|
||||
" - **Broadway**: Renowned for its world-class theater productions.\n",
|
||||
" - **Museums**: Such as the Metropolitan Museum of Art, the Museum of Modern Art (MoMA), and the American Museum of Natural History.\n",
|
||||
" - **Music and Nightlife**: A vibrant scene with numerous music venues, bars, and clubs.\n",
|
||||
"\n",
|
||||
"4. **Financial Hub**: \n",
|
||||
" - **Wall Street**: The financial district is home to the New York Stock Exchange and numerous financial institutions.\n",
|
||||
"\n",
|
||||
"5. **Fashion and Shopping**: \n",
|
||||
" - **Fifth Avenue**: Known for its high-end shopping.\n",
|
||||
" - **Fashion Week**: One of the major fashion events held twice a year.\n",
|
||||
"\n",
|
||||
"6. **Cuisine**: \n",
|
||||
" - **Diverse Food Scene**: From street food like hot dogs and pretzels to fine dining and international cuisines.\n",
|
||||
" - **Famous Foods**: New York-style pizza, bagels, and cheesecake.\n",
|
||||
"\n",
|
||||
"7. **Media and Publishing**: \n",
|
||||
" - Home to major media companies, newspapers like The New York Times, and numerous publishing houses.\n",
|
||||
"\n",
|
||||
"8. **Sports**: \n",
|
||||
" - Home to several major sports teams, including the New York Yankees (baseball), New York Mets (baseball), New York Knicks (basketball), Brooklyn Nets (basketball), New York Giants (football), and New York Jets (football).\n",
|
||||
"\n",
|
||||
"9. **Education and Research**: \n",
|
||||
" - Prestigious institutions like Columbia University, New York University (NYU), and Rockefeller University.\n",
|
||||
"\n",
|
||||
"10. **Public Transportation**: \n",
|
||||
" - An extensive subway system, buses, and taxis that make getting around the city convenient.\n",
|
||||
"\n",
|
||||
"NYC is a city that never sleeps, offering endless opportunities for exploration and experiences.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [(\"user\", \"What's it known for?\")]}\n",
|
||||
"print_stream(graph.stream(inputs, config=config, stream_mode=\"values\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ef765ab6-3ea0-4de0-8737-de8f9d9add40",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"And if we pass a different thread ID, the chat history is reset"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"id": "1bb09dbe-bbba-4e8f-b44c-b18bd2ed96ff",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||||
"\n",
|
||||
"What's it known for?\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"Could you please specify what \"it\" refers to? Are you asking about a specific city, person, object, or something else?\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [(\"user\", \"What's it known for?\")]}\n",
|
||||
"print_stream(\n",
|
||||
" graph.stream(\n",
|
||||
" inputs, config={\"configurable\": {\"thread_id\": 2}}, stream_mode=\"values\"\n",
|
||||
" )\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "42e730aa-8fac-48a5-9c67-b51393665469",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## How to add human-in-the-loop to `create_react_agent`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6314e49c-9143-45a8-871b-8bd3f489425c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's add an interrupt to let the user confirm before LLM takes an action:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"id": "8cd2cbcc-8a1f-443e-9cc9-dc18cd0ceac6",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||||
"\n",
|
||||
"What's the weather in SF?\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"Tool Calls:\n",
|
||||
" get_weather (call_I7B9YW4ENth7QXYzDpiIoLCE)\n",
|
||||
" Call ID: call_I7B9YW4ENth7QXYzDpiIoLCE\n",
|
||||
" Args:\n",
|
||||
" city: sf\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"graph = create_react_agent(\n",
|
||||
" model, tools, interrupt_before=[\"tools\"], checkpointer=MemorySaver()\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"config = {\"configurable\": {\"thread_id\": \"42\"}}\n",
|
||||
"inputs = {\"messages\": [(\"user\", \"What's the weather in SF?\")]}\n",
|
||||
"\n",
|
||||
"print_stream(graph.stream(inputs, config, stream_mode=\"values\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"id": "493d4963-b27b-46ef-8474-1527b08c1c03",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Next step: ('tools',)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"snapshot = graph.get_state(config)\n",
|
||||
"print(\"Next step: \", snapshot.next)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"id": "7ad01f3a-e838-4230-ab07-2f850bb7e7cb",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
||||
"Name: get_weather\n",
|
||||
"\n",
|
||||
"It's always sunny in sf\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"The weather in San Francisco is currently sunny.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print_stream(graph.stream(None, config, stream_mode=\"values\"))"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "langgraph-example-dev",
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "langgraph-example-dev"
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
@@ -643,7 +266,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.9"
|
||||
"version": "3.11.1"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"id": "e327e9bd-effc-4bee-a875-1c383c17f43d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to extract structured output with re-prompting\n",
|
||||
"# Complex data extraction with function calling\n",
|
||||
"\n",
|
||||
"Function calling is a core primitive for integrating LLMs within your software stack. We use it throughout the LangGraph docs, since developing with function calling (aka tool usage) tends to be much more stress-free than the traditional way of writing custom string parsers.\n",
|
||||
"\n",
|
||||
@@ -1036,7 +1036,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.2"
|
||||
"version": "3.11.1"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -577,7 +577,7 @@
|
||||
"\n",
|
||||
"There are multiple options, and the ideal option may depend on the specifics of your application and capabilities of your chosen LLM. Note that many chat models require that messages with tool calls be immediately followed by a tool message containing the result of the tool call. So our intervention may:\n",
|
||||
"\n",
|
||||
"1. Update the parameters of the tool call before proceeding normally (see this [how-to guide](https://langchain-ai.github.io/langgraph/how-tos/time-travel/#pause-before-tools) for an example);\n",
|
||||
"1. Update the parameters of the tool call before proceeding normally (see this [how-to guide](./human_in_the_loop/breakpoints.ipynb) for an example);\n",
|
||||
"2. Add a tool message to the conversation history indicating the user's desired intervention (see an example [here](https://langchain-ai.github.io/langgraph/tutorials/customer-support/customer-support/#state-assistant));\n",
|
||||
"3. Catch the tool call message, replacing it with a `AIMessage` asking for verification and only adding the tool call message to the conversation history if approved.\n",
|
||||
"\n",
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to add breakpoints\n",
|
||||
"\n",
|
||||
"When creating LangGraph agents, it is often nice to add a human-in-the-loop component.\n",
|
||||
"This can be helpful when giving them access to tools.\n",
|
||||
"Often in these situations you may want to manually approve an action before taking.\n",
|
||||
"\n",
|
||||
"This can be in several ways, but the primary supported way is to add an \"interrupt\" before a node is executed.\n",
|
||||
"This interrupts execution at that node.\n",
|
||||
"You can then resume from that spot to continue. "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7cbd446a-808f-4394-be92-d45ab818953c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup\n",
|
||||
"\n",
|
||||
"First we need to install the packages required"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langgraph langchain_anthropic"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0abe11f4-62ed-4dc4-8875-3db21e260d1d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we need to set API keys for Anthropic (the LLM we will use)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdin",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"ANTHROPIC_API_KEY: ········\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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(\"ANTHROPIC_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f0ed46a8-effe-4596-b0e1-a6a29ee16f5c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"_set_env(\"LANGCHAIN_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3333b771",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Build the agent\n",
|
||||
"\n",
|
||||
"We can now build the agent. We will build a relatively simple ReAct-style agent that does tool calling. We will use Anthropic's models and a fake tool (just for demo purposes)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "6098e5cb",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Set up the tool\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langgraph.graph import MessagesState\n",
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def search(query: str):\n",
|
||||
" \"\"\"Call to surf the web.\"\"\"\n",
|
||||
" # This is a placeholder for the actual implementation\n",
|
||||
" # Don't let the LLM know this though 😊\n",
|
||||
" return [\n",
|
||||
" \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\"\n",
|
||||
" ]\n",
|
||||
"\n",
|
||||
"tools = [search]\n",
|
||||
"tool_node = ToolNode(tools)\n",
|
||||
"\n",
|
||||
"# Set up the model\n",
|
||||
"\n",
|
||||
"model = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\n",
|
||||
"model = model.bind_tools(tools)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define nodes and conditional edges\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # If there is no function call, then we finish\n",
|
||||
" if not last_message.tool_calls:\n",
|
||||
" return \"end\"\n",
|
||||
" # Otherwise if there is, we continue\n",
|
||||
" else:\n",
|
||||
" return \"continue\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that calls the model\n",
|
||||
"def call_model(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" response = model.invoke(messages)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [response]}\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(MessagesState)\n",
|
||||
"\n",
|
||||
"# Define the two nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", call_model)\n",
|
||||
"workflow.add_node(\"action\", tool_node)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.set_entry_point(\"agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"\n",
|
||||
"# Set up memory\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"\n",
|
||||
"# We add in `interrupt_before=[\"action\"]`\n",
|
||||
"# This will add a breakpoint before the `action` node is called\n",
|
||||
"app = workflow.compile(checkpointer=memory, interrupt_before=[\"action\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2a1b56c5-bd61-4192-8bdb-458a1e9f0159",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Interacting with the Agent\n",
|
||||
"\n",
|
||||
"We can now interact with the agent and see that it stops before calling a tool.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "cfd140f0-a5a6-4697-8115-322242f197b5",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||||
"\n",
|
||||
"search for the weather in sf now\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"[{'text': \"Certainly! I'll search for the current weather in San Francisco for you. Let me use the search function to find this information.\", 'type': 'text'}, {'id': 'toolu_014E4227Tpw958rhbUeJTWXz', 'input': {'query': 'current weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}]\n",
|
||||
"Tool Calls:\n",
|
||||
" search (toolu_014E4227Tpw958rhbUeJTWXz)\n",
|
||||
" Call ID: toolu_014E4227Tpw958rhbUeJTWXz\n",
|
||||
" Args:\n",
|
||||
" query: current weather in San Francisco\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"thread = {\"configurable\": {\"thread_id\": \"3\"}}\n",
|
||||
"inputs = [HumanMessage(content=\"search for the weather in sf now\")]\n",
|
||||
"for event in app.stream({\"messages\": inputs}, thread, stream_mode=\"values\"):\n",
|
||||
" event[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1bca3814-db08-4b0b-8c0c-95b6c5440c81",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**Resume**\n",
|
||||
"\n",
|
||||
"We can now call the agent again with no inputs to continue, ie. run the tool as requested.\n",
|
||||
"\n",
|
||||
"Running an interrupted graph with `None` in the inputs means to \"proceed as if the interruption didn't occur.\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "51923913-20f7-4ee1-b9ba-d01f5fb2869b",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
||||
"Name: search\n",
|
||||
"\n",
|
||||
"[\"It's sunny in San Francisco, but you better look out if you're a Gemini \\ud83d\\ude08.\"]\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"Based on the search results, I can provide you with information about the current weather in San Francisco:\n",
|
||||
"\n",
|
||||
"The weather in San Francisco right now is sunny. \n",
|
||||
"\n",
|
||||
"It's worth noting that the search result includes a playful reference to astrology, suggesting that Geminis should \"look out.\" However, this is likely just a humorous addition and not related to the actual weather conditions.\n",
|
||||
"\n",
|
||||
"Is there anything else you'd like to know about the weather in San Francisco or any other location?\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for event in app.stream(None, thread, stream_mode=\"values\"):\n",
|
||||
" event[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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,402 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to edit graph state\n",
|
||||
"\n",
|
||||
"When creating LangGraph agents, it is often nice to add a human-in-the-loop component.\n",
|
||||
"This can be helpful when giving them access to tools.\n",
|
||||
"Often in these situations you may want to edit the graph state before continuing (for example, to edit what tool is being called, or how it is being called).\n",
|
||||
"\n",
|
||||
"This can be in several ways, but the primary supported way is to add an \"interrupt\" before a node is executed.\n",
|
||||
"This interrupts execution at that node.\n",
|
||||
"You can then use `update_state` to update the state, and then resume from that spot to continue. "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7cbd446a-808f-4394-be92-d45ab818953c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup\n",
|
||||
"\n",
|
||||
"First we need to install the packages required"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langgraph langchain_anthropic"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0abe11f4-62ed-4dc4-8875-3db21e260d1d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we need to set API keys for Anthropic (the LLM we will use)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdin",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"ANTHROPIC_API_KEY: ········\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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(\"ANTHROPIC_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f0ed46a8-effe-4596-b0e1-a6a29ee16f5c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"_set_env(\"LANGCHAIN_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3333b771",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Build the agent\n",
|
||||
"\n",
|
||||
"We can now build the agent. We will build a relatively simple ReAct-style agent that does tool calling. We will use Anthropic's models and a fake tool (just for demo purposes)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "6098e5cb",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Set up the tool\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langgraph.graph import MessagesState\n",
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def search(query: str):\n",
|
||||
" \"\"\"Call to surf the web.\"\"\"\n",
|
||||
" # This is a placeholder for the actual implementation\n",
|
||||
" # Don't let the LLM know this though 😊\n",
|
||||
" return [\n",
|
||||
" \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\"\n",
|
||||
" ]\n",
|
||||
"\n",
|
||||
"tools = [search]\n",
|
||||
"tool_node = ToolNode(tools)\n",
|
||||
"\n",
|
||||
"# Set up the model\n",
|
||||
"\n",
|
||||
"model = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\n",
|
||||
"model = model.bind_tools(tools)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define nodes and conditional edges\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # If there is no function call, then we finish\n",
|
||||
" if not last_message.tool_calls:\n",
|
||||
" return \"end\"\n",
|
||||
" # Otherwise if there is, we continue\n",
|
||||
" else:\n",
|
||||
" return \"continue\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that calls the model\n",
|
||||
"def call_model(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" response = model.invoke(messages)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [response]}\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(MessagesState)\n",
|
||||
"\n",
|
||||
"# Define the two nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", call_model)\n",
|
||||
"workflow.add_node(\"action\", tool_node)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.set_entry_point(\"agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"\n",
|
||||
"# Set up memory\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"\n",
|
||||
"# We add in `interrupt_before=[\"action\"]`\n",
|
||||
"# This will add a breakpoint before the `action` node is called\n",
|
||||
"app = workflow.compile(checkpointer=memory, interrupt_before=[\"action\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2a1b56c5-bd61-4192-8bdb-458a1e9f0159",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Interacting with the Agent\n",
|
||||
"\n",
|
||||
"We can now interact with the agent and see that it stops before calling a tool.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "cfd140f0-a5a6-4697-8115-322242f197b5",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||||
"\n",
|
||||
"search for the weather in sf now\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"[{'text': \"Certainly! I'll search for the current weather in San Francisco for you. Let me use the search function to find this information.\", 'type': 'text'}, {'id': 'toolu_019xfFeuGu6taKqA8unr6VxP', 'input': {'query': 'current weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}]\n",
|
||||
"Tool Calls:\n",
|
||||
" search (toolu_019xfFeuGu6taKqA8unr6VxP)\n",
|
||||
" Call ID: toolu_019xfFeuGu6taKqA8unr6VxP\n",
|
||||
" Args:\n",
|
||||
" query: current weather in San Francisco\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"thread = {\"configurable\": {\"thread_id\": \"3\"}}\n",
|
||||
"inputs = [HumanMessage(content=\"search for the weather in sf now\")]\n",
|
||||
"for event in app.stream({\"messages\": inputs}, thread, stream_mode=\"values\"):\n",
|
||||
" event[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "78e3f5b9-9700-42b1-863f-c404861f8620",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**Edit**\n",
|
||||
"\n",
|
||||
"We can now update the state accordingly. Let's modify the tool call to have the query `\"current weather in SF\"`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "1aa7b1b9-9322-4815-bc0d-eb083870ac15",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'configurable': {'thread_id': '3',\n",
|
||||
" 'thread_ts': '1ef355a9-ab59-6102-8002-56ee1c266b09'}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# First, lets get the current state\n",
|
||||
"current_state = app.get_state(thread)\n",
|
||||
"\n",
|
||||
"# Let's now get the last message in the state\n",
|
||||
"# This is the one with the tool calls that we want to update\n",
|
||||
"last_message = current_state.values['messages'][-1]\n",
|
||||
"\n",
|
||||
"# Let's now update the args for that tool call\n",
|
||||
"last_message.tool_calls[0]['args'] = {'query': 'current weather in SF'}\n",
|
||||
"\n",
|
||||
"# Let's now call `update_state` to pass in this message in the `messages` key\n",
|
||||
"# This will get treated as any other update to the state\n",
|
||||
"# It will get passed to the reducer function for the `messages` key\n",
|
||||
"# That reducer function will use the ID of the message to update it\n",
|
||||
"# It's important that it has the right ID! Otherwise it would get appended\n",
|
||||
"# as a new message\n",
|
||||
"app.update_state(thread, {\"messages\": last_message})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0dcc5457-1ba1-4cba-ac41-da5c67cc67e5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's now check the current state of the app to make sure it got updated accordingly"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "a3fcf2bd-f881-49fe-b20e-ad16e6819bc6",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[{'name': 'search',\n",
|
||||
" 'args': {'query': 'current weather in SF'},\n",
|
||||
" 'id': 'toolu_019xfFeuGu6taKqA8unr6VxP'}]"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"current_state = app.get_state(thread).values['messages'][-1].tool_calls\n",
|
||||
"current_state"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1bca3814-db08-4b0b-8c0c-95b6c5440c81",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**Resume**\n",
|
||||
"\n",
|
||||
"We can now call the agent again with no inputs to continue, ie. run the tool as requested. We can see from the logs that it passes in the update args to the tool."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "51923913-20f7-4ee1-b9ba-d01f5fb2869b",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
||||
"Name: search\n",
|
||||
"\n",
|
||||
"[\"It's sunny in San Francisco, but you better look out if you're a Gemini \\ud83d\\ude08.\"]\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"Based on the search results, I can provide you with information about the current weather in San Francisco (SF):\n",
|
||||
"\n",
|
||||
"The weather in San Francisco is currently sunny. This means it's a clear day with plenty of sunshine.\n",
|
||||
"\n",
|
||||
"However, I should note that the search result includes an unusual additional comment about astrology, which isn't typically part of a standard weather report. The mention of Geminis is likely not relevant to the actual weather conditions.\n",
|
||||
"\n",
|
||||
"If you'd like more specific details about the temperature, humidity, or wind conditions in San Francisco, I'd be happy to perform another search with a more focused query. Just let me know if you need any additional weather information!\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for event in app.stream(None, thread, stream_mode=\"values\"):\n",
|
||||
" event[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "78780afe-409d-46cd-a734-e82538cdd8de",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"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,589 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to view and update past graph state\n",
|
||||
"\n",
|
||||
"Once you start [checkpointing](../persistence.ipynb) your graphs, you can easily **get** or **update** the state of the agent at any point in time. This permits a few things:\n",
|
||||
"\n",
|
||||
"1. You can surface a state during an interrupt to a user to let them accept an action.\n",
|
||||
"2. You can **rewind** the graph to reproduce or avoid issues.\n",
|
||||
"3. You can **modify** the state to embed your agent into a larger system, or to let the user better control its actions.\n",
|
||||
"\n",
|
||||
"The key methods used for this functionality are:\n",
|
||||
"\n",
|
||||
"- [get_state](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.graph.CompiledGraph.get_state): fetch the values from the target config\n",
|
||||
"- [update_state](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.graph.CompiledGraph.update_state): apply the given values to the target state\n",
|
||||
"\n",
|
||||
"**Note:** this requires passing in a checkpointer.\n",
|
||||
"\n",
|
||||
"Below is a quick example."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7cbd446a-808f-4394-be92-d45ab818953c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup\n",
|
||||
"\n",
|
||||
"First we need to install the packages required"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langgraph langchain_anthropic"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0abe11f4-62ed-4dc4-8875-3db21e260d1d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we need to set API keys for Anthropic (the LLM we will use)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdin",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"ANTHROPIC_API_KEY: ········\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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(\"ANTHROPIC_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f0ed46a8-effe-4596-b0e1-a6a29ee16f5c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"_set_env(\"LANGCHAIN_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e36f89e5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Build the agent\n",
|
||||
"\n",
|
||||
"We can now build the agent. We will build a relatively simple ReAct-style agent that does tool calling. We will use Anthropic's models and a fake tool (just for demo purposes)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "f5319e01",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Set up the tool\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langgraph.graph import MessagesState\n",
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def search(query: str):\n",
|
||||
" \"\"\"Call to surf the web.\"\"\"\n",
|
||||
" # This is a placeholder for the actual implementation\n",
|
||||
" # Don't let the LLM know this though 😊\n",
|
||||
" return [\n",
|
||||
" \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\"\n",
|
||||
" ]\n",
|
||||
"\n",
|
||||
"tools = [search]\n",
|
||||
"tool_node = ToolNode(tools)\n",
|
||||
"\n",
|
||||
"# Set up the model\n",
|
||||
"\n",
|
||||
"model = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\n",
|
||||
"model = model.bind_tools(tools)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define nodes and conditional edges\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # If there is no function call, then we finish\n",
|
||||
" if not last_message.tool_calls:\n",
|
||||
" return \"end\"\n",
|
||||
" # Otherwise if there is, we continue\n",
|
||||
" else:\n",
|
||||
" return \"continue\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that calls the model\n",
|
||||
"def call_model(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" response = model.invoke(messages)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [response]}\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(MessagesState)\n",
|
||||
"\n",
|
||||
"# Define the two nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", call_model)\n",
|
||||
"workflow.add_node(\"action\", tool_node)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.set_entry_point(\"agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"\n",
|
||||
"# Set up memory\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"\n",
|
||||
"# We add in `interrupt_before=[\"action\"]`\n",
|
||||
"# This will add a breakpoint before the `action` node is called\n",
|
||||
"app = workflow.compile(checkpointer=memory)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2a1b56c5-bd61-4192-8bdb-458a1e9f0159",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Interacting with the Agent\n",
|
||||
"\n",
|
||||
"We can now interact with the agent. Let's ask it for the weather in SF.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "cfd140f0-a5a6-4697-8115-322242f197b5",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||||
"\n",
|
||||
"Use the search tool to look up the weather in SF\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"[{'text': \"Certainly! I'll use the search tool to look up the weather in San Francisco for you. Let me do that right away.\", 'type': 'text'}, {'id': 'toolu_01Bpq6yiKqk9moPuGYKdLr8r', 'input': {'query': 'weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}]\n",
|
||||
"Tool Calls:\n",
|
||||
" search (toolu_01Bpq6yiKqk9moPuGYKdLr8r)\n",
|
||||
" Call ID: toolu_01Bpq6yiKqk9moPuGYKdLr8r\n",
|
||||
" Args:\n",
|
||||
" query: weather in San Francisco\n",
|
||||
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
||||
"Name: search\n",
|
||||
"\n",
|
||||
"[\"It's sunny in San Francisco, but you better look out if you're a Gemini \\ud83d\\ude08.\"]\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"Based on the search results, I can provide you with information about the weather in San Francisco:\n",
|
||||
"\n",
|
||||
"The current weather in San Francisco is sunny. This is great news for residents and visitors who want to enjoy outdoor activities or explore the city.\n",
|
||||
"\n",
|
||||
"However, there's an interesting and somewhat humorous addition to the weather report. It mentions, \"but you better look out if you're a Gemini 😈.\" This appears to be a playful reference to astrology, suggesting that Geminis might have some challenges despite the good weather. Of course, this is not a scientific weather prediction and is likely just a fun addition to the report.\n",
|
||||
"\n",
|
||||
"To summarize:\n",
|
||||
"1. The weather in San Francisco is currently sunny.\n",
|
||||
"2. It's a good day for outdoor activities.\n",
|
||||
"3. There's a playful astrological warning for Geminis, but this shouldn't be taken seriously in terms of actual weather conditions.\n",
|
||||
"\n",
|
||||
"Is there anything else you'd like to know about the weather in San Francisco or any other location?\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
|
||||
"input_message = HumanMessage(content=\"Use the search tool to look up the weather in SF\")\n",
|
||||
"for event in app.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n",
|
||||
" event[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1c38c505-6cee-427f-9dcd-493a2ade7ebb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Checking history\n",
|
||||
"\n",
|
||||
"Let's browse the history of this thread, from start to finish."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "8578a66d-6489-4e03-8c23-fd0530278455",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"StateSnapshot(values={'messages': []}, next=('__start__',), config={'configurable': {'thread_id': '1', 'thread_ts': '1ef355ac-b80d-6e18-bfff-c903ebc4bdfd'}}, metadata={'source': 'input', 'step': -1, 'writes': {'messages': [HumanMessage(content='Use the search tool to look up the weather in SF')]}}, created_at='2024-06-28T14:29:14.932371+00:00', parent_config=None)\n",
|
||||
"--\n",
|
||||
"StateSnapshot(values={'messages': [HumanMessage(content='Use the search tool to look up the weather in SF', id='9558b3e7-fa30-4e8b-9587-d58ae3491577')]}, next=('agent',), config={'configurable': {'thread_id': '1', 'thread_ts': '1ef355ac-b810-60dc-8000-a9c67d8cc5e0'}}, metadata={'source': 'loop', 'step': 0, 'writes': None}, created_at='2024-06-28T14:29:14.933257+00:00', parent_config=None)\n",
|
||||
"--\n",
|
||||
"StateSnapshot(values={'messages': [HumanMessage(content='Use the search tool to look up the weather in SF', id='9558b3e7-fa30-4e8b-9587-d58ae3491577'), AIMessage(content=[{'text': \"Certainly! I'll use the search tool to look up the weather in San Francisco for you. Let me do that right away.\", 'type': 'text'}, {'id': 'toolu_01Bpq6yiKqk9moPuGYKdLr8r', 'input': {'query': 'weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}], response_metadata={'id': 'msg_011ae64fY2jEcfS8kgrt4Fn9', 'model': 'claude-3-5-sonnet-20240620', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 363, 'output_tokens': 81}}, id='run-cfef25ca-d1be-4e79-8798-3bb9a7002287-0', tool_calls=[{'name': 'search', 'args': {'query': 'weather in San Francisco'}, 'id': 'toolu_01Bpq6yiKqk9moPuGYKdLr8r'}], usage_metadata={'input_tokens': 363, 'output_tokens': 81, 'total_tokens': 444})]}, next=('action',), config={'configurable': {'thread_id': '1', 'thread_ts': '1ef355ac-c6b1-6028-8001-82bd095f9a87'}}, metadata={'source': 'loop', 'step': 1, 'writes': {'agent': {'messages': [AIMessage(content=[{'text': \"Certainly! I'll use the search tool to look up the weather in San Francisco for you. Let me do that right away.\", 'type': 'text'}, {'id': 'toolu_01Bpq6yiKqk9moPuGYKdLr8r', 'input': {'query': 'weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}], response_metadata={'id': 'msg_011ae64fY2jEcfS8kgrt4Fn9', 'model': 'claude-3-5-sonnet-20240620', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 363, 'output_tokens': 81}}, id='run-cfef25ca-d1be-4e79-8798-3bb9a7002287-0', tool_calls=[{'name': 'search', 'args': {'query': 'weather in San Francisco'}, 'id': 'toolu_01Bpq6yiKqk9moPuGYKdLr8r'}], usage_metadata={'input_tokens': 363, 'output_tokens': 81, 'total_tokens': 444})]}}}, created_at='2024-06-28T14:29:16.467180+00:00', parent_config=None)\n",
|
||||
"--\n",
|
||||
"StateSnapshot(values={'messages': [HumanMessage(content='Use the search tool to look up the weather in SF', id='9558b3e7-fa30-4e8b-9587-d58ae3491577'), AIMessage(content=[{'text': \"Certainly! I'll use the search tool to look up the weather in San Francisco for you. Let me do that right away.\", 'type': 'text'}, {'id': 'toolu_01Bpq6yiKqk9moPuGYKdLr8r', 'input': {'query': 'weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}], response_metadata={'id': 'msg_011ae64fY2jEcfS8kgrt4Fn9', 'model': 'claude-3-5-sonnet-20240620', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 363, 'output_tokens': 81}}, id='run-cfef25ca-d1be-4e79-8798-3bb9a7002287-0', tool_calls=[{'name': 'search', 'args': {'query': 'weather in San Francisco'}, 'id': 'toolu_01Bpq6yiKqk9moPuGYKdLr8r'}], usage_metadata={'input_tokens': 363, 'output_tokens': 81, 'total_tokens': 444}), ToolMessage(content='[\"It\\'s sunny in San Francisco, but you better look out if you\\'re a Gemini \\\\ud83d\\\\ude08.\"]', name='search', id='b5aa87cb-335a-4ee0-8809-381e33d0f02e', tool_call_id='toolu_01Bpq6yiKqk9moPuGYKdLr8r')]}, next=('agent',), config={'configurable': {'thread_id': '1', 'thread_ts': '1ef355ac-c6ba-63a8-8002-48d076c3c4b7'}}, metadata={'source': 'loop', 'step': 2, 'writes': {'action': {'messages': [ToolMessage(content='[\"It\\'s sunny in San Francisco, but you better look out if you\\'re a Gemini \\\\ud83d\\\\ude08.\"]', name='search', id='b5aa87cb-335a-4ee0-8809-381e33d0f02e', tool_call_id='toolu_01Bpq6yiKqk9moPuGYKdLr8r')]}}}, created_at='2024-06-28T14:29:16.470958+00:00', parent_config=None)\n",
|
||||
"--\n",
|
||||
"StateSnapshot(values={'messages': [HumanMessage(content='Use the search tool to look up the weather in SF', id='9558b3e7-fa30-4e8b-9587-d58ae3491577'), AIMessage(content=[{'text': \"Certainly! I'll use the search tool to look up the weather in San Francisco for you. Let me do that right away.\", 'type': 'text'}, {'id': 'toolu_01Bpq6yiKqk9moPuGYKdLr8r', 'input': {'query': 'weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}], response_metadata={'id': 'msg_011ae64fY2jEcfS8kgrt4Fn9', 'model': 'claude-3-5-sonnet-20240620', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 363, 'output_tokens': 81}}, id='run-cfef25ca-d1be-4e79-8798-3bb9a7002287-0', tool_calls=[{'name': 'search', 'args': {'query': 'weather in San Francisco'}, 'id': 'toolu_01Bpq6yiKqk9moPuGYKdLr8r'}], usage_metadata={'input_tokens': 363, 'output_tokens': 81, 'total_tokens': 444}), ToolMessage(content='[\"It\\'s sunny in San Francisco, but you better look out if you\\'re a Gemini \\\\ud83d\\\\ude08.\"]', name='search', id='b5aa87cb-335a-4ee0-8809-381e33d0f02e', tool_call_id='toolu_01Bpq6yiKqk9moPuGYKdLr8r'), AIMessage(content='Based on the search results, I can provide you with information about the weather in San Francisco:\\n\\nThe current weather in San Francisco is sunny. This is great news for residents and visitors who want to enjoy outdoor activities or explore the city.\\n\\nHowever, there\\'s an interesting and somewhat humorous addition to the weather report. It mentions, \"but you better look out if you\\'re a Gemini 😈.\" This appears to be a playful reference to astrology, suggesting that Geminis might have some challenges despite the good weather. Of course, this is not a scientific weather prediction and is likely just a fun addition to the report.\\n\\nTo summarize:\\n1. The weather in San Francisco is currently sunny.\\n2. It\\'s a good day for outdoor activities.\\n3. There\\'s a playful astrological warning for Geminis, but this shouldn\\'t be taken seriously in terms of actual weather conditions.\\n\\nIs there anything else you\\'d like to know about the weather in San Francisco or any other location?', response_metadata={'id': 'msg_01NWeLrkQRLiGsVsxnepzq3p', 'model': 'claude-3-5-sonnet-20240620', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 486, 'output_tokens': 217}}, id='run-09f1b7d5-50ec-4f00-a31b-c4dec858b312-0', usage_metadata={'input_tokens': 486, 'output_tokens': 217, 'total_tokens': 703})]}, next=(), config={'configurable': {'thread_id': '1', 'thread_ts': '1ef355ac-f0f5-6942-8003-b79974988738'}}, metadata={'source': 'loop', 'step': 3, 'writes': {'agent': {'messages': [AIMessage(content='Based on the search results, I can provide you with information about the weather in San Francisco:\\n\\nThe current weather in San Francisco is sunny. This is great news for residents and visitors who want to enjoy outdoor activities or explore the city.\\n\\nHowever, there\\'s an interesting and somewhat humorous addition to the weather report. It mentions, \"but you better look out if you\\'re a Gemini 😈.\" This appears to be a playful reference to astrology, suggesting that Geminis might have some challenges despite the good weather. Of course, this is not a scientific weather prediction and is likely just a fun addition to the report.\\n\\nTo summarize:\\n1. The weather in San Francisco is currently sunny.\\n2. It\\'s a good day for outdoor activities.\\n3. There\\'s a playful astrological warning for Geminis, but this shouldn\\'t be taken seriously in terms of actual weather conditions.\\n\\nIs there anything else you\\'d like to know about the weather in San Francisco or any other location?', response_metadata={'id': 'msg_01NWeLrkQRLiGsVsxnepzq3p', 'model': 'claude-3-5-sonnet-20240620', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 486, 'output_tokens': 217}}, id='run-09f1b7d5-50ec-4f00-a31b-c4dec858b312-0', usage_metadata={'input_tokens': 486, 'output_tokens': 217, 'total_tokens': 703})]}}}, created_at='2024-06-28T14:29:20.899258+00:00', parent_config=None)\n",
|
||||
"--\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"all_states = []\n",
|
||||
"for state in app.get_state_history(config):\n",
|
||||
" print(state)\n",
|
||||
" all_states.append(state)\n",
|
||||
" print(\"--\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0ec41c37-7c09-4cc7-8475-bf373fe66584",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Replay a state\n",
|
||||
"\n",
|
||||
"We can go back to any of these states and restart the agent from there! Let's go back to right before the tool call gets executed."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "02250602-8c4a-4fb5-bd6c-d0b9046e8699",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"to_replay = all_states[2]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "21e7fc18-6fd9-4e11-a84b-e0325c9640c8",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'messages': [HumanMessage(content='Use the search tool to look up the weather in SF', id='9558b3e7-fa30-4e8b-9587-d58ae3491577'),\n",
|
||||
" AIMessage(content=[{'text': \"Certainly! I'll use the search tool to look up the weather in San Francisco for you. Let me do that right away.\", 'type': 'text'}, {'id': 'toolu_01Bpq6yiKqk9moPuGYKdLr8r', 'input': {'query': 'weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}], response_metadata={'id': 'msg_011ae64fY2jEcfS8kgrt4Fn9', 'model': 'claude-3-5-sonnet-20240620', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 363, 'output_tokens': 81}}, id='run-cfef25ca-d1be-4e79-8798-3bb9a7002287-0', tool_calls=[{'name': 'search', 'args': {'query': 'weather in San Francisco'}, 'id': 'toolu_01Bpq6yiKqk9moPuGYKdLr8r'}], usage_metadata={'input_tokens': 363, 'output_tokens': 81, 'total_tokens': 444})]}"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"to_replay.values"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "d4b01634-0041-4632-8d1f-5464580e54f5",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"('action',)"
|
||||
]
|
||||
},
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"to_replay.next"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "29da43ea-9295-43e2-b164-0eb28d96749c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"To replay from this place we just need to pass its config back to the agent. Notice that it just resumes from right where it left all - making a tool call."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "e986f94f-706f-4b6f-b3c4-f95483b9e9b8",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'messages': [ToolMessage(content='[\"It\\'s sunny in San Francisco, but you better look out if you\\'re a Gemini \\\\ud83d\\\\ude08.\"]', name='search', tool_call_id='toolu_01Bpq6yiKqk9moPuGYKdLr8r')]}\n",
|
||||
"{'messages': [AIMessage(content='Based on the search results, I can provide you with information about the weather in San Francisco:\\n\\nThe current weather in San Francisco is sunny. This is great news for residents and visitors who want to enjoy outdoor activities or explore the city.\\n\\nHowever, there\\'s an interesting and somewhat humorous addition to the weather report. It mentions, \"but you better look out if you\\'re a Gemini 😈.\" This appears to be a playful reference to astrology, suggesting that Geminis might have some challenges despite the good weather. Of course, this is not a scientific weather prediction and is likely just a fun addition to the weather report.\\n\\nTo summarize:\\n1. The weather in San Francisco is currently sunny.\\n2. It\\'s a good day for outdoor activities.\\n3. There\\'s a playful astrological reference for Geminis, but this shouldn\\'t be taken as actual weather information.\\n\\nIs there anything else you\\'d like to know about the weather in San Francisco or any other location?', response_metadata={'id': 'msg_01S4uzzxbGwvsk1vfoLJAD7Z', 'model': 'claude-3-5-sonnet-20240620', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 486, 'output_tokens': 214}}, id='run-570bd10f-4007-4b33-8c29-44b9a19b6978-0', usage_metadata={'input_tokens': 486, 'output_tokens': 214, 'total_tokens': 700})]}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for event in app.stream(None, to_replay.config):\n",
|
||||
" for v in event.values():\n",
|
||||
" print(v)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "59910951-fae1-4475-8511-f622439b590d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Branch off a past state\n",
|
||||
"\n",
|
||||
"Using LangGraph's checkpointing, you can do more than just replay past states. You can branch off previous locations to let the agent explore alternate trajectories or to let a user \"version control\" changes in a workflow.\n",
|
||||
"\n",
|
||||
"Let's show how to do this to edit the state at a particular point in time. Let's update the state to change the input to the tool"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "fbd5ad3b-5363-4ab7-ac63-b04668bc998f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Let's now get the last message in the state\n",
|
||||
"# This is the one with the tool calls that we want to update\n",
|
||||
"last_message = to_replay.values['messages'][-1]\n",
|
||||
"\n",
|
||||
"# Let's now update the args for that tool call\n",
|
||||
"last_message.tool_calls[0]['args'] = {'query': 'current weather in SF'}\n",
|
||||
"\n",
|
||||
"branch_config = app.update_state(\n",
|
||||
" to_replay.config, {\"messages\": [last_message]},\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bced65eb-2158-43e6-a9e3-3b047c8d418e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can then invoke with this new `branch_config` to resume running from here with changed state. We can see from the log that the tool was called with different input."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "9a92d3da-62e2-45a2-8545-e4f6a64e0ffe",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'messages': [ToolMessage(content='[\"It\\'s sunny in San Francisco, but you better look out if you\\'re a Gemini \\\\ud83d\\\\ude08.\"]', name='search', tool_call_id='toolu_01Bpq6yiKqk9moPuGYKdLr8r')]}\n",
|
||||
"{'messages': [AIMessage(content=\"Based on the search results, I can provide you with information about the current weather in San Francisco (SF):\\n\\nThe weather in San Francisco is currently sunny. This means it's a clear day with plenty of sunshine, which is great for outdoor activities or simply enjoying the city's beautiful views.\\n\\nIt's worth noting that San Francisco's weather can be quite variable, even within the city itself, due to its unique geography and microclimates. While it's sunny now, it's always a good idea to be prepared for potential changes, as the city is known for its foggy conditions, especially in certain areas and during specific times of the day.\\n\\nThe search result also includes a playful reference to astrology, mentioning Geminis. However, this is likely just a humorous addition and not related to the actual weather conditions.\\n\\nIs there any specific information about the weather in San Francisco that you'd like to know more about, such as temperature, wind conditions, or forecast for the coming days?\", response_metadata={'id': 'msg_01AoFmmzZxbMLuu3npVXJKG7', 'model': 'claude-3-5-sonnet-20240620', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 486, 'output_tokens': 211}}, id='run-ccb9e786-c23e-4017-bf29-8405f17cec9f-0', usage_metadata={'input_tokens': 486, 'output_tokens': 211, 'total_tokens': 697})]}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for event in app.stream(None, branch_config):\n",
|
||||
" for v in event.values():\n",
|
||||
" print(v)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "511e319e-d10d-4b04-a4e0-fc4f3d87cb23",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Alternatively, we could update the state to not even call a tool!"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"id": "01abb480-df55-4eba-a2be-cf9372b60b54",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.messages import AIMessage\n",
|
||||
"\n",
|
||||
"# Let's now get the last message in the state\n",
|
||||
"# This is the one with the tool calls that we want to update\n",
|
||||
"last_message = to_replay.values['messages'][-1]\n",
|
||||
"\n",
|
||||
"# Let's now get the ID for the last message, and create a new message with that ID.\n",
|
||||
"new_message = AIMessage(content=\"its warm!\", id=last_message.id)\n",
|
||||
"\n",
|
||||
"branch_config = app.update_state(\n",
|
||||
" to_replay.config, {\"messages\": [new_message]},\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"id": "1a7cfcd4-289e-419e-8b49-dfaef4f88641",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"branch_state = app.get_state(branch_config)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"id": "5198f9c1-d2d4-458a-993d-3caa55810b1e",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'messages': [HumanMessage(content='Use the search tool to look up the weather in SF', id='9558b3e7-fa30-4e8b-9587-d58ae3491577'),\n",
|
||||
" AIMessage(content='its warm!', id='run-cfef25ca-d1be-4e79-8798-3bb9a7002287-0')]}"
|
||||
]
|
||||
},
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"branch_state.values"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"id": "5d89d55d-db84-4c2d-828b-64a29a69947b",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"()"
|
||||
]
|
||||
},
|
||||
"execution_count": 16,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"branch_state.next"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "cc168c90-a374-4280-a9a6-8bc232dbb006",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"You can see the snapshot was updated and now correctly reflects that there is no next step."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "74a7a5ed-0c14-4883-a16b-d70aaf40f7ea",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"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,426 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to wait for user input\n",
|
||||
"\n",
|
||||
"One of the main human-in-the-loop interaction patterns is waiting for human input. A key use case involves asking the user clarifying questions. One way to accomplish this is simply go to the END node and exit the graph. Then, any user response comes back in as fresh invocation of the graph. This is basically just creating a chatbot architecture.\n",
|
||||
"\n",
|
||||
"The issue with this is it is tough to resume back in a particular point in the graph. Often times the agent is halfway through some process, and just needs a bit of a user input. Although it is possible to design your graph in such a way where you have a `conditional_entry_point` to route user messages back to the right place, that is not super scalable (as it essentially involves having a routing function that can end up almost anywhere).\n",
|
||||
"\n",
|
||||
"A separate way to do this is to have a node explicitly for getting user input. This is easy to implement in a notebook setting - you just put an `input()` call in the node. But that isn't exactly production ready.\n",
|
||||
"\n",
|
||||
"Luckily, LangGraph makes it possible to do similar things in a production way. The basic idea is:\n",
|
||||
"\n",
|
||||
"- Set up a node that represents human input. This can have specific incoming/outgoing edges (as you desire). There shouldn't actually be any logic inside this node.\n",
|
||||
"- Add a breakpoint before the node. This will stop the graph before this node executes (which is good, because there's no real logic in it anyways)\n",
|
||||
"- Use `.update_state` to update the state of the graph. Pass in whatever human response you get. The key here is to use the `as_node` parameter to apply this update **as if you were that node**. This will have the effect of making it so that when you resume execution next it resumes as if that node just acted, and not from the beginning.\n",
|
||||
"\n",
|
||||
"**Note:** this requires passing in a checkpointer.\n",
|
||||
"\n",
|
||||
"Below is a quick example."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7cbd446a-808f-4394-be92-d45ab818953c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup\n",
|
||||
"\n",
|
||||
"First we need to install the packages required"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install --quiet -U langgraph langchain_anthropic"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0abe11f4-62ed-4dc4-8875-3db21e260d1d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we need to set API keys for Anthropic (the LLM we will use)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdin",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"ANTHROPIC_API_KEY: ········\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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(\"ANTHROPIC_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f0ed46a8-effe-4596-b0e1-a6a29ee16f5c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"_set_env(\"LANGCHAIN_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e36f89e5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Build the agent\n",
|
||||
"\n",
|
||||
"We can now build the agent. We will build a relatively simple ReAct-style agent that does tool calling. We will use Anthropic's models and a fake tool (just for demo purposes)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "f5319e01",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Set up the state\n",
|
||||
"from langgraph.graph import MessagesState\n",
|
||||
"\n",
|
||||
"# Set up the tool\n",
|
||||
"# We will have one real tool - a search tool\n",
|
||||
"# We'll also have one \"fake\" tool - a \"ask_human\" tool\n",
|
||||
"# Here we define any ACTUAL tools\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def search(query: str):\n",
|
||||
" \"\"\"Call to surf the web.\"\"\"\n",
|
||||
" # This is a placeholder for the actual implementation\n",
|
||||
" # Don't let the LLM know this though 😊\n",
|
||||
" return [\n",
|
||||
" f\"I looked up: {query}. Result: It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\"\n",
|
||||
" ]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [search]\n",
|
||||
"tool_node = ToolNode(tools)\n",
|
||||
"\n",
|
||||
"# Set up the model\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"\n",
|
||||
"model = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# We are going \"bind\" all tools to the model\n",
|
||||
"# We have the ACTUAL tools from above, but we also need a mock tool to ask a human\n",
|
||||
"# Since `bind_tools` takes in tools but also just tool definitions,\n",
|
||||
"# We can define a tool definition for `ask_human`\n",
|
||||
"\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel\n",
|
||||
"\n",
|
||||
"class AskHuman(BaseModel):\n",
|
||||
" \"\"\"Ask the human a question\"\"\"\n",
|
||||
" question: str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"model = model.bind_tools(tools + [AskHuman])\n",
|
||||
"\n",
|
||||
"# Define nodes and conditional edges\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import ToolMessage\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import ToolInvocation\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
"def should_continue(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" # If there is no function call, then we finish\n",
|
||||
" if not last_message.tool_calls:\n",
|
||||
" return \"end\"\n",
|
||||
" # If tool call is asking Human, we return that node\n",
|
||||
" # You could also add logic here to let some system know that there's something that requires Human input\n",
|
||||
" # For example, send a slack message, etc\n",
|
||||
" elif last_message.tool_calls[0]['name'] == \"AskHuman\":\n",
|
||||
" return \"ask_human\"\n",
|
||||
" # Otherwise if there is, we continue\n",
|
||||
" else:\n",
|
||||
" return \"continue\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that calls the model\n",
|
||||
"def call_model(state):\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" response = model.invoke(messages)\n",
|
||||
" # We return a list, because this will get added to the existing list\n",
|
||||
" return {\"messages\": [response]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# We define a fake node to ask the human\n",
|
||||
"def ask_human(state):\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
"# Build the graph\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, StateGraph\n",
|
||||
"\n",
|
||||
"# Define a new graph\n",
|
||||
"workflow = StateGraph(MessagesState)\n",
|
||||
"\n",
|
||||
"# Define the three nodes we will cycle between\n",
|
||||
"workflow.add_node(\"agent\", call_model)\n",
|
||||
"workflow.add_node(\"action\", tool_node)\n",
|
||||
"workflow.add_node(\"ask_human\", ask_human)\n",
|
||||
"\n",
|
||||
"# Set the entrypoint as `agent`\n",
|
||||
"# This means that this node is the first one called\n",
|
||||
"workflow.set_entry_point(\"agent\")\n",
|
||||
"\n",
|
||||
"# We now add a conditional edge\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" # First, we define the start node. We use `agent`.\n",
|
||||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||||
" \"agent\",\n",
|
||||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||||
" should_continue,\n",
|
||||
" # Finally we pass in a mapping.\n",
|
||||
" # The keys are strings, and the values are other nodes.\n",
|
||||
" # END is a special node marking that the graph should finish.\n",
|
||||
" # What will happen is we will call `should_continue`, and then the output of that\n",
|
||||
" # will be matched against the keys in this mapping.\n",
|
||||
" # Based on which one it matches, that node will then be called.\n",
|
||||
" {\n",
|
||||
" # If `tools`, then we call the tool node.\n",
|
||||
" \"continue\": \"action\",\n",
|
||||
" # We may ask the human\n",
|
||||
" \"ask_human\": \"ask_human\",\n",
|
||||
" # Otherwise we finish.\n",
|
||||
" \"end\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||||
"workflow.add_edge(\"action\", \"agent\")\n",
|
||||
"\n",
|
||||
"# After we get back the human response, we go back to the agent\n",
|
||||
"workflow.add_edge(\"ask_human\", \"agent\")\n",
|
||||
"\n",
|
||||
"# Set up memory\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"# We add a breakpoint BEFORE the `ask_human` node so it never executes\n",
|
||||
"app = workflow.compile(checkpointer=memory, interrupt_before=['ask_human'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2a1b56c5-bd61-4192-8bdb-458a1e9f0159",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Interacting with the Agent\n",
|
||||
"\n",
|
||||
"We can now interact with the agent. Let's ask it to ask the user where they are, then tell them the weather. This should make it use the `ask_human` tool first, then use the normal tool.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "cfd140f0-a5a6-4697-8115-322242f197b5",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||||
"\n",
|
||||
"Use the search tool to ask the user where they are, then look up the weather there\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"[{'text': \"Certainly! I'll use the AskHuman function to ask the user about their location, and then use the search function to look up the weather. Let's start by asking the user where they are.\", 'type': 'text'}, {'id': 'toolu_01HgfTkwJ2pZhPuCuunBYUq4', 'input': {'question': 'Where are you currently located?'}, 'name': 'AskHuman', 'type': 'tool_use'}]\n",
|
||||
"Tool Calls:\n",
|
||||
" AskHuman (toolu_01HgfTkwJ2pZhPuCuunBYUq4)\n",
|
||||
" Call ID: toolu_01HgfTkwJ2pZhPuCuunBYUq4\n",
|
||||
" Args:\n",
|
||||
" question: Where are you currently located?\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"config = {\"configurable\": {\"thread_id\": \"2\"}}\n",
|
||||
"input_message = HumanMessage(content=\"Use the search tool to ask the user where they are, then look up the weather there\")\n",
|
||||
"for event in app.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n",
|
||||
" event[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "cc168c90-a374-4280-a9a6-8bc232dbb006",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We now want to update this thread with a response from the user. We then can kick off another run. \n",
|
||||
"\n",
|
||||
"Because we are treating this as a tool call, we will need to update the state as if it is a response from a tool call. In order to do this, we will need to check the state to get the ID of the tool call."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "63598092-d565-4170-9773-e092d345f8c1",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"('agent',)"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"tool_call_id = app.get_state(config).values['messages'][-1].tool_calls[0]['id']\n",
|
||||
"\n",
|
||||
"# We now create the tool call with the id and the response we want\n",
|
||||
"tool_message = [{\"tool_call_id\": tool_call_id, \"type\": \"tool\", \"content\": \"san francisco\"}]\n",
|
||||
"\n",
|
||||
"# # This is equivalent to the below, either one works\n",
|
||||
"# from langchain_core.messages import ToolMessage\n",
|
||||
"# tool_message = [ToolMessage(tool_call_id=tool_call_id, content=\"san francisco\")]\n",
|
||||
"\n",
|
||||
"# We now update the state\n",
|
||||
"# Notice that we are also specifying `as_node=\"ask_human\"`\n",
|
||||
"# This will apply this update as this node,\n",
|
||||
"# which will make it so that afterwards it continues as normal\n",
|
||||
"app.update_state(config, {\"messages\": tool_message}, as_node=\"ask_human\")\n",
|
||||
"\n",
|
||||
"# We can check the state\n",
|
||||
"# We can see that the state currently has the `agent` node next\n",
|
||||
"# This is based on how we define our graph, \n",
|
||||
"# where after the `ask_human` node goes (which we just triggered)\n",
|
||||
"# there is an edge to the `agent` node\n",
|
||||
"app.get_state(config).next"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6a30c9fb-2a40-45cc-87ba-406c11c9f0cf",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can now tell the agent to continue. We can just pass in `None` as the input to the graph, since no additional input is needed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "a9f599b5-1a55-406b-a76b-f52b3ca06975",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"[{'text': \"Thank you for letting me know that you're in San Francisco. Now, I'll use the search function to look up the weather in San Francisco.\", 'type': 'text'}, {'id': 'toolu_01BKzfQfHBSUcgTbz7c4gs8w', 'input': {'query': 'current weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}]\n",
|
||||
"Tool Calls:\n",
|
||||
" search (toolu_01BKzfQfHBSUcgTbz7c4gs8w)\n",
|
||||
" Call ID: toolu_01BKzfQfHBSUcgTbz7c4gs8w\n",
|
||||
" Args:\n",
|
||||
" query: current weather in San Francisco\n",
|
||||
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
||||
"Name: search\n",
|
||||
"\n",
|
||||
"[\"I looked up: current weather in San Francisco. Result: It's sunny in San Francisco, but you better look out if you're a Gemini \\ud83d\\ude08.\"]\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"Based on the search results, I can provide you with information about the current weather in San Francisco:\n",
|
||||
"\n",
|
||||
"The weather in San Francisco is currently sunny. \n",
|
||||
"\n",
|
||||
"It's worth noting that the search result included a playful comment about Geminis, but that's not directly related to the weather information you requested. If you need more specific details about the temperature, humidity, or forecast, please let me know, and I'd be happy to search for more detailed weather information for San Francisco.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for event in app.stream(None, config, stream_mode=\"values\"):\n",
|
||||
" event[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f6f972d1-3d99-4fc1-8b33-92b71e74835d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"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
|
||||
}
|
||||
@@ -5,9 +5,9 @@
|
||||
"id": "4a1aae78-88a6-4133-b905-7e46c8e3772f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Introduction to LangGraph\n",
|
||||
"# Quick Start\n",
|
||||
"\n",
|
||||
"In this tutorial, we will build a support chatbot in LangGraph that can:\n",
|
||||
"In this comprehensive quick start, we will build a support chatbot in LangGraph that can:\n",
|
||||
"\n",
|
||||
"- Answer common questions by searching the web\n",
|
||||
"- Maintain conversation state across calls\n",
|
||||
@@ -343,16 +343,13 @@
|
||||
"\n",
|
||||
"However, 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 to expand the bot's knowledge and make it more capable.\n",
|
||||
"\n",
|
||||
"Below is the full code for this section for your reference:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "e014741f-cfad-4045-b6fa-ebd74ae93a5a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"Below is the full code for this section for your reference:\n",
|
||||
"\n",
|
||||
"<details>\n",
|
||||
"<summary>Full Code</summary>\n",
|
||||
" <pre>\n",
|
||||
" \n",
|
||||
"```python\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
@@ -382,7 +379,11 @@
|
||||
"graph_builder.add_node(\"chatbot\", chatbot)\n",
|
||||
"graph_builder.set_entry_point(\"chatbot\")\n",
|
||||
"graph_builder.set_finish_point(\"chatbot\")\n",
|
||||
"graph = graph_builder.compile()"
|
||||
"graph = graph_builder.compile()\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"</pre>\n",
|
||||
"</details>"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -409,7 +410,8 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U tavily-python"
|
||||
"%pip install -U tavily-python\n",
|
||||
"%pip install -U langchain_community"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -766,16 +768,13 @@
|
||||
"Our chatbot still can't remember past interactions on its own, limiting its ability to have coherent, multi-turn conversations. In the next part, we'll add **memory** to address this.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"The full code for the graph we've created in this section is reproduced below, replacing our `BasicToolNode` for the prebuilt [ToolNode](https://langchain-ai.github.io/langgraph/reference/prebuilt/#toolnode), and our `route_tools` condition with the prebuilt [tools_condition](https://langchain-ai.github.io/langgraph/reference/prebuilt/#tools_condition)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"id": "8755d551-160e-4f8f-afac-0e4e07ca79ff",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"The full code for the graph we've created in this section is reproduced below, replacing our `BasicToolNode` for the prebuilt [ToolNode](https://langchain-ai.github.io/langgraph/reference/prebuilt/#toolnode), and our `route_tools` condition with the prebuilt [tools_condition](https://langchain-ai.github.io/langgraph/reference/prebuilt/#tools_condition)\n",
|
||||
"\n",
|
||||
"<details>\n",
|
||||
"<summary>Full Code</summary>\n",
|
||||
" <pre>\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
@@ -817,7 +816,11 @@
|
||||
"# Any time a tool is called, we return to the chatbot to decide the next step\n",
|
||||
"graph_builder.add_edge(\"tools\", \"chatbot\")\n",
|
||||
"graph_builder.set_entry_point(\"chatbot\")\n",
|
||||
"graph = graph_builder.compile()"
|
||||
"graph = graph_builder.compile()\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"</pre>\n",
|
||||
"</details>"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1170,16 +1173,13 @@
|
||||
"\n",
|
||||
"In the next part, we'll introduce human oversight to our bot to handle situations where it may need guidance or verification before proceeding.\n",
|
||||
" \n",
|
||||
"Check out the code snippet below to review our graph from this section."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "9c50a794-3ae5-484c-8edd-50e0d54da982",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"Check out the code snippet below to review our graph from this section.\n",
|
||||
"\n",
|
||||
"<details>\n",
|
||||
"<summary>Full Code</summary>\n",
|
||||
" <pre>\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
@@ -1221,7 +1221,10 @@
|
||||
")\n",
|
||||
"graph_builder.add_edge(\"tools\", \"chatbot\")\n",
|
||||
"graph_builder.set_entry_point(\"chatbot\")\n",
|
||||
"graph = graph_builder.compile(checkpointer=memory)"
|
||||
"graph = graph_builder.compile(checkpointer=memory)\n",
|
||||
"```\n",
|
||||
"</pre>\n",
|
||||
"</pre>details"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1478,16 +1481,13 @@
|
||||
"\n",
|
||||
"Next, we'll explore how to further customize the bot's behavior using custom state updates.\n",
|
||||
"\n",
|
||||
"Below is a copy of the code you used in this section. The only difference between this and the previous parts is the addition of the `interrupt_before` argument."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "a7228caf-a5aa-4f68-b775-81ea5402aca8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"Below is a copy of the code you used in this section. The only difference between this and the previous parts is the addition of the `interrupt_before` argument.\n",
|
||||
"\n",
|
||||
"<details>\n",
|
||||
"<summary>Full Code</summary>\n",
|
||||
" <pre>\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
@@ -1537,7 +1537,10 @@
|
||||
" interrupt_before=[\"tools\"],\n",
|
||||
" # Note: can also interrupt __after__ actions, if desired.\n",
|
||||
" # interrupt_after=[\"tools\"]\n",
|
||||
")"
|
||||
")\n",
|
||||
"```\n",
|
||||
"</pre>\n",
|
||||
"</details>"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2508,16 +2511,13 @@
|
||||
"\n",
|
||||
"We're almost done with the tutorial, but there is one more concept we'd like to review before finishing that connects `checkpointing` and `state updates`. \n",
|
||||
"\n",
|
||||
"This section's code is reproduced below for your reference."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 26,
|
||||
"id": "6516baf8-bbb6-4400-b867-0add1a087342",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"This section's code is reproduced below for your reference.\n",
|
||||
"\n",
|
||||
"<details>\n",
|
||||
"<summary>Full Code</summary>\n",
|
||||
" <pre>\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
@@ -2617,7 +2617,10 @@
|
||||
"graph = graph_builder.compile(\n",
|
||||
" checkpointer=memory,\n",
|
||||
" interrupt_before=[\"human\"],\n",
|
||||
")"
|
||||
")\n",
|
||||
"```\n",
|
||||
"</pre>\n",
|
||||
"</details>"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -3066,7 +3069,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
"version": "3.11.1"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -303,7 +303,9 @@
|
||||
"\n",
|
||||
"{examples}\n",
|
||||
"\n",
|
||||
"Assist the user as they require!\"\"\".format(examples=_examples)\n",
|
||||
"Assist the user as they require!\"\"\".format(\n",
|
||||
" examples=_examples\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" else:\n",
|
||||
" system_message = \"\"\"You are a helpful assistant\"\"\"\n",
|
||||
|
||||
@@ -469,7 +469,8 @@
|
||||
" args_for_tasks[task[\"idx\"]] = task[\"args\"]\n",
|
||||
" if (\n",
|
||||
" # Depends on other tasks\n",
|
||||
" deps and (any([dep not in observations for dep in deps]))\n",
|
||||
" deps\n",
|
||||
" and (any([dep not in observations for dep in deps]))\n",
|
||||
" ):\n",
|
||||
" futures.append(\n",
|
||||
" executor.submit(\n",
|
||||
|
||||
@@ -5,55 +5,949 @@
|
||||
"id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Persistence with Postgres\n",
|
||||
"\n",
|
||||
":::note\n",
|
||||
"The langchain-postgres package has not kept up-to-date with the LangGraph package. While we work to make improvements, we will leave the following code for historic context.\n",
|
||||
":::\n",
|
||||
"# How to create a custom checkpointer using Postgres\n",
|
||||
"\n",
|
||||
"When creating LangGraph agents, you can also set them up so that they persist their state. This allows you to do things like interact with an agent multiple times and have it remember previous interactions.\n",
|
||||
"\n",
|
||||
"This example shows how to use `Postgres` as the backend for persisting checkpoint state.\n",
|
||||
"\n",
|
||||
"The distinguishing code is as follows:\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"from psycopg_pool import ConnectionPool\n",
|
||||
"from langchain_postgres import PostgresSaver, PickleCheckpointSerializer\n",
|
||||
"\n",
|
||||
"pool = ConnectionPool(\n",
|
||||
" # Example configuration. Update to your DB\n",
|
||||
" conninfo=\"postgresql://langchain:langchain@localhost:6024/langchain\",\n",
|
||||
" max_size=20,\n",
|
||||
")\n",
|
||||
"# Remember to close the pool once you're done\n",
|
||||
"\n",
|
||||
"PostgresSaver.create_tables(pool)\n",
|
||||
"\n",
|
||||
"memory = PostgresSaver(\n",
|
||||
" serializer=PickleCheckpointSerializer(),\n",
|
||||
" sync_connection=pool,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Finally, we compile it!\n",
|
||||
"# This compiles it into a LangChain Runnable,\n",
|
||||
"# meaning you can use it as you would any other runnable\n",
|
||||
"app = workflow.compile(checkpointer=memory)\n",
|
||||
"```"
|
||||
"NOTE: this is just an example implementation. You can implement your own checkpointer using a different database or modify this one as long as it conforms to the `BaseCheckpointSaver` interface."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2d486fd6",
|
||||
"id": "ddc5cc5f-89fa-4d7a-9f2b-5b099feb6ecc",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Checkpointer implementation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "faadfb1b-cebe-4dcf-82fd-34044c380bc4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U psycopg psycopg-pool langgraph"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "a35dba8e-5562-4803-ad80-160f53592dd7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"\"\"\"Implementation of a langgraph checkpoint saver using Postgres.\"\"\"\n",
|
||||
"from contextlib import asynccontextmanager, contextmanager\n",
|
||||
"from typing import Any, AsyncGenerator, AsyncIterator, Generator, Optional, Union, Tuple, List\n",
|
||||
"\n",
|
||||
"import psycopg\n",
|
||||
"from langchain_core.runnables import RunnableConfig\n",
|
||||
"from langgraph.checkpoint import BaseCheckpointSaver\n",
|
||||
"from langgraph.serde.jsonplus import JsonPlusSerializer\n",
|
||||
"from langgraph.checkpoint.base import Checkpoint, CheckpointMetadata, CheckpointTuple\n",
|
||||
"from psycopg_pool import AsyncConnectionPool, ConnectionPool\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class JsonAndBinarySerializer(JsonPlusSerializer):\n",
|
||||
" def _default(self, obj):\n",
|
||||
" if isinstance(obj, (bytes, bytearray)):\n",
|
||||
" return self._encode_constructor_args(\n",
|
||||
" obj.__class__, method=\"fromhex\", args=[obj.hex()]\n",
|
||||
" )\n",
|
||||
" return super()._default(obj)\n",
|
||||
"\n",
|
||||
" def dumps(self, obj: Any) -> tuple[str, bytes]:\n",
|
||||
" if isinstance(obj, bytes):\n",
|
||||
" return \"bytes\", obj\n",
|
||||
" elif isinstance(obj, bytearray):\n",
|
||||
" return \"bytearray\", obj\n",
|
||||
"\n",
|
||||
" return \"json\", super().dumps(obj)\n",
|
||||
"\n",
|
||||
" def loads(self, s: tuple[str, bytes]) -> Any:\n",
|
||||
" if s[0] == \"bytes\":\n",
|
||||
" return s[1]\n",
|
||||
" elif s[0] == \"bytearray\":\n",
|
||||
" return bytearray(s[1])\n",
|
||||
" elif s[0] == \"json\":\n",
|
||||
" return super().loads(s[1])\n",
|
||||
" else:\n",
|
||||
" raise NotImplementedError(f\"Unknown serialization type: {s[0]}\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@contextmanager\n",
|
||||
"def _get_sync_connection(\n",
|
||||
" connection: Union[psycopg.Connection, ConnectionPool, None],\n",
|
||||
") -> Generator[psycopg.Connection, None, None]:\n",
|
||||
" \"\"\"Get the connection to the Postgres database.\"\"\"\n",
|
||||
" if isinstance(connection, psycopg.Connection):\n",
|
||||
" yield connection\n",
|
||||
" elif isinstance(connection, ConnectionPool):\n",
|
||||
" with connection.connection() as conn:\n",
|
||||
" yield conn\n",
|
||||
" else:\n",
|
||||
" raise ValueError(\n",
|
||||
" \"Invalid sync connection object. Please initialize the check pointer \"\n",
|
||||
" f\"with an appropriate sync connection object. \"\n",
|
||||
" f\"Got {type(connection)}.\"\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@asynccontextmanager\n",
|
||||
"async def _get_async_connection(\n",
|
||||
" connection: Union[psycopg.AsyncConnection, AsyncConnectionPool, None],\n",
|
||||
") -> AsyncGenerator[psycopg.AsyncConnection, None]:\n",
|
||||
" \"\"\"Get the connection to the Postgres database.\"\"\"\n",
|
||||
" if isinstance(connection, psycopg.AsyncConnection):\n",
|
||||
" yield connection\n",
|
||||
" elif isinstance(connection, AsyncConnectionPool):\n",
|
||||
" async with connection.connection() as conn:\n",
|
||||
" yield conn\n",
|
||||
" else:\n",
|
||||
" raise ValueError(\n",
|
||||
" \"Invalid async connection object. Please initialize the check pointer \"\n",
|
||||
" f\"with an appropriate async connection object. \"\n",
|
||||
" f\"Got {type(connection)}.\"\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class PostgresSaver(BaseCheckpointSaver):\n",
|
||||
" sync_connection: Optional[Union[psycopg.Connection, ConnectionPool]] = None\n",
|
||||
" \"\"\"The synchronous connection or pool to the Postgres database.\n",
|
||||
" \n",
|
||||
" If providing a connection object, please ensure that the connection is open\n",
|
||||
" and remember to close the connection when done.\n",
|
||||
" \"\"\"\n",
|
||||
" async_connection: Optional[\n",
|
||||
" Union[psycopg.AsyncConnection, AsyncConnectionPool]\n",
|
||||
" ] = None\n",
|
||||
" \"\"\"The asynchronous connection or pool to the Postgres database.\n",
|
||||
" \n",
|
||||
" If providing a connection object, please ensure that the connection is open\n",
|
||||
" and remember to close the connection when done.\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" def __init__(\n",
|
||||
" self,\n",
|
||||
" sync_connection: Optional[Union[psycopg.Connection, ConnectionPool]] = None,\n",
|
||||
" async_connection: Optional[\n",
|
||||
" Union[psycopg.AsyncConnection, AsyncConnectionPool]\n",
|
||||
" ] = None\n",
|
||||
" \n",
|
||||
" ):\n",
|
||||
" super().__init__(serde=JsonPlusSerializer())\n",
|
||||
" self.sync_connection = sync_connection\n",
|
||||
" self.async_connection = async_connection\n",
|
||||
"\n",
|
||||
" @contextmanager\n",
|
||||
" def _get_sync_connection(self) -> Generator[psycopg.Connection, None, None]:\n",
|
||||
" \"\"\"Get the connection to the Postgres database.\"\"\"\n",
|
||||
" with _get_sync_connection(self.sync_connection) as connection:\n",
|
||||
" yield connection\n",
|
||||
"\n",
|
||||
" @asynccontextmanager\n",
|
||||
" async def _get_async_connection(\n",
|
||||
" self,\n",
|
||||
" ) -> AsyncGenerator[psycopg.AsyncConnection, None]:\n",
|
||||
" \"\"\"Get the connection to the Postgres database.\"\"\"\n",
|
||||
" async with _get_async_connection(self.async_connection) as connection:\n",
|
||||
" yield connection\n",
|
||||
"\n",
|
||||
" CREATE_TABLES_QUERY = \"\"\"\n",
|
||||
" CREATE TABLE IF NOT EXISTS checkpoints (\n",
|
||||
" thread_id TEXT NOT NULL,\n",
|
||||
" thread_ts TEXT NOT NULL,\n",
|
||||
" parent_ts TEXT,\n",
|
||||
" checkpoint BYTEA NOT NULL,\n",
|
||||
" metadata BYTEA NOT NULL,\n",
|
||||
" PRIMARY KEY (thread_id, thread_ts)\n",
|
||||
" );\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" @staticmethod\n",
|
||||
" def create_tables(connection: Union[psycopg.Connection, ConnectionPool], /) -> None:\n",
|
||||
" \"\"\"Create the schema for the checkpoint saver.\"\"\"\n",
|
||||
" with _get_sync_connection(connection) as conn:\n",
|
||||
" with conn.cursor() as cur:\n",
|
||||
" cur.execute(PostgresSaver.CREATE_TABLES_QUERY)\n",
|
||||
" \n",
|
||||
"\n",
|
||||
" @staticmethod\n",
|
||||
" async def acreate_tables(\n",
|
||||
" connection: Union[psycopg.AsyncConnection, AsyncConnectionPool], /\n",
|
||||
" ) -> None:\n",
|
||||
" \"\"\"Create the schema for the checkpoint saver.\"\"\"\n",
|
||||
" async with _get_async_connection(connection) as conn:\n",
|
||||
" async with conn.cursor() as cur:\n",
|
||||
" await cur.execute(PostgresSaver.CREATE_TABLES_QUERY)\n",
|
||||
"\n",
|
||||
" @staticmethod\n",
|
||||
" def drop_tables(connection: psycopg.Connection, /) -> None:\n",
|
||||
" \"\"\"Drop the table for the checkpoint saver.\"\"\"\n",
|
||||
" with connection.cursor() as cur:\n",
|
||||
" cur.execute(\"DROP TABLE IF EXISTS checkpoints;\")\n",
|
||||
"\n",
|
||||
" @staticmethod\n",
|
||||
" async def adrop_tables(connection: psycopg.AsyncConnection, /) -> None:\n",
|
||||
" \"\"\"Drop the table for the checkpoint saver.\"\"\"\n",
|
||||
" async with connection.cursor() as cur:\n",
|
||||
" await cur.execute(\"DROP TABLE IF EXISTS checkpoints;\")\n",
|
||||
"\n",
|
||||
" UPSERT_CHECKPOINT_QUERY = \"\"\"\n",
|
||||
" INSERT INTO checkpoints \n",
|
||||
" (thread_id, thread_ts, parent_ts, checkpoint, metadata)\n",
|
||||
" VALUES \n",
|
||||
" (%s, %s, %s, %s, %s)\n",
|
||||
" ON CONFLICT (thread_id, thread_ts)\n",
|
||||
" DO UPDATE SET checkpoint = EXCLUDED.checkpoint,\n",
|
||||
" metadata = EXCLUDED.metadata;\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" def put(self, config: RunnableConfig, checkpoint: Checkpoint, metadata: CheckpointMetadata) -> RunnableConfig:\n",
|
||||
" \"\"\"Put the checkpoint for the given configuration.\n",
|
||||
" Args:\n",
|
||||
" config: The configuration for the checkpoint.\n",
|
||||
" A dict with a `configurable` key which is a dict with\n",
|
||||
" a `thread_id` key and an optional `thread_ts` key.\n",
|
||||
" For example, { 'configurable': { 'thread_id': 'test_thread' } }\n",
|
||||
" checkpoint: The checkpoint to persist.\n",
|
||||
" Returns:\n",
|
||||
" The RunnableConfig that describes the checkpoint that was just created.\n",
|
||||
" It'll contain the `thread_id` and `thread_ts` of the checkpoint.\n",
|
||||
" \"\"\"\n",
|
||||
" thread_id = config[\"configurable\"][\"thread_id\"]\n",
|
||||
" parent_ts = config[\"configurable\"].get(\"thread_ts\")\n",
|
||||
" with self._get_sync_connection() as conn:\n",
|
||||
" with conn.cursor() as cur:\n",
|
||||
" cur.execute(\n",
|
||||
" self.UPSERT_CHECKPOINT_QUERY,\n",
|
||||
" (\n",
|
||||
" thread_id,\n",
|
||||
" checkpoint[\"ts\"],\n",
|
||||
" parent_ts if parent_ts else None,\n",
|
||||
" self.serde.dumps(checkpoint),\n",
|
||||
" self.serde.dumps(metadata),\n",
|
||||
" ),\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" return {\n",
|
||||
" \"configurable\": {\n",
|
||||
" \"thread_id\": thread_id,\n",
|
||||
" \"thread_ts\": checkpoint[\"ts\"],\n",
|
||||
" },\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" async def aput(\n",
|
||||
" self, config: RunnableConfig, checkpoint: Checkpoint, metadata: CheckpointMetadata\n",
|
||||
" ) -> RunnableConfig:\n",
|
||||
" \"\"\"Put the checkpoint for the given configuration.\n",
|
||||
" Args:\n",
|
||||
" config: The configuration for the checkpoint.\n",
|
||||
" A dict with a `configurable` key which is a dict with\n",
|
||||
" a `thread_id` key and an optional `thread_ts` key.\n",
|
||||
" For example, { 'configurable': { 'thread_id': 'test_thread' } }\n",
|
||||
" checkpoint: The checkpoint to persist.\n",
|
||||
" Returns:\n",
|
||||
" The RunnableConfig that describes the checkpoint that was just created.\n",
|
||||
" It'll contain the `thread_id` and `thread_ts` of the checkpoint.\n",
|
||||
" \"\"\"\n",
|
||||
" thread_id = config[\"configurable\"][\"thread_id\"]\n",
|
||||
" parent_ts = config[\"configurable\"].get(\"thread_ts\")\n",
|
||||
" async with self._get_async_connection() as conn:\n",
|
||||
" async with conn.cursor() as cur:\n",
|
||||
" await cur.execute(\n",
|
||||
" self.UPSERT_CHECKPOINT_QUERY,\n",
|
||||
" (\n",
|
||||
" thread_id,\n",
|
||||
" checkpoint[\"ts\"],\n",
|
||||
" parent_ts if parent_ts else None,\n",
|
||||
" self.serde.dumps(checkpoint),\n",
|
||||
" self.serde.dumps(metadata),\n",
|
||||
" ),\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" return {\n",
|
||||
" \"configurable\": {\n",
|
||||
" \"thread_id\": thread_id,\n",
|
||||
" \"thread_ts\": checkpoint[\"ts\"],\n",
|
||||
" },\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" LIST_CHECKPOINTS_QUERY_STR = \"\"\"\n",
|
||||
" SELECT checkpoint, metadata, thread_ts, parent_ts\n",
|
||||
" FROM checkpoints\n",
|
||||
" {where}\n",
|
||||
" ORDER BY thread_ts DESC\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" def list(\n",
|
||||
" self,\n",
|
||||
" config: Optional[RunnableConfig],\n",
|
||||
" *,\n",
|
||||
" filter: Optional[dict[str, Any]] = None,\n",
|
||||
" before: Optional[RunnableConfig] = None,\n",
|
||||
" limit: Optional[int] = None,\n",
|
||||
" ) -> Generator[CheckpointTuple, None, None]:\n",
|
||||
" \"\"\"Get all the checkpoints for the given configuration.\"\"\"\n",
|
||||
" where, args = self._search_where(config, filter, before)\n",
|
||||
" query = self.LIST_CHECKPOINTS_QUERY_STR.format(where=where)\n",
|
||||
" if limit:\n",
|
||||
" query += f\" LIMIT {limit}\"\n",
|
||||
" with self._get_sync_connection() as conn:\n",
|
||||
" with conn.cursor() as cur:\n",
|
||||
" thread_id = config[\"configurable\"][\"thread_id\"]\n",
|
||||
" cur.execute(query, tuple(args))\n",
|
||||
" for value in cur:\n",
|
||||
" checkpoint, metadata, thread_ts, parent_ts = value\n",
|
||||
" yield CheckpointTuple(\n",
|
||||
" config={\n",
|
||||
" \"configurable\": {\n",
|
||||
" \"thread_id\": thread_id,\n",
|
||||
" \"thread_ts\": thread_ts,\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" checkpoint=self.serde.loads(checkpoint),\n",
|
||||
" metadata=self.serde.loads(metadata),\n",
|
||||
" parent_config={\n",
|
||||
" \"configurable\": {\n",
|
||||
" \"thread_id\": thread_id,\n",
|
||||
" \"thread_ts\": thread_ts,\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
" if parent_ts\n",
|
||||
" else None,\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" async def alist(\n",
|
||||
" self,\n",
|
||||
" config: Optional[RunnableConfig],\n",
|
||||
" *,\n",
|
||||
" filter: Optional[dict[str, Any]] = None,\n",
|
||||
" before: Optional[RunnableConfig] = None,\n",
|
||||
" limit: Optional[int] = None,\n",
|
||||
" ) -> AsyncIterator[CheckpointTuple]:\n",
|
||||
" \"\"\"Get all the checkpoints for the given configuration.\"\"\"\n",
|
||||
" where, args = self._search_where(config, filter, before)\n",
|
||||
" query = self.LIST_CHECKPOINTS_QUERY_STR.format(where=where)\n",
|
||||
" if limit:\n",
|
||||
" query += f\" LIMIT {limit}\"\n",
|
||||
" async with self._get_async_connection() as conn:\n",
|
||||
" async with conn.cursor() as cur:\n",
|
||||
" thread_id = config[\"configurable\"][\"thread_id\"]\n",
|
||||
" await cur.execute(query, tuple(args))\n",
|
||||
" async for value in cur:\n",
|
||||
" checkpoint, metadata, thread_ts, parent_ts = value\n",
|
||||
" yield CheckpointTuple(\n",
|
||||
" config={\n",
|
||||
" \"configurable\": {\n",
|
||||
" \"thread_id\": thread_id,\n",
|
||||
" \"thread_ts\": thread_ts,\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" checkpoint=self.serde.loads(checkpoint),\n",
|
||||
" metadata=self.serde.loads(metadata),\n",
|
||||
" parent_config={\n",
|
||||
" \"configurable\": {\n",
|
||||
" \"thread_id\": thread_id,\n",
|
||||
" \"thread_ts\": thread_ts,\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
" if parent_ts\n",
|
||||
" else None,\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" GET_CHECKPOINT_BY_TS_QUERY = \"\"\"\n",
|
||||
" SELECT checkpoint, metadata, thread_ts, parent_ts\n",
|
||||
" FROM checkpoints\n",
|
||||
" WHERE thread_id = %(thread_id)s AND thread_ts = %(thread_ts)s\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" GET_CHECKPOINT_QUERY = \"\"\"\n",
|
||||
" SELECT checkpoint, metadata, thread_ts, parent_ts\n",
|
||||
" FROM checkpoints\n",
|
||||
" WHERE thread_id = %(thread_id)s\n",
|
||||
" ORDER BY thread_ts DESC LIMIT 1\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:\n",
|
||||
" \"\"\"Get the checkpoint tuple for the given configuration.\n",
|
||||
" Args:\n",
|
||||
" config: The configuration for the checkpoint.\n",
|
||||
" A dict with a `configurable` key which is a dict with\n",
|
||||
" a `thread_id` key and an optional `thread_ts` key.\n",
|
||||
" For example, { 'configurable': { 'thread_id': 'test_thread' } }\n",
|
||||
" Returns:\n",
|
||||
" The checkpoint tuple for the given configuration if it exists,\n",
|
||||
" otherwise None.\n",
|
||||
" If thread_ts is None, the latest checkpoint is returned if it exists.\n",
|
||||
" \"\"\"\n",
|
||||
" thread_id = config[\"configurable\"][\"thread_id\"]\n",
|
||||
" thread_ts = config[\"configurable\"].get(\"thread_ts\")\n",
|
||||
" with self._get_sync_connection() as conn:\n",
|
||||
" with conn.cursor() as cur:\n",
|
||||
" if thread_ts:\n",
|
||||
" cur.execute(\n",
|
||||
" self.GET_CHECKPOINT_BY_TS_QUERY,\n",
|
||||
" {\n",
|
||||
" \"thread_id\": thread_id,\n",
|
||||
" \"thread_ts\": thread_ts,\n",
|
||||
" },\n",
|
||||
" )\n",
|
||||
" value = cur.fetchone()\n",
|
||||
" if value:\n",
|
||||
" checkpoint, metadata, thread_ts, parent_ts = value\n",
|
||||
" return CheckpointTuple(\n",
|
||||
" config=config,\n",
|
||||
" checkpoint=self.serde.loads(checkpoint),\n",
|
||||
" metadata=self.serde.loads(metadata),\n",
|
||||
" parent_config={\n",
|
||||
" \"configurable\": {\n",
|
||||
" \"thread_id\": thread_id,\n",
|
||||
" \"thread_ts\": thread_ts,\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
" if thread_ts\n",
|
||||
" else None,\n",
|
||||
" )\n",
|
||||
" else:\n",
|
||||
" cur.execute(\n",
|
||||
" self.GET_CHECKPOINT_QUERY,\n",
|
||||
" {\n",
|
||||
" \"thread_id\": thread_id,\n",
|
||||
" },\n",
|
||||
" )\n",
|
||||
" value = cur.fetchone()\n",
|
||||
" if value:\n",
|
||||
" checkpoint, metadata, thread_ts, parent_ts = value\n",
|
||||
" return CheckpointTuple(\n",
|
||||
" config={\n",
|
||||
" \"configurable\": {\n",
|
||||
" \"thread_id\": thread_id,\n",
|
||||
" \"thread_ts\": thread_ts,\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" checkpoint=self.serde.loads(checkpoint),\n",
|
||||
" metadata=self.serde.loads(metadata),\n",
|
||||
" parent_config={\n",
|
||||
" \"configurable\": {\n",
|
||||
" \"thread_id\": thread_id,\n",
|
||||
" \"thread_ts\": parent_ts,\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
" if parent_ts\n",
|
||||
" else None,\n",
|
||||
" )\n",
|
||||
" return None\n",
|
||||
"\n",
|
||||
" async def aget_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:\n",
|
||||
" \"\"\"Get the checkpoint tuple for the given configuration.\n",
|
||||
" Args:\n",
|
||||
" config: The configuration for the checkpoint.\n",
|
||||
" A dict with a `configurable` key which is a dict with\n",
|
||||
" a `thread_id` key and an optional `thread_ts` key.\n",
|
||||
" For example, { 'configurable': { 'thread_id': 'test_thread' } }\n",
|
||||
" Returns:\n",
|
||||
" The checkpoint tuple for the given configuration if it exists,\n",
|
||||
" otherwise None.\n",
|
||||
" If thread_ts is None, the latest checkpoint is returned if it exists.\n",
|
||||
" \"\"\"\n",
|
||||
" thread_id = config[\"configurable\"][\"thread_id\"]\n",
|
||||
" thread_ts = config[\"configurable\"].get(\"thread_ts\")\n",
|
||||
" async with self._get_async_connection() as conn:\n",
|
||||
" async with conn.cursor() as cur:\n",
|
||||
" if thread_ts:\n",
|
||||
" await cur.execute(\n",
|
||||
" self.GET_CHECKPOINT_BY_TS_QUERY,\n",
|
||||
" {\n",
|
||||
" \"thread_id\": thread_id,\n",
|
||||
" \"thread_ts\": thread_ts,\n",
|
||||
" },\n",
|
||||
" )\n",
|
||||
" value = await cur.fetchone()\n",
|
||||
" if value:\n",
|
||||
" checkpoint, metadata, thread_ts, parent_ts = value\n",
|
||||
" return CheckpointTuple(\n",
|
||||
" config=config,\n",
|
||||
" checkpoint=self.serde.loads(checkpoint),\n",
|
||||
" metadata=self.serde.loads(metadata),\n",
|
||||
" parent_config={\n",
|
||||
" \"configurable\": {\n",
|
||||
" \"thread_id\": thread_id,\n",
|
||||
" \"thread_ts\": thread_ts,\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
" if thread_ts\n",
|
||||
" else None,\n",
|
||||
" )\n",
|
||||
" else:\n",
|
||||
" await cur.execute(\n",
|
||||
" self.GET_CHECKPOINT_QUERY,\n",
|
||||
" {\n",
|
||||
" \"thread_id\": thread_id,\n",
|
||||
" },\n",
|
||||
" )\n",
|
||||
" value = await cur.fetchone()\n",
|
||||
" if value:\n",
|
||||
" checkpoint, metadata, thread_ts, parent_ts = value\n",
|
||||
" return CheckpointTuple(\n",
|
||||
" config={\n",
|
||||
" \"configurable\": {\n",
|
||||
" \"thread_id\": thread_id,\n",
|
||||
" \"thread_ts\": thread_ts,\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" checkpoint=self.serde.loads(checkpoint),\n",
|
||||
" metadata=self.serde.loads(metadata),\n",
|
||||
" parent_config={\n",
|
||||
" \"configurable\": {\n",
|
||||
" \"thread_id\": thread_id,\n",
|
||||
" \"thread_ts\": parent_ts,\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
" if parent_ts\n",
|
||||
" else None,\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" return None\n",
|
||||
"\n",
|
||||
" def _search_where(\n",
|
||||
" self,\n",
|
||||
" config: Optional[RunnableConfig],\n",
|
||||
" filter: Optional[dict[str, Any]] = None,\n",
|
||||
" before: Optional[RunnableConfig] = None,\n",
|
||||
" ) -> Tuple[str, List[Any]]:\n",
|
||||
" \"\"\"Return WHERE clause predicates for given config, filter, and before parameters.\n",
|
||||
" Args:\n",
|
||||
" config (Optional[RunnableConfig]): The config to use for filtering.\n",
|
||||
" filter (Optional[Dict[str, Any]]): Additional filtering criteria.\n",
|
||||
" before (Optional[RunnableConfig]): A config to limit results before a certain timestamp.\n",
|
||||
" Returns:\n",
|
||||
" Tuple[str, Sequence[Any]]: A tuple containing the WHERE clause and parameter values.\n",
|
||||
" \"\"\"\n",
|
||||
" wheres = []\n",
|
||||
" param_values = []\n",
|
||||
"\n",
|
||||
" # Add predicate for config\n",
|
||||
" if config is not None:\n",
|
||||
" wheres.append(\"thread_id = %s \")\n",
|
||||
" param_values.append(config[\"configurable\"][\"thread_id\"])\n",
|
||||
"\n",
|
||||
" if filter:\n",
|
||||
" raise NotImplementedError()\n",
|
||||
"\n",
|
||||
" # Add predicate for limiting results before a certain timestamp\n",
|
||||
" if before is not None:\n",
|
||||
" wheres.append(\"thread_ts < %s\")\n",
|
||||
" param_values.append(before[\"configurable\"][\"thread_ts\"])\n",
|
||||
"\n",
|
||||
" where_clause = \"WHERE \" + \" AND \".join(wheres) if wheres else \"\"\n",
|
||||
" return where_clause, param_values"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "456fa19c-93a5-4750-a410-f2d810b964ad",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup environment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "eca9aafb-a155-407a-8036-682a2f1297d7",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"OPENAI_API_KEY: ········\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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": "e26b3204-cca2-414c-800e-7e09032445ae",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup model and tools for the graph"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "e5213193-5a7d-43e7-aeba-fe732bb1cd7a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Literal\n",
|
||||
"from langchain_core.runnables import ConfigurableField\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def get_weather(city: Literal[\"nyc\", \"sf\"]):\n",
|
||||
" \"\"\"Use this to get weather information.\"\"\"\n",
|
||||
" if city == \"nyc\":\n",
|
||||
" return \"It might be cloudy in nyc\"\n",
|
||||
" elif city == \"sf\":\n",
|
||||
" return \"It's always sunny in sf\"\n",
|
||||
" else:\n",
|
||||
" raise AssertionError(\"Unknown city\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [get_weather]\n",
|
||||
"model = ChatOpenAI(model_name=\"gpt-4o\", temperature=0)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e9342c62-dbb4-40f6-9271-7393f1ca48c4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Use sync connection"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "2b9d13b1-9d72-48a0-b63a-adc062c06c29",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"DB_URI = \"postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e39fc712-9e1c-4831-9077-dd07b0c13594",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### With a connection pool"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "2657c1c4-d8a5-4fe3-8f77-95415a98ed6c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from psycopg_pool import ConnectionPool\n",
|
||||
"\n",
|
||||
"pool = ConnectionPool(\n",
|
||||
" # Example configuration\n",
|
||||
" conninfo=DB_URI,\n",
|
||||
" max_size=20,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"checkpointer = PostgresSaver(\n",
|
||||
" sync_connection=pool\n",
|
||||
")\n",
|
||||
"checkpointer.create_tables(pool)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "6d388241-de57-4b4e-af7b-eb1081fb8f36",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"graph = create_react_agent(model, tools=tools, checkpointer=checkpointer)\n",
|
||||
"config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
|
||||
"res = graph.invoke({\"messages\": [(\"human\", \"what's the weather in sf\")]}, config)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "a7e0e7ec-a675-470b-9270-e4bdc59d4a4d",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'messages': [HumanMessage(content=\"what's the weather in sf\", id='8481d24b-d505-40fc-8c55-347277d50f2a'),\n",
|
||||
" AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_cGApaEshW0YNp5WsirAcFTBv', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_ce0793330f', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-bf85107a-02a4-48d1-83ba-5448498f5320-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_cGApaEshW0YNp5WsirAcFTBv'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71}),\n",
|
||||
" ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='f685da3d-b196-44a1-9fe5-c7ce4d3d7d1d', tool_call_id='call_cGApaEshW0YNp5WsirAcFTBv'),\n",
|
||||
" AIMessage(content='The weather in San Francisco is currently sunny.', response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_4008e3b719', 'finish_reason': 'stop', 'logprobs': None}, id='run-0d681a28-8116-4cb2-a25f-e4f7949481eb-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94})]}"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"res"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "96efd8b2-97c9-4207-83b2-00131723a75a",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'v': 1,\n",
|
||||
" 'ts': '2024-06-27T01:06:18.970784+00:00',\n",
|
||||
" 'id': '1ef34217-5fac-63f8-8003-9f1ea087dd14',\n",
|
||||
" 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in sf\", id='8481d24b-d505-40fc-8c55-347277d50f2a'),\n",
|
||||
" AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_cGApaEshW0YNp5WsirAcFTBv', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_ce0793330f', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-bf85107a-02a4-48d1-83ba-5448498f5320-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_cGApaEshW0YNp5WsirAcFTBv'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71}),\n",
|
||||
" ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='f685da3d-b196-44a1-9fe5-c7ce4d3d7d1d', tool_call_id='call_cGApaEshW0YNp5WsirAcFTBv'),\n",
|
||||
" AIMessage(content='The weather in San Francisco is currently sunny.', response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_4008e3b719', 'finish_reason': 'stop', 'logprobs': None}, id='run-0d681a28-8116-4cb2-a25f-e4f7949481eb-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94})],\n",
|
||||
" 'agent': 'agent'},\n",
|
||||
" 'channel_versions': {'__start__': 2,\n",
|
||||
" 'messages': 5,\n",
|
||||
" 'start:agent': 3,\n",
|
||||
" 'agent': 5,\n",
|
||||
" 'branch:agent:should_continue:tools': 4,\n",
|
||||
" 'tools': 5},\n",
|
||||
" 'versions_seen': {'__start__': {'__start__': 1},\n",
|
||||
" 'agent': {'start:agent': 3, 'tools': 4},\n",
|
||||
" 'tools': {'branch:agent:should_continue:tools': 3}},\n",
|
||||
" 'pending_sends': []}"
|
||||
]
|
||||
},
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"checkpointer.get(config)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "967c95c7-e392-4819-bd71-f29e91c68df3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### With a connection"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "180d6daf-8fa7-4608-bd2e-bfbf44ed5836",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from psycopg import Connection\n",
|
||||
"\n",
|
||||
"with Connection.connect(DB_URI) as conn:\n",
|
||||
" checkpointer = PostgresSaver(\n",
|
||||
" sync_connection=conn\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" graph = create_react_agent(model, tools=tools, checkpointer=checkpointer)\n",
|
||||
" config = {\"configurable\": {\"thread_id\": \"2\"}}\n",
|
||||
" res = graph.invoke({\"messages\": [(\"human\", \"what's the weather in sf\")]}, config)\n",
|
||||
"\n",
|
||||
" checkpoint_tuple = checkpointer.get_tuple(config)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "613d0bbc-0e38-45c4-aace-1f6f7ae27c7b",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"CheckpointTuple(config={'configurable': {'thread_id': '2', 'thread_ts': '2024-06-27T01:06:20.104263+00:00'}}, checkpoint={'v': 1, 'ts': '2024-06-27T01:06:20.104263+00:00', 'id': '1ef34217-6a7b-687e-8003-ab84b18fa9dc', 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in sf\", id='3ef79bb9-4249-419d-b142-8d18de63c2eb'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_zqfYbNrD30Qa9YHFSI7nJVJN', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_ce0793330f', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-5dd2d9d1-e291-43e4-86cd-96a50a546f48-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_zqfYbNrD30Qa9YHFSI7nJVJN'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71}), ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='f3eadc9a-9340-41d3-a0fb-521a78e6c438', tool_call_id='call_zqfYbNrD30Qa9YHFSI7nJVJN'), AIMessage(content='The weather in San Francisco is sunny! 🌞', response_metadata={'token_usage': {'completion_tokens': 11, 'prompt_tokens': 84, 'total_tokens': 95}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_ce0793330f', 'finish_reason': 'stop', 'logprobs': None}, id='run-f897e17e-8f0b-4140-be91-35f351609d82-0', usage_metadata={'input_tokens': 84, 'output_tokens': 11, 'total_tokens': 95})], 'agent': 'agent'}, 'channel_versions': {'__start__': 2, 'messages': 5, 'start:agent': 3, 'agent': 5, 'branch:agent:should_continue:tools': 4, 'tools': 5}, 'versions_seen': {'__start__': {'__start__': 1}, 'agent': {'start:agent': 3, 'tools': 4}, 'tools': {'branch:agent:should_continue:tools': 3}}, 'pending_sends': []}, metadata={'source': 'loop', 'step': 3, 'writes': {'agent': {'messages': [AIMessage(content='The weather in San Francisco is sunny! 🌞', response_metadata={'token_usage': {'completion_tokens': 11, 'prompt_tokens': 84, 'total_tokens': 95}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_ce0793330f', 'finish_reason': 'stop', 'logprobs': None}, id='run-f897e17e-8f0b-4140-be91-35f351609d82-0', usage_metadata={'input_tokens': 84, 'output_tokens': 11, 'total_tokens': 95})]}}}, parent_config={'configurable': {'thread_id': '2', 'thread_ts': '1ef34217-659f-62ec-8002-7f9b6b4c2a2f'}})"
|
||||
]
|
||||
},
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"checkpoint_tuple"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c0a47d3e-e588-48fc-a5d4-2145dff17e77",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Use async connection"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ee6b6cf7-d8f7-4777-a48d-93b5855fe681",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### With a connection pool"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "20cea8b7-8f13-4dc7-a3c9-825040eb4c57",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"/Users/vadymbarda/.virtualenvs/langgraph-postgres/lib/python3.11/site-packages/psycopg_pool/pool_async.py:138: RuntimeWarning: opening the async pool AsyncConnectionPool in the constructor is deprecated and will not be supported anymore in a future release. Please use `await pool.open()`, or use the pool as context manager using: `async with AsyncConnectionPool(...) as pool: `...\n",
|
||||
" warnings.warn(\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from psycopg_pool import AsyncConnectionPool\n",
|
||||
"\n",
|
||||
"pool = AsyncConnectionPool(\n",
|
||||
" # Example configuration\n",
|
||||
" conninfo=DB_URI,\n",
|
||||
" max_size=20,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"checkpointer = PostgresSaver(\n",
|
||||
" async_connection=pool\n",
|
||||
")\n",
|
||||
"await checkpointer.acreate_tables(pool)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"id": "f889dce6-7ec1-4277-b8af-ace7811733fa",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"graph = create_react_agent(model, tools=tools, checkpointer=checkpointer)\n",
|
||||
"config = {\"configurable\": {\"thread_id\": \"3\"}}\n",
|
||||
"res = await graph.ainvoke({\"messages\": [(\"human\", \"what's the weather in nyc\")]}, config)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"id": "ed58c722-1662-4ae2-9bb7-4872158a5b29",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"checkpoint_tuple = await checkpointer.aget_tuple(config)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"id": "e0c42044-4de6-4742-8e00-fe295d50c95a",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"CheckpointTuple(config={'configurable': {'thread_id': '3', 'thread_ts': '2024-06-27T01:06:21.430879+00:00'}}, checkpoint={'v': 1, 'ts': '2024-06-27T01:06:21.430879+00:00', 'id': '1ef34217-7722-6438-8003-0a35c0f65f25', 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in nyc\", id='cb5a1b8c-f329-4bee-857a-5d2c3d020147'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_gPUiz98ETlDNXttk98w6iThc', 'function': {'arguments': '{\"city\":\"nyc\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 58, 'total_tokens': 73}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_ce0793330f', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-2d35ac1c-0a99-4538-a8fc-4c182d9df69a-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'nyc'}, 'id': 'call_gPUiz98ETlDNXttk98w6iThc'}], usage_metadata={'input_tokens': 58, 'output_tokens': 15, 'total_tokens': 73}), ToolMessage(content='It might be cloudy in nyc', name='get_weather', id='f5f80d01-92e8-4961-b2b0-3b4d190c4d26', tool_call_id='call_gPUiz98ETlDNXttk98w6iThc'), AIMessage(content='The weather in NYC might be cloudy.', response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 88, 'total_tokens': 97}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_d576307f90', 'finish_reason': 'stop', 'logprobs': None}, id='run-aea6306e-f6cc-4f87-aaf4-b0dcfbdf9684-0', usage_metadata={'input_tokens': 88, 'output_tokens': 9, 'total_tokens': 97})], 'agent': 'agent'}, 'channel_versions': {'__start__': 2, 'messages': 5, 'start:agent': 3, 'agent': 5, 'branch:agent:should_continue:tools': 4, 'tools': 5}, 'versions_seen': {'__start__': {'__start__': 1}, 'agent': {'start:agent': 3, 'tools': 4}, 'tools': {'branch:agent:should_continue:tools': 3}}, 'pending_sends': []}, metadata={'source': 'loop', 'step': 3, 'writes': {'agent': {'messages': [AIMessage(content='The weather in NYC might be cloudy.', response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 88, 'total_tokens': 97}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_d576307f90', 'finish_reason': 'stop', 'logprobs': None}, id='run-aea6306e-f6cc-4f87-aaf4-b0dcfbdf9684-0', usage_metadata={'input_tokens': 88, 'output_tokens': 9, 'total_tokens': 97})]}}}, parent_config={'configurable': {'thread_id': '3', 'thread_ts': '1ef34217-7099-68be-8002-1a2b75cf9196'}})"
|
||||
]
|
||||
},
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"checkpoint_tuple"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "56552584-9eb8-40df-a6a0-44151018b509",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Use connection"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"id": "386b78bc-2f73-49ba-a2a4-47bce6fc49b7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from psycopg import AsyncConnection\n",
|
||||
"\n",
|
||||
"async with await AsyncConnection.connect(DB_URI) as conn:\n",
|
||||
" checkpointer = PostgresSaver(\n",
|
||||
" async_connection=conn\n",
|
||||
" )\n",
|
||||
" graph = create_react_agent(model, tools=tools, checkpointer=checkpointer)\n",
|
||||
" config = {\"configurable\": {\"thread_id\": \"4\"}}\n",
|
||||
" res = await graph.ainvoke({\"messages\": [(\"human\", \"what's the weather in nyc\")]}, config)\n",
|
||||
" checkpoint_tuples = [c async for c in checkpointer.alist(config)]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"id": "d1ed1344-c923-4a46-b04e-cc3646737d48",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[CheckpointTuple(config={'configurable': {'thread_id': '4', 'thread_ts': '2024-06-27T01:06:22.556635+00:00'}}, checkpoint={'v': 1, 'ts': '2024-06-27T01:06:22.556635+00:00', 'id': '1ef34217-81de-6c5a-8003-04d6ed02b672', 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in nyc\", id='9df9dda1-5e80-4938-bf24-9207738d9f5f'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_TJWuPJovXruJVafVcI540OfS', 'function': {'arguments': '{\"city\":\"nyc\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 58, 'total_tokens': 73}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_4008e3b719', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-5ba0db1c-45e7-4389-975f-489135ff50b4-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'nyc'}, 'id': 'call_TJWuPJovXruJVafVcI540OfS'}], usage_metadata={'input_tokens': 58, 'output_tokens': 15, 'total_tokens': 73}), ToolMessage(content='It might be cloudy in nyc', name='get_weather', id='04dd3f8a-c063-4885-bfcd-901aaeb63797', tool_call_id='call_TJWuPJovXruJVafVcI540OfS'), AIMessage(content='The weather in NYC might be cloudy.', response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 88, 'total_tokens': 97}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_d576307f90', 'finish_reason': 'stop', 'logprobs': None}, id='run-de373cba-dc06-4603-bed1-ba4534a2a45a-0', usage_metadata={'input_tokens': 88, 'output_tokens': 9, 'total_tokens': 97})], 'agent': 'agent'}, 'channel_versions': {'__start__': 2, 'messages': 5, 'start:agent': 3, 'agent': 5, 'branch:agent:should_continue:tools': 4, 'tools': 5}, 'versions_seen': {'__start__': {'__start__': 1}, 'agent': {'start:agent': 3, 'tools': 4}, 'tools': {'branch:agent:should_continue:tools': 3}}, 'pending_sends': []}, metadata={'source': 'loop', 'step': 3, 'writes': {'agent': {'messages': [AIMessage(content='The weather in NYC might be cloudy.', response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 88, 'total_tokens': 97}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_d576307f90', 'finish_reason': 'stop', 'logprobs': None}, id='run-de373cba-dc06-4603-bed1-ba4534a2a45a-0', usage_metadata={'input_tokens': 88, 'output_tokens': 9, 'total_tokens': 97})]}}}, parent_config={'configurable': {'thread_id': '4', 'thread_ts': '2024-06-27T01:06:22.556635+00:00'}}),\n",
|
||||
" CheckpointTuple(config={'configurable': {'thread_id': '4', 'thread_ts': '2024-06-27T01:06:22.010058+00:00'}}, checkpoint={'v': 1, 'ts': '2024-06-27T01:06:22.010058+00:00', 'id': '1ef34217-7ca8-64ca-8002-c710e3b07a9c', 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in nyc\", id='9df9dda1-5e80-4938-bf24-9207738d9f5f'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_TJWuPJovXruJVafVcI540OfS', 'function': {'arguments': '{\"city\":\"nyc\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 58, 'total_tokens': 73}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_4008e3b719', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-5ba0db1c-45e7-4389-975f-489135ff50b4-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'nyc'}, 'id': 'call_TJWuPJovXruJVafVcI540OfS'}], usage_metadata={'input_tokens': 58, 'output_tokens': 15, 'total_tokens': 73}), ToolMessage(content='It might be cloudy in nyc', name='get_weather', id='04dd3f8a-c063-4885-bfcd-901aaeb63797', tool_call_id='call_TJWuPJovXruJVafVcI540OfS')], 'tools': 'tools'}, 'channel_versions': {'__start__': 2, 'messages': 4, 'start:agent': 3, 'agent': 4, 'branch:agent:should_continue:tools': 4, 'tools': 4}, 'versions_seen': {'__start__': {'__start__': 1}, 'agent': {'start:agent': 2}, 'tools': {'branch:agent:should_continue:tools': 3}}, 'pending_sends': []}, metadata={'source': 'loop', 'step': 2, 'writes': {'tools': {'messages': [ToolMessage(content='It might be cloudy in nyc', name='get_weather', id='04dd3f8a-c063-4885-bfcd-901aaeb63797', tool_call_id='call_TJWuPJovXruJVafVcI540OfS')]}}}, parent_config={'configurable': {'thread_id': '4', 'thread_ts': '2024-06-27T01:06:22.010058+00:00'}}),\n",
|
||||
" CheckpointTuple(config={'configurable': {'thread_id': '4', 'thread_ts': '2024-06-27T01:06:22.004456+00:00'}}, checkpoint={'v': 1, 'ts': '2024-06-27T01:06:22.004456+00:00', 'id': '1ef34217-7c9a-6a64-8001-65cb5b71ff36', 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in nyc\", id='9df9dda1-5e80-4938-bf24-9207738d9f5f'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_TJWuPJovXruJVafVcI540OfS', 'function': {'arguments': '{\"city\":\"nyc\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 58, 'total_tokens': 73}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_4008e3b719', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-5ba0db1c-45e7-4389-975f-489135ff50b4-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'nyc'}, 'id': 'call_TJWuPJovXruJVafVcI540OfS'}], usage_metadata={'input_tokens': 58, 'output_tokens': 15, 'total_tokens': 73})], 'agent': 'agent', 'branch:agent:should_continue:tools': 'agent'}, 'channel_versions': {'__start__': 2, 'messages': 3, 'start:agent': 3, 'agent': 3, 'branch:agent:should_continue:tools': 3}, 'versions_seen': {'__start__': {'__start__': 1}, 'agent': {'start:agent': 2}, 'tools': {}}, 'pending_sends': []}, metadata={'source': 'loop', 'step': 1, 'writes': {'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_TJWuPJovXruJVafVcI540OfS', 'function': {'arguments': '{\"city\":\"nyc\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 58, 'total_tokens': 73}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_4008e3b719', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-5ba0db1c-45e7-4389-975f-489135ff50b4-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'nyc'}, 'id': 'call_TJWuPJovXruJVafVcI540OfS'}], usage_metadata={'input_tokens': 58, 'output_tokens': 15, 'total_tokens': 73})]}}}, parent_config={'configurable': {'thread_id': '4', 'thread_ts': '2024-06-27T01:06:22.004456+00:00'}}),\n",
|
||||
" CheckpointTuple(config={'configurable': {'thread_id': '4', 'thread_ts': '2024-06-27T01:06:21.494039+00:00'}}, checkpoint={'v': 1, 'ts': '2024-06-27T01:06:21.494039+00:00', 'id': '1ef34217-77bc-67e0-8000-0dcc15e3e392', 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in nyc\", id='9df9dda1-5e80-4938-bf24-9207738d9f5f')], 'start:agent': '__start__'}, 'channel_versions': {'__start__': 2, 'messages': 2, 'start:agent': 2}, 'versions_seen': {'__start__': {'__start__': 1}, 'agent': {}, 'tools': {}}, 'pending_sends': []}, metadata={'source': 'loop', 'step': 0, 'writes': None}, parent_config={'configurable': {'thread_id': '4', 'thread_ts': '2024-06-27T01:06:21.494039+00:00'}}),\n",
|
||||
" CheckpointTuple(config={'configurable': {'thread_id': '4', 'thread_ts': '2024-06-27T01:06:21.491815+00:00'}}, checkpoint={'v': 1, 'ts': '2024-06-27T01:06:21.491815+00:00', 'id': '1ef34217-77b7-6114-bfff-cceb4fa6453c', 'channel_values': {'messages': [], '__start__': {'messages': [['human', \"what's the weather in nyc\"]]}}, 'channel_versions': {'__start__': 1}, 'versions_seen': {}, 'pending_sends': []}, metadata={'source': 'input', 'step': -1, 'writes': {'messages': [['human', \"what's the weather in nyc\"]]}}, parent_config=None)]"
|
||||
]
|
||||
},
|
||||
"execution_count": 17,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"checkpoint_tuples"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6a39d1ff-ca37-4457-8b52-07d33b59c36e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"display_name": "langgraph-postgres",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
"name": "langgraph-postgres"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
@@ -65,7 +959,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
"version": "3.11.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"* Web-search\n",
|
||||
"* Iterative RAG\n",
|
||||
"\n",
|
||||
"We'll use [Command R](https://txt.cohere.com/command-r/), a recent release from Cohere that:\n",
|
||||
"We'll use [Command R](https://cohere.com/blog/command-r), a recent release from Cohere that:\n",
|
||||
"\n",
|
||||
"* Has strong accuracy on RAG and Tool Use\n",
|
||||
"* Has 128k context\n",
|
||||
|
||||
@@ -146,8 +146,7 @@
|
||||
"vectorstore = Chroma.from_documents(\n",
|
||||
" documents=doc_splits,\n",
|
||||
" collection_name=\"rag-chroma\",\n",
|
||||
" embedding=NomicEmbeddings(model=\"nomic-embed-text-v1.5\",\n",
|
||||
" inference_mode='local'),\n",
|
||||
" embedding=NomicEmbeddings(model=\"nomic-embed-text-v1.5\", inference_mode=\"local\"),\n",
|
||||
")\n",
|
||||
"retriever = vectorstore.as_retriever()"
|
||||
]
|
||||
|
||||
@@ -120,8 +120,7 @@
|
||||
"vectorstore = Chroma.from_documents(\n",
|
||||
" documents=doc_splits,\n",
|
||||
" collection_name=\"rag-chroma\",\n",
|
||||
" embedding=NomicEmbeddings(model=\"nomic-embed-text-v1.5\",\n",
|
||||
" inference_mode='local'),\n",
|
||||
" embedding=NomicEmbeddings(model=\"nomic-embed-text-v1.5\", inference_mode=\"local\"),\n",
|
||||
")\n",
|
||||
"retriever = vectorstore.as_retriever()"
|
||||
]
|
||||
|
||||
@@ -163,8 +163,7 @@
|
||||
"vectorstore = Chroma.from_documents(\n",
|
||||
" documents=doc_splits,\n",
|
||||
" collection_name=\"rag-chroma\",\n",
|
||||
" embedding=NomicEmbeddings(model=\"nomic-embed-text-v1.5\",\n",
|
||||
" inference_mode='local'),\n",
|
||||
" embedding=NomicEmbeddings(model=\"nomic-embed-text-v1.5\", inference_mode=\"local\"),\n",
|
||||
")\n",
|
||||
"retriever = vectorstore.as_retriever()"
|
||||
]
|
||||
|
||||
@@ -259,7 +259,7 @@
|
||||
"\n",
|
||||
"The executor receives the plan and executes the tools in sequence.\n",
|
||||
"\n",
|
||||
"Below, instantiate the search engine and define the toole execution node."
|
||||
"Below, instantiate the search engine and define the tool execution node."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -460,8 +460,14 @@
|
||||
"Congratulations on implementing ReWOO! Before you leave, I'll leave you with a couple limitations of the current implementation from the paper:\n",
|
||||
"\n",
|
||||
"1. If little context of the environment is available, the planner will be ineffective in its tool use. This can typically be ameliorated through few-shot prompting and/or fine-tuning.\n",
|
||||
"2. The tasks are still executed in sequence, meaning the total execution time is impacted by _every_ tool call, not just he longest-running in a given step."
|
||||
"2. The tasks are still executed in sequence, meaning the total execution time is impacted by _every_ tool call, not just the longest-running in a given step."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "95dd0535",
|
||||
"metadata": {},
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 108 KiB |
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## JS SDK\n",
|
||||
"Coming soon"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -1,617 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Initialization\n",
|
||||
"\n",
|
||||
"### Initializing client\n",
|
||||
"\n",
|
||||
"To get started we need to initialize our client. The process for initializing our client is almost identical for both the local deployment and cloud deployment using Langsmith."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph_sdk import get_client\n",
|
||||
"\n",
|
||||
"# If you deployed using Langsmith use this option\n",
|
||||
"# Find this url on your Langsmith deployment page\n",
|
||||
"example_deployed_url = (\n",
|
||||
" \"https://ht-unhealthy-buffalo25-39d00f953458585aa9f7b5a4fa-g3ps4aazkq-uc.a.run.app\"\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# If you deployed locally using langgraph up -c langgraph.json use this option\n",
|
||||
"# This is the default URL, and you can just call get_client() to use it\n",
|
||||
"example_local_url = \"http://localhost:8123\"\n",
|
||||
"\n",
|
||||
"client = get_client(url=\"whatever-your-url-is\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Selecting an Assistant\n",
|
||||
"\n",
|
||||
"To select an assistant we can search the assistants that are hosted on our client, and then select the one we want,"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"assistants = await client.assistants.search()\n",
|
||||
"assistants = [a for a in assistants if not a[\"config\"]]\n",
|
||||
"assistant = assistants[0]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In our example we are only hosting a single assistant, but you have the option to host many, in which case you will most likely want to do more filtering than just selecting the first one. Each assistant is a JSON object with the following format, allowing you to select based on a variety of parameters."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n",
|
||||
" 'graph_id': 'agent',\n",
|
||||
" 'created_at': '2024-06-11T20:12:45.862108+00:00',\n",
|
||||
" 'updated_at': '2024-06-11T20:12:45.862108+00:00',\n",
|
||||
" 'config': {},\n",
|
||||
" 'metadata': {'created_by': 'system'}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"assistant"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Creating a thread\n",
|
||||
"\n",
|
||||
"Threads are what we will actually use to run our graphs (assistants). Each thread will update the same state for the graph, meaning we can run the graph multiple times while the state will persist. We can also look back at our thread history, add meta data to different steps of our thread, and update the thread state manually if we wish. We will dive into all of those topics later in this article, but for now let’s just see how to start a thread:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"thread = await client.threads.create()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can examine the structure of our thread, which similar to the assistants object provides us with some information about the thread itself, including its id, timestamps, and metadata:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'thread_id': '6c2e8002-5712-4388-bed6-0747e9a86e31',\n",
|
||||
" 'created_at': '2024-06-19T15:58:59.243657+00:00',\n",
|
||||
" 'updated_at': '2024-06-19T15:58:59.243657+00:00',\n",
|
||||
" 'metadata': {}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"thread"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now we are ready to actually use our graph!\n",
|
||||
"\n",
|
||||
"## Invoking the graph\n",
|
||||
"\n",
|
||||
"The graph used in this example is a simple example of a StateGraph, but it allows us to show most of the API functionality. The state of our graph is defined as follows:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Annotated, TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import AnyMessage\n",
|
||||
"\n",
|
||||
"from langgraph.graph import add_messages\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def update_user_info(old_info, new_info):\n",
|
||||
" if \"name\" not in new_info or new_info[\"age\"] == -1:\n",
|
||||
" return old_info\n",
|
||||
" return new_info\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class UserInformation(TypedDict):\n",
|
||||
" age: int\n",
|
||||
" name: str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
" messages: Annotated[list[AnyMessage], add_messages]\n",
|
||||
" user_info: Annotated[UserInformation, update_user_info]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"It is important to note that our member variables can be updated by using the `Annotated` class. This is especially important when we make API calls that will update our variables. This is also a good example that your graph can hold much more information than just messages. In our example we use a very simple `UserInformation` class, but you can imagine holding much richer information in your state.\n",
|
||||
"\n",
|
||||
"Our graph looks like follows:\n",
|
||||
"\n",
|
||||
"<div style=\"text-align:center\">\n",
|
||||
" <img src=\"./img/graph_diagram.png\" style=\"width:30%\">\n",
|
||||
"</div>\n",
|
||||
"\n",
|
||||
"The workflow is as follows: first the user inputs some message, our llm decides how to configure the call to our tool `get_user_info` , and after getting the results of the tool call we respond to our user using another LLM.\n",
|
||||
"\n",
|
||||
"### Simple Invocation\n",
|
||||
"\n",
|
||||
"Ok, now that we have set up our client, assistant, and thread we can actually invoke the graph above. Let’s first define the function we will use to invoke the graph, since we don’t want to have to rewrite this code every single run."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 48,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"async def run_input(client, thread, assistant, input, metadata={}):\n",
|
||||
" # client.runs.stream will stream the results of running our graph\n",
|
||||
" async for chunk in client.runs.stream(\n",
|
||||
" thread[\"thread_id\"],\n",
|
||||
" assistant[\"assistant_id\"],\n",
|
||||
" input=input,\n",
|
||||
" config={\"configurable\": metadata},\n",
|
||||
" stream_mode=\"updates\",\n",
|
||||
" ):\n",
|
||||
" if chunk.data and \"run_id\" not in chunk.data:\n",
|
||||
" print(chunk.data)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let’s now see what happens to our graph when we run it with a simple sentence:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 28,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'llm': {'messages': [{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_ioeUDw39bXQ2nap5f593xZMW', 'function': {'arguments': '{\"age\":26,\"name\":\"Bagatur\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-75a0d9db-df99-4b0c-b356-f24e76f5ca5a', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': 26, 'name': 'Bagatur'}, 'id': 'call_ioeUDw39bXQ2nap5f593xZMW'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n",
|
||||
"{'get_user_info': {'messages': [{'content': 'Hello! My name is Bagatur and I am 26 years old.', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'abafa365-1280-439d-b2ac-d4c547284ff9', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_E6NK18NXOHFkp8CeIuF7iI3K', 'function': {'arguments': '{\"age\":26,\"name\":\"Bagatur\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-c018c6a2-b93a-4787-aae3-46df0ef24c5b', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': 26, 'name': 'Bagatur'}, 'id': 'call_E6NK18NXOHFkp8CeIuF7iI3K'}], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Hello Bagatur! How can I assist you today?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-fb1ac0ca-b1f8-454b-ba3f-849c266dcc05', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Hello! What is my name?', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '8bf0637b-8214-4c85-85ff-873ec193a7b8', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_6EFvGDGWHuoOKm4p7dq3qazN', 'function': {'arguments': '{\"age\":-1,\"name\":\"John Doe\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-30a91dfa-4526-4bb7-b4a2-017e5c530ed2', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': -1, 'name': 'John Doe'}, 'id': 'call_6EFvGDGWHuoOKm4p7dq3qazN'}], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Your name is Bagatur. How can I assist you today?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-f3dcd3ad-b438-4011-9353-83978758ee81', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Hello! My name is Bagatur and I am 26 years old.', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '88e7bb12-552d-4607-8bc5-ce2b342f0604', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_ioeUDw39bXQ2nap5f593xZMW', 'function': {'arguments': '{\"age\":26,\"name\":\"Bagatur\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-75a0d9db-df99-4b0c-b356-f24e76f5ca5a', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': 26, 'name': 'Bagatur'}, 'id': 'call_ioeUDw39bXQ2nap5f593xZMW'}], 'invalid_tool_calls': [], 'usage_metadata': None}], 'user_info': {'age': 26, 'name': 'Bagatur'}}}\n",
|
||||
"{'respond_to_user': {'messages': [{'content': 'Hello Bagatur! How can I assist you today?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-49176b69-aae5-41ac-8c1e-7bab373b2b9b', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = {\n",
|
||||
" \"messages\": [\n",
|
||||
" {\"role\": \"user\", \"content\": \"Hello! My name is Bagatur and I am 26 years old.\"}\n",
|
||||
" ]\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"await run_input(client, thread, assistant, input)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can see our graph ran as expected, by calling our three nodes sequentially. Let’s now examine the state to take a further look under the hood. We can get the state by using the following command: "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"dict_keys(['values', 'next', 'config', 'metadata', 'created_at', 'parent_config'])"
|
||||
]
|
||||
},
|
||||
"execution_count": 17,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"state = await client.threads.get_state(thread_id=thread[\"thread_id\"])\n",
|
||||
"state.keys()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Our state variable contains a variety of important information. Here is a quick summary of the keys and what they represent:\n",
|
||||
"\n",
|
||||
"- `values` contains the actual state values, so in our case you could call `state['values']['messages']` or `state['values']['user_info']` and get the actual values of each of the state variables.\n",
|
||||
"- `next` tells us what action in the graph is next at the current state. Since we just finished running our graph and reached the end node, it is currently empty because there is no next action to take. However, if you go through the state at each point of the run you will see that the `next` value goes from `__start__` → `llm` → `get_user_info` →`respond_to_user` .\n",
|
||||
"- `metadata` stores the metadata associated with our state. This is data that is outside of the agent state, but is important to keep track of across multiple runs. An example of this is shown in the next section.\n",
|
||||
"- `config` tells us what the configuration of the state is. This is important for when we want to run a query starting at a previous state instead of the one we are at. An example of this is shown in the Invoking from a previous checkpoint section\n",
|
||||
"\n",
|
||||
"### Invoking with Metadata\n",
|
||||
"\n",
|
||||
"Let’s create a new thread to reset our state and start fresh."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 49,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"thread = await client.threads.create()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let’s add some metadata to our request. In this example we are going to treat each run of our assistant as a separate “node”. For each run, we will pass in a “node_id” as well as a “parent_node” in the metadata. This way we can easily go “back in time” and rerun our graph from a previous checkpoint. \n",
|
||||
"\n",
|
||||
"> NOTE: The reason we add this metadata instead of using the `parent_config` attribute is because `parent_config` tracks every individual step of a run, not the entire run itself.\n",
|
||||
">"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 50,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'llm': {'messages': [{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_VrA7UKg2w99BvIpsqrwjoawk', 'function': {'arguments': '{\"age\":26,\"name\":\"Bagatur\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-067609c1-4f1a-4d6d-bc1c-224a29153d37', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': 26, 'name': 'Bagatur'}, 'id': 'call_VrA7UKg2w99BvIpsqrwjoawk'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n",
|
||||
"{'get_user_info': {'messages': [{'content': 'Hello! My name is Bagatur and I am 26 years old.', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '05faf88c-9f85-462e-84ee-4667de35625d', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_VrA7UKg2w99BvIpsqrwjoawk', 'function': {'arguments': '{\"age\":26,\"name\":\"Bagatur\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-067609c1-4f1a-4d6d-bc1c-224a29153d37', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': 26, 'name': 'Bagatur'}, 'id': 'call_VrA7UKg2w99BvIpsqrwjoawk'}], 'invalid_tool_calls': [], 'usage_metadata': None}], 'user_info': {'age': 26, 'name': 'Bagatur'}}}\n",
|
||||
"{'respond_to_user': {'messages': [{'content': 'Hello Bagatur! How can I assist you today?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-48041247-fe04-419f-9f12-511e28c5f8aa', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = {\n",
|
||||
" \"messages\": [\n",
|
||||
" {\"role\": \"user\", \"content\": \"Hello! My name is Bagatur and I am 26 years old.\"}\n",
|
||||
" ]\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"metadata = {\"node_id\": 1, \"parent_node\": None}\n",
|
||||
"\n",
|
||||
"await run_input(client, thread, assistant, input, metadata)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Using our `run_input` function makes it easy to pass in metadata and you can inspect the function as well as the API docs to see exactly how metadata gets passed.\n",
|
||||
"\n",
|
||||
"We can continue our thread by creating a second node as follows:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 51,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'llm': {'messages': [{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_b2T2e0doVrh6uB1aHLIQglYR', 'function': {'arguments': '{\"age\":-1,\"name\":\"John Doe\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-808dca3a-0188-4c6b-9fc7-0037949fd820', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': -1, 'name': 'John Doe'}, 'id': 'call_b2T2e0doVrh6uB1aHLIQglYR'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n",
|
||||
"{'get_user_info': {'messages': [{'content': 'Hello! My name is Bagatur and I am 26 years old.', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '05faf88c-9f85-462e-84ee-4667de35625d', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_VrA7UKg2w99BvIpsqrwjoawk', 'function': {'arguments': '{\"age\":26,\"name\":\"Bagatur\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-067609c1-4f1a-4d6d-bc1c-224a29153d37', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': 26, 'name': 'Bagatur'}, 'id': 'call_VrA7UKg2w99BvIpsqrwjoawk'}], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Hello Bagatur! How can I assist you today?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-48041247-fe04-419f-9f12-511e28c5f8aa', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Hello! What is my name?', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '0970d6dd-e071-49bf-bf71-5345185b8291', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_b2T2e0doVrh6uB1aHLIQglYR', 'function': {'arguments': '{\"age\":-1,\"name\":\"John Doe\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-808dca3a-0188-4c6b-9fc7-0037949fd820', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': -1, 'name': 'John Doe'}, 'id': 'call_b2T2e0doVrh6uB1aHLIQglYR'}], 'invalid_tool_calls': [], 'usage_metadata': None}], 'user_info': {'age': -1, 'name': 'John Doe'}}}\n",
|
||||
"{'respond_to_user': {'messages': [{'content': 'Your name is Bagatur. How can I assist you today, Bagatur?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-9ada26f0-951e-4dd5-a2c5-4357bdeeac9d', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = {\"messages\": [{\"role\": \"user\", \"content\": \"Hello! What is my name?\"}]}\n",
|
||||
"metadata = {\"node_id\": 2, \"parent_node\": 1}\n",
|
||||
"\n",
|
||||
"await run_input(client, thread, assistant, input, metadata)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Perfect! The state persisted across separate runs, and the LLM remembers the name of our user. In a future we will explore non-sequential runs, i.e. not having each run just follow the last one but choosing which checkpoint we start our run from.\n",
|
||||
"\n",
|
||||
"## Querying and Updating the thread\n",
|
||||
"\n",
|
||||
"### Getting checkpoints by metadata\n",
|
||||
"\n",
|
||||
"Let’s say we want to start a new run from a previous state (not the current state). This state lives somewhere in our history, so we can utilize the `get_history` function to try and find it."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 52,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"history = await client.threads.get_history(thread_id=thread[\"thread_id\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This is helpful for inspecting the specifics of our current thread, but remember that the history contains all the intermediate steps a graph takes. In our case, where the graph has 5 nodes (remember that Start and End both count as nodes), our history array grows quickly. Luckily, there is a way to query by using metadata. For example if we wanted to start a run from Node 1(from the example from above) we need to find the state from the end of run with metadata node_id:1 , which we can do like so:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 53,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"node_1_history = await client.threads.get_history(\n",
|
||||
" thread_id=thread[\"thread_id\"], metadata={\"node_id\": 1}\n",
|
||||
")\n",
|
||||
"# At the end of the run there will be no 'next' for the graph to execute\n",
|
||||
"node_1_end_of_run = [h for h in node_1_history if h[\"next\"] == []][0]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let’s explore how we could use this information to create a new branch in our thread.\n",
|
||||
"\n",
|
||||
"### Invoking from a previous checkpoint\n",
|
||||
"\n",
|
||||
"The following diagram describes what we would like to happen:\n",
|
||||
"\n",
|
||||
"<div style=\"text-align:center\">\n",
|
||||
" <img src=\"./img/thread_diagram.png\" style=\"width:30%\">\n",
|
||||
"</div>\n",
|
||||
"Basically, we want to have 3 runs of our graph, but instead of having them sequentially - we want both the second and third run to originate from the same state. We can do this by utilizing the code we used above, and passing additional metadata to our run."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 54,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'llm': {'messages': [{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_yC7HUAQfcLzheepMD8SnAojR', 'function': {'arguments': '{\"age\":-1}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-b6a653e2-11ea-4f82-aa6a-ef321deed9ce', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': -1}, 'id': 'call_yC7HUAQfcLzheepMD8SnAojR'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n",
|
||||
"{'get_user_info': {'messages': [{'content': 'Hello! My name is Bagatur and I am 26 years old.', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '05faf88c-9f85-462e-84ee-4667de35625d', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_VrA7UKg2w99BvIpsqrwjoawk', 'function': {'arguments': '{\"age\":26,\"name\":\"Bagatur\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-067609c1-4f1a-4d6d-bc1c-224a29153d37', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': 26, 'name': 'Bagatur'}, 'id': 'call_VrA7UKg2w99BvIpsqrwjoawk'}], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Hello Bagatur! How can I assist you today?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-48041247-fe04-419f-9f12-511e28c5f8aa', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Hello! What is my age?', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'e94fa535-f821-4bfb-9d96-68c414adad8d', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_yC7HUAQfcLzheepMD8SnAojR', 'function': {'arguments': '{\"age\":-1}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-b6a653e2-11ea-4f82-aa6a-ef321deed9ce', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': -1}, 'id': 'call_yC7HUAQfcLzheepMD8SnAojR'}], 'invalid_tool_calls': [], 'usage_metadata': None}], 'user_info': {'age': -1}}}\n",
|
||||
"{'respond_to_user': {'messages': [{'content': 'You are 26 years old, Bagatur! How can I assist you today?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-e3b6e627-b4b1-4249-a1bc-a6359ad7b961', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = {\"messages\": [{\"role\": \"user\", \"content\": \"Hello! What is my age?\"}]}\n",
|
||||
"metadata = {\n",
|
||||
" **{\"node_id\": 3, \"parent_node\": 1},\n",
|
||||
" **{\n",
|
||||
" \"thread_ts\": node_1_end_of_run[\"checkpoint_id\"],\n",
|
||||
" \"thread_id\": thread[\"thread_id\"],\n",
|
||||
" },\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"await run_input(client, thread, assistant, input, metadata)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"To check that everything actually worked as planned, let’s check our current state and check that message history to ensure that the message we passed to Node 2 is nowhere to be found."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 55,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"False"
|
||||
]
|
||||
},
|
||||
"execution_count": 55,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"\"Hello! What is my name?\" in [\n",
|
||||
" message[\"content\"] for message in state[\"values\"][\"messages\"]\n",
|
||||
"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Great! This has worked as expected. Being able to go back to previous states and execute new graph runs from those checkpoints is a great way to develop flexible applications that don’t require reloading or restarting everything when an error is detected or a user changes their mind.\n",
|
||||
"\n",
|
||||
"### Updating/Patching the thread state\n",
|
||||
"\n",
|
||||
"Lastly, let’s discuss the ability to manually change both the thread state as well as the metadata for a given state. Let’s say we incorrectly inputted data to the LLM and we want to rectify it. \n",
|
||||
"\n",
|
||||
"Continuing our previous example, let’s say the user mistyped their age and we want to let the graph know that without actually running it. In this case we can rectify this by using `update_state`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 56,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"new_state = await client.threads.update_state(\n",
|
||||
" thread_id=thread[\"thread_id\"], values={\"user_info\": {\"name\": \"Bagatur\", \"age\": 35}}\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let’s make sure that the state did in fact update and ask our LLM again how old we are by invoking the graph again:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 57,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'llm': {'messages': [{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_Ph3tYdt2UNdwqAF3kVL198Bg', 'function': {'arguments': '{\"age\":-1}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-373f3e22-8549-4f77-9ff2-05133099bb09', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': -1}, 'id': 'call_Ph3tYdt2UNdwqAF3kVL198Bg'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n",
|
||||
"{'get_user_info': {'messages': [{'content': 'Hello! My name is Bagatur and I am 26 years old.', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '05faf88c-9f85-462e-84ee-4667de35625d', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_VrA7UKg2w99BvIpsqrwjoawk', 'function': {'arguments': '{\"age\":26,\"name\":\"Bagatur\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-067609c1-4f1a-4d6d-bc1c-224a29153d37', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': 26, 'name': 'Bagatur'}, 'id': 'call_VrA7UKg2w99BvIpsqrwjoawk'}], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Hello Bagatur! How can I assist you today?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-48041247-fe04-419f-9f12-511e28c5f8aa', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Hello! What is my age?', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'e94fa535-f821-4bfb-9d96-68c414adad8d', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_yC7HUAQfcLzheepMD8SnAojR', 'function': {'arguments': '{\"age\":-1}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-b6a653e2-11ea-4f82-aa6a-ef321deed9ce', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': -1}, 'id': 'call_yC7HUAQfcLzheepMD8SnAojR'}], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'You are 26 years old, Bagatur! How can I assist you today?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-e3b6e627-b4b1-4249-a1bc-a6359ad7b961', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Hello! What is my age?', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '24ac02be-7b08-4e91-9d20-90507ed25391', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_Ph3tYdt2UNdwqAF3kVL198Bg', 'function': {'arguments': '{\"age\":-1}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-373f3e22-8549-4f77-9ff2-05133099bb09', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': -1}, 'id': 'call_Ph3tYdt2UNdwqAF3kVL198Bg'}], 'invalid_tool_calls': [], 'usage_metadata': None}], 'user_info': {'age': -1}}}\n",
|
||||
"{'respond_to_user': {'messages': [{'content': 'My apologies for the confusion earlier. You are 35 years old, Bagatur! How can I assist you today?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-298306df-34a5-459a-a8c3-a0f16440c429', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"input = {\"messages\": [{\"role\": \"user\", \"content\": \"Hello! What is my age?\"}]}\n",
|
||||
"\n",
|
||||
"await run_input(client, thread, assistant, input)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Voila! The LLM knows our users age updated without us having to prompt it at all.\n",
|
||||
"\n",
|
||||
"The last thing we will talk about is patching the thread, which is used when we want to update the metadata of a state. For example, say we actually wanted to update our last state to have `node_id:4` instead of `node_id:3`. To do this, we can call:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 58,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'configurable': {'thread_id': '4f044e5a-6f6e-4663-923e-6333c052ce9f',\n",
|
||||
" 'thread_ts': '1ef2e5d6-e39f-6d26-800e-22245f8220a7'}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 58,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"await client.threads.patch_state(thread_id=thread[\"thread_id\"], metadata={\"node_id\": 4})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can check that this worked by checking the metadata of our state"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 59,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Current node id is 4\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"state = await client.threads.get_state(thread_id=thread[\"thread_id\"])\n",
|
||||
"print(f\"Current node id is {state['metadata']['node_id']}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Perfect! The patch worked as expected."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3631f2b9-aa79-472e-a9d6-9125a90ee704",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to configure multiple streaming modes at the same time"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "858c7499-0c92-40a9-bd95-e5a5a5817e92",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This guide covers how to configure multiple streaming modes at the same time."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7c2f84f1-0751-4779-97d4-5cbb286093b7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "323db423-b644-40bd-9c2d-976a53f602f7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We'll be using a simple ReAct agent for this guide."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "6b4285e4-7434-4971-bde0-aabceef8ee7e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U langgraph langchain-openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "f7f9f24a-e3d0-422b-8924-47950b2facd6",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdin",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"OPENAI_API_KEY: ········\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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": "code",
|
||||
"execution_count": 3,
|
||||
"id": "85cf2e23-29f2-40cc-b302-5377b3b49da9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Literal\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"from langchain_core.runnables import ConfigurableField\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def get_weather(city: Literal[\"nyc\", \"sf\"]):\n",
|
||||
" \"\"\"Use this to get weather information.\"\"\"\n",
|
||||
" if city == \"nyc\":\n",
|
||||
" return \"It might be cloudy in nyc\"\n",
|
||||
" elif city == \"sf\":\n",
|
||||
" return \"It's always sunny in sf\"\n",
|
||||
" else:\n",
|
||||
" raise AssertionError(\"Unknown city\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [get_weather]\n",
|
||||
"\n",
|
||||
"model = ChatOpenAI(model_name=\"gpt-4o\", temperature=0)\n",
|
||||
"graph = create_react_agent(model, tools)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "48a7751c-3f06-452b-89f4-70267e4dd305",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Stream multiple"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "e9e9ffb0-2cd5-466f-b70b-b6ed51b852d1",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'task', 'timestamp': '2024-06-25T16:12:29.144117+00:00', 'step': 1, 'payload': {'id': '8399d8fd-4b28-515a-b0e9-1679557c0953', 'name': 'agent', 'input': {'messages': [HumanMessage(content=\"what's the weather in sf\", id='44ff9154-9485-49c9-b679-791314cc19e3')], 'is_last_step': False}, 'triggers': ['start:agent']}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: updates...\n",
|
||||
"{'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_gZEyPpcgwnzsnee1HH4geKmB', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_3e7d703517', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-37ca191f-f68f-4a70-8924-a40f90c8c0ed-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_gZEyPpcgwnzsnee1HH4geKmB'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71})]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'task_result', 'timestamp': '2024-06-25T16:12:29.802322+00:00', 'step': 1, 'payload': {'id': '8399d8fd-4b28-515a-b0e9-1679557c0953', 'name': 'agent', 'result': [('messages', [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_gZEyPpcgwnzsnee1HH4geKmB', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_3e7d703517', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-37ca191f-f68f-4a70-8924-a40f90c8c0ed-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_gZEyPpcgwnzsnee1HH4geKmB'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71})])]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'task', 'timestamp': '2024-06-25T16:12:29.802738+00:00', 'step': 2, 'payload': {'id': 'f22971bf-6eff-55a2-84ab-fb97f629b133', 'name': 'tools', 'input': {'messages': [HumanMessage(content=\"what's the weather in sf\", id='44ff9154-9485-49c9-b679-791314cc19e3'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_gZEyPpcgwnzsnee1HH4geKmB', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_3e7d703517', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-37ca191f-f68f-4a70-8924-a40f90c8c0ed-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_gZEyPpcgwnzsnee1HH4geKmB'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71})], 'is_last_step': False}, 'triggers': ['branch:agent:should_continue:tools']}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: updates...\n",
|
||||
"{'tools': {'messages': [ToolMessage(content=\"It's always sunny in sf\", name='get_weather', tool_call_id='call_gZEyPpcgwnzsnee1HH4geKmB')]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'task_result', 'timestamp': '2024-06-25T16:12:29.806676+00:00', 'step': 2, 'payload': {'id': 'f22971bf-6eff-55a2-84ab-fb97f629b133', 'name': 'tools', 'result': [('messages', [ToolMessage(content=\"It's always sunny in sf\", name='get_weather', tool_call_id='call_gZEyPpcgwnzsnee1HH4geKmB')])]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'task', 'timestamp': '2024-06-25T16:12:29.807014+00:00', 'step': 3, 'payload': {'id': '3e1a91b9-b94c-56a7-ace5-6fd8ee73fe8d', 'name': 'agent', 'input': {'messages': [HumanMessage(content=\"what's the weather in sf\", id='44ff9154-9485-49c9-b679-791314cc19e3'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_gZEyPpcgwnzsnee1HH4geKmB', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_3e7d703517', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-37ca191f-f68f-4a70-8924-a40f90c8c0ed-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_gZEyPpcgwnzsnee1HH4geKmB'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71}), ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='afc3ceaa-6663-4f7a-b874-e77e5515b175', tool_call_id='call_gZEyPpcgwnzsnee1HH4geKmB')], 'is_last_step': False}, 'triggers': ['tools']}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: updates...\n",
|
||||
"{'agent': {'messages': [AIMessage(content='The weather in San Francisco is currently sunny.', response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_3e7d703517', 'finish_reason': 'stop', 'logprobs': None}, id='run-575efeca-fdeb-4b4f-80f8-08ff177c34a5-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94})]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving new event of type: debug...\n",
|
||||
"{'type': 'task_result', 'timestamp': '2024-06-25T16:12:30.355658+00:00', 'step': 3, 'payload': {'id': '3e1a91b9-b94c-56a7-ace5-6fd8ee73fe8d', 'name': 'agent', 'result': [('messages', [AIMessage(content='The weather in San Francisco is currently sunny.', response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_3e7d703517', 'finish_reason': 'stop', 'logprobs': None}, id='run-575efeca-fdeb-4b4f-80f8-08ff177c34a5-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94})])]}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [(\"human\", \"what's the weather in sf\")]}\n",
|
||||
"async for event, chunk in graph.astream(inputs, stream_mode=[\"updates\", \"debug\"]):\n",
|
||||
" print(f\"Receiving new event of type: {event}...\")\n",
|
||||
" print(chunk)\n",
|
||||
" print(\"\\n\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8cc57240-243c-4d9a-a845-cb55ef973a59",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "langgraph",
|
||||
"language": "python",
|
||||
"name": "langgraph"
|
||||
},
|
||||
"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,185 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3631f2b9-aa79-472e-a9d6-9125a90ee704",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to stream state updates of your graph"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "858c7499-0c92-40a9-bd95-e5a5a5817e92",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"LangGraph supports multiple streaming modes. The main ones are:\n",
|
||||
"\n",
|
||||
"- `values`: This streaming mode streams back values of the graph. This is the **full state of the graph** after each node is called.\n",
|
||||
"- `updates`: This streaming mode streams back updates to the graph. This is the **update to the state of the graph** after each node is called.\n",
|
||||
"\n",
|
||||
"This guide covers `stream_mode=\"updates\"`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7c2f84f1-0751-4779-97d4-5cbb286093b7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "323db423-b644-40bd-9c2d-976a53f602f7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We'll be using a simple ReAct agent for this guide."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "6b4285e4-7434-4971-bde0-aabceef8ee7e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U langgraph langchain-openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "f7f9f24a-e3d0-422b-8924-47950b2facd6",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdin",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"OPENAI_API_KEY: ········\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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": "code",
|
||||
"execution_count": 3,
|
||||
"id": "85cf2e23-29f2-40cc-b302-5377b3b49da9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Literal\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"from langchain_core.runnables import ConfigurableField\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def get_weather(city: Literal[\"nyc\", \"sf\"]):\n",
|
||||
" \"\"\"Use this to get weather information.\"\"\"\n",
|
||||
" if city == \"nyc\":\n",
|
||||
" return \"It might be cloudy in nyc\"\n",
|
||||
" elif city == \"sf\":\n",
|
||||
" return \"It's always sunny in sf\"\n",
|
||||
" else:\n",
|
||||
" raise AssertionError(\"Unknown city\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [get_weather]\n",
|
||||
"\n",
|
||||
"model = ChatOpenAI(model_name=\"gpt-4o\", temperature=0)\n",
|
||||
"graph = create_react_agent(model, tools)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "956db549-5207-4be1-a823-78311738e3f8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Stream updates"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "e9e9ffb0-2cd5-466f-b70b-b6ed51b852d1",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Receiving update from node: 'agent'\n",
|
||||
"{'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_kc6cvcEkTAUGRlSHrP4PK9fn', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_3e7d703517', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-cd68b3a0-86c3-4afa-9649-1b962a0dd062-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_kc6cvcEkTAUGRlSHrP4PK9fn'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71})]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving update from node: 'tools'\n",
|
||||
"{'messages': [ToolMessage(content=\"It's always sunny in sf\", name='get_weather', tool_call_id='call_kc6cvcEkTAUGRlSHrP4PK9fn')]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Receiving update from node: 'agent'\n",
|
||||
"{'messages': [AIMessage(content='The weather in San Francisco is currently sunny.', response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_3e7d703517', 'finish_reason': 'stop', 'logprobs': None}, id='run-009d83c4-b874-4acc-9494-20aba43132b9-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94})]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [(\"human\", \"what's the weather in sf\")]}\n",
|
||||
"async for chunk in graph.astream(inputs, stream_mode=\"updates\"):\n",
|
||||
" for node, values in chunk.items():\n",
|
||||
" print(f\"Receiving update from node: '{node}'\")\n",
|
||||
" print(values)\n",
|
||||
" print(\"\\n\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8cc57240-243c-4d9a-a845-cb55ef973a59",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "langgraph",
|
||||
"language": "python",
|
||||
"name": "langgraph"
|
||||
},
|
||||
"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,239 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3631f2b9-aa79-472e-a9d6-9125a90ee704",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to stream full state of your graph"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "858c7499-0c92-40a9-bd95-e5a5a5817e92",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"LangGraph supports multiple streaming modes. The main ones are:\n",
|
||||
"\n",
|
||||
"- `values`: This streaming mode streams back values of the graph. This is the **full state of the graph** after each node is called.\n",
|
||||
"- `updates`: This streaming mode streams back updates to the graph. This is the **update to the state of the graph** after each node is called.\n",
|
||||
"\n",
|
||||
"This guide covers `stream_mode=\"values\"`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7c2f84f1-0751-4779-97d4-5cbb286093b7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "323db423-b644-40bd-9c2d-976a53f602f7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We'll be using a simple ReAct agent for this guide."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "6b4285e4-7434-4971-bde0-aabceef8ee7e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U langgraph langchain-openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "f7f9f24a-e3d0-422b-8924-47950b2facd6",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdin",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"OPENAI_API_KEY: ········\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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": "code",
|
||||
"execution_count": 3,
|
||||
"id": "ef5a3ec6-0cd0-4541-ab1b-d63ede22720e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Literal\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"from langchain_core.runnables import ConfigurableField\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def get_weather(city: Literal[\"nyc\", \"sf\"]):\n",
|
||||
" \"\"\"Use this to get weather information.\"\"\"\n",
|
||||
" if city == \"nyc\":\n",
|
||||
" return \"It might be cloudy in nyc\"\n",
|
||||
" elif city == \"sf\":\n",
|
||||
" return \"It's always sunny in sf\"\n",
|
||||
" else:\n",
|
||||
" raise AssertionError(\"Unknown city\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [get_weather]\n",
|
||||
"\n",
|
||||
"model = ChatOpenAI(model_name=\"gpt-4o\", temperature=0)\n",
|
||||
"graph = create_react_agent(model, tools)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "002a715b-e0be-4e89-8d42-f0098882586b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Stream values"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "e9e9ffb0-2cd5-466f-b70b-b6ed51b852d1",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||||
"\n",
|
||||
"what's the weather in sf\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"Tool Calls:\n",
|
||||
" get_weather (call_61VvIzqVGtyxcXi0z6knZkjZ)\n",
|
||||
" Call ID: call_61VvIzqVGtyxcXi0z6knZkjZ\n",
|
||||
" Args:\n",
|
||||
" city: sf\n",
|
||||
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
||||
"Name: get_weather\n",
|
||||
"\n",
|
||||
"It's always sunny in sf\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"The weather in San Francisco is currently sunny.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [(\"human\", \"what's the weather in sf\")]}\n",
|
||||
"async for chunk in graph.astream(inputs, stream_mode=\"values\"):\n",
|
||||
" chunk[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d73de237-bf45-4fa7-93ef-6dae7eacffc0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"If we want to just get the final result, we can use the same method and just keep track of the last value we received"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "c122bf15-a489-47bf-b482-a744a54e2cc4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [(\"human\", \"what's the weather in sf\")]}\n",
|
||||
"async for chunk in graph.astream(inputs, stream_mode=\"values\"):\n",
|
||||
" final_result = chunk"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "316022e5-4c65-48e4-9878-8d94a2425ed4",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'messages': [HumanMessage(content=\"what's the weather in sf\", id='54b39b6f-054b-4306-980b-86905e48a6bc'),\n",
|
||||
" AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_avoKnK8reERzTUSxrN9cgFxY', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_5e6c71d4a8', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-f2f43c89-2c96-45f4-975c-2d0f22d0d2d1-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_avoKnK8reERzTUSxrN9cgFxY'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71}),\n",
|
||||
" ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='fc18a798-c7b2-4f73-84fa-8ffdffb6ddcb', tool_call_id='call_avoKnK8reERzTUSxrN9cgFxY'),\n",
|
||||
" AIMessage(content='The weather in San Francisco is currently sunny. Enjoy the sunshine!', response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 84, 'total_tokens': 98}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_5e6c71d4a8', 'finish_reason': 'stop', 'logprobs': None}, id='run-21418147-da8e-4738-a076-239377397c40-0', usage_metadata={'input_tokens': 84, 'output_tokens': 14, 'total_tokens': 98})]}"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"final_result"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "0f64ebbe-535c-4b35-a95f-0a7490cfed90",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"The weather in San Francisco is currently sunny. Enjoy the sunshine!\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"final_result[\"messages\"][-1].pretty_print()"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "langgraph",
|
||||
"language": "python",
|
||||
"name": "langgraph"
|
||||
},
|
||||
"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
|
||||
}
|
||||