Compare commits

..
Author SHA1 Message Date
Arjun Natarajan f393d7ac8b consolidate installing deps 2025-05-14 10:15:11 -04:00
110 changed files with 23771 additions and 19803 deletions
+88
View File
@@ -0,0 +1,88 @@
# An action for setting up poetry install with caching.
# Using a custom action since the default action does not
# take poetry install groups into account.
# Action code from:
# https://github.com/actions/setup-python/issues/505#issuecomment-1273013236
name: poetry-install-with-caching
description: Poetry install with support for caching of dependency groups.
inputs:
python-version:
description: Python version, supporting MAJOR.MINOR only
required: true
poetry-version:
description: Poetry version
required: true
cache-key:
description: Cache key to use for manual handling of caching
required: true
runs:
using: composite
steps:
- uses: actions/setup-python@v5
name: Setup python ${{ inputs.python-version }}
id: setup-python
with:
python-version: ${{ inputs.python-version }}
- uses: actions/cache@v3
id: cache-bin-poetry
name: Cache Poetry binary - Python ${{ inputs.python-version }}
env:
SEGMENT_DOWNLOAD_TIMEOUT_MIN: "1"
with:
path: |
/opt/pipx/venvs/poetry
# This step caches the poetry installation, so make sure it's keyed on the poetry version as well.
key: bin-poetry-${{ runner.os }}-${{ runner.arch }}-py-${{ inputs.python-version }}-${{ inputs.poetry-version }}
- name: Refresh shell hashtable and fixup softlinks
if: steps.cache-bin-poetry.outputs.cache-hit == 'true'
shell: bash
env:
POETRY_VERSION: ${{ inputs.poetry-version }}
PYTHON_VERSION: ${{ inputs.python-version }}
run: |
set -eux
# Refresh the shell hashtable, to ensure correct `which` output.
hash -r
# `actions/cache@v3` doesn't always seem able to correctly unpack softlinks.
# Delete and recreate the softlinks pipx expects to have.
rm /opt/pipx/venvs/poetry/bin/python
cd /opt/pipx/venvs/poetry/bin
ln -s "$(which "python$PYTHON_VERSION")" python
chmod +x python
cd /opt/pipx_bin/
ln -s /opt/pipx/venvs/poetry/bin/poetry poetry
chmod +x poetry
# Ensure everything got set up correctly.
/opt/pipx/venvs/poetry/bin/python --version
/opt/pipx_bin/poetry --version
- name: Install poetry
if: steps.cache-bin-poetry.outputs.cache-hit != 'true'
shell: bash
env:
POETRY_VERSION: ${{ inputs.poetry-version }}
PYTHON_VERSION: ${{ inputs.python-version }}
# Install poetry using the python version installed by setup-python step.
run: pipx install "poetry==$POETRY_VERSION" --python '${{ steps.setup-python.outputs.python-path }}' --verbose
- name: Restore pip and poetry cached dependencies
uses: actions/cache@v3
env:
SEGMENT_DOWNLOAD_TIMEOUT_MIN: "4"
with:
path: |
~/.cache/pip
~/.cache/pypoetry/virtualenvs
~/.cache/pypoetry/cache
~/.cache/pypoetry/artifacts
./.venv
key: py-deps-${{ runner.os }}-${{ runner.arch }}-py-${{ inputs.python-version }}-poetry-${{ inputs.poetry-version }}-${{ inputs.cache-key }}-${{ hashFiles('./poetry.lock') }}
+7 -2
View File
@@ -3,6 +3,9 @@ name: CLI integration test
on:
workflow_call:
env:
POETRY_VERSION: "2.1.2"
jobs:
build:
runs-on: ubuntu-latest
@@ -22,11 +25,13 @@ jobs:
uses: Ana06/get-changed-files@v2.3.0
with:
filter: "libs/cli/**"
- name: Set up Python ${{ matrix.python-version }}
- name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }}
if: steps.changed-files.outputs.all
uses: astral-sh/setup-uv@v6
uses: "./.github/actions/poetry_setup"
with:
python-version: ${{ matrix.python-version }}
poetry-version: ${{ env.POETRY_VERSION }}
cache-key: integration-test-cli
- name: Setup env
if: steps.changed-files.outputs.all
working-directory: libs/cli/examples
+33 -8
View File
@@ -9,6 +9,8 @@ on:
description: "From which folder this pipeline executes"
env:
POETRY_VERSION: "2.1.2"
# This env var allows us to get inline annotations when ruff has complaints.
RUFF_OUTPUT_FORMAT: github
@@ -34,18 +36,32 @@ jobs:
uses: Ana06/get-changed-files@v2.3.0
with:
filter: "${{ inputs.working-directory }}/**"
- name: Set up Python ${{ matrix.python-version }}
- name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }}
if: steps.changed-files.outputs.all
uses: astral-sh/setup-uv@v6
uses: "./.github/actions/poetry_setup"
with:
python-version: ${{ matrix.python-version }}
enable-cache: true
cache-suffix: lint-${{ inputs.working-directory }}
poetry-version: ${{ env.POETRY_VERSION }}
cache-key: lint-${{ inputs.working-directory }}
- name: Check Poetry File
if: steps.changed-files.outputs.all
shell: bash
working-directory: ${{ inputs.working-directory }}
run: poetry 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:
# https://github.com/langchain-ai/langchain/pull/10249/files#diff-935185cd488d015f026dcd9e19616ff62863e8cde8c0bee70318d3ccbca98341
#
# If you change this configuration, make sure to change the `cache-key`
# in the `poetry_setup` action above to stop using the old cache.
# It doesn't matter how you change it, any change will cause a cache-bust.
working-directory: ${{ inputs.working-directory }}
run: uv sync --frozen --group dev
run: poetry install --with dev
- name: Get .mypy_cache to speed up mypy
if: steps.changed-files.outputs.all
@@ -55,7 +71,7 @@ jobs:
with:
path: |
${{ inputs.working-directory }}/.mypy_cache
key: mypy-lint-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-${{ inputs.working-directory }}-${{ hashFiles(format('{0}/uv.lock', inputs.working-directory)) }}
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
@@ -70,8 +86,17 @@ jobs:
- 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:
# https://github.com/langchain-ai/langchain/pull/10249/files#diff-935185cd488d015f026dcd9e19616ff62863e8cde8c0bee70318d3ccbca98341
#
# If you change this configuration, make sure to change the `cache-key`
# in the `poetry_setup` action above to stop using the old cache.
# It doesn't matter how you change it, any change will cause a cache-bust.
working-directory: ${{ inputs.working-directory }}
run: uv sync --group dev
run: |
poetry install --with dev
- name: Get .mypy_cache_test to speed up mypy
if: steps.changed-files.outputs.all
@@ -81,7 +106,7 @@ jobs:
with:
path: |
${{ inputs.working-directory }}/.mypy_cache_test
key: mypy-test-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-${{ inputs.working-directory }}-${{ hashFiles(format('{0}/uv.lock', inputs.working-directory)) }}
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
+11 -15
View File
@@ -8,6 +8,9 @@ on:
type: string
description: "From which folder this pipeline executes"
env:
POETRY_VERSION: "2.1.2"
jobs:
build:
runs-on: ubuntu-latest
@@ -23,12 +26,12 @@ jobs:
name: "test #${{ matrix.python-version }}"
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: astral-sh/setup-uv@v6
- name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }}
uses: "./.github/actions/poetry_setup"
with:
python-version: ${{ matrix.python-version }}
enable-cache: true
cache-siffix: test-${{ inputs.working-directory }}
poetry-version: ${{ env.POETRY_VERSION }}
cache-key: test-${{ inputs.working-directory }}
- name: Login to Docker Hub
uses: docker/login-action@v3
if: ${{ !github.event.pull_request.head.repo.fork }}
@@ -39,21 +42,14 @@ jobs:
- name: Install dependencies
shell: bash
working-directory: ${{ inputs.working-directory }}
run: uv sync --frozen --group dev
run: |
poetry install --with dev
- name: Run tests
shell: bash
working-directory: ${{ inputs.working-directory }}
run: make test
- name: Install min version of deps
shell: bash
run: uv sync --frozen --all-extras --resolution lowest-direct --force-reinstall
- name: Run tests with min version of deps
shell: bash
run: make test
working-directory: ${{ inputs.working-directory }}
run: |
make test
- name: Ensure the tests did not create any additional files
shell: bash
+11 -15
View File
@@ -3,6 +3,9 @@ name: test
on:
workflow_call:
env:
POETRY_VERSION: "2.1.2"
jobs:
build:
runs-on: ubuntu-latest
@@ -21,12 +24,12 @@ jobs:
name: "test #${{ matrix.python-version }}"
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: astral-sh/setup-uv@v6
- name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }}
uses: "./.github/actions/poetry_setup"
with:
python-version: ${{ matrix.python-version }}
enable-cache: true
cache-suffix: "test-langgraph"
poetry-version: ${{ env.POETRY_VERSION }}
cache-key: test-langgraph
- name: Login to Docker Hub
uses: docker/login-action@v3
if: ${{ !github.event.pull_request.head.repo.fork }}
@@ -36,20 +39,13 @@ jobs:
- name: Install dependencies
shell: bash
run: uv sync --frozen --group dev
run: |
poetry install --with dev
- name: Run tests
shell: bash
run: make test_parallel
- name: Install min version of deps
shell: bash
run: uv sync --frozen --all-extras --resolution lowest-direct --force-reinstall
- name: Run tests with min version of deps
shell: bash
run: make test
working-directory: ${{ inputs.working-directory }}
run: |
make test_parallel
- name: Ensure the tests did not create any additional files
shell: bash
+8 -7
View File
@@ -9,6 +9,7 @@ on:
description: "From which folder this pipeline executes"
env:
POETRY_VERSION: "2.1.2"
PYTHON_VERSION: "3.10"
jobs:
@@ -23,12 +24,12 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python $${ env.PYTHON_VERSION }}
uses: astral-sh/setup-uv@v6
- name: Set up Python + Poetry ${{ env.POETRY_VERSION }}
uses: "./.github/actions/poetry_setup"
with:
python-version: ${{ env.PYTHON_VERSION }}
enable-cache: true
cache-suffix: "release"
poetry-version: ${{ env.POETRY_VERSION }}
cache-key: release
# We want to keep this build stage *separate* from the release stage,
# so that there's no sharing of permissions between them.
@@ -42,7 +43,7 @@ jobs:
# > from the publish job.
# https://github.com/pypa/gh-action-pypi-publish#non-goals
- name: Build project for distribution
run: uv build
run: poetry build
working-directory: ${{ inputs.working-directory }}
- name: Upload build
@@ -56,8 +57,8 @@ jobs:
shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
echo pkg-name=$(grep -m 1 "^name = " pyproject.toml | cut -d '"' -f 2)
echo version=$(grep -m 1 "^version = " pyproject.toml | cut -d '"' -f 2)
echo pkg-name="$(poetry version | cut -d ' ' -f 1)" >> $GITHUB_OUTPUT
echo version="$(poetry version --short)" >> $GITHUB_OUTPUT
publish:
needs:
+11 -15
View File
@@ -3,6 +3,9 @@ name: test
on:
workflow_call:
env:
POETRY_VERSION: "2.1.2"
jobs:
build:
runs-on: ubuntu-latest
@@ -18,12 +21,12 @@ jobs:
name: "test #${{ matrix.python-version }}"
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: astral-sh/setup-uv@v6
- name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }}
uses: "./.github/actions/poetry_setup"
with:
python-version: ${{ matrix.python-version }}
enable-cache: true
cache-suffix: "test-scheduler-kafka"
poetry-version: ${{ env.POETRY_VERSION }}
cache-key: test-scheduler-kafka
- name: Login to Docker Hub
uses: docker/login-action@v3
if: ${{ !github.event.pull_request.head.repo.fork }}
@@ -33,20 +36,13 @@ jobs:
- name: Install dependencies
shell: bash
run: uv sync --frozen --group dev
run: |
poetry install --with dev
- name: Run tests
shell: bash
run: make test
- name: Install min version of deps
shell: bash
run: uv sync --frozen --all-extras --resolution lowest-direct --force-reinstall
- name: Run tests with min version of deps
shell: bash
run: make test
working-directory: ${{ inputs.working-directory }}
run: |
make test
- name: Ensure the tests did not create any additional files
shell: bash
+8 -5
View File
@@ -7,6 +7,9 @@ on:
paths:
- "libs/**"
env:
POETRY_VERSION: "2.1.2"
jobs:
benchmark:
runs-on: ubuntu-latest
@@ -16,14 +19,14 @@ jobs:
steps:
- uses: actions/checkout@v4
- run: SHA=$(git rev-parse HEAD) && echo "SHA=$SHA" >> $GITHUB_ENV
- name: Set up Python 3.11
uses: astral-sh/setup-uv@v6
- name: Set up Python 3.11 + Poetry ${{ env.POETRY_VERSION }}
uses: "./.github/actions/poetry_setup"
with:
python-version: "3.11"
enable-cache: true
cache-suffix: "bench"
poetry-version: ${{ env.POETRY_VERSION }}
cache-key: bench
- name: Install dependencies
run: uv sync --group dev
run: poetry install --with dev
- name: Run benchmarks
run: OUTPUT=out/benchmark-baseline.json make -s benchmark
- name: Save outputs
+9 -6
View File
@@ -5,6 +5,9 @@ on:
paths:
- "libs/**"
env:
POETRY_VERSION: "2.1.2"
jobs:
benchmark:
runs-on: ubuntu-latest
@@ -18,14 +21,14 @@ jobs:
uses: Ana06/get-changed-files@v2.3.0
with:
format: json
- name: Set up Python 3.11
uses: astral-sh/setup-uv@v6
- name: Set up Python 3.11 + Poetry ${{ env.POETRY_VERSION }}
uses: "./.github/actions/poetry_setup"
with:
python-version: "3.11"
enable-cache: true
cache-suffix: "bench"
poetry-version: ${{ env.POETRY_VERSION }}
cache-key: bench
- name: Install dependencies
run: uv sync --group dev
run: poetry install --with dev
- name: Download baseline
uses: actions/cache/restore@v4
with:
@@ -50,7 +53,7 @@ jobs:
echo 'OUTPUT<<EOF'
mv out/benchmark-baseline.json out/main.json
mv out/benchmark.json out/changes.json
uv run pyperf compare_to out/main.json out/changes.json --table --group-by-speed
poetry run pyperf compare_to out/main.json out/changes.json --table --group-by-speed
echo EOF
} >> "$GITHUB_OUTPUT"
- name: Annotation
+10 -7
View File
@@ -16,6 +16,9 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
POETRY_VERSION: "2.1.2"
jobs:
changes:
runs-on: ubuntu-latest
@@ -122,26 +125,26 @@ jobs:
- "3.11"
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: astral-sh/setup-uv@v6
- name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }}
uses: "./.github/actions/poetry_setup"
with:
python-version: "3.11"
enable-cache: true
cache-suffix: "schema-check-cli"
poetry-version: ${{ env.POETRY_VERSION }}
cache-key: schema-check-cli
- name: Install CLI dependencies
run: |
cd libs/cli
uv sync
poetry install
- name: Generate schema and check for changes
run: |
cd libs/cli
# Create a temporary copy of the current schema
cp schemas/schema.json schemas/schema.current.json
# Generate new schema
uv run python generate_schema.py
poetry run python generate_schema.py
# Compare the new schema with the original
if ! diff -q schemas/schema.json schemas/schema.current.json > /dev/null; then
echo "Error: Langgraph.json configuration schema has changed. Please run 'uv run python generate_schema.py' in the libs/cli directory and commit the changes."
echo "Error: Langgraph.json configuration schema has changed. Please run 'poetry run python generate_schema.py' in the libs/cli directory and commit the changes."
diff schemas/schema.json schemas/schema.current.json
exit 1
fi
+11 -8
View File
@@ -9,6 +9,9 @@ on:
- main
workflow_dispatch:
env:
POETRY_VERSION: "2.1.2"
permissions:
contents: read
pages: write
@@ -54,21 +57,21 @@ jobs:
with:
fetch-depth: 0
- name: Set up Python
uses: astral-sh/setup-uv@v6
- name: Set up Python + Poetry ${{ env.POETRY_VERSION }}
uses: "./.github/actions/poetry_setup"
with:
python-version: "3.12"
enable-cache: true
cache-suffix: "docs"
poetry-version: ${{ env.POETRY_VERSION }}
cache-key: docs
- name: Install dependencies
run: |
yarn
uv sync --all-groups
poetry install --with test --with docs --no-root
# we run this installation only for internal PRs
# as GITHUB_TOKEN is not available for PRs from outside contributors
if [ -n "${GITHUB_TOKEN}" ]; then
uv run pip install "git+https://${GITHUB_TOKEN}@github.com/langchain-ai/mkdocs-material-insiders.git"
poetry run pip install "git+https://${GITHUB_TOKEN}@github.com/langchain-ai/mkdocs-material-insiders.git"
fi
- name: Run unit tests
@@ -100,7 +103,7 @@ jobs:
run: |
if [ "${{ github.event_name }}" == "schedule" ]; then
echo "Running link check on all HTML files matching notebooks in docs directory..."
uv run pytest -v \
poetry run pytest -v \
--check-links-ignore "https://(api|web|docs)\.smith\.langchain\.com/.*" \
--check-links-ignore "https://academy\.langchain\.com/.*" \
--check-links-ignore "https://x.com/.*" \
@@ -125,7 +128,7 @@ jobs:
echo "Changed files: ${CHANGED_FILES}"
if [ -n "${CHANGED_FILES}" ]; then
echo "Running link check on HTML files matching changed notebook files..."
uv run pytest -v \
poetry run pytest -v \
--check-links-ignore "https://(api|web|docs)\.smith\.langchain\.com/.*" \
--check-links-ignore "https://academy\.langchain\.com/.*" \
--check-links-ignore "http://localhost:8123/.*" \
+3
View File
@@ -11,6 +11,9 @@ on:
- cron: "0 5 * * *"
workflow_dispatch:
env:
POETRY_VERSION: "2.1.2"
jobs:
markdown-link-check:
runs-on: ubuntu-latest
+25 -24
View File
@@ -10,6 +10,7 @@ on:
env:
PYTHON_VERSION: "3.11"
POETRY_VERSION: "2.1.2"
jobs:
build:
@@ -25,12 +26,12 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: astral-sh/setup-uv@v6
- name: Set up Python + Poetry ${{ env.POETRY_VERSION }}
uses: "./.github/actions/poetry_setup"
with:
python-version: ${{ env.PYTHON_VERSION }}
enable-cache: true
cache-suffix: "release"
poetry-version: ${{ env.POETRY_VERSION }}
cache-key: release
# We want to keep this build stage *separate* from the release stage,
# so that there's no sharing of permissions between them.
@@ -44,7 +45,7 @@ jobs:
# > from the publish job.
# https://github.com/pypa/gh-action-pypi-publish#non-goals
- name: Build project for distribution
run: uv build
run: poetry build
working-directory: ${{ inputs.working-directory }}
- name: Upload build
@@ -58,8 +59,8 @@ jobs:
shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
PKG_NAME=$(grep -m 1 "^name = " pyproject.toml | cut -d '"' -f 2)
VERSION=$(grep -m 1 "^version = " pyproject.toml | cut -d '"' -f 2)
PKG_NAME="$(poetry version | cut -d ' ' -f 1)"
VERSION="$(poetry version --short)"
SHORT_PKG_NAME="$(echo "$PKG_NAME" | sed -e 's/langgraph//g' -e 's/-//g')"
if [ -z $SHORT_PKG_NAME ]; then
TAG="$VERSION"
@@ -162,11 +163,11 @@ jobs:
# - The package is published, and it breaks on the missing dependency when
# used in the real world.
- name: Set up Python
uses: astral-sh/setup-uv@v6
- name: Set up Python + Poetry ${{ env.POETRY_VERSION }}
uses: "./.github/actions/poetry_setup"
with:
python-version: ${{ env.PYTHON_VERSION }}
enable-cache: true
poetry-version: ${{ env.POETRY_VERSION }}
- name: Import published package
shell: bash
@@ -184,18 +185,18 @@ jobs:
# - attempt install again after 5 seconds if it fails because there is
# sometimes a delay in availability on test pypi
run: |
uv run pip install \
poetry run pip install \
--extra-index-url https://test.pypi.org/simple/ \
"$PKG_NAME==$VERSION" || \
( \
sleep 5 && \
uv run pip install \
poetry run pip install \
--extra-index-url https://test.pypi.org/simple/ \
"$PKG_NAME==$VERSION" \
)
if [[ "$PKG_NAME" == *prebuilt* ]]; then
uv run pip install langgraph
poetry run pip install langgraph
fi
if [[ "$PKG_NAME" == *checkpoint* || "$PKG_NAME" == *prebuilt* ]]; then
@@ -208,10 +209,10 @@ jobs:
IMPORT_NAME="$(echo "$PKG_NAME" | sed s/-/_/g)"
fi
uv run python -c "import $IMPORT_NAME; print(dir($IMPORT_NAME))"
poetry run python -c "import $IMPORT_NAME; print(dir($IMPORT_NAME))"
- name: Import test dependencies
run: uv sync --group dev
run: poetry install --with dev
working-directory: ${{ inputs.working-directory }}
# Overwrite the local version of the package with the test PyPI version.
@@ -222,7 +223,7 @@ jobs:
PKG_NAME: ${{ needs.build.outputs.pkg-name }}
VERSION: ${{ needs.build.outputs.version }}
run: |
uv run pip install \
poetry run pip install \
--extra-index-url https://test.pypi.org/simple/ \
"$PKG_NAME==$VERSION"
@@ -252,12 +253,12 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: astral-sh/setup-uv@v6
- name: Set up Python + Poetry ${{ env.POETRY_VERSION }}
uses: "./.github/actions/poetry_setup"
with:
python-version: ${{ env.PYTHON_VERSION }}
enable-cache: true
cache-suffix: "release"
poetry-version: ${{ env.POETRY_VERSION }}
cache-key: release
- uses: actions/download-artifact@v4
with:
@@ -293,12 +294,12 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: astral-sh/setup-uv@v6
- name: Set up Python + Poetry ${{ env.POETRY_VERSION }}
uses: "./.github/actions/poetry_setup"
with:
python-version: ${{ env.PYTHON_VERSION }}
enable-cache: true
cache-suffix: "release"
poetry-version: ${{ env.POETRY_VERSION }}
cache-key: release
- uses: actions/download-artifact@v4
with:
+9 -9
View File
@@ -27,30 +27,30 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python + Poetry
uses: astral-sh/setup-uv@v6
uses: "./.github/actions/poetry_setup"
with:
python-version: "3.11"
enable-cache: true
cache-suffix: "test-langgraph-notebooks"
python-version: 3.11
poetry-version: 2.1.2
cache-key: test-langgraph-notebooks
- name: Install dependencies
run: |
uv sync --group test
uv run pip install jupyter
poetry install --with test --no-root
poetry run pip install jupyter
- name: Start services
run: make start-services
- name: Pre-download tiktoken files
run: |
uv run python _scripts/download_tiktoken.py
poetry run python _scripts/download_tiktoken.py
- name: Prepare notebooks
run: |
if [ "${{ matrix.lib-version }}" = "development" ]; then
uv run python _scripts/prepare_notebooks_for_ci.py --comment-install-cells
poetry run python _scripts/prepare_notebooks_for_ci.py --comment-install-cells
else
uv run python _scripts/prepare_notebooks_for_ci.py
poetry run python _scripts/prepare_notebooks_for_ci.py
fi
- name: Run notebooks
-1
View File
@@ -227,7 +227,6 @@ see a preview of the documentation on the pull request page.
From the **monorepo root**, run the following command to install the dependencies:
<!-- TODO -->
```bash
poetry install --with docs --no-root
```
+18 -16
View File
@@ -12,30 +12,32 @@ build-prebuilt:
# generates the final prebuilt page.
@if [ "$(DOWNLOAD_STATS)" = "true" ]; then \
set -x; \
uv run python -m _scripts.third_party_page.get_download_stats stats.yml; \
poetry run python -m _scripts.third_party_page.get_download_stats stats.yml; \
set +x; \
else \
set -x; \
uv run python -m _scripts.third_party_page.get_download_stats --fake stats.yml; \
poetry run python -m _scripts.third_party_page.get_download_stats --fake stats.yml; \
set +x; \
fi
uv run python -m _scripts.third_party_page.create_third_party_page stats.yml docs/agents/prebuilt.md --language python
poetry run python -m _scripts.third_party_page.create_third_party_page stats.yml docs/agents/prebuilt.md --language python
build-docs: build-typedoc build-prebuilt
uv run python -m mkdocs build --clean -f mkdocs.yml --strict
poetry run python -m mkdocs build --clean -f mkdocs.yml --strict
llms-text:
uv run python -m _scripts.generate_llms_text docs/llms-full.txt
poetry run python -m _scripts.generate_llms_text docs/llms-full.txt
install-vercel-deps:
curl -sL "https://astral.sh/uv/install.sh" | bash -s
export PATH="${HOME}/.cargo/bin:${PATH}"
uv venv --python 3.11
uv sync --all-groups
dnf install -y python3.11
curl -sSL https://install.python-poetry.org | python3 -
poetry self update 1.8.5
# don't use vercel's python - it wasn't compiled with sqlite support, and it fails when installing ipython's kernel
poetry env use /usr/bin/python3.11
poetry install --with docs --with test --no-root
tests:
# Run unit tests
uv run pytest tests/unit_tests
poetry run pytest tests/unit_tests
vercel-build-docs: install-vercel-deps
@@ -43,10 +45,10 @@ vercel-build-docs: install-vercel-deps
serve-clean-docs: clean-docs
uv run python -m mkdocs serve -c -f mkdocs.yml --strict -w ../libs/langgraph
poetry run python -m mkdocs serve -c -f mkdocs.yml --strict -w ../libs/langgraph
serve-docs: build-typedoc
uv run python -m mkdocs serve -f mkdocs.yml -w ../libs/langgraph -w ../libs/checkpoint -w ../libs/sdk-py --dirty
poetry run python -m mkdocs serve -f mkdocs.yml -w ../libs/langgraph -w ../libs/checkpoint -w ../libs/sdk-py --dirty
clean-docs:
find ./docs -name "*.ipynb" -type f -delete
@@ -54,13 +56,13 @@ clean-docs:
## Run format against the project documentation.
format-docs:
uv run ruff format docs
uv run ruff check --fix docs
poetry run ruff format docs
poetry run ruff check --fix docs
# Check the docs for linting violations
lint-docs:
uv run ruff format --check docs
uv run ruff check docs
poetry run ruff format --check docs
poetry run ruff check docs
codespell:
./codespell_notebooks.sh .
+1 -1
View File
@@ -3,7 +3,7 @@
To setup requirements for building docs you can run:
```bash
uv sync --group test
poetry install --with test
```
## Serving documentation locally
+1 -1
View File
@@ -8,7 +8,7 @@ execute_notebook() {
file="$1"
echo "Starting execution of $file"
start_time=$(date +%s)
if ! output=$(time uv run jupyter execute "$file" 2>&1); then
if ! output=$(time poetry run jupyter execute "$file" 2>&1); then
end_time=$(date +%s)
execution_time=$((end_time - start_time))
echo "Error in $file. Execution time: $execution_time seconds"
+17 -19
View File
@@ -29,7 +29,7 @@ from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
# highlight-next-line
client = MultiServerMCPClient(
async with MultiServerMCPClient(
{
"math": {
"command": "python",
@@ -39,24 +39,22 @@ client = MultiServerMCPClient(
},
"weather": {
# Ensure your start your weather server on port 8000
"url": "http://localhost:8000/mcp",
"transport": "streamable_http",
"url": "http://localhost:8000/sse",
"transport": "sse",
}
}
)
# highlight-next-line
tools = await client.get_tools()
agent = create_react_agent(
"anthropic:claude-3-7-sonnet-latest",
# highlight-next-line
tools
)
math_response = await agent.ainvoke(
{"messages": [{"role": "user", "content": "what's (3 + 5) x 12?"}]}
)
weather_response = await agent.ainvoke(
{"messages": [{"role": "user", "content": "what is the weather in nyc?"}]}
)
) as client:
agent = create_react_agent(
"anthropic:claude-3-7-sonnet-latest",
# highlight-next-line
client.get_tools()
)
math_response = await agent.ainvoke(
{"messages": [{"role": "user", "content": "what's (3 + 5) x 12?"}]}
)
weather_response = await agent.ainvoke(
{"messages": [{"role": "user", "content": "what is the weather in nyc?"}]}
)
```
## Custom MCP servers
@@ -89,7 +87,7 @@ if __name__ == "__main__":
mcp.run(transport="stdio")
```
```python title="Example Weather Server (Streamable HTTP transport)"
```python title="Example Weather Server (SSE transport)"
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Weather")
@@ -100,7 +98,7 @@ async def get_weather(location: str) -> str:
return "It's always sunny in New York"
if __name__ == "__main__":
mcp.run(transport="streamable-http")
mcp.run(transport="sse")
```
## Additional resources

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

+11 -16
View File
@@ -56,27 +56,22 @@ cloudpickle>=3.0.0
Example `pyproject.toml` file:
```toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
[tool.poetry]
name = "my-agent"
version = "0.0.1"
description = "An excellent agent build for LangGraph Platform."
authors = [
{name = "Polly the parrot", email = "1223+polly@users.noreply.github.com"}
]
license = {text = "MIT"}
authors = ["Polly the parrot <1223+polly@users.noreply.github.com>"]
license = "MIT"
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
"langgraph>=0.2.0",
"langchain-fireworks>=0.1.3"
]
[tool.hatch.build.targets.wheel]
packages = ["my_agent"]
[tool.poetry.dependencies]
python = ">=3.9"
langgraph = "^0.2.0"
langchain-fireworks = "^0.1.3"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
```
Example file directory:
+1 -1
View File
@@ -20,7 +20,7 @@ To deploy an application to **LangGraph Platform**, your application code must r
## 2. Deploy to LangGraph Platform
1. Log in to [LangSmith](https://smith.langchain.com/).
1. In the left sidebar, select **Deployments**.
1. In the left sidebar, select **LangGraph Platform**.
1. Click the **+ New Deployment** button. A pane will open where you can fill in the required fields.
1. If you are a first time user or adding a private repository that has not been previously connected, click the **Import from GitHub** button and follow the instructions to connect your GitHub account.
1. Select your New LangGraph Project repository.
+1 -1
View File
@@ -7,7 +7,7 @@ search:
**LangGraph Server** offers an API for creating and managing agent-based applications. It is built on the concept of [assistants](assistants.md), which are agents configured for specific tasks, and includes built-in [persistence](persistence.md#memory-store) and a **task queue**. This versatile API supports a wide range of agentic application use cases, from background processing to real-time interactions.
Use LangGraph Server to create and manage [assistants](assistants.md), [threads](../cloud/concepts/threads.md), [runs](../cloud/concepts/runs.md), [cron jobs](../cloud/concepts/cron_jobs.md), [webhooks](../cloud/concepts/webhooks.md), and more.
Use LangGraph Serverto create and manage [assistants](assistants.md), [threads](../cloud/concepts/threads.md), [runs](../cloud/concepts/runs.md), [cron jobs](../cloud/concepts/cron_jobs.md), [webhooks](../cloud/concepts/webhooks.md), and more.
!!! tip "API reference"
@@ -8,23 +8,7 @@ Before you begin, ensure you have the following:
- An API key for [LangSmith](https://smith.langchain.com/settings) - free to sign up
## 1. Install the LangGraph CLI
=== "Python server"
```shell
# Python >= 3.11 is required.
pip install --upgrade "langgraph-cli[inmem]"
```
=== "Node server"
```shell
npx @langchain/langgraph-cl
```
## 2. Create a LangGraph app 🌱
## 1. Create a LangGraph app 🌱
Create a new app from the [`new-langgraph-project-python` template](https://github.com/langchain-ai/new-langgraph-project) or [`new-langgraph-project-js` template](https://github.com/langchain-ai/new-langgraphjs-project). This template demonstrates a single-node application you can extend with your own logic.
@@ -44,25 +28,25 @@ Create a new app from the [`new-langgraph-project-python` template](https://gith
If you use `langgraph new` without specifying a template, you will be presented with an interactive menu that will allow you to choose from a list of available templates.
## 3. Install dependencies
In the root of your new LangGraph app, install the dependencies in `edit` mode so your local changes are used by the server:
## 2. Install dependencies and LangGraph CLI
=== "Python server"
```shell
# Python >= 3.11 is required.
cd path/to/your/app
pip install -e .
pip install -e . "langgraph-cli[inmem]"
```
=== "Node server"
```shell
cd path/to/your/app
npx @langchain/langgraph-cli
yarn install
```
## 4. Create a `.env` file
## 3. Create a `.env` file
You will find a `.env.example` in the root of your new LangGraph app. Create a `.env` file in the root of your new LangGraph app and copy the contents of the `.env.example` file into it, filling in the necessary API keys:
@@ -70,7 +54,7 @@ You will find a `.env.example` in the root of your new LangGraph app. Create a `
LANGSMITH_API_KEY=lsv2...
```
## 5. Launch LangGraph Server 🚀
## 4. Launch LangGraph Server 🚀
Start the LangGraph API server locally:
@@ -100,7 +84,7 @@ Sample output:
The `langgraph dev` command starts LangGraph Server in an in-memory mode. This mode is suitable for development and testing purposes. For production use, deploy LangGraph Server with access to a persistent storage backend. For more information, see [Deployment options](../../concepts/deployment_options.md).
## 6. Test your application in LangGraph Studio
## 5. Test your application in LangGraph Studio
[LangGraph Studio](../../concepts/langgraph_studio.md) is a specialized UI that you can connect to LangGraph API server to visualize, interact with, and debug your application locally. Test your graph in LangGraph Studio by visiting the URL provided in the output of the `langgraph dev` command:
@@ -118,7 +102,7 @@ For a LangGraph Server running on a custom host/port, update the baseURL paramet
langgraph dev --tunnel
```
## 7. Test the API
## 6. Test the API
=== "Python SDK (async)"
+1 -1
View File
@@ -583,7 +583,7 @@
"def check_query(state: MessagesState):\n",
" system_message = {\n",
" \"role\": \"system\",\n",
" \"content\": check_query_system_prompt,\n",
" \"content\": generate_query_system_prompt,\n",
" }\n",
"\n",
" # Generate an artificial user message to check\n",
+2 -2
View File
@@ -122,8 +122,8 @@ As noted in the Anthropic blog on `Building Effective Agents`:
# Simple check - does the joke contain "?" or "!"
if "?" in state["joke"] or "!" in state["joke"]:
return "Pass"
return "Fail"
return "Fail"
return "Pass"
def improve_joke(state: State):
+9178
View File
File diff suppressed because it is too large Load Diff
+75 -86
View File
@@ -1,99 +1,88 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
[tool.poetry]
name = "langgraph-docs"
version = "0.0.1"
description = "LangGraph docs"
authors = []
requires-python = "~=3.10"
readme = "README.md"
license = "MIT"
dependencies = [
"aiohappyeyeballs==2.4.3",
"hub>=3.0.1,<4",
"xxhash>=3.5.0,<4",
"black>=25.1.0,<26",
]
readme = "README.md"
package-mode = false
[dependency-groups]
docs = [
"langgraph",
"langgraph-prebuilt",
"langgraph-checkpoint",
"langgraph-checkpoint-sqlite",
"langgraph-checkpoint-postgres",
"langgraph-sdk",
"langgraph-supervisor",
"langgraph-swarm",
"langchain-mcp-adapters",
"langchain-ollama",
"mkdocs",
"mkdocs-autorefs",
"mkdocstrings",
"mkdocstrings-python",
"mkdocs-minify-plugin",
"mkdocs-rss-plugin",
"mkdocs-git-committers-plugin-2",
"mkdocs-material[imaging]",
"markdown-callouts",
"markdown-include",
"mkdocs-exclude",
"psycopg[binary]",
"psycopg-pool",
"pygments-ansi-color",
"vcrpy",
"click",
"ruff",
"jupyter",
"langchain-cohere",
]
test = [
"langchain",
"langchain-core",
"langchain-openai",
"langchain-anthropic",
"langchain-nomic",
"langchain-fireworks",
"langchain-community",
"langchain-tavily",
"langchain-experimental",
"langchain-mistralai",
"langgraph-checkpoint-mongodb",
"langmem",
"langsmith",
"chromadb",
"gpt4all",
"scikit-learn",
"numexpr",
"numpy",
"matplotlib",
"redis",
"pymongo",
"motor",
"grandalf",
"pyppeteer",
"networkx",
"autogen ; python_version >= '3.8' and python_version < '3.13'",
"pytest",
"pytest-check-links",
]
[tool.poetry.dependencies]
python = "^3.10"
aiohappyeyeballs = "2.4.3"
hub = "^3.0.1"
xxhash = "^3.5.0"
black = "^25.1.0"
[tool.uv]
package = false
default-groups = ["docs","test",]
[tool.uv.sources]
langgraph = { path = "../libs/langgraph/", editable = true }
langgraph-prebuilt = { path = "../libs/prebuilt", editable = true }
langgraph-checkpoint = { path = "../libs/checkpoint/", editable = true }
langgraph-checkpoint-sqlite = { path = "../libs/checkpoint-sqlite", editable = true }
langgraph-checkpoint-postgres = { path = "../libs/checkpoint-postgres", editable = true }
langgraph-sdk = { path = "../libs/sdk-py", editable = true }
[tool.poetry.group.docs.dependencies]
langgraph = { path = "../libs/langgraph/", develop = true }
langgraph-prebuilt = {path = "../libs/prebuilt", develop = true}
langgraph-checkpoint = { path = "../libs/checkpoint/", develop = true }
langgraph-checkpoint-sqlite = { path = "../libs/checkpoint-sqlite", develop = true }
langgraph-checkpoint-postgres = { path = "../libs/checkpoint-postgres", develop = true }
langgraph-sdk = {path = "../libs/sdk-py", develop = true}
# TODO: switch these to published versions
langgraph-supervisor = { git = "https://github.com/langchain-ai/langgraph-supervisor-py" }
langgraph-swarm = { git = "https://github.com/langchain-ai/langgraph-swarm-py" }
langchain-mcp-adapters = { git = "https://github.com/langchain-ai/langchain-mcp-adapters" }
langchain-ollama = "^0.2.3"
mkdocs = "*"
mkdocs-autorefs = "*"
mkdocstrings = "*"
mkdocstrings-python = "*"
mkdocs-minify-plugin = "*"
mkdocs-rss-plugin = "*"
mkdocs-git-committers-plugin-2 = "*"
mkdocs-material = {extras = ["imaging"], version = "*"}
markdown-callouts = "*"
markdown-include = "*"
mkdocs-exclude = "*"
psycopg = {extras = ["binary"], version = "^3.2.0"}
psycopg-pool = "^3.2.0"
pygments-ansi-color = ">=0.3"
vcrpy = "^6.0.1"
click = "^8.1.7"
ruff = "^0.6.8"
jupyter = "^1.1.1"
langchain-cohere = "^0.4.2"
[tool.poetry.group.test.dependencies]
langchain = "^0.3.8"
langchain-core = "^0.3.54"
langchain-openai = "^0.3.7"
langchain-anthropic = "^0.3.8"
langchain-nomic = "^0.1.3"
langchain-fireworks = "^0.2.0"
langchain-community = "^0.3.0"
langchain-tavily = "^0.1.5"
langchain-experimental = "^0.3.2"
langchain-mistralai = "^0.2.6"
langgraph-checkpoint-mongodb = "^0.1.0"
langmem = "^0.0.19"
langsmith = "^0.3.0"
chromadb = "^0.5.5"
gpt4all = "^2.8.2"
scikit-learn = "^1.5.2"
numexpr = "^2.10.1"
numpy = "^1.26.4"
matplotlib = "^3.9.2"
redis = "^5.0.8"
pymongo = "^4.8.0"
motor = "^3.5.1"
grandalf = "^0.8"
pyppeteer = "^2.0.0"
networkx = "^3.3"
autogen = { version = "^0.3.0", python = "<3.13,>=3.8" }
pytest = "^8.3.5"
pytest-check-links = "^0.10.1"
[tool.poetry.group.test]
optional = true
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.ruff]
extend-include = ["*.ipynb"]
Generated
-6769
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -18,7 +18,7 @@ POSTGRES_VERSIONS ?= 15 16
test_pg_version:
@echo "Testing PostgreSQL $(POSTGRES_VERSION)"
@POSTGRES_VERSION=$(POSTGRES_VERSION) make start-postgres
@uv run pytest $(TEST)
@poetry run pytest $(TEST)
@EXIT_CODE=$$?; \
make stop-postgres; \
echo "Finished testing PostgreSQL $(POSTGRES_VERSION); Exit code: $$EXIT_CODE"; \
@@ -36,7 +36,7 @@ test:
TEST ?= .
test_watch:
POSTGRES_VERSION=${POSTGRES_VERSION:-16} make start-postgres; \
uv run ptw $(TEST); \
poetry run ptw $(TEST); \
EXIT_CODE=$$?; \
make stop-postgres; \
exit $$EXIT_CODE
@@ -55,12 +55,12 @@ lint_tests: PYTHON_FILES=tests
lint_tests: MYPY_CACHE=.mypy_cache_test
lint lint_diff lint_package lint_tests:
uv run ruff check .
[ "$(PYTHON_FILES)" = "" ] || uv run ruff format $(PYTHON_FILES) --diff
[ "$(PYTHON_FILES)" = "" ] || uv run ruff check --select I $(PYTHON_FILES)
poetry run ruff check .
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff format $(PYTHON_FILES) --diff
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff check --select I $(PYTHON_FILES)
[ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE)
[ "$(PYTHON_FILES)" = "" ] || uv run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
[ "$(PYTHON_FILES)" = "" ] || poetry run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
format format_diff:
uv run ruff format $(PYTHON_FILES)
uv run ruff check --select I --fix $(PYTHON_FILES)
poetry run ruff format $(PYTHON_FILES)
poetry run ruff check --select I --fix $(PYTHON_FILES)
@@ -333,7 +333,7 @@ class BasePostgresStore(Generic[C]):
# First handle main store insertions
for op in inserts:
if op.ttl is not None:
expires_at_str = f"NOW() + INTERVAL '{op.ttl * 60} seconds'"
expires_at_str = f"NOW() + INTERVAL '{op.ttl*60} seconds'"
ttl_minutes = op.ttl
else:
expires_at_str = "NULL"
File diff suppressed because it is too large Load Diff
+32 -38
View File
@@ -1,53 +1,47 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
[tool.poetry]
name = "langgraph-checkpoint-postgres"
version = "2.0.21"
description = "Library with a Postgres implementation of LangGraph checkpoint saver."
authors = []
requires-python = ">=3.9"
readme = "README.md"
license = "MIT"
license-files = ['LICENSE']
dependencies = [
"langgraph-checkpoint>=2.0.21",
"orjson>=3.10.1",
"psycopg>=3.2.0",
"psycopg-pool>=3.2.0",
]
readme = "README.md"
repository = "https://www.github.com/langchain-ai/langgraph"
packages = [{ include = "langgraph" }]
[project.urls]
Repository = "https://www.github.com/langchain-ai/langgraph"
[tool.poetry.dependencies]
python = ">=3.9"
langgraph-checkpoint = "^2.0.21"
orjson = ">=3.10.1"
psycopg = "^3.2.0"
psycopg-pool = "^3.2.0"
[dependency-groups]
dev = [
"ruff",
"codespell",
"pytest",
"anyio",
"pytest-asyncio",
"pytest-mock",
"mypy",
"psycopg[binary]",
"langgraph-checkpoint",
"pytest-watcher",
]
[tool.uv]
default-groups = ['dev']
[tool.uv.sources]
langgraph-checkpoint = { path = "../checkpoint", editable = true }
[tool.hatch.build.targets.wheel]
include = ["langgraph"]
[tool.poetry.group.dev.dependencies]
ruff = "^0.6.2"
codespell = "^2.2.0"
pytest = "^7.2.1"
anyio = "^4.4.0"
pytest-asyncio = "^0.21.1"
pytest-mock = "^3.11.1"
mypy = "^1.10.0"
psycopg = {extras = ["binary"], version = ">=3.0.0"}
langgraph-checkpoint = {path = "../checkpoint", develop = true}
pytest-watcher = { version = ">=0.4.3", python = "<4.0" }
[tool.pytest.ini_options]
# --strict-markers will raise errors on unknown marks.
# https://docs.pytest.org/en/7.1.x/how-to/mark.html#raising-errors-on-unknown-marks
#
# https://docs.pytest.org/en/7.1.x/reference/reference.html
# --strict-config any warnings encountered while parsing the `pytest`
# section of the configuration file raise errors.
addopts = "--strict-markers --strict-config --durations=5 -vv"
asyncio_mode = "auto"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.ruff]
lint.select = [
"E", # pycodestyle
-1206
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -5,10 +5,10 @@
######################
test:
uv run pytest tests
poetry run pytest tests
test_watch:
uv run ptw .
poetry run ptw .
######################
# LINTING AND FORMATTING
@@ -24,12 +24,12 @@ lint_tests: PYTHON_FILES=tests
lint_tests: MYPY_CACHE=.mypy_cache_test
lint lint_diff lint_package lint_tests:
uv run ruff check .
[ "$(PYTHON_FILES)" = "" ] || uv run ruff format $(PYTHON_FILES) --diff
[ "$(PYTHON_FILES)" = "" ] || uv run ruff check --select I $(PYTHON_FILES)
poetry run ruff check .
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff format $(PYTHON_FILES) --diff
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff check --select I $(PYTHON_FILES)
[ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE)
[ "$(PYTHON_FILES)" = "" ] || uv run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
[ "$(PYTHON_FILES)" = "" ] || poetry run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
format format_diff:
uv run ruff format $(PYTHON_FILES)
uv run ruff check --select I --fix $(PYTHON_FILES)
poetry run ruff format $(PYTHON_FILES)
poetry run ruff check --select I --fix $(PYTHON_FILES)
@@ -1,9 +1,8 @@
import random
import sqlite3
import threading
from collections.abc import AsyncIterator, Iterator, Sequence
from contextlib import closing, contextmanager
from typing import Any, Optional, cast
from typing import Any, AsyncIterator, Dict, Iterator, Optional, Sequence, Tuple
from langchain_core.runnables import RunnableConfig
@@ -262,12 +261,7 @@ class SqliteSaver(BaseCheckpointSaver[str]):
return CheckpointTuple(
config,
self.serde.loads_typed((type, checkpoint)),
cast(
CheckpointMetadata,
self.jsonplus_serde.loads(metadata)
if metadata is not None
else {},
),
self.jsonplus_serde.loads(metadata) if metadata is not None else {},
(
{
"configurable": {
@@ -289,7 +283,7 @@ class SqliteSaver(BaseCheckpointSaver[str]):
self,
config: Optional[RunnableConfig],
*,
filter: Optional[dict[str, Any]] = None,
filter: Optional[Dict[str, Any]] = None,
before: Optional[RunnableConfig] = None,
limit: Optional[int] = None,
) -> Iterator[CheckpointTuple]:
@@ -355,12 +349,7 @@ class SqliteSaver(BaseCheckpointSaver[str]):
}
},
self.serde.loads_typed((type, checkpoint)),
cast(
CheckpointMetadata,
self.jsonplus_serde.loads(metadata)
if metadata is not None
else {},
),
self.jsonplus_serde.loads(metadata) if metadata is not None else {},
(
{
"configurable": {
@@ -439,7 +428,7 @@ class SqliteSaver(BaseCheckpointSaver[str]):
def put_writes(
self,
config: RunnableConfig,
writes: Sequence[tuple[str, Any]],
writes: Sequence[Tuple[str, Any]],
task_id: str,
task_path: str = "",
) -> None:
@@ -507,7 +496,7 @@ class SqliteSaver(BaseCheckpointSaver[str]):
self,
config: Optional[RunnableConfig],
*,
filter: Optional[dict[str, Any]] = None,
filter: Optional[Dict[str, Any]] = None,
before: Optional[RunnableConfig] = None,
limit: Optional[int] = None,
) -> AsyncIterator[CheckpointTuple]:
@@ -2,7 +2,7 @@ import asyncio
import random
from collections.abc import AsyncIterator, Iterator, Sequence
from contextlib import asynccontextmanager
from typing import Any, Callable, Optional, TypeVar, cast
from typing import Any, Callable, Optional, TypeVar
import aiosqlite
from langchain_core.runnables import RunnableConfig
@@ -374,12 +374,7 @@ class AsyncSqliteSaver(BaseCheckpointSaver[str]):
return CheckpointTuple(
config,
self.serde.loads_typed((type, checkpoint)),
cast(
CheckpointMetadata,
self.jsonplus_serde.loads(metadata)
if metadata is not None
else {},
),
self.jsonplus_serde.loads(metadata) if metadata is not None else {},
(
{
"configurable": {
@@ -454,12 +449,7 @@ class AsyncSqliteSaver(BaseCheckpointSaver[str]):
}
},
self.serde.loads_typed((type, checkpoint)),
cast(
CheckpointMetadata,
self.jsonplus_serde.loads(metadata)
if metadata is not None
else {},
),
self.jsonplus_serde.loads(metadata) if metadata is not None else {},
(
{
"configurable": {
@@ -1,6 +1,5 @@
import json
from collections.abc import Sequence
from typing import Any, Optional
from typing import Any, Dict, Optional, Sequence, Tuple
from langchain_core.runnables import RunnableConfig
@@ -8,8 +7,8 @@ from langgraph.checkpoint.base import get_checkpoint_id
def _metadata_predicate(
metadata_filter: dict[str, Any],
) -> tuple[Sequence[str], Sequence[Any]]:
metadata_filter: Dict[str, Any],
) -> Tuple[Sequence[str], Sequence[Any]]:
"""Return WHERE clause predicates for (a)search() given metadata filter.
This method returns a tuple of a string and a tuple of values. The string
@@ -18,7 +17,7 @@ def _metadata_predicate(
for each of the corresponding parameters.
"""
def _where_value(query_value: Any) -> tuple[str, Any]:
def _where_value(query_value: Any) -> Tuple[str, Any]:
"""Return tuple of operator and value for WHERE clause predicate."""
if query_value is None:
return ("IS ?", None)
@@ -53,9 +52,9 @@ def _metadata_predicate(
def search_where(
config: Optional[RunnableConfig],
filter: Optional[dict[str, Any]],
filter: Optional[Dict[str, Any]],
before: Optional[RunnableConfig] = None,
) -> tuple[str, Sequence[Any]]:
) -> Tuple[str, Sequence[Any]]:
"""Return WHERE clause predicates for (a)search() given metadata filter
and `before` config.
+1047
View File
File diff suppressed because it is too large Load Diff
+28 -34
View File
@@ -1,49 +1,43 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
[tool.poetry]
name = "langgraph-checkpoint-sqlite"
version = "2.0.7"
description = "Library with a SQLite implementation of LangGraph checkpoint saver."
authors = []
requires-python = ">=3.9"
readme = "README.md"
license = "MIT"
license-files = ['LICENSE']
dependencies = [
"langgraph-checkpoint>=2.0.15",
"aiosqlite>=0.20",
]
readme = "README.md"
repository = "https://www.github.com/langchain-ai/langgraph"
packages = [{ include = "langgraph" }]
[project.urls]
Repository = "https://www.github.com/langchain-ai/langgraph"
[tool.poetry.dependencies]
python = ">=3.9"
langgraph-checkpoint = "^2.0.15"
aiosqlite = ">=0.20,<0.22"
[dependency-groups]
dev = [
"ruff",
"codespell",
"pytest",
"pytest-asyncio",
"pytest-mock",
"pytest-watcher",
"mypy",
"langgraph-checkpoint",
]
[tool.uv]
default-groups = ['dev']
[tool.uv.sources]
langgraph-checkpoint = { path = "../checkpoint", editable = true }
[tool.hatch.build.targets.wheel]
include = ["langgraph"]
[tool.poetry.group.dev.dependencies]
ruff = "^0.6.2"
codespell = "^2.2.0"
pytest = "^7.2.1"
pytest-asyncio = "^0.21.1"
pytest-mock = "^3.11.1"
pytest-watcher = { version = ">=0.4.1", python = "<4.0" }
mypy = "^1.10.0"
langgraph-checkpoint = {path = "../checkpoint", develop = true}
[tool.pytest.ini_options]
# --strict-markers will raise errors on unknown marks.
# https://docs.pytest.org/en/7.1.x/how-to/mark.html#raising-errors-on-unknown-marks
#
# https://docs.pytest.org/en/7.1.x/reference/reference.html
# --strict-config any warnings encountered while parsing the `pytest`
# section of the configuration file raise errors.
addopts = "--strict-markers --strict-config --durations=5 -vv"
asyncio_mode = "auto"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.ruff]
lint.select = [
"E", # pycodestyle
@@ -59,7 +59,7 @@ class TestAsyncSqliteSaver:
async def test_combined_metadata(self) -> None:
async with AsyncSqliteSaver.from_conn_string(":memory:") as saver:
config: RunnableConfig = {
config = {
"configurable": {
"thread_id": "thread-2",
"checkpoint_ns": "",
@@ -69,7 +69,7 @@ class TestAsyncSqliteSaver:
}
await saver.aput(config, self.chkpnt_2, self.metadata_2, {})
checkpoint = await saver.aget_tuple(config)
assert checkpoint is not None and checkpoint.metadata == {
assert checkpoint.metadata == {
**self.metadata_2,
"thread_id": "thread-2",
"run_id": "my_run_id",
+2 -2
View File
@@ -60,7 +60,7 @@ class TestSqliteSaver:
def test_combined_metadata(self) -> None:
with SqliteSaver.from_conn_string(":memory:") as saver:
config: RunnableConfig = {
config = {
"configurable": {
"thread_id": "thread-2",
"checkpoint_ns": "",
@@ -70,7 +70,7 @@ class TestSqliteSaver:
}
saver.put(config, self.chkpnt_2, self.metadata_2, {})
checkpoint = saver.get_tuple(config)
assert checkpoint is not None and checkpoint.metadata == {
assert checkpoint.metadata == {
**self.metadata_2,
"thread_id": "thread-2",
"run_id": "my_run_id",
-1109
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -7,10 +7,10 @@
TEST ?= .
test:
uv run pytest $(TEST)
poetry run pytest $(TEST)
test_watch:
uv run ptw $(TEST)
poetry run ptw $(TEST)
######################
# LINTING AND FORMATTING
@@ -26,12 +26,12 @@ lint_tests: PYTHON_FILES=tests
lint_tests: MYPY_CACHE=.mypy_cache_test
lint lint_diff lint_package lint_tests:
uv run ruff check .
[ "$(PYTHON_FILES)" = "" ] || uv run ruff format $(PYTHON_FILES) --diff
[ "$(PYTHON_FILES)" = "" ] || uv run ruff check --select I $(PYTHON_FILES)
poetry run ruff check .
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff format $(PYTHON_FILES) --diff
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff check --select I $(PYTHON_FILES)
[ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE)
[ "$(PYTHON_FILES)" = "" ] || uv run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
[ "$(PYTHON_FILES)" = "" ] || poetry run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
format format_diff:
uv run ruff format $(PYTHON_FILES)
uv run ruff check --select I --fix $(PYTHON_FILES)
poetry run ruff format $(PYTHON_FILES)
poetry run ruff check --select I --fix $(PYTHON_FILES)
+2 -2
View File
@@ -1,8 +1,8 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from collections.abc import Mapping, Sequence
from typing import Generic, TypeVar
from collections.abc import Mapping
from typing import Generic, Sequence, TypeVar
from langgraph.checkpoint.serde.base import SerializerProtocol
from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer
@@ -6,7 +6,7 @@ Bundled in to avoid install issues with uuid6 package
import random
import time
import uuid
from typing import Optional
from typing import Optional, Tuple
_last_v6_timestamp = None
@@ -21,7 +21,7 @@ class UUID(uuid.UUID):
hex: Optional[str] = None,
bytes: Optional[bytes] = None,
bytes_le: Optional[bytes] = None,
fields: Optional[tuple[int, int, int, int, int, int]] = None,
fields: Optional[Tuple[int, int, int, int, int, int]] = None,
int: Optional[int] = None,
version: Optional[int] = None,
*,
@@ -20,11 +20,11 @@ from ipaddress import (
)
from typing import Any, Callable, Optional, Union, cast
from uuid import UUID
from zoneinfo import ZoneInfo
import ormsgpack
from langchain_core.load.load import Reviver
from langchain_core.load.serializable import Serializable
from zoneinfo import ZoneInfo
from langgraph.checkpoint.serde.base import SerializerProtocol
from langgraph.checkpoint.serde.types import SendProtocol
@@ -1,8 +1,8 @@
from collections.abc import Sequence
from typing import (
Any,
Optional,
Protocol,
Sequence,
TypeVar,
runtime_checkable,
)
@@ -10,10 +10,10 @@ Core types:
"""
from abc import ABC, abstractmethod
from collections.abc import Iterable
from datetime import datetime
from typing import (
Any,
Iterable,
Literal,
NamedTuple,
Optional,
@@ -9,8 +9,7 @@ asynchronous operations.
import asyncio
import functools
import json
from collections.abc import Awaitable, Sequence
from typing import Any, Callable, Optional, Union
from typing import Any, Awaitable, Callable, Optional, Sequence, Union
from langchain_core.embeddings import Embeddings
@@ -104,10 +104,9 @@ import concurrent.futures as cf
import functools
import logging
from collections import defaultdict
from collections.abc import Iterable
from datetime import datetime, timezone
from importlib import util
from typing import Any, Optional
from typing import Any, Iterable, Optional
from langchain_core.embeddings import Embeddings
+1069
View File
File diff suppressed because it is too large Load Diff
+29 -29
View File
@@ -1,43 +1,43 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
[tool.poetry]
name = "langgraph-checkpoint"
version = "2.0.26"
version = "2.0.25"
description = "Library with base interfaces for LangGraph checkpoint savers."
authors = []
requires-python = ">=3.9"
readme = "README.md"
license = "MIT"
license-files = ['LICENSE']
dependencies = [
"langchain-core>=0.2.38",
"ormsgpack>=1.8.0",
]
readme = "README.md"
repository = "https://www.github.com/langchain-ai/langgraph"
packages = [{ include = "langgraph" }]
[project.urls]
Repository = "https://www.github.com/langchain-ai/langgraph"
[tool.poetry.dependencies]
python = ">=3.9"
langchain-core = { version = ">=0.2.38", python = "<4.0" }
ormsgpack = "^1.8.0"
[dependency-groups]
dev = [
"ruff",
"codespell",
"pytest",
"pytest-asyncio",
"pytest-mock",
"pytest-watcher",
"mypy",
"dataclasses-json",
]
[tool.hatch.build.targets.wheel]
include = ["langgraph"]
[tool.poetry.group.dev.dependencies]
ruff = "^0.6.2"
codespell = "^2.2.0"
pytest = "^7.2.1"
pytest-asyncio = "^0.21.1"
pytest-mock = "^3.11.1"
pytest-watcher = { version = ">=0.4.1", python = "<4.0" }
mypy = "^1.10.0"
dataclasses-json = { version = ">=0.6.7", python = "<4.0" }
[tool.pytest.ini_options]
# --strict-markers will raise errors on unknown marks.
# https://docs.pytest.org/en/7.1.x/how-to/mark.html#raising-errors-on-unknown-marks
#
# https://docs.pytest.org/en/7.1.x/reference/reference.html
# --strict-config any warnings encountered while parsing the `pytest`
# section of the configuration file raise errors.
addopts = "--strict-markers --strict-config --durations=5 -vv"
asyncio_mode = "auto"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.ruff]
lint.select = [
"E", # pycodestyle
+1 -1
View File
@@ -8,12 +8,12 @@ from datetime import date, datetime, time, timezone
from decimal import Decimal
from enum import Enum
from ipaddress import IPv4Address
from zoneinfo import ZoneInfo
import dataclasses_json
from pydantic import BaseModel, SecretStr
from pydantic.v1 import BaseModel as BaseModelV1
from pydantic.v1 import SecretStr as SecretStrV1
from zoneinfo import ZoneInfo
from langgraph.checkpoint.serde.jsonplus import (
JsonPlusSerializer,
+1 -2
View File
@@ -1,9 +1,8 @@
# mypy: disable-error-code="operator"
import asyncio
import json
from collections.abc import Iterable
from datetime import datetime
from typing import Any
from typing import Any, Iterable
import pytest
from pytest_mock import MockerFixture
-1108
View File
File diff suppressed because it is too large Load Diff
+9 -9
View File
@@ -5,9 +5,9 @@
######################
test:
uv run pytest tests/unit_tests
poetry run pytest tests/unit_tests
test-integration:
uv run pytest tests/integration_tests
poetry run pytest tests/integration_tests
######################
# LINTING AND FORMATTING
@@ -23,14 +23,14 @@ lint_tests: PYTHON_FILES=tests
lint_tests: MYPY_CACHE=.mypy_cache_test
lint lint_diff lint_package lint_tests:
uv run ruff check .
[ "$(PYTHON_FILES)" = "" ] || uv run ruff format $(PYTHON_FILES) --diff
[ "$(PYTHON_FILES)" = "" ] || uv run ruff check --select I $(PYTHON_FILES)
[ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE) || uv run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
poetry run ruff check .
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff format $(PYTHON_FILES) --diff
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff check --select I $(PYTHON_FILES)
[ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE) || poetry run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
format format_diff:
uv run ruff format $(PYTHON_FILES)
uv run ruff check --select I --fix $(PYTHON_FILES)
poetry run ruff format $(PYTHON_FILES)
poetry run ruff check --select I --fix $(PYTHON_FILES)
update-schema:
uv run python generate_schema.py
poetry run python generate_schema.py
+4 -4
View File
@@ -87,17 +87,17 @@ To develop the CLI itself:
1. Clone the repository
2. Navigate to the CLI directory: `cd libs/cli`
3. Install development dependencies: `uv pip install`
3. Install development dependencies: `poetry install`
4. Make your changes to the CLI code
5. Test your changes:
```bash
# Run CLI commands directly
uv run langgraph --help
poetry run langgraph --help
# Or use the examples
cd examples
uv pip install
uv run langgraph dev # or other commands
poetry install
poetry run langgraph dev # or other commands
```
## License
+4 -4
View File
@@ -1,13 +1,13 @@
.PHONY: run_w_override
run:
uv run langgraph up --watch --no-pull
poetry run langgraph up --watch --no-pull
run_faux:
cd graphs && uv run langgraph up --no-pull
cd graphs && poetry run langgraph up --no-pull
run_graphs_reqs_a:
cd graphs_reqs_a && uv run langgraph up --no-pull
cd graphs_reqs_a && poetry run langgraph up --no-pull
run_graphs_reqs_b:
cd graphs_reqs_b && uv run langgraph up --no-pull
cd graphs_reqs_b && poetry run langgraph up --no-pull
+1 -2
View File
@@ -1,5 +1,4 @@
from collections.abc import Sequence
from typing import Annotated, Literal, TypedDict
from typing import Annotated, Literal, Sequence, TypedDict
from langchain_anthropic import ChatAnthropic
from langchain_community.tools.tavily_search import TavilySearchResults
+13 -13
View File
@@ -1,6 +1,6 @@
import asyncio
import json
from typing import Annotated, Optional
from typing import Annotated, List, Optional
from langchain_community.retrievers import WikipediaRetriever
from langchain_community.tools.tavily_search import TavilySearchResults
@@ -51,7 +51,7 @@ class Subsection(BaseModel):
class Section(BaseModel):
section_title: str = Field(..., title="Title of the section")
description: str = Field(..., title="Content of the section")
subsections: Optional[list[Subsection]] = Field(
subsections: Optional[List[Subsection]] = Field(
default=None,
title="Titles and descriptions for each subsection of the Wikipedia page.",
)
@@ -67,7 +67,7 @@ class Section(BaseModel):
class Outline(BaseModel):
page_title: str = Field(..., title="Title of the Wikipedia page")
sections: list[Section] = Field(
sections: List[Section] = Field(
default_factory=list,
title="Titles and descriptions for each section of the Wikipedia page.",
)
@@ -93,7 +93,7 @@ Topic of interest: {topic}
class RelatedSubjects(BaseModel):
topics: list[str] = Field(
topics: List[str] = Field(
description="Comprehensive list of related subjects as background research.",
)
@@ -123,7 +123,7 @@ class Editor(BaseModel):
class Perspectives(BaseModel):
editors: list[Editor] = Field(
editors: List[Editor] = Field(
description="Comprehensive list of editors with their roles and affiliations.",
# Add a pydantic validation/restriction to be at most M editors
)
@@ -200,7 +200,7 @@ def update_editor(editor, new_editor):
class InterviewState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
messages: Annotated[List[AnyMessage], add_messages]
references: Annotated[Optional[dict], update_references]
editor: Annotated[Optional[Editor], update_editor]
@@ -255,7 +255,7 @@ async def generate_question(state: InterviewState):
class Queries(BaseModel):
queries: list[str] = Field(
queries: List[str] = Field(
description="Comprehensive list of search engine queries to answer the user's questions.",
)
@@ -278,7 +278,7 @@ class AnswerWithCitations(BaseModel):
answer: str = Field(
description="Comprehensive answer to the user's question with citations.",
)
cited_urls: list[str] = Field(
cited_urls: List[str] = Field(
description="List of urls cited in the answer.",
)
@@ -437,11 +437,11 @@ class SubSection(BaseModel):
class WikiSection(BaseModel):
section_title: str = Field(..., title="Title of the section")
content: str = Field(..., title="Full content of the section")
subsections: Optional[list[Subsection]] = Field(
subsections: Optional[List[Subsection]] = Field(
default=None,
title="Titles and descriptions for each subsection of the Wikipedia page.",
)
citations: list[str] = Field(default_factory=list)
citations: List[str] = Field(default_factory=list)
@property
def as_str(self) -> str:
@@ -506,10 +506,10 @@ writer = writer_prompt | long_context_llm | StrOutputParser()
class ResearchState(TypedDict):
topic: str
outline: Outline
editors: list[Editor]
interview_results: list[InterviewState]
editors: List[Editor]
interview_results: List[InterviewState]
# The final sections output
sections: list[WikiSection]
sections: List[WikiSection]
article: str
@@ -1,6 +1,5 @@
from collections.abc import Sequence
from pathlib import Path
from typing import Annotated, TypedDict
from typing import Annotated, Sequence, TypedDict
from langchain_anthropic import ChatAnthropic
from langchain_community.tools.tavily_search import TavilySearchResults
@@ -1,6 +1,5 @@
from collections.abc import Sequence
from pathlib import Path
from typing import Annotated, TypedDict
from typing import Annotated, Sequence, TypedDict
from langchain_anthropic import ChatAnthropic
from langchain_community.tools.tavily_search import TavilySearchResults
+12 -16
View File
@@ -1,21 +1,17 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
[tool.poetry]
name = "langgraph-examples"
version = "0.1.0"
description = ""
authors = []
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
"langgraph-cli",
"langgraph-sdk",
]
[tool.uv.sources]
langgraph-cli = { path = "../cli", editable = true }
langgraph-sdk = { path = "../sdk_py", editable = true }
[tool.hatch.build]
packages = []
package-mode = false
[tool.poetry.dependencies]
python = ">=3.9"
langgraph-cli = {path = "../../cli", develop = true}
langgraph-sdk = {path = "../../sdk-py", develop = true}
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
+3 -4
View File
@@ -4,8 +4,7 @@ import os
import pathlib
import shutil
import sys
from collections.abc import Sequence
from typing import Callable, Optional
from typing import Callable, List, Optional, Sequence, Tuple
import click
import click.exceptions
@@ -742,7 +741,7 @@ def prepare_args_and_stdin(
image: Optional[str] = None,
# Like "langchain/langgraphjs-api" or "langchain/langgraph-api
base_image: Optional[str] = None,
) -> tuple[list[str], str]:
) -> Tuple[List[str], str]:
assert config_path.exists(), f"Config file not found: {config_path}"
# prepare args
stdin = langgraph_cli.docker.compose(
@@ -788,7 +787,7 @@ def prepare(
postgres_uri: Optional[str] = None,
image: Optional[str] = None,
base_image: Optional[str] = None,
) -> tuple[list[str], str]:
) -> Tuple[List[str], str]:
"""Prepare the arguments and stdin for running the LangGraph API server."""
config_json = langgraph_cli.config.validate_config_file(config_path)
# pull latest images
+2 -2
View File
@@ -2,13 +2,13 @@ import os
import shutil
import sys
from io import BytesIO
from typing import Optional
from typing import Dict, Optional
from urllib import error, request
from zipfile import ZipFile
import click
TEMPLATES: dict[str, dict[str, str]] = {
TEMPLATES: Dict[str, Dict[str, str]] = {
"New LangGraph Project": {
"description": "A simple, minimal chatbot with memory.",
"python": "https://github.com/langchain-ai/new-langgraph-project/archive/refs/heads/main.zip",
+2041
View File
File diff suppressed because it is too large Load Diff
+44 -43
View File
@@ -1,62 +1,63 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
[tool.poetry]
name = "langgraph-cli"
version = "0.2.10"
description = "CLI for interacting with LangGraph API"
authors = []
requires-python = ">=3.9"
readme = "README.md"
license = "MIT"
license-files = ['LICENSE']
dependencies = [
"click>=8.1.7",
"langgraph-sdk>=0.1.0 ; python_version >= '3.11'",
]
readme = "README.md"
repository = "https://www.github.com/langchain-ai/langgraph"
packages = [{ include = "langgraph_cli" }]
[project.optional-dependencies]
inmem = [
"langgraph-api>=0.1.20 ; python_version >= '3.11'",
"langgraph-runtime-inmem>=0.0.8 ; python_version >= '3.11'",
"python-dotenv>=0.8.0",
]
[project.urls]
Repository = "https://www.github.com/langchain-ai/langgraph"
[project.scripts]
[tool.poetry.scripts]
langgraph = "langgraph_cli.cli:cli"
[dependency-groups]
dev = [
"ruff",
"codespell",
"pytest",
"pytest-asyncio",
"pytest-mock",
"pytest-watch",
"mypy",
"msgspec",
]
[tool.poetry.dependencies]
python = ">=3.9"
click = "^8.1.7"
langgraph-api = { version = ">=0.1.20", optional = true, python = ">=3.11,<4.0" }
langgraph-runtime-inmem = { version = ">=0.0.8", optional = true, python = ">=3.11,<4.0" }
langgraph-sdk = { version = ">=0.1.0", optional = true, python = ">=3.11,<4.0" }
python-dotenv = { version = ">=0.8.0", optional = true }
[tool.uv]
default-groups = ['dev']
[tool.poetry.group.dev.dependencies]
ruff = "^0.6.2"
codespell = "^2.2.0"
pytest = "^7.2.1"
pytest-asyncio = "^0.21.1"
pytest-mock = "^3.11.1"
pytest-watch = "^4.2.0"
mypy = "^1.10.0"
msgspec = "^0.19.0"
[tool.hatch.build.targets.wheel]
include = ["langgraph_cli"]
[tool.poetry.extras]
inmem = ["langgraph-api", "langgraph-runtime-inmem", "python-dotenv"]
[tool.pytest.ini_options]
# --strict-markers will raise errors on unknown marks.
# https://docs.pytest.org/en/7.1.x/how-to/mark.html#raising-errors-on-unknown-marks
#
# https://docs.pytest.org/en/7.1.x/reference/reference.html
# --strict-config any warnings encountered while parsing the `pytest`
# section of the configuration file raise errors.
addopts = "--strict-markers --strict-config --durations=5 -vv"
asyncio_mode = "auto"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.ruff]
lint.select = [
"E", # pycodestyle
"F", # Pyflakes
"UP", # pyupgrade
"B", # flake8-bugbear
"I", # isort
# pycodestyle
"E",
# Pyflakes
"F",
# pyupgrade
"UP",
# flake8-bugbear
"B",
# isort
"I",
]
lint.ignore = ["E501", "B008"]
+1 -2
View File
@@ -1,7 +1,6 @@
import asyncio
import os
from collections.abc import Sequence
from typing import Annotated, TypedDict
from typing import Annotated, Sequence, TypedDict
from langchain_core.language_models.fake_chat_models import FakeListChatModel
from langchain_core.messages import BaseMessage, HumanMessage, ToolMessage
-1607
View File
File diff suppressed because it is too large Load Diff
+18 -21
View File
@@ -11,28 +11,25 @@ all: help
OUTPUT ?= out/benchmark.json
install: ## Install dependencies
uv sync --frozen --all-extras --all-packages --group dev
benchmark: .uv
benchmark:
mkdir -p out
rm -f $(OUTPUT)
uv run python -m bench -o $(OUTPUT) --rigorous
poetry run python -m bench -o $(OUTPUT) --rigorous
benchmark-fast:
mkdir -p out
rm -f $(OUTPUT)
uv run python -m bench -o $(OUTPUT) --fast
poetry run python -m bench -o $(OUTPUT) --fast
GRAPH ?= bench/fanout_to_subgraph.py
profile:
mkdir -p out
sudo uv run py-spy record -g -o out/profile.svg -- python $(GRAPH)
sudo poetry run py-spy record -g -o out/profile.svg -- python $(GRAPH)
# Run unit tests and generate a coverage report.
coverage:
uv run pytest --cov \
poetry run pytest --cov \
--cov-config=.coveragerc \
--cov-report xml \
--cov-report term-missing:skip-covered
@@ -44,7 +41,7 @@ stop-postgres:
docker compose -f tests/compose-postgres.yml down -v
start-dev-server:
uv run langgraph dev --config tests/example_app/langgraph.json --no-browser &
poetry run langgraph dev --config tests/example_app/langgraph.json --no-browser &
@echo "Dev server started."
@echo "Dev server PID: $$!" > .devserver.pid
@@ -61,7 +58,7 @@ TEST ?= .
test:
make start-postgres &&\
make start-dev-server &&\
uv run pytest $(TEST); \
poetry run pytest $(TEST); \
EXIT_CODE=$$?; \
make stop-postgres; \
make stop-dev-server; \
@@ -70,14 +67,14 @@ test:
test_parallel:
make start-postgres &&\
make start-dev-server &&\
uv run pytest -n auto --dist worksteal $(TEST); \
poetry run pytest -n auto --dist worksteal $(TEST); \
EXIT_CODE=$$?; \
make stop-postgres; \
make stop-dev-server; \
exit $$EXIT_CODE
integration_tests:
uv run pytest integration_tests
poetry run pytest integration_tests
WORKERS ?= auto
XDIST_ARGS := $(if $(WORKERS),-n $(WORKERS) --dist worksteal,)
@@ -89,7 +86,7 @@ XDIST_ARGS := $(if $(WORKERS),-x $(XDIST_ARGS),)
test_watch:
make start-postgres &&\
make start-dev-server &&\
uv run ptw . -- --ff -vv $(XDIST_ARGS) $(MAXFAIL_ARGS) $(TEST); \
poetry run ptw . -- --ff -vv $(XDIST_ARGS) $(MAXFAIL_ARGS) $(TEST); \
EXIT_CODE=$$?; \
make stop-postgres; \
make stop-dev-server; \
@@ -113,21 +110,21 @@ lint_tests: PYTHON_FILES=tests
lint_tests: MYPY_CACHE=.mypy_cache_test
lint lint_diff lint_package lint_tests:
uv run ruff check .
[ "$(PYTHON_FILES)" = "" ] || uv run ruff format $(PYTHON_FILES) --diff
[ "$(PYTHON_FILES)" = "" ] || uv run ruff check --select I $(PYTHON_FILES)
poetry run ruff check .
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff format $(PYTHON_FILES) --diff
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff check --select I $(PYTHON_FILES)
[ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE)
[ "$(PYTHON_FILES)" = "" ] || uv run mypy langgraph --cache-dir $(MYPY_CACHE)
[ "$(PYTHON_FILES)" = "" ] || poetry run mypy langgraph --cache-dir $(MYPY_CACHE)
format format_diff:
uv run ruff format $(PYTHON_FILES)
uv run ruff check --select I --fix $(PYTHON_FILES)
poetry run ruff format $(PYTHON_FILES)
poetry run ruff check --select I --fix $(PYTHON_FILES)
spell_check:
uv run codespell --toml pyproject.toml
poetry run codespell --toml pyproject.toml
spell_fix:
uv run codespell --toml pyproject.toml -w
poetry run codespell --toml pyproject.toml -w
######################
+4 -2
View File
@@ -2486,6 +2486,7 @@ class Pregel(PregelProtocol):
CONFIG_KEY_RUNNER_SUBMIT, weakref.WeakMethod(loop.submit)
),
put_writes=weakref.WeakMethod(loop.put_writes),
schedule_task=weakref.WeakMethod(loop.accept_push),
node_finished=config[CONF].get(CONFIG_KEY_NODE_FINISHED),
)
# enable subgraph streaming
@@ -2528,7 +2529,7 @@ class Pregel(PregelProtocol):
[t for t in loop.tasks.values() if not t.writes],
timeout=self.step_timeout,
get_waiter=get_waiter,
schedule_task=loop.accept_push,
match_cached_writes=loop.match_cached_writes,
):
# emit output
yield from output()
@@ -2798,6 +2799,7 @@ class Pregel(PregelProtocol):
CONFIG_KEY_RUNNER_SUBMIT, weakref.WeakMethod(loop.submit)
),
put_writes=weakref.WeakMethod(loop.put_writes),
schedule_task=weakref.WeakMethod(loop.accept_push),
use_astream=do_stream,
node_finished=config[CONF].get(CONFIG_KEY_NODE_FINISHED),
)
@@ -2831,7 +2833,7 @@ class Pregel(PregelProtocol):
[t for t in loop.tasks.values() if not t.writes],
timeout=self.step_timeout,
get_waiter=get_waiter,
schedule_task=loop.aaccept_push,
match_cached_writes=loop.amatch_cached_writes,
):
# emit output
for o in output():
-1
View File
@@ -19,7 +19,6 @@ from typing import (
overload,
)
# meaningless change to trigger tests
from langchain_core.callbacks import Callbacks
from langchain_core.callbacks.manager import AsyncParentRunManager, ParentRunManager
from langchain_core.runnables.config import RunnableConfig
+7 -13
View File
@@ -108,12 +108,7 @@ def draw_graph(
for w in task.writers:
# apply regular writes
if isinstance(w, ChannelWrite):
empty_input = (
cast(BaseChannel, specs["__root__"]).ValueType()
if "__root__" in specs
else None
)
w.invoke(empty_input, task.config)
w.invoke(None, task.config)
# apply conditional writes declared for static analysis, only once
if w not in static_seen:
static_seen.add(w)
@@ -125,7 +120,7 @@ def draw_graph(
edges.add((task.name, t[0], True, t[2]))
writes = [t for t in writes if t[0] != END]
conditionals.update(
{(task.name, t[0], t[1] or None): t[2] for t in writes}
{(task.name, *t[:2]): t[2] for t in writes}
)
task.config[CONF][CONFIG_KEY_SEND]([t[:2] for t in writes])
# collect sources
@@ -133,8 +128,8 @@ def draw_graph(
task.name: {
(
w[0],
(task.name, w[0], w[1] or None) in conditionals,
conditionals.get((task.name, w[0], w[1] or None)),
(task.name, *w) in conditionals,
conditionals.get((task.name, *w)),
)
for w in task.writes
}
@@ -234,10 +229,9 @@ def draw_graph(
first, last = graph.extend(subgraph, prefix=name)
for idx, edge in enumerate(graph.edges):
if edge.source == name:
edge = edge.copy(source=cast(Node, last).id)
if edge.target == name:
edge = edge.copy(target=cast(Node, first).id)
graph.edges[idx] = edge
graph.edges[idx] = edge.copy(source=cast(Node, last).id)
elif edge.target == name:
graph.edges[idx] = edge.copy(target=cast(Node, first).id)
return graph
+1 -1
View File
@@ -25,7 +25,7 @@ T = TypeVar("T")
class Submit(Protocol[P, T]):
def __call__( # type: ignore[valid-type]
def __call__(
self,
fn: Callable[P, T],
*args: P.args,
-14
View File
@@ -1079,13 +1079,6 @@ class SyncPregelLoop(PregelLoop, AbstractContextManager):
matched.append(task)
return matched
def accept_push(
self, task: PregelExecutableTask, write_idx: int, call: Optional[Call] = None
) -> Optional[PregelExecutableTask]:
if pushed := super().accept_push(task, write_idx, call):
self.match_cached_writes()
return pushed
def put_writes(self, task_id: str, writes: WritesT) -> None:
"""Put writes for a task, to be read by the next tick."""
super().put_writes(task_id, writes)
@@ -1275,13 +1268,6 @@ class AsyncPregelLoop(PregelLoop, AbstractAsyncContextManager):
matched.append(task)
return matched
async def aaccept_push(
self, task: PregelExecutableTask, write_idx: int, call: Optional[Call] = None
) -> Optional[PregelExecutableTask]:
if pushed := super().accept_push(task, write_idx, call):
await self.amatch_cached_writes()
return pushed
def put_writes(self, task_id: str, writes: WritesT) -> None:
"""Put writes for a task, to be read by the next tick."""
super().put_writes(task_id, writes)
+6 -6
View File
@@ -249,12 +249,12 @@ class PregelNode(Runnable):
)
def join(self, channels: Sequence[str]) -> PregelNode:
assert isinstance(channels, list) or isinstance(channels, tuple), (
"channels must be a list or tuple"
)
assert isinstance(self.channels, dict), (
"all channels must be named when using .join()"
)
assert isinstance(channels, list) or isinstance(
channels, tuple
), "channels must be a list or tuple"
assert isinstance(
self.channels, dict
), "all channels must be named when using .join()"
return self.copy(
update=dict(
channels={
+120 -139
View File
@@ -39,7 +39,7 @@ from langgraph.types import (
PregelScratchpad,
RetryPolicy,
)
from langgraph.utils.future import chain_future, run_coroutine_threadsafe
from langgraph.utils.future import chain_future
F = TypeVar("F", concurrent.futures.Future, asyncio.Future)
E = TypeVar("E", threading.Event, asyncio.Event)
@@ -119,6 +119,12 @@ class PregelRunner:
*,
submit: weakref.ref[Submit],
put_writes: weakref.ref[Callable[[str, Sequence[tuple[str, Any]]], None]],
schedule_task: weakref.ref[
Callable[
[PregelExecutableTask, int, Optional[Call]],
Optional[PregelExecutableTask],
]
],
use_astream: bool = False,
node_finished: Optional[Callable[[str], None]] = None,
) -> None:
@@ -126,6 +132,7 @@ class PregelRunner:
self.put_writes = put_writes
self.use_astream = use_astream
self.node_finished = node_finished
self.schedule_task = schedule_task
def tick(
self,
@@ -135,10 +142,9 @@ class PregelRunner:
timeout: Optional[float] = None,
retry_policy: Optional[Sequence[RetryPolicy]] = None,
get_waiter: Optional[Callable[[], concurrent.futures.Future[None]]] = None,
schedule_task: Callable[
[PregelExecutableTask, int, Optional[Call]],
Optional[PregelExecutableTask],
],
match_cached_writes: Optional[
Callable[[], Sequence[PregelExecutableTask]]
] = None,
) -> Iterator[None]:
tasks = tuple(tasks)
futures = FuturesDict(
@@ -163,7 +169,8 @@ class PregelRunner:
weakref.ref(t),
retry=retry_policy,
futures=weakref.ref(futures),
schedule_task=schedule_task,
schedule_task=self.schedule_task,
match_cached_writes=match_cached_writes,
submit=self.submit,
reraise=reraise,
),
@@ -205,7 +212,8 @@ class PregelRunner:
weakref.ref(t),
retry=retry_policy,
futures=weakref.ref(futures),
schedule_task=schedule_task,
schedule_task=self.schedule_task,
match_cached_writes=match_cached_writes,
submit=self.submit,
reraise=reraise,
),
@@ -269,10 +277,9 @@ class PregelRunner:
timeout: Optional[float] = None,
retry_policy: Optional[Sequence[RetryPolicy]] = None,
get_waiter: Optional[Callable[[], asyncio.Future[None]]] = None,
schedule_task: Callable[
[PregelExecutableTask, int, Optional[Call]],
Awaitable[Optional[PregelExecutableTask]],
],
match_cached_writes: Optional[
Callable[[], Awaitable[Sequence[PregelExecutableTask]]]
] = None,
) -> AsyncIterator[None]:
loop = asyncio.get_event_loop()
tasks = tuple(tasks)
@@ -300,7 +307,8 @@ class PregelRunner:
stream=self.use_astream,
retry=retry_policy,
futures=weakref.ref(futures),
schedule_task=schedule_task,
schedule_task=self.schedule_task,
match_cached_writes=match_cached_writes,
submit=self.submit,
reraise=reraise,
loop=loop,
@@ -347,7 +355,8 @@ class PregelRunner:
retry=retry_policy,
stream=self.use_astream,
futures=weakref.ref(futures),
schedule_task=schedule_task,
schedule_task=self.schedule_task,
match_cached_writes=match_cached_writes,
submit=self.submit,
reraise=reraise,
loop=loop,
@@ -526,9 +535,12 @@ def _call(
cache_policy: Optional[CachePolicy] = None,
callbacks: Callbacks = None,
futures: weakref.ref[FuturesDict],
schedule_task: Callable[
[PregelExecutableTask, int, Optional[Call]], Optional[PregelExecutableTask]
schedule_task: weakref.ref[
Callable[
[PregelExecutableTask, int, Optional[Call]], Optional[PregelExecutableTask]
]
],
match_cached_writes: Optional[Callable[[], Sequence[PregelExecutableTask]]],
submit: weakref.ref[Submit],
reraise: bool,
) -> concurrent.futures.Future[Any]:
@@ -539,11 +551,13 @@ def _call(
# schedule PUSH tasks, collect futures
scratchpad: PregelScratchpad = task().config[CONF][CONFIG_KEY_SCRATCHPAD] # type: ignore[union-attr]
# schedule the next task, if the callback returns one
if next_task := schedule_task(
if next_task := schedule_task()( # type: ignore[misc]
task(), # type: ignore[arg-type]
scratchpad.call_counter(),
Call(func, input, retry=retry, cache_policy=cache_policy, callbacks=callbacks),
):
if match_cached_writes:
match_cached_writes()
if fut := next(
(
f
@@ -581,6 +595,7 @@ def _call(
retry=retry,
callbacks=callbacks,
schedule_task=schedule_task,
match_cached_writes=match_cached_writes,
submit=submit,
reraise=reraise,
),
@@ -607,140 +622,106 @@ def _acall(
callbacks: Callbacks = None,
# injected dependencies
futures: weakref.ref[FuturesDict],
schedule_task: Callable[
[PregelExecutableTask, int, Optional[Call]],
Awaitable[Optional[PregelExecutableTask]],
schedule_task: weakref.ref[
Callable[
[PregelExecutableTask, int, Optional[Call]], Optional[PregelExecutableTask]
]
],
match_cached_writes: Optional[
Callable[[], Awaitable[Sequence[PregelExecutableTask]]]
] = None,
submit: weakref.ref[Submit],
loop: asyncio.AbstractEventLoop,
reraise: bool = False,
stream: bool = False,
) -> Union[asyncio.Future[Any], concurrent.futures.Future[Any]]:
fut: Optional[asyncio.Future] = None
# schedule PUSH tasks, collect futures
scratchpad: PregelScratchpad = task().config[CONF][CONFIG_KEY_SCRATCHPAD] # type: ignore[union-attr]
# schedule the next task, if the callback returns one
if next_task := schedule_task()( # type: ignore[misc]
task(), # type: ignore[arg-type]
scratchpad.call_counter(),
Call(func, input, retry=retry, cache_policy=cache_policy, callbacks=callbacks),
):
if fut := next(
(
f
for f, t in futures().items() # type: ignore[union-attr]
if t is not None and t == next_task.id
),
None,
):
# if the parent task was retried,
# the next task might already be running
pass
elif next_task.writes:
# if it already ran, return the result
fut = asyncio.Future(loop=loop)
ret = next((v for c, v in next_task.writes if c == RETURN), MISSING)
if ret is not MISSING:
fut.set_result(ret)
elif exc := next((v for c, v in next_task.writes if c == ERROR), None):
fut.set_exception(
exc if isinstance(exc, BaseException) else Exception(exc)
)
else:
fut.set_result(None)
futures()[fut] = next_task # type: ignore[index]
else:
# schedule the next task
fut = cast(
asyncio.Future,
submit()( # type: ignore[misc]
arun_with_retry,
next_task,
retry,
stream=stream,
match_cached_writes=match_cached_writes,
configurable={
CONFIG_KEY_CALL: partial(
_acall,
weakref.ref(next_task),
stream=stream,
futures=futures,
schedule_task=schedule_task,
match_cached_writes=match_cached_writes,
submit=submit,
loop=loop,
reraise=reraise,
),
},
__name__=task().name, # type: ignore[union-attr]
__cancel_on_exit__=True,
__reraise_on_exit__=reraise,
# starting a new task in the next tick ensures
# updates from this tick are committed/streamed first
__next_tick__=True,
),
)
futures()[fut] = next_task # type: ignore[index]
fut = cast(Union[asyncio.Future, concurrent.futures.Future], fut)
# return a chained future to ensure commit() callback is called
# before the returned future is resolved, to ensure stream order etc
try:
in_async = asyncio.current_task() is not None
except RuntimeError:
in_async = False
# if in async context return an async future, otherwise return a sync future
# if in async context return an async future
# otherwise return a chained sync future
if in_async:
fut: Union[asyncio.Future[Any], concurrent.futures.Future[Any]] = (
asyncio.Future(loop=loop)
)
else:
fut = concurrent.futures.Future()
# schedule the next task
run_coroutine_threadsafe(
_acall_impl(
fut,
task,
func,
input,
retry=retry,
cache_policy=cache_policy,
callbacks=callbacks,
futures=futures,
schedule_task=schedule_task,
submit=submit,
loop=loop,
reraise=reraise,
stream=stream,
),
loop,
lazy=False,
)
return fut
async def _acall_impl(
destination: Union[asyncio.Future[Any], concurrent.futures.Future[Any]],
task: weakref.ref[PregelExecutableTask],
func: Callable[[Any], Union[Awaitable[Any], Any]],
input: Any,
*,
retry: Optional[Sequence[RetryPolicy]] = None,
cache_policy: Optional[CachePolicy] = None,
callbacks: Callbacks = None,
# injected dependencies
futures: weakref.ref[FuturesDict[asyncio.Future, asyncio.Event]],
schedule_task: Callable[
[PregelExecutableTask, int, Optional[Call]],
Awaitable[Optional[PregelExecutableTask]],
],
submit: weakref.ref[Submit],
loop: asyncio.AbstractEventLoop,
reraise: bool = False,
stream: bool = False,
) -> None:
try:
fut: Optional[asyncio.Future] = None
# schedule PUSH tasks, collect futures
scratchpad: PregelScratchpad = task().config[CONF][CONFIG_KEY_SCRATCHPAD] # type: ignore[union-attr]
# schedule the next task, if the callback returns one
if next_task := await schedule_task(
task(), # type: ignore[arg-type]
scratchpad.call_counter(),
Call(
func, input, retry=retry, cache_policy=cache_policy, callbacks=callbacks
),
):
if fut := next(
(
f
for f, t in futures().items() # type: ignore[union-attr]
if t is not None and t == next_task.id
),
None,
):
# if the parent task was retried,
# the next task might already be running
pass
elif next_task.writes:
# if it already ran, return the result
fut = asyncio.Future(loop=loop)
ret = next((v for c, v in next_task.writes if c == RETURN), MISSING)
if ret is not MISSING:
fut.set_result(ret)
elif exc := next((v for c, v in next_task.writes if c == ERROR), None):
fut.set_exception(
exc if isinstance(exc, BaseException) else Exception(exc)
)
else:
fut.set_result(None)
futures()[fut] = next_task # type: ignore[index]
else:
# schedule the next task
fut = cast(
asyncio.Future,
submit()( # type: ignore[misc]
arun_with_retry,
next_task,
retry,
stream=stream,
configurable={
CONFIG_KEY_CALL: partial(
_acall,
weakref.ref(next_task),
stream=stream,
futures=futures,
schedule_task=schedule_task,
submit=submit,
loop=loop,
reraise=reraise,
),
},
__name__=task().name, # type: ignore[union-attr]
__cancel_on_exit__=True,
__reraise_on_exit__=reraise,
# starting a new task in the next tick ensures
# updates from this tick are committed/streamed first
__next_tick__=True,
),
)
futures()[fut] = next_task # type: ignore[index]
if fut is not None:
chain_future(fut, destination)
if isinstance(fut, asyncio.Task):
sfut: Union[asyncio.Future[Any], concurrent.futures.Future[Any]] = (
asyncio.Future(loop=loop)
)
loop.call_soon_threadsafe(chain_future, fut, sfut)
return sfut
else:
destination.set_exception(RuntimeError("Task not scheduled"))
except Exception as exc:
destination.set_exception(exc)
# already wrapped in a future
return fut
else:
sfut = concurrent.futures.Future()
loop.call_soon_threadsafe(chain_future, fut, sfut)
return sfut
+4112
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
[virtualenvs]
in-project = true
[installer]
modern-installation = false
+51 -58
View File
@@ -1,65 +1,46 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
[tool.poetry]
name = "langgraph"
version = "0.4.5"
version = "0.4.3"
description = "Building stateful, multi-actor applications with LLMs"
authors = []
requires-python = ">=3.9"
readme = "README.md"
license = "MIT"
license-files = ['LICENSE']
dependencies = [
"langchain-core>=0.1",
"langgraph-checkpoint>=2.0.26",
"langgraph-sdk>=0.1.42",
"langgraph-prebuilt>=0.1.8",
"xxhash>=3.5.0",
"pydantic>=2.7.4",
]
readme = "README.md"
repository = "https://www.github.com/langchain-ai/langgraph"
[project.urls]
Repository = "https://www.github.com/langchain-ai/langgraph"
[tool.poetry.dependencies]
python = ">=3.9"
langchain-core = { version = ">=0.1", python = "<4.0" }
langgraph-checkpoint = "^2.0.10"
langgraph-sdk = { version = ">=0.1.42", python = "<4.0" }
langgraph-prebuilt = { version = ">=0.1.8", python = "<4.0" }
xxhash = "^3.5.0"
pydantic = { version = ">=2.7.4"}
[dependency-groups]
dev = [
"pytest>=8.3.2",
"pytest-cov>=4.0.0",
"pytest-dotenv>=0.5.2",
"pytest-mock>=3.10.0",
"syrupy>=4.0.2",
"httpx>=0.26.0",
'pytest-watcher>=0.4.1',
"mypy>=1.6.0",
"ruff>=0.6.2",
"jupyter>=1.0.0",
"pytest-xdist[psutil]>=3.6.1",
"pytest-repeat>=0.9.3",
"langgraph-prebuilt",
"langgraph-checkpoint",
"langgraph-checkpoint-sqlite",
"langgraph-checkpoint-postgres",
"langgraph-sdk",
'psycopg[binary]>=3.0.0; python_version >= "3.10"',
"uvloop==0.21.0beta1",
"pyperf>=2.7.0",
"py-spy>=0.3.14",
"types-requests>=2.32.0.20240914",
"pycryptodome>=3.21.0",
"langgraph-cli[inmem]>=0.2.8",
]
[tool.uv]
default-groups = ['dev']
[tool.uv.sources]
langgraph-prebuilt = { path = "../prebuilt", editable = true }
langgraph-checkpoint = { path = "../checkpoint", editable = true }
langgraph-checkpoint-sqlite = { path = "../checkpoint-sqlite", editable = true }
langgraph-checkpoint-postgres = { path = "../checkpoint-postgres", editable = true }
langgraph-sdk = { path = "../sdk-py", editable = true }
[tool.poetry.group.dev.dependencies]
pytest = "^8.3.2"
pytest-cov = "^4.0.0"
pytest-dotenv = "^0.5.2"
pytest-mock = "^3.10.0"
syrupy = "^4.0.2"
httpx = "^0.26.0"
pytest-watcher = { version = ">=0.4.1", python = "<4.0" }
mypy = "^1.6.0"
ruff = "^0.6.2"
jupyter = "^1.0.0"
pytest-xdist = {extras = ["psutil"], version = "^3.6.1"}
pytest-repeat = "^0.9.3"
langgraph-prebuilt = {path = "../prebuilt", develop = true}
langgraph-checkpoint = {path = "../checkpoint", develop = true}
langgraph-checkpoint-sqlite = {path = "../checkpoint-sqlite", develop = true}
langgraph-checkpoint-postgres = {path = "../checkpoint-postgres", develop = true}
langgraph-sdk = {path = "../sdk-py", develop = true}
psycopg = {extras = ["binary"], version = ">=3.0.0", python = ">=3.10"}
uvloop = "0.21.0beta1"
pyperf = "^2.7.0"
py-spy = "^0.3.14"
types-requests = "^2.32.0.20240914"
pycryptodome = "^3.21.0"
langgraph-cli = {extras = ["inmem"], version = "^0.2.8"}
[tool.ruff]
lint.select = [ "E", "F", "I", "TID251", "UP" ]
@@ -98,8 +79,20 @@ now = true
delay = 0.1
patterns = ["*.py"]
[tool.hatch.build.targets.wheel]
packages = ["langgraph"]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.pytest.ini_options]
# --strict-markers will raise errors on unknown marks.
# https://docs.pytest.org/en/7.1.x/how-to/mark.html#raising-errors-on-unknown-marks
#
# https://docs.pytest.org/en/7.1.x/reference/reference.html
# --strict-config any warnings encountered while parsing the `pytest`
# section of the configuration file raise errors.
#
# https://github.com/tophat/syrupy
# --snapshot-warn-unused Prints a warning on unused snapshots rather than fail the test suite.
addopts = "--full-trace --strict-markers --strict-config --durations=5 --snapshot-warn-unused"
# Registering custom markers.
# https://docs.pytest.org/en/7.1.x/example/markers.html#registering-markers
@@ -396,123 +396,6 @@
'''
# ---
# name: test_get_graph_root_channel
'''
{
"nodes": [
{
"id": "__start__",
"type": "runnable",
"data": {
"id": [
"langchain",
"schema",
"runnable",
"RunnablePassthrough"
],
"name": "__start__"
}
},
{
"id": "child",
"type": "runnable",
"data": {
"id": [
"langgraph",
"graph",
"state",
"CompiledStateGraph"
],
"name": "child"
}
},
{
"id": "__end__"
}
],
"edges": [
{
"source": "__start__",
"target": "child"
},
{
"source": "child",
"target": "__end__"
}
]
}
'''
# ---
# name: test_get_graph_root_channel.1
'''
graph TD;
__start__ --> child;
child --> __end__;
'''
# ---
# name: test_get_graph_self_loop
'''
{
"nodes": [
{
"id": "__start__",
"type": "runnable",
"data": {
"id": [
"langchain",
"schema",
"runnable",
"RunnablePassthrough"
],
"name": "__start__"
}
},
{
"id": "worker_node",
"type": "runnable",
"data": {
"id": [
"langgraph",
"utils",
"runnable",
"RunnableCallable"
],
"name": "worker_node"
}
},
{
"id": "__end__"
}
],
"edges": [
{
"source": "__start__",
"target": "worker_node"
},
{
"source": "worker_node",
"target": "__end__",
"conditional": true
},
{
"source": "worker_node",
"target": "worker_node",
"conditional": true
}
]
}
'''
# ---
# name: test_get_graph_self_loop.1
'''
graph TD;
__start__ --> worker_node;
worker_node -.-> __end__;
worker_node -.-> worker_node;
'''
# ---
# name: test_in_one_fan_out_state_graph_defer_node[memory-False]
'''
graph TD;
@@ -1582,9 +1582,9 @@ def test_migrate_checkpoints(source: str, target: str) -> None:
migrated["versions_seen"][c][v].split(".")[0]
)
# check that the migrated checkpoint matches the target checkpoint
assert migrated == target_checkpoint.checkpoint, (
f"Checkpoint mismatch at index {idx}"
)
assert (
migrated == target_checkpoint.checkpoint
), f"Checkpoint mismatch at index {idx}"
@NEEDS_CONTEXTVARS
+3 -3
View File
@@ -2803,9 +2803,9 @@ def test_state_graph_packets(
# Define decision-making logic
def should_continue(data: dict) -> str:
assert isinstance(data["session"], httpx.Client)
assert data["something_extra"] == "hi there", (
"nodes can pass extra data to their cond edges, which isn't saved in state"
)
assert (
data["something_extra"] == "hi there"
), "nodes can pass extra data to their cond edges, which isn't saved in state"
# Logic to decide whether to continue in the loop or exit
if tool_calls := data["messages"][-1].tool_calls:
return [Send("tools", tool_call) for tool_call in tool_calls]
-42
View File
@@ -8727,45 +8727,3 @@ def test_get_graph_loop(snapshot: SnapshotAssertion) -> None:
app = workflow.compile()
assert json.dumps(app.get_graph().to_json(), indent=2) == snapshot
assert app.get_graph().draw_mermaid(with_styles=False) == snapshot
def test_get_graph_self_loop(snapshot: SnapshotAssertion) -> None:
import random
subgraph_builder = StateGraph(MessagesState)
subgraph_builder.add_node("agent", lambda x: x)
subgraph_builder.add_edge(START, "agent")
subgraph = subgraph_builder.compile()
def worker_node(state: MessagesState) -> Command[Literal["worker_node", "__end__"]]:
subgraph_result = subgraph.invoke(state)
if random.choice([True, False]):
next_node_name = "worker_node"
else:
next_node_name = END
return Command(update=subgraph_result, goto=next_node_name)
self_loop_builder = StateGraph(MessagesState)
self_loop_builder.add_node("worker_node", worker_node)
self_loop_builder.add_edge(START, "worker_node")
self_loop_graph = self_loop_builder.compile()
assert json.dumps(self_loop_graph.get_graph().to_json(), indent=2) == snapshot
assert self_loop_graph.get_graph().draw_mermaid(with_styles=False) == snapshot
def test_get_graph_root_channel(snapshot: SnapshotAssertion) -> None:
child_builder = StateGraph(list)
child_builder.add_node("child_node", lambda x: x)
child_builder.add_edge(START, "child_node")
child_graph = child_builder.compile()
graph_builder = StateGraph(list)
graph_builder.add_node("child", child_graph)
graph_builder.add_edge(START, "child")
graph = graph_builder.compile()
assert json.dumps(graph.get_graph().to_json(), indent=2) == snapshot
assert graph.get_graph().draw_mermaid(with_styles=False) == snapshot
+1 -3
View File
@@ -863,9 +863,7 @@ async def test_ainvoke():
assert result == {"messages": [{"type": "human", "content": "world"}]}
@pytest.mark.skip(
"Unskip this test to manually test the LangGraph Platform integration"
)
@pytest.mark.skip("Unskip this test to manually test the LangGraph Platform integration")
@pytest.mark.anyio
async def test_langgraph_cloud_integration():
from langgraph_sdk.client import get_client, get_sync_client
-3460
View File
File diff suppressed because it is too large Load Diff
+10 -10
View File
@@ -16,13 +16,13 @@ stop-postgres:
TEST ?= .
test:
make start-postgres && uv run pytest $(TEST); \
make start-postgres && poetry run pytest $(TEST); \
EXIT_CODE=$$?; \
make stop-postgres; \
exit $$EXIT_CODE
test_watch:
make start-postgres && uv run ptw $(TEST); \
make start-postgres && poetry run ptw $(TEST); \
EXIT_CODE=$$?; \
make stop-postgres; \
exit $$EXIT_CODE
@@ -41,21 +41,21 @@ lint_tests: PYTHON_FILES=tests
lint_tests: MYPY_CACHE=.mypy_cache_test
lint lint_diff lint_package lint_tests:
uv run ruff check .
[ "$(PYTHON_FILES)" = "" ] || uv run ruff format $(PYTHON_FILES) --diff
[ "$(PYTHON_FILES)" = "" ] || uv run ruff check --select I $(PYTHON_FILES)
poetry run ruff check .
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff format $(PYTHON_FILES) --diff
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff check --select I $(PYTHON_FILES)
[ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE)
[ "$(PYTHON_FILES)" = "" ] || uv run mypy langgraph --cache-dir $(MYPY_CACHE)
[ "$(PYTHON_FILES)" = "" ] || poetry run mypy langgraph --cache-dir $(MYPY_CACHE)
format format_diff:
uv run ruff format $(PYTHON_FILES)
uv run ruff check --select I --fix $(PYTHON_FILES)
poetry run ruff format $(PYTHON_FILES)
poetry run ruff check --select I --fix $(PYTHON_FILES)
spell_check:
uv run codespell --toml pyproject.toml
poetry run codespell --toml pyproject.toml
spell_fix:
uv run codespell --toml pyproject.toml -w
poetry run codespell --toml pyproject.toml -w
######################
+1599
View File
File diff suppressed because it is too large Load Diff
+31 -40
View File
@@ -1,55 +1,46 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
[tool.poetry]
name = "langgraph-prebuilt"
version = "0.1.8"
description = "Library with high-level APIs for creating and executing LangGraph agents and tools."
authors = []
requires-python = ">=3.9"
readme = "README.md"
license = "MIT"
license-files = ['LICENSE']
dependencies = [
"langgraph-checkpoint>=2.0.10",
"langchain-core>=0.3.22",
]
readme = "README.md"
repository = "https://www.github.com/langchain-ai/langgraph"
packages = [{ include = "langgraph" }]
[project.urls]
Repository = "https://www.github.com/langchain-ai/langgraph"
[tool.poetry.dependencies]
python = ">=3.9"
langgraph-checkpoint = "^2.0.10"
langchain-core = { version = ">=0.3.22", python = "<4.0" }
[dependency-groups]
dev = [
"ruff",
"codespell",
"pytest",
"pytest-asyncio",
"pytest-mock",
"pytest-watcher",
"mypy",
"langgraph",
"langgraph-checkpoint",
"langgraph-checkpoint-sqlite",
"langgraph-checkpoint-postgres",
]
[tool.uv]
default-groups = ['dev']
[tool.uv.sources]
langgraph = { path = "../langgraph", editable = true }
langgraph-checkpoint = { path = "../checkpoint", editable = true }
langgraph-checkpoint-sqlite = { path = "../checkpoint-sqlite", editable = true }
langgraph-checkpoint-postgres = { path = "../checkpoint-postgres", editable = true }
[tool.hatch.build.targets.wheel]
include = ["langgraph"]
[tool.poetry.group.dev.dependencies]
ruff = "^0.6.2"
codespell = "^2.2.0"
pytest = "^7.2.1"
pytest-asyncio = "^0.21.1"
pytest-mock = "^3.11.1"
pytest-watcher = { version = ">=0.4.1", python = "<4.0" }
mypy = "^1.10.0"
langgraph = {path = "../langgraph", develop = true}
langgraph-checkpoint = {path = "../checkpoint", develop = true}
langgraph-checkpoint-sqlite = {path = "../checkpoint-sqlite", develop = true}
langgraph-checkpoint-postgres = {path = "../checkpoint-postgres", develop = true}
[tool.pytest.ini_options]
# --strict-markers will raise errors on unknown marks.
# https://docs.pytest.org/en/7.1.x/how-to/mark.html#raising-errors-on-unknown-marks
#
# https://docs.pytest.org/en/7.1.x/reference/reference.html
# --strict-config any warnings encountered while parsing the `pytest`
# section of the configuration file raise errors.
addopts = "--strict-markers --strict-config --durations=5 -vv"
asyncio_mode = "auto"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.ruff]
lint.select = [ "E", "F", "I", "TID251" ]
lint.ignore = [ "E501" ]
+6 -6
View File
@@ -883,9 +883,9 @@ def test_tool_node_inject_store() -> None:
for result in (node_result, graph_result):
result["messages"][-1]
tool_message = result["messages"][-1]
assert tool_message.content == "Some val: 1, store val: bar", (
f"Failed for tool={tool_name}"
)
assert (
tool_message.content == "Some val: 1, store val: bar"
), f"Failed for tool={tool_name}"
tool_call = {
"name": "tool3",
@@ -899,9 +899,9 @@ def test_tool_node_inject_store() -> None:
for result in (node_result, graph_result):
result["messages"][-1]
tool_message = result["messages"][-1]
assert tool_message.content == "Some val: 1, store val: bar, state val: baz", (
f"Failed for tool={tool_name}"
)
assert (
tool_message.content == "Some val: 1, store val: bar, state val: baz"
), f"Failed for tool={tool_name}"
# test injected store without passing store to compiled graph
failing_graph = builder.compile()
-1379
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -13,13 +13,13 @@ stop-services:
TEST_PATH ?= .
test:
make start-services && uv run pytest $(TEST_PATH); \
make start-services && poetry run pytest $(TEST_PATH); \
EXIT_CODE=$$?; \
make stop-services; \
exit $$EXIT_CODE
test_watch:
make start-services && uv run ptw . -- -x $(TEST_PATH); \
make start-services && poetry run ptw . -- -x $(TEST_PATH); \
EXIT_CODE=$$?; \
make stop-services; \
exit $$EXIT_CODE
@@ -38,11 +38,11 @@ lint_tests: PYTHON_FILES=tests
lint_tests: MYPY_CACHE=.mypy_cache_test
lint lint_diff lint_package lint_tests:
uv run ruff check .
[ "$(PYTHON_FILES)" = "" ] || uv run ruff format $(PYTHON_FILES) --diff
[ "$(PYTHON_FILES)" = "" ] || uv run ruff check --select I $(PYTHON_FILES)
[ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE) || uv run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
poetry run ruff check .
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff format $(PYTHON_FILES) --diff
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff check --select I $(PYTHON_FILES)
[ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE) || poetry run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
format format_diff:
uv run ruff format $(PYTHON_FILES)
uv run ruff check --select I --fix $(PYTHON_FILES)
poetry run ruff format $(PYTHON_FILES)
poetry run ruff check --select I --fix $(PYTHON_FILES)
@@ -1,6 +1,5 @@
import concurrent.futures
from collections.abc import Sequence
from typing import Optional
from typing import Optional, Sequence
from kafka import KafkaConsumer, KafkaProducer
from langgraph.scheduler.kafka.types import ConsumerRecord, TopicPartition
@@ -221,10 +221,9 @@ class AsyncKafkaExecutor(AbstractAsyncContextManager):
runner = PregelRunner(
submit=weakref.ref(submit),
put_writes=weakref.ref(put_writes),
schedule_task=weakref.WeakMethod(self._schedule_task),
)
async for _ in runner.atick(
[task], reraise=False, schedule_task=self._schedule_task
):
async for _ in runner.atick([task], reraise=False):
pass
else:
# task was not found
@@ -439,10 +438,9 @@ class KafkaExecutor(AbstractContextManager):
runner = PregelRunner(
submit=weakref.ref(submit),
put_writes=weakref.ref(put_writes),
schedule_task=weakref.WeakMethod(self._schedule_task),
)
for _ in runner.tick(
[task], reraise=False, schedule_task=self._schedule_task
):
for _ in runner.tick([task], reraise=False):
pass
else:
# task was not found
@@ -2,8 +2,7 @@ import asyncio
import logging
import random
import time
from collections.abc import Awaitable
from typing import Callable, Optional
from typing import Awaitable, Callable, Optional
from typing_extensions import ParamSpec
@@ -1,7 +1,6 @@
import asyncio
import concurrent.futures
from collections.abc import Sequence
from typing import Any, NamedTuple, Optional, Protocol, TypedDict, Union
from typing import Any, NamedTuple, Optional, Protocol, Sequence, TypedDict, Union
from langchain_core.runnables import RunnableConfig
+1692
View File
File diff suppressed because it is too large Load Diff
+32 -40
View File
@@ -1,54 +1,46 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
[tool.poetry]
name = "langgraph-scheduler-kafka"
version = "1.0.0"
description = "Library with Kafka-based work scheduler."
authors = []
requires-python = ">=3.9"
readme = "README.md"
license = "MIT"
license-files = ['LICENSE']
dependencies = [
"orjson>=3.10.7",
"crc32c~=2.7.post1",
"aiokafka>=0.11.0",
"langgraph>=0.2.19",
]
readme = "README.md"
repository = "https://www.github.com/langchain-ai/langgraph"
packages = [{ include = "langgraph" }]
[project.urls]
Repository = "https://www.github.com/langchain-ai/langgraph"
[tool.poetry.dependencies]
python = ">=3.9"
orjson = "^3.10.7"
crc32c = "^2.7.post1"
aiokafka = "^0.11.0"
langgraph = ">=0.2.19"
[dependency-groups]
dev = [
"ruff",
"codespell",
"pytest",
"pytest-mock",
"pytest-watcher ; python_version < '4.0'",
"mypy",
"langgraph",
"langgraph-checkpoint-postgres",
"langgraph-checkpoint",
"kafka-python-ng",
]
[tool.uv]
default-groups = ['dev']
[tool.uv.sources]
langgraph = { path = "../langgraph", editable = true }
langgraph-checkpoint-postgres = { path = "../checkpoint-postgres", editable = true }
langgraph-checkpoint = { path = "../checkpoint", editable = true }
[tool.hatch.build.targets.wheel]
include = ["langgraph"]
[tool.poetry.group.dev.dependencies]
ruff = "^0.6.2"
codespell = "^2.2.0"
pytest = "^7.2.1"
pytest-mock = "^3.11.1"
pytest-watcher = { version = ">=0.4.1", python = "<4.0" }
mypy = "^1.10.0"
langgraph = {path = "../langgraph", develop = true}
langgraph-checkpoint-postgres = {path = "../checkpoint-postgres", develop = true}
langgraph-checkpoint = {path = "../checkpoint", develop = true}
kafka-python-ng = "^2.2.2"
[tool.pytest.ini_options]
# --strict-markers will raise errors on unknown marks.
# https://docs.pytest.org/en/7.1.x/how-to/mark.html#raising-errors-on-unknown-marks
#
# https://docs.pytest.org/en/7.1.x/reference/reference.html
# --strict-config any warnings encountered while parsing the `pytest`
# section of the configuration file raise errors.
addopts = "--strict-markers --strict-config --durations=5 -vv"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.ruff]
lint.select = [
"E", # pycodestyle
+1 -1
View File
@@ -1,4 +1,4 @@
from collections.abc import AsyncIterator, Iterator
from typing import AsyncIterator, Iterator
from uuid import uuid4
import kafka.admin

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